Skip to content

day18.集合

java
课前回顾:
  1.获取Stream流:
    a.针对于集合:stream()
    b.针对于数组:Stream.of(可变参数)
  2.方法:
    filter forEach count limit skip concat collect distinct  map
  3.Collection:单列集合顶级接口
    add addAll remove  clear contains size toArray
  4.迭代器:Iterator
    a.获取:iterator()
    b.方法:
      hasNext()
      next()
    c.并发修改异常:调用add之后,只给实际操作次数+1了,但是并没有修改预期操作次数,导致了调用了next方法之后,底层判断实际操作次数和预期操作次数不一样了,所以出现了异常
  5.数据结构:
    a.栈:先进后出
    b.队列:先进先出
    c.数组:查询快,增删慢
    d.链表:
      查询慢,增删快

      单向链表:前面节点记录后面节点地址,但是后面节点不记录前面节点地址
      双向链表:前后节点互相记录
  6.ArrayList集合:
    元素有序 有索引 元素可重复 线程不安全 数据结构:数组
    add add(索引,元素) remove(Object o) remove(index) get size set

今日重点:
  1.知道LinkedList的特点以及基本使用
  2.会使用增强for遍历集合
  3.会定义含有泛型的类,方法,接口
  4.知道HashSet和LinkedHashSet的特点以及基本使用
  5.知道set集合如何保证元素唯一的

第一章.List 集合下的实现类

1.ArrayList 底层源码分析

java
1.ArrayList()  构造一个初始容量为 10 的空列表(数组)
2.ArrayList(int initialCapacity) 构造一个具有指定初始容量的空列表

3.源码分析:
  a.不是一new底层就为其创建一个长度为10的空数组,而是第一次调用add方法的时候
  b.如果超出了数组容量,会自动扩容
  c.扩多少倍:1.5倍
java
ArrayList<String> list = new ArrayList<>();
============================================
public ArrayList() {
    //transient Object[] elementData  这就是ArrayList底层的数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

==============================================
list.add("abc");

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

private Object[] grow() {
     return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        //return elementData = new Object[10]
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}
java
假设我们正在存第11个abc
=================================================================
public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

private Object[] grow() {
     return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        //return elementData = new Object[10]
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}
java
public class Demo02ArrayList {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(2);
        /*
            调用remove方法,传递的是整数,所以会自动找
            remove(int index) 按照索引删元素,所以会出现索引越界异常

            解决:
              a.可以按照0索引删除
              b.将int改成Integer -> 此时传递的是Integer类型,会去找remove(Object obj)-> 这个数直接删除指定元素

         */
        //list.remove(2);
        list.remove(Integer.valueOf(2));
        System.out.println(list);
    }
}

在实际开发过程中,我们一般使用集合不会先自己 new 一下子,我们都是调用某个方法查询了很多数据,人家方法返回值类型是集合类型

1743989638798

第二章.LinkedList 集合

java
1.概述:是List接口的实现类
2.特点:
  a.元素有序
  b.无索引
  c.元素可重复
  d.线程不安全
3.数据结构:
  双向链表
4.使用:和ArrayList一样
5.特有方法:大量直接操作首尾元素的方法
  public void addFirst(E e):将指定元素插入此列表的开头。
  public void addLast(E e):将指定元素添加到此列表的结尾。
  public E getFirst():返回此列表的第一个元素。
  public E getLast():返回此列表的最后一个元素。
  public E removeFirst():移除并返回此列表的第一个元素。
  public E removeLast():移除并返回此列表的最后一个元素。
  public E pop():从此列表所表示的堆栈处弹出一个元素。
  public void push(E e):将元素推入此列表所表示的堆栈。
  public boolean isEmpty():如果列表没有元素,则返回true。
java
public class Demo03LinkedList {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");

        list.addFirst("田七");
        System.out.println(list);
        list.addLast("朱八");
        System.out.println(list);

        list.removeFirst();
        System.out.println(list);

        System.out.println(list.getFirst());

        System.out.println("=======================");
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
java
public E pop():从此列表所表示的堆栈处弹出一个元素。
public void push(E e):将元素推入此列表所表示的堆栈。
java
:先进后出
java
public class Demo05LinkedList {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        list.push("张三");
        list.push("李四");
        list.push("王五");
        list.push("赵六");
        list.push("田七");
        System.out.println(list);

