I/O流(万流齐发、万流归宗)
本章目标:
掌握

讲  解:★★★★★
http://kuaibao.qq.com/s/20200527A0LR3000?refer=spider
1.I/O流概述
所谓I/O,也就是Input与Output的缩写。
I => Input 输入—将数据源读到内存中;相当于读取硬盘上的文件
O => Output 输出—将内存数据写(保存)到介质(硬盘)里;相当于保存文件。

流的概念
流是一组有顺序的、有起点和终点的字节集合,是对数据传输的总称和抽象。
即数据在两个设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

什么叫数据流?
“流”就像一根管道,数据在管道中的走向就叫数据流(也称IO流)。
流是有方向性的

作用:用来处理设备之间的数据传输。
(硬盘存放的文件,内存中的数据…硬盘和内存都是设备)。

电脑中文件的展现形式:文件和文件夹;
电脑中文件的操作方式:读 和 写;

2.IO流的分类
(1)根据数据类型不同分为:字节流、字符流
(2)根据数据流向不同分为:输入流、输出流
(3)根据使用方式不同分为:节点流、处理流

字符流和字节流
字节是数据最小的基本单位,字节流就是对字节进行操作的流对象。多个字节可以组成字符,所以字符流的本质是基于字节流读取时,根据数据编码的不同,查询对应的码表,从而对字符进行高效操作的流对象。

字节流和字符流的区别:
(a)读写单位不同:字节流以字节(8bit)为单位;字符流以字符为单位,根据码表映射字符,一次可能读取多个字节。0000 0000
(b)处理对象不同:字节流能处理所有类型的数据(如图片、视频等);字符流只能处理字符类型的数据。
©字节流在操作的时候本身是不会用到缓冲区的,是对文件本身的直接操作;字符流在操作的时候是会用到缓冲区的,是通过缓冲区来操作文件。

结论:优先选用字节流。因为硬盘上所有的文件都是以字节的形式进行传输或保存的,但是字符只会在内存中形成,所以在开发中,字节流使用广泛。

输入流和输出流
读入写出,即对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

节点流和处理流
节点流是连接两个节点的最基本的流,处理流是处理节点流、增强其性能和可操作性的流。

3.I/O流的体系

流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。
(1)字节流抽象类:InputStream、OutputStream
(2)字符流抽象类:Reader、Writer

