本文源自参考《Think in Java》,多篇博文以及阅读源码的总结

前言

Java的集合其实就是各种基本的数据结构(栈,队列,hash表等),基于业务需求进而演变出的Java特有的数据结构(因为不仅仅是基本数据结构)。现在,我们以数据结构的视角来看看Java的集合到底是什么样子。并分析他们的性能。

一 JAVA集合体系

JAVA的集合体系分为两类,Collection接口和Map接口

主要分为三种:

  • Set。无插入顺序的不重复数据集接口(集合演变而来)
  • List。有插入顺序的数据集接口(队列演变而来)
  • Map。Key-Value的键值对数据集接口(Hash表演变而来)

其中Set和List继承自Collection接口,Map则就是Map接口。

接口中都定义了一些基本增删改查的方法。

具体继承体系如下图:

基本可以从名字知道集合的内部数据结构。

  • 看后缀,有Set,List,Map后缀的集合,代表着该集合的基本结构,所以会具有以上所说的特性。
  • 看前缀,前缀往往代表着该数据结构的具体实现方式。一般有这几种:
    1. Hash或者Array,代表着以哈希(基本)数组实现的数据结构。
    2. Linked,代表着集合内各个数据之间存在链表关系。
    3. Tree或者Sorted,代表着内部使用红黑树实现了排序。(需要提供Comparator或者实现Comparable)

下面大略说下每个集合的数据结构,懒得贴源码了。

1.1 List

最常用的List就是ArrayList和LinkedList了,在此不讨论并发的List集合。
讨论下底层源码对它们的具体实现。

1.1.1 ArrayList

使用JAVA的基本数组实现的动态数组集合,源码底层维护着List的容量与实际长度

因为使用的基本数组,不像哈希数组一样需要考虑哈希碰撞问题,因此负载因子默认为1。当List数组容量不够时才进行扩容,扩容的倍数为1.5倍

通过Arrays.copyOf方法,返回复制的新数组。Arrays.copyOf底层调用的System.arraycopy方法。而在ArrayList初始化时,如果不指定初始数组长度,在JDK1.6之后默认初始长度为0,在JDK1.6之前则默认为10。在JDK1.6后,ArrayList在第一次扩容时,如果扩容长度不足10,则会直接扩容到10。

具体集合怎么使用就不废话了。

1.1.2 LinkedList

这是一个双向链表,其中节点用的是LinkedList的内部类。和数据结构中的链表差不多。可以用它实现栈和队列。

1.1.3 ArrayList与LinkedList比较

很明显,ArrayList是某种程度上的哈希表,适合随机读,但是不适合在集合中间插入和删除(会造成后续数据的位移)。
而LinkedList适合在头尾部插入删除,不适合随机读。

值得一提的是ArrayList随机读的时间复杂度是O(1),LinkedList是O(n)。而ArrayList在中间插入和删除的时间复杂度是O(n),LinekdList在中间插入删除时间复杂度也是O(n)

可以明显看出来ArrayList在插入删除上和LinkedList理论上所用的时间是一个级别的,但是ArrayList慢于LinkedList是因为在修改集合后需要进行其他数组数据的移动,而LinkedList则是查找节点花费了O(n),不需要额外移动数据,所以在同样数据量时,LinkedList进行数据修改优于ArrayList。

1.2 Map

最常用的Map就是HashMap和TreeMap。

1.2.1 HashMap

HashMap是底层用哈希数组实现的Map。HashMap就是一个个Entry(Key-Value键值对)存储在一个哈希数组上(Entry是HashMap的内部类)。

哈希数组的使用不可避免的需要考虑哈希碰撞问题,常用的解决方案有:

  • 拉链法
  • 再哈希法
  • 开放地址法
  • 建立公共溢出区。

在JDK里,使用的就是拉链法解决的哈希碰撞问题,因此每个哈希数组上的数组元素(又被称为桶——bucket),都是一个链表的表头。这样基本保证了HashMap的平均查找时间是O(1)。

HashMap的负载因子为0.75

但是当出现频繁哈希碰撞时,会导致某个链表过长进而导致了查找时间会趋近于O(n)。对此JDK原本的解决方案是设置负载因子为0.75。当哈希表总负载量达到0.75时,就会进行扩容,扩容为原本的2倍。这样当数据平均下来后,不太容易出现过长的链表(因为扩容会分解链表重新放入桶中)。

但是这并没有解决特殊情况下查找效率的问题,只是让这种特殊情况更难以出现了。

JDK1.8中 HashMap出现了红黑树

因此在JDK1.8中又做出了改进,当某个桶中的链表的长度大于8时。链表会重构成一个红黑树。这样保证了HashMap的最坏时间复杂度也仅仅是O(logn)。同时负载因子引起的扩容也保证了红黑树的重构不会频繁发生,不会因为频繁建树导致过多的性能开销。

HashMap的初始化与扩容

另外值得一说的就是HashMap在不知道初始长度进行初始化时,JDK1.6前默认长度为16,JDK1.6后默认长度为0。基本在JDK1.6中,需要初始化底层容器的集合都做出了这种优化。不会提前构造底层容器造成开销,会等到使用时才进行底层的初始化。

而HashMap默认长度设置为16,并且每次扩容都是2倍。这是为了方便底层的哈希数组进行取模时的运算,可以把取模的除法运算改写成位移运算,提升性能。

并且在JDK1.8中,HashMap关于取模运算还做了另一个优化。在JDK1.8之前,每次哈希数组扩容时,链表里的数据都会再次进行哈希运算。而在JDK1.8后,不需要再进行运算了,只需要在每个桶中选择一半数据往后移动oldLength位就行(oldLength是集合在扩容前的容量)。

