Java集合笔记

Java集合笔记

一.Java集合框架概述

​ 一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。
​ 另一方面,使用Array存储对象方面具有一些弊端,而Java集合就像一种容器,可以动态的把多个对象的引用放入容器中。
在这里插入图片描述

1.数组Array存储

(1)数组在内存存储方面的特点

​ ①数组初始化以后,长度就确定了。

​ ②数组声明的类型,就决定了进行元素初始化的类型。

(2)数组在存储数据方面的弊端(长度、类型不可变)

​ ①数组初始化以后,长度就不可变了,不便于拓展。

​ ②数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数。

​ ③数组存储的数据是有序的、可以重复的。------->存储数据的特点单一。

2.Java集合类存储

​ Java集合类可以用于存储数量不等的多个对象,还可以用于保存具有映射关系的关联数组。Java集合类都位于java.util包中,所以使用时一定要注意导包问题,否则会出现异常,为了方便,程序中可以统一使用“ import java.util.*; ”来进行导包,其中“ * ”为通配符,整个语句的意思是将java.util包中的内容都导入进来。

3. Java集合体系

(1)Collection接口

单列集合的根接口,定义了存取一组对象的方法的集合.

​ ①List:元素有序、可重复的集合**(List 有序可重复,有下标,下标从0开始)**

​ ②Set:元素无序、不可重复的集合**(Set 无序不可重复,无下标)**

​ ③Queue

(2)Map接口

双列数据,保存具有映射关系**“key-value对”**的集合。

​ ①TreeMap

​ ②HashMap

4.Collection接口继承树

在这里插入图片描述

5.Map接口继承树

在这里插入图片描述

6.集合体系核心架构图

3f50db8dd5adb464c24b8c61e165ec8

二.Collection 接口

1.Collection接口

(1)Collection 接口是ListSetQueue接口的父接口,是所有单列集合的根接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。
(2)JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List实现)。
(3)在Java5之前,Java集合会丢失容器中所有对象的数据类型,把所有对象都当成Object类型处理;从JDK5.0增加了泛型之后,Java集合可以记住容器中对象的数据类型。

2.Collection接口方法

(1)添加
add(Object obj);      //将元素obj添加到此集合中

addAll(Collection coll);    //将指定集合中的所有元素添加到此集合中
(2)获取有效元素的个数
int size();    //注意:size()方法获取的是集合有效元素的个数,不是获取集合的容量。
(3)清空集合
void clear();
(4)是否是空集合
boolean isEmpty();    //是空集合,则返回True
(5)是否包含某个元素
boolean contains(Object obj);    //如果此集合中包含指定的元素,则返回True 

boolean containsAll(Collection coll);    //如果此集合包含指定集合中的`所有`元素,则返回True 
(6)删除
boolean remove(Object obj);     //删除集合中的一个元素,并且只会删除找到的第一个元素,返回Ture

boolean removeAll(Collection coll);     //删除此集合中的也包含在指定集合中的所有元素,返回Ture
(7)取两个集合的交集
boolean retainAll(Collection coll);     //把两集合交集的结果存在当前集合中,不影响coll集合
(8)集合是否相等
boolean equals(Object obj);
(9)转成对象数组
Object[] toArray();    //把集合转为数组
(10)获取集合对象的哈希值
int hashCode();     //返回此集合的哈希码值
(11)遍历
Iterator<E> iterator();    //返回迭代器对象,用于集合遍历。
(12)转化为有序的流对象
Stream<E> stream();     //将集合转化为有序元素的流对象

三.Iterator迭代器接口

1.使用Iterator接口遍历集合元素

(1)Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
(2)GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“空姐”、“火车上的乘务员”。
(3)Collection 接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
(4)Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
(5)集合对象每次调用iterator()方法都能得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

2. Iterator 接口的方法

(1)hasNext() 方法
boolean hasNext();     //判断是否还有下一个元素
(2)next() 方法
E next();    //指针下移,将下移以后集合位置上的元素返回
(3)remove() 方法
void remove();     //删除元素

3.迭代器遍历元素过程图

54035c854766b22d5f32274c1f5110e

注意:

① 在调用it.next() 方法之前必须要调用it.hasNext() 进行检测。若不调用,且下一条记录无效,直接调用it.next() 会抛出NoSuchElementException异常。

②当集合的结构改变时,必须重新获取迭代器,否则会报:ConcurrentModificationException。

③在迭代集合元素的过程中,不能调用集合对象的remove()方法来删除元素,要用迭代器对象的remove()方法,否则会报:ConcurrentModificationException。

④如果还未调用next()或在上一次调用naxt方法之后已经调用了remove方法,再调用remove 都会报IllegalStateException。

