Java 集合,也叫作容器,主要是由两大接口派生而来:一个是 Collection
接口,主要用于存放单一元素;另一个是 Map
接口,主要用于存放键值对。对于Collection
接口,下面又有三个主要的子接口:List
、Set
、 Queue
List
: 存储的元素有序、可重复Set
: 存储的元素不可重复Queue
: 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。Map
: 使用键值对存储,key 无序、不可重复,value 无序、可重复
Java 集合提高了数据的存储和处理的灵活性,可以更好地适应现代软件开发中多样化的数据需求,并支持高质量的代码编写
一、List
1.ArrayList
(1)ArrayList 和 Array 的区别
ArrayList
内部基于动态数组实现,比 Array
(静态数组) 更加灵活:
ArrayList
会动态地扩容或缩容ArrayList
允许使用泛型来确保类型安全,Array
不可以ArrayList
中只能存储对象。对于基本类型数据,需要使用对应的包装类。Array
可以直接存储基本类型数据,也可以存储对象ArrayList
支持插入、删除、遍历等常见操作,并且提供了丰富的 API 操作ArrayList
创建时不需要指定大小,而Array
创建时必须指定大小
// 初始化一个 String 类型的 ArrayList
ArrayList<String> stringList = new ArrayList<>(Arrays.asList("hello", "world", "!"));
// 添加元素到 ArrayList 中
stringList.add("goodbye");
System.out.println(stringList);// [hello, world, !, goodbye]
// 修改 ArrayList 中的元素
stringList.set(0, "hi");
System.out.println(stringList);// [hi, world, !, goodbye]
// 删除 ArrayList 中的元素
stringList.remove(0);
System.out.println(stringList); // [world, !, goodbye]
(2)ArrayList 可以添加 null 值吗?
可以但不建议, null
值无意义,会让代码难以维护(比如忘记做判空处理就会导致空指针异常)
ArrayList<String> listOfStrings = new ArrayList<>();
listOfStrings.add(null);
listOfStrings.add("java");
System.out.println(listOfStrings); // [null, "java"]
(3)ArrayList 扩容机制
2.LinkedList
3.ArrayList 与 LinkedList 区别?
- 是否保证线程安全:
ArrayList
和LinkedList
都不同步的,不保证线程安全 - 底层数据结构:
ArrayList
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构 - 是否支持快速随机访问:
LinkedList
不支持高效的随机元素访问,而ArrayList
(实现了RandomAccess
接口) 支持快速随机访问(get(int index)
) - 内存空间占用:
ArrayList
的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗更多空间
项目中一般不会使用 LinkedList
,几乎都可以用 ArrayList
来代替,并且性能通常会更好!
二、Set
三、Queue
四、Map
1.HashMap
(1)HashMap 和 Hashtable 的区别
- 线程是否安全:
HashMap
非线程安全的,Hashtable
线程安全的(内部的方法基本都经过synchronized
修饰)(保证线程安全使用ConcurrentHashMap
) - 效率: 因为线程安全的问题,
HashMap
要比Hashtable
效率高一点。另外,Hashtable
基本被淘汰,不要在代码中使用它 - 对 Null key 和 Null value 的支持:
HashMap
可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出NullPointerException
- 初始容量大小和每次扩充容量大小的不同: ① 创建时如果不指定容量初始值,
Hashtable
默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap
默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么Hashtable
会直接使用你给定的大小,而HashMap
会将其扩充为 2 的幂次方大小(HashMap
中的tableSizeFor()
方法保证HashMap
总是使用 2 的幂作为哈希表的大小) - 底层数据结构: JDK1.8 以后的
HashMap
在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间(后文中我会结合源码对这一过程进行分析)。Hashtable
没有这样的机制。 - 哈希函数的实现:
HashMap
对哈希值进行了高位和低位的混合扰动处理以减少冲突,而Hashtable
直接使用键的hashCode()
值。
(2)HashMap 和 HashSet的区别
HashSet
底层是基于 HashMap
实现的
HashMap | HashSet |
---|---|
实现了 Map 接口 | 实现 Set 接口 |
存储键值对 | 仅存储对象 |
调用 put() 向 map 中添加元素 | 调用 add() 方法向 Set 中添加元素 |
HashMap 使用键(Key)计算 hashcode | HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以equals() 方法用来判断对象的相等性 |