输入/输出

File类

File类表示文件或者文件夹,比如File file = new File("dog.gif");代表了当前文件夹下的dog.gif这个文件,或者File file = new File("images");代表了当前文件夹下的images这个文件夹。File类的常见操作有:

File

创建File对象时,构造函数File(String pathname)中的字符串是文件或者文件夹的路径,这个路径可以是相对路径,也可以是绝对路径。像dog.gif,images\dog.gif就是相对路径,像Windows下的d:\images\dog.gif,UNIX下的/home/test/images/dog.gif就是绝对路径。

因为在Java中,\表示转义字符,因此在字符串字面量中\表示路径分隔符要写成\\,或者可以写成//是UNIX中的路径分隔符,也可以用在Windows系统中。因此在Windows系统里,路径d:\images\dog.gif的写法有:
- “d:\images\dog.gif” 正确
- “d:/images/dog.gif” 正确
注意,下面的写法是错误的
- “d:\images\dog.gif”

例子:

public class TestFileClass {
  public static void main(String[] args) {
    java.io.File file = new java.io.File("image/us.gif");
    System.out.println("Does it exist? " + file.exists());
    System.out.println("The file has " + file.length() + " bytes");
    System.out.println("Can it be read? " + file.canRead());
    System.out.println("Can it be written? " + file.canWrite());
    System.out.println("Is it a directory? " + file.isDirectory());
    System.out.println("Is it a file? " + file.isFile());
    System.out.println("Is it absolute? " + file.isAbsolute());
    System.out.println("Is it hidden? " + file.isHidden());
    System.out.println("Absolute path is " +
      file.getAbsolutePath());
    System.out.println("Last modified on " +
      new java.util.Date(file.lastModified()));
  }
}

文本(Text)输入/输出

File类没有创建文件以及向文件写/读数据的方法,向文件文件中写数据也称为输出Output,从文件中读数据也称为输入Input,合起来称为文件输入输出IO。

输出用PrintWriter类

可以用java.io.PrintWriter类来创建文本文件,以及向它里面写文本。

首先创建PrintWriter类对象

PrintWriter output = new PrintWriter(filename);

然后可以调用PrintWriter类的print(),println(),printf()等方法来写数据到文件中。

PrintWriter

下面的程序向文件scores.txt中写两行文本,每行文本由字符串和整数组成。

public class WriteData {
  public static void main(String[] args) throws java.io.IOException {
    java.io.File file = new java.io.File("scores.txt");
    if (file.exists()) {
      System.out.println("File already exists");
      System.exit(0);
    }

    // Create a file
    java.io.PrintWriter output = new java.io.PrintWriter(file);

    // Write formatted output to the file
    output.print("John T Smith ");
    output.println(90);
    output.print("Eric K Jones ");
    output.println(85);

    // Close the file
    output.close();
  }
}

try-with-resources来自动关闭资源

上面的程序中,如果注释掉output.close()可能会发生的情况是数据没有写到文件中,写完数据后调用流的close()方法会将数据最终写到目标中去。但是有时程序员会忘记调用它。因此从JDK 7开始Java支持一种新的语法来自动调用对象的close()方法。一种语法称为try-with-resources语法。

try(声明和创建资源){
   处理资源
}

这样的资源变量的类必须实现了AutoClosealbe接口,这个接口里面只声明了一个close()方法。PrintWriter就实现了AutoClosealbe接口。当大括号里处理资源代码完成,不论其中是否有异常抛出,在try括号里声明的资源对象变量会被自动调用close()方法。因此上述代码可以写成

public class WriteDataWithAutoClose {
  public static void main(String[] args) throws Exception {
    java.io.File file = new java.io.File("scores.txt");
    if (file.exists()) {
      System.out.println("File already exists");
      System.exit(0);
    }

    try (
      // Create a file
      java.io.PrintWriter output = new java.io.PrintWriter(file);
    ) {
      // Write formatted output to the file
      output.print("John T Smith ");
      output.println(90);
      output.print("Eric K Jones ");
      output.println(85);
    }
  }
}

