一、集合框架
1、集合框架的概念
1.1、什么叫做集合框架?
简单来说集合框架可以说是以数组为基础,根据不同的数据结构封装形成的一堆可以操作不同类型的数据的工具类的总称。
1.2、集合框架组成:接口、实现类、数据结构。
2、集合框架的结构
---------------------------------------------------------------------------------------手动分割-------------------------------------------------------------------------------
集合框架—首先要明白各个接口的特点。
比如:List接口存储数据的特点是:有序、可重复、可存null。set接口则是:有序,不可重复,可存null,使用这个集合一般也是用到它可以对数据进行去重的特点。Map:存储特点:K-V形式,K不可以重复,但是V可重复。
结合框架中主要的也就是List接口下的ArrayList与LinkedList需要特别的明白,而Vector属于远古的实现类,基本不说它的事,Set下的HashSet,Map中的HashMap。其他的,我没接触,了解程度:知道存在。
通过学习可以发现,这些接口的实现类中的方法大致都是相同,除了Map为put,他们的添加数据的方法都为add,查找都为get,删除都叫remove。当初应该是为了好记忆所以都叫同名的方法,其实他们的区别就是数据结构的不同,当然这也是为应对不同的环境所应该产生的,毕竟所有新的事物的产生,基本都是现有的不能满足才会诞生。
3、LIst接口中的ArrayList:
就ArrayList而言,其实我们也同样可以写自己的。首先脑海要做一道逻辑题。
官方大牛能写→→(大牛==ArrayList)
我们也可以写→→(我们==ArrayList)
过程→→(ArrayList==ArrayList)
结果:→→(我们==大牛)
这太对了,逻辑莫得一点问题!
我们的构造器
int[] arr;
int size;
public MyArray(){
this(10);
}
public MYArray(int n){
this.arr = new int[];
}
//封装一个MyArry工具类,这里的关键就是将创建数组的方式放在构造函数中,这样好处是一旦我们创建本类的对象,就会直接创建数组,不用在main中再写数组。
大牛的构造器:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//上面三个静态常量
transient Object[] elementData;
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*
*上面这句是官方注释,说初始化一个容量为10的数组,但是对比上面的静态常量会发现是空
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 这里的官方写的是一个Object类型的数组,并且为了没有资源的浪费也没有直接初始化数组的容量
* 是因为在add函数中尽心了动态扩容。
*/
我们的add:
public void add(int data){
this.arr[size++] = data;
}
//上面定义了一个arr的数组,和一个成员变量size,每次调用add函数size就会++,从而将数据进行保存.
大牛的add:
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//可以看出最后两句与我们写的大致相同,只不过多出许多代码,是因为官方写了动态扩容,最后再说这个机制。
其他的方法:
isEmpty()---非空判断--思想:存数据使用的size ,只需判断size是否等于0
contains()--判断元素是否包含---基本思路是循环判断数组中的数据,有就返回,没有就返回-1,但是,官方直接调用了内部方法indexOf,看是否返回-1,这一点很巧妙。
indexOf()--判断输入数据的索引位置没有就返回,没有返回-1。
size()--判断集合中数据长度,集合没有length方法。
get()--输入索引,获取那个索引的数据
remove()--通过索引移除数据
4、ArrayList的底层原理和扩容机制:
4.1底层的存储
重点就是下面这部分,可以看到底层使用Object数组声明的elementData对象进行存储。
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//上面三个静态常量
transient Object[] elementData;
private int size;
4.2底层动态扩容分析
首先:ArrayList()在JDK1.8中,底层使用Object数组声明的elementData对象进行存储,由构造函数可以看出,ArrayList的构造方法不会直接进行创建数组,但是我们可以初始化数组容量,这个容量是我们自己设定的。
//有参构造函数
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);
}
}
如果我们不进行初始化数组容量,而是使用无参数的构造方法那底层就不会直接创建数组并初始化容量,而是先让elementData数组等于一个空的静态数组,这个的意义就是为调用add函数的时候为我们再进行创建。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
扩容的整个流程如下:
此时我们不进行初始化容量,当第一次调用add函数时,这个函数会首先执行确认内部容量这个方法(ensureCapacityInternal)至于为什么要传入(size+1)是因为后面要用他作为-----要扩容的判断条件。
public boolean add(E e) {
ensureCapacityInternal( size+1 );
elementData[size++] = e;
return true;
}
在ensureCapacityInternal这个方法中会首先判断elementData这个数组对象是否等于一个ArrayList定义的那个静态常量,因为我们没有初始化容量,所以他们是相等的都为空,这时候执行比较大小的函数max(),比较我们传入的容量minCapacity和ArrayList定义的另一个静态常量DEFAULT_CAPACITY(官方设定为10)哪个大,并将这个最大值赋再给变量(minCapacity)因为我们没有设置初始化的容量,所以本应是0,但官方使用了+1的操作,所以是1,比较过后就会返回minCapacity=10
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
接着就会调用函数确认精确的容量(ensureExpandCapacity)里面会判断minCapacity与elementData数组的长度,此时状态是minCapacity为10,数组的长度为0,判断(size+1)的作用就会体现出来,size++是我们存进去的索引,判断是的时候会加一进行超前的判断size+1,这样就可以提前扩容。就比如不初始化ArrayList的容量,就会创建一个容量为10的数组,当size++,对数据的存储到9的时候此时扩容判断的条件为size+1,就满足了扩容机制,这时候就会进行grow函数的执行,走了这么多函数这才真正的要进行扩容了。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
进入grow函数后,就是互相赋值的操作,这句才是重点oldCapacity + (oldCapacity >> 1),这既是扩容后的大小,旧的数组长度,加上其本身二进制数字右移一位的长度。
右移一位的长度是多少?不知道?上例子:数字4的二进制为0100, 右移一位是0010转换为十进制就是2,所以扩容的大小就是原来 的1.5倍。
最后调用官方的数组工具类Arrays中的CopyOf函数将旧的数组和扩容的大小传入,完成数组最终扩容。
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);
}
总结:正常的扩容机制就是:add()→ ensureCapacity()→ ensureExpandCapacity()→ grow()→ copyOf()→ 最终实现扩容操作。