java 集合结构 java中的集合体系_开发语言

Java 的集合类还是挺多的,在这里我只把需要理解和比较常用的集合类列在这里了。

Java 集合类是一种十分有用的工具类,可用于存储数量不等的对象还可以实现常用的数结构,如栈和队列等。Java 集合大致分为四种体系 Set、List、Queue和 Map 。其中,Set 代表无须、不可重复的集合;List 代表有序、可重复的集合;Map 代表具有映射关系的集合; Queue 是代表一种队列的集合。

下面是各个接口和实现类的关系图,方便各位读者来了解它们之间的关系。

(图来源:java 3y)

java 集合结构 java中的集合体系_java_02

Collection

上述的思维导图出现了 Collection 在这说明一下,Collection 从源码来看是一个接口并且继承了Iterable接口,同时也是是 List、Set、Queue 接口的父接口。而 Collection 接口里定义了如下操作集合元素的方法:

  • **boolean add(Object o):该方法用于向集合里添加一个元素。**如果结合被添加操作改变了,则返回 true。
  • **boolean addAll(Collection c):把集合 c 中所有元素添加到指定集合里。**如果集合被添加操作改变了,则返回 true。
  • void clear():清除集合里所有元素,集合长度将变为 0。
  • boolean contains(Object o):返回集合中是否包含元素。
  • boolean containsAll(Collection c):返回集合里是否包含集合 c 里所有元素。
  • **boolean isEmpty():返回集合是否为空。**当集合长度为 0 时返回 true,反之返回 false。
  • Iterator iterator():返回一个 Iterator 对象,用于遍历集合里的元素。
  • boolean remove(Object o):删除集合中指定元素 o,当集合包含多个元素 o 时,该方法只删除第一个符合条件的元素,该方法将返回 true。
  • boolean removeAll(Collection o):从集合中删除集合 c 里包含的所有元素,如果删除一个或多个元素,则返回 true。
  • **boolean retainAll(Collection o):从集合中删除集合 c 里不包含的元素,**如果该操作改变了调用该方法的集合,则返回 true。
  • int size():该方法返回集合里元素的个数。
  • Object[] toArray():该方法把集合转换成一个数组,所有的集合元素变成相应的数组元素。

Iterator

在这里十分有必要介绍一下迭代器 Iterator ,也是一个接口,并且里面定义了一下4个方法。

  • boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回 true;
  • Object next():返回集合里的下一个元素;
  • void remove():删除集合里上一次 next 方法返回的元素;
  • voidforEachRemaining(Consumer action):这是 Java8 为 Iterator 新增的默认方法,可使用 Lambda 表达式来遍历集合元素。

下面我们来尝试一下使用这些方法:

public static void main(String[] args) {
        //因为迭代器的使用使用来迭代集合,所以我们需要先有一个集合
        Collection books = new ArrayList();
        books.add("java");
        books.add("集合");
        books.add("容器");
        //获取books集合的迭代器
        Iterator it = books.iterator();
        while(it.hasNext()){
            //因为next()返回的数据类型是Object,在这里需要数据类型的转换
            String book = (String)it.next();
            System.out.println(book);
            if(book.equals("容器")){
                it.remove();
            }
        }
        System.out.println(books);
    }
**从上面我们很容易看出,Iterator 仅仅是用来遍历集合的,而且必须要有一个被迭代的集合。当然,除了使用 Iterator 进行迭代,我们还可以选择使用 foreach 或者直接 for 循环也是可以的(for循环时,需要用 toArray() 方法把集合转换成数组,才能进行遍历)**

List 集合

List 集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。
而 List 作为 Collection 的子接口,是可以使用 Collection 接口里的全部方法,并且因为 List 是有序集合,所有添加了一些根据索引来操作集合的方法。

  • void add(int index,Object element):将元素 element 插入到 List 集合的 index 处。
  • boolean addAll(int index,Collection c):将集合 c 插入到 List 集合的 index 处。
  • Object get(int index):返回集合 index 索引处的元素。
  • int indexOf(Object o):返回对象 o 在 List 集合中第一次出现的位置的索引值。
  • int lastIndexOf(Object o):返回对象 o 在 List 集合中最后一次出现的位置的索引值。
  • Object remove(int index):删除并返回集合 index 索引处的元素。
  • Object set(int index,Object element):将 index 索引处的元素替换成 element 对象,返回被替换的旧元素。
  • List subList(int fromIndex,int toIndex):返回从 fromIndex(包含)到索引 toIndex (不包含)处所有集合元素组成的子集合。

List的子类都可以使用其父类的所有方法

ArrayList 类

我们先来看一下这个类的源码,还是比较好理解的

(图来源:Java 3y)

