Java 集合详解

目录

1.集合体系结构

2.Collection集合

2.1 Collection集合

2.1.1 Collection基本方法

2.1.2 Collection遍历方式

2.1.2.1 迭代器遍历

2.1.2.2 增强for循环

2.1.2.3 Lambda表达式

3.List集合

3.1 List集合的基本方法

3.2 List集合的遍历方式

4.数据结构

4.1 数据结构概述

4.2 常见的数据结构

4.2.1 栈

 4.2.2 队列

 4.2.3 数组

4.2.4 链表

4.2.5 二叉树

4.2.6 二叉查找树

4.2.7 平衡二叉树

4.2.8 红黑树

5.ArrayList集合

6.LinkedList集合

7. 泛型

7.1 泛型可以定义的地方

7.1.1 泛型类

7.1.2 泛型方法

7.1.3 泛型接口

7.2 泛型的继承和通配符

8. Set系列集合

8.1 HashSet

8.2 LinkedHashSet

8.3 TreeSet


1.集合体系结构

 

2.Collection集合

2.1 Collection集合

Collection:单列集合,每次在添加数据的时候只能添加一个元素

Map:双列集合,每次再添加数据的时候,每次添加的是一对数据

 List系列集合:添加的元素是有序、可重复、有索引

这里的有序指存和取得的顺序一样,存1,2,3。取出来的也是1,2,3。和排序无任何关系。

可重复指集合中的元素可以重复。

有索引,我们可以通过索引获取集合中每一个元素。

Set系列集合:添加的元素是无序,不重复,无索引

无序指存和取得的顺序可能不一样,存1,2,3。取出来的不一定是1,2,3。

不重复指集合中存储的元素不能相同,可以利用这个特性进行元素的数据去重。

无索引指不能通过索引获取Set集合里面的每一个元素。

2.1.1 Collection基本方法

Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

Collection是单列集合的顶层接口,所有方法被List和Set系列集合共享。

方法名称说明
public boolean add(E e)把给定的对象添加到当前集合中
public void clear()清空集合中所有的元素
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数/集合的长度

例如:

import java.util.ArrayList;
import java.util.Collection;

public class Collection_Demo01 {
    public static void main(String[] args) {
/**
 * 注意:Collection是一个接口,我们不能直接创建它的对象
 * 只能创建它的实现类的对象
 * 实现类:ArrayList
 */
        Collection<String> collection = new ArrayList<>();
        //添加元素
        //如果往List系列集合中添加数据,永远返回true,因为List集合中允许元素重复
        //如果往Set集合中添加元素,如果没有该元素,返回true,如果要添加的元素已经存在,返回false
        System.out.println(collection.add("aaa"));//true
        collection.add("bbb");
        collection.add("ccc");
        collection.add("ddd");
        System.out.println(collection);//[aaa, bbb, ccc, ddd]

        //根据对象删除元素
        //因为Collection里面定义的是共性的方法,所以此时不能通过索引进行删除,只能通过元素的对象进行删除。
        //删除成功返回true,失败返回false,(如果要删除的元素不存在)
        System.out.println(collection.remove("ccc"));//true
        System.out.println(collection);//[aaa, bbb, ddd]

        //判断元素是否包含
        //contains底层是依赖equals方法进行判断是否存在的。
        //如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么在javabean类中,一定要重写equals方法
        System.out.println(collection.contains("aaa"));//true
        System.out.println(collection.contains("ccc"));//false

        //当前集合是否为空
        System.out.println(collection.isEmpty());//false

        //获取集合长度
        System.out.println(collection.size());//3

        //清空
        collection.clear();
        System.out.println(collection);//[]

        //当前集合是否为空
        System.out.println(collection.isEmpty());//true
    }
}

例2:如果集合中存储的是自定义对象,也想通过contains方法来判断是否包含,那么在javabean类中,一定要重写equals方法

import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

public class Collection_Demo02 {
    public static void main(String[] args) {
        Collection<CollectionStudent> collstu = new ArrayList<>();
        //创建学生对象
        CollectionStudent stu1 = new CollectionStudent("nn", 5);
        CollectionStudent stu2 = new CollectionStudent("bb", 15);
        CollectionStudent stu3 = new CollectionStudent("pd", 25);

        //学生对象添加到集合中
        collstu.add(stu1);
        collstu.add(stu2);
        collstu.add(stu3);

        //判断集合中某一个学生对象是否包含
        CollectionStudent stu4 = new CollectionStudent("pd", 25);
        //同姓名、年龄就是同一个学生
        //不重写equals方法下面为false,因为object类中equals方法,依赖地址值进行判断。
        System.out.println(collstu.contains(stu4)); //true 因为下面重写了equals方法
    }
}

class CollectionStudent {
    private String name;
    private int age;

    public CollectionStudent() {
    }