用Scanner来读取文本

可以用java.util.Scanner类来从控制台和文件中读取文本和数值,Scanner把输入视为以分隔符(默认为空白字符)分开的字符串”token”。
从键盘上读取,可以创建一个Scanner,并把它和System.in关联起来,
Scanner input = new Scanner(System.in);
从文件中读取,需要把Scanner与文件关联,
Scanner input = new Scanner(new File(filename));
或者
Scanner input = new Scanner(filename);

Scanner

假设scores.txt中是

John T Smith 90
Eric K Jones 85

import java.util.Scanner; 

public class ReadData {
  public static void main(String[] args) throws Exception {
    // Create a File instance
    java.io.File file = new java.io.File("scores.txt");

    // Create a Scanner for the file
    Scanner input = new Scanner(file);

    // Read data from a file
    while (input.hasNext()) {
      String firstName = input.next();
      String mi = input.next();
      String lastName = input.next();
      int score = input.nextInt();
      System.out.println(
        firstName + " " + mi + " " + lastName + " " + score);
    }

    // Close the file
    input.close();
  }
}

上述代码的运行结果是

John T Smith 90
Eric K Jones 85

nextInt()、nextDouble()等方法读取字符的时候会忽略掉分隔符(默认为空白字符,即’ ‘, \t, \f, \r, 或者 \n),把连续的非分隔符读到字符串中直到遇到某个分隔符,遇到的分隔符不会被读取,留给后续的读取方法处理。然后nextInt()会把字符串解析为整数值并返回,其它的方法类似,如果不能正确解析,会抛出java.util.InputMismatchException非检查异常。next()方法不解析读取的字符串,直接返回它。

nexLine()方法读取包括分隔符在内的一行中的字符串直到遇到换行符。换行符也被读取,但是不作为字符串的一部分返回,后续的next…()方法从下一行开始读取。

文本(text)输入和输出原理

使用PrintWriter向文件中写的过程中,可以视为经过了这几步:

  1. 将二进制的数值转换为可读的文本格式,即字符序列(数组)
  2. 将字符序列中的各个字符以平台默认编码格式编码为字节序列
  3. 将字节序列(数组)原样输出到文件中

PrintWriter实质上完成的是第1步,2、3两步是借助于其它的类来完成的。第2步,将字符编码为字节序列是由OutputStreamWriter类来实现, 第3步,将字节序列写到文件中是由FileOutputStream来实现的。

Scanner将文件中的整数字符串扫描到整数变量中的过程可以看成PrintWriter的反过程:

  1. 将文件中的内容读入到字节序列(数组)
  2. 将字节序列以平台默认编码格式解码为字符序列(数组)
  3. 扫描字符序列(数组),将它们从可读字符解析为二进制的数值并存储到变量中

Scanner实质上完成的是第3步,1、2两步是借助于其它类来实现的。第1步,将文件中的数据读入到字节序列是由文件输入类FileInputStream来实现。第2步,将字节序列解码为字符是由InputStreamReader类来实现。

比如,以文本形式输出变量x的值到文件value.txt中,

PrintWriter pw = new PrintWriter("value.txt");
int x = 199;
pw.print(x);
  1. 将二进制的数值199即00000000 00000000 00000000 1100 0111这4个字节转换为可读的文本格式,即字符序列’1’、’9’、’9’。(Java中的字符以Unicode值存储在2个字节中,’1’是0x0031,’9’是0x0039。)
  2. 将字符’1’、’9’,转换为以平台默认编码格式编码为字节序列,假设是ASCII编码,那么字符’1’的编码是0x31(1个字节),字符’9’的编码是0x39(1个字节)
  3. 将字节序列(数组)原样输出到文件中,即将0x31 0x39 0x39输出到文件中

用Scanner读取文本文件是这个过程的逆过程。

文本/二进制的联系

注意,上面的图对应的是PrintWriter工作过程的2、3步。PrintWrite的主要工作,即过程1并没有展示在图中。