1.2.2 TreeMap

而另一个常用的Map——TreeMap,底层就是用JAVA写了一个红黑树,感觉没什么好说的。有兴趣的可以回去翻翻数据结构的书。

1.2.3 LinkedHashMap

HashMap的每个Node还会以插入顺序相互关联成为双向链表。

1.3 Set

Set主要是SortedSet和HashSet。打开源码一看,分别new了一个TreeMap和HashMap,然后把数据存在了Key里。嗯,这就是Set的底层实现了。

Java:集合类的数据结构的更多相关文章

  1. (4)java数据结构--集合类及其数据结构归纳-有大图

    Java集合类及其数据结构归纳 - s小小的我 - 博客园http://www.cnblogs.com/shidejia/p/6433788.html ---------大图可以 在新标签中打开图片 ...

  2. JAVA中的数据结构——集合类(序):枚举器、拷贝、集合类的排序

    枚举器与数据操作 1)枚举器为我们提供了访问集合的方法,而且解决了访问对象的“数据类型不确定”的难题.这是面向对象“多态”思想的应用.其实是通过抽象不同集合对象的共同代码,将相同的功能代码封装到了枚举 ...

  3. Java集合类--温习笔记

    最近面试发现自己的知识框架有好多问题.明明脑子里知道这个知识点,流程原理也都明白,可就是说不好,不知道是自己表达技能没点,还是确实是自己基础有问题.不管了,再巩固下基础知识总是没错的,反正最近空闲时间 ...

  4. 做JavaWeb开发不知Java集合类不如归家种地

    Java作为面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储.但是使用数组存储对象方面具有一些弊端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容 ...

  5. Java集合类中的哈希总结

    JAVA集合类中的哈希总结 目 录 1.哈希表 2.Hashtable.HashMap.ConcurrentHashMap.LinkedHashMap.TreeMap区别 3.Hashtable.Ha ...

  6. Java集合类: Set、List、Map、Queue使用场景梳理

    本文主要关注Java编程中涉及到的各种集合类,以及它们的使用场景 相关学习资料 http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E ...

  7. Java集合类: Set、List、Map、Queue使用

    目录 1. Java集合类基本概念 2. Java集合类架构层次关系 3. Java集合类的应用场景代码 1. Java集合类基本概念 在编程中,常常需要集中存放多个数据.从传统意义上讲,数组是我们的 ...

  8. 基础知识《六》---Java集合类: Set、List、Map、Queue使用场景梳理

    本文转载自LittleHann 相关学习资料 http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E6%8E%92%E5%BA%8F% ...

  9. java集合类(六)About Queue

    接上篇“java集合类(五)About Map” 终于来到了java集合类的尾声,太兴奋了,不是因为可以休息一阵了,而是因为又到了开启新知识的时刻,大家一起加油打气!!Come on...Fighti ...

随机推荐

  1. ckeditor粘贴word文档图片的思路

    由于工作需要必须将word文档内容粘贴到编辑器中使用 但发现word中的图片粘贴后变成了file:///xxxx.jpg这种内容,如果上传到服务器后其他人也访问不了,网上找了很多编辑器发现没有一个能直 ...

  2. cc.Sprite组件

    1.精灵 精灵(Sprite)是Cocos系列的核心概念之一,是Cocos Creator最常用的显示图像的组件. 游戏中显示一个图片,我们就可以把这个叫做"精灵" sprite, ...

  3. 注解之 @RestController 和 @RequestMapping

    Controller 是 Spring 中最基本的组件,主要处理用户交互,一般每个业务逻辑都会有一个 Controller,供用户请求接口进行数据访问:@RequestMapping 注解用于绑定UR ...

  4. CF1200A

    CF1200A 解法: 给出长度为n的字符串,字符串由'L'.'R'以及数字0~9组成.旅馆有10间房子,L代表客人从左边入住,R代表客人从右边入住,数字则表示第i间房子客人退房了.问经过这n次操作后 ...

  5. ICEM—气体罐子

    原视频下载地址:https://yunpan.cn/cPfzaLzkcFXVK  访问密码 62cf

  6. 互联网IT当线上出现 bug 时,是怎么处理的?

    线上BUG说处理方法:1.关于线上BUG问题,目前公司有一整套线上故障流程规范,包括故障定义.定级.处理流程.故障处理超时升级机制.故障处理小组.故障处罚(与故障存在时长有关)等:2.最主要的是,线上 ...

  7. SSH交互式脚本StrictHostKeyChecking选项 benchmode=yes

    SSH 公钥检查是一个重要的安全机制,可以防范中间人劫持等黑客攻击.但是在特定情况下,严格的 SSH 公钥检查会破坏一些依赖 SSH 协议的自动化任务,就需要一种手段能够绕过 SSH 的公钥检查. 什 ...

  8. [Java复习] JVM

    Part1:Java类加载机制:类加载器,类加载机制,双亲委派模型 1. Java 类加载过程? 类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的 ...

  9. ELK的安全解决方案 X-Pack(1)

    安装 X-Pack 前必须安装 elasticsearch. Kibana.logstash,因为之前安装ELK选择的版本都是5.4.1,所以这次选择X-Pack的版本也要是5.4.1的 第一步:下载 ...

  10. pymysql检查是否断开, 断开重连

    python mysql使用持久链接 python链接mysql中没有长链接的概念,但我们可以利用mysql的ping机制,来实现长链接功能~ 思路: 1 python mysql 的cping 函数 ...