JavaSE部分
java基础
基本语法
为什么重写equals还要重写hashcode?
两者事实关系如下:
- 如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同!!!!
- 如果两个对象不同(即用equals比较返回true),那么它们的hashCode值可能相同也可能不同
- 如果两个对象的hashCode相同(存在哈希冲突),那么它们可能相同也可能不同(即equals比较可能是false也可能是true)
- 如果两个对象的hashCode不同,那么他们肯定不同(即用equals比较返回false)
- HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地址,这样即便有相同含义的两个对象,比较也是不相等的。HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。
- 如果只重写hashcode()不重写equals()方法,当比较equals)时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素是否相等。重载hashCode()是为了对同一个key,能得到相同的Hash Code,这样HashMap就可以定位到我们指定的key上。重载equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样我们才真正地获得了这个key所对应的这个键值对。
说一下map的分类和常见的情况
- HashMap:最常用的Map,根据键的hashcode值来存储数据,根据键可以直接获得他的值(因为相同的键hashcode值相同,在地址为hashcode值的地方存储的就是值,所以根据键可以直接获得值),具有很快的访问速度,遍历时,取得数据的顺序完全是随机的,HashMap最多只允许一条记录的键为null,允许多条记录的值为null,HashMap不支持线程同步,即任意时刻可以有多个线程同时写HashMap,这样对导致数据不一致,如果需要同步,可以使用synchronziedMap的方法使得HashMap具有同步的能力或者使用concurrentHashMap
- HashTable:与 HashMap类似,不同的是:key和value的值均不允许为null;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。
- LinkedHashMap:是HahsMap的一个子类,但它保持了记录的插入顺序,遍历时先得到的肯定是先插入的,也可以在构造时带参数,按照应用次数排序,在遍历时会比HahsMap慢,不过有个例外,当HashMap的容量很大,实际数据少时,遍历起来会比LinkedHashMap慢(因为它是链啊),因为HashMap的遍历速度和它容量有关,LinkedHashMap遍历速度只与数据多少有关。key和value均允许为空,非同步的。
- TreeMap:能够把它保存的记录根据key排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。
什么情况用什么类型的Map:
- 在Map中插入,删除,定位元素:HashMap
- 要按照自定义顺序或自然顺序遍历:TreeMap
- 要求输入顺序和输出顺序相同:LinkedHashMap
Object 若不重写hashCode()的话,hashCode()如何计算出来的?
Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。
==比较的是什么?
“==”对比两个对象基于内存引用,如果两个对象的引用完全相同(指向同一个对象)时,“==”操作将返回true,否则返回false。“==”如果两边是基本类型,就是比较数值是否相等。
若对一个类不重写,它的equals()方法是如何比较的?
比较是对象的地址。
java8新特性
- Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
Lamda表达式的优缺点
优点:1.简洁。2.非常容易并行计算。3.可能代表未来的编程趋势。
缺点:1.若不用并行计算,很多时候计算速度没有比传统的for循环快。(并行计算有时需要预热才显示出效率优势)2.不容易调试。3.若其他程序员没有学过1ambda表达式,代码不容易让其他语言的程序员看懂。
一个十进制的数在内存中是怎么存的?
补码的形式
为啥有时会出现4.0-3.6=0.40000001这种现象?
原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。
Java支持的数据类型有哪些?什么是自动拆装箱?
基本数据类型:byte(1字节),short(2字节),int(4字节),long(8字节),char(2字节),boolean(不确定,取值是true/false),float(4字节),double(8字节)
引用数据类型:包括数组,集合,字符串,接口以及类等
自动装箱/自动拆箱:就是指基本数据类型可以和其对应包装类之间自动转换。对应上面分别是:
1 | Byte, Short, Int, Long, Float, Double, Char, Boolean |
什么是值传递和引用传递?
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。所以对引用对象进行操作会同时改变原对象.一般认为,java内的传递都是值传递.
数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
存储内容比较:
Array 数组可以包含基本类型和对象类型,
ArrayList 却只能包含对象类型。
Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。
空间大小比较:
Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。
ArrayList 的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
方法上的比较:
ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。
适用场景:
- 如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。
- 如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。
你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?
大0符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。
大0符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大0符号基于时间,内存和性能来选择最好的实现。大0符号可以对大量数据的性能给出一个很好的说明。
大O符号表示一个程序运行时所需要的渐进时间复杂度上界。
其函数表示是:
对于函数f(n),g(n),如果存在一个常数c,使得f(n)<=c*g(n),则f(n)=O(g(n)); 大O描述当数据结构中的元素增加时,算法的规模和性能在最坏情景下有多好。
大O还可以描述其它行为,比如内存消耗。因为集合类实际上是数据结构,因此我们一般使用大O符号基于时间,内存,性能选择最好的实现。大O符号可以对大量数据性能给予一个很好的说明。
String是最基本的数据类型吗?
基本数据类型包括byte、int、char、long、float、double、boolean 和short。
java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类。
int和Integer有什么区别
- Integer是int的包装类,int则是java的一种基本数据类型
- Integer变量必须实例化后才能使用,而int变量不需要
- Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
- Integer的默认值是null,int的默认值是0
String、StringBuffer和StringBuilder的区别
三者在执行速度方面的比较:
StringBuilder > StringBuffer > String
StringBuilder与 StringBuffer比较:
StringBuilder:线程非安全的
StringBuffer:线程安全的
当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最 快,但是可以保证StringBuffer是可以正确操作的。
当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。
对于三者使用的总结
1.如果要操作少量的数据用String
2.单线程操作字符串缓冲区 下操作大量数据用StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据用StringBuffer
我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串?
1 | public String translate (String str) { |
&和&&的区别?
相同点:&和&&都可以用作逻辑与的运算符,表示逻辑与(and)。
不同点:
(1)&&具有短路的功能,而&不具备短路功能。
(2)当&运算符两边的表达式的结果都为true时,整个运算结果才为true。而&&运算符第一个表达式为false时,则结果为false,不再计算第二个表达式。
(3)&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如:0x31 & 0x0f的结果为0x01。
在Java中,如何跳出当前的多重嵌套循环?
在java里面,我们知道有goto这个关键字,但是实际却没有啥作用,这就让我们不像在c/c++里面能够随便让程序跳到那去执行,而break只能跳出当前的一个循环语句。
- 我们可以在循环体开头设置一个标志位,也就是设置一个标记,然后使用带此标号的break语句跳出多重循环。
- 设置一个boolean值的标记位,在for循环中使用判断是否继续循环来达到目的。
关键字
介绍一下Syncronized锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?
- synchronized修饰静态方法以及同步代码块的synchronized(类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。
- synchronized修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁。
介绍一下 volatile?
- olatile关键字是用来保证有序性和可见性的。这跟Java内存模型有关。比如我们所写的代码,不一定是按照我们自己书写的顺序来执行的,编译器会做重排序,CPU也会做重排序的,这样的重排序是为了减少流水线的阻塞的,引起流水阻塞,比如数据相关性,提高CPU的执行效率。需要有一定的顺序和规则来保证,不然程序员自己写的代码都不知带对不对了,所以有happens-before规则,其中有条就是volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;有序性实现的是通过插入内存屏障来保证的。可见性:首先Java内存模型分为,主内存,工作内存。比如线程A从主内存把变量从主内存读到了自己的工作内存中,做了加1的操作,但是此时没有将i的最新值刷新会主内存中,线程B此时读到的还是i的旧值。
- 加了volatile关键字的代码生成的汇编代码发现,会多出一个lock前缀指令。Lock指令对Intel平台的CPU,早期是锁总线,这样代价太高了,后面提出了缓存一致性协议,MESI,来保证了多核之间数据不一致性问题。
锁有了解嘛,说一下Synchronized
- synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5以后引入了自旋锁、锁粗化、轻量级锁,偏向锁来有优化关键字的性能。
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。