常见的编码形式除了ASCII(针对字符集为ASCII字符集),对包含ASCII字符集的超集Unicode字符集来说,常见的编码为UTF-8,UTF-16等编码格式。

二进制(Binary)输入/输出

可以简单理解为,处理文本文件处理的是字符,主要是要对字符进行编码,而处理二进制文件处理的是字节。以二进制(字节)形式来处理输入输出的常见的类有,

  • FileInputStream/FileOutputStream处理文件的字节级别的读写。
  • DataInputStream/DataOutputStream把数值或者字符串以内存中的形式和字节序列相互转换。
  • BufferedInputStream/BufferedOutputStream开辟字节序列的缓存,使对文件的读写先读写到缓存中,当缓存满了之后再一次性的将多个字节读写到文件中,加快文件的输入和输出。

Binary I/O Classes

OutputStream是二进制输出的基类

OutputStream

InputStream是二进制输入的基类

InputStream

从这两个类的方法可以看到,它们处理的都是字节一级的数据。基本所有方法都会抛出java.io.IOException或者其子类的检查异常。

二进制输入/输出到文件

二进制处理的基本含义就是把内存中的字节原样输入/输出到目标中。如果要输出到文件中,需要使用FileInputStream/FileOutputStream。FileInputStream用来从文件中以字节为单元读到内存中,FileOutputStream用来将内存中字节序列写到文件中。FileInputStream/FileOutputStream没有在它们的父类InputStream/OutputStream基础上新增新的方法,当然,它们重写了父类InputStream/OutputStream的相关方法。

FileInputStream的构造函数如下,

  • public FileInputStream(String filename)
  • public FileInputStream(File file)

创建FileInputStream时,如果文件不存在,会抛出java.io.FileNotFoundException异常。

FileOutputStream的构造函数如下,

  • public FileOutputStream(String filename)
  • public FileOutputStream(File file)
  • public FileOutputStream(String filename, boolean append)
  • public FileOutputStream(File file, boolean append)

创建FileOutputStream时,如果文件不存在,会创建一个新文件。如果文件已经存在,前两个构造函数会删除文件中的内容。后面两个构造函数可以指定是否追加数据到文件中,而不是删除文件中的内容。

在I/O类中的几乎所有的方法都会抛出java.io.IOException。因此,我们需要声明抛出它,或者捉住它。

FilterInputStream/FilterOutputStream

Filter stream是一类输入输出流,它们会关联到文件输入和输出流,对它们的输入和输出操作经过它们的“filter”后最终会操作到文件中。FiterInputStream/FilterOutputStream有两种子类:

  • DataInputStream/DataOutputStream
  • BufferedInputStream/BufferedOutputStream

DataInputStream/DataOutputStream

DataOutputStream将基本数值类型以及字符串String类型以内存中的原本形式转换为字节流并输出到文件中。DataInputStream将文件中的字节流转换为基本数值类型或许字符串String类型。

DataOutputStream

DataInputStream

DataInputStream实现了DataInput接口来读数值和字符串。DataOutputStream实现了DataOutput接口来写基本数值和字符串。可以看成它们将基本数值类型没有任何修改的在内存和文件中复制。字符串中的字符有多种写方法。

writeChar(char c)和writeChars(Stirng s)方法将字符和字符串中的字符写入到文件中,字符类型在内存中以2个字节的Unicode值存储,那么它们就以这种2个字节的形式写到文件中,或者从文件中读取。writeChars(String s)不写字符中字符的个数,因此如果要写字符串中含有字符的个数,可以在调用writeChars(String s)之前用writeInt(int v)的方法来写字符串的长度。

writeUTF(String s)首先写2个字节的字符串长度信息到输出流,紧接着将字符串s中的字符以修改的UTF-8形式写到输出流里。比如,writeUTF(“ABCDEF”)将八个字节写到文件中,这八个字节以16进制表示是00 06 41 42 43 44 45 46。与writeChars(String s)比较,writeUTF(String s)会记录字符串s的长度。而且如果字符串s中的字符大部分都是ASCII字符的话,writeUTF(String s)会比writeChars(String s)更加节省空间。

