第十二章、 集合

第十二章、 集合

12.1 集合的理解和好处

  1. 数组
    (1). 长度开始时必须指定,而且一旦指定,不能修改
    (2). 保存的必须为同一类型的元素
    (3). 使用数组进行增加/删除元素的示意代码—比较麻烦
    假如要写一个Person对象数组扩容:
//原数组
Person[] per=new Person[1];
per[0]=new Person();
//扩容后
Person per2=new Person[per.length+1];
for{}//拷贝per数组的元素到per2中
per2[per.length-1]=new Person();//添加新对象
  1. 集合
    (1). 可以动态保存任意多个对象,使用比较方便!
    (2). 提供了一系列方便操作对象的方法:add、remove、set、get等
    (3). 使用集合添加,删除新元素的示意代码-简洁明了

12.2 集合框架体系

Java的集合类很多,主要分为两大类,如下图:【要求背下来】

在这里插入图片描述
在这里插入图片描述

小结:
(1). 集合主要是两组(单列集合、双列集合)
(2). Collection 接口有两个重要的子接口 List Set ,他们的实现子类都是单列集合
(3). Map 接口的实现子类 是双列集合,存放的K-V

package chapter12.collection_;

import java.util.*;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class Collection_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //单列集合
        ArrayList arrayList = new ArrayList();
        arrayList.add("mark");
        arrayList.add("smath");
        //双列集合
        HashMap hashMap = new HashMap();
        hashMap.put("1", "小明");
        hashMap.put("2", "小红");
    }
}

12.3 Collection

12.3.1 Collection接口和常用方法

  1. Collection接口实现类的特点
public interface Collection<E> extends Iterable<E>

(1). Collection实现子类可以存放多个元素,每个元素可以是Object
(2). 有些Collection的实现类,可以存放重复的元素,有些是不可以
(3). 有些Collection的实现类,有些是有序的(List),有些不是有序的(Set)
(4). Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

  1. Collection接口常用方法,已实现子类.(由于接口不能实例化,用实现类ArrayList演示)
    (1). add:添加单个元素
    (2). remove:删除指定元素
    (3). contains:查找元素是否存在
    (4). size:获取元素个数
    (5). isEmpty:判断是否为空
    (6). clear:清空
    (7). addAll:添加多个元素
    (8). containsAll:查找多个元素是否都存在
    (9). removeAll:删除多个元素
    (10). 说明:以ArrayList实现类来演示
package chapter12.collection_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
public class CollectionMethod_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection col=new ArrayList();
        //(1). add:添加单个元素
        col.add("hello");
        col.add(1000);//本质:col.add(new Integer(1000));
        col.add(false);
        col.add('h');
        col.add(new Integer(100));
        col.add("hello");
        System.out.println(col);//[hello, 100, false, h, 100, hello]

        //(2). remove:删除指定元素,删除找到的第一个元素
        col.remove("hello");
        System.out.println(col);//[100, false, h, 100, hello]

        //(3). contains:查找元素是否存在
        boolean contains=col.contains(100);
        System.out.println(contains);//true

        //(4). size:获取元素个数
        System.out.println(col.size());//5

        //(5). isEmpty:判断是否为空
        System.out.println(col.isEmpty());//false

        //(6). clear:清空
        col.clear();
        System.out.println(col);//[]

        //(7). addAll:添加多个元素
        Collection co1=new ArrayList();
        Collection co2=new ArrayList();
        co1.add("A");
        co1.add("B");
        co1.add("C");
        co2.addAll(co1);
        System.out.println(co2);//[A, B, C]

        //(8). containsAll:查找多个元素是否都存在
        co2.add("D");
        boolean contain=co2.containsAll(co1);
        System.out.println(contain);//true

        //(9). removeAll:删除多个元素
        co2.removeAll(co1);
        System.out.println(co2);//[D]
    }
}

12.3.2 Collection接口遍历元素方式1-使用Iterator(迭代器)

  1. Iterator基本介绍
    (1). Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
    (2). 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
    (3). Iterator仅用于遍历集合,Iterator本身并不存放对象。
    (4). 迭代器的执行原理:
Collection coll=new ArrayList();
Iterator iterator=coll.iterator();//得到一个迭代器
//hashNext():判断是否还有下一个元素
while(iterator.hasNext()){
    iterator.next();//next():作用:1.指针下移 2.将下移后集合上的元素返回
    System.out.println(iterator.next());
}
  1. Iterator 接口主要有以下三个方法:
    (1). boolean hasNext(); 检查集合中是否还有下一个元素。
    (2). E next(); 返回集合中的下一个元素。
    (3). void remove(); 从集合中移除 next() 方法返回的最后一个元素。这个方法是可选的,某些实现可能不支持这个操作。
    (4). 下面是一个使用 Iterator 的示例:
package chapter12.collection_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
public class IteratorExample {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        // 创建一个ArrayList并添加一些元素
        Collection coll = new ArrayList();
        coll.add("苹果");
        coll.add("香蕉");
        coll.add("草莓");

        // 获取coll 对应的Iterator对象
        Iterator iterator = coll.iterator();

        // 遍历集合
        // 这里有个快捷键,快速生成while =>itit
        // 显示所有快捷键的快捷键 ctrl + j
        while (iterator.hasNext()) {
            // 获取下一个元素,类型是Object
            Object obj=iterator.next();
            System.out.println(obj);
            // 如果需要移除某些元素,可以使用remove方法
            // iterator.remove(); // 可选操作
        }
    }
}
/*
苹果
香蕉
草莓
 */
  1. 注意事项
    (1). hasNext() 方法在集合的元素遍历完毕后返回 false。
    (2). next() 方法在集合遍历到最后一个元素时必须调用 hasNext() 来确保还有更多的元素,否则会抛出 NoSuchElementException。
    (3). remove() 方法在调用 next() 之后才有效,且在调用 remove() 之前不能再调用 next() 方法,否则会抛出 IllegalStateException。
    (4). 当退出while循环后,这时iterator迭代器,指向最后的元素,此时如果再使用next()方法就会报错NoSuchElementException。如果希望再次遍历,就需要重置一下迭代器即可。即再次获取coll 对应的Iterator对象。iterator = coll.iterator();

  2. 小结:
    在调用iterator.next();方法之前必须要调用iterator.hasNext();进行检测。若不调用,且下一条记录无效,直接调用iterator.next()的话,就会抛出NoSuchElementException。

12.3.3 Collection接口遍历元素方式2-使用for循环增强。

增强for循环,可以替代Iterator迭代器,特点:增强for就是简化的Iterator,本质是一样的。只能用于遍历集合或数组。

  1. 基本语法
for(元素类型 元素名:集合名或数组名){
	访问元素;
}
package chapter12.collection_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
public class CollectionFor_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection coll = new ArrayList();
        coll.add("苹果");
        coll.add("香蕉");
        coll.add("草莓");

        //增强for循环,底层仍然是通过迭代器去实现的
        //快捷键 I
        for (Object str:coll) {
            System.out.println(str);
        }
    }
}
/*
苹果
香蕉
草莓
 */

12.3.4 Collection练习

  1. 编写程序
    (1). 创建 3个Dog{name,age}对象,放入到ArrayList中,赋给List引用
    (2). 用迭代器和增强for循环两个方式来遍历
    (3). 重写Dog的toString方法,输出name和age
package chapter12.collection_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
public class CollectionExercise {
    public static void main(String[] args) {
        Dog dog1 = new Dog("小黑", 2);
        Dog dog2 = new Dog("小黄", 3);
        Dog dog3 = new Dog("小白", 5);
        List arrayList=new ArrayList();
        arrayList.add(dog1);
        arrayList.add(dog2);
        arrayList.add(dog3);
        //迭代器
        Iterator iterator = arrayList.iterator();
        while(iterator.hasNext()){
            Object obj= iterator.next();
            System.out.println(obj);
        }
        //Dog{name='小黑', age=2}
        //Dog{name='小黄', age=3}
        //Dog{name='小白', age=5}
        //增强for
        for (Object o :arrayList) {
            System.out.println(o);
        }
        //Dog{name='小黑', age=2}
        //Dog{name='小黄', age=3}
        //Dog{name='小白', age=5}
    }
}
class Dog{
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

12.3.5 List接口和常用方法

  1. List接口基本介绍
    (1). List接口是Collection接口的子接口。
    (2). List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
    (3). List集合类中每个元素都有其对应的顺序索引,即支持索引。
    (4). List容器中的元素都对应一个整数型的序号记载器在容器中的位置,可以根据序号存取容器中的元素。
    (5). JDK API中List接口的实现类有:

在这里插入图片描述
其中常用的有ArrayList、LinkedList和Vector

package chapter12.list_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
public class List_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //(1). List接口是Collection接口的子接口。
        //(2). List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
        List list=new ArrayList();
        list.add("123");
        list.add("234");
        list.add("345");
        list.add("456");
        //(3). List集合类中每个元素都有其对应的顺序索引,即支持索引。
        //(4). List容器中的元素都对应一个整数型的序号记载器在容器中的位置,可以根据序号存取容器中的元素。
        System.out.println(list.get(3));//456
        
    }
}
  1. List接口的常用方法
    List集合里添加了一些根据索引来操作集合元素的方法
    (1). void add(int index,Object ele): 在index位置插入ele元素
    (2). boolean addAll(int index,Collection else):从index位置开始将eles中的所有元素添加进来
    (3). Object get(int index):获取指定index位置的元素
    (4). int indexOf(Object obj):返回obj在集合中首次出现的位置
    (5). int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
    (6). Object remove(int index):移除指定index位置的元素,并返回此true
    (7). Object set(int index,Object ele):设置指定index位置的元素为ele,相当于是替换
    (8). List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合 ,左闭右开
package chapter12.list_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
public class ListMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("金角");
        list.add("银角");
        System.out.println(list);//[金角, 银角]

        //(1). void add(int index,Object ele):
        list.add(1,"到");
        System.out.println(list);//[金角, 到, 银角]

        //(2). boolean addAll(int index,Collection else):从index位置开始将eles中的所有元素添加进来
        Collection coll=new ArrayList();
        coll.add("黑白");
        coll.add("到");
        list.addAll(2,coll);
        System.out.println(list);//[金角, 到, 黑白, 到, 银角]

        //(3). Object get(int index):获取指定index位置的元素
        System.out.println(list.get(2));//黑白

        //(4). int indexOf(Object obj):返回obj在集合中首次出现的位置
        System.out.println(list.indexOf("到"));//1

        //(5). int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
        System.out.println(list.lastIndexOf("到"));//3

        //(6). Object remove(int index):移除指定index位置的元素,并返回true
        boolean b=list.remove("到");
        System.out.println(b);//true
        System.out.println(list);//[金角, 黑白, 到, 银角]

        //(7). Object set(int index,Object ele):设置指定index位置的元素为ele,相当于是替换
        list.set(1,"到");
        System.out.println(list);//[金角, 到, 到, 银角]

        //(8). List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合,左闭右开
        List list1=list.subList(2,4);
        System.out.println(list1);//[到, 银角]
    }
}

12.3.6 List的三种遍历方式【ArrayList、LinkedList、Vector】

  1. 因为ArrayList、LinkedList、Vector是List的实现类,所以这三种遍历方式都是通用的。
package chapter12.list_;

import java.util.*;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class ListTraversal {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
//        List list=new ArrayList();
//        List list=new LinkedList();
        List list=new Vector();
        list.add("Dog");
        list.add("Cat");
        list.add("Red");
        list.add("Blue");
        list.add("Green");

        //使用Iterator 遍历
        Iterator iterator= list.iterator();
        while(iterator.hasNext()){
            Object o=iterator.next();
            System.out.print(o+" ");
        }
        System.out.println();
        //使用增强for 快捷键 输入I
        for (Object o :list) {
            System.out.print(o+" ");
        }
        System.out.println();
        //普通for  快捷键 输入fori
        for(int i=0;i<list.size();i++){
            System.out.print(list.get(i)+" ");
        }
    }
}
/*
Dog Cat Red Blue Green
Dog Cat Red Blue Green
Dog Cat Red Blue Green
 */

12.3.7 List接口练习

  1. 添加10个以上的元素(比如String “hello”),在2号位插入一个元素“Jack”,获得第5个元素,删除第六个元素,修改第7个元素,在使用迭代器遍历集合,要求:使用List的实现类ArrayList完成。
package chapter12.list_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
public class ListExercise {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list=new ArrayList();
        //1.添加10个hello
        for (int i = 0; i < 10; i++) {
            list.add("hello");
        }
        System.out.println(list);//[hello, hello, hello, hello, hello, hello, hello, hello, hello, hello]

        //2.在2号位添加一个Jack
        list.add(1,"jack");
        System.out.println(list);//[hello, jack, hello, hello, hello, hello, hello, hello, hello, hello, hello]

        //3.获取第五个元素
        String fifth=(String) list.get(4);
        System.out.println(fifth);//hello

        //4.删除第六个元素
        list.remove(5);
        System.out.println(list);//[hello, jack, hello, hello, hello, hello, hello, hello, hello, hello]

        //5.修改第七个元素
        list.set(6,"mark");

        //6.通过迭代器遍历集合
        Iterator iterator=list.iterator();
        while(iterator.hasNext()) {
            Object o=iterator.next();
            System.out.print(o+" ");
        }
        //hello jack hello hello hello hello mark hello hello hello
    }
}
  1. 使用List的实现类添加三本图书,并遍历,打印如下效果:
    名称:xx 价格:xx 作者:xx
    名称:xx 价格:xx 作者:xx
    名称:xx 价格:xx 作者:xx
    (1). 按价格排序,从低到高(使用冒泡排序)
    (2). 要求使用ArrayList、LinkedList、Vector三种集合实现
    (3). 结论:主要说明,只要实现了List接口,那么List的实现类都可以使用List接口中的方法。
  • ListExercise02类
package chapter12.list_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class ListExercise02 {

    public static void main(String[] args) {
        List arrayList = new ArrayList();
//        List linkedList=new LinkedList();
//        List vector=new Vector();
        arrayList.add(new Book("水浒传", 120, "施耐庵"));
        arrayList.add(new Book("三国演义", 80, "罗贯中"));
        arrayList.add(new Book("西游记", 160, "吴承恩"));
        arrayList.add(new Book("红楼梦", 200, "曹雪芹"));

        System.out.println(arrayList.get(0).getClass());
        System.out.println();
        
        for (Object o : arrayList) {
            System.out.println(o);
        }
        System.out.println("======排序后======");
        bubbleSort(arrayList);
        for (Object o : arrayList) {
            System.out.println(o);
        }
    }

    public static void bubbleSort(List list) {
        int listSize = list.size();
        for (int i = 0; i < listSize - 1; i++) {
            for (int j = 0; j < listSize - i - 1; j++) {
                Book b1 = (Book) list.get(j);
                Book b2 = (Book) list.get(j + 1);
                if (b1.getPrice() > b2.getPrice()) {
                    list.set(j + 1, b1);
                    list.set(j, b2);
                }
            }
        }
    }
}
  • Book类
package chapter12.list_;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class Book {
    private String name;
    private double price;
    private String author;

    public Book(String name, double price, String author) {
        this.name = name;
        this.price = price;
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return "名称:" + name + "\t\t价格:" + price + "\t\t作者:" + author + "\t\t";
    }
}

12.3.8 ArraysList底层结构和源码分析

  1. ArrayList的注意事项
    (1). Permits all elements,including null,ArrayList 可以假如null,并且多个
    (2). ArrayList 是由数组来实现存储的
    (3). ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)。在多线程情况下,不建议使用ArrayList。
package chapter12.list_;

import java.util.ArrayList;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class ArrayListDetail {
    public static void main(String[] args) {
        //ArrayList 是线程不安全的。源码中没有synchronized关键字
        /*
        public boolean add(E e) {
            modCount++;
            add(e, elementData, size);
            return true;
        }
         */
        ArrayList arrayList=new ArrayList();
        arrayList.add(null);
        arrayList.add("hello");
        arrayList.add(null);
        System.out.println(arrayList);
    }
}

注意:IDEA默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据,需要做设置。如下图:
这是没有修改过看的数据:

在这里插入图片描述
这是修改过后看到的数据:

在这里插入图片描述

这是修改的地方:
在这里插入图片描述
2. ArrayList底层操作机制和源码分析(重点、难点)
(1). ArrayList中维护了一个Object类型的数组elementData.【debug看源码】
transient Object[] elementData;//transient 表示瞬间,短暂的,表示该属性不会被序列化。
(2). 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0(JDK7是10)
(3). 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到适合位置。
(4). 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍。
(5). 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity,如果需要扩容,则扩容elementData为1.5倍。
具体要进行Debug代码如下:

package chapter12.list_;

import java.util.ArrayList;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class ArrayListSource {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
//        ArrayList list =new ArrayList();
        ArrayList list =new ArrayList(8);
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
        for (int i = 11; i < 15; i++) {
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);
        for (Object o :list) {
            System.out.print(o+"  ");
        }

    }
}

Debug流程(无参构造器):

在这里插入图片描述

Debug流程(有参构造器):

在这里插入图片描述

12.3.9 Vector底层结构和源码分析

  1. Vector的基本介绍
    (1). Vector类的定义说明
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

(2). Vector底层也是一个对象数组,protected Object[] elementData;
(3). Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized

public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    return elementData(index);
}

(4). 在开发中,需要线程同步安全时,考虑使用Vector

  1. Vector和ArrayList的比较
底层结构最早出现版本线程安全(同步)效率扩容倍数
ArrayList可变数组Jdk1.2不安全,效率高如果有参构造1.5倍如果是无参:1.第一次102.从第二次开始按1.5倍扩容
Vector可变数组Jdk1.1安全,效率不高如果是无参,默认是10,满后按2倍扩容如果是指定大小,则每次直接按2倍扩容
package chapter12.list_;

import java.util.Vector;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class Vector_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Vector vector=new Vector();
//        Vector vector=new Vector(9);
        for (int i = 1; i < 11; i++) {
            vector.add(i);
        }
        vector.add(100);
        System.out.println(vector);
        //1.创建Vector对象,初始化空间为10
        /*
        public Vector() {
            this(10);
        }
         */
        //2.下面这个代码将数据添加到Vector集合里面去
        /*
        private void add(E e, Object[] elementData, int s) {
            if (s == elementData.length)
                elementData = grow();
            elementData[s] = e;
            elementCount = s + 1;
        }
        里面的if条件判断语句就已经进行了是否需要扩容的判断,条件是s == elementData.length
         */
        //3.如果数组大小不够用,就进行扩容,扩容算法如下:capacityIncrement为0;
        /*
        private int newCapacity(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                             capacityIncrement : oldCapacity);
            if (newCapacity - minCapacity <= 0) {
                if (minCapacity < 0) // overflow
                    throw new OutOfMemoryError();
                return minCapacity;
            }
            return (newCapacity - MAX_ARRAY_SIZE <= 0)
                ? newCapacity
                : hugeCapacity(minCapacity);
        }
         */
        //有参构造器,自己Debug追一下即可
    }
}

