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

  • InputStream/OutPutStream - - -字节流基类
  • FileInputStream/FileOutputStream - - - - -处理文件类型
  • ByteArrayInputStream/ByteArrayOutputStream - - - -字节数组类型
  • DataInputStream/DataOutputStream - - - -装饰类
  • BufferedInputStream/BufferedOutputStream - - - -缓冲流



    一、基类流

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

public int read(byte b[]) throws IOException

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

public long skip(long n) throws IOException

public int available() throws IOException

public void close() throws IOException

public synchronized void mark(int readlimit)
public synchronized void reset() throws IOException
public boolean markSupported()

          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,主要有以下两个构造方法:

 public FileInputStream(String name)

 public FileInputStream(File file)

          第一种构造方法传的是一个字符串,实际上是一个确定文件的路径,内部将此路径封装成File类型,调用第二种构造方法。第二中构造方法,直接绑定的是一个具体的文件。

          FileInputStream 的内部方法其实和父类InputStream中定义的方法差不多,我们通过一个读文件的实例来演示用法。

public class Test_InputOrOutput {
public static void main(String[] args) throws IOException{
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();
}
}
输出结果:
hello world
13

          结果意料之中,调用了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)。下面通过一个实例演示其用法:

public class Test_InputOrOutput {
public static void main(String[] args) throws IOException{
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则会将内置数组转换成指定类型返回。

          下面我们利用他们解决刚开始说的效率问题。

public class Test_InputOrOutput {
public static void main(String[] args) throws IOException{
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方法。

//这是writeInt的具体实现
public final void writeInt(int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(4);
}

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

public class Test_InputOrOutput {
public static void main(String[] args) throws IOException{
DataOutputStream da = new DataOutputStream(new FileOutputStream("hello.txt"));
da.writeInt(11);
da.close();
}
}

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



     五、缓冲流

          在这之前,我们读取一个字节就要将它写会磁盘,这样来回开销很大,我们可以使用缓冲区来提高效率,在缓冲区满的时候,或者流关闭时候,将缓冲区中所有的内容全部写会磁盘。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方法读取的时候会将数据读入内部的缓冲区中,当然缓冲区也是可以动态扩容的。

public class Test_InputOrOutput {
public static void main(String[] args) throws IOException{
BufferedInputStream bi = new BufferedInputStream(new FileInputStream("hello.txt"));
bi.read();
bi.read();
bi.read();
bi.read();
System.out.println(bi.available());
}
}

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

          最后,本文如有错误指出,望大家指出!下一篇会写字符流。

Java 字节流操作的更多相关文章

  1. Java 字节流实现文件读写操作(InputStream-OutputStream)

    Java 字节流实现文件读写操作(InputStream-OutputStream) 备注:字节流比字符流底层,但是效率底下. 字符流地址:http://pengyan5945.iteye.com/b ...

  2. Java之字节流操作-复制文件

    package test_demo.fileoper; import java.io.FileInputStream; import java.io.FileOutputStream; import ...

  3. java字节流和字符流,以及java文件操作

    A.首先说字节流:1.字节流在操作的时候不会用到缓冲区(也就是内存)2.字节流可用于任何类型的对象,包括二进制对象3.字节流处理单元为1个字节,操作字节和字节数组.InputStream是所有字节输入 ...

  4. java 字节流与字符流的区别

    字节流与和字符流的使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同呢?实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作 ...

  5. (转)java字节流和字符流的区别

    转载: http://www.cnblogs.com/dolphin0520/category/361055.html 字节流与和字符流的使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同 ...

  6. java 字节流和字符流的区别 转载

    转载自:http://blog.csdn.net/cynhafa/article/details/6882061 java 字节流和字符流的区别 字节流与和字符流的使用非常相似,两者除了操作代码上的不 ...

  7. java 字节流和字符流的区别

    转载自:http://blog.csdn.net/cynhafa/article/details/6882061 java 字节流和字符流的区别 字节流与和字符流的使用非常相似,两者除了操作代码上的不 ...

  8. java 字节流与字符流的区别 (转)

    字节流与和字符流的使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同呢? 实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操 ...

  9. 关于java字节流的read()方法返回值为int的思考

    我们都知道java中io操作分为字节流和字符流,对于字节流,顾名思义是按字节的方式读取数据,所以我们常用字节流来读取二进制流(如图片,音乐 等文件).问题是为什么字节流中定义的read()方法返回值为 ...

随机推荐

  1. abstract、override、new、virtual、sealed使用和示例

    abstract修饰类名为抽象类,修饰方法为抽象方法.如果一个类为抽象类,则这个类智能是其他某个类的基类.抽象方法在抽象类中没有函数体.抽象类中的抽象方法是没有方法体的,继承其的子类必须实现抽象类的抽 ...

  2. 如何使excel表格的内容自动添加前缀

    一.假设是要在一列的单元格内容前加上固定的内容,则 方法一在原单元格实现,分两种情况 如果原单元格的内容是数字内容,要在原数字前添加"ABC"这样的前缀则选中这些单元格----右键 ...

  3. sqlserver查询数据库中有多少个表

    sql server 数表: select count(1) from sysobjects where xtype='U' 数视图: select count(1) from sysobjects ...

  4. 【求Java工程师/专家】大型互联网公司急招

    互联网行业排名Top公司,目前项目扩张阶段,需求数量较多的JAVA中高端后台开发人员,需要有分布式系统开发经验,年后有找工作的同学可以联系我了. 工作地点:杭州 岗位要求: 1.精通Java SE和J ...

  5. SpringMVC通过实体类返回json格式的字符串,并在前端显示

    一.除了搭建springmvc框架需要的jar包外,还需要这两个jar包 jackson-core-asl-1.9.2.jar和jackson-mapper-asl-1.9.2.jar 二.web,. ...

  6. echarts 显示下载按钮,echarts 自定义按钮,echarts 添加按钮

    echarts 显示下载按钮,echarts 自定义按钮,echarts 添加按钮 >>>>>>>>>>>>>>&g ...

  7. shell编程其实真的很简单(四)

    上篇我们学习了shell中条件选择语句的用法.接下来本篇就来学习循环语句.在shell中,循环是通过for, while, until命令来实现的.下面就分别来看看吧. for for循环有两种形式: ...

  8. 初始化css样式的原因

    不同的浏览器对有些标签属性的默认值是不同的,如果没有做初始化处理,往往会出现不同浏览器页面表现的差异

  9. Spring Mvc中使用Task实现定时任务,以及遇到的一个问题

    Spring中实现定时任务其实很简单,可以使用spring中自带的task 相当于轻量级的Quartz,并且spring 3.0 之后支持注解的方式,使用起来非常简单,方便,具体实现如下: 第一步,修 ...

  10. 在IntelliJ IDEA 13中配置OpenCV的Java开发环境

    准备工作: 下载IDEA 13(这里以版本13为例,后面简称IDEA): 下载Java JDK(用于配置基本的Java开发环境): 下载OpenCV 2.4.9(这里以版本2.4.9为例,据这篇文章说 ...