文章目录
- 一、故事背景
- 二、知识点主要构成
- 1、List
- 1.1、ArrayList
- 1.1.1、添加元素
- 1.1.2、删除元素
- 1.1.3、修改元素
- 1.1.4、遍历元素
- 1.2、LinkedList
- 1.2.1、添加元素
- 1.2.2、删除元素
- 1.2.3、修改元素
- 1.2.4、遍历元素
- 1.3、Vector
- 2、Set
- 2.1、HashSet
- 2.2、LinkedHashSet
- 2.3、TreeSet
- 3、Queue
- 3.1、ArrayDeque
- 4、Map
- 4.1、HashMap
- 4.2、LinkedHashMap
- 4.3、TreeMap
- 三、总结提升
一、故事背景
最近在整理Java整个集合框架相关的内容,有很多东西在日常项目开发中没有用到过,总结一篇博客给大家扫扫盲,此篇博客包含Java中所有集合容器的相关使用,如有遗漏还请指出;
二、知识点主要构成
在介绍集合框架之前,先来看一张图,俗话说不谋全局者不足以谋一域么,先整理了解一下集合框架都有哪些东西;
Java 集合框架可以分为两条大的支线:
- Collection,主要由 List、Set、Queue 组成,List 代表有序、可重复的集合,典型代表就是封装了动态数组的 ArrayList 和封装了链表的 LinkedList;Set 代表无序、不可重复的集合,典型代表就是 HashSet 和 TreeSet;Queue 代表队列,典型代表就是双端队列 ArrayDeque,以及优先级队列 PriorityQueue。
- Map,代表键值对的集合,典型代表就是 HashMap。
1、List
1.1、ArrayList
ArrayList 是由数组实现的,支持随机存取,也就是可以通过下标直接存取元素;
从尾部插入和删除元素会比较快捷,从中间插入和删除元素会比较低效,因为涉及到数组元素的复制和移动;
如果内部数组的容量不足时会自动扩容,因此当元素非常庞大的时候,效率会比较低。
1.1.1、添加元素
public static void main(String[] args) {// 创建一个集合ArrayList<String> list = new ArrayList<>();// 添加元素list.add("www");list.add("mmm");list.add("jjj");}
1.1.2、删除元素
// 删除元素public static void main(String[] args) {// 创建一个集合ArrayList<String> list = new ArrayList<>();// 添加元素list.add("www");list.add("mmm");list.add("jjj");// 删除元素list.remove(1);}
1.1.3、修改元素
// 删除元素public static void main(String[] args) {// 创建一个集合ArrayList<String> list = new ArrayList<>();// 添加元素list.add("www");list.add("mmm");list.add("jjj");// 修改元素list.set(1, "mjw");}
1.1.4、遍历元素
// 遍历集合 for 循环for (int i = 0; i < list.size(); i++) {String s = list.get(i);System.out.println(s);}// 遍历集合 for eachfor (String s : list) {System.out.println(s);}
1.2、LinkedList
LinkedList 是由双向链表实现的,不支持随机存取,只能从一端开始遍历,直到找到需要的元素后返回;
任意位置插入和删除元素都很方便,因为只需要改变前一个节点和后一个节点的引用即可,不像 ArrayList 那样需要复制和移动数组元素;
因为每个元素都存储了前一个和后一个节点的引用,所以相对来说,占用的内存空间会比 ArrayList 多一些。
1.2.1、添加元素
public static void main(String[] args) {// 创建一个集合LinkedList<String> list = new LinkedList<>();// 添加元素list.add("王");list.add("沉");list.add("qubg");}
1.2.2、删除元素
public static void main(String[] args) {// 创建一个集合LinkedList<String> list = new LinkedList<>();// 添加元素list.add("你");list.add("好");list.add("呀");// 删除元素list.remove(1);}
1.2.3、修改元素
public static void main(String[] args) {// 创建一个集合LinkedList<String> list = new LinkedList<>();// 添加元素list.add("你");list.add("好");list.add("呀");// 修改元素list.set(1, "二狗");}
1.2.4、遍历元素
public static void main(String[] args) {// 创建一个集合LinkedList<String> list = new LinkedList<>();// 添加元素list.add("你");list.add("好");list.add("呀");// 遍历集合 for 循环for (int i = 0; i < list.size(); i++) {String s = list.get(i);System.out.println(s);}// 遍历集合 for eachfor (String s : list) {System.out.println(s);}}
LinkedList也实现了 Deque 接口,可以作为队列来使用。等于说,LinkedList 同时实现了 Stack、Queue、PriorityQueue 的所有功能。
通过源码可以看到LinkedList同时实现了List和Deque接口,说明LinkedList是Java 集合框架中的双向队列。
1.3、Vector
List 的实现类还有一个 Vector,是一个元老级的类,比 ArrayList 出现得更早。ArrayList 和 Vector 非常相似,只不过 Vector 是线程安全的,像 get、set、add 这些方法都加了 synchronized 关键字,就导致执行执行效率会比较低,所以现在已经很少用了。
同时也可以看到JDK官方注释说明:如果不需要线程安全,建议使用ArrayList代替Vector。
2、Set
Set 的特点是存取无序,不可以存放重复的元素,不可以用下标对元素进行操作,和 List 有很多不同。
2.1、HashSet
废话不多话,直接看相关操作;
public static void main(String[] args) {// 创建一个新的HashSetHashSet<String> set = new HashSet<>();// 添加元素set.add("www");set.add("王");set.add("ddd");// 输出HashSet的元素个数System.out.println("HashSet size: " + set.size()); // output: 3// 判断元素是否存在于HashSet中boolean containsWanger = set.contains("王");System.out.println("Does set contain '王'? " + containsWanger); // output: true// 删除元素boolean removeWanger = set.remove("王");System.out.println("Removed '王'? " + removeWanger); // output: true// 修改元素,需要先删除后添加boolean removeChenmo = set.remove("ddd");boolean addBuChenmo = set.add("vvvvvvv");System.out.println("Modified set? " + (removeChenmo && addBuChenmo)); // output: true// 输出修改后的HashSetSystem.out.println("HashSet after modification: " + set);}
2.2、LinkedHashSet
LinkedHashSet 虽然继承自 HashSet,其实是由 LinkedHashMap 实现的。
看一下LinkedHashSet的构造方法:
其中在LinkedHashSet的无参构造方法中执行super(16,.75f,true),意思是执行父类的构造函数,继续执行可以看到:
实际new出来的是一个LinkedHashMap。
接下来看一段基于LinkedHashSet的相关操作:
public static void main(String[] args) {LinkedHashSet<String> set = new LinkedHashSet<>();// 添加元素set.add("大家");set.add("晚上");set.add("好呀");// 删除元素set.remove("大家");// 修改元素set.remove("晚上");set.add("中午");// 查找元素boolean hasChenQingYang = set.contains("好呀");System.out.println("set包含好呀?" + hasChenQingYang);}
LinkedHashSet是一种基于哈希表实现的Set接口,它继承自HashSet,并且使用链表维护了元素的插入顺序。因此,它既具有HashSet的快速查找、插入和删除操作的优点,又可以维护元素的插入顺序。
2.3、TreeSet
同样,TreeSet是基于TreeMap实现的,可以看TreeSet的构造方法:
TreeSet 是一种基于红黑树实现的有序集合,它实现了 SortedSet 接口,可以自动对集合中的元素进行排序。按照键的自然顺序或指定的比较器顺序进行排序。
public static void main(String[] args) {// 创建一个 TreeSet 对象TreeSet<String> set = new TreeSet<>();// 添加元素set.add("大家");set.add("晚上");set.add("好呀");System.out.println(set); // 输出 [大家,晚上,好呀]// 删除元素set.remove("大家");System.out.println(set); // 输出 [晚上,好呀]// 修改元素:TreeSet 中的元素不支持直接修改,需要先删除再添加set.remove("晚上");set.add("中午");System.out.println(set); // 输出 [中午,好呀]// 查找元素System.out.println(set.contains("晚上")); // 输出 trueSystem.out.println(set.contains("喵喵喵")); // 输出 false}
3、Queue
队列,学习过基本数据结构的大家应该都知道,队列是遵循先进先出(FIFO)原则的。来看一个简易队列:
3.1、ArrayDeque
ArrayDeque 是一个基于数组实现的双端队列,为了满足可以同时在数组两端插入或删除元素的需求,数组必须是循环的,也就是说数组的任何一点都可以被看作是起点或者终点。
public static void main(String[] args) {// 创建一个ArrayDequeArrayDeque<String> deque = new ArrayDeque<>();//添加元素deque.add("兄弟们");deque.add("姐妹们");deque.add("早上好");deque.addFirst("1");deque.addFirst("3");deque.addLast("2");System.out.println(deque.getLast());System.out.println(deque.getFirst());// 删除元素deque.remove("兄弟们");// 修改元素deque.remove("姐妹们");deque.add("大家");// 查找元素boolean hasChenQingYang = deque.contains("大家");System.out.println("deque包含大家吗?" + hasChenQingYang);}
LinkedList 和 ArrayDeque 都是 Java 集合框架中的双向队列(deque),它们都支持在队列的两端进行元素的插入和删除操作。不过,LinkedList 和 ArrayDeque 在实现上有一些不同:
- 底层实现方式不同:LinkedList 是基于链表实现的,而 ArrayDeque 是基于数组实现的。
- 随机访问的效率不同:由于底层实现方式的不同,LinkedList 对于随机访问的效率较低,时间复杂度为 O(n),而 ArrayDeque 可以通过下标随机访问元素,时间复杂度为 O(1)。
- 迭代器的效率不同:LinkedList 对于迭代器的效率比较低,因为需要通过链表进行遍历,时间复杂度为 O(n),而 ArrayDeque 的迭代器效率比较高,因为可以直接访问数组中的元素,时间复杂度为 O(1)。
- 内存占用不同:由于 LinkedList 是基于链表实现的,它在存储元素时需要额外的空间来存储链表节点,因此内存占用相对较高,而 ArrayDeque 是基于数组实现的,内存占用相对较低。
因此,在选择使用 LinkedList 还是 ArrayDeque 时,需要根据具体的业务场景和需求来选择。如果需要在双向队列的两端进行频繁的插入和删除操作,并且需要随机访问元素,可以考虑使用 ArrayDeque;如果需要在队列中间进行频繁的插入和删除操作,可以考虑使用 LinkedList。
4、Map
Map 保存的是键值对,键要求保持唯一性,值可以重复。
4.1、HashMap
HashMap 实现了 Map 接口,可以根据键快速地查找对应的值——通过哈希函数将键映射到哈希表中的一个索引位置,从而实现快速访问。
HashMap 有一个初始容量和一个负载因子。初始容量是指哈希表的初始大小,负载因子是指哈希表在扩容之前可以存储的键值对数量与哈希表大小的比率。默认的初始容量是 16,负载因子是 0.75。
public static void main(String[] args) {// 创建一个 HashMap 对象HashMap<String, String> hashMap = new HashMap<>();// 添加键值对hashMap.put("wmj", "王梦杰");hashMap.put("cyx", "陈奕迅");hashMap.put("zj", "张杰");// 获取指定键的值String value1 = hashMap.get("zj");System.out.println("zj对应的值为:" + value1);// 修改键对应的值hashMap.put("wmj", "wangmengjie");String value2 = hashMap.get("沉默");System.out.println("修改后wmj对应的值为:" + value2);// 删除指定键的键值对hashMap.remove("zj");// 遍历 HashMapfor (String key : hashMap.keySet()) {String value = hashMap.get(key);System.out.println(key + " 对应的值为:" + value);}}
4.2、LinkedHashMap
LinkedHashMap 是 HashMap 的子类,它使用链表来记录插入/访问元素的顺序。
LinkedHashMap 可以看作是 HashMap + LinkedList 的合体,它使用了哈希表来存储数据,又用了双向链表来维持顺序。
来看一下LinkedHashMap和HashMap对比程序:
首先是LinkedHashMap:创建了一个LinkedHashMap,插入几个键值对,然后遍历打印结果;
public static void main(String[] args) {// 创建一个 LinkedHashMap,插入键值对LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();linkedHashMap.put("wmj", "王梦杰");linkedHashMap.put("cyx", "陈奕迅");linkedHashMap.put("zj", "张杰");// 遍历 LinkedHashMapfor (String key : linkedHashMap.keySet()) {String value = linkedHashMap.get(key);System.out.println(key + " 对应的值为:" + value);}}
从打印结果我们得知,输出打印的次序和插入的顺序是一致的,说明LinkedHashMap是一个有序的键值对集合;
接下来可以看一下HashMap的打印结果产生一下对比:
public static void main(String[] args) {// 创建一个 HashMap 对象HashMap<String, String> hashMap = new HashMap<>();// 添加键值对hashMap.put("wmj", "王梦杰");hashMap.put("cyx", "陈奕迅");hashMap.put("zj", "张杰");// 遍历 HashMapfor (String key : hashMap.keySet()) {String value = hashMap.get(key);System.out.println(key + " 对应的值为:" + value);}}
来看结果,得知HashMap没有维持键值对的插入顺序。
4.3、TreeMap
TreeMap实现了 SortedMap 接口,可以自动将键按照自然顺序或指定的比较器顺序排序,并保证其元素的顺序。内部使用红黑树来实现键的排序和查找。
public static void main(String[] args) {// 创建一个 TreeMap 对象Map<String, String> treeMap = new TreeMap<>();// 向 TreeMap 中添加键值对treeMap.put("wmj", "王梦杰");treeMap.put("cyx", "陈奕迅");treeMap.put("zj", "张杰");// 查找键值对String name = "wmj";if (treeMap.containsKey(name)) {System.out.println("找到了 " + name + ": " + treeMap.get(name));} else {System.out.println("没有找到 " + name);}// 修改键值对name = "wmj";if (treeMap.containsKey(name)) {System.out.println("修改前的" + name + ": " + treeMap.get(name));treeMap.put(name, "wangmengjie");System.out.println("修改后的 " + name + ": " + treeMap.get(name));} else {System.out.println("没有找到 " + name);}// 删除键值对name = "zj";if (treeMap.containsKey(name)) {System.out.println("删除前的 " + name + ": " + treeMap.get(name));treeMap.remove(name);System.out.println("删除后的 " + name + ": " + treeMap.get(name));} else {System.out.println("没有找到 " + name);}// 遍历 TreeMapfor (Map.Entry<String, String> entry : treeMap.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}}
三、总结提升
此篇文章总结了常用集合的一些基本操作,当然,我们不能只会使用它,更加去研究它是如何实现的,这样才能去学习它,成为它,超越它。
如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏哦。