当前位置 : 首页> 交流分享 > 一文妙懂java字节流及java输入输出流

一文妙懂java字节流及java输入输出流

时间:2018-07-03 14:18:56   已访问:482次
热门专业

在java学习中,java字节流是什么?该如何应用?在java中我们使用输入流来向一个字节序列对象中写入,使用输出流来向输出其内容。C语言中只使用一个File包处理一切文件操作,而在java中却有着60多种流类型,构成了整个流家族。看似庞大的体系结构,其实只要使用适合的方法将其分门别类,就显得清晰明了了。

按照处理文件类型的不同,分为字节流类型和字符流类型。从字节流开始,主要包含以下内容:

·InputStream/OutPutStream - - -字节流基类

·FileInputStream/FileOutputStream - - - - -处理文件类型

·ByteArrayInputStream/ByteArrayOutputStream - - - -字节数组类型

·DataInputStream/DataOutputStream - - - -装饰类

·BufferedInputStream/BufferedOutputStream - - - -缓冲流

一、基类流

其实始终有人搞不清楚到底InputStream是读还是OutputStream是读。其实很简单就可以记住,你把你自己想象为是一个程序,InputStream对你来说是输入,也就是你要从某个地方读到自己这来,而OutputStream对你来说就是输出,也就是说你需要写到某个地方。这样就可以简单的区分输入输出流。下面看看InputStream的成员方法:

publicabstractintread() throwsIOException;

publicintread(byteb[]) throwsIOException

publicintread(byteb[], intoff, intlen)

publiclongskip(longn) throwsIOException

publicintavailable() throwsIOException

publicvoidclose() throwsIOException

publicsynchronizedvoidmark(intreadlimit)

publicsynchronizedvoidreset() throwsIOException

publicbooleanmarkSupported()

InputStream是一个输入流,也就是用来读取文件的流,抽象方法read读取下一个字节,当读取到文件的末尾时候返回-1。如果流中没有数据read就会阻塞直至数据到来或者异常出现或者流关闭。这是一个受查异常,具体的调用者必须处理异常。除了一次读取一个字节,InputStream中还提供了read(byte[]),读取多个字节。read(byte[])其实默认调用的还是read(byte b[], int off, int len)方法,表示每读取一个字节就放在b[off++]中,总共读取len个字节,但是往往会出现流中字节数小于len,所以返回的是实际读取到的字节数。接下来是一些高级的用法,skip方法表示跳过指定的字节数,来读取。调用这种方法需要知道,一旦跳过就不能返回到原来的位置。当然,我们可以看到还有剩下的三种方法,他们一起合作实现了可重复读的操作。mark方法在指定的位置打上标记,reset方法可以重新回到之前的标记索引处。但是我们可以想到,它一定是在打下mark标记的地方,使用字节数组记录下接下来的路径上的所有字节数据,直到你使用了reset方法,取出字节数组中的数据供你读取(实际上也不是一种能够重复读,只是用字节数组记录下这一路上的数据而已,等到你想要回去的时候将字节数组给你重新读取)。
OutputStream是一种输出流,具体的方法和InputStream差不多,只是,一个读一个写。但是,他们都是抽象类,想要实现具体的功能还是需要依赖他们的子类来实现,例如:FileInputStream/FileOutputStream等。

二、文件字节流
FileInputStream继承与InputStream,主要有以下两个构造方法:

publicFileInputStream(Stringname)

publicFileInputStream(Filefile)

第一种构造方法传的是一个字符串,实际上是一个确定文件的路径,内部将此路径封装成File类型,调用第二种构造方法。第二中构造方法,直接绑定的是一个具体的文件。
FileInputStream 的内部方法其实和父类InputStream中定义的方法差不多,我们通过一个读文件的实例来演示用法。

publicclassTest_InputOrOutput{

publicstaticvoidmain(String[] args) throws IOException{

FileInputStream fin = newFileInputStream("hello.txt");

byte[] buffer = newbyte[1024];

intx = fin.read(buffer,0,buffer.length);

String str = newString(buffer);

System.out.println(str);

System.out.println(x);

fin.close();

}

}

