数组
数组是内存中的一块连续的空间,里面存储的元素都是相同的,并且按顺序存储,所以支持按索引直接读取数据。根据上面我们可以得出数组的两个重要属性,“存储类型 和 大小”
数组使用时需要提前向内存申请空间大小,这在实际应用中不是很方便。
比如我们使用数组来存储学校的学生信息,因为学生的信息总是在增加,而我们申请的大小的固定的,就存在数组溢出的情况。
动态数组
为了解决这个问题,出现了动态数组,及数组的大小不是固定的。那么大小不固定,我们就需要知道开始位置、结束位置、使用的最后一个位置和存储类型。
在Java中,动态数组并没有提供查看数组容量的参数,但是在C++中提供了,我们可以作为参考
template<class_Tp,class_Alloc=__STL_DEFAULT_ALLOCATOR(_Tp)>
classvector:protected_Vector_base<_Tp,_Alloc>
{
...protected:_Tp*_M_start;//表示目前使用空间的头
_Tp*_M_finish;//表示目前使用空间的尾
_Tp*_M_end_of_storage;//表示目前可用空间的尾
...
};
使用这几个参数我们就可以知道数据的使用情况。
当前元素个数 = _M_finish - _M_start
数组容量 = _M_end_of_storage - _M_start
我们编写了这样一个代码,创建了一个存储int类型的动态数组
public static void main(String[] args) {
ArrayList list = new ArrayList<Integer>();
list.add(123);
}
默认是向数组的最后一位添加数据
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
再进入add我们发现,开始逻辑判定,先判断当前数组是否已经走到末尾,如果走到了,需要进行扩容。如果没有走到,那么直接向数组的后面添加数据。
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
再进入我们来到这里,这里的minCapacity是当前数组的容量+1。
graph TD
如果数组不是空 --> 数组扩容扩大0.5倍 --> 拷贝原来的数据到新的数组
如果数组不为空 --> 将现有申请的长度与DEFAULT_CAPACITY进行比较申请空间 -->返回新数组
DEFAULT_CAPACITY = 10
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
那么为什么要这么扩容?
在C++ 中为两倍扩容大小,这就存在一个问题无法利用已经释放的内存空间。
我们假设已经进行了n次扩容,那么当前的空间就为我们忽略这个常数C那么扩容的空间就为
之前释放掉的大小就为
假设扩容的倍率为,首次分配的空间为1,则第一次扩容分配的空间为,第二次扩容分配的空间就,如果希望第二次扩容能用上之前申请过的空间,那么
解方程得
如果希望第二次扩容能用上第一次的空间,那么需要扩容倍率小于1.618。
如果我们需要缩容,那么该怎么做
在Java的ArrayList中,没有自动缩容,但是提供了一个trimToSize()的方法可以让使用者手动缩容;如果要设计自动缩容的话,可以简单设计成size减少到到capacity/4的时候,将容量缩减成原来的一半。
复杂度震荡:
如果我们在扩容时设计的是容量是2倍,缩容设计的是旧容量的一半,此时如果我们在容量满的时候插入一个数据,会导致扩容,然后在删除一个元素,又会发生缩容,这样的复杂度就是O(n), 就会出现复杂度震荡。