要创建DataInputStream/DataOutputStream对象,需要使用如下的构造函数:

  • public DataInputStream(InputStream instream)
  • public DataOutputStream(OutputStream outstream)

下面的代码创建了一个input stream,它会从in.dat文件中读取字节流。output stream将数值写到out.data文件中。

import java.io.*;

public class TestFileStream {
  public static void main(String[] args) throws IOException {
    try (
      // Create an output stream to the file
      FileOutputStream output = new FileOutputStream("temp.dat");
    ) {
      // Output values to the file
      byte byteArray[] = {1,2,3,4,5,6,7,8,9, 10};
      for (int i = 0; i < byteArray.length; i++)
        output.write(byteArray[i]);
    }

    try (
      // Create an input stream for the file
      FileInputStream input = new FileInputStream("temp.dat");
    ) {
      // Read values from the file
      int value;
      while ((value = input.read()) != -1)
        System.out.print(value + " ");
    }
  }
}

处理文本文件时的文件读写操作最终也是使用这两个类的。只是有时候它们被封装到PrintWriter和Scanner类中了,我们不需要对它们直接操作。

BufferedInputStream/BufferedOutputStream

BufferedInputStream/BufferedOutputStream也是FilterInputStream/FilterOutputStream的子类,它们的作用很单纯,给我们在内存中开辟读/写文件的缓冲区,这样不用每次调用函数都直接从文件中读/写了,因为文件操作相对内存操作来说非常的慢。BufferedInputStream/BufferedOutputStream含有一个byte的数组,当调用方法从文件中读字节流时先一次性从文件中读很多个byte到byte的数组中,然后再次读取函数就从byte的数组中读取字节,直到byte的数组被读完。写操作类型。

类图

例如,我们可以在创建DateOutputStream或者DataInputStream时添加BufferedOutputStream对象或者BufferedInputStream对象。

DataInputStream/DataOutputStream

DataOutputStream将基本数值类型以及字符类型以内存中的原本二进制形式转换为字节流并输出到文件中。DataInputStream将文件中的内容转换以原本二进制形式存储到基本数值类型或字符类型的变量中。

DataOutputStream

DataInputStream

DataOutputStream实现了DataOutput接口来写基本数值和字符。DataInputStream实现了DataInput接口来读数值和字符。可以看成它们将基本数值类型没有任何修改的在内存和文件中复制。

下面以DataOutputStream为例,来说明类中的方法,

比如,writeInt(int x)将x直接输出到文件中。例如,整数199在内存中是00000000 00000000 00000000 1100 0111这4个字节(写成十六进制是0x000000C7),writeInt(199)将这4个字节写到文件中。

有几种方法来处理字符和字符串。

writeChar(char c)将字符写入到文件中,字符类型在内存中以2个字节的Unicode值存储,那么它们就以这种2个字节的形式写到文件中。例如字符’1’在内存中存储的是’1’的Unicode码值0x0031,writeChar(‘1’)将这2个字节存储到文件中。

writeChars(String s)将字符串中的字符依次写入到文件中。它不写字符中字符的个数,因此如果要写字符串中含有字符的个数,以供读取时确定字符串的字符个数,可以在调用writeChars(String s)之前用writeInt(int v)的方法来写字符串的长度。

writeUTF(String s)首先写2个字节的字符串长度信息到输出流,紧接着将字符串s中的字符以修改的UTF-8形式写到输出流里。比如,writeUTF(“ABCDEF”)将八个字节写到文件中,这八个字节以16进制表示是00 06 41 42 43 44 45 46。与writeChars(String s)比较,writeUTF(String s)会记录字符串s的长度。而且如果字符串s中的字符大部分都是ASCII字符的话,writeUTF(String s)会比writeChars(String s)更加节省空间。

DataInputStream/DataOutputStream

要创建DataInputStream/DataOutputStream对象,需要使用如下的构造函数:

  • public DataInputStream(InputStream instream)
  • public DataOutputStream(OutputStream outstream)

