前言
io流在Java的知识体系中,属于很杂的那种了,主要是涉及的类太多,输入流、输出流、文件输入流、文件输出流、字节输入流和输出流、字符输入流和输出流等等,经常把人弄晕。但是它的用途却非常广泛,文件的操作、网络的操作等都离不开它,本文通过平时常用操作来梳理一下io流中涉及的重要类及方法。
什么是流
在Java中,文件一般不是单独处理的,而是视为输入输出设备的一种,Java使用一个统一的概念来处理所有的输入输出,包括键盘输入和网络输入输出等。这个统一的概念就是流,流又分为输入流和输出流,InputStream和OutputStream分别表示输入流和输出流,但这两个类是抽象类,大部分方法都是子类来实现的,如:
- BufferedInputStream和BufferedOutputStream对流起缓冲作用
- DataInputStream和DataOutputStream可以按8种基本类型和字符串类型对流进行读写
- ZipInputStream和ZipOutputStream可以对流进行压缩和解压缩
- printStream可以对基本类型和对象输出为其字符串表示
以InputStream和OutputStream为基类的流基本都是以二进制形式处理数据的,不能够方便地处理文本文件,没有编码的概念,能够方便地处理文本数据的基类是Reader和Writer,它也有很多子类,如:
- FileReader和FilerWriter用来读写文件
- BufferedReader和BufferedWriter起缓存装饰
- CharArrayReader和CharArrayWriter可以将字符数组包装为Reader和Writer
- StringReader和StringWriter可以将字符串包装为Reader和Writer
- InputStreamReader和OutputStreamWriter可以将InputStream和OutputStream转换为Reader和Writer
- printWriter可以将基本类型和对象输出为其字符串表示
二进制文件和字节流
二进制文件:所有文件里面的数据都是通过二进制来保存的,但为了方便处理数据,引入了文件类型的概念,主要分为文本文件和二进制文件。文本文件是指那些文本编辑器可以直接查看和编辑的文件,如.txt、.html、.java等文件;二进制文件是指一些特殊格式和功能的文件,如.zip、.jpg、.doc、.pdf等文件
Java主要通过InputStream和OutputStream来实现二进制字节形式处理文件。
OutputStream
OutputStream主要有下面几个方法:
public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException
public void write(byte b[], int off, int len) throws IOException
public void close() throws IOException
public void flush() throws IOException
write(int b)
方法向流中写入一个字节,需要子类来实现。
write(byte b[])
方法里面调用write(byte b[], int off, int len)
方法来实现,将b[]数组从第off位置开始,len长度的数据写入到流中。
close()
关闭流,上面几个方法就算流中的数据都操作完了,也会阻塞,等待流中继续添加数据,因此我们操作完数据以后,需要在finally方法中通过close()方法来关闭流。
flush()
将缓冲而未实际写入的数据进行实际写入,一般作用于带缓冲的流中,如BufferedOutputStream。
举例
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream("hello.txt");
String data = "hello world";
outputStream.write(data.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
outputStream.close();
}
InputStream
InputStream主要有下面几个方法:
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException
public int read(byte b[], int off, int len) throws IOException
public void close() throws IOException
read()
方法从流中读取下一个字节,需要子类来实现,返回这个字节的值,当读到流结尾时返回-1。
read(byte b[])
里面也是调用read(byte b[], int off, int len)
来实现,从流的第off位置开始,读取len长度放入b[]数组中。
close()
关闭流,同OutputStream。
举例:
InputStream inputStream = null;
try {
inputStream = new FileInputStream("hello.txt");
byte[] arr = new byte[1024];
inputStream.read(arr);
System.out.println(new String(arr));
} catch (Exception e) {
e.printStackTrace();
} finally {
inputStream.close();
}
首先构建一个需要读取的文件的输入流,然后通过read(byte b[])
方法将流中的数据读取到字节数组中,这样就简单实现了一个读取文件的功能。但这里有一个前提,文件中的字节长度不超过1024,那如果超过这个长度,我们怎么样才能将流中数据读取到一个字节数组中呢?
可以借助ByteArrayOutputStream。通过名字可以看出是将流中数据输出到字节数组中。
InputStream inputStream = null;
try {
inputStream = new FileInputStream("hello.txt");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] arr = new byte[4];
int len;
while ((len = inputStream.read(arr)) > -1) {
byteArrayOutputStream.write(arr, 0, len);
}
String data = byteArrayOutputStream.toString();
System.out.println(data);
} catch (Exception e) {
e.printStackTrace();
} finally {
inputStream.close();
}
ByteArrayOutputStream的输出目标是byte数组,这个数组的长度是根据数据内容动态扩展的,当调用write方法的过程中,如果数组大小不够,会进行扩展,每次扩展长度翻倍。
BufferedInputStream和BufferedOutputStream
FileInputStream和FileOutputStream是没有缓冲的,按单个字节读写时性能比较低,虽然可以按字节数组读取来提高性能,但有时必须按字节读写,这时就需要将文件流包装到缓冲流中来提高性能。
使用方法只要在对应的流外层包上对应的缓冲类就行了,如下:
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("hello.txt"));
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("hello.txt"));
文本文件和字符流
上面介绍了如何以字节流的方式处理文件,对于文本文件,字节流没有编码的概念,不能按行处理,使用不太方便,更适合用字符流来处理。
字符流和字节流类似,也是有两个基类,Reader和Writer。Reader和InputStream类似,Writer和OutputStream类似,具体的操作都是通过子类来实现。
InputStreamReader和OutputStreamWriter
InputStreamReader可以将InputStream转换为Reader,构造方法如下:
public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in, String charsetName)
一个重要的参数是编码类型,对于字符流来说,编码是很重要的,相同二进制内容,不同编码情况下,可能会出现不同结果。通过传入输入流和编码类型来构造出字符输入流,如果不传编码类型,则为系统默认编码,默认编码可以通过Charset.defaultCharset()得到。
使用举例
Reader reader = null;
try {
reader = new InputStreamReader(new FileInputStream("hello.txt"));
char[] chars = new char[1024];
reader.read(chars);
System.out.println(new String(chars));
}finally {
reader.close();
}
将文件中数据读取到内存中,这段代码一次reader就读取了所有的内容,前提是文件内容不超过数组长度,如果文件内容很长,可以使用CharArrayWriter或StringWriter,后面会介绍。
OutputStreamWriter可以将OutputStream转换为Writer,构造方法如下:
public OutputStreamWriter(OutputStream out)
public OutputStreamWriter(OutputStream out, String charsetName)
构造方法和InputStreamReader类似
使用举例
Writer writer = null;
try {
writer = new OutputStreamWriter(new FileOutputStream("hello.txt"),"UTF-8");
writer.write("hello pipi蛋");
}finally {
writer.close();
}
InputStreamReader和OutputStreamWriter内部都有一个类型为StreamDecoder的解码器,能将字节根据编码转换为char。
CharArrayReader和CharArrayWriter
CharArrayWriter与ByteArrayOutputStream类似,它的输出目标是char数组,这个数组长度可以根据内容动态扩展。
上面通过字符流读取文件内容的代码,我们可以优化一下,如下:
Reader reader = null;
try {
reader = new InputStreamReader(new FileInputStream("hello.txt"));
CharArrayWriter arrayWriter = new CharArrayWriter();
char[] chars = new char[1024];
int len = 0;
while ((len = reader.read(chars))> -1) {
arrayWriter.write(chars, 0, len);
}
System.out.println(arrayWriter.toString());
}finally {
reader.close();
}
StringReader和StringWriter与CharArrayReader和CharArrayWriter类似,只是输出目标是StringBuffer,而且String和StringBuffer内部都是由char数组组成的,所以他们本质上是一样的。
BufferedReader和BufferedWriter
BufferedReader/BufferedWriter与BufferedInputStream/BufferedOutputStream类似,提供缓冲功能,只需要在外层包装一层缓冲类就行了,而且可以按行读写。
按行写入举例:
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter("hello.txt"));
writer.write("第一行");
writer.newLine();
writer.write("第二行");
writer.newLine();
}finally {
writer.close();
}
按行读取举例:
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("hello.txt"));
String s = null;
while ((s = reader.readLine()) != null) {
System.out.println(s);
}
}finally {
reader.close();
}
PrintWriter
PrintWriter有很多构造方法,可以接受文件路径名、文件对象、OutputStream、Writer等。PrintWriter是一个非常方便的类,可以直接指定文件名作为参数,可以指定编码类型,可以自动缓冲,可以字段将多种类型转换为字符串,在输出到文件时,可以优先选择该类
上面按行写入的代码,可以使用PrintWriter写为:
PrintWriter printWriter = null;
try {
printWriter = new PrintWriter("hello.txt");
printWriter.println("第一行");
printWriter.println("第二行");
printWriter.println("第三行");
}finally {
printWriter.close();
}
可以看出printWriter相对而言,方便很多。
封装方法
字节流
复制InputSteam到OutputStream
public void copy(InputStream in, OutputStream out) throws Exception{
byte[] arr = new byte[1024];
int len = 0;
while ((len = in.read(arr)) > -1) {
out.write(arr, 0, len);
}
}
将文件内容读取到字节数组中
public byte[] file2Array(String fileName) throws Exception{
InputStream in = null;
try {
in = new FileInputStream(fileName);
ByteArrayOutputStream out = new ByteArrayOutputStream();
copy(in,out);
return out.toByteArray();
}finally {
in.close();
}
}
再将字节数组写入到文件中
public void array2File(String fileName,byte[] bytes) throws Exception{
OutputStream out = null;
try {
out = new FileOutputStream(fileName);
out.write(bytes);
}finally {
out.close();
}
}
字符流
复制Reader到Writer中
public void copy(Reader reader, Writer writer) throws Exception{
char[] chars = new char[1024];
int len = 0;
while ((len = reader.read(chars)) > -1) {
writer.write(chars, 0, len);
}
}
将文件内容读入到一个字符串中
public String file2String(String fileName,String encoding) throws Exception {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), encoding));
StringWriter writer = new StringWriter();
copy(reader,writer);
return writer.toString();
}finally {
reader.close();
}
}
将字符串内容写入到文件中
public void string2File(String fileName,String encoding,String data) throws Exception{
Writer writer = null;
try {
writer = new OutputStreamWriter(new FileOutputStream(fileName),encoding);
writer.write(data);
}finally {
writer.close();
}
}
按行写入到文件中
public void writeLines(String fileName, String encoding, Collection lines) throws Exception{
PrintWriter writer = null;
try {
writer = new PrintWriter(fileName,encoding);
for (Object line : lines) {
writer.println(line);
}
}finally {
writer.close();
}
}
按行读取文件中内容,返回内容集合
public List<String> readLines(String fileName,String encoding)throws Exception {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName),encoding));
List<String> list = new ArrayList<>();
String line = null;
while ((line = reader.readLine()) != null) {
list.add(line);
}
return list;
}finally {
reader.close();
}
}
总结
我们平时开发的时候对文件或者网络内容使用流操作时,可能都是使用第三方类库,比如Apache有一个类库Commons IO,里面提供了很多简单易用的方法,当然使用这些类库一点毛病都没有,但是,如果有些时候不能使用第三方类库的时候,比如开发一些通用的jar包时,肯定越少依赖越好,这时候就要熟悉jdk中的一些常用io操作了。