目录
集合有哪些 ?
java集合有两个接口1、Collection接口:单列集合用于存储单个对象2、Map接口双列集合用于存储一对数据(键值对 k-value)。注:以下代码片段是jdk8中的Java源码。
Collection接口
底下又分为两个接口List接口(有序,可重复)该接口实现类有:ArrayList、LinkedList、Vector,set接口(无序不可重复)实现类有:HashSet、LinkedSet、TreeSet。
jdk1.8中ArrayList首先是空数组(jdk1.7开始就创建了一个长度为10的数组)只有添加数据时才会创建长度为10的数组当数组添加元素大于10时会扩容到原来的1.5倍即第一次扩容为15
//默认长度10
private static final int DEFAULT_CAPACITY = 10;
//创建空
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储数据的数组
transient Object[] elementData;
//无参构造,空数组jdk1.8
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//开始添加方法
public boolean add(E e) {
//判断是否超长进入分析 /*假设添加第11个元素*/
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//继续进入calculateCapacity分析
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//第一次添加一个元素时,找出最大长值并返回
/*添加第11个元素,数组不为空*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//添加第11个元素返回11
return minCapacity;
}
//此时第一次添加一个元素时minCapacity为10
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//第一次添加一个元素时,elementData.length=0,minCapacity10
/*添加第11个元素 elementData.length=10 minCapacity =11*/
if (minCapacity - elementData.length > 0)
//进入grow
grow(minCapacity);
}
//扩容方法
private void grow(int minCapacity) {
// overflow-conscious code
//第一次原来初始长度 oldCapacity=0
/*添加第11个元素 oldCapacity=10*/
int oldCapacity = elementData.length;
//第一次newCapacity =0
/*添加第11个元素 newCapacity =15 扩容原来1.5倍*/
int newCapacity = oldCapacity + (oldCapacity >> 1);
//第一次这里成立
/*添加第11个元素 不成立,newCapacity=15 minCapacity=11*/
if (newCapacity - minCapacity < 0)
//第一次minCapacity 是10,newCapacity变成是10
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//将老的数组里的元素复制到新长度中 /*添加第11个元素 和第一次一样*/
elementData = Arrays.copyOf(elementData, newCapacity);
}
HashSet不做源码分析其实他底层就是利用HashMap实现的只是把HashMap的value值设置成一个固定的值Object。
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
Map接口
实现类有:HashMap、TreeMap、HashTable、Properties
HashMap底层源码分析
JDK1.8与JDK1.7区别在于初始创建不同和后续达到某个条件由链表结构转化为红黑树结构。
HashMap map=new HashMap(),底层会创建一个数组长度为16(jdk1.7)类型是Entry[] table{jdk1.8没有创建长度为16的数组类型是Node[]}。
//默认长度16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//最大64
static final int MIN_TREEIFY_CAPACITY = 64;
//链表长度8
static final int TREEIFY_THRESHOLD = 8;
//jdk1.8的存储类型是Node类型但也实现了Entry其实本质它也是Entry
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
//.........
//内容省略
}
//无参构造并此时创建空的,只是把加载因子复制给loadFactor
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//调用resize方法创建好16的数组,此方法是在调用添加方法put()时调用
final Node<K,V>[] resize() {
//oldTab是null
Node<K,V>[] oldTab = table;
// oldCap =0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//oldThr=0
int oldThr = threshold;
int newCap, newThr = 0;
//第一次这里条件不满足,不进此if
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//oldCap =0不大于等于 DEFAULT_INITIAL_CAPACITY=16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//不满足第一次,oldThr=0
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//这里满足,所以newCap =16 当前创建为长度16, newThr 12 最大阈值
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//此时newThr=12
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//threshold=12
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建长度为16的Node类型数组
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;
}
当调用put(key1,value1)插入时首先会调用键(key1)的hashCode()计算出哈希值在通过算法计算出Entry数组所在的位置:
如果该数组位置上没有数据则插入成功,否则:比较要插入key1的哈希值与所在数组当前位置的其他数据key的哈希值是否相同(此处的Entry{数据}是一个或者多个),假设不相同则插入成功。假设key1的哈希值与该数组位置中某一个key哈希值相同则调用key1所在类中equals(key)方法,此时假设返回false说明两个key值不相同则插入成功,如果返回true此时两个key值是相同的会将插入进来的数据的value值将原来的value值替换。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//计算key的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//初次进入首先要进入初始化 resize()
if ((tab = table) == null || (n = tab.length) == 0)
//初始化完毕n=16
n = (tab = resize()).length;
//第一次key的hash计算出位置是否有元素没有则添加进当前位置
//假设有不进入该判断
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//假如有元素来到else
Node<K,V> e; K k;
/*假设当前位置的hash值与要添加的元素hash值相同并且key值也相同
那我们把要添加的值Node p,有它新创建的Node e进行保存
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//这里判断该元素是否是红黑树类型,是进入红黑树添加
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//假设上述当前位置的hash值与要添加的元素hash值不相同或者key值不相同,来到这里
//遍历在此位置存储的链表
for (int binCount = 0; ; ++binCount) {
//遍历到链表最后个元素,都不成立,最后个元素尾指针是null的
if ((e = p.next) == null) {
//添加到尾部
p.next = newNode(hash, key, value, null);
/*变成红黑树逻辑在这里当长度大于8么?大于执行下面treefyBin()方法
那大于8就变红黑树了么进入此方法看看*/
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
/*
能进此循环都说明要添加的元素和此位置的key的hash值相同
此位置的key值与要添加的key值相同,注意两个条件
1、hash值相同2、key值相同
然后结束循环
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//遍历下一个元素
p = e;
}
}
//如果e元素不为空,这里就是替换value值
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;
}
//来到判断是否红黑树关键方法,上面是一个条件
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//第二个条件长度大于64在这里,大于64的话就走else if。小于或者空就继续调用resize()扩容方法
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
当不断添加过程中某一个存储位置超过1(Entry数组,这里位置指数组下标的位置)会以链表的形式将后面添加进这个位置的数据存储起来。当数组长度超过了临界值且要存放位置非空时(临界值等于当前数组长度*加载因子,举例第一次长度是16当数组长度超过12时就开始扩容了而非到达16时扩容)会扩容到原来的2倍并把原来数据复制过来(要重新计算所有元素的位置)。jdk1.8与jdk1.7不同。jdk1.8当该处位置存储链表长度达到大于8并且数组长度大于64会将此位置的存储方式转换成红黑树进行存储。