Java集合框架概述
集合是什么?
在Java中,集合是一种用于存储和操作一组对象的容器,定义在java.util包下。
有数组为什么还要有集合?
在Java中,数组(Array)是一种固定长度、连续的数据结构,用于保存多个相同类型的对象或数据。数组(Array)在创建时长度就固定,无法动态改变长度,如果需要改变数组的长度,需要通过数组拷贝的方式操作,不太方便。
1
| int[] array = {1, 2, 3, 4, 5};
|
为了解决数组长度固定和操作不便的问题,Java提供了一些集合类用于存储、操作和处理数据集合,这些集合类属于Java集合框架(Collections Framework),相比数组,集合具有以下优势:
- 动态改变长度:集合的长度是动态可变的,可以根据需要自动扩展或缩小。
- 提供丰富的操作方法:集合类提供了许多方便的方法来添加、删除、查找、遍历等操作,简化了对集合的操作。
- 支持泛型:集合类支持泛型,可以在编译时强制类型检查,提高代码的安全性和可读性。
- 提供迭代器:集合类提供了迭代器(Iterator)来遍历集合元素,可以方便地进行循环操作。
- 内置算法和排序:集合类内置了各种算法和排序方法,可以方便地对集合元素进行排序、查找和比较等操作。
集合体系结构
在 Java 集合框架中,有两个基本的根接口,分别是 Collection 单列集合接口和 Map 双列集合接口。

单列集合Collection体系
Collection 接口是存储单列集合对象的根接口,其中包括 List、Set 和 Queue 接口
- List接口:单列有序集合,存储
有序、可重复的对象,对象按插入顺序有序排列,元素可重复。常用的实现类有ArrayList、LinkedList、Vector - Set接口:单列无序集合,存储
无序、不可重复的对象,对象按一定规则无序排列,元素不可重复。常用的实现类有HashSet、LinkedHashSet、TreeSet - Queue接口:队列,存储
有序、可重复的对象,对象按照先进先出(FIFO)的原则进行操作。常用的实现类有:ArrayDeque、ArrayBlockingQueue、PriorityQueue

双列集合Map体系
Map接口:双列集合,存储键值对映射,键和值可以是任意类型的对象,可以根据键(key)来获取对应的值(value)。常用的实现类有HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

