本文转载自JDK源码阅读-RandomAccessFile

导语

FileInputStream只能用于读取文件,FileOutputStream只能用于写入文件,而对于同时读取文件,并且需要随意移动文件当前偏移量的话,就需要使用RandomAccessFile这个类了。RandomAccessFile是对操作系统提供的文件读写能力最完整的封装。

打开文件

RAF打开文件时,除了指定文件对象,还需要指定一个模式,取值有:

  • “r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
  • “rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
  • “rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
  • “rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。

“rws”和”rwd”的效率比”rw”低非常非常多,因为每次读写都需要刷到磁盘才会返回,这两个中”rwd”比”rws”效率高一些,因为”rwd”只刷新文件内容,”rws”刷新文件内容与元数据,文件的元数据就是文件更新时间等信息。

这些特性是操作系统提供的特性,通过这次阅读源码,我们来看看是如何使用这些特性来实现上面的这些模式的。

// 4个标志位,用于组合表示4种模式
private static final int O_RDONLY = 1;
private static final int O_RDWR = 2;
private static final int O_SYNC = 4;
private static final int O_DSYNC = 8; public RandomAccessFile(File file, String mode)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
int imode = -1;
// 只读模式
if (mode.equals("r"))
imode = O_RDONLY;
else if (mode.startsWith("rw")) {
// 读写模式
imode = O_RDWR;
rw = true; // 读写模式下,可以结合O_SYNC和O_DSYNC标志
if (mode.length() > 2) {
if (mode.equals("rws"))
imode |= O_SYNC;
else if (mode.equals("rwd"))
imode |= O_DSYNC;
else
imode = -1;
}
}
if (imode < 0)
throw new IllegalArgumentException("Illegal mode \"" + mode
+ "\" must be one of "
+ "\"r\", \"rw\", \"rws\","
+ " or \"rwd\"");
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
if (rw) {
security.checkWrite(name);
}
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
// 新建文件描述符
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name, imode);
} private void open(String name, int mode)
throws FileNotFoundException {
open0(name, mode);
} private native void open0(String name, int mode)
throws FileNotFoundException;
// jdk/src/share/native/java/io/RandomAccessFile.c
JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_open0(JNIEnv *env,
jobject this, jstring path, jint mode)
{
int flags = 0;
// JAVA中的标志位与操作系统标志位转换
if (mode & java_io_RandomAccessFile_O_RDONLY)
flags = O_RDONLY;
else if (mode & java_io_RandomAccessFile_O_RDWR) {
flags = O_RDWR | O_CREAT;
if (mode & java_io_RandomAccessFile_O_SYNC)
flags |= O_SYNC;
else if (mode & java_io_RandomAccessFile_O_DSYNC)
flags |= O_DSYNC;
} // 调用fileOpen打开函数
fileOpen(env, this, path, raf_fd, flags);
}

fileOpen之后的流程与FileInputStream的一致,可以参考JDK源码阅读-FileInputStream

可以看出,相比于FileInputStream固定使用O_RDONLYFileOutputStream固定使用O_WRONLY | O_CREATRandomAccessFile提供了在Java中指定打开模式的能力。

读取文件

public int read() throws IOException {
return read0();
} public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
} public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}

这三个读取函数的实现与FileInputStream一致,可以参考JDK源码阅读-FileInputStream

RandomAccessFile还提供了一个遍历方法,用于读取指定长度的数据,如果还没读取到指定长度就到文件尾,抛出EOFException

public final void readFully(byte b[]) throws IOException {
readFully(b, 0, b.length);
} public final void readFully(byte b[], int off, int len) throws IOException {
int n = 0;
do {
int count = this.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
} while (n < len);
}

文件偏移量相关操作

获取当前文件偏移量

public native long getFilePointer() throws IOException;
// jdk/src/share/native/java/io/RandomAccessFile.c
JNIEXPORT jlong JNICALL
Java_java_io_RandomAccessFile_getFilePointer(JNIEnv *env, jobject this) {
FD fd;
jlong ret; // 获取记录在FileDescriptor中的文件描述符
fd = GET_FD(this, raf_fd);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return -1;
}
// 通过seek当前偏移量0个字节的方式获取当前文件偏移量
if ((ret = IO_Lseek(fd, 0L, SEEK_CUR)) == -1) {
JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
}
return ret;
}

设置当前文件偏移量