4.使用foreach循环(增强for循环)遍历集合元素

(1)JDK5开始提供了foreach循环迭代访问Collection和数组。
(2)遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
(3)遍历集合的底层调用Iterator完成操作。
(4)foreach还可以用来遍历数组。
(5)foreach循环具体语法格式
for ( 容器中元素类型 临时变量 :容器变量 ){
	//执行语句
}

注意:foreach循环只能访问集合中的元素,不能对其元素进行修改!!!

四.Collection子接口之一:List 接口

1.List接口概述

(1)鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组。
(2)List集合类中元素有序可重复,集合中的每个元素都有其对应的顺序索引。
(3)List容器中的元素都对应一个整数型的序号(索引)记载其在容器中的位置,可以根据序号(索引)存取容器中的元素(索引从零开始,最后一个索引是size-1)。
(4)JDK API中List接口的实现类常用的有:ArrayList、LinkList 和 Vector。

2.List接口方法

​ List接口是Collection的子接口,除了从Collection集合继承的方法外,List集合里添加了一些根据索引来操作集合元素的特有(不能用Collection对象调用)方法:

(1)add() 方法
void add(int index,Object ele);  //在index位置插入ele元素(使用率不高,因为效率比较低)。
(2)addAll() 方法
boolean addAll(int index,Collection eles);   //从index位置开始将集合eles中的所有元素添加进来。
(3)set() 方法
Object set(int index,Object ele) ;     //把指定index位置的元素改为ele。
(4)get() 方法
Object get(int index);    //获取指定index位置的元素。
(5)indexOf() 方法
int indexOf(Object obj);   //返回obj在集合中首次出现的位置。
(6)lastIndexOf() 方法
int lastIndexOf(Object obj);   //返回obj在集合中末次出现的位置。
(7)remove() 方法
Object remove(int index);    //移除指定index位置的元素,并返回此元素。
(8)subList() 方法
javaList subList(int fromIndex,int toIndex);     //返回从fromIndex到toIndex位置的子集合。
(9)toArray()方法
Object[] toArray();    //将集合元素转化为数组

3.List 实现类之一:ArrayList

(1)ArrayList的继承关系

在这里插入图片描述

(2)ArrayList 是List接口的典型实现类、主要实现类。
(3)本质上,ArrayList是对象引用的一个能“变长”的Object[]数组。
(4)ArrayList的JDK1.8之前与之后的实现区别?

​ JDK1.7:ArrayList 像饿汉式,直接创建一个初始容量为10的数组

​ JDK1.8:ArrayList 像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时在创建一个始容量为10的数组。

// 注:当添加进ArrayList中的元素超过了数组能存放的最大值就会进行扩容。
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 采用右移运算,就是原来的一半,所以是扩容1.5倍。比如10的二进制是1010,右移后变成101就是5。
(5)Arrays.List(…)方法返回的List集合,既不是ArrayList实例,也不是Vector实例。ArrayList(…)返回值是一个固定长度的List集合。
(6)ArrayList的优缺点

​ 优点:检索效率比较高。

​ 缺点:随机增删元素时效率较低,但如果是向数组末尾添加元素,效率还是很高的。

(7)ArrayList使用建议

​ 建议给定一个预估计的初始化容量,减少数组的扩容次数,这是ArrayList集合比较重要的优化策略。

(8)ArryList的modCount 的属性

​ ArrayList中有一个modCount的属性,表示该实例修改的次数。(所有集合中都有modCount 这样一个记录修改次数的属性),每次增改添加都会增加一次该ArrayList修改次数,即该ArrayList的modCount属性值。

(9)构造ArryList 集合的方法
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

public class Main {
      public static void main(String[] args) {
            // 默认初始化容量为10
            List myList1 = new ArrayList();

            // 默认初始化容量为100
            List myList2 = new ArrayList(100);

            // 创建一个HashSet集合
            Collection collection = new HashSet();
            // 添加元素到Set集合
            collection.add(100);
            collection.add(200);
            collection.add(400);
            collection.add(500);

            //通过这个构造方法就可以将HashSet集合转化为List集合。
            List myList3 = new ArrayList(collection);
            for (int i = 0; i < myList3.size(); i++) {
                  System.out.println(myList3.get(i));
            }
      }
}

4.List实现类之二:LinkedList

(1)LinkedList 继承关系

在这里插入图片描述

(2)对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高。
(3)因为LinkedList底层是包含有两个Node类型的first和last属性的双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。链表的内存地址不连续,所以查询效率比较低。

​ 注:链表的优缺点:

​ ①优点:随机增删元素效率较高。(因为链表的内存地址不连续,增删元素不涉及到元素的位移)