    public CollectionStudent(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 boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CollectionStudent that = (CollectionStudent) o;
        return age == that.age && Objects.equals(name, that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

2.1.2 Collection遍历方式

2.1.2.1 迭代器遍历

迭代器在遍历的时候不依赖索引。

迭代器在Java中的类是lterator,迭代器是集合专用的遍历方式

Collection集合获取迭代器

方法名称说明
Iterator<E> iterator()返回迭代器对象,默认指向当前集合的0索引

lterator中的常用方法

方法名称说明
boolean hasNext()判断当前位置是否有元素,有元素返回true没有元素返回false
E next()获取当前位置的元素,并将迭代器对象移向下一个位置

注意:
1. 如果当前位置没有元素,强行获取,报错NoSuchElementException(没有这个元素异常)

2. 迭代器遍历完毕,不会复位

3. 循环中只能用一次next方法 
4. 迭代器遍历时,不能用集合的方法进行增加或者删除(不是不能删或增)

例如:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Collection_遍历_迭代器 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        c.add("eee");
        c.add("fff");

        //获取迭代器对象
        // 迭代器,不是自己new出来的,而是集合"."出来的
        Iterator<String> iterator = c.iterator();
        while (iterator.hasNext()) {// 集合中当前位置还有没有没被遍历的元素
            String str = iterator.next();// 获取当前位置的元素,并将迭代器对象移向下一个位置
            // c.remove(); //报错
            // 不要在迭代的过程中,使用集合.remove(obj)这个操作,如果要删除,用iterator.remove();
            System.out.print(str + " ");//aaa bbb ccc ddd eee fff
        }
        System.out.println();


        //System.out.println(iterator.next());//运行报错 NoSuchElementException
        //因为上面的while方法结束,已经指向了没有元素的位置

        System.out.println(iterator.hasNext());//false    没有元素了
        //迭代器遍历完毕,不会复位,

        System.out.println("-----------------");
        //上面的while方法结束是因为没有下一个了,重写一次不会回去
        while (iterator.hasNext()) {
            String str = iterator.next();
            System.out.print(str + " "); //无内容输出
        }
        System.out.println();

        System.out.println("--------要想再次输出,用下面这方式,重新获取一次----------");
        iterator = c.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            System.out.print(str + " ");//aaa bbb ccc ddd eee fff
        }
    }
}

例:循环中只能用一次next方法 ,迭代器遍历时,不能用集合的方法进行增加或者删除

2.1.2.2 增强for循环

增强for的底层就是迭代器,为了简化迭代器的代码书写的。

它是JDK5之后出现的,其内部原理就是一个Iterator迭代器

所有的单列集合(Collection集合)和数组才能用增强for进行遍历。

格式:

idea快捷方式: 集合的名字.for (c.for)

for(元素的数据类型 变量名 : 数组或者集合){
}

修改增强for中的变量,不会改变集合中原本的数据。

遍历过程中,把集合中的每个数据用第三方变量进行记录,进行修改也仅仅是修改的第三方变量的数据

例如:

import java.util.ArrayList;
import java.util.Collection;

public class Collection_遍历_增强for循环 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        c.add("eee");
        c.add("fff");
        //str就是一个第三方变量,再循环的过程中一次表示集合中的每一个元素
        //idea快捷方式:  集合的名字.for   (c.for)
        for (String str : c) {
            System.out.print(str + " ");//aaa bbb ccc ddd eee fff
            str = "asd"; //遍历过程中,把集合中的每个数据用第三方变量进行记录,进行修改也仅仅是修改的第三方变量的数据
        }
        System.out.println();//换行

        System.out.print(c);//[aaa, bbb, ccc, ddd, eee, fff]

        System.out.println();//换行
        int[] arr = {1, 2, 3, 6, 5, 4, 8, 20, 6, 5, 4};
        for (int a : arr) {
            System.out.print(a + " ");//1 2 3 6 5 4 8 20 6 5 4 
        }
    }
}
2.1.2.3 Lambda表达式

JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式

方法名称说明
default void forEach(Consumer<? super T> action):结合lambda遍历集合

例如:

import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;

public class Collection_遍历_Lambda表达式 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        c.add("eee");
        c.add("fff");

        //利用匿名内部类的方式
        //底层原理:方法的底层其实也会自己遍历集合,依次得到每一个元素,把得到的每一个元素,传递给下面的accept方法
        c.forEach(new Consumer<String>() {
            @Override
            //s依次表示集合中每一个数据
            public void accept(String s) {
                System.out.print(s + " ");//aaa bbb ccc ddd eee fff
            }
        });
        System.out.println();//换行
        //Lambda表达式
        //()->{}
        //数据类型可以省略,如果只有一个参数()可以省略。方法体只有一行{}可以省略,return可以省略,;可以省略
        c.forEach(s -> System.out.print(s + " "));//aaa bbb ccc ddd eee fff 
    }
}

仅仅想遍历,用增强for循环或Lambda表达式,想要删除元素,用迭代器

3.List集合

3.1 List集合的基本方法

特点:

  • 有序:存和取的元素顺序一致
  • 有索引:可以通过索引操作元秦
  • 可重复:存储的元素可以重复

Collection的方法,List都继承了。

List集合因为有索引,所以多了很多索引操作的方法。

方法名称说明
void add(int index,E element)在此集合中的指定位置插入指定的元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
get(int index)返回指定索引处的元素

例如:


import java.util.ArrayList;
import java.util.List;

public class ListDemo01 {
    public static void main(String[] args) {
        //1.创建集合
        //List也是接口,不能直接创建,创建它的实现类对象
        List<String> list = new ArrayList<>();

        //添加元素
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        System.out.println(list);//[aaa, bbb, ccc, ddd]

        //指定位置添加元素,原来的元素依次往后移
        list.add(0, "000");//0索引的位置添加000
        System.out.println(list);//[000, aaa, bbb, ccc, ddd]


        //删除指定索引出元素
        System.out.println(list.remove(0));//删除指定索引处的元素,返回被删除的元素 //000
        System.out.println(list.remove("aaa"));//true 把给定的对象在当前集合中删除
        System.out.println(list);//[bbb, ccc, ddd]

        //修改指定元素
        System.out.println(list.set(0, "111"));//bbb  修改指定索引处的元素,返回被修改的元素
        System.out.println(list);

        //返回指定索引处的元素
        System.out.println(list.get(2));//ddd
    }
}

例:remove删除元素注意事项

import java.util.ArrayList;
import java.util.List;

public class ListDemo02 {
    public static void main(String[] args) {
        //List系列集合中的两个删除的方法
        // 1.直接制除 元素//2.通过索引进行删除

        //1.创建集合并添加元素
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);

        //删除元素
        list.remove(1);
        System.out.println(list);//[1, 3, 4]
        //因为在调用方法的时候,如果方法出现了重载现象,优先调用,实参跟形参类型一致的那个方法。
        //此时的remove不会自动装箱
        //所以这里的1是根据索引来删除的

