聊聊ArrayList的问题

简介: 聊聊ArrayList的问题

List这种数据结构可以说在我们的实际工作中经常会使用到,最常见的就是ArrayList和LinkedList两种实现类了,两种结构的操作看似相似,但是原理却差别巨大。关于这两者的差别,小编特意总结了一些干货和各位读者们进行分享。

 

ArrayList和LinkedList的区别?

 

通过阅读util包里面的源码可以很容易的看出两者的大致区别:

ArrayList是一种具有动态扩容特性且基于数组基础的数据结构,而LinkedList则是一种基于链表的数据结构。

 

在进行元素查找的时候适合用ArrayList进行操作,在进行元素的添加和删除的时候适合用LinkedList。由于ArrayList是采用数组作为数据结构的,因此在进行查找的时候只需要根据下标的索引进行判断即可。

 

LinkedList数据结构则是采用链表的结构进行设计的,因此在查找的时候需要进行逐一比较,所以效率会比较慢(并非是全链查询,而是采用了折半搜索的方式来进行优化)。在添加或者删除元素的时候,由于ArrayList需要进行元素的位置移动,而链表的移动和删除只需要将链表节点的头尾指针进行修改即可。

 

ArrayList的动态扩容有什么特点?

 

当我们在进行ArrayList的插入元素时候,相应的元素会被插入到动态数组里面,但是由于数组本身所能存储的数据量是有限制的,因此在插入数据的时候,需要进行相应的动态扩容,在看源码的时候,可以看到相应的代码部分:

add操作源码:

 

1   /**
 2     * Appends the specified element to the end of this list.
 3     *
 4     * @param e element to be appended to this list
 5     * @return <tt>true</tt> (as specified by {@link Collection#add})
 6     */
 7    public boolean add(E e) {
 8        ensureCapacityInternal(size + 1);  // Increments modCount!!
 9        elementData[size++] = e;
10        return true;
11    }
复制代码

 

核心扩容部分的实现:


1     private void ensureCapacityInternal(int minCapacity) {
 2        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 3            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 4        }
 5
 6        ensureExplicitCapacity(minCapacity);
 7    }
 8
 9    private void ensureExplicitCapacity(int minCapacity) {
10        modCount++;
11
12        // overflow-conscious code
13        if (minCapacity - elementData.length > 0)
14            grow(minCapacity);
15    }
复制代码

 

ArrayList默认数组的大小为10,扩容的时候采用的是采用移位运算


11 int newCapacity = oldCapacity + (oldCapacity >> 1); 
复制代码


这里也可以看出ArrayList的扩容因子为1.5。(4>>1 就相当于4除以2 即缩小了一半)。

 

什么时候会选择使用ArrayList

 

这又是一个大多数面试者都会困惑的问题。多数情况下,当遇到访问元素比插入或者是删除元素更加频繁的时候,应该使用ArrayList。另外一方面,当在某个特别的索引中,插入或者是删除元素更加频繁,或者压根就不需要访问元素的时候,不妨考虑选择LinkedList。

 

这里的主要原因是,在ArrayList中访问元素的最糟糕的时间复杂度是O(1),而在LinkedList中可能就是O(n)了。在ArrayList中增加或者删除某个元素时候,如果触发到了扩容机制,那么底层就会调用到System.arraycopy方法,如果有兴趣深入挖掘jdk源码的话,会发现这是一个本地调用方法,被native修饰,该方法会直接通过内存复制,省去了大量的数组寻址访问等时间,但是相比于LinkedList而言,在频繁的修改元素的情况下,选用LinkedList的性能会更加好一点。

 

1  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
 2        @SuppressWarnings("unchecked")
 3        T[] copy = ((Object)newType == (Object)Object[].class)
 4            ? (T[]) new Object[newLength]
 5            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
 6        //复制集合
 7        System.arraycopy(original, 0, copy, 0,
 8                         Math.min(original.length, newLength));
 9        return copy;
10    }
复制代码

 

如果读者有去学习过jvm的话,应该会对“内存碎片“这个名词比较熟悉。基于数组结构的数据在存储信息的时候都需要有连续的内存空间,所以如果当内存碎片化情况较为严重的时候,可能在使用ArrayList的时候会有OOM的异常抛出。

 

如何复制某个ArrayList到另一个ArrayList中去?

 

1.使用clone()方法,比如ArrayList newArray = oldArray.clone();

2.使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject);

3.使用Collection的copy方法。

