阶段:第一阶段
计划时间:1.12-1.14
计划内容:
Java IO的相关知识点学习,Java 8流操作、 Lambda表达式、Optional类使用
- IO
- 流
- File
- OutputStream
- InputStream
- Writer
- Reader
- Serializable
- Lambda表达式
- Optional类
Java IO
整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable.
流式部分
- InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
- OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
- Reader(文件格式操作):抽象类,基于字符的输入操作。
- Writer(文件格式操作):抽象类,基于字符的输出操作。
非流式部分
- File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
- RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object。它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
流
本质:数据传输,将数据传输特性抽象为各种类,方便更直观的进行数据操作
作用:为数据源和目的第建立一个传输通道
InputStream
方法
读方法
// 读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。 public abstract int read() throws IOException; // 将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。 public int read(byte b[]) throws IOException { return read(b, 0, b.length); } // 将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数。 public int read(byte b[], int off, int len) throws IOException { ... }
其他方法
- long skip(long n):在输入流中跳过n个字节,并返回实际跳过的字节数。
- int available() :返回在不发生阻塞的情况下,可读取的字节数。
- oid close() :关闭输入流,释放和这个流相关的系统资源。
- voidmark(int readlimit) :在输入流的当前位置放置一个标记,如果读取的字节数多于readlimit设置的值,则流忽略这个标记。
- void reset() :返回到上一个标记。
- booleanmarkSupported() :测试当前流是否支持mark和reset方法。
OuntputStream
写方法
// 将指定的字节写入此输出流。必须实现该方法 public abstract void write(int b) throws IOException; // 将 b.length字节从指定的字节数组写入此输出流。 public void write(byte b[]) throws IOException // 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。 public void write(byte b[], int off, int len) throws IOException
其他方式
- void flush() :刷新输出流,强制缓冲区中的输出字节被写出。
- void close() :关闭输出流,释放和这个流相关的系统资源。
Reader
读方法
// 尝试将字符读入指定的字符缓冲区。 public int read(java.nio.CharBuffer target) throws IOException // 读一个字符 public int read() throws IOException // 将字符读入数组。 public int read(char cbuf[]) throws IOException // 将字符读入数组的一部分。抽象方法由子类实现 abstract public int read(char cbuf[], int off, int len) throws IOException;
其他方法
- ready()
- reset()
- skip(long n)
- mrk()
- close()
Writer
写方法
public void write(int c) throws IOException
public void write(char cbuf[]) throws IOException
abstract public void write(char cbuf[], int off, int len) throws IOException;
public void write(String str) throws IOException
public void write(String str, int off, int len) throws IOException
字节流和字符流的区别
- 字节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用colse()方法时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法。
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
File
public class File implements Serializable, Comparable<File>
File类是Object的直接子类,同时它继承了Comparable接口可以进行数组的排序。File类的操作包括文件的创建、删除、重命名、得到路径、创建时间等,通过对象的思想来操作文件和文件夹。
构造函数
提供了三个构造函数,以便不同的参数形式灵活地接收文件和目录名信息。
// 已经标准化的路径名称来构造
private File(String pathname, int prefixLength) {
this.path = pathname;
this.prefixLength = prefixLength;
}
// 从父抽象路径名和子路径名字符串创建新的 File实例。
private File(String child, File parent) {
assert parent.path != null;
assert (!parent.path.equals(""));
this.path = fs.resolve(parent.path, child);
this.prefixLength = parent.prefixLength;
}
// 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
// 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
public File(URI uri) {
// 整个流程就是,检查合法性,并初始化为标准化的路径
// Check our many preconditions
if (!uri.isAbsolute())
throw new IllegalArgumentException("URI is not absolute");
if (uri.isOpaque())
throw new IllegalArgumentException("URI is not hierarchical");
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equalsIgnoreCase("file"))
throw new IllegalArgumentException("URI scheme is not \"file\"");
if (uri.getAuthority() != null)
throw new IllegalArgumentException("URI has an authority component");
if (uri.getFragment() != null)
throw new IllegalArgumentException("URI has a fragment component");
if (uri.getQuery() != null)
throw new IllegalArgumentException("URI has a query component");
String p = uri.getPath();
if (p.equals(""))
throw new IllegalArgumentException("URI path component is empty");
// Okay, now initialize
p = fs.fromURIPath(p);
if (File.separatorChar != '/')
p = p.replace('/', File.separatorChar);
this.path = fs.normalize(p);
this.prefixLength = fs.prefixLength(this.path);
}
方法
一个对应于某磁盘文件或目录的File对象一经创建, 就可以通过调用它的方法来获得文件或目录的属性。
- public boolean exists( ) 判断文件或目录是否存在
- public boolean isFile( ) 判断是文件还是目录
- public boolean isDirectory( ) 判断是文件还是目录
- public String getName( ) 返回文件名或目录名
- public String getPath( ) 返回文件或目录的路径。
- public long length( ) 获取文件的长度
- public String[ ] list ( ) 将目录中所有文件名保存在字符串数组中返回。
File类中还定义了一些对文件或目录进行管理、操作的方法,常用的方法有:
- public boolean renameTo( File newFile ); 重命名文件
- public void delete( ); 删除文件
- public boolean mkdir( ); 创建目录
RandomAccessFile
public class RandomAccessFile implements DataOutput, DataInput, Closeable
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。该对象特点:
该对象只能操作文件,所以构造函数接收两种类型的参数:
a.字符串文件路径;
b.File对象。
该对象既可以对文件读写,在进行对象实例化时可指定操作模式(r,rw)
public RandomAccessFile(String name, String mode) throws FileNotFoundException
该对象在实例化时,如文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。可以用于多线程下载或多个线程同时写数据到文件。
System对IO的支持
针对一些频繁的设备交互,Java语言系统预定了3个可以直接使用的流对象,分别是:
-
System.in(标准输入),通常代表键盘输入。
-
System.out(标准输出):通常写往显示器。
-
System.err(标准错误输出):通常写往显示器。
标准I/O
Java程序可通过命令行参数与外界进行简短的信息交换,同时,也规定了与标准输入、输出设备,如键盘、显示器进行信息交换的方式。而通过文件可以与外界进行任意数据形式的信息交换。
在Java语言中使用字节流和字符流的步骤基本相同,以输入流为例,首先创建一个与数据源相关的流对象,然后利用流对象的方法从流输入数据,最后执行close()方法关闭流。
Java 8 Stream
用函数式编程方式在集合类上进行复杂操作的工具。简而言之,是以内部迭代的方式处理集合数据的操作,内部迭代可以将更多的控制权交给集合类。Stream 和 Iterator 的功能类似,只是 Iterator 是以外部迭代的形式处理集合数据的操作。
Java8以前,对集合的操作需要写出处理的过程。如筛选,需要一 一遍历集合中的每个元素,再把每个元素逐一判断是否满足条件,最后将满足条件的元素保存返回。而Stream 对集合筛选的操作提供了一种更为便捷的操作,只需将实现函数接口的筛选条件作为参数传递进来,Stream会自行操作并将合适的元素同样以stream 的方式返回,最后进行接收即可。
- 中间操作:中间操作的结果是刻画了一个Stream,没有产生一个新集合,这种操作也叫做惰性求值方法。
- 终止操作:最终会从Stream中得到值
该部分具体在demo代码中查看,大部分是方法的示例
创建
-
.stream()
/** * 流的特性:支持并行流与顺序流 * 并行流:多个线程同时运行 * 顺序流:使用主线程,单线程 */ // 自定义一个学生类集合(ArrayList) List<Student> students = StudentData.getStudents(); // 第一种:返回一个顺序流 Stream<Student> stream = students.stream(); // 第二种:返回一个并行流 Stream<Student> stream1 = students.parallelStream();
-
Arrays.stream
int []arr = {1, 34, 54, 6, 8}; IntStream intStream = Arrays.stream(arr);
-
stream.of()
// 基础数据类型 Stream<String> stringStream = Stream.of("1","3","6","9","3"); // 自定义类型 Stream<Student> studentStream = Stream.of(new Student(1,"cherry",23,99.5),new Student(1,"1",1, 1.1);
使用
一般和Lambda表达式结合使用,更好的完成常见集合操作
-
filter-过滤
// 过滤出年纪大于22的学生 list.stream().filter(item -> item.getAge() > 22)
-
limit-截断流
// 筛选出前三条 list.stream().limit(3)
-
skip-跳过元素
-
distinct-跳过重复元素
-
map-映射
将一种类型的值映射为另一种类型的值,可以将 Stream 中的每个值都映射为一个新的值,最终转换为一个新的 Stream 流。
Stream<Student> stream1 = students.stream(); //把所有的年龄取出来变为一个新的stream Stream<Integer> stream2 = stream1.map(Student::getAge); //过滤出大于23的名字 stream2.filter(age -> age > 23).forEach(System.out::println);
终止
-
allMatch–检查是否匹配所有元素
-
anyMatch–检查是否至少匹配一个元素
-
noneMatch–检查是否没有匹配所有元素
-
findFirst–返回第一个元素
-
findAny–返回当前流中的任意元素
-
count–返回流中元素的总个数
-
max–返回流中最大值
-
min–返回流中最小值
//判断所有的学生年龄是否都大于20岁 boolean allMatch = list.stream().allMatch(student -> student .getAge() > 20); //判断是否存在学生的年龄大于20岁 boolean anyMatch = list.stream().anyMatch(student -> student .getAge() > 20); //判断是否不存在学生叫小白 boolean noneMatch = list.stream().noneMatch(student -> "小白".equals(student .getName())); //查找第一个学生 Optional<Student> first = list.stream().findFirst() ; //查找当前流中的元素 Optional<Student> any = list.stream().findAny() ; //查找所有的学生数量 long count = list.stream().count();
Lambda表达式
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。
lambda表达式拆分为两部分
左侧:lambda 表达式的参数列表
右侧:lambda 表达式中所需要执行的功能,即lambda体
不需要参数,返回值为 5
() -> 5
接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
接收2个int型整数,返回他们的和
(int x, int y) -> x + y
接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
支持引用写法
对象::实例方法名 System.out::println 等价于 (x) -> System.out.println(x)
类::静态方法名 Integer::compare 等价于 (x,y) -> Integer.compare(x,y)
类::实例方法名 String::equals 等价于 bp = (x,y) -> x.equals(y);
构造器引用 Supplier sup2 = String::new; 等价于 Supplier sup = () -> new String();
注意点
- lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
- 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
Optional
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
类方法
-
static Optional empty()
返回空的Optional实例
-
boolean equals(Object obj)
判断其他对象是否等于 Optional。
-
Optional filter(Predicate<? super predicate)
如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
-
Optional flatMap(Function<? super T,Optional> mapper)
如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
-
T get()
** T get()**如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
-
int hashCode()
返回存在值的哈希码,如果值不存在 返回 0。
-
void ifPresent(Consumer<? super T> consumer)
如果值存在则使用该值调用 consumer , 否则不做任何事情。
-
boolean isPresent()
如果值存在则方法会返回true,否则返回 false。
-
Optional map(Function<? super T,? extends U> mapper)
如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
-
static Optional of(T value)
返回一个指定非null的Optional
-
static Optional ofNullable(T value)
如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
-
T orElse(T other)
如果存在该值,返回值, 否则返回 other。
-
T orElseGet(Supplier<? extends T> other)
如果存在该值,返回值, 否则返回 otherr,并返回 other 调用的结果。
-
T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果存在该值,返回包含的值,否则抛出由Supplier继承的异常
-
String toString()
返回一个Optional的非空字符串,用来调试
注意点:
1、orElse和orElseGet的区别
// 对比二者源码
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
- 无论optional是否有值,orElse都会被执行
- 只有optional为空时,orElseGet才会被执行
在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。
2、of和ofNullable的使用
从以下源码来分析
// of直接返回一个新的Optional,所以如果为空时抛出异常
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
// ofNullable在传入空值时调用empty()
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
// empty会创建一个空的Option实例
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
使用示例
// Optional.ofNullable - 允许传递为 null 参数,在不确定对象是不是一定非空用ofNullable
Optional<Integer> a = Optional.ofNullable(value1);
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException,只有在确认对象一定不为空时使用
Optional<Integer> b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
3、转换值
如使用map()
***map()* 对值应用(调用)作为参数的*函数*,然后将返回的值包装在 *Optional* 中。**这就使对返回值进行链试调用的操作成为可能
User user = new User("anna@gmail.com", "1234");
String email = Optional.ofNullable(user)
.map(u -> u.getEmail()).orElse("default@gmail.com");
4、map和faltmap区别
map是把结果自动封装成一个Optional,但是flatmap需要你自己去封装。
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { // 参数中需要Optonal
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
// 不封装Optional,不要自行调用of()/ofNullable去封装Optional后传入
return Objects.requireNonNull(mapper.apply(value));
}
}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
// 封装Optional
return Optional.ofNullable(mapper.apply(value));
}
}
5、链式方法
为了更充分的使用 Optional,你可以链接组合其大部分方法,因为它们都返回相同类似的对象。
// 示例
String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");