上一篇文章中列举了JavaIO中FileDescriptor和File类提供的一些文件操作,这些操作还只是对文件系统中的文件进行创建或删除操作。鉴于大一玩过Window编程(对Linux API不是很熟悉),所以这篇文章会从Windows C API去分析一下Java提供给我们的几个文件读写类。
建议结合着源代码看这篇文章(这篇文章就是记录我看源代码的过程,这里的java版本是1.8.0_131)
RandomAccessFile
这个类就是完全模仿C语言的文件读写操作,允许随机读取,想读文件的哪个部分就可以把文件流指针指到哪儿。下面会列一张表将这个类中的常用方法和标准C语言API进行对比,然后再看一下Java在Native层是怎么实现这个类的:
Java | C |
---|---|
public int read(byte b[], int off, int len) public int read(byte b[]) public final void readFully(byte b[]) public final void readFully(byte b[], int off, int len) |
size_t fread( void *buffer, size_t size, size_t count, FILE *stream ); |
public int read() |
int fgetc( FILE *stream ); int getc( FILE *stream ); |
public void write(byte b[]) public void write(byte b[], int off, int len) |
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream ); |
public void write(int b) |
int fputc( int ch, FILE *stream ); int putc( int ch, FILE *stream ); |
public void seek(long pos) public int skipBytes(int n) |
int fseek( FILE *stream, long offset, int origin ); int fsetpos( FILE *stream, const fpos_t *pos ); void rewind(FILE *stream); |
public native long getFilePointer() |
long ftell( FILE *stream ); int fgetpos( FILE *stream, fpos_t *pos ); |
RandomAccessFile还同时实现了DataOutput, DataInput
两个接口,所以同时拥有了DataInputStream
和DataOutputStream
两个类的基本方法。
open
// 首先从构造方法开始看
public RandomAccessFile(String name, String mode)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, mode);
}
// mode参数指定文件的打开模式
public RandomAccessFile(File file, String mode)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
int imode = -1;
// r 表示“只读”,调用写操作将会抛出IO异常
if (mode.equals("r"))
imode = O_RDONLY;
// rw 表示“可读可写”,如果文件不存在就会创建它
else if (mode.startsWith("rw")) {
imode = O_RDWR;
rw = true;
if (mode.length() > 2) {
// rws 表示每一次写入操作文件内容(content)或元数据(metadata),
// 底层存储设备也会同步写入
if (mode.equals("rws"))
imode |= O_SYNC;
// rws 表示每一次写入操作文件内容(content)
// 底层存储设备也会同步写入
else if (mode.equals("rwd"))
imode |= O_DSYNC;
else
imode = -1;
// “rwd”模式可用于减少执行的I / O操作的数量。
// 使用“rwd”更新时只要写入文件内容;
// 使用“rws”更新时要写入文件内容以及文件元数据(文件大小,文件名等信息),
// 而文件元数据的更新,通常需要至少一个底层IO操作
}
}
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;
// 最终辗转调用native方法
open(name, imode);
}
private void open(String name, int mode)
throws FileNotFoundException {
open0(name, mode);
}
// 最终的native方法
private native void open0(String name, int mode)
throws FileNotFoundException;
下面看一下native层是怎么打开文件的:
/
// RandomAccessFile.c文件
JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_open(JNIEnv *env,
jobject this, jstring path, jint mode)
{
int flags = 0;
// 构造标志位
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);
}
/
// io_util_md.c文件
// 这里看的windows上的实现
void
fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags)
{
FD h = winFileHandleOpen(env, path, flags);
if (h >= 0) {
SET_FD(this, h, fid);
}
}
FD
winFileHandleOpen(JNIEnv *env, jstring path, int flags)
{
// 将标志位解析成Windows API中规定的标志位
const DWORD access =
(flags & O_WRONLY) ? GENERIC_WRITE :
(flags & O_RDWR) ? (GENERIC_READ | GENERIC_WRITE) :
GENERIC_READ;
const DWORD sharing =
FILE_SHARE_READ | FILE_SHARE_WRITE;
const DWORD disposition =
/* Note: O_TRUNC ove