7.Java基础之集合框架+JDK8新特性
1.集合概述1.1 为什么学集合思考:数组有什么缺点?长度一旦定义,不能改变!定义大了,浪费空间;小了,可能不够 ----》动态的数组对于增删,需要移动位置—》有人帮我们做这个事情,LinkedList数组存储的单列数据,对于双列数据的映射关系,怎么存储(key-value,键值对,类似数学中的函数映射)?Map基于以上问题,我们需要学习集合框架。开发中,数组用的非常少,几乎不怎么用!1.2 什么
1.集合概述
1.1 为什么学集合
思考:数组有什么缺点?
- 长度一旦定义,不能改变!定义大了,浪费空间;小了,可能不够 ----》动态的数组
- 对于增删,需要移动位置 —》有人帮我们做这个事情,LinkedList
- 数组存储的单列数据,对于双列数据的映射关系,怎么存储(key-value,键值对,类似数学中的函数映射)?Map
基于以上问题,我们需要学习集合框架。
开发中,数组用的非常少,几乎不怎么用!
1.2 什么是集合
集合就是一个存储数据的容器。
1.3 集合的整体架构图
Collection继承Iterable接口,使得我们的Collection具有迭代(遍历)作用,因为Iterable接口中有一个**iterator()**方法,返回值是一个Iterator
2.List接口
List接口扩展出来的方法:
List接口特点:
- 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
2.1 ArrayList(用的最多)
java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList
是最常用的集合。
许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。
2.1.1 源码解读
【高频面试】说一说ArrayList
1. 底层使用什么存数据?
2. 初始化容量多少?
3. 容量不够,怎么扩容?
4. 线程是否安全?安全 bye 不安全:说另外一个CopyOnWriteArrayList
5. 说一说CopyOnWriteArrayList
........
最基础到第3点
-
底层使用什么存数据:Object对象数组
private static final Object[] EMPTY_ELEMENTDATA = {}
-
初始化容量多少
private static final int DEFAULT_CAPACITY = 10;
-
扩容机制?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 数据的存储结构初识
- 栈结构:后进先出/先进后出(手枪弹夹) FILO (first in last out)
- 队列结构:先进先出/后进后出(银行排队) FIFO(first in first out)
- 数组结构:
查询快:通过索引快速找到元素
增删慢:每次增删都需要开辟新的数组,将老数组中的元素拷贝到新数组中
开辟新数组耗费资源 - 链表结构
查询慢:每次都需要从链头或者链尾找起
增删快:只需要修改元素记录的下个元素的地址值即可不需要移动大量元素 - 树 【非常非常重要,二叉树、满二叉树、平衡二叉树、红黑树…】
- 图【数据结构、离散数学】
2.1.5 泛型集合
- 我们知道,集合中可以存放任意数据类型,但是我们在遍历自己的类型的时候,需要调用自己的方法,此时需要向下转型 ----->省略
- 能否限定集合中只能放某一种类型,将运行时异常提前到编译时期。
基于以上两点:我们需要使用泛型约束。即集合中只能存放某一种数据类型
好处:
- 无需向下转型
- 将运行时异常提前到编译时
- 让使用变得更灵活【即定义泛型的类,其实只规定类型,具体什么类型,由使用者决定】
小结:在使用集合的时候,要使用泛型,泛型集合
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>{
}
-
泛型方法
public T getA(int index){ return t; }
-
方法参数(成员变量、形参)
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对象,我们称为中间操作【只会存储中间计算过程,并不会出结果,所以我们需要一些聚合操作【保存在集合中】或输出【打印出来】】
获取方式:
Stream<Stu> stream = list.stream();
- 通过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
是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存储和查找性能。保证元素唯一性的方式依赖于:hashCode
与equals
方法。
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,是一种基于红黑树的实现,其特点为:
- 元素唯一
- 元素没有索引
- 使用元素的自然顺序对元素进行排序,或者根据创建 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.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 小于数组最大值限制 且扩容之前的阈值 >= 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)
:判断该集合中是否有此键。
- key重复,会覆盖
- key、value都可以为null
- 直接输出的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=酒八}
案例演示比较器排序
需求:
- 创建一个TreeMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
- 要求按照学生的年龄进行升序排序,如果年龄相同,比较姓名的首字母升序, 如果年龄和姓名都是相同,认为是同一个元素;
实现:
为了保证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”,“谢霆锋”->”张柏芝”。
要求如下:
- 创建HashMap
- 使用put方法添加元素
- 使用keySet方法获取所有的键
- 获取到keySet的迭代器
- 循环判断迭代器是否有下一个元素
- 使用迭代器next方法获取到一个键
- 通过一个键找到一个值
- 输出键和值
4.8.2 玩转水浒
已知Map中保存如下信息:{“及时雨”=”宋江”, “玉麒麟”=”卢俊义”, “智多星”=”吴用”}
其中键表示水浒中人物的外号,value表示人物的姓名.
- 往Map中添加“入云龙”=”公孙胜”, ”豹子头”=”林冲”两位好汉
- 删除“玉麒麟”=”卢俊义”
- 将key为“智多星”的value修改为null,
- 将“及时雨”=”宋江”,修改为”呼保义”=” 宋江”
4.8.3 统计字符出现次数
需求:
输入一个字符串中每个字符出现次数。
分析:
- 获取一个字符串对象
- 创建一个Map集合,键代表字符,值代表次数。
- 遍历字符串得到每个字符。
- 判断Map中是否有该键。
- 如果没有,第一次出现,存储次数为1;如果有,则说明已经出现过,获取到对应的值进行++,再次存储。
- 打印最终结果
方法介绍
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 案例简介
按照斗地主的规则,完成洗牌发牌的动作。
具体规则:
- 组装54张扑克牌
- 54张牌顺序打乱
- 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
- 查看三人各自手中的牌(按照牌的大小排序)、底牌
规则:手中扑克牌从大到小的摆放顺序:大王,小王,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 相关知识点
-
掌握字符串切割规则
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];
-
掌握数组和list集合之间的互相转换
-
知道Arrays类的基本使用
-
掌握list和map集合的存储数据特点和基本应用场景
-
掌握list和map的遍历
-
掌握list的自定义排序
-
掌握map的value排序
-
理解方法的意义和封装
8.2.3 需求
-
获取每个人的好友个数并排序
-
获取任意两人的共同好友,A-B:C,D getShareFriends()
-
获取所有人两两共同好友,即全部数据
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 完成斗地主
案例中的几个步骤,全部抽取成方法来实现。
以下为扩展功能,可以尝试完成:
- 地主谁抢到了 ,可以使用 随机数
- 地主是否要底牌,使用键盘输入询问,最多三次,即又回到该地主手里,必须接
- 模拟出牌的效果
8.4 Stream的使用
这几个自己测试:flatMap【讲过】、collect、count、distinct、max/min、reduce
8.5 map操作员工
研发部门有5个人,信息如下:(姓名-工资)【柳岩=2100, 张亮=1700, 诸葛亮=1800, 灭绝师太=2600, 东方不败=3800】。
要求:
- 定义HashMap,姓名作为key,工资作为value
- 使用put方法添加需要的元素
- 获取到柳岩的工资
- 修改柳岩的工资为当前工资加上300
- 使用增强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调试窗口介绍
更多推荐
所有评论(0)