Java集合知识点总结

Java集合知识点总结——ArrayList、LinkedList、HashMap

  • Java集合
    • 1. java集合框架
    • 2. Collection
      • List
      • Set
    • 3. Map
      • HashMap
      • LinkedHashMap
      • TreeMap
    • 4. 总结
    • 参考

Java集合

1. java集合框架

Java集合知识点总结_第1张图片Java集合是一种存储对象的容器,从框架图中可以看出,集合框架中主要包含两种类型的基本接口:1.Collection接口,用于存储元素集合,包括List、Set和Queue接口;2.Map接口,用于存储键/值对。

List、Set、Map区别

List:List实现类存储的元素是有序的,可以重复,一般来说检索效率高,但插入和删除效率低(不过具体也要看底层数据结构是数组实现的还是链表)
Set:Set实现类存储的元素无序,不可重复,一般来说检索效率比较低,但是插入和删除效率高
Map:Map采用键值对(key-value)存储,每个键最多映射一个值,可以通过key快速查找元素

下面分别介绍Collection接口和Map接口下的一些具体实现类。

2. Collection

List

查阅官方文档可知,List接口的实现类共计十个,其中最常使用的有 ArrayList和LinkedList。

  1. ArrayList
    底层采用数组实现,数组大小可以动态变化,适用于需要大量随机访问数组元素的情景,但插入和删除效率低,可能需要大量移动元素。该类非同步,线程不安全,如果有多个线程同时访问,并且至少有一个线程在结构上进行了修改,则必须在外部进行同步。
    ArrayList扩容机制
  2. LinkedList
    底层采用双向链表实现,因此插入和删除元素效率比较高,但是在具体位置进行插入或删除时,也要从头开始遍历。与ArrayList一样也是非同步的,不保证线程安全。
  3. 二者比较
    (1) 底层实现机制不同,导致某些操作如查找、删除等的时间复杂度存在较大差异。
    (2) 都是非同步的,线程不安全。
    (3) ArrayList支持随机访问,LinkedList不可以,必须遍历集合以找到对应位置。

故对LinkedList进行遍历输出时,不要使用for循环,最好使用foreach或迭代器iterator,在for循环中,每次都调用一次LinkedList的get函数,时间复杂度为O(n)
具体请看: ArrayList和LinkedList的几种循环遍历方式及性能对比分析

Set

  1. HashSet
    Set常用类,底层采用HashMap实现,元素存取无序,索引位置根据哈希值来确定,因此查找、删除、添加性能良好。线程不安全。

    HashSet元素添加机制:
    首先调用hashCode()方法获取元素的哈希值,然后计算得到元素的索引位置。如果集合中没有元素的哈希值与之相同,则添加成功;否则调用equals方法判断两个元素是否完全相同,如果相同则添加失败,否则以链表形式存储在该索引位置,添加成功。

因此使用HashSet一定要重写hashCode()和equals()方法,否则可能插入内容相同的两个对象。
参看:hashCode()和equals()方法详解

  1. LinkedHashSet
    HashSet的子类,使用双向链表保证元素的添加顺序,因此可按添加顺序遍历,也不允许元素重复。

  2. TreeSet
    TreeSet底层采用红黑树结构来存储元素,能按添加顺序遍历元素,排序方式有自然排序和定制排序。自然排序要求添加的实例类实现了Comparable接口,并重写接口中的compareTo方法。定制排序可以通过将实现Comparator接口的实例作为参数传入TreeSet的构造方法中,在实现Comparator接口时通过重写其对应的compare方法来实现自定义的排序。

    以上三种Set类都是非同步的,线程不安全。

3. Map

HashMap

Map家族中使用频率非常高的成员。
底层采用数组+链表+红黑树(链表长度大于阈值时转为红黑树,默认阈值为8)存储元素,与HashSet相似,不保证元素的添加顺序。
HashMap的key值不可重复,可取null值,但也只能有一个null值,任意引用类型的数据都可作为key值传入,但是要重写hashCode()和equals()方法以确保传入键值的唯一性。

HashMap的put操作:

  1. 判断数组是否为空,如果为空则进行扩容;

  2. 根据key值得到该对象对应的哈希值,然后根据哈希算法(高位运算+取模)计算得到待插入的索引位置;

  3. 若该索引位置尚未存储数据,直接插入;否则判断该位置的首个元素是否和待插入对象相同,相同则直接覆盖value值,添加成功;

  4. 若该索引位置为链表,遍历链表,若有相同元素直接覆盖,若无判断链表长度是否大于8,大于8则转为红黑树进行添加操作,否则直接在链表中添加;

  5. 若该索引位置为红黑树,则在树中插入键值对,有相同元素覆盖即可。

    添加前后都会进行容量判断,看是否需要扩容。

    参看:HashMap详解

还有一个需要注意的点就是HashMap的容量大小始终保证为2的幂次方 ,这是因为在HashMap的哈希算法中是采用(n-1)&hashValue来进行取模运算,进而计算元素存放的数组下标的,其中n为数组大小。
&运算符相较于%的取模操作,运行效率更高,但能用hash&(n-1)代替hash%n来取模的前提条件就是n要是2的幂次方

LinkedHashMap

是HashMap的子类,但在其基础上用双向链表来保证元素的迭代顺序,因此性能相对于HashMap来说较低。

TreeMap

TreeMap底层采用红黑树实现,与TreeSet类似,TreeMap根据键的自然顺序排序,或者根据映射创建时提供的比较器排序,这取决于使用的是何种构造函数。

上述都是线程不安全的,如果要保证线程安全可以使用ConcurrentHashMap。

4. 总结

在使用过程中具体要选择哪种集合作为容器,要根据不同集合的特点并结合自身需求而定。
如果要采用键值对存储,则考虑Map家族成员,有迭代顺序要求则考虑LinkedHashMap或TreeMap,没有顺序要求则考虑HashMap。
如果不需要采用键值对且不要求元素唯一性,则考虑List下的实现类,有排序要求选择LinkedList,没有则选择ArrayList。要求元素不能重复,则考虑Set接口下的实现类,包括HashSet、LinkedHashSet、TreeSet等。

Java集合知识点总结_第2张图片

参考

  1. Java API文档-Java Collections Framework
  2. Github优秀Java知识点总结-JavaGuide

你可能感兴趣的:(Java基础,java)