1.集合概述

1.1 为什么学集合

思考:数组有什么缺点?

  1. 长度一旦定义,不能改变!定义大了,浪费空间;小了,可能不够 ----》动态的数组
  2. 对于增删,需要移动位置 —》有人帮我们做这个事情,LinkedList
  3. 数组存储的单列数据,对于双列数据的映射关系,怎么存储(key-value,键值对,类似数学中的函数映射)?Map

基于以上问题,我们需要学习集合框架。

开发中,数组用的非常少,几乎不怎么用!

1.2 什么是集合

集合就是一个存储数据的容器。

1.3 集合的整体架构图

在这里插入图片描述

Collection继承Iterable接口,使得我们的Collection具有迭代(遍历)作用,因为Iterable接口中有一个**iterator()**方法,返回值是一个Iterator

在这里插入图片描述

在这里插入图片描述

2.List接口

List接口扩展出来的方法:

在这里插入图片描述

List接口特点:

  1. 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
  2. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
  3. 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

2.1 ArrayList(用的最多)

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

2.1.1 源码解读

【高频面试】说一说ArrayList

1. 底层使用什么存数据?
2. 初始化容量多少?
3. 容量不够,怎么扩容?
4. 线程是否安全?安全 bye  不安全:说另外一个CopyOnWriteArrayList
5. 说一说CopyOnWriteArrayList
........
最基础到第3点
  1. 底层使用什么存数据:Object对象数组

     private static final Object[] EMPTY_ELEMENTDATA = {}
    
  2. 初始化容量多少

    private static final int DEFAULT_CAPACITY = 10;
    
  3. 扩容机制?1.5倍

     int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //使用Arrays.copyOf 进行数组元素的copy
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

2.1.2 常用方法使用

public class TestArrayList {
    public static void main(String[] args) {
        List list = new ArrayList();

        //常用方法
        //add(Object e):向集合末尾处,添加指定的元素 
        //add(int index, Object e)   向集合指定索引处,添加指定的元素,原有元素依次后移
        //1.add()
        list.add(1);
        list.add("aa");

        //2.判读长度:size()
        int size = list.size();
        System.out.println(size);

        //3.遍历,3种遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        System.out.println("------------");
        for (Object o : list) {
            System.out.println(o);
        }

        System.out.println("------------");
        //使用Iterator对象
        Iterator it = list.iterator();
        while (it.hasNext()) {
            //你要防止 :NoSuchElementException
            Object o = it.next();
            System.out.println(o);
        }
        
        //lambda表达式的写法
         list.forEach(System.out::println);

        //4.获取某个下标处的值:get(int index)

        //5.修改:set(),使用不多
        list.set(0,"asfdfsdf");
        System.out.println(list.get(0));

        //6.判断集合是否为空
        System.out.println(list.isEmpty());

        //========以下用的不是很多
        //7.获取某个对象的索引(第一个,最后一个)
        System.out.println(list.indexOf("aa"));
        System.out.println(list.lastIndexOf("aa"));

        //8.清空
        list.clear();
        System.out.println(list.size());
        
        //9.移除某一个
        //remove(Object e):将指定元素对象,从集合中删除,返回值为被删除的元素
        //remove(int index):将指定索引处的元素,从集合中删除,返回值为被删除的元素
        //list.remove()
    }
}

2.1.3 迭代器的并发修改异常

 /*
  *  迭代器的并发修改异常 java.util.ConcurrentModificationException
  *  就是在遍历的过程中,使用了集合方法修改了集合的长度,不允许的
  */
 public class ListDemo1 {
  public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("abc1");
    list.add("abc2");
    list.add("abc3");
    list.add("abc4");
    
    //对集合使用迭代器进行获取,获取时候判断集合中是否存在 "abc3"对象
    //如果有,添加一个元素 "ABC3"
    Iterator<String> it = list.iterator();
    while(it.hasNext()){
      String s = it.next();
      //对获取出的元素s,进行判断,是不是有"abc3"
      if(s.equals("abc3")){
        list.add("ABC3");
      }
      System.out.println(s);
    }
  }
 }

 运行上述代码发生了错误 java.util.ConcurrentModificationException这是什么原因呢?
   在迭代过程中,使用了集合的方法对元素进行操作。
   导致迭代器并不知道集合中的变化,容易引发数据的不确定性。
   
 并发修改异常解决办法:
    在迭代时,不要使用集合的方法操作元素。
    或者通过ListIterator迭代器操作元素是可以的,ListIterator的出现,解决了使用Iterator迭代过程中可能会发生的错误情况。【使用ListIterator的add/remove/set】

在这里插入图片描述

2.1.4 数据的存储结构初识

  1. 栈结构:后进先出/先进后出(手枪弹夹) FILO (first in last out)
  2. 队列结构:先进先出/后进后出(银行排队) FIFO(first in first out)
  3. 数组结构:
    查询快:通过索引快速找到元素
    增删慢:每次增删都需要开辟新的数组,将老数组中的元素拷贝到新数组中
    开辟新数组耗费资源
  4. 链表结构
    查询慢:每次都需要从链头或者链尾找起
    增删快:只需要修改元素记录的下个元素的地址值即可不需要移动大量元素
  5. 树 【非常非常重要,二叉树、满二叉树、平衡二叉树、红黑树…】
  6. 图【数据结构、离散数学】

2.1.5 泛型集合

  1. 我们知道,集合中可以存放任意数据类型,但是我们在遍历自己的类型的时候,需要调用自己的方法,此时需要向下转型 ----->省略
  2. 能否限定集合中只能放某一种类型,将运行时异常提前到编译时期。

基于以上两点:我们需要使用泛型约束。即集合中只能存放某一种数据类型

好处:

  1. 无需向下转型
  2. 将运行时异常提前到编译时
  3. 让使用变得更灵活【即定义泛型的类,其实只规定类型,具体什么类型,由使用者决定】

小结:在使用集合的时候,要使用泛型,泛型集合

2.1.6 泛型

我们在编写通用类(给别人继承、实现,直接使用)使用,具体某个类存放什么数据类型,这个通用类并不关心,但是我们又要给一个类型限定,此时就可以定义一个带泛型的类。具体是什么类型,由使用者传递。

一般我们在定义泛型的时候,通常使用T(Type)、E(Element),其实你使用什么字母无所谓

泛型类:即在类上加泛型约束

①可以为任意类型

public class A<T> {
    public T t;

    public A(T t){
        this.t = t;
    }

    public static void main(String[] args) {
        String a = "aaa";
       A<String> b= new A<>(a);
       b.t = "aaaaaa";

        String a1 = b.getA(0);
        System.out.println(a1);

       // b.t = 10;
    }
}

②限定为某种类型或其子类

class B<T extends Stu>{

}
  1. 泛型方法

    public T getA(int index){
        return t;
    }
    
  2. 方法参数(成员变量、形参)

    public T t;
    
    public A(T t){
        this.t = t;
    }
    

2.1.7 集合的交并差等操作

public class TestArrayList {
    public static void main(String[] args) {
        List list1 = new ArrayList();
        for (int i = 1; i < 9; i++) {
            list1.add(i);
        }

        List list2 = new ArrayList();
        for (int i = 3; i < 14; i++) {
            list2.add(i);
        }

        //交集
        //list1.retainAll(list2);   //交集,交完之后,返回一个list,给调用者
        //list2.retainAll(list1);
       // System.out.println(list1);
        //并集
        //list1.addAll(list2);
       // System.out.println(list1);

        //差集
       // list1.removeAll(list2);
       // System.out.println(list1);

        //去重并集
        list1.removeAll(list2);
        list1.addAll(list2);
        System.out.println(list1);
    }
}

2.1.8 Stream 的使用

明确:使用Stream对象,应该先获取到Stream对象

要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

支持:链式调用(每个方法的返回值都是Stream对象)

很多方法,返回结果是Stream对象,我们称为中间操作【只会存储中间计算过程,并不会出结果,所以我们需要一些聚合操作【保存在集合中】或输出【打印出来】】

