文件的读取操作(1)

前面一节说了File类不可以访问数据(即对文件的读写操作)。那么java也设计了对与文件数据处理的方式。
在说对于数据的处理之前,需要对计算机的进制编码解码有一定的了解,如果这部分不是特别明白的,自行脑补,我就在这里不赘述了。


处理文件数据的方式一共有两种:
1.基于指针的操作来玩成对文件数据的读写
(1) 一个类用不同的方法完成对于文件的读写操作
(2) 因为由指针控制,所以可以在文件任意位置进行读写操作
2.基于流的方式完成对数据的读写
(1)使用不同的低级流或者高级流完成对于文件的读写操作,使用不同的高级流可以简化对于读写的操作(例如写对象,写字符等操作)
(2)因为不是指针控制,所以只能覆盖写(读)或者追加写(读)


java.io.RandomAccessFile
该类是用于读写文件数据的。读写数据是基于指正的操作
即:总是在指正当前位置进行读写
RandomAccessFile有两种创建模式:
只读模式:仅用于读取文件数据
读写模式,对文件可以编辑(可读,可写)


构造方法:

/*
         * 针对raf.dat文件进行读写操作
         * 构造方法:
         * RandomAccessFile(String path,String mode)
         * RandomAccessFile(File file,Sting mode)
         * 
         * 模式对应的字符串:
         * “r”只读
         * “rw”读写
         */
        RandomAccessFile  raf=
        new RandomAccessFile("raf.dat","rw");

RandomAccessFile的构造方法需要传入两个参数,第一个参数即可以传入文件路径,也可以直接传入File对象,第二个参数是两种模式。“r”只读模式,“rw”读写模式。
会抛出java.io.FileNotFoundException异常


write方法:

        /*
         * void write(int d)
         * 将给定的int值的“低八位”2进制信息写入文件中
         *                             vvvvvvvv(只写入这个地方)   
         * 00000000 000000000 00000000 00000001
         */
        raf.write(97);//97对应的二进制0110001,对应解码为a
        System.out.println("写出完毕!");
        //读写完毕后一定colse
        raf.close();

需要在这里说明一下,write写入的是一个int值,大家都知道int是4个字节,也就是32位二进制,write方法写入的只是32位中的底八位,也就是说这里写入的int值范围是0-255之间的数字

那么问题来了,如果我写入的int值大于255会是什么样?
例如

raf.write(256);

这个时候写入的是什么呢?
这里其实写入的是0
二进制表示256为:
00000000 000000000 00000001 00000000
只写入底八位的字节,也就是只写入00000000,
前面的00000000 000000000 00000001并不会写入。
所以在读取时,只会读取到0。

既然每次只写入一个字节,那么为什么不直接用byte来写入呢?
假如:写入的是一个字节byte,那么这么做就会导致它的写入范围变成了-128到127之间的数了,在表示文件末尾就没办法用-1来表示了(会在read方法中看到)。

那么这就问题来了,想要写入的编码大于一个字节怎么办呢?
多个字节拼接就可以了。

注意:write方法会抛出异常


从文件中读取字节

RandomAccessFile raf=
                new RandomAccessFile("raf.dat","r");
        /**
         * int read()
         * 读取一个字节,并以int形式返回
         * 若返回值为-1,则表示读取到了文件末尾
         * 即:EOF(end of file)
         */
        int d=raf.read();
        System.out.println(d+" "+"写出完毕!");
        raf.close();

在读取时,每次只会读取一个字节并且会把这一个字节填充到int的低八位去
例如上面的例子:raf.dat文件写入的是256即000000000
读取时,读取到00000000,
并在前面填充00000000 000000000 0000000
这样读取到的就int值就为0
注意:int值-1的二进制表述为
111111111 11111111 11111111 11111111
因为在读取时,只会读取底八位的值,然后填充24个0,所以不论怎么样都不会读取到-1。
这也就是为什么write方法和read方法每次都是读写低八位的原因

此方法同样会抛出异常


RandomAccessFile提供了可以方便读写Java中不同数据类型数据的方法

        RandomAccessFile raf=
        new RandomAccessFile("raf.dat","rw");

        /*
         *long getFilePointer()
         *该方法可以获取当前RandomAccessFile的指针位置
         *刚创建的RAF指针位置在文件开始处,以下标形式表示,所以第一个字节位置为0. 
         */
        long pos=raf.getFilePointer();
        System.out.println("指正位置:"+pos);
        /*
         * 二进制对应的int最大值
         * 01111111 111111111 11111111 11111111
         */
        int imax=Integer.MAX_VALUE; 
        /*
         * 一次性写入4个字节,将给定的int值对应的32位2进制全部写出
         */
        raf.writeInt(imax);
        System.out.println("指正位置:"+raf.getFilePointer());
        //一次性写入8个字节
        double d=123.132;
        raf.writeDouble(d);
        System.out.println("指正位置:"+raf.getFilePointer());
        //一次性写入8个字节
        long l=12345;
        raf.writeLong(l);
        System.out.println("指正位置:"+raf.getFilePointer());


        /*
         * void seek(long pos)
         * 将指针移动到pos位置
         */
        raf.seek(0);
        int i=raf.readInt();
        System.out.println(raf.getFilePointer()+"  "+i);

        raf.seek(12);
        long l1=raf.readLong();
        System.out.println(raf.getFilePointer()+" "+l1);

        raf.seek(4);
        char c1=raf.readChar();
        System.out.println(raf.getFilePointer()+"   "+c1);
        /*
         * int readInt()
         * 连续读取4个字节,并转换为int值返回
         * 若读取到文件末尾会抛出EOFException(EndOFFile)
         */

        raf.close();

