目录
1. 数组
1.1 简介
数组是一种基础且重要的数据结构,对于每一种编程语言都有着不可或缺的作用,它用于存储一系列按顺序排列的同类型数据。
int[] a = new int[5]{1,2,3,4,5};
1.2 特点
特点:
- 数组是线性表的一种实现方式,它使用连续的内存空间来存放数据。
- 数组中的元素可以通过索引(或称为下标)进行访问,这些索引通常从0开始计数,并按照元素的存放顺序依次递增。
- 在数组中,每个元素所占用的存储空间大小是固定的,并且所有的元素类型都是一致的。
1.3 优缺点
优点:
- 访问高效:由于数组采用连续内存存储,可以直接通过索引快速定位到任意一个元素,因此访问速度非常快。
- 操作简单:对于基本的读取操作来说,数组的操作非常简单明了。
缺点:
- 动态操作效率低:数组的大小在初始化时需要固定,因此在不知道数据量的情况下,可能需要频繁地进行数组扩容或缩容操作,这会导致额外的计算和数据迁移开销。
- 插入和删除操作效率低:与访问操作相比,如果在数组中间位置插入或删除元素,需要移动大量元素以维护连续性,因此效率较低。
2. 动态数组 -- ArrayList
2.1 简介
由于数组的大小固定的局限性,很多高级语言都会能够动态增长和缩减的数据结构。
2.2 与数组区别
1. 大小:
- 数组(Array)的大小是固定的,一旦创建后就无法改变。
- ArrayList 的大小是动态的,可以根据需要增加或减少元素。
2. 类型:
- 数组可以包含基本类型和对象类型。
- ArrayList 只能包含对象类型,对于基本类型,需要使用自动装箱转化为包装类后才能存储。
3. 使用场景:
- 如果数据量大小固定不变,可以使用数组,因为它有较小的内存开销。
- 如果数据量大小需要动态变化,则应使用 ArrayList,因为它提供了更多的方法和灵活性来管理集合数据。
2.3 ArrayList细节
2.3.1 接口细节
ArrayList继承了 AbstractList 类,支持迭代器Iterator。
ArrayList实现了 RandomAccess 接口,它可以快速地通过索引访问元素,这使得它在随机访问时非常高效。
ArrayList实现了 List 接口,可进行列表的相关操作。
ArrayList实现了 Cloneable 接口,可实现克隆。
ArrayList实现了 Serializable 接口,可支持序列化,能通过序列化去传输。
2.3.2 扩容细节
ArrayList底层是通过数组实现的,要实现动态添加功能,无法避免的就是空间不够,则需要扩容。但如果每次添加数据时都进行扩容,会浪费很多性能。接下来就追溯Java源码,来聊聊ArrayList是如何扩容的。
1. add()源码
在添加前会判断是否能将数据放入数组,如果不能,将执行grow()进行扩容。
2. grow()源码
步骤有三:
- 确定所需大小(size + 1)
- 生成新的数组,大小为 老数组大小 + 老数组的一半
- 将老数组拷贝到刚刚新生成的数组中,返回数组
扩容规则:
新数组大小 = 老数组大小 + 老数组大小的一半
这种扩容方法在c++的vector的也有体现,在vector中称这种方法为半倍扩容法。
注:由于每次扩容都会拷贝一次数组,如果数据量很大,就将面临频繁扩容的问题。这样添加数据时会有很多时间都在拷贝数组,这样会很占内存,效率十分低下。所以当数据量很大,就需要通过指定初始容量的构造方法来避免频繁扩容。
3 总结
数组是计算机科学中一种基本的、重要的数据结构,用于存储一系列按顺序排列且类型相同的数据元素。
定义和特性:
- 数组由固定数量的内存位置组成,每个位置可以存储一个特定类型的值(例如整数、浮点数或字符)。
- 这些内存位置通过连续的索引进行访问,通常从0开始编号。
- 数组在内存中是连续分配的,这使得元素可以通过其索引快速访问。
优势:
- 高效的随机访问:可以直接通过索引在常数时间内访问任意元素。
- 内存效率:由于数组在内存中连续存储,它们通常比链表等其他数据结构更节省空间。
高级语言中的实现:
- 在像Java这样的高级语言中,数组被赋予了额外的属性和方法,如自动内存管理和各种便捷的操作方法。
- Java中的ArrayList是一种类似于数组的数据结构,它允许动态调整大小并提供了更多的功能。
最后,需要注意的是,ArrayList不是线程安全的,因此在多线程环境下使用时需要额外的同步措施。如果需要在多线程环境中使用,可以考虑使用Vector或者Collections工具类中的synchronizedList方法来获取一个同步的List。