获取方式:

  1. Stream<Stu> stream = list.stream();
  2. 通过Stream.of():Stream<int[]> aa1 = Stream.of(aa);
package test05;

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author azzhu
 * @create 2020-04-23 13:38:27
 */
public class TestStream {
    public static void main(String[] args) {
        List<Stu> list = new ArrayList<>();

        list.add(new Stu("zs",1001,90));
        list.add(new Stu("zs2",1002,89));
        list.add(new Stu("zs3",1003,82));
        list.add(new Stu("zs4",1004,88));
        list.add(new Stu("zs5",1005,100));

        //求集合中的分数最大值、最小值、均值、平均分、分数在90-100之间的人数
        //select max(score) from stu;
        //遍历 stream流
        //1.需要将list包装成stream
        Stream<Stu> stream = list.stream();
        //2.可以在流中做各种中间操作,最后将操作的结果打印或者保存到另外一个地方
        //2.1 变换结构,比如为每个人的分数 * 2  map():变换结构
        //stream.map(stu -> stu.score*2).forEach(stu -> System.out.println(stu) );
       // stream.map(stu -> stu.score*2).forEach(System.out::println);
        //2.2 先过滤出分数在80-90之间的人,然后分数 * 2,然后求最大值 filter max
//        Integer max = stream.filter(stu -> stu.score > 80 && stu.score < 90)
//                .map(stu -> stu.score * 2)
//                .max(Comparator.comparingInt(Integer::intValue))
//                .get();
//        System.out.println(max);

        //2.3 需求:按照分数降序排列,取前3
//       stream.sorted((s1,s2) -> s2.score - s1.score)
//               .limit(3)
//               .forEach(System.out::println);

        //2.4 一次性获取到 最大值、最小值、均值、平均分
        //select max(score),min(score),sum(score),avg(score),count(*) from stu;
//        IntSummaryStatistics statistics = stream
//                .filter(stu -> stu.score > 85)
//                .mapToInt((x) -> x.score)
//                .summaryStatistics();
//        System.out.println("最大值:"+statistics.getMax());
//        System.out.println("最小值:"+statistics.getMin());
//        System.out.println("总分:"+statistics.getSum());
//        System.out.println("平均分:"+statistics.getAverage());
//        System.out.println("总人数:"+statistics.getCount());

        //其他方法  reduce  count distinct collect  findFirst  flatMap
        //System.out.println(stream.count());
       // stream.distinct()
        //collect 将stream执行完的中间结果保存起来,以便复用
        //List<Stu> newList = stream.filter(stu -> stu.score > 85).collect(Collectors.toList());
       // System.out.println(newList);
        List<List<String>> lists = Arrays.asList(Arrays.asList("Jordan"),
                Arrays.asList("Kobe","James"),Arrays.asList("James","Curry")
        );
        System.out.println(lists);  //[[Jordan], [Kobe, James], [Durant, Curry]] -> [s,s,s,s]
        //扁平化:即可以将list炸平
       // Stream<String> streamFlatmap = lists.stream().flatMap(l -> l.stream());
       // streamFlatmap.forEach(System.out::println);

        //TODO :上面的 lists,能否实现单词统计 James-2、Jordan-1
        //大体思路:先flatMap,在map变换结构、James-1、James-1,如何根据James-1 去分组,求个数


      //  lists.stream().forEach(System.out::println);

        //常用的函数式接口
        //定义:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
        //需要掌握如下4大函数式接口
        //1.Function<T,R>  接受一个输入参数,返回一个结果。
        //比如map(Function)、flatMap(Function)。。。。,一般用于变换结构

        //2.Consumer<T>:消费型函数式接口  代表了接受一个输入参数并且无返回的操作
        //foreach(Consumer<T>)

        //3.Supplier<T>:无参数,返回一个结果。


        //4.Predicate<T>:接受一个输入参数,返回一个布尔值结果
        //filter(),一般跟条件相关

    }
}

2.1.9 Lambda中常用的函数式接口

@FunctionalInterface

四大函数式接口

1.Consumer<T>:消费型接口
     void accept(T t);
2.Supplier<T>:供给型接口
     T get();
3.Predicate<T>:接受一个输入参数,返回一个布尔值结果
    boolean test(T t);
4.Function:功能型接口
     R apply(T t);
//=============其他应用也蛮多的
1.Comparator<T> :用于比较、排序
    int compare(T o1, T o2);
	boolean equals(Object obj);

方法引用参照:https://www.runoob.com/java/java8-method-references.html

2.1.10 方法引用

目的:简化方法调用

package test05;

import java.util.function.Supplier;

class Car {
    String color;
    //Supplier是jdk1.8的接口,这里和lambda一起使用了
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }

    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }

    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }

    public void repair() {
        System.out.println("Repaired " + this.toString());
    }

    @Override
    public String toString() {
        return "Car{" +
                "color='" + color + '\'' +
                '}';
    }
}
public class TestMethodReference {
    public static void main(String[] args) {
        //1.构造方法引用
        final Car car = Car.create( Car::new );
        final List< Car > cars = Arrays.asList( car );
        car.repair();

        //2.静态方法引用
        cars.forEach(Car::collide);

       //3. 特定类的任意对象的方法引用:它的语法是Class::method实例如下:
        cars.forEach( Car::repair );

        //4.特定对象的方法引用:它的语法是instance::method实例如下:
        cars.forEach( car::follow );
    }
}

2.1.11 Optional 类

Optional 类的引入很好的解决空指针异常。

public class TestOptional {
    public static void main(String[] args) {
        List<Stu> list = new ArrayList<>();
        list.add(new Stu("zs",1001,90));
        list.add(new Stu("zs2",1002,89));

       Stu stu1 =  new Stu("zs",1001,90);
       stu1 = null;
        Optional<Stu> optional = Optional.ofNullable(stu1);
       // List<Stu> stus = optional.get();

        //System.out.println(stus);
      //  optional.orElseGet(() -> new Stu("zs",1001,90));
      // System.out.println(optional.get());
        System.out.println(optional.orElseGet(() -> new Stu("zs", 1002, 90)));
    }
}

2.1.12 静态导入

静态导入:如果本类中有和静态导入的同名方法会优先使用本类的
如果还想使用静态导入的,依然需要类名来调用

/*
* JDK1.5新特性,静态导入
* 减少开发的代码量
* 标准的写法,导入包的时候才能使用
* import static java.lang.System.out;最末尾,必须是一个静态成员
*/
import static java.lang.System.out;
import static java.util.Arrays.sort;
public class StaticImportDemo {
    public static void main(String[] args) {
        out.println("hello");

        int[] arr = {1,4,2};
        sort(arr);
    }
}

2.2 LinkedList

存储结构:通过双向链表结构进行维护。通过一个静态内部类Node进行维护的。对于删除和插入的效率比较高【直接修改元素记录的地址值即可,不要大量移动元素】

每次查询都要从链头或链尾找起,查询相对数组较慢

链表:单向链表和双向链表(手拉手)----自补数据结构

LinkedList的索引决定是从链头开始找还是从链尾开始找

如果该元素小于元素长度一半,从链头开始找起,如果大于元素长度的一半,则从链尾找起

在这里插入图片描述

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可

  • 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。

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。

public class Demo04LinkedList {
	public static void main(String[] args) {
		method4();
	}
	/*
	 *  void push(E e): 压入。把元素添加到集合的第一个位置。
	 *  E pop(): 弹出。把第一个元素删除,然后返回这个元素。
	 */
	public static void method4() {
		//创建LinkedList对象
		LinkedList<String> list = new LinkedList<>();
		//添加元素
		list.add("达尔文");
		list.add("达芬奇");
		list.add("达尔优");
		System.out.println("list:" + list);
		//调用push在集合的第一个位置添加元素
		//list.push("爱迪生");
		//System.out.println("list:" + list);//[爱迪生, 达尔文, 达芬奇, 达尔优]
		
		//E pop(): 弹出。把第一个元素删除,然后返回这个元素。
		String value = list.pop();
		System.out.println("value:" + value);//达尔文
		System.out.println("list:" + list);//[达芬奇,达尔优]
	}
	
