提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
常见集合相关知识整理
一、Collection 和 Collections
Collection 是Java集合框架的根接口,它定义了一组通用的操作和方法,用于操作和处理一组对象。Collection接口派生出了一些具体的子接口,例如List、Set和Queue等,分别表示列表、集合和队列等不同的集合类型。
Collections 是Java中提供的一个工具类,它包含了一系列静态方法,用于对集合进行操作和处理。Collections类提供了诸如排序、搜索、遍历等常用的集合操作方法,它们都是通过静态方法实现的。
二、List集合:ArrayList 和 LinkedList
ArrayList:
底层是数组用来保存数据,没有实现Queue接口、Deque接口,不支持队列操作,
扩容机制:新建一个ArrayList,他的默认长度是10,如果长度不够会以原来长度的1.5倍扩增,有个grow方法,每一次扩原来的1.5倍,空间扩容完了之后,会调用arraycopy来对数组进行拷贝
LinkedList:
底层数据结构是链表便于进行插入删除操作 ,LinkedList 是非同步的,双向链表
三、Set集合:HashSet 和 LinkedHashSet 和 TreeSet
set:
1.不允许重复值
2.没有索引值,不能通过普通for循环进行遍历,没有 xxset.get(index) 这个方法获取值
3.使用通过给存入的数据后计算其hash值来去重
HashSet:
1.底层结构是hash表
2.不保证有序
3.没有索引,数据不可重复
LinkedHashSet:
1.数据有序,使用链表来保证
TreeSet:
1.底层是treeMap
2.存入treeSet集合数据等于存入数据到treeMap 的key值中
3.数据无序,但可排序
四、Map集合:hashMap
map:
1.用来存储键值对的集合
2.不能存储重复的键,一个键只能对应一个值
hashMap:
1.使用了数组 + 链表/红黑树 的结构存储数据,通过hash函数计算出键值映射到数组的索引位置,其中数组的每个位置称为桶,每个桶可以存储一个或者多个键值对
怎么存储键值对:将k,与原来的链表上的k进行equls 比较,如果值不一样,一次向后追加链接,如果k是一样的,覆盖原来的kv数据(jdk1.8后,链表的长度大于8 就会转成红黄树)
2.HashMap集合的默认初始化容量是16,默认加载因子是0.75
3.HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
4.hash冲突是:不同的键通过hash函数的计算等到相同的hashcode,然后hash值将数据映射到数组的同一个索引位置就会造成hash冲突
4.1.链地址法(Chaining):
在哈希桶(数组)的每个位置上维护一个链表,每个链表节点包含冲突的键值对。当发生冲突时,将新的键值对添加到对应位置的链表上。
4.2.开放地址法:
当发生冲突时,在哈希桶中寻找下一个可用的位置来存储冲突的键值对。
常用的开放地址法有线性探测、二次探测和双重哈希等方法。
再哈希法:当发生冲突时,使用另一个哈希函数对冲突的键值重新计算哈希值,再放入哈希桶中。
hashtable(hash表):
1.线程安全,k-v均不可以为null
内部源码:
//存储k -v 的地方 Node<K,V>
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //hash值
final K key; //key值
V value; //value 值
Node<K,V> next;//下个节点的内存地址
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
//计算hash 值
static final int hash(Object key) {
int h;
// (h = key.hashCode()) ^ (h >>> 16)扰动计算:把哈希值右移16位,也就正好是自己长度的一半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位计算hash值增大了随机性, 减少碰撞
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
五、ConcurrentHashMap(采用了数组+链表/红黑树 + 分段锁)
1.分段锁(Segment Locking):ConcurrentHashMap引入了分段锁的概念,将整个哈希表分成多个小的段(Segment),每个段都有自己的锁。不同的线程可以同时访问不同的段,从而提供了更高的并发性能。每个段类似于一个小的HashMap,它包含一部分桶(Bucket)。
2.数组+链表/红黑树:每个段内部仍然使用数组和链表(或红黑树)的结构来存储键值对。每个桶可以存储一个或多个键值对。当链表或红黑树的长度超过一定阈值时,会进行相应的转换。
3.锁粒度:ConcurrentHashMap的锁粒度更细,每个段都有一个锁,不同的段之间可以并发操作,提高了并发性能。在读取操作时,可以并发地访问不同的段。在写入操作时,只需要锁定对应的段,而不是整个哈希表。
4.CAS操作:ConcurrentHashMap使用CAS(Compare and Swap)操作来实现线程安全的并发更新。CAS操作是一种无锁的原子操作,它允许多个线程同时尝试更新一个变量的值,只有一个线程能够成功。CAS操作可以避免使用锁带来的性能开销。
5.扩容与重新哈希:ConcurrentHashMap在扩容时,只需要对部分段进行扩容,而不是对整个哈希表进行扩容。这样可以减少扩容操作对其他线程的影响。同时,ConcurrentHashMap使用了一种优化的重新哈希算法,减少了重新哈希的时间和开销。