Java集合类总结

一. Java集合类简介

  • 问题1:为什么要使用Java集合类?
    答:集合类实际上属于动态对象数组,在实际开发中,因为数组的长度是固定的,所以在使用时,面临一定的问题,从JDK1.2 开始,为了解决这种数组长度问题,就提供了Java集合类框架。

  • 问题2:Java的集合类都有哪些?
    答:
    在这里插入图片描述

  • 问题3:主要的两个接口Collection 和 Map 的两个区别:
    答:Collection 每一次进行数据操作的时候只能对单个对象进行处理。
    在开发中,我们很少会直接使用 Collection 接口,一般用 List(允许数据重复) 和 Set(不允许数据重复)

Collection 的子接口都有 add(),iterable() 这两个方法,注意还有个toString() ,和 toArray()这两个方法

二. List 接口

在这里插入图片描述
注意
LinkedList 有 addFirst() 和 addLast() 两个方法, 而ArrayList 没有。还有 pop(),peek()等等方法。这些方法应该是来自 Queue 接口的
在这里插入图片描述在这里插入图片描述

List 子接口与 Collection 接口相比最大的特点在其于有一个 get() 方法,可以根据索引获得内容

2.1 ArrayList 子类

ArrayList 的原理:ArrayList 的底层使用 数组 实现的,是将数据元素放到一块连续的内存存储空间,相邻数据元素的存放地址也相邻 —— 逻辑与物理地址统一。

ArrayList 的优缺点

  • 优点(3点):
    • 空间利用高 —— 连续存放,命中率高
    • 查找迅速 —— 实现了 RandomAccess 接口
    • 存储效率高 —— 通过下标直接存储
  • 缺点(2点):
    • 插入和删除效率差 —— 插入或删除一个元素时,在极端情况下,需要将链表中的每个元素都进行重新排序。
    • 长度不能再增加 —— 若在创建链表时,指定了长度,在操作时,可能就会有元素个数的考量

性能:查找O(1),插入或删除 O(n)

ArrayList 适合于顺序添加、随机访问的场景

2.2 LinkedList 子类

LinkedList 的原理:LinkedList 的底层结构是双向链表数据结构,数据元素在内存存储不连续 —— 逻辑与物理地址不统一

LinkedList 的优缺点

  • 优点:
    • 插入和删除速度快 —— 只需要改变指针指向即可。
    • 没有空间限制
  • 缺点:
    • 浪费空间 —— 占用额外的空间存储指针
    • 查找速度慢 —— 需要循环整个链表访问。

性能:查找O(n),插入和删除O(1)

2.3 Vector 子类

特点:底层是数组。线程安全的,增删慢,查询快

ArrayList 和 Vector 的区别:

  • 提出时间:Vector 是从 JDK 1.0 提出的,而 ArrayList 是在 JDK1.2提出的。
  • 处理形式:ArrayList 是异步处理,性能更高;Vector 是同步处理,性能较低
  • 数据安全:ArrayList 是非线程安全;Vector 是线程安全

如果ArrayList 需要考虑同步问题,也可以使用 concurrent 包提供的工具将ArrayList 变为线程安全

三. Set 接口

HashSet —— 无序存储
TreeSet —— 有序存储
在这里插入图片描述

3.1 TreeSet子类

TreeSet 使用的是升序排列的模式完成的

TreeSet 要对象进行排序处理 —— 对象所在的类一定实现 Comparable 接口并覆写 compareTo(),只有通过此方法才能直到大小关系。

3.2 HashSet 子类
3.3 List 和 Set 的区别
  • List 特点:
    • 一个有序容器 —— 相对元素放入集合和取出集合的顺序
    • 元素可以重复 —— 可以插入多个 null 元素
    • 元素都有索引,可以用 get() 方法访问
    • 常见的实现类有ArrayList、LinkedList 和 Vector
    • List 可以 用 for 循环遍历,也可以用迭代器
  • Set 特点:
    • 一个无序容器 —— 存入和取出的顺序可能不一致
    • 不可以存储重复元素 —— 必须保证元素唯一性
    • Set 常用的实现类有 —— TreeSet、HashSet 和 LinkedHashSet
    • Set 只能用迭代器

Set:检索元素效率低下,删除删除效率高,插入和删除不会引起元素位置变化。
List:查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
在这里插入图片描述

