标签:address 容器 null 逻辑 void 插入 无法 最大的 动态扩容
数组是一种线性的数据结构.它同一组连续的内存空间,来存储一组具有相同类型的数据。
简单说明几点:
(1).线性表:就是数据排成像一条线一样的结构。每个线性表的数据最多只有前和后两个方向。除了数组,链表,队列,栈等也是线性表结构。
对立的是非线性表,比如二叉树,堆,图等之所以被称为非线性,是因为,在非线性表中,数据之间并不简单的前后关系。
(2).数组是连续的内存空间和相同的数据类型。正是因为这样数据的随机访问的速度很快,有利就有弊,比如在数组中删除和插入一个数据,为了保证连续性,就需要大量的数据搬用 的工作。
我们拿一个长度为 10 的 int 类型的数组 int[] a = new int[10] 来举例。在我画的这个图 中,计算机给数组 a[10],分配了一块连续内存空间 1000~1039,其中,内存块的首地址 为 base_address = 1000。
我们知道,计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。 当计算机需要随机访问数组中的某个元素时,它会首先通过啊a[i]_address=base-address+i * data_type_size 的寻址公式,计算出该元素 存储的内存地址.
其中 data_type_size 表示数组中每个元素的大小。我们举的这个例子里,数组中存储的是 int 类型数据,所以 data_type_size 就为 4 个字节。这个公式非常简单,我就不多做解释 了。
,数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)
假设数组长度是n ,现在需要将一个数据插入到数组的第K个位置,为了吧k位置挪出来,给新来的数据,我们需要吧k—n 这部分的元素顺序往后移动一位,那么时间的复杂度是多少呢?
下面我们来分析一下:
如果在数组的末尾插入元素,那就不需要移动元素了,那么时间的复杂度为O(1),如果在数组的开头插入元素,那么所有的数据需要一次向后移动一位,所以最坏的时间复杂度是O(N),因为我们在每个位置插入元素的概率是一样的,所以平均时间复杂度(1+2+。。。。+n)/n=O(n)
如果数组中的数据是有序的,我们在某一个位置插入一个新元素时,就必须搬移k之后的数据。但是数组中的数据是无序的,没有规律,数据只是存储数据的集合。在这种情况下,如果要将元素插入到K位置,为了避免大规模的数据移动,之间将K位置的数据搬移到数组元素的最后,把新的元素之间放到第K个位置。删除的操作,为了内存的连续性,也需要移动数据,和插入类似,如果删除数组末尾的数据,则最好情况时间复杂度为 O(1);如果删除开头的 数据,则最坏情况时间复杂度为 O(n);平均情况时间复杂度也为 O(n)。
实际上,在某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次 删除操作集中在一起执行,删除的效率是不是会提高很多呢?
我们继续来看例子。数组 a[10] 中存储了 8 个元素:a,b,c,d,e,f,g,h。现在,我 们要依次删除 a,b,c 三个元素,为了避免 d,e,f,g,h 这几个数据会被搬移三次,我们可以先记录下已经删除的数据。 每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存 储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。
(1).在Java中提供的容器类,ArrayList最大的优点就是可以将很多数组操作的细节封装起来,便于调用,另一个优点是支持动态扩容。
(2).数组本身在定义的时候预先指定了大小,因为需要分配连续的内存空间,如果我们分配了大小为10的数组,当第11个元素需要存储到数组当中时,我们需要重新分配一块更大的空间,将原来的数据复制过去,然后将新的数据插入。
(3).使用ArrayList,我们完全不需要关心底层的扩容逻辑,ArrayList 已经帮我们实现好了,存储空间不够的时候,会将空间自动扩容1.5倍的大小。
(4).ArrayList无法存储基本的数据类型,(8种基本数据类型),而是引用类型,所以基本数据类型,可以选用数组.
public class ArrayTest { private Object[] elementData; //定义一个空的数组 private int size; //数组的大小 //初始话数组的大小 public ArrayTest() { elementData = new Object[10]; } /** * 增加元素 */ public boolean add(Object obj) { ensureCapacityInternal(size + 1); elementData[size++] = obj; return true; } //主要的判断数组是否需要扩容 private void ensureCapacityInternal(int minCapaCity) { if (minCapaCity - elementData.length > 0) { grow(minCapaCity); } } //数组扩容的操作 private void grow(int minCapaCity) { int oldcapaCity = elementData.length; int newCapaCity = oldcapaCity + oldcapaCity >> 1; if (newCapaCity - minCapaCity < 0) { newCapaCity = minCapaCity; } if (newCapaCity - Integer.MAX_VALUE > 0) { newCapaCity = Integer.MAX_VALUE; } elementData = Arrays.copyOf(elementData, newCapaCity); } /** * 删除数组中的元素,通过索引 */ public Object remove(int index) { rangCheck(index); Object oldValue = elementData[index]; int moveNum = size - index - 1; //数组移动的次数 if (moveNum > 0) { System.arraycopy(elementData, index + 1, elementData, index, moveNum); } elementData[--size] = null; return oldValue; } //查询指定索引下数组的元素 public Object get(int index) { rangCheck(index); return elementData[index]; } private void rangCheck(int index) { if (index < 0 && index > size) { throw new IndexOutOfBoundsException("index:" + index + "size:" + size); } } public static void main(String[] args) { ArrayTest arrayTest = new ArrayTest(); for (int i = 0; i < 10; i++) { arrayTest.add("第" + i + "个元素"); } arrayTest.remove(3); for (int i = 0; i < 9; i++) { System.out.println(arrayTest.get(i)); } } }
数组用一块连续的内存空 间,来存储相同类型的一组数据,最大的特点就是支持随机访问,但插入、删除操作也因此 变得比较低效,平均情况时间复杂度为 O(n)。在平时的业务开发中,我们可以直接使用编 程语言提供的容器类,但是,如果是特别底层的开发,直接使用数组可能会更合适。
标签:address 容器 null 逻辑 void 插入 无法 最大的 动态扩容
原文地址:https://www.cnblogs.com/xiaofuzi123456/p/11651331.html