资讯专栏INFORMATION COLUMN

Java I/O简介

darkbug / 2123人阅读

摘要:如果不指定字符集,则使用系统默认字符编码,系统的默认字符编码一般是。所以更准确的说,是将一个字节输入流按照给定的字符编码来解码,从而得到一个字符输入流。当然,缺点就是不能选择使用的字符编码。

相对于PythonC来说,Java的I/O操作API比较复杂,因此本文打算做个简单的介绍。

1. I/O分类

总的来说Java的I/O按照处理数据的粒度和方向来划分,一共可以分为4类:

基于字节

输入 InputStream

输出 OutputStream

基于字符

输入 Reader

输出 Writer

使用原则:要读写二进制数据时,使用基于字节的API;要读写文本数据时,使用基于字符的API,文本数据操作需要指定字符编码。强调一点,本文说的字符是指Java的数据类型char类型,并不是C语言中的char类型(该类型长度为8位,一个字节),即Java中的一个字符有可能包含多个字节。

这里提到的InputStream, OutputStream, ReaderWriter 是Java API里的4个抽象类,不能用来初始化新的实例,我们只能从这4个类的子类(或者后代类)来创建I/O操作的实例,而且正是这些子类实现类不同介质和不同功能的I/O。另外,两个用于输入的抽象类都定义了一个抽象的int read()方法,两个用于输出的抽象类都定义了一个抽象的void write()方法,这些抽象方法则由子类来实现。

2. 文件I/O的使用

Java I/O可以可以应用于各种输入输出介质,包括文件、控制台(也是文件的一种)、内存、网络等。这里先介绍文件I/O,搞懂了文件I/O相关的API后,其他的I/O就都好理解了。

最基本方法

根据第一节的分类,文件I/O的API也分为基于字节基于字符 的两大类。我们先来看最基础的文件I/O的类:

基于字节

FileInputStream
该类的read()方法每次从文件读取一个字节。

FileOutputStream
该类的write()方法每次向文件写入一个字节。

基于字符

InputStreamReader
该类的read()方法每次从一个输入流中读取一个字符。该类的构造函数的第一个参数是一个InputStream实例,也就是将说该类将一个基于字节的输入流变成一个基于字符的输入流。如果不指定字符集,则使用系统默认字符编码,Ubuntu系统的默认字符编码一般是UTF-8。所以更准确的说,是将一个字节输入流按照给定的字符编码来解码,从而得到一个字符输入流。

OutputStreamWriter
该类的write()方法每次向一个输出流中写入一个字符。该类的构造函数的第一个参数是一个OutputStream实例,也就是说该类将一个基于字节的输出流变成一个基于字符的输出流。如果不指定字符集,则使用系统默认字符编码,Ubuntu系统的默认字符编码一般是UTF-8。所以更准确的说,是将一个字节输出流按照给定的字符编码来编码(把要输出的字符转换成二进制数据),从而得到一个字符输出流。

FileReader
该类的read()方法每次从文件读取一个字符。这个类的作用等于如下代码:

InputStreamReader in = new InputStreamReader(new FileInputStream(pathToFile));
也就是先从一个文件创建一个字节输入流,然后再采用系统默认编码方式转换成一个字符输入流。当然,缺点就是不能选择使用的字符编码。

FileWriter
该类的write()方法每次向文件写入一个字符。这个类的作用等于如下代码:

`OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(pathToFile));
具体解释和FileReader类似。

介绍完常用类,我们来讲下如何使用。

二进制数据读写(基于字节)

如果要从文件读入二进制数据,则先构造一个FileInputStream实例,然后调用read()方法每次读入一个字节,也可以调用read方法的其他实现,每次读入多个字节。

如果要向文件写入二进制数据,则先构造一个FileOutputStream示例,然后调用write()方法每次写入一个字节,也可以调用write方法的其他实现,每次写入多个字节。

文本数据读写(基于字符)

如果要从文件读入文本数据,可以选择如下两种方式:

构造一个FileReader实例,使用系统默认编码,然后调用read()方法每次读入一个字符,也可以调用read方法的其他实现,每次读入多个字符。

采用如下方式构造一个InputStreamReader实例:new InputStreamReader(new FileInputStream(pathToFile), codecName),使用指定的字符编码,然后调用read()方法每次读入一个字符,也可以调用read方法的其他实现,每次读入多个字符。

如果要向文件写入文本数据,可以选择如下两种方式:

构造一个FileWriter实例,使用系统默认编码,然后调用write()方法每次写入一个字符,也可以调用write方法的其他实现,每次写入多个字符。

采用如下方式构造一个OutputStreamwriter实例:new OutputStreamWriter(new FileOutputStream(pathToFile), codecName),使用指定的字符编码,然后调用write()方法每次写入一个字符,也可以调用write方法的其他实现,每次写入多个字符。

来看下代码实例,文件~/tmp/words中存放了一行中文字符,采用的是UTF-8编码方式:

~/tmp/words 
你好

下面的代码展示了基于字节和基于字符两种方式读取文件内容的区别:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class Hello {

  public static void main(String[] args) {
    String wordFileName = "~/tmp/words";
    String wordFilePath = wordFileName.replace("~", System.getProperty("user.home"));

    try {
      // byte-based input
      System.out.println("InputStream");
      FileInputStream fin = new FileInputStream(wordFilePath);
      int c;
      while ((c = fin.read()) != -1) {
        System.out.printf("%02x ", c);
      }
      System.out.println();

      // char-based input
      System.out.println("Reader");
      InputStreamReader rin = new InputStreamReader(
          new FileInputStream(wordFilePath), "UTF-8");
      while ((c = rin.read()) != -1) {
        System.out.printf("%02x ", c);
      }
      System.out.println();
    } catch (IOException e) {
      System.out.println(e);
      System.exit(1);
    }
  }
}

运行结果为:

InputStream
e4 bd a0 e5 a5 bd 0a 
Reader
4f60 597d 0a 

可以看出,基于字节的输入每次读入一个字节,两个汉字一共6个字节,换行符一个字节(0x0a),一共读了7次。而基于字符的输入每次读入后保存的结果为两个字节(因为Java内部都是UTF-16表示的,因此从文件读入字符的时候已经做了UTF-8到UTF-16转换),两个汉字和一个换行符一共读了三次。

更方便的方法

上一小节说的方法其实相当于C语言中的中的如下函数:

FileInputStream: read, fgetc, fread等

FileOutputStream: write, fputc, fwrite等

InputStreamReader, FileReader: 如果文件不是ASCII编码的,则相当于fgetwc(wchar.h文件中定义)等;如果文件是ASCII编码,则相当于fgetc等。

OutputStreamWriter, FileWriter: 同上,相当于fputwc和fputc等。

这些只能基于单个字符或者单个字节进行输入输出的API使用起来比较麻烦,比较使用用来操作二进制数据。本节会介绍一些更方便的文件I/O方法。

读写二进制文件

在不考虑对象序列化等更复杂的方法时,Java也提供了DataInputStreamDataOutputStream 的类,用来从二进制流中读取Java的基本类型数据或者向二进制流中写入基本类型数据,比如读一个整型和写入一个整型。

DataInputStream是FilterInputStream的子类,DataOutputStream则是FilterOutputStream的子类,都属于过滤类的流,其作用是从一个流读入数据,然后转换一下表达方式在输出,比如DataInputStream可以从FileInputStream连续读出4个字节,然后转换成一个整型返回给调用者。

读写二进制文件更高级的就是各种对象序列化方法了,这个本文不讨论。

读写文本文件

读写文本文件我们很习惯于按照行来进行读写,比如C语言的scanfprintf 函数。在Java中分别使用下面两个类来进行:

Scanner: 有众多构造函数,其中一个可以从指定输入流,然后实现类似scanf函数的效果。

PrintWritter: 该类在一个Writer的基础上实现了常用的print, println和printf接口。

如下代码从一个文件构造一个Scanner实例,然后你就可以调用Scanner类的next, nextInt, nextLine的函数来从文件读取输入:

Scanner in = new Scanner(new FileInputStream(pathToFile));

Scanner类还有其他构造函数,能够指定字符编码,以及从其他类型的参数构造出一个实例。

如下代码从一个文件构造出一个PrintWriter实例,然后你就可以调用print, println和printf了:

PrintWriter out = new PrintWriter(new FileWriter(pathToFile));

不过还有个更方便的构造函数:

PrintWriter out = new PrintWriter(pathToFile);
3. 标准输入,标准输出和标准错误

System类的三个成员in, out, err分别系统的标准输入、标准输出和标准错误,通过查看源码可以发现他们的定义是这样的:

public final class System {
  ...
  public static final InputStream in = null;
  public static final PrintStream out = null;
  public static final PrintStream err = null;
  ...
}

因此要从标准输出读取数据的化,可以直接使用InputStream的read方法,或者构造一个Scanner实例来使用:

System.in.read();
Scanner in = new Scanner(System.in);

标准输出和标准错误则是两个PrintStream类的实例,查看代码可以发现,这个类的实现基本上和PrintWriter一样,也就是说可以直接使用print, println和printf等方法进行数据输出。还有,PrintStream类是继承自FilterOutputStream,因此write方法也是可用的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64168.html

相关文章

  • JAVA NIO.1

    摘要:特点面向块的传统是面向流的。有四个基本属性容量,能够容纳的最大元素数目,在创建时设定并不能更改中有效位置数目,不能对超过中的区域进行读写。与缓冲区不同,通道主要由接口指定。方法获取支持的所有字符集获取实例编解码文件锁进程级支持文件锁定功能。 简介 NIO的所有类都被放在java.nio包或其子包下。 特点 面向块的I/O:传统JavaIO是面向流的I/O。流I/O一次处理一个字节。N...

    Steve_Wang_ 评论0 收藏0
  • 高并发 - 收藏集 - 掘金

    摘要:在中一般来说通过来创建所需要的线程池,如高并发原理初探后端掘金阅前热身为了更加形象的说明同步异步阻塞非阻塞,我们以小明去买奶茶为例。 AbstractQueuedSynchronizer 超详细原理解析 - 后端 - 掘金今天我们来研究学习一下AbstractQueuedSynchronizer类的相关原理,java.util.concurrent包中很多类都依赖于这个类所提供的队列式...

    levius 评论0 收藏0
  • 高并发 - 收藏集 - 掘金

    摘要:在中一般来说通过来创建所需要的线程池,如高并发原理初探后端掘金阅前热身为了更加形象的说明同步异步阻塞非阻塞,我们以小明去买奶茶为例。 AbstractQueuedSynchronizer 超详细原理解析 - 后端 - 掘金今天我们来研究学习一下AbstractQueuedSynchronizer类的相关原理,java.util.concurrent包中很多类都依赖于这个类所提供的队列式...

    fantix 评论0 收藏0
  • 通用文件服务组件(Netty实现版本)

    摘要:操作指引该文件服务组件的使用需要分为两个部分,一个是服务端配置与启动,一个是客户端的配置与启动。在调用文件服务返回的路径的时候,需要用到服务端访问文件的地址,进而访问相应的文件内容。 本文所述文件服务组件在笔者此前一篇文章中已有阐述(基于netty的文件上传下载组件),不过本文将基于之前这个实现再次进行升级改造,利用基于注解的方式进行自动装配。 1. 简介 1.1 Netty简介 Ne...

    fou7 评论0 收藏0
  • java中的NIO

    摘要:缓冲区一个对象是固定数量的数据的容器。缓冲区的工作与通道紧密联系。对于操作而言,从通道读取的数据会按顺序被散布称为到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。文件通道总是阻塞式的,因此不能被置于非阻塞模式。 简介 从JDK1.4开始,java中提供一个种叫NIO(Non-Blocking IO)的IO处理机制。与以往的标准IO机制(BIO,Blockin...

    crossoverJie 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<