集合学习笔记——Collection 全家桶

news2025/2/27 23:09:40

Collection是我们日常开发中使用频率非常高的集合,它的主要实现有ListSet,区别是List是有序的,元素可以重复;Set是无序的,元素不可以重复,我们简单看下继承关系:

  1. List的实现类主要线程不安全的ArrayListLinkedList以及线程安全的CopyOnWriteArrayList;
  2. Set的实现类主要有线程不安全的HashSetTreeSet以及线程安全的CopyOnWriteArraySet

个人觉得,以上集合组件类其实并不难,所以我不打算分析源码,其中HashSetTreeSet底层使用分别是HashMap和TreeMap, 所以只要看下之前的文章就比较容易理解了。

1.ArrayList VS LinkedList

ArrayList:

  1. 底层是通过数组(内存地址连续)实现的,直接可以通过下标找到目标元素,时间复杂度是O(1),而LinkedList需要移动指针遍历每个元素,时间复杂度是O(N),所以 Arraylist查找元素的速度比Linkedlist快.
  2. Arraylist在新增和删除元素时,可能扩容和复制数组。而Linkedlist实例化对象只需要修改指针即可.
  3. Arraylist 可能会造成一定空间的浪费,因为它要预先开辟空间
  4. 默认的初始容量是10个,每次扩容为原来的1.5倍(当数组满了才会扩容,不像HashMap有扩容因子)

LinkedList:

  1. 底层是通过线性表(链表)(内存空间不连续)来实现的,是一个双向链表
  2. LinkedList不支持高效的随机访问,它需要移动指针遍历每个元素
  3. 没有初始容量,没有扩容机制

时间复杂度对比

操作数组链表说明
随机访问O(1)O(N)随机访问数组比链表快
头部插入O(N)O(1)插入:链表快于数组,因为数组要移动其他元素的位置
头部删除O(N)O(1)删除:链表快于数组,因为数组要移动其他元素的位置
尾部插入O(1)O(1)插入速度一样快,但是数组有可能是触发扩容动作
尾部删除O(1)O(1)删除速度一样快
指定位置插入O(N)O(N)数组:在第几个元素后面插入,后面的元素需要向后移动,链表:需要先找到第几个元素,然后修改指针指向操作

总体来说:ArrayList查询速度快,LinkedList插入删除速度快。

使用场景

  1. 如果应用程序对数据有较多的随机访问,ArrayList性能要优于LinkedList
  2. 如果应用程序有更多的插入或者删除操作,较少的随机访问,LinkedList性能要优于ArrayList
  3. 不过ArrayList的插入,删除操作也不一定比LinkedList慢,如果在ArrayList靠近末尾的地方插入,那么ArrayList只需要移动较少的数据(或者无需移动数据),此时二者耗时几乎差不多(LinkedList是双向链表,可以快速定位头尾部的节点)

2.HashSet VS TreeSet

HashSetTreeSet 都是元素不能重复的集合,其中TreeSet具有排序的功能。

HashSet源码分析

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    /**
     *
     * HashSet底层用的就是HashMap,只不过只使用了HashMap的key
     */
    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * 无参构造器:内部创建一个HashMap
     */
    public HashSet() {
        map = new HashMap<>();
    }

    /**
     * 参数是集合
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/0.75f) + 1, 16));
        addAll(c);
    }

    /**
     * 指定容量的构造器,这个容量将传递给HashMap
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * add元素,发现它确实是添加到了HashMap中
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    //.....省略其他代码
}
复制代码

不难发现,HashSet底层使用的就是HashMap,只不过是只使用了HashMapkey,根据HashMap的特点,key如果相同,则旧值覆盖新值,所以达到去重的效果。

TreeSet源码分析:

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    /**
     * 内存真正存储元素的组件:TreeMap, 只不过是只使用了key
     */
    private transient NavigableMap<E,Object> m;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * 空参数构造器:其内部创建了TreeMap, 但是只使用TreeMap的key
     */
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    /**
     * 可以传入一个比较器,这个比较器将交给TreeMap
     */
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

    /**
     * 添加元素:将添加到TreeMap中
     */
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
  }  
复制代码

不难发现,TreeSet底层使用的是TreeMap,只不过是只使用了TreeMapkey,根据TreeMap的特点,默认是按照自然排序(key必须要实现Comparable接口),或者指定比较器Comparator,自定义比较规则。

3.线程安全的CopyOnWriteArrayList

CopyOnWriteArraySet 底层使用的也是CopyOnWriteArrayList