public void seek(long pos) throws IOException {
if (pos < 0) {
throw new IOException("Negative seek offset");
} else {
seek0(pos);
}
} private native void seek0(long pos) throws IOException;
// jdk/src/share/native/java/io/RandomAccessFile.c
JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_seek0(JNIEnv *env,
jobject this, jlong pos) { FD fd; // 获取记录在FileDescriptor中的文件描述符
fd = GET_FD(this, raf_fd);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return;
} if (pos < jlong_zero) {
JNU_ThrowIOException(env, "Negative seek offset");
} else if (IO_Lseek(fd, pos, SEEK_SET) == -1) {
// 设置文件偏移量为pos指定的位置,SEEK_SET表示表示移动到的位置距离文件开始处pos长度
JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
}
}

RandomAccessFile还提供了相对当前位置移动文件偏移量的方法:

// jdk/src/share/native/java/io/RandomAccessFile.c
public int skipBytes(int n) throws IOException {
long pos;
long len;
long newpos; if (n <= 0) {
return 0;
}
// 当前文件当前偏移量
pos = getFilePointer();
// 获取文件长度
len = length();
newpos = pos + n; // 如果文件偏移量大于文件尾,则设置为文件尾(Java自己做的限制)
if (newpos > len) {
newpos = len;
}
seek(newpos); /* return the actual number of bytes skipped */
return (int) (newpos - pos);
} 这个方法不是原子的,所以多线程操作的时候要注意。 ## 获取文件长度 ```java
public native long length() throws IOException;
// jdk/src/share/native/java/io/RandomAccessFile.c
JNIEXPORT jlong JNICALL
Java_java_io_RandomAccessFile_length(JNIEnv *env, jobject this) {
FD fd;
jlong cur = jlong_zero;
jlong end = jlong_zero; // 获取记录在FileDescriptor中的文件描述符
fd = GET_FD(this, raf_fd);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return -1;
} if ((cur = IO_Lseek(fd, 0L, SEEK_CUR)) == -1) {
JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
} else if ((end = IO_Lseek(fd, 0L, SEEK_END)) == -1) {
JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
} else if (IO_Lseek(fd, cur, SEEK_SET) == -1) {
JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
}
return end;
}

获取文件长度的流程:

  1. 获取当前文件偏移量,记录下来
  2. 设置当前文件偏移量到文件未,得到文件的长度
  3. 设置当前文件偏移量到之前记录的位置
  4. 返回文件长度

这么来看,UNIX系统上精确获取文件长度的做法就只这个流程了。

设置文件长度

public native void setLength(long newLength) throws IOException;
// jdk/src/share/native/java/io/RandomAccessFile.c
JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_setLength(JNIEnv *env, jobject this,
jlong newLength)
{
FD fd;
jlong cur; // 获取记录在FileDescriptor中的文件描述符
fd = GET_FD(this, raf_fd);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return;
}
// 获取当前文件偏移量
if ((cur = IO_Lseek(fd, 0L, SEEK_CUR)) == -1) goto fail; // 调用ftruncate来设置文件长度
if (IO_SetLength(fd, newLength) == -1) goto fail; // 设置文件长度后,恢复文件偏移量
// 如果是缩小了文件,并且文件偏移量大于现在的文件长度,设置文件偏移量为文件尾
if (cur > newLength) {
if (IO_Lseek(fd, 0L, SEEK_END) == -1) goto fail;
} else {
if (IO_Lseek(fd, cur, SEEK_SET) == -1) goto fail;
}
return; fail:
JNU_ThrowIOExceptionWithLastError(env, "setLength failed");
} // jdk/src/solaris/native/java/io/io_util_md.h
#define IO_SetLength handleSetLength // jdk/src/solaris/native/java/io/io_util_md.c
jint
handleSetLength(FD fd, jlong length)
{
int result;
RESTARTABLE(ftruncate64(fd, length), result);
return result;
}

写入文件

public void write(int b) throws IOException {
write0(b);
} public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length);
} public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len);
}

这三个write方法实现与FileOutputStream相同,可以参考JDK源码阅读-FileOutputStream

读取Java数据类型方法

RandomAccessFile还提供了读取Java数据类型的方法,这些方法与DataInputStreamDataOutputStream中提供的一样。

boolean readBoolean()
byte readByte()
int readUnsignedByte()
short readShort()
int readUnsignedShort()
char readChar()
int readInt()
long readLong()
float readFloat()
double readDouble()
String readLine()
String readUTF()
void writeBoolean(boolean v)
void writeByte(int v)
void writeShort(int v)
void writeChar(int v)
void writeInt(int v)
void writeLong(long v)
void writeFloat(float v)
void writeDouble(double v)
void writeBytes(String s)
void writeChars(String s)
void writeUTF(String str)

这些方法都是调用RandomAccessFile中基础的read/write方法实现的。

