文章目录
Java进阶——集合
1、集合框架简介
集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下。
Java集合框架(Java Collections Framework,JCF)是为表示和操作集合而规定的一种统一的标准的体系结构。 任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
- 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
- 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
- 算法**:**是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现(实现类),诸如: LinkedList, HashSet, 和 TreeSet 等,除此之外你也可以通过这些接口实现自己的集合。
集合框架体系如图所示:

Java 集合框架主要包括两种类型的接口,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。
2、集合框架涉及到的API
Java 集合可分为 Collection 和 Map 两种体系。
Collection 接口:Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如 List 和 set )。
- List 接口:List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。List 接口存储一组可重复,有序(插入顺序)的对象。
- Set接口:Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组不可重复,无序的对象。
Map接口: Map 接口存储一组键值对象,提供key(键)到value(值)的映射。
Collection 接口继承树:实线表示继承,虚线表示实现类

Map 接口继承树:实线表示继承,虚线表示实现

3、Collection 接口的使用
3.1、Collection 接口中的常用方法
方法说明及测试代码如下:
add(Object e),将元素e添加到集合中
addAll(Collection,coll1),将coll1集合中的元素移到当前集合中,移完后coll1集合就为空
size(),获取集合中元素的个数
isEmpty(),判断当前集合是否为空
clear(),清除集合中的元素
//以Collection接口的实现类 ArrayList为例
Collection coll = new ArrayList();
//add(Object e),将元素e添加到集合中
coll.add("AA");
coll.add(123);
coll.add(new Date());
//size(),获取集合中元素的个数
System.out.println(coll.size());//3
Collection coll1 = new ArrayList();
coll.add(456);
coll.add("CC");
//addAll(Collection,coll1),将coll1集合中的元素移到当前集合中,添加完后coll1集合就为空
coll.addAll(coll1);//5
System.out.println(coll.size());
System.out.println(coll);//[AA, 123, Tue Oct 05 21:20:16 CST 2021, 456, CC]
System.out.println(coll1);//[]
//isEmpty(),判断当前集合是否为空
System.out.println(coll.isEmpty());//false
//clear(),清除集合中的元素
coll.clear();
System.out.println(coll.isEmpty());//true
contains(Object obj),判断对象obj是否在当前集合中,在判断时,会调用obj对象所在类的equals()方法
containsAll(Collection<> coll1),判断集合coll1中的元素是否都在当前集合中
自定义类:
package collection;
import java.util.Objects;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//重写equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
测试代码:
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
//往collection集合中添加obj对象时,要求obj对象所在类重写equals()方法
coll.add(new Person("wanli",3));
//contains(Object obj),判断对象obj是否在当前集合中,在判断时,会调用obj对象所在类的equals()方法
System.out.println(coll.contains("DD"));//true
System.out.println(coll.contains(new String("EE")));//true
System.out.println(coll.contains(new Person("wanli", 3)));//true
Collection coll1 = Arrays.asList(123,456);
//containsAll(Collection<> coll1),判断集合coll1中的元素是否都在当前集合中
System.out.println(coll.containsAll(coll1));
remove(Object obj),从当前集合中移除obj元素
removeAll(Collection<> coll1),从当前集合中移除coll1集合中的元素
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
coll.add(new Person("wanli",3));
//remove(Object obj),从当前集合中移除obj元素
System.out.println(coll.remove(456));//true
System.out.println(coll.remove(789));//false
System.out.println(coll.remove(new Person("wanli",3)));//true
System.out.println(coll);//[123, false, EE, DD]
//removeAll(Collection<> coll1),从当前集合中移除coll1集合中的元素
Collection coll2 = new ArrayList();
coll2.add("DD");
System.out.println(coll.removeAll(coll2));//true
System.out.println(coll);//[123, false, EE]
retainAll(Collection<> coll1),获取当前集合和coll1集合的交集,并返回给当前集合
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
//往collection集合中添加obj对象时,要求obj对象所在类重写equals()方法
coll.add(new Person("wanli",3));
Collection coll1 = Arrays.asList(123,456);
//retainAll(Collection<> coll1),获取当前集合和coll1集合的交集,并返回给当前集合
System.out.println(coll.retainAll(coll1));//true
System.out.println(coll);//[123, 456]
equals(Object obj),比较指定对象与此集合是否相等
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
coll.add(new Person("wanli",3));
Collection coll1 = new ArrayList();
coll1.add(123);
coll1.add(456);
coll1.add(false);
coll1.add(new String("EE"));
coll1.add("DD");
coll1.add(new Person("wanli",3));
//equals(Object obj),比较指定对象与此集合是否相等
System.out.println(coll.equals(coll1));//true
hashCode(),输出当前对象的哈希值
toArray(),将当前集合转换成数组
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
coll.add(new Person("wanli",3));
//hashCode(),输出当前对象的哈希值
System.out.println(coll.hashCode());
//toArray(),将当前集合转换成数组
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//数组转换成集合
List<String> strings = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(strings);//[AA, BB, CC]
3.2、遍历 Collection
3.2.1、使用 Iterator 遍历 Collection
Iterator 迭代器接口
lterator对象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素。GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
Collection接口继承了java.lang.Ilterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象。
lterator仅用于遍历集合,lterator木身并不提供承装对象的能力。如果需要创建lterator对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Iterator 接口的方法
hasNext() , 如果迭代具有更多元素,则返回true 。
next(), 返回迭代中的下一个元素。
在调用next()方法之前必须要调用hasNext()进行检测。若不调用,且下一条记录无效,直接调用next()会抛出NoSuchElementException异常。
remove(), 从底层集合中删除此迭代器返回的最后一个元素(可选操作)。
使用 Iterator 遍历 Collection 代码实例
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
coll.add(new Person("wanli", 3));
//创建迭代器对象
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
remove() 方法测试
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
coll.add(new Person("wanli", 3));
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
if ("EE".equals(next)){
//移除元素
iterator.remove();
}
}
//遍历集合
iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}