先简单总结下它的底层原理:

  1. CopyOnWriteArrayList内部也是通过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行(写时复制的思想)
  2. 写操作会加锁,防止并发写入造成数据丢失的问题
  3. 写操作结束后会把原数组指向新数组
  4. CopyOnWriteArrayList允许在进行写操作的同时来读取数据,大大提高了读的性能,因此适合读多写少的场景(写多读少的话,会大量复制数组,非常耗费内存),但是CopyOnWriteArrayList可能读到的数据不是最新的数据,所以不适合实时性要求高的场景(数据不一致的问题)。

只能保证最终一致,不能保证实时一致

我们看下源码:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** 
     * 通过 ReentrantLock 来保证写时的线程安全
     */
    final transient ReentrantLock lock = new ReentrantLock();

    /** 
     * 底层仍然是使用数组来存储数据
     */
    private transient volatile Object[] array;
  

    /**
     * 读取数据,没有加锁,直接读就可以
     */
    public E get(int index) {
        return get(getArray(), index);
    }
    
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    /**
     * 添加元素(写)
     * 
     * 1.首先加锁
     * 2.获取原数组
     * 3.根据原数据复制一个新的数组
     * 4.然后对新数组进行写操作(这中间读的话,仍然读的是原数组)
     * 5.将新数组赋给原数组
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

     /**
     * 删除元素(写):和 add一样
     * 
     * 1.首先加锁
     * 2.获取原数组
     * 3.根据原数据复制一个新的数组
     * 4.然后对新数组进行写操作(这中间读的话,仍然读的是原数组)
     * 5.将新数组赋给原数组
     */
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
 } 
复制代码

源码并不复杂,就是写时复制的思想,我们简单用一张图来展示下:



好了,关于Collection集合下常用的组件就分析到这里吧,对比Map,这些就显得比较简单了,源码也很容易理解。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

推挽输出和开漏输出-三极管-mos管

一、推挽输出 1.1推挽输出的概念 推挽&#xff08;push-pull&#xff09;输出是由两个MOS或者三极管组成&#xff0c;两个管子始终保持一个导通&#xff0c;另一个截止的状态。 图1 推挽电路示意图 当输入高电平时&#xff0c;叫做推&#xff1b; 上管Q1导通&#xff0c;下管…

【目标检测】Faster R-CNN论文的讲解

目录&#xff1a;Faster R-CNN论文的讲解一、前言二、回顾Fast R-CNN三、引入Faster R-CNN四、Faster R-CNN的介绍4.1 框架结构4.2 RPN如何产生候选区域的4.3 损失函数4.4 训练候选框提取网络4.5 RPN和Fast R-CNN共享特征的方法4.5.1 交替训练法4.5.2 近似联合训练法一、前言 …

C语言——学生信息管理系统

目录 功能展示 界面展示 所有功能模块&#xff1a; 功能1&#xff1a;菜单模块&#xff08;显示功能菜单&#xff09; 功能2&#xff1a;增加学生信息 功能3&#xff1a;输出学生信息&#xff08;查看所有学习信息&#xff09; 功能4&#xff1a;修改学生信息 功能5&a…

python3-GUI概述及应用

目录一、什么是GUI二、Python GUIPySimpleGUI概述一、PySimpleGUI简介二、PySimpleGUI特征三、输出设备hello,world猜数字一、玩家猜数字二、电脑猜数字21点游戏一、21点游戏简介二、程序代码一、什么是GUI 图形用户界面&#xff08;Graphical User Interface&#xff0c;简称…

十六、CANdelaStudio深入-CDD与CDDT的差异(新建自定义服务)

本专栏将由浅入深的展开诊断实际开发与测试的数据库编辑,包含大量实际开发过程中的步骤、使用技巧与少量对Autosar标准的解读。希望能对大家有所帮助,与大家共同成长,早日成为一名车载诊断、通信全栈工程师。 本文介绍CANdelaStudio的CDD与CDDT的差异与新建自定义服务,欢迎…

数字图像处理(一)——什么是数字图像

一、什么是数字图像处理&#xff1f; 一副图像可以被定义为一个二维函数f(x,y)&#xff0c;其中x和y是空间平面坐标&#xff0c;而对任意一对空间坐标(x,y)处幅值f称为图像在该点的强度或者灰度。当x和y以及灰度值f是有限的离散数值时&#xff0c;我们称该图像为数字图像。像素…

排序算法简述

一、概述 常见的排序算法有冒泡排序、插入排序、选择排序、快速排序、归并排序、桶排序、基数排序&#xff0c;这些排序各自有各自的特点。按照时间时间复杂度可以分为 O(n^2):冒泡、插入、选择排序&#xff1b;O(nlogn):归并、快速排序&#xff1b;O(n):桶排序、计数排序、基…

