Java 集合框架详情
集合框架总览图
Iterable接口
Iterable接口是Java集合框架的顶级接口,实现此接口可以通过迭代器遍历自身元素。Java容器中,Collection继承Iterable接口。
Iterable接口源码
public interface Iterable<T> {
// 创建一个类型为T的Iterator的实例
Iterator<T> iterator();
// 遍历操作集合内的元素
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// 并行遍历元素的迭代器
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
Iterator接口
Iterator接口中提供了对hasNext()、next()等元素的操作方式。Iterator的实现类中完成具体的元素的操作。Collection的子类中匿名内部类会实现Iterator接口进行对容器中元素的操作。
Collection接口
Collection接口中定义了元素属性、元素方法等方法。
@Test
public void test1(){
Collection list = new ArrayList();
Collection linkList = new LinkedList();
Collection set = new HashSet();
Collection linkSet = new LinkedHashSet();
Collection queue = new ArrayDeque();
Collection deQueue = new DelayQueue();
}
继承Collection的接口
List
List接口扩展Collection接口中的方法。
ArrayList
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
- ArrayList概述:ArrayList是基于数组实现的,能够动态扩容。ArrayList不是线程安全的,实现了
Serializable接口能够序列化传输。支持快速随机访问,实现了Cloneable接口可以实现对象克隆。 - ArrayList初始化
// 默认无参构造函数
public ArrayList() ;
// 制定大小初始化
public ArrayList(int initialCapacity);
// Collection实例初始化
public ArrayList(Collection<? extends E> c)
- ArrayList动态扩容:在add()中
// 最具体的扩容方法
private void grow(int minCapacity) {
// 目前数组的长度
int oldCapacity = elementData.length;
// 新的数组的大小 旧数组大小+就数组大小右移1位
// newCapacity =oldCapacity + 0.5 * oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// MAX_ARRAY_SIZE 为int最大值减去8
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);
}
如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时,才真正分配容量。每次按照 oldCapacity + (oldCapacity >> 1)。通过copeOf的方式扩容。
Set
Set接口继承Collection扩展Collection接口中的方法,
HashSet类关系
Java.util.Map接口
HashMap类关系
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}
HashMap底层存储结构
HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做一个Entry。HashMap中key和value都可以是null。
- 数组:数组中的元素以Entry<K,V>的方式存储。
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
// 定位数组索引位置
final int hash;
final K key;
V 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() {}
public final V setValue(V newValue) {}
public final boolean equals(Object o) {}
}
- 链表
- 红黑树:链表过长时候转换为红黑树,而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
HashMap 使用哈希表存储
- Map.put(“name”,“张三”)
调用hashCode()计算name的哈希值,通过hash算法的高位运算和取模运算定位该键值对存储的位置。当key的hashCode值相同时候则发生hash碰撞。好的Hash算法和扩容机制可以减小hash碰撞的概率。
int threshold; // 所能容纳的key-value对极限
final float loadFactor; // 负载因子
int modCount; // 记录HashMap内部结构发生变化的次数
int size; // Map中存的键值对数量
- Node[K,V] table[]的初始化长度是16,负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。
- threshold就是在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。
HashMap确定数组索引位置
- hashCode算索引
- 高位运算
- 取模运算:h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
static final int hash(Object key) {
int h;
// 计算key的hash值 h = key.hashCode()
// 高位参与运算 h^(h>>>16) h异或(h右移16位)
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
扩容机制
- 扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组,就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。
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;
}
// 没超过最大值,就扩充为原来的2倍
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;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 计算新的resize上限
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循环把oldTab中的每个节点node,reHash操作并移动到新的数组newTab中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 若是单个节点,即没有后继next节点,则直接 newTab 在进行重定位
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//若节点为TreeNode,则需要进行红黑树的rehash操作
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//else则节点为链表,需进行链表的rehash操作,链表重组并保持原有顺序
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;
//通过与位运算&,判断rehash后节点位置是否发生改变
if ((e.hash & oldCap) == 0) {
if (loTail == null)
//loHead 指向新的 hash 在原位置的头节点
loHead = e;
else
//loTail 指向新的 hash 在原位置的尾节点
loTail.next = e;
loTail = e;
}
//else则rehash后节点位置变为:原位置+oldCap位置
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;
}
Map 基本操作和常用方法
找出Map中Values的最大和最小值(Arrays.sort(object))
@Test
public void test1(){
Map<String, String> map=new HashMap<>();
map.put("4","5");
map.put("5","8");
map.put("6","9");
map.put("7","7");
Collection<String> values = map.values();
Object[] objects = values.toArray();
Arrays.sort(objects);
String min = String.valueOf(objects[0]);
String max = objects[objects.length - 1].toString();
System.out.println("最大和最小值为:"+max+"===="+min);
}
map.get() 或者map.getOrDefault()
- map.get(“test”):当值为空时候返回null
- map.getOrDefault(“test”):当值为空时候可以给给定默认值
@Test
public void test6() {
Map<String, String> map = new HashMap<>();
map.put("6", "9");
map.put("7", "7");
String test = map.get("test");
String test1 = map.getOrDefault("test", "test1");
System.out.println(test);
System.out.println(test1);
}
判断集合为空 map.isEmpty()
@Test
public void test6() {
Map<String, String> map = new HashMap<>();
Map<String, String> map1=new HashMap<>();
boolean empty = map.isEmpty(); # false
boolean empty1 = map1.isEmpty(); #true
}
Map遍历
map.foreach()
@Test
public void test2(){
Map<String, String> map=new HashMap<>();
map.put("6","9");
map.put("7","7");
map.forEach((x,y)->{
System.out.println("键值为:"+x+"===="+y);
});
}
Map的Entry
@Test
public void test3(){
Map<String, String> map=new HashMap<>();
map.put("6","9");
map.put("7","7");
for(Map.Entry entry:map.entrySet()){
System.out.println(entry.getKey()+"====="+entry.getValue());
}
}
For循环map.keyset()
@Test
public void test4(){
Map<String, String> map=new HashMap<>();
map.put("6","9");
map.put("7","7");
for(String entry:map.keySet()){
System.out.println(entry+"===="+map.get(entry));
}
}
得到Map中的所有Values map.values()
@Test
public void test5(){
Map<String, String> map=new HashMap<>();
map.put("6","9");
map.put("7","7");
Collection<String> values = map.values();
System.out.println(values);
}