Java IO之FileInputStream与FileOutputStream对象常用操作
涉及到文件(非文件夹)内容的操作,除了要用到File(见之前文章),另外就必须用到输入流或输出流。
输入流:该流处理时,数据由外部流向程序(内存),一般指代“读取数据”,更清晰点地说:从外部读取数据到内存中。
输出流:该流处理时,数据由程序(内存)流向外部,一般指代“写入数据”,更清晰点地说:将数据从内存写入到外部。
如果要操作字节(比如Soket,字节数据、图片、视屏等非纯文本文件),则采用字节输入流与字节输出流。在Java中,可使用: InputStream 与 OutputStream 及其子类。
如果要操作字符(比如字符数据、纯文本文件),则采用字符输入流与字符输出流。在Java中,可使用:Reader 与 Writer 及其子类。
对字节的操作,采用 InputStream与OutputStream。它们的为声明分别为:
public abstract class InputStream
public abstract class OutputStream
它们都是抽象类,需要由具体子类进行实例化。
InputStream主要子类有:
- AudioInputStream:读取音像媒体的字节流。
- ByteArrayInputStream:读取字节数组的字节流。
- FileInputStream:读取文件的字节流。
- FilterInputStream:过滤器输入流,用于装饰。其子类有:PushbackInputStream , BufferedInputStream
- ObjectInputStream:读取序列化的对象的字节流。
- PipedInputStream:读取管道的字节流。
- SequenceInputStream:读取序列的字节流。
- PushbackInputStream:读取可以推回的字节流。继承自:FilterInputStream
- BufferedInputStream:读取有缓存效果的字节流。继承自:FilterInputStream
- StringBufferInputStream:读取字符缓冲的字节流。已过时,不作介绍。
常用的有FileInputStream,我们将以它为例。
OutputStream主要子类有:
- ByteArrayOutputStream:将字节写入到字节数组
- FileOutputStream:将字节写入到文件
- FilterOutputStream:过滤器输出流,用于装饰
- ObjectOutputStream:将字节写入到序列化对象
- PipedOutputStream:将字节写入管道
常用的有FileOutputStream,我们将以它为例。
InputStream与OutputStream的方法
InputStream字节输入流,其主要方法有:
int available():获取可用的字节总长度。返回int类型,该方法只要用于获取可被后续线程使用的流的长度,并且随已读取字节的变化而变化(见下边例子),特别是用它来表示文件大小时并不可靠(因为特大文件的字节长度可能是long字节。对于文件字节数,可以使用file.length()来代替)。
void close():释放流对象。用于关闭资源。close()后无法进行read及skip等操作。所以一般在流操作结束后进行close()调用。
void mark(int readlimit):标记流的位置。系统不一定支持。不推荐使用。
boolean markSupported():检测是否支持流位置标记。(mark方法与reset方法在其支持下才可用。一般能不用则不用此3个方法)
abstract int read():从流中读取一个字节,如果没有数据,返回-1。
int read(byte[] b):从流中读取字节,并将数据存入到指定的字节数组中,读取的字节数为指定数组的长度。如果没有数据,返回-1。
int read(byte[] b, int off, int len):从流中读取字节,并将数据存入到指定的字节数组中,读取的字节数为len,存入时从数组off开始存。如果没有数据,返回-1。
void reset():从新设置流的开始。系统不一定支持。不推荐使用。
long skip(long n):跳过指定数目字节。可以是正数负数或0(对于文件流来说,该方法已被重载,可以用来解决系统不支持reset()问题。负数是往回转跳,0不进行转跳。)。
输入字节流InputStream用于“读取”,其中最常用的方法是:read(), read(byte[] b), read(byte[] b, int off, int len)
综合测试代码:
1 import java.io.File;
2 import java.io.FileInputStream;
3 import java.io.IOException;
4 import java.io.InputStream;
5
6 public class File001 {
7 public static void main(String[] args) throws IOException {
8 File file = new File("g:/java2019/file.txt");//文件内容:abc
9
10 //打开流
11 InputStream is = new FileInputStream(file);
12 long len = file.length();
13 System.out.println("available:"+is.available());
14 System.out.println(is.read());//读取第一个字节:a
15 System.out.println(is.read());//读取第一个字节:b
16 System.out.println(is.read());//读取第一个字节:c
17 System.out.println(is.read());//-1
18 System.out.println(is.read());//-1
19 System.out.println(is.read());//-1
20
21 System.out.println("available:"+is.available());
22
23 if(is.markSupported()){
24 System.out.println("IO支持标记,可以进行重定位到开头标记处。");
25 is.reset();
26 }else{
27 System.out.println("IO不支持标记,不可以进行重定位到开头标记处。使用回跳处理。");
28 is.skip(-len);
29 }
30
31 for(int i=0;(i=is.read())!=-1;){
32 System.out.println(i);
33 }
34
35 is.skip(-len);
36
37 byte[] b3 = new byte[3];
38 int len2 = is.read(b3,0,1);//从流中读取1个字节,存在b3字节数组中,从b3的0位置开始存。
39 System.out.println("读取长度为::"+len2);
40 System.out.println("first byte:"+b3[0]);
41
42 is.skip(-1);
43 is.read(b3);//从流中读取b3字节数组对应大小的字节存入b3中。
44 for(byte b : b3){
45 System.out.println(b);
46 }
47
48 //关闭流
49 is.close();
50 }
51
52 }
输出:
available:3
97
98
99
-1
-1
-1
available:0
IO不支持标记,不可以进行重定位到开头标记处。使用回跳处理。
97
98
99
读取长度为::1
first byte:97
97
98
99
说明:
- FileInputStream是文件字节输入流。其构造方法有:FileInputStream(File file) ,FileInputStream(String name), FileInputStream(FileDescriptor fdObj)。其中FileInputStream(String name)最为常用,它其实调用的也是FileInputStream(File file)。如果没有什么获取文件指定大小(需要用到File对象的length()方法:其实也可以通过name再构造一个File对象),推荐使用FileInputStream(String name),它最为简洁。
- FileInputStream对象一旦进行实例化,就会立刻进行文件的打开,所以如果文件不存在,会抛出FileNotFoundException异常(属于IOException)。所以该流使用结束后必须调用close()方法来进行关闭,进行资源释放。
- read()方法一次从文件输入流中读取一个字节,并返回int类型,并不返回byte类型,因为具体读取到的字节属于属于-128到127(占8位),直接返回的话,难以判定是读取到的字节数是-1,还是读取到结尾了,可能造成读取中断问题。所以返回int的话,会将8位的-1转化为32位int类型的255,避免冲突。read(byte[] b)以及read(byte[] b, int off, int len)方法一次读取数个字节,返回读取到的字节数,读取到结尾时返回-1。
- read()方法是阻塞的。即线程运行到read()处,该线程必须等待read()完成才能进行该线程后续代码执行操作。在网络通信状态不稳定或者socket等待时,往往配合多线程进行处理。read(byte[] b)以及read(byte[] b, int off, int len)同理。
- 对于文件流的读取,推荐使用read(byte[] b, int off, int len)来处理,可以减少读取次数,取得高效。
- for(int i=0;(i=is.read())!=-1;){} 块可用于循环读取输入流的字节,当读取到的结尾时返回-1。也可以用int i=0;while((i=is.read())!=-1){}块,效果同样(只是多了一行)。
OutputStream字节输入流,其主要方法有:
void close():释放流对象。用于关闭资源。close()后无法进行write及flush等操作。所以一般在流操作结束后进行close()调用。
void flush():刷新输出流,以便数据能够完全被写入。
void write(byte[] b):一次写入所给的字节数组的所有数据。
void write(byte[] b, int off, int len):一次写入所给的字节数组从off位置开始,长度为len的数据。
abstract void write(int b):一次写入一个字节。
输出字节流InputStream用于“写入”,其中最常用的方法是:write(byte[] b) , write(byte[] b, int off, int len) , write(int b)
测试代码:
1 import java.io.FileOutputStream;
2 import java.io.IOException;
3 import java.io.OutputStream;
4
5 public class File002 {
6 public static void main(String[] args) throws IOException {
7 String filepath = "G:/java2019/file123.txt";
8 //打开文件构造输出流,如果文件不存在则进行创建,如果文件上级路径不存在,则抛出FileNotFoundException
9 OutputStream os = new FileOutputStream(filepath);
10
11 os.write(97);//写入a
12 os.write(98);//写入b
13 os.write('c');//写入c
14
15 byte[] b = new byte[]{97,98,99};
16
17 os.write(b);
18
19 os.write(b, 2, 1);
20 os.write(b, 1, 1);
21 os.write(b, 0, 1);
22
23 //关闭输出流
24 os.close();
25
26 //再次打开,需要再次构造。在文件内容后边进行写入。
27 os = new FileOutputStream(filepath, true);
28 String LINE = System.getProperty("line.separator");
29 byte[] bLine = null;
30 if(LINE.equals("\r")){
31 bLine = new byte[]{10};
32 }else if(LINE.equals("\n")){
33 bLine = new byte[]{13};
34 }else{// LINE.equals("\r\n")
35 bLine = new byte[]{10,13};
36 }
37
38 //输出换行
39 os.write(bLine);
40
41 os.write(new byte[]{'A','B','C'});
42
43 //关闭输出流
44 os.close();
45
46 }
47 }
文件内容:
abcabccba
(这时是空白行)
ABC
说明:
- FileOutputStream是文件字节输出流。其构造方法有:FileOutputStream(File file) ,FileOutputStream(File file, boolean append) ,FileOutputStream(String name),FileOutputStream(String name, boolean append), FileOutputStream(FileDescriptor fdObj)。其中FileOutputStream(String name)及FileOutputStream(String name, boolean append)最为常用,它其实调用的也是FileOutputStream(File file)或FileOutputStream(File file, boolean append)。如果没有什么获取文件指定大小(需要用到File对象的length()方法:其实也可以通过name再构造一个File对象),推荐使用FileoutputStream(String name)及FileOutputStream(String name, boolean append),它最为简洁。boolean类型的参数append指明对文件内容的写入是否要接在文件末尾,如果不想覆盖原来文件的内容,则需要使用FileOutputStream(name,true),因为默认情况下是false(会覆盖)。
- FileOutputStream对象一旦进行实例化,就会立刻进行文件的打开,所以如果文件不存在,会自动创建,如果文件所在路径(上级目录)不存在,会抛出FileNotFoundException异常(属于IOException)。所以该流使用结束后必须调用close()方法来进行关闭,进行资源释放。
- write(int b)方法一次向文件输出流中写入一个字节,int类型会最终转化为byte类型。write(byte[] b)以及write(byte[] b, int off, int len)方法一次写入数个字节。
- write()方法是不会阻塞的。wrie(byte[] b)以及write(byte[] b, int off, int len)同理。
- 对于文件流的写入,推荐使用write(byte[] b, int off, int len)来处理,可以减少写入次数,取得高效。
- byte[] b=new byte[1024*8];int len=0;while((len=is.read(b))!=-1){os.write(b,0,len);}块可以配合读取、写入操作,进行高效的文件的复制。
高效的字节文件复制操作代码:
1 import java.io.FileInputStream;
2 import java.io.FileOutputStream;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6
7 public class File003 {
8 public static void main(String[] args) throws IOException {
9 String from = "G:/java2019/file.txt";
10 String to = "G:/java2019/file2.txt";
11 InputStream is = new FileInputStream(from);
12 OutputStream os = new FileOutputStream(to);
13
14 byte[] b = new byte[1024*8];
15 int len = 0;
16 while((len=is.read(b))!=-1){
17 os.write(b, 0, len);
18 }
19
20 is.close();
21 os.close();
22 }
23 }
IO中,要处理具体的异常,可以这样:
1 import java.io.FileInputStream;
2 import java.io.FileOutputStream;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6
7 public class File004 {
8 public static void main(String[] args) {
9 String from = "G:/java2019/file.txt";
10 String to = "G:/java2019/file2.txt";
11 InputStream is = null;
12 OutputStream os = null;
13 try {
14 is = new FileInputStream(from);
15 os = new FileOutputStream(to);
16
17 byte[] b = new byte[1024 * 8];
18 int len = 0;
19 while ((len = is.read(b)) != -1) {
20 os.write(b, 0, len);
21 }
22
23 } catch (IOException e) {
24 e.printStackTrace();
25 } finally {
26 if (is != null) {
27 try {
28 is.close();
29 } catch (IOException e) {
30 e.printStackTrace();
31 }
32 }
33
34 if (os != null) {
35 try {
36 os.close();
37 } catch (IOException e) {
38 e.printStackTrace();
39 }
40 }
41 }
42
43 }
44 }
不过这样很烦索,如果在JDK1.7及以上版本,可以使用try-resource改进,代码:
1 import java.io.FileInputStream;
2 import java.io.FileOutputStream;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6
7 public class File005 {
8 public static void main(String[] args) {
9 String from = "G:/java2019/file.txt";
10 String to = "G:/java2019/file2.txt";
11 try (InputStream is = new FileInputStream(from);OutputStream os = new FileOutputStream(to);) {
12 byte[] b = new byte[1024 * 8];
13 int len = 0;
14 while ((len = is.read(b)) != -1) {
15 os.write(b, 0, len);
16 }
17 } catch (IOException e) {
18 e.printStackTrace();
19 }
20
21 }
22 }
JAVA也提供了比较高效的内置缓存流:BufferedInputStream及BufferedOutputStream。其内置缓冲区大小默认为1024*8(如果需要改变,可以在构造的时候传入其它值)。它们直接由InputSteam及OutputStream构造,所有方法都与InputStream及OutputStream相同。不过不同的是对于OutputStream,其write()实际上是写入到缓冲区,然后判断缓冲区是否满了,如果满了则使用flush()刷新缓冲区,同时写入文件,如果最后一次的时候刚才未满,则不能保证是否写入,这样会造成断尾了。可以使用其flush()方法,或者当进行close()的时候,也会进行缓冲区的刷新及文件的写入(不过对于JDK1.7及以上版本使用try-resource处理异常的情况下,会自动进行close(),不太需要担心)
BufferedInputStream构造器:
public BufferedInputStream(InputStream in):以字节输入流构造缓冲输入流,默认1024*8的缓冲区,常用。
public BufferedInputStream(InputStream in, int size)
BufferedOutputStream构造器:
public BufferedOutputStream(OutputStream out):以字节输出流构造缓冲输出流,默认1024*8的缓冲区,常用。
public BufferedOutputStream(OutputStream out, int size):以字节输出流构造缓冲输出流,缓冲区大小由size指定。
测试代码:
1 import java.io.BufferedInputStream;
2 import java.io.BufferedOutputStream;
3 import java.io.FileInputStream;
4 import java.io.FileOutputStream;
5 import java.io.IOException;
6
7 public class File006 {
8 public static void main(String[] args) {
9 String from = "G:/java2019/file.txt";
10 String to = "G:/java2019/file2.txt";
11 try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(from));
12 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(to));) {
13 byte[] b = new byte[1024 * 8];
14 int len = 0;
15 while ((len = is.read(b)) != -1) {
16 os.write(b, 0, len);
17 }
18 } catch (IOException e) {
19 e.printStackTrace();
20 }
21
22 }
23 }
说明:
可以知道,使用字节缓冲流处理输入输出的时候,跟之前的代码相比,只需要改变构造器就可以,其它代码可以一概不变,因为缓冲字节流使用的是字节流对象构造并且直接重写其所有方法。
要进行高效的字节流处理,一方面可以使用缓冲字节流,另一方面可以使用自己写的高效处理方法(同上边“字节文件的复制”)。推荐使用后者,更灵活,更高效。
除了InputStream与OutputStream的应用中,除了FileInputStream与FileOutputStream常用来处理文件,也可以处理字节数组,数据,序列对象,管道,字符缓冲等。
ByteArrayInputStream可以从字节数组中获取字节输入流数据,ByteArrayOutputStream可以将数据写入字节数组输出流。代码:
1 import java.io.ByteArrayInputStream;
2 import java.io.ByteArrayOutputStream;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6
7 public class File007 {
8 public static void main(String[] args) {
9 byte[] b = new byte[] {97,98,99};
10 try(InputStream is = new ByteArrayInputStream(b); OutputStream os = new ByteArrayOutputStream()){
11 int i = 0;
12 while((i=is.read())!=-1){
13 os.write(i);
14 }
15
16 System.out.println(os.toString());
17 } catch (IOException e) {
18 e.printStackTrace();
19 }
20
21 }
22 }
DataInputStream可以从int,long,float,double,char,byte[],boolean等中获取字节输入流的数据,DataOutputStream可以将int,long,float,double,char,byte[],boolean等数据写入字节输出流。代码:
1 public class File007 {
2 public static void main(String[] args) {
3 byte[] b = new byte[] { 97, 98, 99, 100 };
4 try (DataInputStream is = new DataInputStream(new ByteArrayInputStream(b));
5 DataOutputStream os = new DataOutputStream(new FileOutputStream("g:/java2019/file.txt"))) {
6 char c = 0;
7 c = is.readChar();// 读取两个字节
8 os.writeChar(c);// 写入两个字节
9 System.out.println(c);
10 c = is.readChar();
11 os.writeChar(c);
12 System.out.println(c);
13 } catch (IOException e) {
14 e.printStackTrace();
15 }
16
17 }
18 }
输出:
慢
捤
文件内容:abcd
ObjectInputStream可以从已经序列化的对象(对象相对应的某种格式化的文件)等中获取字节输入流的数据,DataOutputStream可以将序列化的对象(实现Serialiazable接口的对象)写入字节输出流(最终变成相应格式文件数据)。代码:
测试代码:
1 import java.io.FileInputStream;
2 import java.io.FileOutputStream;
3 import java.io.IOException;
4 import java.io.ObjectInputStream;
5 import java.io.ObjectOutputStream;
6 import java.io.Serializable;
7
8 public class File008 {
9 public static void main(String[] args) {
10 try (ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("g:/java2019/file.txt"))) {
11 User user = new User();
12 user.setId(1);
13 user.setName("dreamyoung");
14 os.writeObject(user);
15 } catch (IOException e) {
16 e.printStackTrace();
17 }
18
19 try(ObjectInputStream is = new ObjectInputStream(new FileInputStream("g:/java2019/file.txt"))){
20 try {
21 User user = (User)is.readObject();
22 System.out.println(user);
23 } catch (ClassNotFoundException e) {
24 e.printStackTrace();
25 }
26 } catch (IOException e) {
27 e.printStackTrace();
28 }
29
30 }
31 }
32
33 class User implements Serializable{
34 private static final long serialVersionUID = 1L;
35 private int id;
36 private String name;
37 public int getId() {
38 return id;
39 }
40 public void setId(int id) {
41 this.id = id;
42 }
43 public String getName() {
44 return name;
45 }
46 public void setName(String name) {
47 this.name = name;
48 }
49
50 @Override
51 public String toString() {
52 return "User [id=" + id + ", name=" + name + "]";
53 }
54 }
输出:
User [id=1, name=dreamyoung]
文件内容:
sr User I idL namet Ljava/lang/String;xp t
dreamyoung
说明:
- 对象序列化通常指的是将对象数据(不是类)保存到文件,其格式由JVM自动生成。而反序列化指的是从文件中读取数据生成对象。
- 要序列化的对象必须实现Serializable接口(通常还可以添加serialVersionUID),不然会出现 NotSerializableException
SequenceInputStream可以从已经一个或多个的输入流中顺序获取字节输入流的数据。需要注意的是,没有SequenceOutputStream。代码:
1 import java.io.FileInputStream;
2 import java.io.FileOutputStream;
3 import java.io.IOException;
4 import java.io.SequenceInputStream;
5
6 public class File009 {
7 public static void main(String[] args) {
8 //通过顺序流,将两个文件内容写入一个新文件
9 try (FileInputStream fis1 = new FileInputStream("g:/java2019/file1.txt");//文件内容:111
10 FileInputStream fis2 = new FileInputStream("g:/java2019/file2.txt");//文件内容:222
11 SequenceInputStream is = new SequenceInputStream(fis1, fis2);
12 FileOutputStream fos = new FileOutputStream("g:/java2019/file3.txt")) {
13 byte[] b = new byte[1024];
14 int len = 0;
15 while((len=is.read(b))!=-1){
16 fos.write(b,0,len);
17 }
18 } catch (IOException e) {
19 e.printStackTrace();
20 }
21
22 }
23 }
文件file3.txt内容:111222
要进行文件分割,必须配套PushbackInputStream类来使用,它可以使用 unread(int b) 或 unread(byte[] b) 或 unread(byte[] b, int off, int len)将已经读取的内容返送回去,对于分割读取后超过的内容,则可以不读取。注意默认只能返回读一个字节,如果要推回的内容长度比较大,还是建议指定长度,否则会出现Push back buffer is full异常。分割文件的代码:
1 import java.io.File;
2 import java.io.FileInputStream;
3 import java.io.FileOutputStream;
4 import java.io.IOException;
5 import java.io.PushbackInputStream;
6 import java.util.Arrays;
7 import java.util.Iterator;
8 import java.util.List;
9
10 public class File011 {
11 public static void main(String[] args) {
12 // 没有SequenceOutputStream,要分割文件,可以采用如下手段:
13 File file1 = new File("g:/java2019/file1.txt");
14 File file2 = new File("g:/java2019/file2.txt");
15 File file3 = new File("g:/java2019/file3.txt");
16
17 try (FileOutputStream fos1 = new FileOutputStream(file1);
18 FileOutputStream fos2 = new FileOutputStream(file2);
19 FileOutputStream fos3 = new FileOutputStream(file3);
20 PushbackInputStream fis = new PushbackInputStream(new FileInputStream("g:/java2019/file.txt"), 1024)) {
21 // file.txt内容:111111222222333333
22 byte[] b = new byte[5];// 缓冲数组大小,不大于分割文件大小
23 int splitSize = 6;// 分割文件大小
24 int len = 0;
25 List<FileOutputStream> list = Arrays.asList(fos1, fos2, fos3);
26 Iterator<FileOutputStream> it = list.iterator();
27 while (it.hasNext()) {
28 FileOutputStream fos = (FileOutputStream) it.next();
29 while (fos.getChannel().size() < splitSize && (len = fis.read(b)) != -1) {
30 int left = (int) fos.getChannel().size() + len - splitSize;
31
32 if (left > 0) {// 读取后会超出
33 for(int i=0;i<left;i++){
34 fis.unread(b[len-left+i]);
35 }
36 //fis.skip(-left);
37 fos.write(b, 0, len - left);
38 } else {
39 fos.write(b, 0, len);
40 }
41
42 System.out.println(fos.toString() + ":" + fos.getChannel().size());
43 }
44 }
45
46 } catch (IOException e) {
47 e.printStackTrace();
48 }
49
50 }
输出:
java.io.FileOutputStream@15db9742:5
java.io.FileOutputStream@15db9742:6
java.io.FileOutputStream@6d06d69c:5
java.io.FileOutputStream@6d06d69c:6
java.io.FileOutputStream@7852e922:5
java.io.FileOutputStream@7852e922:6
分割后各文件内容为:
file1.txt: 111111
file2.txt: 222222
file3.txt: 333333
PipedInputStream可以从管道中获取字节输入流数据,PipedOutputStream可以将数据写入管道输出流。将在网络编程的时候进行说明,本篇暂不介绍。
总结:
- FileInputStream与FileOutputStream可以以字节流形式处理文件,进行读取或写入操作。
- 要进行高效的字节流处理,可以使用内置的BufferedInputStream及BufferedOutputStream缓冲字节流,它可以处理各种InputStream及OutputStream(不仅仅是FileInputStream及FileOutputStream)
- 使用read(byte[] b, int off, int len)及write(byte[], int off, int len) 可以更高效的进行字节复制(可以替代缓冲字节流)
流