史上最全的集合框架讲解 ----- Java 集合框架 (1)---- 概述

6 篇文章 0 订阅
4 篇文章 0 订阅

引言

此篇开始集合框架的学习,集合是我们在 Java 编程中相当常用的一个数据结构集。在看这个集合系列之前,希望你对 Java 中一些常见的集合有初步的了解,这样的话这个系列对你来说就没有很大的难度了,当然,如果你没有任何关于集合的基础也没有关系,我会尽力将知识点写的简单一些。请小伙伴伴们跟随我的脚步,一起走进集合框架的世界吧!!!

在这里插入图片描述
概述

什么是集合??
集合就是用于存储数据的容器,集合在我们生活中无时无刻存在。

举例:
 • 新闻列表
 • 就业喜报
 • 就业明星
 • 邮件列表
 • 购物车

当我们需要将一些相同结构的个体整合在一起时,就可以考虑使用集合了 。

那么肯定就会有小伙伴问了:为什么不使用数组,而使用集合呢???

集合的特点主要有如下两点:

   • 对象封装数据,对象多了也需要存储。集合用于存储对象。

   • 对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的。

集合和数组的区别:

  • 数组是固定长度的;集合可变长度的。
  • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
  • 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

数据结构:就是容器中存储数据的方式。

  • 对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。
  • 集合容器在不断向上抽取过程中,出现了集合体系。在使用一个体系的原则:参阅顶层内容。建立底层对象。

使用集合框架的好处:

  • 容量自增长。
  • 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量。
  • 允许不同 API 之间的互操作,API之间可以来回传递集合。
  • 可以方便地扩展或改写集合,提高代码复用性和可操作性。
  • 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。

常用的集合类有哪些?

Map接口和Collection接口是所有集合框架的父接口:

  • Collection接口的子接口包括:Set接口和List接口
  • Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  • Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
    在这里插入图片描述

Java 容器分为 CollectionMap 两大类,Collection集合的子接口有SetListQueue三种子接口。我们比较常用的是SetListMap接口不是collection的子接口。

Collection集合主要有List和Set两大接口:

  • List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayListLinkedListVector
  • Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSetLinkedHashSet 以及 TreeSet
  • Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。Map 的常用实现类:HashMapTreeMapHashTableLinkedHashMapConcurrentHashMap

集合框架底层数据结构:

Collection:

1、List

  • Arraylist(有序不唯一,线程不安全): Object数组。
  • Vector(有序不唯一,线程安全): Object数组。
  • LinkedList(有序不唯一,线程不安全): 双向循环链表。

2、Set

  • HashSet(无序唯一,线程不安全):基于 HashMap 实现的,底层采用 HashMap 来保存元素。
  • LinkedHashSet(有序唯一,线程不安全) LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
  • TreeSet(自然排序唯一,线程不安全): 红黑树(自平衡的排序二叉树)。

Map:

  • HashMap(无序唯一,线程不安全): JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突),JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
  • LinkedHashMap(有序唯一,线程不安全):LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
  • HashTable(无序唯一,线程安全): 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap(自然排序唯一,线程不安全): 红黑树(自平衡的排序二叉树)

附上图:
在这里插入图片描述

在这里插入图片描述
线程安全分类:

1、线程安全

  • vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
  • statck:堆栈类,先进后出。
  • hashtable:就比hashmap多了个线程安全,全局锁。
  • CopyOnWriteArrayList(JDK 1.5)。
  • ConcurrentHashMap(JDK 1.5),分段锁。
  • enumeration:枚举,相当于迭代器。

2、线程不安全:

  • ArrayList
  • LinkedList
  • Queue(队列)
  • Deque(双端队列) (Queue 和 Deque 是两个接口,其实现是 LinkedList,所以也是非线程安全的)。
  • HashMap
  • TreeMap
  • LinkedHashMap
  • WeakHashMap
  • IdentifyHashMap
  • HashSet
  • TreeSet
  • LinkedHashSet

关于Key和Value能否为null的问题:
在这里插入图片描述
补充:

HashMap:允许keyvalue都为null,但key只能有一个为nullvalue可以有多个为null一个相同的value可以对应多个不同的key

HashSet: 允许元素为null,但是也只能有一个为null

迭代器 Iterator 是什么?

Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。

Iterator 怎么使用?有什么特点?

Collection 接口继承了 Iterable 接口。我想大家都应该用过 Java 中的 for each 语句吧。不知道大家有没有想过为什么对于一些数据结构(数组、ArrayList 等)可以使用 for each 语句去遍历它,其实就是通过这个 Iterable 接口来实现的,在这个接口中有一个用于产生 Iterator (迭代器)对象的 iterator() 方法:

/**
 * Returns an iterator over elements of type {@code T}.
 * @return an Iterator.
 */
Iterator<T> iterator();

可以看到这个方法返回一个 Iterator 的泛型对象,Iterator 也是一个接口,也就是迭代器,其作用就是提供了统一的方法接口来方便我们遍历容器。即我们可以通过一个集合提供的迭代器对象来遍历这个集合中的元素。同样的我们把提供了迭代器遍历元素的对象称为可迭代对象。如果你记得设计模式中的相关知识的话会发现 Collection 接口在元素遍历的设计上采用迭代器的设计模式,我们来具体看看:

迭代器 Iterator

