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 codeint 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 进行数组元素的copyelementData = 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()) {//你要防止 :NoSuchElementExceptionObject 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包装成streamStream<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());}@Overridepublic 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));//遍历setfor (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@Overridepublic 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);}@Overridepublic 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> {@Overridepublic 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));//遍历setfor (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.最大值为多少1073741824static 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.5 到 1.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 小于数组最大值限制 且扩容之前的阈值 >= 1612.初始化容量是一上来就初始化,还是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.是否包含某个keySystem.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@Overridepublic 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);}@Overridepublic 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;}@Overridepublic String toString() {return "Student{" +"age=" + age +", name='" + name + '\'' +'}';}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(age, name);}
}
public static void main(String[] args) {TreeMap<Student, String> map = new TreeMap<Student, String>(new Comparator<Student>() {@Overridepublic 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,getValueString 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 getValueString 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.发牌采用的是集合索引%3for(int i = 0 ; i < pookerNumber.size() ; i++){//先将底牌做好if(i < 3){//存到底牌去bottom.add( pookerNumber.get(i));//对索引%3判断}else if(i % 3 == 0){//索引上的编号,发给玩家1player1.add( pookerNumber.get(i) );}else if( i % 3 == 1){//索引上的编号,发给玩家2player2.add( pookerNumber.get(i) );}else if( i % 3 == 2){//索引上的编号,发给玩家3player3.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 totalMoneyOrderItem
Product Product
int pCount    Product
int id
String name
double price1个订单  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);}//遍历mapSet<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) {// 获取mapMap<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);  // AList<String> fs1 = map.get(uid1);for (int j = i+1; j < list.size(); j++) {String uid2 = list.get(j);          // B C DList<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调试窗口介绍

7.Java基础之集合框架+JDK8新特性相关推荐

  1. 集合框架,JDK8新特性

    一.集合框架 1.为什么会有集合? 集合和数组都是java中提供的可以用来存储多个数据的一种容器.由于数组类型特点是存储同一类型的元素且长度固定,可以存储基本数据类型值.为了满足现实需求, Java中 ...

  2. Thinking in java基础之集合框架

    Thinking in java基础之集合框架 大家都知道我的习惯,先上图说话. 集合简介(容器) 把具有相同性质的一类东西,汇聚成一个整体,就可以称为集合,例如这里有20个苹果,我们把每一个苹果当成 ...

  3. java把map值放入vector_Thinking in java基础之集合框架

    Thinking in java基础之集合框架 大家都知道我的习惯,先上图说话. 集合简介(容器) 把具有相同性质的一类东西,汇聚成一个整体,就可以称为集合,例如这里有20个苹果,我们把每一个苹果当成 ...

  4. Java番外篇2——jdk8新特性

    Java番外篇2--jdk8新特性 1.Lambda 1.1.无参无返回值 public class Test {interface Print{void print();}public static ...

  5. java基础复习-集合框架(1)

    java集合概述 Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collection接口,主要用于存放单一元素:另一个是 Map 接口,主要用于存放键值对.对于Collection ...

  6. Java基础(集合框架——Collection、List、Set、泛型)

    为什么出现集合类? 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多 个对象的操作,就对对象进行存储,集合就是存储对象常用的一 种方式. 数组和集合类同是容器,有何不同? 数组虽然也可以存储 ...

  7. 小汤学编程之JAVA基础day11——集合框架:List/Set/Map集合、Collections集合工具类、泛型、TreeMap和TreeSet

    一.集合的特点 二.继承结构图 三.List集合 1.特点     2.ArrayList类     3.LinkedList类     4.两者的对比     5.集合的遍历 四.Set集合 1.特 ...

  8. Java基础_集合框架1

    一.集合框架(体系概述) 为什么会出现集合框架(集合类)? 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式. 数组和集合框架 ...

  9. java基础_集合框架

    集合:例如 偶数.水仙花数.奇数,班集体 都属于集合 还有{"111","sss",aaa"}也是集合 集合框架JCF(java collection ...

最新文章

  1. 伍六七带你学算法 入门篇-最小的k个数
  2. Udacity机器人软件工程师课程笔记(二十) - 感知 - 校准,过滤, 分段, RANSAC
  3. ZOJ 3829 Known Notation(贪心)
  4. 微服务架构编码,构建
  5. PHP操作使用Redis
  6. win2008一键配置php mysql_Windows 2008一键安装包配置环境:Windows+IIS+Php+Mysql
  7. php 查看扩展 代码,[扩展推荐] 使用 PHP Insights 在终端查看 PHP 项目代码质量
  8. C++递归或非递归实现求斐波拉契数列第n项
  9. Asp.Net Core基于JWT认证的数据接口网关Demo
  10. dell2100服务器组装,戴尔poweredge r730服务器配置及系统安装详解教程
  11. 十年了,斯坦福和CMU的这场对决,开启了无人车时代
  12. portlet示例_Java Portlet示例教程
  13. (day 10 - 双指针)剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
  14. 瑞昱rtl8168网卡支持Linux吗,瑞昱 RTL8168F PCI-E专用网卡驱动,亲测可用
  15. Python穷举法连接WiFi
  16. oracle中的或者是什么,oracle中=是什么意思呢?
  17. 微信文章如何自动排版
  18. Word2Vec的安装与使用
  19. 考研英国文学复习要点
  20. 如何把很多照片拼成一张照片_怎样用手机将多张照片拼成一张组合图

热门文章

  1. 戴珊取代张勇成阿里法人代表 官方:只是B2B业务变更
  2. 一维河流污染持续排放模拟(水污染扩散)
  3. 国外Essay写作能力怎么正确提升?
  4. 微信直播有哪些功能优势?
  5. 大数据和云计算究竟有什么关系?
  6. android毕业论文结束语,毕业论文结束语与致谢词
  7. 我是如何进行日常时间管理的
  8. KLARI-CORD 4 CAN 通讯的低压模块|KLARIC车辆静态电流采集
  9. 如何才能成为信息顾问
  10. C++实现cat021(0.26版)报文解析