RandomAccessFile类详解

RandomAccessFile类详解

 

    文件存取通常是循序的,每在文件中存取一次,文件的读取位置就会相对于目前的位置前进一次。然而有时必须指定文件的某个区段进行读取或写入
的动作,也就是进行随机存取(Random Access),即要能在文件中随意地移动读取位置。这时可以使用RandomAccessFile,使用它的seek()方法来指定
文件存取的位置,指定的单位是字节。

为了移动存取位置时的方便,通常在随机存取文件中会固定每一个数据的长度。例如长度固定为每一个学生个人数据,Java中并没有直接的
方法可写入一个固定长度数据(像C/C++中的structure),所以在固定每一个长度方面必须自行设计。下面设计Employee类来说明如何自行设计每
个类的长度 ( 细看其中的size()方法 ),然后通过RandomAccessFileDemo类来说明如何从文件中随机读。

 

Java实例:Employee.java
public class Employee {
  private String name;
  private int sale;

  public Employee() {
   setName( "noname" );
  }

  public Employee( String name, int sale ) {
   setName( name );
   this.sale = sale;
  }

  public void setName( String name ) {
   StringBuilder builder = null;
   if ( name != null )
    builder = new StringBuilder( name );
   else
   builder = new StringBuilder( 15 );
   builder.setLength( 15 ); // 最长 15 字符
   this.name = builder.toString();
  }

  public void setSale( int sale ) {
   this.sale = sale;
  }

  public String getName() {
   return name;
  }

  public int getSale() {
   return sale;
  }

  // 每个数据固定写入34字节
  // 为什么是34字节呢?一个int类型为4个字节,类中的String固定为长度为15的字符(一个char占2字节)
  public static int size() {
   return 34;
  }
}
//Java示例:RandomAccessFileDemo.java
import java.io.*;
import java.util.*;

 public class RandomAccessFileDemo {

  public static void main( String[] args ) {
   Employee[] students = { 
     new Employee( "Chenmi", 5477 ),
     new Employee( "doulmi", 2566 ), 
     new Employee( "karin", 4366 ),
     };
   try {
    File file = new File( "src/test.txt" );
    
    //建立RandomAccessFile实例并以读写模式打开文件
    //RandomAccessFile randomAccessFile = new RandomAccessFile( file, "rw" );
    RandomAccessFile randomAccessFile = new RandomAccessFile( "src/test.txt", "rw" );

    for ( int i = 0; i < students.length; i++ ) {
     // 使用对应的write方法写入数据
     randomAccessFile.writeChars( students[ i ].getName() );
     randomAccessFile.writeInt( students[ i ].getSale() );
    }
    Scanner scanner = new Scanner( System.in );
    System.out.print( "读取第几个数据?" );

    int num = scanner.nextInt();

    // 使用seek()方法操作存取位置
    randomAccessFile.seek( 0 );
    randomAccessFile.skipBytes( ( num - 1 ) * Employee.size() );
    Employee student = new Employee();
    
    // 使用对应的read方法读出数据
    student.setName( readName( randomAccessFile ) );
    student.setSale( randomAccessFile.readInt() );
    System.out.println( "姓名:" + student.getName() );
    System.out.println( "薪水:" + student.getSale() );
    
    // 设置关闭文件
    randomAccessFile.close();
   } catch ( ArrayIndexOutOfBoundsException e ) {
    System.out.println( "请指定文件名称" );
   } catch ( IOException e ) {
    e.printStackTrace();
   }
  }

  private static String readName( RandomAccessFile randomAccessfile )
    throws IOException {
   char[] name = new char[ 15 ];
   for ( int i = 0; i < name.length; i++ )
    name[ i ] = randomAccessfile.readChar();
   // 将空字符取代为空格符并返回
   return new String( name ).replace( '\0', ' ' );
  }
}

 