12.3.10 LinkedList底层结构和源码分析

  1. LinkedList的全面说明
    (1). LinkedList实现了双向链表和双端队列特点
    (2). 可以添加任意元素(元素可以重复),包括null
    (3). 线程不安全,没有实现同步

  2. LinkedList的底层操作机制
    (1). LinkedList底层维护了一个双向链表
    (2). LinkedList中维护了两个属性first和last分别指向 首节点和尾节点
    (3). 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
    (4). 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
    (5). 模拟一个简单的双向链表
    在这里插入图片描述

package chapter12.list_;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class LinkedList01 {
    public static void main(String[] args) {
        Node jack = new Node("jack");
        Node mark = new Node("mark");
        Node smith = new Node("smith");

        //连接三个节点,形成双向链表
        //jack -> mark -> smith
        jack.next=mark;
        mark.next=smith;

        //smith -> mark -> jack
        smith.pre=mark;
        mark.pre=jack;

        Node first=jack;//让first引用指向Jack,就是双向链表的头结点
        Node last=smith;//让last引用指向smith,就是双向链表的尾结点

        System.out.println("===从头开始遍历输出===");
        //从头开始遍历输出
        while(true){
            if(first==null){
                break;
            }
            System.out.println(first);
            first=first.next;
        }

        System.out.println("===从尾到头遍历===");
        //从尾到头遍历
        while(true){
            if(last==null){
                break;
            }
            System.out.println(last);
            last=last.pre;
        }

        //现要求:在mark  后面插入一个hello对象
        //1.首先创建hello对象
        Node hello = new Node("hello");
        hello.next=smith;
        hello.pre=mark;

        mark.next=hello;
        smith.pre=hello;

        first=jack;//让first重新引用指向Jack,就是双向链表的头结点,因为上面first已经移到到最后的smith去了

        System.out.println("===插入hello后的链表===");
        while(true){
            if(first==null){
                break;
            }
            System.out.println(first);
            first=first.next;
        }

    }
}

class Node{
    public Object item;//真正存放数据
    public Node next;//指向后一个节点
    public Node pre;//指向前一个节点

    public Node(Object item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Node item= " + item;
    }
}
  1. LinkedList源码分析
package chapter12.list_;

import java.util.LinkedList;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class LinkedListuse {
    public static void main(String[] args) {
        LinkedList linkedList=new LinkedList();
        for (int i = 0; i < 2; i++) {
            linkedList.add(i);
        }
        /*通过debug演示添加两个节点的源码以及分析
        第一步:执行LinkedList linkedList=new LinkedList();进行初始化操作,first=null,last=null
          public LinkedList() {}
        第二步:执行for循环add(0),先是进行装箱,然后再执行添加操作
            public boolean add(E e) {
                linkLast(e);
                return true;
            }
        第三部:执行linkLast(e),将创建双向链表的第一个节点,item=0,first=null,last=null,l是pre,null是next
            void linkLast(E e) {
                final Node<E> l = last;
                final Node<E> newNode = new Node<>(l, e, null);
                last = newNode;
                if (l == null)
                    first = newNode;
                else
                    l.next = newNode;
                size++;
                modCount++;
            }
        第四步:执行for循环add(1),先是进行装箱,然后再执行添加操作,代码和第三步执行的一样。
        不过条件不一样,执行的结果也不一样。通过和代码去分析即可。
        这里做一个简单的分析:首先将@112这个节点的last指向赋给l,那么l也就指向了@112这个节点,
        然后new一个新节点,因为l传参的位置是pre,所以这个新节点@122的pre就指向@112
        其次,l!=null,执行的是l.next.l由上面知道是@112,那么@112的next就指向@122
         */
        linkedList.add(100);
        linkedList.add(200);
        linkedList.add(300);
        linkedList.add("hello");
        System.out.println(linkedList);//[0, 1, 100, 200, 300, hello]

        linkedList.remove();//默认删除第一个节点
        System.out.println(linkedList);//[1, 100, 200, 300, hello]
        linkedList.remove(3);//删除指定索引节点,索引从0开始
        System.out.println(linkedList);//[1, 100, 200, hello]
        linkedList.remove("hello");//删除指定内容节点
        System.out.println(linkedList);//[1, 100, 200]
        /*通过debug演示默认删除第一个节点的源码以及分析linkedList.remove();
        第一步:    public E remove() {
                      return removeFirst();
                  }
        第二步:    public E removeFirst() {
                        final Node<E> f = first;
                        if (f == null)
                            throw new NoSuchElementException();
                        return unlinkFirst(f);
                    }
             让f也指向第一个节点,判断第一个节点是否存在,若不抛出异常,否则进行删除return unlinkFirst(f);
         第三步:    private E unlinkFirst(Node<E> f) {
                        // assert f == first && f != null;
                        final E element = f.item;//把第一个节点的值item赋给:element
                        final Node<E> next = f.next;//将第一个节点(@112)赋给next(@122),也就是next(@122)指向了第二个节点
                        f.item = null;//再把第一个节点的item和next置为null
                        f.next = null; // help GC//资源回收
                        first = next;//然后将first指向第二个节点,第二个节点(@122)就变成了第一个节点
                        if (next == null)
                            last = null;
                        else
                            next.prev = null;//将已经变成了第一个节点@122的pre置为null
                        size--;
                        modCount++;
                        return element;
                    }
         */
        //修改某个节点的对象
        linkedList.set(0,"hello");
        System.out.println(linkedList);//[hello, 100, 200]


        Object object=linkedList.get(1);//获取某个索引节点的对象
        System.out.println(object);//100
        System.out.println(linkedList.getFirst());//获取第一个节点,hello
        System.out.println(linkedList.getLast());//获取最后一个节点,200

        //因为LinkedList是实现了List接口,所以遍历方法也有三种:迭代器、增强for、普通for,这里就不写了

    }
}

在这里插入图片描述
4. ArrayList和LinkedList的比较

底层结构增删的效率改查的效率
ArrayList可变数组较低、数组扩容较高
LinkedList双向链表较高、通过链表追加较低

如何选择ArrayList和LinkedList
(1). 如果需要改查的操作多,选择ArrayList
(2). 如果需要增删的操作多,选择LinkedList
(3). 一般来说,在程序中,80%~90%都是查询,因此大部分情况下会选择ArrayList
(4). 在一个项目中,根据业务灵活选择,也可能这样:一个模块使用的是ArrayList,另一个模块使用的是LinkedList,也就是说,要根据业务来进行选择

12.3.11 Set

  1. Set接口基本介绍
    (1). 无序(添加和取出的顺序不一致),没有索引
    (2). 不允许重复元素,所以最多包含一个null
    (3). JDK API中Set接口的实现类有:
    在这里插入图片描述
  2. Set接口的常用方法
    和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样。
  3. Set接口的变量方法
    (1). 同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
    (2). 可以使用迭代器和增强for循环,但是由于没有索引不能使用普通for循环
package chapter12.set_;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class MapMethod {
    public static void main(String[] args) {
        //1.用Set的实现类HashSet,演示用的是Set的方法。
        //2.set接口的实现类的对象(Set接口对象),不能存放重复的元素,只能添加一个
        //3.set接口对象存放数据是无序的(即添加顺序和取出顺序不一致)
        //4.注意:取出的顺序虽然不是添加的顺序,但是它的顺序是固定的
        Set set = new HashSet();
        set.add("jack");
        set.add("mark");
        set.add("smith");
        set.add("smith");
        set.add("john");
        set.add(null);
        //使用迭代器输出
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object o = iterator.next();
            System.out.print(o + "  ");
        }
        System.out.println();
        //使用增强for循环
        for (Object o : set) {
            System.out.print(o + "  ");
        }
/*
null  smith  john  jack  mark
null  smith  john  jack  mark
 */
    }
}

12.3.12 Set 接口实现类-HashSet

  1. HashSet的全面说明
    (1). HashSet实现了Set接口
    (2). HashSet实际上是HashMap,看源码
public HashSet() {
    map = new HashMap<>();
}

(3). 可以存放null值,但是只能有一个null
(4). HashSet不保证元素是有序的,取决于hash后,再确定索引的结果
(5). 不能有重复元素/对象(难点,要解析其源码怎么定义这个重复)。如下代码:

package chapter12.set_;

import java.util.HashMap;
import java.util.HashSet;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashSet01 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        //1.在执行add方法后,会返回一个boolean值;如果添加成功则返回一个true,否则就返回一个false
        //2.可以通过remove指定删除哪个对象
        System.out.println(hashSet.add("john"));//t
        System.out.println(hashSet.add("lucy"));//t
        System.out.println(hashSet.add("john"));//f
        System.out.println(hashSet.add("jack"));//t
        System.out.println(hashSet.add("Rose"));//t
        hashSet.remove("john");
        System.out.println(hashSet);//[Rose, lucy, jack]
    }
}
  1. HashSet底层机制说明
    (1). 分析HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
    (2). 模拟简单的数组+链表结构HashSetStructure.java。先看图在看代码。

在这里插入图片描述

package chapter12.set_;

/**
 * @aim java基础学习
 * @note java笔记
 */
public class HashSetStructure {
    public static void main(String[] args) {
        //模拟一个HashSet的底层(HashMap的底层结构)

        //1.创建一个数组,数组的类型是Node[]
        //2.Node[]这个数组也可以成为表
        Node[] table=new Node[16];
        System.out.println(table);
        //3.创建节点
        Node john = new Node("john", null);
        table[2]=john;
        Node jack = new Node("jack", null);
        john.next=jack;//将jack挂载到john        N
        Node rose = new Node("Rose", null);
        jack.next=rose;//将rose挂载到jack

        Node lucy = new Node("lucy", null);
        table[3]=lucy;
        System.out.println(table);

    }
}