	/*
	 * E removeFirst():删除第一个元素
	 * E removeLast():删除最后一个元素。
	 */
	public static void method3() {
		//创建LinkedList对象
		LinkedList<String> list = new LinkedList<>();
		//添加元素
		list.add("达尔文");
		list.add("达芬奇");
		list.add("达尔优");
		//删除集合的第一个元素
//		String value = list.removeFirst();
//		System.out.println("value:" + value);//达尔文
//		System.out.println("list:" + list);//[达芬奇,达尔优]
		
		//删除最后一个元素
		String value = list.removeLast();
		System.out.println("value:" + value);//达尔优
		System.out.println("list:" + list);//[达尔文, 达芬奇]
	}
	
	/*
	 * E getFirst(): 获取集合中的第一个元素
	 * E getLast(): 获取集合中的最后一个元素
	 */
	public static void method2() {
		//创建LinkedList对象
		LinkedList<String> list = new LinkedList<>();
		//添加元素
		list.add("达尔文");
		list.add("达芬奇");
		list.add("达尔优");
		
		System.out.println("list:" + list);
		//获取集合中的第一个元素
		System.out.println("第一个元素是:" + list.getFirst());
		//获取集合中的最后一个元素怒
		System.out.println("最后一个元素是:" + list.getLast());
	} 
	
	
	/*
	 * void addFirst(E e): 在集合的开头位置添加元素。
	 * void addLast(E e): 在集合的尾部添加元素。
	 */
	public static void method1() {
		//创建LinkedList对象
		LinkedList<String> list = new LinkedList<>();
		//添加元素
		list.add("达尔文");
		list.add("达芬奇");
		list.add("达尔优");
		//打印这个集合
		System.out.println("list:" + list);//[达尔文, 达芬奇, 达尔优]
		//调用addFirst添加元素
		list.addFirst("曹操");
		System.out.println("list:" + list);//[曹操, 达尔文, 达芬奇, 达尔优]
		//调用addLast方法添加元素
		list.addLast("大乔");
		System.out.println("list:" + list);//[曹操, 达尔文, 达芬奇, 达尔优, 大乔]
		
	}
}

静态内部类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;
    }
}

2.3 Vector

特点

  • Vector集合数据存储的结构是数组结构,为JDK中最早提供的集合,它是线程同步
  • Vector中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早期的迭代器。
  • 此接口Enumeration的功能与 Iterator 接口的功能是类似的。
  • Vector集合已被ArrayList替代。枚举Enumeration已被迭代器Iterator替代。

3.Set接口

数据存放是无序的,不可重复

HashSet+TreeSet

3.1 HashSet

3.1.1 简介

存储结构就是HashMap

    public HashSet() {
        map = new HashMap<>();
    }

HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存储和查找性能。保证元素唯一性的方式依赖于:hashCodeequals方法。

public class TestHashSet {
    public static void main(String[] args) {
        Set<Stu> set = new HashSet<>();
        Stu zs = new Stu("zs", 80);
        Stu zz = zs;
        set.add(zs);
        set.add(zz);
        set.add(new Stu("zs2",100));
        set.add(new Stu("zs3",90));

        //遍历set
        for (Stu stu : set) {
            System.out.println(stu);
        }

        Iterator<Stu> it = set.iterator();
        while (it.hasNext()){
            Stu stu = it.next();
        }
    }
}

3.1.2 存储数据的结构(哈希表)

什么是哈希表呢?

JDK1.8之前,哈希表底层采用数组+链表实现,即使用数组处理冲突,同一hash值的链表都存储在一个数组里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

在这里插入图片描述

看到这张图就有人要问了,这个是怎么存储的呢?

为了方便大家的理解我们结合一个存储流程图来说明一下:

在这里插入图片描述

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

3.1.3 存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一.

创建自定义Student类:

public class Student {
    private String name;
    private int age;

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

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

创建测试类:

public class HashSetDemo2 {
    public static void main(String[] args) {
        //创建集合对象   该集合中存储 Student类型对象
        HashSet<Student> stuSet = new HashSet<Student>();
        //存储 
        Student stu = new Student("于谦", 43);
        stuSet.add(stu);
        stuSet.add(new Student("郭德纲", 44));
        stuSet.add(new Student("于谦", 43));
        stuSet.add(new Student("郭麒麟", 23));
        stuSet.add(stu);

        for (Student stu2 : stuSet) {
            System.out.println(stu2);
        }
    }
}
执行结果:
Student [name=郭德纲, age=44]
Student [name=于谦, age=43]
Student [name=郭麒麟, age=23]

3.2 TreeSet

TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树的实现,其特点为:

  1. 元素唯一
  2. 元素没有索引
  3. 使用元素的自然顺序对元素进行排序,或者根据创建 TreeSet 时提供的 Comparator 比较器
    进行排序,具体取决于使用的构造方法:
public TreeSet():								根据其元素的自然排序进行排序
public TreeSet(Comparator<E> comparator):    根据指定的比较器进行排序

可以排序,前提是你的类型需要实现Comparable。否则抛出:ClassCastException

自然排序:

(20,18,23,22,17,24,19)

public static void main(String[] args) {
	//无参构造,默认使用元素的自然顺序进行排序
	TreeSet<Integer> set = new TreeSet<Integer>();
	set.add(20);
	set.add(18);
  	set.add(23);
  	set.add(22);
  	set.add(17);
  	set.add(24);
  	set.add(19);
  	System.out.println(set);
}

控制台的输出结果为:
[17, 18, 19, 20, 22, 23, 24]

比较器排序:

public class Stu implements Comparable<Stu> {
    @Override
    public int compareTo(Stu o) {
        //二级排序:先按照分数降序,若分数一样,按照名字升序排序
        if(this.getScore() - o.getScore() == 0) {
            return -o.getName().compareTo(this.getName());
        }
        return o.getScore() - this.getScore();
    }
}
public class TestTreeSet {
    public static void main(String[] args) {
        Set<Stu> set = new TreeSet<>();
        Stu zs = new Stu("zs", 80);
        Stu zs2 = new Stu("ls", 80);
        Stu zs3 = new Stu("bs", 80);
        set.add(zs);
        set.add(zs2);
        set.add(zs3);
        set.add(new Stu("zs2",100));
        set.add(new Stu("zs3",90));

        //遍历set
        for (Stu stu : set) {
            System.out.println(stu);
        }

        Iterator<Stu> it = set.iterator();
        while (it.hasNext()){
            Stu stu = it.next();
        }
    }
}

需求:去重list中的元素,比如list中的值{1,3,4,5,3,4}====》{1,3,4,5}

3.3 LinkedHashSet

/*
*   LinkedHashSet 基于链表的哈希表实现
*   继承自HashSet
*       LinkedHashSet 自身特性,具有顺序,存储和取出的顺序相同的
*   	线程不安全的集合,运行速度块
*/
 public class LinkedHashSetDemo {
   public static void main(String[] args) {
    LinkedHashSet<Integer> link = new LinkedHashSet<Integer>();
    link.add(123);
    link.add(44);
    link.add(33);
    link.add(33);
    link.add(66);
    link.add(11);
    System.out.println(link);
  }
}

4.Map接口

4.1 概述

现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map接口。

我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图。

在这里插入图片描述

  • Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
  • Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
  • Collection中的集合称为单列集合,Map中的集合称为双列集合。
  • 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。

常用的HashMap+线程安全类ConcurrentHashMap

存储是kv结构,键值对结果,电话本为例

13799999 张三人

4.2 Map的常用子类

通过查看Map接口描述,看到Map有多个子类,这里我们主要讲解常用的HashMap集合、LinkedHashMap集合。

  • HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
  • LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
  • TreeMap<K,V>:TreeMap集合和Map相比没有特有的功能,底层的数据结构是红黑树;可以对元素的**进行排序,排序方式有两种:自然排序比较器排序

tips:Map接口中的集合都有两个泛型变量<K,V>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量<K,V>的数据类型可以相同,也可以不同。

4.3 HashMap相关面试

源码分析:https://blog.csdn.net/fourth1/article/details/105431691

【面试】hashMap的相关面试

1. HashMap的存储结构在 JDK 1.8 中它都做了哪些优化 Node[]数组
    JDK7:数组+链表;JDK8:数组++红黑树【链表大于 8 并且容量大于 64 时,会将链表转为红黑树】
    static final int TREEIFY_THRESHOLD = 8;
	static final int MIN_TREEIFY_CAPACITY = 64;
    // 转换链表的临界值,当元素小于此值时,会将红黑树结构转换成链表结构
    static final int UNTREEIFY_THRESHOLD = 6;
    红黑树有啥特点?脑补
2. HashMap没有给初始容量,默认为多少?
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
3.最大值为多少1073741824
    static final int MAXIMUM_CAPACITY = 1 << 30
4. 加载因子:扩容的阈值
    ①值是多少
     /**
     * The load factor used when none specified in constructor.
     */
     static final float DEFAULT_LOAD_FACTOR = 0.75f;
	②为什么是0.75,而不是别的值?出于容量和性能之间平衡的结果
	当加载因子设置比较大的时候,扩容的门槛就被提高了,扩容发生的频率比较低,占用的空间会比较小,但此时发生 Hash 冲突的几率就会提升,因此需要更复杂的数据结构来存储元素,这样对元素的操作时间就会增加,运行效率也会因此降低;
	而当加载因子值比较小的时候,扩容的门槛会比较低,因此会占用更多的空间,此时元素的存储就比较稀疏,发生哈希冲突的可能性就比较小,因此操作性能会比较高。
   所以综合了以上情况就取了一个 0.51.0 的平均数 0.75 作为加载因子。
        
5.什么时候 链表 转红黑树?
   链表大于 8 并且容量大于 64 时,会将链表转为红黑树
6.为什么是2的幂
  即使你在构造函数时,不传2的n次方,在后来的初始化方法中,也会强制变成2的n次方
  让元素能够快速定位哈希桶;让Hash表元素分布均匀,减少哈希碰撞的几率  
7.若传入了一个初始化容量,则就是你传入的那个值吗?不一定
  是大于或等于你传入的那个值的,离它最近的那个2的幂的数
8.put方法的流程
   index :(table.length-1) & hash值
9.Node[]数组
       Node的属性有哪些,分别干啥用的
10.get方法
     三种情况:直接数组中命中;需要在树中找;需要在链表中找  
11.扩容相关
   1)扩容原因
      a.为了解决哈希冲突导致的链化影响查询效率的问题,扩容会缓解该问题
      b.容量不够也要扩容
   2)扩容多大
      a.若原来Node[]就是最大值,不扩
      b.oldCap左移一位实现数据翻倍,并且赋值给newCap,newCap 小于数组最大值限制 且扩容之前的阈值 >= 16
 12.初始化容量是一上来就初始化,还是put时候才初始化?put时候才初始化      

在这里插入图片描述

4.4 HashMap常用方法和遍历方式

Map接口中定义了很多方法,常用的如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
  • public boolean containKey(Object key):判断该集合中是否有此键。
  1. key重复,会覆盖
  2. key、value都可以为null
  3. 直接输出的k=v的结构
public class TestHashMap {
    public static void main(String[] args) {
        Map<String,Stu> map = new HashMap<>();
        //常用方法
        //1.put(k,v)
        map.put("1001",new Stu("zs",10));
        map.put("1002",new Stu("zs2",10));
        //2.get(k)
        Stu stu = map.get("1001");
        System.out.println(stu);
        //3.isEmpty():判断是否为空
        boolean empty = map.isEmpty();
        //4.获取长度
        System.out.println(map.size());
        System.out.println(map);
        //5.是否包含某个key
        System.out.println(map.containsKey("1001"));
        //6.遍历
        //6.1.获取键集
        Set<String> set = map.keySet();
        for (String key : set) {
            System.out.println(key+":"+map.get(key));
        }
        System.out.println("===========");
        //6.2.获取值集,使用的不是很多
        Collection<Stu> values = map.values();
        values.forEach(System.out::println);
        System.out.println("==================");
        //6.3.获取EntrySet,即kv对
        Set<Map.Entry<String, Stu>> entries = map.entrySet();
        entries.forEach(s -> System.out.println(s.getKey()+":"+s.getValue()));

        //7.不太常用的其他方法
        //map.remove()
        //map.clear();
        //.out.println(map.get("11111"));
        //有则返回,没有则返回一个默认值
        //System.out.println(map.getOrDefault("1001", new Stu("ss", 1)));
        map.replace("1001",new Stu("sdfdsfdsf",12));
        System.out.println(map.get("1001"));
    }
}

遍历方式1:先获取键集

在这里插入图片描述

遍历方式2:获取Entry

在这里插入图片描述

4.5 HashMap存储自定义类型

练习:每位学生(姓名,年龄)都有自己的家庭住址。那么,既然有对应关系,则将学生对象和家庭住址存储到map集合中。学生作为键, 家庭住址作为值。

注意,学生姓名相同并且年龄相同视为同一名学生。

编写学生类:

public class Student {
    private String name;
    private int age;

    //构造方法
    //get/set
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

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

编写测试类:

public class HashMapTest {
    public static void main(String[] args) {
        //1,创建Hashmap集合对象。
        Map<Student,String> map = new HashMap<Student,String>();
        //2,添加元素。
        map.put(new Student("lisi",28), "上海");
        map.put(new Student("wangwu",22), "北京");
        map.put(new Student("wangwu",22), "南京");
        
        //3,取出元素。键找值方式
        Set<Student> keySet = map.keySet();
        for(Student key: keySet){
            String value = map.get(key);
            System.out.println(key.toString()+"....."+value);
        }
    }
}
  • 当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法(如果忘记,请回顾HashSet存放自定义对象)。
  • 如果要保证map中存放的key和取出的顺序一致,可以使用java.util.LinkedHashMap集合来存放。

4.6 LinkedHashMap

我们知道HashMap保证成对元素唯一,并且查询速度很快,可是成对元素存放进去是没有顺序的,那么我们要保证有序,还要速度快怎么办呢?

在HashMap下面有一个子类LinkedHashMap,它是链表和哈希表组合的一个数据存储结构。

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        map.put("邓超", "孙俪");
        map.put("李晨", "范冰冰");
        map.put("刘德华", "朱丽倩");
        Set<Entry<String, String>> entrySet = map.entrySet();
        for (Entry<String, String> entry : entrySet) {
            System.out.println(entry.getKey() + "  " + entry.getValue());
        }
    }
}

结果:

邓超  孙俪
李晨  范冰冰
刘德华  朱丽倩

4.7 TreeMap

TreeMap集合和Map相比没有特有的功能,底层的数据结构是红黑树;可以对元素的**进行排序,排序方式有两种:自然排序比较器排序;到时使用的是哪种排序,取决于我们在创建对象的时候所使用的构造方法;

public TreeMap()									使用自然排序
public TreeMap(Comparator<? super K> comparator) 	比较器排序

案例演示自然排序

public static void main(String[] args) {
 	TreeMap<Integer, String> map = new TreeMap<Integer, String>();
  	map.put(1,"张三");
  	map.put(4,"赵六");
  	map.put(3,"王五");
  	map.put(6,"酒八");
  	map.put(5,"老七");
  	map.put(2,"李四");
  	System.out.println(map);
}

控制台的输出结果为:
{1=张三, 2=李四, 3=王五, 4=赵六, 5=老七, 6=酒八}

案例演示比较器排序

需求:

  1. 创建一个TreeMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
  2. 要求按照学生的年龄进行升序排序,如果年龄相同,比较姓名的首字母升序, 如果年龄和姓名都是相同,认为是同一个元素;

实现:

为了保证age和name相同的对象是同一个,Student类必须重写hashCode和equals方法