        //手动装箱,手动把基本数据类型的2,变成Integer类型
        Integer i = Integer.valueOf(1);
        list.remove(i);
        System.out.println(list); //[3, 4]
    }
}

3.2 List集合的遍历方式

List集合有五种遍历方式:

  1. 迭代器遍历
  2. 列表迭代器遍历
  3. 增强for遍历
  4. Lambda表达式遍历
  5. 普通for循环 (因为List集合存在索引)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Consumer;

public class List_遍历方式 {
    public static void main(String[] args) {
        //创建集合对象(还是多态的方式)
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.add("eee");

        //1.迭代器遍历
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String str = it.next();
            System.out.print(str + " ");//aaa bbb ccc ddd eee
        }
        System.out.println(); //换行

        //2.增强for遍历
        for (String s : list) {
            //下面的变量s,其实就是一个第三方的变量而已,在循环的过程中,依次表示集合中的每一个元素
            System.out.print(s + " ");//aaa bbb ccc ddd eee
        }
        System.out.println(); //换行

        //3.Lambda表达式遍历
        //内部类方式
        //forEach的底层就是循环遍历
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.print(s + " ");//aaa bbb ccc ddd eee
            }
        });
        System.out.println(); //换行

        list.forEach(s -> System.out.print(s + " "));//aaa bbb ccc ddd eee
        System.out.println(); //换行

        //4.普通for循环 (因为List集合存在索引)
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");//aaa bbb ccc ddd eee
        }
        System.out.println(); //换行

        //5.列表迭代器遍历
        //ListIterator同样是接口

        ListIterator<String> its = list.listIterator();
        while (its.hasNext()) {
            System.out.print(its.next() + " ");//aaa bbb ccc ddd eee
        }
        System.out.println(); //换行

        //在迭代器的基础上额外添加了一个方法,可以添加元素
        ListIterator<String> its1 = list.listIterator();
        while (its1.hasNext()) {
            String str = its1.next();
            if ("aaa".equals(str)) {
                its1.add("0000");
            }
        }
        System.out.print(list + " ");//[aaa, 0000, bbb, ccc, ddd, eee]
    }
}

4.数据结构

4.1 数据结构概述

数据结构是计算机底层存储、组织数据的方式。

是指数据相互之间是以什么方式排列在一起的。

数据结构是为了更加方便的管理和使用数据需要结合具体的业务场景来进行选择。

4.2 常见的数据结构

8种常见的数据结构:栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树。

4.2.1 栈

栈的特点:后进先出,先进后出(一端开口 栈顶,一段封闭 栈底)

数据进入栈模型的过程称为:压/进栈

数据离开栈模型的过程称为:弹/出栈

可以理解为手枪弹夹,先装进去的子弹,后出来,第一个装进去的子弹,最后打出来,最后装进去的子弹,第一个打出来。

 4.2.2 队列

队列的特点:先进先出,后进后出(一端开口 后端,一端开头 前端)

数据从后面进入,从前面出去

数据从后端进入队列模型的过程称为:入队列

数据从前端离开队列模型的过程称为:出队列

可以理解为排队买票

 4.2.3 数组

查询速度快:查询数据通过地址值和索定位,查询任意数据耗时相同。 (元素在内存中是连续存储的)

删除效率低: 要将原始数据删除同时后面每个数据前移

添加效率极低:添加位置后的每个数据后移,再添加元素。

数组是一种查询快,增删慢的模型

4.2.4 链表

链表中的每个元素我们称为结点

每一个结点都是一个独立的对象,自身就会有一个地址值,表示结点在内存中的位置

结点里面会存储具体的数据,同时还会存储下一个结点的地址值

 在链表中第一个被创建出来的结点称为头结点

链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。 

链表查询慢,无论查询哪个数据都要从头开始找。

链表增删相对快