List 和 Map、Set 的区别
  • List 和 Set 是存储单列数据的集合;Map 是存储键和值这样的双列数据的集合
  • List :
    • List中存储的数据是有顺序的 ——放入集合的顺序和取出集合的顺序是一致的
    • 元素可以重复,可以插入多个 null 元素
    • 元素都有索引 —— 可以用 get / set 方法
    • 常见的实现类有 —— ArrayList、LinkedList 和 Vector
  • Set:
    • Set 中的数据是无序的 —— 放入集合的顺序和取出集合的顺序不一致
    • 不可以存储重复元素,只允许存入一个 null 元素 —— 必须保证元素的唯一性
    • 常见的实现类有 —— HashSet、TreeSet 和 LinkedHashSet
  • Map:
    • 数据存储是无序的
    • 键不能重复,值可以重复 (键:值)
    • 继承自 Map
    • 常见的实现类有 —— HashMap、TreeMap、HashTable、LinkedHashMap 和 ConcurrentHashMap
    • 元素在集合中的位置由元素的 hashcode 决定,位置是固定的,但位置不是用户可以控制的,所以对于用户而言它是无序的。
总结:各个实现类
  • ArrayList:基于 Object[] 数组实现,非线程安全,查询效率高,删除增加元素效率低;
  • LinkedList:基于双向链表实现,非线程安全,链表内存是散乱的,每个元素存储本身内存地址数据的同时还存储下一个元素的地址,增删效率高,查询慢;
  • Vector:基于数组实现,线程安全,效率低
  • HashSet:基于 HashMap 实现,无序,唯一,使用该方式需要重写 equals() 和 hashCode() 方法
  • TreeSet:有序,唯一,红黑树 —— 自平衡的排序二叉树
  • LinkedHashSet:继承自 HashSet,同时又基于 LinkedHashMap 实现,底层使用的是 LinkedHashMap
  • HashMap:1)基于 hash 表的 Map 接口实现,非线程安全,高效,支持null键和null值 ,2)在JDK1.8之前 HashMap由数组 + 链表组成,数组是 HashMap 的主体,链表是为了解决哈希冲突而存在的,3)在JDK1.8以后当链表的长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
  • HashTable:线程安全,低效,不支持 null 键和 null 值,是 HashMap 的线程安全版本,用synchronized实现,所以低效
  • TreeMap:红黑树,能够把它保存的记录根据键排序,默认是键值的升序排序。
  • LinkedHashMap:继承自 HashMap,所以它的底层仍然是基于拉链式散列结构,在此基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。

四. Map 接口

4.1 Map的常见方法

在这里插入图片描述
Map 本身是一个接口,常见的子类有:HashMap、HashTable、TreeMap、ConcurrentHashMap。

4.2 HashMap/HashSet 他们背后的 哈希表
4.2.1 哈希函数的概念

哈希函数,也称为散列函数,就是把任意长度的输入通过散列算法,变成固定长度的输出,该输出就是散列值;这种转换是一种压缩映射;也就是,散列值的空间远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。—— 简单来说,就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数

比较:根据同一散列函数计算出的散列值如果不同,那么输入肯定不同;根据同一散列函数计算出的散列值如果相同,输出不一定相同。

4.2.2 为什么计算散列值的过程不可逆

散列函数计算散列值的运算过程存在信息丢失,当运算过程中出现进位时,进位值被直接丢弃而不会保存。由于不知道运算过程中会有多少个进位,不知道在哪一步被丢弃,因而仅仅根据散列函数的计算过程和得到的结果,是无法逆向计算出明文的。

4.2.3 哈希表背后的机制(为什么哈希表的查找/插入/删除的时间复杂度非常好)

搜索是在一组数据集中查找某一特定的数据 —— 需要考虑性能的时候,说明这个数据集的数量非常的 —— 把一个大数据集的查找问题,转化为多个小数据集的问题。
利用了现代内存的随机访问是O(1) 这一特性 —— 数组根据下标的访问是O(1) 的
例如:在 500W 个数据集中查找一个数
在这里插入图片描述
原问题:在 500 W 中查找一个数
现问题:

  1. 先确定这个数在哪个小集合中 —— O(1)
  2. 利用数组下标访问 O(1) 的特性找到小集合
  3. 在大概 50W 个数中查找某个数 —— O(Y)
    解决步骤:
  • 拿着 Key 确定在哪个小集合的问题:
    • 利用 Key 求出 Key 对应的hash值
    • 利用求出的 hash 值得到数组中合法的下标
  • 根据下标,找到小集合(一个 key 对应多个 数据,即一个小集合中有多个数据。假设:数据的个数为 N,数组的长度是 L,为了提升空间利用率,N 的值往往是远远大于 L 的)—— 不同的 key 对应相同下标的情况九叫做哈希冲突
    • 哈希冲突是必然的,无法消除的
    • 但是哈希冲突太高是不好的
      1. 如何尽可能地避免哈希冲突
      2. 如果遇到哈希冲突了怎么办 —— 如何组织小集合地数据
  • 在小集合中继续查找 —— 因为小集合长度有限,近似地认为复杂度是 O(1),如果是插入/删除,只有这一步发生变化即可。
