`

J2SE基础-IO-字节流(位流)

阅读更多

字节流(位流)


计算机中的数据都是以0与1的方式来存储,如果要在两个对象/容器/之间进行数据的存取,也是以0与1位的方式来进行;Java将数据在目的地与来源之间的流动抽象化为一个流(Stream),而流当中流动的则是位数据。流是一个数据队列。


1、InputStream和OutputStream

计算机中实际数据的流动是通过板卡/电路/线缆,而这些载体中流动的则是电流,电流的电位有低位与高位,即数字的0与1位。从程序的观点来说,通常会将数据目的地(例如内存)与来源(例如文件)之间的数据流动抽象化为一个流(Stream),而其中流动的则是位数据,如图所示:



在Java SE中有两个类用来作流的抽象表示:java.io.InputStreamjava.io.OutputStream


InputStream是所有表示位输入流(字节流)的类的父类,它是一个抽象类,继承它的子类要重新定义其中所定义的抽象方法【public abstract int read() throws IOException;】。InputStream是从装置来源地读取数据的抽象表示,例如System中的标准输入流in对象就是一个InputStream类型的实例。在Java程序开始之后,in流对象就会开启,目的是从标准输入装置中读取数据,这个装置通常是键盘或是用户定义的输入装置。




 输入流(Input Stream)的意义:用于从资源中读取数据



OutputStream是所有表示位输出流(字节流的类父类,它是一个抽象类。子类要重新定义其中所定义的抽象方法【public abstract void write(int b) throws IOException;】。OutputStream是用于将数据写入目的地的抽象表示。例如System中的标准输出流对象out其类型是java.io.PrintStream,这个类是OutputStream的子类(java.io.FilterOutputStream继承OutputStream, PrintStream再继承FilterOutputStream)。在程序开始之后,out流对象就会开启,可以通过out来将数据写至目的地装置,这个装置通常是屏幕显示或用户定义的输出装置。




 输出流(Output Stream)的意义:向一个目标写或者发送数据


由于InputStream和OutputStream操作的是字节,而字节是二进制数据,所以字节流可以操作任何类型的数据。


范例 StreamDemo.java

可以读取键盘输入流,in对象的read()方法一次读取一个字节的数据,读入的数据以int类型返回。所以在使用out对象将数据显示出来时,就是10进制方式。



package onlyfun.caterpillar;

import java.io.*;

public class StreamDemo { 
  public static void main(String[] args) { 
    try { 
      System.out.print("输入字符: "); 
      System.out.println("输入字符十进制表示: " + 
      System.in.read());  
    } catch(IOException e) { 
      e.printStackTrace(); 
    } 
  } 
}


执行结果:

输入字符: A
输入字符十进制表示: 65

字符A输入后由标准输入流in读取,A的位表示以十进制来看就是65,这是A字符的编码




2、FileInputStream和FileOutputStream

java.io.FileInputStream是InputStream的子类。从开头File名称上就可以知道,FileInputStream与从指定的文件中读取数据至目的地有关。


java.io.FileOutputStream是OutputStream的子类,顾名思义,FileOutputStream主要与从来源地写入数据至指定的文件中有关。


当建立一个FileInputStream或FileOutputStream的实例时,必须指定文件位置及文件名称,实例被建立时文件的流就会开启;而不使用流时,必须关闭文件流,以释放与流相依的系统资源,完成文件读/写的动作。


FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。



范例  FileStreamDemo.java

使用FileInputStreamFileOutputStream的一个例子。程序可以复制文件,它会先从来源文件读取数据至一个byte数组中,然后再将byte数组的数据写入目的文件。



package onlyfun.caterpillar;
import java.io.*;
public class FileStreamDemo {
  public static void main(String[] args) {
    try {
      byte[] buffer = new byte[1024]; // 来源文件
      FileInputStream fileInputStream = new FileInputStream(new File(args[0]));
      // 目的文件
      FileOutputStream fileOutputStream = new FileOutputStream(new File(args[1]));
      // available()可取得未读取的数据长度
      System.out.println("复制文件:" + fileInputStream.available() + "字节");
      while(true) {
        if(fileInputStream.available() < 1024) {
        // 剩余的数据比1024字节少
        // 一位一位读出再写入目的文件
        int remain = -1;
        while((remain = fileInputStream.read()) != -1) {
          fileOutputStream.write(remain);
        }
        break;
        } else {
        // 从来源文件读取数据至缓冲区
        fileInputStream.read(buffer);
        // 将数组数据写入目的文件
        fileOutputStream.write(buffer);
      }
      }
      // 关闭流
      fileInputStream.close();
      fileOutputStream.close();
      System.out.println("复制完成");
    } catch(ArrayIndexOutOfBoundsException e) {
      System.out.println("using: java FileStreamDemo src des");
      e.printStackTrace();
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
}


程序中示范了两个read()方法,一个方法可以读入指定长度的数据至数组,另一个方法一次可以读入一个字节。每次读取之后,读取的光标都会往前进,如果读不到数据则返回-1,使用available()方法获得还有多少字节可以读取。除了使用File来建立FileInputStream、FileOutputStream的实例之外,也可以直接使用字符串指定路径来建立。

// 来源文件
FileInputStream fileInputStream = new FileInputStream(args[0]);
//
目的文件
FileOutputStream fileOutputStream = new FileOutputStream(args[1]);

在不使用文件流时,记得使用close()方法自行关闭流,以释放与流相依的系统资源。


执行的结果范例如下,它将FileDemo.java复制为FileDemo.txt:

java onlyfun.caterpillar.FileStreamDemo FileDemo.java FileDemo.txt
复制文件:1723字节
复制完成


FileOutputStream默认会以新建文件的方式来开启流。如果指定的文件名称已经存在,则原文件会被覆盖;如果想以附加的模式来写入文件,则可以在构建FileOutputStream实例时指定为附加模式。例如:

FileOutputStream fileOutputStream = new FileOutputStream(args[1], true);

构建方法的第二个append参数如果设置为true,在开启流时如果文件不存在则会新建一个文件,如果文件存在就直接开启流,并将写入的数据附加至文件末端。




3、BufferedInputStream和BufferedOutputStream

在介绍FileInputStream和FileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。


java.io.BufferedInputStream与java.io.BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能。构建BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例。同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现BufferedOutputStream时,实际上最后是实现OutputStream实例


BufferedInputStream的数据成员buf是一个位数组,默认为2048字节。当读取数据来源时,例如文件,BufferedInputStream会尽量将buf填满。当使用read()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取。当buf中的数据不足时,BufferedInputStream才会再实现给定的InputStream对象的read()方法,从指定的装置中提取数据,如图所示。



BufferedOutputStream的数据成员buf是一个位数组,默认为512字节。当使用write()方法写入数据时,实际上会先将数据写至buf中,当buf已满时才会实现给定的OutputStream对象的write()方法,将buf数据写至目的地,而不是每次都对目的地作写入的动作。



范例  BufferedStreamDemo.java

下面将上例做个改写,这次不用自行设置缓冲区,而使用BufferedInputStream和BufferedOutputStream让程序看来简单一些,也比较有效率。



package onlyfun.caterpillar;

import java.io.*;

public class BufferedStreamDemo {
  public static void main(String[] args) {
    try {
    byte[] data = new byte[1];
    File srcFile = new File(args[0]); 
    File desFile = new File(args[1]);
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFile)); 
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(desFile));

    System.out.println("复制文件:" +  srcFile.length() + "字节");
    while(bufferedInputStream.read(data) != -1) { 
      bufferedOutputStream.write(data); 
    }

    // 将缓冲区中的数据全部写出 
    bufferedOutputStream.flush();

    // 关闭流 
    bufferedInputStream.close(); 
    bufferedOutputStream.close();
    System.out.println("复制完成"); 
    } catch(ArrayIndexOutOfBoundsException e) { 
    System.out.println("using: java UseFileStream src des"); 
    e.printStackTrace(); 
    } catch(IOException e) { 
    e.printStackTrace(); 
    } 
  }
}


为了确保缓冲区中的数据一定被写出至目的地,建议最后执行flush()将缓冲区中的数据全部写出目的流中。这个范例的执行结果与上例是相同的。


BufferedInputStream和BufferedOutputStream并没有改变InputStream或OutputStream的行为,读入或写出时的动作还是InputStream和OutputStream负责。BufferedInputStream和BufferedOutputStream只是在操作对应的方法之前,动态地为它们加上一些额外功能(像缓冲区功能),在这里是以文件存取流为例,实际上可以在其他流对象上也使用BufferedInputStream和BufferedOutputStream功能。



4、DataInputStream和DataOutputStream

java.io.DataInputStream和java.io.DataOutputStream可提供一些对Java基本数据类型写入的方法,像读写int、double和boolean等的方法。由于Java的数据类型大小是规定好的,在写入或读出这些基本数据类型时,就不用担心不同平台间数据大小不同的问题。


这里还是以文件存取来进行说明。有时只是要存储一个对象的成员数据,而不是整个对象的信息,成员数据的类型假设都是Java的基本数据类型,这样的需求不必要使用到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream来写入或读出数据。



下面使用范例来介绍如何使用DataInputStream与DataOutputStream。

先设计一个Member类。

范例 Member.java



package onlyfun.caterpillar;
public class Member {
    private String name;
    private int age;
    public Member() {
    }
    public Member(String name, int age) {
      this.name = name;
      this.age = age;
    }
    public void setName(String name) {
      this.name = name;
    }
    public void setAge(int age) {
      this.age = age;
    }
    public String getName() {
      return name;
    }
    public int getAge() {
      return age;
    }
}

 

打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象。


下例简单示范了如何实现这个需求。

范例 DataStreamDemo.java


package onlyfun.caterpillar;

import java.io.*;

public class DataStreamDemo {
    public static void main(String[] args) {
      Member[] members = { new Member("Justin", 90),  new Member("momor", 95),  new Member("Bush", 88) }; 
      try { 
        DataOutputStream dataOutputStream =  new DataOutputStream(new FileOutputStream(args[0])); 

        for(Member member : members) { 
          // 写入UTF字符串
          dataOutputStream.writeUTF(member.getName()); 
          // 写入int数据
          dataOutputStream.writeInt(member.getAge()); 
        } 
        // 读出所有数据至目的地
        dataOutputStream.flush(); 
        // 关闭流
        dataOutputStream.close(); 

        DataInputStream dataInputStream = new DataInputStream(new FileInputStream(args[0])); 
        // 读出数据并还原为对象
        for(int i = 0; i < members.length; i++) { 
          // 读出UTF字符串
          String name = dataInputStream.readUTF(); 
          // 读出int数据
          int score = dataInputStream.readInt();
          members[i] = new Member(name, score); 
        } 
        // 关闭流
        dataInputStream.close(); // 显示还原后的数据
        for(Member member : members) { 
          System.out.printf("%s\t%d%n", 
          member.getName(), member.getAge());
        } 
      } catch(IOException e) { 
        e.printStackTrace(); 
      } 
    }
}

 

在从文件中读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时该停止,使用对应的 readUTF() 或 readInt() 方法就可以正确地读入完整类型数据。同样地,DataInputStream、DataOutputStream并没有改变InputStreamOutputStream的行为,读入或写出时的动作还是InputStream、OutputStream负责。DataInputStream、DataOutputStream只是在实现对应的方法时,动态地为它们加上类型判断功能,在这里虽然是以文件存取流为例,实际上可以在其他流对象上也使用DataInputStream、DataOutputStream功能。




注:本文主体参考以前看过的一篇博文,但是时间太长,不知道最初出处,谢谢这位原作者,特此声明!

 

  • 大小: 8.6 KB
  • 大小: 12.8 KB
  • 大小: 5.4 KB
  • 大小: 5.7 KB
分享到:
评论

相关推荐

    J2SE-IO.ppt

    流可以是字节流(处理8位字节数据)或字符流(处理16位Unicode字符)。 2. **Java流类的分类** - **节点流**:直接与数据源(如文件、网络连接)交互的流,提供实际的读写操作。 - **处理流**:不直接与数据源...

    J2SE基础知识大全

    - **字节流与字符流**:FileInputStream、FileOutputStream、BufferedReader、BufferedWriter等,以及它们在读写文件时的角色。 - **流的组合与转换**:使用InputStreamReader和OutputStreamWriter进行字节流到...

    JAVA基础知识点,J2SE部分内容

    字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以(使用用字节流来传输字符文件会产生乱码) 注意,不管是哪一种流,在数据传输的底层,还是以字节方式传输。

    J2SE基础源代码

    5. **I/O流**:Java的I/O流模型非常强大,涵盖了字符流和字节流,以及输入流、输出流、缓冲流和转换流等。源代码会展示如何进行文件读写、网络通信等操作。 6. **集合框架**:`java.util`包中的ArrayList、...

    IOUtils(commons-io-1.4.jar、commons-fileupload-1.2.1.jar等等)

    - **IOUtils**:这是Apache Commons IO中的核心类,提供了一系列静态方法,用于处理字节流和字符流的读写、复制、关闭等操作。例如,`IOUtils.copy()`方法可以方便地在不同流之间复制数据,`IOUtils.closeQuietly()...

    (java)J2SE_核心技术基础

    学习IO流需要理解字节流和字符流的区别,以及缓冲区、过滤流的概念。 六、多线程 Java内置了对多线程的支持,通过Thread类或Runnable接口可以创建并运行线程。理解线程的同步和互斥机制,如synchronized关键字和...

    关于JAVA面试题、J2SE基础

    为了在Java开发者岗位上取得成功,掌握扎实的J2SE(Java Standard Edition)基础知识至关重要。本资料集合了多个来源的Java面试题,涵盖了广泛的知识点,旨在帮助求职者充分准备面试。 一、Java基础 1. 类与对象:...

    J2SE学习代码

    - 字节流(InputStream/OutputStream)和字符流(Reader/Writer):用于读写二进制和文本数据。 - 文件操作:File类提供了创建、删除、重命名文件及获取文件属性的方法。 - 缓冲流(BufferedInputStream/...

    J2SE官方文档

    7. **I/O流**:Java的I/O流模型允许高效地处理数据输入和输出,包括字符流、字节流、缓冲流、对象流、文件流等。 8. **Swing组件**:J2SE提供了丰富的GUI组件库Swing,用于创建桌面应用,如JButton、JFrame、JPanel...

    经典JAVA/J2SE笔试面试

    5. **IO流**:Java的IO流分为字节流和字符流,掌握InputStream/OutputStream和Reader/Writer的基本操作,以及缓冲流BufferedInputStream/BufferedReader,转换流InputStreamReader/OutputStreamWriter等。...

    j2se全部学习代码

    5. **IO流**:用于读写文件、网络通信和其他数据源,包括字节流、字符流、对象流以及缓冲流等。 6. **多线程**:Java提供了Thread类和Runnable接口来实现并发执行,还有synchronized关键字、wait()、notify()和...

    j2se练习小程序

    5. **IO流**:Java的IO流用于读写文件,网络通信等,分为字符流和字节流。掌握InputStream、OutputStream、Reader、Writer等基类,以及BufferedReader、FileWriter等常用类的用法,对于处理数据输入输出至关重要。 ...

    java基础(j2se代码)

    9. **输入输出流**:Java的IO流系统用于读写文件和网络数据,包括字节流和字符流,以及缓冲流、转换流和对象序列化。 10. **多线程**:Java内置了对多线程的支持,可以使用Thread类或者实现Runnable接口创建线程,...

    java基础课程j2se

    6. **输入/输出流**:学习I/O流的概念,包括文件读写、字符流和字节流,以及如何使用缓冲区提高效率。理解InputStream、OutputStream、Reader、Writer四个核心抽象类及其子类。 7. **异常处理**:Java强制使用try-...

    j2se基础面试

    Java基础是所有Java开发者必备的知识体系,J2SE(Java 2 Platform, Standard Edition)是Java的核心部分,包含了用于开发和部署桌面应用的基本工具和API。以下是对J2SE面试中常见知识点的详细阐述: 一、基本规则:...

    j2se doc 中文版

    1. **Java API 参考**:文档中包含了 J2SE 平台上所有类库的详细信息,如 `java.lang`、`java.util`、`java.io`、`java.net` 等包。这些类库提供了基础数据类型、字符串处理、输入/输出、网络通信、集合框架等功能。...

    J2SE金典例题(包括注释),自己看的时候做的每个例题

    理解字节流和字符流、缓冲流以及文件操作的方法,可以有效处理数据交换。 6. **多线程**:Java提供了并发编程的支持,包括Thread类和Runnable接口。掌握同步和互斥的概念,以及synchronized关键字和Lock接口的应用...

    j2se 小程序源代码

    分为字节流和字符流,以及输入流和输出流,还有缓冲流、转换流和对象流等。 8. **多线程**:Java内置了对多线程的支持,可以使用`Thread`类或实现`Runnable`接口创建线程。线程同步和互斥是多线程编程中的关键概念...

Global site tag (gtag.js) - Google Analytics