输出结果:

hello world

13

结果意料之中,调用了read方法将hello.txt中的内容读到字节数组buffer中,然后通过String类构造方法将字节数组转换成字符串。返回实际上读取到的字节数13。(10个字母+两个空格+一个字符串结束符)
FileOutputStream继承父类OutputStream,主要方法代码如下:

privatefinalbooleanappend;

publicFileOutputStream(String name)

publicFileOutputStream(String name, booleanappend)

publicFileOutputStream(File file)

publicFileOutputStream(File file, booleanappend)

privatenativevoidwriteBytes(byteb[], intoff, intlen, booleanappend)

publicvoidwrite(byteb[]) throwsIOException

FileOutputStream的一些基本的操作和FileInputStream类似,只是一个是读一个是写。我们主要要知道,append属性是指定对于文件的操作是覆盖方式(false),还是追加方式(true)。下面通过一个实例演示其用法:

publicclassTest_InputOrOutput{

publicstaticvoidmain(String[] args) throwsIOException{

FileOutputStream fou = newFileOutputStream("hello.txt");

String str = "Walker_YAM";

byte[] buffer = str.getBytes("UTF-8");

fou.write(buffer,0 ,buffer.length);

fou.close();

}

}

如我们所料,字符串"Walker_YAM"将会被写入hello.txt,由于没有指定append,所以将会覆盖hello.txt中的所有内容。

三、动态字节数组流

在我们上述的文件读取流中,我们定义byte[] buffer = new byte[1024];,buffer数组为1024,如果我们将要读取的文件中的内容有1025个字节,buffer是不是装不下?当然我们也可以定义更大的数组容量,但是从内存的使用效率上,这是低效的。我们可以使用动态的字节数组流来提高效率。
ByteArrayInputStream的内部使用了类似于ArrayList的动态数组扩容的思想。

protectedbytebuf[];

protectedintcount;

publicByteArrayInputStream(bytebuf[])

publicByteArrayInputStream(bytebuf[], intoffset, intlength)

publicsynchronizedintread()

publicsynchronizedintread(byteb[], intoff, intlen)

ByteArrayInputStream内部定义了一个buf数组和记录数组中实际的字节数,read方法也很简单,读取下一个字节,read(byte b[], int off, int len) 将内置字节数组读入目标数组。实际上,整个ByteArrayInputStream也就是将一个字节数组封装在其内部。为什么这么做?主要还是为了方便参与整个InputStream的体系,复用代码。
ByteArrayOutputStream的作用要比ByteArrayInputStream更加的实际一点:

protectedbytebuf[];

protectedintcount;

publicByteArrayOutputStream() {

this(32);

}

publicByteArrayOutputStream(intsize)

privatevoidensureCapacity(intminCapacity)

publicsynchronizedvoidwrite(byteb[], intoff, intlen)

publicsynchronizedvoidwriteTo(OutputStream out)

publicsynchronizedbytetoByteArray()[]

publicsynchronizedString toString()

和ByteArrayInputStream一样,内部依然封装了字节数组buf和实际容量count,通过构造方法可以指定内置字节数组的长度。主要的是write方法,将外部传入的字节数组写到内置数组中,writeTo方法可以理解为将自己内置的数组交给OutputStream 的其他子类使用。toByteArray和toString则会将内置数组转换成指定类型返回。下面我们利用他们解决刚开始说的效率问题。

publicclassTest_InputOrOutput{

publicstaticvoidmain(String[] args) throws IOException{

FileInputStream fin = newFileInputStream("hello.txt");

ByteArrayOutputStream bou = newByteArrayOutputStream();

intx = 0;

while((x = fin.read()) !=-1){

bou.write(x);

}

System.out.println(bou.toString());

}

}

