数组(一):ArrayList的实现和Arrays类的使用

女爷i 2022-05-16 01:47 257阅读 0赞

一、数组是否可以变长?

我们都知道,数组时定长的,初始化时一定要给定长度,由于这个长度的问题,我们在实际的开发中,会更倾向于使用容器,如ArrayList等,使用容器类时,无需考虑长度问题,因为容器已经帮我们处理了,那么数组就没有办法变长了吗?当然不是,ArrayList就是基于数组实现的,我们可以看看ArrayList是如何处理的

二、ArrayList的实现原理

ArrayList用一个Object数组作为其内部操作,并有一个成员变量size代表容器的长度

  1. <span style="color:#000000"><code> <span style="color:#000088">private</span> <span style="color:#000088">transient</span> Object[] elementData;
  2. <span style="color:#000088">private</span> <span style="color:#000088">int</span> size;</code></span>

添加数据时,调用add()方法:

  1. <span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#000088">boolean</span> <span style="color:#009900">add</span>(E e) {
  2. ensureCapacityInternal(size + <span style="color:#006666">1</span>); <span style="color:#880000">// Increments modCount!!</span>
  3. elementData[size++] = e;
  4. <span style="color:#000088">return</span> <span style="color:#000088">true</span>;
  5. }</code></span>
  6. <span style="color:#000000"><code> <span style="color:#000088">private</span> <span style="color:#000088">void</span> <span style="color:#009900">ensureCapacityInternal</span>(<span style="color:#000088">int</span> minCapacity) {
  7. modCount++;
  8. <span style="color:#880000">// 如果添加了新元素后的长度超出了原来的长度,则需要扩容</span>
  9. <span style="color:#000088">if</span> (minCapacity - elementData.length > <span style="color:#006666">0</span>)
  10. grow(minCapacity);
  11. }</code></span>

可以看到扩容的核心代码在grow方法中:

  1. <span style="color:#000000"><code> <span style="color:#000088">private</span> <span style="color:#000088">void</span> <span style="color:#009900">grow</span>(<span style="color:#000088">int</span> minCapacity) {
  2. <span style="color:#000088">int</span> oldCapacity = elementData.length;
  3. <span style="color:#880000">//扩容,生成新容量,为原来的2倍</span>
  4. <span style="color:#000088">int</span> newCapacity = oldCapacity + (oldCapacity >> <span style="color:#006666">1</span>);
  5. <span style="color:#000088">if</span> (newCapacity - minCapacity < <span style="color:#006666">0</span>)
  6. newCapacity = minCapacity;
  7. <span style="color:#000088">if</span> (newCapacity - MAX_ARRAY_SIZE > <span style="color:#006666">0</span>)
  8. newCapacity = hugeCapacity(minCapacity);
  9. <span style="color:#880000">// 把数组扩大到新长度</span>
  10. elementData = Arrays.copyOf(elementData, newCapacity);
  11. }</code></span>

elementData = Arrays.copyOf(elementData, newCapacity)

API文档的解释是:复制指定的数组,截取或用 null 或 0 填充(如有必要),以使副本具有指定的长度

也就是说,通过Arrays.copyOf,将数组elementData的长度扩大到newCapacity,扩大的部分填充由数组类型决定

例如:

  1. <span style="color:#000000"><code> int[] a = new int[]{<span style="color:#006666">1</span>,<span style="color:#006666">2</span>}<span style="color:#880000">;</span>
  2. System<span style="color:#009900">.out</span><span style="color:#009900">.println</span>(<span style="color:#009900">"扩容前长度:"</span> + a<span style="color:#009900">.length</span>)<span style="color:#880000">;</span>
  3. a = Arrays<span style="color:#009900">.copyOf</span>(a, <span style="color:#006666">3</span>)<span style="color:#880000">;</span>
  4. System<span style="color:#009900">.out</span><span style="color:#009900">.println</span>(<span style="color:#009900">"扩容后长度:"</span> + a<span style="color:#009900">.length</span>)<span style="color:#880000">;</span>
  5. System<span style="color:#009900">.out</span><span style="color:#009900">.println</span>(<span style="color:#009900">"填充的数据:"</span> + a[<span style="color:#006666">2</span>])<span style="color:#880000">;</span></code></span>

输出:
扩容前长度:2
扩容后长度:3
填充的数据:0

  我们来查看一下Arrays.copyOf源代码,看看它做了什么:

  1. <span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">static</span> <span style="color:#000088">int</span>[] <span style="color:#009900">copyOf</span>(<span style="color:#000088">int</span>[] original, <span style="color:#000088">int</span> newLength) {
  2. <span style="color:#000088">int</span>[] copy = <span style="color:#000088">new</span> <span style="color:#000088">int</span>[newLength];
  3. System.arraycopy(original, <span style="color:#006666">0</span>, copy, <span style="color:#006666">0</span>,
  4. Math.min(original.length, newLength));
  5. <span style="color:#000088">return</span> copy;
  6. }</code></span>

  Arrays.copyOf方法中,重新创建了一个新长度的数组,并且通过System.arraycopy方法把原来的数组复制到新数组中,最后返回。System.arraycopy是一个本地的系统方法,采用的是直接对内存中的数据块进行复制,是一块一块复制的(操作系统中有讲到),所以比我们平时for循环一个一个复制要快得多。

