一、动态数组
这个图能够说明一些问题,因为数组在堆内存中申请的空间是连续的,所以我们可以根据索引很快的查询到数据。数组的容量是固定的,所以必须实现数组的动态扩容。
二、动态数组的设计
动态数组里包含2个重要的成员变量:size表示元素的数量,elements就是一个定长的数组,所以ArrayList是由数组实现的。我记得有次面试被问到ArrayList里为什么不能放int,只能放Integer,当时被问懵逼了,现在看ArrayList源码就明白了,因为ArrayList设计的是泛型。
接口的设计:
其实主要就是我们最熟悉的:元素添加、删除、修改、查询。
构造函数
/**
* @param capaticy 用户自定义的容量
*/
public ArrayList(int capaticy){
capaticy = (capaticy<DEFAULT_CAPACITY)? DEFAULT_CAPACITY : capaticy;
elements = (E[])new Object[capaticy];
}
/**
* 默认初始化为10的数组
*/
public ArrayList(){
this(DEFAULT_CAPACITY);
}
用户可以指定arrayList的大小,那么我们需要给他创建个initialCapacity大小的数组。
当用户传负数,我这里会给他初始化一个容量为10的数组。
添加元素
第一种情况是向数组的末尾添加元素,第二种情况是向数组的某个指定位置添加元素,如图我们向index=2的位置添加元素时,那么从索引为2开始的所有元素都要向后面移动一位。
-
/** * 在index位置插入一个元素 index = 3 element=88 size=7 * @param index 11 22 33 44 55 66 77 ->11 22 33 88 44 55 66 77 * @param element */ public void add(int index, E element) { rangeCheck(index); ensureCapacity(size+1); //保证数组的容量是size+1 for(int i=size;i>index;i--){ elements[i] = elements[i-1]; } elements[index] = element; size++; }
这里ensureCapacity(size+1)的含义是保证我们数组的容量是size+1,举个例子:假设数组的容量是10,此时的size=10;说明数组中元素存满了,当我们再添加一个元素时,就需要保证数组的容量是size+1
数组扩容:
/**
* 保证要有capacity的容量
* @param capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length; //原数组的容量
if(oldCapacity>capacity){
return;
}
//扩容为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[])new Object[newCapacity];
for(int i=0;i<size;i++){
newElements[i] = elements[i];
}
elements = newElements;
}
- 由于数组elementData最大的容量只有10, 所以当数组存满元素时, 就需要对数组进行扩容
- 因为数组是无法动态扩容的, 所以需要创建一个新的数组,这个数组的容量要比之前数组的容量大
- 然后在将原数组中的元素存放到新数组中, 这样就实现了数组的扩容
clear方法和remove方法的细节:
在clear方法中不仅需要将size改为0,同事需要将对象的值都改为null,而在remove方法中需要将index位置的元素也置为空,这样jvm才会去回收数组中的对象。
ArrayList的时间复杂度分析:
其实就是分析增删查改的时间复杂度,我们经常说这个方法的时间复杂度是O(n),这里的n指的不是参数,而是数据规模,那么arrayList的数据规模是什么呢?毫无疑问是size的大小。
get()和set()时间复杂度都是O(1),很好理解,它们跟size无关。那么另外一个需要解释的问题就是get(1)和get(1000)时间复杂度为什么是一样的呢?原因就是数组里的地址是连续的,假设数组里放的是整数,那么一个元素就占4个字节,编译器就很容易计算出index=100位置的地址:数组的首地址+4*index,所以get(1)和get(100)所需的时间是一样的。
add()和remove()方法是类似的,分析add()就可以了。
假设我们往数组的末尾添加元素,那么add()最好的时间复杂度就是O(1),这里需要注意,在大多数情况下是O(1),在最坏的情况下是O(n),如果数组满了,那么数组需要扩容,所以此时是O(n)。
如果我们往index=0的位置添加元素,那么add()的时间复杂度就是O(size),因为所有的元素都要往前移动,所以add()最坏时间复杂度就是O(n),同理平均时间复杂度也是O(n)。
最后附上ArrayList完整的代码:
package 动态数组;
public class ArrayList<E> {
private int size; //元素的数量
private E[] elements; //数组
private static final int DEFAULT_CAPACITY = 10; //数组默认容量
private static final int ELEMENT_NOT_FOUND = -1;
/**
* @param capaticy 用户自定义的容量
*/
public ArrayList(int capaticy){
capaticy = (capaticy<DEFAULT_CAPACITY)? DEFAULT_CAPACITY : capaticy;
elements = (E[])new Object[capaticy];
}
/**
* 默认初始化为10的数组
*/
public ArrayList(){
this(DEFAULT_CAPACITY);
}
/**
* 清除所有元素
*/
public void clear() {
for(int i=0;i<size;i++){
elements[i] = null;
}
size = 0;
}
/**
* 元素的数量
* @return
*/
public int size() {
return size;
}
/**
* 是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 是否包含某个元素
* @param element
* @return
*/
public boolean contains(E element) {
return indexOf(element)!=ELEMENT_NOT_FOUND;
}
/**
* 添加元素到尾部
* @param element
*/
public void add(E element) {
add(size,element);
}
/**
* 获取index位置的元素
* @param index
* @return
*/
public E get(int index) {
rangeCheck(index);
return elements[index];
}
/**
* 设置index位置的元素
* @param index
* @param element
* @return 原来的元素ֵ
*/
public E set(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
/**
* 在index位置插入一个元素 index = 3 element=88 size=7
* @param index 11 22 33 44 55 66 77 ->11 22 33 88 44 55 66 77
* @param element
*/
public void add(int index, E element) {
rangeCheck(index);
ensureCapacity(size+1); //保证数组的容量是size+1
for(int i=size;i>index;i--){
elements[i] = elements[i-1];
}
elements[index] = element;
size++;
}
/**
*
* 删除index位置的元素 index = 3 size=7
* @param index 11 22 33 44 55 66 77
* @return
*/
public E remove(int index) {
rangeCheck(index); //检查索引
E element = elements[index];
for(int i=size+1;i<size;i++){
elements[i-1] = elements[i];
}
size--;
elements[size] =null; //处理索引为7的元素,将其变为null
return element;
}
/**
* 查看元素的索引
* @param element
* @return
*/
public int indexOf(E element) {
if(element == null){
for(int i=0;i<size;i++){
if(elements[i] == null){
return i;
}
}
}else{
for(int i=0;i<size;i++){
if(element.equals(elements[i])){
return i;
}
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 保证要有capacity的容量
* @param capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length; //原数组的容量
if(oldCapacity>capacity){
return;
}
//扩容为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[])new Object[newCapacity];
for(int i=0;i<size;i++){
newElements[i] = elements[i];
}
elements = newElements;
}
private void rangeCheck(int index) {
if(index<0 || index > size){
throw new IndexOutOfBoundsException("数组越界了");
}
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
}