1Java集合框架有哪些?
- collection结合的层级接口。一个集合代表一组对象,这些对象叫做元素。
- List有序集合,数组格式,可重复,通过下标获取元素。
- Set无序集合,数组格式,不可重复,通过下标获取元素。
- Map无需,key-value,vlaue可以重复,key不可以重复,支持空key,空value。
2.Collection和Collections的区别?
- Collection是定义集合的接口。
- Collections是集合的工具类包含如排序,sort等方法。
3.List、Set、Map都是实现Collection接口吗?
- List、Set实现Collection接口、Map不实现。
- Map是键值对映射关系,Set存储的零散的元素是不允许重复的,List是线性结构的容器,适用于按数值索引查询的情况。
4.Collections.sort排序的原理?
- 1.6以前Arrays.sort和Collections.sort都是MergeSort,1.7更更新为TimeSort。
5.List、Set、Map的区别?
名称 | 类别 | 线程安全 | 优点 | 缺点 |
---|
ArrayList | List | 非线程安全 | 集合有序、可以重复、自动扩容、扩容默认1*1.5、随机访问效率高 | 插入和删除的效率比较低、根据内容查找元素的效率较低。 |
linkedList | List | 非线程安全 | 双向链表,不必保证内存上的连续,所以增删快(非首尾位置) | 查询时必须要经历从头到尾的遍历,所以查询慢 |
Vector | List | 线程安全 | 可以重复、自动扩容、默认扩种1*2、可以null | 插入和删除效率低,复杂度高f(n) |
HashSet | Set | 线程不安全 | 元素不可以重复、自动扩容、无序 | |
HashMap | Map | 线程不安全 | key-value映射存储、支持空key空value、key不可以重复、自动扩容(数组大小为16,开始扩容的大小=原来的数组大小*loadFactor(loadFactor的默认值为0.75))、1.7之前首插、1.8尾插 | 获取只能通过Key获取,不能随机获取 |
6.HashMap、和HashTable的区别
- hashMap线程不安全、支持Key和Value为null,HashTable线程安全不支持key和value为null;
7.如何决定使用HashMap还是TreeMap?
- 插入、删除、定位使用HashMap,有序遍历用TreeMap。
8.HashMap的实现原理
- hashMap是基于hash实现的、通过put存入、get key获取。当传入key是通过key.HashCode()方法计算Hash值,根据Key的Hash将Value保存在Bucket(桶)中,当两个对象的Hash重复时,被称为Hash冲突,Map通过链表和红黑树的方式来处理。
- HashMap底层是数组+链表+红黑树(1.8之后、1.7之前没有红黑树、加入红黑树是为了提高查询效率)当链表的长度大于8时就会转换成红黑树(因为红黑树需要进行左旋,右旋操作, 而单链表不需要,如果元素小于8个,查询成本高,新增成本低、低于8个、新增成本高、查询成本低);
- HashMap
- put过程
static final int **DEFAULT_INITIAL_CAPACITY** = 1 << 4; // aka 16 // 默认数组大小 table Node<K,V>
static final int **MAXIMUM_CAPACITY** = 1 << 30; // 数组最大大小
static final float **DEFAULT_LOAD_FACTOR** = 0.75f; //负载因子 默认大小 影响 扩容后大小
static final int **TREEIFY_THRESHOLD** = 8; // 数组上的链表转树华的 默认值 大于8 转为树华
static final int **UNTREEIFY_THRESHOLD** = 6; // 树转回链表的默认值
static final int **MIN_TREEIFY_CAPACITY** = 64; // 数组内的所有元素超过64位的时候才能允许树华
Node<key,value>[] table // 哈希桶
transient int size; //当前哈希表中元素个数
transient int modCount; // 当前哈希表中 结构修改次数 指增删 修改不计数
int threshold; // 扩容系数 当哈希表中的元素 大于 系数时 扩容 默认为 **DEFAULT_INITIAL_CAPACITY**
final float loadFactor; // 负载因⼦ ,默认 0.75f **DEFAULT_LOAD_FACTOR** threshold = size*loadFactor)
// 计算 hash值 高位运算 (目的让hash值大于16的也参与路由运算)
// 路由运算 (length - 1 & hash )
// 如果key位null 默认 hash 为0 放在数组 0 的位置 因为 0 参与运算结果 也为0
static final int hash(Object key) {
int h; // 如果 key == null 那么hash为0
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value key是否存在
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; //引用当前 hashMap的散列表
Node<K,V> p; // 当前散列表的元素
int n, i; // n 为 当前散列表长度 i 路由计算的结果
// 延迟初始化 节省内存空间 hashMap 只有在你 put数据的时候第一次调用 putVal 才会初始化 。 节省内存空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// (n-1)& hash 路由算法 计算 value应该存放在散列表中的哪个位置 如果 该位置 是 null 那么 创建 node 存入数据
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; //临时的散列表元素
K k; // 临时的元素key
// p 路由计算后的哈希桶内的 node 元素
// 表示 我当前要插入的 key 与我 现在map中的 key重复 e = p 后续要进行替换操作
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode) // 如果 p 元素 所在的 桶位置 已经 树化
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { // 处理链表的情况 1. 我和散列表上的元素的key不同 但是 hash值相同 我要便利 链表 看看链表上的key是否有与我相同的 如果有 替换 没有插入末尾
for (int binCount = 0; ; ++binCount) {
// e 临时 散列表 元素 = 当前的散列表元素的 下一位 结果如果是 0 那么 说明当前已经到了 末尾 我可以直接 插入进去
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// binCount 如果 = 7 说明我这个链表的长度 已经是 8位了 在插入 数据 我就是9位 要进行 树化
// TREEIFY_THRESHOLD 默认缺省值 表示 链表长度超过 多少 树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 我的hash值 于 链表上的 某个元素的 hash值相同 那么我要替换他
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//表示 e不是 null 需要做替换操作
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; // 不做替换的操作时 我的表结构改动次数 +1 modCount 结构改动次数
//如果我操作后的 大小 大于我设置的 阈值 那么 出发扩容机制
if (++size > threshold)
resize(); // 扩容机制
afterNodeInsertion(evict);
return null;
}
- 扩容机制
为什么要扩容 1. 当前数组容量不足以存储 2. 解决链表过长 查询效率过低
实现思路 常见一个更大的数组 来 容纳现在小的数组 然后回收小数组内存
final Node<K,V>[] resize() {
// oldTab 扩容前的 哈希数组
// oldThr 扩容前的Node数组长度
Node<K,V>[] oldTab = table;
// 扩容前的数组 == null 说明没有初始化 那么长度就为0 : 否则就是 扩容前数组的长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// oldThr 扩容前的 阈值 默认 16
int oldThr = threshold;
// 扩容后的哈希数组长度 和 阈值
int newCap, newThr = 0;
// >0说明 散列表 已经初始化过了 正常 扩容
if (oldCap > 0) {
// 说明 当前 大小已经达到了 最大值 将 阈值扩大到 最大 返回当前列表
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 没有到达最大 newCap = 当前的 2倍 小于 最大值 并且 当前的数组大小 大于等于 16时 那么 阈值翻倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// oldCap 不大于0 说明 散列表还没有创建 , 但是创建 HashMap时调用了 有参构造函数的情况下 设置散列表长度 = 阈值
//public HashMap(int initialCapacity) public HashMap(int initialCapacity) public HashMap(Map<? extends K, ? extends V> m)
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 通过无参构造函数时 创建的 对象 第一次扩容
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; 16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //阈值 = 负载因子 (默认 0.75f) * 默认长度 16
}
// 手动 指定了 负载因子大小 计算 阈值大小
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
// 创建新的 哈希数组
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 将 老的 table 替换成新的哈希数组
table = newTab;
// 旧的 阈值 不是null 说明已经初始化 哈希数组
if (oldTab != null) {
// 根据老数组的 长度遍历
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
// e 临时的 元素
// 这里判断 元素 有值
if ((e = oldTab[j]) != null) {
// 清空 老数组内的内容 方便内存被 GC回收
oldTab[j] = null;
// 这里说明 该节点上只有一个单元素 没有发生过哈希冲突
if (e.next == null)
// 将原来的元素 通过 路由运算 (hash & (length-1)) 复制新新的数组
newTab[e.hash & (newCap - 1)] = e;
// 如果原来的 数组下标 是 红黑树
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
// 处理 哈希数组中 已经形成链表的 元素
// 低位链表 扩容后的位置 与 当前所在位置一致
Node<K,V> loHead = null, loTail = null;
// 高位链表 扩容后的位置 为 当前所在位置 + 扩容前的数组长度
Node<K,V> hiHead = null, hiTail = null;
// 下一个 元素
Node<K,V> next;
do {
next = e.next;
// 经行 异位 运算 如果 = 0 说明 是低位数组 反之 是高位数组
if ((e.hash & oldCap) == 0) {
// 如果低位链表 没有内容了 直接存入 否则存入地位链表的下一个位置
if (loTail == null)
loHead = e;
else
loTail.next = e;
// 是当前的低位链表 保持一致
loTail = e;
}
// 处理 高位链表内容
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null); //如果当前的元素 == 最后一个元素 并且不是null
// 低位元素 不是nul 将低位链表尾部 下一位 设null 将 将当前链表存入新的数组指定下标位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
同上
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
- HashMap 获取value get
核心 getNode
final Node<K,V> getNode(int hash, Object key) {
// tab 当前 哈希数组 first 桶位中的头元素 e 当前下标元素 n数组长度 k 当前kye
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// & 之前 是确定 我们的 哈希数组内有东西
if ((tab = table) != null && (n = tab.length) > 0 &&
// 确定 我们的 头位元素 不是null 是有数据的
(first = tab[(n - 1) & hash]) != null) {
// 头元素 就是我要查的 所以返回 头元素的hash值 = 我要查的目标的 hash 值 并且 我的key 也是相同的
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 头元素的下一位不是null 说明是链表 或树化
if ((e = first.next) != null) {
// 树化
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//do while 便利 链表 比对 元素
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
- HashMap 核心 removeNode方法
//matchValue 如果是 true 比对 value 不是 值找key
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
// 判断我的 哈希数组 初始化过 并且是有 东西的
if ((tab = table) != null && (n = tab.length) > 0 &&
// 查找当前的桶位是 元素是有内容的
(p = tab[index = (n - 1) & hash]) != null) {
//node 是当前查到的元素 e是当前元素的下一个元素
Node<K,V> node = null, e; K k; V v;
//说明我当前要找的 元素 就是当前的元素
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
// 说明当前的元素是链表或者 树
else if ((e = p.next) != null) {
// 但前是树
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//遍历链表 找到当前的的key 相同的 元素
do {
// 链表第一位就是我们要找的
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null); // 链表下一位不是null
}
}
// 找到了 要找的元素 并且 桶位不是null的
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 查询到的原为 和 当前桶位元素相同
else if (node == p)
// 哈希数组当前下标的元素 从当前元素的下一位替换当前桶位元素
tab[index] = node.next;
else
p.next = node.next;
++modCount; // 表结构 修改次数+ 1
--size; // 数组长度 -1
afterNodeRemoval(node);
return node; //返回新的 桶位
}
}
return null;
}