开发者社区> rooanlee> 正文

Java 集合:Collection,List,ArrayList,Vector,LinkedList(实现方式,对比)

简介: Collection 与 List Collection 是 Java 集合的一个根接口,JDK 没有它的实现类。 内部仅仅做 add(),remove(),contains(),size() 等方法的声明。 List 接口是Collection 接口的一个子类,在Collection 基础上扩
+关注继续查看

Collection 与 List


Collection 是 Java 集合的一个根接口,JDK 没有它的实现类。
内部仅仅做 add(),remove(),contains(),size() 等方法的声明。

List 接口是Collection 接口的一个子类,在Collection 基础上扩充了方法。同时可以对每个元素插入的位置进行精确的控制,它的主要实现类有 ArrayList,Vector,LinkedList

LIST接口实现的类:插入值允许为空,也允许重复。

ArrayLIst


ArrayList实现了List接口,意味着可以插入空值,也可以插入重复的值,非同步,它是 基于数组的一个实现。

部分成员变量如下:

//默认初始值
private static final int DEFAULT_CAPACITY = 10    
//存放数据的数组
transient Object[] elementData;                               

**这里可以看出,elementData提示了是基于数组的实现,DEFAULT_CAPACITY指定了默认容器为10.
下面重点理解add()方法,这将有助于理解ArrayList的应用场景和性能消耗:**

add()

