Vector 和 ArrayList的比较:
vector是最早版本的动态数组,线程安全,不够扩容为原来的2倍,初始容量为10
支持遍历的集合方式有:(1)foreach (2)Iterator (3)支持旧版的Enumeration迭代器
ArrayList相对于vector新一点,线程不安全的,不够扩容为原来的1.5倍,初始容量为10
支持遍历的集合方式有:(1)foreach (2)Iterator
2倍造成空间浪费的可能性比较大
1.5倍造成扩容次数增大
为了比较空间浪费,和扩容次数太多,如果能够预估大概的元素个数,那么可以用ArrayList(int initialCapacity)和Vector(int initialCapacity)直接初始化为一定容量的数组
Stack:栈
先进先出(FILO)或后进先出
Stack是Vector的子类,比Vector多了几个方法,它的后进先出的特征,就是通过调用这几个方法实现的
LinkedList:双向链表
内部有一个节点类型:
class Node{
Object data;
Node previous;
Node next;
}
class LinkedList{
Node first;
Node last;
}
LinkedList可以被当作双向链表、栈、队列、双端队列等数据结构使用
Vector源码:
(1)、Vector v = new Vector(); //无参构造,初始化长度为10的数组,默认增量是0
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
(2)、v.add("haha")
// 默认扩容为原来的2倍,如果手动指定了capacityIncrement的值,那么可以按照你指定的增量进行扩容
(3)、v.add(index,e)
// 1 考虑扩容
2 移动元素
3 添加元素
4 元素个数增加
(4)、v.remove(index)
// 1 计算要移动的个数
2 如果需要移动,调用System.arraycopy方法进行移动
3 将最后一位设置为Null
(5)、v.remove(Object obj)
// 1 先获取要删除元素的下标 indexOf(Objec obj)
2 再调用v.remove(index)方法
ArrayList源码:
(1) ArrayList list = new ArrayList();
JDK1.8版本:发现内部初始化为一个长度为0的空数组
JDK1.7版本:也是初始化为长度为0的空数组
JDK1.6版本:初始化为长度为10的数组
为什么要初始化为空数组呢?
因为开发中,很多时候创建了ArrayList的对象,但是没有装元素,但是这个时候如果初始化为10就浪费空间了
(2) list.add(Object e)
JDK1.8 第一次添加元素,扩容为长度为10的数组
JDK1.8 如果不够了,再扩容为1.5倍
Stack源码:
(1) Stack stack = new Stack();
(1) stack.push()
等价于Vector的add方法,因为他们是父子类关系
(2) stack.peek()
返回size-1位置的元素
(3) stack.pop()
先peek()返回栈顶元素
删除size-1位置的元素
LinkedList源码:
(1) LinkedList linkL = new LinkedList()
啥也没干
(2) linkL.add(Object e)
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
// 新节点的pre是刚刚的最后一个节点
// 新节点的下一个节点是null
final Node<E> newNode = new Node<>(l, e, null);
// 新节点称为了最后一个节点
last = newNode;
// 如果之前的最后一个节点是null,表示刚才链表是空的,此时新节点也是第一个节点
if (l == null)
first = newNode;
else
// 如果刚才链表不是空的,原来的最后一个节点的next指向新节点
l.next = newNode;
// 元素个数加一
size++;
modCount++;
}
Set源码:HashSet TreeSet LinkedHashSet
(1) Set系列集合元素不能重复
(2) LinkedHashSet可以保证元素的添加顺序
(3) TreeSet可以保证元素的大小顺序
(4) 添加到TreeSet必须的对象必须实现java.langComparable接口
(5) 如何保证元素不可重复呢?
换句话说,如何判断两个元素是重复的呢?
HashSet和LinkedHashSet:
(1) 先比较hash值,如果hash值不一样,说命一定不相同
(2) 如果hash值一样,再调用equals方法比较
TreeSet:
按照元素的大小来决定是否是相同元素
Map
HashMap
HashTable
TreeMap
LinkedHashMap
Properties
1、HashMap和Hashtable (哈希表)的区别
Hashtable:旧版,线程安全的,它的key和value不能为null
HashMap:相对Hashtable来说新一点,线程不安全的,它允许key和value为null
2、LinkedHashMap 和 HashMap 的区别
LinkedHashMap是HashMap的子类,多维护了映射关系的添加顺序
3、HashMap和TreeMap的区别
HashMap:无序的
TreeMap:按照key排大小顺序
4、Properties是Hashtable的子类,不允许key和value是null,并且key和value都是String类型
5、所有的Map的key不能重复,如何实现不重复?
HashMap、Hashtable、LinkedHashMap、Properties:依据key的hashCode和equals方法
TreeMap:一句key的大小,认为大小相等的两个key就是重复的
6、Set的底层实现是什么?
HashSet : HashMap
TreeSet : TreeMap
LinkedHashSet : LinkedHashMap
Set添加元素是用add(key),而Map添加元素用put(key,value)
发现add底层实现是调用的put,只是add底层调用put时给了同一个对象作为value
7、Map的底层实现是什么:
(1)、哈希表系列:
数组 + 链表
数组 + 链表/红黑树
(2)、TreeMap:红黑树
HashMap的底层实现:
JDK1.7以及之前:数组 + 链表
JDK1.8以及之后:数组 + 链表/红黑树
HashMap根据key的hashCode--->公式/算法--->[index]
因为不同的hashCode值,可能得到的[index]是相同的,那么冲突来了,那么只能把[index]的多个映射关系用链表链接起来
二叉树特点:查找速度比链表快
旧版的HashMap,如果key的hashCode算出了[index]相同的话,都在一个table[index]下面,
如果严重的话,会导致[index]下面的链表很长,就会导致查询速度减慢,当链表长到一定程度时,就需要把链表变为二叉树,以提高我们查找速度
HashMap JDK1.7 : 数组 + 链表
(1)、HashMap hs = new HashMap()
public HashMap(){
// 调用自身构造方法
this(DFAULT_INITIAL_CAPACITY(16),DEFAULTY_LOAD_FACTOR(0.75));
}
this(int initialCapacity, float loadFactor){
// 次构造方法中没看到声明一个有长度的容量,因为其默认是空容量的
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
new HashMap()
table数组初始化为了一个长度为0的空数组
DFAULT_INITIAL_CAPACITY:16 默认容量
DEFAULTY_LOAD_FACTOR:0.75 默认加载因子
threshold: 阈值/临界值 数组需要考虑扩容的阈值
threshold = capacity(容量,数组的长度)*load_factor(加载因子,默认是0.75) = 12
最开始初始化时 threshold = initialCapacity=16
例如:threshold = 16*0.75 当数组大概3/4满的时候就考虑扩容
思考:load factor设置为0.9和0.1有什么区别?
0.1:扩容太频繁
0.9:会导致table[index]下面的链表会很长,查询速度就低
(2)、初始容量为0,什么时候扩容为16?
HashMap会首先初始化为一个0的容量,在第一次put时,threshold还不是12(没有使用 threshold = capacity(容量,数组的长度)*load_factor(加载因子,默认是0.75),使用的是16,
过程为:先判断表是否为空表,如果是空表,就使用threshold=initialCapacity=16,随后会立刻修改threshold的值为两者相乘,即12)
也就是说最开始初始化为0,后来第一次添加put元素时变成16的容量,并把阈值修改为12
如果不是空数组:或者说第二次以后添加put元素 数
public V put(K key, V value) {
if (table == EMPTY_TABLE) { // 判断table数组是否为空
inflateTable(threshold); // 刚刚初始化后 threshold = 16
}
public void inflateTable(int toSize){
int capacity = roundUpToPowerOf2(toSize);
// 此时threshold变为 16 * 0.75 = 12
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
// 此时才开始初始化一个容量为 16 的table
table = new Entry[capacity];
}
if (key == null)
return putForNullKey(value);
int hash = hash(key); //根据key的hashCode用异或,无符号右移等各种运算,得到一个int类型的hash值
因为我们下面要用hash值来算[index],他的设计者认为用户重写的hashCode可能不够散列
int i = indexFor(hash, table.length); // hash & table.length-1 找到要插入table的下标
/*
table数组的长度一定是2的n次方,
table.length-1 的二进制前面都是0,后面都是连续的1
hash & table.length-1 做按位与运算的结果一定是在[0,table.length-1]范围内
*/
// 判断table[i]下面的链表中是否有映射关系的key是和我重复的,如果有就替换成新的value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
// 当元素的总个数达到阈值 && 并新的映射关系要添加的table[index]下面不是空的
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length); // 数组扩容为原来的两倍
hash = (null != key) ? hash(key) : 0; // 重新计算hash
bucketIndex = indexFor(hash, table.length); // index也重新计算
}
createEntry(hash, key, value, bucketIndex);
}
void resize(int newCapacity){
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if(oldCapacity == MAXIMUM_CAPACITY){
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity]; // 开辟一个两倍于之前大小的数组
transfer(newTable,initHashSeedAsNeeded(newCapacity)); // 将之前数组的元素移动位置,因为要重新计算hash和index
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor,MAXIMUM_CAPACITY + 1); // threshold 跟进改变
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
// 把table[index]下面原来的Entry连接到新的Entry的next中
size++;
}
a、发现数组table是空数组后,会把数组初始化为长度为16的Entry类型的数组,并且把threshold计算为12
这里如果手动指定了数组的capacity,那么如果这个capacity不是2的n次方,会自动纠正为2的n次方
为什么要纠正为2的n次方?
后面算index = hash & table.length-1,这样才能保证[0,table.length-1]范围内
2的n次方,根据它的散列算法,可以保证比较均匀的分散在他的数组的各个位置
b、 hash = hash(key)
为了干扰我们key的hashCode值
c、index = hash & table.length-1
d、先判断table[index]下面是否有映射关系的key是和我新添加的映射关系的key有重复的,如果有,就用新的value替换旧的value,就结束了
e、如果没有重复的,决定添加新的映射关系
(e1)、看是否需要扩容
扩容条件:A:size达到阈值threshold B:table[index]下面已经有映射关系,即不为空
如果扩容了,会重新计算hash和index
(e2)把新的映射关系new为一个Entry的对象,放到table[index]中,原来table[index]的映射关系作为新的映射关系的next连接起来
Entry相当于一个节点类型,是一个单向链表的节点类型
class Entry{
int hash;
Object key;
Object value;
Entry next;
}
HashMap JDK1.8 : 数组 + 链表/红黑树
(1)、DEFAULT_INITIAL_CAPACITY = 16 默认初始化容量16
(2)、MAXIMUM_CAPACITY = 2的30次方 最大容量
(3)、DEFAULT_LOAD_FACTOR = 0.75 默认加载因子
(4)、TREEIFY_THRESHOLD = 8 默认树化阈值为8,当链表长度达到这个值时,就考虑是否要树化
(5)、UNTREEIFY_THRESHOLD = 6 默认反树化阈值为6,当链表长度小于这个值时就考虑将树转换为链表
(6)、MIN_TREEIFY_CAPACITY = 64 最小树化容量64
当单个链表的节点个数达到8,并且table的长度达到64,才会树化
当单个链表的节点个数达到8,但是table的长度未达到64,会先扩容
(7)、Node<K,V>[] table: 数组
(8)、size: 记录有效映射关系的对数,也是Entry对象的个数
(9)、threshold: 阈值,当size达到阈值时,考虑扩容
(10)、loadFactor: 加载因子,影响扩容的频率
源码:
1、HashMap hashMap = new HashMap();
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
// 初始化只赋值了加载因子,其他都是默认值
// threshold = 0
// table = null
// size = 0
}
2、put(key,value)
(1)、如果第一次添加时
把table初始化为长度16的数组,threshold = 12
(2)、如果不是第一次添加
a、会考虑是否key有重复,那么就替换value
b、如果table[i]下面不是树,统计table[i]的结点个数,添加之前达到7个,考虑树化
当单个链表的节点个数添加之前达到7,并且table的长度达到64,才会树化
当单个链表的结点个数添加之前达到7,table的长度未达到64,先扩容
c、table[i]下面已经是树,单独处理,直接把新的映射关系连接到树的叶子结点
d、添加后,size达到threshold,还要扩容
一旦扩容,就会调整所有映射关系的位置
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// hash()目的是干扰hashCode值
static final int hash(Object key) {
// 如果key是null,hash是0
// 如果key非Null,用key的hashCode值 与 key的hashCode值高16进行异或
// 即就是用key的hashCode值高16位与低16位进行异或的干扰运算
/*
index = hash & table.length - 1
如果用key的原始的hashCode值 与 table.length-1 进行按位与,那么基本上高16位不会参与进来
这样就会增加重复的概率,为了降低冲突的概率,把高16位加入到hash信息中
*/
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; // n为数组长度 i是下标
// tab和table等价
// 如果table是空的
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
/*
如果table是空的,resize()完成了
a、创建了一个长度为16的数组
b、threshold = 12
n = 16
*/
// i = (n - 1) & hash, 下标 = 数组数组长度 - 1 & hash
// p = tab[i]
// if(p == null) 条件满足的话说命,table[i]还没有元素
if ((p = tab[i = (n - 1) & hash]) == null)
// 把新的映射关系直接放入table[i]
tab[i] = newNode(hash, key, value, null);
// 根据传入的key和value生成一个新结点,此新结点的next是空,并把此新结点放入tab[i]
else {
Node<K,V> e;
K k;
// p 是table[i]中的第一个结点
// if(table[i]的第一个结点与新的映射关系的key重复)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p; // 记录这个table[i]的第一个结点
else if (p instanceof TreeNode) // 如果table[i]第一个结点是树结点
// 单独处理树结点
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// table[i]的第一个结点不是树结点,也与新的映射关系的key不重复
// binCount记录了table[i]下面的结点的个数
for (int binCount = 0; ; ++binCount) {
// 如果p的下一个结点是空的,说命当前的P是最后一个结点
if ((e = p.next) == null) {
// 把新的结点连接到table[i]的最后
p.next = newNode(hash, key, value, null);
// 如果binCount>=8-1,达到7个时
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 要么扩容,要么树化
treeifyBin(tab, hash);
break;
}
// 如果key重复了,就跳出for循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果e不是null,说命有key重复,就考虑替换
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 元素个数增加
// size达到阈值
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //oldTable原来的table
// oldCap:原来数组长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// oldThr:原来阈值
int oldThr = threshold;
// newCap: 新容量
// newThr:新阈值
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)
// newCap = 旧的容量*2,新的容量<最大数组容量限制
// 新容量:32 64 ...
// oldCap >= 初始容量16
// 新阈值重新算 = 24,48 ,。。。
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 = 12
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);
}
// 阈值赋值为新阈值12
threshold = newThr;
// 创建一个新数组,长度为newCap16
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 把原来的table中映射关系,倒腾到新的table中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) { // e是table下面的结点
oldTab[j] = null; // 把旧的table[j]位置清空
if (e.next == null) // 如果是最后一个结点
newTab[e.hash & (newCap - 1)] = e; //重新计算e的在新的table中的存储位置,然后放入
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;
}
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
// 创建一个新结点
return new Node<>(hash, key, value, next);
}
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index;
Node<K,V> e;
// MIN_TREEIFY_CAPACITY:最小树化容量64
// 如果table是空的,或者,table的长度没有达到64
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 ... while,把table[index]链表变为红黑树
*/
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);
}
}