集合
1.集合框架概述
-
集合 Collection The Collection in Java is a framework that provides an architecture to store and manipulate the group of objects.
数组同样也是对多个数据进行存储操作的结构(此时的存储指内存层面的此时,不涉及持久化的存储)
-
数组存储的特点
- 长度固定:一旦初始化完毕,其长度就不可修改
- 数组类型固定:数组定义好后,只能操作指定类型的数据
-
数组存储的弊端
- 长度不可修改:初始化后,同一个数组对象长度不可再修改
- 增删改效率低
- 缺乏属性或方法
- 无法满足无序、不可重复的需求:数组的存储特点是有序、可重复
-
集合的优点
- 动态长度
- 增删改效率高
- 相应属性或方法
- 有无序、不可重复的Set接口,也有有序、可重复的List接口
-
接口继承树
2. Collection接口
2.1 概述
Collection是单列数据,定义了存取一组对象的方法的集合。
Collection接口是List、Set和Queue接口的父接口,在JDK1.5之前,集合会丢掉容器中所有对象的数据类型,在JDK1.5加了泛型之后,集合可以记住容器中所有对象的数据类型。
2.2 方法
-
添加
add(Object obj)
addAll(Collection coll)
-
获取有效元素的个数
int size()
-
清空集合
void clear()
-
是否是空集合
boolean isEmpty()
-
是否包含某个元素
boolean contains(Object obj)
:是通过元素的equals方法来判断是否是同一个对象boolean containsAll(Collection c)
:也是调用元素的equals方法来比较的。比较集合中是否包含c集合中的所有元素 -
删除
boolean remove(Object obj)
:通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素boolean removeAll(Collection coll)
:取当前集合的差集 -
取两个集合的交集
boolean retainAll(Collection c)
:把交集的结果存在当前集合中,不影响c -
集合是否相等(注意顺序)
boolean equals(Object obj)
-
转成对象数组
Object[] toArray()
-
获取集合对象的哈希值
hashCode()
-
遍历
iterator()
:返回迭代器对象,用于集合遍历
2.3 集合和数组的转换
-
集合到数组:Collection的
toArray()
方法Collection coll1 = new ArrayList();
coll1.add(123);
coll1.add("abc");
coll1.add(new Person("Tom", 18));
Object[] os = coll1.toArray();
for (Object o : os) {
System.out.println(o);
}
/*Output
123
abc
Person{name='Tom', age=18}
*/ -
数组到集合:Arrays的
asList()
方法Collection coll2 = new ArrayList();
String[] str = new String[]{"AA", "BB", "CC"};
coll2 = Arrays.asList(str);
coll2.forEach(String -> System.out.println(String));
/*Output
AA
BB
CC
*/-
深入Arrays的asList()方法
首先查看asList()方法的源码
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}返回的ArrayList类是Arrays的私有类ArrayList,它同样实现了AbstractList接口
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
...
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
....
}可以看到,Arrays中的ArrayList和List接口的ArrayList类是不同的
String[] str = new String[]{"AA", "BB", "CC"};
//此时不再向上转型
ArrayList coll3 = new ArrayList();
coll3 = Arrays.asList(str);//报错
-
3.Iterator迭代器接口
3.1 概述
-
Iterator迭代器主要用于遍历Collection集合中的元素
在设计模式中,迭代器提供一种方式访问一个容器对象中各个元素,而又不暴露该对象的内部细节
-
Collection接口继承了java.lang.Iterable接口,该接口有一个Iterator()方法,每次调用Iterator()方法都会返回一个全新的Iterator对象
3.2 方法
Iterator方法
Modifier and Type | Method and Description |
---|---|
default void | forEachRemaining(Consumer action) Performs the given action for each remaining element until all elements have been processed or the action throws an exception. |
boolean | hasNext() Returns true if the iteration has more elements. |
E | next() Returns the next element in the iteration. |
default void | remove() Removes from the underlying collection the last element returned by this iterator (optional operation). |
Code
使用hasNext()、next()、remove()方法
public class IteratorTest {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Hello"));
coll.add(false);
Iterator iterator = coll.iterator();
//remove方法
while (iterator.hasNext()){
Object o = iterator.next();
if(o.equals(123)){
iterator.remove();
}
}
iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
/*Output:
456
Person{name='Jerry', age=20}
Hello
false
*/
}
}
3.3 遍历集合的四种方式
先构建一个集合,并添加测试数据
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Hello"));
coll.add(false);
方式一:使用Iterator结合while循环
Iterator iterator = coll.iterator();
//方式一:使用while循环
while(iterator.hasNext()){
System.out.println(iterator.next());
}
方式二:使用foreach语句,本质还是调用Iterator迭代器
for (Object o : coll){
System.out.println(o);
}
方式三:使用Iterator中的forEachRemaining()方法结合Lambda表达式
Iterator iterator = coll.iterator();
//方式三:使用forEachRemaining()方法
iterator.forEachRemaining((Object o)-> System.out.println(o));
//再次遍历输出为空
iterator.forEachRemaining((Object o)-> System.out.println(o));
方式四:使用集合中的forEach()方法结合Lambda表达式
//方式四:使用forEach()方法
coll.forEach((Object o)-> System.out.println(o));
//forEach()方法可多次遍历
coll.forEach((Object o)-> System.out.println(o));
方法引用:
coll.forEach(System.out::println);
参考博客:方法引用
4.Collection子接口之一:List接口
4.1 List接口概述
- List中元素可有序、可重复
4.2 List接口方法
List除了Collection接口外,还增加了一些根据索引来操作集合元素的
void add(int index, Object ele)
:在index位置插入ele元素boolean addAll(int index, Collection eles)
:从index位置开始将eles中 的所有元素添加进来Object get(int index)
:获取指定index位置的元素int indexOf(Object obj)
:返回obj在集合中首次出现的位置int lastIndexOf(Object obj)
:返回obj在当前集合中末次出现的位置Object remove(int index)
:移除指定index位置的元素,并返回此元素Object set(int index, Object ele)
:设置指定index位置的元素为eleList subList(int fromIndex, int toIndex)
:返回从fromIndex到toIndex位置的子集合
4.3 List三个实现类
- java.util.Arraylist:JDK1.2开始,最主要、最常用的实现类,底层使用Object[] elementData存储,线程不安全但效率高
- java.util.LinkedList:JDK1.2开始,一般使用在频繁增删的场景,底层使用双向链表存储
- java.util.Vector:JDK1.1开始,线程安全但效率低,底层也是使用Object[] elementData存储
4.3.1 List实现类之一:ArrayList
- JDK1.8之前:类似饿汉式,直接创建容量为10的Object数组,扩容时以原先数组的1.5倍起
- JDK1.8:类似懒汉式,第一次调用时才创建容量为10的Object数组,扩容时同JDK1.8之前
4.3.2 List实现类之二:LinkedList
底层是双向链表
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
4.3.3 List实现类之三:Vector
不常用
5. Collection子接口之二:Set接口
5.1 Set接口概述
5.1.1 Set特性
-
Set中元素无序、不可重复
- 无序性:Set的底层也是数组,它存储数据的规则是按照数据的哈希值来决定的,所以这也决定了元素存放位置的数组索引不等于添加顺序。
- 不可重复:保证添加的元素按照
equals()
方法判断时,不能返回true
-
Set判断两个对象相关不是用
==
,而是用equals()
方法Set判断两个对象相关是用
equals()
方法,如果类没有重写equals()
方法,那么它会调用Object类中的equals()
方法,Object的equals()
方法底层仍是使用==
-
Set中没有额外定义新的方法,使用的都是Collection声明的方法
5.1.2 Set添加元素的过程
以HashSet为例:
向HashSet添加元素a时,首先调用元素a所在类的hashCode()方法,得到a的hash值,接着使用该hash值经过某种算法计算出a应该在HashSet底层数组的存放位置
如果该存放位置为空,则直接添加元素a
如果存放位置有b,则调用a所在类的equals方法进行比较
如果相等,则添加失败
如果不等,则添加a(以链表的方式添加,JDK8中会让b仍存在原来的数组中,然后指向a)
5.2 Set三个实现类
- java.util.HashSet
- 作为Set的主要实现类
- 线程不安全
- 可以存储一个
null
值
- java.util.LinkedHashSet
- HashSet的子类
- 遍历内部数据时,可以按照添加时的顺序遍历
- java.util.TreeSet
- 可以按照添加对象的指定属性进行排序
5.2.1 Set实现类之一:HashSet
5.2.2 Set实现类之二:LinkedHashSet
- HashSet的子类
- LinkedHashSet使用双向链表维护元素的次序
- 对于频繁的遍历操作,LinkedHashSet效率高于HashSet
5.2.3 Set实现类之三:TreeSet
- 只能添加相同类的对象
- 会按照添加对象的属性去排序
CODE
代码一
public class TreeSetTest {
public static void main(String[] args) {
TreeSet set = new TreeSet();
set.add(129);
set.add(-1);
set.add(30);
set.add(4);
set.forEach((o)-> System.out.println(o));
/*Output:
-1
4
30
129
*/
}
}
代码二
让Person类实现Comparable接口,实现接口的compareTo()
方法,比较name
public class Person implements Comparable{
private String name;
private int age;
//省略
@Override
public int compareTo(Object o) {
if(o instanceof Person){
Person person = (Person)o;
return this.name.compareTo(person.name);
}else{
throw new RuntimeException("The type of input is mismatch################");
}
}
}
在TreeSet中添加Person类
public class TreeSetTest {
public static void main(String[] args) {
TreeSet set = new TreeSet();
set.add(new Person("Tom",13));
set.add(new Person("Jerry",2));
set.add(new Person("Mike",31));
set.add(new Person("Jack",80));
set.forEach((o)-> System.out.println(o));
/*Output
Person{name='Jack', age=80}
Person{name='Jerry', age=2}
Person{name='Mike', age=31}
Person{name='Tom', age=13}
*/
}
}
6.Map接口
6.1 概述
Map和Collection并列存在,所以称呼集合并不准确,更好的应该是称呼为容器,集合是容器的一个子集。
Map数列数据,用于保存具有映射关系的键值对:key-value
Map中的key用set进行存放,不允许重复,
key所在的类要重写
equals()
方法和hashCode()
方法,而value所在的类要重写equals()
,hashCode()方法在储存时被调用
常用的实现类是HashMap
主要实现类:
-
HashMap:线程不安全,效率高;可以存储
null
的key和valueJDK1.8之前,HashMap底层是数组+链表,JDK1.8之后,底层是数组+链表+红黑树
-
子类LinkedHashMap:遍历元素时,可以按照添加的顺序实现遍历,其原理同LinkedHashSet一样,每个元素添加一对指针来维护其顺序
当频繁增删数据时,LinkedHashMap效率高于HashMap
-
-
TreeMap:保证按照添加的key-value进行排序,实现排序遍历,此时考虑key的自然排序或者定制排序
-
HashMap:线程安全,效率低;可以存储null的key和value
- 子类Properties:常用来处理配置文件,key和value都是String类型
public class TestMap {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
Map<String,Integer> map2 = new Hashtable<>();
map.put(null, null);
map.put("1",null);
// map2.put(null, null); //编译通过,运行时报空指针异常
// map2.put("1",null); //同样报空指针异常
}
}
6.2 Map的常用方法
-
添加、删除、修改操作:
-
V put(K key,Object V)
:将指定key-value添加到(或修改)当前map对象中返回值中,如果为null表明做的是添加操作,如果返回的不是null,那么此时做的是修改操作,它返回的是key的上一个值
Map<String,Integer> map = new HashMap<>();
map.put("A", 1);
int a = map.put("A",2);
System.out.println("a="+a);
/*Output:
a=1
*/ -
void putAll(Map m)
:将m中的所有key-value对存放到当前map中 -
V remove(K key)
:移除指定key的key-value对,并返回value -
void clear()
:清空当前map中的所有数据
-
-
元素查询的操作:
V get(V key)
:获取指定key对应的valueboolean containsKey(K key)
:是否包含指定的keyboolean containsValue(V value)
:是否包含指定的valueint size()
:返回map中key-value对的个数boolean isEmpty()
:判断当前map是否为空boolean equals(Object o)
:判断当前map和参数对象obj是否相等
-
元视图操作的方法:
Set<K> keySet()
:返回所有key构成的Set集合Collection<V> values()
:返回所有value构成的Collection集合Set<Map.Entry<K,V>> entrySet()
:返回所有key-value对构成的Set集合
遍历map元素:
Map<String,Integer> map = new HashMap<>();
map.put("A",1);
map.put("B",2);
map.put("C",3);
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry<String,Integer> entry = iterator.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
/*Output
A:1
B:2
C:3
*/
6.3 Map的实现类
6.3.1 Map实现类之一:java.util.HashMap类
- jdk1.8之前的HashMap存储结构:数组+链表
实例化之后,底层创建一个长度是16的一维数组Entry[] table
;
当重复put同一个key的时候,新的value会替代原来的value;
默认的扩容方式:当超出阈值并且要存放的位置非空时,会扩容为原来的两倍,并将原有的数据复制过来;
- jdk1.8之后的HashMap存储结构
JDK8的改变
- new HashMap()实例化之后不会创建数组,而是在调用put()方法时才会创建一个长度为16的数组(饿汉式-->懒汉式)
- 底层数组是Node(),而非Entry()
- 数组上某一个索引的位置数据个数大于8并且当前数组长度大于64(小于64时会先扩容数组直到64)时,此索引位置的所有数据从原先链表存储改为红黑树存储
重要的源码常量
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子,0.75
threshold:需要扩容的阈值,=容量*加载因子,刚开始为16*0.75=12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树,8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。64
6.3.2 Map实现类之二:java.util.LinkedHashMap
LinkedHashMap是HashMap的子类,它多了一对链表来维护添加元素顺序的顺序。
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
6.3.3 Map实现类之三:java.util.TreeMap
- TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。 TreeMap 可以保证所有的 Key-Value 对处于有序状态。
- TreeSet底层使用红黑树结构存储数据
- TreeMap 的 Key 的排序:
- 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
- TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0
自然排序
class Person implements Comparable<Person>{
private String Name;
private int age;
//省略构造器、getter、setter、hashCode()、equals()、toString()....
@Override
public int compareTo(Person o) {
return this.age-o.age;
}
}
public class TestTreeMap {
@Test
public void test01(){
TreeMap<Person,String> treeMap = new TreeMap<>();
treeMap.put(new Person("Mike",30),"Boss");
treeMap.put(new Person("Jerry",18),"Student");
treeMap.put(new Person("Rose",24),"HouseWife");
System.out.println("自然排序:");
Set<Map.Entry<Person,String>> entrySet = treeMap.entrySet();
Iterator<Map.Entry<Person,String>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry<Person,String> entry = iterator.next();
System.out.println(entry.getKey()+" : "+entry.getValue());
}
}
}
/*Output:
自然排序:
Person{Name='Jerry', age=18} : Student
Person{Name='Rose', age=24} : HouseWife
Person{Name='Mike', age=30} : Boss
*/
定制排序
class Customer{
private String name;
private int age;
//省略构造器、getter、setter、hashCode()、equals()、toString()....
}
//Customer比较器
class CustomerComparator implements Comparator<Customer>{
@Override
public int compare(Customer o1, Customer o2) {
return o1.getAge()-o2.getAge();
}
}
public class TestTreeMap {
@Test
public void test02(){
TreeMap<Customer,String> treeMap = new TreeMap<>(new CustomerComparator());
// TreeMap<Customer,String> treeMap = new TreeMap<>((Customer o1,Customer o2)->o1.getAge()-o2.getAge());
treeMap.put(new Customer("Mike",30),"Boss");
treeMap.put(new Customer("Jerry",18),"Student");
treeMap.put(new Customer("Rose",24),"HouseWife");
System.out.println("定制排序:");
Set<Map.Entry<Customer,String>> entrySet = treeMap.entrySet();
Iterator<Map.Entry<Customer,String>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry<Customer,String> entry = iterator.next();
System.out.println(entry.getKey()+" : "+entry.getValue());
}
}
}
/*Output:
定制排序:
Customer{name='Jerry', age=18} : Student
Customer{name='Rose', age=24} : HouseWife
Customer{name='Mike', age=30} : Boss
*/
6.3.4 Map实现类之四:java.util.Hashtable
- Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的。
- Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
- 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
- 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
- Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。
6.3.5 Map实现类之五 java.util.Properties
- Porperties是Hashtable的子类,常用来处理配置文件
- 由于配置文件是文本,所有key和value都是字符串类型
- 存取推荐方法:setProperties(String key,String value)和getProperties(String key)
创建一个Resource Bundle文件
写入内容
public class PropertiesTest {
public static void main(String[] args) {
Properties pros = new Properties();
try {
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);
System.out.println(pros.getProperty("name"));
System.out.println(pros.getProperty("password"));
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
Tom
abc123
*/
7. Collections工具类
7.1 概述
- Collections是一个可以操作Collection、Map的工具类
- Collections工具类中提供对集合的排序、查询和修改等操作,还提供了对集合中元素不可变、对集合对象实现同步控制等方法
7.2 排序操作的方法
- 排序操作:(均为static方法)
reverse(List)
:反转 List 中元素的顺序shuffle(List)
:对 List 集合元素进行随机排序sort(List)
:根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator)
:根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List,int, int)
:将指定 list 集合中的 i 处元素和 j 处元素进行交换
7.3 查找、替换的方法
-
查找、替换
<T extends Object & Comparable<? super T>> T max(Collection<? extends > coll)
:根据元素的自然顺序,返回给定集合中的最大元素<T> T max(Collection<? extends T> coll, Comparator<? super T> comp)
:根据 Comparator 指定的顺序,返回给定集合中的最大元素<T extends Object & Comparable<? super T>> T min(Collection<? extends > coll)
<T> T min(Collection<? extends T> coll, Comparator<? super T> comp)
int frequency(Collection<?> c, Object o)
:返回指定集合中指定元素的出现次数<T> void copy(List<? super T> dest, List<? extends T> src)
:将src中的内容复制到dest中
@Test
public void test02(){
List<String> src = new ArrayList<>();
src.add("Tom");
src.add("Jack");
src.add("Lili");
List<String> dest = new ArrayList<>();
//Collections.copy(dest, src);
//报错
System.out.println(dest);
}上述代码会报一个错误:Source does not fit in dest,原因是因为在copy中
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");可知src的长度要小于或等于要赋值到的dest,下面是对dest代码的改造
@Test
public void test02(){
List<String> src = new ArrayList<>();
src.add("Tom");
src.add("Jack");
src.add("Lili");
//填充dest
List<String> dest = Arrays.asList(new String[src.size()]);
Collections.copy(dest, src);
System.out.println(dest);
/*Output
[Tom, Jack, Lili]
*/
}<T> boolean replaceAll(List<T> list, T oldVal, T newVal)
:使用新值替换List 对象的所有旧值
7.4 同步控制的方法
- Collections中提供synchronizedXxx()方法,这些方法可将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
Modifier and Type | Method and Description |
---|---|
static <T> Collection<T> | synchronizedCollection(Collection<T> c) Returns a synchronized (thread-safe) collection backed by the specified collection. |
static <T> List<T> | synchronizedList(List<T> list) Returns a synchronized (thread-safe) list backed by the specified list. |
static <K,V> Map<K,V> | synchronizedMap(Map<K,V> m) Returns a synchronized (thread-safe) map backed by the specified map. |
static <K,V> NavigableMap<K,V> | synchronizedNavigableMap(NavigableMap<K,V> m) Returns a synchronized (thread-safe) navigable map backed by the specified navigable map. |
static <T> NavigableSet<T> | synchronizedNavigableSet(NavigableSet<T> s) Returns a synchronized (thread-safe) navigable set backed by the specified navigable set. |
static <T> Set<T> | synchronizedSet(Set<T> s) Returns a synchronized (thread-safe) set backed by the specified set. |
static <K,V> SortedMap<K,V> | synchronizedSortedMap(SortedMap<K,V> m) Returns a synchronized (thread-safe) sorted map backed by the specified sorted map. |
static <T> SortedSet<T> | synchronizedSortedSet(SortedSet<T> s) Returns a synchronized (thread-safe) sorted set backed by the specified sorted set. |
总结点
Collection接口中的Vector类和Map接口中的Hashtable类都是从JDK1.0开始的,并且都是线程安全但效率不高,目前常用ArrayList类和HashMap类,如果要实现线程安全也不用Vector和Hashtable,而是使用Collections工具类
面试题
-
ArrayList、LinkedList、Vector三者的异同
- 相同点
- 实现来源相同:都实现了List接口
- 存储特点相同:有序、可重复
- 不同点
- 底层实现不同
- ArrayList、Vector底层为数组
- LinkedList底层为双向链表
- 线程安全性问题
- ArrayList、LinkedList线程不安全
- Vector线程安全
- 扩容机制
- ArrayList:1.5倍
- Vector:2倍
- 增删查效率不同
- ArrayList、Vector查询效率高
- LinkedList增删效率高
- 底层实现不同
- 相同点
-
HashMap的底层实现原理?
-
HashMap和Hashtable的异同?
- 相同点
- 父接口相同:都是实现了Map接口
- 存储结构相同:都是以键值对的方式存储
- 不同点
- 底层实现不同
- HashMap在JDK1.8之后是数组+链表+红黑树
- LinkedHashMap除了HashMap结构外,还有一对指针来维护其添加的顺序
- 增删效率不同
- HashMap增删效率低
- LinkedHashMap增删效率高
- 线程安全性不同
- HashMap线程不安全
- Hashtable线程安全
- 底层实现不同
-
CurrentHashMap和Hashtable的区别?
-
Collection和Collections的区别?