public boolean add(E e){
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity){
    if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int mincapacity){
    modcount++;
    if(minapacity - 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 - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

这里可以看到,进行一个 add()方法,首先通过 ensureCapacityInternal(size + 1); 判断需不需要扩容;然后再进行 elementData[size++] = e。 插入的时间复杂度O(1)。是非常高效的。(But,这不是在固定位置插入的情况下),如果在固定位置插入,那么代码:

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++;
    }

System.arraycopy(elementData, index, elementData, index + 1, size - index); 这个可以看出,这时候就是比较低效的了。

那么网上很多说 ArrayList 不适合 增删操作非常多的操作,这是怎么回事呢?首先可以看到这句话: elementData = Arrays.copyOf(elementData, newCapacity); 需要知道的是, Arrays.copyOf 函数的内部实现是再创建一个数组,然后把旧的数组的值一个个复制到新数组中。当经常增加操作的时候,容量不够的时候,就会进行上述的扩容操作,这样性能自然就下来了。或者说,当我们在固定位置进行增删的时候,都会进行 System.arraycopy(elementData, index, elementData, index + 1, size - index); 也是非常低效的。

分析了低效出现的原因,那么我们就可以知道:如果我们需要经常进行特定位置的增删操作,那么最好还是不要用这个了,但是,如果我们基本上没有固定位置的增删操作,最好是要预估数据量的大小,然后再初始化最小容量,这样可以有效的避免扩容。如下代码:

ArrayList arrayList = new ArrayList(20);

总结:

  1. ArrayList 可以插入空值,也可以插入重复值
  2. ArrayList 是基于数组的时候,所以很多数组的特性也直接应用到了 ArrayList。
  3. ArrayList 的性能消耗主要来源于扩容和固定位置的增删。
  4. ArrayList 创建的时候 需要考虑是否要初始化最小容量,以此避免扩容带来的消耗。

Vector


上述的 ArrayList 不是线程安全的。那么 Vector 就可以看作是 ArrayList 的一个线程安全版本。由于也是实现了 List 接口,所以也是 可以插入空值,可以插入重复的值。

内部成员变量:

protected Object[] elementData;
public Vector() {
    this(10);
}

可以看到,也是基于 数组的实现,初始化也是 10 个容量。 那么,再来看看 add()方法是否和 ArrayList 相同。

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

可以看到,和 ArrayList 也是一样的,只是加了 synchronized 进行同步, 其实很多其他方法都是通过加 synchronized 来实现同步。

总结:

  1. 可以插入空值,也可以插入重复值
  2. 也是基于数组的时候,所以很多数组的特性也直接应用到了 VVector。
  3. 性能消耗也主要来源于 扩容。
  4. 创建的时候 需要考虑是否要初始化最小容量,以此避免扩容带来的消耗。
  5. 相当于 ArrayList 的线程安全版本,实现同步的方式 是通过 synchronized。

LinkedList


LinkedList 实现了 List 接口,所以LinkedList 也可以放入重复的值,也可以放入空值。LinkedList不支持同步。LinkedList 不同于ArrayList 和Vector,它是使用链表的数据结构,不再是数组。

成员变量:

transient int size = 0;    //数量    
transient Node<E> first;   //首节点    
transient Node<E> last;    //最后节点  

private static class Node<E> {    
    E item;    
    Node<E> next;    
    Node<E> prev;    
    Node(Node<E> prev, E element, Node<E> next) {    
        this.item = element;    
        this.next = next;    
        this.prev = prev;    
    }    
}     

那么,在进行 add() 方法的时候:

public boolean add(E e) {    
    linkLast(e);    
    return true;    
}   
void linkLast(E e) {    
    final Node<E> l = last;    
    final Node<E> newNode = new Node<>(l, e, null);    
    last = newNode;    
    if (l == null)    
        first = newNode;    
    else    
        l.next = newNode;    
    size++;    
    modCount++;    
}    

当进行增删的时候,只需要改变指针,并不会像数组那样出现整体数据的大规模移动,复制等消耗性能的操作。

ArrayList,Vector,LinkedList 的整体对比


实现方式:

  • ArrayList,Vector 是基于数组的实现。
  • LinkedList 是基于链表的实现。

同步问题:

  • ArrayList,LinkedList 不是线程安全的。
  • Vector 是线程安全的,实现方式是在方法中加 synchronized 进行限定。

适用场景和性能消耗:

  • ArrayList 和 Vector 由于是基于数组的实现,所以在固定位置插入,固定位置删除这方面会直接是 O(n)的时间复杂度,另外可能会出现扩容的问题,这是非常消耗性能的。
  • LinkedList 不会出现扩容的问题,所以比较适合增删的操作。但是由于不是数组的实现,所以在 定位方面必须使用 遍历的方式,这也会有 O(n)的时间复杂度

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Java实现单词计数MapReduce
本文分享实现单词计数MapReduce的方法
91 0
JAVA 实现上传图片添加水印(详细版)(上)
JAVA 实现上传图片添加水印(详细版)
168 0
Java 实现汉字按照首字母分组排序
Java 实现汉字按照首字母分组排序
113 0
Java实现图书管理系统
本篇文章是对目前Java专栏已有内容的一个总结练习,希望各位小主们在学习完面向对象的知识后,可以阅览本篇文章后,自己也动手实现一个这样的demo来加深总结应用已经学到知识并进行巩固。
130 0
java实现spring boot项目启动时,重启Windows进程
java实现spring boot项目启动时,重启Windows进程
159 0
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
如果要在某一个界面里面添加功能的话,都在一个类中,会显得代码难以阅读,而且修改起来也会很困难,所以我们将游戏主界面、登录界面、以及注册界面都单独编成一个类,每一个类都继承JFrame父类,并且在类中创建方法来来实现页面
107 0
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
ip地址的分类: 1、ipv4、ipv6 127.0.0.1:4个字节组成,0-255,42亿;30亿都在北美,亚洲就只有4亿 2011年就用尽了。
96 0
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
注意由于我们计步功能的步数要在重写方法中用到,所以不能将初始化语句写在方法体内,而是要写在成员位置。在其名字的时候也要做到“见名知意”,所以我们给它起名字为step
69 0
Java实现拼图小游戏(7)—— 作弊码和判断胜利
当我们好不容易把拼图复原了,但是一点提示也没有,完全看不出来是成功了,那么我们就需要有判断胜利的功能去弹出“成功”类的图片,以便于玩家选择是重新开始还是退出小游戏
69 0
Java实现拼图小游戏(7)——查看完整图片(键盘监听实例2)
由于在移动和图片中我们已经添加了键盘监听,也继承了键盘监听的接口,那么我们只需要在重写方法内输入我们的代码即可
41 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Java工程师必读手册
立即下载
Java应用提速(速度与激情)
立即下载
Java单元测试实战
立即下载