java中collection接口下的数据结构

一、collection接口

collection接口结构参考如图:
这里写图片描述

二、java.util.List

2.1 ArrayList

1.ArrayList是实现了基于动态数组的数据结构,连续内存存储,适合下标访问(随机访问)。

扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(linkedList需要创建大量的node对象)

ArrayList的层级结构如图所示:

这里写图片描述
2.扩容原理

jdk1.6版本
我们看一下,构造ArrayList的时候,默认的底层数组大小是10

public ArrayList() {
this(10);
}

那么有一个问题来了,底层数组的大小不够了怎么办?
答案就是扩容,普通数组将直接抛数组跃标异常。

这也就是为什么一直说ArrayList的底层是基于动态数组实现的原因,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容,扩容的代码就在ensureCapacity里面:

public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
    Object oldData[] = elementData;
    int newCapacity = (oldCapacity * 3)/2 + 1;
        if (newCapacity < minCapacity)
    newCapacity = minCapacity;
           // minCapacity is usually close to size, so this is a win:
           elementData = Arrays.copyOf(elementData, newCapacity);
}
}

扩容的算法:把元素组大小先乘以3,再除以2,最后加1。
老数据迁移:将老数组中元素复制到新的数组里面去。

jdk1.7版本之后

从jdk_1.7开始,当你进行new ArrayList();创建的是一个空数组初始容量就不是10了,而是一个空数组

 /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

是否有疑问jdk1.7初始容量为0,它是怎么进行扩容的?
在jdk_1.7中的ArrayList定义了一个常量这个值就是10

在这里插入图片描述
这个常量在进行扩容的时候,会和当前容器的最小容量进行比较,取最大的作为新容器的容量
例如当你第一次调用add进行添加元素的时候,会触发扩容

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

进入ensureCapacityInternal(size + 1);,一开始是一个空容器所以size=0传入的minCapacity=1。

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

会发现minCapacity被重新赋值为10 (DEFAULT_CAPACITY=10)
传入ensureExplicitCapacity(minCapacity);这时minCapacity=10
下面是方法体:

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    //大于原数组大小是则会触发扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

扩容的算法为:1.5倍原来数组的长度

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;// oldCapacity = 0
    int newCapacity = oldCapacity + (oldCapacity >> 1);// newCapacity = 0
    if (newCapacity - minCapacity < 0) // true
        newCapacity = minCapacity;// newCapacity = 10
    if (newCapacity - MAX_ARRAY_SIZE > 0) // false , MAX_ARRAY_SIZE =Integer.MAX_VALUE - 8;
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);//创建了长度为10的数组
}

2.2 LinkedList

基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历。

LinkedList的继承关系如图:

这里写图片描述
LinkedList还额外实现了Deque接⼝,所以 LinkedList还可以当做队列来使⽤

LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下:

这里写图片描述
linkedList 是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。

2.3 ArrayList和LinkedList性能对比

1.ArrayList读写

读取

从指定的位置(用index)检索一个对象其时间复杂度可表示为O(1)。

插入和删除

如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。

2.LinkedList

插入和删除

在插入、删除集合中头部或者尾部的元素所花费的时间都是一样的—O(1) ,但它在索引一个元素的时候比较慢,为O(n),其中n是链表的长度。

注意如果要在LinkedList指定的位置插入或删除元素,它的时间复杂也是O(n),因为需要遍历到指定位置然后进行插入。

读取

因为LinkedList中的get方法是按照顺序从列表的一端开始检查,直到另外一端。故时间复杂度为O(n),其中n是链表的长度。

3.总结

1.对于随机访问get,ArrayList绝对优于LinkedList,因为LinkedList要遍历链表

2.对于新增和删除操作add和remove,如果是顺序写入,ArrayList比较占优势,因为LinkedList要创建节点耗费资源。如果是随机操作,得分具体情况,n的值越靠近两端,linkedlist性能越高,arraylist越低。

3.ArrayList是连续的内存空间,对空间占用较大,而LinkedList不是连续的,对空间要求相对较小

在这里插入图片描述

而属于数组的list有:ArrayList,Vector。
属于链表的list有:LinkedList

2.4 集合中几种遍历方式的性能比较

Java遍历List的方法主要有:

1.for each

2.Iterator

3.loop without size

int size = list.size();

   for(int i=0;i<size;i++){
      Object o= list.get(i);

   }

4.loop with size

for(int i=0;i<list.size();i++){
      Object o= list.get(i);

   }

ForEach和Iterator都是通过迭代的方式,故其效率基本一致。

结论:
(1)对于ArrayList和LinkedList,在size小于1000时,每种方式的差距都在几ms之间,差别不大,选择哪个方式都可以。

(2)对于ArrayList,无论size是多大,差距都不大,选择哪个方式都可以。

(3)对于LinkedList,当size较大时,建议使用迭代器或for-each的方式进行遍历,否则效率会有较明显的差距。

所以,综合来看,建议使用for-each,代码简洁,性能也不差。同时也要注意使用for-each前一定要做非空判断。

其中迭代的原理如下,它会通过指针的移动来保持特定的顺序

这里写图片描述

具体的测试报告请参考:
Java遍历List四种方法的效率对比http://blog.csdn.net/dengnanhua/article/details/64692191

思考

1.List和Set的区别?

List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素

Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元
素,在逐一遍历各个元素

参考资料:

1.图解集合1:ArrayList
https://www.cnblogs.com/xrq730/p/4989451.html

2.LinkedList的增删一定比ArrayList快吗?
https://blog.csdn.net/yxh13521338301/article/details/105812841

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值