java.io总共涉及40多个类,都是从上面四个抽象类中派生出来的。
4.File类
java.io.File类是对文件和文件夹进行操作的类。
在Java中,不管是文件还是文件夹都是叫做File对象,File类将文件系统中的文件和文件夹封装成了对象,并提供了对文件和文件夹的操作方法(这些是流对象办不到的,因为流只操作数据)。
而其他语言(如c#)对文件和文件夹的操作是分开的。

File类的作用:
对文件或文件夹(目录)进行操作(新建、删除、重命名)。
File类不能操作文件中的内容,如需要则使用字节流或字符流操作。

4.1.File类常见方法
构造方法:
方法 说明
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
提示:parent指定路径(父目录),可以是File类对象也可以是字符串,child中也可以加入路径层级,但要注意,所用的路径必须存在,不存在的路径不会新建。

其他方法:
方法 说明
boolean createNewFile() 在指定目录下创建文件。
如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir() 创建此抽象路径名指定的目录。
boolean mkdirs() 创建多级目录。
boolean delete() 删除此抽象路径名表示的文件或目录。
注1:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。
注2:window的删除动作,是从里往外删。java删除文件不走回收站。要慎用。
boolean equals(Object obj) 比较的文件名字相同为true,不同为false
void deleteOnExit() 在虚拟机退出时删除。
long length() 获取文件大小。
String getName() 获得文件名或目录名
String getPath() 获得文件的路径(包含目录与文件名)
String getParent() 获得文件的上一级父目录的名字,如果此路径名没有指定父目录,则返回 null。
File getParentFile() 获得文件的上一级父目录的对象,如果此路径名没有指定父目录,则返回 null。
String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串(与getPath()类似)
long lastModified() 返回文件最后一次被修改的时间
boolean exists() 判断文件或者文件夹是否存在。
boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。
boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden() 测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。
boolean renameTo(File dest) 可以实现移动的效果。(剪切 + 重命名)。
String[] list() 列出指定目录下的当前的文件和文件夹的名称(包含隐藏文件)。
如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。
File[] listFiles() 获得文件夹下的所有文件的对象
static File listRoots() 获得当前系统的所有盘符

File类静态属性:
File.pathSeparator 返回当前系统默认的路径分隔符,windows默认为 “;”
File.Separator 返回当前系统默认的目录分隔符,windows默认为 “\”,Linux默认为“/”

/abc/

重点提示:
如果对文件或文件夹操作,开发环境使用windows,生产环境是Linux,一定要注意路径中的\,因为windows系统中与Linux系统中表现不一样。
//其中,File.separator表示系统相关的分隔符,Linux下为:/ Windows下为:\
String path = File.separator + “home” + File.separator + “siu” +
File.separator + “work” + File.separator + “demo”;

File类的基本操作示例:
public class FileTest {
public static void main(String[] args){
//构造方法
File f1 = new File(“d:\abc\120.txt”);
File f2 = new File(“d:\abc”, “120.txt”);
File f3 = new File(“http://www.taobao.com/abc/120.txt”);

关于斜杠和反斜杠,只能如下生硬地先记住吧:
\:右手用两个,比如new File(“D:\abc\” + “Test.txt”);
/:左手用一个。比如new File(“D:/abc/” + “Test.txt”);

//创建文件
f1.createNewFile();

//创建文件夹(目录)
f1.mkdir();
f1.mkdirs();

//修改文件名
f1.renameTo(new File(“d:\abc\119.txt”));
//删除文件或文件夹
f1.delete();
//获得文件属性
f1.getName();
f1.getPath();
f1.lastModified();
f1.length();

//获得指定目录下所有文件对象
f1.listFiles();

}
}

5.递归
递归是一种应用非常广泛的算法(或者编程技巧)。递归求解问题的分解过程,去的过程叫“递”,回来的过程叫“归”。

应用场景:当某一功能要重复使用时。

递归的两个条件:
(1)可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式。(自身调用)
(2)存在一种简单情境,可以使递归在简单情境下退出。(递归出口)

递归三要素:
(1)一定有一种可以退出程序的情况;
(2)是在尝试将一个问题化简到更小的规模;
(3)父问题与子问题不能有重叠的部分。

递归的分类:
递归分为两种,直接递归和间接递归。
(1)直接递归称为方法自身调用自己。
(2)间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。

递归算法的一般形式:
func( mode){
if(endCondition){ //递归出口 end;

}else{
func(mode_small) //调用本身,递归
}
}

题1:N级台阶(比如100级),每次可走1步或者2步,求总共有多少种走法?
分析:如果有大于2级的n级台阶,那么假如第一次跳一级台阶,剩下还有n-1级台阶,有f(n-1)种跳法,假如第一次跳2级台阶,剩下n-2级台阶,有f(n-2)种跳法。这就表示f(n)=f(n-1)+f(n-2),即斐波那契数列。假设只有一个台阶,那么只有一种跳法,那就是一次跳一级,f(1)=1;如果有两个台阶,那么有两种跳法,第一种跳法是一次跳一级,第二种跳法是一次跳两级,f(2)=2。
//斐波那契数列变形,求n个台阶的走法,递归方式
public int f(int n){
if(n<=2){
return n;
}
return f(n-1)+f(n+2);
}

编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

递归代码要警惕重复计算
为了避免重复计算,我们可以通过一个数据结构(比如散列表)来保存已经求解过的 f(k)。

//斐波那契数列变形,求n个台阶的走法,递归方式
public int f(int n){
//新建一个HashMap,用来保存已经求过的f(n),避免重复计算
HashMap<Integer, Integer> hashMap = new HashMap<>();
if(n<=2){
return n;
}
//求f(n)时,先判断map中是否已经存在,如果存在直接取值
if(hashMap.containsKey(n)){
return hashMap.get(n);
}
int ret = f(n-1) + f(n+2);
hashMap.put(n, ret); //将f(n)存进map中
return ret;
}

递归代码要警惕堆栈溢出
我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题,递归调用超过一定深度(比如 1000)之后,我们就不继续往下再递归了,直接抛出异常。

int depth = 0; //全局变量,表示递归的深度
//斐波那契数列变形,求n个台阶的走法,递归方式
public int f(int n){
depth++;
if(depth > 1000){
System.out.println(“超过设定深度了”);
}
//新建一个HashMap,用来保存已经求过的f(n),避免重复计算
HashMap<Integer, Integer> hashMap = new HashMap<>();
if(n<=2){
return n;
}
//求f(n)时,先判断map中是否已经存在,如果存在直接取值
if(hashMap.containsKey(n)){
return hashMap.get(n);
}
int ret = f(n-1) + f(n+2);
hashMap.put(n, ret); //将f(n)存进map中
return ret;
}

怎么将递归代码改写为非递归代码?
递归本身就是借助栈来实现的,如果我们自己在内存堆上实现栈,手动模拟入栈、出栈过程,便可以将递归改成非递归。

//斐波那契数列变形,求n个台阶的走法,非递归方式
public int f1(int n){
//n=1或n=2,直接返回f(1)=1, f(2)=2
if(n<=2){
return n;
}
int ret = 0;
int prepre = 1;
int pre = 2;
for(int i=3; i<=n; i++){
//手动摸拟栈的过程
ret = prepre + pre;
prepre = pre;
pre = ret;
}
return ret;
}

https://wenku.baidu.com/view/3e46530af12d2af90242e6b8.html
https://www.jianshu.com/p/3b0b92da124c

练习2:
列出一个文件夹下所有子文件夹以及子文件。思路:
(1)指定文件夹路径;
(2)判断是否是文件,是文件就打印文件路径(到最底层了);相反如果是目录,则继续;
(3)获取文件夹下所有文件对象;
(4)遍历对象数组,执行递归。

递归基本写法:
File fs = new File(“e:\”);
File[] fa = fs.listFiles();
for(File f : fa){
File[] fas = f.listFiles();
for(File f1 : fas){
File[] fass = f1.listFiles();
……
}
}

递归写成方法:
public static void prints(File f){
if(f.isFile()){ //判断是否是文件
System.out.println(f.getPath());
}else if(f.isDirectory()){ //如果是文件夹
File[] fs=f.listFiles(); //得到下面所有的文件对象
for(File fa:fs){
prints(fa);
}
}
}

思考:

1.删除一个目录的过程是如何进行的?

6.字节流
字节流用于处理二进制(0、1)数据的流对象。它是按字节来处理的。

设备上的数据无论是文字、图片、声音或视频,它们都以二进制存储的。
二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。
意味着,字节流可以处理设备上的所有数据,当前也包括字符数据。

6.1.字节输入/输出流
字节流主要是对文件内部的数据进行读写操作。
字节流主要分:
字节输入流:InputStream
字节输出流:OutputStream

6.2.InputStream
InputStream是所有字节输入流的父类(抽象类)。它提供了一些方法对文件内容进行操作。
方法 说明
int read() 读取下一个数据字节,如果到达文件尾,返回-1。
int read(byte[] b) 将文件流中的字节存入byte数组中(一次性读出来)。
int read(byte[] b, int off, int len) 将文件流中的最多len个字节的数据读入一个byte数组中。
void close() 关闭文件输入流并释与之有关的系统资源
long skip(long n) 从输入流中跳过并丢弃n个字节的数据。

基本操作:
public class InputStreamDemo{
public static void main(String[] args){
File file = new File(“D:\abc.txt”);
InputStream is = new FileInputStream(file);
int len = is.read();
System.out.println(len);
is.close();
}
}

6.3.OutputStream
OutputStream是所有字节输出流的父类(抽象类)。它提供了一些方法对文件内容进行操作。
方法 说明
void write(int b) 将指定字节写入文件输出流中,一次写一个字节。
void write(byte[] b) 将指定数组写入文件输出流中。
void write(byte[] b, int off, int len) write(byte[],起始位置,长度)表示从字节数组的开始位置写多长
void close() 关闭文件输入流并释与之有关的系统资源
void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。

基本操作:
public class OutputStreamDemo{
public static void main(String[] args){
File file = new File(“D:\abc.txt”);
OutputStream os = new FileOutputStream(file);
os.write(97);
os.write(98);
os.write(99);
os.close();
}
}

7.字符流
因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时 + 指定的编码表才可以解析正确数据。为了方便文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。

只要操作字符数据,优先考虑使用字符流体系。

7.1.字符输入/输出流
字符流主要分:
字符输入流:Reader
字符输出流:Writer
7.2.Reader
常用方法:
方法 说明
int read() 读一个字符
int read(char[] cbuf) 将字符读入数组
int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
int read(CharBuffer target) 将字符读入指定的字符缓冲区

示例:
public class BufferedOutputStreamTest {
public static void main(String[] args){

}
}

7.3.Writer
常用方法:
方法 说明

示例:
public class BufferedOutputStreamTest {
public static void main(String[] args){

}
}

8.文件流
即对文件的内容进行读写操作。
FileInputStream 文件输入流(读)
FileOutputStream 文件输出流(写)

8.1.1.FileInputStream
文件输入流是用于将文件或文件夹的内容数据读到内存中。
方法 说明
int read() 读取下一个数据字节,如果到达文件尾,返回-1。
int read(byte[] b) 将文件流中的字节存入byte数组中(一次性读出来)。
int read(byte[] b, int off, int len) 将文件流中的最多len个字节的数据读入一个byte数组中。
void close() 关闭文件输入流并释与之有关的系统资源
long skip(long n) 从输入流中跳过并丢弃n个字节的数据。

示例:
public class InputStreamTest {
public static void main(String[] args){

File f1 = new File(“D:\abc\120.txt”);
FileInputStream fis = new FileInputStream(f1);
//读取一个字节
int r = fis.read();
System.out.println((char)r);

//循环读取
int len;
while((len = fis.read()) != -1){
fis.skip(len); //跳过长度
System.out.println((char)len);
}

//读取多个字节
byte[] by = new byte[512]; //设置一次读取字节数
//byte[] by = new byte[f1.length()]; //一次性设置文件大小
int len;
while((len = fis.read(by)) != -1){ //通过流读取的数据存储在字节数组中
System.out.println(new String(by)); //存在bug
}

//关闭流通道并释放资源
fis.close();
}
}

8.1.2.FileOutputStream
文件输出流是用于将数据写入文件或目录的输出流。
方法 说明
void write(int b) 将指定字节写入文件输出流中,一次写一个字节。
void write(byte[] b) 将指定数组写入文件输出流中。
void write(byte[] b, int off, int len) write(byte[],起始位置,长度)表示从字节数组的开始位置写多长
void close() 关闭文件输入流并释与之有关的系统资源
void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。

示例:
public class OutputStreamTest {
public static void main(String[] args){

File f1 = new File(“D:\abc\120.txt”);
FileOutputStream fos = new FileOutputStream(f1);
//写入一个字节
fos.writer(97);
fos.writer(100);

//写入多个字节
String s1 = “大吉大利,今晚吃鸡”;
fos.writer(s1.getBytes()); //将字符串转换成字节数组,再进行写入

//写入部分字节
String s2 = “大吉大利,今晚吃鸡”;
fos.writer(s2.getBytes(), 0, 4); //将字符串下标为0位置开始,写入连续四个字符

//关闭流通道
fos.close();
}
}

案例:实现文件的复制和剪切功能。(边读边写)

9.缓冲流
上面学习了基本的一些流,作为IO流的入门,今天我们要见识一些更强大的流。比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等。这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。

缓冲流也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:
字节缓冲流:BufferedInputStream、BufferedOutputStream
字符缓冲流:BufferedReader、BufferedWriter

缓冲流的基本原理:
在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

缓冲流的目的:
为了减少磁盘IO的开销,提高文件的输入流和输出流的性能。

注意:以后用到InputStream或OutputStream时尽量用BufferedInputStream或BufferedOutputStream包装一次。

9.1.字节缓冲流
9.1.1.BufferedInputStream
构造方法:
方法 说明
BufferedInputStream(InputStream in) 创建一个新的缓冲输入流。
BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流。

构造方法示例:
public class BufferedInputStreamTest {
public static void main(String[] args){
//创建字节级冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(“bis.txt”));
//创建辽节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(“bos.txt”));
}
}

常用方法(与InputStream相同):
方法 说明
int read() 读取下一个数据字节,如果到达文件尾,返回-1。
int read(byte[] b) 将文件流中的字节存入byte数组中(一次性读出来)。
int read(byte[] b, int off, int len) 将文件流中的最多len个字节的数据读入一个byte数组中。
void close() 关闭文件输入流并释与之有关的系统资源
long skip(long n) 从输入流中跳过并丢弃n个字节的数据。

示例:
public class BufferedInputStreamTest {
public static void main(String[] args){
File f =new File(“e:\abc.txt”);
FileInputStream fis = new FileInputStream(f);
//创建缓冲输入流
BufferedInputStream bis=new BufferedInputStream(fi);
byte[] bs=new byte[(int)f.length()];
bis.read(bs);
System.out.println(new String(bs)); //输出内容
bis.close();
fi.close();
}
}

问题1:为什么要用Buffered包装下?
因为它们原理是一样的,调的方法也是一样,Buffered在使用时,性能比File的要强一些。
问题2:输出内容时会出现白板?
这是关于编码的问题。

9.1.2.BufferedOutputStream
常用方法:
方法 说明
void write(int b) 将指定字节写入文件输出流中,一次写一个字节。
void write(byte[] b) 将指定数组写入文件输出流中。
void write(byte[] b, int off, int len) write(byte[],起始位置,长度)表示从字节数组的开始位置写多长
void close() 关闭文件输入流并释与之有关的系统资源
void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。

示例:
public class BufferedOutputStreamTest {
public static void main(String[] args){
File f = new File(“e:\abc.txt”);
FileOutputStream fos = new FileOutputStream(f);
//创建字节级冲输出流
BufferedOutputStream bos = new BufferedOutputStream(fos);
//要写入的内容
String s = “abcd”;
//getBytes()将字符串转成字节数组byte[]
bos.writer(s.getBytes());
bos.close();
fos.close();
}
}

9.1.3.效率测试
查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(375MB),测试它的效率。

基本流代码如下:
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
FileInputStream fis = new FileInputStream(“jdk9.exe”);
FileOutputStream fos = new FileOutputStream(“copyJDK9.exe”);
){
// 读写数据
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println(“普通流复制时间:”+(end - start)+" 毫秒");
}
}