下面的代码创建了一个input stream,它会从in.dat文件中读取字节流。output stream将数值写到out.data文件中。

DataInputStream input =
    new DataInputStream(new FileInputStream("in.dat"));
DataOutputStream output =
    new DataOutputStream(new FileOutputStream("out.dat"));

例子

import java.io.*;

public class TestDataStream {
  public static void main(String[] args) throws IOException {
    try ( // Create an output stream for file temp.dat
      DataOutputStream output =
        new DataOutputStream(new FileOutputStream("temp.dat"));
    ) {
      // Write student test scores to the file
      output.writeUTF("John");
      output.writeDouble(85.5);
      output.writeUTF("Jim");
      output.writeDouble(185.5);
      output.writeUTF("George");
      output.writeDouble(105.25);
    }

    try ( // Create an input stream for file temp.dat
      DataInputStream input =
        new DataInputStream(new FileInputStream("temp.dat"));
    ) {
      // Read student test scores from the file
      System.out.println(input.readUTF() + " " + input.readDouble());
      System.out.println(input.readUTF() + " " + input.readDouble());
      System.out.println(input.readUTF() + " " + input.readDouble());
    }
  }
}

BufferedInputStream/BufferedOutputStream

BufferedInputStream/BufferedOutputStream也是FilterInputStream/FilterOutputStream的子类,它们的作用很单纯,给我们在内存中开辟读/写文件的缓冲区,这样不用每次调用write/read函数都直接从磁盘上文件中读/写了,因为磁盘文件操作相对内存操作来说非常的慢。可以理解成BufferedInputStream/BufferedOutputStream含有一个byte的数组。这个数组起到缓冲的作用。比如BufferedOutputStream在数组满了才把数组里面的内容一次性写到磁盘文件中,这比调用一次write函数就把字节写到磁盘文件中要快。

BufferedInputStream/BufferedOutputStream

BufferedInputStream/BufferedOutputStream没有新增方法,它们的方法都是从InputStream/OutputStream继承下来的,当然,它们重写了父类的这些方法。

我们可以在创建DateOutputStream或者DataInputStream时添加BufferedOutputStream对象或者BufferedInputStream对象。

DataOutputStream output = new DataOutputStream(
    new BufferedOutputStream(new FileOutputStream("temp.dat")));
DataInputStream input = new DataInputStream(
    new BufferedInputStream(new FileInputStream("temp.dat")));

对于小量数据的读写,buffered stream作用不明显,但是当读写的数据较大时,作用就比较显著。

例子

import java.io.*;

public class Copy {
  /** Main method
     @param args[0] for sourcefile 
     @param args[1] for target file
   */
  public static void main(String[] args) throws IOException { 
    // Check command-line parameter usage
    if (args.length != 2) { 
      System.out.println(
        "Usage: java Copy sourceFile targetfile");
      System.exit(1);
    }

    // Check if source file exists
    File sourceFile = new File(args[0]);
    if (!sourceFile.exists()) {
       System.out.println("Source file " + args[0] 
         + " does not exist");
       System.exit(2);
    }

    // Check if target file exists
    File targetFile = new File(args[1]);
    if (targetFile.exists()) {
      System.out.println("Target file " + args[1] 
        + " already exists");
      System.exit(3);
    }

    try (
      // Create an input stream
      BufferedInputStream input = 
        new BufferedInputStream(new FileInputStream(sourceFile));

      // Create an output stream
      BufferedOutputStream output = 
        new BufferedOutputStream(new FileOutputStream(targetFile));
    ) {
      // Continuously read a byte from input and write it to output
      int r, numberOfBytesCopied = 0;
      while ((r = input.read()) != -1) {
        output.write((byte)r);
        numberOfBytesCopied++;
      }

      // Display the file size
      System.out.println(numberOfBytesCopied + " bytes copied");
    }
  }
}

猜你喜欢

转载自blog.csdn.net/cb_east/article/details/80189962