class Node{//节点,存储数据,可以指向下一个节点,从而形成链表
    Object item;//存放数据
    Node next;//可以指向下一个节点

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}

table最终的部分数据如下:其他为null
在这里插入图片描述3. HashSet底层源码分析和结论如下(重点、难点)
(1). 分析HashSet在添加元素时底层是如何实现(hash()+equals())HashSetSource.java
(2). HashSet底层是HashMap
(3). 添加一个元素时,先得到hash值-会转出索引值
(4). 找到存储数据表table,看这个索引位置是否已经存放有元素,如果没有则直接加入;如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后。
(5). 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进化为红黑树。

package chapter12.set_;

import java.util.HashSet;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet= new HashSet();
        hashSet.add("java");
        hashSet.add("python");
        hashSet.add("java");
        System.out.println(hashSet);
        /*
        第一步:执行 HashSet hashSet= new HashSet();创建HashSet对象
            public HashSet() {
                map = new HashMap<>();
            }

        第二步:执行 hashSet.add("java");
                public boolean add(E e) { //e="java"
                    return map.put(e, PRESENT)==null;
                }
       其中PRESENT:private static final Object PRESENT = new Object();   它是不可变的,起一个站位符作用

        第三步:执行 put()方法
                public V put(K key, V value) { //key="java"  value=PRESENT 共享的
                    return putVal(hash(key), key, value, false, true);
                }
        其中要进入hash(kay) 方法中不能用 步入 F7 要使用 强制步入 ctrl+shift+F7
        执行hash(key)方法 会得到key对应的hash值,算法:h = key.hashCode()) ^ (h >>> 16);
        这个算法这样做可以理解:为了不同的key可以得到不同的hash值,从而避免碰撞。且这个hash不等价于hashCode

        第四步:执行putVal()方法,对下面代码进行解析
            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就是HashMap 的一个数组,类型就是Node[]
                    //if语句表示如果当前table是null 或者大小=0
                    //即进行第一次扩容,resize()就是对table扩容的方法操作,即扩容到16个空间
                    if ((tab = table) == null || (n = tab.length) == 0)
                        n = (tab = resize()).length;
                    //1.从上面得到n=16之后,根据key,得到hash 去计算该key应该存放到table表中的哪个索引位置,
                    //  hash=hash(key),hash(key)是传入的值
                    //2.并把这个位置的对象 ,赋值给p,即p=tab[i],即指向链表的第一个节点
                    //3.通过进行&按位与运算,得到具体的索引位置,并判断这个位置是否为空
                    // 如果为空,就创建一个Node(key="java",value=PRESENT,next=null)
                    // 如果不为空,就进入else语句:又有三个判断。当再次add("java"),就会else,因为它们计算出来的索引i都一样
                    if ((p = tab[i = (n - 1) & hash]) == null)
                        tab[i] = newNode(hash, key, value, null);
                    else {
                        //一个开发技巧提示:在需要局部变量(辅助变量)的时候,再创建
                        Node<K,V> e; K k;
                        //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
                        //并且满足 下面两个条件之一:
                        //(1).p.key(p指向链表的第一个节点的key)和准备要添加的key是同一个对象
                        //(2).通过要添加的key的equals和 p指向链表的第一个节点的key比较后相同
                        //则不能添加
                        if (p.hash == hash &&
                            ((k = p.key) == key || (key != null && key.equals(k))))
                            e = p;
                        //再判断p是不是一颗红黑树,如果是一颗红黑树,就调用putTreeVal()来进行添加
                        else if (p instanceof TreeNode)
                            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                        //如果table对应索引位置,已经是有多个Node链表,就是使用for循序进行比较,从第二个开始比较,因为上面第一个if已经比较了
                        //(1).依次和该链表的每个元素比较后,都不相同,则添加到该链表的最后
                            注意:在把该元素添加到链表最后时,立即判断该链表是否已经达到了8个节点,
                            就调用treeifyBin(),对当前索引的链表进行树化操作(转成红黑树)
                            注意:在转成红黑树时,还需进行判断(该table是否达到了64),如果达到就转成红黑树,否则就继续扩容
                                    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                                        resize();
                        //(2).依次和该链表中的每个元素比较过程中,如果有相同情况,就直接break,不添加
                        else {
                            for (int binCount = 0; ; ++binCount) {
                                if ((e = p.next) == null) {
                                    p.next = newNode(hash, key, value, null);
                                    //下面这个if就是判断是否需要进行树化
                                    if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
                                        treeifyBin(tab, hash);
                                    break;
                                }
                                if (e.hash == hash &&
                                    ((k = e.key) == key || (key != null && key.equals(k))))
                                    break;
                                p = e;
                            }
                        }
                        //经过了上面三个条件判断,如果存在相同的情况,就将存在的这个节点赋值给e
                        //最终return oldValue;即不返回null,就表示添加失败
                        if (e != null) { // existing mapping for key
                            V oldValue = e.value;
                            if (!onlyIfAbsent || oldValue == null)
                                e.value = value;
                            afterNodeAccess(e);
                            return oldValue;
                        }
                    }
                    ++modCount;
                    if (++size > threshold)//threshold临界值,如果当前数组的元素大于这个临界值,就执行resize()扩容操作
                        resize();
                    afterNodeInsertion(evict);//在HashMap中方法体是空的,其主要目的是让其子类实现
                    return null;//返回null表示添加元素成功
                }
         */
    }
}
  1. HashSet的扩容和转成红黑树机制
    (1). HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16加载因子(LoadFactor)是0.75=12
    (2). 如果table数组使用到临界值12,就会扩容到16
    2=32,新临界值就是32*0.75=24,依次类推。
    (3). 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制。
    (4). 每添加一个节点Node,就会size+=1,就不管是否在同一个位置上形成链表,即所有的节点Node达到了临界值12就会扩容
package chapter12.set_;

import java.util.HashSet;
import java.util.Objects;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashSetIncrement {
    public static void main(String[] args) {
        /*
        debug查看table数组
        HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(LoadFactor)是0.75=12
        如果table数组使用到临界值12,就会扩容到16*2=32,新临界值就是32*0.75=24,依次类推。
         */
        HashSet hashSet = new HashSet();
//        for (int i = 0; i < 100; i++) {
//            hashSet.add(i);
//        }
        /*
        在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),
        并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制。
        也是使用debug查看
         */
        for (int i = 1; i <= 12; i++) {
            hashSet.add(new A(i));
        }
    }//当i=8时,链表就满了,超过8时就会进行一个判断是否需要进行树化,或者需要扩容直到64
    // 当i=9,i=10都是进行扩容,链表的位置可能会发生变化:tab[i = (n - 1) & hash]
    //当i=11时,就进行树化,链表 由Node变成了TreeNode 红黑树
}

class A{
    private int n;

    public A(int n) {
        this.n = n;
    }
    //不重写equals(),又保证了它们对比的不是值相等
    @Override
    public int hashCode() {//重写HashCode(),保证计算出来的hash不一样:h = key.hashCode()) ^ (h >>> 16);
        return 100;
    }
}

12.3.13 HashSet练习

  1. 定义一个Employee类,该类包含:private成员属性name,age要求:
    HashSetExercise01.java
    (1). 创建3个Employee放入HashSet中
    (2). 当name和age的值相同时,认为是相同员工,不能添加到HashSet集合中
package chapter12.set_;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashSetExercise01 {
    public static void main(String[] args) {
        Employee jack = new Employee("jack", 18);
        Employee mark1 = new Employee("mark", 18);
        Employee john = new Employee("john", 18);
        Employee mark2 = new Employee("mark", 18);

        HashSet hashSet = new HashSet();
        hashSet.add(jack);
        hashSet.add(mark1);
        hashSet.add(mark2);
        hashSet.add(john);

        Iterator iterator=hashSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.getAge() && name.equals(employee.getName());
    }

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

    @Override
    public String toString() {
        return "name=" + name + " age=" + age ;
    }
}
/*
name=john age=18
name=jack age=18
name=mark age=18
 */
  1. 定义一个Employee类,该类包含:private成员属性name,sal,birthday(MyDate类型),其中birthday为MyDate类型(属性包括:year,month,day),要求:
    (1). 创建3个Employee放入到HashSet
    (2). 当name和birthday的值相同时,认为是相同员工,不能添加到HashSet集合中。
package chapter12.set_;

import java.util.HashSet;
import java.util.Objects;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashSetExercise02 {
    public static void main(String[] args) {
        MyDate myDate1 = new MyDate("2011", "10", "23");
        MyDate myDate2 = new MyDate("2000", "3", "12");

        HashSet hashSet=new HashSet();
        hashSet.add(new EmployeeInfo("mark",3000,myDate1));
        hashSet.add(new EmployeeInfo("jack",1600,myDate1));
        hashSet.add(new EmployeeInfo("mark",2100,myDate2));
        hashSet.add(new EmployeeInfo("mark",2300,myDate1));

        for (Object o :hashSet) {
            System.out.println(o);
        }
        /*输出如下:
        Employee_{name='jack', sal=1600.0, birthday=2011-10-23}
        Employee_{name='mark', sal=2100.0, birthday=2000-3-12}
        Employee_{name='mark', sal=3000.0, birthday=2011-10-23}
         */
    }
}
@SuppressWarnings({"all"})
class EmployeeInfo{
    private String name;
    private double sal;
    private MyDate birthday;

    public EmployeeInfo(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        EmployeeInfo employee = (EmployeeInfo) o;
        return  Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);
    }
    //确保是姓名和出生日期是相同的,其hash值也相同
    @Override
    public int hashCode() {
        return Objects.hash(name,birthday.hashCode());
    }
    //确保可以看到输出信息
    @Override
    public String toString() {
        return "Employee_{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }
}
@SuppressWarnings({"all"})
class MyDate{
    private String year;
    private String month;
    private String day;