4.2.4 什么是哈希冲突

因为输入空间大于散列表的空间,冲突是无法消除的(因为N >> L + 鸽笼原理)
当输入不同关键字,但通过同一散列函数计算出相同的哈希地址,这种现象就是哈希冲突
由于设计哈希表 底层数组 的容量往往 小于 实际要存储的关键字的数量,这就导致冲突的发生是必然的,但是我们能做的就是降低哈希冲突率

4.2.5 避免冲突 —— 哈希函数设计

常见的哈希函数:直接定制法除留余数法、平方取中法、折叠法、随机数法、数据分析法

1. 什么叫做比较好的哈希函数?
  • 输入的 key,理论上符合高斯分布
  • 经过哈希函数计算后,得到的哈希值(下标)最好是符合均匀分布。
2. 负载因子

人为的规定:LF = N/L = load Factor(负载因子)
LF 和 冲突率之间应该有一个函数递增的函数关系
在这里插入图片描述
目标是降低冲突率,所以我们要做的就是降低 LF 即可
LF = N/L,我们要降低 LF,N 的数量往往不能变(降低 N 就是把集合中的数据扔掉),所以只能通过提升 L 来降低 LF,进而降低冲突率。—— 哈希表的扩容:扩容的目的就是为了降低冲突率。

思考:什么时候开始扩容呢???
经过测算出,某个位置的冲突率无法忍受时,就应该扩容,推出的 LF = 0.75(Java的测算结果,不用深究)

哈希表和顺序表扩容时机不同的地方:

  1. 顺序表是在空间满了的时候才要求扩容,N/L = 1开始扩容
  2. 哈希主要是因为冲突率考虑的扩容时机,所以在 N/L<1 的时候就开始扩容了
3. 如果真的遇到 哈希冲突怎么办?
  1. 在本数组内解决 —— 开放地址 —— 事先制定一个规则,如果冲突了,就按照这个规则找下一个下标即可,直到找到可用位置即可。
  2. 在数组下标位置处外挂另一个数据结构,把所有冲突的数组放到这个数据结构中。—— 拉链法
    一般常用的方法就是一个链表解决 —— 基于冲突的 key 的个数不是很多的情况下
4.2.6 从Java语言的角度来看,一个 key 的插入过程

map.put(key, value)

  1. HashMap 利用泛型来作为 Key 和 Value 的类型,所以key 和 value 必须是引用类型,不能基本类型
  2. 三步过程:
  • 利用 Key 找到下标的过程
    • 通过 key 得到其哈希值:int hash = key.hashCode(); —— 非负整数(Object中的hashCode() 方法的作用:计算一个 key 的哈希值)
    • 拿着 hash 值,怎么得到一个合法的下标呢?求余法性能不高。java 数组的长度L一定是 2 的幂次方
      1. hash 高16位异或 hash 低16位:目的使得hash值得每一位都尽可能地参与到运算中,得到下标尽可能均匀。int h = (hash >>> 16) ^ 16
      2. h & (L - 1) = 合法下标
        在这里插入图片描述
  • 拿着得到的下标,把 key 和对应的 value 放入下标后边的集合中
    Java 的 HashMap 实现了延迟加载:数组没有赋值,只有在第一次插入值时,才开辟了空间
  • 默认情况下,Java 使用链表解决冲突,所以只要把 key-value 放在链表结点中插入即可。
4.2.7 使用了负载因子,冲突率还是很高

原因:1)hashCode() 设计的不好;2)恶意构造数据

Hash表的本质是把大数据集的查找问题,转换为 n 多个小数据集的查找问题。

当小数据集长度不大时,链表足以
当小数据集长度太大,如何解决一个数据集中查找的问题:

  • 哈希表
  • 搜索平衡树
    • 红黑树:为什么转换红黑树的阈值是 8?
      在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值