JDK源码阅读-RandomAccessFile的更多相关文章

  1. JDK源码阅读-FileDescriptor

    本文转载自JDK源码阅读-FileDescriptor 导语 操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数.Java虽然在设计上使用了抽象程度更高的流来作为文 ...

  2. JDK源码阅读(三):ArraryList源码解析

    今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 ...

  3. JDK源码阅读(一):Object源码分析

    最近经过某大佬的建议准备阅读一下JDK的源码来提升一下自己 所以开始写JDK源码分析的文章 阅读JDK版本为1.8 目录 Object结构图 构造器 equals 方法 getClass 方法 has ...

  4. 利用IDEA搭建JDK源码阅读环境

    利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...

  5. JDK源码阅读-FileOutputStream

    本文转载自JDK源码阅读-FileOutputStream 导语 FileOutputStream用户打开文件并获取输出流. 打开文件 public FileOutputStream(File fil ...

  6. JDK源码阅读-FileInputStream

    本文转载自JDK源码阅读-FileInputStream 导语 FileIntputStream用于打开一个文件并获取输入流. 打开文件 我们来看看FileIntputStream打开文件时,做了什么 ...

  7. JDK源码阅读-ByteBuffer

    本文转载自JDK源码阅读-ByteBuffer 导语 Buffer是Java NIO中对于缓冲区的封装.在Java BIO中,所有的读写API,都是直接使用byte数组作为缓冲区的,简单直接.但是在J ...

  8. JDK源码阅读-Reference

    本文转载自JDK源码阅读-Reference 导语 Java最初只有普通的强引用,只有对象存在引用,则对象就不会被回收,即使内存不足,也是如此,JVM会爆出OOME,也不会去回收存在引用的对象. 如果 ...

  9. JDK源码阅读-DirectByteBuffer

    本文转载自JDK源码阅读-DirectByteBuffer 导语 在文章JDK源码阅读-ByteBuffer中,我们学习了ByteBuffer的设计.但是他是一个抽象类,真正的实现分为两类:HeapB ...

随机推荐

  1. Django(静态文件or路由)

    静态文件 在开发中同创会使用到 css,js,img等静态文件,这里带大家简单的介绍一下django如何处理静态页面, 配置 setting.py文件 STATIC_URL = '/static/' ...

  2. Uva 10815 Andy's First Dictionary(字符串)

    题目链接:https://vjudge.net/problem/UVA-10815 题意 找出一段文本中的所有单词,以小写形式按照字典序输出. 思路 用空白符替换文本中所有非字母字符后再次读入. 代码 ...

  3. pta—紧急救援 (dijkstra)

    题目连接:https://pintia.cn/problem-sets/994805046380707840/problems/994805073643683840 题面: 作为一个城市的应急救援队伍 ...

  4. POJ1087 A Plug for UNIX(网络流)

    在会议开始之前,你收集所有记者想要使用的设备,并尝试设置它们.你注意到有些设备使用没有插座的插头.你想知道这些设备是否来自建造这个房间时并不存在的国家.对于一些插座,有几个设备使用相应的插头.对于其他 ...

  5. 深入了解typeof与instanceof的使用场景及注意事项

    JavaScript中的数据类型分为两类,undefined,number,boolean,string,symbol,bigint,null[1]组成的基础类型和Object.Function.Ar ...

  6. LeetCode刷题笔记 - 12. 整数转罗马数字

    学好算法很重要,然后要学好算法,大量的练习是必不可少的,LeetCode是我经常去的一个刷题网站,上面的题目非常详细,各个标签的题目都有,可以整体练习,本公众号后续会带大家做一做上面的算法题. 官方链 ...

  7. Redis的主从架构+哨兵模式

    Redis主从架构 redis主从架构搭建,配置从节点步骤: 1.复制一份redis.conf文件的目录 cd /usr/local/java cp -a redis redis_6380 2.将相关 ...

  8. 数理统计10(习题篇):寻找UMVUE

    利用L-S定理,充分完备统计量法是寻找UMVUE的最方便方法,不过实际运用时还需要一些小技巧,比如如何写出充分完备统计量.如何找到无偏估计.如何求条件期望,等等.课本上的例题几乎涵盖了所有这些技巧,我 ...

  9. java之 javassist简单使用

    0x01.javassist介绍 什么是javassist,这个词一听起来感觉就很懵,对吧~ public void DynGenerateClass() { ClassPool pool = Cla ...

  10. 微服务架构Day04-SpringBoot之web开发

    引入项目 把html页面放在模板引擎文件夹templates下,这样能使用模板引擎的功能. 登录页面国际化 国际化:编写国际化配置文件 1.编写国际化配置文件,抽取页面需要显示的国际化消息 2.Spr ...