缓冲流代码如下:
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(“jdk9.exe”));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(“copyJDK9.exe”));
){
// 读写数据
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println(“缓冲流复制时间:”+(end - start)+" 毫秒");
}
}

如何更快呢?
使用数组的方式,代码如下:
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(“jdk9.exe”));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(“copy.exe”));
){
// 读写数据
int len;
byte[] bytes = new byte[8*1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println(“缓冲流使用数组复制时间:”+(end - start)+" 毫秒");
}
}

10.对象流
10.1.1.ObjectInputStream
常用方法:
方法 说明

示例:
public class ObjectInputStreamTest {
public static void main(String[] args){

}
}

10.1.2.ObjectOutputStream
常用方法:
方法 说明

示例:
public class ObjectOutputStreamTest {
public static void main(String[] args){

}
}

11.数据流(了解)
11.1.1.DataInputStream
常用方法:
方法 说明

示例:
public class DataInputStreamTest {
public static void main(String[] args){

}
}

11.1.2.DataOutputStream
常用方法:
方法 说明

示例:
public class DataOutputStreamTest {
public static void main(String[] args){

}
}

12.内存流(了解)
12.1.1.ByteArrayInputStream
常用方法:
方法 说明

示例:
public class ByteArrayInputStreamTest {
public static void main(String[] args){

}
}

