Java集合(学习笔记)

本文深入探讨Java集合框架,包括List(ArrayList, LinkedList)的特性、HashMap的底层实现(JDK1.8前后对比)、线程安全问题、ConcurrentHashMap的优化策略,以及Set(HashSet, LinkedHashSet, TreeSet)的选择和比较。掌握这些,提升Java集合应用能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其他文章链接
Java基础
Java集合
多线程
JVM
MySQL
Redis
docker
计算机网络
操作系统



一.概述

1.常见的集合

Java集合类主要由两个根接口CollectionMap派生出来的,Collection派生出了三个子接口:List、Set、Queue(Java5新增的队列),因此Java集合大致也可分成List、Set、Queue、Map四种接口体系。

在这里插入图片描述

  • List:存储的元素是有序的、可重复的。
  • Set:存储的元素是⽆序的、不可重复的。
  • Map:使⽤键值对(kye-value)存储,Key是⽆序的、不可重复的,value是⽆序的、可重复的,每个键最多映射到⼀个值。

2.线程安全/不安全的集合

线程安全

  • Hashtable:比HashMap多了个线程安全。
  • ConcurrentHashMap:是一种高效但是线程安全的集合。
  • Vector:比Arraylist多了个同步化机制。
  • Stack:栈,也是线程安全的,继承于Vector。

线程不安全

  • HashMap
  • Arraylist
  • LinkedList
  • HashSet
  • TreeSet
  • TreeMap

二.List

1.ArrayList的扩容机制

  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
  • 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

2.Arraylist 与 LinkedList

  1. 是否保证线程安全:ArrayList和LinkedList都是不同步的,也就是不保证线程安全;
  2. 底层数据结构:Arraylist底层使⽤的是Object 数组;LinkedList底层使⽤的是双向(不循环)链表数据结构;
  3. 插⼊和删除是否受元素位置的影响:①ArrayList采⽤数组存储,所以插⼊和删除元素的时间复杂度受元素位置的影响。②LinkedList采⽤链表存储,所以对于插⼊链表头,remove()删除头元素时间复杂度不受元素位置的影响,近似O(1);如果是要在指定位置i插⼊和删除元素的话,时间复杂度近似为o(n))因为需要先移动到指定位置再插⼊。
  4. 是否⽀持快速随机访问:LinkedList不⽀持⾼效的随机元素访问,⽽ArrayList⽀持。快速随机访问就是通过元素的序号快速获取元素对象。
  5. 内存空间占⽤:ArrayList的空间浪费主要体现在在list列表的结尾会预留⼀定的容量空间,⽽LinkedList的空间花费则体现在它的每⼀个元素都需要消耗⽐ArrayList更多的空间(因为除了要存放数据,还要存放直接后继、直接前驱)。

补充:RandomAccess 接⼝
RandomAccess接⼝中什么都没有定义,用来标识实现这个接⼝的类具有随机访问功能。
ArrayList 实现了 RandomAccess 接⼝, ⽽ LinkedList 没有实现。 ArrayList 底层是数组,⽽ LinkedList 底层是链表。数组天然⽀持随机访 问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不⽀持快速随机访问。ArrayList 实现了 RandomAccess 接 ⼝,就表明了他具有快速随机访问功能。

3.ArrayList 与 Vector

  • Vector是线程安全的,ArrayList不是线程安全的。其中,Vector在关键性的方法前面都加了synchronized关键字,来保证线程的安全性。
  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍,这样ArrayList就有利于节约内存空间。

4.Array 与 ArrayList

  • Array可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
  • Array大小是固定的,ArrayList 的大小是动态变化的。
  • ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