        String element1 = list.pop();
        String element2 = list.pop();
        String element3 = list.pop();
        String element4 = list.pop();
        String element5 = list.pop();
        System.out.println(element1);
        System.out.println(element2);
        System.out.println(element3);
        System.out.println(element4);
        System.out.println(element5);

    }
}

1.1 LinkedList 底层成员解释说明

java
1.LinkedList底层成员
  transient int size = 0;  元素个数
  transient Node<E> first; 第一个节点对象
  transient Node<E> last;  最后一个节点对象

2.Node代表的是节点对象
   private static class Node<E> {
        E item;//节点上的元素
        Node<E> next;//记录着下一个节点地址
        Node<E> prev;//记录着上一个节点地址

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

1.2 LinkedList 中 add 方法源码分析

java
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");

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++;
}
1743992916361

1.3.LinkedList 中 get 方法源码分析

java
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
java
index < (size >> 1)采用二分思想,先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历

第三章.增强 for

1.基本使用

java
1.格式:
  for(元素类型 变量名:集合名或者数组名){
      变量名就代表每一个元素
  }

2.作用:
  遍历集合或者数组

3.快捷键:集合名或者数组名.for
java
public class Demo01ForEach {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");

        for (String s : list) {
            System.out.println(s);
        }

        System.out.println("=================================");
        int[] arr = {1,2,3,4,5,6,7,8,9};
        for (int i : arr) {
            System.out.println(i);
        }
    }
}

2.注意

java
1.使用增强for遍历集合时:底层原理为迭代器 -> 所以在使用增强for遍历集合过程中,也不能随意修改集合长度
2.使用增强for遍历数组时:底层原理为普通for
1743993882926

第四章.Collections 集合工具类

java
1.概述:集合工具类
2.作用:操作集合
3.特点:
  a.构造私有
  b.成员静态
4.使用:类名直接调用
5.方法:
  static <T> boolean addAll(Collection<? super T> c, T... elements)->批量添加元素
  static void shuffle(List<?> list) ->将集合中的元素顺序打乱
  static <T> void sort(List<T> list) ->将集合中的元素按照默认规则排序-> ASCII码值
  static <T> void sort(List<T> list, Comparator<? super T> c)->将集合中的元素按照指定规则排序
java
public class Demo01Collections {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        list.add("田七");
        //static <T> boolean addAll(Collection<? super T> c, T... elements)->批量添加元素
        Collections.addAll(list, "张三丰", "赵四丰", "孙五丰");
        System.out.println(list);
        //static void shuffle(List<?> list) ->将集合中的元素顺序打乱
        Collections.shuffle(list);
        System.out.println(list);
        //static <T> void sort(List<T> list) ->将集合中的元素按照默认规则排序-> ASCII码值
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("c.举头望明月");
        list2.add("b.疑是地上霜");
        list2.add("d.低头思故乡");
        list2.add("a.床前明月光");
        Collections.sort(list2);
        System.out.println(list2);
    }
}
java
1.概述:Comparator比较器接口
2.方法:
  int compare(T o1, T o2)

  o1-o2 -> 升序
  o2-o1 -> 降序
java
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
public class Demo02Collections {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("张三",20));
        list.add(new Person("李四",18));
        list.add(new Person("王五",19));
/*        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });*/

        Collections.sort(list, (o1,o2)-> o1.getAge()-o2.getAge());
        System.out.println(list);
    }
}
java
1.概述:Comparable接口-> 比较器
2.方法:
  public int compareTo(T o)

  this-o : 升序
  o-this : 降序
java
public class Person implements Comparable<Person>{
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

    @Override
    public int compareTo(Person o) {
        return o.getAge()-this.getAge();
    }
}
java
public class Demo03Collections {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("张三",20));
        list.add(new Person("李四",18));
        list.add(new Person("王五",19));
        Collections.sort(list);
        System.out.println(list);
    }
}
java
Arrays中的静态方法:
static <T> List<T> asList(T...a) -> 直接指定元素,转存到list集合中

1.注意:
使用此方法批量添加之后不要修改集合长度了,因为底层是一个数组,数组被final定死了
java
public class Demo04Arrays {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("张三", "李四", "王五", "赵六");
        System.out.println(list);
        //list.add("田七");
        //System.out.println(list);
    }
}

第五章.泛型

java
1.格式:
  <E>
2.作用:
  同一类型
3.注意:
  只能写引用类型,如果啥也不写,默认是Object类型