public class Student {
    private int age;
    private String name;
	//省略get/set..
    public Student() {}
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }
}
public static void main(String[] args) {
  	TreeMap<Student, String> map = new TreeMap<Student, String>(new Comparator<Student>() {
    	@Override
    	public int compare(Student o1, Student o2) {
      		//先按照年龄升序
      		int result = o1.getAge() - o2.getAge();
      		if (result == 0) {
        		//年龄相同,则按照名字的首字母升序
        		return o1.getName().charAt(0) - o2.getName().charAt(0);
      		} else {
        		//年龄不同,直接返回结果
        		return result;
      		}
    	}
  	});
  	map.put(new Student(30, "jack"), "深圳");
  	map.put(new Student(10, "rose"), "北京");
  	map.put(new Student(20, "tom"), "上海");
  	map.put(new Student(10, "marry"), "南京");
  	map.put(new Student(30, "lucy"), "广州");
  	System.out.println(map);
}
控制台的输出结果为:
{
  Student{age=10, name='marry'}=南京, 
  Student{age=10, name='rose'}=北京, 
  Student{age=20, name='tom'}=上海, 
  Student{age=30, name='jack'}=深圳, 
  Student{age=30, name='lucy'}=广州
}

4.8 Map练习

6.8.1 明星夫妻

Map集合中包含5对元素: “邓超”->“孙俪”, “李晨”->“范冰冰”, “刘德华”->“柳岩”, “黄晓明”->” Baby”,“谢霆锋”->”张柏芝”。

要求如下:

  1. 创建HashMap
  2. 使用put方法添加元素
  3. 使用keySet方法获取所有的键
  4. 获取到keySet的迭代器
  5. 循环判断迭代器是否有下一个元素
  6. 使用迭代器next方法获取到一个键
  7. 通过一个键找到一个值
  8. 输出键和值

4.8.2 玩转水浒

已知Map中保存如下信息:{“及时雨”=”宋江”, “玉麒麟”=”卢俊义”, “智多星”=”吴用”}
其中键表示水浒中人物的外号,value表示人物的姓名.

  1. 往Map中添加“入云龙”=”公孙胜”, ”豹子头”=”林冲”两位好汉
  2. 删除“玉麒麟”=”卢俊义”
  3. 将key为“智多星”的value修改为null,
  4. 将“及时雨”=”宋江”,修改为”呼保义”=” 宋江”

4.8.3 统计字符出现次数

需求:

输入一个字符串中每个字符出现次数。

分析:

  1. 获取一个字符串对象
  2. 创建一个Map集合,键代表字符,值代表次数。
  3. 遍历字符串得到每个字符。
  4. 判断Map中是否有该键。
  5. 如果没有,第一次出现,存储次数为1;如果有,则说明已经出现过,获取到对应的值进行++,再次存储。
  6. 打印最终结果

方法介绍

public boolean containKey(Object key):判断该集合中是否有此键。

代码:

public class MapTest {
public static void main(String[] args) {
        //友情提示
        System.out.println("请录入一个字符串:");
        String line = new Scanner(System.in).nextLine();
        // 定义 每个字符出现次数的方法
        findChar(line);
    }
    private static void findChar(String line) {
        //1:创建一个集合 存储  字符 以及其出现的次数
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        //2:遍历字符串
        for (int i = 0; i < line.length(); i++) {
            char c = line.charAt(i);
            //判断 该字符 是否在键集中
            if (!map.containsKey(c)) {//说明这个字符没有出现过
                //那就是第一次
                map.put(c, 1);
            } else {
                //先获取之前的次数
                Integer count = map.get(c);
                //count++;
                //再次存入  更新
                map.put(c, ++count);
            }
        }
        System.out.println(map);
    }
}

4.9 Hashtable

    /*
	    *  Map接口实现类 Hashtable
	    *  底层数据结果哈希表,特点和HashMap是一样的
	    *  Hashtable 线程安全集合,运行速度慢
	    *  HashMap 线程不安全的集合,运行速度快
	    *  
	    *  Hashtable命运和Vector是一样的,从JDK1.2开始,被更先进的HashMap取代
	    *  
	    *  HashMap 允许存储null值,null键
	    *  Hashtable 不允许存储null值,null键
	    *  
	    *  Hashtable他的孩子,子类 Properties 依然活跃在开发舞台
	    */
public class HashtableDemo {
    public static void main(String[] args) {	
        Map<String,String> map = new Hashtable<String,String>();
        map.put(null, null);
        System.out.println(map);
    }
}

5.Collections工具类

【面试】说一说Collections【类】 vs Collection【接口】

  • java.utils.Collections是集合工具类,用来对集合进行操作。

    常用方法如下:

  • public static void shuffle(List<?> list):打乱集合顺序。

  • public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。

  • public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。

重点掌握:sort(2参)

/**
 * 工具类的使用
 * @author azzhu
 * @create 2019-12-20 16:27:16
 */
public class TestCollections {
    public static void main(String[] args) {
        List<Stu> stus = new ArrayList<>();
        Stu s1 = new Stu("zs",222);
        stus.add(s1);
        stus.add(new Stu("ls",22));
        stus.add(new Stu("zz",2542));
        stus.add(new Stu("ww",24552));
        //排序对象需要实现Comparable
        //Collections.sort(stus);
        //使用lambda,Stu不需要实现任何接口
        Collections.sort(stus,(s3,s2)->{
            //可以定义多个排序规则,自己补全
            int result = s3.getScore()-s2.getScore();
            if(result==0) {
                return 1;
            }
                return result;
        });
        System.out.println(stus);

        //集合的反转
        //Collections.reverse(stus);
        stus.forEach(System.out::println);

        //Collections.binarySearch(stus,s1)
        //int index = Collections.binarySearch(stus, s1, (e1, e2) -> e2.getScore() - e1.getScore());
        //System.out.println(index);

        //Collections.max(stus,null);
        
        Collections.shuffle(stus);	//对List集合中的元素,进行随机排列,类似洗牌
    }
}

6.Map集合的嵌套

6.1 需求

Map集合的嵌套,Map中存储的还是Map集合

/*
 *  要求:
 *    艾瑞教育  
 *      JavaEE班
 *        001  张三
 *        002  李四
 *      
 *      大数据班
 *        001  王五
 *        002  赵六
 *  对以上数据进行对象的存储
 *   001 张三  键值对
 *   JavaEE班: 存储学号和姓名的键值对
 *   大数据班:
 
 *   艾瑞教育: 存储的是班级
 *   
 *   JavaEE班Map   <学号,姓名>
 *   艾瑞教育Map  <班级名字, JavaEE班Map>
 */

public class MapMapDemo {
    public static void main(String[] args) {
        //定义JavaEE班集合
        HashMap<String, String> javaee = new HashMap<String, String>();
        //定义大数据班集合
        HashMap<String, String> bigdata = new HashMap<String, String>();
        //向班级集合中,存储学生信息
        javaee.put("001", "张三");
        javaee.put("002", "李四");
        bigdata.put("001", "王五");
        bigdata.put("002", "赵六");
        //定义艾瑞教育集合容器,键是班级名字,值是两个班级容器
        HashMap<String, HashMap<String,String>> arjy =
            new HashMap<String, HashMap<String,String>>();
        arjy.put("JavaEE班", javaee);
        arjy.put("大数据班", bigdata);

        keySet(arjy);

    }
} 

6.2 keySet遍历

public static void keySet(HashMap<String,HashMap<String,String>> arjy){
    //调用arjy集合方法keySet将键存储到Set集合
    Set<String> classNameSet = arjy.keySet();
    //迭代Set集合
    Iterator<String> classNameIt = classNameSet.iterator();
    while(classNameIt.hasNext()){
        //classNameIt.next获取出来的是Set集合元素,arjy集合的键
        String classNameKey = classNameIt.next();
        //arjy集合的方法get获取值,值是一个HashMap集合
        HashMap<String,String> classMap = arjy.get(classNameKey);
        //调用classMap集合方法keySet,键存储到Set集合
        Set<String> studentNum = classMap.keySet();
        Iterator<String> studentIt = studentNum.iterator();

        while(studentIt.hasNext()){
            //studentIt.next获取出来的是classMap的键,学号
            String numKey = studentIt.next();
            //调用classMap集合中的get方法获取值
            String nameValue = classMap.get(numKey);
            System.out.println(classNameKey+".."+numKey+".."+nameValue);
        }
    }

    System.out.println("==================================");
    for(String className: arjy.keySet()){
        HashMap<String, String> hashMap = arjy.get(className);	
        for(String numKey : hashMap.keySet()){
            String nameValue = hashMap.get(numKey);
            System.out.println(className+".."+numKey+".."+nameValue);
        }
    }
}

