I/O 流
关于数据的存储,分为内部存储和外部存储。内部存储就像内存一样,是一种临时性的对数据进行储存的方式,这种方式存储的数据量不大,但是获取数据块。另一种就是持久性的存储,以文件的方式来保存数据,可以是数据库、文本文件、图片文件、视频文件等等,存储的数据量非常大。
Java提供了一种对文件进行操作的API,就是I/O流。I/O流是Java中的一个非常庞大的家族,同时这个家族也非常强大。关于流的概念,我们可以这样的理解。水流,流的是水;电流,流的是电;I/O流,流的就是与计算机相关的二进制字节码、字符码。
I/O(Input / Output)就是标准的输入和输出,加上流的话。那么就是InputStream / OutputStream。流这个家族成员有很多,下面我们来通过一个表格来看看常用的流。
(1)流的家族
![](https://img.haomeiwen.com/i18813666/94b746b519b771b0.png)
整个流的家族差不多也就这些了。通过以上的表格,我们大致了解了流的家族成员,下面我们来看看关于流的分类。
(2)流的分类
![](https://img.haomeiwen.com/i18813666/d3bd45085602d5a0.png)
-
输入流 、输出流的作用
输入流,用于从 .txt文件中读取数据;输出流,向文件中写入数据 -
字节流 、字符流的区别
字节流,byte来接收数据,作用于任何类型的文件。字符流,char来接收数据。只能作用于纯文本(.txt)文件。如果是纯文本文件,读取速度比字节流更快。
-
节点流 、 处理流
节点流,直接作用于文件上,如:new FileInputStream(File file); -
处理流 , 作用于流上,如:new BufferedInputStream(InputStream is),作用是加速流的读取/写入速度。
File 类
介绍了整个流家族,我们还缺一个与流息息相关的 File 类。顾名思义,这是对文件进行创建、删除操作的一个类。而流则就是对文件的读取/写入操作的类。对于文件的操作,我们直接看代码例子比较鲜明,这也没什么理论好说的。
@Test
public void file() throws IOException {
// 绝对路径:d:/FileTest/test ,新建文件夹
File dirFile = new File("d:/FileTest/test");
if (!dirFile.exists()) {
dirFile.mkdirs();
}
// 相对路径:C:\Users\x\eclipse-workspace\HelloJava\Test.txt ,新建文件
File helloFile = new File("Test.txt");
if (!helloFile.exists()) {
helloFile.createNewFile();
}
// 列出 d:/FileTest/test 文件夹下的所有文件名
File nameFile = new File("d:/FileTest/test");
String[] fileName = nameFile.list();
for (String name : fileName) {
System.out.println(name);
}
//判断文件的存在性
System.out.println(helloFile.exists());
//获取绝对路径
System.out.println(helloFile.getAbsolutePath());
//获取父目录
System.out.println(helloFile.getParent());
// 获取最后修改文件的时间
System.out.println(new Date(helloFile.lastModified()));
}
注意点:例如,D:/FileTest/test ,使用 mkdir 与 mkdirs 的区别 mkdir 前提是 FileTest 存在的情况下才可以创建成功 mkdirs 不需要,若 FileTest 文件夹不存在,则一并创建。
I/O流的用法
-
FileInputStream/FileOutputStream
这两个是节点流,可以直接作用于文件上。例如,从文本文件中读取数据打印到控制台上: -
FileInputStream 实现读取文件
@Test
public void fisTest() {
File file = new File("Hello.txt");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] b = new byte[10];
int length;
while ((length = fis.read(b)) != -1) {
String str = new String(b, 0, length);
System.out.print(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
比较难理解的就是while循环,它表达的是从 Hello.txt 文件中读取byte[10]这样长度的字节,然后打印到控制台。若已经读到结尾,则没有数据可以读了,就会返回 -1
- FileOutputStream 实现写入文件
@Test
public void fosTest() {
// 指定文件路径,若不存在,则会自动创建
File file = new File("Hello.txt");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
byte[] b = new String("hello Java 2").getBytes();
fos.write(b, 0, b.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileInputStream/FileOutputStream 实现文件拷贝
@Test
public void copyTest() {
long start = System.currentTimeMillis();
File srcFile = new File("mv.mp4");
File destFile = new File("mv3.mp4");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
byte[] b = new byte[4 * 1024];
int len;
while ((len = fis.read(b)) != -1) {
fos.write(b, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
System.out.println("time:" + (end - start));
}
- BufferedInputStream/BufferedOutputStream
这两个是作用于字节流的处理流,它可以加速文件的读取和写入,所以我就尝试了一下。通过对比,我认为是有快一点,决定因素更大的在于byte[]数组的缓冲区大小。据说4k的缓冲区是读写大文件的最快方式。BufferedInputStream/BufferedOutputStream用法也非常简单,其实流族的代码写法都差不太多。下面来看看代码的使用:
@Test
public void copy2Test() {
long start = System.currentTimeMillis();
//备注:mv.mp4 测试文件大小 500M
File file = new File("mv.mp4");
File file2 = new File("mv2.mp4");
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file2);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
byte[] b = new byte[4 * 1024];// 4k缓冲区
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
bos.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//把 bis 和 bos 关闭,那么 fis 和 fos 也就自动被关闭
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
System.out.println("time:" + (end - start));
}
- FileReader/FileWriter
这两个也是节点流,可以直接作用于文件上。但这个只能用于读取/写入纯文本文件(.txt),比如你新建的.doc文件也纯写文字,它也不能给你读取出来。它的用法类似,只是接收的缓冲区改为 char 型,代码如下:
/**
* 使用 FileReader 来读取文件 注意:只能读取文本文件,如.txt
*/
@Test
public void frTest() {
FileReader fr = null;
try {
File file = new File("Hello.txt");
fr = new FileReader(file);
char[] c = new char[10];
int len;
while ((len = fr.read(c)) != -1) {
String str = new String(c, 0, len);
System.out.println(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileReader/FileWriter实现纯文本文件的拷贝
@Test
public void txtCopyTest() {
FileReader fr = null;
FileWriter fw = null;
try {
File file = new File("Hello.txt");
File file2 = new File("Test.txt");
fr = new FileReader(file);
fw = new FileWriter(file2);
char[] c = new char[10];
int len = 0;
while ((len = fr.read(c)) != -1) {
fw.write(c, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- BufferedReader/BufferedWriter
这两个是作用于字符流的处理流,目的也是加速文件的读取和写入速度。具体用法:
@Test
public void txtCopy2Test() {
BufferedReader br = null;
BufferedWriter bw = null;
try {
File file = new File("Hello.txt");
File file2 = new File("Test.txt");
FileReader fr = new FileReader(file);
FileWriter fw = new FileWriter(file2);
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
String str = null;
while ((str = br.readLine()) != null) {
bw.write(str);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-
InputStreamReader/InputStreamWriter
这是一个转换流,例如从键盘上输入一条字符串,本质是二进制的字符码,我们可以将它转为能看的懂的字符。代码如下:
/**
* 系统的输入和输出,利用InputStreamReader(转换流)来转二进制码
*/
@Test
public void systemOut() {
InputStreamReader isr = null;
try {
System.out.println("请输入字符串:");
InputStream is = System.in;
// 用到转换流
isr = new InputStreamReader(is);
char[] c = new char[10];
int len;
while ((len = isr.read(c)) != -1) {
String str = new String(c, 0, len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- PrintStream/PrintWriter
这两个是打印流,用于打印数据。也可以直接作用文件上,例如,向文本文件中打印数据:
/**
* PrintStream 和 PrintWriter 用法一致,只不过流的单位不同
*/
@Test
public void printIOTest() {
PrintStream ps = null;
try {
File file = new File("Hello2.txt");
ps = new PrintStream(file);
ps.append("Java");
ps.append("Android");
ps.append("Python");
ps.append("kotlin");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
- DataInputStream/DataOutputStream
这两个流比较特殊,它只能用于读取/写入基本数据类型(byte、char、short、int、long、float、double、boolean),额外还可以写入 String (readUTF)。但是写入的基本数据类型会形成字节码,我们只能通过 DataInputStream来读取。代码例子:
/**
* DataOutputStream 只能用于写入基本数据类型,反之也只能用DataInputStream来读取,否则读到的就是乱码
*/
@Test
public void dataIOTest() {
DataInputStream dis = null;
DataOutputStream dos = null;
try {
dis = new DataInputStream(new FileInputStream(new File("Hello2.txt")));
dos = new DataOutputStream(new FileOutputStream(new File("Hello2.txt")));
dos.writeUTF("Java");
dos.writeChar('c');
dos.writeLong(4564114564l);
dos.writeBoolean(false);
dos.write(22);
System.out.println(dis.readUTF());
System.out.println(dis.readChar());
System.out.println(dis.readLong());
System.out.println(dis.readBoolean());
System.out.println(dis.read());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用 FileInputStream 来读取的话,只能读到乱码文件:
![](https://img.haomeiwen.com/i18813666/439c77dcdea7c761.png)
使用DataInputStream来都,则正常显示:
![](https://img.haomeiwen.com/i18813666/4cbe178a90f84e31.png)
-
ObjectInputStream/ObjectOutputStream
这两个是对象处理流,主要是把对象持久性的存储。比如我有一个Student类,我创建了2个Student类的对象并对它进行了赋值,然后我想存储持久性这个对象,就需要这两个处理流。 -
序列化:允许把内存中的Java对象转换为二进制流来持久性的储存,通过网络可以传输序列化对象(二进制流)。
-
反序列化:可以通过网络接收到的序列化对象(二进制流)来恢复原来的Java对象。
需注意:处理流在存储对象的时候,比如一个类,这个类必须实现序列化接口(Serializable 或 Externalizable)。如果被 static 或 transient 修饰的变量,不可被序列化,接收到的就会是空值。例子如下:
ObjectOutputStream 序列化对象
/**
* 序列号过程
*/
@Test
public void objIOTest() {
Student stu1 = new Student("001", "张三");
Student stu2 = new Student("002", "李四");
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(new File("student.txt")));
oos.writeObject(stu1);
oos.flush();
oos.writeObject(stu2);
oos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ObjectInputStream 反序列化
/**
* 反序列化
*/
@Test
public void objIOTest2() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(new File("student.txt")));
Student get_stu1 = (Student) ois.readObject();
System.out.println(get_stu1);
Student get_stu2 = (Student) ois.readObject();
System.out.println(get_stu2);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-
RandomAccessFile
这个流属于比较特殊,它自身可以充当输入流,也可以充当输出流。原因是它的构造器可以传入一种 mode,这种 mode 共分为4种情况: -
r 只读
-
rw 读和写
-
rwd 读和写,并且同步更新内容
-
rws 读和写,并且同步更新内容和元数据
例子:通过RandomAccessFile对文本文件的插入操作,RandomAccessFile(以及所有流)在写入的时候,都会将文件中的数据覆盖。所以,通过seek();方法将光标往后移动,保存后面的字符数据后再插入,最后将字符数据加到后面即可。
/**
* RandomAccessFile 实现在文本中插入数据
*/
@Test
public void randomAccessTest() {
RandomAccessFile raf_rw = null;
try {
raf_rw = new RandomAccessFile(new File("Hello3.txt"), "rw");
// 光标移动到要插入的位置
raf_rw.seek(5);
// 保存后面的所有字符串
StringBuffer buf = new StringBuffer();
String str;
while ((str = raf_rw.readLine()) != null) {
buf.append(str + "\n");
}
System.out.println(buf.toString());
raf_rw.seek(5);
raf_rw.writeUTF("Java" + buf.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
raf_rw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
IO流的大致使用都过了一遍,这些仅仅是的基本用法。
最后
最后我准备了一些面试的知识汇总,数据结构,计算机网络等等都有。自己整理和分类的,还请尊重知识产出。
分享给大家的资料包括高级架构技术进阶脑图、Android开发面试专题资料,还有高级进阶架构资料包括但不限于【高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术】希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的!
资料免费领取方式:加群:797404811
![](https://img.haomeiwen.com/i18813666/2610317d5393a081.jpg)
网友评论