三、效率

  值得注意这些问题,什么时候会扩容?扩容会影响效率吗?如果影响要怎么避免?
  当增加元素后的size 大于 原来的size时,就需要扩容,而扩容需要复制数组,小数据量还好,而大数据量的时候肯定很影响效率!注意到,ArrayList中有两个和size相关构造函数:

  1. <span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#009900">ArrayList</span>() {
  2. <span style="color:#000088">this</span>(<span style="color:#006666">10</span>);
  3. }
  4. <span style="color:#000088">public</span> <span style="color:#009900">ArrayList</span>(<span style="color:#000088">int</span> initialCapacity) {
  5. <span style="color:#000088">super</span>();
  6. <span style="color:#000088">if</span> (initialCapacity < <span style="color:#006666">0</span>)
  7. <span style="color:#000088">throw</span> <span style="color:#000088">new</span> IllegalArgumentException(<span style="color:#009900">"Illegal Capacity: "</span>+
  8. initialCapacity);
  9. <span style="color:#000088">this</span>.elementData = <span style="color:#000088">new</span> Object[initialCapacity];
  10. }</code></span>

  第一个构造函数给定默认容量size为10,而第二个函数默认size自定义,那么我们看这两个构造函数在大数据量的情况:

  1. <span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#000088">abstract</span> <span style="color:#000088">class</span> Array1 {
  2. <span style="color:#000088">public</span> <span style="color:#000088">static</span> <span style="color:#000088">void</span> <span style="color:#009900">main</span>(String[] args) {
  3. <span style="color:#000088">long</span> b1 = System.currentTimeMillis();
  4. List<Integer> list1 = <span style="color:#000088">new</span> ArrayList<Integer>();
  5. <span style="color:#000088">for</span>(<span style="color:#000088">int</span> i =<span style="color:#006666">0</span>; i < <span style="color:#006666">10000000</span>; i++){
  6. list1.add(i);
  7. }
  8. <span style="color:#000088">long</span> e1 = System.currentTimeMillis();
  9. System.<span style="color:#000088">out</span>.println(<span style="color:#009900">"时间为 : "</span> + (e1-b1));
  10. <span style="color:#000088">long</span> b2 = System.currentTimeMillis();
  11. List<Integer> list2 = <span style="color:#000088">new</span> ArrayList<Integer>(<span style="color:#006666">10000000</span>);
  12. <span style="color:#000088">for</span>(<span style="color:#000088">int</span> i =<span style="color:#006666">0</span>; i < <span style="color:#006666">10000000</span>; i++){
  13. list2.add(i);
  14. }
  15. <span style="color:#000088">long</span> e2 = System.currentTimeMillis();
  16. System.<span style="color:#000088">out</span>.println(<span style="color:#009900">"时间为 : "</span> + (e2-b2));
  17. }
  18. }</code></span>

结果如下:
时间为 : 7878
时间为 : 283

所以:请为你的ArrayList指定初始容量!

三、Arrays类的使用

Arrays类包含用来操作数组(比如排序和搜索)的各种方法,这个类是必须熟练使用的!
该类的常用方法有:


































方法 解释
asList(T… a) 返回一个受指定数组支持的固定大小的列表
copyOf(int[] original,int newLength) 复制指定的数组,填充的数据由数组类型决定,以使副本具有指定的长度
copyOfRange(long[] original, int from, int to) 将指定数组的指定范围复制到一个新数组
equals(int[] a, int[] a2) 如果两个指定的 int 型数组彼此相等,则返回 true
fill(int[] a, int val) 将指定的 int 值分配给指定 int 型数组的每个元素
sort(int[] a) 对指定的 int 型数组按数字升序进行排序
  1. <span style="color:#000000"><code>以上只用int类型数组做为example,同理其他类型数组同样的操作
  2. </code></span>

四、copyOf的陷阱,数组的浅拷贝

当数组类型不是基本数据类型时,数组内存放的是对象的引用,因此在copyOf复制对象数组时,千万要注意,复制的对象的引用而不是对象本身!

  1. <span style="color:#000000"><code>class Person{
  2. <span style="color:#000088">int</span> age;
  3. <span style="color:#000088">public</span> <span style="color:#000088">int</span> <span style="color:#009900">getAge</span>() {
  4. <span style="color:#000088">return</span> age;
  5. }
  6. <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">setAge</span>(<span style="color:#000088">int</span> age) {
  7. <span style="color:#000088">this</span>.age = age;
  8. }
  9. }
  10. <span style="color:#000088">public</span> <span style="color:#000088">class</span> Array2 {
  11. <span style="color:#000088">public</span> <span style="color:#000088">static</span> <span style="color:#000088">void</span> <span style="color:#009900">main</span>(String[] args) {
  12. Person p = <span style="color:#000088">new</span> Person();
  13. p.setAge(<span style="color:#006666">20</span>);
  14. Person[] a = <span style="color:#000088">new</span> Person[]{p};
  15. Person[] b = Arrays.copyOf(a, a.length);
  16. a[<span style="color:#006666">0</span>].setAge(<span style="color:#006666">30</span>);
  17. System.<span style="color:#000088">out</span>.println(<span style="color:#009900">"a的年龄:"</span> + a[<span style="color:#006666">0</span>].getAge());
  18. System.<span style="color:#000088">out</span>.println(<span style="color:#009900">"b的年龄:"</span> + b[<span style="color:#006666">0</span>].getAge());
  19. }
  20. }</code></span>

输出:
a的年龄:30
b的年龄:30

可以看见,a改变其值后,连b的值都改变了,这就是浅拷贝问题

发表评论

表情:
评论列表 (有 0 条评论,257人围观)

还没有评论,来说两句吧...

相关阅读