5.哈希冲突解决办法

  1. 开放地址方法
    线性探测
    平方探测:按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
    伪随机探测:按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。
  2. 再哈希法(双重散列,多重散列):提供多个不同的hash函数,当R1=H1(key1)发生冲突时,再计算R2=H2(key1),直到没有冲突为止。这样做虽然不易产生堆集,但增加了计算的时间。
  3. 链式地址法:将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。(HashMap的哈希冲突解决方法
  4. 建立公共溢出区:将哈希表分为基本表和溢出表,将发生冲突的都存放在溢出表中。

三.Map

1. HashMap的底层实现

1.1 JDK1.8及之前

HashMap底层是数组和链表结合在⼀起使⽤也就是链表散列。HashMap通过key的hashCode经过扰动函数处理过后得到hash值,然后通过(n-1)&hash判断当前元素存放的位置(这⾥的n指的是数组的⻓度),如果当前位置存在元素的话,就判断该元素与要存⼊的元素的hash值以及key是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是HashMap的hash⽅法。使⽤hash⽅法也就是扰动函数是为了防⽌⼀些实现⽐较差的hashCode()⽅法换句话说使⽤扰动函数之后可以减少碰撞。

1.2 JDK1.8之后

JDK1.8之后在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。

链表(寻址时间复杂度为O(N))
红⿊树(寻址时间复杂度为O(log(N)))

2.解决hash冲突的时候,不直接用红黑树,而选择先用链表,再转红黑树的原因

因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于8个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于8个的时候,红黑树搜索时间复杂度是O(logn),而链表是O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。
因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。

3.HashMap 的put方法流程(JDK1.8)

  1. 首先根据key的值计算hash值,找到该元素在数组中存储的下标;
  2. 如果数组是空的,则调用resize进行初始化;
  3. 如果没有哈希冲突直接放在对应的数组下标里;
  4. 如果冲突了,且key已经存在,就覆盖掉value;
  5. 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;
  6. 如果冲突后是链表,判断该链表是否大于8,如果大于8并且数组容量小于64,就进行扩容;如果链表节点大于8并且数组的容量大于64,则将这个结构转换为红黑树;否则,链表插入键值对,若key存在,就覆盖掉value。
    在这里插入图片描述

4.HashMap 的扩容机制

HashMap在容量超过负载因子所定义的容量之后,就会扩容。Java里的数组是无法自动扩容的,方法是将HashMap的大小扩大为原来数组的两倍,并将原来的对象放入新的数组中。

4.1 HashMap指定初始值大小

指定初始值大小应为2的幂。如果指定的初始值不是2的幂,则HashMap的容量为大于指定初始值的2的幂;如果不指定,容量默认为16。

4.2 什么时候才需要扩容

  • 在首次调用put方法的时候,初始化数组table
  • 当HashMap中的元素个数超过数组大小(数组长度)*loadFactor(负载因子)时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)是0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中的元素个数超过16×0.75=12(这个值就是阈值或者边界值threshold值)的时候,就把数组的大小扩展为2×16=32,即扩大一倍,然后重新计算每个元素在数组中的位置(这是一个非常耗性能的操作)。
  • 当HashMap中的其中一个链表的对象个数如果达到了8个,此时如果数组长度没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链表会变成红黑树,节点类型由Node变成TreeNode类型。当然,如果映射关系被移除后,下次执行resize方法时判断树的节点个数低于6,也会再把树转换为链表。

5.HashMap的⻓度为什么是2的幂次⽅

  为了能让 HashMap 存取⾼效,尽量较少碰撞,也就是要尽量把数据分配均匀。Hash 值的范围值-2147483648到2147483647,前后加起来⼤概40亿的映射空间,只要哈希函数映射得⽐较均匀松散,⼀般应⽤是很难出现碰撞的。但问题是⼀个40亿⻓度的数组,内存是放不下的。所以这个散列值是不能直接拿来⽤的。⽤之前还要先做对数组的⻓度取模运算, 得到的余数才能⽤来要存放的位置也就是对应的数组下标。这个数组下标的计算⽅法是“ (n - 1) & hash ”。(n代表数组⻓度)。这也就解释了 HashMap 的⻓度为什么是2的幂次⽅。

这个算法应该如何设计呢?
  我们⾸先可能会想到采⽤%取余的操作来实现。重点来了:取余(%)操作中如果除数是2 的幂次则等价于与其除数减⼀的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次⽅;)。并且采⽤⼆进制位操作 &,相对于%能够提⾼运算效率,这就解释了 HashMap 的⻓度为什么是2的幂次⽅。

6.一般用什么作为HashMap的key

一般用Integer、String这种不可变类当HashMap当key,而且String最为常用。

  • 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就是HashMap中的键往往都使用字符串的原因。
  • 因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的重写了hashCode()以及equals()方法。

7.HashMap线程不安全的原因

在这里插入图片描述

  • 多线程下扩容死循环。JDK1.7中的HashMap使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此,JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题。
  • 多线程的put可能导致元素的丢失。多线程同时执行put操作,如果计算出来的索引位置是相同的,那会造成前一个key被后一个key覆盖,从而导致元素的丢失。此问题在JDK1.7和JDK1.8中都存在。
  • put和get并发时,可能导致get为null。线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题。此问题在JDK1.7和JDK1.8中都存在。

8.HashMap 和 Hashtable

  1. 线程是否安全:HashMap是⾮线程安全的,HashTable是线程安全的,因为HashTable内部的⽅法基本都经过synchronized修饰。(如果要保证线程安全的话就使⽤ConcurrentHashMap);
  2. 效率:因为线程安全的问题,HashMap要⽐HashTable效率⾼⼀点。另外,HashTable基本被淘汰,不要在代码中使⽤它;
  3. 对Nullkey和Nullvalue的⽀持:HashMap可以存储null的key和value,但null作为键只能有⼀个,null作为值可以有多个;HashTable不允许有null键和null值,否则会抛出NullPointerException;
  4. 初始容量⼤⼩和每次扩充容量⼤⼩的不同:①创建时如果不指定容量初始值,Hashtable默认的初始⼤⼩为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化⼤⼩为16。之后每次扩充,容量变为原来的2倍。创建时如果给定了容量初始值,那么Hashtable会直接使⽤你给定的⼤⼩,⽽HashMap会将其扩充为2的幂次⽅⼤⼩(HashMap中的tableSizeFor()⽅法保证)。也就是说HashMap总是使⽤2的幂作为哈希表的⼤⼩。
  5. 底层数据结构:JDK1.8以后的HashMap在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。Hashtable底层是数组+链表。

9.ConcurrentHashMap线程安全的具体实现⽅式/底层具体实现

9.1 JDK1.7

在这里插入图片描述
⾸先将数据分为⼀段⼀段的存储,然后给每⼀段数据配⼀把锁,当⼀个线程占⽤锁访问其中⼀个段数据时,其他段的数据也能被其他线程访问。

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。

Segment实现了ReentrantLock,所以Segment是⼀种可重⼊锁,扮演锁的⻆⾊。HashEntry⽤于存储键值对数据。

⼀个 ConcurrentHashMap ⾥包含⼀个 Segment 数组。 Segment 的结构和 HashMap 类似,是⼀种数组和链表结构,⼀个 Segment 包含⼀个 HashEntry 数组,每个 HashEntry 是⼀个链表结构的元素,每个 Segment 守护着⼀个 HashEntry 数组⾥的元素,当对 HashEntry 数组的数据进⾏修改时,必须⾸先获得对应的 Segment 的锁

9.2 JDK1.8

在这里插入图片描述

在数据结构上,JDK1.8中的ConcurrentHashMap选择了与HashMap相同的数组+链表+红黑树结构;在锁的实现上,抛弃了原有的Segment分段锁,采用CAS+synchronized实现更加低粒度的锁。将锁的级别控制在了更细粒度的哈希桶元素级别,也就是说只需要锁住这个链表头结点/红黑树的根节点,这样只要hash不冲突,就不会产⽣并发,效率得以提升。

10.JDK1.7与JDK1.8 中ConcurrentHashMap的区别

  • 数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
  • 保证线程安全机制:JDK1.7采用Segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
  • 锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
  • 链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
  • 查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。