链表是一种增删快的模型(对比数组

LinkedList和ArrayList对比

  • LinkedList适合增删、不适合遍历
  • ArrayList不适合增删 、适合遍历

栈:后进先出,先进后出。
队列:先进先出,后进后出。
数组:内存连续区域,查询快,增删慢。
链表:元素是游离的,查询慢,首尾操作极快。

4.2.5 二叉树

 每一个结点都是一个独立的对象,里面存当前的数据,左子节点的地址值,右子节点的地址值

 一个结点没有父节点,或者没有子节点,记为null(上面的22、15、17、19、21、23、25、27、29)这些节点对应的位置记为null

度: 每一个节点的子节点数量。(18、16、20、26、24、28)的度都为2。(15、17、19、21、23、25、27、29)的度为0。

二叉树中,任意节点的度<=2(最多两个分叉)

树高: 树的总层数

根节点:最顶层的节点(上面的树是22)

左子节点: 左下方的节点
右子节点: 右下方的节点

根节点的左子树:蓝色虚线
根节点的右子树: 绿色虚线

4.2.6 二叉查找树

 二又查找树,又称二叉排序树或者二叉搜索树

特点:

每一个节点上最多有两个子节点
任意节点左子树上的值都小于当前节点
任意节点右子树上的值都大于当前节点

数据结构 ( 二又查找树 ) 添加节点的规则:

小的存左边,大的存右边,一样的不存

 

 数据结构 ( 二又查找树 ) 查找节点,比节点小左边找,大就右边找

二叉树遍历方式:前序遍历,中序遍历,后序遍历,层序遍历

前序遍历:从根结点开始,然后按照当前结点,子结点,子结点的顺序遍历。

 中序遍历:从最左边的子节点开始,然后按照子结点,当前结点,子结点的顺序遍历。

 后序遍历:从最左边的子节点开始,然后按照子结点,子结点,当前结点的顺序遍历

 层序遍历:一层一层的遍历

4.2.7 平衡二叉树

 二叉查找树弊端

按照规则将下列节点添加到二叉查找树中

 

平衡二叉树 规则:任意节点左右子树高度差不超过1

 

二叉树,二叉查找树,平衡二叉树的演变

最基本的二叉树在存储数据的时候没有规则,所以在查找数据的时候非常麻烦,只能遍历查找,效率非常低。为了解决这个问题,提高查找效率,我们在每次添加数据的时候,都会先进行比较,遵守一个规则,小的存左边,大的存右边,一样的不存,这样形成的一个二叉树,我们称为二叉查找树,二叉查找树在查找效率的时候就比较高了。但这样有可能导致左右子树高度差很大,查找效率一样很低,为了避免这种情况出现,我们就有了平衡二叉树,平衡二叉树中,任意节点左右子树高度差不超过1。

平衡二叉树为什么平衡?

旋转机制:规则1:左旋。规则2:右旋

触发时机:当添加一个节点之后,该树不再是一颗平衡二叉树

左旋:

确定支点: 从添加的节点开始,不断的往父节点找不平衡的节点

步骤:以不平衡的点作为支点,把支点左旋降级,变成左子节点,晋升原来的右子节点 。


步骤: 以不平衡的点作为支点,将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点

右旋:

确定支点: 从添加的节点开始,不断的往父节点找不平衡的节点


步骤: 以不平衡的点作为支点,把支点右旋降级,变成右子节点,晋升原来的左子节点

 

步骤: 以不平衡的点作为支点,就是将根节点的左侧往右拉,原先的左子节点变成新的父节点,并把多余的右子节点出让,给已经降级的根节点当左子节点

平衡二叉树需要旋转的四种情况:左左、左右、右右、右左

左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡

 一次右旋

左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡

 一次局部左旋,一次整体右旋

 右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡

一次左旋

 

右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡 

  一次局部右旋,一次整体左旋

  1. 在平衡二叉树中,如何添加节点?
    1. 小的存左边,大的存右边,一样的不存
  2. 在平衡二叉树中,如果查找单个节点?
    1. 从根节点开始,小的左边找,大的右边找
  3. 为什么要旋转?
    1. 普通二叉树和二叉查找树不需要旋转,平衡二叉树才需要旋转
    2. 当添加了一个节点后导致这个树不平衡了,需要旋转让这棵树重新保持平衡
  4. 旋转的触发时机?
    1. 当添加一个节点之后,该树不再是一颗平衡二叉树
  5. 左左是什么意思?如何旋转?
    1.  把新节点添加到根节点左子树的左子树,导致二叉树不平衡
    2. 一次右旋
  6. 左右是什么意思?如何旋转?
    1. 把新节点添加到根节点左子树的右子树,导致二叉树不平衡
    2.   一次局部右旋,一次整体左旋
  7. 右右是什么意思?如何旋转?
    1.  把新节点添加到根节点右子树的右子树,导致二叉树不平衡
    2. 一次左旋
  8. 右左是什么意思?如何旋转?
    1. 把新节点添加到根节点右子树的左子树,导致二叉树不平衡
    2.   一次局部左旋,一次整体右旋

4.2.8 红黑树

红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构

1972年出现,当时被称之为平衡二叉B树。后来,1978年被修改为如今的"红黑树"

它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色,

每一个节点可以是红或者黑,红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的

红黑规则

  1. 每一个节点或是红色的,或者是黑色的
  2. 根节点必须是黑色
  3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Ni)是黑色的
  4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
  5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点,

红黑树增删改查的性能都很好

以上面6节点为例

 

红黑树添加节点的规则
默认颜色:添加节点默认是红色的(效率高)

以下面三个weili

(添加三个节点,调整两次)

 

 

如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)

对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

 

如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)

对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点,

例如:

(添加三个节点,调整一次)

 

 根节点必须是黑色

 

 

 例如:

 

 根节点必须是黑色直接变为黑色

 

 不需要任何操作

 不需要任何操作

 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)

将“父”设为黑色,将“叔叔”设为黑色

 将“祖父”设为“红色”


如果祖父为根,再将根变回黑色


 不需要任何操作

 

 如果继续添加

最后为

 

 如果添加的是红15 16

最后为

每一个节点或是红色的,或者是黑色的

根节点必须是黑色

如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Ni)是黑色的

如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)

对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点,

5.ArrayList集合

ArrayList集合底层原理

  1. 利用空参创建的集合,在底层创建一个默认长度为0的数组
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组
  3. 数组存满时,会扩容1.5倍
  4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

6.LinkedList集合

底层数据结构是双链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的。

特有方法说明
public void addFirst(E e)在该列表开头插入指定的元素
public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素

例如:模拟单向链表

public class Demo01_模拟单向链表 {
	public static void main(String[] args) {
		MyList list = new MyList();
		list.add(3);
		list.add(5);
		list.add(7);
		list.add(9);
		list.add(7);
	}
}

/**
 * 链表类
 */
class MyList {
	Node first;
	// 添加
	public void add(Object data) {
		Node node = new Node();
		node.data = data;
		// 如果还没有头节点
		if (first == null) {
			first = node; // 新添加的节点就是头节点
			return;
		}
		Node temp = first;
		while (temp.next != null) {
			temp = temp.next;
		}
		temp.next = node;
	}
}

/**
 * 节点类
 */
class Node {// 节点
	Object data;// 节点中的数据
	Node next;
}

例如:LinkedList和ArrayList对比



import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.junit.Test;

/**
 * LinkedList
 *	1、双向链表:又有next,又有prev
 *	2、它也是实现了List接口
 *	3、arrayList底层 维护的是一个数组
 *		当add时,是执行 elementData[size++] = e;
 *	4、linkedList底层维护的是 一个链表
 *
 *	5、LinkedList和ArrayList对比:
 *		LinkedList适合增删、不适合遍历
 *		ArrayList不适合增删	、适合遍历
 */
public class Demo02_LinkedList_ArrayList对比 {
	public static void main(String[] args) {
		List<Integer> list = new LinkedList<>();
		list.add(3);//arrayList.add(3)-->arr[i] = 3;
		list.add(4);
		list.add(5);
	}
	