    public MyDate(String year, String month, String day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyDate myDate = (MyDate) o;
        return Objects.equals(year, myDate.year) && Objects.equals(month, myDate.month) && Objects.equals(day, myDate.day);
    }
    //获取birthday的hash,即出生日期相同的hash就相同
    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }
    //确保可以看到输出信息
    @Override
    public String toString() {
        return  year + "-" + month + "-" + day ;
    }
}

12.3.14 Set接口实现类-LinkedHashCodeSet

  1. LinkedHashSet的全面说明
    (1). LinkedHahsSet是HashSet的子类
    (2). LinkedHashSet底层是一个LinkedHashSet,底层维护了一个数组+双向链表
    (3). LinkedHashSet根据元素的HashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序来保存的。
    (4). LinkedHashSet不允许添加重复元素

在这里插入图片描述
2. LinkedHashSet源码分析(debug分析)

package chapter12.set_;

import java.util.*;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class LinkedHashSetSource {
    public static void main(String[] args) {
        Set set=new LinkedHashSet();
        set.add("hello");
        set.add("456");
        set.add("456");
        set.add(new Customer());
        set.add("124");
        set.add("hi");

        //1.LinkedHashSet 添加顺序和取出元素/数据的顺序一致
        //2.LinkedHashSet 底层维护的是一个LinkedHashSet(是HashMap的子类)
        //3.LinkedHashSet 底层结构(数组table+双向链表)
        //4.添加第一次时,直接将 数组table 扩容到 16 ,存放的节点类型是 LinkedHashMap$Entry
        //5.数组是 HashMap$Node[] 存放的元素/数据类型是  LinkedHashMap$Entry
	// 6.用的也是putVal()方法,和前面HashSet的一样。
        /*
            static class Entry<K,V> extends HashMap.Node<K,V> {
                Entry<K,V> before, after;
                Entry(int hash, K key, V value, Node<K,V> next) {
                    super(hash, key, value, next);
                }
            }
         */
        System.out.println(set);//[hello, 456, chapter12.set_.Customer@1e643faf, 124, hi]
    }
}
class Customer{}

12.3.15 LinkedHashSet练习

  1. Car类(属性:name,price),如果name和price一样,则认为是相同元素,就不能添加。
package chapter12.set_;

import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class LinkedHashSetExercise01 {
    public static void main(String[] args) {
        Set set=new LinkedHashSet();
        set.add(new Cat("小黑车", 20000));//t
        set.add(new Cat("小黑车", 30000));//t
        set.add(new Cat("小白车", 10000));//t
        set.add(new Cat("小黑车", 20000));//f
        set.add(new Cat("小紫车", 20000));//t
        set.add(new Cat("小黑车", 20000));//f
        System.out.println(set);
        //[
        //Cat{name='小黑车', price=20000.0},
        //Cat{name='小黑车', price=30000.0},
        //Cat{name='小白车', price=10000.0},
        //Cat{name='小紫车', price=20000.0}]
    }
}

class Cat{
    private String name;
    private double price;

    public Cat(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Cat cat = (Cat) o;
        return Double.compare(cat.price, price) == 0 && Objects.equals(name, cat.name);
    }

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

    @Override
    public String toString() {
        return "\nCat{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

12.3.16 Set实现接口类-TreeSet

  1. TreeSet源码解读
package chapter12.set_;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class TreeSet_ {
    public static void main(String[] args) {
        //1.当使用TreeSet的无参构造器,数据仍然是无序的
        //2.现在希望在添加元素的时候,按照字符串大小来排序
        //3.这个时候就可以使用TreeSet提供的一个有参构造器,并可以传入一个比较器(匿名内部类实现) ,并且指定排序规则
        // 参照之前的Arrays的练习和String的compareTo()
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o1).compareTo((String) o2);
            }
        });
        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("single");
        treeSet.add("abc");
        System.out.println(treeSet);// [abc, jack, single, tom]
    }
    /*   源码解析是如何进行排序的(具体讲排序其他的不讲):
    1.构造器中传入的比较器对象,其实是赋给了TreeSet的底层TreeMap的属性this.comparator
    (1).加载类信息之类的,然后进行构造器初始化
        public TreeSet(Comparator<? super E> comparator) {
            this(new TreeMap<>(comparator));
        }
    (2).实际上使用的TreeMap对象,然后将这个构造器对象赋给TreeMap的属性
        public TreeMap(Comparator<? super K> comparator) {
            this.comparator = comparator;
        }
    2.接下来就看看它是如何进行排序的,添加第一个元素就不用看了,默认添加
    (1).先执行add()方法
        public boolean add(E e) {
            return m.put(e, PRESENT)==null;//m.put(e, PRESENT)返回null表示添加成功
        }
     (2).实际上执行的是TreeMap的put()方法
            public V put(K key, V value) {
        int cmp;
        Entry<K,V> parent;
        //这个comparator,就是初始化赋的值,也就是那个匿名内部类对象(实现Comparator接口),现在又赋给cpr
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                //compare()就是重写的方法,key是当前要添加的key,t.key是已经添加成功的元素,这里做循环去比较
                //调用重写的compare(),若返回负数,则不移;若返回正数,key则往前移;若相同不添加
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
    }
     */
}
  1. TreeSet练习
    要求使用TreeSet添加元素的时候,按照字符串的长度来排序。
package chapter12.set_;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class TreeSetExercise {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        treeSet.add("abcdef");
        treeSet.add("abcde");
        treeSet.add("abcd");
        treeSet.add("abc");
        treeSet.add("tom");//问这个tom可以添加吗?答:不可以,因为底层己经改为判断了同一长度不添加
        System.out.println(treeSet);//[abc, abcd, abcde, abcdef]
    }
}

12.4 Map

12.4.1 Map接口实现类的特点

  1. 注意:这里讲的是JDK8的Map接口特点
    (1). Map与Collection并列存在。用于保存具有映射关系的数据Key-Value
    (2). Map中的Key和Value可以是任何引用类型的数据,会封装到HashMap$Node对象中
    (3). Map中的key不允许重复,原因和HashSet一样,前面分析过源码
    (4). Map中的value可以重复。
    (5). Map中的key可以为null,value也可以为null,注意可以为null只能有一个,而value为null可以有多个。
    (6). 最常用String类作为Map的key
    (7). key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
package chapter12.map_;

import java.util.HashMap;
import java.util.Map;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class Map_ {
    public static void main(String[] args) {
        //1.Map与Collection并列存在。用于保存具有映射关系的数据Key-Value
        //2.Map中的Key和Value可以是任何引用类型的数据,会封装到HashMap$Node对象中
        //3.Map中的key不允许重复,原因和HashSet一样,前面分析过源码
        //4.Map中的key可以为null,value也可以为null,注意可以为null只能有一个,而value为null可以有多个。
        //5.最常用String类作为Map的key
        //6.key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
        Map map=new HashMap();
        map.put("no.1","小明");//第一个值是k,第二个是v
        map.put("no.2","小黄");
        map.put("no.1","小菊");//当有相同的k时,就等价于把v替换了
        map.put("no.3","小菊");
        map.put(null,null);
        map.put(null,"abc");//又相当于替换k,所以只能有一个null
        map.put("np.4",null);//但是相同的v可以有多个,只是k不一样。
        map.put(1,"笑笑");
        System.out.println(map); //{null=abc, 1=笑笑, no.3=小菊, no.2=小黄, no.1=小菊, np.4=null}
        //通过get方法, 传入 key , 会返回对应的value
        System.out.println(map.get("no.2"));//小黄
    }
}

(8). Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的,有因为Node实现了Entry接口,有些书也说一对k-v就是一个Entry

在这里插入图片描述

package chapter12.map_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class MapSource_ {
    public static void main(String[] args) {
        Map map=new HashMap();
        map.put("no.1","小明");//第一个值是k,第二个是v
        map.put("no.2","小黄");
        //1. k-v值最后是存储在 tab[i] = newNode(hash, key, value, null);由之前代码知道tab[i]实际上是HashMap$Node类型的
        //   所以实际上是HashMap$Node node = newNode(hash, key, value, null);
        //2. k-v 为了方便程序员的遍历, 还会 创建EntrySet集合 ,该集合存放的元素类型是 Entry, 而一个Entry对象就有k,v,
        //   也就是EntrySet<Entry<K,V>>,即 transient Set<Map.Entry<K,V>> entrySet;
        //3. 在entrySet中, 定义的类型是 Map.Entry ,但实际上存放的类型还是 HashMap$Node,其实也就是一个向上转型,如下源码:
        //   static class Node<K,V> implements Map.Entry<K,V>
        //4. 当把 HashMap$Node 对象 存放到entrySet 就方便了遍历它的k-v,因为在Map.Entry 中提供了相关的方法,如下源码
        //    interface Entry<K, V> {K getKey();V getValue();.....}

        Set set=map.entrySet();
        System.out.println(set.getClass());//HashMap$EntrySet
        for (Object entry :set) {
            System.out.println(entry.getClass());//HashMap$Node
        }

        for (Object o :set) {
            Map.Entry entry=(Map.Entry) o;
            System.out.println(entry.getKey()+"-"+entry.getValue());
            //no.2-小黄
            //no.1-小明
        }
        Set key=map.keySet();
        System.out.println(key.getClass());//HashMap$KeySet
        Collection value=map.values();
        System.out.println(value.getClass());//HashMap$Values
    }
}

12.4.2 Map接口常用方法

  1. put:添加键值对。如果键已经存在,则更新对应的值。
  2. remove:根据键删除映射关系,如果键不存在,则不做任何操作。
  3. get:根据键获取值。如果键不存在,返回 null。
  4. size:返回 Map 中键值对的数量。
  5. isEmpty:检查 Map 是否为空,即是否没有键值对
  6. clear:清空 Map 中的所有键值对
  7. containsKey:查找键是否存在