关于指针在上面的代码中注释的很清楚(如果不懂什么是指针,请去查看一下C语言中的指针),现在着重来说说怎么做到一次写入4个字节的int值

在源码中可以看到

 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;
    }

从源码中我们可以清楚的看到,其实在写入int值的时候还是用了write方法,每次只写入一个字节,四个字节的写了四次。先将四个字节的头部一个字节,左移3个字节(即将头部字节移动到底八位去),写入文件,这样写入的底八位其实就是int值的头部一个字节。后面的三次写入也做了同样的移位操作。这样就保证了能完整的写入一个int值

在读取时,读取到每个字节做左移运算后加起来,这样就完整的还原了这个int值。

源码:

public final int readInt() throws IOException {
        int ch1 = this.read();
        int ch2 = this.read();
        int ch3 = this.read();
        int ch4 = this.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

介绍了这么多的方法,利用这些方法来完成复制文件的操作

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * 复制文件
 * @author Analyze
 *
 */
public class CopyDemo1 {

    public static void main(String[] args) throws IOException {
        RandomAccessFile src=
                new RandomAccessFile("music.flac","r");
        RandomAccessFile desc=new RandomAccessFile("music_copy.mp3","rw");

        int d=-1;
        long start=System.currentTimeMillis();
        while((d=src.read())!=-1){
            desc.write(d);
        }

        long end=System.currentTimeMillis();
        System.out.println("复制完毕!");
        System.out.println("耗时"+(end-start)+"ms");
        src.close();
        desc.close();
    }

}

注意:最好不要在主函数上抛出异常,这里只是用于测试。

在上面的这段代码中,大家在测试中可能会发现一个问题,如果当复制一个文件比较大的时候(大于20MB),计算机会处理很长的时间。按照我们平时在操作系统上复制文件时,压根就不需要花费这么长的时间,这是为什么呢?

我们前一节有提到,所有文件都是在硬盘上,而你在读取时每次讲读取到到值赋值给d的时候,d这个变量都是在内存中开辟的,复制的时候又将内存中开辟的这个变量复制给了硬盘上的某个文件。这样类似于在这么干一件事,你想要将很多块砖头搬到在20层楼的家里,你每次只搬一块砖头就往二十层跑,每搬一块就跑一次,这样当然特别耗费时间精力。

那么有什么好的改良方法呢?我可以准备一个小推车,每次在小推车上把砖头装满,一次就搬运小推车的量。也就是说,我们可以准备一个数组用来承担每次运输到硬盘上的任务。

代码示例:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * 若想提高读写效率,需要通过提高每次读写的数据量来减少读写的次数达到
 * 
 * 读写硬盘的次数越多,读写效率越差
 * @author Analyze
 *
 */
public class CopyDemo2 {

    public static void main(String[] args) throws IOException {
        RandomAccessFile src=
                new RandomAccessFile("music.flac","r");
        RandomAccessFile desc=
                new RandomAccessFile("music_copy1.mp3","rw");
        /*
         * 一次读取一组字节的方法
         * int read(byte[] date)
         * 一次性读取给定数组date的length个字节
         * 并且将读取的字节全部存入到date数组中
         * 返回值为实际读取到的字节量,若为-1,则表示本次没有读取到任何字节(文件末尾)
         */
        //10kb(推荐使用,一般来讲10kb是最合适的读写,并不是越大越好)
        byte[] buf=new byte[1024*10];
        int len=-1;
        long start=System.currentTimeMillis();
        while((len=src.read(buf))!=-1){
            /*
             * void write(byte[] date)
             * 将给定字节数组中的所有字节一次性写出
             * 
             * 重载的方法
             * void write(byte[] d,int s,int len)
             * 将给定数组中从下标s处的字节开始的连续len个字节一次性写出
             */
            desc.write(buf, 0, len);
        }
        long end=System.currentTimeMillis();
        System.out.println("复制完毕!");
        System.out.println("耗时"+(end-start)+"ms");
        src.close();
        desc.close();

    }

}

这里需要说明一点,为什么不write方法直接将buf数组直接写入,而用了重载的方法限制写入长度呢?

模拟一下这个过程,每次都读取到一个buf数组的容量并写入,那么如果最后一次读取时,读取到一半这个文件就到文件末尾了,那么这个buf数组就会变成前面一半是这次读到的,后面一半是上次读的后一半(因为每次都是用同一个buf数组),这样就会导致复制的最后一部分出现重复。

关于访问文件流的处理方式,会在下一节详细总结。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值