transient 这个关键字保证其修饰的数据不被序列化
ArrayList
内部保存了一个数组 transient Object[] elementData; 初识容量为10,用来存储数据,所以在内存分配上需要连续的存储空间,
查找: get(int index) 通过elementData收地址 + (index * 每个元素所占的空间大小)即可得到要查找元素的地址,所以效率很快
插入和删除: 内部时通过数组实现的,所以插入和删除操作, 先从头遍历找到需要插入或删除的位置,然后通过移动,拷贝数组元素,这种效率狠低
插入时,当数组慢了的时候,会有扩容操作,扩容后的容量变为之前容量的1.5呗
线程安全: 否
LinkedList
内部通过单链表的形式实现
transient Node<E> first; //头指针
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last; // 尾指针
查找:每次都需要从头开始遍历,计数,效率慢
插入 和 删除: 先从头遍历找到需要插入或删除的位置,然后修改前后指针即可,不涉及到数组拷贝等操作,效率高
插入时 无需扩容
线程安全: 否
Vector
同ArrayList的存储,读写 几乎完全一样
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
// 扩容时后的长度,如果指定capacityIncrement,则oldCapacity+capacityIncrement,否则变为2倍
唯一的区别时所有的操作都加锁了,一定程度上保证了线程安全,但是每个方法都加了Synchronized,会影响一些并发效率
Map系列
HashMap,Treemap,ArrayMap,ConcurrentHashMap
HashMap
内部也是采用了一个数组 + 链表的形式,数组中的元素为Map.Entry<K,V>组成的链表,体现为key,value的组合,默认初始容量为16,默认loadFactor 为0.75
查找index的过程: 对hashcode % (length -1);
put 操作
判断hash(key)查找在数组中的index,如果index中有数据,沿着对应的链表中查找是否有相应的key,如果有直接修改value
如果链表中没有,则在链表末尾创建新的entry<key,value>
如果当前数组中的元素数量到达loadFactor * capacity时,触发扩容操作,扩容后,容量变为变为2倍
为什么扩容? 如果不扩容,会导致每个index中的链表越来越长,降低了查找效率
扩容过程:
1 创建一个长度为旧长度的2倍
2 旧数据迁移到新数组中
do {
next = e.next;
// 在链表中的偶数index保存中低index的数据
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 在链表中的偶数index 保存在高index的数据
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
} while ((e = next) != null);
if (loTail != null) {
//低index
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
// 高index
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
迁移数据后,将之前所有的链表一份为2,部分存在低index,部分存在高index
线程安全:否
关于为什么hashmap的长度是2的n次方,是因为再计算table的index时,hash % length 等同于 (hash & length -1) ,后者的按位与效率更高
https://blog.youkuaiyun.com/sybnfkn040601/article/details/73194613
ArrayMap
存储结构为2个数组
int[] mHashes;
// 有序数组(包含重复属于,因为存在多个key对应同一个hashcode的情况),存储所有key的hash,用于查找key的index
Object[] mArray; //存储了key,value,如key的pos2,则mArray[2]为key,mArray[3]为value,key,value在数组中连续存储
查找key的index过程就是在mHashes中通过二分查找hash(key)的index,
然后key = mArray[index<<1] ,value = mArray[(index<<1)+1] ;
put操作:
涉及到index的查找,两个数组都会涉及到数组拷贝,还有扩容操作,基本上扩容长度变为1.5倍,扩容后,迁移旧数据到新数组中
线程安全: 否
SparseArray:
与ArrayMap类似,采用两个数组,key和value在两个数组中的index一致,两个数组长度一致,
private int[] mKeys; // key都是int的,
private Object[] mValues;// values
查找index也是通过二分查找(时间复杂度O(logn))进行,也存在扩容,扩容后长度变为2倍,扩容后的数据迁移
HashMap,ArrayMap,SparseArray 比较参考: https://www.jianshu.com/p/679ea6534bc0
ArrayMap,SparseArray相对HashMap来说只是减少了对象的创建,如HashMap中需要创建Entry<key,value>,查找效率不HashMap,
SparseArray中储存的key是int的,所以减少了hash操作,效率更高一点
TreeMap:
TreeMap的设计目的是将所有的数据,按照key进行排序,
内部采用了红黑树的结构,其实本质上就是一个链表,每个节点的数据结构如下:
static final class TreeMapEntry<K,V> implements Map.Entry<K,V> {
K key;
V value;
TreeMapEntry<K,V> left; //左孩子
TreeMapEntry<K,V> right;// 右孩子
TreeMapEntry<K,V> parent;// 父节点,如果为空,则其为树的根节点
boolean color = BLACK;
}
线程安全:否
TreeMap参考文档 https://blog.youkuaiyun.com/SEU_Calvin/article/details/52746000
List,Map在通过for循环遍历过程中中,删除元素,会出现一个ConcurrentModificationException,其实就是内部在通过for循环遍历时,存在判断modCount != exceptedModCount,导致
concurrenthashmap: https://www.cnblogs.com/ITtangtang/p/3948786.html
https://juejin.im/post/5ca89afa5188257e1d4576ff#heading-5
concurrent在java8实现类似与之前的hashmap,只是和java7一样,采用了分段锁概念,插入和删除数据时,使用tab[index]的链表头作为锁,内部采用了大量的cas原理,来保证来更新数据。初识容量16,加载因子0.75,包括扩容操作。