Java 集合框架

本贴最后更新于 2148 天前,其中的信息可能已经时移世改

    本文主要是记录在学习 Java 集合框架过程中的一些知识点备忘!

20181218

1、List VS Set VS Map

  • List:核心区别在于有序性,该接口存储一组不唯一(可以有多个元素引用相同的对象)、有序的对象
  • Set:核心区别在于唯一性,该接口是不允许元素重复的集合,即不会有多个元素引用相同的对象
  • Map:核心趋避在于键值对存储,搜索效率较高;Map 会维护与 Key 有关联的值,两个 Key 可以引用相同的对象,但 Key 不能重复,典型的 Key 是 String 类型,也可以是任何对象

2、Arraylist VS LinkedList

Arraylist 底层使用的是数组(=> 存储、读取效率高,插入删除特定位置效率低【时间复杂度浸塑为 O(n)】);LinkedList 底层使用的是双向循环链表数据结构(插入删除特定位置侠侣特别高【时间复杂度近似为 O(1)】)

3、Arraylist VS Vector

20181217

1、HashMap 为什么是线程不安全的?

在多线程下,进行 put 操作可能会导致 HashMap 死循环问题,原因在于 HashMap 的扩容 resize()方法;

这是由于扩容是新建一个数组,复制原数据到数组;又由于数组下标挂有链表,因此也需要复制链表,但是多线程操作可能导致出现环形链表,例如:

若 2 个线程同时扩容,比如线程 1 先将 A 复制到新的 hash 表中,然后接着复制 B 到链头,本来 B.next = null 到此也就结束了(跟线程 2 过程一样);但是,由于线程 2 扩容的原因,将 B.next = A,继续复制 A,让 A.next=B,由此出现 B.next=A;A.next=B

(线程 2:将**0-A->**B->NULL =》0-B->A->NULL,则线程 1:0->B->A->B

=》JDK1.8 中已经解决了死循环问题(在 resize 方法中,声明两个引用地址,维护两个链表,依次在末端添加新元素,在多线程操作情况下,无非是第二个线程重复第一个线程一模一样的操作而已),虽然多线程 put 操作不会导致死循环问题,但依然有其他的弊端如数据丢失等问题,因此多线程情况下还是应该使用 ConcurrentHashMap

2、HashMap VS HashSet

HashSet 底层是基于 HashMap 实现的,HashSet 中的方法除了 clone\writeObject\readObject 方法外,其他方法都是直接调用 HashMap 中的方法的

  • HashMap 实现了 Map 接口,HashSet 实现了 Set 接口
  • HashMap 存储键值对,HashSet 仅存储对象
  • HashMap 调用 put 方法向 map 中添加元素,HashSet 调用 add 方法向 Set 中添加元素
  • HashMap 使用键 Key 计算 HashCode;HashSet 使用成员对象来计算 hashcode 值
  • HashMap 相对于 HashSet 较快,因为它是使用唯一的键获取对象

3、ConcurrentHashMap VS Hashtable

二者的区别主要体现于线程安全的实现方式上不同

  • 底层数据结构:JDK1.7 中的 ConcurrentHashMap 底层采用分段的数组 + 链表实现,JDK1.8 则采用的是跟 HashMap1.8 的结构一样,即数组 + 链表/红黑二叉树;Hashtable 和 JDK1.8 之前的 HashMap 底层数据结构类似也是采用的数组 + 链表形式,数组是 HashMap 的主体,链表则主要是为了解决哈希冲突而存在的
  • 实现线程安全的方式(重要):(1)JDK1.7 中,ConcurrentHashMap(分段锁)对整个桶数组进行分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率(默认分配 16 个 Segment,笔 Hashtable 效率提高 16 倍);而在 JDK1.8 中则摒弃了 Segment 的概念,直接使用 Node 数组 + 链表 + 红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作(JDK1.6 以后对 synchronized 锁做了很多优化),整个看起来就像是优化过且线程安全的 HashMap,尽管在 DJK1.8 中还能够看得到 Segment 的数据结构,但已经简化了属性只是为了兼容旧版本;(2)Hashtable(同一把锁):使用 synchronized 来保证线程安全,效率非常低下;当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,则另一个线程不能使用 put 添加元素也不能使用 get,竞争会越来越激烈效率越低

4、ConcurrentHashMap 线程安全的具体实现方式/底层实现原理

  • JDK1.7:将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问;

ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成;Segment 实现了 ReentrantLock,是一种可重入锁,扮演锁的角色;HashEntry 用于存储键值对数据

一个 ConcurrentHashMap 中包含一个 Segment 数组,Segment 的结构与 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁

  • JDK1.8:ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全,数据结构跟 HashMap1.8 类似,数组 + 链表/红黑二叉树

synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍

5、集合框架底层数据结构总结

  • Collection

List:

(1)Arraylist:Object数组
(2)Vector:Object数组
(3)LinkedList:双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)

