Java IO 2-字节流与字符流
FILE类只参与文件的创建删除等操作,而不对文件本身内容进行修改。如果要处理文件内容,就需要使用流来进行操作。
1. 流的分类
流分为输入流与输出流。 输入输出流又分为字节流与字符流,所以总共加起来有如下四种流:
-
InputStream: 字节输入流 Reader: 字符输入流 OutputStream: 字节输出流 Writer: 字符输出流 四者之间的关系如下:
字节流与字符流操作的本质区别只有一个:字节流是原生的操作,而字符流是经过处理后的操作。
在进行网络数据传输、磁盘数据保存所保存所支持的数据类型只有:字节。 而所有磁盘中的数据必须先读取到内存后才能进行操作,而内存中会帮助我们把字节变为字符。字符更加适合处理。 中文。
另外,由于IO操作属于资源处理操作,所以在使用完毕之后必须进行关闭。
2. 字节输出流OutputStream
OutputStream是字节输出流的顶层抽象类,它的定义如下:
public abstract class OutputStream implements Closeable, Flushable
它实现了两个接口,这两个接口中各自只有一个抽象方法,所以这两个接口又称为标识接口。 实现的抽象方法定义如下:
// Closeable public void close() throws IOException; // Flushable void flush() throws IOException
OutputStream类中常用的方法:
// 将给定的字节数组内容全部输出 public void write(byte b[]) throws IOException // 将部分字节数组内容输出 public void write(byte b[], int off, int len) throws IOException // 输出单个字节 public abstract void write(int b) throws IOException;
因为OutputStream是顶层抽象类,所以我们一般使用它的一个实现子类FileOutputStream进行父类的实例化。
下面是FileOutputStream的两个构造。
// 接收File类,输出时会覆盖原有内容 public FileOutputStream(File file) throws FileNotFoundException // 接收File类,输出时在原有内容后面追加 public FileOutputStream(File file, boolean append)
我们看下面的例子
import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; /** * 测试路径:E:IOdemo.txt */ public class Test { public static void main(String[] args) throws Exception { File file = new File("E:\IO\demo.txt"); // 如果父目录不存在,创建 if(!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } // 字节输出流 OutputStream outputStream = new FileOutputStream(file); // 输出内容 String msg = "hello world"; // 字节输出流只接受字节数组,所以要将字符串转换为字节数组 outputStream.write(msg.getBytes()); // 关闭输出流 outputStream.close(); } }
运行程序前,测试文件夹如下:
运行后,如下:
注意,只要是输出流,输出文件如果不存在,JDK会自动帮用户创建,不需要用户手工调用createNewFile()。但是文件路径必须存在,所以一定要在程序中检查父路径是否存在。
JDK还提供了一种自动关闭流的接口。
// 该接口也只有一个抽象方法,为标识接口 public interface AutoCloseable { void close() throws Exception; }
关于流的四个顶层抽象类都实现了该接口,都可以直接使用。
虽然使用该接口可以省掉恼人的关闭流操作,可是它还要与try…catch一起使用,这样变得更加复杂了,操作如下:
import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; /** * 测试路径:E:IOdemo.txt */ public class Test { public static void main(String[] args) throws Exception { File file = new File("E:\IO\demo.txt"); if(!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } try(OutputStream output = new FileOutputStream(file)) { // 实现了AutoCloseable接口的字节输出流要定义在try内 String str = "hello world"; output.write(str.getBytes()); } catch(Exception e) { e.printStackTrace(); } } }
这种反人类的操作大家自己取舍。
3. 字节输入流InputStream
InputStream与OutputStream一样,时字节输入的顶层抽象类,具体应用时使用的它的实现子类。
public abstract class InputStream implements Closeable
对应字节输出流,字节输入流的常用方法为 read 方法,这里我们要注意它的返回值。
// 读取数据到字节数组中,返回数据的读取个数 // 如果此时开辟的字节数组大小大于读取的数据大小,则返回的就是读取个数 // 如果要读取的数据大于数组的内容,那么这个时候返回的就是数组长度 // 如果没有数据了还在读,则返回-1 public int read(byte b[]) throws IOException (最常用方法) // 读取部分数据到字节数组中,每次只读取传递数组的部分内容 // 如果读取满了则返回长度(len) // 如果没有读取满则返回读取的数据个数 // 如果读取到最后没有数据了返回-1 public int read(byte b[], int off, int len) throws IOException // 读取单个字节,每次读取一个字节的内容,直到没有数据了返回-1 public abstract int read() throws IOException;
看下面的例子,我们读取的文件为上个例子中写入”hello world”的文件。
import java.io.File; import java.io.FileInputStream; import java.io.InputStream; /** * 测试路径:E:IOdemo.txt */ public class Test { public static void main(String[] args) throws Exception { File file = new File("E:\IO\demo.txt"); // 输入流和输出流不同,不会自动创建文件,所以这里要保证文件存在 if(file.exists()) { InputStream in = new FileInputStream(file); // 存放读取数据的字节数组 byte[] data = new byte[1024]; // 将内容读取到数组中 int len = in.read(data); // 将数组内容转换为字符串,便于我们观察结果 String result = new String(data, 0, len); System.out.println(result); in.close(); } } }
运行结果:
4. 字符输出流Writer
字符比起字节来说能够更好的处理中文数据,Writer类定义如下:
public abstract class Writer implements Appendable, Closeable, Flushable
看下面的例子:
import java.io.File; import java.io.FileWriter; import java.io.Writer; /** * 测试路径:E:IOdemo.txt */ public class Test { public static void main(String[] args) throws Exception { File file = new File("E:\IO\demo.txt"); // 同字节输出流,一定要保证父路径存在 if(!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } String str = "爱生活爱Java"; Writer out = new FileWriter(file); out.write(str); out.close(); } }
运行前:
运行后:
Writer类的结构与方法的使用与OutputStream非常相似,只是Writer类对于中文的支持很好并且提供了直接写入String的方法而已,而OutputStream的输出方法writer()必须先将字符串转为字节数组再进行参数传入。所以以后输出字符串中文的场景,最好使用Writer类,能省下很多的功夫。
5. 字符输入流Reader
Reader依然是一个抽象类。使用时使用它的实现子类 FileReader。
Writer类中有直接接受字符串的write方法,而同作为字符操作流的Reader是没有提供的,所以Reader也只能通过字符数组进行操作。
看下面的例子,文件使用上个例子中被写入的文件。
import java.io.File; import java.io.FileReader; import java.io.Reader; /** * 测试路径:E:IOdemo.txt */ public class Test { public static void main(String[] args) throws Exception { File file = new File("E:\IO\demo.txt"); // 保证文件存在才能进行操作 if(file.exists()) { Reader in = new FileReader(file); char data[] = new char[1024]; int len = in.read(data); String str = new String(data, 0, len); System.out.println(str); in.close(); } } }
运行结果: