Java-Day-25( 字节输入流 + FileInputStream 和 FileOutputStream + FileReader 和 FileWriter + 节点流和处理流 )
Java-Day-25
InputStream ( 字节输入流 )
- InputStream 抽象类是所有类字节输入流的超类
- InputStream 常用的子类
- FileInputStream:文件输入流
- BufferedInputStream:缓冲字节输入流
- ObjectInputStream:对象字节输入流
FileInputStream 和 FileOutputStream
FileInputStream ( 文件输入流 )
- 读取 hello.txt 文件,并将文件内容显示到控制台
- read():从该输入流读取一个字节的数据,如果没有输入可用,此方法将阻止
- 返回值是数据的下一个字节,如果到文件末尾就输出 -1
- read():从该输入流读取一个字节的数据,如果没有输入可用,此方法将阻止
public class test {
public static void main(String[] args) { }
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
int readData = 0; // *1
// 字节数组(优化)
byte[] buf = new byte[8]; // 一次读取八个字节 ~1
int readLen = 0; // ~1 实际读取的字节数
// 写在外面,扩大范围,便于释放时获取
FileInputStream fileInputStream = null;
try {
// 创建对象,用于读取文件
fileInputStream = new FileInputStream(filePath);
// 存在编译异常,必须处理
// 所有想要全输出就需要while循环
// while ((readData = fileInputStream.read()) != -1) { // *1 返回值是数据的下一个字节,如果到文件末尾就输出 -1
while ((readLen = fileInputStream.read(buf)) != -1) { // ~1 读取正常,返回的是实际读取到的字节数,如果到文件末尾就输出 -1
// System.out.print((char) readData); // 因为用的int接收,所有输出原内容需要char转 *1
System.out.print(new String(buf, 0, readLen)); // 需要构建出字符串加以显示
// 在第一次读到了8个字节并存到buf数组里后,再读第二次,第二次若不满足8个就只替代读取到的数量,剩下没替代的就保存着上次剩下的,
// buf实行的是指定位置替代
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 流是一种资源,所以每次接收需要:
// 关闭文件流,释放资源(否则越来越多的流会造成资源的浪费)
// 此异常或try或抛
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 因为若是 utf-8 编码,汉字就是三个字节组成,所以字节读取时只能读到这个汉字的第一个字节 ( 但三个字节一起才是一个字 ),所以会乱码 ( 中文字符也会乱码 )
- 所以优化用 read(byte[] b),一次性存储多个字节存进 btye 数组中
- 但是中文符号依旧出现个别乱码
FileOutputStream ( 文件输出流 )
- 演示如何使用 FileOutputStream 将数据写到文件中,如果该文件不存在,则创建该文件
- 两种对象创建方法,三种写入方法
@Test
public void writeFile() {
// 创建 FileOutputStream 对象
String filePath = "e:\\hello.txt"; // 若无此文件,就加以创建先
FileOutputStream fileOutputStream = null;
try {
// 得到 FileOutputStream 对象,两个方法
// F1: new FileOutputStream(filePath)方式,当写入内容时,会覆盖原来的内容
// fileOutputStream = new FileOutputStream(filePath);
// F2: new FileOutputStream(filePath, true)方式,当写入内容时是追加到文件的后面(保留原来)
fileOutputStream = new FileOutputStream(filePath, true);
// 1. 写入一个字节: 'H'
// fileOutputStream.write('H');
// 2. 想加入多个,就写入字符串
String str = "hello, world!";
// 2.1 getBytes() 可以把字符串 ——> 字节数组
// fileOutputStream.write(str.getBytes());
// 3. write(byte[] b, int off, int len) 将 len 个字节从位于偏移量off的指定字节数组写入此文件输出流
fileOutputStream.write(str.getBytes(), 0, 3); // 只写前三个字符,
// 若是想全部输出,也可以选择在off处写:str.length()
} catch (IOException e) {
e.printStackTrace();
}
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- 演示编程完成图片 / 音乐的拷贝,将 e:\\student.jpg 拷贝到 d:\\
- 文件 1 —( 输入流 )—> java 程序 —( 输出流 )—> 文件 2
- 如果文件 1 很大,那就是读了一部分数据就输出到了 2,即读取部分数据就写入到指定文件 ( 使用循环操作 )
public class test {
public static void main(String[] args) {
// 完成文件拷贝,将 e:\\student.jpg 拷贝 d:\\
String srcFilePath = "e:\\student.jpg"; // 获取
String destFilePath = "d:\\student.jpg"; // 复制到
FileInputStream fileInputStream = null; // 创建输入流
FileOutputStream fileOutputStream = null; // 创建输出流
try {
fileInputStream = new FileInputStream(srcFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
// 定义一个字节数组,提高读取效果
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = fileInputStream.read(buf)) != -1) {
// 读取到后就写入文件,注意是:一边读一边写
fileOutputStream.write(buf, 0 , readLen); // 一定要用含有三个参数的方法
// write(buf) 的话,第一次读1024,若是第二次只剩了888,还是写进1024就会文件受损
}
System.out.println("拷贝成功~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 关闭输入流和输出流,及时释放
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileReader 和 FileWriter
- FileReader 和 FileWriter 是字符流,即按照字符来操作 IO
FileReader 常用方法
-
new FileReader(File/String):通过字符串或文件对象来构建对象
-
read:每次读取单个字符 ( 汉字就不会乱码了,仍是三个字节 ),返回该字符,到末尾返回 -1
-
read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回 -1
-
new String(char[]):将 char[] 转换成 String
-
new String(char[], off, len):将 char[] 的指定部分转换成 String
-
例:输入时 byte 汉字乱码部分时
-
-
莫忘 close,如果有多个 try / catch,就用 IOException
-
练习:
/**
* 单个字符读取
*/
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
FileReader fileReader = null;
int data = 0;
// 1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
// 循环读取 使用 read(字符一个个读取)单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data); // char转
}
} catch (IOException e) { // 有多个需要抛出catch的话,就要用IO总
e.printStackTrace();
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 使用字符数组读取文件
*/
@Test
public void readFile02() {
String filePath = "e:\\Internet.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8]; // 数组大小自行设定
// 1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
// 循环读取 使用 read(字符数组)返回的是实际读取到的字符数 ——> 汉字不会乱码
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen)); // new String
}
} catch (IOException e) { // 有多个需要抛出catch的话,就要用IO总
e.printStackTrace();
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileWriter 常用方法
-
new FileWriter(File/String):覆盖模式,相当于流的指针在首端
-
new FileWriter(File/String, true):追加模式,相当于流的指针在尾端
-
writer(int):写入单个字符
-
writer(char[]):写入指定数组
-
writer(char[], off, len):写入指定数组的指定部分
-
writer(String):写入整个字符串
-
writer(String, off, len):写入字符串的指定部分
- String 类:toCharArray 方法,将 String 转换成 char[]
- 注意指定部分时,后面的是 len 指长度,不是截止序号
-
FileWriter 使用后,必须要关闭 ( close ) 或刷新 ( flush ),否则写入不到指定的文件
-
练习:
public class test {
public static void main(String[] args) {
// 创建 FileWriter 对象
FileWriter fileWriter = null;
String filePath = "e:\\note.txt";
char[] chars = {'z', 'y', 'z'};
try {
fileWriter = new FileWriter(filePath); // 覆盖型
// - writer(int):写入单个字符
fileWriter.write('Z');
// - writer(char[]):写入指定数组
fileWriter.write(chars);
// - writer(char[], off, len):写入指定数组的指定部分
fileWriter.write("哇zyz·duang".toCharArray(), 0, 3);
// - writer(String):写入整个字符串
fileWriter.write(" hunter来了...");
// - writer(String, off, len):写入字符串的指定部分
fileWriter.write("哇zyz·duang", 5, 5);
// 在数据量大的情况下,可以使用循环操作
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 对应 FileWriter,一定要close关闭流或是flush才能真正的把数据写入到文件
// 否则就会看到只创建了文件(没有此文件名的话),却没有写入
// 原因源码可见
// fileWriter.flush();
// close 关闭文件流,其实就等价于 flush() + 关闭
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- close() —> se.close() —> this.implClose() —> this.writeBytes( 真正处理 ) —> this.out.write(this.bb.array(), this.bb.arrayOffset() + var2, var3);
节点流和处理流
-
节点流可以从一个特定的数据源读写数据 ( 比较底层,直接操作数据源 ),如:
- 针对某种具体的数据源来操作的,如访问文件:
- FileReader、FileWriter ( 使用字符方式来处理 )
- FileInputStream、FileOutputStream ( 使用字节来处理 )
- 针对一个字符串 / 数组来操作的,如访问数组:
- ByteArrayInputStream、ByteArrayOutputStream ( 字节 )
- CharArrayReader、CharArrayWriter ( 字符 )
- 访问管道、访问字符串等
- 缺点:节点流固定,灵活性不足,所以提出了处理流,如下
- 针对某种具体的数据源来操作的,如访问文件:
-
处理流 ( 也叫包装流 ),是 “ 连接 ” 在已存在的流 ( 节点流或处理流 ) 之上,为程序提供更为强大的读写功能,也更加灵活,如
-
缓冲流,不再局限于数组或是其他什么数据源了,用字符的方式:
- BufferedReader ( class 类内有属性 Reader,即可以封装一个节点流 —> 节点流可以任意,只要是 Reader 的一个子类就行 )
- BufferedWriter ( 同理,也有 Writer 属性,可以封装任一 Writer 的子类 )
-
部分源码:
public class BufferedWriter extends Writer { private Writer out; // ...... // 构造器其一: public BufferedWriter(Writer out)
-
上图构造器可见,能放入 CharArrayWriter、StringWriter 等对象,这便是包装流 ( 一种修饰器模式的设计模式 )
-
-
节点流和处理流都可分为字节和字符,字节和字符又分为输入流或是输出流
区别和联系
-
节点流是底层流 / 低级流,直接跟数据源相接
-
处理流 ( 包装流 ) 包装节点流,既可以消除不同节点流的实现差异 ( 有些对文件操作,有些对数组操作 ),也可以提供更方便的方法来完成输入和输出 ( 想对谁操作就传入哪个相关的实现子类 )
-
处理流 ( 包装流 ) 对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连 ( 只是调用 )
-
Reader_ 抽象类:
public abstract class Reader_ { public void readFile() { // FileReader_ 实现 } public void readString() { // StringReader_ 实现 } }
-
FileReader_ 实现类
public class FileReader_ extends Reader_ { // 节点流 public void readFile() { System.out.println("对文件进行读取..."); } }
-
StringReader_ 实现类
public class StringReader_ extends Reader_ { // 节点流 public void readString() { System.out.println("读取字符串..."); } }
-
BufferedReader_
public class BufferedReader_ extends Reader_ { // 包装流 private Reader_ reader_; // 属性就是 Reader_ 类型 public Reader_ getReader_() { // F1:为了在Test中也能够调用传入的对象的方法 return reader_; } // 构造器接收Reader_子类对象 public BufferedReader_(Reader_ reader_) { this.reader_ = reader_; } // 在此类中让方法更加灵活,如: // 扩展为多次读取文件 public void readFiles(int num) { for (int i = 0; i < num; i++) { reader_.readFile(); } } // F2:可以选择放入原封不动的实现的方法 public void readString() { // 相当封装了一层 reader_.readString(); } // 再扩展 readString(),使之批量处理字符串数据 public void readStrings(int num) { for (int i = 0; i < num; i++) { reader_.readString(); } } // 在一定程度上扩展了原先的FileReader_和StringReader_ }
-
Test.java:
public class Test { public static void main(String[] args) { System.out.println("对文件操作~~~~~~~~~~~~~"); BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_()); bufferedReader_.readFile(); // F1:bufferedReader_里没有此方法,所以调用了父类的readFile()空方法 // 应该拿到bufferedReader_的放入的实现类对象(私有属性要get),才能调用实现类中实现了的方法 System.out.println("既可以调用FileReader_实现类读只一次:"); bufferedReader_.getReader_().readFile(); // 此方法是在BufferedReader_类里,借用实现类的实现方法进行增强,可直接调用 System.out.println("也可以用bufferedReader_进行增强的方法,从而读多次:"); bufferedReader_.readFiles(2); System.out.println("对字符串操作~~~~~~~~~~~~~~~"); BufferedReader_ bufferedReader_2 = new BufferedReader_(new StringReader_()); // F2:或者是更简单的,既然想能够直接调用传入实现类的方法,那就在BufferedReader_类里接收、封装下就好 bufferedReader_2.readString(); // 这样就可以直接用来 bufferedReader_2.readStrings(2); // 增强后 } }
-
以上是为了区分明显,实则也可以:
// Reader_: public abstract class Reader_ { // 抽象类 Reader // 在 Reader_ 抽象类,使用read抽象方法统一管理 // 后面调用时,利用对象动态绑定机制,绑定到对应的实现子类即可 public abstract void read(); } // FileReader_: public class FileReader_ extends Reader_ { public void read() { // 这样用多态的动态绑定机制,绑定到对应的实现子类FileReader_即可 System.out.println("对文件进行读取..."); } } // BufferedReader_: public class BufferedReader_ extends Reader_ { private Reader_ reader_; // 属性就是 Reader_ 类型 // 构造器接收Reader_子类对象 public BufferedReader_(Reader_ reader_) { this.reader_ = reader_; } @Override public void read() { reader_.read(); } public void reads(int num) { for (int i = 0; i < num; i++) { reader_.read(); } } } // Test_: BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_()); bufferedReader_.read(); bufferedReader_.reads(2);
-
处理流的功能主要体现:
- 性能的提高:主要以增加缓冲的方式来提高输入和输出的效率
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
处理流 BufferedReader 和 BufferedWriter
-
两者属于字符流,是按照字符来读取数据的
- 尽量是文本文件会比较高效
- 因为二进制文件 ( 图片、视频等 ) 是按照字节组织的,可能会造成损毁
-
关闭处理流时,只需要关闭外层流即可
- 即,
BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_());
时,只需要关闭 bufferedReader_,内部 FileReader ( 包装的节点流 ) 会在底层自动关闭
- 即,
练习
-
使用 BufferedReader 读取文件,并显示在控制台
public class Test { public static void main(String[] args) throws Exception { String filePath = "e:\\index.jsp"; // 创建 BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); // 读取 String line; // 按行读取: // 1. bufferedReader.readLine() 是按行来读取文件 // 2. 此方法当文件读取完毕到达流末尾时,就返回null while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } // 关闭流,只需要关闭BufferedReader即可,因为底层会自动关节点流 /* private Reader in; * ... * if (in == null) return; try { in.close(); // 节点流关闭 } finally { in = null; cb = null; } */ bufferedReader.close(); } }
-
使用 BufferedWriter 将 “ 哈罗呀,朱呀朱 ”,写入到文件中
public class Test { public static void main(String[] args) throws Exception { String filePath = "e:\\zyz.txt"; // 创建 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath, true)); // 要写入多句的话就插入一个换行 newLine bufferedWriter.write("哈罗呀,朱呀朱No1"); bufferedWriter.newLine(); // 插入一个和系统相关的换行 bufferedWriter.write("哈罗呀,朱呀朱No2"); bufferedWriter.newLine(); bufferedWriter.write("哈罗呀,朱呀朱No3"); bufferedWriter.newLine(); // 关闭包装流 bufferedWriter.close(); } }
- 想要不被覆盖还得在节点流中操作 ( 如 FileWriter 加上 true 成追加方式 ),BufferedWriter 构造器没有防覆盖操作
-
综合使用 BufferedReader 和 BufferedWriter 完成文本文件的拷贝
- 切记不要忘了是字符操作,不要操作二进制文件 ( 声音、视频、doc、pdf 等 )
public class Test { public static void main(String[] args) { String srcFilePath = "e:\\index.jsp"; String desFilePath = "e:\\zyzWa.txt"; // 创建 BufferedReader br = null; BufferedWriter bw = null; String line; try { br = new BufferedReader(new FileReader(srcFilePath)); bw = new BufferedWriter(new FileWriter(desFilePath, true)); // readLine 读取了一行内容,但没用换行,所以还要自行插入换行 while ((line = br.readLine()) != null) { // 每读取一行,就写入 bw.write(line); // 插入一个换行 bw.newLine(); } System.out.println("拷贝完毕~"); } catch (IOException e) { e.printStackTrace(); } finally { try { if (br != null) { br.close(); } if (bw != null) { bw.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
- try-catch 是直接处理,而 throw 是将异常抛给了上级
处理流 BufferedInputStream 和 BufferedOutputStream
- BufferedInputStream 是字节流,在创建BufferedInputStream 时,会创建一个内部缓冲区数组
- InputStream 实现类 in 在 FilterInputStream 里获取,在 BufferedInputStream 里操作
- buf 是缓冲区数组
- BufferedOutputStream 是字节流,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每次字节写入调用底层系统
练习
public class Test {
public static void main(String[] args) {
String srcFilePath = "e:\\student.jpg";
// String filePath = "e:\\student.jpg";
String destFilePath = "e:\\zyz.jpg";
// 创建对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// FileInputStream 是 InputStream 的子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
// 循环地读取文件,并写入到 des文件路径下
byte[] buff = new byte[1024];
int readLen;
// 返回-1 表示读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}
System.out.println("文件拷贝完成~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭处理流,节点流在底层会自行关闭
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 因为是字节流,所以图片、音频 ( 二进制文件)、文本都可以操作的