Set:

(1)HashSet(无序,唯一):基于HashMap实现,底层采用HashMap来保存元素
(2)LinkedHashSet:继承于HashSet,且其内部是通过LinkedHashMap实现的
(3)TreeSet(有序,唯一):红黑树(自平衡的排序二叉树)
  • Map

    (1)HashMap:JDK1.8 之前是由数组 + 链表组成,数组是其主体,链表主要是为了解决哈希冲突而存在的(拉链法解决冲突);JDK1.8 之后在解决哈希冲突时,增加了红黑树数据结构即当链表长度大于阈值(默认 8)时,则会将链表转化为红黑树以减少搜索时间
    (2)LinkedHashMap:继承于 HashMap,底层仍然是基于拉链式散列结构即数组 + 链表/红黑树,在此基础之上增加了一条双向链表,使得该结构可以保持键值对的插入顺序,同时通过链表进行相应的操作,实现了访问顺序相关逻辑
    (3)Hashtable:数据 + 链表组成,数组是其主体,链表则主要是为了解决哈希冲突而存在的
    (4)TreeMap:红黑树(自平衡的排序二叉树)

20181216

1、ArrayList VS LinkedList

  • 是否线程安全:二者均是不同步的,即不保证线程安全;
  • 底层数据结构:ArrayList - Object 数组,LinkedList - 双向链表数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环,注意双向链表和双向循环链表的区别)
  • 插入和删除是否受元素位置的影响:(1)数组,因此插入和删除元素的时间复杂度受元素位置的影响,近似为 O(n);(2)链表,因此插入和删除元素的时间复杂度不受元素位置的影响,近似为 O(1)
  • 是否支持快速随机访问:LinkedList 不支持高效的随机元素访问,而 ArrayList 支持,直接通过元素的序号快速获取元素对象
  • 内存空间占用:ArrayList 的空间浪费主要是 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)

=>

  • RandomAccess 接口:该接口中无任何定义,因此只是一个标识,即标识实现这个接口的类具有随机访问功能!
  • binarySearch()方法:该方法会判断传参 List 是否是 RandomAccess 的实例,若是则调用 indexedBinarySearch 方法,否则调用 iteratorBinarySearch 方法

=>

  • ArrayList 实现了 RandomAccess 接口,LinkedList 没有实现;
  • 数组天然支持随机访问,时间复杂度 O(1),因此称为快速随机访问;链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问
  • ArrayList 是实现了 RandomAccess 接口,是表明了其具有快速随机访问功能,该接口仅是标识,并不是说 ArrayList 实现了该接口才具有快速随机访问功能的

=>

  • 实现了 RandomAccess 接口的 List,优先使用普通 for 循环,其次是 foreach

  • 未实现 RandomAccess 接口的 List,优先选择 iterator 遍历(foreach 遍历底层也是通过 iterator 实现的),大 size 的 List 数据不要使用普通 for 循环

  • 双向链表:也即双链表,是链表的一种,它的每个数据节点均有两个指针,分别指向直接后继和直接前驱;因此,从双向链表中的任意一个节点开始,均可以很方便地访问它的前驱节点和后继节点,一般都是构造双向循环链表,JDK1.6 之前的 LinkedList 底层使用的就是双向循环链表

2、ArrayList VS Vector

  • Vector 类的所有方法均是同步的,两个线程可以安全地访问同一个 Vector 对象,但是一个线程访问 Vector 需要在同步操作上耗费大量的时间
  • ArrayList 不是同步的,不需要保证线程安全时建议使用 ArrayList

3、HashMap 的底层实现

  • JDK1.8 之前:

底层是“数组 + 链表”数据结构,即链表散列;

HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过(n-1)&hash 判断当前元素存放的位置(n 即数组的长度),如果当前位置存在元素的话则判断该元素与要存入的 h 元素的 ash 值以及 key 是否相同,如果相同的话则直接覆盖,否则不相同则通过拉链法解决冲突

扰动函数:也就是 HashMap 的 hash 方法,该方法即扰动函数主要是为了防止一些实现比较差的 hashCode 方法,以减少碰撞

hash 方法源码:

//jdk1.8方法相较于jdk1.7更加简化,但是原理不变
static final int hash(Object key) {
      int h;
      // key.hashCode():返回散列值也就是hashcode
      // ^ :按位异或
      // >>>:无符号右移,忽略符号位,空位都以0补齐
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//jdk1.7,该方法性能会稍差一点点,因为毕竟扰动了4次
static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

拉链法:将链表和数组相结合,即创建一个链表数组,数组中每一格都是一个链表,若遇到 hash 冲突则将冲突的值加到链表中即可

  • JDK1.8 之后

在 JDK1.8 中,对于解决哈希冲突有了较大的变化,当链表长度大于阈值(默认 8),则会将链表转化为红黑树,以减少搜索时间

TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层均用到红黑树,红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构

4、HashMap VS HashTable

  • 线程安全:HashMap 非线程安全,HashTable 线程安全;HashTable 内部的方法基本都是经过 synchronized 修饰的(若需要保证线程安全的话,可以使用 ConcurrentHashMap)
  • 效率:由于线程安全的问题,HashTable 的效率比 HashMap 低一点,且 HashTable 已经基本被淘汰,不要在代码中使用它
  • 对 Null key 和 Null value 的支持:HashMap 中,null 可以作为主键,这样的键只有一个,可以有一个或多个键所对应的值为 null;但是在 HashTable 中 put 进的键值只要有一个 null,则直接抛出 NullPointerException
  • 初始容量大小&每次扩充容量大小的不同:(1)创建时若未指定初始容量值,HashTable 默认初始大小为 11,每次扩充容量变为原来的 2n+1;HashMap 默认初始大小为 16,每次扩充容量变为原来的 2 倍;(2)创建时若指定初始容量值,则 HashTable 会直接使用给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的 tableSizeFor 方法保证)
  • 底层数据结构:JDK1.8 以后的 HashMap 在解决哈希冲突时,当链表长度大于阈值(默认 8),则会将链表转化为红黑树以减少搜索时间,而 HashTable 则没有这样的机制

5、HashMap 的长度为什么是 2 的幂次方

为了能够让 HashMap 存取高效,尽量减少碰撞,也即要尽量把数据分配均匀!Hash 值范围是-2147483648~2147483648,共约 40 亿映射空间,只要 hash 函数映射的比较均匀松散,一般很难出现碰撞,但是 40 亿长度的数组在内存中存放不下的,因此这个散列值是不能直接使用的

=> 考虑先对数组的长度进行取模运算,计算的余数用来作为存放的位置也即数组下标,即数组下标的计算方法是“(n-1) & hash”,其中 n 为数组长度,这也就是为什么 HashMap 的长度是 2 的幂次方

=> 为什么是 2 的幂次方?::取模运算,首先就是采用 % 操作进行实现,=>"取余 % 操作中,在除数是 2 的幂次方时,等价于与其除数减一的与&操作,也即 hash%length == hash&(length-1),,这个等价的前提就是 length 是 2 的 n 次方"

=> 并且,在采用二进制位操作&,相对于 % 能够提高运算效率,这也是为什么 HashMap 的长度要是 2 的幂次方!

  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    168 引用 • 504 回帖
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3186 引用 • 8212 回帖
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 52 关注
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 383 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 754 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 596 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    311 引用 • 546 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 2 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1705 回帖
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    75 引用 • 1737 回帖
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    186 引用 • 318 回帖 • 306 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    131 引用 • 1114 回帖 • 131 关注
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 30 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 8 关注
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 383 回帖 • 5 关注
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖 • 1 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    541 引用 • 672 回帖 • 1 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 615 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 637 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖 • 1 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 63 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    142 引用 • 442 回帖
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    176 引用 • 995 回帖 • 1 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖 • 3 关注
  • SMTP

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。

    4 引用 • 18 回帖 • 613 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