	@Test
	public void testArrayList01(){
		long a = System.currentTimeMillis();
		List<Integer> list = new ArrayList<>();
		for (int i = 0; i < 100000; i++) {
			list.add(0, i);//前面加0,是为了让数组中后面的元素,都往后移一位
		}
		long b = System.currentTimeMillis();
		System.out.println(b-a);//405
		System.out.println("-----------遍历fori测试---------------");
		for (int i = 0; i < list.size(); i++) {
			list.get(i);
		}
		long c = System.currentTimeMillis();
		System.out.println(c-b);//2
		System.out.println("-----------遍历foreach测试---------------");
		for (Integer i:list) {}
		long d = System.currentTimeMillis();
		System.out.println(d-c);//3
	}
	
	
	@Test
	public void testLinkedList01(){
		long a = System.currentTimeMillis();
		List<Integer> list = new LinkedList<>();
		for (int i = 0; i < 100000; i++) {
			list.add(0, i);//new Node() addFirst  first.prev = node; node.next=first;
		}
		long b = System.currentTimeMillis();
		System.out.println(b - a);// 6
		
		System.out.println("-----------遍历测试-------------");
		for (int i = 0; i < list.size(); i++) {
			list.get(i);
		}
		long c = System.currentTimeMillis();
		System.out.println(c - b);// 3927
		
		System.out.println("-----------遍历foreach测试---------------");
		for (Integer i:list) {}
		long d = System.currentTimeMillis();
		System.out.println(d - c);// 3
	}
	
}

例如:模拟队列


/*
 * 我们用LinkedList来模拟队列
 * 1.向队列添加:offer
 * 2.从队列中取出:poll
 * 3.如果队列的元素被取空了,会返回null
 * 4.遍历取出队列中的元素
 */
public class Demo03_模拟队列 {
	public static void main(String[] args) {
		Queue<Integer> list = new LinkedList<>();
		list.offer(3);
		list.offer(5);
		list.offer(6);
		list.offer(2);
		list.offer(3);
		// while (list.size() > 0) {
		// System.out.println(list.poll());
		// }
		while (!list.isEmpty()) {
			System.out.print(list.poll() + " ");//3 5 6 2 3 
		}
		// for 丢内容
		// for (int i = 0; i < list.size(); i++) {
		// System.out.println(list.poll());
		// }
		System.out.println(list);// []
	}
}

例如:用LinkedList模拟栈

import java.util.Deque;
import java.util.LinkedList;

/*
 * 用LinkedList模拟栈
 * 栈:stack 压栈
 * 压栈:push
 * 弹栈:pop
 * 如果集合中没有数据了,执行pop会报错:NoSuchElementException
 */
public class Demo04_LinkedList模拟栈_Stack {
	public static void main(String[] args) {
		Deque<Integer> dq = new LinkedList<>();
		dq.push(4);
		dq.push(5);
		dq.push(1);
		System.out.println(dq.pop());//1
		System.out.println(dq.pop());//5
		System.out.println(dq.pop());//4
	}
}

7. 泛型

泛型:是]DK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查

泛型的格式:<数据类型>
注意:泛型只能支持引用数据类型

如果我们没有给集合指定类型,默认认为所有的数据类型都是object类型,此时集合中添加任意数据类型,导致我们在获取数据的时候,无法使用它的特有行为

在添加数据的时候就把类型进行统一,而且我们在获取数据的时候,也省的强转了。

泛型的好处:

  • 统一数据类型。
  • 把运行时期的问题提前到编译期间避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来

扩展知识点:

  • Java中的泛型是伪泛型
  • java文件中存在泛型,当java文件编译成字节码文件,泛型就会消失,这个过程叫泛型的擦除

注意:

  • 泛型中不能写基本数据类型
  • 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
  • 如果不写泛型,类型默认是object

7.1 泛型可以定义的地方

类后面(泛型类)

方法上面(泛型方法)

接口后面(泛型接口)

7.1.1 泛型类

使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式:
 

修饰符 class 类名<类型>{
}

例如:

public class ArrayList<E>{
}

此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成: T、E、K、V等。创建该类对象时,E就确定类型

例如:自己编写一个泛型类

import java.util.Arrays;

/*
 *编写一个类的时候,如果不确定类型,那么这个类就可以定义为泛型类。
 */
public class GenericsDemo02 {
    public static void main(String[] args) {
        MyArrayLiat<Object> list = new MyArrayLiat<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        System.out.println(list);//[aaa, bbb, ccc, null, null, null, null, null, null, null]


        MyArrayLiat<Integer> list2 = new MyArrayLiat<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        System.out.println(list2);//[1, 2, 3, null, null, null, null, null, null, null]


    }
}

class MyArrayLiat<E> {
    Object[] obj = new Object[10];
    int size;

    public boolean add(E e) { //E不确定的类型 e形参的名字
        obj[size] = e;
        size++;
        return true;
    }

    public E get(int index) {
        return (E) obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}

7.1.2 泛型方法

方法中形参类型不确定时:

  1. 可以使用类名后面定义的泛型<E>,(所有方法都能用)
  2. 在方法申明上定义自己的泛型,(只有本方法能用)

格式:

修饰符 <类型> 返回值类型 方法名(类型 变量名){
}

例如:

public static <T> void test1(T t) {
}

此处T可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T、E、K、V等。调用该方法时,T就确定类型

例如:定义一个泛型方法

import java.util.ArrayList;

public class GenericsDemo02 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        ListUtil.addAll(list, "aaa", "bbb", "ccc", "ddd");//[aaa, bbb, ccc, ddd]
        System.out.println(list);

        ArrayList<Integer> list2 = new ArrayList<>();
        ListUtil.addAll(list2, 111, 222, 333, 444);//[111, 222, 333, 444]
        System.out.println(list2);
        ListUtil.addAll2(list2, 111, 222, 333, 444, 555, 666, 777);//[111, 222, 333, 444, 111, 222, 333, 444, 555, 666, 777]
        System.out.println(list2);
    }
}

