打怪升级之小白的大数据之旅(二十四)<Java面向对象进阶之IO流二 字节流与字符流>
打怪升级之小白的大数据之旅(二十四)
Java面向对象进阶之IO流二 字节流与字符流
上次回顾
上一章介绍了IO流的基本概念以及FIle类与IO的基类,本章将对最常使用的两大流字节流与字符流进行讲解,今天内容结束后,我们就可以手撸一个复制视频/图片/音频的小程序啦…
字节流
- 字节流就跟它的名字一样,它的存储格式是以字节进行存储的,它将一切文件数据(文本、图片、视频、音频)等,全部以二进制数字的形式保存
- 同样的,在数据传输时,都是一个一个字节进行传输,所以,字节流可以传输任意的文件数据
- 字节流的操作分为字节输出流和字节输入流
OutputStream 字节输出流
- java.io.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):将指定的字节输出流
- 注:当完成流的操作时,必须调用
close()方法,释放系统资源,不调用的后果,我等下会提到 - OutputStream有很多子类,这里我们就介绍一个FileOutputStream,其他的子类操作基本雷同
FileOutputStream
java.io.FileOutputStream类是文件输出流,用于将数据写出到文件
当创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据
构造方法
public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件- 示例代码:
public class FileOutputStreamConstructor throws IOException { public static void main(String[] args) { // 使用File对象创建流对象 File file = new File("a.txt"); FileOutputStream fos = new FileOutputStream(file); // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("b.txt"); } }
输出字节数据
- 写出字节:
write(int b)方法,每次可以写出一个字节数据- 示例代码:
public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 写出数据 fos.write(97); // 写出第1个字节 fos.write(98); // 写出第2个字节 fos.write(99); // 写出第3个字节 // 关闭资源 fos.close(); } }
- 示例代码:
- 写出字节数组:
write(byte[] b),每次可以写出数组中的数据- 示例代码:
public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 字符串转换为字节数组 byte[] b = "大数据".getBytes(); // 写出字节数组数据 fos.write(b); // 关闭资源 fos.close(); } }
- 示例代码:
- 写出指定长度字节数组:
write(byte[] b, int off, int len),每次写出从off索引开始,len个字节- 示例代码:
public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 字符串转换为字节数组 byte[] b = "abcde".getBytes(); // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。 fos.write(b,2,2); // 关闭资源 fos.close(); } }
- 示例代码:
- 上面的示例中,小伙伴们有没有发现,我们每次运行,都会覆盖掉前面的结果,那么怎么才能不覆盖结果而是追加呢?
- public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
- public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件
- 这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了
- 示例代码:
public class FOSWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt",true); // 字符串转换为字节数组 byte[] b = "abcde".getBytes(); // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。 fos.write(b); // 关闭资源 fos.close(); } }
InputStream字节输入流
- 介绍完输出流,接下来,我来介绍输入流,和输出流一样,InputStream也是输入流的基类
- 它的共有方法如下:
- public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
- public abstract int read(): 从输入流读取数据的下一个字节。
- public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中
- 同样的,完成流的操作后,需要调用close()方法进行释放资源
FileInputStream类
我依旧使用其中一个子类来说明输入流的用法
java.io.FileInputStream类是文件输入流,从文件中读取字节
构造方法
- FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
- FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名
- 示例代码:
public class FileInputStreamConstructor throws IOException{ public static void main(String[] args) { // 使用File对象创建流对象 File file = new File("a.txt"); FileInputStream fis = new FileInputStream(file); // 使用文件名称创建流对象 FileInputStream fis = new FileInputStream("b.txt"); } }
读取字节数据
- 读取字节
read()方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1- 示例代码:
public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名称创建流对象 FileInputStream fis = new FileInputStream("read.txt"); // 读取数据,返回一个字节 int read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); // 读取到末尾,返回-1 read = fis.read(); System.out.println( read); // 关闭资源 fis.close(); } } public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名称创建流对象 FileInputStream fis = new FileInputStream("read.txt"); // 定义变量,保存数据 int b ; // 循环读取 while ((b = fis.read())!=-1) { System.out.println((char)b); } // 关闭资源 fis.close(); } }
- 示例代码:
- 使用字节数组读取:
read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1- 示例代码:
public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名称创建流对象. FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde // 定义变量,作为有效个数 int len ; // 定义字节数组,作为装字节数据的容器 byte[] b = new byte[2]; // 循环读取 while (( len= fis.read(b))!=-1) { // 每次读取后,把数组变成字符串打印 System.out.println(new String(b)); } // 关闭资源 fis.close(); } } - 在上面的示例中,会出现错误数据d,是由于最后一次读取时,只读取一个字节e,数组中,上次读取的数据没有被完全替换,所以要通过len ,获取有效的字节,正确的写法如下:
public class FISRead { public static void main(String[] args) throws IOException{ // 使用文件名称创建流对象. FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde // 定义变量,作为有效个数 int len ; // 定义字节数组,作为装字节数据的容器 byte[] b = new byte[2]; // 循环读取 while (( len= fis.read(b))!=-1) { // 每次读取后,把数组的有效字节部分,变成字符串打印 System.out.println(new String(b,0,len));// len 每次读取的有效字节个数 } // 关闭资源 fis.close(); } }
- 示例代码:
字节流练习
为了熟悉字节流的相关操作,我为大家提供一个小练习;就是对图片的复制,同样的,先不要看我后面的示例代码,先自己写,我来说明一下复制文件的基本原理,如图:
我写了一个图片复制的小demo,不是特别严谨哈,异常捕获依旧主动抛出的
public class Copy {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 指定数据源
FileInputStream fis = new FileInputStream("D:\\test.jpg");
// 1.2 指定目的地
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
// 2.读写数据
// 2.1 定义数组
byte[] b = new byte[1024];
// 2.2 定义长度
int len;
// 2.3 循环读取
while ((len = fis.read(b))!=-1) {
// 2.4 写出数据
fos.write(b, 0 , len);
}
// 3.关闭资源
fos.close();
fis.close();
}
}
流的关闭原则:先开后关,后开先关
完整的图片复制代码以及新的异常捕获
好了,练习介绍完了,我为大家介绍一下完整的异常写法,写完就知道我为啥偷懒了…还是以复制图片为例子:
package com.test02iotest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo {
public static void main(String[] args) {
// 定义图片目录
String filePath = "F:\\图片目录";
// 定义输入输出流
FileInputStream file=null;
FileOutputStream file2=null;
// 异常捕获
try {
// 创建输入/输出对象
file = new FileInputStream(filePath+"\\xxx.jpg");
file2 = new FileOutputStream("xxxCopy.jpg");
// 定义字节数组
byte[] byt = new byte[1024];
// 获取文件的总长度
int len;
// 开始循环读取文件
while ((len = file.read(byt))!=-1){
// 对读取的文件进行复制写出
file2.write(byt,0,len);
}
}catch (FileNotFoundException e){
// 捕获文件不存在的异常
System.out.println("文件不存在");
} catch (IOException e) {
// 文件读取,写出和其他的异常
e.printStackTrace();
} finally {
// 释放资源,因为前面初始化该文件的值为null,因此需要判断
if (file!=null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (file2!=null){
try {
file2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
是不是感觉特别麻烦,就一个close资源释放,都需要这么繁琐?这就是我偷懒的原因,在jdk1.7之后对IO异常的操作有了新的写法
语法格式:
try(需要关闭的资源对象的声明){
业务逻辑代码,可能发生异常的代码
}catch(异常类型 e){
处理异常代码
}catch(异常类型 e){
处理异常代码
}
下面我根据新的异常方法,对上面的图片复制进行改进
package com.test02iotest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) {
// 定义图片目录
String filePath = "F:\\图片目录";
// 异常捕获
try (// 定义输入输出流
FileInputStream file = new FileInputStream(filePath + "\\xxx.jpg");
FileOutputStream file2 = new FileOutputStream("xxxCopy.jpg");
){
// 定义字节数组
byte[] byt = new byte[1024];
// 获取文件的总长度
int len;
// 开始循环读取文件
while ((len = file.read(byt))!=-1){
// 对读取的文件进行复制写出
file2.write(byt,0,len);
}
}catch (FileNotFoundException e){
// 捕获文件不存在异常
System.out.println("文件不存在");
}catch (IOException e){
// 文件读取,写出和其他的异常
e.printStackTrace();
}
}
}
好了,字节流的用法和异常就介绍到这里,哦,我下面的代码依旧偷懒了…
字符流
- 既然字节流都可以存取任意的数据了,那为什么还要有字符流呢?
- 那就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件
Reader字符输入流
- 触类旁通,介绍完字节流的输入与输出,字符流的相关操作跟它一样,首先介绍字符流的基类Reader
- java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法:
public void close():关闭此流并释放与此流相关联的任何系统资源。public int read(): 从输入流读取一个字符。public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
FileReader类
- java.io.FileReader类是读取字符文件的子类。构造时使用系统默认的字符编码和默认字节缓冲区
- 等会讲到转换流的时候,我会讲解字符编码问题
构造方法
FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称- 示例代码:
public class FileReaderConstructor throws IOException{ public static void main(String[] args) { // 使用File对象创建流对象 File file = new File("a.txt"); FileReader fr = new FileReader(file); // 使用文件名称创建流对象 FileReader fr = new FileReader("b.txt"); } }
读取字符数据
- 读取字符:
read()方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取,示例代码如下:public class FRRead { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileReader fr = new FileReader("read.txt"); // 定义变量,保存数据 int b ; // 循环读取 while ((b = fr.read())!=-1) { System.out.println((char)b); } // 关闭资源 fr.close(); } } - 读取字符数组:
read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1- 示例代码:
public class FRRead { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileReader fr = new FileReader("read.txt"); // 定义变量,保存有效字符个数 int len ; // 定义字符数组,作为装字符数据的容器 char[] cbuf = new char[2]; // 循环读取 while ((len = fr.read(cbuf))!=-1) { System.out.println(new String(cbuf)); } // 关闭资源 fr.close(); } } - 同样的,我们使用字符数组读取时,需要对其进行改进:
public class FISRead { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileReader fr = new FileReader("read.txt"); // 定义变量,保存有效字符个数 int len ; // 定义字符数组,作为装字符数据的容器 char[] cbuf = new char[2]; // 循环读取 while ((len = fr.read(cbuf))!=-1) { System.out.println(new String(cbuf,0,len)); } // 关闭资源 fr.close(); } }
- 示例代码:
Writer字符输出流
- 大家是不是闭着眼睛都知道我下面内容是什么了?没错,介绍基类和共有方法,然后使用子类和介绍构造方法,接着使用单个,数组等举例…
- java.io.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()关闭此流,但要先刷新它
FileWriter类
- java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区
- 咦?有一个新的知识点,默认字节缓冲区?卖个关子,我在缓冲流为大家介绍这个内容
构造方法
FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称- 示例代码:
public class FileWriterConstructor { public static void main(String[] args) throws IOException { // 使用File对象创建流对象 File file = new File("a.txt"); FileWriter fw = new FileWriter(file); // 使用文件名称创建流对象 FileWriter fw = new FileWriter("b.txt"); } }
写出数据
- 写出字符:
write(int b)方法,每次可以写出一个字符数据,示例代码如下:public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 写出数据 fw.write(97); // 写出第1个字符 fw.write('b'); // 写出第2个字符 fw.write('C'); // 写出第3个字符 fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。 /* 【注意】关闭资源时,与FileOutputStream不同。 如果不关闭,数据只是保存到缓冲区,并未保存到文件。 */ // fw.close(); } } - 写出字符数组:
write(char[] cbuf)和write(char[] cbuf, int off, int len),每次可以写出字符数组中的数据,用法类似FileOutputStream,示例代码如下:public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 字符串转换为字节数组 char[] chars = "尚硅谷".toCharArray(); // 写出字符数组 fw.write(chars); // 尚硅谷 // 写出从索引1开始,2个字节。索引1是'硅',两个字节,也就是'硅谷'。 fw.write(b,1,2); // 硅谷 // 关闭资源 fos.close(); } } - 写出字符串:
write(String str)和write(String str, int off, int len),每次可以写出字符串中的数据,更为方便,示例代码如下:public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 字符串 String msg = "尚硅谷"; // 写出字符数组 fw.write(msg); //尚硅谷 // 写出从索引1开始,2个字节。索引1是'硅',两个字节,也就是'硅谷'。 fw.write(msg,1,2); // 尚硅谷 // 关闭资源 fos.close(); } } - 续写和换行: 根字节流一样,需要添加一个参数true,示例代码如下:
public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象,可以续写数据 FileWriter fw = new FileWriter("fw.txt",true); // 写出字符串 fw.write("尚"); // 写出换行 fw.write("\r\n"); // 写出字符串 fw.write("硅谷"); // 关闭资源 fw.close(); } }
关闭和刷新
-
想了想,还是先铺垫一下再介绍缓冲流,会更好接受这个知识点…
-
我在上一章介绍IO的共有方法时说过,每次流的操作都需要进行close()释放资源
-
这是因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了
-
flush :刷新缓冲区,流对象可以继续使用。字符流默认缓冲区为8k的字符数组。
-
close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了
-
示例代码:
public class FWWrite { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 写出数据,通过flush fw.write('刷'); // 写出第1个字符 fw.flush(); fw.write('新'); // 继续写出第2个字符,写出成功 fw.flush(); // 写出数据,通过close fw.write('关'); // 写出第1个字符 fw.close(); fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed fw.close(); } }
总结
- 字节流是对任意数据操作的流,因为字节流是以字节对数据进行保存的,当存储文本数据时,可能会出现乱码(中文),因为中文是3个字节,而字节流只会存储2个字节
- 因此,需要在文件中保存数据时,通常使用字节流,当数据中是纯文本的时候,就使用字符流
- 字节流应用很广泛,我们只要会使用并独立完成对图片进行复制的那个练习,IO流的常用操作知识点基本上就掌握了…好了,下一章,我会对IO的剩下知识点进行分享.
浙公网安备 33010602011771号