1.为什么要使用泛型

java
public class Demo01FanXing {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList();
        list.add("张三");
        list.add("李四");
        //list.add(1);
        //list.add(true);
        //list.add(2.5);
        //遍历集合,获取集合中字符串的长度
        for (String o : list) {
            System.out.println(o.length());
        }
    }
}
java
1.从使用层面来看:统一类型,防止类型转换异常
2.从定义层面来看:定义的时候不确定将来统一什么类型,此时我们可以定义泛型,等着将来使用的时候再规定和统一类型,代码更灵活
1743997834362

2.泛型的定义

2.1 含有泛型的类

java
1.格式:
  public class 类名<E>{
      此类中方法的参数和返回值类型都可以直接用E表示了
  }

2.什么时候确定泛型类型:
  new 对象的时候
java
public class MyArrayList<E> {
    //定义长度为10的数组
    Object[] obj = new Object[10];
    //定义size,代表集合中元素的个数
    int size = 0;

    /**
     * 定义一个add方法,代表往集合中添加元素
     */
    public boolean add(E e){
        obj[size] = e;
        size++;
        return true;
    }

    /**
     * 定义一个get方法,代表获取集合中指定位置的元素
     */
    public E get(int index){
        return (E) obj[index];
    }

    /**
     * 定义一个toString方法,直接打印集合元素
     */
    public String toString(){
        String result = Arrays.toString(obj);
        return result;
    }
}
java
public class Demo02FanXing {
    public static void main(String[] args) {
        MyArrayList<String> list1 = new MyArrayList<>();
        list1.add("a");
        list1.add("b");
        list1.add("c");
        String s = list1.get(0);
        System.out.println("s = " + s);

        System.out.println(list1);
    }
}

2.2 含有泛型的方法

java
1.格式:
  修饰符 <E> 返回值类型 方法名(E e){
      方法体
      return 结果
  }
2.什么时候确定泛型类型:
  调用的时候
java
public class MyCollections {
    public static <E> void  addAll(ArrayList<E> list, E... e){
        for (E element : e) {
            list.add(element);
        }
    }
}
java
public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        MyCollections.addAll(list,"张三丰","张无忌","涛哥");
        System.out.println(list);
    }
}

2.3 含有泛型的接口

java
1.格式:
  public interface 接口名<E>{}
2.什么时候确定泛型类型:
  a.在实现类的时候还没有确定泛型类型,只能到new实现类对象的时候确定了 -> ArrayList
  b.在实现类的时候直接确定泛型类型 -> Scanner
java
public interface MyList <E>{
    public boolean add(E e);
}
java
public class MyArrayList<E> implements MyList<E>{
    //定义长度为10的数组
    Object[] obj = new Object[10];
    //定义size,代表集合中元素的个数
    int size = 0;

    /**
     * 定义一个add方法,代表往集合中添加元素
     */
    public boolean add(E e){
        obj[size] = e;
        size++;
        return true;
    }

    /**
     * 定义一个get方法,代表获取集合中指定位置的元素
     */
    public E get(int index){
        return (E) obj[index];
    }

    /**
     * 定义一个toString方法,直接打印集合元素
     */
    public String toString(){
        String result = Arrays.toString(obj);
        return result;
    }
}
java
public class Test01 {
    public static void main(String[] args) {
        MyArrayList<String> list = new MyArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        System.out.println(list);
    }
}
java
public interface MyIterator <E>{
    public E next();
}
java
public class MyScanner implements MyIterator<String>{
    @Override
    public String next() {
        return "录入一个字符串";
    }
}
java
public class Test02 {
    public static void main(String[] args) {
        MyScanner myScanner = new MyScanner();
        String next = myScanner.next();
        System.out.println("next = " + next);
    }
}

3.泛型的高级使用

3.1 泛型通配符 ?

java
1.一般常见在方法的参数位置
java
public class Demo01FanXing {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("张三");
        list1.add("李四");
        list1.add("王五");
        list1.add("赵六");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        list2.add(4);

        method(list1);
        method(list2);
    }

    public static void method(ArrayList<?> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }
}

3.2 泛型的上限下限

java
1.作用:可以规定泛型的范围
2.上限:
  a.格式:<? extends 类型>
  b.含义:?只能接收extends后面的本类类型以及子类类型
3.下限:
  a.格式:<? super 类型>
  b.含义:?只能接收super后面的本类类型以及父类类型