[附源码]java毕业设计自治小区物业设备维护管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]Python计算机毕业设计房地产销售系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

SSM+Mysql实现的共享单车管理系统(功能包含分角色,登录、用户管理、服务点管理、单车管理、分类管理、学生信息管理、单车租赁、信息统计、系统设置等)

博客目录SSMMysql实现的共享单车管理系统实现功能截图系统功能使用技术代码完整源码SSMMysql实现的共享单车管理系统 本系统一个学校共享单车管理的项目&#xff0c;通过线上系统化的管理&#xff0c;可以为后续的运营以及单车的项目运转提供极大的帮助。 (文末查看完整源码) …

【计算机视觉(CV)】基于图像分类网络VGG实现中草药识别(二)

【计算机视觉&#xff08;CV&#xff09;】基于图像分类网络VGG实现中草药识别&#xff08;二&#xff09; 作者简介&#xff1a;在校大学生一枚&#xff0c;华为云享专家&#xff0c;阿里云专家博主&#xff0c;腾云先锋&#xff08;TDP&#xff09;成员&#xff0c;云曦智划项…

Graph (discrete mathematics)

In mathematics, and more specifically in graph theory, a graph is a structure amounting to a set of objects in which some pairs of the objects are in some sense “related”. The objects correspond to mathematical abstractions called vertices (also called n…

餐厅食材采购信息管理系统的设计与实现

摘 要 网络的广泛应用给生活带来了十分的便利。所以把餐厅食材采购信息管理与现在网络相结合&#xff0c;利用JSP技术建设餐厅食材采购信息管理系统&#xff0c;实现餐厅食材采购的信息化。则对于进一步提高餐厅食材采购信息管理发展&#xff0c;丰富餐厅食材采购信息管理经验…

SpringBoot SpringBoot 原理篇 3 核心原理 3.5 启动流程【4】【5】【6】

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇3 核心原理3.5 启动流程【4】【5】【6】3.5.1 看源码咯3.5.2 总结3 核心原理 …

剑指 Offer 10- I. 斐波那契数列

一、题目描述 写一个函数&#xff0c;输入 n &#xff0c;求斐波那契&#xff08;Fibonacci&#xff09;数列的第 n 项&#xff08;即 F(N)&#xff09;。斐波那契数列的定义如下&#xff1a; F(0) 0, F(1) 1 F(N) F(N - 1) F(N - 2), 其中 N > 1. 斐波那契数列由 0 和…

leetcode:6248. 统计中位数为 K 的子数组【问题转化 + 排序二分】

目录题目截图题目分析ac code总结题目截图 题目分析 找到k的位置然后一步步往左走&#xff0c;一步步往右走统计左边和右边的比当前k小的和比k大的lst [[small, big]]&#xff0c;分为left和right两部分可以先一侧的单独看small和big&#xff0c;找到big - small 0或者1的即…

NETCONF、RESTCONF和YANG

目录 一、NETCONF、RESTCONF和YANG是之间什么关系&#xff1f; 二、Netconf简介 2.1、一般使用工具&#xff1a;MG-Soft 简介 三、Netconf YANG 原理与实践 3.1、NETCONF协议 3.2、YANG建模语言 3.3、RESTCONF协议 网管协议&#xff1a; SNMP&#xff08;基于UDP&#…

C++员工考勤管理系统

目录 1 考勤管理系统的设计 1 1.1 需求分析 1 1.2 功能模块构成 1 1.3 数据库结构设计 2 2 考勤管理系统的实现 4 2.1 系统登陆功能的实现 4 2.2 基本信息管理模块 5 4.2.1 节假日信息管理 5 4.2.2 部门信息管理 6 4.2.3 员工信息管理 8 2.3 考勤管理模块 10 4.3.1 出勤信息管理…

怎么才能学会Python?

前言 新手小白学Python在还没有人带的情况下很容易半途而废&#xff0c;首先给大家总结一下我这两年Python的学习、开发经验遇到一些问题&#xff0c;大家首先得正视这些问题&#xff0c;因为超90%的人在初学Python时都会也遇到。 ①自学网上资料多&#xff0c;但质量参差不齐…

[报错解决](Error Creating bean with name ‘xxx‘)类问题解决思路

遇到Error Creating bean with name ’ 这类问题的解决思路 错误日志关键部分&#xff1a; org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name productHandler: Unsatisfied dependency expressed through field productMap…