目录
认识数组
数组的定义
计算机科学中,数组是由一组元素组成的数据结构,每个元素都有索引来标识
根据索引计算元素的地址
数组的存储规则是连续存储元素的,所以数组中元素的地址可以通过其索引计算出来。
如:已知数组的数据起始地址BaseAddress,可以由公式:BaseAddress + (index * size) 来计算索引为index的元素地址
- index为索引,在Java、C语言中都是从0开始
- size是每个元素占用的字节,如int占4字节,double占8字节
通过索引查找元素的时间复杂度为O(1),不会受到数据规模的影响
Java中数组的结构
组成部分
- 8字节的markword
- 4字节的类型指针(指针压缩的情况下)
- 4字节的数组长度
- 数组元素大小
- 对齐填充(不足8的整数倍时补充至8的整数倍)
示例
一、需求:定义一个数组,计算其所占空间大小
java
// 定义一个数组array,计算array所占用的空间大小
int[] array = {1,2,3,4,5};
二、理论:数组array所占用的空间大小为40byte
- markword:8byte
- 类型指针:4byte
- 数组长度:4byte
- 数组元素大小:5 * 4 = 20byte (int类型元素占用4byte大小)
- 对齐填充:4byte
三、证明:通过编程来求证数组array所占用的空间大小为:40byte
- 添加依赖
XML
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
- 编码
java
package algorithm.array;
import org.openjdk.jol.info.ClassLayout;
public class ArrayStructure {
public static void main(String[] args) {
// 定义一个数组array
int[] array = {1, 2, 3, 4, 5};
// 查看数组array所占用的空间大小
System.out.println(ClassLayout.parseInstance(array).toPrintable());
}
}
- 查看测试结果