/*
 *类中定义一个静态方法addAll,用来添加多个元素
 */
class ListUtil {
    private ListUtil() {
    }

    public static <E> void addAll(ArrayList<E> list, E e1, E e2, E e3, E e4) {
        list.add(e1);
        list.add(e2);
        list.add(e3);
        list.add(e4);
    }

    public static <E> void addAll2(ArrayList<E> list, E... e) {
        for (E e1 : e) {
            list.add(e1);
        }
    }
}

7.1.3 泛型接口

格式:

修饰符 interface 接口名<类型>{
}

例如:

public interface List<E> {
}

重点: 如何使用一个带泛型的接口

泛型接口两种使用方式:
方式1:实现类给出具体类型
方式2:实现类延续泛型,创建对象时再确定

例如:泛型接口两种使用方式:

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class GenericsDemo03_泛型接口两种使用方式 {
    public static void main(String[] args) {
        //方式1:实现类给出具体类型
        MyArrayList2 list2 = new MyArrayList2();
        list2.add("aaa");

        //方式2:实现类延续泛型,创建对象时再确定
        MyArrayList3<String> list3 = new MyArrayList3<>();
        list3.add("aaa");
        MyArrayList3<Integer> list4 = new MyArrayList3<>();
        list4.add(111);
    }
}

//方式1:实现类给出具体类型
class MyArrayList2 implements List<String> {

    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public Iterator<String> iterator() {
        return null;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return null;
    }

    @Override
    public boolean add(String s) {
        return false;
    }

    @Override
    public boolean remove(Object o) {
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends String> c) {
        return false;
    }

    @Override
    public boolean addAll(int index, Collection<? extends String> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public String get(int index) {
        return null;
    }

    @Override
    public String set(int index, String element) {
        return null;
    }

    @Override
    public void add(int index, String element) {

    }

    @Override
    public String remove(int index) {
        return null;
    }

    @Override
    public int indexOf(Object o) {
        return 0;
    }

    @Override
    public int lastIndexOf(Object o) {
        return 0;
    }

    @Override
    public ListIterator<String> listIterator() {
        return null;
    }

    @Override
    public ListIterator<String> listIterator(int index) {
        return null;
    }

    @Override
    public List<String> subList(int fromIndex, int toIndex) {
        return null;
    }
}

//方式2:实现类延续泛型,创建对象时再确定
class MyArrayList3<E> implements List<E> {
    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public Iterator<E> iterator() {
        return null;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return null;
    }

    @Override
    public boolean add(E e) {
        return false;
    }

    @Override
    public boolean remove(Object o) {
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public E get(int index) {
        return null;
    }

    @Override
    public E set(int index, E element) {
        return null;
    }

    @Override
    public void add(int index, E element) {

    }

    @Override
    public E remove(int index) {
        return null;
    }

    @Override
    public int indexOf(Object o) {
        return 0;
    }

    @Override
    public int lastIndexOf(Object o) {
        return 0;
    }

    @Override
    public ListIterator<E> listIterator() {
        return null;
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return null;
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return null;
    }
}

7.2 泛型的继承和通配符

泛型不具备继承性,但是数据具备继承性

例如:泛型不具备继承性,但是数据具备继承性

import java.util.ArrayList;

public class GenericsDemo03 {
    public static void main(String[] args) {
        //创建集合对象
        ArrayList<Ye> list1 = new ArrayList<>();
        ArrayList<Fu> list2 = new ArrayList<>();
        ArrayList<Zi> list3 = new ArrayList<>();
        //调用方法
        method(list1);
        //method(list2); //报错
        //method(list3); //报错
        //泛型里面写的什么类型,就只能传递什么类型

        //数据具备继承性
        list1.add(new Ye());
        list1.add(new Fu());
        list1.add(new Zi());
    }

    //泛型不具备继承性,

    public static void method(ArrayList<Ye> list) {

    }
}

class Ye {
}

class Fu extends Ye {
}

class Zi extends Fu {
}

例如:定义一个方法,形参是一个集合,但集合中的数据类型不确定

import java.util.ArrayList;

/*
 *定义一个方法,形参是一个集合,但集合中的数据类型不确定
 */
public class GenericsDemo04{
    public static void main(String[] args) {
        //创建集合对象
        ArrayList<Ye> list1 = new ArrayList<>();
        ArrayList<Fu> list2 = new ArrayList<>();
        ArrayList<Zi> list3 = new ArrayList<>();
        method(list1);
        method(list2);
        method(list3);
    }

    public static <E> void method(ArrayList<E> list) {

    }
}
class Ye {
}

class Fu extends Ye {
}

class Zi extends Fu {
}

例3:通配符

import java.util.ArrayList;

/*
 *定义一个方法,形参是一个集合,但集合中的数据类型不确定
 */
public class GenericsDemo04_泛型的通配符 {
    public static void main(String[] args) {
        //创建集合对象
        ArrayList<Ye> list1 = new ArrayList<>();
        ArrayList<Fu> list2 = new ArrayList<>();
        ArrayList<Zi> list3 = new ArrayList<>();
        ArrayList<Student> list4 = new ArrayList<>();
        method(list1);
        method(list2);
        method(list3);
        //method(list4);//报错

        method2(list1);
        method2(list2);
        // method2(list3);//报错
        //method(list4);//报错
    }

    /*
     *利用泛型方法有一个小弊端,此时他可以接受任意的数据类型
     *      Ye Fu zi Student
     * 希望:本方法虽然不确定类型,但是以后我希望只能传递Ye Fu zi
     *
     * 此时我们就可以使用泛型的通配符:
     *      ?也表示不确定的类型 通配
     *      它可以进行类型的限定
     *      ? extends E: 表示可以传递E或者E所有的子类类型,通配符的上限
     *      ? super E:表示可以传递E或者E所有的父类类型,通配符的下限
     *
     *应用场景:
     *  1.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
     *  2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符
     *
     * 泛型的通配符:
     *  关键点:可以限定类型的范围。
     */
    public static <E> void method(ArrayList<? extends Ye> list) {

    }

