java 学习笔记-IO(六)
io
标签:常见 io 流总结
输入输出流
JAVA中流方向的分类:
-
输入流:数据流向是数据源到程序(以InputStream、Reader结尾的流)。
-
输出流:数据流向是程序到目的地(以OutPutStream、Writer结尾的流)。
按处理数据的单元分类:
-
字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流,如FileInputStream、FileOutputStream。
-
字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWriter。
按处理对象不同分类
-
节点流:可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等。
-
处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。处理流也叫包装流。
-
节点流处于IO操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。
显然是对文件做输入和输出操作的。我们下面简单做个总结:
- InputStream/OutputStream
字节流的抽象类。
- Reader/Writer
字符流的抽象类。
- FileInputStream/FileOutputStream
节点流:以字节为单位直接操作“文件”。
- ByteArrayInputStream/ByteArrayOutputStream
节点流:以字节为单位直接操作“字节数组对象”。
- ObjectInputStream/ObjectOutputStream
处理流:以字节为单位直接操作“对象”。
- DataInputStream/DataOutputStream
处理流:以字节为单位直接操作“基本数据类型与字符串类型”。
- FileReader/FileWriter
节点流:以字符为单位直接操作“文本文件”(注意:只能读写文本文件)。
- BufferedReader/BufferedWriter
处理流:将Reader/Writer对象进行包装,增加缓存功能,提高读写效率。
- BufferedInputStream/BufferedOutputStream
处理流:将InputStream/OutputStream对象进行包装,增加缓存功能,提高 读写效率。
- InputStreamReader/OutputStreamWriter
处理流:将字节流对象转化成字符流对象。
- PrintStream
处理流:将OutputStream进行包装,可以方便地输出字符,更加灵活。
java IO体系架构图
inputStream
继承自InputSteam的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)。
常用方法:int read():读取一个字节的数据,并将字节的值作为int类型返回(0-255之间的一个值)。如果未读出字节则返回-1(返回值为-1表示读取结束)。
小贴士:void close():关闭输入流对象,释放相关系统资源。
Reader
-
Reader用于读取的字符流抽象类,数据单位为字符。
-
int read(): 读取一个字符的数据,并将字符的值作为int类型返回(0-65535之间的一个值,即Unicode值)。如果未读出字符则返回-1(返回值为-1表示读取结束)。
-
void close() : 关闭流对象,释放相关系统资源。
将字符串/字节数组的内容写入到文件中
public class TestOutputStream {
public static void main(String[] args) {
FileOutputStream fops = null ;
String s = "将字符串/字节数组的内容写入到文件中";
try {
// true表示内容会追加到文件末尾;false表示重写整个文件内容。
fops = new FileOutputStream("char.txt",true);
//该方法是直接将一个字节数组写入文件中; 而write(int n)是写入一个字节
fops.write(s.getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fops != null){
try {
fops.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
用到一个write方法:void write(byte[ ] b),该方法不再一个字节一个字节地写入,而是直接写入一个字节数组;另外其还有一个重载的方法:void write(byte[ ] b, int off, int length),这个方法也是写入一个字节数组,但是我们程序员可以指定从字节数组的哪个位置开始写入,写入的长度是多少。
利用文件流实现文件的复制
/**
* 〈一句话功能简述〉<br>
* 〈利用文件流实现文件的复制〉
*
* @author willem
* @create 2019-05-15
* @since 1.0.0
*/
public class TestFileCopy {
public static void main(String[] args) {
copyFile("char.txt","char1.txt");
}
static void copyFile(String src,String dec){
FileInputStream fis = null;
FileOutputStream fos = null;
//为了提高效率设置缓存数组(读取的字节数据暂时会存在该字节数组中)
byte[] buffer = new byte[1024];
int temp = -1;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//边读边写
//temp指的是本次读取的真实长度,-1的时候表示读取完成
while ((temp = fis.read(buffer)) != -1){
/*将缓存数组中的数据写入文件中,注意:写入的是读取的真实长度;
*如果使用fos.write(buffer)方法,那么写入的长度将会是1024,即缓存
*数组的长度*/
fos.write(buffer,0,temp);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
-
在使用文件字节流时,我们需要注意以下两点:
-
为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,读取时使用的方法为:read(byte[] b);写入时的方法为:write(byte[ ] b, int off, int length)。
-
程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况。
-
利用文件字符流复制
/**
* 〈一句话功能简述〉<br>
* 〈使用FileReader与FileWriter实现文本文件的复制〉
*
* @author willem
* @create 2019-05-15
* @since 1.0.0
*/
public class TestFileCopy2 {
public static void main(String[] args) {
copyFile2("char.txt","char3.txt");
}
static void copyFile2(String src ,String dec){
FileReader fr = null;
FileWriter fw = null;
int len = -1;
try {
fr = new FileReader(src);
fw = new FileWriter(dec);
//为了提供效率引入缓存数组
char[] buffer = new char[1024];
while((len = fr.read(buffer)) != -1){
fw.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意
-
在关闭流时,应该先关闭最外层的包装流,即“后开的先关闭”。
-
缓存区的大小默认是8192字节,也可以使用其它的构造方法自己指定大小。
使用BufferReader和BufferWriter进行文本复制
/**
* Copyright (C), 2015-2019, 中信银行有限公司
* FileName: TestBufferedFileCopy2
* Author: willem
* Date: 2019-05-16 08:48
* Description: 使用BufferReader和BufferWriter
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.citic.java.io;
import java.io.*;
/**
* 〈一句话功能简述〉<br>
* 〈使用BufferReader和BufferWriter进行文件复制〉
*
* @author willem
* @create 2019-05-16
* @since 1.0.0
*/
public class TestBufferedFileCopy2 {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
BufferedReader br = null;
BufferedWriter bw = null;
String tempString = "";
try {
fr = new FileReader("char.txt");
fw = new FileWriter("char4.txt");
//使用缓存字符流进行包装
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
//bufferreader提供了更好的方法readline()方法
while((tempString = br.readLine()) != null){
bw.write(tempString);
bw.newLine();
}
}catch (FileNotFoundException e) {
e.printStackTrace();
} 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();
}
}
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意
-
readLine()方法是BufferedReader特有的方法,可以对文本文件进行更加方便的读取操作。
-
写入一行后要记得使用newLine()方法换行。
对象流
-
我们前边学到的数据流只能实现对基本数据类型和字符串类型的读写,并不能读取对象(字符串除外),如果要对某个对象进行读写操作,我们需要学习一对新的处理流:ObjectInputStream/ObjectOutputStream。
-
ObjectInputStream/ObjectOutputStream是以“对象”为数据源,但是必须将传输的对象进行序列化与反序列化操作。
注意:
-
对象流不仅可以读写对象,还可以读写基本数据类型。
-
使用对象流读写对象时,该对象必须序列化与反序列化。
-
系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。
转换流
-
InputStreamReader/OutputStreamWriter用来实现将字节流转化成字符流。比如,如下场景:
-
System.in是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必须用到缓冲字符流BufferedReader特有的方法readLine(),但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象,这时候我们的转换流InputStreamReader就派上用场了。
-
而System.out也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符串直接显示到控制台,就需要用到字符流的write(String str)方法,所以我们要使用OutputStreamWriter将字节流转化为字符流。
序列化和反序列化是什么
-
当两个进程远程通信时,彼此可以发送各种类型的数据。 无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息;我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象才能正常读取。
-
把Java对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为对象的反序列化。
对象序列化的作用有如下两种:
-
持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中,比如:休眠的实现。以后服务器session管理,hibernate将对象持久化实现。
-
网络通信:在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。
序列化涉及的类和接口
-
ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
-
ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
-
只有实现了Serializable接口的类的对象才能被序列化。 Serializable接口是一个空接口,只起到标记作用。
/**
* Copyright (C), 2015-2019, 中信银行有限公司
* FileName: TestSerializable
* Author: willem
* Date: 2019-05-16 11:00
* Description: 将Person类的实例进行序列化和反序列化
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.citic.java.io;
import java.io.*;
import java.util.Objects;
/**
* 〈一句话功能简述〉<br>
* 〈将Person类的实例进行序列化和反序列化〉
*
* @author willem
* @create 2019-05-16
* @since 1.0.0
*/
class Person implements Serializable {
private static final long serialVersionUID = 1l;
private String name;
private int id ;
private int age;
public Person(){}
public Person(String name,int id,int age){
this.name = name;
this.id = id;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Person)) {
return false;
}
Person person = (Person) o;
return id == person.id &&
age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, id, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
public class TestSerializable {
public static void main(String[] args) {
FileOutputStream fos = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
FileInputStream fis = null;
//通过ObjectOutStream将Person对象数据写入到文件中,序列化。
Person person = new Person("哈皮",1,3);
try {
//序列化
fos = new FileOutputStream("heool.txt");
oos = new ObjectOutputStream(fos);
oos.writeObject(person);
oos.flush();
//序列化
fis = new FileInputStream("heool.txt");
ois = new ObjectInputStream(fis);
Person p = (Person) ois.readObject();
System.out.println(p);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意:
-
static属性不参与序列化。
-
对象中的某些属性如果不想被序列化,不能使用static,而是使用transient修饰。
-
为了防止读和写的序列化ID不一致,一般指定一个固定的序列化ID。
IO流体系中的装饰器模式
- IO流体系中大量使用了装饰器模式,让流具有更强的功能、更强的灵活性。比如:
- 显然BufferedInputStream装饰了原有的FileInputStream,让普通的FileInputStream也具备了缓存功能,提高了效率。 大家举一反三,可以翻看本章代码,看看还有哪些地方使用了装饰器模式。
Apache IOUtils和FileUtils的使用
FieUtils类中常常方法的介绍
-
打开文件实用程序的API文档,我们抽出一些工作中比较常用的方法,进行总结和讲解总结如下:
-
cleanDirectory:清空目录,但不删除目录。
-
contentEquals:比较两个文件的内容是否相同。
-
copyDirectory:将一个目录内容拷贝到另一个目录。可以通过FileFilter过滤需要拷贝的文件。
-
CopyFile:将一个文件拷贝到一个新的地址。
-
copyFileToDirectory:将一个文件拷贝到某个目录下。
-
copyInputStreamToFile:将一个输入流中的内容拷贝到某个文件。
-
deleteDirectory:删除目录。
-
deleteQuietly:删除文件。
-
listFiles:列出指定目录下的所有文件。
-
openInputSteam:打开指定文件的输入流。
-
readFileToString:将文件内容作为字符串返回。
-
readlines方法:将文件内容按行返回到一个字符串数组中。
-
尺寸:返回文件或目录的大小。
-
写:将字符串内容直接写到文件中。
-
writeByteArrayToFile:将字节数组内容写到文件中。
-
writeLines:将容器中的元素的的toString方法返回的内容依次写入文件中。
-
writeStringToFile:将字符串内容写到文件中。
IOUtils的妙用
-
buffer方法:将传入的流进行包装,变成缓冲流。并可以通过参数指定缓冲大小。
-
closeQueitly方法:关闭流。
-
contentEquals方法:比较两个流中的内容是否一致。
-
copy方法:将输入流中的内容拷贝到输出流中,并可以指定字符编码。
-
copyLarge方法:将输入流中的内容拷贝到输出流中,适合大于2G内容的拷贝。
-
lineIterator方法:返回可以迭代每一行内容的迭代器。
-
read方法:将输入流中的部分内容读入到字节数组中。
-
readFully方法:将输入流中的所有内容读入到字节数组中。
-
readLine方法:读入输入流内容中的一行。
-
toBufferedInputStream,toBufferedReader:将输入转为带缓存的输入流。
-
toByteArray,toCharArray:将输入流的内容转为字节数组、字符数组。
-
toString:将输入流或数组中的内容转化为字符串。
-
write方法:向流里面写入内容。
-
writeLine方法:向流里面写入一行内容。