Collection接口(单列集合)
Collection接口简介
(1)java.util.Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法 既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合
(2)JDK不提供此接口的任何直接实现,而是提供更具体的子接口(Set和List)实现
(3)在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都 当成 Object 类型处理;从 JDK 5.0 后增加了泛型以后,Java 集合可以记住容 器中对象的数据类型。
Collection接口方法
添加操作
| 方法名 | 作用 |
|---|
| add() | 添加单个数据,结果返回布尔值 |
| addALl(Collection<? extends E>) | 批量添加 |
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void test() { Collection arrayList = new ArrayList(); arrayList.add("AA"); arrayList.add(123); System.out.println(arrayList);
Collection newArrayList = new ArrayList(); newArrayList.addAll(arrayList); System.out.println(newArrayList); }
|
删除操作
| 方法名 | 作用 |
|---|
| remove() | 删除单个数据,结果返回布尔值 |
| removeALL(Collection<?>) | 批量删除,取差集,,从当前集合中移除另一集合中相同的元素 |
| retainAll(Collection<?>) | 批量删除,取交集 |
| removeIF(Predicate<? super E>) | 条件删除,参数是过滤器,判断元素是否要删除 |
| clear() | 清空集合 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class Demo { @Test public void test1() { Collection arrayList = new ArrayList(); arrayList.add("AA"); arrayList.add(123); System.out.println(arrayList);
arrayList.remove(123); System.out.println(arrayList);
arrayList.removeIf(test -> test.equals("AA")); System.out.println(arrayList);
arrayList.clear(); }
@Test public void test2() { Collection arrayList1 = new ArrayList(); arrayList1.add("AA"); arrayList1.add(123); Collection arrayList2 = new ArrayList(); arrayList2.add("BB"); arrayList2.add(123); arrayList1.removeAll(arrayList2); System.out.println(arrayList1); }
@Test public void test3() { Collection arrayList1 = new ArrayList(); arrayList1.add("AA"); arrayList1.add(123); Collection arrayList2 = new ArrayList(); arrayList2.add("BB"); arrayList2.add(123); arrayList1.retainAll(arrayList2); System.out.println(arrayList1); } }
|
查询操作
| 方法名 | 作用 |
|---|
| size() | 返回此集合中有效的元素数。 |
| isEmpty() | 是否是空集合,如果集合中不包含元素,则返回 true 。 |
| contains(Object o) | 是否包含元素,如果此集合包含指定的元素,则返回true。 |
| containsAll(Collection<?>) | 如果此集合包含指定集合中的所有元素,则返回true。 |
| equals(Object obj) | 判断集合元素是否相等 |
| hashCode() | 返回当前对象的哈希值 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Demo { @Test public void test() { Collection arrayList = new ArrayList(); arrayList.add("AA"); Collection newArrayList = new ArrayList(); newArrayList.add("AA");
System.out.println(arrayList.size());
System.out.println(arrayList.isEmpty());
System.out.println(arrayList.contains("AA")); System.out.println(arrayList.containsAll(newArrayList));
System.out.println(arrayList.equals(newArrayList));
System.out.println(arrayList.hashCode()); } }
|
转换操作
| 方法名 | 作用 |
|---|
| toArray() | 集合 —> 数组,正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Demo { @Test public void test() { Collection arrayList = new ArrayList(); arrayList.add("AA"); arrayList.add("BB"); arrayList.add("CC"); System.out.println(arrayList);
Object[] arr = arrayList.toArray(); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); }
String[] array = {"AA", "BB", "CC"}; Collection<String> asList = Arrays.asList(array); System.out.println(asList); } }
|
遍历操作
| 方法名 | 作用 |
|---|
| iterator() | 返回Iterator迭代器接口的示例,用于遍历集合元素 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class Demo { Collection<String> asList = Arrays.asList("AA", "BB", "CC"); Iterator iterator = asList.iterator();
@Test public void test1() { System.out.println(iterator.next()); System.out.println(iterator.next()); System.out.println(iterator.next()); }
@Test public void test2() { for (int i = 0; i < asList.size(); i++) { System.out.println(iterator.next()); } }
@Test public void test3() { while (iterator.hasNext()) { System.out.println(iterator.next()); } }
@Test public void test4() { for (Object obj : asList) { System.out.println(obj); } } }
|
其他操作
| 方法名 | 作用 |
|---|
| stream() | 返回一个顺序Stream与此集合作为其来源。 |
| parallelStream() | 返回可能并行的Stream与此集合作为其来源。 该方法允许返回顺序流。 |
1 2 3 4 5 6 7 8 9 10
| public class Demo { @Test public void test() { Collection<String> asList = Arrays.asList("AA", "BB", "CC"); List<String> collect1 = asList.stream().collect(Collectors.toList()); System.out.println(collect1); List<String> collect2 = asList.parallelStream().collect(Collectors.toList()); System.out.println(collect2); } }
|
List接口(有序集合)
List接口简介
单列有序集合,存储有序、可重复的对象,对象按插入顺序有序排列,元素可重复。常用的实现类有ArrayList、LinkedList、Vector
| 名称 | 底层数据结构 | 特点 | 线程安全性 |
|---|
| ArrayList | 数组 | 查询快(支持随机访问),增删慢 | 不安全 |
| LinkedList | 双向链表 | 查询慢,增删快(支持快速插入和删除) | 不安全 |
| Vector | 数组 | 查询快,增删慢,线程安全 | 安全 |
List接口实现类ArrayList
(1)ArrayList 是List接口的典型实现类、主要实现类。本质上ArrayList是对象引用的一个“变长”数组
(2)ArrayList是一个有顺序的容器,底层是一个数组,不过它是会进行动态扩容
(3)ArrayList的JDK1.8之前与之后的实现区别?
| JDK版本 | 简介 |
|---|
| JDK1.7 | ArrayList像饿汉式,直接创建一个初始容量为10的数组,缺点就是如果没使用就浪费空间。 |
| JDK1.8 | ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时在创建一个始容量为10的数组 |
成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size; }
|
构造方法
| 构造方法 | 简介 |
|---|
| ArrayList() | 构造一个空容器,底层的数组长度默认为10 |
| ArrayList(int initialCapacity) | 构造一个初始长度为initialCapacity大小的容器,也就是底层的数组长度为initialCapacity |
| ArrayList(Collection<? extends E> c) | 构造一个内容为入参容器c的有序的容器。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } }
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } } }
|
常用方法
| 方法 | 作用 |
|---|
| add(E e) | 添加元素,向集合中添加元素 |
| add(int index, E element) | 向集合指定位置后面添加一个元素 |
| addAll(Collection<? extends E> c) | 向集合中添加另外一个集合的元素 |
| addAll(int index, Collection<? extends E> c) | 向集合指定位置后面添加另外一个集合的全部元素 |
| set(int index,E element) | 覆盖指定位置的元素 |
| remove(int index) | 删除指定位置的元素 |
| get(int index) | 获取指定位置的元素 |
| indexOf(Object o) | 获取指定位置的索引 |
| iterator() | 获取迭代器 |
| size() | 获取集合大小 |
| isEmpty() | 判断集合是否为空 |
| clear() | 清空集合 |
| stream() | 为集合创建流 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
private void ensureExplicitCapacity(int minCapacity) { modCount++;
if (minCapacity - elementData.length > 0) grow(minCapacity); }
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity); } }
|
实现原理
- ArrayList底层是用 Object 类型的动态数组来存储元素。
- 当创建ArrayList集合时,ArrayList默认创建一个容量为0的空数组。
- 当第一次向 ArrayList 中添加元素时,会将空数组初始化为容量为10的数组。
- 后续向 ArrayList 中添加元素时,会先检查当前数组是否已满,如果数组未满,则直接将元素添加到数组的末尾。
- 如果数组已满,会触发扩容机制创建一个新数组(新数组的容量是旧集合容量的1.5倍),然后将旧数组中的元素拷贝到新数组中。
- 如果扩容时数组容量是偶数,那么新容量就是旧容量的1.5倍;
- 如果扩容时数组容量是奇数,会先将数组容量右移一位,然后再乘以1.5,得到新容量。
- 虽然扩容过程中数组的容量会逐渐增大,但并不会无限增大,不能超过MAX_ARRAY_SIZE(约为2^31-1)
List接口实现类LinkedList
(1)对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
(2)ArrayList与LinkedList的区别
| 对比 | ArrayLIst | LinkedList |
|---|
| 数据结构 | 数组 | 链表 |
| 查询速度 | 快 | 慢 |
| 增删速度 | 慢 | 快 |
| 内存空间 | 小 | 大 |
| 应用场景 | 查询较多 | 增删较多 |
(3)LinkedList的常用方法
| 方法 | 作用 |
|---|
| getFirst | 返回此列表中的第一个元素。 |
| getLast | 返回此列表中的最后一个元素。 |
| removeFirst | 从此列表中删除并返回第一个元素。 |
| removeLast | 从此列表中删除并返回最后一个元素。 |
| add | 将指定的元素追加到此列表的末尾。 |
| addFirst | 在该列表开头插入指定的元素。 |
| size | 返回此列表中的元素数。 |
| clear | 从列表中删除所有元素。 此呼叫返回后,列表将为空。 |
| contains | 如果此列表包含指定的元素,则返回true` |
| listIterator | 从列表中的指定位置开始,返回此列表中元素的列表迭代器(按适当的顺序)。 |
| straem | 为集合创建流 |
List接口实现类Vector
(1)Vector是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的
(2)在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList。Vector总是比ArrayList慢,所以尽量避免使用
Set接口(无序集合)
Set接口简介
单列无序集合,存储无序、不可重复的对象,对象按一定规则无序排列,元素不可重复。常用的实现类有HashSet、LinkedHashSet、TreeSet
| 名称 | 底层数据结构 | 特点 | 线程安全性 |
|---|
| HashSet | 哈希表(数组+链表/红黑树) | 不允许重复元素,不保证元素顺序 | 不安全 |
| LinkedHashSet | 哈希表(数组+双向链表) | 不允许重复元素,但使用双向链表维护元素插入顺序 | 不安全 |
| TreeSet | 红黑树 | 不允许重复元素,元素可以自然顺序或指定Comparator排序 | 不安全 |
Set接口实现类HashSet
(1)HashSet是Set接口的典型实现,大多数时候使用Set集合时都是用这个实现类,HashSet底层:数组+链表的结构
(2)HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
(3)HashSet具有的特点:不能保证元素的排列顺序、HashSet不是线程安全的、集合元素可以是null
(4)HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相同
(5)对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象想等原则。即:“相等的对象必须具有相等的散列码”
成员变量
1 2 3 4 5 6 7 8 9 10 11
| public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object(); }
|
构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
public HashSet() { map = new HashMap<>(); }
public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size() / .75f) + 1, 16)); addAll(c); }
public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); }
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } }
|
常见方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
| public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
public HashSet() { map = new HashMap<>(); }
public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size() / .75f) + 1, 16)); addAll(c); }
public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); }
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
public Iterator<E> iterator() { return map.keySet().iterator(); }
public int size() { return map.size(); }
public boolean isEmpty() { return map.isEmpty(); }
public boolean contains(Object o) { return map.containsKey(o); }
public boolean add(E e) { return map.put(e, PRESENT) == null; }
public boolean remove(Object o) { return map.remove(o) == PRESENT; }
public void clear() { map.clear(); }
@SuppressWarnings("unchecked") public Object clone() { try { HashSet<E> newSet = (HashSet<E>) super.clone(); newSet.map = (HashMap<E, Object>) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(e); } }
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); s.writeInt(map.size()); for (E e : map.keySet()) s.writeObject(e); }
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); int capacity = s.readInt(); if (capacity < 0) { throw new InvalidObjectException("Illegal capacity: " + capacity); }
float loadFactor = s.readFloat(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new InvalidObjectException("Illegal load factor: " + loadFactor); }
int size = s.readInt(); if (size < 0) { throw new InvalidObjectException("Illegal size: " + size); } capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), HashMap.MAXIMUM_CAPACITY); SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity)); map = (((HashSet<?>) this) instanceof LinkedHashSet ? new LinkedHashMap<E, Object>(capacity, loadFactor) : new HashMap<E, Object>(capacity, loadFactor));
for (int i = 0; i < size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
public Spliterator<E> spliterator() { return new HashMap.KeySpliterator<E, Object>(map, 0, -1, 0, 0); } }
|
实现原理
- HashSet 的父类接口是Set集合,以Hash表数据结构存储数据
- HashSet的底层是基于HashMap实现,它使用 HashMap 的 key 存储元素,并将 value 设置为一个固定的 Object 对象来占位。
- 因为 HashMap 不能存储重复的 key,所以 HashSet 不能存放重复元素
- HashSet 的元素是无序的,并且允许存储Null元素,只能有存储一个Null元素
- HashSet 没有提供 get 方法,但可以通过 Iterator 迭代器来遍历HashSet中的元素。
Set接口实现类LinkedHashSet
(1)LinkedHashSet是HashSet的子类,插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能
(2)LinkedHashSet根据元素的hashCode来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
(4)LinkedHashSet不允许集合元素重复,对于频繁的遍历操作,LinkedHashSet效率高于HashSet
Set接口实现类TreeSet
(1)Tree是SortedSet接口的实现类,TreeSet可以确保集合元素除余排序状态
(2)Tree底层使用红黑树结构存储数据
(3)TreeSet和TreeMap采用红黑树的存储结构。特点:有序,查询速度比List快
(4)Tree两种排序方法:自然排序和定制排序。默认情况下,Tree采用自然排序
Query接口(队列)
Query接口简介
队列,存储有序、可重复的对象,对象按照先进先出(FIFO)的原则进行操作。常用的实现类有:ArrayDeque、ArrayBlockingQueue、PriorityQueue
| 名称 | 底层数据结构 | 特点 | 线程安全性 |
|---|
| ArrayDeque | 数组 | 双端队列,支持在队头和队尾进行元素操作 | 不安全 |
| ArrayBlockingQueue | 数组 | 有界阻塞队列,支持并发操作具有固定容量限制 | 安全 |
| PriorityQueue | 堆(数组/树) | 优先级队列,按照元素的优先级进行排序 | 不安全 |
Map接口(双列集合)
Map接口简介
双列集合,存储键值对映射,键和值可以是任意类型的对象,可以根据键(key)来获取对应的值(value)。常用的实现类有HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
| 名称 | 底层数据结构 | 特点 | 线程安全性 |
|---|
| HashMap | 哈希表(数组+链表/红黑树) | 存储键值对映射,通过键的哈希值快速查找对应的值 | 不安全 |
| LinkedHashMap | 哈希表(数组+双向链表) | 存储键值对映射,在HashMap的基础上使用双向链表维护键值对的插入顺序 | 不安全 |
| TreeMap | 红黑树 | 存储键值对映射,元素可以按照键自然顺序或指定Comparator排序 | 不安全 |
| Hashtable | 哈希表(数组+链表) | 存储键值对映射,内部使用synchronized关键字实现同步 | 安全 |
| Properties | 哈希表(数组+链表) | 存储键值对映射,继承自Hashtable类,主要用于处理属性文件 | 安全 |
HashMap
(1)HashMap是 Map 接口使用频率最高的实现类,HashMap无序但增删改查快、key不可以重复、可以为null、哈希值为0
成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;
transient int modCount; int threshold;
final float loadFactor;
}
|
构造方法
| 方法 | 作用 |
|---|
| HashMap() | 构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。 |
| HashMap(int initialCapacity) | 构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75) |
| HashMap(int initialCapacity, float loadFactor) | 构造一个空的HashMap具有指定的初始容量和负载因子。 |
| HashMap(Map<? extends K,? extends V> m) | HashMap(int initialCapacity, float loadFactor)构造一个新的 HashMap与指定的相同的映射 Map` |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; }
public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } }
|
静态内部类Node
静态内部类Node表示HashMap中每个键值对的节点,包含了键、值以及与下一个节点的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); }
public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; }
public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } } }
|
常用的方法
| 方法名 | 作用 |
|---|
| size | 返回此地图中键值映射的数量。 |
| isEmpty | 如果此地图不包含键值映射,则返回 true 。 |
| get | 返回到指定键所映射的值,或null如果此映射包含该键的映射。 |
| put | 将指定的值与此映射中的指定键相关联。 如果地图先前包含了该键的映射,则替换旧值。 |
| remove | 从该地图中删除指定键的映射(如果存在)。 |
| clear | 从这张地图中删除所有的映射。 此呼叫返回后,地图将为空。 |
| containsKey | 如果此映射包含指定键的映射,则返回 true 。 |
| keySet | 返回此地图中包含的键的Set视图。 该集合由地图支持,因此对地图的更改将反映在集合中,反之亦然。 如果在集合中的迭代正在进行中修改映射(除了通过迭代器自己的remov操作),迭代的结果是未定义的。 该组支持元件移除,即从映射中相应的映射,经由Iterator.remove,Set.remove,removeAll,retainAll和clear操作。 它不支持add或addAll操作。 |
HashMap中的put()方法
- 首先,
put()方法接收一个键和一个值作为参数。 - 传入的键会通过
hashCode()方法获取键的哈希值,用于确定键值对在HashMap内部数组中的位置。 - 如果该位置还没有任何元素,则直接将键值对存储在该位置。
- 如果该位置已经有元素存在(发生了哈希冲突),则遍历该位置上的链表或红黑树,找到具有相同键的节点。
- 如果找到具有相同键的节点,则更新该节点的值为新的值,并返回旧的值。
- 如果未找到具有相同键的节点,则将新的键值对添加到链表或红黑树的末尾,根据需要转换链表为红黑树。
- 如果插入后,链表长度超过了阈值(默认为8),则将链表转换为红黑树,以提高查找效率。
- 如果红黑树的节点数量达到了树化阈值(默认为6),则进行树化操作,将红黑树转换为一个更高效的结构。
- 如果HashMap的元素数量超过扩容阈值,将进行扩容操作,重新计算键值对的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } }
|

