JavaEE培训

您当前的位置:首页 > Java培训 > JAVA开发 >

Java字节流使用方法

来源:Java字节流讲解 2018-03-01

流的概念

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

基类流

其实始终有人搞不清楚到底InputStream是读还是OutputStream是读。其实很简单就可以记住,你把你自己想象为是一个程序,InputStream对你来说是输入,也就是你要从某个地方读到自己这来,而OutputStream对你来说就是输出,也就是说你需要写到某个地方。这样就可以简单的区分输入输出流。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等。

Java字节流

文件字节流

FileInputStream继承与InputStream,主要有以下两个构造方法:

public FileInputStream(String name)

public FileInputStream(File file)

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

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

byte[] buffer = new byte[1024];

int x = fin.read(buffer,0,buffer.length);

String str = new String(buffer);

System.out.println(str);

System.out.println(x);

fin.close();

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

private final boolean append;

public FileOutputStream(String name)

public FileOutputStream(String name, boolean append)

public FileOutputStream(File file)

public FileOutputStream(File file, boolean append)

private native void writeBytes(byte b[], int off, int len, boolean append)

public void write(byte b[]) throws IOException

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

FileOutputStream fou = new FileOutputStream("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的动态数组扩容的思想。

protected byte buf[];

protected int count;

public ByteArrayInputStream(byte buf[])

public ByteArrayInputStream(byte buf[], int offset, int length)

public synchronized int read()

public synchronized int read(byte b[], int off, int len)

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

protected byte buf[];

protected int count;

public ByteArrayOutputStream() { this(32); }

public ByteArrayOutputStream(int size)

private void ensureCapacity(int minCapacity)

public synchronized void write(byte b[], int off, int len)

public synchronized void writeTo(OutputStream out)

public synchronized byte toByteArray()[]

public synchronized String toString()

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

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

ByteArrayOutputStream bou = new ByteArrayOutputStream();

int x = 0;

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

bou.write(x);

}

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

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

装饰者字节流

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

public DataOutputStream(OutputStream out)

public synchronized void write(byte b[], int off, int len)

public final void writeBoolean(boolean v)

public final void writeByte(int v)

public final void writeShort(int v)

public final void writeInt(int v)

public final void writeDouble(double v)

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

缓冲流

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

private static int DEFAULT_BUFFER_SIZE = 8192;

protected volatile byte buf[];

protected int pos;

protected int count;

public BufferedInputStream(InputStream in)

public BufferedInputStream(InputStream in, int size)

public synchronized int read()

public synchronized void mark(int readlimit)

public synchronized void reset()

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

BufferedInputStream bi = new BufferedInputStream(new FileInputStream("hello.txt"));

bi.read();

bi.read();

bi.read();

bi.read();

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

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

400-611-6270

Copyright ©2004-2024 华清远见 版权所有
京ICP备16055225号,京公海网安备11010802025203号