​ ②缺点:查询效率较低,每一次查找某个元素的时候都需要从节点开始往下遍历。

​ 注:双向循环链表增删元素示意图

4fc448a0bb12b85d79ea98e41d2d50f

(4)新增方法:

​ LinkedList 集合除了从接口Collection和List中继承并实现了集合操作方法外,还专门针对元素的增删操作定义了一些特有的方法。

①添加元素
  void add(int index,E element);   //在此列表指定的位置插入指定的元素。
  void addFirst(Object obj);   //在此列表的开头插入指定的元素。
  void addLast(Object obj);    //将指定的元素追加到此列表的末尾。
②获取元素
  Object getFirst();   //返回此列表中的第一个元素。
  Object getLast();    //返回此列表中的最后一个元素。
③删除元素
  Object removeFirst();    //从此列表中删除并返回第一个元素。
  Object removeLast();     //此列表中删除并返回最后一个元素。 
(5)LinkedList:双向链表,由一个头节点和一个尾节点组成,分别指向链表的头部和尾部。内部没有声明数组,而是定义了Node类型的 first 和 last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:
① prev 变量记录前一个元素的位置
② next 变量记录下一个元素的位置
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;
    }
}

单向链表结构示意图:
在这里插入图片描述

双向链表结构示意图:

在这里插入图片描述

5.List 实现类之三:Vector

(1)Vector 底层是一个数组,初始化容量为: 10,满了之后会扩容到原来的2倍(10 ----> 20 ----> 40 ----> 80)
(2)Vector是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的(都带有synchronized关键字)。
(3)在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList。Vector总是比ArrayList慢,所以尽量避免使用。
(4)新增方法:
① addElement()方法
void addElement(Object obj);   //将指定的组件添加到此向量的末尾,相当于add();
② insertElement()方法
void insertElement(Object obj,int index);   //将指定对象作为此向量中的组件插入指定的index 
③ elementAt()方法
E elementAt(int index);    //返回指定索引处的组件,相当于get();
④ removeElement()方法
void removeElement(Object obj);    //从此向量中移除参数的第一个(索引最低)事件。
⑤ removeAllElements()方法
 void removeAllElements();    //从此向量中移除所有组件并将其大小设置为零。
⑥ synchronizedList()方法
 List synchronizedList(List list)   //返回由指定列表支持的同步(线程安全)列表。

6.相应面试题

(1)ArrayList、LinkedList的异同:二者都线程不安全,相对线程安全的Vector,执行效率高。此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList 优先于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList 比较占优势,因为ArrayList 要移动数据。

(2)ArrayList 和Vector 的区别:ArrayList 和Vector 几乎是完全相同的,唯一的区别是在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList 要大,访问要慢。正常情况下,大多数的程序员使用ArrayList 而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的两倍空间,而ArrayList 是1.5倍。Vector 还有一个子类Stack。

五. Collection 子接口之二:Set 接口

1.Set 接口概述

(1)Set集合存储元素无序不可重复,无序指的是没有下标。
(2)Set接口是Collection的子接口,set 接口没有提供额外的方法。
(3)Set 集合不允许包含相同的元素 ,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。
(4)Set 判断两个对象是否相同不是使用“ == ”运算符,而是根据 equals() 方法。
(5)Set集合常用方法以及遍历方式
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

//Set集合常用方法以及遍历方式
public class SetDemo {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("han");
        set.add("hsz");
        set.add("hhh");
        //判断该集合是否为空
        System.out.println(set.isEmpty());
        //判断该集合是否包含某元素
        System.out.println(set.contains("han"));
        //获取集合中的元素数
        System.out.println(set.size());
        //删除集合中指定位置的元素
        System.out.println(set.remove("hsz"));
        
        //1、使用增强型for循环遍历(因为set集合没有索引,所以不能使用普通for循环遍历)
        for (String s : set){
            System.out.println(s);
        }
        System.out.println("======================================================");
        
        //2、使用迭代器Iterator遍历
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            System.out.println(next);
        }
    }
}

2. Set 实现类之一 : HashSet

(1)HashSet 概述

​ ①HashSet 是Set 接口的典型实现,大多数时候使用 Set 集合时都是用这个实现类。

​ ②HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能

​ ③HashSet 具有的特点:Ⅰ. 不能保证元素的排列顺序

Ⅱ. HashSet不是线程安全的

Ⅲ. 集合元素可以是 null

​ ④ HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相同

​ ⑤对于存放在 Set 容器中的对象,对应的类一定要重写 equals() 和 hashCode(Object obj) 方法,以实现对象想等原则。即:“相等的对象必须具有相等的散列码”。