12.1.2.ByteArrayOutputStream
常用方法:
方法 说明

示例:
public class ByteArrayOutputStreamTest {
public static void main(String[] args){

}
}

13.打印流
13.1.1.PrintOutputStream
常用方法:
方法 说明

示例:
public class PrintOutputStreamTest {
public static void main(String[] args){

}
}

13.2.FileReader

13.3.FileWriter

close()和flush()的区别:
flush():将缓冲区的数据刷到目的地中后,流可以使用。
close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。
io异常的处理方式:io一定要写finally;


public static void main(String[] args) throws IOException { //读、写都会发生IO异常
/*
1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。
2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。
3:如果指定位置,出现了同名文件,文件会被覆盖。
/
FileWriter fw = new FileWriter(“demo.txt”); // FileNotFoundException
/

调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。怎么把数据弄到文件中?
*/
fw.write(“abcde”);
fw.flush(); // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。
fw.close(); // 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。
}


FileWriter写入数据的细节:
1:window中的换行符:\r\n两个符号组成。 linux:\n。
2:续写数据,只要在构造方法中传入新的参数true。
3:目录分割符:window \ /

public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter(“demo.txt”,true);
fw.write(“abcde”);
}
catch (IOException e ){
System.out.println(e.toString()+"…");
}
finally{
if(fw!=null)
try{
fw.close();
}
catch (IOException e){
System.out.println(“close:”+e.toString());
}
}
}

FileReader:使用Reader体系,读取一个文本文件中的数据。返回 -1 ,标志读到结尾。
import java.io.;
class FileReaderDemo {
public static void main(String[] args) throws IOException {
/

创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。
*/
FileReader fr = new FileReader(“demo.txt”);
int ch = 0;
while((ch = fr.read())!= -1) { //条件是没有读到结尾
System.out.println((char)ch); //调用读取流的read方法,读取一个字符。
}
fr.close();
}
}

读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。
import java.io.*;
class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader(“demo.txt”); //创建读取流对象和指定文件关联。
//因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。
char[] buf = new char[1024];
int len = 0;
while(( len=fr.read(buf)) != -1) {
System.out.println(new String(buf,0,len));
}
fr.close();
}
}

IO中的使用到了一个设计模式:装饰设计模式。
装饰设计模式解决:对一组类进行功能的增强。
包装:写一个类(包装类)对被包装对象进行包装;

  • 1、包装类和被包装对象要实现同样的接口;
  • 2、包装类要持有一个被包装对象;
  • 3、包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;

13.4.第三节 综合案例 :文件搜索
搜索D:\aaa 目录中的.java 文件。
分析:

目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录。

遍历目录时,获取的子文件,通过文件名称,判断是否符合条件。

代码实现:
public class DiGuiDemo3 {
   public static void main(String[] args) {
       // 创建File对象
       File dir  = new File(“D:\aaa”);
    // 调用打印目录方法
       printDir(dir);
  }

   public static void printDir(File dir) {
    // 获取子文件和目录
       File[] files = dir.listFiles();
   
    // 循环打印
       for (File file : files) {
           if (file.isFile()) {
            // 是文件,判断文件名并输出文件绝对路径
               if (file.getName().endsWith(".java")) {
                   System.out.println(“文件名:” + file.getAbsolutePath());
              }
          } else {
               // 是目录,继续遍历,形成递归
               printDir(file);
          }
      }
  }
}
13.5.3.2 文件过滤器优化
java.io.FileFilter是一个接口,是File的过滤器。 该接口的对象可以传递给File类的listFiles(FileFilter) 作为参数, 接口中只有一个方法。
boolean accept(File pathname) :测试pathname是否应该包含在当前File目录中,符合则返回true。
分析:
1.
接口作为参数,需要传递子类对象,重写其中方法。我们选择匿名内部类方式,比较简单。
2.
3.
accept方法,参数为File,表示当前File下所有的子文件和子目录。保留住则返回true,过滤掉则返回false。保留规则:
4.
1.
要么是.java文件。
2.
3.
要么是目录,用于继续遍历。
4.
5.
通过过滤器的作用,listFiles(FileFilter)返回的数组元素中,子文件对象都是符合条件的,可以直接打印。
6.
代码实现:
public class DiGuiDemo4 {
   public static void main(String[] args) {
       File dir = new File(“D:\aaa”);
       printDir2(dir);
  }
 
   public static void printDir2(File dir) {
    // 匿名内部类方式,创建过滤器子类对象
       File[] files = dir.listFiles(new FileFilter() {
           @Override
           public boolean accept(File pathname) {
               return pathname.getName().endsWith(".java")||pathname.isDirectory();
          }
      });
    // 循环打印
       for (File file : files) {
           if (file.isFile()) {
               System.out.println(“文件名:” + file.getAbsolutePath());
          } else {
               printDir2(file);
          }
      }
  }
}      
13.6.3.3 Lambda优化
分析:FileFilter是只有一个方法的接口,因此可以用lambda表达式简写。
lambda格式:
()->{ }
代码实现:
public static void printDir3(File dir) {
// lambda的改写
   File[] files = dir.listFiles(f ->{
    return f.getName().endsWith(".java") || f.isDirectory();
  });

// 循环打印

for (File file : files) {
       if (file.isFile()) {
           System.out.println(“文件名:” + file.getAbsolutePath());
    } else {
      printDir3(file);
    }
  }
}



