Java基础
1.hashMap底层数据结构
JDK1.7及之前:entry数组+双向链表
JDK1.8:数组+链表+红黑树
hashMap默认数值:
数组长度为16,可以在new hashMap的时候指定数组长度,这样会生成一个长度为大于输入参数的最小的2的整数次幂的entry数组;
load_factor为3/4;
put方法根据key的hash值来决定将<key, value>这个entry放在哪个位置:
entry的index = hash(key) & (n - 1),其中n是entry数组的长度。
其实这个式子就是在取模,之所以不用hash(key)%n,是因为取模的效率太低了。hash(key) & (n - 1)等价于取模,当且仅当n为2的整数次幂,所以hashMap要求数组长度为2 的整数次幂
当hashMap中的元素越来越多的时候,hash冲突是难以避免的,hashMap采用的是链地址法来避免hash冲突,即相同index的entry以链表的形式共存。
链表太长会影响效率,所以在jdk1.8,当链表的长度达到了8及以上,链表就升级成为红黑树。其查询/插入效率从O(n)变为O(logn)
为什么要等长度为8才升级呢?
一方面,链表长度太短,根本没必要升级为红黑树,因为从查询效率上来讲,他们差不多,而维护一棵红黑树是需要代价的,所以长度很短无需升级;
另一方面,长度为8的时候,红黑树的平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这时候才有了真正的提升。
jdk源码中的注释解释道:hashMap元素的到来满足泊松分布,根据泊松分布计算概率,链表长度为8的概率为0.00000006,其概率相当的小。
2.JAVA集合的面经
3.CopyOnWriteArrayList
4.两个对象怎么进行比较?hashCode和equals的区别?
先比较hashCode,若两个对象的hashCode不相等,那么两个对象一定不相等;否则再用equals进行比较。
为什么不直接用equals进行比较?
有一个集合,里面有若干对象,现在有一个新加入的对象,要求判断新来的对象与是否存在于集合中,如果用equals得一个一个比较。而使用hashMap的思想,根据hashCode分桶,先比较hashCode,再用equals比较,大大提高效率。
5.序列化
6.final关键字的用法?final两种重排序规则?
1.用在class,表示类不能被继承;
2.用在method,表示方法不能被重写;
3.用在变量,有两种情况,一是用在基础变量,则该变量对应的值不能改变;二是用在引用变量,则该变量引用的地址不能变,但他引用的对象的属性可以改变。
1、在构造函数内对一个 final 域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。原理是在写final域后插入storestore内存屏障。
2、初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序。原理是在读final域前插入loadload内存屏障。
7.Object类中的方法(待完善)
8.HashMap的put方法和HashMap的扩容过程
put:
resize:
当插入一个新entry时,entry数大于阈值(capacity*load factor)的时候,就会触发扩容。
- 判断当前entry数组的长度是否达到了MAXIMUM_CAPACITY,如果达到了,就不扩容了,直接返回;否则,去第2步;
- 初始化一个长度为当前entry数组长度2倍的新数组;
- 遍历entry数组的每个entry,如果这个entry是链表的头节点,且jdk版本是1.7及之前,去第4步;如果jdk版本是1.8及以后,去第五步;如果当前entry是红黑树的根节点,去第六步;
- 把当前链表每个元素从头到尾的头插到新数组中,最后去第7步;
- 根据元素在新entry数组中的位置要么是当前index,要么是index+old_entry_length,可以把这个链表拆成两个链表:index不变的,或index+old_entry_length的;然后把这二者各自插入新数组,最后去第7步;
- 同理,红黑树的节点也分为两类,一类index不变,一类index+old_entry_length,每一类要根据其元素个数是否超过8,来决定将其变成链表或是继续保留红黑树结构,插入新数组,最后去第7步;
- end