一文搞懂java集合框架
目录
- 一文搞懂java集合框架
- 什么是集合?
- 有什么特点?
- 框架图
- Collection
- 基本介绍:
- 接口常用方法
- 使用代码示例
- List
- 基本介绍
- 常用方法
- 使用代码示例
- ArrayList
- 注意事项和细节
- Vector
- 注意事项和细节
- ArrayList和Vector
- 如何创建与使用它们
- LinkedList
- 注意事项和细节
- 代码使用示例
- ArrayList和LinkedList
- Set
- 基本介绍
- 特点
- 常用方法
- 使用示例代码
- HashSet
- 注意事项和细节
- LinkedHashSet
- 注意事项和细节
- TreeSet
- 三者的异同与使用
- 示例代码
- Map
- 注意事项和细节
- 常用方法
- 使用示例代码
- HashMap
- 注意事项和细节
- Hashtable
- 注意事项和细节
- TreeMap
- 注意事项和细节
- 三者的异同与使用
- 异同点:
- 使用场景:
- 总结
什么是集合?
Java集合框架主要包括两个部分:接口和实现类。其中,接口定义了一系列的方法,而实现类则是实现了这些接口中定义的方法。Java集合框架中的接口主要分为三大类:List(列表)、Set(集合)和Map(映射)。
有什么特点?
- 可以动态保存任意多个对象
- 提供了⼀系列方便的操作对象的方法
- 使用集合添加、删除新元素的示意代码更简介
框架图
Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue(Java5新增的队列),因此Java集合大致也可分成List、Set、Queue、Map四种接口体系,(注意:Map不是Collection的子接口)。
其中List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合;Map代表的是存储key-value对的集合,可根据元素的key来访问value。
Collection
基本介绍:
- Collection实现子类可以存放多个元素,每个元素可以是Object
- Collection的实现类,有些可以存放重复的元素,有些不可以
- Collection的实现类,有些是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set 和 List 来实现的
接口常用方法
- add:添加单个元素
- remove:删除指定元素
- contains:查找元素是否存在
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清空
- addAll:添加多个元素
- containsAll:查找多个元素是否都存在
- removeAll:删除多个元素
使用代码示例
public static void main(String[] args) {
List list = new ArrayList();
list.add("jack");
list.add(10);
list.add(true);
list.remove(true);
System.out.println("list = " + list);
System.out.println(list.size());
System.out.println(list.isEmpty());
list.clear();
System.out.println("list = " + list);
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));
}
List
基本介绍
- List集合类中元素有序(即添加顺序和取出顺序⼀致)、且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应⼀个整数型的序号记载其在容器中的位置,可以根据序号存取容器元素
- 常用: ArrayList、LinkedList、Vector
常用方法
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位置的元素为ele,相当于是替换List sublist(int fromlndex, int tolndex)
:返回从fromlndex到tolndex位置的⼦集合
使用代码示例
public static void main(String[] args) {
// 创建一个ArrayList对象
List<String> list = new ArrayList<>();
// 添加元素
list.add("apple");
list.add("banana");
list.add("cherry");
list.add("date");
list.add("elderberry");
System.out.println(list );
// 获取元素
String firstElement = list.get(0);
String lastElement = list.get(list.size() - 1);
// 修改元素
list.set(3, "dragonfruit");
// 删除元素
list.remove("banana");
list.remove(2);
// 判断元素是否存在
boolean hasApple = list.contains("apple");
// 获取元素的位置
int index = list.indexOf("date");
System.out.println(index);
// 获取子列表
List<String> subList = list.subList(1, 3);
// 清空列表
list.clear();
// 判断列表是否为空
boolean isEmpty = list.isEmpty();
System.out.println(isEmpty);
//快速遍历列表
for (String element : subList) {
System.out.println(element);
}
//普通遍历方法
for (int i = 0; i < subList.size(); i++) {
System.out.println(subList.get(i));
}
}
ArrayList
注意事项和细节
- 允许所有元素包括null加入
- 由数组来实现数据存储
- 基本等同于Vector,除了是ArrayList线程不安全的但是执行效率高,在多线程下不建议使用ArrayList。
Vector
注意事项和细节
- Vector底层是⼀个对象数组,
protected Object[] elementData
- Vector 是线程同步的,即线程安全,Vector类的操作⽅法带有
synchronized
ArrayList和Vector
ArrayList和Vector都是Java中常用的List实现类,它们都实现了List接口,具有类似的功能,但在实现细节和性能上有所不同。
ArrayList是一个基于数组实现的动态数组,它支持随机访问和快速遍历,并且可以动态添加和删除元素。由于ArrayList的内部实现是基于数组,因此它的随机访问和修改操作的时间复杂度为O(1),但在插入和删除操作时需要移动数组中的元素,因此时间复杂度为O(n)。ArrayList的优点是在随机访问和修改操作时性能较好,适合用于读取和修改操作比较频繁的场景。
Vector也是一个动态数组,但它是线程安全的,因此在多线程环境下使用更加可靠。Vector的内部实现与ArrayList类似,但它的每个操作都是同步的,可以保证多个线程同时访问时的安全性。由于Vector的每个操作都需要进行同步,因此在性能上可能比ArrayList略慢一些。Vector的优点是可以在多线程环境下安全地使用,适合用于需要线程安全性的场景。
如何创建与使用它们
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
public class ListExample {
public static void main(String[] args) {
// 创建一个ArrayList对象
List<String> arrayList = new ArrayList<>();
// 向列表中添加元素
arrayList.add("apple");
arrayList.add("banana");
arrayList.add("cherry");
// 输出列表中的元素
System.out.println("ArrayList elements: " + arrayList);
// 获取列表中的元素
String element = arrayList.get(1);
System.out.println("Element at index 1: " + element);
// 修改列表中的元素
arrayList.set(2, "date");
System.out.println("ArrayList elements after modification: " + arrayList);
// 删除列表中的元素
arrayList.remove("banana");
System.out.println("ArrayList elements after removal: " + arrayList);
// 创建一个Vector对象
Vector<String> vector = new Vector<>();
// 向列表中添加元素
vector.add("apple");
vector.add("banana");
vector.add("cherry");
// 输出列表中的元素
System.out.println("Vector elements: " + vector);
// 获取列表中的元素
String firstElement = vector.firstElement();
System.out.println("First element: " + firstElement);
// 修改列表中的元素
vector.set(2, "date");
System.out.println("Vector elements after modification: " + vector);
// 删除列表中的元素
vector.remove("banana");
System.out.println("Vector elements after removal: " + vector);
}
}
LinkedList
注意事项和细节
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素包括null
- 线程不安全,没有实现同步
代码使用示例
import java.util.LinkedList;
import java.util.List;
public class LinkedListExample {
public static void main(String[] args) {
// 创建一个LinkedList对象
List<String> linkedList = new LinkedList<>();
// 向列表中添加元素
linkedList.add("apple");
linkedList.add("banana");
linkedList.add("cherry");
// 输出列表中的元素
System.out.println("LinkedList elements: " + linkedList);
// 获取列表中的元素
String element = linkedList.get(1);
System.out.println("Element at index 1: " + element);
// 修改列表中的元素
linkedList.set(2, "date");
System.out.println("LinkedList elements after modification: " + linkedList);
// 删除列表中的元素
linkedList.remove("banana");
System.out.println("LinkedList elements after removal: " + linkedList);
// 在列表中插入元素
linkedList.add(1, "orange");
System.out.println("LinkedList elements after insertion: " + linkedList);
// 遍历列表中的元素
for (String e : linkedList) {
System.out.println(e);
}
}
}
ArrayList和LinkedList
- 如果我们改查的操作多,选择ArrayList
- 如果我们增删的操作多,选择LinkedList
- ⼀般来说,在程序中,80%-90%都是查询,因此⼤部分情况下会选择ArrayList
Set
基本介绍
- Set是Java中的一个集合接口,它继承自Collection接口,表示一个不允许重复元素的集合。
- Set集合中的元素是无序的,不可以通过下标访问,而是需要使用迭代器或者foreach循环遍历集合中的元素。
- Set集合有多个实现类,其中最常见的为HashSet、TreeSet和LinkedHashSet。
- HashSet是基于哈希表实现的Set,它具有快速的插入、删除和查找操作,但不保证元素的顺序。
- TreeSet是基于红黑树实现的Set,它可以对元素进行排序,并提供了一些额外的操作,如获取子集和范围查找。由于元素需要进行排序,因此插入、删除和查找操作的性能可能比HashSet略慢。
- LinkedHashSet是基于哈希表和双向链表实现的Set,它保留了元素插入的顺序,并具有HashSet的快速操作。
特点
Set的特点是不允许重复元素,因此它常用于需要去重的场景,如统计单词出现的次数、过滤重复的数据等。它也可以用于对元素进行排序和快速查找。需要注意的是,Set对元素的去重是基于元素的equals和hashCode方法实现的,在使用自定义类时需要注意实现这两个方法。
常用方法
- add:添加单个元素
- remove:删除指定元素
- contains:查找元素是否存在
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清空
- addAll:添加多个元素
- containsAll:查找多个元素是否都存在
- removeAll:删除多个元素
使用示例代码
import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
// 创建一个HashSet对象
Set<String> set = new HashSet<>();
// 向Set中添加元素
set.add("apple");
set.add("banana");
set.add("cherry");
set.add("apple"); // 重复元素不会被添加
// 判断Set中是否包含元素
boolean containsApple = set.contains("apple");
System.out.println("Set contains apple: " + containsApple);
// 删除Set中的元素
set.remove("banana");
// 遍历Set中的元素
for (String element : set) {
System.out.println(element);
}
// 获取Set中元素的个数
int size = set.size();
System.out.println("Set size: " + size);
// 判断Set是否为空
boolean isEmpty = set.isEmpty();
System.out.println("Set is empty: " + isEmpty);
// 清空Set中的所有元素
set.clear();
}
}
HashSet
注意事项和细节
- Hashset实现了Set接口
- Hashset实际上是HashMap
- 可以存放null值,但是只能有⼀个null
- Hashset不保证元素是有序的,取决于hash后,再确定索引的结果
- 不能有重复元素/对象
LinkedHashSet
注意事项和细节
- LinkedHashset 是Hashset 的⼦类
- LinkedHashSet 底层是⼀个 LinkedHashMap,底层维护了⼀个 数组+双向链表
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使⽤链表维护元素的次序(图), 这使得元素看起来是以插⼊顺序保存的
- LinkedHashSet 不允许添重复元素
TreeSet
TreeSet 是基于红黑树实现的有序集合,它可以对元素进行排序,并提供了一些额外的操作,如获取子集和范围查找。由于元素需要进行排序,因此插入、删除和查找操作的性能可能比 HashSet 和 LinkedHashSet 稍慢。如果需要对元素进行排序,那么可以选择使用 TreeSet。
三者的异同与使用
ashSet、LinkedHashSet 和 TreeSet 都是实现了 Set 接口的集合类,它们都用于存储不重复的元素,并提供了对元素的快速访问和操作,但它们的实现方式不同,具有不同的特点和适用场景。
-
HashSet
HashSet 基于哈希表实现,它的元素是无序的,不保证元素的顺序。HashSet 的优点是在查找、添加和删除元素时速度非常快,时间复杂度为 O(1),但它不保证元素的顺序。如果不需要关心元素的顺序,而且需要快速地查找、添加和删除元素,那么 HashSet 是一个不错的选择。
-
LinkedHashSet
LinkedHashSet 是 HashSet 的子类,它基于哈希表和双向链表来实现,它保留了元素插入的顺序,并具有 HashSet 的快速操作。LinkedHashSet 的优点是可以按照元素的插入顺序进行遍历,因此如果需要按照元素的插入顺序来遍历集合,那么可以选择使用 LinkedHashSet。
-
TreeSet
TreeSet 是基于红黑树实现的有序集合,它可以对元素进行排序,并提供了一些额外的操作,如获取子集和范围查找。由于元素需要进行排序,因此插入、删除和查找操作的性能可能比** HashSet 和 LinkedHashSet 稍慢**。如果需要对元素进行排序,那么可以选择使用 TreeSet。
示例代码
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetExample {
public static void main(String[] args) {
// 使用 HashSet
Set<String> hashSet = new HashSet<>();
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("cherry");
hashSet.add("apple");
System.out.println("HashSet: " + hashSet);
// 使用 LinkedHashSet
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("apple");
linkedHashSet.add("banana");
linkedHashSet.add("cherry");
linkedHashSet.add("apple");
System.out.println("LinkedHashSet: " + linkedHashSet);
// 使用 TreeSet
Set<String> treeSet = new TreeSet<>();
treeSet.add("apple");
treeSet.add("banana");
treeSet.add("cherry");
treeSet.add("apple");
System.out.println("TreeSet: " + treeSet);
}
}
综上所述,可以根据具体的场景和需求来选择使用哪个集合类:
- 如果不需要关心元素的顺序,而且需要快速地查找、添加和删除元素,那么可以选择使用 HashSet。
- 如果需要按照元素的插入顺序来遍历集合,那么可以选择使用 LinkedHashSet。
- 如果需要对元素进行排序,那么可以选择使用 TreeSet。
Map
注意事项和细节
- Map与Collection井列存在,⽤于保存具有映射关系的数据
- Map 中的key 和 value 可以是任何引⽤类型的数据,会封装到HashMap$Node对象中
- Map 中的key 不允许重复,原因和HashSet ⼀样
- Map 中的**value 可以重复 **
- Map 的key可以为null,value也可以为null,key为null只有能有⼀个,value为null可以为多个
- 常用String类作为Map的key
- key 和 value 之间存在单向⼀对⼀关系,即通过指定的 key 总能找到对应的 value 8. Map存放数据的key-value示意图,⼀对 k-y是放在⼀个Node中的,有因为Node 实现了 Entry 接口
常用方法
- put:添加
- remove:根据键删除映射关系
- get:根据键获取值
- size:获取元素个数
- put:添加
- remove:根据键删除映射关系
- get:根据键获取值
- size:获取元素个数
使用示例代码
import java.util.HashMap;
import java.util.Map;
public class MapExample {
public static void main(String[] args) {
// 创建 HashMap
Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("apple", 3);
map.put("banana", 2);
map.put("cherry", 5);
// 获取键对应的值
int appleCount = map.get("apple");
System.out.println("The count of apple is " + appleCount);
// 判断是否包含特定的键
boolean hasBanana = map.containsKey("banana");
System.out.println("Does the map contains banana? " + hasBanana);
// 删除键值对
map.remove("cherry");
// 遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
System.out.println(key + ": " + value);
}
}
}
在这个示例中,我们首先创建一个 HashMap
对象,然后使用 put()
方法添加三个键值对。我们使用 get()
方法获取键对应的值,使用 containsKey()
方法判断是否包含特定的键,使用 remove()
方法删除键值对。最后,我们使用 entrySet()
方法遍历所有的键值对,并打印出每个键和对应的值。
HashMap
注意事项和细节
- HashMap是Map 接⼝使⽤频率最⾼的实现类
- Hashap 是以 key-val 对的⽅式来存储数据(HashMap$Node类型)
- key 不能重复,但是值可以重复,允许使⽤null和null值
- 如果添加相同的key,则会覆盖原来的key-val ,等同于修改(key不会替换,val会替换)
- 与HashSet⼀样,不保证映射的顺序,因为底层是以hash表的⽅式来存储的(jdk8的hashMap 底层 数组 +链表+红⿊树)
- HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
Hashtable
注意事项和细节
- 存放的元素是键值对:即K-V
- hashtable的键和值都不能为null, 否则会抛出NulPointerException
- hashTable 使用方法基本上和HashMap⼀样
- hashTable 是线程安全的(synchronized),hashMap 是线程不安全的
TreeMap
注意事项和细节
-
自然排序和自定义排序
TreeMap 可以使用自然排序或自定义排序,如果使用自然排序,则键必须实现 Comparable 接口;如果使用自定义排序,则需要传入一个实现了 Comparator 接口的比较器对象。
-
键不能为 null
TreeMap 的键不能为 null,否则会抛出 NullPointerException 异常。
-
线程不安全
TreeMap 是非线程安全的,如果需要在多线程环境下使用,需要进行适当的同步处理。
-
性能
由于 TreeMap 是基于红黑树实现的,因此它的插入、删除和查找操作的时间复杂度为 O(log n),比 HashMap 和 LinkedHashMap 稍慢。
-
遍历
TreeMap 提供了多种遍历方式,包括按照键的自然顺序遍历、按照键的降序遍历、按照键的升序遍历等。
-
subMap 方法
TreeMap 提供了 subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) 方法,用于获取部分键值对的子映射。其中 fromKey 和 toKey 分别表示子映射的起始键和结束键,而 fromInclusive 和 toInclusive 表示是否包含起始键和结束键。
三者的异同与使用
异同点:
- HashMap 和 Hashtable 都是基于哈希表实现的映射表,而 TreeMap 则是基于红黑树实现的有序映射表。
- HashMap 允许键和值为 null,而 Hashtable 不允许键或值为 null。TreeMap 不允许键为 null,但允许值为 null。
- HashMap 不是线程安全的,而 Hashtable 是线程安全的。TreeMap 不是线程安全的。
- HashMap 的查询、插入和删除操作的时间复杂度为 O(1),而 TreeMap 的时间复杂度为 O(log n),其中 n 表示映射表中的元素个数。
- HashMap 的键是无序的,而 TreeMap 的键是有序的。
- HashMap 允许使用自定义对象作为键,需要实现 hashCode() 和 equals() 方法,而 TreeMap 需要使用实现了 Comparable 接口或传入比较器对象进行排序。
使用场景:
- HashMap:适用于快速查找、添加和删除键值对的场景。由于 HashMap 的插入、删除和查找操作的时间复杂度为 O(1),因此在需要频繁进行插入、删除和查找操作的场景中,使用 HashMap 是非常合适的。例如,缓存系统、路由表等。
- Hashtable:由于 Hashtable 是线程安全的,因此适用于多线程环境下需要频繁进行插入、删除和查找操作的场景。但由于其相对较慢的查询速度和不允许键或值为 null 的限制,Hashtable 的使用场景相对较少,可以使用 ConcurrentHashMap 替代。
- TreeMap:适用于需要按照键的顺序进行排序的场景。由于 TreeMap 的键是有序的,因此可以使用 TreeMap 进行范围查找、排序等操作。例如,需要将元素按照键的顺序进行遍历、查找最大/小值等场景。但由于 TreeMap 的时间复杂度相对较慢,因此在需要频繁进行插入、删除和查找操作的场景中,使用 TreeMap 不是很合适。
总结
- 先判断存储的类型(⼀组对象[单列]或⼀组键值对[双列])
- ⼀组对象[单列]: Collection接⼝
- 允许重复:List、
- 增删多:LinkedList [底层维护双向链表]
改查多:ArrayList [底层維护 Object类型的可变数组] - 不允许重复:Set
⽆序:HashSet [底层是HashMap,维护了⼀个哈希表,即(数组+链表+红⿊树)]
排序:Treeset []
插⼊和取出顺序⼀致:LinkedHashSet [底层维护数组+双向链表]
- 增删多:LinkedList [底层维护双向链表]
- ⼀组键[值对双列]:Map
- 键⽆序:HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红⿊树]
- 键排序:TreeMap []
- 键插入和取出顺序⼀致:LinkedHashMap
- 允许重复:List、