HashMap中的get()方法
- 根据传入的键,调用
hashCode()方法获取键的哈希值 - 根据哈希值,计算出在HashMap内部数组中的索引位置,在确定的索引位置上查找元素
- 如果该索引位置没有元素,则返回null,表示未找到对应的值。
- 如果该索引位置有元素,首先检查该位置上的元素是否与传入的键相等(使用
equals()方法进行比较)。 - 如果传入的键与该位置上的键相等,说明找到了对应的值,HashMap会返回该键对应的值。
- 如果传入的键与该位置上的键不相等,可能存在哈希冲突,即该位置上的链表或红黑树中可能存在具有相同键但不同值的节点。
- 存在哈希冲突HashMap会遍历链表或红黑树,查找具有相同键的节点,如果找到则返回该节点的值。
- 如果遍历完链表或红黑树仍未找到具有相同键的节点,则返回null,表示未找到对应的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) { return first; }
if ((e = first.next) != null) { if (first instanceof TreeNode) { return ((TreeNode<K,V>)first).getTreeNode(hash, key); } do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { return e; } } while ((e = e.next) != null); } } return null; } }
|
HashMap中的resize()方法
- 初始化数组容量:在创建HashMap对象时,会根据指定的初始容量(或者默认值)创建一个初始数组。这个初始容量通常是2的幂,以提高哈希算法的效率。
- 添加元素并触发扩容:每当向HashMap中添加元素时,都会检查当前元素数量是否已经达到了扩容阈值,即当前容量与负载因子(默认为0.75)的乘积。如果达到了阈值,就会触发扩容操作。
- 创建新数组:触发扩容后,HashMap会创建一个更大的新数组,通常大小是原始容量的两倍。这样可以提供更多的槽位用于存储元素,减少哈希冲突的概率,从而提高性能。
- 重新哈希化元素:接下来,HashMap会遍历原数组中的每个元素,并重新计算它们的哈希值和在新数组中的位置。元素的哈希值通过与新数组大小减一的位与操作,得到在新数组中的索引位置。然后将元素放置到新数组的相应位置上。
- 处理冲突:如果多个元素计算得到相同的新索引位置,就会发生哈希冲突。HashMap使用链表或红黑树来解决冲突,将相同索引位置上的元素以链表或红黑树的方式存储起来,并保持它们在新数组中的相对顺序。
- 更新引用:最后,HashMap会将内部的数组引用指向新的数组,以便后续的操作可以使用新的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; } else if (oldThr > 0) { newCap = oldThr; } else { newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
}
|

