答案:ArrayList实现了Serializable接口的writeObject方法,这个方法把elementData中的元素全部序列化到文件中了。这个方法时私有的,但序列化时是反射调用。之所以不使用elementData直接来序列化,主要原因是elementData是一个缓存数组,为了性能考虑,它通常会预留一些容量,当容量不足时又扩容,因此可能有大量空间没有实际存储元素。不直接序列化elementData可以保证序列化实际有值的那些元素,而不是序列化整个数组。
答案:ArrayList在创建的时候,如果不指定初始容量,默认容量为10,之后随着add中ensureCapacity方法的需要,存不下新数据时进行扩容,grow的逻辑很简单。首先找出当前容量,把新容量设置为旧容量的1.5倍(通过位运算左移实现),如果新容量比可用最小容量小,那么设置新容量为最小容量,如果新容量比极限容量要大,那么设置极限容量常量和最大整型数中的最大值,接着使用该新容量初始化一个新的数组,将原有elementData中的元素等位复制过去。
答案:LinkedList与ArrayList的比较实际上是顺序访问序列和随机访问序列的比较,LinkedList有两个主要特性:顺序访问和写快读慢。
引申:
LinkedList继承AbstractSequentialList,提供了顺序访问的存储结构。实现了Deque双向队列接口,因为Queue特性是FIFO,而Deque则可以同时在头尾处完成读写操作,而LinkedList还可以操作头尾之间的任意节点。
答案:Vector的实现和ArrayList基本一致,vector的所有public方法都是用了synchronized关键字,保证线程安全。Vector根据capacityIncrement的数值扩容(new时指定),而不是每次扩容1.5倍。Stack时Vector的子类,提供了一些栈操作的方法。
引申:
Vector和Stack使用了大量synchronized关键字来保证线程安全,但这并不是当下推荐的方式,因为这种实现效率比较低,在java.util.Collections工具类中提供了synchronizedList方法可以提供线程安全的列表,拥有更好的性能,因此这两个类被看作已经过时的容器。
答案:在jdk1.8以前,HashMap的底层实现是数组和链表,而从1.8开始使用数组+链表+红黑树来实现。
Jkd1.8以前的put流程:
a) 计算键key的hash值。(利用异或操作与无符号右移操作构建特定哈希松散算法hash(Object k),尽可能减少哈希碰撞)
b) 根据hash值和table长度来确定下标。(利用hashCode与table的长度使用&运算构建索引算法indexFor(int,int),检测哈希冲突,确定数据地址),根据key值和hash值对比,确定是存入数组,还是创建链表节点还是替代之前的链表值。
c) 通过size>=threshold,确定是否需要扩容resize(2*table.length),确定下一个扩容阈值size*loadFactor(加载因子,常量0.75),确定useAltHashing改变算法标志,决定是否需要rehash重构哈希,使用新容量构建新的Entry<K,V>table数组,调用transfer(newTable,rehash)来重新计算所有节点并转移到新数组table下。
Jdk1.8以后的put流程:
a) 获取key值的hashCode。(简化了哈希松散算法,之前版本多次位移异或并不能避免过多哈希碰撞,反倒增加了计算次数,Hashmap的效率问题主要集中在链表部分的遍历,这是一个经验性质的改进。)
b) 调用putVal方法进行存值。该方法主要流程为:使用table的长度-1&hash来计算下标,table为空或超过阈值进行扩容,根据下标位置的节点类型保存数据,决定插入数组还是链节点或树节点,如果链表深度超过建树阈值(TREEIFY_THRESHOLD-1),即7,则调用treeifyBin把整个链表重构成树。
c) Resize即重新规划table长度和阈值,如果数量超过阈值,扩容两倍(最小16,最大1<<30),阈值为容量*加载因子0.75。重新排列数据节点,遍历所以节点普通节点重新计算哈希值,树节点调用split方法,决定是否需要将其退化为链表。
引申:
New HashMap时指定容量可以提高效率,合适的initCapacity=(需要存储的元素个数/加载因子)+1
答案:TreeMap是有序Map,需要指定比较器或使用默认比较器。与HashMap组合了数组、链表、红黑树不同,TreeMap时完全有红黑树实现的。
引申:
TreeMap的Put方法:
a) 如果TreeMap是空的,那么指定数据作为根节点。
b) 如果comparator不为空,使用comparator决定插入位置,否则认为key实现了Comparable,调用compareTo决定插入位置。
c) 插入完成,修复红黑树。
答案:LinkedHashMap的存储中包含了一个额外的双向链表结构,private transient Entry<K,V>header既是头又是尾(JDK1.8后有单独的tail),可以看成是一个环状链表,Hash桶中的每个节点都被这个环状链表的某个节点引用,从而达到有序访问目的。
答案:HashTable的实现方式被synchronized修饰,因此HashTable是线程安全的,而HashMap不是。此外HashTable不能存放null作为key,HashMap会把null key存在下标为0的位置。
引申:
虽然Hashtable是线程安全的,但在多线程环境下并不推荐使用,因为采用synchronized方式实现的多线程安全容器在大量并发的情况下效率比较低,Java还引入了专门在大并发量情况下使用的并发容器,这种容器由于在实现时使用了更加细粒度的锁,由此在大并发量的情况下有着更好的性能。
答案:Java集合框架Iterator具有fast-fail的特性,在创建Iterator后,其它线程对Collection做了修改或当前线程调用非Iterator接口对集合做了修改,导致类似modCount!=expectedModCount,那么会抛出ConcurrentModificationException异常,产生fast-fail事件。
引申:
针对ArrayList,fast-fail的实现是在其父类AbastractList中,当执行add、remove、clear等方法时,modCount会发生改变,expectedodCount不变,调用checkForModification检测是否modCount==expectedModCount。而JUC中的CopyOnWriteArrayList没有继承AbstractList,仅仅实现了List接口,进行增删改时没有所谓checkForModification。
原文:https://www.cnblogs.com/guanghe/p/13440571.html