本节学习目标:
单列集合指只有一列数据的集合,单列集合中的每个数据被称为元素(Element),单列集合使用泛型E
限制元素的数据类型。
Java单列集合框架由Collection
根接口,继承Collection
接口的List
和Set
子接口,以及它们对应的多种实现类构成:
Collection
接口为Java单列集合框架中的根接口,它预先定义了单列集合中的基本方法,单列集合中的常用方法如下:
方法 | 返回值类型 | 功能 |
---|---|---|
size() |
int |
返回单列集合的长度 |
isEmpty() |
boolean |
判断单列集合是否为空集合 |
contains(Object o) |
boolean |
判断单列集合内是否包含o 对象 |
iterator() |
Iterator |
继承于Iterable 接口的方法,返回当前单列集合的迭代器 |
toArray() |
Object[] |
以Object 数组形式返回当前单列集合 |
toArray(T[] a) |
T[] |
将当前单列集合转换为T 类型数组后返回 |
add(E e) |
boolean |
向单列集合中添加元素E ,返回是否添加成功 |
remove(Object o) |
boolean |
移除单列集合中的对象o ,返回是否移除成功 |
containsAll(Collection> c) |
boolean |
判断单列集合c 是否为当前单列集合的子集 |
addAll(Collection extends E> c) |
boolean |
将单列集合c 中的所有元素添加至当前单列集合,返回是否添加成功 |
removeAll(Collection> c) |
boolean |
移除当前单列集合中与单列集合c 的交集,返回是否移除成功 |
removeIf(Predicate super E> filter) |
boolean |
根据选择器中的条件移除当前单列集合中的元素,返回是否移除成功 |
retainAll(Collection> c) |
boolean |
移除当前单列集合中与单列集合c的补集,即保留交集内的元素,返回是否移除成功 |
clear() |
void |
移除当前单列集合中的所有元素 |
编写代码进行测试:
public class TestCollection {
public static void main(String[] args) {
Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.println("集合的长度:" + collection.size());
System.out.println("集合是否为空:" + collection.isEmpty());
System.out.println("集合的Object数组形式:" + Arrays.toString(collection.toArray()));
Integer[] i = collection.toArray(new Integer[0]);
System.out.println("将Integer集合转换为Integer数组:" + Arrays.toString(i));
collection.add(1);
System.out.println("向集合中添加元素1:" + Arrays.toString(collection.toArray()));
collection.remove(2);
System.out.println("移除集合中的2:" + Arrays.toString(collection.toArray()));
System.out.println("判断集合{1, 3}是否为当前集合的子集:" + collection.containsAll(new ArrayList<>(Arrays.asList(1, 3))));
collection.addAll(new ArrayList<>(Arrays.asList(6, 7)));
System.out.println("将集合{6, 7}加入到当前集合:" + Arrays.toString(collection.toArray()));
collection.removeAll(new ArrayList<>(Arrays.asList(1, 3)));
System.out.println("将集合{1, 3}从当前集合移除:" + Arrays.toString(collection.toArray()));
collection.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer == 4;
}
});
System.out.println("使用选择器移除集合中的4:" + Arrays.toString(collection.toArray()));
collection.retainAll(new ArrayList<>(Arrays.asList(6, 7, 8)));
System.out.println("移除当前集合与集合{6, 7, 8}的补集:" + Arrays.toString(collection.toArray()));
collection.clear();
System.out.println("移除当前集合的所有元素,集合的长度为:" + collection.size());
}
}
运行结果:
集合的长度:5
集合是否为空:false
集合的Object数组形式:[1, 2, 3, 4, 5]
将Integer集合转换为Integer数组:[1, 2, 3, 4, 5]
向集合中添加元素1:[1, 2, 3, 4, 5, 1]
移除集合中的2:[1, 3, 4, 5, 1]
判断集合{1, 3}是否为当前集合的子集:true
将集合{6, 7}加入到当前集合:[1, 3, 4, 5, 1, 6, 7]
将集合{1, 3}从当前集合移除:[4, 5, 6, 7]
使用选择器移除集合中的4:[5, 6, 7]
移除当前集合与集合{6, 7, 8}的补集:[6, 7]
移除当前集合的所有元素,集合的长度为:0
Arrays
工具类的asList
方法返回的ArrayList
对象与集合中的ArrayList
对象是两个类,前者的全限定类名为java.util.Arrays.ArrayList
,后者为java.util.ArrayList
。
前者是Arrays
工具类的内部类,继承于AbstractList
抽象类,间接实现了List
接口,但未重写add()
方法。如果直接调用由asList()
方法返回的ArrayList
对象的add()
方法,
会抛出UnsupportedOperationException
异常。所以要使用后者的构造方法重新包装一下,才能使用add()
方法。
List集合包括List
接口及其实现类。List集合中的元素允许重复且有序,各元素的顺序就是元素插入时的顺序。
List集合中的每一个元素都有对应的顺序索引,所以List集合也被称为动态数组。
List
接口继承了Collection
接口,因此List集合具有Collection集合的所有特性,包含Collection
接口的所有方法。
List
接口除了有继承于Collection
接口的所有方法,自身又定义了很多方法,常用方法如下:
方法 | 返回值类型 | 功能 |
---|---|---|
get(int index) |
E |
返回集合中指定索引位置的元素 |
set(int index, E e) |
E |
将指定索引位置的元素替换为新的元素E ,返回被替换的元素 |
add(int index, E e) |
void |
在指定索引位置插入新的元素E ,位于指定索引后的所有元素索引后退 |
remove(int index) |
E |
将指定索引位置的元素移除,返回被移除的元素 |
addAll(int index, Collection extends E> c) |
boolean |
在指定索引位置插入集合c 中的所有元素,位于指定索引后的所有元素索引后退,返回是否插入成功 |
sort(Comparetor super E> c) |
void |
使用比较器对集合内的元素进行排序,排序后原集合的顺序将被更改 |
indexOf(Object o) |
int |
返回对象o 在集合中第一次出现的索引 |
lastIndexOf(Object o) |
int |
返回对象o 在集合中最后一次出现的索引 |
subList(int fromIndex, int toIndex) |
List |
将集合中从索引fromIndex 到索引toIndex 的元素(不包括位于toIndex索引的元素)以子集合的形式返回 |
编写代码进行测试:
import java.util.*;
public class TestList {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(57, 62, 6, -47, 6));
System.out.println("集合中索引为2的元素:" + list.get(2));
list.set(1, 5);
System.out.println("将集合中索引为1的元素替换为5:" + Arrays.toString(list.toArray()));
list.add(2, 50);
System.out.println("向集合中2索引处添加元素50:" + Arrays.toString(list.toArray()));
list.remove(5);
System.out.println("移除集合中索引为5的元素:" + Arrays.toString(list.toArray()));
list.addAll(2, new ArrayList<>(Arrays.asList(-68, -68)));
System.out.println("向集合中2索引处添加集合{-68, -68}:" + Arrays.toString(list.toArray()));
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1.equals(o2)) {
return 0;
} else if (o1 < o2) {
return -1;
} else {
return 1;
}
}
});
System.out.println("使用选择器(从小到大)将集合排序:" + Arrays.toString(list.toArray()));
System.out.println("返回-68在集合中第一次出现的索引:" + list.indexOf(-68));
System.out.println("返回-68在集合中最后一次出现的索引:" + list.lastIndexOf(-68));
System.out.println("返回集合从索引1到索引3之间的元素:" + Arrays.toString(list.subList(1, 3).toArray()));
}
}
运行结果:
集合中索引为2的元素:6
将集合中索引为1的元素替换为5:[57, 5, 6, -47, 6]
向集合中2索引处添加元素50:[57, 5, 50, 6, -47, 6]
移除集合中索引为5的元素:[57, 5, 50, 6, -47]
向集合中2索引处添加集合{-68, -68}:[57, 5, -68, -68, 50, 6, -47]
使用选择器(从小到大)将集合排序:[-68, -68, -47, 5, 6, 50, 57]
返回-68在集合中第一次出现的索引:0
返回-68在集合中最后一次出现的索引:1
返回集合从索引1到索引3之间的元素:[-68, -47]
ArrayList
类为List
接口的实现类之一,它是List集合的主要实现,一般情况下都使用ArrayList集合作为List集合的实现。
ArrayList
类的成员变量:
成员变量名 | 类型 | 说明 |
---|---|---|
elementData |
Object[] |
用于存储集合中的元素 |
size |
int |
集合的长度(并非容量),未添加任何元素时长度为0 |
elementData.length
指当前集合的容量(集合可以容纳元素的个数);size
指当前集合的长度(集合现有元素的个数)。ArrayList
类的常用构造方法:
构造方法 | 说明 |
---|---|
ArrayList(int initialCapacity) |
创建一个指定容量大小的ArrayList集合对象 |
ArrayList() |
创建一个ArrayList集合对象,且elementData 为空数组({} ) |
ArrayList(Collection extends E> c) |
以集合c 创建一个ArrayList集合对象 |
进入调试,调用add()
方法:
// java.util.ArrayList.add(E)
// ArrayList.java jdk1.8.0_202 Line:439~443
public boolean add(E e) {
// 检测当前容量,容量需要能装下当前集合长度+1的长度(最小容量)
ensureCapacityInternal(size + 1);
// 在Object数组末尾添加数据,同时集合的长度+1(size++)
elementData[size++] = e;
return true;
}
继续深入调试,进入ensureCapacityInternal()
方法:
// java.util.ArrayList.ensureCapacityInternal
// ArrayList.java jdk1.8.0_202 Line:230~232
private void ensureCapacityInternal(int minCapacity) {
// 先调用calculateCapacity()方法确定所需容量:
// private static int calculateCapacity(Object[] elementData, int minCapacity) {
// // 如果此时集合中没有任何元素,即等于常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA({})
// if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// // 在默认容量和最小容量取最大值返回
// return Math.max(DEFAULT_CAPACITY, minCapacity);
// }
// // 如果集合中存在元素,直接返回最小容量
// return minCapacity;
// }
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
继续深入调试,进入ensureExplicitCapacity()
方法:
// java.util.ArrayList.ensureExplicitCapacity
// ArrayList.java jdk1.8.0_202 Line:234~240
private void ensureExplicitCapacity(int minCapacity) {
// 自增修改次数
modCount++;
// overflow-conscious code
// 如果最小容量比当前容量大,则需要扩容,调用扩容方法grow()
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
继续深入调试,进入grow()
方法:
// java.util.ArrayList.grow
// ArrayList.java jdk1.8.0_202 Line:256~266
private void grow(int minCapacity) {
// overflow-conscious code
// 获取旧容量(当前容量)
int oldCapacity = elementData.length;
// 计算扩容后的容量(旧容量的1.5倍)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容后的容量比最小容量小
if (newCapacity - minCapacity < 0)
// 直接扩容至最小容量
newCapacity = minCapacity;
// 如果扩容后的容量比ArrayList最大容量(Integer.MAX_VALUE - 8)大
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 调用hugeCapacity()确定扩容后的容量:
// private static int hugeCapacity(int minCapacity) {
// // 如果扩容后的容量溢出(指计算后的)
// if (minCapacity < 0) // overflow
// // 抛出OutOfMemoryError异常
// throw new OutOfMemoryError();
// // 返回扩容后的容量与int类型最大值的较大值
// return (minCapacity > MAX_ARRAY_SIZE) ?
// Integer.MAX_VALUE :
// MAX_ARRAY_SIZE;
// }
newCapacity = hugeCapacity(minCapacity);
// 将原Object数组中的内容复制到一个长度为扩容后的容量的一个新数组中
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
总结JDK1.8中ArrayList集合的添加和扩容机制:
add()
)时,会进行容量检测,检测所需的最小容量(size + 1
)是否满足当前容量(elementData.length
):
elementData
是否为空数组,如果是,则在最小容量与默认容量(10)之间取最大值,并赋给最小容量,如果不是则无变化;OutOfMemoryError
异常。int
类型最大值。如果不是,将容量扩容至ArrayList最大容量。Object
数组,将原Object
数组内容复制进新的Object
数组里,并将elementData
引用指向新数组。size++
),操作结束。在JDK1.7中机制相似,唯一不同点在于构造方法:
Object
数组为一个长度为10的默认数组。Object
数组为空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA
);ArrayList
类提供的常用常量:
常量名 | 类型 | 值 | 说明 |
---|---|---|---|
DEFAULT_CAPACITY |
int |
10 |
默认容量 |
DEFAULTCAPACITY_EMPTY_ELEMENTDATA |
Object[] |
{} |
默认Object 数组 |
MAX_ARRAY_SIZE |
int |
Integer.MAX_VALUE - 8 |
ArrayList最大容量 |
Object
数组(顺序表)存储数据;null
;LinkedList
类也为List
接口的实现类之一,实际开发中也经常使用。
LinkedList集合的核心结构为它的内部类——节点(Node):
// java.util.LinkedList.Node
// LinkedList.java jdk1.8.0_202 Line:970~980
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;
}
}
LinkedList集合底层采用双向链表存储数据,每个元素就是一个节点,每个节点相互连接构成了双向链表结构。
一个元素为{57, 61}
的LinkedList集合对象list
的内存结构示意图如图所示:
LinkedList
类的成员变量:
成员变量名 | 类型 | 说明 |
---|---|---|
first |
Node |
指向双向链表的首节点的引用 |
last |
Node |
指向双向链表的尾节点的引用 |
size |
int |
集合的长度(并非容量),未添加任何元素时长度为0 |
Node
(节点)类的成员变量:
成员变量名 | 类型 | 说明 |
---|---|---|
item |
E |
存储在节点中的元素 |
next |
Node |
指向下一个节点的引用 |
prev |
Node |
指向上一个节点的引用 |
构造方法 | 说明 |
---|---|
LinkedList() |
默认构造方法,创建一个空的集合 |
LinkedList(Collection extends E> c) |
以集合c 创建一个LinkedList集合对象 |
调用add(E e)
方法进行添加操作,开始调试,进入add()
方法:
// java.util.LinkedList.add(E)
// LinkedList.java jdk1.8.0_202 Line:337~340
public boolean add(E e) {
// 使用linkLast()方法进行添加操作
linkLast(e);
return true;
}
继续深入调试,进入linkLast()
方法:
// java.util.LinkedList.linkLast
// LinkedList.java jdk1.8.0_202 Line:140~150
void linkLast(E e) {
// 获取链表的最后一个节点(last引用)
final Node<E> l = last;
// 将要添加的数据包装为一个节点对象,同时将新节点对象的prev引用指向最后一个节点,next引用设为null
final Node<E> newNode = new Node<>(l, e, null);
// 将last引用指向新节点对象
last = newNode;
// 如果最后一个节点为null,即当前链表为空链表
if (l == null)
// 则将first引用指向新节点对象,创建一个新链表
first = newNode;
else
// 不为null说明当前链表不为空,将最后一个节点的next引用指向新节点,将新节点连接至最后
l.next = newNode;
// 集合长度自增
size++;
// 修改计数自增
modCount++;
}
总结LinkedList集合的添加操作机制:
null
,则将first
引用指向新节点对象;null
,将最后一个节点的next
引用指向新节点对象;调用add(int index, E element)
方法进行插入操作,开始调试,进入add()
方法:
// java.util.LinkedList.add(int, E)
// LinkedList.java jdk1.8.0_202 Line:506~513
public void add(int index, E element) {
// 使用checkPositionIndex()方法检查索引是否合法
// 索引必须大于等于0且小于等于集合的长度
// 否则抛出IndexOutOfBoundsException异常
checkPositionIndex(index);
// 如果索引等于集合长度
if (index == size)
// 即在集合末尾添加元素,调用linkLast()方法
linkLast(element);
else
// 否则调用linkBefore()方法
// node()方法遍历获取索引位置的元素
// 遍历方法为中值遍历
// 如果索引比集合长度的一半大,则从后往前遍历,否则从前往后遍历
linkBefore(element, node(index));
}
继续深入调试,进入linkBefore()
方法:
// java.util.LinkedList.linkBefore
// LinkedList.java jdk1.8.0_202 Line:155~166
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 获取索引前一个节点
final Node<E> pred = succ.prev;
// 将要添加的元素包装为一个节点对象,同时将新节点的prev引用指向索引前一个节点,next引用指向索引节点
final Node<E> newNode = new Node<>(pred, e, succ);
// 将索引节点的prev引用指向新节点
succ.prev = newNode;
// 如果索引前一个节点为null
if (pred == null)
// 即向链表头部插入节点
// 将first引用指向新节点
first = newNode;
else
// 不为null说明向链表中的某个位置插入节点
// 将索引前一个节点的next引用指向新节点
pred.next = newNode;
// 集合长度自增
size++;
// 修改计数自增
modCount++;
}
总结LinkedList集合的插入操作机制:
IndexOutOfBoundsException
异常;prev
引用指向索引前一个节点,next
引用指向索引节点;prev
引用指向新节点(完成新节点和原索引节点的连接);first
引用指向新节点;next
引用指向新节点(完成索引前一个节点和新节点的连接)。null
;Vector
类是List
接口的实现类,是List
接口还未出现前(JDK1.2之前)Collection
接口的早期实现类。
Vector
类的成员变量:
成员变量名 | 类型 | 说明 |
---|---|---|
elementData |
Object[] |
用于存储集合中的元素 |
elementCount |
int |
元素的个数,即集合的长度(ArrayList 类中的size ) |
capacityIncrement |
int |
每次进行扩容时的增量 |
Vector类的常用构造方法:
构造方法 | 说明 |
---|---|
Vector() |
默认构造方法,以默认容量10 创建一个Vector集合 |
Vector(int initialCapacity) |
以指定容量创建一个Vector集合 |
Vector(Collection extends E> c) |
以集合c 创建一个Vector集合 |
Vector集合的扩容原理与ArrayList集合相似。唯一不同点在于扩容时如果没有指定capacityIncrement
(小于或等于0)时,
扩容为当前容量的2倍(ArrayList为1.5倍);指定capacityIncrement
(大于0)时,扩容为当前容量加上capacityIncrement
。
Vector
类提供的常用常量:
常量名 | 类型 | 值 | 说明 |
---|---|---|---|
MAX_ARRAY_SIZE |
int |
Integer.MAX_VALUE - 8 |
Vector最大容量 |
Object
数组(顺序表)存储数据;null
。Collections
工具类提供的synchronizedList()
方法获取同步集合。List
接口的三大实现类:ArrayList
,LinkedList
与Vector
。
List集合 | 元素可为null |
查找/修改效率 | 添加/插入/删除效率 | 是否线程安全 | 存储结构 | 说明 |
---|---|---|---|---|---|---|
ArrayList | 是 | 较高 | 较低 | 否 | 顺序表(Object 数组) |
经常使用的List集合,适合用于固定元素数量的集合,需要进行频繁的查改操作的 |
LinkedList | 是 | 较低 | 较高 | 否 | 双向链表 | 适合用于未知元素数量的集合,需要进行频繁的增删插操作的 |
Vector | 是 | 较高 | 较低 | 是 | 顺序表(Object 数组) |
实际开发中很少使用,以Collections.synchronizedList() 替代 |
Set集合包括Set
接口及其实现类。Set集合中的元素不可重复(唯一)且无序,对应数学意义上的“集合”。
无序性并不等于随机性。Set集合的无序性是相对于List集合的有序性而言的,List集合中元素的顺序是按照它们添加到集合中的顺序紧挨排列。
而Set集合的元素的顺序是按照元素的哈希值进行排列,第一个添加的元素不一定就在集合的最前面,有可能随着元素的添加而排在最后面。
所以Set集合相对于List集合是无序的。
学习JavaBean与其方法章节的equals()
和hashCode()
方法后,我们知道两个对象相等,则它们的哈希值一定相同;反之,两个对象的哈希值相同,但这两个对象不一定相等。
Set集合的不可重复性可以基于这句话理解。Set集合添加或插入数据时,会先调用hashCode()
方法查找是否有相同哈希值的元素,再调用equals()
方法查找是否有相同的元素。
通过对比哈希值和equals()
方法的返回值来保证元素的唯一性。
Set
接口也继承了Collection
接口,Set集合同样具有Collection集合的所有特性。包含Collection
接口的所有方法。
Set
接口本身没有新增方法,它的方法全都继承于Collection
接口的方法。
HashSet
类为Set
接口的实现类之一,它是Set集合的主要实现,一般情况下都使用HashSet集合作为Set集合的实现。
HashSet
类底层采用哈希表存储数据(实际使用一个HashMap集合对象存储)。
HashSet
类只有一个成员变量:
成员变量名 | 类型 | 说明 |
---|---|---|
map |
HashMap |
存储数据的HashMap 对象 |
HashSet
类的常用构造方法:
构造方法 | 说明 |
---|---|
HashSet() |
默认构造方法,创建一个空HashSet集合,以默认参数初始化map |
HashSet(Collection extends E> c) |
以集合c创建一个HashSet集合,将数据存入map |
HashSet(int initialCapacity) |
以指定容量创建一个HashSet集合,以指定容量初始化map |
HashSet集合中所有的操作方法都是调用的HashMap集合的操作方法实现的。所以源码分析和实现细节放在下章节。
hashCode()
方法返回值),使用哈希函数计算元素在哈希表(数组)中的存放位置。E2.next = E1
),JDK1.8中是旧元素指向新元素(E1.next = E2
);E1.equals(E2)
E1和E2不同,则也通过链表结构将两个元素存入同一个位置,连接方法同上;E1.equals(E2)
E1和E2也相同,则认为E1和E2是同一个元素,不做任何处理。元素E1与E2在哈希表中存储的内存示意图:
HashSet只有一个常量:
常量名 | 类型 | 值 | 说明 |
---|---|---|---|
PRESENT |
Object |
new Object() |
map 中与每个元素关联的虚拟值 |
null
,但只能有一个元素为null
;LinkedHashSet
类为Set
接口的实现类之一,同时它也是HashMap
类的子类。它可以使元素按照插入顺序排序。
LinkedHashSet
类底层采用哈希表和双向链表存储数据(实际使用一个LinkedHashMap集合对象存储)。
LinkedHashSet
类没有自己的成员变量,只有构造方法。常用的构造方法:
构造方法 | 说明 |
---|---|
LinkedHashSet() |
默认构造方法,创建一个空LinkedHashSet集合,以默认参数初始化map |
LinkedHashSet(Collection extends E> c) |
以集合c创建一个LinkedHashSet集合,将数据存入map |
LinkedHashSet(int initialCapacity) |
以指定容量创建一个LinkedHashSet集合,以指定容量初始化map |
LinkedHashSet
类的构造方法都调用了它的父类HashSet
的构造方法:
// java.util.HashSet.HashSet(int, float, boolean)
// HashSet.java jdk1.8.0_202 Line:161~163
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
// 调用此构造方法时将map初始化为一个LinkedHashMap对象并使用它存储数据
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
LinkedHashSet
类本身没有定义任何成员变量和成员方法,它的功能全部继承于HashSet
类。
LinkedHashSet集合中的所有操作方法都是调用的LinkedHashMap集合的操作方法实现的。由于LinkedHashMap类继承于HashMap,
所以LinkedHashSet的实现方式和HashSet相似。
在哈希表的基础上使用双向链表对元素的插入顺序进行排序(内存示意图):
红色为一般引用,绿色为哈希表中相同索引位置的元素链表引用,蓝色为排序的双向链表引用。
head = E1
);E1.equals(E2)
E1与E2不同,所以E2也插入成功并连接到E1之后(E1.next = E2
),双向链表将E1与E2按照插入顺序排序(E1.after = E2
且E2.before = E1
);E2.after = E3
且E3.before = E2
),双向链表的尾节点引用设为E3(即tail = E3
)。null
,但只能有一个元素为null
;TreeSet
类为SortedSet
接口的实现类之一,间接实现了Set
接口。TreeSet是一种有序Set集合,按照元素的某一属性进行排序。
TreeSet底层使用红黑树(自平衡二叉树)存储数据(实际使用一个TreeMap集合对象存储)。
TreeSet
类只有一个成员变量:
成员变量名 | 类型 | 说明 |
---|---|---|
m |
NavigableMap |
存储数据的NavigableMap 对象(实际使用TreeMap 对象) |
TreeSet
类的常用构造方法:
构造方法 | 说明 |
---|---|
TreeSet() |
默认构造方法,创建一个空的TreeSet集合,排序方法为自然排序 |
TreeSet(Comparator super E> comparator) |
使用指定比较器创建一个空的TreeSet集合,排序方法由比较器指定 |
TreeSet(Collection extends E> c) |
使用集合c 创建一个TreeSet集合,排序方法为自然排序 |
TreeSet中元素排序和判断元素是否重复不再使用equals()
和hashCode()
方法,取而代之的是比较器的返回值:
0
则两元素相同;1
则新元素在前;-1
则旧元素在前。比较器两种实现方式:
Comparable
接口,重写compareTo()
方法(自然排序);Comparator
接口,重写compare()
方法(定制排序);编写代码进行测试:
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
// 未传入比较器,将使用元素的compareTo()方法进行自然排序
Set<Integer> set1 = new TreeSet<>();
set1.add(24);
set1.add(6);
set1.add(-5);
// 传入了自定义比较器,将使用比较器进行定制排序
Set<Integer> set2 = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1.equals(o2)) {
return 0;
}
if (o1 < o2) {
return 1;
}
return -1;
}
});
set2.add(247);
set2.add(21);
set2.add(-8);
System.out.println("自然排序set1:" + Arrays.toString(set1.toArray()));
System.out.println("定制排序set2:" + Arrays.toString(set2.toArray()));
}
}
// 运行结果
// 自然排序set1:[-5, 6, 24]
// 定制排序set2:[247, 21, -8]
Comparable
接口或者传入比较器,否则将无法进行排序,抛出异常。null
;Set
接口的三大实现类:HashSet
,LinkedHashSet
与TreeSet
。
Set集合 | 元素可为null |
查找/修改效率 | 添加/插入/删除效率 | 是否线程安全 | 存储结构 | 说明 |
---|---|---|---|---|---|---|
HashSet | 是(只有一个) | 较高 | 较高 | 否 | 哈希表+红黑树 | Set集合的主要实现,元素为无序 |
LinkedHashSet | 是(只有一个) | 较高 | 较高 | 否 | 哈希表+红黑树+双向链表 | 元素为有序(按照插入顺序) |
TreeSet | 否 | 较高 | 较高 | 否 | 红黑树 | 元素为有序(按照元素的某个属性排序) |
如果在多线程环境中需要使用Set集合,使用Collections
工具类提供的synchronizedSet()
方法获取线程安全的Set集合。