1、 ArrayList中的元素由底层数组承载
2、 如果使用午餐构造方法,那么默认调用this(10),即,若不指定list长度,默认为10
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}
3、ArrayList参数为Collection的构造方法底层调用的是Arrays.copyOf=>System.arraycopy方法
4、ArrayList通过size成员变量来记录元素个数
5、indexOf方法需要注意,如果传入的参数为null,那么会返回数组中第一个为null的索引,如果不存在,则返回-1
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
6、数组长度扩容,如果需要扩容,则默认扩容为原来的1.5倍
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//这里就是扩容1.5倍的地方
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
7、每次add或者remove,底层都会调用System.ayyayCopy方法,这也就是为什么ArrayList add和remove方法效率低下的原因
8、如果需要在循环的时候add或者remove,记住使用Iterator
至于为什么??以后再去深究
9、ArrayList没有加锁,并发时,会存在问题
1、底层由链表实现,维护了头指针和尾指针
2、add方法会将新Node加到链表的末尾
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
//将新节点加到链表的结尾
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
3、indexOf 参数若是null,返回链表中第一个为null的索引,若不存在,返回-1
4、toArray使用的是new Object[size]结合for循环完成的
5、未使用synchronize,线程不安全。
1、ArrayList的线程安全版,通过对方法加锁实现
2、扩容时的策略与ArrayList略有不同,vector维护了一个capacityIncrement变量用来控制每次扩容时扩容量,若为0,则默认扩充为原来的2倍,ArrayList选择默认扩容为原来的1.5倍
继承了Vector,使用数组构造的堆结构
底层维护一个HashMap,只使用HashMap的key部分,value部分置为Object对象
底层维护一个TreeMap
1、底层由Entry数组和链表组成
2、实例化HashMap需要指定初始容量(默认16)和初始加载因子(默认0.75)
这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量。
加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。
对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;
如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况下我们是无需修改的。
3、如果put方法的键值对中,key为null,对索引为0的链表进行for循环,如果没找到则将这个元素添加到talbe[0]链表的表头。
4、put方法(key非null)过程
计算key的hash值(通过扰动函数保留高位信息,避免h^(length-1)出现大概率碰撞)—>通过indexFor方法计算索引—>查找相关索引对应的链表是否含有key,若已存在,则覆盖value,若不存在,则将新键值对置于链表第一个元素之后
5、put扩容
若出现 size >= threshold的情况,默认会将容量扩容为原来的两倍;
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
扩容结束后,需要重新计算hash,并将键值对置于新的Entry数组的相应位置上。
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry e : table) {
while(null != e) {
Entry next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
6、解决hash碰撞常用的方法:链地址法(即HashMap所采用)、开放地址法(在原有hash结果的基础上+d,然后再取余)、再hash(即使用多个hash方法)
1、ConcurrentHashMap没有锁住整个hash表,而是使用分段锁。其内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
2、有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁
3、对于一个key,需要经过三次hash操作,才能最终定位这个元素的位置,这三次hash分别为:
4、concurrencyLevel、sshift、ssize、segmentShift 、segmentMask
6、put方法加锁,get方法不加锁(通过volatile关键字控制HashEntry中value的值来保证线程安全)
7、size、containsValue方法加锁机制:前面三次不对segment加锁,若出现modCount(s1) != modCount(s2) !=modCount(s3),则对所有segment加锁,来获取size或者进行其他操作
8、ConcurrentHashMap中的key和value值都不能为null
1、LinkedHashMap可以看做是HashMap和LinkedList的融合体,它在自己实现的Entry中新增了befor和after两个成员,用来维护双向链表
2、可以用来实现LRU缓存
3、可以记录访问和插入的顺序(默认记录插入顺序)
1、HashTable不支持null 键(若为null,会自动抛异常)
2、HashTable默认扩容为原来的2n+1,这是因为其与HashMap策略不同,HashTable选择素数,以降低碰撞率,但是这样会导致,求Entry[]索引时比HashTable效率低(因为除法运算速度远远小于位运算速度)
3、使用了synchronized保证了线程安全
1、TreeMap基于红黑树实现。红黑树是一种二叉搜索树,红黑树与AVL(平衡二叉树)相比,其旋转次数较少。
2、红黑树的时间复杂度为O(log(n)),因为给定n个节点,红黑树的高度最大为2(log(n+1))=>log(n)