List集合了解一下

导航

前言

首先声明本文使用的是jdk1.8

List 常见实现类

根据上篇Collection的文章我们了解了List的几个常见实现类。

image
  • ArrayList:
    底层数据结构是数组。线程不安全
  • LinkedList:
    底层数据结构是链表。线程不安全
  • Vector:
    底层数据结构是数组。线程安全

ArrayList

ArrayList的实现和继承结构

image

ArrayList类特点

image
  • 可动态的改变数组的大小(数组的拷贝)。
  • 和Vector类函数实现相似,区别只是线程不安全。

另外Vector有许多设计上的不足,不建议使用。当多条线程访问ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。

ArrayList解析

ArrayList属性解析

其实注释写的都很清晰

构造函数

前两种方式十分的简单,需要注意的就是第三种:

在通过一个Collection来构造ArrayList的时候,其toArray函数可能不能成功返回一个Object[]类型的数组。需要我们再把elementData数组中的元素拷贝到Object[size]类型的数组中并重新赋值给elementData。

add()

函数处理步骤:

  • 检查是否需要扩容
  • 插入元素

元素在插入到集合前需要先对ArrayList进行判断是否需要扩容。

image

ensureCapacityInternal函数来确定真正的数组最小容量minCapacity

拿(当前元素个数+1)来进行判断。如果当前容器没有进行初始化。那么取minCapacity和DEFAULT_CAPACITY两个元素中较大的值作为数组的最小容量(也就是需求容量在10以下我们也把数组初始化成了容量为10,防止频繁的扩容操作)。

ensureExplicitCapacity函数记录容器的更改状态并判断容器是否真的需要扩容

modCount记录容器被修改过的次数。当通过ensureCapacityInternal函数确定的容器最小容量minCapacity大于数组当前大小时。调用grow函数来对数组进行扩容

image
  • 如果扩容1.5倍后还小于需要的最小容量,干脆让数组容量等于minCapacity。
  • 如果数组要求的最小容量已经大于数组所被允许的最大容量限制。让其容量最大只能等于Integer.MAX_VALUE。

image
/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 根据释义我们可以知道一些虚拟机在数组头会保存一些头元素。
  • 请求的数组过大的话会出现OOM异常。可能会超出部分VM的限制

image
  • 源码叙述的很清晰。接着上面的看,对elementData进行拷贝。将该数组元素拷贝到一个也是该数组类型的且容量为newCapacity的数组中。

总结:

  • 首先去检查一下数组的容量是否足够
  • 足够:直接添加
  • 不足够:扩容
    • 扩容到原来的1.5倍
    • 第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。

add(int index, E element)

image

函数处理步骤:

  • 要插入元素的位置判断。是否小于0或超出当前数组元素
  • 扩容判断
  • 数组拷贝

由于ensureCapacityInternal函数的保障,所以这里数组元素移位即可。


image

可以发现在ArrayList中由于底层实现是由数组来作为容器。所以与扩容相关的add函数底层其实都是System类的arraycopy()函数来实现的

而其又是native函数,底层最终调用了C的memmove() 函数。该函数的相比于我们自定义的数组拷贝实现要性能高得多。更有可能得到细致的优化,应该尽可能去使用它。

参考R大回答:https://www.zhihu.com/question/53749473

get(int index)

image

字面意思

set(int index, E element)

image

字面意思

remove(int index)

正如注释中所述。

函数处理步骤:

  • index索引边界判断
  • modCount变量的维护
  • 取出数组index处元素用于结果返回
  • 数组元素从index+1处开始依次往前移一位。

其实看完remove函数之后,我比较疑惑为什么jdk开发人员在设计remove函数的时候不和add函数一样动态的维护数组的大小呢?因为我自己在学习数据结构的时候,设计我的ArrayList集合类的时候其add函数和remove函数都根据数组现有元素数量和数组总容量做了一个系数比,根据系数比动态的维护了其容量。

后来仔细一想,这么设计也确实很合理。ArrayList作为List接口常用的实现类之一,既然其容量确实到达过一个峰值。即便其后来被降下来了,也很难保证其后续不会再升回去。因为既然原来能到达,那么后续也肯定可以到达。所以既然这样就需要函数体中频繁的判断,频繁的扩容缩容性能上无疑会更大的放大了ArrayList在增删上的劣势。不可取

trimToSize()

image

remove函数由于性能影响在删除元素后既然不能缩容,Java开发人员提供了该函数供开发人员根据自己的需要来对数组缩容。底层依旧调用的是System类的arraycopy函数。

我设计的ArrayList

Github地址 - 我设计的ArrayList

实现了Arraylist的部分常见功能。

Vector与ArrayList区别

image

Java开发人员已经告诉我们了。

  • 出生于jdk1.0时代,并在jdk1.2的时候被改造为实现List接口。
  • 底层也是动态数组实现
  • 它是线程安全的
  • 如果不需要线程安全的实现,建议使用ArrayList代替Vector(早期设计有些许缺陷)。

可以从图中很明显的看出,Vector类在很多对其底层数组结构有修改作用的函数上都加上了synchronized关键字来保证其线程安全性。


image

如果想要ArrayList实现同步,可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(...));,就可以实现同步了。

image