package chapter12.map_;

import jdk.nashorn.api.tree.GotoTree;

import java.util.HashMap;
import java.util.Map;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class MapMethod {
    public static void main(String[] args) {
        //1. put:添加键值对。如果键已经存在,则更新对应的值。
        //2. remove:根据键删除映射关系,如果键不存在,则不做任何操作。
        //3. get:根据键获取值。如果键不存在,返回 null。
        //4. size:返回 Map 中键值对的数量。
        //5. isEmpty:检查 Map 是否为空,即是否没有键值对
        //6. clear:清空 Map 中的所有键值对
        //7. containsKey:查找键是否存在
        Map map = new HashMap();
        map.put("1", "苹果"); // 添加键值对
        map.put("2", "香蕉");
        map.put("1",new Fruit("草莓",100));
        map.put("3", "香蕉");
        map.put("4", null);
        map.put(null,"石榴");
        System.out.println(map);//{null=石榴, 1=name=草莓和price=100.0, 2=香蕉, 3=香蕉, 4=null}

        map.remove("1");
        System.out.println(map);//{null=石榴, 2=香蕉, 3=香蕉, 4=null}

        System.out.println(map.get(null));//石榴

        boolean containsKey= map.containsKey("3");
        System.out.println(containsKey);//true
        System.out.println(map.containsValue("香蕉"));//true

        System.out.println(map.size());//4
        System.out.println(map.isEmpty());//false

        map.clear();
        System.out.println(map);//{}

    }
}
class Fruit{
    private String name;
    private double price;

    public Fruit(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "name=" + name + "和price=" + price ;
    }
}

12.4.3 Map接口遍历的方法

  1. containsKey:查找键是否存在
  2. keySet:获取所有键
  3. entrySet:获取所有关系
  4. values:获取所有的值
package chapter12.map_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class MapFor {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("1", "苹果"); // 添加键值对
        map.put("2", "香蕉");
        map.put("3", "香蕉");
        map.put("4", "西瓜");
        map.put("5","石榴");
        Set keys=map.keySet();
        System.out.println("======第一种方式=======");
        Iterator iterator=keys.iterator();
        while(iterator.hasNext()){
            Object key=iterator.next();
            Object value=map.get(key);
            System.out.println(key+"-"+value);
        }
        System.out.println("=====第二种方式=======");
        for (Object key :keys) {
            System.out.println(key+"-"+map.get(key));
        }
        System.out.println("====第三种方式======");
        Set entrys=map.entrySet();
        for (Object entry :entrys) {
            Map.Entry e=(Map.Entry) entry;
            Object key=e.getKey();
            Object value=e.getValue();
            System.out.println(key+"-"+value);
        }//也可以用迭代器
    }
} 
/*
======第一种方式=======
1-苹果
2-香蕉
3-香蕉
4-西瓜
5-石榴
=====第二种方式=======
1-苹果
2-香蕉
3-香蕉
4-西瓜
5-石榴
====第三种方式======
1-苹果
2-香蕉
3-香蕉
4-西瓜
5-石榴
 */

12.4.4 Map接口练习

  1. 使用HashMap添加3个员工对象,要求键:员工id;值:员工对象并遍历显示工资>18000的员工(遍历方式最小两种)员工类:姓名、工资、员工id
package chapter12.map_;

import java.util.*;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class MapExercise01 {
    public static void main(String[] args) {
        Employee mark = new Employee("mark", "213", 15000);
        Employee jack = new Employee("jack", "214", 20000);
        Employee rose = new Employee("rose", "215", 26000);
        Map map = new HashMap();
        map.put(mark.getId(), mark);
        map.put(jack.getId(), jack);
        map.put(rose.getId(), rose);

        System.out.println("=====增强for遍历方式======");
        Set keys = map.keySet();
        for (Object key : keys) {
            Employee employee=(Employee) map.get(key);
            if(employee.getSalary()>18000){
                System.out.println(key + "-" + map.get(key));
            }
        }

        System.out.println("=====迭代器遍历方式======");
        Set entrys = map.entrySet();
        Iterator iterator = entrys.iterator();
        while (iterator.hasNext()){
            Map.Entry es=(Map.Entry) iterator.next();
            Employee employee=(Employee) es.getValue();
            if(employee.getSalary()>18000){
                System.out.println(es.getKey() + "-" + es.getValue());
            }
        }
        /*
        =====增强for遍历方式======
        214-Employee{name='jack', salary=20000.0}
        215-Employee{name='rose', salary=26000.0}
        =====迭代器遍历方式======
        214-Employee{name='jack', salary=20000.0}
        215-Employee{name='rose', salary=26000.0}
         */
    }
}

@SuppressWarnings({"all"})
class Employee {
    private String name;
    private String id;
    private double salary;

    public Employee(String name, String id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    public String getId() {
        return id;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}

12.4.5 Map实现接口类-HashMap

  1. HashMap小结
    (1). Map接口的常用实现类:HashMap、Hashtable和Properties
    (2). HashMap是Map接口使用频率最高的实现类。
    (3). HashMap是以key-val对的方式来存储数据(HashMap$Node类型)
    (4). Key不能重复,但是值是可以重复的,允许使用null键和null值
    (5). 如果添加相同的key,则会覆盖原来的key-val,等同与修改(key不会替换,val会替换)
    (6). 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的(JDK8的HashMap 底层是 数组+链表+红黑树)
    (7). HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized

  2. HashMap底层机制及源码剖析
    看一个示意图
    在这里插入图片描述
    (1). (k,v)是一个Node节点 实现了Map.Entry<K,V>,查看HashMap的源码时可以看到
    (2). Jdk7.0的HashMap底层实现【数组+链表】,jdk8.0 底层【数组+链表+红黑树】

  3. HashMap底层扩容机制【和HashSet相同】
    (1). HashMap底层维护了Node类型的数组table,默认为null
    (2). 当创建对象时,将加载因子(loadFactor)初始化为0.75.
    (3). 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引是否有元素,如果没有元素直接添加。如果该索引有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应的处理。如果添加时发现容量不够,则需要扩容。
    (4). 第1次添加,则需要扩容table容量为16,临界值(threshold)为12(160.75)
    (5). 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的两倍即24(32
    0.75),依次类推。
    (6). 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIF_CAPACITY(默认64),就会进化树化(红黑树)

  4. HashMap底层源码剖析

package chapter12.map_;

import java.util.HashMap;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashMapSource {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("java", 10);
        hashMap.put("python", 10);
        hashMap.put("java", 20);

        System.out.println(hashMap);
        /*
        1.创建HashMap对象时,初始化加载因子 loadFactor=0.75,HashMap$Node[] table=null;
            public HashMap() {
                this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
            }
        2.执行put("java"),先装箱,然后在执行put,其中key="java"  value=10
            public V put(K key, V value) {
                return putVal(hash(key), key, value, false, true);
            }
            其中hash(key)方法是计算hash值返回,代码如下: 将原始哈希码h 按位异或^ 无符号右移操作符 >>> 将 h 右移 16 位
            static final int hash(Object key) {
                int h;
                return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
            }
        3.得到hash值之后,就开始执行putVal()方法
            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 数组为null 或者 length=0 ,就扩容到16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //tab[i = (n - 1) & hash]&按位与运算,计算出索引的位置,如果该索引位置为null
        //就直接创建一个Node,将k-v加入到该Node,最后放入到该索引位置即可。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;//辅助变量
            //如果当前索引位置对应的链表的元素和准备添加的key的hash值一样
            //并且满足 下面两个条件之一:
            //(1).p.key(p指向链表的第一个节点的key)和准备要添加的key是同一个对象
            //(2).通过要添加的key的equals和 p指向链表的第一个节点的key比较后相同
            //则不能添加 将该位置的Node赋给e
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //判断p是不是一颗红黑树,如果是一颗红黑树,就调用putTreeVal()来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果找到节点,后面是链表,就用循环去比较
                for (int binCount = 0; ; ++binCount) {//死循环
                   //如果整个链表Node的可以没有和要添加的key相同,就添加到该链表的最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //添加后,判断当前链表个个数,如果已经到8个了,就调用treeifyBin方法进行红黑树转换
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //这里e不是空的,执行上面的if  e = p.next后得到了节点,如果在循环比较过程中,发现有相同 就break
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;//替换key对应的value值
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//每增加一个Node,就size++
        if (++size > threshold)//如size > 临界值,就扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }
        4.关于树化(转成红黑树)
        // 如果table 为null,或者大小还没到64 ,暂时不树化,而是进行扩容
        // 否则才会真正的树化 ->  剪枝
        final void treeifyBin(Node<K,V>[] tab, int hash) {
            int n, index; Node<K,V> e;
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                resize();
         */
    }
}
  1. 模拟HashMap触发扩容、树化情况,并验证。Debug
    (1). 将同一个索引的地方一直挂载元素,看看什么时候进行扩容,什么时候进行树化。
package chapter12.map_;

import java.util.HashMap;
import java.util.Objects;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashMapIncrement {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        for (int i = 1; i <= 12; i++) {
            hashMap.put(new A(i),10+i);
        }
        System.out.println(hashMap);
    }
    //当i=1时,进行第一次扩容 table=16
    //当i=8时,链表就满了,超过8时就会进行一个判断是否需要进行树化,或者需要扩容直到64
    //当i=9,i=10都是进行扩容,链表的位置可能会发生变化:tab[i = (n - 1) & hash], n = tab.length, tab=table
    //当i=11时,就进行树化,链表 由Node变成了TreeNode 红黑树
}
class A{
    private int num;