java
/**
 * Integer -> Number -> Object
 * String -> Object
 */
public class Demo02FanXing {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        ArrayList<Number> list3 = new ArrayList<>();
        ArrayList<Object> list4 = new ArrayList<>();

        get1(list1);
        //get1(list2);
        get1(list3);
        //get1(list4);

        System.out.println("=================");

        //get2(list1);
        //get2(list2);
        get2(list3);
        get2(list4);
    }

    //上限  ?只能接收extends后面的本类类型以及子类类型
    public static void get1(Collection<? extends Number> collection){

    }

    //下限  ?只能接收super后面的本类类型以及父类类型
    public static void get2(Collection<? super Number> collection){

    }
}

应用场景:

1.如果我们在定义类,方法,接口的时候,如果类型不确定,我们可以考虑定义含有泛型的类,方法,接口

2.如果类型不确定,但是能知道以后只能传递某个类的继承体系中的子类或者父类,就可以使用泛型的上限或者下限 -> 给泛型类型规定了范围

第六章.斗地主案例(扩展案例)

1 案例介绍

按照斗地主的规则,完成洗牌发牌的动作。 具体规则:

使用 54 张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人 17 张牌,最后三张留作底牌。

2 案例分析

  • 准备牌:

    牌可以设计为一个ArrayList<String>,每个字符串为一张牌。 每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。 牌由 Collections 类的 shuffle 方法进行随机排序。

  • 发牌

    将每个人以及底牌设计为ArrayList<String>,将最后 3 张牌直接存放于底牌,剩余牌通过对 3 取模依次发牌。

  • 看牌

    直接打印每个集合。

    1744008499855

3 代码实现

javascript
1.创建一个集合number,存储牌号
2.创建一个集合color,存储花色
3.创建一个集合poker,存储组合好的牌面
4.遍历number和color,进行字符串拼接组合牌,保存到poker中
5.调用Collections中的shuffle方法,进行打乱
6.创建4个集合对象分别为:player1  player2  player3  dipai
7.发牌,让牌面的索引%3,如果为0,存到player1中,如果为1,存到player2中;如果为2,存到player3中;如果索引大于等于51,证明是最后三张牌,就存到dipai中
8.遍历集合,看牌
java
public class Poker {
    public static void main(String[] args) {
        //1.创建一个集合number,存储牌号
        //ArrayList<String> number = new ArrayList<>();
        //Collections.addAll(number, "2","3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A");

        String[] number = "2-3-4-5-6-7-8-9-10-J-Q-K-A".split("-");
        //2.创建一个集合color,存储花色
        //ArrayList<String> color = new ArrayList<>();
        //Collections.addAll(color, "♠", "♥", "♣", "♦");
        String[] color = "♠-♥-♣-♦".split("-");
        //3.创建一个集合poker,存储组合好的牌面
        ArrayList<String> poker = new ArrayList<>();
        //4.遍历number和color,进行字符串拼接组合牌,保存到poker中
        for (String num : number) {
            for (String huaSe : color) {
                String key = huaSe + num;
                poker.add(key);
            }
        }
        //System.out.println(poker);
        poker.add("😀");
        poker.add("😔");
        //5.调用Collections中的shuffle方法,进行打乱
        Collections.shuffle(poker);
        //6.创建4个集合对象分别为:player1  player2  player3  dipai
        ArrayList<String> p1 = new ArrayList<>();
        ArrayList<String> p2 = new ArrayList<>();
        ArrayList<String> p3 = new ArrayList<>();
        ArrayList<String> dipai = new ArrayList<>();
        //7.发牌,让牌面的索引%3,如果为0,存到player1中,如果为1,存到player2中;如果为2,存到player3中;如果索引大于等于51,证明是最后三张牌,就存到dipai中
        for (int i = 0; i < poker.size(); i++) {
            String pokerPai = poker.get(i);
            if (i>=51){
                dipai.add(pokerPai);
            }else{
                if (i%3==0){
                    p1.add(pokerPai);
                }else if (i%3==1){
                    p2.add(pokerPai);
                }else{
                    p3.add(pokerPai);
                }
            }
        }
        //8.遍历集合,看牌
        System.out.println("涛哥:"+p1);
        System.out.println("萌姐:"+p2);
        System.out.println("金莲:"+p3);
        System.out.println("底牌:"+dipai);
    }
}

