1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 7.Java基础之集合框架+JDK8新特性

7.Java基础之集合框架+JDK8新特性

时间:2019-07-24 18:47:52

相关推荐

7.Java基础之集合框架+JDK8新特性

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 不安全:说另外一个CopyOnWriteArrayList5. 说一说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 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 数据的存储结构初识

栈结构:后进先出/先进后出(手枪弹夹) 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 -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(paringInt(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);//=============其他应用也蛮多的parator<T> :用于比较、排序int compare(T o1, T o2);boolean equals(Object obj);

方法引用参照:/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,是一种基于红黑树的实现,其特点为:

元素唯一元素没有索引使用元素的自然顺序对元素进行排序,或者根据创建 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相关面试

源码分析:/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 163.最大值为多少1073741824static final int MAXIMUM_CAPACITY = 1 << 304. 加载因子:扩容的阈值①值是多少/*** 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):判断该集合中是否有此键。

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.是否包含某个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=酒八}

案例演示比较器排序

需求:

创建一个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;}@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”,“谢霆锋”->”张柏芝”。

要求如下:

创建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 -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 案例简介

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

具体规则:

组装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.发牌采用的是集合索引%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:使用集合完成模拟下单OrderString id List<OrderItem> orderItemsdouble totalMoneyOrderItem Product Product int pCount Productint 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,OB:A,C,E,KC:F,A,D,ID:A,E,F,LE:B,C,D,M,LF:A,B,C,D,E,O,MG:A,C,D,E,FH:A,C,D,E,OI:A,OJ:B,OK:A,C,DL:D,E,FM:E,F,GO:A,H,I,J

8.2.2 相关知识点

掌握字符串切割规则

String str = A:B,C,D,F,E,OString[] 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 -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(paring(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 -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 -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 完成斗地主

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

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

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

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调试窗口介绍

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。