1. 集合体系图
下面所有的实现类都重写了toString方法
1.1 单列集合
1.2 双列集合
存放的是K-V
2.Collection方法
因为Collection Set List都是接口不能直接实例化,所以我们选用ArrayList来演示各种方法
2.1 添加操作
2.2 删除操作
2.3 查找
2.4 获取元素个数
list.size()
2.5 判断为空
list.isEmpty()
2.6清空
list.clear();
2.7 遍历
2.7.1 Iterator法
-
Iterator对象称为迭代器 主要用于遍历Collection集合中的元素
-
所有实现了Collection接口的集合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
-
Iterator仅用于遍历集合 其本身不存放对象
-
遍历实现步骤
//hasNext()判断是否还有下一个元素
//Next() 1.下移 2.将下移以后的元素返回
//Next() 放回的元素是Object类型
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//如过希望再次遍历需要重置 iterator
iterator = list.iterator();
- 上述结构的快捷键是itit
- 显示所有快捷键的快捷键Ctrl + j
2.7.2 增强for循环
可以替代iterator 特点:增强for循环就是简化版的iterator 底层就是迭代器
可以使用在集合 或者 数组
for (Object a:list) {
System.out.println(a);
}
//快捷键 I
--------List篇---------
3. List方法
- List集合中的元素是有序的(即添加顺序和取出顺序是一致的) 且可以重复
- List集合中的每一个元素都有其对应的顺序索引 即支持索引 如 list.get(index)
- 主要实现类有 ArrayList LinkedList Vector
3.1 添加操作
相比于Collections方法多了index选项,index为可选选项,表示插入的位置
3.2 删除操作
- clear()
- retain()
3.3 查找操作
-
contains 与Collections中的一致
get(index)
-
indexof(Object obj) //返回obj在集合中首次出现的位置
-
lastIndexof(Object obj) //返回obj在集合中末次出现的位置
3.5 赋值(替换)
set(int index,Object o ) //设定指定index位置的元素为o 不能添加元素
3.6 子集合
subList(int fromIndex,int lastIndex)//返回的是左闭右开的
3.7遍历
- 迭代器
- 增强for
- 普通for
4. ArrayList
4.1 性质
- 允许放入null
- 本质上来说是由数组来实现数据存储的
- 基本等同于Vector 除了ArrayList是线程不安全的(执行效率高) 多线程不使用ArrayList
4.1.1 字段
- ArrayList中维护的是一个Object类型的数组 transient Object[] elementData
- transient 表示该属性不会被序列化
4.1.2 构造器
- 如果使用无参构造器 则elemData容量为0 第一次添加 扩容为10 如再次需要扩容 则为原来的1.5倍
- 如果使用指定大小的构造器 则初始容量为指定大小 如再次需要扩容 则为原来的1.5倍
4.2 源码分析
4.2.1 构造
I 无参构造
默认构造一个空数组
II 有参构造
注意如果赋值为0的话 那么数组初始化为EMPTY_ELEMENTDATA 这个和默认构造有区别的
区别就在于以下
则第一次扩容的时候就不是扩为10了 而是扩为1
4.2.2 添加操作
- 在添加之前我们要先判断容量是否足够
- 而判断的方法就是将所需的最小容量(minCapacity)与现有的容量进行比较,如果不够就进行扩容
- 所以我们首先要确定所需的最小容量为多少
ensureCapacityInternal()方法的内部为
这个函数主要有两个目的
- 确定最小容量也就是 calculateCapacity()
- 判断是否容量足够,不够则进行扩容
I 判断是否扩容
- 确定最小容量
通过此方法来确定minCapacity,这里我们传入的minCapacity形式参数一般为size + numNew
- 如果elemData是空参构造的数组,且这是第一次添加操作,则会将DEFAULT_CAPACITY(也就是10)与minCapacity(我们传入的是size+1)比较 将较大的作为新的minCapacity。否则就直接将size + numNew作为minCapacity
- 如果是通过有参构造的,或者不是第一次添加操作。那么直接将minCapacity返回
- 判断是否需要进行扩容
II 扩容操作
- 确定新的容量:将minCapacity 与 原空间的1.5倍进行比较 选择较大的那个作为新的 newCapcaity
- 之后就是调用copyof来进行扩容
5. Vector
相对于ArrayList他是安全的 但是效率不高 因为做了线程判断
扩容情况
- 如果是无参 默认为10,满了以后按两倍扩容
- 如果是有参 则每次按照两倍扩容
源码分析
无参构造
本质上是调用了初始化为10的有参构造
关于capacityIncrement:每次扩充扩多少大小
The amount by which the capacity of the vector is automatically incremented when its size becomes greater than its capacity. If the capacity increment is less than or equal to zero, the capacity of the vector is doubled each time it needs to grow.
添加过程
同样要先判断空间是否足够 若不够 则需要扩容
扩容方法默认为扩容为原来的两倍 若设置了增长量 则按照自己设置的来
6. LinkedList
6.1 特点
- 底层实现了双向链表和双端队列特点
- 可以添加任意元素(可以重复),包括null
- 线程不安全,没有实现同步
- 底层维护了双向链表
6.2 底层结构
Node
LinkedList
6.3 add方法
7. List集合的选择
--------Set篇---------
8. Set接口
-
无序(添加和取出顺序不一样,但是顺序是固定的) 没有索引
-
不允许重复元素,所以最多包含一个null
-
和List接口一样,Set接口也是Collection的子接口,因此常用方法和Collection接口一样
-
遍历方法
- 可以使用迭代器
- 增强for
- 不能使用索引
9. HashSet
-
实现了Set接口
-
底层实际上是HashMap hashMap的底层是数组+链表+红黑树
-
不保证元素是有序的 取决于hash
-
不能有重复元素,可以存放null值,但是只能有一个
9.1 扩容机制
- HashSet底层是HashMap
- 添加一个元素时,会将元素的hashCode经过某种运算先计算得到他的
hash值
之后根据hash值 转化为对应的索引
- 找到存储表table,看这个索引位置是否已经存放元素
- 如果没有 直接加入
- 如果有 调用
equals
比较,- 如果相同 则放弃添加
- 如果不相同 则添加到最后
- 在Java8中,如果一条链表的元素个数 > TREEIFY_THRESHOLD(默认是8),并且table的 >= MIN_TREEIFY_CAPACITY(默认是64) 就会进行树化(红黑树)
9.2 第一次add
9.2.1 得到对应的key和value
调用hashSet的add方法
在其中调用map的put 方法,其中有两个参数
- 作为key的 e
- 作为value 的PRESENT(作用是占位)
public boolean add(E e) { return map.put(e, PRESENT)==null;}
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
9.2.2 求出对应的hash值
通过求出元素的hashCode值,之后将hash值无符号右移16位 进行异或运算,得到新的hash值
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
//不同的类型的hashCode算法是不同的//这里以String为例子public int hashCode() { int h = hash;// Default to 0 if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h;}
9.2.3 添加操作
Params:
- hash – hash for key
- key – the key
- value – the value to put
- onlyIfAbsent – if true, don’t change existing value
- evict – if false, the table is in creation mode.
- Returns: previous value, or null if none
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //如果table为null 或者为空 就先进行扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //如果要加入的索引的位置没有元素 那就直接加入 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //如果当前索引位置对应的链表的第一个元素的hash和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个结点 对当前链表就进行树化 // 再转成红黑树时 如果table的长度小于64 就会进行扩容 //(2)依次和该链表的每一个元素比较后,如果有相同的情况,则break else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null;}
扩容操作
扩容操作即调用resize函数。
触发扩容操作的有以下情况
- 如果原来的table为null或者空表
if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;
- 如果size大于临界值
if (++size > threshold) resize();
扩容的函数如下:
我们要得到新的
- table
- threshold
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; 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;}
10. LinkedHashSet
- LinkedHashSet是HashSet的子类
- 底层是一个LinkedHashMap,底层维护了一个 数组 +双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
- LinkedHashSet不允许添加重复元素
- LinkedHashSet 加入顺序和取出顺序是一致的
- LinkedHashSet 底层维护的是一个LinkedHashMap
- LinkedHashMap 底层结构是
数组table
+双向链表
- 添加第一次时, 直接将table扩容为16 存放的结点类型是LinkedHashMap$Entry
- 数组是 HashMap N o d e [ ] 存 放 的 元 素 是 L i n k e d H a s h M a p Node[] 存放的元素是 LinkedHashMap Node[]存放的元素是LinkedHashMapEntry
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); }}
-------Map篇----------
11. Map接口
11.1 特点
- Map与Collection并列存在。用于保存具有映射关系的数据 key-value
- Map中的key和value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet一样
- Map中的value可以重复
- Map中的key可以为null,value也可以为null
- 常用String作为key
- key和value之间存在单一一对一关系,即通过指定的key总能找到对应的value
-
k-v 最后是 HashMap$Node node= new newNode(hash,key,value,null)
-
k-v为了方便程序员遍历 还会创建 EntrySet集合 该集合存放的元素类型Entry ,而一个Entry 对象就有k,v EntrySet<Entry<k,v>> 即transinet Set<Map.Entry<K,V>> entrySet;
-
entrySet中,定义的类型是Map.Entry 但是实际上存放的还是HashMap$Node
这是因为 static class Node<K,V> implements Map.Entry<K,V>
-
Map.Entry 提供了重要方法 getKey() getValue()
-
得到所有key keySet()
-
得到所有values 用values()
11.2 方法
增加(修改)
public V put(K key, V value)
public void putAll(Map<? extends K, ? extends V> m)
删除
public V remove(Object key)
default boolean remove(Object key, Object value)
public void clear()
查找
public V get(Object key)
public boolean containsKey(Object key)
public boolean containsValue(Object value)
public V getOrDefault(Object key, V defaultValue) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;}
遍历
public Set<K> keySet()
public Collection<V> values()
public Set<Map.Entry<K,V>> entrySet();//其中Entry的方法有1. getKey();2. V getValue();3. V setValue(V value);4. boolean equals(Object o);
杂项
public int size()
public boolean isEmpty()
- put 添加
- remove 根据键删除
- get 根据键获取值
- containsKey 是否存在键
- size
- isEmpty
- clear
11.3 遍历
-
containsKey
-
keySet 获取所有的键
-
entrySet 获取所有的关系k-v
-
values 获取所有的值
12. HashMap
12.1 特点
12.2 底层源码
13. HashTable
1)存放的元素是键值对:即K-V
-
hashtable的键和值都不能为null,否则会抛出NullPointerException
-
hashtable的键和值都不能为null,否则会抛出NullPointerException
-
hashTable是线程安全的(synchronized), hashMap 是线程不安全的