SequenceInputStream:序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
该对象的构造方法参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。
但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。

合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。

import java.io.;
import java.util.
;
class SplitFileDemo{
private static final String CFG = “.properties”;
private static final String SP = “.part”;
public static void main(String[] args) throws IOException{
File file = new File(“c:\0.bmp”);
File dir = new File(“c:\partfiles”);
meger(dir);
}
//数据的合并。
public static void meger(File dir)throws IOException{
if(!(dir.exists() && dir.isDirectory()))
throw new RuntimeException(“指定的目录不存在,或者不是正确的目录”);
File[] files = dir.listFiles(new SuffixFilter(CFG));
if(files.length==0)
throw new RuntimeException(“扩展名.proerpties的文件不存在”);
//获取到配置文件
File config = files[0];
//获取配置文件的信息。
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(config);
prop.load(fis);
String fileName = prop.getProperty(“filename”);
int partcount = Integer.parseInt(prop.getProperty(“partcount”));
//--------------------------
File[] partFiles = dir.listFiles(new SuffixFilter(SP));
if(partFiles.length!=partcount)
throw new RuntimeException(“缺少碎片文件”);
//---------------------
ArrayList al = new ArrayList();
for(int x=0; x<partcount; x++){
al.add(new FileInputStream(new File(dir,x+SP)));
}
Enumeration en = Collections.enumeration(al);
SequenceInputStream sis = new SequenceInputStream(en);
File file = new File(dir,fileName);
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
//带有配置信息的数据切割。
public static void splitFile(File file)throws IOException{
//用一个读取流和文件关联。
FileInputStream fis = new FileInputStream(file);
//创建目的地。因为有多个。所以先创建引用。
FileOutputStream fos = null;
//指定碎片的位置。
File dir = new File(“c:\partfiles”);
if(!dir.exists())
dir.mkdir();
//碎片文件大小引用。
File f = null;
byte[] buf = new byte[1024*1024];
//因为切割完的文件通常都有规律的。为了简单标记规律使用计数器。
int count = 0;
int len = 0;
while((len=fis.read(buf))!=-1){
f = new File(dir,(count++)+".part");
fos = new FileOutputStream(f);
fos.write(buf,0,len);
fos.close();
}
//碎片文件生成后,还需要定义配置文件记录生成的碎片文件个数。以及被切割文件的名称。
//定义简单的键值信息,可是用Properties。
String filename = file.getName();
Properties prop = new Properties();
prop.setProperty(“filename”,filename);
prop.setProperty(“partcount”,count+"");
File config = new File(dir,count+".properties");
fos = new FileOutputStream(config);
prop.store(fos,"");
fos.close();
fis.close();
}
}
class SuffixFilter implements FileFilter{
private String suffix;
SuffixFilter(String suffix){
this.suffix = suffix;
}
public boolean accept(File file){
return file.getName().endsWith(suffix);
}
}

RandomAccessFile:
特点:
1:该对象即可读取,又可写入。
2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组。
3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。
4:该对象操作的源和目的必须是文件。
5:其实该对象内部封装了字节读取流和字节写入流。
注意:实现随机访问,最好是数据有规律。

class RandomAccessFileDemo{
public static void main(String[] args) throws IOException{
write();
read();
randomWrite();
}
//随机写入数据,可以实现已有数据的修改。
public static void randomWrite()throws IOException{
RandomAccessFile raf = new RandomAccessFile(“random.txt”,“rw”);
raf.seek(84);
System.out.println(“pos :”+raf.getFilePointer());
raf.write(“王武”.getBytes());
raf.writeInt(102);
raf.close();
}
public static void read()throws IOException{
RandomAccessFile raf = new RandomAccessFile(“random.txt”,“r”);//只读模式。
//指定指针的位置。
raf.seek(8
1);//实现随机读取文件中的数据。注意:数据最好有规律。
System.out.println(“pos1 :”+raf.getFilePointer());
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+"::"+age);
System.out.println(“pos2 :”+raf.getFilePointer());
raf.close();
}
public static void write()throws IOException{
//rw:当这个文件不存在,会创建该文件。当文件已存在,不会创建。所以不会像输出流一样覆盖。
RandomAccessFile raf = new RandomAccessFile(“random.txt”,“rw”);//rw读写模式
//往文件中写入人的基本信息,姓名,年龄。
raf.write(“张三”.getBytes());
raf.writeInt(97);
raf.close();
}
}

管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。
注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。
public static void main(String[] args) throws IOException{
PipedInputStream pipin = new PipedInputStream();
PipedOutputStream pipout = new PipedOutputStream();
pipin.connect(pipout);
new Thread(new Input(pipin)).start();
new Thread(new Output(pipout)).start();
}

对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。
注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。

如何将非静态的数据不进行序列化?用transient 关键字修饰此变量即可。

Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。

import java.io.*;
class ObjectStreamDemo {
public static void main(String[] args) throws Exception{
writeObj();
readObj();
}
public static void readObj()throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“obj.txt”));
Object obj = ois.readObject();//读取一个对象。
System.out.println(obj.toString());
}
public static void writeObj()throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“obj.txt”));
oos.writeObject(new Person(“lisi”,25)); //写入一个对象。
oos.close();
}
}
class Person implements Serializable{
private static final long serialVersionUID = 42L;
private transient String name;//用transient修饰后name将不会进行序列化
public int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return name+"::"+age;
}
}

DataOutputStream、DataInputStream:专门用于操作基本数据类型数据的对象。
DataOutputStream dos = new DataOutputStream(new FileOutputStream(“data.txt”));
dos.writeInt(256);
dos.close();

DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
int num = dis.readInt();
System.out.println(num);
dis.close();

ByteArrayInputStream:源:内存
ByteArrayOutputStream:目的:内存。
这两个流对象不涉及底层资源调用,操作的都是内存中数组,所以不需要关闭。
直接操作字节数组就可以了,为什么还要把数组封装到流对象中呢?因为数组本身没有方法,只有一个length属性。为了便于数组的操作,将数组进行封装,对外提供方法操作数组中的元素。