RandomAccessFile上的相关方法实现都在批注中说明了,可以看到读写文件时几个必要的流程:

     1)打开文件并指定读写方式在Java中,当实例化一个与文件相关的输入/输出类时,就会进行打开文件的动作。在实例化的同时要指定文件是要以读出(r)、写入(w)或可读可写(rw)的方式打开,可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖。

     2)使用对应的写入方法对文件进行写入,要使用对应的写入方法。在Java中通常是write的名称作为开头,在低级的文件写入中,要写入某种类型的数据,就要使用对应该类型的方法,如writeInt()(一次将写入4个字节)、writeChar()(一次写入2个字节)等。

     3)使用对应的读出方法对文件进行读出,要使用对应的读出方法。在Java中通常是read的名称作为开头,在低级的文件读出中,要读出某种类型的数据,就要使用对应该类型的方法,如readInt()(一次将读取4个字节)、readChar()(一次将读出2个字节)等。

     现在我们具体来看RandomAccessFile中最常用的两个方法(seek( long a ) 和 skipBytes( long a ) ):

     seek( long a )是定位文件指针在文件中的位置。参数a确定读写位置距离文件开头的字节个数(注意哦,是字节个数)
     skipBytes( long a )是指在文件中跳过a个字节。
     两者的比较:seek()是绝对定位,而skipBytes()是相对定位,seek在使用过程中非常影响系统的开销,所以尽可能的少用

     在使用这两个方法时一般会遇到这两个最常见的问题:
    1、 使用raf.seek(0)或者是raf.seek(4),可以读出100以及200
  但是raf.seek(1)、raf.seek(2)、raf.seek(3)就读取出很多的数字。。。。
  比如设置raf.seek(1)后,在读取出来的值就是:25600。使用skipBytes也是一样。
  这是为什么呢?

  首先来解释为什么raf.seek(4)可以读出正确的数值:
  我们可以这样来理解,raf.seek(4)其实应该表示成raf.seek( Integer.SIZE() / 8 ),
  因为在Java中int为32位即是4个字节数,而seek读的是字节数(前面已经提到过),所以raf.seek(4)能读出200就不言而喻了

  那raf.seek(1)为什么会读出那么令人惊诧的结果呢?
  因为100200在16进制编辑器中为:00 00 00 64 00 00 00 C8()
  00 00 00 64 = 100 ; 00 00 00 C8 = 200;(高位在前)
  所以seek(1) 读到的就是 00 00 64 00 = 25600
  
    2、 但为什么通过writeInt( int n )写入文件时文件中存入的都是乱码呢?

Java示例:
import java.io.File;
import java.io.RandomAccessFile;

public class Test2 {
   
   private static final int MAX_NUM = 20;
   
   public static void main( String[] args ) throws Exception {
    File newFile = new File( "src/test.txt" );
    RandomAccessFile raf = null; 
    raf = new RandomAccessFile( newFile, "rw" );
    for ( int i = 0; i < MAX_NUM; i ++ ) {
     raf.writeInt( i );
    }
  }
}

   在查看test.txt时,将会惊奇的发现,写在文件中的是:
                              等这样一堆乱码,如果将其放在16进制阅读器中,就可发现其中的诀窍:

  00 00 00 00 00 00 00 01  00 00 00 02 00 00 00 03
  00 00 00 04 00 00 00 05  00 00 00 06 00 00 00 07
  00 00 00 08 00 00 00 09  00 00 00 0D 0A 00 00 00
  0B 00 00 00 0C 00 00 00  0D 00 00 00 0E 00 00 00
  0F 00 00 00 10 00 00 00  11 00 00 00 12 00 00 00
  13 0D 0A 0D 0A


  我们来分析Java的API源码:
  public final void writeInt(int v) throws IOException {
   write( ( v >>> 24 ) & 0xFF );
   write( ( v >>> 16 ) & 0xFF );
   write( ( v >>>  8 ) & 0xFF );
   write( ( v >>>  0 ) & 0xFF );
   //written += 4;
  }

  假设我们令 v = 125,将v转为32位的2进制 0000 0000 0000 0000 0000 0000 0111 1101
  1) >>>是什么符号
   >>>为无符号右移,意思是移入位始终补0 ,如 125  >>> 1 = 0000 0000 0000 0000 0000 0000 0011 1110

  2) v & 0xFF又是什么意思呢?
   0xFF为16进制数,转化为二进制则为1111 1111,&为与运算,所以
   0000 0000 0000 0000 0000 0000 0011 1110
  & 0000 0000 0000 0000 0000 0000 1111 1111
  结果为 0000 0000 0000 0000 0000 0000  0011 1110,即是取v的低8位(一个char类型)

  3) 这和解决我们乱码的问题有什么关系呢?
   也就是说其实RandomAccessFile类的writeInt( int v )方法在写入的过程中,是将一个Int类型的数分解成4个char类型之后写入,所
   以我们写到文件里写出的“乱码”也就不再是所谓的乱码了
   Java示例:
   for ( int n = 0; n < 100; n ++ ) {
    System.out.print( ( char ) n );
   }
   可以看到输出为 等与前面文件所存储的一样的字符,这也证明了我们的想法是正确的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值