从hello文件中每读取一个字节写入ByteArrayOutputStream 中,我们不用担心hello文件太大而需要设置较大的数组,使用ByteArrayOutputStream 动态增加容量,如果添加字节即将超过容量上限,进行扩充(往往是指数级扩充)

四、装饰者字节流

上述的流都是直接通过操作字节数组来实现输入输出的,那如果我们想要输入一个字符串类型或者int型或者double类型,那还需要调用各自的转字节数组的方法,然后将字节数组输入到流中。我们可以使用装饰流,帮我们完成转换的操作。我们先看DataOutputStream。

publicDataOutputStream(OutputStream out)

publicsynchronizedvoidwrite(byteb[], intoff, intlen)

publicfinalvoidwriteBoolean(booleanv)

publicfinalvoidwriteByte(intv)

publicfinalvoidwriteShort(intv)

publicfinalvoidwriteInt(intv)

publicfinalvoidwriteDouble(doublev)

简单的列举了一些方法,可以看到,DataOutputStream只有一个构造方法,必须传入一个OutputStream类型参数。(其实它的内部还是围绕着OutputStream,只是在它的基础上做了些封装)。我们看到,有writeBoolean、writeByte、writeShort、writeDouble等方法。他们内部都是将传入的boolean,Byte,short,double类型变量转换为了字节数组,然后调用从构造方法中接入的OutputStream参数的write方法。

//这是writeInt的具体实现

publicfinalvoid writeInt(intv) throws IOException {

out.write((v >>> 24) & xFF);

out.write((v >>> 16) & xFF);

out.write((v >>> 8) & xFF);

out.write((v >>> 0) & xFF);

incCount(4);

}

将一个四个字节的int类型,分开写入,先写入高八位。总共写四次,第一次将高八位移动到低八位与上0xFF获得整个int的低八位,这样就完成了将原高八位写入的操作,后续操作类似。

publicclassTest_InputOrOutput{

publicstaticvoidmain(String[] args) throwsIOException{

DataOutputStream da = newDataOutputStream(newFileOutputStream("hello.txt"));

da.writeInt(11);

da.close();

}

}

一文妙懂java字节流及java输入输出流_www.cnitedu.cn一文妙懂java字节流及java输入输出流_www.cnitedu.cn

使用UltraEditor打开hello文件,可以看到11这个int型数值被存入文件中。DataInputStream完成的就是读取的操作,基本和DataOutputStream 的操作是类似的,是一个逆操作。

五、缓冲流

在这之前,我们读取一个字节就要将它写会磁盘,这样来回开销很大,我们可以使用缓冲区来提高效率,在缓冲区满的时候,或者流关闭时候,将缓冲区中所有的内容全部写会磁盘。BufferedInputStream和BufferedOutputStream也是一对装饰流,我们先看看BufferedInputStream:

privatestaticintDEFAULT_BUFFER_SIZE = 8192;

protectedvolatilebytebuf[];

protectedintpos;

protectedintcount;

publicBufferedInputStream(InputStream in)

publicBufferedInputStream(InputStream in, intsize)

publicsynchronizedintread()

publicsynchronizedvoidmark(intreadlimit)

publicsynchronizedvoidreset()

一样也是装饰类流,第一种构造方法要求必须传入InputStream类型参数,DEFAULT_BUFFER_SIZE 指定了默认的缓冲区的大小,当然还可以使用第二种构造方法指定缓冲区的大小(当然不能超过上界),read方法读取的时候会将数据读入内部的缓冲区中,当然缓冲区也是可以动态扩容的。

publicclassTest_InputOrOutput{

publicstaticvoidmain(String[] args) throws IOException{

BufferedInputStream bi = newBufferedInputStream(newFileInputStream("hello.txt"));

bi.read();

bi.read();

bi.read();

bi.read();

System.out.println(bi.available());

}

}

BufferedOutputStream和它是逆操作,不在赘述。这种缓冲字节流可以很大程度上提高我们的程序执行的效率,所以一般在使用别的流的时候都会包装上这层缓冲流。


推荐内容