    public static <E> void method2(ArrayList<? super Fu> list) {

    }
}
class Ye {
}

class Fu extends Ye {
}

class Zi extends Fu {
}

class Student {

}

例4:

import java.util.ArrayList;

/*
 *需求:
 * 定义一个继承结构:
 *                          动物
 *          猫                          狗
 *   波斯猫      狸花猫             泰迪        哈士奇
 *
 * 属性:名字,年龄
 * 行为:吃东西
 *  波斯猫方法体打印:一只叫做XXX的,X岁的波斯猫,正在吃小饼干
 *  狸花猫方法体打印: 只叫做XXX的,X岁的狸花猫,正在吃鱼
 *  泰迪方法体打印:一只叫做XXX的,X岁的泰迪,正在吃骨头,边吃边蹭
 *  哈士奇方法体打印:一只叫做XXX的,X岁的哈士奇,正在吃骨头,边吃边拆家
 *
 * 测试类中定义一个方法用于饲养动物
 *  public static void keepPet(Arraylist<???> list)(
 *      //遍历集合,调用动物的eat 方法
 *  }
 *
 * 要求
 *  1: 该方法能养所有品种的猫。但是不能养狗要求
 *  2: 该方法能养所有品种的狗。但是不能养猫要求
 *  3: 该方法能养所有的动物,但是不能传递其他类型
 */
public class GenericsDemo05_泛型的通配符 {
    public static void main(String[] args) {
        ArrayList<PersianCat> list1 = new ArrayList<>();
        ArrayList<LiHuaCat> list2 = new ArrayList<>();
        ArrayList<TeddyDog> list3 = new ArrayList<>();
        ArrayList<HuskDog> list4 = new ArrayList<>();
        keepPet(list1);
        keepPet(list2);
        //keepPet(list3);   //报错
        //keepPet(list4);   //报错

        // keepPet2(list1); //报错
        //keepPet2(list2);//报错
        keepPet2(list3);
        keepPet2(list4);

        keepPet3(list1);
        keepPet3(list2);
        keepPet3(list3);
        keepPet3(list4);
    }

    //该方法能养所有品种的猫。但是不能养狗要求
    public static void keepPet(ArrayList<? extends Cat> list) {
        //遍历集合,调用动物的eat 方法
    }

    //该方法能养所有品种的狗。但是不能养猫要求
    public static void keepPet2(ArrayList<? extends Dog> list) {
        //遍历集合,调用动物的eat 方法
    }

    //该方法能养所有的动物,但是不能传递其他类型
    public static void keepPet3(ArrayList<? extends Animal> list) {
        //遍历集合,调用动物的eat 方法
    }
}

abstract class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(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 "Animal{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public abstract void eat();
}

//1.继承抽象类,重写里而所有的抽象方法
// 2.本身Cat也是一个抽象的,让Cat的子类再重写重写方法

abstract class Cat extends Animal {
//因为Cat的两个子类中eat方法体不一样
}

class PersianCat extends Cat {
    @Override
    public void eat() {
        System.out.println("一只叫做" + getName() + "的," + getAge() + "岁的波斯猫,正在吃小饼干");
    }
}

class LiHuaCat extends Cat {
    @Override
    public void eat() {
        System.out.println("一只叫做" + getName() + "的," + getAge() + "岁的狸花猫,正在吃鱼");
    }
}

abstract class Dog extends Animal {

}

class TeddyDog extends Dog {
    @Override
    public void eat() {
        System.out.println("一只叫做" + getName() + "的," + getAge() + "岁的泰迪,正在吃骨头,边吃边蹭");
    }
}

class HuskDog extends Dog {
    @Override
    public void eat() {
        System.out.println("一只叫做" + getName() + "的," + getAge() + "岁的哈士奇,正在吃骨头,边吃边拆家");
    }
}

8. Set系列集合

List系列集合:添加的元素是有序、可重复、有索引

这里的有序指存和取得的顺序一样,存1,2,3。取出来的也是1,2,3。和排序无任何关系。

可重复指集合中的元素可以重复。

有索引,我们可以通过索引获取集合中每一个元素。

Set系列集合:添加的元素是无序,不重复,无索引

无序指存和取得的顺序可能不一样,存1,2,3。取出来的不一定是1,2,3。

不重复指集合中存储的元素不能相同,可以利用这个特性进行元素的数据去重。

无索引指不能通过索引获取Set集合里面的每一个元素。

Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

Collection是单列集合的顶层接口,所有方法被List和Set系列集合共享。

方法名称说明
public boolean add(E e)把给定的对象添加到当前集合中
public void clear()清空集合中所有的元素
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数/集合的长度

例如:

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
​
public class SetDemo01_Set集合特点 {
    public static void main(String[] args) {
        //创建Set集合的对象
        Set<String> list = new HashSet<>();
​
        //添加元素
        //System.out.println(list.add("aaa"));//true
        //System.out.println(list.add("aaa"));//false
        //不重复指集合中存储的元素不能相同,可以利用这个特性进行元素的数据去重。
        //第一次添加true,第二次添加失败
​
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ccc");
        System.out.println(list);//[aaa, ccc, bbb]
        //无序
​
        //迭代器遍历
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");//aaa ccc bbb
        }
        System.out.println();//换行
​
        //增强for
        for (String s : list) {
            System.out.print(s + " ");//aaa ccc bbb
        }
        System.out.println();//换行
​
        //lambda表达式
        list.forEach(s -> {
            System.out.print(s + " ");//aaa ccc bbb
        });
    }
}

set集合的实现类 HashSet:无序、不重复、无索引 LinkedHashSet: 有序、不重复、无索引

TreeSet:可排序、不重复、无索引

set接口中的方法上基本上与Collection的API一致。

8.1 HashSet

HashSet 底层原理 HashSet集合底层采取哈希表存储数据哈希表是一种对于增删改查数据性能都较好的结构

哈希表组成 JDK8之前:数组+链表 JDK8开始:数组+链表+红黑树

哈希值:对象的整数表现形式

根据hashCode方法算出来的int类型的整数 该方法定义在object类中,所有对象都可以调用,默认使用地址值进行计算

一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

对象的哈希值特点: 1.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的 2.如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的

3.在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)

