您的位置:首页 > 理论基础 > 数据结构算法

(二)Android数据结构学习之数组

2017-08-04 17:11 369 查看
需要看本系列其他文章的请转到:

(一)Android数据结构学习之链表

(二)Android数据结构学习之数组

(三)Android数据结构学习之队列

(四)Android数据结构学习之栈

前言

上一篇文章讲述了数据结构的基本知识以及链表的相关知识,地址:(一)Android数据结构学习与算法之链表,本文是系列文章的第二篇,讲的是数组

概述

数组的使用

数组在java中的实现为下:

String[] arr1 = new String[3];// 创建一个大小为3的数组
arr1[0] = "0";
arr1[1] = "1";
arr1[2] = "2";
String[] arr2 = new String[]{"0", "1", "2"};// 创建一个大小为3的数据并赋值


相信大家都明白上述代码,但是实际开发中,像这样使用数组的地方并不多,我们更多的使用的是像ArrayList这样的数组,其实用容器来形容ArrayList更贴切,在他的内部维护着一个数组,我们可以非常方便的增删查改一个ArrayList就是因为容器中已经提供了大量的方法供我们使用且不需要我们自己维护,可以说是非常棒了。

数组的特点

数组分配在一块连续的数据空间上,所以在给他分配空间时必须要确定它的大小,像我们上面两种声明数组的方式都是确定了大小。但是链表却不同,它是一块动态的空间,可以随意的改变长短,所以初始化时不需要确定大小。

优点:

- 访问、查找元素的效率很高

缺点:

- 由于空间是固定的,所以插入和删除元素效率会很低

数组容器ArrayList

ArrayList我们使用的非常多,里面有很多方法都非常的好用,我们就通过这些方法来理解数组以及数组容器,形成数据结构的思想

构造方法

transient Object[] elementData;

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


这里有一个elementData,它就是一个数组,transient修饰符是用来描述序列化相关的,这里我们不关心,这个数组在我们初始化ArrayList就被创建了,容器的用处就是来维护这个数组方便我们使用。

再来看一下另外一个构造方法,此构造方法的参数就是数组的初始化大小:

public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}


还有另外一个,此构造方法是把另外一个数组的内容复制到这个新的数组中,这里有一个Arrays.copyOf()方法,Arrays是一个专门用来操作数组的类,里面提供了大量调用底层c的方法,这类我们就不深究了,再钻进去就不好抽出来了。

public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}


add系列方法

public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}


总共2行代码,第2行就是一个简单的数组赋值操作,把添加新来的元素加入到elementData中并使数组的size加1,还记得我前面讲过的数组的大小是在初始化的时候固定的吗,如果一个大小为1的数组,那么怎么才能往它里面加第二个元素呢,所以我们就可以想到第一行的ensureCapacityInternal方法肯定使用了某种方法增大了原数组的大小,那我们追进去看一下:

private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}


这段代码先会判断elementData的内容是否为空,如果是就会给数组设置一个最小容量,这个最小容量是默认大小(10)和当前数组当前大小+1的最大值。确定这个大小之后,剩下的就是给数组扩容了,我们继续看下去:

private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}


如果数组的新容量是大于数组当前大小就增容,grow方法名字也取得相当到位

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}


这里还会修正一次容量大小,当数组元素较少,大概为个位数的时候,容量会增到到10,当元素较多,像20个以上时容量会以1.5倍增大,最后一行又用到了Arrays中的方法,同样的是一个复制数组元素的方法。

public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);  // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}


这个add方法是向指定位置插入元素,首先同样的会给数组扩容,因为想一个入组中的某一个位置插入元素,假设这个位置是数组的中间,那么就意味着从数组中间+1位置的元素到最后一个元素都要后移,但是我们知道数组是不具备这个功能的,所以还是用到了System.arraycopy方法来复制数组,这个方法其实就是Arrays.copy()的底层方法。

public static native void arraycopy(Object src,  int  srcPos,
Object dest, int destPos,
int length);


src代表数据源,srcPos代表复制的位置,dest代表要复制的结果数据源,destPos,结果数据源的复制位置,length代表复制长度。代入之后就是从elementData中,复制从index到size - index这段元素,然后替换位置从index + 1开始,长度为size - index这段元素,最后给位置为index的地方赋值。看图:



看了以上两个add方法后,addAll方法的流程其实也是相同的,第一步:扩容,第二步:复制数组。我们就不详述了

get方法

public E get(int index) {
rangeCheck(index);
return elementData(index);
}

E elementData(int index) {
return (E) elementData[index];
}


get方法就这么点。。。所以说数组查询元素很简单嘛,是不是

remove方法

public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}


这里的大多数方法和内容都多么的似曾相似,见图,不多说了:



然后,remove(Object o)流程也是相似的,有兴趣的朋友自己去稍微去看一下就能明白了。

总结

把ArrayList中的一系列方法都过了一遍之后,我们可以总结出如下内容:

1. 查询数组中的元素效率非常高。

2. 插入和删除数组中的元素需要复制数组中的元素,效率是很低的。

3. ArrayList作为一个容器动态的帮我们维护了数组的大小和内容,我们只需要关心怎么使用即可。

4. Arrays中有大量操作数组的方法

5. 文章上述的内容更多的是想让大家理解数组以及数组容器而不是源码,希望大家有所收获。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android java 数据结构
相关文章推荐