代码已上传到GitHub,有兴趣的同学可以下载来看看:https://github.com/ylw-github/Java-CodeAnalysis-Demo
1.集合框架的介绍
下面先来看下两张图:
从上面的框架图可以看出:
- 所有集合类都位于java.util包下:Java的集合类主要由两个接口派生而出:
Collection
和Map
,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。 - 集合接口 :6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。
- 抽象类 :5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。
- 实现类 :8个实现类(实线表示),对接口的具体实现。
- Collection接口 是一组允许重复的对象。
- Set接口 继承 Collection,集合元素不重复。
- List接口 继承 Collection,允许重复,维护元素插入顺序。
- Map接口 是键-值对象,与Collection接口没有什么关系。
Set、List和Map可以看做集合的三大类:
- List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
- Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
- Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。
2. 手写List框架
List集合代表一个有序集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。
List接口继承于Collection接口,它可以定义一个允许重复的有序集合。因为List中的元素是有序的,所以我们可以通过使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
2.1 底层实现原理
2.1.2 ArrayList底层实现原理
1. Arraylist底层基于数组实现:
private Object[] elementData;
2. Arraylist底层默认数组初始化大小为10个object数组:
public ExtArraylist() throws Exception { this(10); } public ExtArraylist(int initialCapacity) throws Exception { if (initialCapacity < 0) { throw new IllegalArgumentException("初始容量不能小于0 " + initialCapacity); } elementData = new Object[initialCapacity]; }
3. 添加元素后大于当前数组的长度,则进行扩容,将数组的长度增加原来数组的一半:
// 增大数组空间 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); // 在原来容量的基础上加上 // oldCapacity/2 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 最少保证容量和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); }
2.1.3 Vector底层实现原理
Vector是线程安全的,但是性能比ArrayList要低。
ArrayList,Vector主要区别为以下几点:
1. Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比;
2. ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍;
3. Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。
2.1.4 实现
自定义List接口:
package com.ylw.jdk.list; public interface ExtList<E> { public void add(E object); public void add(int index, E object); public Object remove(int index); public boolean remove(E object); public int getSize(); public Object get(int index); }
手写ArrayList:
package com.ylw.jdk.list; import java.util.Arrays; public class ExtArrayList<E> implements ExtList<E> { // 保存ArrayList中数据的数组 private transient Object[] elementData; // ArrayList实际数量 private int size; public ExtArrayList() { // 默认初始容量为10 this(10); } public ExtArrayList(int initialCapacity) { if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } // 初始化数组容量 elementData = new Object[initialCapacity]; } // 添加方法实现 public void add(Object object) { ensureExplicitCapacity(size + 1); elementData[size++] = object; } public void add(int index, Object object) { rangeCheck(index); ensureExplicitCapacity(size + 1); System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = object; size++; } public void ensureExplicitCapacity(int minCapacity) { // 如果存入的数据,超出了默认数组初始容量 就开始实现扩容 if (size == elementData.length) { // 获取原来数组的长度 2 int oldCapacity = elementData.length; // oldCapacity >> 1 理解成 oldCapacity/2 新数组的长度是原来长度1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 3 if (newCapacity < minCapacity) { // 最小容量比新容量要小的,则采用初始容量minCapacity newCapacity = minCapacity; } // System.out.println("oldCapacity:" + oldCapacity + ",newCapacity:" // + newCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } } // public void add(Object object) { // elementData[size++] = object; // // 如果存入的数据,超出了默认数组初始容量 就开始实现扩容 // if (size == elementData.length) { // // 新的数组容量 // int newCapacity = 2 * size; // // 实现数组扩容 // Object[] newObjct = new Object[newCapacity]; // for (int i = 0; i < elementData.length; i++) { // newObjct[i] = elementData[i]; // } // elementData = newObjct; // } // } // 获取数据 public Object get(int index) { rangeCheck(index); return elementData[index]; } public Object remove(int index) { Object object = get(index); int numMoved = elementData.length - index - 1; if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved); elementData[--size] = null; return object; } public boolean remove(E object) { for (int i = 0; i < elementData.length; i++) { Object element = elementData[i]; if (element.equals(object)) { remove(i); return true; } } return false; } private void rangeCheck(int index) { if (index >= size) { throw new IndexOutOfBoundsException("数组越界啦!"); } } public int getSize() { return size; } }
3.知识补充
Java运算符:
<< : 左移运算符,num << 1,相当于num乘以2 >> : 右移运算符,num >> 1,相当于num除以2 >>> : 无符号右移,忽略符号位,空位都以0补齐
数组拷贝:
- Arrays.copyOf 功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度。
- System.arraycopy 方法:如果是数组比较大,那么使用System.arraycopy会比较有优势,因为其使用的是内存复制,省去了大量的数组寻址访问等时间复制指定源数组src到目标数组dest。复制从src的srcPos索引开始,复制的个数是length,复制到dest的索引从destPos开始。
src(源数组) : srcPos源数组要复制的起始位置;
dest(目的数组) : destPos目的数组放置的起始位置; length:复制的长度。
src and dest都必须是同类型或者可以进行转换类型的数组,有趣的是这个函数可以实现自己到自己复制,比如:
int[] fun ={0,1,2,3,4,5,6}; System.arraycopy(fun,0,fun,3,3);
结果为:{0,1,2,0,1,2,6};
实现过程:先生成一个长度为length的临时数组,将fun数组中srcPos 到srcPos+length-1之间的数据拷贝到临时数组中,再执行System.arraycopy(临时数组,0,fun,3,3).
总结