关于list集合的拷贝问题在面试中可能还会引申出深拷贝和浅拷贝的对比,小编后期会去专门写一篇笔记做讲解。

 

请说说ArrayList、Vector和LinkedList的区别

 

相同点

这三者都是单列集合Collection下List集合的实现类,所以他们的共同点,元素有序,允许重复元素 。

不同点

1.ArrayList和Vector底层都是数组实现,这样的实现注定查找快、增删慢 。

2.Vector支持线程同步,是线程访问安全的,ArrayList线程不安全 。

3.LinkedList底层是链表结构,查找元素慢、增删元素速度快,线程不安全。

 

Collection和Collections的区别

 

Collection是单列集合的父接口,翻译过来则是指容器的意思,常见的Set接口,List接口都是属于Collection接口下边的内容。

Collections是工具类,提供了关于集合的常用操作,例如,排序、二分法查找、反转元素等。

常用的一些Collections的api:

 

1   Collections.sort(list);
2   Collections.emptyList();
3   Collections.binarySearch(list,"your key");
4   Collections.copy(list,list1);
5   System.arraycopy(list,1,list1,2,1);
复制代码

 

其实java里面已经默认提供好了很多有用的工具类给开发者们,我们除了要熟悉里面的api使用之外,还需要了解各种api的使用策略以及不同场景下的使用优势。

 

modCount参数的意义



在ArrayList设计的时候,其实还包含有了一个modCount参数,这个参数需要和expectedModCount 参数一起使用,expectedModCount参数在进行修改的时候会被modCount进行赋值操作,当多个线程同时对该集合中的某个元素进行修改之前都会进行expectedModCountmodCount的比较操作,只有当二者相同的时候才会进行修改,两者不同的时候则会抛出异常。这个机制也被称之为fail-fast机制。

 

COW容器



jdk1.5之前,由于常用的ArrayList并不具有线程安全的特性,因此在1.5之后的并发包里面出现了CopyOnWrite容器,简称为COW。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

并发包juc里面的CopyOnWriteArrayList中,核心原理主要是通过加入了ReentrantLock来保证线程安全性,从而解决了ArrayList的线程安全隐患问题。

相应的add操作源码如下

 

1    /**
 2     * Appends the specified element to the end of this list.
 3     *
 4     * @param e element to be appended to this list
 5     * @return {@code true} (as specified by {@link Collection#add})
 6     */
 7    public boolean add(E e) {
 8        final ReentrantLock lock = this.lock;
 9        lock.lock();
10        try {
11            Object[] elements = getArray();
12            int len = elements.length;
13            Object[] newElements = Arrays.copyOf(elements, len + 1);
14            newElements[len] = e;
15            setArray(newElements);
16            return true;
17        } finally {
18            lock.unlock();
19        }
20    }
复制代码

 

 

如果读者对于ArrayList的具体实现充满好奇心的话,不妨可以试着自己边看源码边动手写一套相应的数据结构,相信坚持下来一定会大有收获。

目录
相关文章
|
8天前
|
存储 Java
ArrayList
ArrayList是线程不安全的,底层使用 Object[]存储数据,可以存储任何类型的对象,包括 null 值,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。 核心属性: private static final int DEFAULT_CAPACITY = 10;//默认容量 transient Object[] 存储元素的集合 private int size; 元素个数 构造方法: public ArrayList() ; public ArrayList(int initialCapacity) ; public ArrayList(Collection<?
|
28天前
|
安全 Java API
ArrayList 全面详解
关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。本文详细解析了Java集合框架中的ArrayList,包括其定义、特点、成员变量、构造函数、API、主要方法和扩容机制等。欢迎留言交流。
|
3月前
|
存储
ArrayList的使用
ArrayList的使用
20 3
|
安全 Java
你对ArrayList了解多少?
你对ArrayList了解多少?
42 0
|
存储 安全 Java
ArrayList引发的一系列问题
ArrayList引发的一系列问题
102 0
ArrayList引发的一系列问题
|
Java 测试技术 索引
深入理解ArrayList(三)
深入理解ArrayList(三)
71 0
|
存储 设计模式 算法
ArrayList和LinkedList
介绍ArrayList和LinkedList
详解ArrayList
1.数据结构 底层使用Object类型的数组实现,线程不安全,添加元素时如果内存已满则会开辟新空间,将原数组copy过去。
96 0
|
算法
深入理解ArrayList(四)
深入理解ArrayList(四)
82 0