Java 从入门到进阶之路(二十九)
在之前的文章我们已经可以对本地对文件和目录进行新建和删除等操作,接下来我们来对文件内对具体内容进行操作。
如下代码,我们实现了一个基本的文件写入:
1 /** 2 * java.io.RandomAccessFile 3 * 用来读写文件数据 4 * RAF是基于指针进行读写的,即RAF总是在指针指向的位置读写字节, 5 * 并且读写后指针会自动向后移动 6 * RAF既可以读取文件数据也可以向文件中写入数据 7 * 8 * @author wjt 9 */ 10 public class RandomAccessFileDemo1 { 11 public static void main(String[] args) throws IOException { 12 /** 13 * RandomAccessFile(String path, String mode) 14 * RandomAccessFile(File file, String mode) 15 * 第二个参数为模式:常用对有 r:只读模式 rw:读写模式 16 */ 17 RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw"); 18 /** 19 * void write(int d) 20 * 写出给定的int值对应的2进制的低八位 21 * 00000000 00000000 00000000 00000001 22 */ 23 raf.write(1); // 00000001 24 System.out.println("写出完毕!"); 25 raf.close(); 26 27 } 28 }
在上面的代码中我们可以看出文件的读写想要到 RandomAccessFile,这里需要注意的是要抛出异常,否则编译器会报错,同时我们用到了它的一个 write() 方法来进行文件写入,接收的是一个2进制的低八位数值,假如我们写入 raf.write(97),那么就会被准化为小写字母 a。
既然能读,那就能写,接下来我们将上面写入的 1 再读取出来
1 /** 2 * 读取文件数据 3 * 4 * @author wjt 5 */ 6 public class RandomAccessFileDemo2 { 7 public static void main(String[] args) throws IOException { 8 RandomAccessFile raf = new RandomAccessFile("raf.txt", "r"); 9 /** 10 * int read() 11 * 读取一个字节,并以10进制的int型返回 12 * 若返回值为-1,则表示读取到了文件的末尾 13 */ 14 int d = raf.read(); 15 System.out.println(d); // 1 16 raf.close(); 17 } 18 }
接下来我们再来看一下如何对文件进行复制
1 /** 2 * 复制文件 3 * 4 * @author wjt 5 */ 6 public class RandomAccessFileDemo3 { 7 public static void main(String[] args) throws IOException { 8 /** 9 * 创建一个RAF读取原文件, 10 * 再创建一个RAF向目标文件中写出, 11 * 顺序从原文件中读取每一个字节并 12 * 写入到目标文件中即可 13 */ 14 // 原文件,在原文件中随便写一些内容 15 RandomAccessFile src = new RandomAccessFile("raf.txt", "r"); 16 // 目标文件 17 RandomAccessFile desc = new RandomAccessFile("raf1.txt", "rw"); 18 // 用来保存读取到每个字节 19 int d = -1; 20 long start = System.currentTimeMillis(); 21 while ((d = src.read()) != -1) { 22 desc.write(d); 23 } 24 long end = System.currentTimeMillis(); 25 System.out.println("复制完毕!耗时:" + (end - start) + "ms"); // 复制完毕!耗时:2ms 26 } 27 }
通过上面的代码我们可以实现文件的复制,但是这样复制是比较耗时的,不如在电脑上直接复制粘贴来的快。因为这是一个从硬盘读取到内存中再写到硬盘的过程,频繁的一个字节一个字节的读取频繁地调用这个过程,所以会很慢。
如果我们不是一个字节一个字节读写,而是一组一组的读写就会提升效率,如下:
1 /** 2 * 若向提高读写效率, 3 * 可以通过提高每次读写的数据量来减少读写次数达到 4 * 5 * @author wjt 6 */ 7 public class RandomAccessFileDemo4 { 8 public static void main(String[] args) throws IOException { 9 RandomAccessFile src = new RandomAccessFile("raf.txt", "r"); 10 RandomAccessFile desc = new RandomAccessFile("raf1.txt", "rw"); 11 /** 12 * int read(byte[] data) 13 * 一次性尝试读取给定的字节数组总长度的字节量并存入到该数组中, 14 * 返回值为实际读取到的字节量, 15 * 若返回值为-1,则表示本次没有读取到任何数据(文件末尾) 16 */ 17 // 10K 18 byte[] buf = new byte[1024 * 10]; 19 // 用来保存读取到每个字节 20 int len = -1; 21 long start = System.currentTimeMillis(); 22 while ((len = src.read(buf)) != -1) { 23 /** 24 * void write(buf) 25 * 一次性将给定的字节数组中的所有数据写入 26 * void write(byte[] d, int start, int end) 27 * 将所给定数组中从小表start处开始的len个字节一次性写出 28 */ 29 desc.write(buf, 0, len); 30 } 31 long end = System.currentTimeMillis(); 32 System.out.println("复制完毕!耗时:" + (end - start) + "ms"); // 复制完毕!耗时:1ms 33 } 34 }
在上面的代码中,我们定义了一个 byte 数组来每次读写 10K 的方式进行批量复制,会发现时间变短了,需要注意的是 write() 方法需要使用重载的方法,因为最后一次可能会写入多余的数组len。
我们知道 write() 方法其实写入的是数据的低八位,那我们想要写入不同的数据类型该怎么写呢?如下代码:
1 /** 2 * RAF还提供了方便读写基本类型数据的方法 3 * 4 * @author wjt 5 */ 6 public class RandomAccessFileDemo5 { 7 public static void main(String[] args) throws IOException { 8 RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw"); 9 /** 10 * 向文件中写入一个int最大值 11 * Integer.MAX_VALUE 的二进制位 12 * 011111111 11111111 11111111 11111111 13 * 文件写入为二进制的低八位,如果我们直接写入的话 14 * 其实写入的是低八位的 11111111,显然是错误的 15 * 我们可以通过位移的方式连续写四次来进行写入操作 16 */ 17 int max = Integer.MAX_VALUE; 18 19 raf.write(max >>> 24); // 01111111 20 raf.write(max >>> 16); // 11111111 21 raf.write(max >>> 8); // 11111111 22 raf.write(max); // 11111111 23 24 // RAF 其实提供了更方便的方法来进行不同类型数据的写入 25 raf.writeInt(max); 26 raf.writeDouble(11.11); 27 raf.writeLong(1234L); 28 raf.close(); 29 } 30 }
从上面的代码中我们可以通过位移的方式按顺序每次写入一个int值的八位,写四次正好是一个int值的四个字节,当然我们我们也可以用提供好的 writeInt() 方法来直接写入,其底层代码远离其实也是用到了位移的思想,如下源码: