Collection Framework
数据扩容
数据表示同种数据类型固定长度在内存中连续存放的数据类型,与基本数据类型不同,数据在内存中有两块空间,一块空间存放实际内容,一块空间存放实际内容的引用。
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3, 4};
arr1 = arr2;
上述代码中将arrw
赋值给了arr1
,如果数组只有一块空间,那么将不能存放arr2
的内容。如果数据有两块内存空间那么可以直接将arr1
存放内容引用的空间值改为arr1
的内容引用,由于arr1
的内容没有被引用,会被垃圾回收机制回收。虽然数据一经定义就不能变更长度,但是可以通过将数组指向不同的内容空间从而让数组实现扩容。
public Object[] copy(Object[] oldValue){
// 1.5 倍扩容
int newLen = oldValue.length+(oldValue.length >>> 1);
// 实例化一个新数组
Object[] newValue = new Object[newLen];
// 值拷贝
for(int i = 0; i < oldValue.length; i++){
newValue[i] = oldValue[i];
}
// 返回
return newValue;
}
集合框架继承关系
集合框架有两个主要的顶级接口分别是collection
和map
。
Collection
的主要子接口是List
和Set
,List
表示有序可重复的集合,而Set
表示的是无序不重复的接口。
Map
不是地图的意思map全称应该是mapping
映射的意思,表示从一个值到另一个值的映射关系。
List
List
是一个有序可重复的集合。
ArrayList
public class ArrayListTest(){
public static void main(String[] args){
ArrayList list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
}
}
ArrayList
底层是数组,可以通过索引访问,实现了RandomAcces
随机访问接口。ArrayList
默认容量是10
,每次扩容后的容量是原容量的1.5
倍。
我们通过查看源代码来看看当我们创建ArrayList
对象并且调用了add
方法,内部做了哪些事情。再次之前我们需要介绍ArrayList
中一些常见属性关于Arrays
请自行查看API。
// 默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储实际的值
Object[] elementData;
// 实际存放元素的个数
private int size;
- 默认构造方法
/**
* 1. 将实际存放元素的数组初始化为一个空数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
add
方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
- 确保容量
ensureCapacityInternal
方法
/**
* @param minCapacity 添加元素需要的最小容量 在add方法中为size+1
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
- 容量计算
calculateCapacity
方法
/**
* @param elementData this.elementData
* @param minCapacity 需要的容量在add方法中为size+1
* 1. 当实际存放内容的属性elementData 为数组时
* 2. 返回需要的容量和默认容量之间的最大值
* 3. 当实际存放内容的属性elementData不为空时(已经存放元素)返回需要的容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
- 确保实际容量
ensureExplicitCapacity
方法
/**
* @param minCapacity 需要的最小容量
* 1. 当需要的最小容量大于实际存放元素的长度时候,调用扩容方法
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 扩容
grow
方法
/**
* @param minCapacity 需要的最小容量
* 1. oldCapacity 表示存放内容数组的长度
* 2. newCapacity = oldCapacity + (oldCapacity >> 1)
* 3. 当新容量小于需要的最小容量时 新容量等于需要的最小容量
* 4. 当新容量大于数组容纳长度时......
* 5. 数组扩容
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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:
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList
LinkedList
底层是双向链表
,内部使用Node
表示。Node
有三个属性,分别是prev
,item
,next
含义分别是:上一个元素的引用,当前值,下一个元素的引用。
LinkedList
的常用属性。
// 实际存放元素的个数
int size;
// 存放第一个节点
Node first;
// 存放最后一个节点
Node last;
- 无参构造方法
// 1. 创建对象
public LinkedList() {
}
add
方法
// 1.
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast
方法
/**
* @param e
* 1. 将最后一个节点存放到变量l
* 2. 创建一个对象newNode
* 2.1 参数上一个节点指向最后一个节点
* 2.2 item存放实际的值也就是e
* 2.3 指向下一个节点的引用为null
* 3. 将指向最后一个节点的引用指向新创建的节点
* 4. 判断最后一个是否为空,如果最后一个节点为空 那么就是第一次添加元素
* 4.1 如果为空 将指向第一个节点的引用指向新创建的节点
* 4.2 否则的话将最后一个节点的引用的下一个属性指向新创建的节点
* 5. 实际存放元素的个数+1
* 6. 修改次数+1
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
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;
}
}
迭代方式
- for循环
- foreach循环
- lambda表达式
- 迭代器
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
for(int i = 0; i < list.size(); i++){
print(list.get(i));
}
foreach(Object element : list){
print(element);
}
list.foreach(element -> {
print(element);
});
Iterator iterator = list.iterator();
while(iterator.hasNext()){
print(iterator.next());
}
foreach本质
foreach
本质上是迭代器。
// 编译前
for (Object element : arrayList) {
System.out.println(element);
}
// 编译后
Iterator var4 = arrayList.iterator();
while(var4.hasNext()) {
Object element = var4.next();
System.out.println(element);
}
Vector
Vector
也是一个动态数组,和ArrayList
很相似,主要区别在于:
- Vector是一个线程安全的容器
- Vector比
Collection
早实现,因此在API
上面与其他Collection
实现类不同
Vector vector = new Vector();
vector.add("a");
vector.addElement("b");
vector.addElement("b");
Enumeration elements = vector.elements();
while (elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
Stack
Stack
是一个模拟栈的集合,特点是先进后出FILO
,Stack
继承了Vector
。
Stack stack = new Stack();
stack.push("a");
stack.push("b");
stack.push("c");
System.out.println(stack.size());
System.out.println("----------------------");
while (!stack.empty()) {
System.out.println(stack.pop());
}
System.out.println(stack.size());
System.out.println("----------------------");
pop方法具体实现
push方法实现
迭代器
迭代器Iterator
是Collection
的父接口,Collection
所有实现类都必须重写迭代器接口方法。迭代器专门服务于Collection
即线性队列。
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
// 1. for 循环
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
// 2. for-each 迭代
// 2.1 for-each 本质上是迭代器操作,编译之后是迭代器
// 2.2 本质上说迭代其更快,因为for-each需要经过编译
for (Object element : arrayList) {
System.out.println(element);
}
// 编译后的结果
Iterator var4 = arrayList.iterator();
while(var4.hasNext()) {
Object element = var4.next();
System.out.println(element);
}
// 3. 迭代迭代
// 3.1 不要在迭代中多次使用next 否则会有 NoSuchElementException 异常
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Object element = iterator.next();
System.out.println(element);
}
// 4. Java8 Stream
从效率上来说使用迭代器,别其他迭代要快,因此,如果使用collection
实现类,可以使用迭代器。
泛型
泛型即类型参数化,将一个类型作为参数传递,可应用于方法、返回值、参数。
Set
Set
是一个无序不重复的集合,每一个Set
实现类底层都对应一个Map
,例TreeSet -> TreeMap
,HashSet -> HashMap
。
TreeSet
TreeSet
表示一个有序不重复的接口。
如何实现有序
TreeSet
内部使用自然排序
或比较器
来实现有序。
自然排序和比较器
使用自然排序需要在类中实现Comparable
接口重写compareTo
方法。使用比较器需要新建一个类实现Comparator
接口重写compare
方法,将比较器对象作为参数传递给TreeSet
的构造方法。对于自己维护的类可以使用自然排序,而对于在不修改类的内容的情况下可以使用比较器。
// 比较器
@Override
public int compare(User o1, User o2) {
if(o2.getId() != o1.getId()){
return o1.getId() - o2.getId();
}
return o2.getAge() - o1.getAge();
}
// 自然排序
@Override
public int compareTo(Person anotherPerson) {
if (this.id != anotherPerson.id) {
return anotherPerson.id - this.id;
} else {
return this.age > anotherPerson.age ? 1 : -1;
}
}
实现
TreeSet
使用平很二叉树中的红黑树表示有序的集合,在内部使用了Entry
来表示对应的结构。
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
具体的添加方法:
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
TreeSet
内部使用比较器或自然排序来保证有序- 使用自定义的类作为
key
需要实现比较器或者自然排序 TreeSet
不允许key
为null
HashSet
线性队列
队列是一种先进先出FIFO
的集合,与栈相反。
Queue queue = new SimpleLinkedList();
queue.offer("a1");
queue.offer("a2");
queue.offer("a3");
queue.offer("a4");
queue.offer("a5");
System.out.println(queue.size());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.size());
具体实现
Collections
Collections
是专门服务于Collection
接口,内部封装了针对Collection
的一些常用API
。
Map
map
表示key
到value
的映射。
TreeMap
HashMap
HashMap
是一个允许key
为null
,线程不安全的Map
实现类。在JDK1.7使用数组+链表表示,从JDK1.8之后使用数组+链表+红黑树表示。HashMap
的默认容量是16
,在>=LOADFATOR
的情况下会实现扩容,容量为原来的两倍。
HashTable
- HashTable线程安全
- HashTable不允许
key
为null
集合作业
- ArrayList图
- LiknedList图
- 模拟LinkedList
- 模拟Vectory pop push方法
- TreeSet
- 队列