Java进阶——集合

本文详细探讨了Java集合框架中的HashMap和TreeSet。HashMap通过数组+链表+红黑树的结构实现,提供高效的存储和查找效率,其put方法涉及到扩容、哈希计算和红黑树转换。TreeSet基于红黑树,保证元素排序,插入元素时会进行自然排序或定制排序。两者在不同场景下各有优势,适合不同需求的存储和访问。

Java进阶——集合

1、集合框架简介

集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下。

Java集合框架(Java Collections Framework,JCF)是为表示和操作集合而规定的一种统一的标准的体系结构。 任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。

  • 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
  • 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
  • 算法**:**是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现(实现类),诸如: LinkedList, HashSet, 和 TreeSet 等,除此之外你也可以通过这些接口实现自己的集合。

集合框架体系如图所示

img

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();
        }
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万里顾—程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值