当前位置: 首页 > news >正文

实现动态数组

在现代软件开发中,动态数组(如Java的ArrayList、C#的List<T>)已成为不可或缺的基础数据结构。与静态数组相比,动态数组能够在运行时自动调整容量,为开发者提供了更高的灵活性和便利性。然而,这种便利性的背后隐藏着复杂的内存管理机制和性能优化策略。


动态数组实现的一些关键点

  • 动态管理内存

动态数组最核心的特性是能够根据实际需求自动调整内存空间。这一特性带来了两个问题:

扩容策略选择:当现有空间不足时,如何确定新容量的大小直接影响性能表现。过于保守的扩容会导致频繁的内存重分配,而过于激进的扩容则可能造成内存浪费。

缩容时机判断:为避免内存浪费,动态数组需要在适当时机缩减容量。但过于频繁的缩容可能导致"容量震荡"现象,影响整体性能。

  • 数据一致性与越界检查

动态数组的实现必须确保在任何操作过程中都能维护数据的一致性,同时提供完善的边界检查机制以防止索引越界错误。

  • 防止内存泄漏

在具有垃圾回收机制的语言中,不当的引用管理可能导致内存泄漏。动态数组的实现需要在删除元素时正确处理引用关系。


关键点实现

自动扩缩容机制

动态数组的扩缩容策略是其性能表现的关键因素。经过大量实践验证,以下策略能够在空间效率和时间效率之间达到良好平衡:

扩容策略:当元素数量达到当前容量上限时,将容量扩展为原来的2倍。这种倍增策略能够确保扩容操作的均摊时间复杂度为O(1)。

缩容策略:当元素数量减少到当前容量的1/4时,将容量缩减为原来的1/2。这种延迟缩容策略有效避免了容量震荡问题。

// 扩容判断
if (size == capacity) {resize(2 * capacity);
}// 缩容判断  
if (size == capacity / 4) {resize(capacity / 2);
}

索引边界检查

动态数组需要实现两种不同的边界检查机制:

元素访问检查:用于get、set、remove等操作,要求索引满足 0 ≤ index < size

位置插入检查:用于add操作,允许在数组末尾插入,要求索引满足 0 ≤ index ≤ size

这种区分的必要性在于插入操作需要支持在数组末尾追加元素的场景:

// 数组状态:[5, 6, 7, 8]
// 合法插入位置:| 5 | 6 | 7 | 8 |
//             0   1   2   3   4

位置4虽然超出了元素索引范围,但仍是合法的插入位置。

内存泄漏预防

在删除元素时,仅仅移动数组元素是不够的,还必须将被删除位置的引用设置为null,以确保垃圾回收器能够正确回收内存:

错误做法:可能导致内存泄漏

// 错误做法:可能导致内存泄漏
data[size - 1] = data[size];
size--;

正确做法:主动清除引用

// 正确做法:主动清除引用
E deletedValue = data[size - 1];
data[size - 1] = null;  // 关键步骤
size--;

这一细节在Java和C#等具有垃圾回收机制的语言中尤为重要。


Java实现

以下是动态数组的完整Java实现:

import java.util.Arrays;
import java.util.NoSuchElementException;/*** 动态数组的自定义实现* @param <E> 元素类型*/
public class DynamicArray<E> {// 底层存储数组private E[] data;// 当前元素数量private int size;// 默认初始容量private static final int DEFAULT_CAPACITY = 10;/*** 默认构造函数*/public DynamicArray() {this(DEFAULT_CAPACITY);}/*** 指定初始容量的构造函数* @param initialCapacity 初始容量*/@SuppressWarnings("unchecked")public DynamicArray(int initialCapacity) {if (initialCapacity < 0) {throw new IllegalArgumentException("初始容量不能为负数: " + initialCapacity);}this.data = (E[]) new Object[initialCapacity];this.size = 0;}/*** 在数组末尾添加元素* @param element 要添加的元素*/public void add(E element) {add(size, element);}/*** 在指定位置插入元素* @param index 插入位置* @param element 要插入的元素*/public void add(int index, E element) {checkPositionIndex(index);ensureCapacity();// 移动元素为新元素腾出空间System.arraycopy(data, index, data, index + 1, size - index);data[index] = element;size++;}/*** 在数组开头添加元素* @param element 要添加的元素*/public void addFirst(E element) {add(0, element);}/*** 删除指定位置的元素* @param index 要删除的位置* @return 被删除的元素*/public E remove(int index) {checkElementIndex(index);E removedElement = data[index];// 移动元素填补空隙int moveCount = size - index - 1;if (moveCount > 0) {System.arraycopy(data, index + 1, data, index, moveCount);}// 清除引用,避免内存泄漏data[--size] = null;// 检查是否需要缩容shrinkIfNecessary();return removedElement;}/*** 删除末尾元素* @return 被删除的元素*/public E removeLast() {if (isEmpty()) {throw new NoSuchElementException("数组为空");}return remove(size - 1);}/*** 删除第一个元素* @return 被删除的元素*/public E removeFirst() {if (isEmpty()) {throw new NoSuchElementException("数组为空");}return remove(0);}/*** 获取指定位置的元素* @param index 位置索引* @return 该位置的元素*/public E get(int index) {checkElementIndex(index);return data[index];}/*** 设置指定位置的元素* @param index 位置索引* @param element 新元素* @return 原来的元素*/public E set(int index, E element) {checkElementIndex(index);E oldElement = data[index];data[index] = element;return oldElement;}/*** 获取数组大小* @return 元素数量*/public int size() {return size;}/*** 检查数组是否为空* @return 是否为空*/public boolean isEmpty() {return size == 0;}/*** 确保容量足够,必要时进行扩容*/private void ensureCapacity() {if (size == data.length) {int newCapacity = Math.max(1, data.length * 2);resize(newCapacity);}}/*** 检查是否需要缩容*/private void shrinkIfNecessary() {if (data.length > DEFAULT_CAPACITY && size <= data.length / 4) {resize(data.length / 2);}}/*** 调整数组容量* @param newCapacity 新容量*/@SuppressWarnings("unchecked")private void resize(int newCapacity) {E[] newData = (E[]) new Object[newCapacity];System.arraycopy(data, 0, newData, 0, size);data = newData;}/*** 检查元素索引的有效性* @param index 索引*/private void checkElementIndex(int index) {if (index < 0 || index >= size) {throw new IndexOutOfBoundsException(String.format("索引越界: %d, 有效范围: [0, %d)", index, size));}}/*** 检查插入位置索引的有效性* @param index 索引*/private void checkPositionIndex(int index) {if (index < 0 || index > size) {throw new IndexOutOfBoundsException(String.format("插入位置越界: %d, 有效范围: [0, %d]", index, size));}}/*** 返回数组的字符串表示* @return 字符串表示*/@Overridepublic String toString() {if (size == 0) {return "[]";}StringBuilder sb = new StringBuilder();sb.append('[');for (int i = 0; i < size; i++) {sb.append(data[i]);if (i < size - 1) {sb.append(", ");}}sb.append(']');return sb.toString();}/*** 演示用法的测试方法*/public static void main(String[] args) {DynamicArray<Integer> array = new DynamicArray<>();// 添加元素for (int i = 1; i <= 5; i++) {array.add(i);}System.out.println("初始数组: " + array);// 插入元素array.add(2, 99);System.out.println("插入99后: " + array);// 删除元素array.remove(3);System.out.println("删除索引3后: " + array);// 修改元素array.set(0, 100);System.out.println("修改首元素后: " + array);System.out.println("数组大小: " + array.size());}
}

C#实现

以下是对应的C#实现:

using System;
using System.Text;/// <summary>
/// 动态数组的自定义实现
/// </summary>
/// <typeparam name="T">元素类型</typeparam>
public class DynamicArray<T>
{// 底层存储数组private T[] data;// 当前元素数量private int size;// 默认初始容量private const int DEFAULT_CAPACITY = 10;/// <summary>/// 默认构造函数/// </summary>public DynamicArray() : this(DEFAULT_CAPACITY){}/// <summary>/// 指定初始容量的构造函数/// </summary>/// <param name="initialCapacity">初始容量</param>public DynamicArray(int initialCapacity){if (initialCapacity < 0){throw new ArgumentException($"初始容量不能为负数: {initialCapacity}");}this.data = new T[initialCapacity];this.size = 0;}/// <summary>/// 在数组末尾添加元素/// </summary>/// <param name="element">要添加的元素</param>public void Add(T element){Add(size, element);}/// <summary>/// 在指定位置插入元素/// </summary>/// <param name="index">插入位置</param>/// <param name="element">要插入的元素</param>public void Add(int index, T element){CheckPositionIndex(index);EnsureCapacity();// 移动元素为新元素腾出空间Array.Copy(data, index, data, index + 1, size - index);data[index] = element;size++;}/// <summary>/// 在数组开头添加元素/// </summary>/// <param name="element">要添加的元素</param>public void AddFirst(T element){Add(0, element);}/// <summary>/// 删除指定位置的元素/// </summary>/// <param name="index">要删除的位置</param>/// <returns>被删除的元素</returns>public T RemoveAt(int index){CheckElementIndex(index);T removedElement = data[index];// 移动元素填补空隙int moveCount = size - index - 1;if (moveCount > 0){Array.Copy(data, index + 1, data, index, moveCount);}// 清除引用,避免内存泄漏data[--size] = default(T);// 检查是否需要缩容ShrinkIfNecessary();return removedElement;}/// <summary>/// 删除末尾元素/// </summary>/// <returns>被删除的元素</returns>public T RemoveLast(){if (IsEmpty){throw new InvalidOperationException("数组为空");}return RemoveAt(size - 1);}/// <summary>/// 删除第一个元素/// </summary>/// <returns>被删除的元素</returns>public T RemoveFirst(){if (IsEmpty){throw new InvalidOperationException("数组为空");}return RemoveAt(0);}/// <summary>/// 获取指定位置的元素/// </summary>/// <param name="index">位置索引</param>/// <returns>该位置的元素</returns>public T Get(int index){CheckElementIndex(index);return data[index];}/// <summary>/// 设置指定位置的元素/// </summary>/// <param name="index">位置索引</param>/// <param name="element">新元素</param>/// <returns>原来的元素</returns>public T Set(int index, T element){CheckElementIndex(index);T oldElement = data[index];data[index] = element;return oldElement;}/// <summary>/// 索引器属性/// </summary>/// <param name="index">索引</param>/// <returns>元素</returns>public T this[int index]{get { return Get(index); }set { Set(index, value); }}/// <summary>/// 获取数组大小/// </summary>public int Count => size;/// <summary>/// 检查数组是否为空/// </summary>public bool IsEmpty => size == 0;/// <summary>/// 确保容量足够,必要时进行扩容/// </summary>private void EnsureCapacity(){if (size == data.Length){int newCapacity = Math.Max(1, data.Length * 2);Resize(newCapacity);}}/// <summary>/// 检查是否需要缩容/// </summary>private void ShrinkIfNecessary(){if (data.Length > DEFAULT_CAPACITY && size <= data.Length / 4){Resize(data.Length / 2);}}/// <summary>/// 调整数组容量/// </summary>/// <param name="newCapacity">新容量</param>private void Resize(int newCapacity){T[] newData = new T[newCapacity];Array.Copy(data, 0, newData, 0, size);data = newData;}/// <summary>/// 检查元素索引的有效性/// </summary>/// <param name="index">索引</param>private void CheckElementIndex(int index){if (index < 0 || index >= size){throw new IndexOutOfRangeException($"索引越界: {index}, 有效范围: [0, {size})");}}/// <summary>/// 检查插入位置索引的有效性/// </summary>/// <param name="index">索引</param>private void CheckPositionIndex(int index){if (index < 0 || index > size){throw new IndexOutOfRangeException($"插入位置越界: {index}, 有效范围: [0, {size}]");}}/// <summary>/// 返回数组的字符串表示/// </summary>/// <returns>字符串表示</returns>public override string ToString(){if (size == 0){return "[]";}StringBuilder sb = new StringBuilder();sb.Append('[');for (int i = 0; i < size; i++){sb.Append(data[i]);if (i < size - 1){sb.Append(", ");}}sb.Append(']');return sb.ToString();}
}/// <summary>
/// 演示用法的测试类
/// </summary>
public class Program
{public static void Main(){var array = new DynamicArray<int>();// 添加元素for (int i = 1; i <= 5; i++){array.Add(i);}Console.WriteLine($"初始数组: {array}");// 插入元素array.Add(2, 99);Console.WriteLine($"插入99后: {array}");// 删除元素array.RemoveAt(3);Console.WriteLine($"删除索引3后: {array}");// 修改元素array[0] = 100;Console.WriteLine($"修改首元素后: {array}");Console.WriteLine($"数组大小: {array.Count}");}
}

http://www.dtcms.com/a/357623.html

相关文章:

  • 【NJU-OS-JYY笔记】操作系统:设计与实现
  • 【开题答辩全过程】以 基于Vue Spring Boot的教师资格证考试助力系统设计与实现为例,包含答辩的问题和答案
  • 黑客之都CSP-J模拟赛题解
  • C6.6:交流参量、电压增益、电流增益的学习
  • 企业级-搭建CICD(持续集成持续交付)实验手册
  • 【面试场景题】三阶段事务提交比两阶段事务提交的优势是什么
  • TypeScript: Symbol.iterator属性
  • 蓝蜂蓝牙模组:破解仪器仪表开发困境
  • 打通安卓、苹果后,小米澎湃OS 3又盯上了Windows
  • 【系列05】端侧AI:构建与部署高效的本地化AI模型 第4章:模型量化(Quantization)
  • AntSK知识库多格式导入技术深度解析:从文档到智能,一站式知识管理的技术奇迹
  • 第十二节 Spring 注入集合
  • 零知识证明的刑事证据困境:隐私权与侦查权的数字博弈
  • Windows 11 跳过 OOBE 的方法和步骤
  • 企业级数据库管理实战(二):数据库权限最小化原则的落地方法
  • 现状摸底:如何快速诊断企业的“数字化健康度”?
  • 嵌入式Linux驱动开发 - 蜂鸣器驱动
  • 25.8.29_NSSCTF——[BJDCTF 2020]Easy_WP
  • VeOmni 全模态训练框架技术详解
  • 深入理解Go 与 PHP 在参数传递上的核心区别
  • 变量声明方式
  • 嵌入式第四十一天(数据库)
  • 海量小文件问题综述和解决攻略(二)
  • C++ DDS框架学习
  • 超详细讲解urllib库的使用
  • 【C语言初阶】指针_野指针,指针运算
  • UCIE Specification详解(九)
  • 马斯克杀入AI编程!xAI新模型Grok Code Fast 1发布,深度评测:速度、价格与API上手指南
  • 数列寻方:等差数列和为完全平方数
  • Node.js汉字转拼音指南:pinyin-pro全解析