LinkedHashMap
(1)LinkedHashMap有序、key唯一可为null
(2)LinkedHashMap在HashMap存储结构的基础上,使用了一对双向链表来记录添加 元素的顺序
(3)LInkedHashMap中常用的方法
| 方法 | 作用 |
|---|
| put(K,V) | 添加元素 |
| get(Object) | 获取指定键的元素 |
| containsKey(Object) | 查询集合中是否包含指定键 |
| remove(Object) | 删除指定键的元素 |
| keyset() | 以set集合的形式返回所有值 |
| size() | 获取集合大小 |
| isEmpty() | 判断集合是否为空 |
| clear() | 清空集合 |
TreeMap
(1)TreeMap的key值不可为null且唯一
(2)keymap的常用方法
| 方法 | 作用 |
|---|
| put(K,V) | 添加元素 |
| get(Object) | 获取指定键的元素 |
| containsKey(Object) | 查询集合中是否包含指定键 |
| remove(Object) | 删除指定键的元素 |
| keyset() | 以set集合的形式返回所有值 |
| size() | 获取集合大小 |
| isEmpty() | 判断集合是否为空 |
| clear() | 清空集合 |
| descendingMap | 倒序遍历 |
Hashtable
(1)Hashtable是个古老的 Map 实现类,JDK1.0就提供了
(2)不同于HashMap, Hashtable是线程安全的
Properties
(1)Properties 类是 Hashtable 的子类,该对象用于处理属性(配置)文件
(2)由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
(3)存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法
(4)使用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 1、创建配置文件test.properties uesr=wen password=123456 2、读取配置文件内容
Properties properties = new Properties();
FileInputStream fileInputStream = new FileInputStream("src/test.properties");
properties.load(fileInputStream);
Object username = properties.get("username"); Object password = properties.get("password"); System.out.println(username); System.out.println(password);
|
Iterator迭代器接口
迭代器简介
(1)迭代器是一种通用的遍历集合,取出集合中元素的方式,Java提供了Iterator迭代器接口,主要用于遍历 Collection 集合中的元素,但Iterator 本身不提供承装对象的能力。
(2)Iterator 本身不提供承装对象的能力,而是提供了一个iterator()方法,用以返回一个实现了 Iterator接口的对象,如果需要创建 Iterator 对象,则必须有一个被迭代的集合
(3)Collection接口继承了java.lang.Iterable接口,所有实现了Collection接口的集合类都有一个iterator()方法,集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
常用方法
| 方法 | 简介 |
|---|
| iterator() | 返回迭代器对象,用于集合遍历,集合每次调用iterator()方法都得到一个全新的迭代器对象 |
| hasNext() | 判断是否还有下一个元素 |
| next() | ①指针下移 ②将下移以后集合位置上的元素返回 |
| remove() | 内部定义了remove(),可以在遍历的时候,删除集合中的元素,此方法不同于集合中直接调用的remove()方法 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class IteratorTest { @Test public void test1() { Collection arrayList = new ArrayList(); arrayList.add("AA"); arrayList.add("BB"); arrayList.add("CC");
Iterator iterator = arrayList.iterator();
for (int i = 0; i < arrayList.size(); i++) { System.out.println(iterator.next()); }
while (iterator.hasNext()) { System.out.println(iterator.next()); } }
@Test public void test2() { Collection arrayList = new ArrayList(); arrayList.add("AA"); arrayList.add("BB"); arrayList.add("CC");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) { Object obj = iterator.next(); if ("CC".equals(obj)) { iterator.remove(); } }
Iterator i = arrayList.iterator(); while (i.hasNext()) { System.out.println(i.next()); } } }
|
排序相关接口
Java排序方案说明
在Java中,对象通常使用==和!=来判断对象是否相等或不相等,但在开发场景中,有时需要对多个对象进行排序或比较大小,Java提供了两种解决方案:
- 实现Comparable接口:用于定义对象的自然排序规则。
- 实现Comparator接口:用于定义对象的定制排序规则。
Comparable接口(自然排序)
Comparable接口是Java中定义的一个接口,用于定义对象之间的自然排序规则。对于自定义类来说,如果需要进行排序,可以让自定义类实现Comparable接口并重写compareTo方法,在其中指定如何进行排序。对于String类和常见的包装类,内部已经实现了Comparable接口,并提供了默认的排序规则。
Comparator接口(定制排序)
Comparator接口是Java中定义的一个接口,用于支持对象之间的定制排序规则。相对于实现Comparable接口,使用Comparator接口可以在不修改原始类的情况下定义不同的比较规则。使用场景如下
- 当元素的类型没有实现Comparable接口,且不能方便地修改代码时;
- 当元素的类型已经实现了Comparable接口,但该实现的排序规则不适合当前的操作时。
Comparable接口与Comparator接口的区别
Comparable和Comparator是Java中用于排序的两个接口,它们有以下区别:
区别一:实现方式不同
- Comparable接口:在被比较的类本身实现,比较当前对象与另一个对象的大小关系,使对象自身具备比较的能力
- Comparator接口:不在被比较的类本身实现,在外部单独定义比较器,比较两个独立的对象之间的大小关系
区别二:使用范围不同
- Comparable接口:只能对实现了Comparable接口的对象进行排序
- Comparator接口:可以对任意的对象进行比较和排序,不管对象有没有实现Comparable接口都可以进行排序
区别三:排序方式不同
- Comparable接口:只能利用默认的比较规则实现自然排序
- Comparator接口:可以根据需要定义不同的比较规则进行排序
排序案例
使Goods对象按价格从低到高排序
创建Goods类实现Comparable接口重写compareTo(T o)方法,返回值有以下三种情况:
- 返回正整数:比较者大于被比较者,当前对象(this)大于形参对象(obj)
- 返回负整数:比较者小于被比较者,当前对象(this)小于形参对象(obj)
- 返回零:比较者等于被比较者,当前对象(this)等于形参对象(obj)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Data @NoArgsConstructor @AllArgsConstructor class Goods implements Comparable { private String name; private double price;
@Override public int compareTo(Object o) { if (o instanceof Goods) { Goods goods = (Goods) o; if (this.price > goods.price) { return 1; } else if (this.price < goods.price) { return -1; } else { return -this.name.compareTo(goods.name); } } throw new RuntimeException("输入的数据类型不一致"); } }
|
调用Collections.sort或Arrays.sort等方法,对对象进行排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Main { public static void main(String[] args) { Goods goods1 = new Goods("苹果", 2.5); Goods goods2 = new Goods("香蕉", 1.8); Goods goods3 = new Goods("橙子", 3.2); Goods[] goods = new Goods[]{goods1,goods2,goods3}; List<Goods> goodsList = new ArrayList<>(); goodsList.add(goods1); goodsList.add(goods2); goodsList.add(goods3);
System.out.println("未排序:"+Arrays.toString(goods)); Arrays.sort(goods); System.out.println("已排序:"+Arrays.toString(goods));
System.out.println("未排序:" + goodsList); Collections.sort(goodsList); System.out.println("已排序:" + goodsList); } }
|
使Goods对象按价格从高到低排序
创建Goods类,不实现Comparable接口
1 2 3 4 5 6 7
| @Data @NoArgsConstructor @AllArgsConstructor class Goods { private String name; private double price; }
|
在进行排序时,可以通过调用Collections.sort或Arrays.sort等方法,并传入相应的Comparator对象来指定比较规则,这样就能根据特定需求对对象进行定制排序。Comparator接口定义了compare方法,该方法接收两个对象作为参数,并返回一个整数值来表示比较结果,整数值有以下含义:
- 返回正整数:表示第一个对象(o1)大于第二个对象(o2)。
- 返回负整数:表示第一个对象(o1)小于第二个对象(o2)。
- 返回零:表示第一个对象(o1)等于第二个对象(o2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public class Main { public static void main(String[] args) { Goods goods1 = new Goods("苹果", 2.5); Goods goods2 = new Goods("香蕉", 1.8); Goods goods3 = new Goods("橙子", 3.2); Goods[] goods = new Goods[]{goods1, goods2, goods3}; List<Goods> goodsList = new ArrayList<>(); goodsList.add(goods1); goodsList.add(goods2); goodsList.add(goods3);
System.out.println("未排序:" + Arrays.toString(goods)); Arrays.sort(goods, new Comparator() {
@Override public int compare(Object o1, Object o2) { Goods g1 = (Goods) o1; Goods g2 = (Goods) o2; if (g1.getPrice() > g2.getPrice()) { return -1; } else if (g1.getPrice() < g2.getPrice()) { return 1; } else { return g1.getName().compareTo(g2.getName()); } } }); System.out.println("已排序:" + Arrays.toString(goods));
System.out.println("未排序:" + goodsList); Collections.sort(goodsList, new Comparator() {
@Override public int compare(Object o1, Object o2) { Goods g1 = (Goods) o1; Goods g2 = (Goods) o2; if (g1.getPrice() > g2.getPrice()) { return -1; } else if (g1.getPrice() < g2.getPrice()) { return 1; } else { return g1.getName().compareTo(g2.getName()); } } }); System.out.println("已排序:" + goodsList); } }
|
序列化与反序列化
什么是序列化与反序列化?
在 Java 中,序列化和反序列化是一种用于对象与字节流互相转换,以便存储或传输的机制。
- 序列化:对象—>字节序列
- 反序列化:字节序列—>对象
什么情况下需要序列化与反序列化?
当 Java 对象需要在网络传输或持久化存储到文件中时,需要序列化
- 网络传输:当需要将 Java 对象在网络上进行传输时,可以使用序列化将对象转换为字节流,然后通过网络发送给接收方。例如,在客户端和服务器之间进行通信时,可以将请求或响应对象序列化并通过网络传输。这样,对象的信息就可以在不同的计算机或进程之间传递,实现分布式系统的协作。
- 持久化存储:当需要将 Java 对象保存到磁盘或数据库中以供以后使用时,可以使用序列化将对象写入文件或数据库字段。序列化后的对象可以被存储,稍后可以从存储位置读取并恢复为原始对象。这种持久化存储方式适用于需要长期保留对象状态的情况,比如保存用户配置、缓存数据、日志等。
序列化与反序列化有什么前提?
在 Java 中,让一个对象可序列化与反序列化,需要满足以下要求:
- 实现
Serializable 接口:将要序列化的类实现 Serializable 接口,Serializable 接口是 Java 提供的一个标记接口,接口中没有任何方法定义,用于标识一个类可以被序列化和反序列化。 - 提供
private static final long serialVersionUID 常量:这个常量用于表示类的版本号,它在反序列化时用于判断对象和字节流之间的兼容性。如果没有显式地定义该字段,Java 编译器会自动生成一个版本号。 - 内部所有属性也必须是可序列化的:将类的内部所有属性都标记为可序列化,这意味着它们要么是基本数据类型(如
int、boolean 等),要么是实现了 Serializable 接口的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @Data @NoArgsConstructor @AllArgsConstructor class Person implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String name; private transient int age; } class SerializationExample { public static void main(String[] args) { Person person = new Person(1L, "Alice", 25);
serializeObject(person, "person.ser");
Person deserializedPerson = (Person) deserializeObject("person.ser");
System.out.println("名字: " + deserializedPerson.getName()); System.out.println("年龄: " + deserializedPerson.getAge()); }
private static void serializeObject(Object obj, String filename) { try { FileOutputStream fileOut = new FileOutputStream(filename); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(obj); out.close(); fileOut.close(); System.out.println("序列化的对象并保存到:" + filename); } catch (IOException e) { e.printStackTrace(); } }
private static Object deserializeObject(String filename) { try { FileInputStream fileIn = new FileInputStream(filename); ObjectInputStream in = new ObjectInputStream(fileIn); Object obj = in.readObject(); in.close(); fileIn.close(); System.out.println("反序列化的对象: " + filename); return obj; } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return null; } } }
|
如果某些数据不想序列化与反序列化怎么办?
方式一:使用transient关键字
在Java中,transient是一个关键字,用来修饰类的成员变量。当一个变量被声明为transient时,它表示该变量不参与对象的序列化过程,将被跳过,不会被序列化,通常用于一些敏感信息或者不需要被序列化和传输的数据
1 2 3 4
| public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; }
|
方式二:自定义序列化方式
你可以实现Serializable接口,并在其中自定义序列化和反序列化过程,通过编写writeObject()和readObject()方法来控制序列化和反序列化的行为,在这些方法中可以决定哪些数据要被序列化,哪些数据要被忽略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Data @NoArgsConstructor @AllArgsConstructor class Person implements Serializable { private static final long serialVersionUID = 1L; private String name;
private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeObject(name.toUpperCase()); }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); name = ((String) in.readObject()).toLowerCase(); } }
|
方式三:使用Externalizable接口自定义序列化方式
Externalizable接口是Java提供的一个用于实现自定义序列化的接口,相比Serializable接口更加灵活,使用步骤如下
- 在类上实现
Externalizable接口,并实现writeExternal()和readExternal()方法。 - 在
writeExternal()方法中定义需要序列化的成员变量。 - 在
readExternal()方法中定义如何从流中恢复成员变量的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Data @NoArgsConstructor @AllArgsConstructor class Person implements Externalizable { private String name;
@Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); }
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); } }
|