11.ConcurrentHashMap 的 put 方法流程(JDK1.8)

  1. 根据key计算出hash值。
  2. 判断是否需要进行初始化。
  3. 定位到Node,拿到首节点f,判断首节点f:
    如果为null,则通过CAS的方式尝试添加。
    如果为f.hash=MOVED=-1,说明其他线程在扩容,参与一起扩容。
    如果都不满足,synchronized锁住f节点,判断是链表还是红黑树,遍历插入。
  4. 当在链表长度达到8的时候,数组扩容或者将链表转换为红黑树。

12.ConcurrentHashMap 的 get 方法是否要加锁

get 方法不需要加锁。
因为 Node 的元素 val 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。

这也是它比其他并发集合比如 Hashtable、用 Collections.synchronizedMap()包装的 HashMap安全效率高的原因之一。

Collections.synchronizedMap()包装的HashMap,也是对HashMap做的方法做了一层包装,里面使用对象锁来保证多线程场景下,线程安全,本质也是对HashMap进行全表锁。在竞争激烈的多线程环境下性能依然也非常差,不推荐使用!

13.get方法不需要加锁与volatile修饰的哈希桶有关吗

没有关系。
哈希桶 table用volatile修饰主要是保证在数组扩容的时候保证可见性。

14.ConcurrentHashMap 不支持 value 为 null 的原因

因为 ConcurrentHashMap是用于多线程的 ,如果map.get(key)得到了 null ,无法判断,是映射的value是 null ,还是没有找到对应的key而为 null ,这就有了二义性。

ConcurrentHashMap 中的key也不能为 null

15.ConcurrentHashMap的并发度是多少?

在JDK1.7中,并发度默认是16,这个值可以在构造函数中设置。如果自己设置了并发度,ConcurrentHashMap会使用大于等于该值的最小的2的幂指数作为实际并发度,也就是比如你设置的值是17,那么实际并发度是32。

16.ConcurrentHashMap 迭代器是强一致性还是弱一致性

与HashMap迭代器是强一致性不同,ConcurrentHashMap迭代器是弱一致性

ConcurrentHashMap的迭代器创建后,就会按照哈希表结构遍历每个元素,但在遍历过程中,内部元素可能会发生变化,如果变化发生在已遍历过的部分,迭代器就不会反映出来,而如果变化发生在未遍历过的部分,迭代器就会发现并反映出来,这就是弱一致性。

这样迭代器线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。

17.ConcurrentHashMap 和 Hashtable 的区别

  • 底层数据结构:JDK1.7的ConcurrentHashMap底层采⽤分段的数组+链表实现,JDK1.8采⽤的数据结构跟HashMap1.8的结构⼀样,数组+链表/红⿊⼆叉树。Hashtable和JDK1.8之前的HashMap的底层数据结构类似都是采⽤数组+链表的形式,数组是HashMap的主体,链表则是主要为了解决哈希冲突⽽存在的;
  • 实现线程安全的⽅式(重要):①在JDK1.7的时候,ConcurrentHashMap(分段锁)对整个桶数组进⾏了分割分段(Segment),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。到了JDK1.8的时候已经摒弃了Segment的概念,⽽是直接⽤Node数组+链表+红⿊树的数据结构来实现,并发控制使⽤synchronized和CAS来操作。(JDK1.6以后对synchronized锁做了很多优化)整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本;②Hashtable(同⼀把锁):使⽤synchronized来保证线程安全,效率⾮常低下。当⼀个线程访问同步⽅法时,其他线程也访问同步⽅法,可能会进⼊阻塞或轮询状态,如使⽤put添加元素,另⼀个线程不能使⽤put添加元素,也不能使⽤get,竞争会越来越激烈效率越低。
  • 效率:ConcurrentHashMap 的效率要高于Hashtable,因为Hashtable给整个哈希表加了一把大锁从而实现线程安全。而ConcurrentHashMap 的锁粒度更低,在JDK1.7中采用分段锁实现线程安全,在JDK1.8 中采用 CAS+Synchronized实现线程安全。

17.Hashtable的锁机制

Hashtable是使用Synchronized来实现线程安全的,给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞等待需要的锁被释放,在竞争激烈的多线程场景中性能就会非常差!
在这里插入图片描述