注意:
-
lterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
-
如果还未调用next()或在上一次调用next方法之后已经调用了remove 方法,再调用remove都会报 llegalStateException。
3.2.2、forEach 循环遍历集合
Java 5.0提供了foreach循环迭代访问Collection和数组。
遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
遍历集合的底层调用Iterator完成操作。
foreach还可以用来遍历数组。

forEach 循环遍历集合代码实例:
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("EE"));
coll.add("DD");
coll.add(new Person("wanli", 3));
for (Object obj : coll) {
System.out.println(obj);
}
4、List 接口
List 接口概述
鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组。
List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
JDKAPI中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
4.1、List 接口常用实现类对比
ArrayList、LinkedList和Vector 三者的异同:
-
同:三个类都实现了 List 接口,存储数据的特点都相同:存储有序、可重复的数据。
-
异:
- ArrayList:是List接口的主要实现类,线程不安全,效率高,底层使用Object[] 存储
- LinkedList:如果要频繁的进行插入、删除操作,使用此类效率高,底层使用双向链表存储
- Vector:是List接口古老的实现类,线程安全,效率低,底层使用Object[] 存储
4.2、ArrayList 源码分析(JDK1.8)
//默认数组元素的初始容量
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认大小的空实例的共享空数组实例。 我们将其与 EMPTY_ELEMENTDATA 区分开来,以了解添加第一个元素时要扩容多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList 的元素存储在其中的数组缓冲区。 ArrayList 的容量就是这个数组缓冲区的长度。 添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为 DEFAULT_CAPACITY。
transient Object[] elementData;
无参构造:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
无参构造初始化了一个空数组,当第一次进行add的时后数组容量才会初始化为10。
add()方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确认list容量,如果不够,容量加1,第一次add时size为0
//将元素e放在size的位置上,并且size++
elementData[size++] = e;
return true;
}
ensureCapacityInternal(int minCapacity):minCapacity:需要的最小容量
//第一次add时,minCapacity为1,
private void ensureCapacityInternal(int minCapacity) {
//检出数组是否需要扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity(Object[] elementData, int minCapacity):
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果是第一次add,elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA都为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回最大的数,也就是DEFAULT_CAPACITY = 10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//此时返回的minCapacity为10
return minCapacity;
}
ensureExplicitCapacity(int minCapacity):
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//第一次add时,最小容量minCapacity为10 ,elementData.length为0
if (minCapacity - elementData.length > 0)
//进入扩容方法
grow(minCapacity);
}
grow(int minCapacity):自动扩容的关键方法
private void grow(int minCapacity) {
// 获取当前数组的容量
int oldCapacity = elementData.length;
//获取扩容数组的容量,新数组容量为以前数组的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的容量还是小于需要的最小容量
if (newCapacity - minCapacity < 0)
//将扩容后的容量再次扩容为需要的最小容量,当第一次add时,在这里才定义了数组的初始化容量10
newCapacity = minCapacity;
//如果扩容后的容量大于临界值,则进行大容量分配
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//新的容量大小已经确定好了,就copy数组,copyof(原数组,新的数组长度)
elementData = Arrays.copyOf(elementData, newCapacity);
}
4.3、List 接口常用方法
方法说明:
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在集合中首次出现的位置,若没有返回-1
int lastIndexOf(object obj):返回obj在当前集合中末次出现的位置,若没有返回-1
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index,object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合,左闭右开[fromIndex,toIndex)
代码测试:
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("wanli",3));
System.out.println(list);//[123, 456, AA, Person{name='wanli', age=3}]
//void add(int index, object ele):在index位置插入ele元素
list.add(2,"BB");
System.out.println(list);// [123, 456, BB, AA, Person{name='wanli', age=3}]
//boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加进来
List<Integer> list1 = Arrays.asList(1, 2, 3);
list.addAll(3,list1);
System.out.println(list);//[123, 456, BB, 1, 2, 3, AA, Person{name='wanli', age=3}]
//object get(int index):获取指定index位置的元素
System.out.println(list.get(2));//BB
list.add(2,"AA");
System.out.println(list);//[123, 456, AA, BB, 1, 2, 3, AA, Person{name='wanli', age=3}]
//int indexOf(Object obj):返回obj在集合中首次出现的位置
System.out.println(list.indexOf("AA"));//2
//int lastIndexOf(object obj):返回obj在当前集合中末次出现的位置
System.out.println(list.lastIndexOf("AA"));//7
//Object remove(int index):移除指定index位置的元素,并返回此元素
System.out.println(list);//移除前:[123, 456, AA, BB, 1, 2, 3, AA, Person{name='wanli', age=3}]
System.out.println(list.remove(1));//456
System.out.println(list);//移除后:[123, AA, BB, 1, 2, 3, AA, Person{name='wanli', age=3}]
//System.out.println(list.remove(789));//若元素不存在,报异常IndexOutOfBoundsException
//Object set(int index,object ele):设置指定index位置的元素为ele,返回index位置原来的元素
list.set(0, 333);
System.out.println(list);//[333, AA, BB, 1, 2, 3, AA, Person{name='wanli', age=3}]
//List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合,左闭右开[fromIndex,toIndex)
System.out.println(list);//[333, AA, BB, 1, 2, 3, AA, Person{name='wanli', age=3}]
System.out.println(list.subList(1, 4));//[AA, BB, 1]
4.4、遍历 List
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
Iterator iterator = list.iterator();
//遍历方式一:Iterator迭代器
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//遍历方式二:forEach
for (Object obj: list) {
System.out.println(obj);
}
//遍历方式三:普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
5、Set 接口
Set 接口概述
Set接口是Collection的子接口,set接口没有提供额外的方法。
Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。
Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法。
Set 和 List 的区别
1、Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
2、Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
3、List 和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。
5.1、Set 接口常用实现类对比
Set 实现类之 HashSet 介绍
HashSet是 Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。HashSet按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet具有以下特点:
- 不能保证元素的排列顺序
- HashSet不是线程安全的
- 集合元素可以是null
HashSet 是无序的,即不会记录插入的顺序。
HashSet 集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Objectobj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
Set 实现类之 LinkedHashSet 介绍
LinkedHashSet 是 HashSet 的子类
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
LinkedHashSet 插入性能略低于HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
LinkedHashSet 不允许集合元素重复。
Set 实现类之 TreeSet 介绍
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
TreeSet 底层使用红黑树结构存储数据
TreeSet 可以按照添加对象的指定属性,进行排序
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
5.2、Set 的无序性和不可重复性理解
以HashSet为例说明:
无序性
HashSet set = new HashSet();
set.add(123);
set.add(798);
set.add(new Person("wanli",3));
set.add(456);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
/*遍历结果顺序:
456
Person{name='wanli', age=3}
123
798
*/
}
无序性:指存储的数据在底层数组中并非按照数组索引的顺序添加,而是由数据的哈希值决定。
不可重复性
建一个实体类
public class User {
private String name;
private int age;
public User(){
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
代码实例:
@Test
public void test1(){
HashSet set = new HashSet();
set.add(123);
set.add(798);
set.add(new User("wanli",3));
set.add(new User("wanli",3));
set.add(456);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
上面代码中,我们在HashSet添加了两个User对象,查看运行结果:

两个User对象都输出了,因为Set集合只能存储不可重复的元素,说明两个User对象是不重复的。
HashSet 集合判断两个元素相等的标准: 两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
User实现类中重写equals()和hashCode(Object obj)方法:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
注意:对于存放在Set容器中的对象, 对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。
再次查看运行结果:

不可重复性:保证添加的元素进行equals()判断时,不能返回true。即相同的元素在Set集合中只能添加一个。
HashSet 中元素的添加过程
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为: 索引位置),
判断数组此位置上是否已经有元素:
-
如果此位置上没有其他元素,则元素a添加成功。
-
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
- 如果hash值不相同,则元素a添加成功。
- 如果hash值相同,进而需要调用元素a所在类的equLas()方法:返回true,添加失败,返回false,添加成功
在已有元素的数组索引上成功添加元素a时,元素a与已经存在指定索引位置上数据以链表的方式存储。存储方式:jdk7 头插法,jdk8 尾插法
5.3、TreeSet 的自然排序及定制排序
向 TreeSet 中添加元素,要求添加元素的对象是相同类型的。
默认实现的自然排序:
@Test
public void test(){
//添加Integer对象
TreeSet set = new TreeSet();
set.add(23);
set.add(12);
set.add(5);
set.add(62);
set.add(45);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//添加String对象
TreeSet set1 = new TreeSet();
set1.add("CC");
set1.add("BB");
set1.add("TT");
set1.add("EE");
set1.add("AA");
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
}
查看运行结果:

默认情况下,TreeSet 采用自然排序。
自定义类实现的自然排序
在 User 实体类中继承Comparable接口,并重写 compareTo 方法:
package collection;
public class User implements Comparable{
private String name;
private int age;
public User(){
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照姓名从小到大排序
@Override
public int compareTo(Object o) {
if (o instanceof User){
User user = (User) o;
return this.name.compareTo(user.name);
}else {
throw new RuntimeException("类型异常无法比较");
}
}
}
测试代码:往TreeSet集合中添加User对象
@Test
public void test(){
TreeSet set = new TreeSet();
set.add(new User("wanli",23));
set.add(new User("shengming",14));
set.add(new User("taoxian",36));
set.add(new User("bimo",12));
set.add(new User("neihan",35));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
查看运行结果:

往测试代码的TreeSet集合中放入两个姓名相同,年龄不同的User对象:
@Test
public void test(){
TreeSet set = new TreeSet();
set.add(new User("wanli",23));
set.add(new User("shengming",14));
set.add(new User("taoxian",36));
set.add(new User("bimo",12));
set.add(new User("neihan",35));
set.add(new User("neihan",46));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
再次查看运行结果:发现两个姓名相同,年龄不同的User对象只输出了一个

这是因为在TreeSet集合的自然排序中,判断两个对象是否相同不是用equals(),而是用compareTo()方法进行判断,如果compareTo()返回的是0,则判断两个对象相同。
在重写的compareTo()方法中加个二级判断可以解决上面问题:
@Override
public int compareTo(Object o) {
if (o instanceof User){
User user = (User) o;
//将姓名从大到小排序
int compare = -this.name.compareTo(user.name);
//如果姓名不相同,就直接返回
if (compare != 0){
return compare;
//如果姓名相同,再将年龄从小到大排序
}else {
return Integer.compare(this.age, user.age);
}
}else {
throw new RuntimeException("类型异常无法比较");
}
}
在有两个姓名相同,年龄不同的User对象的情况下,查看运行结果:发现两个姓名相同,年龄不同的User对象都输出。

TreeSet 定制排序
@Test
public void test1(){
Comparator comparator = new Comparator() {
//按照年龄从小到大排序
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User user1 = (User) o1;
User user2 = (User) o2;
return Integer.compare(user1.getAge(),user2.getAge());
}else {
throw new RuntimeException("类型异常无法比较");
}
}
};
//将排序方式选择为定制排序,不加comparator 则默认为自然排序
TreeSet set = new TreeSet(comparator);
set.add(new User("wanli",23));
set.add(new User("shengming",14));
set.add(new User("taoxian",36));
set.add(new User("bimo",12));
set.add(new User("neihan",12));
set.add(new User("neihan",46));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}

在TreeSet集合的定制排序中,判断两个对象是否相同不是用equals(),而是用compare()方法进行判断,如果compare()返回的是0,则判断两个对象相同。
6、Map 接口
Map接口: Map 接口存储一组键值对象,提供key(键)到value(值)的映射。
6.1、Map 接口常用实现类对比
HashMap:作为 Map 的主要实现类,线程不安全,效率高,可以存储 null 的 key 和 value。
LinkedHashMap:HashMap 的子类,保证在遍历 map 元素时,可以按照添加的顺序实现遍历,适用于需要频繁遍历的情况。
TreeMap:按照添加的key 和 value 进行排序,实现排序遍历。此时自然排序和定制排序都只考虑key,不考虑value。
Hashtable:是Map古老的实现类,线程安全,效率低,不能存储null 的 key 和 value。
Properties:Hashtable 的子类,常用来处理配置文件,key 和 value 都是string 类型。
HashMap 和 Hashtable的区别
与HashMap相比Hashtable是线程安全的,且不允许key、value是null。
Hashtable默认容量是11。
Hashtable是直接使用key的hashCode(key.hashCode())作为hash值,不像HashMap内部使用static final int hash(Object key)扰动函数对key的hashCode进行扰动后作为hash值。
Hashtable取哈希桶下标是直接用模运算%.(因为其默认容量也不是2的n次方。所以也无法用位运算替代模运算)
扩容时,新容量是原来的2倍+1。int newCapacity = (oldCapacity << 1) + 1;
Hashtable是Dictionary的子类同时也实现了Map接口,HashMap是Map接口的一个实现类;
6.2、HashMap 中存储的key-value特点
HashMap是 Map接口使用频率最高的实现类,允许使用null键和null值,与HashSet一样,不保证映射的顺序。
HashMap 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和lhashCode()
HashMap 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
HashMap 一个key-value 键值对构成一个entry
HashMap 所有的entry构成的集合是Set:无序的、不可重复的
HashMap 判断两个 key 相等的标准是:两个key通过equals()方法返回 true,hashCode值也相等。
HashMap判断两个 value 相等的标准是:两个value通过 equals()方法返回 true。
6.3、HashMap 源码分析(JDK 8)
6.3.1、HashMap 底层结构
在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,新增了红黑树作为底层数据结构,结构变得复杂了,但是使用效率也变的更高效。

6.3.2、HashMap 涉及的数据结构
链表(单链表)
Node 是 HashMap 的一个内部类,实现了Map.Entry接口,本质上是一个映射(键值对)。
具体源码:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//哈希值
final K key;// 键key
V value;//值value
Node<K,V> next;//指向下一个key-value键值对的引用
//构造函数
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; }
//每一个节点的hash值,是将key的hashCode 和 value的hashCode 异或得到的。
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
//设置新的value,并将旧的value返回
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//判断两个node是否相等,若key和value都相等,返回true。可以与自身比较
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;
}
}
HashMap 涉及的数据结构之——数组
//存储元素的数组,transient关键字表示该属性不能被序列化
transient Node<K,V>[] table;
HashMap 涉及的数据结构之——红黑树
关于红黑树的特性:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点(NIL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的路径上包含的黑色节点数量都相同。
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 父节点
TreeNode<K,V> left;//左子节点
TreeNode<K,V> right;//右子节点
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red; //颜色属性
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
/**
*返回包含此节点的树的根节点
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
6.3.3、HashMap 基本属性
//默认初始容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//数组最大容量 2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认负载因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表节点转换红黑树节点的阈值,当链表超过8个节点时,转换成红黑树
static final int TREEIFY_THRESHOLD = 8;
//红黑树节点转换链表节点的阈值,当链表节点小于等于6,会将红黑树转换成普通的链表
static final int UNTREEIFY_THRESHOLD = 6;
//当内部数组长度小于64时,不会将链表转化成红黑树,而是优先扩充数组。64是转红黑树时, table的最小长度
static final int MIN_TREEIFY_CAPACITY = 64;
//存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table;
//存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
//存放的键值映射数
transient int size;
//每次扩容和更改map结构的计数器
transient int modCount;
//临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
int threshold;
//负载因子
final float loadFactor;
6.3.4、构造函数
//构造一个具有默认初始容量 (16) 和默认负载因子 (0.75) 的空HashMap
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//构造一个具有指定初始容量和默认负载因子 (0.75) 的空HashMap
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//构造一个具有指定初始容量和负载因子的空HashMap
public HashMap(int initialCapacity, float loadFactor) {
//如果指定初始容量小于0,抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果指定的初始容量大于数组最大容量,设置指定容量为最大容量 2的30次方
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//如果指定的负载因子小于0,抛出异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//返回大于initialCapacity的最小的二次幂数值,作为临界值
this.threshold = tableSizeFor(initialCapacity);
}
//新建一个哈希表,同时将另一个map m 里的所有元素加入表中
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
tableSizeFor方法源码:
//返回大于initialCapacity的最小的二次幂数值,因为数组容量必须是2的次方,
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//判断n是否越界,返回 2的n次方作为 table(哈希桶)的阈值
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
6.3.5、put() 和 putVal()方法源码分析
put()方法源码分析
//将指定值与此映射中的指定键相关联。 如果映射先前包含键的映射,则旧值将被替换。
public V put(K key, V value) {
//先获得key的hash值,
return putVal(hash(key), key, value, false, true);
}
hash()算法源码分析
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
首先获取key的哈希值,然后将哈希值右移16位,再将右移后的值与原来的hashCode做异或运算,再将运算后的结果返回。
hash方法的作用是将hashCode进一步的混淆,增加其“随机度”,试图减少插入HashMap时的hash冲突,换句更专业的话来说就是提高离散性能。而这个方法知乎上有人回答时称为“扰动函数”。
putVal()源码分析
put方法就是通过putVal()来插入元素。
//参数:处理过的哈希值,键key,值value
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
//table是储存元素的数组,只有当table未初始化或者长度为0时,才会进入下面的resize()扩容方法
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//通过算法计算出key的哈希值在数组中对应的下标,并把该下标对应的元素取出来赋给p,
if ((p = tab[i = (n - 1) & hash]) == null)
//如果p为null,说明该位置没有元素,就直接添加到数组上
tab[i] = newNode(hash, key, value, null);
//如果该位置上已经有元素
else {
Node<K,V> e; K k;
//判断p节点(此时以链表形式存储)的key和hash值是否跟要添加的key-value相等,如果相等,则p节点即为要查找的目标节点,将p节点赋值给e节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果p节点属于红黑树的节点(即hash值不相等,即key不相等),将键值对插添加到红黑树
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//判断p节点后面的节点是否和要添加的节点冲突,
else {
//binCount 统计链表的节点数
for (int binCount = 0; ; ++binCount) {
//如果p节点后面没有节点了,就把要添加的节点放到p后面(尾插)
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//判断节点数是否超过8个,如果超过则调用treeifyBin方法将链表节点转为红黑树节点,-1是因为循环是从p节点的下一个节点开始的
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果p节点下一个节点e不为null,判断e节点的key和hash值是否跟要添加的key-value相等,如果相等,则e节点即为要查找的目标节点,退出循环,不再进行比较,去执行新值覆盖旧值操作
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//将p指向下一个节点,相当于p下移,继续判断下一个元素
p = e;
}
}
//如果e节点不为空,则代表目标节点存在,使用传入的value覆盖该节点的value,并返回oldValue
if (e != null) { // existing mapping for key
V oldValue = e.value;
//如果onlyIfAbsent为false或者旧值为null
if (!onlyIfAbsent || oldValue == null)
//新值覆盖旧值
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//实际大小大于临界值则扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
6.3.6、resize()源码分析
resize()源码分析
final Node<K,V>[] resize() {
//把原始数组赋给oldTab,oldTab为老表的容量
Node<K,V>[] oldTab = table;
//如果是第一次调用扩容方法,table为null,oldTab也为null,所以oldCap为0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//临界值threshold赋给oldThr,初始值都为0,oldThr为老表的临界值
int oldThr = threshold;
//定义新表容量和新表临界值,初始值都为0
int newCap, newThr = 0;
//如果老表容量大于0
if (oldCap > 0) {
//如果老表容量大于最大容量(2的30次方)了,就把最大的整数赋值给临界值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//如果当前hash桶数组的长度在扩容成原来两倍后仍然小于最大容量 并且oldCap大于默认值16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 双倍扩容阀值threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//如果老表容量和老表的临界值都不大于0,说明table还未初始化
else { // zero initial threshold signifies using defaults
//把默认初始容量DEFAULT_INITIAL_CAPACITY = 16赋给新表容量newCap
newCap = DEFAULT_INITIAL_CAPACITY;
//再计算出新表临界值赋给newThr,临界值 = 负载因子0.75 x 默认初始容量16 = 12
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);
}
//把上面计算得到的临界值newThr赋给threshold
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//新建一个新的hash桶数组,长度为16
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//把新创建的数组赋给table,所以当第一次put的时候,才把Node类型的数组容量定义为16
table = newTab;
//如果老表不为null,就进行扩容操作,复制Node对象值到新的hash桶数组
if (oldTab != null) {
//遍历老表
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//如果索引处存在节点,就把该索引上的节点设为null
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 { // preserve order
//loHead,loTail为原链表的节点,索引不变
Node<K,V> loHead = null, loTail = null;
//hiHeadm, hiTail为新链表节点,原索引 + 原数组长度
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
//开始遍历链表
do {
//把当前节点的指向下一个节点的引用赋给当前节点
next = e.next;
//如果节点e的hash值与原hash桶数组的长度作与运算为0
if ((e.hash & oldCap) == 0) {
//如果loTail为null
if (loTail == null)
//将节点e赋给loHead
loHead = e;
//如果loTail不为null
else
//将节点e赋给loTail的下一个节点
loTail.next = e;
//然后将节点e赋给loTail
loTail = e;
}
//如果节点e的hash值与原hash桶数组的长度作与运算不为0
else {
//如果hiTail为null
if (hiTail == null)
//将节点e赋给hiHead
hiHead = e;
//如果hiTail不为null
else
//将节点e赋给hiTail的下一个节点
hiTail.next = e;
//然后将节点e赋给hiTail
hiTail = e;
}
//退出遍历的条件:已经遍历到最后一个元素
} while ((e = next) != null);
//完成遍历后,如果loTail不等于null
if (loTail != null) {
//将loTail.next设置为空
loTail.next = null;
//将loHead放入新的hash桶数组[j]处
newTab[j] = loHead;
}
//如果hiTail不等于null
if (hiTail != null) {
//将hiTail.next设置为空
hiTail.next = null;
//将hiHead放入新的hash桶数组[j+旧hash桶数组长度]处
newTab[j + oldCap] = hiHead;
}
}
}
}
}
//将扩容后的数组返回
return newTab;
}
6.3.7、treeifyBin()源码分析
treeifyBin()源码分析
将给定数组索引处的链表节点转为红黑树节点
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//内部数组长度小于64时,不会将链表转化成红黑树,而是优先扩充数组,MIN_TREEIFY_CAPACITY = 64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//根据hash值计算出位于数组的的索引index,并把index上的节点赋给e,当e节点不为null
else if ((e = tab[index = (n - 1) & hash]) != null) {
//hd代表头节点,tl代表尾节点
TreeNode<K,V> hd = null, tl = null;
do {
//把e节点转成TreeNode类型,并赋给p
TreeNode<K,V> p = replacementTreeNode(e, null);
//如果尾节点为null
if (tl == null)
//将当前节点p设为头节点
hd = p;
else {
//把tl设置为p的前驱节点
p.prev = tl;
//把p设置为tl的后继节点
tl.next = p;
}
//把头节点设置成p后,把p赋值给尾节点tl,然后会再取链表的下一个节点,转成TreeNode类型后再赋值给p,进行循环
tl = p;
//当下一个节点为空,就代表这条链表遍历好了
} while ((e = e.next) != null);
//将table该索引位置赋值为新转换的TreeNode的头节点,如果该节点不为空,则以头节点(hd)为根节点, 构建红黑树
if ((tab[index] = hd) != null)
//头结点调用treeify方法,构建红黑树
hd.treeify(tab);
}
}
6.3.8、treeify()源码分析
treeify()源码分析
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
//将调用此方法的节点(this)赋值给x,以x作为起点,开始进行遍历
for (TreeNode<K,V> x = this, next; x != null; x = next) {
//将x下个节点的引用指向x
next = (TreeNode<K,V>)x.next;
//将x节点的左右置为null
x.left = x.right = null;
//如果根节点为null(说明红黑树还未构建),就将x赋给根节点root
if (root == null) {
x.parent = null;//x节点的父节点置为null
x.red = false;//x节点的颜色设为false
root = x;
}
//如果根节点root不为null(说明红黑树已经构建,此时在往树中添加节点)
else {
//取出x节点的key,存入k中
K k = x.key;
//取出x节点的哈希值,存入h中
int h = x.hash;
Class<?> kc = null;
//如果当前节点x不是根节点, 则从根节点开始遍历,此遍历没有设置边界,只能从内部跳出
for (TreeNode<K,V> p = root;;) {
//dir 标识方向(左右)、ph标识当前树节点的hash值
int dir, ph;
//取出当前树节点的key存入pk
K pk = p.key;
//如果当前树节点的哈希值大于调用treeify方法的链表节点(this)的哈希值
if ((ph = p.hash) > h)
//this节点会放在当前树节点左侧
dir = -1;
//如果当前树节点的哈希值小于调用treeify方法的链表节点(this)的哈希值
else if (ph < h)
//this节点会放在当前树节点右侧
dir = 1;
//如果两个节点的哈希值相等,再通过comparable的方式和tieBreakOrder的方式进行比较
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
//保存当前树节点,存入xp中
TreeNode<K,V> xp = p;
//如果dir大于0,说明当前链表节点(this/x)位于当前树节点右侧
//情况一:如果当前树节点为叶子节点,就执行x.parent = xp;,把当前树节点当做当前链表节点的父节点
//情况二:如果当前树节点不是叶子节点,以当前树节点的右子节点为起始节点,返回上面重新寻找当前链表节点的位置
//如果dir不大于0,说明当前链表节点(this/x)位于当前树节点左侧
//情况一:如果当前树节点为叶子节点,就执行x.parent = xp;,把当前树节点当做当前链表节点的父节点
//情况二:如果当前树节点不是叶子节点,以当前树节点的右子节点为起始节点,返回上面重新寻找当前链表节点的位置
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;//当前链表节点是当前树节点的左子节点
else
xp.right = x;//当前链表节点是当前树节点的右子节点
//balanceInsertion指的是红黑树的插入平衡算法,当树结构中新插入了一个节点后,要对树进行重新的结构化,以保证该树始终维持红黑树的特性。平衡之后,就可以针对下一个链表节点进行处理了。
root = balanceInsertion(root, x);
break;
}
}
}
}
//作用:保证如果HashMap元素数组根据下标取得的元素是一个TreeNode类型,那么这个TreeNode节点一定要是这颗树的根节点,同时也要是整个链表的首节点。
moveRootToFront(tab, root);
}
6.3、Map 中常用方法
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove():移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
测试代码:
Map map = new HashMap<String, Object>();
//添加
map.put("AA", 12);
map.put("BB", 13);
map.put("CC", 14);
//修改
map.put("AA", 15);
System.out.println(map);//{AA=15, BB=13, CC=14}
Map map1 = new HashMap<String, Object>();
map.put("DD", 16);
map.put("EE", 17);
//将map1中的所有key-value对存放到当前map中
map.putAll(map1);
System.out.println(map);//{AA=15, BB=13, CC=14, DD=16, EE=17}
//移除指定key的key-value对,并返回value
map.remove("AA");
map.remove("BB",13);
System.out.println(map);//{CC=14, DD=16, EE=17}
//清空当前map中的所有数据
map1.clear();
System.out.println(map1);//{}
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
测试代码:
Map map = new HashMap<String, Object>();
Map map1 = new HashMap<String, Object>();
map.put("AA", 12);
map.put("BB", 13);
map.put("CC", 14);
map.put("DD", 16);
map.put("EE", 17);
//获取指定key对应的value
System.out.println(map.get("BB"));//13
System.out.println(map.get("DD"));//16
//是否包含指定的key
System.out.println(map.containsKey("AA"));//true
System.out.println(map.containsKey("FF"));//false
//是否包含指定的value
System.out.println(map.containsValue(12));//true
System.out.println(map.containsValue(18));//false
//返回map中key-value对的个数
System.out.println(map.size());//5
System.out.println(map1.size());//0
//判断当前map是否为空
System.out.println(map.isEmpty());//false
System.out.println(map1.isEmpty());//true
//判断当前map和参数对象obj是否相等
Map map2 = new HashMap<String, Object>();
map2.put("AA", 12);
map2.put("BB", 13);
map2.put("CC", 14);
map2.put("DD", 16);
map2.put("EE", 17);
System.out.println(map.equals(map2));//true
遍历Map的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
测试代码:
Map map = new HashMap<String, Object>();
map.put("AA", 12);
map.put("BB", 13);
map.put("CC", 14);
map.put("DD", 16);
map.put("EE", 17);
//返回所有key构成的Set集合
Set set = map.keySet();
//调用Set集合的迭代器进行遍历操作
Iterator iterator = set.iterator();
//遍历所有的key
while (iterator.hasNext()){
System.out.println(iterator.next());//AA BB CC DD EE
}
//返回所有value构成的Collection集合
Collection values = map.values();
//调用Collection集合的迭代器进行遍历操作
Iterator iterator1 = values.iterator();
//遍历所有的value
while (iterator1.hasNext()){
System.out.println(iterator1.next());//12 13 14 15 16 17
}
//返回所有key-value对构成的Set集合
Set entrySet = map.entrySet();
//调用Set集合的迭代器进行遍历操作
Iterator iterator2 = entrySet.iterator();
//遍历所有的key-value键值对
while (iterator2.hasNext()){
Object next = iterator2.next();
Map.Entry map1 = (Map.Entry) next;
System.out.println(map1);//AA=12 BB=13 CC=14 DD=16 EE=17
}
6.4、TreeMap 两种排序实现
自然排序 Comparable
自定义类:
package map;
public class User implements Comparable{
private String name;
private int age;
public User(){
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Object o) {
if (o instanceof User){
User user = (User) o;
//将姓名从大到小排序
int compare = -this.name.compareTo(user.name);
//如果姓名不相同,就直接返回
if (compare != 0){
return compare;
//如果姓名相同,再将年龄从小到大排序
}else {
return Integer.compare(this.age, user.age);
}
}else {
throw new RuntimeException("类型异常无法比较");
}
}
}
测试代码:
TreeMap<User, Integer> map = new TreeMap<>();
User user1 = new User("wanli", 11);
User user2 = new User("shengming", 12);
User user3 = new User("yumo", 13);
map.put(user1,1);
map.put(user2,2);
map.put(user3,3);
Set<Map.Entry<User, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<User, Integer>> iterator = entrySet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
定制排序 Comparator
测试代码:
TreeMap<User, Integer> map = new TreeMap<>(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
//按照年龄从大到小排序
return -Integer.compare(o1.getAge(), o2.getAge());
}
});
User user1 = new User("wanli", 11);
User user2 = new User("shengming", 12);
User user3 = new User("yumo", 13);
map.put(user1,1);
map.put(user2,2);
map.put(user3,3);
Set<Map.Entry<User, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<User, Integer>> iterator = entrySet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
7、Collections 工具类使用
Collections是一个操作Set、List、Map 等集合的工具类。
Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
7.1、常用方法
排序操作:(均为static方法):
-
reverse(List):反转List中元素的顺序
-
shuffle(List):对List集合元素进行随机排序
-
sort(List):根据元素的自然顺序对指定List集合元素按升序排序
-
sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
-
swap(List,int,int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
方法测试代码:
ArrayList list = new ArrayList();
list.add(2);
list.add(12);
list.add(22);
list.add(31);
list.add(6);
//everse(List):反转List中元素的顺序
Collections.reverse(list);
System.out.println(list);//[6, 31, 22, 12, 2]
//shuffle(List):对List集合元素进行随机排序
Collections.shuffle(list);
System.out.println(list);//[12, 6, 22, 31, 2]
//sort(List):根据元素的自然顺序对指定List集合元素按升序排序
Collections.sort(list);
System.out.println(list);//[2, 6, 12, 22, 31]
//sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
ArrayList list1 = new ArrayList();
User user1 = new User("wanli", 3);
User user2 = new User("wanli", 60);
User user3 = new User("wanli", 24);
User user4 = new User("wanli", 18);
list1.add(user1);
list1.add(user2);
list1.add(user3);
list1.add(user4);
Collections.sort(list1, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
//按照年龄从小到大排序
return Integer.compare(o1.getAge(),o2.getAge());
}
});
System.out.println(list1);
//swap(List,int,int):将指定 list 集合中的下标 i 处元素和 j 处元素进行交换
Collections.swap(list,2,3);
System.out.println(list);//[2, 6, 22, 12, 31]
查找、替换
-
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
-
Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
-
Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素
-
Object min(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最小元素
-
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
-
void copy(List dest,List src):将src中的内容复制到dest中
-
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
方法测试代码:
List list = new ArrayList();
list.add(2);
list.add(12);
list.add(22);
list.add(22);
list.add(31);
list.add(6);
//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println(Collections.max(list));//31
//Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素
System.out.println(Collections.min(list));//2
ArrayList list1 = new ArrayList();
User user1 = new User("wanli", 3);
User user2 = new User("wanli", 60);
User user3 = new User("wanli", 24);
User user4 = new User("wanli", 18);
list1.add(user1);
list1.add(user2);
list1.add(user3);
list1.add(user4);
//Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
System.out.println(Collections.max(list1, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
//按照年龄从大到小排序
return -Integer.compare(o1.getAge(), o2.getAge());
}
}));//User{name='wanli', age=3}
//Object min(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最小元素
System.out.println(Collections.min(list1, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
//按照年龄从大到小排序
return -Integer.compare(o1.getAge(), o2.getAge());
}
}));//User{name='wanli', age=60}
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println(Collections.frequency(list, 22));//2
//void copy(List dest,List src):将src中的内容复制到dest中
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest);//[null, null, null, null, null, null]
Collections.copy(dest,list);
System.out.println(list);//[2, 12, 22, 22, 31, 6]
System.out.println(dest);//[2, 12, 22, 22, 31, 6]
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
Collections.replaceAll(list,22,10);
System.out.println(list);//[2, 12, 10, 10, 31, 6]
同步控制
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
synchronizedList()
synchronizedSet()
synchronizedMap()
synchronizedList() 使用实例:
public class ListTest {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 1; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
本文详细探讨了Java集合框架中的HashMap和TreeSet。HashMap通过数组+链表+红黑树的结构实现,提供高效的存储和查找效率,其put方法涉及到扩容、哈希计算和红黑树转换。TreeSet基于红黑树,保证元素排序,插入元素时会进行自然排序或定制排序。两者在不同场景下各有优势,适合不同需求的存储和访问。
1007

被折叠的 条评论
为什么被折叠?



