动态数组
动态数组是一个能够在程序运行时根据需要动态扩展大小的数组,它通常用于存储和管理元素数量不确定的数据。与静态数组不同,静态数组的大小一旦定义就不可改变,而动态数组则可以根据需要增加或减少存储空间。Java中没有内建的"动态数组"类型,但可以通过ArrayList类实现动态数组的功能,或者自己手动实现一个简单的动态数组。
本文将介绍如何在Java中实现动态数组,分析其基本原理,并提供相关的代码示例。
1. 动态数组的基本原理
动态数组的核心思想是,当数组满时,会创建一个更大的数组,将原数组中的元素复制到新数组中。通过这种方式,动态数组的大小可以不断增长。
主要特点
- 动态调整大小:当数组的大小不够时,数组会自动扩大,通常是原大小的2倍。
- 顺序存储:与静态数组类似,动态数组使用连续的内存空间来存储数据,因此可以实现常数时间的随机访问。
- 增加/删除操作:虽然动态数组可以灵活扩展,但增加或删除元素时可能需要移动其他元素,这会增加一定的时间复杂度。
2. 动态数组的实现
Java 提供了 ArrayList 类来简化动态数组的操作。ArrayList 底层就是通过动态数组实现的,开发者可以直接使用这个类进行增删查改操作。
2.1 使用 ArrayList 实现动态数组
java
import java.util.ArrayList;
public class DynamicArrayExample {
public static void main(String[] args) {
// 创建一个空的动态数组
ArrayList<Integer> dynamicArray = new ArrayList<>();
// 向动态数组添加元素
dynamicArray.add(10);
dynamicArray.add(20);
dynamicArray.add(30);
dynamicArray.add(40);
// 输出数组内容
System.out.println("ArrayList: " + dynamicArray);
// 获取数组中的元素
int element = dynamicArray.get(2);
System.out.println("Element at index 2: " + element);
// 修改数组中的元素
dynamicArray.set(1, 25);
System.out.println("Updated ArrayList: " + dynamicArray);
// 删除数组中的元素
dynamicArray.remove(3); // 删除索引为3的元素
System.out.println("ArrayList after removal: " + dynamicArray);
// 获取动态数组的大小
System.out.println("Size of ArrayList: " + dynamicArray.size());
}
}
2.2 解释
- ArrayList 是一个动态数组实现,它的大小会随着元素的增加而自动增长。
- add(E e) 方法用于向数组中添加元素。
- get(int index) 用于获取指定索引的元素。
- set(int index, E element) 用于修改指定索引的元素。
- remove(int index) 用于删除指定索引的元素。
- size() 返回动态数组当前包含的元素数量。
通过 ArrayList 类,我们可以轻松实现动态数组的功能,避免手动扩展数组。
3. 自定义实现一个动态数组
如果你想更深入理解动态数组的实现原理,或者想自己实现一个简单的动态数组,我们可以手动写一个动态数组类。这里提供一个简单的实现示例:
3.1 自定义动态数组类
java
package com.shenlink.list;
import java.util.Objects;
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;
public ArrayList(int capacity) {
capacity = capacity < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capacity;
elements = (E[]) new Object[capacity];
}
public ArrayList() {
this(DEFAULT_CAPACITY);
}
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
public void add(E element) {
add(size, element);
}
public E get(int index) {
rangeCheck(index);
return elements[index];
}
public E set(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = element;
size++;
}
public E remove(int index) {
E old = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
elements[--size] = null;
trim();
return old;
}
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 (Objects.equals(element, elements[i])) {
return i;
}
}
}
return ELEMENT_NOT_FOUND;
}
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity) return;
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
}
public void trim() {
int oldCapacity = elements.length;
int newCapacity = oldCapacity >> 1;
if (size > newCapacity || oldCapacity < DEFAULT_CAPACITY) return;
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
}
private void outOfBounds(int index) {
throw new IndexOutOfBoundsException("index=" + index + ", size=" + size);
}
private void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
private void rangeCheckForAdd(int index) {
if (index < 0 || index > size) {
outOfBounds(index);
}
}
}
4. 性能分析
4.1 添加元素的时间复杂度
- 当数组有足够空间时,add() 操作的时间复杂度是 O(1)。
- 当数组已满,需要扩展数组时,expandCapacity() 方法需要复制所有的元素,这会使得此操作的时间复杂度是 O(n),其中 n 是数组中当前元素的数量。
4.2 获取和修改元素的时间复杂度
- 获取和修改元素的操作都是 O(1),因为它们只涉及直接索引访问。
4.3 删除元素的时间复杂度
- 删除元素通常需要移动数组中的其他元素,因此删除操作的时间复杂度是 O(n),其中 n 是删除元素之后数组的大小。
5. 总结
动态数组是一种非常实用的数据结构,它能够自动调整大小,从而有效地处理大小不固定的集合。Java 中的 ArrayList 类已经封装了这一实现,我们可以直接使用它来进行动态数组的操作。如果需要更多控制,也可以手动实现一个简单的动态数组,理解其底层的实现机制。
动态数组具有常数时间的访问和修改元素的能力,但在扩展和删除操作时可能会有性能瓶颈。因此,选择合适的数据结构需要根据具体的应用场景来决定。