    public A(int num) {
        this.num = num;
    }

    //所有A对象的HashCode都是100,保证了所有的A对象在同一索引位置上,这样可以清楚看到扩容效果和树化效果
    @Override
    public int hashCode() {
        return 100;
    }

    @Override
    public String toString() {
        return "A{" +
                "num=" + num +
                '}';
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2). 在table每个索引中都添加元素,查看临界值到达哪里会扩容,扩容多少。

package chapter12.map_;

import java.util.HashMap;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashMapInrement02 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        for (int i = 1; i <= 13; i++) {
            hashMap.put(i, "hello");
        }
        for (int i = 14; i <= 25; i++) {
            hashMap.put(i, "hello");
        }
        for (int i = 26; i <= 49; i++) {
            hashMap.put(i, "hello");
        }
        for (int i = 50; i <= 97; i++) {
            hashMap.put(i, "hello");
        }
        for (int i = 98; i <= 1073741824; i++) {
            hashMap.put(i, "hello");
        }
        hashMap.put(null,2);
        hashMap.put("1",2);
        //0->16(12)->32(24)->64(48)->128(96).....
        //正常若Node节点超过了临界值就会进行扩容,但是这个数组也是有最大值的,达到最大值1073741824后,就不会再次扩容了
        //理论是这样的,但是数太大了,电脑不好的不建议验证
    }
}

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

12.4.6 Map实现接口类-Hashtable

  1. Hashtable的基本介绍
    (1). 存放的元素是键值对:即k-v
    (2). Hashtable的键和值都不能为null
    (3). Hashtable使用方法基本上和HashMap一样
    (4). Hashtable是线程安全的,HashMap是线程不安全的
    (5). 了解底层结构

  2. 判断下面的代码是否正确,如果错误,为什么

package chapter12.map_;

import java.util.Hashtable;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class HashTableExercise01 {
    public static void main(String[] args) {
        Hashtable hashtable=new Hashtable();
        hashtable.put("jack",100);//ok
//        hashtable.put(null,100);//NullPointerException异常,键不能为null
//        hashtable.put("jack",null);//NullPointerException异常,值不能为null
        hashtable.put("lucy",100);//ok
        hashtable.put("rose",100);//ok
        hashtable.put("rose",500);//替换
        for (int i = 1; i <= 6; i++) {
            hashtable.put(i,10);
        }
        System.out.println(hashtable);//{jack=100, lucy=100, 6=10, 5=10, 4=10, 3=10, 2=10, 1=10, rose=500}

        //Hashtable的底层:
        //1.底层有一个数组 Hashtable$Entry[] 初始化大小为11
        //2.临界值 threshold 为8, 11*0.75=8
        //3.Hashtable 扩容机制:直接从添加第九个元素开始debug,
        /*
        (1).先对基本数据类型进行装箱操作
        (2).执行put()方法
        (3).然后执行真正添加到数据的方法:addEntry(hash, key, value, index); 添加k-v 封装到Entry
        (4).再然后执行addEntry方法的判断语句:count表示hashtable当前存在元素的个数,threshold=8临界值
            if (count >= threshold) {
                // Rehash the table if the threshold is exceeded
                rehash();
                tab = table;
                hash = key.hashCode();
                index = (hash & 0x7FFFFFFF) % tab.length;
            }
        明显上面的if条件为true,进行扩容操作rehash()
        (5). rehash()里面的方法就按照 int newCapacity = (oldCapacity << 1) + 1;规则进行扩容,也就是乘2加+1,具体的怎么实现,自行查看
         */
    }
}

在这里插入图片描述
在这里插入图片描述
3. Hashtable和HashMap对比

发行版本线程安全(同步)效率允许null键null值
HashMap1.2不安全可以
Hashtable1.0安全较低不可以

12.4.7 Map实现接口类-Properties

  1. 基本介绍
    (1). Properties类继承自Hashtable类并实现了Map接口,也是使用一种键值对的形式来保存数据。
    (2). 它的使用特点和Hashtable类似
    (3). Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
    (4). 说明:工作后 xxx.properties文件通常作为配置文件,这个知识点在IO流举例。
package chapter12.map_;

import java.util.Properties;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class Properties_ {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("john", 100);
//        properties.put(null, 100);//NullPointerException异常,键不能为null
//        properties.put("jack", null);//NullPointerException异常,值不能为null
        properties.put("lucy", 100);//ok
        properties.put("rose", 100);//ok
        properties.put("rose", 500);//替换
        System.out.println(properties);//{john=100, rose=500, lucy=100}
        
        //查找,如果使用的getProperty(key),它会判断值是不是String,如果不是就返回null
        //至于getProperty(key,defaultValue),它会判断getProperty(key)是不是null,如果是就返回defaultValue
        System.out.println(properties.get("john"));//100
        System.out.println(properties.getProperty("john"));//null
        System.out.println(properties.getProperty("john","没有该键"));//没有该键
        //删除
        properties.remove("rose");
        System.out.println(properties);//{john=100, lucy=100}

        //修改
        properties.put("lucy","hello");
        System.out.println(properties);//{john=100, lucy=hello}
    }
}

12.4.8 Map实现接口类-TreeMap

  1. TreeMap源码解析:
package chapter12.map_;

import java.util.Comparator;
import java.util.TreeMap;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class TreeMap_ {
    public static void main(String[] args) {
        /*
        1.使用默认的无参构造器,创建TreeMap,是无序的,即没有排序
        2.现要求按照传入的key(String)的大小进行排序,从z-a
        3.put(e, PRESENT),其中e是key  PRESENT是值,即
        private static final Object PRESENT = new Object();
         */
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o2).compareTo(((String) o1));
            }
        });
        treeMap.put("Mark", "hello");
        treeMap.put("Jack", "hello");
        treeMap.put("Alan", "hello");
        treeMap.put("John", "hello");
        treeMap.put("Bella", "hello");
        System.out.println(treeMap);
        //{Mark=hello, John=hello, Jack=hello, Bella=hello, Alan=hello}

        /*
        1.构造器,把传入的实现了Comparator接口的匿名内部类(对象),传给给TreeMap的comparator
        public TreeMap(Comparator<? super K> comparator) {
            this.comparator = comparator;
        }
        2.调用put方法
        2.1第一次添加, 把k-v 封装到Entry对象, 放入到root中
        Entry<K,V> t = root;
        if (t == null) {//第一次添加时,其实也调用了匿名内部类的compare,其目的是防治传入的值是null,方便抛出异常。
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        2.2 以后添加的元素
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do { //遍历所有的key,给当前的key找到适当的位置
                parent = t;
                cmp = cpr.compare(key, t.key);//动态绑定到匿名内部类的compare
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else //如果过程中,发现准备添加的key和当前已有的key相等,那么就不添加
                    return t.setValue(value);
            } while (t != null);
        }
         */
    }
}

12.5 总结-开发中如何选择集合实现类(记住)

  1. 在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
    (1). 先判断存储类型(一组对象[单列]或一组键值对[双列])
    (2). 一组对象[单列]:Collection接口
    ① 允许重复:List
  • 增删多:LinkedList(底层维护了一个双向链表)
  • 改查多:ArrayList(底层维护了Object类型的可变数组)

② 不允许重复[双列]:Set

  • 无序:HashSet【底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)】
  • 排序:TreeSet【底层是TreeMap】
  • 插入和取出顺序一致:LinkedHashSet【底层是LinkedHashMap,LinkedHashMap的底层HashMap】,维护了数组+双向链表

(3). 一组键值对:Map
① 键无序:HashMap【底层是:哈希表,jdk7:数组+链表 ,jdk8:数组+链表+红黑树】
② 键排序:TreeMap
③ 键插入和取出顺序一致:LinkedHashMap
④ 读取文件:Properties

12.6 Collections工具类

  1. Collections工具类介绍
    (1). Collections是一个操作Set、List和Map等集合的工具类
    (2). Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
  2. 排序操作
    (1). reverse(List): 反转List中元素的顺序
    (2). shuffle(List): 对List集合元素进行随机排序
    (3). sort(List): 根据元素的自然顺序对指定List集合元素按升序排序
    (4). sort(List,Comparetor): 根据指定的Comparator产生的顺序对List集合元素进行排序
    (5). swap(List,int,int): 将指定List集合中的i处元素和j处元素进行交换
package chapter12.collections_;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class Collections_ {
    public static void main(String[] args) {
//        Collections
        //创建ArrayList集合,用于测试
        List list = new ArrayList();
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");
        list.add("Peach");
        System.out.println("原版=" + list);//原版=[Apple, Banana, Orange, Peach]

        //(1). reverse(List): 反转List中元素的顺序
        Collections.reverse(list);
        System.out.println("反转=" + list);//反转=[Peach, Orange, Banana, Apple]

        //(2). shuffle(List): 对List集合元素进行随机排序
        Collections.shuffle(list);
        System.out.println("随机=" + list);//随机=[Orange, Apple, Banana, Peach]

        //(3). sort(List): 根据元素的自然顺序对指定List集合元素按升序排序
        Collections.sort(list);
        System.out.println("自然排序=" + list);//自然排序=[Apple, Banana, Orange, Peach]

        //(4). sort(List,Comparetor):  根据指定的Comparator产生的顺序对List集合元素进行排序
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(!(o1 instanceof String) || !(o2 instanceof String)){
                    throw new ClassCastException("类型错误 要String");
                }
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        System.out.println("比较器排序=" + list);//比较器排序=[Apple, Peach, Banana, Orange]

        //(5). swap(List,int,int): 将指定List集合中的i处元素和j处元素进行交换
        Collections.swap(list,0,2);
        System.out.println("交换=" + list);//交换=[Banana, Peach, Apple, Orange]
    }
}
  1. 查找、替换
    (1). Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
    (2). Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
    (3). Object min(Collection):
    (4). Object min(Collection, Comparator):
    (5). int frequency(Collection, Object):返回指定集合中指定元素的出现次数
    (6). void copy(List dest,List src):将src中的内容复制到dest中
    (7). boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值。
