数组-Array
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组
具有相同类型数据的集合。
数组的特点:
1.数组是相同数据类型的集合。(int类型的数组不能放double类型)
2.数组中各元素的存储是有先后顺序的,它们在内存中按照这个顺序连续存放到一起。内存地址连续。
3. 数组获取元素的时间复杂度为 O(1)
- 实现数组列表
在 Java 的源码中,数组是一个非常常用的数据结构。
package array_list;
/**
* @author cv master
* @date 2022/12/6 9:59
*/
public interface List<E> {
boolean add(E e);
E remove(int index);
E get(int index);
}
1. 初始化 ArrayList 阶段,如果不指定大小,默认会初始化一个空的元素。这个时候是没有默认长度的。
2. 那么什么时候给初始化的长度呢?是在首次添加元素的时候,因为所有的添加元素
操作,也都是需要判断容量,以及是否扩容的。那么在 add 添加元素时统一完成这个操作。
3. 之后就是随着元素的添加,容量是会出现不足。当容量不足的是,需要进行扩容操作。同时还需要把旧数据迁移到新的数组上。另外数据的迁移算是一个比较耗时的操
作。
package array_list;
import java.util.Arrays;
/**
* @author cv master
* @date 2022/12/6 10:00
*/
public class ArrayList<E> implements List<E> {
/**
* 默认初始化空间
**/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空元素
**/
private static final Object[] DEFAULT_CAPACITY_EMPTY_ELEMENT_DATA = {};
/**
* ArrayList 元素数据缓存区
**/
transient Object[] elementData;
/**
* List集合元素数量
**/
private int size;
public ArrayList() {
this.elementData = DEFAULT_CAPACITY_EMPTY_ELEMENT_DATA;
}
@Override
public boolean add(E e) {
//确保内部容量
int minCapacity = size + 1;
if (elementData == DEFAULT_CAPACITY_EMPTY_ELEMENT_DATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断扩容操作
if (minCapacity - elementData.length > 0) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
elementData=Arrays.copyOf(elementData, newCapacity);
}
elementData[size++] = e;
return true;
}
@Override
public E remove(int index) {
E element = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;// clear to let GC do its work
return element;
}
@Override
public E get(int index) {
return (E) elementData[index];
}
@Override
public String toString() {
return "ArrayList{" +
"elementData=" + Arrays.toString(elementData) +
", size=" + size +
'}';
}
}
- 这是一份简化后的 ArrayList##add 操作
1. 判断当前容量与初始化容量,使用 Math.max 函数取最大值最为最小初始化空间。
2. 接下来是判断 minCapacity 和元素的数量,是否达到了扩容。首次创建 ArrayList 是
一定会扩容的,也就是初始化 DEFAULT_CAPACITY = 10 的容量。
3. Arrays.copyOf 实际上是创建一个新的空间数组,之后调用的 System.arraycopy 迁
移到新创建的数组上。这样后续所有的扩容操作,也就都保持统一了。
4. ArrayList 扩容完成后,就是使用 elementData[size++] = e; 添加元素操作了。
- ArrayList 的元素删除。
就是在确定出元素位置后,使用 System.arraycopy 拷贝数据
方式移动数据,把需要删除的元素位置覆盖掉。
此外它还会把已经删除的元素设置为 null 一方面让我们不会在读取到这个元素,另
外一方面也是为了 GC。
package array_list;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author cv master
* @date 2022/12/6 10:34
*/
public class ArrayListTest {
private final Logger logger = LoggerFactory.getLogger(ArrayListTest.class);
@Test
public void test_array_list() {
List<String> list = new ArrayList<>();
list.add("01");
logger.info(list.toString());
list.add("02");
list.add("03");
list.add("04");
list.add("05");
list.add("06");
list.add("07");
list.add("08");
list.add("09");
list.add("10");
list.add("11");
list.add("12");
logger.info(list.toString());
list.remove(9);
logger.info(list.toString());
System.out.println(list.get(1));
}
}
- 常见的问题
1. 数据结构中有哪些是线性表数据结构?
2. 数组的元素删除和获取,时间复杂度是多少?
3. ArrayList 中默认的初始化长度是多少?
4. ArrayList 中扩容的范围是多大一次?
5. ArrayList 是如何完成扩容的,System.arraycopy 各个入参的作用是什么?
1. 1、数组(array)2、链表 3、队列 4、栈
2.
获取
数组相对于链表等其他结构,是比较适合查找操作的,但是查找时间复杂度并不为O(1)。即便是排序好的数组,用二分查找,时间复杂度也为O(log2n)。所以正确的表述应该是数组支持随机访问,根据下标随机访问的时间复杂度为O(1)
插入和删除
插入和删除操作对于数组来说比较低效,假设数组的长度为n,现在,如果我们需要将一个数据插入到数组中的第K个位置。为了把第k个位置腾出来,我们需要将第k~n的数据往后挪,那么来分析一下其时间复杂度:
如果在数组的末尾插入元素,那就不需要移动数据了,这时的时间复杂度为 O(1)。但如果
在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以最坏时间复杂度是
O(n)。因为在每个位置插入数据的概率是一样的所以时间复杂度平均为O(n/2),省略常数1/2,即O(n)。
删除同理。
3. ArrayList的初始化容量为10
4.
- jdk1.8之后“ArrayList的默认初始化容量是10”,但是是在添加第一个元素时才真正将容量赋值为10。而在jdk1.7中默认初始化容量确实是10。
- ArrayList的扩容机制在什么时候触发?是在集合中的元素超过原来的容量时触发。
- ArrayList的扩容因子是1.5,扩容为原来的1.5倍。
5.


浙公网安备 33010602011771号