6.3 entrySet遍历

public static void entrySet(HashMap<String,HashMap<String,String>> arjy){
    //调用arjy集合方法entrySet方法,将arjy集合的键值对关系对象,存储到Set集合
    Set<Map.Entry<String, HashMap<String,String>>> 
        classNameSet = arjy.entrySet();
    //迭代器迭代Set集合
    Iterator<Map.Entry<String, HashMap<String,String>>> classNameIt = classNameSet.iterator();
    while(classNameIt.hasNext()){
        //classNameIt.next方法,取出的是arjy集合的键值对关系对象
        Map.Entry<String, HashMap<String,String>> classNameEntry =  classNameIt.next();
        //classNameEntry方法 getKey,getValue
        String classNameKey = classNameEntry.getKey();
        //获取值,值是一个Map集合
        HashMap<String,String> classMap = classNameEntry.getValue();
        //调用班级集合classMap方法entrySet,键值对关系对象存储Set集合
        Set<Map.Entry<String, String>> studentSet = classMap.entrySet();
        //迭代Set集合
        Iterator<Map.Entry<String, String>> studentIt = studentSet.iterator();
        while(studentIt.hasNext()){
            //studentIt方法next获取出的是班级集合的键值对关系对象
            Map.Entry<String, String> studentEntry = studentIt.next();
            //studentEntry方法 getKey getValue
            String numKey = studentEntry.getKey();
            String nameValue = studentEntry.getValue();
            System.out.println(classNameKey+".."+numKey+".."+nameValue);
        }
    }
    System.out.println("==================================");

    for (Map.Entry<String, HashMap<String, String>> me : arjy.entrySet()) {
        String classNameKey = me.getKey();
        HashMap<String, String> numNameMapValue = me.getValue();
        for (Map.Entry<String, String> nameMapEntry : numNameMapValue.entrySet()) {
            String numKey = nameMapEntry.getKey();
            String nameValue = nameMapEntry.getValue();
            System.out.println(classNameKey + ".." + numKey + ".." + nameValue);
        }
    }
}

7.综合案例-斗地主

7.1 案例简介

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

在这里插入图片描述

具体规则:

  1. 组装54张扑克牌
  2. 54张牌顺序打乱
  3. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
  4. 查看三人各自手中的牌(按照牌的大小排序)、底牌

规则:手中扑克牌从大到小的摆放顺序:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3

7.2 功能分析

1.准备牌:

完成数字与纸牌的映射关系:

使用双列Map(HashMap)集合,完成一个数字与字符串纸牌的对应关系(相当于一个字典)。

2.洗牌:

通过数字完成洗牌发牌

3.发牌:

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

存放的过程中要求数字大小与斗地主规则的大小对应。

将代表不同纸牌的数字分配给不同的玩家与底牌。

4.看牌:

通过Map集合找到对应字符展示。

通过查询纸牌与数字的对应关系,由数字转成纸牌字符串再进行展示。

在这里插入图片描述

7.3 功能实现

/*
*  实现模拟斗地主的功能
*   1. 组合牌
*   2. 洗牌
*   3. 发牌
*   4. 看牌
*/
public class DouDiZhu {
    public static void main(String[] args) {
        //1. 组合牌
        //创建Map集合,键是编号,值是牌
        HashMap<Integer,String> pooker = new HashMap<Integer, String>();
        //创建List集合,存储编号
        ArrayList<Integer> pookerNumber = new ArrayList<Integer>();
        //定义出13个点数的数组
        String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
        //定义4个花色数组
        String[] colors = {"♠","♥","♣","♦"};
        //定义整数变量,作为键出现
        int index = 2;
        //遍历数组,花色+点数的组合,存储到Map集合
        for(String number : numbers){
            for(String color : colors){
                pooker.put(index, color+number);
                pookerNumber.add(index);
                index++;
            }
        }
        //存储大王,和小王,索引是从0~54,对应大王,小王,...3(牌的顺序从大到小)
        pooker.put(0, "大王");
        pookerNumber.add(0);
        pooker.put(1, "小王");
        pookerNumber.add(1);
        
        //2.洗牌,将牌的编号打乱
  		Collections.shuffle(pookerNumber);
        
        //发牌功能,将牌编号,发给玩家集合,底牌集合
		ArrayList<Integer> player1 = new ArrayList<Integer>();
		ArrayList<Integer> player2 = new ArrayList<Integer>();
		ArrayList<Integer> player3 = new ArrayList<Integer>();
		ArrayList<Integer> bottom = new ArrayList<Integer>();
		
		//3.发牌采用的是集合索引%3
		for(int i = 0 ; i < pookerNumber.size() ; i++){
			//先将底牌做好
			if(i < 3){
				//存到底牌去
				bottom.add( pookerNumber.get(i));
			   //对索引%3判断
			}else if(i % 3 == 0){
				//索引上的编号,发给玩家1
				player1.add( pookerNumber.get(i) );
			}else if( i % 3 == 1){
				//索引上的编号,发给玩家2
				player2.add( pookerNumber.get(i) );
			}else if( i % 3 == 2){
				//索引上的编号,发给玩家3
				player3.add( pookerNumber.get(i) );
			}
		}
        
        //对玩家手中的编号排序
 		Collections.sort(player1);
 		Collections.sort(player2);
 		Collections.sort(player3);
 		//看牌,将玩家手中的编号,到Map集合中查找,根据键找值
 		//定义方法实现
 		look("刘德华",player1,pooker);
 		look("张曼玉",player2,pooker);
 		look("林青霞",player3,pooker);
 		look("底牌",bottom,pooker);
    }
    
     	public static void look(String name,ArrayList<Integer> player,HashMap<Integer,String> pooker){
 		//遍历ArrayList集合,获取元素,作为键,到集合Map中找值
 		System.out.print(name+" ");
 		for(Integer key : player){
 			String value = pooker.get(key);
 			System.out.print(value+" ");
 		}
 		System.out.println();
 	}
}

8.练习

8.1 模拟下单

需求如下:

需求1:使用集合完成模拟下单
Order
String id  
List<OrderItem>  orderItems
double totalMoney

OrderItem 
Product Product   
int pCount    

Product
int id 
String name 
double price

1个订单  1个订单下挂1-2个OrderItem 
你应该初始化一批Product,ArrayList<Product>
1.输出订单明细
2.输出订单总钱
【扩展】加一个购物车Cart List<OrderItem>,有选择性的生成订单

集合:添加元素、获取元素、size、遍历

需求2:使用map实现
将订单放入到map<String,Order>中,根据订单编号,查找订单,并输出订单信息;遍历订单

在这里插入图片描述

8.2 找共同好友

8.2.1 数据源

先存放到List

A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
D:A,E,F,L
E:B,C,D,M,L
F:A,B,C,D,E,O,M
G:A,C,D,E,F
H:A,C,D,E,O
I:A,O
J:B,O
K:A,C,D
L:D,E,F
M:E,F,G
O:A,H,I,J

8.2.2 相关知识点

  1. 掌握字符串切割规则

    String str = A:B,C,D,F,E,O
    String[] names = str.split(":");
    String a = "A" = names[0];
    String ss = "B,C,D,F,E,O" = names[1];
    
  2. 掌握数组和list集合之间的互相转换

  3. 知道Arrays类的基本使用

  4. 掌握list和map集合的存储数据特点和基本应用场景

  5. 掌握list和map的遍历

  6. 掌握list的自定义排序

  7. 掌握map的value排序

  8. 理解方法的意义和封装