第七章.红黑树(了解)

java
哈希表:
  jdk8之前:数组+链表
  jdk8开始:数组+链表+红黑树

加入红黑树原因:
  提高查询效率

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

2. 根节点必须是黑色

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

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

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

https://www.cs.usfca.edu/~galles/visualization/RedBlack

第八章.Set 集合

1.Set 的介绍

java
1.概述:Set是一个接口,是Collection接口的子接口
2.特点:
  a.Set接口中的方法并没有对Collection接口进行扩充,而且所有set集合底层都是依靠map实现的
1744011316644

2.HashSet 集合的介绍和使用

java
1.概述:是Set接口的实现类
2.特点:
  a.元素无序(存进去的数据和取出来的顺序不一样)
  b.无索引
  c.元素唯一,不能重复
  d.线程不安全
3.数据结构:哈希表
  jdk8之前: 哈希表 = 数组+链表
  jdk8开始: 哈希表 = 数组+链表+红黑树
4.方法:
  和Collection一样
java
public class Demo01HashSet {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("赵六");
        set.add("赵六");
        System.out.println(set);

        //迭代器遍历
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            String name = iterator.next();
            System.out.println(name);
        }
        System.out.println("=========================");

        //增强for遍历
        for (String s : set) {
            System.out.println(s);
        }
    }
}

3.LinkedHashSet 的介绍以及使用

java
1.概述:是HashSet的子类
2.特点:
  a.元素有序
  b.无索引
  c.元素唯一,不能重复
  d.线程不安全
3.数据结构:哈希表+链表
4.方法:
  和HashSet一样
java
public class Demo02LinkedHashSet {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("赵六");
        set.add("赵六");
        System.out.println(set);

        //迭代器遍历
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            String name = iterator.next();
            System.out.println(name);
        }
        System.out.println("=========================");

        //增强for遍历
        for (String s : set) {
            System.out.println(s);
        }
    }
}

4.哈希值

java
1.概述:计算机自动计算出来的十进制数,可以代表对象的地址值
2.获取:调用Object中的方法:
       public native int hashCode();

3.注意:
  a.没有重写Object中的hashCode方法,获取的是对象本身的哈希值
  b.重写了Object中的hashCode方法之后,获取的是对象内容的哈希值

4.将下面的话背下来:
  a.哈希值不一样,内容肯定不一样
  b.哈希值一样,内容也有可能不一样
java
public class Demo01Hash {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 10);
        Person p2 = new Person("张三", 10);
        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());

        String s1 = new String("abc");
        String s2 = new String("abc");
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        System.out.println("=============================");

        String s3 = "通话";
        String s4 = "重地";
        System.out.println(s3.hashCode());//1179395
        System.out.println(s4.hashCode());//1179395
    }
}
java
public class Person{
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

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

5.字符串的哈希值时如何算出来的

java
String s = "abc"
到了String底层的数组中变成了: byte[] value = {97,98,99}

public static int hashCode(byte[] value) {
    int h = 0;
    for (byte v : value) {
        h = 31 * h + (v & 0xff);
    }
    return h;
}
java
第一圈计算: h = 31*0+97 -> h=97
第二圈计算: h = 31*97+98 -> h = 3105
第三圈计算: h = 31*3105+99 -> h = 96354
java
在相乘的过程中为啥用31作为常量,31是一个质数,用31可以更好的解决哈希值一样,但内容不一样(哈希冲突,哈希碰撞)的情况

6.HashSet 的存储去重复的过程_背下来

java
1.先比较元素哈希值,再比较元素内容
  如果哈希值不一样,存
  如果哈希值一样,再比较内容
  如果哈希值一样,内容不一样,存;如果哈希值一样,内容也一样去重复
java
public class Demo03HashSet {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("abc");
        set.add("通话");
        set.add("重地");
        set.add("abc");
        System.out.println(set);
    }
}

7.HashSet 存储自定义类型如何去重复

java
public class Person{
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
public class Demo02HashSet {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("张三", 20));
        set.add(new Person("李四", 15));
        set.add(new Person("张三", 20));
        System.out.println(set);
    }
}
java
set存储自定义类型时需要重写hashCode和equals方法,让set比较对象内容的哈希值以及对象的内容

第九章.Map 集合

双列集合:
  一个元素分成2部分 -> key和value (键值对)
  我们想要找value都是根据key去找
1744016370950