(2)向 hashSet 中添加元素的过程

​ ①当向HashSet 集合中存入一个元素时,HashSet 会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值,通过某种散列函数决定该对象在HashSet底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)

​ ②如果两个元素的hashCode()值相等,会再调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续连接。

​ ③如果两个元素的equals() 方法返回为true,但他们的hashCode()返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
在这里插入图片描述

(3)重写hashCode()方法的基本原则

​ ①在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。

​ ②当两个对象的equals()方法比较返回为true时,这两个对象的hashCode()方法的返回值也应相等。

​ ③对象中用作equals()方法比较的Field,都应该用来计算hashCode值。

(4)重写equals()方法的基本原则

​ ①当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals()方法(改写之后),两个截然不同的实例有可能是在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象。

​ ②因此违反了“相等的对象必须具有相等的散列码”。

​ ③结论:复写equals方法的时候一般需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。

3.Set实现类之二 : LinkedHashSet

(1)LinkedHashSet是HashSet的子类
(2)LinkedHashSet根据元素的hashCode来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
(3)LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
(4)LinkedHashSet不允许集合元素重复

4.Set实现类之三 : TreeSet

(1)TreeSet概述

​ ①TreeSet是SortedSet接口的实现类,放到TreeSet集合中的元素: 无序不可重复,但是可以按照元素的大小顺序自动排序

    // 集合的创建
    TreeSet<Integer> ts = new TreeSet<>();
    // 添加元素
    ts.add(1);
    ts.add(100);
    ts.add(10);
    ts.add(0);
    ts.add(15);
    //迭代器遍历
    Iterator<Integer> it = ts.iterator();
    while (it.hasNext()) {
		Integer next = it.next();
		System.out.println(next);
    }
    //增强for循环遍历
    for (Integer i : ts) {
		System.out.println(i);
	}
//运行结果:
0
1
10
15
100

​ ②TreeSet集合底层实际上是一个TreeMap集合,TreeMap集合底层一个二叉树。

​ ③放在TreeSet集合中的元素,等同于放在TreeMap集合key部分。

​ ④TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序,称为:可排序集合。

​ ⑤Tree两种排序方法:自然排序和定制排序。默认情况下,Tree采用自然排序。

​ ⑥TreeSet和后面的TreeMap采用红黑树的存储结构。特点:有序,查询速度比List快。

​ ⑦TreeSet/TreeMap底层都采用的是自平衡二叉树(TreeSet底层是TreeMap):遵循左小右大的原则存放,存放的过程也就是排序的过程。

(2)常用方法(了解)
① comparator()方法
Comparator comparator();    //返回用于如果此set使用命令在该组中的元素,或null比较natural ordering中的元素。 
② higher(Object e)方法
Object higher(Object e);    //返回此集合中的最小元素严格大于给定元素,如果没有这样的元素,则 null 。
③first()方法
Object first();    //返回此集合中当前的第一个(最低)元素。
④ last() 方法
Object last();    //返回此集合中当前的最后一个(最高)元素。 
⑤ subSet(E fromElement, E toElement)方法
SortedSet subSet(E fromElement, E toElement);    
//返回此set的部分视图,其元素范围从 fromElement (包括 fromElement )到 toElement (独占)。 
⑥ headSet(toElement)方法
SortedSet headSet(toElement);     //返回此set的部分视图,其元素严格小于 toElement 。
⑦lower(Object e)方法
Object lower(Object e);     //返回此集合中小于给定元素的最大元素,如果没有这样的元素,则 null 。
⑧ higher(Object e)方法
Object higher(Object e);     //返回此集合中大于给定元素的最小元素,如果没有这样的元素,则 null 。
⑨taiSet(fromElement) 方法
SortedSet taiSet(fromElement);     //返回此set的部分视图,其元素大于或等于 fromElement 。
(3)应用场景:在页面展示用户信息的时候按照生日升序或者降序
数据库中有很多数据:
userid  name       birth
----------------------------
  1      zs      1980-11-11
  2      ls      1980-10-11
  3      ww      1981-11-11
  4      zl      2001-12-23
编写程序从数据库当中取出数据,在页面展示用户信息的时候按照生日升序或者降序。
这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来是有序的

TreeSet集合,对自定义类型不能进行排序!!!

example:

public class TreeSetTest03 {
      public static void main(String[] args) {
            Person p1 = new Person(32);
            Person p2 = new Person(20);
            Person p3 = new Person(25);
            TreeSet<Person> ts = new TreeSet<>();
            ts.add(p1);
            ts.add(p2);
            ts.add(p3);
            for (Person x : ts) {
                  System.out.println(x);
            }
      }
}
class Person {
      int age;

