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 codeif (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 codeint 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;elsel.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的构造方法。对于自己维护的类可以使用自然排序,而对于在不修改类的内容的情况下可以使用比较器。
// 比较器@Overridepublic int compare(User o1, User o2) {if(o2.getId() != o1.getId()){return o1.getId() - o2.getId();}return o2.getAge() - o1.getAge();}// 自然排序@Overridepublic 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 nullroot = new Entry<>(key, value, null);size = 1;modCount++;return null;}int cmp;Entry<K,V> parent;// split comparator and comparable pathsComparator<? 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;elsereturn 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;elsereturn t.setValue(value);} while (t != null);}Entry<K,V> e = new Entry<>(key, value, parent);if (cmp < 0)parent.left = e;elseparent.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
- 队列
