ArrayList
实现了List、RandomAccess、Cloneable、Serializable接口,支持随机访问、克隆、序列化。
类内部维护了elementData数组,默认长度是10,然后每次添加元素会检测长度是否充足,如果长度不足则进行1.5倍扩容。
还有一个非常有意思的知识点、elementData是被transient修饰的,可是arrayList明明支持序列化啊,原来是通过readObject和writeObject方法来进行序列化的,不直接通过elementData序列化应该是为了节省空间。
每当删除元素、或者扩容的时候会调用System.arraycopy方法。
(迭代器遍历删除元素不会出问题,因为删除只是改变了集合的结构,并没有改变迭代器的结构)
LinkedList
实现了list、Deque、Cloneable、Serializable接口,是一个双端队列(链表),不支持随机访问、内存空间不连续,因此增删快,遍历慢。
linkedlist记录了容器的长度、头节点、尾节点,每个节点都是Node对象(Node分别记录了pre和next)。
增删节点就是记录这些指针的值。
添加元素使用的是尾插法。
HashMap
实现了map接口,内部有个Node对象数组table保存数据,初始容量是16,和arraylist一样,被transient修饰,通过readObject和writeObject方法进行序列化。Node的属性有K、V、Next(用于处理hash冲突、指向下一个Node节点)、hash值。
1.hashmap的负载因子为啥是0.75
因为负载因子决定了hashmap什么时候扩容,0.75是一个好的选择,如果过大,hash冲突概率高。如果过小就扩容,就会造成空间的浪费。
2.为什么容量总是2的指数次幂
因为每个key的索引位置,是根据这个key的hash值和最大容量计算出来的。
(table.length - 1) & hash:计算元素位置
2的n次幂减1就能保证最低位永远是1,0&1=0,1&1=1,这样就能保证了元素相对均匀分布。
3.为什么jdk1.8改成尾插法
jdk1.7之前是用的头插法,但是在多线程下会出现节点死循环的bug。
4.为什么转红黑树
只有当hashmap容量和链表长度达到阈值时才转红黑树,查询速度是对数阶。而链表查询速度为线性阶。虽然查询速度比较快,但是插入新元素树的左旋右旋也是很麻烦的。
LinkedHashMap
继承自HashMap,Entry继承自HashMap的Node,在原先基础上添加After和Before属性。并且记录整个map的头节点和尾节点。有了这些属性之后,就可以按照插入/访问顺序去遍历这个map了,而不是像hashmap那样,只能按照hash的顺序去遍历。
AccessOrder,默认为false,即每次插入数据都会把数据放到map尾部。为true时,每次插入,访问都会将数据放到尾部。