8.2.3 需求

  1. 获取每个人的好友个数并排序

  2. 获取任意两人的共同好友,A-B:C,D getShareFriends()

  3. 获取所有人两两共同好友,即全部数据

    A-B:C,E,....
    A-C:D,G,....,
    A-D:......
    A-Z:B,D,....
    B-C:    
    

8.24 参考代码

package io;

import javax.sound.midi.Soundbank;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.*;

/**
 * 统计每个人好友个数,并排序
 * @author azzhu
 * @create 2019-12-14 19:33:11
 */
public class TestDemo1 {
    public static void main(String[] args) throws Exception {
        //使用map来存储每个人对应好友个数
        Map<String,Integer> map = new HashMap<>();
        //1.获取数据,从文件中读取
        BufferedReader bfr = new BufferedReader(new FileReader(new File("D:\\IDEA\\mycode\\beike_Java\\src\\io\\xx.txt")));
        String line = null;
        while ((line=bfr.readLine()) != null) {
            //System.out.println(line);
            // A:B,C,D,F,E,O
            //2.切割数据,处理
            String[] split = line.split(":");
            String uid = split[0];
            String[] fs = split[1].split(",");
            //3.将每个人对应的好友个数放在map中
            map.put(uid,fs.length);
        }

        //遍历map
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        for (Map.Entry<String, Integer> entry : entrySet) {
            String uid = entry.getKey();
            Integer length = entry.getValue();
          //  System.out.println(uid+"====>"+length);
        }

        System.out.println("-------------------------");
        // 对map的value排序
        ArrayList<Map.Entry<String, Integer>> list = new ArrayList<>(entrySet);
        Collections.sort(list, (o1, o2) -> o1.getValue()-o2.getValue());

        for (Map.Entry<String, Integer> entry : list) {
            System.out.println(entry);
        }
    }
}
public class FindCommonFriends {

    /**
     * 构建原始数据
     * @return
     */
    public static List<String> getRawData() {
        ArrayList<String> rawData = new ArrayList<>();
        rawData.add("A:B,C,D,F,E,O");
        rawData.add("B:A,C,E,K");
        rawData.add("C:F,A,D,I");
        rawData.add("D:A,E,F,L");
        rawData.add("E:B,C,D,M,L");
        rawData.add("F:A,B,C,D,E,O,M");
        rawData.add("G:A,C,D,E,F");
        rawData.add("H:A,C,D,E,O");
        rawData.add("I:A,O");
        rawData.add("J:B,O");
        rawData.add("K:A,C,D");
        rawData.add("L:D,E,F");
        rawData.add("M:E,F,G");
        rawData.add("O:A,H,I,J");
        return rawData;
    }

    public static Map<String,Integer> getFriendsCount(List<String> list) {
        LinkedHashMap<String,Integer> result = new LinkedHashMap<>();
        Map<String,Integer> map = new HashMap<>();
        for (String line : list) {
            String[] fields = line.split(":");
            map.put(fields[0],fields[1].split(",").length);
        }
        //对map的v进行排序
        map.entrySet().stream().sorted(Comparator.comparing(e -> e.getValue())).forEach(e -> result.put(e.getKey(),e.getValue()));

        return result;
    }

    public static void main(String[] args) {
        System.out.println(getFriendsCount(getRawData()));
    }
}
package io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.*;

/**
 * 获取两个人的共同好友
 * 数据  文件
 *  获取 Map<String,List<String>>
 *  方法的封装  获取一个数据 传递  返回数据
 *
 * @author azzhu
 * @create 2019-12-14 19:44:30
 */
public class TestDemo2 {
    public static void main(String[] args) {
      //  Map<String, List<String>> map = getUserFsInfo();
        getSameFriends("A","B");
    }

    /**
     *
     * @param uid1
     * @param uid2
     * @return
     */
    public static List<String> getSameFriends(String uid1,String uid2) {
        // 获取map
        Map<String, List<String>> map = getUserFsInfo();
        List<String> list1 = map.get(uid1);
        List<String> list2 = map.get(uid2);
        // 获取两个人的共同好友  将两个集合的共同数据存储在前面集合中
        list1.retainAll(list2);
        if(list1 != null && list1.size() > 0) {
            //说明有数据 两个好友有数据
            System.out.println(uid1+"和"+uid2+"的共同好友是:"+list1);
            return list1;
        }
        return null;
    }

