目录
一、Collection接口
集合是java中提供的一种容器,可以用来存储多个数据,Collection即单列集合。统一定义了一套单列集合的接口。
二、List接口
ArrayList
本质就是数组,实现动态扩容,有序可重复
ArrayList接口属性
// 默认数组的长度
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于默认大小的空实例的共享空数组实例。将其与EMPTY_ELEMENTDATA区分开来,
* 以了解何时添加了第一个元素。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储数据的数组对象
transient Object[] elementData;
// 数组长度
private int size;
初始操作
无参构造 new ArrayList();
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
// this.elementData = {}
}
有参构造 new ArrayList(int size);
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 初始长度大于0 就创建一个指定大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// {}数组赋值给 this.elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
add 方法
1.第一次添加
public boolean add (E e) {
// 确定容量 动态扩容 初
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将要添加的元素 添加到数组中 elemenrData[0] = 1 --> size = 1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// ensureExplicitCapacity(10)
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* elementData {}
minCapacity 1
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 10 1 return 10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 5
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 增长 操作次数
// minCapacity 10
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) { // 10
// overflow-conscious code
int oldCapacity = elementData.length; // 0
// newCapacity = 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
// newCapacity = 10
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// {} {,,,,,,,,,} 返回一个新的数组 长度为10
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.第二次添加
elementData = {1,,,,,,,,,};
size = 1;
public boolean add(E e) {
// 2
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; // elementData[1] = 2 size = 2
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 2
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code 2 - 10
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3.第十一次添加
elementData = {1,2,3,4,5,6,7,8,9,10};
size = 10;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// ensureExplicitCapacity(11)
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 11 - 10 > 0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) { // 11
// 10
int oldCapacity = elementData.length;
// 15 newCapacity 是oldCapacity的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// {1,2,3,4,5,6,7,8,9,10} -- > {1,2,3,4,5,6,7,8,9,10,,,,,}
elementData = Arrays.copyOf(elementData, newCapacity);
}
get方法
public E get(int index) {
// 检查下标是否合法
rangeCheck(index);
// 通过下标获取数组对应的元素
return elementData(index);
}
set方法
public E set(int index, E element) {
rangeCheck(index); // 检查下标
// 获取下标原来的值
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
remove方法
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
// 获取要移动的元素的个数 {1,2,3,4,5,6,7,8,9} // 3 size=9 index=3
// {1,2,3,5,6,7,8,9,null}
int numMoved = size - index - 1; // 5
if (numMoved > 0)
// 源数组 开始下标 目标数组 开始下标 长度
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
// 删除的节点对应的信息
return oldValue;
}
FailFast机制
快速失败的机制,Java集合类为了应对并发访问在集合迭代过程中,内部结构发生变化的一种防护措施,这种错误检查的机制为这种可能发生错误通过抛出 java.util.ConcurrentModificationException。
LinkedList
LinkedList
是通过双向链表去实现的,他的数据结构具有双向链表的优缺点,既然是双向链表,那么的它的顺序访问效率会非常高,而随机访问的效率会比较低,它包含一个非常重要的私有内部静态类:Node。
private static class Node<E> {
E item; // 节点的元素
Node<E> next; // 下一个节点
Node<E> prev; // 上一个节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
get方法
本质上还是遍历链表中的数据
Node<E> node(int index) {
// assert isElementIndex(index);
// index 和 长度的一半比较
if (index < (size >> 1)) {
Node<E> x = first;
// 从头开始循环
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
// 从尾部开始循环
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
set方法
public E set(int index, E element) {
checkElementIndex(index);// 检查下标是否合法
Node<E> x = node(index); // 根据下标获取对应的node对象
E oldVal = x.item; // 记录原来的值
x.item = element; // 赋予新的值
return oldVal; // 返回修改之前的值
}
Vector
和ArrayList
很类似,都是以动态数组的形式来存储数据;
Vector
线程安全的;
每个操作方法都加的有synchronized
关键字,针对性能来说会比较大的影响,慢慢就被放弃了。
Collections
可以增加代码的灵活度,在我们需要同步是时候就通过如下代码实现。
List syncList = Collections.synchronizedList(list);
本质上
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
三、Set接口
1.HashSet
概述
HashSet
实现Set
接口,由哈希表支持,它不保证set
的迭代顺序,特别是它不保证该顺序永久不变,运行使用null
。
public HashSet() {
map = new HashMap<>();
}
add方法``
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
本质上是将数据保持在 HashMap
中 key
就是我们添加的内容,value
就是我们定义的一个Object
对象
特点
底层数据结构是哈希表,HashMap
的本质是一个"没有重复元素"的集合,他是通过HashMap
实现的.HashSet
中含有一个HashMap
类型的成员变量map
.
2.TreeSet
概述
基于TreeMap
的 NavigableSet
实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator
进行排序,具体取决于使用的构造方法。
public TreeSet() { this(new TreeMap<E,Object>());}
本质是将数据保存在TreeMap
中,key是我们添加的内容,value是定义的一个Object对象。
四、Map接口
Map集合的特点
1.能够存储唯一的列的数据(唯一,不可重复) Set
2.能够存储可以重复的数据(可重复) List
3.值的顺序取决于键的顺序
4.键和值都是可以存储null元素的
TreeMap
本质上就是红黑树
的实现
1.每个节点要么是红色,要么是黑色。
2.根节点必须是黑色
3.每个叶子节点【NIL】是黑色
4.每个红色节点的两个子节点必须是黑色
5.任意节点到每个叶子节点的路径包含相同数量的黑节点
K key; // key
V value; // 值
Entry<K,V> left; // 左子节点
Entry<K,V> right; // 右子节点
Entry<K,V> parent; // 父节点
boolean color = BLACK; // 节点的颜色 默认是黑色
HashMap
HashMap底层结构
Jdk1.7及以前是采用数组+链表
Jdk1.8之后 采用数组+链表 或者 数组+红黑树方式进行元素的存储
存储在hashMap集合中的元素都将是一个Map.Entry的内部接口的实现
// 默认的HashMap中数组的长度 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// HashMap中的数组的最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的扩容的平衡因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 链表转红黑树的 临界值
static final int TREEIFY_THRESHOLD = 8;
// 红黑树转链表的 临界值
static final int UNTREEIFY_THRESHOLD = 6
// 链表转红黑树的数组长度的临界值
static final int MIN_TREEIFY_CAPACITY = 64;
// HashMap中的数组结构
transient Node<K,V>[] table;
// HashMap中的元素个数
transient int size;
// 对HashMap操作的次数
transient int modCount;
// 扩容的临界值
int threshold;
// 实际的扩容值
final float loadFactor;
put方法原理分析
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
hash(key):获取key对应的hash值
static final int hash(Object key) {
int h;
// key.hashCode() 32长度的二进制的值
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么要右移16位?
A:1000010001110001000001111000000
B:0111011100111000101000010100000
A 和 B 对 15 11111&预算 得到的都是 0 相同,会造成散列分布不均匀
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
// 初始的判断
// resize() 初始数组 扩容 初始的时候 获取了一个容量为16的数组
n = (tab = resize()).length; // n 数组长度
// 确定插入的key在数组中的下标 15 11111
// 100001000111000
// 1111
// 1000 = 8
if ((p = tab[i = (n - 1) & hash]) == null)
// 通过hash值找到的数组的下标 里面没有内容就直接赋值
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash // hash值相同&&
// key也相同
((k = p.key) == key || (key != null && key.equals(k))))
// 插入的值的key 和 数组当前位置的 key是同一个 那么直接修改里面内容
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);
// 将新的节点添加到了链表的尾部
// 判断是否满足 链表转红黑树的条件
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;
}
五、Iterator 接口
迭代器
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator。Iterator接口也是Java集合中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。
常用方法
- public E next():返回迭代的下一个元素。
- public boolean hasNext():如果仍有元素可以迭代,则返回 true。
使用案例
public class IteratorTest {
public static void main(String[] args) {
Collection<String> collection = new ArrayList();
collection.add("张三");
collection.add("李四");
collection.add("赵五");
collection.add("老六");
// 获取集合对应的迭代器,用来遍历集合中的元素的。
Iterator<String> ite = collection.iterator();
while (ite.hasNext()) {
System.out.println("元素:" + ite.next());
}
}
}
实现原理
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。
Iterator<?> iterator = collection.iterator();获取迭代器实现类对象,并且会把指针(索引)只想集合的-1索引。
iterator.hasNext();判断集合中是否含有下一个元素。
iterator.next();取出下一个元素,并把指正指向下一个元素。
在调用.net()方法之前,迭代器的索引位于集合的第一个元素之前,不指向任何元素,当第一次调用迭代器后,迭代器索引会向后移一位,指向集合的第一个元素并取出该元素。当再次调用.next()方法后,迭代器的索引指向集合的第二位并取出该元素,以此类推,直到.hasNext()返回false,表示到达了集合的末尾,终止遍历。
增强for循环
增强for循环是JDK1.5之后的一种高级for循环,内部原理其实为Iterator迭代器,所以在遍历过程中,不能对集合进行增删操作。
使用案例
public static void main(String[] args) {
Collection<String> collection = new ArrayList();
collection.add("张三");
collection.add("李四");
collection.add("赵五");
collection.add("老六");
// String:元素的数据类型 item:变量 : Collection集合or数组
for (String item : collection) {
System.out.println("元素:" + item);
}
}
六、集合常用工具类
- Collections : 提供一系静态的方法对集合元素进行排序 , 查询和修改等操作
- Arrays : 针对数组进行操作的工具类。比如说排序和查找。没有构造,方法全部是静态。
七、比较器
Comparable 和Comparator
- 1)首先这两个接口一般都是用来实现集合内的排序,comparable还可以用于两个对象大小的比较。
- 2)Comparable接口在java.lang包下面。里面有一个compareTo(T)接口方法。当一个类需要比较的时候,需自行实现Comparable接口的CompareTo方法。当调用集合排序方法的时候,就会调用对象的compareTo()方法来实现对象的比较。
- 3)Comparator接口在java.util包下面。Comparator是一个比较器接口,一般单独定义一个比较器实现该接口中的比较方法compare();在集合sort方法中传入对应的比较器实现类。一般使用匿名内部类来实现比较器。
- 4)Comparator相对于Comparable来说更加的灵活,耦合度低。