可以将JAVA 中的集合分成两派分别是 Collection 派别 和 Map 派别
Collection
List接口
List接口的特点:存放的元素是有序的,而且可以重复
ArrayList
- ArrayList 继承关系图
ArrayList 常用API TODO
ArrayList 底层数据结构
ArrayList底层数据结构是数组: 数组的特点:它是一块连续的存储空间,使用下标索引进行访问,所以说他的访问速度是比较块的
源码分析:
/**
* 默认容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享一个空的数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/* 只是创建了一个空的ArrayList对象的时候它的默认大小是一个空的对象,当添加第一个元素的时候默认的初 *始化大小是10*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
* 元素的个数
* @serial
*/
private int size;
/**
* 初始化的时候指定容量大小
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 空参构造器,初始化一个空的对象数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
1.在创建一个ArrayList的时候没有添加元素,他会创建长度空的对象数组
ArrayList<Integer> list = new ArrayList<>();
2.添加对象的时候,数组长度的情况
添加一个对象的默认长度,赋值为10
3.触发第一次扩容
通过上面的源码可以知道,当添加了第一个元素以后默认的容量大小是10,那么在添加第11个元素的时候会触发扩容的机制。
LinkedList
- LinkedList 继承关系图
LinkedList 常用API TODO
底层数据结构
LinkedList底层的数据结构是双向链表:链表的特点,他的next指针会执向内存的随机的一块区域, 所以他在查找的时候效率会低一点,但是在插入的时候和删除的时候效率是比较好的
底层数据结构:
可以看的到他是一个双向链表
- add方法:添加一个元素
Vector
- Vector 继承关系图
常用API
底层数据结构
从下面的这个图中可以知道Vector底层的数据结构也是采用了一个对象数组的结构,而且他在JDK1.0版本的时候就已经存在了
- 创建的Vector的时候的默认大小是10
- 它的操作方法是线程安全的
- Vector它的扩容机制
指定 capacityIncrement
不指定 capacityIncrement 就是扩容 2倍
总结对比ArrayList、LinkedList、Vector他们之间的相同点和不同点
先说一说 ArrayList 和 Vertory
相同:首先他们两个底层都是使用的数组,也都是在List体系下所以他们都是有序可以重复的数据
不同:他俩的不同点在于 ArrayList他的操作方法是线程不安全的,而Vertory的操作方法是线程安全的。其次就是他们的扩容机制不同,ArrayList的扩容它扩容的大小是原来的0.5倍,Vertory扩容的大小是可以指定的,如果不指定扩容大小,那么扩容的大小翻倍,如果指定了则扩容 原来的容量 + 指定大小
LinkedList 它的底层数据结构就是链表了,底层数据结构是链表,不需要连续的内存,随机访问的慢,但是增删块头部,尾部插入的速度块,但是如果想要往中间插入并不快,内存容量占用比较大
Set接口
Set接口特点 元素无序、不可重复的集合
HashSet 继承图
SortedSet 继承图
Queue接口
Map
Map 集合是和Collection并列的存在,用来保存具有映射关系的数据Key-Value
Map中的Key和Value可以是任何应用类型的数据,
Map中的key不允许重复,但是Value可以重复
Map中的key可以为Null 但是只能有一个,Value可以为null但是可以有多个
Map集合的遍历方式
@Test
public void mapBianLi(){
Map map = new HashMap();
map.put("1","牛小牛");
map.put("2","牛小牛2");
map.put("3","牛小牛3");
map.put("4","牛小牛4");
System.out.println("**********第一种遍历方式***********");
Set keySet = map.keySet();
for (Object key:keySet){
System.out.println(key + "-" + map.get(key));
}
System.out.println("***********第二种遍历方式***********");
Iterator iterator = keySet.iterator();
while(iterator.hasNext()){
/*获取到的是Map的Key*/
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
System.out.println("***********第三种遍历方式***********");
//取出来所有的values
Collection values = map.values();
for(Object value : values){
System.out.println(value);
}
System.out.println("***********迭代器方式获取值***********");
Iterator iterator2 = values.iterator();
while(iterator2.hasNext()){
Object value = iterator2.next();
System.out.println(value);
}
System.out.println("**************第三组遍历方式***********");
Set entrySet = map.entrySet();
for (Object entry:entrySet){
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
System.out.println("****************迭代器****************");
Iterator iterator3 = entrySet.iterator();
while(iterator3.hasNext()){
Map.Entry next = (Map.Entry) iterator3.next();
System.out.println(next.getKey() + "-" + next.getValue());
}
}
}
HashMap源码分析
1、执行内部的构造器
初始化加载因子loadfactor 默认值0.75
2、执行put调用Hash方法,计算key的hash值
3、执行putVal
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return 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;
//1、如果table为空,或者lenth == 0 执行resize方法,扩容到16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//2、通过hash计算索引的位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
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 {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 树化操作,链表的节点数量大于8个并且table的长度大于64才会树化
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;
}
树化代码:
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
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);
}
}
扩容方法:
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
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
//如果Table为空赋值初始容量 16
newCap = DEFAULT_INITIAL_CAPACITY;
//计算他的一个加载因子 加载因子(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;
}
HashTable 源码分析
1、存放的也是键值对
2、HashTable的键值不能为null
3、HashTable是线程安全的,HashMap是线程不安全的
HashTable 构造方法:
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
//如果容量小于 0 抛出非法参数异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 如果 loadFactor 传过来的不是一个浮点数,或者是说传过来的值小于0,则抛出异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
可以看到他的默认初始容量是11
临界值的计算规则
Put方法:
public synchronized V put(K key, V value) {
// 判断value是否为null,如果为null,则抛出异常
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
// 计算索引下标
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
--- TODO