还有另一个区别:

ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,而Vector如果未指定其capacityIncrement参数时,其默认初始容量为10,且该参数++容量自动增长值++为0。那么Vector的容量扩展1倍。

image

LinkedList

LinkedList的实现和继承结构


image

LinkedList类特点

image
  • LinkedList底层是双向链表
  • 允许存储NULL
  • 线程不安全的,解决办法和Arraylist一样,通过Collections工具类来封装一下。并且在迭代的时候需要外部手动锁住该LinkedList的实例
image

LinkedList 类还为在列表的开头及结尾 get,remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

LinkedList解析

LinkedList属性解析

image

LinkedList的属性和我们自己设计的LinkedList一模一样。

很简单由于是双向链表就必然有头结点和尾节点两个属性,再加一个size变量指定LinkedList元素数量方便我们维护。

构造函数

image

其实构造思路很简单。第二种无非就是把一个集合迭代的跟到LinkedList实例之后。然后根据一些不同的case进行处理。和我们设计的LinkedList处理思路是一样的,只是可能它设计的更严谨一些。

add(E e)

image

我设计的LinkedList是单向链表,所以为了保证add函数的性能选择了头插法。而LinkedList的实现由于是双向链表,所以必然是尾插法才合理。

remove(Object o)

image
  • 如注释所述,删除链表中第一次出现和指定元素的item属性equals的元素。

前提是需要对可能存在的NULL元素做过滤处理。避免空指针异常。

unlike函数

  • 其实就是具体的负责链表中元素的删除工作。
  • modCount变量的维护
  • 具体逻辑的判断不同case的处理(其实和我们的实现思路一样)

可以看出unlink函数的局部变量使用了final关键字来修饰。从一定程度上避免了一些由于LinkedList是线程不安全的类所带来的线程安全问题。同时也带来了一些性能上的提升

get(int index)

image

和我们正常获取元素的思路一致。对于LinkedList来说由于底层是双向链表,我们要向获取到指定位置的元素只能通过遍历的方式。但是Java开发人员想的非常周到,首先对index索引进行了大小判断,看时候大于size的一半,直接就是查询范围缩小了一半。更加高效

set(int index, E element)

image

如同注释描述的一致,没有多余操作,由于关系到index索引,所以按例检查一下。然后进行替换即可。

我设计的LinkedList

Github地址 - 我设计的ArrayList

总结

ArrayList

  • ArrayList是基于动态数组实现的,在增删时候,需要调用System类的arraycopy函数进行拷贝复制((navite 函数底层由C/C++实现)。
  • ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
  • 删除元素时不会减少容量,若希望减少容量则调用trimToSize()
  • 它大体上相似Vector,但不是线程安全的。多线程环境下,任何关于ArrayList集合类结构上的更改,为保障线程安全,我们都需要程序内手动的维护
  • 能存放NULL值,元素可重复,且存取有序。

LinkedList

  • LinkedList底层是双向链表(更方便获取指定元素)
  • 允许存储NULL,元素可重复,且存取有序
  • 线程不安全的,解决办法和Arraylist一样,通过Collections工具类来封装一下。涉及迭代操作的时候需要外部手动锁住该LinkedList的实例

Vector

  • 出生于jdk1.0时代,并在jdk1.2的时候被改造为实现List接口。
  • 底层也是动态数组实现
  • 它是函数操作是线程安全的
  • 现在已少用,被ArrayList替代,原因:
    • Vector所有方法都是同步,有性能损失。
    • Vector初始length是10 超过length时,如果未指定扩容大小。以成倍态势增长,相比于ArrayList开销更多内存。
    • 出生早,设计可能不那么好,且为并发而生,但只限于自并发的情况下才安全。当作用在符合并发的情况下,也同样不满足并发的安全性,需要在整个符合并发的操作外重新上锁,如此一来再加上自己本来就具有同步锁的原因,反而性能相比ArrayList性能更差。

并且它唯一的优点,部分函数的线程安全对于我们来说,也不是必须的。

具体可参考:java数据结构中,什么情况下用Vector,什么情况下用ArrayList呢?

场景

  • 查询多用ArrayList,增删多用LinkedList。
  • ArrayList增删慢也是在大数据量的前提下。在我实现的数据结构中进行了测试(10w~),少量数据差别不大。随机查询ArrayList性能更好,少量数据的增删ArrayList和LinkedList性能差别不大(依靠于System类的的arrayCopy函数),大数据量则LinkedList更好,涉及扩容等操作。指针移动性能相比则开销明显更小一些
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 一、集合入门总结 集合框架: Java中的集合框架大类可分为Collection和Map;两者的区别: 1、Col...
    程序员欧阳阅读 11,530评论 2 61
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,413评论 1 14
  • Collection & Map Collection 子类有 List 和 Set List --> Array...
    任教主来也阅读 3,145评论 1 9
  • 大年初一,张婶早早起床,把封好的红包一字儿摆开,居然摆了一桌子,老人家高兴啊,只等儿孙媳前来道贺。 很快,最小的孙...
    篱笆影阅读 275评论 1 4
  • 所谓自动摘要,就是从文章中自动抽取关键句。何谓关键句?人类的理解是能够概括文章中心的句子,机器的理解只能模拟人...
    famiking阅读 4,177评论 0 2