例如:

package yj.com.easthome2.day0813;
​
import java.util.Objects;
​
public class HashSetDemo01 {
    public static void main(String[] args) {
        HashStudent s1 = new HashStudent("nn", 5);
        HashStudent s2 = new HashStudent("nn", 5);
​
        //如果设有重写hashcode方法,不同对象计算出的哈看值是不同的
        System.out.println(s1.hashCode());//110086
        System.out.println(s2.hashCode());//110086
​
        //在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
        System.out.println("abc".hashCode());//96354
        System.out.println("acD".hashCode());//96354
​
    }
}
​
class HashStudent {
    private String name;
    private int age;
​
    public HashStudent() {
    }
​
    public HashStudent(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 "HashStudent{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
​
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        HashStudent that = (HashStudent) o;
        return age == that.age && Objects.equals(name, that.name);
    }
​
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

HashSet底层原理 1.JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树 2.如果集合中存储的是自定义对象,必须要重写hashCode和equals方法

8.2 LinkedHashSet

LinkedHashSet底层原理 1.有序、不重复、无索引。 2.这里的有序指的是保证存储和取出的元素顺序一致 3.原理:底层数据结构是依然哈希表只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

例如:

import java.util.LinkedHashSet;
​
public class LinkedHashDemo01 {
    public static void main(String[] args) {
        HashStudent s1 = new HashStudent("nn", 15);
        HashStudent s2 = new HashStudent("bb", 25);
        HashStudent s3 = new HashStudent("pd", 5);
        HashStudent s4 = new HashStudent("nn", 15);
​
        LinkedHashSet<Object> l = new LinkedHashSet<>();
​
        System.out.println(l.add(s1));//true
        System.out.println(l.add(s2));//true
        System.out.println(l.add(s3));//true
        System.out.println(l.add(s4));//false
        System.out.println(l);//[HashStudent{name='nn', age=15}, HashStudent{name='bb', age=25}, HashStudent{name='pd', age=5}]
    }
}

1.LinkedHashSet集合的特点和原理是怎么样的?

  • 有序、不重复、无索引
  • 底层基于哈希表,使用双链表记录添加顺序

2.在以后如果要数据去重,我们使用哪个?

  • 默认使用HashSet
  • 如果要求去重且存取有序,才使用LinkedHashSet

8.3 TreeSet

TreeSet 的特点

  1. 不重复、无索引、可排序
  2. 可排序: 按照元素的默认规则 (有小到大)排序。
  3. TreeSet集合底层是基红黑树的数据结构实现排序的,增删改查性能都较好

例如:

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo01 {
    public static void main(String[] args) {
        TreeSet<Integer> set = new java.util.TreeSet<>();
        set.add(1);
        set.add(2);
        set.add(3);
        set.add(4);
        set.add(5);
        System.out.println(set);//[1, 2, 3, 4, 5]

        //遍历(3种都行)
        Iterator<Integer> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");//1 2 3 4 5
        }
        System.out.println();

        //增强for
        for (Integer integer : set) {
            System.out.print(integer + " ");//1 2 3 4 5
        }
        System.out.println();

        //Lambda
        set.forEach(integer -> {
                    System.out.print(integer + " ");//1 2 3 4 5
                }
        );
    }
}

TreeSet集合默认的规则:
对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序

对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序

TreeSet的两种比较方式
方式一:默认排序/自然排序:Javabean类实现Comparable接口指定比较规则
方式二:比较器排序:创建TreeSet对象时候,传递比较器Comparator指定规则
使用原则:默认使用第一种,如果第一种不能满足当前需求,就使用第二种

例如:方式一

import java.util.TreeSet;

public class TreeSetDemo02 {
    public static void main(String[] args) {
        TreeStudent s1 = new TreeStudent("zhangsan", 23);
        TreeStudent s2 = new TreeStudent("lisi", 25);
        TreeStudent s3 = new TreeStudent("wangwu", 24);

        TreeSet<TreeStudent> ts = new TreeSet<>();
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        System.out.println(ts);//[TreeStudent{name='zhangsan', age=23}, TreeStudent{name='wangwu', age=24}, TreeStudent{name='lisi', age=25}]


        //方式一


    }
}

class TreeStudent implements Comparable<TreeStudent> {
    private String name;
    private int age;

    public TreeStudent() {
    }

    public TreeStudent(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 "TreeStudent{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(TreeStudent o) {
        //排序规则
        return this.getAge() - o.getAge();
    }
}

例如:方式二

import java.util.TreeSet;

/*
需求:请自行选择比较器排序和自然排序两种方式;
要求:存入四个字符小,c”,"ab","df",“qwer"
按照长度排序,如果一样长则按照首字卧排序
 */
public class TreeSetDemo03 {
    public static void main(String[] args) {
        TreeSet<String> ts = new TreeSet<>((o1, o2) -> {
            //o1当前要添加的元素  o2已经在红黑树中存在的元素
            //按照长度排序
            int i = o1.length() - o2.length();
            //如果一样,默认排序
            i = i == 0 ? o1.compareTo(o2) : i;
            return i;
        }
        );
        ts.add("c");
        ts.add("ab");
        ts.add("df");
        ts.add("qwer");

        System.out.println(ts);//[c, ab, df, qwer]

    }
}

慢慢摸索的过程就是你学习的收获的过程。学习是一条令人时而喜极若狂、时而郁郁寡欢的道路,成长路上让我们携手共同进步。

文章过长,上传的时候容易丢失内容,难免有不对的地方,还请指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

疯狂的小鸡仔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值