动态数组
自实现一个动态数组
编码
java
package algorithm.array;
import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Consumer;
/**
* 实现一个动态数组
*/
public class DynamicArray implements Iterable<Object> {
// 存储容器:数组
private Object[] container;
// 容器中有效元素个数
private int size;
// 默认初始化容量
private static final int DEFAULT_INIT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ARRAY = {};
/**
* 无参构造方法:将容器设置为空数组
*/
public DynamicArray() {
container = EMPTY_ARRAY;
}
/**
* 可以指定容量的构造方法
*/
public DynamicArray(int initCapacity) {
if (initCapacity < 0) {
throw new IllegalArgumentException("指定容量不合法");
}
container = new Object[initCapacity];
}
/**
* 获取元素(通过下标访问元素)
* 时间复杂度:O(1)
*/
public Object get(int index) {
if (index < 0 || index >= size) {
throw new ArrayIndexOutOfBoundsException("不合法的下标");
}
return container[index];
}
/**
* 顺序添加元素
* 非每次添加元素都会扩容,因此不考虑扩容带来的时间开销,时间复杂度:O(1)
*/
public boolean add(Object element) {
return add(size, element);
}
/**
* 随机添加元素
* 涉及元素的移动,时间复杂度:O(n)
*/
public boolean add(int index, Object element) {
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException("不合法的下标");
}
// 扩容(包含判断是否要进行扩容的逻辑)
grow(size + 1);
if (index < size) {
// 非顺序添加,需要移动元素
System.arraycopy(container, index, container, (index + 1), (size - index));
}
container[index] = element;
size++;
return true;
}
/**
* 数组扩容
*/
private void grow(int minCapacity) {
// 此时若容器为空数组,则创建默认容量大小的数组
if (container == EMPTY_ARRAY) {
container = new Object[DEFAULT_INIT_CAPACITY];
return;
}
// 若此时无需扩容
if (container.length >= minCapacity) {
return;
}
// 需要扩容
int oldCapacity = container.length;
// 扩容为原来的1.5倍大小
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 保证扩容达到最小需要的容量
newCapacity = Math.max(newCapacity, minCapacity);
// 创建新容器,并赋值
container = Arrays.copyOf(container, newCapacity);
}
/**
* 随机删除元素
* 涉及元素的移动,时间复杂度:O(n)
*/
public boolean remove(int index) {
if (index < 0 || index >= size) {
throw new ArrayIndexOutOfBoundsException("不合法的下标");
}
if (index == size - 1) {
// 删除最后一个元素
container[index] = null;// help gc
} else {
// 不是删除最后一个元素(非顺序删除),需要移动元素
System.arraycopy(container, (index + 1), container, index, (size - index - 1));
container[size - 1] = null;// help gc
}
size--;
return true;
}
/**
* 是否为空
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 元素个数
*/
public int size() {
return size;
}
/**
* 使动态数组具有增强for的能力
*/
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int p;
@Override
public boolean hasNext() {
return p < size;
}
@Override
public Object next() {
return container[p++];
}
};
}
/**
* 遍历容器中所有元素
*/
public void foreach(Consumer<Object> consumer) {
for (int i = 0; i < size; i++) {
consumer.accept(container[i]);
}
}
}
测试
- 添加元素、删除元素、遍历
java
package algorithm.array;
public class DynamicArrayTest {
public static void main(String[] args) {
// 实例化动态数组
DynamicArray dynamicArray = new DynamicArray();
// 查看动态数组
watchDynamicArray(dynamicArray);
// 添加元素
dynamicArray.add("111");
dynamicArray.add("aaa");
dynamicArray.add("222");
dynamicArray.add("bbb");
dynamicArray.add(2, "333");
dynamicArray.add(0, "000");
dynamicArray.add(6, "ccc");
// 查看动态数组
watchDynamicArray(dynamicArray);
// 删除元素
dynamicArray.remove(0);
dynamicArray.remove(5);
dynamicArray.remove(3);
// 查看动态数组
watchDynamicArray(dynamicArray);
}
/**
* 查看动态数组的情况
*/
public static void watchDynamicArray(DynamicArray dynamicArray) {
// 是否为空
System.out.println("是否为空:" + dynamicArray.isEmpty());
// 元素个数
System.out.println("元素个数:" + dynamicArray.size());
// 遍历
System.out.println("遍历方式一:");
for (Object item : dynamicArray) {
System.out.println(item);
}
System.out.println("遍历方式二:");
dynamicArray.foreach(System.out::println);
System.out.println("----------------------------------");
}
}
bash
是否为空:true
元素个数:0
遍历方式一:
遍历方式二:
----------------------------------
是否为空:false
元素个数:7
遍历方式一:
000
111
aaa
333
222
bbb
ccc
遍历方式二:
000
111
aaa
333
222
bbb
ccc
----------------------------------
是否为空:false
元素个数:4
遍历方式一:
111
aaa
333
bbb
遍历方式二:
111
aaa
333
bbb
----------------------------------
- 数组的扩容
java
package algorithm.array;
public class DynamicArrayTest {
public static void main(String[] args) {
// 实例化动态数组
DynamicArray dynamicArray = new DynamicArray();
// 添加10个元素,到达临界值
for (int i = 0; i < 10; i++) {
dynamicArray.add(i);
}
// 查看动态数组
watchDynamicArray(dynamicArray);
// 继续添加元素,数组发生扩容
dynamicArray.add("b");
dynamicArray.add(0, "a");
// 查看动态数组
watchDynamicArray(dynamicArray);
}
/**
* 查看动态数组的情况
*/
public static void watchDynamicArray(DynamicArray dynamicArray) {
// 是否为空
System.out.println("是否为空:" + dynamicArray.isEmpty());
// 元素个数
System.out.println("元素个数:" + dynamicArray.size());
// 遍历
System.out.println("遍历数组中的元素:");
dynamicArray.foreach(item -> System.out.print(item + "\t"));
System.out.println();
System.out.println("----------------------------------------------");
}
}
bash
是否为空:false
元素个数:10
遍历数组中的元素:
0 1 2 3 4 5 6 7 8 9
----------------------------------------------
是否为空:false
元素个数:12
遍历数组中的元素:
a 0 1 2 3 4 5 6 7 8 9 b
----------------------------------------------
Java中的动态数组
在Java中提供了动态数组:ArrayList
底层存储方式
java
package java.util;
// ...
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// ...
// 存储容器:使用数组存储元素
transient Object[] elementData;
// 默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
// 定义一个空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 记录数组中有效的元素个数
private int size;
// 使用无参构造方法实例化ArrayList时,存储容器为空数组
// 在第一次添加元素时,创建长度为10的数组,体现懒加载思想
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 顺序添加元素
// 这里说下,顺序添加元素的时间复杂度是O(1),并非每次添加元素都会扩容
public boolean add(E e) {
ensureCapacityInternal(size + 1);
// ...
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断是否为第一次添加元素(无参构造方法实例化时将存储容器赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY=10,通过无参构造方法实例化且第一次添加元素时,这里返回10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// ...
grow(minCapacity);
}
private void grow(int minCapacity) {
// ...
// 通过无参构造方法实例化且第一次添加元素时,minCapacity=10
newCapacity = minCapacity;
// ...
// 通过无参构造方法实例化且第一次添加元素时,创建长度为10的数组容器
elementData = Arrays.copyOf(elementData, newCapacity);
}
// ...
}
随机添加或删除元素
数组存储元素的特点是连续的,对于随机增删元素,需要进行元素的移动,时间复杂度为O(n)
java
// 随机添加元素
public void add(int index, E element) {
// ...
// 移动容器中的元素
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
// 随机删除元素
public E remove(int index) {
// ...
// 移动容器中的元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
扩容机制
ArrayList的扩容机制体现在它的grow方法中
java
private void grow(int minCapacity) {
// 旧容量
int oldCapacity = elementData.length;
// 新容量 = 旧容量的1.5倍大小
int newCapacity = oldCapacity + (oldCapacity >> 1);
// ...
// 扩容为原来的1.5倍
elementData = Arrays.copyOf(elementData, newCapacity);
}
获取元素
通过下标获取元素,性能高,时间复杂度为O(1)
java
// 通过下标获取元素
public E get(int index) {
// ...
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
判断是否包含某元素
底层使用的是equals方法
java
// 判断是否包含元素o
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
// ...
for (int i = 0; i < size; i++)
// 使用equals方法进行判断
if (o.equals(elementData[i]))
return i;
}
return -1;
}
LeetCode-88题
题解
java
class Solution {
/**
* 合并两个有序数组
* 这里使用的是双指针的方法来解题
*/
public void merge(int[] nums1, int m, int[] nums2, int n) {
if (m == 0) {
System.arraycopy(nums2, 0, nums1, 0, n);
return;
}
if (n == 0) {
return;
}
int[] help = new int[m + n];
// 定义两个指针
int p1 = 0;
int p2 = 0;
int i = 0;
while (p1 < m && p2 < n) {
help[i++] = nums1[p1] < nums2[p2] ? nums1[p1++] : nums2[p2++];
}
while (p1 < m) {
help[i++] = nums1[p1++];
}
while (p2 < n) {
help[i++] = nums2[p2++];
}
// 放入数组nums1中
System.arraycopy(help, 0, nums1, 0, (m + n));
}
}
测试
