IOStream
记录 File class 和 IO Stream
学习笔记,仅供参考
目录
🧷 File class
File 类是对一个文件夹或文件路径的抽象表示,文件路径可以是 绝对路径 (absolute path)抑或是 相对路径 (relative path)
不同操作系统下,文件路径的分隔符会不同,Unix
下用 /
; 而 Win 10
下用 \\
构造器
File(String pathname)
// 将所给的文件名转为抽象路径
方法
canExecute() | canRead() | canWrite()
// 判断该文件是否可执行 | 可读 | 可写length()
// 返回所给文件的字符长度exists()
// 判断所给的文件是否存在isAbsolute() | isDirectory() | isFile() | isHidden()
// 判断文件路径是否为绝对路径 | 该文件是否是文件夹 | 是否是文件 | 是否是隐藏文件createNewFile()
// 返回 true 表明该文件不存在,以该文件名为名创建新的空文件;返回 false 表明该文件已存在;用时还要处理 IOExceptiondelete()
// 删除该文件/文件夹mkdir()
// 以该抽象路径名创建文件夹getCanonicalFile()
// 获取该文件的规范形式文件,即绝对路径且唯一的,与操作系统有关;同样要处理异常getAbsolutePath()
// 获取该文件的绝对路径字符串getName()
// 获取该文件/文件夹的名getParent()
// 返回此文件的父目录的路径名字符串,如果此路径名未命名父目录,则返回 null。getPath()
// 获取文件的路径名list()
// 遍历获取此抽象路径下的所有文件和子文件夹,并以字符串的形式返回listFile()
// 遍历获取此抽象路径下的所有文件和子文件夹,并以 File 的形式返回
🌠 I/O Stream
I
: input source; O
: output distination; Stream
: the process/pipe (from source to dist or from dist to source) and it support many different kinds of data, include: bytes, primitive data types, localized characters, and objects.
下面以水库输水来说明流的过程
输入输出是有相对性的,可以从 src 到 dist, 或者 dist 到 src
接着来探讨程序中的 IO 流概念,在程序中流就是一串数据
,当程序要 从源读取数据
时就要使用 input
; 当程序要 向源写入数据
时就要使用 output
。所以一切都是以程序为主,以它作为主角,输入输出都是相对它而言的。
图片来自官网文档
从图片中也可以看出,数据源/数据目标可以有很多种形式:另一个程序、文件、数据库等
字节流 InputStream 和 OutputStream
InputStream 是抽象类,它是所有字节输入类的父类;同样,OutputStream 是所有字节输出类的父类。所有的 字节流类 都是继承自他俩
InputStream 的常用方法:close()
- 当程序不在读数据时,关闭输入流;read()
- 从输入流中读取下个字节
OutputStream 的常用方法:close()
- 当程序不在写数据时,关闭输出流;write(int b)
- 写字节到输出流中
因为抽象类的子类会重写父类的方法,所以上面的方法也是其子类常用的
FileInputStream 和 FileOutputStream
它俩用于读文件或写文件,通过字节的形式来完成。
public class Test {
public void fileInputStreamTest() throws IOException {
// 创建文件流
FileInputStream in = new FileInputStream("xx/text.txt");
try {
int c;
// 读取文件流源文件中的数据到程序中
while ((c = in.read()) != -1) {
// 将从文件流中获取的字节强转为 char 型
// 然后将其输入
System.out.print((char) c);
} finally {
// 读取之后,关闭所用的文件流
if (in != null) {
in.close();
}
}
}
}
其中用到 read()
方法,它返回下个数据的字节,要是到底了就返回 -1;还有不论如何最后都要关流,以防止数据泄露
public class Test {
public void fileOutputStreamTest() throws IOException {
// 创建输出流
FileOutputStream out = new FileOutputStream("xx/output.txt");
try {
// 因为是字节流写入,所以要将所写的 String 转为字节数组
byte[] bytes = "Hello World!".getBytes();
// 将程序中的字节写入到输出流的目标文件中
out.write(bytes);
} finally {
if (out != null) {
// 写好之后,关闭输出流
out.close();
}
}
}
}
用 write()
方法将数据写入到目标文件中,放还有几个重载的方法,以满足不同字节类型输入
有了读取和写入,就可以满足对文件的读写要求了,后面只是对性能的提升。通过读写还可以完成文件的 copy 操作
public class Test {
public void fileCopyTest() throws IOException {
// 创建输入输出流
FileInputStream in = new FileInputStream("xx/input.txt")
FileOutputStream out = new FileOutputStream("xx/output.txt");
// 从 src 中读向 dist 中写
try {
int c;
// 单字节读取
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
// 关流
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
上面所用的是单个字节来进行数据的读写,就像搬砖一样,一次只搬一块砖就会显得效率太低。所以引入一个 byte[] 类型的 buffer 来增加一次搬运的容量,就像一次搬十块砖一样,效率就会提高很多。下面就来看看代码的实现
public class Test {
public void fileCopyTest() throws IOException {
// 创建输入输出流
FileInputStream in = new FileInputStream("xx/input.txt")
FileOutputStream out = new FileOutputStream("xx/output.txt");
// 读取数据到缓存区,缓存区大小为 1KB
try {
byte[] buff = new byte[1024];
int c;
while ((c = in.read(buff)) != -1) {
out.write(buff, 0, c);
}
} finally {
// 关流
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
从上述代码中,可以看到 buffer 的大小被设置为 1024 bytes(1KB),同时还用到了 read(byte[] b)
和 write(byte[], int off, int len)
的重载方法,
read(byte[] b)
// 读取数据到 byte 数组中,并返回所读到的总字节数(即 1024, 数组的大小),若没读到就返回 -1write(byte[] b, int off, int len)
// 将字节数组写到输出流中,off 指要从数组的哪开始写,len 指要写入多大的字节
BufferedInputStream 和 BufferedOutputStream
Java 作为高级语言,自然有直接可以用的字节缓存读写类,它们就是 BufferedInputStream 和 BufferedOutputStream ,其实是它们的内部新建了一个 byte 数组,原理上跟上面的差不多,只是它们使用起来要更加地方便
public class Test {
public void fileCopyTest() throws IOException {
// 创建字节缓存输入输出流
BufferedInputStream buffIn = new BufferedInputStream(new FileInputStream("xx/input.txt"));
BufferedOutputStream buffOut = new BufferedOutputStream(new FileOutputStream("xx/output.txt"));
// 读取数据
try {
int c;
while ((c = buffIn.read()) != -1) {
buffOut.write(c);
}
} finally {
// 关流
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
由于 BufferedInputStream(InputStream in)
构造器要接受一个输入流,所以用到 new FileInputStream("xx/input.txt")
来生成一个文件输入流,output 同理。本来用 FileInputStream 就可以完成读写,为了让读写更加高效,相当于给 FileInputStream 对象开了个 VIP 一样。所以要想开 VIP,你得先要注册账号(FileInputStream 对象),在设计模式中这种类似升级 VIP 的服务叫 装饰设计模式
心血来潮,顺便简要测试了单字节读写、手写缓存区读写、自带缓存区读写三种读写所消耗的时间
由图可知,缓存区读写大约是单字节读写的十分之一,而手写的缓存区要稍慢于自带的,可能是内部建的 byte 数组底层优化的好
字符流 Reader & Writer
除了字节流外,还有字符流,但实质上字符还是用字节来表示。为了更方便地处理字符文本文件,所以有了 Reader & Writer
。与之前的 InputStream & OutputStream
一样,它们也是抽象的,所有的 字符流 类都继承自他俩。
通常字符流用来读写文本文件更方便,字节流来读写视频、音频、图片等格式的文件更方便,详细查看字符流与字节流的区别
同样提供了一些读写必要的方法 close()
, read()
, write()
FileReader & FileWriter
它俩具体实现 Reader & Writer 类
public class Test {
public void fileReaderTest() throws IOException {
// 新建输入流
FileReader fr = new FileReader("xx/input.txt");
// 读取数据
int c;
while ((c = fr.read()) != -1) {
System.out.print((char) c);
}
// 关流
if (fr != null) {
fr.close();
}
}
}
/**********************分割线*********************/
public class Test {
public void fileWriterTest() throws IOException {
// 创建输出流
FileWriter fr = new FileWriter("xx/output.txt");
// 写入数据(直接将要写的字符串丢进去即可)
fw.write("hello world");
// 关流
if (fw != null) {
fw.close();
}
}
}
/***************分割线********************/
// 复制的操作与字节流复制的一样,也是边读边写
// 只不过只能读写文本文件
同样,为了让读写更高效,有了 BufferedReader & BufferedWriter
public class Test {
public void bufferedReaderTest() throws IOException {
// 新建输入流
BufferedReader buffReader = new BufferedReader(new Reader("xx/input.txt"));
// 以字符串的形式读取数据
String str;
while ((str = buffReader.readLine()) != null) {
System.out.println(str);
}
// 关流
if (buffReader != null) {
buffReader.close();
}
}
}
BufferedReader 可以使用 readLine()
方法来读取,要打印时就要是用 System.out.println()
来打印,否则文本中的回车换行就会不显示
public class Test {
public void bufferedWriterTest() throws IOException {
// 新建输入流
BufferedWriter buffWriter = new BufferedWriter(new Writer("xx/output.txt"));
// 以字符串的形式写入数据
buffWriter.write("hello");
buffWriter.newLine(); // 用该方法写入换行,防止不同系统的换行符不同而引起错误
buffWriter.write("world!");
// 关流
if (buffWriter != null) {
buffWriter.close();
}
}
}
复制的话也是类似的,下面仅列出读写数据的部分
// 定义字符串变量来接收读取到的数据
String str;
while ((str = buffReader.readLine()) != null) {
buffWriter.write(str);
buffWriter.newLine(); // 每写一行数据换一行
}
转换流 (InputStreamReader & OutputStreamWriter)
它俩是作为字节和字符相互转换的桥梁,通过 InputStreamReader
可以将文件中的字节转为字符,然后进行读取;同样,OutputStreamWriter
是将写入的字符转为字节,然后放入文件中进行存储。
使用场合:
源或目标所对应的设备是字节流,但操作的确实文本数据,可以使用其来作为桥梁
一旦操作的文本涉及到具体的指定编码表时,必须使用转换流
下面以系统的输入输出流(字节)读写到文本文件为例
public class Test {
/**
* 读取系统输入(字节)
* 写入到文件中(字符)
*/
public void readTest() throws IOException {
// 将系统输入流转为字符流进行读取
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter writer = new BufferedWriter(new FileWrite("xx/output.txt"));
// BufferedReader 的 readLine() 获取的是字符串
String data;
// 当读取系统输入为 “exit” 时,停止读取
while (! "exit".equals(data = reader.readLine())) {
writer.write(data);
writer.newLine();
}
// 关流
if (writer != null) {
writer.close();
}
if (reader != null) {
reader.close();
}
}
/*-----------------------分割线-----------------------*/
/**
* 从文件中读取数据(字符)
* 写入数据到系统输出(字节)
*/
public void writeTest() throws IOException {
// 将系统输入流转为字符流进行读取
BufferedReader reader = new BufferedReader(new FileReader("xx/input.txt"));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
// BufferedReader 的 readLine() 获取的是字符串
String data;
// 读写数据
while ((data = reader.readLine()) != null) {
writer.write(data);
writer.newLine();
}
// 关流
if (writer != null) {
writer.close();
}
if (reader != null) {
reader.close();
}
}
}
数据流 (DataInputStream & DataOutputStream)
数据流支持 Java 的基本类型读写,但要注意的是,所读的文件必须是用数据流 DataOutputStream
写入的数据文件,不然会造成 字符编码问题
// 定义一个全局变量的文件名, 没有后缀,即使给了后缀打开也是乱码
static final String dataFile = "invoicedata"
/**
* 用数据流写入数据
*/
public void dataWriteStream() throws IOException {
// 定义一些要写入的变量
final double[] prices = {19.99, 9.99, 15.99, 3.99, 4.99};
final int[] units = { 12, 8, 13, 29, 50 };
final String[] descs = {
"Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain"
};
// create out stream and write data into specify file
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i++) {
out.writeDouble(prices[i]);
out.writeInt(units[i]);
out.writeUTF(descs[i]);
}
// 关流
if (out != null) {
out.close();
}
}
/*******************分割线*********************/
/**
* 用数据流读取写好的数据
*/
public void dataReadTest() throws IOException {
// create in stream and read data from specify file
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
// 定义接受不同类型数据的变量
double price;
int unit;
String desc;
try {
while (true) {
// 读取数据的类型要与写入时的保持一致
price = in.readDouble();
unit = in.readInt();
desc = in.readUTF();
System.out.format("You ordered %d" + " units of %s at %.2f%n", unit, desc, price);
}
} catch (EOFException e) {
e.printStackTrace();
}
}
注意:
DataStreams 通过捕获 EOFException
来检测文件结束条件,而不是测试无效的返回值。
对象流 (ObjectInputStream & ObjectOutputStream)
跟数据流一样,对象流支持读写对象,通过 ObjectOutputStream
将对象序列化,然后存储到磁盘中或发布到网上,用户再通过 ObjectIntputStream
将数据反序列化为对象,然后操作使用。所以想要用对象流,那么该对象就必须是 实现 Serializable
接口类的实例。
跟数据流相比,对象流的数据类型就更加的丰富了,不仅有基本的数据类型,还可能包含其他的对象。下面仍然是订单的例子,只不过它的价格属性用的是 Math.BigDecimal
类,BigDecimal 类是用来计算 更高精度要求的小数,比如商业的金钱交易,使用它的算术方法来进行加减乘除运算,而不是用运算符运算。下面的例子来源于官方教程
public class ObjectStreams {
// 定义数据
static final String dataFile = "invoicedata";
static final BigDecimal[] prices = {
new BigDecimal("19.99"),
new BigDecimal("9.99"),
new BigDecimal("15.99"),
new BigDecimal("3.99"),
new BigDecimal("4.99") };
static final int[] units = { 12, 8, 13, 29, 50 };
static final String[] descs = { "Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain" };
public static void main(String[] args)
throws IOException, ClassNotFoundException {
// 对象流写数据到文件
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new
BufferedOutputStream(new FileOutputStream(dataFile)));
// 写入日期、价格、数量、名称到文件
out.writeObject(Calendar.getInstance());
for (int i = 0; i < prices.length; i ++) {
out.writeObject(prices[i]);
out.writeInt(units[i]);
out.writeUTF(descs[i]);
}
} finally { // 关流
out.close();
}
/********************分割线****************************/
// 对象流从文件中读数据
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new
BufferedInputStream(new FileInputStream(dataFile)));
// 定义接收数据的变量
Calendar date = null;
BigDecimal price;
int unit;
String desc;
BigDecimal total = new BigDecimal(0);
// 先接收日期
date = (Calendar) in.readObject();
System.out.format ("On %tA, %<tB %<te, %<tY:%n", date);
// 跟数据流一样按照写入时的顺序读数据
try {
while (true) {
price = (BigDecimal) in.readObject();
unit = in.readInt();
desc = in.readUTF();
System.out.format("You ordered %d units of %s at $%.2f%n",
unit, desc, price);
total = total.add(price.multiply(new BigDecimal(unit)));
}
} catch (EOFException e) {} // 同样,等捕获到异常后退出循环
System.out.format("For a TOTAL of: $%.2f%n", total);
} finally { // 关流
in.close();
}
}
}