      public Person(int age) {
            this.age = age;
      }

      // 重写toString方法
      @Override
      public String toString() {
            return "Person{" + "age=" + age + '}';
      }
}

运行结果:

Exception in thread "main" java.lang.ClassCastException: com.lxz.review1.Person cannot be cast to java.lang.Comparable
	at java.util.TreeMap.compare(TreeMap.java:1294)
	at java.util.TreeMap.put(TreeMap.java:538)
	at java.util.TreeSet.add(TreeSet.java:255)
	at com.lxz.review1.TreeSetTest03.main(TreeSetTest03.java:23)

​ 程序运行结果抛出了一个异常:Person cannot be cast to java.lang.Comparable.程序无法进行排序是因为没有指定自定义类型Person对象之间的比较规则,谁大谁小没有说明!

在TreeSet中实现自定义类型的排序有两种方法

方式一:自然排序:放在TreeSet集合中的元素需要实现java.lang.Comparable接口

public class TreeSetTest04 {
      public static void main(String[] args) {
            Person1 p1 = new Person1(32);
            Person1 p2 = new Person1(20);
            Person1 p3 = new Person1(25);
            TreeSet<Person1> ts = new TreeSet<>();
            ts.add(p1);
            ts.add(p2);
            ts.add(p3);
            for (Person1 x : ts) {
                  System.out.println(x);
            }
      }
}

/**
 * 放在TreeSet集合中的元素需要实现java.lang.Comparable接口
 * 并且实现compareTo方法。equals可以不写
 */
class Person1 implements Comparable<Person1> {
      int age;
      public Person1(int age) {
            this.age = age;
      }

      // 重写toString方法
      @Override
      public String toString() {
            return "Person1{" + "age=" + age + '}';
      }

      /**
       * 需要在这个比较的方法中编写比较的逻辑或者比较的规则,按照什么进行比较
       * 拿着参数k和集合中的每一个key进行比较,返回值可能是大于0 小于0 或者等于0
       * 比较规则最终还是由程序员指定的; 例如按照年龄升序,或者按照年龄降序。
       *
       * @param o
       * @return
       */
      @Override
      public int compareTo(Person1 o) { // c1.compareTo(c2)
            return this.age - o.age; // >0 =0 <0 都有可能
      }
}

方式二:定制排序:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象

public class TreeSetTest06 {
    public static void main(String[] args) {
        // 此时创建TreeSet集合的时候,需要使用这个比较器。
        // TreeSet<WuGui> wuGui = new TreeSet<>();
        // 这样不行,没有通过构造方法传递一个比较器进去。
        // 给构造方法传递一个比较器
        TreeSet<WuGui> wuGui = new TreeSet<>(new WuGuiComparator()); 
        // 底层源码可知其中一个构造方法的参数为比较器
        // 大家可以使用匿名内部类的方式
        wuGui.add(new WuGui(100));
        wuGui.add(new WuGui(10));
        wuGui.add(new WuGui(1000));
        for (WuGui wugui:
             wuGui) {
            System.out.println(wugui);
        }
    }
}
class WuGui {
    int age;
    public WuGui(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "WuGui{" + "age=" + age + '}';
    }
}
// 单独再这里编写一个比较器
// 比较器实现java.util.Comparator接口 (Comparable是java.lang包下的。Comparator是java.util包下的。)
class WuGuiComparator implements Comparator<WuGui>{
    @Override
    public int compare(WuGui o1, WuGui o2) {
        // 指定比较规则
        // 按照年龄排序
        return o1.age-o2.age;
    }
}

**注意:**Comparable 和 Comparator 两种方式怎么选择:

​ 比较规则经常变换时,建议使用: Comparator 接口的设计符合OCP原则(可切换)
​ 比较规则较固定时,建议使用: Comparable

example: 先按照年龄升序排序,如果年龄一样再按照姓名升序排序(采用实现Comparable接口的方法)

public class TreeSetTest05 {
    public static void main(String[] args) {
        TreeSet<Vip> vips = new TreeSet<>();
        vips.add(new Vip("zhangsi",20));
        vips.add(new Vip("zhangsan",20));
        vips.add(new Vip("liuxiangzheng",18));
        for (Vip vip : vips) {
            System.out.println(vip);
        }
    }
}
class Vip implements Comparable<Vip>{
    String name;
    int age;

    public Vip(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Vip{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    @Override
    public int compareTo(Vip o){    //这面写排序规则
        if(this.age==o.age){
            // 年龄一样按照姓名升序来
            // note: this.name 是字符串,String实现了compareTo方法,故直接调用即可
            return this.name.compareTo(o.name);
        }else{
            // 年龄不一样
            return this.age-o.age;
        }
    }
}

总结:

​ 对于自定义的类型,想要在TreeSet中实现排序,必须实现Comparable接口或者编写Comparator比较器,制定其比较规则!JDK自己提供的数据类型不用程序员实现Comparable接口或者编写Comparator比较器是因为它的底层源码都实现了Comparable接口,具有了比较规则,故可以排序!

Comparable接口中重写的CompareTo方法的返回值很重要:

返回0表示相同,value会覆盖
返回>0,会继续在右子树上找
返回<0,会继续在左子树上找

二叉树简介:

遍历二叉树的三种方式: (遵循**左小右大**原则存放)

前序遍历:根左右
中序遍历:左根右(满足自平衡二叉树的存放方式,中序遍历取出数据的时候就为自动排序好的数据)
后序遍历:左右根
TreeSet/TreeMap集合采用的是:中序遍历

二叉树的遍历均可以看成是递归的过程,也就是将一个树不断的划分成左子树、根、右子树的过程,直到不能再划分成一个子树

六. Map接口

在这里插入图片描述

1.Map接口概述

​ ① Map接口与Collection接口并列存在,它是一个双列集合,用于保存具有映射关系的数据:key-value(键值对) ,Map 中的 key 和 value 都是引用数据类型,都是存储对象的内存地址。

​ ② Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法

​ ③ 常用String类作为Map的“键” ,key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一的、确定的 value(key起到主导的地位,value是key的附属品)。

​ ④ Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和 Properties。其中,HashMap是 Map 接口使用频率最高的实现类

2.Map接口常用方法

​ ① 添加、删除、修改操作:

Object put(Object key,Object value);   //将指定key-value添加到(或修改)当前map对象中
void putAll(Map m);   //将m中的所有key-value对存放到当前map中
Object remove(Object key);    //移除指定key的key-value对,并返回value
void clear();    //清空当前map中的所有数据
boolean replace(Object key, Object value);    //删除Map集合中键值映射同时匹配的元素

​ ② 元素查询的操作:

Object get(Object key);     //获取指定key对应的value
boolean containsKey(Object key);    //判断Map集合中是否包含指定的key
boolean containsValue(Object value);    //判断Map集合中是否包含指定的value
int size();   //获取Map集合中key-value对的个数
boolean isEmpty();   //判断当前Map集合的元素个数是否为0
boolean equals(Object obj);  //判断当前map和参数对象obj是否相等

​ ③ 元视图操作的方法:

Set keySet();   //获取Map集合中所有的key
Collection values();   //以Collection集合的形式,获取Map集合中所有的value
Set entrySet();    //将Map集合转换成Set集合

3.Map 集合的遍历方式

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class Test01 {
      public static void main(String[] args) {

//第一种方式:获取所有的key,来遍历value
            Map<Integer, String> map = new HashMap<>();
            map.put(1, "value1");
            map.put(2, "value2");
            map.put(3, "value3");
            map.put(4, "value4");
            map.put(5, "value5");
            // 遍历Map集合
            //获取所有的key,所有的key是一个Set集合
            Set<Integer> keys = map.keySet();
            //遍历key,通过key获取value

            //①迭代器可以
            Iterator<Integer> iterator = keys.iterator();
            while (iterator.hasNext()) {
                  //取出其中一个key
                  Integer key = iterator.next();
                  //通过key获取value
                  String value = map.get(key);
                  System.out.println(key + "=" + value);
            }
            System.out.println("================================");

            //②foreach也可以
            for (Integer key : keys) {
                  System.out.println(key + "=" + map.get(key));
            }
            System.out.println("================================");

//第二种方式:Set<Map.Entry<K, V>>   entrySet()
//entrySet()方法是把Map集合直接全部转化为Set集合
//Set集合中的元素的类型是Map.Entry
            Set<Map.Entry<Integer, String>> set = map.entrySet();
            //遍历Set集合,每一次取出一个Node

            //①迭代器可以
            Iterator<Map.Entry<Integer, String>> iterator2 = set.iterator();
            while (iterator2.hasNext()) {
                  Map.Entry<Integer, String> node = iterator2.next();
                  Integer key = node.getKey();
                  String value = node.getValue();
                  System.out.println(key + "=" + value);
            }
            System.out.println("================================");
          
            //②foreach也可以
            //这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
            //这种方式比较适用于大数据量。
            for (Map.Entry<Integer, String> node : set) {
                  System.out.println(node.getKey() + "=" + node.getValue());
            }
      }
}

4.Map实现类之一:HashMap

(1)HashMap概述

​ ①HashMap是Map接口使用频率最高的实现类,底层是哈希表/散列表的数据结构。

​ ②HashMap集合的默认初始化容量为: 16,默认加载因子是0.75(默认加载因子是当HashMap集合底层数组的容量达到75%时,数组开始扩容,扩容到原来的2倍。注意:HashMap集合初始化容量必须为2的倍数,这是官方推荐的,目的是为了达到散列均匀,为了提高HashMap集合的存取效率。)

​ ③允许使用null键和null值,与HashSet一样,不保证映射的顺序。

​ ④所有的key构成的集合是HashSet:无序不可重复的,所以key所在的类要重写equals() 和 hashCode()方法

​ ⑤所有的value构成的集合是Collection:无序可重复的。所以value所在的类要重写equals()

​ ⑥一个key-value构成一个entry

​ ⑦所有的entry构成的集合是Set:无序的、不可重复的

​ ⑧HashMap 判断两个key相等的标准是:两个key通过equals()方法返回true,hashCode值也相等。

​ ⑨HashMap 判断两个value相等的标准是:两个value通过equals()方法返回true。

​ ⑩同一单向链表上所有节点的hash值相同,因为他们的数组下标相同。

​ ⑪同一单向链表上所有节点的key值不相同,因为集合是不可重复。

(2)HashMap的存储结构

在这里插入图片描述

(3)HashMap的put(k, v)方法实现过程:

​ ①首次扩容:

​ 先判断数组是否为空,若数组为空则进行第一次扩容(resize);并将k, v封装在Node对象中。

​ ②计算索引:

​ 底层调用k的hashCode()方法得出hash值,然后通过hash算法,将hash值转化为数组下标。

​ ③插入数据:

​ 如果当前下标位置没有元素,则直接插入数据;
​ 如果当前下标位置有元素,且k已存在(通过对k和链表中的k进行equals比较),则直接覆盖其value;
​ 如果当前下标位置有元素,且k不存在,则将数据链到链表末端;
​ 若链表长度达到8,则将链表转换成红黑树,并将数据插入树中(JDK8新特性);

​ ④再次扩容

​ 如果数组中元素个数(size)超过threshold,则再次进行扩容操作。

​ 注:添加数据的详细过程,如下图:

(4)HashMap的get(k)方法实现过程:

​ ①首先调用k的hashCode()方法获取到hash值,并通过哈希算法转化为数组下标;

​ ②通过下标定位到某个位置上,判断此位置是否为空,为空就返回null;

​ ③如果不为空,就把k与单向链表中每个节点的k进行equals比较;

​ ④如果所有的equals()方法都返回false,则get方法返回null;

​ ⑤只要其中有一个节点的equals()方法返回ture,那么此节点的value就是我们要找的value,get()方法返回该value。

(5)HashMap重写equals() 和 hashCode()

​ 注:放在HashMap集合key部分的,以及放在HashSet集合中的元素,都需要同时重写equals() 方法和 hashCode()方法。

public class HashMapTest {
      public static void main(String[] args) {
            Student s1 = new Student("张三");
            Student s2 = new Student("张三");
            //重写equals之前比较的是二者的地址 返回结果为false 重写后为true
            System.out.println(s1.equals(s2));
            //比较二者的hashcode值 两个名字相同的学生应该是同一个对象
            //但重写之前 hashcode不相等 会重复放入 HashMap放对象先看hashcode
            System.out.println(((s1.hashCode()) == s2.hashCode()));
            //调用IDEA自带的equals和hashcode 二者返回的boolean一定要相同
      }
}
import java.util.Objects;

class Student {
      private String name;

      public String getName(String name) {
            return name;
      }

      public void setName(String name) {
            this.name = name;
      }

      public Student(String name) {
            this.name = name;
      }

    //重写equals()
      @Override
      public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return Objects.equals(name, student.name);
      }

    //重写hashCode()
      @Override
      public int hashCode() {
            return Objects.hash(name);
      }
}
(6)HashMap的新特性

​ 在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。

5.Map实现类之二:Hashtable

(1)Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的。
(2)Hashtable的初始化容量为: 11,默认加载因子为: 0.75f,Hashtable集合扩容是:原容量×2+1
(3) Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
(4) 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
(5)与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
(6)Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。

6. Map实现类之三:Properties

(1)Properties 类是 Hashtable 的子类,该对象用于处理属性文件由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
(2)Properties 被称为属性类对象,是线程安全的。
(3)存取数据时,建议使用 setProperty(String key, String value) 方法和 getProperty(String key) 方法

7. Map实现类之四:LinkedHashMap

​ LinkedHashMap概述

​ ①LinkedHashMap是HashMap的子类