java 集合结构 java中的集合体系_java_03

我们可以发现 ArrayList 是用数组实现的,但不同的是 ArrayList 可以进行扩容,而数组不行。如果最小容量比数组长度要大,那么就要调用 grow() 方法来进行扩容。每次扩容1.5倍。

• ArrayList是基于动态数组实现的,在增删时候,需要数组的拷贝复制
ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
• 删除元素时不会减少容量,若希望减少容量则调用trimToSize()
• 它不是线程安全的。它能存放null值。

Vector 和 ArrayList 的区别

Vector 这个类是 JDK1.2 的类了,已经过时了,在这里只说明 Vector 和 ArrayList 的区别。

  • Vector底层也是数组,与ArrayList最大的区别就是:同步(线程安全)
  • Vector 的方法都有 synchronized 关键字进行修饰。
  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
  • 如果想要ArrayList实现同步,可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(…));,就可以实现同步了

Queue

Queue 用于模拟队列这种数据结构,队列,指的是“先进先出”的容器。

java 集合结构 java中的集合体系_数组_04

这些是 Queue 接口中定义的方法。

在这里比较重要的是** Deque 接口 和 ArrayDeque 实现类**

  • Deque 接口是 Queue 接口的子接口,它代表了一个双端队列(可以同时从两端来添加、删除元素,可以当成队列也可以当成栈进行使用)
  • 并且 Deque 接口中还包含了 pop(出栈)、push(入栈)两个方法。
  • ArrayDeque 是 Deque 接口提供的实现类,它是基于数组实现的双端队列
  • 可以指定 numElements 参数即,指定 Object[] 数组的长度,默认长度 16。
  • 和 ArrayList 的实现机制相似,都是采用了一个动态数组。

LinkedList

java 集合结构 java中的集合体系_List_05

从结构上,我们还、看到了LinkedList底层是双向链表,并且 LinkedList实现了Deque接口,因此,我们可以操作LinkedList像操作队列和栈一样。
而 LinkedList 的增删改查的一些基本方法和平时做的链表的训练几乎一样,感兴趣的可以去看一下源码。
我们一般会在增删操作比较多的时候会使用 LinkedList ,这取决于底层的实现是双向链表。双向链表方便我们向前进行遍历。

Map 集合

前面我们介绍了 Collection ,它可以快速查找现有的元素。
我们在了解 Map 的用途时,应该先知道它的存储结构:

  • Map 集合中保存这两组值,一组用于保存 key ,另一种保存 value ,key 和 value 都可以是任何引用类型的数据。
  • key 不允许重复
  • key 和 value 之间存在单向一对一关系,即通过 key 可以找到惟一的、确定的 value,反之则不行。

image.png
Map 集合在我们生活当中的例子也很多,身份证的号码是唯一的,可以通过这个号码找到对应的信息,但不可能说通过这个人的姓名等信息得到身份证号码。

下面是 Map 集合的常用的功能方法:

java 集合结构 java中的集合体系_java 集合结构_06

HashMap

要想很好的了解一个集合,最好的方法是看官方的介绍~

(图来源:Java 3y)

java 集合结构 java中的集合体系_List_07

看完介绍,来看一下 HashMap 的属性:

java 集合结构 java中的集合体系_java_08

