Java输入与输出(4)
1、RandomAccessFile类
-
RandomAccessFile类,不属于流,但具有读写文件数据的功能,可以随机从文件的任何位置开始执行读写数据的操作
-
RandomAccessFile类可以将文件以指定的操作权限(如只读,只写等)的方式打开,具体使用哪种权限取决于创建它所采用的构造方法
通过两种构造方法创建RandomAccessFile对象,需要接受两个参数:第一个参数指定关联的文件,第二个参数mode指定访问文件的模式(文件的权限)
RandomAccessFile构造方法如下:
RandomAccessFile(File file,String mode)//使用参数file指定被访问的文件,并使用mode来决定指定访问模式
RandomAccessFile(File name,String mode)//使用参数name指定被访问文件的路径,并使用mode来决定指定访问模式
RandomAccessFile特点
RandomAccessFile是java Io体系中功能最丰富的文件内容访问类。即可以读取文件内容,也可以向文件中写入内容。但是和其他输入/输入流不同的是,程序可以直接跳到文件的任意位置来读写数据。
因为RandomAccessFile可以自由访问文件的任意位置,所以如果我们希望只访问文件的部分内容,那就可以使用RandomAccessFile类。
与OutputStearm,Writer等输出流不同的是,RandomAccessFile类允许自由定位文件记录指针,所以RandomAccessFile可以不从文件开始的地方进行输出,所以RandomAccessFile可以向已存在的文件后追加内容。则应该使用RandomAccessFile。
RandomAccessFile的整体介绍
RandomAccessFile类包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile可以自由的移动记录指针,即可以向前移动,也可以向后移动。
RandomAccessFile包含了以下两个方法来操作文件的记录指针:
- long getFilePointer(); 返回文件记录指针的当前位置
- void seek(long pos); 将文件记录指针定位到pos位置
RandomAccessFile即可以读文件,也可以写,所以它即包含了完全类似于InputStream的3个read()方法,其用法和InputStream的3个read()方法完全一样;也包含了完全类似于OutputStream的3个write()方法,其用法和OutputStream的3个Writer()方法完全一样。除此之外,RandomAccessFile还包含了一系类的readXXX()和writeXXX()方法来完成输入和输出。
RandomAccessFile有两个构造器,其实这两个构造器基本相同,只是指定文件的形式不同而已,一个使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建RandomAccessFile对象还需要指定一个mode参数。该参数指定RandomAccessFile的访问模式,有以下4个值:
- r:表示以只读方式打开。如果试图对RandomAccessFile对象执行写入操作,会抛出I/OException异常
- rw:表示以"读写"方式打开。如果该文件不存在,则会自动创建该文件。
- rws:表示以"读写"方式打开。与"rw"相比,它要求对文件的内容或者元数据的每个更新都同步写入到底层的存储设备
- rwd:表示以**"读写"方式打开。它要求对文件的内容**的每个更新都同步写入到底层的存储设备
RandomAccessFile对象中包含了一个记录指针来标识当前读写处的位置**。顺序读写,指针在开始处为0的位置**,除此之外,可以自由的移动记录指针,既可以向前也可以向后
int skipBytes(int n) //使读写指针从当前位置开始,跳过n个字节
void write(byte[] b) //将指定的字节数组写入到这个文件,并从当前文件指针开始
long getFilePointer() //返回当前读写指针所处的位置
void seek(long pos) //设定读写指针的位置,与文件的开头相隔pos个字节数
void setLength(long newLength) //设置此文件的长度
final String readLine() //从指定文件当前指针读取下一行内容
使用RandomAccessFile实现从指定位置读取文件的功能
public static void main(String[] args)throws IOException {
String filePath="E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt";
RandomAccessFile raf=null;
File file=null;
try {
file=new File(filePath);
raf=new RandomAccessFile(file,"r");
// 获取 RandomAccessFile对象文件指针的位置,初始位置为0
System.out.print("输入内容:"+raf.getFilePointer());
//移动文件记录指针的位置
raf.seek(1000);
byte[] b=new byte[1024];
int hasRead=0;
//循环读取文件
while((hasRead=raf.read(b))>0){
//输出文件读取的内容
System.out.print(new String(b,0,hasRead));
}
}catch (IOException e){
e.printStackTrace();
}finally {
raf.close();
}
}
在上面的程序的关键代码两处,一处是创建了RandomAccessFile对象,该对象以只读模式打开了Test.txt文件,这意味着RandomAccessFile文件只能读取文件内容,不能执行写入。第二处调用了seek(1000)方法,是指把文件的记录指针定位到1000字节的位置。也就是说程序将从1000字节开始读取数据。其他部分的代码的读取方式和其他的输入流没有区别。
public class RandomAccessFileTest2 {
public static void main(String[] args)throws IOException {
String filePath="E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt";
RandomAccessFile raf=null;
File file=null;
try {
file=new File(filePath);
// 以读写的方式打开一个RandomAccessFile对象
raf=new RandomAccessFile(file,"rw");
//将记录指针移动到该文件的最后
raf.seek(raf.length());
//向文件末尾追加内容
raf.writeChars("这是追加内容。。");
}catch (IOException e){
e.printStackTrace();
}finally {
raf.close();
}
}
}
上面代码先以读,写方式创建了一个RandomAccessFile对象,然后将文件记录指针移动到最后,接下来使用RandomAccessFile向文件中写入内容。和其他输出例OutputStream的方式相同。每运行一次上面的程序,就能发现text.txt文件中多添加了一行内容。
使用RandomAccessFile实现向文件指定位置插入内容的功能
注:RandomAccessFile不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件原有的内容,如果需要向指定位置插入内容,程序需要先把插入点后面的内容写入缓存区,等把需要插入的数据写入到文件后,再将缓存区的内容追加到文件后面。
/**
* 插入文件指定位置的指定内容
* @param filePath 文件路径
* @param pos 插入文件的指定位置
* @param insertContent 插入文件中的内容
* @throws IOException
*/
public static void insert(String filePath,long pos,String insertContent)throws IOException{
RandomAccessFile raf=null;
File tmp=File.createTempFile("tmp",null);
tmp.deleteOnExit();
try {
// 以读写的方式打开一个RandomAccessFile对象
raf = new RandomAccessFile(new File(filePath), "rw");
//创建一个临时文件来保存插入点后的数据
FileOutputStream fileOutputStream = new FileOutputStream(tmp);
FileInputStream fileInputStream = new FileInputStream(tmp);
//把文件记录指针定位到pos位置
raf.seek(pos);
raf.seek(pos);
//------下面代码将插入点后的内容读入临时文件中保存-----
byte[] bbuf = new byte[64];
//用于保存实际读取的字节数据
int hasRead = 0;
//使用循环读取插入点后的数据
while ((hasRead = raf.read(bbuf)) != -1) {
//将读取的内容写入临时文件
fileOutputStream.write(bbuf, 0, hasRead);
}
//-----下面代码用于插入内容 -----
//把文件记录指针重新定位到pos位置
raf.seek(pos);
//追加需要插入的内容
raf.write(insertContent.getBytes());
//追加临时文件中的内容
while ((hasRead = fileInputStream.read(bbuf)) != -1) {
//将读取的内容写入临时文件
raf.write(bbuf, 0, hasRead);
}
}catch (Exception e){
throw e;
}
}
public static void main(String[] args)throws IOException {
String filePath="E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt";
insert(filePath,1000,"插入指定位置指定内容");
}
上面的程序使用File类的createTempFile方法创建了一个临时文件(该文件将在JVM退出后被删除),用于保存被插入点后面的内容。程序先将文件中插入点后的内容读入临时文件中,然后重新定位到插入点,将需要插入的内容添加到文件后面,最后将临时文件的内容添加到文件后面,通过这个过程就可以向指定文件,指定位置插入内容。每次运行上面的程序,都会看到Test.txt文件中多了一行内容。
2、过滤器流
在Java开发中,过滤器流(FilterStream)是一种非常实用的工具,用于对输入流或输出流进行过滤和处理。它可以实现对数据的即时处理,使得数据的传输和处理更加高效和方便。本文将指导你如何使用Java过滤器流,并给出了详细的步骤和示例代码。
流程概述
使用Java过滤器流进行数据过滤和处理的流程如下:
步骤 | 描述 |
---|---|
1 | 创建输入流或输出流 |
2 | 创建过滤器流对象并连接到输入流或输出流 |
3 | 进行数据的读取或写入 |
4 | 关闭过滤器流和底层流 |
下面将逐步介绍每一步的具体操作和所需的代码。
步骤详解
步骤1:创建输入流或输出流
首先,我们需要创建一个输入流(InputStream)或输出流(OutputStream)对象,作为数据的来源或目标。具体的创建方式根据需求而定,例如可以使用文件输入流(FileInputStream)读取文件,或者使用网络输入流(URLInputStream)读取网络数据等。
示例代码:
// 创建文件输入流
InputStream inputStream = new FileInputStream("input.txt");
步骤2:创建过滤器流对象并连接到输入流或输出流
接下来,我们需要创建一个过滤器流对象,并将其连接到步骤1中创建的输入流或输出流上。Java提供了多种过滤器流的实现类,可以根据需求选择合适的实现类。例如,如果需要对输入流进行缓冲处理,可以使用缓冲输入流(BufferedInputStream)。
示例代码:
// 创建缓冲输入流对象,并连接到输入流上
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
步骤3:进行数据的读取或写入
现在,我们可以使用过滤器流对象进行数据的读取或写入操作。具体的操作方式根据需求而定,例如可以使用过滤器流的read()方法读取数据,或者使用write()方法写入数据。
示例代码:
// 从缓冲输入流中读取数据
int data = bufferedInputStream.read();
// 使用输出流写入数据
outputStream.write(data);
步骤4:关闭过滤器流和底层流
最后,我们需要关闭过滤器流和底层流,释放资源。关闭过滤器流会自动关闭底层流,所以只需关闭过滤器流即可。
示例代码:
// 关闭缓冲输入流
bufferedInputStream.close();
使用Java过滤器流可以对输入流或输出流进行过滤和处理,提高数据的传输和处理效率。本文介绍了使用Java过滤器流的步骤和示例代码,希望对你理解和使用过滤器流有所帮助
3、对象序列化
Java 序列化是一种将对象转换为字节流的过程,以便可以将对象保存到磁盘上,将其传输到网络上,或者将其存储在内存中,以后再进行反序列化,将字节流重新转换为对象。
序列化在 Java 中是通过 java.io.Serializable 接口来实现的,该接口没有任何方法,只是一个标记接口,用于标识类可以被序列化。
当你序列化对象时,你把它包装成一个特殊文件,可以保存、传输或存储。反序列化则是打开这个文件,读取序列化的数据,然后将其还原为对象,以便在程序中使用。
序列化是一种用于保存、传输和还原对象的方法,它使得对象可以在不同的计算机之间移动和共享,这对于分布式系统、数据存储和跨平台通信非常有用。
以下是 Java 序列化的基本概念和用法:
实现 Serializable 接口: 要使一个类可序列化,需要让该类实现 java.io.Serializable 接口,这告诉 Java 编译器这个类可以被序列化,例如:
import java.io.Serializable;
public class MyClass implements Serializable {
// 类的成员和方法
}
序列化对象: 使用 ObjectOutputStream 类来将对象序列化为字节流,以下是一个简单的实例:
MyClass obj = new MyClass();
try {
FileOutputStream fileOut = new FileOutputStream("object.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(obj);
out.close();
fileOut.close();
} catch (IOException e) {
e.printStackTrace();
}
上述代码将一个名为 “object.ser” 的文件中的 obj 对象序列化。
反序列化对象: 使用 ObjectInputStream 类来从字节流中反序列化对象,以下是一个简单的实例:
MyClass obj = null;
try {
FileInputStream fileIn = new FileInputStream("object.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
obj = (MyClass) in.readObject();
in.close();
fileIn.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
上述代码从 “object.ser” 文件中读取字节流并将其反序列化为一个 MyClass 对象。
类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法。
ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:
public final void writeObject(Object x) throws IOException
上面的方法序列化一个对象,并将它发送到输出流。相似的 ObjectInputStream 类包含如下反序列化一个对象的方法:
public final Object readObject() throws IOException,
ClassNotFoundException
该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。
实例
为了演示序列化在 Java 中是怎样工作的,我将使用之前教程中提到的 Employee 类,假设我们定义了如下的 Employee 类,该类实现了Serializable 接口。
public class Employee implements java.io.Serializable
{
public String name;
public String address;
public transient int SSN;
public int number;
public void mailCheck()
{
System.out.println("Mailing a check to " + name
+ " " + address);
}
}
请注意,一个类的对象要想序列化成功,必须满足两个条件:
该类必须实现 java.io.Serializable 接口。
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
如果你想知道一个 Java 标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现 java.io.Serializable接口。
序列化对象
ObjectOutputStream 类用来序列化一个对象,如下的 SerializeDemo 例子实例化了一个 Employee 对象,并将该对象序列化到一个文件中。
该程序执行后,就创建了一个名为 employee.ser 文件。该程序没有任何输出,但是你可以通过代码研读来理解程序的作用。
注意: 当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名。
import java.io.*;
public class SerializeDemo
{
public static void main(String [] args)
{
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
try
{
FileOutputStream fileOut =
new FileOutputStream("/tmp/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in /tmp/employee.ser");
}catch(IOException i)
{
i.printStackTrace();
}
}
}
反序列化对象
下面的 DeserializeDemo 程序实例了反序列化,/tmp/employee.ser 存储了 Employee 对象。
import java.io.*;
public class DeserializeDemo
{
public static void main(String [] args)
{
Employee e = null;
try
{
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
以上程序编译运行结果如下所示:
Deserialized Employee...
Name: Reyan Ali
Address:Phokka Kuan, Ambehta Peer
SSN: 0
Number:101
这里要注意以下要点:
readObject() 方法中的 try/catch代码块尝试捕获 ClassNotFoundException 异常。对于 JVM 可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException 异常。
注意,readObject() 方法的返回值被转化成 Employee 引用。
当对象被序列化时,属性 SSN 的值为 111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后 Employee 对象的 SSN 属性为 0。