前言:
在刚接触Java的时候对于IO的相关知识点整体掌握的还是比较牢靠的,在从事android相关的工作开发后,对于java IO的相关知识逐渐的模糊起来。其一应该是android中直接使用IO相关的功能比较少,大多数都是封装好的第三方类库。其二即使使用到了IO操作封装成为一个工具类之后,后期回头看的次数相对较少。总之对于Java IO 的相关知识使用的少,缺少总结。
为此这篇文章主要介绍IO类之间的关系,针对在android开发中常用到的IO 操作类给出实例代码参考。
"输入"和"输出"在刚接触java IO的时候这两个概念给我带来了一丝的疑惑。输入的对象是谁?又输出到哪里?
输入流可以理解为向内存[程序内存]输入,输出流可以理解为从内存[程序内存]输出。
Java的IO包主要关注的是从原始数据源的读取以及输出原始数据到目标媒介。
一个程序需要InputStream或者Reader从数据源读取数据,需要OutputStream或者Writer将数据写入到目标媒介中
能够理解 IO 输入和输出 概念,对java IO的学习有重要的帮助。
IO类概述表
Java的IO包主要关注的是从原始数据源的读取以及输出原始数据到目标媒介。
一个程序需要InputStream或者Reader从数据源读取数据,需要OutputStream或者Writer将数据写入到目标媒介中:
根据类的关系又可以划分:
- OutputStream、InputStream是操作字节流的抽象类无法直接使用。需要根据操作对象选择合适的子类操作。
- FileOutputStream、FileInputStream对 文件操作
- ObjectInputStream 和ObjectOutputStream 对 java对象操作
- DataInputStream 和DataOutputStream 对 java基本类型和字符串操作
- BufferedInputStream和BufferedOutputStream 组合流:将流整合起来以便实现更高级的输入和输出操作。比如,一次读取一个字节是很慢的,所以可以从磁盘中一次读取一大块数据,然后从读到的数据块中获取字节。为了实现缓冲,可以把InputStream包装到BufferedInputStream中。代码示例:
InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"));
- Reader、Writer是操作字符的抽象类无法直接使用。需要根据操作对象选择合适的子类操作。
- InputStreamReader、OutputStreamWriter 这两个类把字节流转换成字符流,中间做了数据的转换,类似适配器模式的思想。
- InputStream的read()方法返回一个字节,意味着这个返回值的范围在0到255之间(当达到流末尾时,返回-1),
- Reader的read()方法返回一个字符,意味着这个返回值的范围在0到65535之间(当达到流末尾时,同样返回-1)。
字节流常用类:
一、FileInputStream和FileOutputStream
读取文件内容和写入文件内容
/**
* 测试 输入流和输出流
* 目标:读取 atext.txt 中的内容 写到 btext.txt中
* 读取 apic.jpeg 中的内容 写到 bpic.jpeg中
*/
private static void testInputAndOutputStream1() {
InputStream is = null;
OutputStream os = null;
// 操作文件
// File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/atext.txt");
// File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/btext.txt");
// 操作图片 apic.jpeg
File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/apic.jpeg");
File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/bpic.jpeg");
if (outFile.exists()) {
outFile.delete();
Utils.printLog("testInputAndOutputStream ", "outFile 已存在 删除!");
}
try {
is = new FileInputStream(inFile);
os = new FileOutputStream(outFile);// 如果文件不存在 会自动创建
int temp = is.read();
while (temp != -1) {
os.write(temp);
temp = is.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关流 模板代码 后续样例省略
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
int read() :每次只读一个字节,如果文件特别大,这样操作会有效率问题
int read(byte b[]) :一次性读取一个字节数组的方式,比一次性读取一个字节的方式快的多,所以,尽可能使用这两个方法代替read()方法。
/**
* 测试 输入流和输出流
* 目标: is.read() 每次只读一个字节,如果文件特别大,这样操作会有效率问题
* Java I/O默认是不缓冲流的,所谓“缓冲”就是先把从流中得到的一块字节序列暂存在一个被称为buffer的内部字节数组里,
* 然后你可以一下子取到这一整块的字节数据,没有缓冲的流只能一个字节一个字节读,效率孰高孰低一目了然。
*
* 有两个特殊的输入流实现了缓冲功能,一个是我们常用的BufferedInputStream
*/
private static void testInputAndOutputStream2() {
InputStream is = null;
OutputStream os = null;
byte[] bufferRead = new byte[512];
int numberRead = 0; // numberRead的目的在于防止最后一次读取的字节小于buffer长度,
// 操作文件
File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/atext.txt");
File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/btext.txt");
// 操作图片 apic.jpeg
// File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/apic.jpeg");
// File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/bpic.jpeg");
if (outFile.exists()) {
outFile.delete();
Utils.printLog("testInputAndOutputStream ", "outFile 已存在 删除!");
}
try {
is = new FileInputStream(inFile);
os = new FileOutputStream(outFile);
while ((numberRead = is.read(bufferRead)) != -1) {
os.write(bufferRead, 0, numberRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关流
}
}
有两个特殊的输入流实现了缓冲功能,一个是我们常用的BufferedInputStream、BufferedOutputStream
二、BufferedInputStream和BufferedOutputStream
BufferedInputStream BufferedInputStream能为输入流提供缓冲区,能提高很多IO的速度。你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。 为了给你的输入流加上缓冲,你需要把输入流包装到BufferedInputStream中,代码如下:
InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"));
你可以给BufferedInputStream的构造函数传递一个值,设置内部使用的缓冲区设置大小(译者注:默认缓冲区大小8 * 1024B),就像这样:
InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"), 8 * 1024);
/**
* 测试 输入流和输出流
* 目标: is.read() 每次只读一个字节,如果文件特别大,这样操作会有效率问题
* 使用 BufferedInputStream
*
* 有两个特殊的输入流实现了缓冲功能,一个是我们常用的BufferedInputStream
*/
private static void testInputAndOutputStream3() {
InputStream is = null;
OutputStream os = null;
byte[] bufferRead = new byte[512];
int numberRead = 0; // numberRead的目的在于防止最后一次读取的字节小于buffer长度,
// 操作文件
File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/atext.txt");
File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/btext.txt");
// 操作图片 apic.jpeg
// File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/apic.jpeg");
// File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/bpic.jpeg");
if (outFile.exists()) {
outFile.delete();
Utils.printLog("testInputAndOutputStream ", "outFile 已存在 删除!");
}
try {
is = new BufferedInputStream(new FileInputStream(inFile));
os = new BufferedOutputStream(new FileOutputStream(outFile));
while ((numberRead = is.read(bufferRead)) != -1) {
os.write(bufferRead, 0, numberRead);
os.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关流
}
}
三、DataInputStream 和DataOutputStream
/**
* 写入基本变量和字符串至文件
* 从文件中读取基本变量和字符串
*/
private static void testDataOutputStream() {
File file = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/datatext.txt");
DataOutputStream dos = null;
DataInputStream dis = null;
if (file.exists()) {
file.delete();
}
try {
dos = new DataOutputStream(new FileOutputStream(file));
dis = new DataInputStream(new FileInputStream(file));
dos.writeUTF("小米");
dos.writeInt(10);
dos.writeDouble(1.10);
// 有用吗???
dos.flush();
// 注意:写入数据和读取数据 顺序保持一致
// 否则:java.io.EOFException 错误
String name = dis.readUTF();
int age = dis.readInt();
double color = dis.readDouble();
Utils.printLog("", "name:" + name + " age:" + age + " color:" + color);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关流
}
}
四、ObjectInputStream 和ObjectOutputStream
如果你希望类能够序列化和反序列化,必须实现Serializable接口,就像所展示的ObjectInputStream和ObjectOutputStream例子一样。
ObjectInputStream能够让你从输入流中读取Java对象,而不需要每次读取一个字节。你可以把InputStream包装到ObjectInputStream中,然后就可以从中读取对象了。代码如下:
/**
* 将对象写入 文件
* 从文件中读取 对象
*/
private static void testObjectStream() {
File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/aotext.txt");
File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/botext.txt");
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
if (inFile.exists()) {
inFile.delete();
}
try {
// 写入
oos = new ObjectOutputStream(new FileOutputStream(outFile));
oos.writeObject(new Dog("aa", "10", "red"));
oos.writeObject(new Dog("bb", "11", "green"));
oos.writeObject(new Dog("cc", "12", "red"));
oos.writeObject(new Dog("dd", "13", "yellow"));
//插入null是用来判断是否读取到结尾
//写入结束标志方便读取(非常重要,如果不写入,在读取的时候无法定位读取结束);
//读取的时候就会报 java.io.EOFException 异常
oos.writeObject(null);
// 读取
ois = new ObjectInputStream(new FileInputStream(outFile));
Object obj = null;
while ((obj = ois.readObject()) != null) {
Dog dog = (Dog) obj;
Utils.printLog("testObjectStream", dog.toString());
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
// // 关流
}
}
写入数组、读取数组
private static void testObjectArrStream() {
File inFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/aotext.txt");
File outFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/botext.txt");
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
if (inFile.exists()) {
inFile.delete();
}
try {
// 写入 数组
Dog[] dogArr = new Dog[10];
for (int i = 0; i < dogArr.length; i++) {
Dog dog = new Dog("aa"+i, "" + (10 + i), "red");
dogArr[i]=dog;
}
oos = new ObjectOutputStream(new FileOutputStream(outFile));
oos.writeObject(dogArr);
// 读取 数组
ois = new ObjectInputStream(new FileInputStream(outFile));
Dog[] dogs = (Dog[]) ois.readObject();
for (Dog dog : dogs) {
Utils.printLog("testObjectStream", dog.toString());
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 关流
}
}
public class Dog implements Serializable {
private String name;
private String age;
private String color;
public Dog(String name, String age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", color='" + color + '\'' +
'}';
}
}
字符流常用类
一、BufferedRead 和 BufferedWriter
整合Reader和Writer 和字节流一样,Reader和Writer可以相互结合实现更多更有趣的IO,工作原理和把Reader与InputStream或者Writer与OutputStream相结合类似。举个栗子,可以通过将Reader包装到BufferedReader、Writer包装到BufferedWriter中实现缓冲。以下是例子:
Reader reader = new BufferedReader(new FileReader(...));
Writer writer = new BufferedWriter(new FileWriter(...));
/**
* 使用 inputStreamReader 和 outputStreamWriter
*/
private static void testReadAndWriter1() {
Reader reader = null;
Writer writer = null;
File readFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/reader.txt");
File writerFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/writer.txt");
try {
reader = new InputStreamReader(new FileInputStream(readFile));
writer = new OutputStreamWriter(new FileOutputStream(writerFile));
char[] chars = new char[512];
int line = 0;
while ((line = reader.read(chars)) != -1) {
writer.write(chars, 0, line);
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关流
}
}
二、InputStreamReader和OutputStreamWriter
整合Reader与InputStream 一个Reader可以和一个InputStream相结合。如果你有一个InputStream输入流,并且想从其中读取字符,可以把这个InputStream包装到InputStreamReader中。把InputStream传递到InputStreamReader的构造函数中:
Reader reader = new InputStreamReader(inputStream);
/**
* 使用 bufferedRead
*/
private static void testReadAndWriter2() {
Reader reader = null;
Writer writer = null;
File readFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/reader.txt");
File writerFile = new File("/Users/fumm/work/my_project/my_lib/CustomView/java_lib/src/main/res/writer.txt");
if (writerFile.exists()) {
writerFile.delete();
}
try {
// reader = new InputStreamReader(new FileInputStream(readFile));
// writer = new OutputStreamWriter(new FileOutputStream(writerFile));
reader = new BufferedReader(new FileReader(readFile));
writer = new BufferedWriter(new FileWriter(writerFile));
char[] chars = new char[512];
int line = 0;
while ((line = reader.read(chars)) != -1) {
writer.write(chars, 0, line);
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关流
}
}
总结:
本文主要梳理Java IO类之间的操作和转换。并通过实例代码加深对IO的理解。对android开发中常见的IO 操作类进行实践。内容比较基础大致对Java IO 知识做了基础的回顾。
参考:
Java IO教程
Java IO最详解