文章目录
- day01【Object类、常用API】
- day02【Collection、泛型】
- day03 【List、Set、数据结构、Collections】
- day04 【Map】
- day05 【异常、线程】
- day06 【线程、同步】
- day07【线程池、Lambda表达式】
- day08【File类、递归】
- day09【字节流、字符流】
- day10 【缓冲流、转换流、序列化流】
- day11【网络编程】
- day12【函数式接口】
- day13【Stream流、方法引用】
- day01【Object类、常用API】
- day02【Collection、泛型】
- day03【List、Set、数据结构、Collections】
- day04 【Map】
- day05 【异常、线程】
- day06 【线程、同步】
- day07【线程池、Lambda表达式】
- day08【File类、递归】
- day09【字节流、字符流】
- day10【缓冲流、转换流、序列化流】
- day11【网络编程】
- day12【函数式接口】
day01【Object类、常用API】
- 能够说出Object类的特点
- 能够重写Object类的toString方法
- 能够重写Object类的equals方法
- 能够使用日期类输出当前日期
- 能够使用将日期格式化为字符串的方法
- 能够使用将字符串转换成日期的方法
- 能够使用System类的数组复制方法
- 能够使用System类获取当前毫秒时刻值
- 能够说出使用StringBuilder类可以解决的问题
- 能够使用StringBuilder进行字符串拼接操作
- 能够说出8种基本类型对应的包装类名称
- 能够说出自动装箱、自动拆箱的概念
- 能够将字符串转换为对应的基本类型
- 能够将基本类型转换为对应的字符串
day02【Collection、泛型】
- 能够说出集合与数组的区别
- 说出Collection集合的常用功能
- 能够使用迭代器对集合进行取元素
- 能够说出集合的使用细节
- 能够使用集合存储自定义类型
- 能够使用foreach循环遍历集合
- 能够使用泛型定义集合对象
- 能够理解泛型上下限
- 能够阐述泛型通配符的作用
day03 【List、Set、数据结构、Collections】
- 能够说出List集合特点
- 能够说出常见的数据结构
- 能够说出数组结构特点
- 能够说出栈结构特点
- 能够说出队列结构特点
- 能够说出单向链表结构特点
- 能够说出Set集合的特点
- 能够说出哈希表的特点
- 使用HashSet集合存储自定义元素
- 能够说出可变参数的格式
- 能够使用集合工具类
- 能够使用Comparator比较器进行排序
day04 【Map】
- 能够说出Map集合特点
- 使用Map集合添加方法保存数据
- 使用”键找值”的方式遍历Map集合
- 使用”键值对”的方式遍历Map集合
- 能够使用HashMap存储自定义键值对的数据
- 能够使用HashMap编写斗地主洗牌发牌案例
day05 【异常、线程】
- 能够辨别程序中异常和错误的区别
- 说出异常的分类
- 说出虚拟机处理异常的方式
- 列举出常见的三个运行期异常
- 能够使用try…catch关键字处理异常
- 能够使用throws关键字处理异常
- 能够自定义异常类
- 能够处理自定义异常类
- 说出进程的概念
- 说出线程的概念
- 能够理解并发与并行的区别
- 能够开启新线程
day06 【线程、同步】
- 能够描述Java中多线程运行原理
- 能够使用继承类的方式创建多线程
- 能够使用实现接口的方式创建多线程
- 能够说出实现接口方式的好处
- 能够解释安全问题的出现的原因
- 能够使用同步代码块解决线程安全问题
- 能够使用同步方法解决线程安全问题
- 能够说出线程6个状态的名称
day07【线程池、Lambda表达式】
- 能够理解线程通信概念
- 能够理解等待唤醒机制
- 能够描述Java中线程池运行原理
- 能够理解函数式编程相对于面向对象的优点
- 能够掌握Lambda表达式的标准格式
- 能够使用Lambda标准格式使用Runnable与Comparator接口
- 能够掌握Lambda表达式的省略格式与规则
- 能够使用Lambda省略格式使用Runnable与Comparator接口
- 能够通过Lambda的标准格式使用自定义的接口(有且仅有一个抽象方法)
- 能够通过Lambda的省略格式使用自定义的接口(有且仅有一个抽象方法)
- 能够明确Lambda的两项使用前提
day08【File类、递归】
- 能够说出File对象的创建方式
- 能够说出File类获取名称的方法名称
- 能够说出File类获取绝对路径的方法名称
- 能够说出File类获取文件大小的方法名称
- 能够说出File类判断是否是文件的方法名称
- 能够说出File类判断是否是文件夹的方法名称
- 能够辨别相对路径和绝对路径
- 能够遍历文件夹
- 能够解释递归的含义
- 能够使用递归的方式计算5的阶乘
- 能够说出使用递归会内存溢出隐患的原因
day09【字节流、字符流】
- 能够说出IO流的分类和功能
- 能够使用字节输出流写出数据到文件
- 能够使用字节输入流读取数据到程序
- 能够理解读取数据read(byte[])方法的原理
- 能够使用字节流完成文件的复制
- 能够使用FileWirter写数据到文件
- 能够说出FileWriter中关闭和刷新方法的区别
- 能够使用FileWriter写数据的5个方法
- 能够使用FileWriter写数据实现换行和追加写
- 能够使用FileReader读数据
- 能够使用FileReader读数据一次一个字符数组
- 能够使用Properties的load方法加载文件中配置信息
day10 【缓冲流、转换流、序列化流】
- 能够使用字节缓冲流流读取数据到程序
- 能够使用字节缓冲流写出数据到文件
- 能够明确字符缓冲流的作用和基本用法
- 能够使用缓冲流的特殊功能
- 能够阐述编码表的意义
- 能够使用转换流读取指定编码的文本文件
- 能够使用转换流写入指定编码的文本文件
- 能够说出打印流的特点
- 能够使用序列化流写出对象到文件
- 能够使用反序列化流读取文件到程序中
day11【网络编程】
- 能够辨别UDP和TCP协议特点
- 能够说出TCP协议下两个常用类名称
- 能够编写TCP协议下字符串数据传输程序
- 能够理解TCP协议下文件上传案例
- 能够理解TCP协议下案例2
day12【函数式接口】
- 能够使用@FunctionalInterface注解
- 能够自定义无参无返回函数式接口
- 能够自定义有参有返回函数式接口
- 能够理解Lambda延迟执行的特点
- 能够使用Lambda作为方法的参数
- 能够使用Lambda作为方法的返回值
- 能够使用Supplier函数式接口
- 能够使用Consumer函数式接口
- 能够使用Function函数式接口
- 能够使用Predicate函数式接口
day13【Stream流、方法引用】
- 能够理解流与集合相比的优点
- 能够理解流的延迟执行特点
- 能够通过集合、映射或数组获取流
- 能够掌握常用的流操作
- 能够使用输出语句的方法引用3
- 能够通过4种方式使用方法引用
- 能够使用类和数组的构造器引用8
day01【Object类、常用API】
1. Object类
1.1 概述
java.lang.Object
类是Java语言中的根类,即所有类的父类。它描述的所有方法子类都可以使用。主要方法两个
public String toString()
:返回该对象的字符串表示public boolean equals(Object obj)
:指示其他某个对象是否与此对象“相等”。
1.2 toString方法
返回的其实是内存地址。一般需要覆盖重写,以便按照对象的属性得到相应的字符串表现形式。
我们直接使用输出语句输出对象名的时候,调用的是toString()方法
1.3 equals方法
默认是地址比较,覆盖重写后是所有或指定的部分成员变量相同,也就是内容相同。
equals方法的源码:
public boolean equals(Object obj) {
return (this == obj);
}
其实调用的就是"=",而String、Integer等类对equals方法进行了重写,所以才不一样了。
1.4 Objects类
JDK7添加了Objects工具类,提供一些静态方法操作对象,是空指针安全的,用于比较对象
public static boolean equals(Object a, Object b)
:判断两个对象是否相等。
日期时间类
2.1 Date类
java.util.Date
类 表示特定的瞬间,精确到毫秒。
毫秒转日期:
public Date()
空参构造,分配Date对象并初始化,自动设置当前时间的毫秒值public Date(long date)
:指定long类型的构造参数,自定义毫秒时刻
常用方法
public long getTime()
把日期对象转换为对应的时间毫秒值
2.2 DateFormat类
java.text.DateFormat
是日期/时间格式化子类SimpleDateFormat
的抽象类,用于日期和文本字符串的转换
构造方法
用子类构造,需要模式
public SimpleDateFormat(String pattern)
:用给定的模式构造SimpleDateFormat
格式规则
标识字母(区分大小写) | 含义 |
---|---|
y | 年 |
M | 月 |
d | 日 |
H | 时 |
m | 分 |
s | 秒 |
format方法
格式化:按照指定的格式,从Date对象转换为String对象
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
把Date对象转换成String
*/
public class Demo03DateFormatMethod {
public static void main(String[] args) {
Date date = new Date();
// 创建日期格式化对象,在获取格式化对象时可以指定风格
DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
String str = df.format(date);
System.out.println(str); // 2008年1月23日
}
}
parse方法
解析: 按照指定格式,从String对象转换为Date对象
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
把String转换成Date对象
*/
public class Demo04DateFormatMethod {
public static void main(String[] args) throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
String str = "2018年12月11日";
Date date = df.parse(str);
System.out.println(date); // Tue Dec 11 00:00:00 CST 2018
}
}
2.4 Calendar类
日历类将所有可能用到的时间信息封装为静态成员变量,方便获取。就是方便获取各个时间属性
Calendar静态方法
public static Calendar getInstance()
:获取当前日历
常用方法
public int get(int field)
:返回给定日历字段的值public void set(int field, int value)
:将给定的日历字段设置为给定值public abstract void add(int field, int amount)
:为给定的日历字段添加或减去指定的时间量public Date getTime()
:返回一个表示此Calendar时间值的Date对象
常见成员变量,代表给定的日历字段
字段值 | 含义 |
---|---|
YEAR | 年 |
MONTH | 月(从0开始,可以+1使用) |
DAY_OF_MONTH | 月中的天(几号) |
HOUR_OF_DAY | 时(24小时制) |
DAY_OF_WEEK | 周中的天(周几,周日为1,可以-1使用) |
MINUTE | 分 |
SECOND | 秒 |
HOUR | 时(12小时制) |
get/set方法
重点,get获取指定字段的值,set设置指定字段的值
西方星期开始为周日,中国为周一
在Calendar类中,月份表示0-11代表1-12月
日期有大小,时间靠后,时间越大
3. System类
提供静态方法,获取与系统相关的信息或系统级操作
常用方法:
public static long currentTimeMillis()
返回毫秒为单位的当前时间public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
:将数组中指定的数据拷贝到另一个数组中
arraycopy方法的5个参数
参数序号 | 参数名称 | 参数类型 | 参数含义 |
---|---|---|---|
1 | src | Object | 源数组 |
2 | srcPos | int | 原数组索引起始位置 |
3 | dest | Object | 目标数组 |
4 | destPos | int | 目标数组索引起始位置 |
5 | length | int | 复制元素个数 |
4. StringBuilder类
4.1 为什么需要?
String类:字符串是常量,他们的值在创建后不能被更改,每次拼接会构建新的String对象,浪费空间
4.2 概述
StringBuilder又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。
它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。(默认16字符空间,超过自动扩容)
4.3 构造方法
public StringBuilder()
:空参构造,空的容器public StringBuilder(String str)
:将字符串添加进去,相当于字符串转StringBuilder对象
4.4 常用方法
public StringBuilder append(...)
:添加任意类型数据的字符串形式,返回当前对象自身public String toString()
:将当前StringBuilder对象转换为String对象
5. 包装类
5.1 概述
基本类型 | 对应的包装类(位于java.lang包中) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
5.2 装箱和拆箱
- 装箱:从基本类型转换为对应的包装类对象。
- 拆箱:从包装类对象转换为对应的基本类型。
上述操作在JDK1.5后自动完成
5.3 基本类型和字符串的转换
基本类型转为String
如34+""
String转对应基本类型
除了Charactor类之外,其他包装类都有parseXxx静态方法转换基本类型
如Integer.parseInt(“100”); 如果没有正确转换,报错NumberFormatException异常
day02【Collection、泛型】
1. Collection集合
1.1 集合概述
集合时java中提供的一种容器,用来存储多个数据
集合和数组的区别:
- 数组的长度是固定的。集合的长度是可变的。
- 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
1.2 集合框架
集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection
和双列集合java.util.Map
。
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List
和java.util.Set
。其中,List
的特点是元素有序、元素可重复。Set
的特点是元素无序,而且不可重复。List
接口的主要实现类有java.util.ArrayList
和java.util.LinkedList
,Set
接口的主要实现类有java.util.HashSet
和java.util.LinkedHashSet
。
1.3 Collection常用功能
public boolean add(E e)
: 把给定的对象添加到当前集合中 。public boolean remove(E e)
: 把给定的对象在当前集合中删除。public boolean contains(E e)
: 判断当前集合中是否包含给定的对象。public boolean isEmpty()
: 判断当前集合是否为空。public int size()
: 返回集合中元素的个数。public Object[] toArray()
: 把集合中的元素,存储到数组中。public void clear()
:清空集合中所有的元素。
2. 迭代器
2.1 Iterator接口
Iterator
主要用于迭代访问(即遍历)Collection
中的元素,因此Iterator
对象也被称为迭代器。
迭代器的获取
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。
迭代
即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
常用方法
public E next()
:返回迭代的下一个元素。public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
public class IteratorDemo {
public static void main(String[] args) {
// 使用多态方式 创建对象
Collection<String> coll = new ArrayList<String>();
// 添加元素到集合
coll.add("串串星人");
coll.add("吐槽星人");
coll.add("汪星人");
//遍历
//使用迭代器 遍历 每个集合对象都有自己的迭代器
Iterator<String> it = coll.iterator();
// 泛型指的是 迭代出 元素的数据类型
while(it.hasNext()){ //判断是否有迭代元素
String s = it.next();//获取迭代出的元素
System.out.println(s);
}
}
}
迭代器的异常
在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
如果一个线程正在迭代一个集合,而另一个线程同时试图修改这个集合;或者在调用remove()方法后,如何我们还试图去修改集合object,会报并发修改异常。
iterator和listIterator的区别
Iterator:只能正向遍历集合,适用于获取移除元素。ListIerator:继承Iterator,可以双向列表的遍历,同样支持元素的修改。
具体方法 hasPrevious(), set(E e)
2.2 迭代器的实现原理
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
代码实现
arrayList的iterator方法:
public Iterator<E> iterator() {
return new Itr();// 调用Itr
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;// 期望值=修改值
// prevent creating a synthetic constructor
Itr() {}//空参构造
public boolean hasNext() {
return cursor != size;// 看游标是否到了末尾
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();// 判断期望值是否==修改值
int i = cursor;
if (i >= size)//游标大于长度
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;//底层数组???元素长度改变?
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
2.3 增强for
也称for each循环,JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
格式:
for(元素的数据类型 变量 : Collection集合or数组){
//写操作代码
}
3. 泛型
3.1 概述
集合Collection什么都可以存储,取出时强转就会引发运行时 ClassCastException。泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。
- 泛型:可以在类或方法中预支地使用未知的类型。
tips:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
3.2 使用泛型的好处
- 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
- 避免了类型强转的麻烦。
tips:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
3.3 泛型的定义和使用
定义和使用含有泛型的类
格式
修饰符 class 类名<代表泛型的变量> { }
含有泛型的方法
格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
public class MyGenericMethod {
public <MVP> void show(MVP mvp) {
System.out.println(mvp.getClass());
}
public <MVP> MVP show2(MVP mvp) {
return mvp;
}
}
含有泛型的接口
格式
修饰符 interface接口名<代表泛型的变量> { }
public interface MyGenericInterface<E>{
public abstract void add(E e);
public abstract E getE();
}
3.4 泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
泛型不存在继承关系
通配符高级使用----受限泛型
泛型的上限:
- 格式:
类型名称 <? extends 类 > 对象名称
- 意义:
只能接收该类型及其子类
泛型的下限:
- 格式:
类型名称 <? super 类 > 对象名称
- 意义:
只能接收该类型及其父类型
数据结构
“树”这种数据结构真的很像我们现实生活中的“树”,这里面每个元素我们叫作“节点”;用来连线相邻节点之间的关系,我们叫作“父子关系”。
A节点就是B节点的父节点,B节点是A节点的子节点。B、C、D这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点。我们把没有父节点的节点叫作根节点,也就是图中的节点E。我们把没有子节点的节点叫作叶子节点或者叶节点,比如图中的G、H、I、J、K、L都是叶子节点。
关于“树”,还有三个比较相似的概念:高度(Height)、深度(Depth)、层(Level)。它们的定义是这样的:
在我们的生活中,“高度”这个概念,其实就是从下往上度量,比如我们要度量第10层楼的高度、第13层楼的高度,起点都是地面。所以,树这种数据结构的高度也是一样,从最底层开始计数,并且计数的起点是0。“深度”这个概念在生活中是从上往下度量的,比如水中鱼的深度,是从水平面开始度量的。所以,树这种数据结构的深度也是类似的,从根结点开始度量,并且计数起点也是0。“层数”跟深度的计算类似,不过,计数起点是1,也就是说根节点的位于第1层。
day03【List、Set、数据结构、Collections】
1. List集合
概述
java.util.List
接口继承自Collection
接口,是单列集合的一个重要分支,习惯称实现List接口的对象为List集合。在list集合中允许重复的元素,可以通过索引访问集合中的指定元素。另一个特点是元素有序,元素的存入顺序和取出顺序一致。
特点:
- 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
常用方法
除了Collection接口的全部方法,还增加根据元素索引来操作集合的特有方法。
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。public E get(int index)
:返回集合中指定位置的元素。public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
2. List的子类
2.1 ArrayList集合
存储的是数组结构,查找快,增删慢。
2.2 LinkedList集合
java.util.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集合可以作为堆栈,队列的结构使用
3. Set接口
也是继承Collection接口,元素无序,并且会以某种规则保证存入的元素不重复
3.1 HashSet集合
是set接口的实现类,它所存储的元素不可重复,并且元素都是无序的(存取顺序不一致)。底层是HashMap支持。
HashSet是根据对象的哈希值确定元素在集合中的存储位置,具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode
和 equals
方法
HashSet集合存储数据的结构(哈希表)
哈希表存储在JDK1.8后,采用数组+链表/红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,提高查询效率
存放自定义的对象,保证唯一性必须复写hashCode和equals方法建立属于当前对象的比较方式。
3.2 LinkedHashSet
HashSet的子类,链表和哈希表组合的一个数据存储结构,保证有序
3.3 可变参数
如果定义一个方法需要接受多个参数,并且多个参数类型一致,简化为:
修饰符 返回值类型 方法名(参数类型... 形参名)
实际上是传递数组,自动完成封装数组,传递。
注意: 如果在方法书写时,方法拥有多个参数,参数中包含可变参数,可变参数要写在参数列表的末尾位置。
4. Collections
4.1 常用功能
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:
public static <T> boolean addAll(Collection<T> c, T... elements)
:往集合中添加一些元素。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> )
:将集合中元素按照指定规则排序。
day04 【Map】
1. map集合
1.1 概述
现实生活中的一种集合,如IP地址和主机名,系统用户名和系统用户对象等,一一对应的关系,叫做映射。java的Map接口,存放这种对应关系。
map和collection集合的区别:
Collection
中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。Map
中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。Collection
中的集合称为单列集合,Map
中的集合称为双列集合。- 需要注意的是,
Map
中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
1.2 Map接口常用的实现类
HashMap集合和LinkedHashMap集合,HashTable也是实现了map
- HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
- LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
tips:Map接口中的集合都有两个泛型变量<K,V>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量<K,V>的数据类型可以相同,也可以不同。
HashTable为什么还会被问到
因为人家有个牛X的儿子,Properties,可以从流中加载,用于读取配置文件,被广泛运用在项目中,常用方法getProperty(key)
二者区别:
- hashtable是扩展了Dictionary类,hashMap是扩展了AbstractMap抽象类
- HashTable是同步的,get/put方法都是synchronized锁;HashMap线程不安全
1.3 Map接口的常用方法
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。public V get(Object key)
根据指定的键,在Map集合中获取对应的值。boolean containsKey(Object key)
判断集合中是否包含指定的键。public Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
tips:
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
1.4 Map集合遍历
entry键值对对象
Map
中存放的是两种对象,一种称为key(键),一种称为value(值),它们在在Map
中是一一对应关系,这一对对象又称做Map
中的一个Entry(项)
。Entry
将键值对的对应关系封装成了对象。
public K getKey()
:获取Entry对象中的键。public V getValue()
:获取Entry对象中的值。
Map集合中也提供了获取所有Entry对象的方法
public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
遍历键找值
步骤:
keyset()得到存储键的Set集合,遍历键的Set集合,得到每一个键,get(key)得到键对应的值
遍历键值对找值
步骤:
entrySet()获取存储键值对对象的集合,遍历得到键值对对象,getkey()和getvalue()获取entry对象中的键与值
1.5 HashMap存储自定义类型键值
自定义对象作为key存在,必须保证对象唯一,需要复写对象的hashCode和equals方法
1.6 LinkedHashMap
是HashMap的子类,链表和哈希表组合的一个数据存储结构,保证有序
2. Debug追踪
3. 单例模式
饿汉模式
缺点:如果调用Car类的其他静态方法,也会创建对象,占用内存,另外如果为空,后续好像再也创建不了对象了???
public class DanLi_1 {
public static void main(String[] args) {
Car car1 = Car.getCar();
Car car2 = Car.getCar();
System.out.println(car1);
System.out.println(car2);
}
}
class Car{
private static Car c = new Car();
//构造
private Car(){
System.out.println("创建对象了");
}
// 需要对象
public static Car getCar(){
return c;
}
//不需要对象
public static void sort(){
System.out.println("排序了");
}
public static void addAll(){
System.out.println("addAll功能执行");
}
}
懒汉模式(科学一点)
缺点:线程安全问题
// 多线程: 加锁,同步锁
public class Danli_02 {
public static void main(String[] args) {
// Car.sort();
Car car1 = Car.getCar();
Car car2 = Car.getCar();
car1 = null;
System.out.println(car1);
System.out.println(car2);
}
}
class Car {
private static Car c;
//构造
private Car(){
System.out.println("创建对象了");
}
//需要对象
public static Car getCar(){
if(c == null){
c=new Car();
}
return c;
}
//不需要对象
public static void sort(){
System.out.println("排序了");
}
}
day05 【异常、线程】
1. 异常
1.1 概念
异常,指的是程序在执行过程中,出现的非正常情况,最终会导致JVM的非正常停止。
在java语言中,异常本身就是一个类,产生异常就是创建异常对象并抛出一个异常对象。java处理异常的方式是中断处理。
异常指的不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行
1.2 异常体系
异常的根类是java.lang.Throwable
,两个子类:java.lang.Error
与java.lang.Exception
,平常所说的异常指java.lang.Exception
Throwable体系
- Error: 严重错误Error,无法通过处理的错误,只能实现避免,好比绝症。
- Exception: 表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
Throwable中的常用方法
public void printStackTrace()
: 打印异常的详细信息public String getMessage()
: 获取发生异常的原因public String toString()
: 获取异常的类型和异常描述信息(不用)
1.3 异常分类
- 编译期异常: check异常。在编译时期,就会检查。如果没有处理,编译失败
- 运行期异常:runtime异常。在运行时期,检查异常,编译阶段,不会报错
2. 异常的处理
java异常处理的五个关键字: try
catch
finally
throw
throws
2.1 抛出异常throw
编写程序时,必须要考虑程序出现问题的情况。如,定义方法时,需要接受参数,首先对参数数据进行合法的判断。数据不合法,需要抛出异常告诉调用者。
具体操作:
throw new 异常类名(参数);
注意: throw将问题描述类即异常抛出,给方法的调用者。对于调用者,一种是进行捕获处理,另一种是继续讲问题声明,用throws声明处理
2.2 Objects非空判断
public static <T> T requireNonNull(T obj)
:查看指定引用对象不是null。
2.3 声明异常throws
问题标识出来,报告给调用者。用于方法声明之上,表示当前方法不处理异常,提醒方法的调用者处理。
格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2...{}
2.4 捕获异常try…catch
对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
try{
编写可能会出现异常的代码
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
如何获取异常信息?
Throwable类中定义一些查看方法:
-
public String getMessage()
:获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。 -
public String toString()
:获取异常的类型和异常描述信息(不用)。 -
public void printStackTrace()
:打印异常的跟踪栈信息并输出到控制台。包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace
2.4 finally代码块
有些特定的代码无论异常是否发生,都需要执行,在finally代码块中存放的代码一定会被执行。用途:释放资源。
- 运行时异常被抛出可以不处理。即不捕获也不声明抛出。(运行期异常)
- 如果finally有return语句,永远返回finally中的结果,避免该情况.
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
- 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
3. 自定义异常
3.1 概述
开发中有些异常情况java并没有定义过,需要我们根据实际业务情况自己定义异常类。
如RegisterException 注册异常类
如何定义?两种方式
- 自定义一个编译期异常类:自定义类并继承Exception
- 自定义一个运行期异常类:继承RuntimeException
4. 多线程
4.1 并发和并行
- 并发: 指两个或多个事件在同一时间段内发生
- 并行: 指两个或多个事件在同一时刻发生(同时发生)
4.2 线程和进程
- 进程: 指一个内存中运行的应用程序。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
- 线程: 线程是进程中的一个执行单元,负责当前进程中程序的执行。一个进程中至少有一个线程。一个进程可以有多个线程,成为多线程程序。
线程调度
-
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
-
抢占式调度
优先让优先级高的线程使用CPU,如果优先级相同,会随机选择一个(线程随机性),java为抢占式调度。
大部分操作系统都支持多进程并发运行,CPU使用抢占式调度模式在多个线程间高速切换。多线程程序不能提高程序的运行速度,但是能提高程序的运行效率,让CPU使用率更高。
day06 【线程、同步】
1. 线程
1.1 多线程原理
程序启动运行main方法,java虚拟机启动一个进程,主线程main在main()调用时被创建,随着调用mt的对象的start方法,另一个新的线程也启动,整个应用在多线程下运行。
多线程执行时,在栈内存中,每一个执行线程都有一片自己所属的栈内空间,进行方法的压栈和弹栈。当执行线程的任务结束,线程自动在栈内存释放,所有的执行线程结束,进程就结束了。
1.2 Thread类
构造方法:
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法:
- public String getName() :获取当前线程名称。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
- public void run() :此线程要执行的任务在此处定义代码。
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
创建线程两种方式,继承Thread类,或者实现Runnable接口方式。
1.3 创建线程方式2
步骤:
- 定义Runnable接口的实现类,重写该接口的run()方法,方法体为需要执行的任务。
- 创建Runnable实现类的实例,作为Thread的参数target创建thread对象,调用该对象的start方法
注意:Runnable对象作为Thread对象的target,实际的线程对象为Thread实例
继承Thread的类可以认为是线程类,而实现runnable接口的类可以认为是任务类。
1.4 Thread和Runnable的区别
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一资源
- 可以避免java中的单继承的局限性
- 增加程序的健壮性,实现解耦操作,任务可被多个线程共享,任务和线程独立
- 线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类
扩展:java中,每次程序运行至少启动2个线程,main线程和垃圾回收线程。每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每个JVM其实就是在操作系统中启动了一个进程。
1.5 匿名内部类实现线程的创建
三种:Thread的匿名内部类,Runnable接口的匿名内部类,Thread匿名对象使用的Runnable接口的匿名内部类
2. 线程安全
2.1 线程安全
多个线程在同时运行,这些线程可能同时执行同一任务,导致程序发生两种问题,以卖票为例:
- 相同的票数,如卖了3张100的票
- 不存在的票,如1,0,-1的票
这种问题,线程的票数不同步了,称为线程不安全。
2.2 线程同步
java采用同步机制(synchronized)解决,保证每个线程都能正常执行原子操作。包括,同步代码块、同步方法、锁机制。
2.3 同步代码块
synchronized
关键字用于方法中的某个区块,表示只对这个区块的资源实行互斥访问
synchronized(同步锁){
需要同步操作的代码
}
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能在外边等着,处于阻塞状态BLOCKED
2.3 同步方法
采用synchronized修饰的方法,叫同步方法。很少用
同步锁是谁?
对于非静态方法,同步锁就是this,
对于静态方法,我们使用当前方法所在类的字节码对象(类名.class)
2.4 LOCK锁
也称同步锁。将加锁和释放锁方法化了
锁是什么? 又从哪儿来呢? 锁的专业名称叫监视器 monitor, 其实 Java 为每个对象都自
动内置了一个锁(监视器 monitor)。当某个线程执行到某代码块时就会自动得到这个对象
的锁, 那么其他线程就无法执行该代码块了, 一直要等到之前那个线程停止(释放锁)。 需要
特别注意的是: 多个线程必须使用同一把锁(对象)
- public void lock() :加同步锁。
- public void unlock() :释放同步锁
3. 线程状态
3.1 线程的6种状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
3.2 Timed Waiting(计时等待)
也就是sleep(时间)
-
进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协作关系。
-
为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠
-
sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
3.3 BLOCKED(锁阻塞)
比如,线程A与线程B代码中使用同一锁,如果线程A获
取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
3.4 Waiting(无限等待)
一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的Object.notify()方法 或Object.notifyAll()方法。
其实Waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系。
day07【线程池、Lambda表达式】
1. 等待唤醒机制
1.1 线程间通信
概念
多个线程在处理同一个资源,但是处理的动作(线程的任务)不相同
如生产者和消费者
为什么要处理线程间通信
多个线程并发执行时,默认情况下CPU随机切换线程,当我们需要多个线程共同完成一件任务,并且我们希望他们有规律的执行,多线程间需要一些协调通信,以便多线程共同操作一份数据。
如何保证线程间通信有效利用资源
多个线程处理同一个资源时,并且任务不同时,需要线程通信来帮助解决线程之间对同一变量的使用或操作,避免对同一共享变量的争夺。采用等待唤醒机制。
1.2 等待唤醒机制
这是多个线程间的一种协作机制,一个线程进行了规定操作后,进入等待状态wait(),等其他线程执行完他们的指定代码后,再将其唤醒notify(),如果需要,可以用notifyAll()唤醒所有的等待线程。
注意:被通知的线程不会立即回复执行,需要再次尝试争夺锁,成功后恢复执行,否则,进入entry set,线程变成BLOCKED状态
wait方法和notify方法必须由同一个锁对象调用,都属于Object类的方法,必须在同步代码块或同步函数中使用
1.3 生产者与消费者问题
以包子的生产和消费为例
包子铺线程生产包子,吃货线程吃包子。包子状态为false,吃货等待,包子铺生产包子,包子状态变为true,通知吃货线程,包子铺线程等待;接下来,吃货线程能否进一步执行取决于锁的获取情况,如果获取到锁,执行吃包子,吃完,包子状态修改为false,通知包子铺线程,吃货线程进入等待。
2. 线程池
2.1 线程池思想
概念
线程池:就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程消耗过多资源。
线程池的好处
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程可重复利用,执行多次任务。
- 提高响应速度。当任务到达时,可以不需要等到线程创建就可以立即执行
- 提高线程的可管理性。可根据系统的承受能力,调整线程池中工作线程的数目,防止内存消耗过多,把服务器宕机。
2.3 线程池的使用
顶级接口是java.util.concurrent.Executor
,Executor
其实是线程工厂类,生产和管理线程。真正的线程池接口是java.util.concurrent.ExecutorService
。
Executors类创建线程池的方法:
public static ExecutorService newFixedThreadPool(int nThreads)
线程池对象的使用:
public Future<?> submit(Runnable task)
获取线程池的某个线程对象并执行。
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建和使用
3. lambda表达式
3.1 函数式编程思想概述
函数强调做什么,而不是以什么形式做
面向对象的思想: 做一件事,找一个能解决这个事情的对象,调用对象的方法,完成事情
函数式编程思想:只要能获得结果,谁做不管,重视结果,不重视过程。
3.2 lambda标准格式
3部分:(参数类型 参数名称) -> {代码语句}
- 一些参数 小括号内的语法和传统方法参数列表一致,无参数留空,多个参数逗号分隔
- 一个箭头
->
代表指向动作 - 一段代码 大括号内的语法与传统方法体要求一致
3.3 lambda省略格式
可推导可省略
凡是能根据上下文推导得知的信息,都可以省略
省略规则
- 小括号内的参数类型可以省略
- 如果小括号内有且仅有一个参数,小括号可以省略
- 大括号内有且仅有一个语句,无论是否有返回值,都可以省略大括号、return和分号
3.4 lambda的使用前提
- 必须有接口,函数式接口,接口有且仅有一个抽象方法
- 必须有上下文推断 方法的参数或局部变量类型必须为lambda对应的接口类型。
day08【File类、递归】
1. file类
1.1 概述
File类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除操作
1.2 构造方法
public File(String pathname)
:通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。public File(String parent, String child)
:从父路径名字符串和子路径名字符串创建新的 File实例public File(File parent, String child)
:从父抽象路径名和子路径名字符串创建新的 File实例。
一个File对象代表硬盘中实际存在的一个文件或者目录
无论该路径下是否存在文件或目录,都不影响file对象的创建
1.3 常用方法
获取功能的方法
- public String getAbsolutePath() :返回此File的绝对路径名字符串
- public String getPath() :将此File转换为路径名字符串
- public String getName() :返回由此File表示的文件或目录的名称
- public long length() :返回由此File表示的文件的长度
绝对路径和相对路径
- 绝对路径: 从盘符开始的路径,这是一个完整的路径
- 相对路径: 相对于项目目录的路径,这是一个便捷的路径,开发中常用。
判断功能
public boolean exists()
:此File表示的文件或目录是否实际存在public boolean isDirectory()
:此File表示的是否为目录public boolean isFile()
:此File表示的是否为文件
创建删除功能
public boolean createNewFile()
:当且仅当具有该名称的文件尚不存在时,创建一个新的空文件public boolean delete()
:删除由此File表示的文件或目录public boolean mkdir()
:创建由此File表示的目录public boolean mkdirs()
:创建由此File表示的目录,包括任何必需但不存在的父目录
目录的遍历
public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录public File[] listFiles()
:返回一个File数组,表示该File目录中的所有的子文件或目录
调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。
2. 递归
2.1 概述
- 递归:指的是在当前方法内调用自己的现象。
- 递归的分类: 直接递归和间接递归
- 注意事项:
- 递归要有条件限定,保证递归能够停止下来,否则栈内存溢出
- 在递归汇总虽有限定条件,但递归次数不能太多,否则也会栈内存溢出
- 构造方法禁止递归
2.2 递归打印多级目录
public class RecursionDemo {
public static void main(String[] args) {
// 创建file对象
File dir = new File("D:\\java_work\\advance\\day08");
// 调用打印目录方法
printDir(dir);
}
private static void printDir(File dir) {
// System.out.println("文件夹:"+dir.getAbsolutePath());
// 获取子文件和目录
File[] files = dir.listFiles();
// 循环打印
/*
判断:
如果是文件,打印绝对路径
如果是文件夹,继续调用打印目录的方法,递归
*/
for (File file : files) {
if (file.isFile()){
System.out.println("文件名:"+file.getAbsolutePath());
}else{
// 目录
System.out.println("文件夹:"+file.getAbsolutePath());
printDir(file);
}
}
}
}
2.3 文件搜索
import java.io.File;
/*
搜索day08目录下的所有的.java文件
思路:在之前文件遍历的基础上判断是否.java结尾
*/
public class RecursionDemo02 {
public static void main(String[] args) {
File dir = new File("D:\\java_work\\advance\\day08");
// 打印目录方法
printDir(dir);
}
private static void printDir(File dir) {
// 获取子文件和目录
File[] files = dir.listFiles();
for (File file : files) {
// 如果文件夹,继续遍历
if (file.isDirectory()){
printDir(file);
}else{
if (file.getName().toLowerCase().endsWith(".java")){
System.out.println(file);
}
}
}
}
}
2.4 文件过滤器优化
java.io.FileFilter
是个接口,是File的过滤器,对象可以传递给File类的listFiles(FileFilter)
作为参数,接口汇总只有boolean accept(File pathname)
方法,测试pathname是否应包含在当前File目录中
import java.io.File;
import java.io.FileFilter;
public class RecursionDemo03 {
public static void main(String[] args) {
File dir = new File("D:\\java_work\\advance\\day08");
printDir(dir);
}
private static void printDir(File dir) {
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory()|| pathname.getName().toLowerCase().endsWith(".java");
}
});
for (File file : files) {
if (file.isDirectory()){
printDir(file);
}else {
System.out.println("文件名:"+file.getAbsolutePath());
}
}
}
}
2.5 lambda优化
import java.io.File;
public class RecursionDemo04 {
public static void main(String[] args) {
File dir = new File("D:\\java_work\\advance\\day08");
printDir(dir);
}
private static void printDir(File dir) {
File[] files = dir.listFiles(pathname->pathname.isDirectory()||pathname.getName().toLowerCase().endsWith(".java"));
for (File file : files) {
if (file.isDirectory()){
printDir(file);
}else{
System.out.println("文件名:"+file.getAbsolutePath());
}
}
}
}
day09【字节流、字符流】
1. IO概述
1.1 什么是IO
编辑文件,忘了ctrl+s
,文件可能就白编辑了,电脑插入U盘,就可以把视频拷贝到电脑硬盘中,等等
我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input
和输出output
,即流向内存是输入流,流出内存为输出流。
java的I/O操作主要指使用java.io
包下的内容,进行输入、输出操作,输入也叫读取数据,输出也叫写出数据。
1.2 IO的分类
根据数据的流向:
- 输入流: 把数据从其他设备读取到内存中的流
- 输出流: 把数据从内存中写出到其他设备的流
根据数据的类型:
- 字节流: 以字节为单位,读写数据的流
- 字符流: 以字符为单位,读写数据的流
2. 字节流
2.1 一切皆为字节
一切文件数据(文本、图片、视频等)在存储和传输的时候都是以二进制数字的形式保存和传输。底层传输始终是二进制数据。
2.2 字节输出流 OutputStream
是表示字节输出流的所有类的父类,将指定的字节信息写出到目的地。
- public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
- public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
- public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
- public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
- public abstract void write(int b) :将指定的字节输出流。
2.3 FileOutputStream类
文件输出流,将数据写出到文件
构造方法:
- public FileOutputStream(File file) :创建文件输出流以写入由指定的 File对象表示的文件。
- public FileOutputStream(String name) : 创建文件输出流以指定的名称写入文件。
写出字节数据
- 写字节 write(int b) 方法,每次可以写出一个字节数据
- 写出字节数组: write(byte[] b) ,每次可以写出数组中的数据
- 写出指定长度字节数组: write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节
数据的追加续写
- public FileOutputStream(File file, boolean append) : 创建文件输出流以写入由指定的 File对象表示的文件
- public FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件
换行
fos.write("\r\n".getBytes());
2.4 字节输入流 InputStream
是表示字节输入流的所有类的父类,可以读取字节信息到内存
- public void close() :关闭此输入流并释放与此流相关联的任何系统资源
- public abstract int read() : 从输入流读取数据的下一个字节。
- public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中
2.5 FileInputStream
文件输入流,从文件中读取字节
构造方法
- FileInputStream(File file) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
- FileInputStream(String name) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
读取字节数据
- 读取字节: read 方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回 -1
- 使用字节数组读取: read(byte[] b) ,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回 -1
使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使
用。
3. 字符流
专门处理文本文件
3.1 字符输入流 Reader
是表示用于读取字符流的所有类的父类,读取字符信息到内存
- public void close() :关闭此流并释放与此流相关联的任何系统资源
- public int read() : 从输入流读取一个字符。
- public int read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
3.2 FileReader类
是读取字符文件的类,构造时使用系统默认的字符编码和默认字节缓冲区
构造方法
- FileReader(File file) : 创建一个新的 FileReader ,给定要读取的File对象
- FileReader(String fileName) : 创建一个新的 FileReader ,给定要读取的文件的名称
读取字符数据
- 读取字符: read 方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回 -1
- 使用字符数组读取: read(char[] cbuf) ,每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回 -1
3.3 字符输出流 Writer
用于写出字符流的所有类的父类,将指定的字符信息写出到目的地
- void write(int c) 写入单个字符
- void write(char[] cbuf) 写入字符数组
- abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
- void write(String str) 写入字符串。
- void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
- void flush() 刷新该流的缓冲
- void close() 关闭此流,但要先刷新它
3.4 FileWriter类
是写出字符到文件的类,构造时使用系统默认的字符编码和默认字节缓冲区
构造方法
- FileWriter(File file) : 创建一个新的 FileWriter,给定要读取的File对象
- FileWriter(String fileName) : 创建一个新的 FileWriter,给定要读取的文件的名称
写出数据
- 写出字符: write(int b) 方法,每次可以写出一个字符数据
- 写出字符数组 : write(char[] cbuf) 和 write(char[] cbuf, int off, int len) ,每次可以写出字符数组中的数据
- 写出字符串: write(String str) 和 write(String str, int off, int len) ,每次可以写出字符串中的数据,更为方便
关闭和刷新
- flush 流对象可以继续使用
- close 先刷新后通知系统释放资源,不能再使用流对象了
续写和换行
类似FileOutputStream
4. IO异常的处理
FileWriter fw = null;
try{
fw = new FileWriter("fw.txt");
fw.write("黑马程序员");
}catch(IOException e){
e.printStackTrace();
}finally{
try {
if (fw != null){
fw.close();
}
}catch (IOException){
e.printStackTrace();
}
}
5. 属性集
5.1 概述
Properties extends HashTable,表示一个持久的属性集,使用键值结构存储数据,键值都是字符串
5.2 Properties类
构造方法
- public Properties() :创建一个空的属性列表。
存储方法
- public Object setProperty(String key, String value) : 保存一对属性。
- public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
- public Set stringPropertyNames() :所有键的名称的集合。
与流相关的方法
- public void load(InputStream inStream) : 从字节输入流中读取键值对
参数中使用字节输入流,通过流对象,关联到某文件如配置文件,这样就能够加载文本中的数据。文本数据的格式
url=www.baidu.com
name=baidu
加载代码
// 创建属性集对象
Properties pro = new Properties();
// 加载文本信息到属性集
pro.load(new FileInputStream("read.txt"))
// 遍历集合并打印
Set<String> set = pro.stringProperties();
for(String key: set){
System.out.println(key+"="+pro.getProperty(key))
}
文本中的数据,必须是键值对形式,可以空格、等号、冒号等符号分隔
day10【缓冲流、转换流、序列化流】
1. 缓冲流
能够高效读写的缓冲流、能够转换编码的转换流、能够持久化存储对象的序列化流等功能更为强大的流,都是在基本的流对象基础上创建,是对基本流对象的一种增强。
1.1 概述
缓冲流也叫高效流,是对4个基本的FileXxx流的增强,分为字节缓冲流和字符缓冲流。
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写效率
1.2 字节缓冲流
- BufferedInputStream(InputStream in) 创建一个新的缓冲输入流
- BufferedOutputStream(OutputStream out)创建一个新的缓冲输出流
1.3 字符缓冲流
构造方法
- BufferedReader(Reader in) 创建一个新的缓冲输入流
- BufferedWriter(Writer out) 创建一个新的缓冲输出流
特有方法:
- BufferedReader: public String readLine() 读取一行文字
- BufferedWriter: public void newLine() 写一行行分隔符
代码:
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
// 定义字符串,保存读取的一行文字
String line = null;
// 循环读取,读取到最后返回null
while ((line = br.readLine())!=null) {
System.out.print(line);
System.out.println("‐‐‐‐‐‐");
} // 释放资源
br.close();
}
}
public class BufferedWriterDemo throws IOException{
public static void main(String[] args) throws IOException{
//创建流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
// 写出数据
bw.write("黑马");
bw.newLine();
bw.write("程序");
// 释放资源
bw.close();
}
}
练习: 文本排序
ArrayList数组和HashMap两种方法
public class Demo01shige {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("day10\\in.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("day10\\out.txt"));
ArrayList<String> list = new ArrayList<>();
String line;
while((line=br.readLine())!=null){
list.add(line);
}
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.charAt(0)-o2.charAt(0);
}
});
for (String s : list) {
bw.write(s);
bw.newLine();
}
bw.close();
br.close();
}
}
public class Demo02shige {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("day10\\in.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("day10\\out.txt"));
// hashmap
HashMap<String,String> map = new HashMap<>();
String line;
while((line=br.readLine())!=null){
String[] strings = line.split("\\.");
map.put(strings[0],strings[1]);
}
for (String key : map.keySet()) {
bw.write(key+"."+map.get(key));
bw.newLine();
}
bw.close();
br.close();
}
}
2. 转换流
2.1 字符编码和字符集
字符编码
按照某种规则,将字符存储到计算机,称为编码;反之,将存储在计算机的二进制数按照某种规则解析显示出来,称为解码
字符编码:character encoding 就是一套自然语言的字符和二进制数之间的对应规则
字符集
字符集:charset 也叫编码表,是一个系统支持的所有字符的集合,需要有一套字符编码
-
ASCII字符集
-
GBxxx字符集:
- GB就是国标
- GBK 最常用中文码表,使用双字节编码方案
-
Unicode字符集:
- 最多用4个字节存储,三种:UTF-8、UTF-16和UTF-32
- UTF-8编码,是电子邮件、网页和其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有的互联网协议都必须支持UTF-8编码。编码规则:
- 128个US-ASCII字符,只需要一个字节编码
- 拉丁文等字符,需要二个字节编码
- 中文使用三个字节编码
- 其他极少数的使用四字节编码
2.2 编码引出的问题
在IDEA中,使用FileReader读取项目中的文本文件,IDEA默认都是UTF-8编码,没有问题;读取windows系统创建的文本文件,windows默认GBK编码,会出现乱码
2.3 InputStreamReader类
转换流,是Reader的子类,从字节流通向字符流的桥梁,读取字节,使用指定的字符集解码为字符。
构造方法
- InputStreamReader(InputStream in) 创建一个使用默认字符集的字符流
- InputStreamReader(InputStream in,String charsetName) 创建一个指定字符集的字符流
2.4 OutputStreamWriter类
转换流,是Writer的子类,从字符流到字节流的桥梁,使用指定的字符集将字符编码为字节。root
构造方法
- OutputStreamWriter(OutputStream in)
- OutputStreamWriter(OutputStream in, String charsetName)
3. 序列化
3.1 概述
java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,包含该对象的数据、对象的类型和对象中存储的属性等信息,写出到文件,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象到内存,进行反序列化。
3.2 ObjectOutputStream 类
将java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。
一个对象想要序列化,需要满足两个条件:
- 该类必须实现
java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。 - 该类的所有属性必须是可序列化的,如果有属性不需要序列化,必须注明是瞬态的使用
transient
关键字修饰
写出对象方法
public final void writeObject (Object obj)
: 将指定的对象写出。
3.3 ObjectInputStream类
反序列化流,将序列化的原始数据恢复为对象
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。
对于JVM可以反序列化对象,必须是能够找到class文件的类。如果找不到该类的class文件,抛出ClassNotFountException异常
JVM反序列化对象时,能找到class文件,但class文件在序列化对象之后发生了修改,也会失败,抛出一个InvalidClassException异常,发生该异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
4. 打印流
4.1 概述
调用print方法和println方法,都来自PrintStream类,该类能够方便的打印各种数据类型的值。
4.2 PrintStream类
构造方法
public PrintStream(String fileName)
: 使用指定的文件名创建一个新的打印流。
改变它的流向System.setOut(ps)
day11【网络编程】
1. 网络编程入门
1. 1 软件结构
- C/S结构: 全称为Client/Server结构,是指客户端和服务器结构。
- B/S结构: 全称为Browser/Server结构,是指浏览器和服务器结构。
网络编程,就是在一定的协议下,实现两台计算机的通信的程序。
1.2 网络通信协议
- 网络通信协议:位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率和传输步骤做了统一规定,通信双方必须同时遵守才能完成数据交换。
- TCP/IP协议: 传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocol),是Internet最基本最广泛的协议,定义了计算机如何炼乳网络,以及数据如何在他们之间传输的标准。采取4层的分层模型。
ICP/IP协议的四层为应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。TCP和UDP位于传输层。
1.3 协议分类
java.net
中包含的类和接口,提供低层次的通信细节,我们可以直接使用这些类和接口。
UDP
用户数据包协议(Uer Datagram Protocol),是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。
特点:消耗资源小,通信效率高。数据被限制在64kb以内。
应用场景: 音频、视频和普通数据的传输如视频会议。
TCP
传输控制协议(Transmission Control Protocol),是面向连接的通信协议,传输数据之前,在发送端和接收端简历逻辑连接,然后再传输数据,提供了两台计算机之间可靠无差错的数据传输。
每次连接的创建都需要经过“三次握手”。
- 第一次握手,客户端向服务器端发出
小demo
1. 需求
客户端给服务器端发送数据 OutputStream:你好服务器
服务器端读取客户端发送的数据 InputStream:你好服务器
服务器端给客户端发送数据 OutputStream: 收到谢谢
客户端读取服务器回送的数据 InputStream:收到谢谢
分析
服务器端需要明确两件事情
-
多个客户端可以同时和服务器进行交互,服务器必须明确和哪个客户进行交互,在服务器端有个方法叫accept可以获取到请求的客户端对象
-
服务器同时和多个客户端交互,需要使用多个IO流对象。服务器没有IO流,服务器可以获取到请求的客户端socket使用每个客户端socket中提供的IO流,进行交互。
服务器使用客户端的字节输入流读取客户端发送的数据
服务器使用客户端的字节输出流回写数据给客户端
简单记:服务器使用客户端的流和客户端交互。
TCPServer
/*
TCP通信的服务器端:接收客户端的请求,读取客户端发哦送的数据,给客户端回写数据
表示服务器的类
ServerSocket
构造方法:
ServerSocket(int port) 创建绑定到特定端口的服务器套接字
服务器端必须明确一件事情,必须知道哪个客户端请求的服务器
所以可以使用accept方法获取到请求的客户端对象socket
成员方法
Socket accept() 侦听并接收到此套接字的链接
服务器的实现步骤:
1. 创建服务器对象和系统要指定的端口号
2. ServerSocket对象的方法accept获取客户端对象socket
3. 使用socket方法getInputStream 获取InputStream对象
4. InputStream对象的方法read,读取客户端发送的数据
5. 使用getOutputStream获取OutputStream对象
6. OutputStream对象的方法write给客户端回写数据
7. 释放资源(socket, ServerSocket)
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
// 获取socket
Socket socket = server.accept();
// 获取InputStream
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read();
System.out.println(new String(bytes,0,len));
OutputStream os = socket.getOutputStream();
os.write("收到谢谢".getBytes());
socket.close();
server.close();
}
}
TCPClient
/*
TCP通信的客户端:向服务器发送请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类Socket
套接字:包含了IP地址和port的网络单位
构造方法:
Socket(String host,int port) 连接指定主机的指定端口号
参数:
String host: 服务器主机的名称/服务器的IP地址
port端口号
成员方法:
getOutputStream 返回此套接字的输出流
getInputStream 返回此套接字的输入流
close()
实现步骤:
1. 创建客户端Socket,
2. 使用Socket对象的方法getOutputStream()获取网络字节输出流对象
3. write给服务器发送数据
4. 使用socket对象的方法getInputStream获取网络字节输入流
5. 使用网络字节输入流度对象方法read读取服务器回写的数据
6. 释放资源socket
注意:
1. 客户端和服务器交互,必须使用socket中提供的网络流,不能用自己创建的流对象
2. 创建客户端对象socket的时候就会去请求服务器和服务器经过三次握手建立连接通路
这时如果服务器没有启动,就会抛出异常
如果服务器已经启动,就可以进行交互了
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
// 创建socket
Socket socket = new Socket("127.0.0.1",8888);
// 获取输出流
OutputStream os = socket.getOutputStream();
// 写数据
os.write("你好服务器".getBytes());
//InputStream
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read();
System.out.println(new String(bytes,0,len));
// 释放资源
socket.close();
}
}
文件上传案例
TCPServer
public class TCPServer {
public static void main(String[] args) throws IOException {
// 网络
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
// 判断本地是否有文件
File file = new File("e:\\upload");
if (!file.exists()){
file.mkdirs();
}
// 读取网络,写本地
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
int len=0;
byte[] bytes = new byte[1024];
while((len=is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
// 回复
socket.getOutputStream().write("文件上传成功!".getBytes());
fos.close();
socket.close();
server.close();
}
}
TCPClient
public class TCPClient {
public static void main(String[] args) throws IOException {
// 本地读取
FileInputStream fis = new FileInputStream("D:\\1.jpg");
// 网络输出
Socket socket = new Socket("127.0.0.1",8888);
int len=0;
byte[] bytes = new byte[1024];
OutputStream os = socket.getOutputStream();
while((len=fis.read(bytes))!=-1){
os.write(bytes,0,len);
}
// 结束标记
socket.shutdownOutput();
// 网络输入
InputStream is = socket.getInputStream();
while((len=is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
/* byte[] b = new byte[1024];
int read = is.read();
System.out.println(new String(b,0,len));*/
fis.close();
socket.close();
}
}
day12【函数式接口】
1. 函数式接口
1.1 概念
有且仅有一个抽象方法的接口
语法糖:是指使用更加方便,但是原理不变的代码语法,如增强for循环相对于迭代器。从应用层面lambda可以当做匿名内部类的语法糖,但是原理不同。
1.2 @FunctionalInterface注解
检测接口是否为函数式接口
2. 函数式编程
2.1 lambda的延迟执行
有些场景的代码执行后,结果不一定被使用,会造成性能浪费,如字符串的拼接,而lambda表达式是延迟执行的,可以作为解决方案,提升性能。
日志案例,在不符合级别要求的情况下,lambda不会执行,节省性能。
实际上使用内部类也可以达到同样的效果,知识将代码操作延迟到了另外一个对象中通过调用方法完成,条件判断后才决定是否执行调用方法。
2.2 使用lambda作为参数和返回值
其实就是使用函数式接口作为方法参数和返回值类型。
3. 常用函数式接口
3.1 Supplier接口
只包含一个无参的方法T get()
用来获取一个泛型参数指定类型的对象数据。生产型接口,对应的lambda表达式需要对外提供一个符合泛型类型的对象数据。
3.2 Consumer接口
和Supplier接口相反,消费一个数据,类型由泛型决定。抽象方法为void accept(T t)
,意为消费一个指定泛型的数据。更好的方法为使用方法引用。
默认方法 andThen
如果一个方法的参数和返回值都是Consumer类型,两个Consumer都消费一个数据,可以实现组合,使用andThen
public class Test {
public static void consumeString(Consumer<String> one,Consumer<String> two){
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s -> System.out.println(s.toUpperCase()),
s -> System.out.println(s.toLowerCase())
);
}
}
3.3 Predicate接口
有时候需要对某种类型的数据进行判断,得到一个boolean值。可以使用java.util.function.Predicate<T>
接口
抽象方法:test
该接口包含一个抽象方法 boolean test(T t)
,用于判断条件
默认方法 and
将两个Predicate
条件使用’与’逻辑连接,实现&&的效果,使用and
默认方法or
和and类似,实现逻辑关系的或 ||
默认方法:negate
非,取反 !
3.4Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法:apply
R apply(T t)
,根据类型T的参数获取类型R的结果,如将String类型转换为Integer类型。最好通过方法引用
默认方法 andThen
组合操作,先做什么,后做什么
day13【Stream流、方法引用】
1. 引言
java8的lambda表达式让我们更加专注于做什么(what),而不是怎么做(how),我们为什么要使用循环?因为要遍历。循环是遍历的唯一方式吗?遍历是指每一个元素逐一处理,而不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
2. 流式思想
类似于工厂车间的“生产流水线”。先拼接好一个“模型”步骤方案,再按照方案执行。
“Stream流”其实是一个集合元素的函数模型,并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
Stream流是一个来自数据源的元素队列
-
元素是特定类型的对象,形成一个队列。java中的Stream并不会存储元素,而是按需计算。
-
数据源 流的来源,可以是集合,数组等
和Collection不同,Stream操作有两个基础的特征:
- pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(fluent style)。这样做可以对操作进行优化,如延迟执行(laziness)和短路(short-circuiting)
- 内部迭代:以前对集合的遍历都是通过iterator或增强for,显式的在集合外部迭代,焦作外部迭代;Stream提供内部迭代的方式,流可以直接调用遍历方法。
当用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)->数据转换->执行操作获取想要的结果,每次转换原有的Stream对象不变,返回一个新的Stream对象,这就允许对其操作可以像链条一样排列,变成一个管道。
3. 获取流
java.util.stream.Stream<T>
是java8新加入的最常用的流接口(不是函数式接口)
获取流的方式:
- 所有的
Collection
集合都可以通过stream
默认方法获取流 Stream
接口的静态方法of
可以获取数组对应的流。
根据Collection获取流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
根据map获取流,需要分key,value,entry等情况
Map<String,String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
根据数组获取流
String[] array = {"a","b","c","d","e","f"};
Stream<String> stream = Stream.of(array);
4. 常用方法
两种
- 延迟方法:返回值类型还是
Stream
接口类型,支持链式调用 - 终结方法:返回值类型不是
Stream
接口类型,包括count
和forEach
方法
逐一处理:forEach
void forEach(Consumer<? super T> action);
接收Consumer接口函数,将每一个流元素交给该函数处理
java.util.function.Consumer<T>
接口是一个消费型接口,包含抽象方法void accept(T t)
意为消费一个指定泛型的数据
代码示范
public class Demo02StreamForEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("tom", "jack", "rose");
stream.forEach(name-> System.out.println(name));\
}
}
过滤 filter
通过filter方法将一个流转换成另一个流
Stream<T> filter(Predicate<? super T> predicate);
接收一个Predicate函数式接口参数作为筛选条件
java.util.stream.Predicate
函数式接口,唯一的抽象方法boolean test(T t);
该方法产生一个boolean值结果,代表指定的条件是否满足,如果结果为true,Stream流的filter
方法会留住元素;如果结果false,filter方法会舍弃元素
代码实现
public class Demo03StreamFilter {
public static void main(String[] args) {
Stream<String> stream = Stream.of("tom", "jack", "rose");
Stream<String> result = stream.filter(s -> s.startsWith("t"));
}
}
映射 map
如果需要将流中的元素映射到另一个流中,使用map方法
<R> Stream<R> map(Function<? super T,? extends R> mapper)
需要一个Function函数式接口参数
java.util.stream.Function
函数式接口,唯一的抽象方法R apply(T t);
可以将一种T类型转换为R类型
代码示范
public class Demo04StreamMap {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "14");
Stream<Integer> result = original.map(str -> Integer.parseInt(str));
}
}
map方法的参数通过方法引用,将字符串类型转换为int类型(并自动装箱为Integer类对象)
统计个数:count
和Collection当中的size一样,流提供count方法计数,返回long值代表元素个数
public class DemoStreamCount {
public static void main(String[] args) {
Stream<String> original = Stream.of("tom", "jack", "rose");
Stream<String> result = original.filter(s -> s.startsWith("j"));
System.out.println(result.count());
}
}
取用前几个 limit
limit方法对流进行截取,只取用前n个
Stream<T> limit(long maxSize);
参数long型,如果集合当前长度大于参数则进行截取否则不操作
public class Demo06StreamLimit {
public static void main(String[] args) {
Stream<String> original = Stream.of("tom", "jack", "rose");
Stream<String> result = original.limit(2);
System.out.println(result.count());
}
}
跳过前几个 skip
想跳过前几个,用skip方法获取一个截取之后的新流 Stream<T> skip(long n);
如果流的当前长度大于n,跳过前n个;否则得到一个长度为0的空流
组合 concat
两个流合并为一个流,用Stream接口的静态方法concat
static <T> Stream<T> concat(Stream<? extends T> a,Stream<? extends T> b);
这是一个静态方法,与java.lang.String当中的concat方法不同
5. 练习
1. 题目
现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以
下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建 Person 对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息。
代码
public class DemoStreamNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一个队伍只要名字为3个字的成员姓名
// 第一个队伍筛选之后只要前三个人
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
// 第二个队伍只要姓张的成员姓名
// 第二个队伍筛选后不要前2个人
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);
// 合并队伍
// 根据姓名创建Person对象
// 打印整个队伍的Person对象信息
Stream.concat(streamOne,streamTwo).map(Person::new).forEach(System.out::println);
}
}
2. 方法引用
1. 概念
简化lambda表达式
双冒号::
为引用运算符,所在的表达式称为方法引用。
也是可推导可省略的原则
2. 通过对象名引用成员方法
3. 通过类名称引用静态方法
4. 通过super引用成员方法
5. 通过this引用成员方法
6. 类的构造器引用
使用类名称::new
的格式表示