    /**
     * 获取存储用户以及用户好友列表的map数据
     * @return
     */
    private static  Map<String,List<String>> getUserFsInfo() {
        Map<String,List<String>> map = new HashMap<>();
        try(BufferedReader bfr = new BufferedReader(new FileReader(new File("D:\\IDEA\\mycode\\beike_Java\\src\\io\\xx.txt")));) {
            String line = null;
            while ((line = bfr.readLine()) != null) {
                String[] split = line.split(":");
                String uid = split[0];
                String fsstr = split[1];
                String[] arr = fsstr.split(",");
                // 将数组 长度 list长度固定,元素不允许修改
                List<String> list = Arrays.asList(arr);
                //创建新的list存储数据
                ArrayList<String> fsList = new ArrayList<>(list);
                map.put(uid,fsList);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }
}
package io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.*;

/**
 * 获取所有人两两共同好友
 * @author azzhu
 * @create 2019-12-14 20:02:03
 */
public class TestDemo3 {
    public static void main(String[] args) {
        Map<String, List<String>> map = getUserFsInfo();
        List<String> list = getAllUsers();
//        for (String str : list) {
//            System.out.println(str);


        //第一个变量到倒数第二个
        for (int i = 0; i < list.size()-1; i++) {
            String uid1 = list.get(i);  // A
            List<String> fs1 = map.get(uid1);
            for (int j = i+1; j < list.size(); j++) {
                String uid2 = list.get(j);          // B C D
                List<String> fs2 = map.get(uid2);
                ArrayList<String> fs = new ArrayList<>(fs2);
                // 交集
                fs.retainAll(fs1);
                if(fs != null && fs.size() > 0) {
                    System.out.println(uid1+"和"+uid2+"的好友是:"+fs);
                }
            }
        }
    }

    private static List<String> getAllUsers() {
        List<String> list = new ArrayList<>();
        // 读取数据将uid 放在list中
        try(BufferedReader bfr = new BufferedReader(new FileReader(new File("D:\\IDEA\\mycode\\beike_Java\\src\\io\\xx.txt")));) {
            String line = null;
            while ((line = bfr.readLine()) != null) {
                String[] split = line.split(":");
                String uid = split[0];
                list.add(uid);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 获取存储用户以及用户好友列表的ma数据
     * @return
     */
    private static Map<String,List<String>> getUserFsInfo() {
        Map<String,List<String>> map = new HashMap<>();
        try(BufferedReader bfr = new BufferedReader(new FileReader(new File("D:\\IDEA\\mycode\\beike_Java\\src\\io\\xx.txt")));) {
            String line = null;
            while ((line = bfr.readLine()) != null) {
                String[] split = line.split(":");
                String uid = split[0];
                String fsstr = split[1];
                String[] arr = fsstr.split(",");
                // 将数组 长度 list长度固定,元素不允许修改
                List<String> list = Arrays.asList(arr);
                //创建新的list存储数据
                ArrayList<String> fsList = new ArrayList<>(list);
                map.put(uid,fsList);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }
}

8.3 完成斗地主

案例中的几个步骤,全部抽取成方法来实现。

以下为扩展功能,可以尝试完成:

  1. 地主谁抢到了 ,可以使用 随机数
  2. 地主是否要底牌,使用键盘输入询问,最多三次,即又回到该地主手里,必须接
  3. 模拟出牌的效果

8.4 Stream的使用

这几个自己测试:flatMap【讲过】、collect、count、distinct、max/min、reduce

8.5 map操作员工

研发部门有5个人,信息如下:(姓名-工资)【柳岩=2100, 张亮=1700, 诸葛亮=1800, 灭绝师太=2600, 东方不败=3800】。

要求:

  1. 定义HashMap,姓名作为key,工资作为value
  2. 使用put方法添加需要的元素
  3. 获取到柳岩的工资
  4. 修改柳岩的工资为当前工资加上300
  5. 使用增强for+keySet迭代出每个员工的工资

9.扩充

9.1 ArrayList,HashSet判断对象是否重复的原因

a:ArrayList的contains方法原理:底层依赖于equals方法
ArrayList的contains方法会使用根据传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。
 此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,
 判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。
 
b:HashSet的add()方法和contains方法()底层都依赖 hashCode()方法与equals方法()
Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
先判断新元素与集合内已经有的旧元素的HashCode值
 如果不同,说明是不同元素,添加到集合。
 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。
所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。

9.2 hashCode和equals的面试题

两个对象  Person  p1 p2
问题: 如果两个对象的哈希值相同 p1.hashCode()==p2.hashCode()
两个对象的equals一定返回true吗  p1.equals(p2) 一定是true吗
正确答案:不一定

如果两个对象的equals方法返回true,p1.equals(p2)==true
两个对象的哈希值一定相同吗
正确答案: 一定

在 Java 应用程序执行期间,
1.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。 
2.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。 
        
两个对象不同(对象属性值不同) equals返回false=====>两个对象调用hashCode()方法哈希值相同
两个对象调用hashCode()方法哈希值不同=====>equals返回true
两个对象不同(对象属性值不同) equals返回false=====>两个对象调用hashCode()方法哈希值不同
两个对象调用hashCode()方法哈希值相同=====>equals返回true

所以说两个对象哈希值无论相同还是不同,equals都可能返回true

10.图书管理系统

10.1 图书管理系统项目演示

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

图书管理系统分析:
1.定义Book类
2.完成主界面和选择
3.完成查询所有图书
4.完成添加图书
5.完成删除图书
6.完成修改图书
7.使用Debug追踪调试

10.2 图书管理系统之标准Book类

在这里插入图片描述

我们发现每一本书都有书名和价格,定义一个Book类表示书籍

public class Book {
    private String name;
    private double price;

    public Book() {
    }

    public Book(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;
    }
}

10.3 图书管理系统之主界面和选择的实现

在这里插入图片描述

主界面的内容其实就是通过打印语句打印出来的.但是要注意因为每个操作过后都会重新回到主界面,所以使用while(true)死循环的方式.

public class BookManager {
    public static void main(String[] args) {
        while (true) {
            //这是学生管理系统的主界面
            System.out.println("--------欢迎来到学生管理系统--------");
            System.out.println("1.查看所有书籍");
            System.out.println("2.添加书");
            System.out.println("3.删除书");
            System.out.println("4.修改书");
            System.out.println("5.退出");
            System.out.println("请输入你的选择:");

            //创建键盘录入对象
            Scanner sc = new Scanner(System.in);
            int num = sc.nextInt();
            switch (num) {
                case 1:
                    // 查看所有书籍
                    break;
                case 2:
                    // 添加书籍
                    break;
                case 3:
                    // 删除书
                    break;
                case 4:
                    // 修改书
                    break;
                case 5:
                    // 退出
                    break;
                default:
                    System.out.println("输入错误,请重新输入");
                    break;
            }
        }
    }
}

10.4 图书管理系统之查询所有图书

在这里插入图片描述

public class BookManager {
    public static void main(String[] args) {
        Map<String, ArrayList<Book>> map = new HashMap<>();
        // 创建集合对象,用于存储学生数据
        ArrayList<Book> it = new ArrayList<Book>();
        it.add(new Book("Java入门到精通", 99));
        it.add(new Book("PHP入门到精通", 9.9));
        map.put("it书籍", it);
        ArrayList<Book> mz = new ArrayList<Book>();
        mz.add(new Book("西游记", 19));
        mz.add(new Book("水浒传", 29));
        map.put("名著", mz);

        while (true) {
            //这是学生管理系统的主界面
            System.out.println("--------欢迎来到学生管理系统--------");
            System.out.println("1.查看所有书籍");
            System.out.println("2.添加书");
            System.out.println("3.删除书");
            System.out.println("4.修改书");
            System.out.println("5.退出");
            System.out.println("请输入你的选择:");

            //创建键盘录入对象
            Scanner sc = new Scanner(System.in);
            int num = sc.nextInt();
            switch (num) {
                case 1:
                    // 查看所有书籍
                    findAllBook(map);
                    break;
                case 2:
                    // 添加书籍
                    break;
                case 3:
                    // 删除书
                    break;
                case 4:
                    // 修改书
                    break;
                case 5:
                    // 退出
                    System.out.println("谢谢你的使用");
                    System.exit(0); // JVM退出
                    break;
                default:
                    System.out.println("输入错误,请重新输入");
                    break;
            }
        }
    }
    
    private static void findAllBook(Map<String, ArrayList<Book>> map) {
        System.out.println("类型\t\t书名\t价格");
        Set<Map.Entry<String, ArrayList<Book>>> entries = map.entrySet();
        for (Map.Entry<String, ArrayList<Book>> entry : entries) {
            String key = entry.getKey();
            System.out.println(key);

            ArrayList<Book> value = entry.getValue();
            for (Book book : value) {
                System.out.println("\t\t" + book.getName() + "\t" + book.getPrice());
            }
        }
    }
}    

10.5 图书管理系统之添加图书

private static void addBook(Map<String, ArrayList<Book>> map) {
    // 创建键盘录入对象
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入要添加书籍的类型:");
    String type = sc.next();
    System.out.println("请输入要添加的书名:");
    String name = sc.next();
    System.out.println("请输入要添加书的价格:");
    double price = sc.nextDouble();
    Book book = new Book(name, price);

    // 拿到书籍列表
    ArrayList<Book> books = map.get(type);
    if (books == null) {
        // 如果书籍列表不存在创建一个书籍列表
        books = new ArrayList<>();
        map.put(type, books);
    }

    // 将书添加到集合中
    books.add(book);
    System.out.println("添加" + name + "成功");
}

10.6 图书管理系统之删除图书

private static void deleteBook(Map<String, ArrayList<Book>> map) {
    // 创建键盘录入对象
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入要删除书籍的类型:");
    String type = sc.next();
    System.out.println("请输入要删除的书名:");
    String name = sc.next();

    // 拿到书籍列表  : 用Map集合的
    ArrayList<Book> books = map.get(type);
    if (books == null) {
        System.out.println("您删除的书籍类型不存在");
        return;
    }

    for (int i = 0; i < books.size(); i++) {
        Book book = books.get(i);
        if (book.getName().equals(name)) {
            books.remove(i); // 找到这本书,删除这本书
            System.out.println("删除" + name + "书籍成功");
            return; // 删除书籍后结束方法
        }
    }
    System.out.println("没有找到" + name + "书籍");
}

10.7 图书管理系统之修改图书

private static void editBook(Map<String, ArrayList<Book>> map) {
    // 创建键盘录入对象
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入要修改书籍的类型:");
    String type = sc.next();
    System.out.println("请输入要修改的书名:");
    String oldName = sc.next();

    System.out.println("请输入新的书名:");
    String newName = sc.next();
    System.out.println("请输入新的价格:");
    double price = sc.nextDouble();

    // 拿到书籍列表
    ArrayList<Book> books = map.get(type); // 根本不不像一个技术人员
    if (books == null) {
        System.out.println("您修改的书籍类型不存在");
        return;
    }

    for (int i = 0; i < books.size(); i++) {
        Book book = books.get(i);
        if (book.getName().equals(oldName)) {
            // 找到这本书,修改这本书
            book.setName(newName);
            book.setPrice(price);
            System.out.println("修改成功");
            return; // 修改书籍后结束方法
        }
    }
    System.out.println("没有找到" + oldName + "书籍");
}

10.8 Debug追踪调试

之前我们看程序的执行流程都是通过System.out.println();但是有不能让程序执行到某条语句后停下来,也不能看到程序具体的执行步骤.而是执行完所有的语句程序结束了。

断点调试可以查看程序的执行流程和暂停程序.可以快速解决程序中的bug

Debug调试窗口介绍

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