0 集合介绍

集合

集合可以动态保存任意多个对象,且提供了一系列的操作对象的方法。集合主要是两组:

  • [>] 单列集合Collection 接口有两个重要子接口 List Set (单列集合就是存一个单独的元素)
  • [>] 双列集合Map 接口实现的子类就是 (存放元素的形式为 K-V)
💡单列集合💡

💡💡双列集合💡💡

1 Collection接口

1.0 ArrayList 常用方法

以下是 ArrayList 常用的一些方法,但根据以上类的关系图,可以发现许多类都实现了同一个接口,所以这些类的方法都 大同小异 ,可以互相参考

方法说明
add()添加单个元素
remove()删除指定元素
contains()查找某个元素是否存在
size()获取元素个数
isEmpty()判断是否为空
clear()清空
addAll()添加多个元素
containsAll()查找多个元素是否都存在
removeAll()删除多个元素

📌📌📌 只要实现了Collection接口的类,都可以使用Iterator迭代器进行遍历(生成迭代器代码,IDEA中的代码模板:itit

😎😎😎 增强for循环 可以用来取代迭代器的写法,其底层还是 迭代器 (IDEA快捷:I)

ArrayList 中维护了一个 Object 类型的数组来存储数据 ⚡ 创建 ArrayList 如果使用的是无参构造器,则 初始化容量为0,第一次添加元素时再扩容至10,之后每次不够再扩容至当前大小的 1.5

1.1 Vector

🐼 通过集合介绍可以知道,VectorArrayList 一样,都实现了 List 接口 📌 最大的区别在于:ArrayList 不安全,效率高 ,但 Vector 安全,效率不高 🤺 在扩容倍数上有差距,ArrayList 每次扩容 1.5 倍,Vector 每次扩容 2

1.2 LinkedList

🌟 LinkedList 底层实现了双向链表双端队列的特点

  • 可以添加任意元素,包括 null
  • 线程不安全,没有实现同步
  • 底层实现的双向链表中,维护两个属性 firstlast ,分别指向了首节点尾节点
  • 每个节点是一个 Node 对象,里面维护了 prev 前指针、next 后指针、item 元素这些属性
  • LinkedList 🆚 ArrayListArrayList 底层为可变数组,LinkedList 底层为双向链表;ArrayList 增删效率较低,改查效率高;Vector 增删效率较高,改查效率低

2 Set接口

Set

📌 Set 代表的是集合,所以其特点有:无序,且没有索引;不允许重复元素,所以最多只能有一个 null

2.0 HashSet

  • 🖇️HashSet 实现了 Set 接口,所以接口的特点它也有
    • HashSet 其底层其实是 HashMap
    • HashSet 不保证元素有序,这取决于 hash 后,再确定索引结果
    • 📌📌📌关于加入元素 add() 方法的细节:
      • 1> add() 方法里调用了 map.put() 方法,该方法如果加入成功,则返回 null ,不成功则返回与之重复的对象。add() 方法里将返回结果与 null 进行比较,通过 boolearn 类型表现添加元素成功与否的结果
      • 2> map.put(e, PRESENT) 调用时是这个样子的,其中 e 就是我们要添加的对象,不过 hash 表里用 Node 这个类对象来进行存储,这个对象需要 <K, V> ,所以传入一个 PRESENT ,这是一个 static finalObject 对象,所以传入的都一样,起到一个占位的作用
      • 3> put() 方法里调用了 putVal(hash(key), key, value, false, true) 方法,这里通过我们要添加的对象 key 计算一个 hash 值
      • 4> 这个 hash(key) 的计算并不是简单的 hashcode() 的值,具体为: (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) ,主要目的就是减少冲突
      • 5> putVal() 方法里涉及到了 HashMap 真正的存储结构:数组+链表+红黑树
      • 6> 数组+链表 其实就是拉链法的一个结构,初始这个数组的长度为 16 ,添加的元素通过计算的 hash值 来确定在该数组中的存储索引,计算索引相同元素,则在该位置开始,通过链表的形式来进行存储
      • 7> 数组 长度的扩容有一个 阈值机制 ,当 hashSet存储的元素个数(不是指数组里元素的个数)已经达到了当前数组长度的 0.75 时,就进行扩容,每次扩容是在当前长度的基础上翻一倍
      • 8> 当一个链表的长度达到了 8 ,则会开始考虑用 红黑树 的结构来代替 链表 的存储结构,真正转为 红黑树 需要同时满足:① 有链表长度等于或超过8;② 数组的长度大于或等于 64 。同时满足才会用 红黑树 ,如果第二条不满足,则会对数组长度进行扩容
      • 9> 📌📌📌判断元素相同的机制 :① 计算出的 hash 值相同;② 两个元素引用相同或通过 equal() 方法比较是相同的。只要同时满足这两个条件,就认为这是一个重复元素,就不能加入🚩🚩🚩(重要,最根本的机制

2.1 LinkedHashSet

  • 🖇️ LinkedHashSetHashSet 的子类
    • 其底层是一个 LinkedHashMap ,底层维护了一个 数组+双向链表
    • 物理上还是分开存的,但是通过双向链表,让我们输出的时候符合 插入顺序
    • 该类也实现了 Set 接口,所以也符合该接口的特点

3 Map接口

  • Map 接口和 Collection 接口并列存在,用于保存具有映射关系类型的数据:<K, V>
  • HashSet 的只存 单个元素 ,但之前源码解析里提到,其底层实现方式就是 HashMap ,主要是将 单个元素 存到 K 的位置,而 V 的位置统一使用了一个 static finalObject 的对象进行占位
  • 🌟🌟🌟 Map 接口的特点:
    • KeyValue 可以是任何 引用类型 的数据,会封装到 HashMap$Node 对象中
    • Mapkey 不允许重复,原因见 2.1小节 HashSet 部分的源码分析,如果通过 Key 判断为重复对象,会使用 覆盖机制 将原来的对象替换
    • Value 可以重复
    • null 可以作为 key,也可以作为 value
    • 实际常用 String 作为 key
    • 为了方便遍历,HashMap 会将存入的 K-V 封装成一个 entry 放到一个 entrySet 的集合中去,实际上,不是拷贝,只是在这个集合中存储了对应 K-V 的一个 引用
    • entrySet 里,key 是存到 Set 里的,value 是存到 Collection 里的。可以分别通过 KeySet()values() 方法查看
方法名描述
put()添加
remove()根据 key 移除元素
get()根据 key 获取对应的 value
size()获取元素个数
isEmpty()判断是否为空
clear()清空表
containsKey()查看某个 key 是否存在

3.0 HashMap

HashMap 其特点在 2.0 小节时已经讲过了,一点点区别在于 HashSet 是调用了 add 方法,在方法里调用了 HashMapput() 方法,所以这里直接调用 put() 方法即可。此外:HashMap 线程不安全

3.1 HashTable

  • 🎞️ 其存放的元素形式仍是 <K, V>
  • 键和值都不允许为 null
  • 📌线程安全
  • ⚡键值相同时仍然是 替换机制
  • 🆚HashMap 底层通过 HashMap$Node 来存储,HashTable 底层通过 HashTable$Entry 来进行存储。两者功能一样,结构也十分类似
    • 初始化数组大小为 11
    • 数组的扩容阈值也同样是 0.75
    • 扩容机制为当前数组长度 翻倍 然后再 加一
    • HashTableHashMap 相比,效率较低

3.2 Properties

  • 继承自 HashTable ,并实现了 Map 接口
  • 常用于读取 xxx.properties 配置文件,将配置信息加载到类对象中进行读取和修改

4 使用总结

  • 一组单列对象:Collection 接口
    • 允许重复:List
      • 增删多:LinkedList ,底层是双向链表
      • 查改多:ArrayList ,底层 Object 类型的可变数组
    • 不允许重复:Set
      • 无序:HashSet ,底层还是 HashMap
      • 有序:TreeSet
      • 插入和取出的顺序一致:LinkedHashSet ,维护数组+双向链表
  • 一组双列对象:Map 接口
    • 键无序:HashMap ,底层 数组+链表+红黑树
    • 键排序:TreeMap
    • 键插入和取出顺序一致:LinkedHashMap
    • 读取配置文件:Properties

5 TreeSet&TreeMap

  • TreeSet 底层就是 TreeMap
  • 如果创建时使用的是无参构造器,这两个对象 仍然是无序的
  • 需要按照自己的需求排序,可以在创建对象时传入一个 Comparator() 对象,该对象必须实现 compare 接口,在该接口中定义排序规则