我们来看看这个接口的定义:

public interface Iterator<E> {
    /**
     * 如果这个可迭代对象(集合)中还有下一个元素,那么返回 true,否则返回 false
     */
    boolean hasNext();

    /**
     * 返回可迭代对象(集合)中的下一个元素,
     * 如果没有下一个元素,方法应该抛出一个 NoSuchElementException 异常
     */
    E next();

    /**
     * 移除集合中最后一次访问的(最后一次调用 next 方法得到的)元素,
     * 如果这个方法在第一次调用 next 方法之前调用,或者被连续调用,
     * 那么方法应该抛出一个 IllegalStateException 异常,
     * 默认实现是抛出一个 UnsupportedOperationException 异常,即不支持的操作
     */
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

当我们得到了一个集合对象提供的 Iterator 对象之后,一个典型的遍历这个集合的对象元素的代码块就是:

Iterator<T> it = obj.iterator();
// 如果集合对象有下一个元素,就遍历元素
while(it.hasNext()) {
    // 得到并打印出集合的下一个元素
    System.out.print(it.next().toString() + " ");
}

我们可以看到,List 接口和 Set 接口都是直接继承了 Collection 接口,那么就意味着线性集合类型 (List)和集合类型(Set)中的元素都是可以通过 for each 语句来进行遍历的,而对于 Map 接口来说,其并没有继承 Iterable 接口,因此对于映射类型,我们不能直接通过 for each 进行遍历。当然,对于映射类型元素的遍历,我们另有方法,在之后的文章中我们再一起探讨。在上图中我们还注意到对于 List 接口来说,它是可以产生一个 ListIterator 对象的,而这个接口也是继承于 Iterator 接口,我们可以看看这个接口中多了什么方法:

public interface ListIterator<E> extends Iterator<E> {

    boolean hasNext();

    E next();

    /**
     * 判断当前元素之前是否还有元素,即类似于反方向的 hasNext 方法
     */
    boolean hasPrevious();

    /**
     * 返回前一个元素,如果前面没有元素,那么抛出 NoSuchElementException 异常,
     * 类似于反方向的 next 方法
     */
    E previous();

    /**
     * 下一个元素在集合中的下标位置
     */
    int nextIndex();

    /**
     * 前一个元素在集合中的下标位置
     */
    int previousIndex();

    /**
     * 移除集合中上一次调用 next 方法所返回的元素
     */
    void remove();

    /**
     * 将上一次通过 next 方法 / previous 方法访问的元素替换为参数指定的对象
     */
    void set(E e);

    /**
     * 添加一个元素到上一次通过 next 方法访问的元素之后,
     * 操作完成后,通过 previous 方法可以访问到该元素
     */
    void add(E e);
}

Iterator 和 ListIterator 有什么区别?

  • Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
  • Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
  • ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素替换一个元素获取前面或后面元素的索引位置。

如何边遍历边移除 Collection 中的元素?

边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:

Iterator<Integer> it = list.iterator();
while(it.hasNext()){
   *// do something*
   it.remove();
}

一种最常见的错误代码如下:

for(Integer i : list){
   list.remove(i)
}

运行以上错误代码会报 ConcurrentModificationException 异常。请参考博客:集合遍历remove时ConcurrentModificationException异常

CopyOnWriterArrayList 详解

弱一致性的迭代器:
所谓弱一致性是指返回迭代器后,其他线程对list的增删改查对迭代器是不可见的:

// 演示多线程下迭代器的弱一致性结果
public class copylist {
    private static volatile CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
    public static void main(String[] args) throws InterruptedException {
        arrayList.add("hello");
        arrayList.add("alibaba");
        arrayList.add("welcome");
        arrayList.add("to");
        arrayList.add("hangzhou");
 
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                // 修改list中下标为1的元素为ali
                arrayList.set(1, "ali");
                // 删除元素
                arrayList.remove(2);
                arrayList.remove(3);
            }
        });
        // 保证在修改线程启动前获取迭代器
        Iterator<String> itr = arrayList.iterator();
        // 启动线程
        threadOne.start();
        // 等待子线程执行完毕
        threadOne.join();
        while(itr.hasNext()) {
            System.out.println(itr.next());
        }
    }
}

执行程序:

hello
alibaba
welcome
to
hangzhou
 
Process finished with exit code 0

从输出结果我们知道,在子线程里面进行的操作一个都没有生效,这就是迭代器弱一致性的体现。需要注意的是,获取迭代器的操作必须在子线程操作之前进行。

遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?

遍历方式有以下几种:

  • for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
  • 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
  • foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。

最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。

  • 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。
  • 如果没有实现该接口,表示不支持 Random Access,如LinkedList。

推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。

到这里,Java 集合系列的第一篇就结束了,我们在这里一起看了一下 Java 中集合的分类,大体分为三大类:ListSetMap ,每一类的集合适用于不同的场景,同时采用不同的方法实现。接下来先从 List 接口开始,从下一篇开始我们将一起具体的看一下实现了 List 接口的相关类。

好了。如果博客中有什么不正确的地方,还请多多指点。如果这篇文章对您有帮助,请不要吝啬您的赞,欢迎继续关注本专栏。

谢谢观看。。。

感谢博主大佬,昵称为:Hiro的支持和昵称为:ThinkWon的支持。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值