OpenDSA 完整目录

Chapter 4 Programming Tutorials

| 关于   «  7. 使用 Java 的 Scanner 类   ::   目录   ::   9. JUnit 测试与你(JUnit Testing And You)  »

8. Java 中的随机访问文件

8.1. 理解文件输入/输出

在之前的教程中,我们看到了如何使用 Java 的 scanner 类读取顺序文本文件。Scanner 很适合读取文件, 但我们还可以更进一步,更抽象地理解我们的数据:与其把文件读成字符串,我们不如直接读取原始字节, 再把这些字节写入文件。这样做,我们能存储的数据类型就不受任何限制了。 此外,RandomAccessFile(随机访问文件)允许在文件中的任意位置进行读写,可以跳转到文件内部的 任何合法位置。 在阅读本教程之前,请务必查看 RandomAccessFile API。

在开始使用 RandomAccessFile 类之前,请确保你理解以下概念。

  1. Reading(读取) - 在磁盘的某个位置读取一块数据(字节)。这些字节可以位于文件的任意位置。

  2. Writing(写入) - 在磁盘的某个位置写入一块数据(字节)。我们可以"写入"文件中的任意位置。

3) File Pointer(文件指针) - 一个表示我们在文件中所处字节位置的数字。我们可以操作文件指针, 让它指向文件中的任何位置。 RandomAccessFile 应该被当作一个字节数组(array of bytes)来对待。 它的索引从 0 开始,最后一个位置是文件总长度减 1。

关于 RandomAccessFile 类,有一点非常重要。很容易把文件指针 seek 到文件末尾之后并写到那个位置 (因为 RandomAccessFile 类假定你可能想要扩展文件),但如果把文件指针移到文件末尾之后并在那个 位置写入,那么当用户试图读取时,可能会在旧的文件长度处遇到 EndOfFile 或 EOF,从而抛出一个 错误。

8.1.1. 使用 RandomAccessFile 类

看看下面的代码示例。它会生成一个大小为 0 到 999 字节的文件,然后按顺序用 65 到 90(随机选取) 之间的数字填充这些字节。程序会输出一个纯文本文件,其中随机填满了英文大写字母。这是为什么? 请记住,对计算机而言,数据不过是一堆比特。对于 ASCII 文本,我们有一组数字值在 0 到 255 (也就是一个字节的最大值)之间的字符。ASCII 中的大写字母范围是 65 到 90,因此当我向文件写入 65-90 之间的值时,它会被解释为一个字母。

import java.io.RandomAccessFile;
import java.util.Random;

public class Main {

   public static void main(String[] args) {
      try {
         RandomAccessFile raf = new RandomAccessFile(args[0], "rw");//Open our file with read/write access
         Random rdn = new Random();//Get a new random generator
         int min = 0; int max = 999;//Our file will have a maximum size of 999 bytes
         int amin = 0; int amax = 25;//The alphabet has 26 letters thus we want to generate a random offset
         //to "pick" those letters
         int length = rdn.nextInt(max - min + 1) + min;//Get the length of our file
         raf.setLength((long)length);//Set the length or size of the file, note that the we do not have to
         //set length of the file as the write method will "grow" the file with each sequential write
         for(int i=0; i < length; i++) {//Run until our file is full
            int chr = rdn.nextInt(amax - amin + 1) + amin;//Randomly get our offset
            chr += 65;//ASCII capital letters range from 65-90, so add 65 to whatever we got
            raf.write(chr);//Write the int to the file
            System.out.printf("Writing %c at %d\n", chr, raf.getFilePointer());//Ouput our char and where
            //we wrote it.
         }
         raf.close();//Close our filestream.
      } catch (Exception e) {       
         e.printStackTrace();
      } 
   }

}

这样我们就生成了一个文件,并用随机创建的字节填充了它。那么如何读取这个文件呢?RandomAccessFile 类提供了若干读取函数。下面这个示例演示了其中几个。示例首先读入整个文件(即上面生成的),并把 内容输出到终端。然后程序在文件中随机生成一个新位置,把文件指针设置到那里,输出该位置、该字节 的 int 值以及它所代表的字符。最后,程序创建一个与文件大小相同的字节数组,并尝试用 read 方法 读取那么多字节。如注释所解释的,read 方法返回一个 int,表示实际读到了多少字节。读入数组之后, 程序再输出读取开始的位置、读入的字节数以及提供给 read 的数组大小。在我的机器上运行这个程序 (先用上面的代码生成文件)后,产生了以下输出。

EXUHIULUQKLZICYUQIKMWZCQPXKYBEHKCJVUIBCIYHKJPCPENWXJLZEMDHVLPNBXDOTNVZIUYNMQTLZVTITTVMMLDWJTMEHSUZUBTSEQPATLOQRUOODL
Set pos to 111, value 85, character U
Tried reading at position 112, read 4 bytes, array was size 116
import java.io.RandomAccessFile;
import java.util.Random;

public class Main {

   public static void main(String[] args) {
      try {
         RandomAccessFile raf = new RandomAccessFile(args[0], "rw");//Open our file with read/write access
         Random rdn = new Random();
         int min = 0; int max = (int)raf.length();
         int pos = rdn.nextInt(max - min + 1) + min;        
         byte[] file = new byte[(int)raf.length()];//create an array to hold our file
         raf.readFully(file);//Read from our RAF up to the length of our array
         String str = new String(file);//Convert the bytes representing our file to text
         System.out.println(str);//Print out our file size
         raf.seek(pos);//Set our position randomly in the file
         byte b = raf.readByte();//Read the byte at our current spot
         System.out.printf("Set pos to %d, value %d, character %c\n", pos, (int)b, (char)b);
         file = new byte[(int)raf.length()];//Create a new array the size of the file
         pos = (int)raf.getFilePointer();//Get the current position in the file
         int read = raf.read(file, 0, file.length);//Use the read function. The read function requires you
         //provide a byte array, the offset from the current position you want to start and the amount of
         //bytes you would like. This is largely based off a c function as traditionally when you pass an
         //array you have no idea what the size of the array is. In most cases you will simply want to 
         //provide the length of the array you are passing in. Note that this function returns an int.
         //This int represents the bytes read from the file.
         System.out.printf("Tried reading at position %d, read %d bytes, array was size %d\n", pos, read, 
               file.length);
         raf.close();//Close our filestream.
      } catch (Exception e) {       
         e.printStackTrace();
      } 
   }

}

   «  7. 使用 Java 的 Scanner 类   ::   目录   ::   9. JUnit 测试与你(JUnit Testing And You)  »

关闭窗口