  ②在HashMap存储结构的基础上,使用了双向链表来记录添加元素的顺序

 ③与LinkedHashSet类似,LinkedHashMap可以维护Map的迭代顺序:迭代顺序与Key-Value对的插入顺序一致 (即:输出顺序与输入顺序相同)

8…Map实现类之五:TreeMap

(1)TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。 TreeMap 可以保证所有的 Key-Value 对处于有序状态。
(2)TreeSet底层使用红黑树结构存储数据
(3)TreeMap 的 Key 的排序:

​ **自然排序:**TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException

​ **定制排序:**创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口

(4)TreeMap判断两个key相等的标准:两个key通过compareTo()方法或 者compare()方法返回0。

七、常用工具类

1.Collections工具类

​ 在Java中,针对集合的操作非常频繁,针对这些常用的操作,Java提供了一个工具类专门用来操作集合,这个类就是Collections,它位于java.util包中。Collections类中提供了大量的静态方法用于对集合中的元素进行排序、查找和修改等操作。

(1)添加、排序操作
static <T> boolean addAll(Collection<? super T> c, T... elements);
//将所有指定的元素添加到指定的集合c中。 
static void reverse(List list);    //反转指定List列表中元素的顺序。  
static void shuffle(List list);   //对List集合中的元素进行随机排序。 
static void sort(List list);    //根据元素的自然顺序对List集合中的元素按升序排序。  
static void swap(List list, int i, int j);     //交换List集合中i和j处的元素。  
(2)查找、替换操作
static int binarySearch(List list,Object key); 
//使用二进制搜索算法在指定列表中搜索指定对象。  
static Object max(Collection col); 
//根据元素的自然顺序,返回给定集合中最大的元素。  
static Object min(Collection col); 
//根据元素的自然顺序,返回给定集合中最小的元素。 
static boolean replaceAll(List list, Object oldVal, Object newVal);
//用列表替换列表中所有出现的指定值。 

2.Arrays工具类

​ 在java.util包中,除了针对集合操作操作了一个集合工具类Collections,还针对数组操作提供了一个数组工具类——Arrays。Arrays工具类提供了大量针对数组操作的静态啊方法。

(1)使用sort()方法排序
static void sort(各类型的数组 a);    //将指定的数组按升序排序。  
static int binarySearch(Object[] a, Object key);
//使用二分查找在指定的数组a中搜索指定的值。(前提:数组必须已经进行排序了) 
static byte[] copyOfRange(int[] original, int from, int to);
//将指定数组的指定范围复制到新数组中。 
static void fill(Object[] a, Object val);
//将指定的值赋给指定数组中的每个元素。  

计算机英语:

增删改查这几个单词要知道:

增:add、save、new

删:delete、set、remove

改:update、set、modify

查:find、get、query、select

集合c中。


```java
static void reverse(List list);    //反转指定List列表中元素的顺序。  
static void shuffle(List list);   //对List集合中的元素进行随机排序。 
static void sort(List list);    //根据元素的自然顺序对List集合中的元素按升序排序。  
static void swap(List list, int i, int j);     //交换List集合中i和j处的元素。  
(2)查找、替换操作
static int binarySearch(List list,Object key); 
//使用二进制搜索算法在指定列表中搜索指定对象。  
static Object max(Collection col); 
//根据元素的自然顺序,返回给定集合中最大的元素。  
static Object min(Collection col); 
//根据元素的自然顺序,返回给定集合中最小的元素。 
static boolean replaceAll(List list, Object oldVal, Object newVal);
//用列表替换列表中所有出现的指定值。 

2.Arrays工具类

​ 在java.util包中,除了针对集合操作操作了一个集合工具类Collections,还针对数组操作提供了一个数组工具类——Arrays。Arrays工具类提供了大量针对数组操作的静态啊方法。

(1)使用sort()方法排序
static void sort(各类型的数组 a);    //将指定的数组按升序排序。  
static int binarySearch(Object[] a, Object key);
//使用二分查找在指定的数组a中搜索指定的值。(前提:数组必须已经进行排序了) 
static byte[] copyOfRange(int[] original, int from, int to);
//将指定数组的指定范围复制到新数组中。 
static void fill(Object[] a, Object val);
//将指定的值赋给指定数组中的每个元素。  

计算机英语:

增删改查这几个单词要知道:

增:add、save、new

删:delete、set、remove

改:update、set、modify

查:find、get、query、select

本笔记为我个人通过看各位博客大佬和b站老杜视频总结所得,再次声明感谢,本笔记仅为学习笔记,无任何商业行为,如有侵权,私信告知立删!!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不想当码农~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值