Java IO_002.InputStream与OutputStream--字节流对数据的操作(读取与写入)

Java IO之FileInputStream与FileOutputStream对象常用操作

涉及到文件(非文件夹)内容的操作,除了要用到File(见之前文章),另外就必须用到输入流输出流

输入流:该流处理时,数据由外部流向程序(内存),一般指代“读取数据”,更清晰点地说:从外部读取数据到内存中。

输出流:该流处理时,数据由程序(内存)流向外部,一般指代“写入数据”,更清晰点地说:将数据从内存写入到外部。

如果要操作字节(比如Soket,字节数据、图片、视屏等非纯文本文件),则采用字节输入流字节输出流。在Java中,可使用: InputStream  与 OutputStream 及其子类。

如果要操作字符(比如字符数据、纯文本文件),则采用字符输入流字符输出流。在Java中,可使用:Reader Writer 及其子类。

对字节的操作,采用 InputStream与OutputStream。它们的为声明分别为:

public abstract class InputStream implements Closeable 

public abstract class OutputStream implements Closeable, Flushable 

它们都是抽象类,需要由具体子类进行实例化。

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) :以字节输入流构造缓冲输入流,缓冲区大小由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) 可以更高效的进行字节复制(可以替代缓冲字节流)

猜你喜欢

转载自www.cnblogs.com/dreamyoung/p/11130063.html