基本内容:ArrayList、HashMap、HashSet、TreeMap、LinkedHashMap、ConcurrentHashMap
1.ArrayList,支持随机访问,插入删除操作时间复杂度为O(n)。
(a) 默认初始容量为10:private static final int DEFAULT_CAPACITY = 10;
(b)扩容时,扩充为原来的1.5倍: int newCapacity = oldCapacity + (oldCapacity >> 1);
(c)基于数组实现,底层数组用transient修饰,表示该数组默认不会被序列化。通过重写writeObject()方法来只序列化数组中有元素填充的那部分。
(d)Fair-Fast机制:用modCount来记录ArrayList结构发生变化的次数,在进行序列化或者迭代操作时,需要比较操作前后modCount是否改变,如果改变了需要抛出ConcurrentModificationException。序列的结构性变化指add()或remove()一个元素或者调整内部数组的大小(trimToSize(),ensureExplicitCapacity()),仅仅设置元素值不算结构发生改变。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
(e)和Vector的区别
Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
为了获得线程安全的 ArrayList,可以调用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList,也可以使用 concurrent 包下的 CopyOnWriteArrayList 类;
(f)和LinkedList的区别:LinkedList双端链表实现,只支持顺序访问访问O(n),利于插入删除操作O(1)。
2.HashMap
(a)存储结构:使用拉链法来解决hash冲突。内部包含了一个Entry类型的数组,存储每个链的头结点,头结点不含有效数据。关于hash冲突有拉链法,再hash,平方散列等处理方式。
(b)默认长度为16(1<<4),默认装载因子为0.75,阈值=Entry.length*装载因子;Entry最大允许大小Max_Capacity为2的30次方,每次resize为原来的2倍,当不能在resize时,阈值调整为Integer.MAX_VALUE;允许null键,null键默认存放在0位置,null键只能保存最新值。
(b)hash值的计算:hashCode()的高16位异或hashCode()的低16位。return key==null?0:(h=key.hashCode())^(h>>>16)。
(c)所属桶的计算:拉链法需要用除余法来得到桶的下标,即bucketIndex=hash%capacity,如果能够保证capacity为2的幂次方,那么该hash取余运算就可以转化为位与运算:bucketIndex=hash & (capacity-1)。提高运算效率。
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;//构造器的initialCapacity实际上是阈值
init();
}
public V put(K key, V value) {//1.insert || update
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)//2.若key==null,约定放在第一个桶
return putForNullKey(value);//若已经存在key==null的结点,则更新它的值;若不存在,新插一个结点,因此key==null的结点始终只能保存最新的值
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {//update
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//insert
modCount++;
addEntry(hash, key, value, i);//头插法
return null;
}
3.HashSet:借助HashMap来实现,准确来说是借助HashMap的key的唯一性来实现。用一个通用对象作为value的占位符。
4.TreeMap:可以实现key有序的map,用红黑树组织数据,红黑树是弱平衡的二叉搜索树,插入、查找元素简单点可以参二叉搜索树的对应操作;拿到有序key,可以通过非递归中序遍历(bst树中序遍历得到的是一个有序序列)。
https://blog.youkuaiyun.com/away_lit/article/details/79600327
5.LinkedHashMap:HashMap的子类,可以实现插入顺序有序。直观的看可以认为是链表+map,用链表维护元素的插入顺序,用map存储k-v对。实际实现上就是对HashMap的Entry改造了下,加了个后继结点索引,并全局保存最后添加的元素。
6.ConcurrentHashMap:对HashTable的改进,采用分段加锁,减小锁的作用域,提高并发度,默认为16段。
https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%AE%B9%E5%99%A8.md#arraylist