package chapter12.collections_;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class CollectionsMethod {
    public static void main(String[] args) {
        //创建ArrayList集合,用于测试
        List list = new ArrayList();
        list.add("Pineapple");
        list.add("Cherry");
        list.add("Watermelon");
        list.add("Stawberry");
        System.out.println("原版的List="+list);//原版的List=[Pineapple, Cherry, Watermelon, Stawberry]

        //(1). Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
        String str= (String) Collections.max(list);
        System.out.println(str);//Watermelon

        //(2). Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
        String str1= (String) Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o1).length()-((String) o2).length();
            }
        });//按照长度
        System.out.println(str1);//Watermelon

        //(3). Object min(Collection):
        //(4). Object min(Collection, Comparator):
        //(5). int frequency(Collection, Object):返回指定集合中指定元素的出现次数
        int f=Collections.frequency(list,"Watermelon");
        System.out.println(f);//1

        //(6). void copy(List dest,List src):将src中的内容复制到dest中,要求dest的size长度大于等于src,否则抛出异常
        List dest = new ArrayList();
        for (int i = 0; i < 4; i++) {
            dest.add("");
        }
        Collections.copy(dest,list);
        System.out.println("赋值的List="+dest);//赋值的List=[Pineapple, Cherry, Watermelon, Stawberry]

        //(7). boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值。
        boolean ans=Collections.replaceAll(list,"Watermelon","Apple");
        System.out.println("替换后的List="+list);//替换后的List=[Pineapple, Cherry, Apple, Stawberry]
    }
}

12.7 本章作业

  1. 编程题 Task01.java
    按要求实现:
    (1). 封装一个新闻类,包含标题和内容属性,提供getter和setter方法,重写toString方法,打印对象时只打印标题;
    (2). 只提供一个带参数的构造器,实例化对象时,只初始化标题;并且实例化两个对象:
    新闻一:新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧
    新闻二:男字突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生
    (3). 将新闻对象添加到ArrayList集合中,并且进行倒序遍历;
    (4). 在遍历集合过程中,对新闻标题进行处理,超过15字的只保留15个,然后在后边加“…”
    (5). 在控制台打印遍历出经过处理的新闻标题
package chapter12.task_;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class Task01 {
    public static void main(String[] args) {
        ArrayList arrayList =new ArrayList();
        arrayList.add(new News("新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧"));
        arrayList.add(new News("男字突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生"));

        //这里有多种处理,可以在倒序循环中。直接处理字符串,也可以写在方法里。自由发挥
        Collections.reverse(arrayList);
        Iterator iterator=arrayList.iterator();
        while (iterator.hasNext()){
            News news=(News) iterator.next();
            String title=news.toString();
            if(title.length()>15){
                System.out.println(title.substring(0,15)+"...");
            }else{
                System.out.println(title);
            }
        }
    }
}

class News{
    private String title;
    private String content;

    public News(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return title;
    }
}
  1. 编程题 Task02.java
    使用ArrayList完成对 对象Car{name,price}的各种操作
    (1). add:添加单个元素
    (2). remove:删除指定元素
    (3). contains:查找元素是否存在
    (4). size:获取元素个数
    (5). isEmpty:判断是否为空
    (6). clear:清空
    (7). addAll:添加多个元素
    (8). containsAll:查找多个元素是否存在
    (9). removeAll:删除多个元素
    (10). 使用增强for和迭代器来遍历所有的car,需要重写car的toString。
package chapter12.task_;

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

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class Task02 {
    public static void main(String[] args) {
        Car car1 = new Car("白纸", 20000);
        Car car2 = new Car("黑书",70000);
        Car car3 = new Car("he",90000);
        ArrayList arrayList = new ArrayList();
        //(1). add:添加单个元素
        arrayList.add(car1);
        arrayList.add(car2);
        arrayList.add(car3);
        System.out.println(arrayList);
        //[Car{name='白纸', price=20000.0}, Car{name='黑书', price=70000.0}, Car{name='he', price=90000.0}]

        //(2). remove:删除指定元素
        arrayList.remove(2);
        System.out.println(arrayList);//[Car{name='白纸', price=20000.0}, Car{name='黑书', price=70000.0}]

        //(3). contains:查找元素是否存在
        boolean c=arrayList.contains(car1);
        System.out.println(c);//true

        //(4). size:获取元素个数
        System.out.println(arrayList.size());//2

        //(5). isEmpty:判断是否为空
        System.out.println(arrayList.isEmpty());//false

        //(6). clear:清空
        arrayList.clear();
        System.out.println(arrayList);//[]

        //(7). addAll:添加多个元素
        ArrayList list = new ArrayList();
        list.add(car3);
        list.add(car2);
        arrayList.addAll(list);
        System.out.println(arrayList);//[Car{name='he', price=90000.0}, Car{name='黑书', price=70000.0}]

        //(8). containsAll:查找多个元素是否存在
        System.out.println(arrayList.containsAll(list));//true

        //(9). removeAll:删除多个元素
        arrayList.removeAll(list);
        System.out.println(arrayList);//[]

        //(10).    使用增强for和迭代器来遍历所有的car,需要重写car的toString。
        System.out.println(list);//[Car{name='he', price=90000.0}, Car{name='黑书', price=70000.0}]
        System.out.println("=====增强for=====");
        for (Object o :list) {
            System.out.println(o);
            //Car{name='he', price=90000.0}
            //Car{name='黑书', price=70000.0}
        }
        System.out.println("======迭代器=====");
        Iterator iterator=list.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
            //Car{name='he', price=90000.0}
            //Car{name='黑书', price=70000.0}
        }
    }
}

class Car{
    private String name;
    private double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
  1. 编程题 Task03.java
    按要求完成下列任务
    (1). 使用HashMap类实例化一个Map类型的对象m,键(String)和值(int)分别用于存储员工的姓名和工资,存入数据如下:Jack-650元;Tom-1200元;Rose-2900元;
    (2). 将Jack的工资更改为2600元
    (3). 为所有员工工资加薪100元
    (4). 遍历集合中所有工资
package chapter12.task_;

import java.util.*;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class Task03 {
    public static void main(String[] args) {
        Map Map = new HashMap();
        //添加数据
        Map.put("Jack",650);
        Map.put("Tom",1200);
        Map.put("Rose",2900);
        System.out.println(Map);//{Tom=1200, Rose=2900, Jack=650}
        //修改Jack的工资为2600
        Map.put("Jack",2600);
        System.out.println(Map);//{Tom=1200, Rose=2900, Jack=2600}
        //为所有员工加薪100元
        Set keys=Map.keySet();
        for (Object key :keys) {
            int value=(Integer) Map.get(key)+100;
            Map.put(key,value);
        }
        System.out.println(Map);//{Tom=1300, Rose=3000, Jack=2700}
        //遍历输出所有工资
        Collection values=Map.values();
        for (Object value :values) {
            System.out.println(value);
            //1300
            //3000
            //2700
        }
    }
}
  1. 简答题
    分析HashSet和TreeSet分别是如何实现去重的
    (1). HashSet的去重机制:HashCode()+equals(),底层先通过存入对象,进行运算得到一个hash值,通过hash值得到对应的索引,如果发现table索引所在的位置,没有数据,就直接存放,如果有数据,就进行equals比较【遍历比较】,如果比较后,不相同,就添加,否则不添加。
    (2). TreeSet的去重机制:如果传入了一个Comparator匿名对象,就使用实现的compare去重,如果方法返回0,就认为是相同的元素/数据,就不添加;如果没有传入一个Comparator匿名对象,则以添加对象实现的Compareable接口的compareTo去重。

  2. 代码分析题
    下面代码运行时会不会抛出异常,并从源码层面说明原因。【考察 接口编程+动态绑定】

TreeSet treeSet =new TreeSet();
treeSet.add(new Person());
class Person{}

在add方法中,因为TreeSet()构造器没有传入Comparator接口的匿名内部类;所以底层还是Comparable<? super K> k = (Comparable<? super K>) key; 在这里进行向上转型时,就会抛出ClassCastException异常。解决办法,让Person类实现Comparator接口即可。

  1. 下面代码输出什么?
    已知Person类按照id和name重写了HashCode和equals方法。
package chapter12.task_;

import java.util.HashSet;
import java.util.Objects;

/**
 * @aim java基础学习
 * @note java笔记
 */
@SuppressWarnings({"all"})
public class Task06 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        Person a = new Person(1001, "A");
        Person b = new Person(1002, "B");
        hashSet.add(a);
        hashSet.add(b);
        a.name = "C";//a的name改变了
        System.out.println(hashSet);
        hashSet.remove(a);//删除不成功
        System.out.println(hashSet);
        hashSet.add(new Person(1001, "C"));//添加成功
        System.out.println(hashSet);
        hashSet.add(new Person(1001, "A"));//添加成功
        System.out.println(hashSet);
        //[Person{id=1001, name='C'}, Person{id=1002, name='B'}]
        //[Person{id=1001, name='C'}, Person{id=1002, name='B'}]
        //[Person{id=1001, name='C'}, Person{id=1002, name='B'}, Person{id=1001, name='C'}]
        //[Person{id=1001, name='C'}, Person{id=1002, name='B'}, Person{id=1001, name='A'}, Person{id=1001, name='C'}]
    }
}

class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && Objects.equals(name, person.name);
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值