File类:
1、File类仅包含文件的信息,不包含文件的内容
2、在File类中指定路径时,”/”和”\”可以混用
RandomAccessFile类:
一、RandomAccess类的特性:
1、RandomAccessFile类提供了众多的文件访问方法
2、RandomAccessFile类支持随机访问方式
所谓随机访问方式,主要指的是每次读写文件时,可以从文件的任意位置开始读写,而不必每次都要从头开始
随机访问的原理是通过一个指示器来实现的,该指示器可以跳转到文件的任意位置,指示器指在哪里,就可以以哪里为起点来对文件进行读写
随机访问在网络中最常见的应用就是断点续传和多线程下载:
断点续传:
当用户继续下载文件时,会将上次已下载的文件信息上传给服务器,然后服务器就可以根据此信息在用户需要下载的文件上创建一个指示器,并从该位置开始继续向用户传输文件。
多线程下载:
所谓多线程,就是用户可以同时开启多个下载线程来下载同一个文件,例如第一个线程读取第0K~100K的内容,第二个线程读取第100K~200K的内容……当所有线程都下载完毕后,再将其拼成一个整体的文件。
所以,在多线程下载时,用户就需要向服务器提供每个线程需要从什么位置开始下载的信息,这样服务器就可以在文件上创建多个指示器,并从这些位置开始,向每个线程分别传输该线程负责下载的部分文件。
3、RandomAccessFile在随机读写等长记录格式的文件时有很大的优势
所谓等长记录格式的文件,主要指的是以一条条记录的方式存储信息的文件,并且每条记录的长度完全相等(例如数据库表文件)。
4、RandomAccessFile类仅限于操作文件,不能访问其他的IO设备,如网络、内存映像等。
5、RandomAccessFile在创建的时候,可以通过向构造方法中传入不同的参数来决定是只读这个文件还是读写这个文件
其构造方法为
RandomAccesssFile(File f, String type);
若向type中传入”r”,则只能对这个文件进行读取操作
若向type中传入”rw”,则可以对这个文件进行读写操作
*Tips:RandomAccessFile无论是读还是写,都是以文件指示器当前的位置为起点开始的
二、RandomAccess类的应用举例:
1、题目:
向一个文件中写入3名员工的信息,每个员工含有姓名和年龄两个字段,然后按照写入的先后以第二位、第一位、第三位的顺序读出员工信息
2、思路:
由于需要对文件进行随机读取,而RandomAccessFile类擅长随机读写等长记录格式的文件
因此如果将3名员工的信息以等长记录的格式存入文件的话,在读取时就会非常方便
3、设计:
年龄可以用int格式来保存。由于int格式始终占有4个字节,因此不需要处理。
姓名只能用String格式来保存。由于String的长度并不固定,因此需要进行处理。
姓名的处理方式为:在员工类中定义一个常量LEN,该变量表示姓名的长度
当输入姓名的长度小于LEN时,就在姓名后补充”\u0000”直到长度为8个字符
当输入姓名的长度大于LEN时,就截取出姓名中的前8个字符
4、实现:
员工类:
public class Employee {
public static final int LEN = 8; //指定姓名的长度为8位
private String name;
private int age;
public Employee (String name, int age) {
//若输入的姓名长度大于指定的长度,则截取至指定的长度
if (name.length() > LEN) {
name.substring(0, LEN);
}
//否则将姓名的长度补足至指定的长度
else {
while (name.length() < LEN) {
name += "\u0000";
}
}
this.name = name;
this.age = age;
}
//getters与setters略
}
用于对文件进行读写的接口:
import java.io.RandomAccessFile;
public interface EmployeeAccess {
/** 向文件中写入员工信息 */
public void writeEmployee (RandomAccessFile ra, Employee e) throws Exception;
/** 从文件中读取员工信息 */
public void readEmployee (RandomAccessFile ra) throws Exception;
/** 获取每条记录所占的字节数 */
public int getRecordBytes ();
}
接口的Byte实现:将员工的姓名以字节的方式写入文件,且以字节的形式读出
import java.io.RandomAccessFile;
public class ByteEmployeeAccessImpl implements EmployeeAccess{
public void writeEmployee(RandomAccessFile ra, Employee e) throws Exception {
ra.write(e.getName().getBytes()); //以字节的方式向文件中写入员工姓名信息
ra.writeInt(e.getAge()); //向文件中写入员工年龄信息
}
public void readEmployee(RandomAccessFile ra) throws Exception {
//使用字节的方式从文件中读取员工姓名信息
byte [] buffer = new byte [Employee.LEN];
int length = ra.read(buffer);
String name = new String(buffer, 0, length);
//从文件中读取员工年龄信息
int age = ra.readInt();
//打印员工信息
System.out.println(name.trim() + ":" + age);
}
public int getRecordBytes () {
return Employee.LEN + 4;
}
}
*Tips:由于int类型的数据长度为4个字节,而write方法每次只能写入1个字节
因此如果使用write方法来写入int类型的数据,则会造成信息丢失
因此必须使用writeInt方法来写入int类型的数据,其余简单数据格式同理
接口的Char实现:将员工的姓名以字符的方式写入文件,且以字符的形式读出
import java.io.RandomAccessFile;
public class CharEmployeeAccessImpl implements EmployeeAccess{
public void writeEmployee(RandomAccessFile ra, Employee e) throws Exception {
ra.writeChars(e.getName()); //以字符的方式向文件中写入员工姓名信息
ra.writeInt(e.getAge()); //向文件中写入员工年龄信息
}
public void readEmployee(RandomAccessFile ra) throws Exception {
//使用字符的方式从文件中读取员工姓名信息
String name = "";
for (int i=0; i<Employee.LEN; i++) {
name += ra.readChar();
}
//从文件中读取员工年龄信息
int age = ra.readInt();
//打印员工信息
}System.out.println(name.trim() + ":" + age);
public int getRecordBytes () {
return Employee.LEN * 2 + 4;
}
}
*Tips:接口的Char实现与接口的Byte实现最主要的区别是:
在接口的Byte实现中,写入员工姓名时使用的是write方法,读取员工姓名时使用的是read方法
而在接口的Char实现中,写入员工姓名时使用的是writeChars方法,读取员工姓名时使用的是readChar方法
两者的区别主要在于:write和read方法是以字节的方式读写文件的,而writeChars和readChar方法是以字符的方式读写文件的
举个例子,若员工的姓名为中文(例如员工的姓名为“张三”)
由于在JAVA中无论中文还是英文都算作是一个字符(占用两个字节),因此JAVA会在张三这个姓名后面补足6个空格
这时如果使用write方法向文件中写入员工姓名的话,由于在windows下中文算作两个字节,而英文只算作一个字节(ASCII码)
那么实际写入文件中的员工姓名长度就是10个字节(两个汉字占用4个字节,6个空格占用6个字节)
这样如果再使用read方法只读取8个字节的话,就会读取出错误的结果
而如果使用writeChar方法向文件中写入员工姓名的话,由于是直接以字符的形式向文件中写入
那么实际写入文件中的员工姓名长度就是8个字符(每个字符占用两个字节,共16个字节)
这样如果再使用readChar去读取8个字符的话,就可以正确读取
测试类:向一个文件中写入3名员工的信息,然后按照写入顺序以第二名、第一名、第三名的先后顺序读出员工信息
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestEmployee {
public static void main(String [] args) throws IOException {
EmployeeAccess ea = new ByteEmployeeAccessImpl(); //若员工姓名中不包含中文,则可以使用byte实现类
//EmployeeAccess ea = new CharEmployeeAccessImpl(); //若员工姓名中包含中文,则可以使用char实现类
//创建3个员工对象
Employee e1 = new Employee ("zhangsan", 23);
Employee e2 = new Employee ("lisi", 24);
Employee e3 = new Employee ("wangwu", 21);
//调用EmployeeAccess接口中的方法,向文件中写入Employee对象
try {
RandomAccessFile ra = new RandomAccessFile("employee.txt", "rw");
ea.writeEmployee(ra, e1);
ea.writeEmployee(ra, e2);
ea.writeEmployee(ra, e3);
ra.close();
} catch (Exception e) {
e.printStackTrace();
}
//调用EmployeeAccess接口中的方法,从文件中读取Employee对象
try {
RandomAccessFile ra = new RandomAccessFile("employee.txt", "r");
//跳过第1条记录,将指示器定位到第2条记录前,并读取第2条记录
ra.skipBytes(ea.getRecordBytes());
ea.readEmployee(ra);
//直接将指示器定位到第1条记录前,并读取第1条记录
ra.seek(0);
ea.readEmployee(ra);
//跳过第2条记录,将指示器定位到第3条记录前,并读取第3条记录
ra.skipBytes(ea.getRecordBytes());
ea.readEmployee(ra);
ra.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
*Tips:
seek方法是绝对定位,意思是直接将指示器定位到指定的位置
skipBytes方法是向后跳过,意思是将指示器从当前位置向后跳转若干个字节
输出结果为:
lisi:24
zhangsan:23
wangwu:21
本文介绍了如何利用Java的RandomAccessFile类进行文件的随机读写操作,特别强调了它在断点续传和多线程下载中的应用。文章通过具体实例展示了如何将员工信息以等长记录的形式存储于文件中,并实现了按顺序读取这些信息的功能。同时,阐述了在处理不同长度的字符串时,如何灵活地进行数据填充或截取,以确保数据的一致性和完整性。

被折叠的 条评论
为什么被折叠?



