java集合— 老韩
Collection 接口 遍历原始 使用 的 是 迭代器 Iterator(迭代器)
1.Iterator 对象 称为 迭代器, 主要 用于 Collection 集合中的 元素
- 实现了 Collection 接口 的 集合类 都有 一个 iterator(),用于 返回 实现了 Iterator 接口的对象
即可以返回 一个 迭代器 - Iterator 仅用于 遍历集合 ,本身 不存放对象
迭代器的 原理 :
Iterator iterator(); 得到 一个 集合的 迭代器
通过 hashNext() 返回 是否为 ture 来判断 是否 还有 下一个元素
如果 返回 ture , 使用 next()
next() 作用 : 1. 指针下移 2. 将 下移的 集合位置 上的元素返回
注意点 : 在调用 iterator.next() 方法 之前 必须 调用 iterator. hasNext()进行检查,
如果不调用的 话 ,且 下一条 记录 无效 ,直接 调用 iteator.next() 就会 抛出 NoSuchElementExection异常
col.iterator();
如果还想再次遍历 ,需要 重置迭代器
Collection 接口 使用 增强 for循环 遍历 底层的话 还是使用 迭代器原理
在底层 new 了 一个 迭代器,然后使用 hashNext() 和 next() 进行输出
Collection 接口 使用 for循环 遍历
List 接口
- List 集合中的 元素 有序 (取出和添加顺序 一致 ) ,且可以重复
- List 集合中的 每个元素 都有对应的顺序索引, 即支持 索引。
contains(Object o); //判断当前元素是否存在
ArrayList 底层原理 源码分析
底层是使用 数组实现的 线程不安全 add方法没有使用 线程安全修饰词
源码结论:
- ArrayList 底层维护了 一个 Object对象的 数组 ElementData
transient Object[] elementData; //transient 修饰的 表示短暂的, 不会被序列化 - 当使用 默认构造器的时候 , elementData 数组对象 默认的初始 容量为0 ,当添加 元素的 时候,扩容为 原来的 1.5 倍。
- 当使用 指定大小 的 构造器的 时候, 初始的 elementData 数组对象 为 指定大小的容量, 当需要扩容的时候 扩容为 原来的 1.5倍
原理文件图: ArrayList add 和 扩容原理.drawio
为什么扩容是1.5倍?
初始容量 的 话 为 10 ,第二次 才 扩容为原来的 1.5倍。
扩容操作 就是 对 原数组的容量 进行 右移 >>1 为, 即 /2 , 加上原数组容量 就为 1.5 倍
扩容操作中,使用的是 Arrays.copyOf() 操作 进行 数组的复制。
add();
每次执行 add 操作的时候 都有 一个 modCount 记录集合被修改的次数
进行add 操作的时候, 对 size(默认为0) +1 范围检查, 判断 size 是否 大于 数组的 长度, 如果 大于 则进行扩容 操作 。然后 把 加入的 元素 添加到数组中。
remove(index);
进行 index 进行判断 是否 超过 数组的长度,超过就 抛出异常。
modCount 修改次数 ++; numMoved = 数组长度 - index -1 ;移除的下标;
使用 System.arrayCopy()进行数组的 复制。
Vector 底层原理 源码分析
底层 也是 一个 对象数组 , 使用了 protected Object[] elementData;
Vector 是 线程 同步的 ,线程安全, 因为 使用了 synchronized 关键字
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
扩容:
无参构造的话, 默认 是 10 , 容量满了 之后 使用的 是 2倍 扩容
指定容量的话, 每次 直接 2倍扩容
源码解读
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
add()方法
- 会进行判断 (minCapacity - elementData.length > 0 当前容量 是否满足,如果满足的话, 直接添加元素
- 如果 容量不满足 ,则进行扩容操作,为原来的2倍
- 扩容的时候 : 根据三元运算符进行判断 , 如果 capacityIncrement 是否大于0 ,如果不大于0,变为原来的2倍,默认为0
1.public synchronized boolean add(E e) {
modCount++; //集合修改次数
//调用此方法 判断 数组容量是否足够
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
//如果 >0 则 进行扩容操作 扩容为原来的2倍
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//重点在于 这个 三元运算符 capacityIncrement默认为 0 原数组的2倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList 底层原理 源码分析
-
LinkedList 底层实现了 双向链表 和 双端队列的 特点
-
可以 添加 任意 元素 (元素 可以 重复 ), 包括 null
-
线程 不安全, 没有 实现同步
first 头节点 last 尾节点
底层机制:
-
LinkedList 底层 维护 了 一个 双向链表
-
LinkedList 中 维护了 两个元素 first 和 last 分别 指向 头节点 和 尾节点
-
每个节点 (Node 对象) , 里面 维护了 prev next item 三个属性,
其中通过 prev 指向前一个,通过 next 指向 后 一个 节点 ,item 存放数据, 最终 实现 双向链表
-
所以 LinkedList 元素 的 添加 和 删除, 不是 通过 数组完成的 ,相对 效率高
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RfDZOsxe-1636205623296)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636100569445.png)]
public LinkedList() {} // 使用无参构造器 创建一个空的链表 first =0 last =0
LinkedList add() 解读
- 使用无参构造器 的话,使用add方法
- 调用linkLast() 进行 尾插法 添加元素
- 创建一个新节点,加入到双向链表的尾部,使得 last first 都指向 新节点
- 当添加第二个节点的时候,使得last 节点 指向 第二个新节点, 第一个节点的 next 指向 第二个新节点
//进行装箱操作
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);}
//执行add方法 调用linkLast() 进行添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
//创建一个新节点,加入到双向链表的尾部,使得 last first 都指向 新节点
//当添加第二个节点的时候,使得last 节点 指向 第二个新节点, 第一个节点的 next 指向 第二个新节点
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
remove() 源码分析
//如果调用 remove() 方法 没有参数的话, 默认删除头结点,也就是调用removeFirst()方法
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//把头节点置为null, first 节点执行 下一个节点 下一个节点的 prev 置为null
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cI0fjUs-1636205623299)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636103310606.png)]
ArrayList 和 LinkedList 的 比较
- 都是 线程不安全的
- ArrayList 底层 是 对象数组 , LinkedList 底层 是双向链表
- ArrayList 查找 是通过 元素的序号 查找 快, LinkedList 插入和删除 不受 元素位置影响 快
- ArrayList 需要预留空间 LinkedList 需要存放 prev 和 next 数据
Set 接口
无序 (添加和取出的顺序不一致,没有索引) 不允许重复元素 ,最多一个null
1、可以使用迭代器
2、增强for
3、不能使用 索引的方式 获取
HashSet 底层原理 源码分析
-
底层是 HashMap
public HashSet() { map = new HashMap<>(); }
-
可以存放 null, 但只能有一个
-
Haset 不保证 元素 是 有序的 ,取决于 hash后, 在确定 索引的结果
(不保证 存放元素的顺序和取出顺序一致)
-
不能有 重复元素对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hLvul6lL-1636205623300)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636104645092.png)]
底层机制:
- HashSet 底层 是 HashMap
- 添加一个元素 时, 先 得到 hash值 , 会转成 --> 索引值
- 找到 存储 数据表 table , 看 这个 索引 位置 是否 已经 存放 元素
- 如果没有, 直接 加入
- 如果有 , 调用 equals 比较 ,如果 相同 , 就 放弃 添加, 不同 则 添加到 尾部
- java 8 中 ,如果 链表的 元素 个数 超过 8 , 并且 table 大小 大于 64, 就会进行树化(红黑树)
扩容机制:
1.HashSet 底层是 HashMap, 第一次添加 时, table 数组 扩容 到16, << 1 左移 一位 *2
临界值 (threshold) 16 * 加载因子(loadFactor) 0.75 =12
- 如果 table 数组 使用 达到了临界值 12 , 就扩容 到 162 =32 ,新临界值: 32 0.75 =24
- 当 链表长度达到 8,并且 数组长度 达到 64, 就会 进行 树化(红黑树 ),否则 采用 数组 扩容 两倍
源码解读:
//创建一个HashMap对象
public HashSet() {
map = new HashMap<>();
}
add() 解读
-
调用 一个put()方法 调用的时候传入两个参数 其中 PRESENT 用于占位 ,没有实际意义 ,一个静态最终static final的常量
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
- put()方法 调用 putval方法,其中 可以得到 key对应的 哈希值 ,
哈希值 是通过 key的哈希方法 异或(^) h无符号右移 16位后的值 ,目的是为了 降低哈希冲突
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
-
执行 putval()方法
-
if 判断 table 和 长度 是否为 null 和 0 , 如果 成立 调用 resize() 创建 新的table
数组最大长度n (16) -1 与 hash值 进行 与(& )运算 获取到 索引值
p = tab[i = (n - 1) & hash
-
如果当前位置没有 元素,构造一个 Node节点 进行加入
-
如果 当前 索引位置 对应的 链表 的 第一个元素 和 准备添加的 key 的 hash值 一样
并且 满足 下面 两个 条件之一:
3.1 准备加入的 key 和 p 指向的 Node 节点的 key 是 同一个对象
3.2 指向的 Node 节点 的 key 的 equals() 和 准备 加入的key 比较后 相同满足就不能加入
-
-
在判断 p 是否 是一个 红黑树 ,如果 是 一颗 红黑树, 就调用putTreeVal 来进行添加
-
如果table 对应的 索引位置, 已经是一个 链表, 使用for进行死循环
(1) 依次 和 该链表的 每一个元素 比较后,都不相同, 则加入到 该链表的最后
注意 在 元素 加入到 链表后, 立即判断, 该链表长度是否 达到 8 个节点
达到调用treeifyBin() 对 当前节点 进行 树化(转成红黑树)
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64)) resize();
注意 转成 红黑树 之前 进行 判断 数组长度 是否 达到64 ,没有 先扩容,有转红黑树
(2) 依次和 该链表的 每一个元素比较过程中 ,如果有相同的情况, 就直接 break
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义辅助变量
//tab 就是 HashMap 底层的 Node[] 数组
//if 判断 table 和 长度 是否为 null 和 0 , 如果 成立 调用 resize() 创建 新的table
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 数组最大长度n (16) -1 与 hash值 进行 与运算
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 如果 当前 索引位置 对应的 链表 的 第一个元素 和 准备添加的 key 的 hash值 一样
// 并且 满足 下面 两个 条件之一:
//1. 准备加入的 key 和 p 指向的 Node 节点的 key 是 同一个对象
//2. p 指向的 Node 节点 的 key 的 equals() 和 准备 加入的key 比较后 相同
// 就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//在判断 p 是否 是一个 红黑树
//如果 是 一颗 红黑树, 就调用putTreeVal 来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果table 对应的 索引位置, 已经是一个 链表, 使用for进行死循环
//(1) 依次 和 该链表的 每一个元素 比较后,都不相同, 则加入到 该链表的最后
// 注意 在 元素 加入到 链表后, 立即判断, 该链表长度是否 达到 8 个节点
// 达到调用treeifyBin() 对 当前节点 进行 树化(转成红黑树)
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64)) resize();
// 注意 转成 红黑树 之前 进行 判断 数组长度 是否 达到64 ,没有 先扩容,有转红黑树
//(2) 依次和 该链表的 每一个元素比较过程中 ,如果有相同的情况, 就直接 break
else {
//如果 找到的 节点 , 后面 是 链表 循环比较
for (int binCount = 0; ; ++binCount) { //死循环
if ((e = p.next) == null) {// 如果整个 链表 没有相同的,则加到 链表末尾
p.next = newNode(hash, key, value, null);
//判断 链表 是否 大于 8 ,大就树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//循环 过程中 发现 hash值 和 key的eqauals 相同 直接跳出循环 后面 替换value
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//当 key哈希值相同 和 (对象相等 或者 key的eqalus相同 ) 2选一 的 话 进行 替换操作
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //进行替换操作
afterNodeAccess(e);
return oldValue;
}
}
++modCount; //修改次数加1
if (++size > threshold) //临界值
// 判断 节点数 是否大于 临界值
//如果大于 则进行扩容操作,数组容量左移 一位 ,变为原来的 2倍 临界值=0.75*新数组容量
resize();
afterNodeInsertion(evict);
return null;
}
resize()创建table也就是HashMap的数组的代码:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults 首次创建
newCap = DEFAULT_INITIAL_CAPACITY; //默认值 为 16
//负载因子 0.75 * 默认值 16
//临界值扩容,预判达到原先的0.75倍准备扩容,以防止多线程的大量涌入
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
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 = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
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;
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);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
LinkedHashSet 底层原理 源码分析
总结:
-
LinkedHashSet 是 HashSet 的子类
-
底层是一个 LinkedHashMap ,底层维护了一个 数组 +双向链表
-
LinkedHashSet 根据元素的 hashCode 值来都决定元素的存储位置,
同时使用链表维护元素的次序,着使得元素看起来是以插入顺序来保存的。
-
LinkedHashSet 不允许添加重复元素
底层分析:
-
在 LinkedHashSet 中 维护了 一个 hash 表 和 双向链表 (LinkedHashSet 有 head 和 tail)
-
每一个节点 有 pre 和 next 属性 ,这样 可以 形成 双向链表
-
在 添加 一个 元素 时, 先 求 hash值 , 再求出 索引。 确定 该元素在 hashtable 的 位置, 然后
将 添加的 元素 加入到 双向链表( 如果 已经存在 ,不添加)【原则上 和 hashset 一致】
- 这样的话, 遍历 LinkedHashSet 也能 确保 插入顺序 和 遍历顺序 一致
add()
-
添加第一次的时候 ,直接 将 数组 table 扩容到 16, 存放的是 LinkedHashMap$Entry
-
数组 HashMap $ Node[ ]
存放的元素/数据 是 LinkedHashMap $Enrty
-
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } static class Node<K,V> implements Map.Entry<K,V> {
-
唯一的变化就是节点为Entry继承于HashMap的Node,变成了双向链表,其他的扩容,创建,比较全部沿用HashMap的
TreeSet (排序) 底层是 TreeMap
-
当 使用 无参 构造器 , TreeSet 仍然 是 无序的
-
使用 TreeSet 提供的 一个 构造器 , 可以 传入 一个 比较器
-
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator));
@FunctionalInterface public interface Comparator<T> {
-
源码分析
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
// 1.构造器 把 传入 的 比较器 对象 赋给了 底层的 TreeMap 的 属性 this.comparator public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
-
add 方法
public boolean add(E e) { return m.put(e, PRESENT)==null;}
public V put(K key, V value) {
Entry<K,V> t = root;//获取树的头节点
if (t == null) {//头节点为null的情况直接添加
compare(key, key); // type (and possibly null) check防止空值
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) { // cpr 匿名内部类(对象)
do {
parent = t;
//动态绑定 到 我们 匿名内部类(对象) compare方法 中
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果 相等 , 即 返回 0 , 这个key数据 加入不了
return t.setValue(value);
}
while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);//创建新元素的节点
if (cmp < 0)//如果
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtNQ28nl-1636205623303)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636204830788.png)]
Map接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIHVbTGc-1636205623304)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636169163536.png)]
Map 接口实现类的特点
-
Map 用于 保存 具有 映射关系的 数据 key-value
-
Map 中的 key 和 value 可以 是 任何 引用类型 的 数据 , 会 封装到 HashMap$Node对象中
-
Map 的 key 可以 为 null ,value 也可以 为 null , 注意 key 为null ,只能有一个, value 为 null ,可以多个
-
常用String 类 作为 Map 的 key
-
key 和 value 之间 存在 单向 一对一的 关系 , 即 通过 指定的 key 总能 找到 对应的 value
map.get(key) 返回 对应的 value
-
Map 存放的数据 的 key -value ,一对K 是放在 一个HashMap$ Node 中的 ,因为 Node 实现了 Entry 接口
-
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y8yxGMMs-1636205623305)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636185238594.png)]
源码解读
-
k-v 最后 是 HashMap$Node node=newNode(hash, key, value, null);
-
k-v 为了 方便 程序员 的 遍历, 还会 创建 EntrySet 集合, 该集合 存放的 元素 类型 是 Entry
-
而 一个 Entry 对象 就有 K V, EntrySet <Entry <K,V> >
-
transient Set<Map.Entry<K,V>> entrySet;
-
entrySet 中的 K 只是 指向 Node节点中的 key ,V 指向 Node节点的 V 为了遍历方便
-
entrySet 中 ,定义的类型 是 Map.Entry 但是 实际上 存放的 还是 HashMap$Node
-
static class Node<K,V> implements Map.Entry<K,V> //实现了接口 ,多态调用
-
当把 HashMap$Node 对象 存放到 entrySet 方便遍历 , 因为 Map.Entry 提供了
- K getKey(); V getValue();
-
HashMap<String,Integer> map=new HashMap();
for (int i = 0; i <10 ; i++)
{
map.put("Value"+i,i);
}
for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() +"--"+ entry.getValue());
}
final Set<String> strings = map.keySet();
final Collection<Integer> values = map.values();
System.out.println(strings.getClass()); //HashMap$KeySet
System.out.println(values.getClass()); // HashMap$Values
Map 接口常用方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cs2RH7go-1636205623307)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636185568334.png)]
Map 六大遍历
用到的基本方法
- containsKey : 查找 键 是否存在
- KeySet : 获取所有的 键
- entrySet : 获取 所有关系 k-v
- values : 获取所有的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsJGa335-1636205623308)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636186335346.png)]
HashMap<String,Integer> map=new HashMap();
map.put("邓超",1); map.put("孙俪",2);
map.put(null,3); map.put("孙晓",null);
//第一组: 取出所有的 key ,通过key 取出 values Set<String> keySet = map.keySet();
//1. 增强for for(String key:keySet){
System.out.println(key+"---"+map.get(key));
}
System.out.println("-----");
//2. 迭代器
Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()){
String next = iterator.next();
System.out.println(next+"---"+map.get(next)); }
//第二组 : 取出所有的 values
Collection<Integer> values = map.values();
//1. 增强for for (Integer value : values) {
System.out.println(value); }
System.out.println("-----");
//2. 迭代器
Iterator<Integer> iterator2 = values.iterator();
while (iterator2.hasNext()){
final Integer next = iterator2.next();
System.out.println(next); }
//第三组 使用EntrySet 来获取 k-v
//1. 增强for Set<Map.Entry<String,Integer>> entries = map.entrySet();
for (Map.Entry<String,Integer> entry: entries) { System.out.println(entry.getKey()+"---"+entry.getValue()); }
//2. 迭代器
Iterator<Map.Entry<String, Integer>> iterator3 = entries.iterator();
while (iterator3.hasNext()){
Map.Entry<String,Integer> next=iterator3.next(); System.out.println(next.getKey()+"---"+next.getValue());
}
HashMap 底层机制 以及 源码 剖析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vtfLHG8Y-1636205623309)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636188046096.png)]
-
HashMap 底层 维护了 Node 类型的 数组 table ,默认 为 null
-
当创建 对象时 , 将 加载因子 初始化为 0.75
-
当添加 key-value 时, 通过 key的 哈希值
(哈希值 是通过 key的哈希方法 异或(^) h无符号右移 16位后的值 ,目的是为了 降低哈希冲突)
得到 table的 索引值 ( 数组的长度-1 & 与 哈希值)
-
根据 索引值 判断 索引处 是否 有 元素 ,没有 则 直接 添加 ,
如果 table 的索引位置 的 key 的hash相同 和新的key的 hash值相同,
并满足 (table 现有的 节点 key 和 准备添加的 key 是 同一个对象 或者 通过 key的eqalus()相等,
直接 替换值 ;
-
不相等 ,判断 是否为 树形结构 , 是 调用putTreeVal 来进行添加
-
为 链表 则 依次 和 该链表的 每一个元素 比较后,都不相同, 则加入到 该链表的最后
注意 在 元素 加入到 链表后, 立即判断, 该链表长度是否 达到 8 个节点
达到调用treeifyBin() 对 当前节点 进行 树化(转成红黑树) -
注意 转成 红黑树 之前 进行 判断 数组长度 是否 达到64 ,没有 先扩容,有转红黑树
-
第一次添加 ,则 需要扩容 table 的 容量 为 16, 临界值 为 12 16*0.75
-
以后的 扩容中 ,扩容 为 原来的 2倍 , 数组容量 左移 一位 ,当 链表个数达到 8 和 数组长度 达到 64 树化
源码解读
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y1RswWWu-1636205623310)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636190655410.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g9tMz1NI-1636205623311)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636191539753.png)]
HashTable 底层原理 源码分析
- 存放的是 元素 是 键值对 k-v
- hashtable 的 键 和 值 都不能 为 null
- 线程安全 加了 修饰词 synchronized
- 使用方法 与 hashmap 差不多
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wq4Tvx9N-1636205623311)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636192089737.png)]
HashTable 底层
private static class Entry<K,V> implements Map.Entry<K,V> {
-
底层 是一个 数组 HashTable $Entry [ ] 初始化 为 11 Entry 为静态的
-
临界值为 8 数组容量11 * 负载因子 0.75 = 8
-
执行 put的时候 ,判断 值是否 为 null ,null抛出异常 ,addEntry() 添加 k-v 封装到 entry
-
addEntry(hash, key, value, index);
-
-
当满足 if (count >= threshold) 即 添加的元素 总数 大于 临界值 进行扩容
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length; }
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;}
- rehash(); 进行 扩容 机制
数组容量 左移一位 变为原来的两倍 +1 (2n+1)
int newCapacity = (oldCapacity << 1) + 1;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-exYrLPMQ-1636205623313)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636202396902.png)]
Properties 分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XwgO5c2L-1636205623314)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636202600548.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FEKzbLZ4-1636205623315)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636202678358.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wujwvyu1-1636205623316)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636202752339.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-isq11mCg-1636205623316)(C:\Users\城市\AppData\Roaming\Typora\typora-user-images\1636202776387.png)]
TreeMap 底层是 Entry
static final class Entry<K,V> implements Map.Entry<K,V> {
- 当 使用 无参 构造器 , TreeSet 仍然 是 无序的
- 使用 TreeMap提供的 一个 构造器 , 可以 传入 一个 比较器
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
Collection工具类
介绍:
- Collection 是一个操作Set、List 和 Map 等集合的工具类
- Collection 中提供了一系列静态方法对集合元素进行排序、查询和修改等操作
排序方法:
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定的 List 集合元素按升序排序
- sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List, int, int):将指定 List 集合中的 i 处元素和 j 处元素进行交换
package com.zhang.CollectionsDemo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Collections_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
arrayList.add("D");
arrayList.add("E");
arrayList.add("F");
// - reverse(List):反转 List 中元素的顺序
Collections.reverse(arrayList);
System.out.println(arrayList);
// - shuffle(List):对 List 集合元素进行随机排序
Collections.shuffle(arrayList);
System.out.println(arrayList);
// - sort(List):根据元素的自然顺序对指定的 List 集合元素按升序排序
Collections.sort(arrayList);
System.out.println(arrayList);
// - sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
Collections.sort(arrayList, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
});
System.out.println(arrayList);
// - swap(List, int, int):将指定 List 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(arrayList,0,1);
System.out.println(arrayList);
}
}
查找、替换:
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copry(List dest,List src):将src中的内容赋值到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
package com.zhang.CollectionsDemo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Collections_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("A");
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
arrayList.add("D");
arrayList.add("E");
arrayList.add("F");
// - Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println(Collections.max(arrayList));
// - Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中最大元素
System.out.println(Collections.max(arrayList,new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
}));
// - Object min(Collection)
System.out.println(Collections.min(arrayList));
// - Object min(Collection,Comparator)
System.out.println(Collections.max(arrayList,new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).compareTo((String)o2);
}
}));
// - int frequency(Collection,Comparator):返回指定集合中指定元素的出现次数
System.out.println(Collections.frequency(arrayList,"A"));
// - void copry(List dest,List src):将src中的内容赋值到dest中
ArrayList arrayList1 = new ArrayList();
for (int i = 0; i < arrayList.size(); i++) {
arrayList1.add("");
}
Collections.copy(arrayList1,arrayList);
System.out.println(arrayList1);
// - boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
Collections.replaceAll(arrayList,"A","hahahah");
System.out.println(arrayList);
}
}