对于数组元素操作无非两种操作:设置(写)和获取(读),而这两操作正好对应流的读写操作。这两个对象就是使用了流的读写思想来操作数组。
//创建源:
ByteArrayInputStream bis = new ByteArrayInputStream(“abcdef”.getBytes());
//创建目的:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int ch = 0;
while((ch=bis.read())!=-1){
bos.write(ch);
}

14.字节流
InputStream:是表示字节输入流的所有类的超类。
|— FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|— FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
|— BufferedInputStream:该类实现缓冲的输入流。
|— Stream:
|— ObjectInputStream:
|— PipedInputStream:

OutputStream:此抽象类是表示输出字节流的所有类的超类。
|— FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
|— FilterOutputStream:此类是过滤输出流的所有类的超类。
|— BufferedOutputStream:该类实现缓冲的输出流。
|— PrintStream:
|— DataOutputStream:
|— ObjectOutputStream:
|— PipedOutputStream:

缓冲区是提高效率用的,给谁提高呢?
BufferedWriter:是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。
FileWriter fw = new FileWriter(“bufdemo.txt”);
BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。
for(int x=0; x<4; x++){
bufw.write(x+“abc”);
bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。
bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。
}
bufw.close();//关闭缓冲区,其实就是在关闭具体的流。

BufferedReader:
FileReader fr = new FileReader(“bufdemo.txt”);
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null){ //readLine方法返回的时候是不带换行符的。
System.out.println(line);
}
bufr.close();

//记住,只要一读取键盘录入,就用这句话。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//输出到控制台
String line = null;
while((line=bufr.readLine())!=null){
if(“over”.equals(line))
break;
bufw.write(line.toUpperCase());//将输入的字符转成大写字符输出
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();

流对象:其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。

流的操作规律:
1,明确源和目的。
数据源:就是需要读取,可以使用两个体系:InputStream、Reader;
数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer;
2,操作的数据是否是纯文本数据?
如果是:数据源:Reader
数据汇:Writer
如果不是:数据源:InputStream
数据汇:OutputStream
3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?
明确操作的数据设备。
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。
4,需要在基本操作上附加其他功能吗?比如缓冲。
如果需要就进行装饰。

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。

转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。

发现转换流有一个子类就是操作文件的字符流对象:
InputStreamReader
|–FileReader
OutputStreamWriter
|–FileWrier

想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
FileReader fr = new FileReader(“a.txt”);
InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”),“gbk”);
以上两句代码功能一致,
如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader(“a.txt”); //因为简化。

如果需要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。

凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。

15.缓冲流
昨天学习了基本的一些流,作为IO流的入门,今天我们要见识一些更强大的流。比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等。这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。

缓冲流也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:
字节缓冲流:BufferedInputStream、BufferedOutputStream
字符缓冲流:BufferedReader、BufferedWriter

缓冲流的基本原理:
在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

缓冲流的目的:
为了减少磁盘IO的开销,提高文件的输入流和输出流的性能。

注意:以后用到InputStream或OutputStream时尽量用BufferedInputStream或BufferedOutputStream包装一次。

15.1.字节缓冲流
数组复制时间:666 毫秒
15.2.字符缓冲流
构造方法
public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

构造举例,代码如下:
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader(“br.txt”));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter(“bw.txt”));
15.3.特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。

BufferedReader:public String readLine(): 读一行文字。
BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。

readLine方法演示,代码如下:
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();
  }
}
newLine方法演示,代码如下:
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.newLine();
       bw.write(“员”);
       bw.newLine();
// 释放资源
       bw.close();
  }
}
输出效果:
智邦
程序

1.4 练习:文本排序
请将文本信息恢复顺序。
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
15.3.1.案例分析
1.逐行读取文本信息。
2.解析文本信息到集合中。
3.遍历集合,按顺序,写出文本信息。
15.3.2.案例实现
public class BufferedTest {
   public static void main(String[] args) throws IOException {
       // 创建map集合,保存文本数据,键为序号,值为文字
       HashMap<String, String> lineMap = new HashMap<>();

       // 创建流对象
       BufferedReader br = new BufferedReader(new FileReader(“in.txt”));
       BufferedWriter bw = new BufferedWriter(new FileWriter(“out.txt”));

       // 读取数据
       String line  = null;
       while ((line = br.readLine())!=null) {
           // 解析文本
           String[] split = line.split("\.");
           // 保存到集合
           lineMap.put(split[0],split[1]);
      }
       // 释放资源
       br.close();

       // 遍历map集合
       for (int i = 1; i <= lineMap.size(); i++) {
           String key = String.valueOf(i);
           // 获取map中文本
           String value = lineMap.get(key);
        // 写出拼接文本
           bw.write(key+"."+value);
        // 写出换行
           bw.newLine();
      }
// 释放资源
       bw.close();
  }
}

16.转换流
16.1.2.1 字符编码和字符集
16.1.1.字符编码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
编码:字符(能看懂的)–字节(看不懂的)
解码:字节(看不懂的)–>字符(能看懂的)

字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

编码表:生活中文字和计算机中二进制的对应规则

16.1.2.字符集

字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

ASCII字符集 :

o
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
o
o
基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
o

ISO-8859-1字符集:

o
拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
o
o
ISO-8859-1使用单字节编码,兼容ASCII编码。
o

GBxxx字符集:

o
GB就是国标的意思,是为了显示中文而设计的一套字符集。
o
o
GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
o
o
GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
o
o
GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
o

Unicode字符集 :

o
Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
o
o
它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
o
o
UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
o
1.
128个US-ASCII字符,只需一个字节编码。
2.
3.
拉丁文等字符,需要二个字节编码。
4.
5.
大部分常用字(含中文),使用三个字节编码。
6.
7.
其他极少使用的Unicode辅助字符,使用四字节编码。
8.
16.2.2.2 编码引出的问题
在IDEA中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
public class ReaderDemo {
   public static void main(String[] args) throws IOException {
       FileReader fileReader = new FileReader(“E:\File_GBK.txt”);
       int read;
       while ((read = fileReader.read()) != -1) {
           System.out.print((char)read);
      }
       fileReader.close();
  }
}
输出结果:
���
那么如何读取GBK编码的文件呢?
16.3.2.3 InputStreamReader类
转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
16.3.1.构造方法

InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。


InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