我们知道Hash的底层是散列表——哈希表,而在Java中散列表的实现是通过数组+链表的
再来简单看看put方法就可以印证我们的说法了:数组+链表–>散列表
总结一下:
在JDK8中HashMap的底层是:数组+链表(散列表)+红黑树
在散列表中有装载因子这么一个属性,当装载因子*初始容量小于散列表元素时,该散列表会再散列,扩容2倍!
装载因子的默认值是0.75,这表明该 hash 表的3/4被填满时, hash 会发生扩容,无论是初始大了还是初始小了对我们HashMap的性能都不好
装载因子初始值大了,可以减少散列表再散列(扩容的次数),但同时会导致散列冲突的可能性变大(散列冲突也是耗性能的一个操作,要得操作链表(红黑树)
装载因子初始值小了,可以减小散列冲突的可能性,但同时扩容的次数可能就会变多!
初始容量的默认值是16,它也一样,无论初始大了还是小了,对我们的HashMap都是有影响的:
•** 初始容量过大**,那么遍历时我们的速度就会受影响~
初始容量过小,散列表再散列(扩容的次数)可能就变得多,扩容也是一件非常耗费性能的一件事~
从源码上我们可以发现:HashMap并不是直接拿key的哈希值来用的,它会将key的哈希值的高16位进行异或操作,使得我们将元素放入哈希表的时候增加了一定的随机性
还要值得注意的是:并不是桶子上有8位元素的时候它就能变成红黑树,它得同时满足我们的散列表容量大于64才行的~

HashMap与Hashtable对比

从存储结构和实现来讲基本上都是相同的。它和 HashMap 的最大的不同是它是线程安全的,另外它不允许 key 和 value 为 null。Hashtable 是个过时的集合类,不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。

java 集合结构 java中的集合体系_java 集合结构_09

LinkedHashMap

LinkedHashMap 使用的是双向链表来维护 key-value 对的次序。该链表负责维护 Map 的迭代顺序,迭代顺序和 key-value 对的插入顺序保持一致。
• 底层是散列表和双向链表
• 允许为null,不同步
• 插入的顺序是有序的(底层链表致使有序)
• 装载因子和初始容量对LinkedHashMap影响是很大的~

**LinkedHashMap 继承了 HashMap **并且实现了 Map 接口

java 集合结构 java中的集合体系_java 集合结构_10

原来LinkedHashMap和HashMap的put方法是一样的!LinkedHashMap继承着HashMap,LinkedHashMap没有重写HashMap的put方法
LinkedHashMap 需要维护元素的插入顺序,因此性能略低于 HashMap 的性能;但因为它以链表来维护内部顺序,所以在迭代访问 Map 里全部元素时有较好的性能。

TreeMap

java 集合结构 java中的集合体系_java_11

• TreeMap实现了NavigableMap接口,而NavigableMap接口继承着SortedMap接口,致使我们的TreeMap是有序的
• TreeMap底层是红黑树,它方法的时间复杂度都不会太高:log(n)
• 非线程同步,想要同步可以使用Collections来进行封装
• 使用Comparator或者Comparable来比较key是否相等与排序的问题

TreeMap 是一个红黑树数据结构,每个 key-value 对即作为红黑树的一个节点。TreeMap 存储 key-value 对时,需要根据 key 对节点进行排序。TreeMap 可以保证所有的 key-value 对处于有序状态。TreeMap 有两种排序方式。

  • 自然排序:TreeMap 的所有 key 必须实现 Comparable 接口,而且所有的 key 应该是同一个类的对象,否则抛出 ClassCastException 异常。
  • 定制排序:创建 TreeMap 的 key ,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有key进行类似排序。采用定制排序时不要求 Map 的 key 实现 Comparable 接口。

Set 集合

Set 集合与 Collection 基本相同,没有提供任何额外的方法,只是 Set不允许包含重复元素。

HashSet

**底层数据结构是哈希表(是一个元素为链表的数组) **,大多数使用 Set 集合就是使用这个实现类。HashSet **按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。**以下是 HashSet 的特点:

  • 不能保证元素的排列顺序,顺序可能与添加顺序不同。
  • HashSet 不是同步的。
  • 集合元素值可以为 null
  • 如果两个对象通过 equals() 方法比较返回 true但 hashCode() 方法返回不同的 hashCode 值,此时,这会导致 HashSet 会把这两个对象保存在 Hash 表的不同位置,从而使两个对象都能添加成功,这与Set集合的规则冲突了
  • 果两个对象通过 equals() 方法比较返回 false,但 hashCode() 方法返回相同的 hashCode 值,因为 HashCode 值相同,HashSet 将试图把它们保存在同一个位置,又不可能一个位置发那个纸两个元素的,实际上会在这个位置使用链式结构来保存多个对象,但 HashSet 访问集合元素时,是根据 hashCode 值来进行定位的,如果这样存储的话,会导致性能下降。
  • 在使用 HashSet 保存时,应该尽量保证两个对象通过 equals() 方法比较返回 true 时,它们的 hashCode() 方法返回值也相等。

TreeSet

  • 底层数据结构是红黑树(是一个自平衡的二叉树)
  • 保证元素的排序方式
  • 可以实现排序功能
  • 非线程同步

LinkedHashSet

  • 底层数据结构由哈希表和链表组成。
  • LinkedHashSet 集合也是根据 hashCode 值来决定元素的存储位置。
  • 使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的,即LinkedHashSet 是有序的。
  • 因为需要维护元素的插入顺序,因此性能低于 HashSet 的性能;
  • 但在迭代访问 Set 里全部元素时会有很好的性能。

Collections

Java 提供了一个操作 Set、List 和 Map 等集合类的工具类:Collections,该工具提供了大量方法对集合元素进行排序、查询、修改等操作,还提供将集合对象设置为不可变、对集合对象实现同步控制等方法。

排序操作

  • void reverse(List list):反转指定 List 集合中元素的顺序
  • void shuffle(List list):对 List 集合元素进行随机排序
  • void sort(List list):根据元素的自然顺序对指定 List 集合的元素按升序排序
  • void sort(List list,Comparator c):根据指定 Comparator 产生的顺序对 List 集合元素进行排序
  • void swap(List list,int i,int j):将指定 List 集合中的 i 和 j 处的元素进行交换
  • void rotate(List list,int distance):当 distance 为正数时,将 list 集合的后 distance 个元素“整体”移到前面;当 distance 为负数时,将 list 集合的前 distance 个元素“整体”移到后面。该方法不会改变集合长度。
public static void main(String[] args) {
        ArrayList nums = new ArrayList();
        nums.add(2);
        nums.add(1);
        nums.add(3);
        nums.add(4);
        //输出nums,和后面的做对比
        System.out.println(nums);
        //反转nums
        Collections.reverse(nums);
        System.out.println(nums);
        //按自然顺序进行排序
        Collections.sort(nums);
        System.out.println(nums);
        //随机排序,每次输出都不一样
        Collections.shuffle(nums);
        System.out.println(nums);

    }
/*
res:
[2, 1, 3, 4]
[4, 3, 1, 2]
[1, 2, 3, 4]
[2, 4, 3, 1]

查找、替换操作

  • int binarySearch(List list,Object key):使用二分搜索法搜索指定 List 集合,获得指定对象在 list 集合中的索引。使用该方法的前提是 List 中的元素已经处于有序状态。
  • Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素。
  • Object max(Collection coll,Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最大元素。
  • Object min(Collection coll):根据元素的自然顺序,返回给定集合中的最小元素。
  • Object min(Collection coll,Comparator comp):根据Comparator 指定的顺序,返回给定集合中的最小元素。
  • void fill(List list,Object obj):使用 指定元素 obj 替换指定 List 集合中的所有元素。
  • inf frequency(Collection c,Object o):返回指定集合中指定元素的出现次数
  • int indexOfSubList(List source,List target):返回子 List 对象在父 List 对象中第一次出现的位置索引;如果父没有出现这样的子 List 则返回-1 。
  • int lastIndexOfSubList(List source,List target):返回子 List 对象在父 List 对象中最后一次出现的位置索引;如果父没有出现这样的子 List 则返回-1 。
  • boolean replaceAll(List list, Object oldVal,Object newVal):使用一个新值 newVal 替换 List 对象的所有旧值 oldVal 。
public static void main(String[] args) {
        ArrayList nums = new ArrayList();
        nums.add(2);
        nums.add(1);
        nums.add(3);
        nums.add(4);
        nums.add(1);
        //输出nums,和后面的做对比
        System.out.println(nums);
        //输出集合nums 的最大值和最小值
        System.out.println(Collections.max(nums));
        System.out.println(Collections.min(nums));
        //将nums中的所有1替换成0
        Collections.replaceAll(nums,1,0);
        System.out.println(nums);
        //判断0在集合中出现的次数
        System.out.println(Collections.frequency(nums, 0));
        //先对nums进行排序,然后使用二分法查找3,如果未找到则返回负值
        Collections.sort(nums);
        System.out.println(Collections.binarySearch(nums, 3));
    }
/*res:
[2, 1, 3, 4, 1]
4
1
[2, 0, 3, 4, 0]
2
3

同步控制

Collections 类中提供了多个 synchronizedXxx() 方法,该方法可以指定集合包装成线程同步的集合,从而解决线程安全问题。
Java 常见的集合中,HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap 和 TreeMap 都是线程不安全的。如果有多个线程访问它们,而且有超过一个线程试图修改它们,则存在线程安全问题。
下面示范用 Collections 提供的方法创建线程安全的集合对象:

public static void main(String[] args) {
        Collection c = Collections
                .synchronizedCollection(new ArrayList<>());
        List list = Collections.synchronizedList(new ArrayList<>());
        Set s = Collections.synchronizedSet(new HashSet());
        Map m = Collections.synchronizedMap(new HashMap());
    }

集合进阶

CopyOnWriteArrayList(Set)介绍

一般来说,我们会认为:CopyOnWriteArrayList是同步List的替代品,CopyOnWriteArraySet是同步Set的替代品。
无论是Hashtable–>ConcurrentHashMap,还是说Vector–>CopyOnWriteArrayList。JUC下支持并发的容器与老一代的线程安全类相比,总结起来就是加锁粒度的问题
Hashtable、Vector加锁的粒度大(直接在方法声明处使用synchronized)
• ConcurrentHashMap、CopyOnWriteArrayList加锁粒度小(用各种的方式来实现线程安全,比如我们知道的ConcurrentHashMap用了cas锁、volatile等方式来实现线程安全…)

思维导图总结

(来源: Java 3y)在这里膜拜一下大佬!太顶了!因为没法写的那么好,所以直接贴上大佬的了~
集合总结篇.xmind