四.Set

1.HashSet如何检查重复

当把对象加⼊HashSet时,HashSet会先计算对象的hashcode值来判断对象加⼊的位置,同时也会与其他加⼊的对象的hashcode值作⽐较,如果没有相同的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调⽤equals()⽅法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加⼊操作成功。

hashCode()与equals():

  1. 如果两个对象相等,则hashcode⼀定也是相同的;
  2. 两个对象相等,对两个equals()⽅法返回true;
  3. 两个对象有相同的hashcode值,它们也不⼀定是相等的;
  4. 综上,equals()⽅法被覆盖过,则hashCode()⽅法也必须被覆盖;
  5. hashCode()的默认⾏为是对堆上的对象产⽣独特值。如果没有重写hashCode(),则该class的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)。

2.HashSet、LinkedHashSet 和 TreeSet

  • HashSet是Set接⼝的主要实现类,HashSet的底层是HashMap(所有value指向同一个key的HashMap),线程不安全的,可以存储null值;
  • LinkedHashSet是HashSet的⼦类,能够按照添加的顺序遍历;
  • TreeSet底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排序。

3.HashMap 和 HashSet

HashSet 底层就是基于 HashMap 实现的。 HashSet是实现了Set接口并且把数据作为K值,而V值一直使用一个相同的虚值来保存。
HashSet 的源码⾮常⾮常少,因为除了 clone() 、writeObject() 、readObject() 是HashSet ⾃⼰不得不实现之外,其他⽅法都是直接调⽤ HashMap 中的⽅法。

区别:

在这里插入图片描述

五.总结

1.Collection框架中实现比较要怎么做?

  1. 实体类实现Comparable接口,并实现 compareTo(T t) 方法,称为内部比较器。
  2. 创建一个外部比较器,这个外部比较器要实现Comparator接口的compare(T t1, T t2)方法。

2.Iterator 和 ListIterator

  • 遍历。使用Iterator,可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。使用ListIterator,只能遍历List实现的对象,但可以向前和向后遍历集合中的元素。
  • 添加元素。Iterator无法向集合中添加元素;而ListIteror可以向集合添加元素。
  • 修改元素。Iterator无法修改集合中的元素;而ListIterator可以使用set()修改集合中的元素。
  • 索引。Iterator无法获取集合中元素的索引;而使用ListIterator,可以获取集合中元素的索引。

3.集合框架底层数据结构总结

3.1 List

  • Arraylist :Object[]数组
  • Vector :Object[]数组
  • LinkedList :双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

3.2 Set

  • HashSet (⽆序,唯⼀): 基于 HashMap 实现的,底层采⽤ HashMap 来保存元素
  • LinkedHashSet : LinkedHashSet 是 HashSet 的⼦类,并且其内部是通过 LinkedHashMap 来实现的。类似 LinkedHashMap 其内部是基于 HashMap 实现, 不过还是有⼀点点区别的
  • TreeSet (有序,唯⼀): 红⿊树(⾃平衡的排序⼆叉树)

3.3 Map

  • HashMap:JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突⽽存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间
  • LinkedHashMap:LinkedHashMap继承⾃HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红⿊树组成。另外,LinkedHashMap在上⾯结构的基础上,增加了⼀条双向链表,使得上⾯的结构可以保持键值对的插⼊顺序。同时通过对链表进⾏相应的操作,实现了访问顺序相关逻辑。
  • Hashtable:数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突⽽存在的
  • TreeMap:红⿊树(⾃平衡的排序⼆叉树)

4.如何选用集合  

  主要根据集合的特点来选⽤,⽐如我们需要根据键值获取到元素值时就选⽤Map接⼝下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选⽤ConcurrentHashMap。
  当我们只需要存放元素值时,就选择实现Collection接⼝的集合,需要保证元素唯⼀时选择实现Set接⼝的集合⽐如TreeSet或HashSet,不需要就选择实现List接⼝的⽐如ArrayList或LinkedList,然后再根据实现这些接⼝的集合的特点来选⽤。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值