构造举例,代码如下:
InputStreamReader isr = new InputStreamReader(new FileInputStream(“in.txt”));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(“in.txt”) , “GBK”);
16.3.2.指定编码读取
public class ReaderDemo2 {
   public static void main(String[] args) throws IOException {
    // 定义文件路径,文件为gbk编码
       String FileName = “E:\file_gbk.txt”;
    // 创建流对象,默认UTF8编码
       InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
    // 创建流对象,指定GBK编码
       InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , “GBK”);
// 定义变量,保存字符
       int read;
    // 使用默认编码字符流读取,乱码
       while ((read = isr.read()) != -1) {
           System.out.print((char)read); // ��Һ�
      }
       isr.close();
     
    // 使用指定编码字符流读取,正常解析
       while ((read = isr2.read()) != -1) {
           System.out.print((char)read);// 大家好
      }
       isr2.close();
  }
}
16.4.2.4 OutputStreamWriter类
转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
16.4.1.构造方法

OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。


OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

构造举例,代码如下:
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream(“out.txt”));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream(“out.txt”) , “GBK”);
16.4.2.指定编码写出
public class OutputDemo {
   public static void main(String[] args) throws IOException {
    // 定义文件路径
       String FileName = “E:\out.txt”;
    // 创建流对象,默认UTF8编码
       OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
       // 写出数据
    osw.write(“你好”); // 保存为6个字节
       osw.close();
   
// 定义文件路径
String FileName2 = “E:\out2.txt”;
    // 创建流对象,指定GBK编码
       OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),“GBK”);
       // 写出数据
    osw2.write(“你好”);// 保存为4个字节
       osw2.close();
  }
}
16.4.3.转换流理解图解
转换流是字节与字符间的桥梁!
16.5.2.5 练习:转换文件编码
将GBK编码的文本文件,转换为UTF-8编码的文本文件。
16.5.1.案例分析
1.
指定GBK编码的转换流,读取文本文件。
2.
3.
使用UTF-8编码的转换流,写出文本文件。
4.
16.5.2.案例实现
public class TransDemo {
  public static void main(String[] args) {      
  // 1.定义文件路径
    String srcFile = “file_gbk.txt”;
       String destFile = “file_utf8.txt”;
// 2.创建流对象
  // 2.1 转换输入流,指定GBK编码
       InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , “GBK”);
  // 2.2 转换输出流,默认utf8编码
       OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
// 3.读写数据
  // 3.1 定义数组
       char[] cbuf = new char[1024];
  // 3.2 定义长度
       int len;
  // 3.3 循环读取
       while ((len = isr.read(cbuf))!=-1) {
           // 循环写出
        osw.write(cbuf,0,len);
      }
  // 4.释放资源
       osw.close();
       isr.close();
}
}

17.打印流
17.1.4.1 概述
平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
17.2.4.2 PrintStream类
17.2.1.构造方法

public PrintStream(String fileName): 使用指定的文件名创建一个新的打印流。

构造举例,代码如下:
PrintStream ps = new PrintStream(“ps.txt”);
17.2.2.改变打印流向
System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏",改变它的流向。
public class PrintDemo {
   public static void main(String[] args) throws IOException {
// 调用系统的打印流,控制台直接输出97
       System.out.println(97);
     
// 创建打印流,指定文件的名称
       PrintStream ps = new PrintStream(“ps.txt”);
   
    // 设置系统的打印流流向,输出到ps.txt
       System.setOut(ps);
    // 调用系统的打印流,ps.txt中输出97
       System.out.println(97);
  }
}

18.序列化
18.1.3.1 概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:
18.2.3.2 ObjectOutputStream类
java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
18.2.1.构造方法

public ObjectOutputStream(OutputStream out): 创建一个指定OutputStream的ObjectOutputStream。

构造举例,代码如下:
FileOutputStream fileOut = new FileOutputStream(“employee.txt”);
ObjectOutputStream out = new ObjectOutputStream(fileOut);
18.2.2.序列化操作
1.
一个对象要想序列化,必须满足两个条件:
2.

该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。


该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

public class Employee implements java.io.Serializable {
   public String name;
   public String address;
   public transient int age; // transient瞬态修饰成员,不会被序列化
   public void addressCheck() {
    System.out.println("Address check : " + name + " – " + address);
  }
}
2.写出对象方法

public final void writeObject (Object obj) : 将指定的对象写出。

public class SerializeDemo{
  public static void main(String [] args)   {
  Employee e = new Employee();
  e.name = “zhangsan”;
  e.address = “beiqinglu”;
  e.age = 20;
  try {
    // 创建序列化流对象
         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“employee.txt”));
      // 写出对象
      out.writeObject(e);
      // 释放资源
      out.close();
      fileOut.close();
      System.out.println(“Serialized data is saved”); // 姓名,地址被序列化,年龄没有被序列化。
      } catch(IOException i)   {
           i.printStackTrace();
      }
  }
}
输出结果:
Serialized data is saved
18.3.3.3 ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
18.3.1.构造方法

public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream。

18.3.2.反序列化操作1
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

public final Object readObject () : 读取一个对象。

public class DeserializeDemo {
  public static void main(String [] args)   {
       Employee e = null;
       try {
            // 创建反序列化流
            FileInputStream fileIn = new FileInputStream(“employee.txt”);
            ObjectInputStream in = new ObjectInputStream(fileIn);
            // 读取一个对象
            e = (Employee) in.readObject();
            // 释放资源
            in.close();
            fileIn.close();
      }catch(IOException i) {
            // 捕获其他异常
            i.printStackTrace();
            return;
      }catch(ClassNotFoundException c) {
      // 捕获类找不到异常
            System.out.println(“Employee class not found”);
            c.printStackTrace();
            return;
      }
       // 无异常,直接打印输出
       System.out.println("Name: " + e.name); // zhangsan
       System.out.println("Address: " + e.address); // beiqinglu
       System.out.println("age: " + e.age); // 0
  }
}
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。
18.3.3.反序列化操作2
另外,当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);
    }
}
18.4.3.4 练习:序列化集合
1.
将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
2.
3.
反序列化list.txt ,并遍历集合,打印对象信息。
4.
18.4.1.案例分析
1.
把若干学生对象 ,保存到集合中。
2.
3.
把集合序列化。
4.
5.
反序列化读取时,只需要读取一次,转换为集合类型。
6.
7.
遍历集合,可以打印所有的学生信息
8.
18.4.2.案例实现
public class SerTest {
public static void main(String[] args) throws Exception {
// 创建 学生对象
Student student = new Student(“老王”, “laow”);
Student student2 = new Student(“老张”, “laoz”);
Student student3 = new Student(“老李”, “laol”);

ArrayList arrayList = new ArrayList<>();
arrayList.add(student);
arrayList.add(student2);
arrayList.add(student3);
// 序列化操作
// serializ(arrayList);

    // 反序列化  ObjectInputStream ois  = new ObjectInputStream(new FileInputStream("list.txt"));// 读取对象,强转为ArrayList类型ArrayList<Student> list  = (ArrayList<Student>)ois.readObject();

for (int i = 0; i < list.size(); i++ ){
        Student s = list.get(i);
      System.out.println(s.getName()+"–"+ s.getPwd());
    }
}

private static void serializ(ArrayList arrayList) throws Exception {
// 创建 序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“list.txt”));
// 写出对象
oos.writeObject(arrayList);
// 释放资源
oos.close();
}
}

