Java集合详解
开篇解答 c++的stl容器和java集合的区别 以及相识之处
(好了 总结一句 一个是java写的 一个是c++写的 很好 就是这样 皮一手)
集合和数组的区别
一、数组声明了它容纳的元素的类型,而集合不声明。
二、数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
三、数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
四、数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
java集合分类
Java 集合类型分为 Collection 和 Map,它们是 Java 集合的根接口,这两个接口又包含了一些子接口或实现类。图 1 和图 2 分别为 Collection 和 Map 的子接口及其实现类。
黄色块为集合的接口,蓝色块为集合的实现类
java集合类 分点做简单介绍 以及使用 介绍 优势以及劣势 以及 基本知识点
Conllection
Collection 接口是 List、Set 和 Queue 接口的父接口,通常情况下不被直接使用。Collection
接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作。定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue
集合。
List
这里 我们介绍一下 Arraylist和LinkedList ,stack和vector 不做过多介绍 具体用法 和stl容器的差不多。
如何使用list接口
在Java中,必须导入 java.util.List 包才能使用List。
List<Integer> numbers =newLinkedList<>();
LinkedList<Integer> numbers =newLinkedList<>();
在这里,我们已经创建Vector,ArrayList和LinkedList类的对象。现在这些对象就可以使用List接口的功能。
List方法
List接口包括Collection接口的所有方法。 这是因为Collection是List的超级接口。
Collection接口中还提供了一些常用的List接口方法:
add() - 将元素添加到列表
addAll() - 将一个列表的所有元素添加到另一个
get() - 有助于从列表中随机访问元素
iterator() - 返回迭代器对象,该对象可用于顺序访问列表的元素
set() - 更改列表的元素
remove() - 从列表中删除一个元素
removeAll() - 从列表中删除所有元素
clear() - 从列表中删除所有元素(比removeAll()效率更高)
size() - 返回列表的长度
toArray() - 将列表转换为数组
contains() - 如果列表包含指定的元素,则返回true
list接口种的集合 通用!
List接口的实现
1.LinkedList
packagecom.example.demo;importjava.util.ArrayList;importjava.util.LinkedList;importjava.util.List;importjava.util.Vector;publicclass working {publicstaticvoidmain(String[] args){//使用LinkedList类创建列表List<Integer> numbers =newLinkedList<>();//将元素添加到列表
numbers.add(1);
numbers.add(2);
numbers.add(3);System.out.println("List: "+ numbers);//从列表中访问元素int number = numbers.get(2);System.out.println("访问元素: "+ number);//使用indexOf()方法int index = numbers.indexOf(2);System.out.println("位置3的元素是 "+ index);//从列表中删除元素int removedNumber = numbers.remove(1);System.out.println("删除元素: "+ removedNumber);//将列表转为数组Object[] array = numbers.toArray();System.out.print("剩余数组元素为:");for(int i=0;i<array.length;i++)System.out.print(array[i]+" ");//换行System.out.println();//从列表中查询某数System.out.println("能不能从数组中查询到1 "+numbers.contains(1));}}
(一)LinkedList的底层是什么?
双向链表
让我们来看看底层源码
privatevoidlinkFirst(E e){finalNode<E> f = first;finalNode<E> newNode =newNode<>(null, e, f);
first = newNode;if(f ==null)
last = newNode;else
f.prev = newNode;
size++;
modCount++;}/**
* Links e as last element.
*/voidlinkLast(E e){finalNode<E> l = last;finalNode<E> newNode =newNode<>(l, e,null);
last = newNode;if(l ==null)
first = newNode;else
l.next = newNode;
size++;
modCount++;}
(二)LinkedList能不能作为队列使用?
开头就说了 因为LinkedList是双向循环链表 所以它也可以当成队列,栈和双端队列来使用
首先我们先来说一下LinkedList关于队列操作的源码。
队列的基本方法
//定义LinkedList<Integer> queue =newLinkedList<Integer>();//添加元素
queue.add(1);//删除队列头元素
queue.poll();//获取队列头元素,不删除
queue.peek();
演示:
首先定义就不说了 直接看源码:
.add()//添加元素
publicbooleanadd(E e){linkLast(e);//添加在队尾returntrue;}
.poll()//删除队列头元素
// 删除并返回第一个节点// 若LinkedList的大小为0,则返回nullpublicEpoll(){if(size ==0)returnnull;returnremoveFirst();}
.peek()//获取队列头元素,不删除
// 返回第一个节点// 若LinkedList的大小为0,则返回nullpublicEpeek(){if(size ==0)returnnull;returngetFirst();}
(三)LinkedList能不能作为栈使用?
栈的基本方法
//定义栈LinkedList<Integer> stack =newLinkedList<Integer>();//将元素插入到栈顶
stack.push(1)//取出栈顶的元素并删除栈顶的元素
stack.pop()//获取栈顶元素,不删除
stack.peek()
演示:
源码分析如下:
.push() //将元素插入到栈顶
// 将e插入到双向链表开头publicvoidpush(E e){addFirst(e);}
.pop() //取出栈顶的元素并删除栈顶的元素
// 删除并返回第一个节点publicEpop(){returnremoveFirst();}
.peek() //获取栈顶元素,不删除
// 返回第一个节点// 若LinkedList的大小为0,则返回nullpublicEpeekFirst(){if(size ==0)returnnull;returngetFirst();}
(四)LinkedList作为双端队列使用
双端队列的基本方法
//定义LinkedList<Integer> deque =newLinkedList<Integer>();
deque.addFirst();//在队列头部添加
deque.pollFirst();//删除头部第一个元素(等价于poll())
deque.peekFirst();//获取头部第一个元素(等价于peek())
deque.addLast(1);//在队列尾部添加(等价于add())
deque.pollLast();//删除尾部第一个元素
deque.peekLast();//获取尾部第一个元素
演示:
.addFirst() ; //在队列头部添加
// 将元素添加到LinkedList的起始位置publicvoidaddFirst(E e){addBefore(e, header.next);}
.pollFirst() ; //删除头部第一个元素(等价于poll())
// 删除并返回第一个节点// 若LinkedList的大小为0,则返回nullpublicEpollFirst(){if(size ==0)returnnull;returnremoveFirst();}
.peekFirst() ; //获取头部第一个元素(等价于peek())
// 返回第一个节点// 若LinkedList的大小为0,则返回nullpublicEpeekFirst(){if(size ==0)returnnull;returngetFirst();}
.addLast() ; //在队列尾部添加(等价于add())
// 将元素添加到LinkedList的结束位置publicvoidaddLast(E e){addBefore(e, header);}
.pollLast() ; // 删除并返回最后一个节点
// 删除并返回最后一个节点// 若LinkedList的大小为0,则返回nullpublicEpollLast(){if(size ==0)returnnull;returnremoveLast();}
.peekLast() ; //获取尾部第一个元素
// 返回最后一个节点// 若LinkedList的大小为0,则返回nullpublicEpeekLast(){if(size ==0)returnnull;returngetLast();}
总结
底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
课后问题:
为什么LinkedList是线程不安全的?
2.ArrayList
packagecom.example.demo;importjava.util.List;importjava.util.ArrayList;importjava.util.LinkedList;importjava.util.Vector;publicclass working {publicstaticvoidmain(String[] args){//使用ArrayList类创建列表List<Integer> numbers =newArrayList<>();// <里面不能使用基本类型> 这个在泛型的时候 会说到 要用包装类//将元素添加到列表
numbers.add(1);
numbers.add(2);
numbers.add(3);System.out.println("List: "+ numbers);//从列表中访问元素int number = numbers.get(2);System.out.println("访问元素: "+ number);//从列表中删除元素int removedNumber = numbers.remove(1);System.out.println("删除元素: "+ removedNumber);//将列表转为数组Object[] array = numbers.toArray();System.out.print("剩余数组元素为:");for(int i=0;i<array.length;i++)System.out.print(array[i]+" ");//换行System.out.println();//从列表中查询某数System.out.println("能不能从数组中查询到1 "+numbers.contains(1));}}
(一)ArrayList的底层是数组,数组的名称是什么?类型是什么?
名称是elementData,类型是Object[],所以ArrayList里面可以存放任意类型的元素。
(二)扩容机制
我们都知道Arraylist是一个动态的数组(说白了 也就是 可以根据内容来确定数组的大小)
ArrayList在JDK1.8与JDK1.7底层区别
JDK1.7:ArrayList像 饿汉式 ,直接创建一个初始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
JDK1.8:ArrayList像
懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
(三)ArrayList里面可以存null吗?
可以,ArrayList存储的类型是object,null属于object类型。
privatevoidprintList(){
List<Integer> dataList =newArrayList<>();
dataList.add(1);
dataList.add(null);
dataList.add(null);for(Integer d : dataList){
System.out.println(d);}
System.out.println("------------------------");for(Integer d : dataList){if(d != null){// 需要这个判断吗?
System.out.println(d);}}}
输出:
1
null
null
------------------------1
(四)ArrayList的底层是数组,它和数组有什么区别吗?
Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
Array 大小是固定的,ArrayList 的大小是动态变化的。
ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
总结
底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
课后问题: 为什么说ArrayList是线程不安全的?
面试常见问题
(一)ArrayList与Vector的区别?
(二)ArrayList与LinkedList的区别有哪些?
(三)在调用ArrayList的remove(int index)方法时,执行流程是怎样的?
(四)在ArrayList的增、删、改、查中,什么地方会修改modCount?
(五)ArrayList的时间复杂度是多少?
答案在以前写的Arraylist源码分析中:https://blog.csdn.net/qq_54729417/article/details/121066332
Queue
实现队列的类
由于Queue是一个接口,因此我们无法提供它的直接实现。
为了使用Queue的功能,我们需要使用实现它的类:
- ArrayDeque
- LinkedList
- PriorityQueue
队列数据结构的工作流程
在队列中,以先进先出的方式存储和访问元素。也就是说,从后面添加元素,从前面删除元素。
Queue的方法
Queue接口的一些常用方法是:
- add() - 将指定的元素插入队列。如果任务成功,则add()返回true,否则将引发异常。
- offer() - 将指定的元素插入队列。如果任务成功,则offer()返回true,否则返回false。
- element() - 返回队列的开头。如果队列为空,则引发异常。
- peek() - 返回队列的开头。 如果队列为空,则返回null。
- remove() - 返回并删除队列的头部。如果队列为空,则引发异常。
- poll() - 返回并删除队列的开头。 如果队列为空,则返回null。
相信大家都看到了 这里很多方法都和相似 那么 它们之间有什么区别呢?
offer,add区别:
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。
这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer()
返回的 false。
poll,remove区别:
remove() 和 poll() 方法都是从队列中删除第一个元素(head)。remove() 的行为与 Collection接口的版本相似,
但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。
peek,element区别:
element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element()
抛出一个异常,而 peek() 返回 null。
Set
如何使用set?
在Java中,必须导入java.util.Set包才能使用Set。
//使用HashSet实现SetSet<String> animals =newHashSet<>();
set方法
Set接口中还提供了Collection接口的一些常用方法:
- add() - 将指定的元素添加到集合中
- addAll() - 将指定集合的所有元素添加到集合中
- iterator() -返回一个迭代器,该迭代器可用于顺序访问集合中的元素
- remove() - 从集合中移除指定的元素
- removeAll() - 从存在于另一个指定集合中的集合中删除所有元素
- keepAll() -保留集合中所有还存在于另一个指定集合中的所有元素
- clear() - 从集合中删除所有元素
- size() - 返回集合的长度(元素数)
- toArray() - 返回包含集合中所有元素的数组
- contains() - 如果集合包含指定的元素,则返回true
- containsAll() - 如果集合包含指定集合的所有元素,则返回true
- hashCode() -返回哈希码值(集合中元素的地址)
Set集合运算
Java Set接口允许我们执行基本的数学集合运算,例如并集,交集和子集。
- Union - 为了得到两个集合x和y的并集,我们可以使用x.addAll(y)
- Intersection - 要获得两个集合x和y的交集,我们可以使用x.retainAll(y)
- Subset - 要检查x是否是y的子集,我们可以使用y.containsAll(x)
Set接口的实现
1.HashSet
packagecom.example.demo;importjava.util.*;publicclass working {publicstaticvoidmain(String[] args){//使用HashSet类创建集合Set<Integer> set1 =newHashSet<>();//将元素添加到set1
set1.add(2);
set1.add(3);System.out.println("Set1: "+ set1);//使用HashSet类创建另一个集合Set<Integer> set2 =newHashSet<>();//添加元素
set2.add(1);
set2.add(2);System.out.println("Set2: "+ set2);//两个集合的并集
set2.addAll(set1);System.out.println("并集是: "+ set2);}}
(一)HashSet的底层是什么?
HashMap
Set不能有重复的元素,HashMap不允许有重复的键
(二)HashSet的扩容机制是多少?HashSet默认的大小是多少?
HashSet和HashMap都是默认初始容量是16(jdk1.7的),但是jdk1.8做了优化,初始容量为0,第一次存元素的时候才扩容为16,加载因子是0.75,扩容为原来的2倍。而带LinkedHashSet和LinkedHashMap是链表不存在扩容的,HashSet:底层是数组+链表的结构。
(三)HashSet能不能存储重复的值
不能 为什么呢 ?
引用对象在存储的时候,会先根据hashcode值,存放到hash表的相应位置,如果该位置已经被占,就通过equals方法判断是否相同,相同就不存,不同就继续存放(并不会覆盖原来的)。
当然我们在用hashset存储对象时,重写hashCode和equals对象,就可以改变其原本的结果。
Map
在Java中,Map元素存储在键/值对中。 键是与各个值相关联的唯一值。
Map集合不能包含重复的键。并且,每个键都与一个值相关联。
Map接口维护3个不同的集合:
- 键集
- 值集
- 键/值关联(Map集合)的集合。
因此,我们可以分别访问键,值和关联。
如何使用map?
在Java中,我们必须导入java.util.Map包才能使用Map。导入包后,将按照以下方法创建map。
//使用HashMap类创建MapMap<Key,Value> numbers =newHashMap<>();
在上面的代码中,我们创建了一个名为numbers的Map。我们已经使用HashMap类来实现Map接口。
这里,
- Key - 用于关联map中每个元素(值)的唯一标识符
- Value - map中按键关联的元素
map方法
Map接口包括Collection接口的所有方法。这是因为Collection是Map的超级接口。
除了Collection接口中可用的方法之外,Map接口还包括以下方法:
- put(K,V) - 将键K和值V的关联插入到map中。如果键已经存在,则新值将替换旧值。
- putAll() - 将指定Map集合中的所有条目插入此Map集合中。
- putIfAbsent(K,V) - 如果键K尚未与value关联,则插入关联V。
- get(K) - 返回与指定键K关联的值。如果找不到该键,则返回null。
- getOrDefault(K,defaultValue) - 返回与指定键K关联的值。如果找不到键,则返回defaultValue。
- containsKey(K) - 检查指定的键K是否在map中。
- containsValue(V) - 检查指定的值V是否存在于map中。
- replace(K,V) - 将键K的值替换为新的指定值V。
- replace(K,oldValue,newValue) - 仅当键K与值oldValue相关联时,才用新值newValue替换键K的值。
- remove(K) - 从键K表示的Map中删除条目。
- remove(K,V) - 从Map集合中删除键K与值V相关联的条目。
- keySet() -返回Map集合中存在的所有键的集合。
- values() -返回一组包含在Map集合中的所有值。
- entrySet() -返回map中存在的所有键/值映射的集合。
map接口的实现
1.HashMap
packagecom.example.demo;importjava.util.*;publicclass working {publicstaticvoidmain(String[] args){//使用HashMap类创建mapMap<String,Integer> numbers =newHashMap<>();//将元素插入map集合
numbers.put("One",1);
numbers.put("Two",2);System.out.println("Map: "+ numbers);//map的键System.out.println("Keys: "+ numbers.keySet());//map的值System.out.println("Values: "+ numbers.values());//map的条目System.out.println("Entries: "+ numbers.entrySet());//从map集合中删除元素int value = numbers.remove("Two");System.out.println("被删除的值是: "+ value);}}
(一)底层是什么?
HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。 HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。 如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。
HashMap采⽤Entry数组来存储key-value对,每⼀个键值对组成了⼀个Entry实体,Entry类实际上是⼀个单向的链表结构,它具有Next指针,可以连接下⼀个Entry实体。 只是在JDK1.8中,链表⻓度⼤于8的时候,链表会转成红⿊树!
(二)为什么底层是这个?
由于我们的数组的值是限制死的,我们在对key值进行散列取到下标以后,放入到数组中时,难免出现两个key值不同,但是却放入到下标相同的格子中,此时我们就可以使用链表来对其进行链式的存放。
(三)冲突如何解决?
- 开放地址法
线性探测再散列
放入元素,如果发生冲突,就往后找没有元素的位置;
平方探测再散列
如果发生冲突,放到(冲突+1平方)的位置,如果还发生冲突,就放到(冲突-1平方)的位置;如果还有人就放到(冲突+2平方)的位置,以此类推,要是负数就倒序数。
- 拉链法 如果发生冲突,就继续往前一个元素上链接,形成一个链表,Java的hashmap就是这种方法。
优点
对于记录总数频繁可变的情况处理的较好;
结点是动态分配,不会造成内存的浪费;
删除记录比较方便,可是直接通过指针操作;
缺点
存储的记录是随机分布在内存中的,跳转访问时会带来额外的开销;
由于使用指针,记录不容易进行序列化操作;
- 再哈希 如果发生冲突,就用另一个方法计算hashcode,两次结果值不一样就不会发生hash冲突;
- 建立公共溢出区 将哈希表分为基本表和溢出表两部分,范式和基本表发生冲突的元素,一律填入溢出表。
不同集合的特点
List:元素有序,元素可重复,添加的元素放在最后(按照插入顺序保存元素)
ArrayList
数据结构为数组,访问快(可以直接通过下标访问),增删慢,未实现线程同步。
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法 并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
LinkedList
数据结构为链表,增删速度快,查询慢,未实现线程同步LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
Vector类
数据结构为数组,访问快(可以直接通过下标访问),增删慢,实现线程同步 Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出
ConcurrentModificationException。
Set:元素无序并且不允许重复元素
HashSet
数据结构为哈希表,元素无序、不重复,至多有一个null元素 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成。 特点如下 不能保证元素的排列顺序,顺序有可能发生变化 不是同步的 集合元素可以是null,但只能放入一个null当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
LinkedHashSet
数据结构是哈希表和链表,与HashSet相比访问更快,插入时性能稍微
LinkedHashSet继承自HashSet。同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
TreeSet
数据结构是二叉树(红黑树),元素可排序、不重复
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式:自然排序和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序 (1)TreeSet内的元素实现Comparable接口,重写该接口的compareTo(Object
obj)方法,以此确定排序。(元素必须实现该接口,否则程序会抛出异常)。
(2)当重写元素对应类的equals()方法时,应该保证该方法与compareTo(Objectobj)方法有一致的结果,即如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较结果应该也为0(即相等) 定制排序-- 自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现int compare(To1,To2)方法
Queue:元素有序,先进先出
ArrayDeque
数据结构为数组,双端队列,在队头队尾均可心插入或删除元素 实现了DeQueue接口。
DeQueue(Double-ended queue)继承了Queue接口,创建双向队列,灵活性更强,可以前向或后向迭代,
PriorityQueue
数据结构为优先级队列,元素不允许null,非同步
扩容机制
ArrayList 和Vector扩容机制
ArrayList 和Vector,底层都是Object数组,默认加载因子都是1(元素满了才扩展容量).默认容量都是10;但是ArrayList 在jdk1.8时默认为空,当添加元素时,才初始化为10个容量。ArrayList:新容量为原容量的1.5倍,Vector:新容量为原容量的2倍.
LinkedList扩容机制
LinkedList:没有扩容机制,因为其底层是双向链表结构。不存在数组的扩容一说,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。
HashSet和HashMap扩容机制
HashSet和HashMap都是默认初始容量是16(jdk1.7的),但是jdk1.8做了优化,初始容量为0,第一次存元素的时候才扩容为16,加载因子是0.75,扩容为原来的2倍。而带LinkedHashSet和LinkedHashMap是链表不存在扩容的,HashSet:底层是数组+链表的结构
面试题:
后续添加
版权归原作者 JWei_7 所有, 如有侵权,请联系我们删除。