I/O流(万流齐发、万流归宗) 本章目标: 掌握 讲  解:★★★★★ http://kuaibao.qq.com/s/20200527A0LR3000?refer=spider 1.I/O流概相关推荐

  1. Hadoop掀起大数据革命 三巨头齐发力

    开源的数据处理平台凭借其低成本.高扩展性和灵活性的优势已经赢得了多数网络巨头的认可.现在Hadoop将进入更多企业.IBM将在明年推出内置NoSQL技术的DB2旗舰级数据库管理系统.上个月Oracle ...

  2. 股市--熬过寒冬证券在市场齐发力

    终于挺过寒冬,众多新股又如l春笋般闪现.在 4月21日-5月20日的统计区间内,巾国A股有15只新 股市场发行;与上一统计区间的两只新股共融资90 多亿元相比,此统计区间内,这15个公司共募集资 金达 ...

  3. 四款 5G 版 iPhone 12 齐发,支持北斗系统,你准备好了吗?

    整理 | 郑丽媛.屠敏 头图 | CSDN下载自东方IC 真快,又见面了.北京时间 10 月 14 日凌晨 1 点,Apple 举办的新品发布会如约而至.今年有关 iPhone 新品的到来有些迟,好在 ...

  4. 三大运营商齐发力大数据

    当前以大数据.云计算.人工智能为代表的现代信息通信技术,正在引领新一轮的产业革命.大数据能够催生出极具创新力的各类应用产品,激发出全新的商业模式,改变人们的生产和生活方式,同时不断孕育出新的产业,培育 ...

  5. TAJ齐发力 互联网巨头抢滩“区块链+票据”市场

    TAJ齐发力 互联网巨头抢滩"区块链+票据"市场 区块链技术的出现并不只是带来了"加密货币",更带来了信任机制的转变,经济运行模式的转变,使经济活动更加智能和透 ...

  6. 三网齐发 HTC One行货确定4月24日发布

    为什么80%的码农都做不了架构师?>>>    台湾和香港的HTC One都发布快半个月了,我大天朝怎么可以一点动静都没有.各位喜欢HTC的同学,好消息来了,我们已经收到了HTC的邀 ...

  7. 三禧科技 工业机器人_redmi note 9 即将发布,三剑齐发! 三禧科技

    原标题:redmi note 9 即将发布,三剑齐发! 三禧科技 11月20日,Redmi官方宣布 Redmi Note 9系列来了,将于11月26日 "三剑齐发". 官宣文案中提 ...

  8. 鸿蒙 电视 安卓,华为鸿蒙2.0来了!打通手机、电视、PC全平台,Mate 40 整装齐发...

    华为鸿蒙2.0来了!打通手机.电视.PC全平台,Mate 40 整装齐发 2020-07-05 15:52:04 1点赞 0收藏 1评论 7月5日消息,据国外知名爆料玩家 Teme 透露,华为今年秋季 ...

  9. maya扇子动画_maya怎么制作一个万箭齐发的效果?

    最近有基友大学作业需要做一个粒子效果的作业,我看到网上很多教程讲万箭齐发的,但是有的不细致有的重点都在表达式上,所以我写了一个快速get作业成果的教程.具体操作位置可以看末尾笔记哦. 软件名称:Aut ...

  10. 三款新品重磅齐发!汉高亮相2021中国国际纺织面料及辅料(秋冬)博览会

    上海2021年10月11日 /美通社/ -- 10月9日至11日,汉高携多款可持续.高效能的粘合剂新品亮相2021中国国际纺织面料及辅料(秋冬)博览会(简称Intertextile或"展会& ...

最新文章

  1. 交换机两个链路相连一些设置
  2. SpringCloud Alibaba Sentinel 项目基础环境搭建
  3. mysql 可重复读 更新覆盖_Mysql事务隔离级别之可重复读
  4. 随想录(从编程语言到库、框架、软件)
  5. 3Sum Closest
  6. Anytime项目开发记录3
  7. python如何使用 b_python中的b
  8. ArcGIS图像配准方法
  9. python学习系列--str类型
  10. 区块链在切实改变世界的35个让人惊艳的实例数据库
  11. 图书管理系统/库存管理系统等计算机毕业论文设计
  12. C++层次分析法一致性检验
  13. ecshop后台getshell
  14. nacos 安装包下载 linux+windows
  15. 第一次用VNPY,通过仿真测试,踩过千万坑,我太难了~~~~~~
  16. sys.path.append方法
  17. e2e测试稳定如何保障
  18. c#语言编程:定义person类,再派生学生类如何存放学生的成绩,C#定义全班学生成绩类,包括姓名,学号,C++成绩,英语成绩,数学成绩,平均成绩...
  19. BRD文件转AD文件
  20. nodejs模拟登陆旧版正方教务系统

热门文章

  1. 学习制作横版游戏——2
  2. 军火库(第一期):无线电硬件安全大牛都用哪些利器?
  3. 解决 win7 不支持此接口 问题
  4. cts测试linux指令skip,CTS测试命令详细
  5. miflash刷机:fastboot模式/保留数据刷机
  6. 让Vim打造成强大的IDE,附_vimrc的配置和使用
  7. 外接显示器如何调整亮度
  8. hdu 5745 la vie en rose
  9. VINS-Mono 论文公式推导与代码解析
  10. 放弃腾讯75W年薪,回老家当公务员,提离职被领导教育。网友:leader嫉妒了