一、序言
IO操作,才程序中比较普遍,JAVA 中提出了IO/NIO 的概念,也一直在说NIO 比IO快,一直不知道原因,就想memcache 和ehcache 比较优劣一样,这些东西得自己看看如何实现的,才 知道区别,从而才知道优劣以及试用范围,而不仅仅是“听说”!这里我可以先了解下JAVA 如何操作IO的。
二、代码示例
我们先看看简单文件操作:
// 这是将文件转换成输入流的的一种方式,获得了流我们就能干很多事
FileInputStream in = new FileInputStream(new File("...file"));
再看看FileInputStream 的源码:
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
// 安全管理器,这里暂时不说
SecurityManager security = System.getSecurityManager();
if (security != null) {
// 检查是否可以按这名字读取
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
// 获得文件描述,这个JDK 未公布
fd = new FileDescriptor();
// 打开文件,这是个native 方法,我们可以用openjdk 看看里面
open(name);
}
在FileInputStream.c下可以找到方法
// open 方法
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open(JNIEnv *env, jobject this, jstring path) {
fileOpen(env, this, path, fis_fd, O_RDONLY);
}
OK。我们从该文件上面的引用io_util_md.c找到实现:
void
fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags)
{
jlong h = winFileHandleOpen(env, path, flags);
if (h >= 0) {
// 设置fd 的值
SET_FD(this, h, fid);
}
}
在上面文件里面可以看到:winFileHandleOpen,
jlong
winFileHandleOpen(JNIEnv *env, jstring path, int flags)
{
const DWORD access =
(flags & O_RDWR) ? (GENERIC_WRITE | GENERIC_READ) :
(flags & O_WRONLY) ? GENERIC_WRITE :
GENERIC_READ;
const DWORD sharing =
FILE_SHARE_READ | FILE_SHARE_WRITE;
const DWORD disposition =
/* Note: O_TRUNC overrides O_CREAT */
(flags & O_TRUNC) ? CREATE_ALWAYS :
(flags & O_CREAT) ? OPEN_ALWAYS :
OPEN_EXISTING;
const DWORD maybeWriteThrough =
(flags & (O_SYNC | O_DSYNC)) ?
FILE_FLAG_WRITE_THROUGH :
FILE_ATTRIBUTE_NORMAL;
const DWORD maybeDeleteOnClose =
(flags & O_TEMPORARY) ?
FILE_FLAG_DELETE_ON_CLOSE :
FILE_ATTRIBUTE_NORMAL;
const DWORD flagsAndAttributes = maybeWriteThrough | maybeDeleteOnClose;
HANDLE h = NULL;
if (onNT) {
WCHAR *pathbuf = pathToNTPath(env, path, JNI_TRUE);
if (pathbuf == NULL) {
/* Exception already pending */
return -1;
}
h = CreateFileW(
pathbuf, /* Wide char path name */
access, /* Read and/or write permission */
sharing, /* File sharing flags */
NULL, /* Security attributes */
disposition, /* creation disposition */
flagsAndAttributes, /* flags and attributes */
NULL);
free(pathbuf);
} else {
WITH_PLATFORM_STRING(env, path, _ps) {
h = CreateFile(_ps, access, sharing, NULL, disposition,
flagsAndAttributes, NULL);
} END_PLATFORM_STRING(env, _ps);
}
if (h == INVALID_HANDLE_VALUE) {
int error = GetLastError();
if (error == ERROR_TOO_MANY_OPEN_FILES) {
JNU_ThrowByName(env, JNU_JAVAIOPKG "IOException",
"Too many open files");
return -1;
}
throwFileNotFoundException(env, path);
return -1;
}
return (jlong) h;
}
好吧,上面代码我也搞不明白- -,但是这几句代码:
h = CreateFileW(
pathbuf, /* Wide char path name */
access, /* Read and/or write permission */
sharing, /* File sharing flags */
NULL, /* Security attributes */
disposition, /* creation disposition */
flagsAndAttributes, /* flags and attributes */
NULL);
以及
WITH_PLATFORM_STRING(env, path, _ps) {
h = CreateFile(_ps, access, sharing, NULL, disposition,
flagsAndAttributes, NULL);
我的理解是,这里通过CreateFileW方法,创建了一个类似文件描述的结构,然后通过CreateFile调windows 下面的底层方法,填充这个结构的数据,那么这个文件对象就能被我们上层对象识别了,就能是获取里面的资源。
OK,我们现在创建了对文件之间的连接,拿到文件流对象之后,来看看我们常用的read 方法。
// 对字节的操作
public native int read() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
在FileInputStream.c 里面同样可以找到
JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_read(JNIEnv *env, jobject this) {
return readSingle(env, this, fis_fd);
}
JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
jbyteArray bytes, jint off, jint len) {
return readBytes(env, this, bytes, off, len, fis_fd);
}
继续看io_util.c 里面:
jint
readSingle(JNIEnv *env, jobject this, jfieldID fid) {
jint nread;
char ret;
FD fd = GET_FD(this, fid);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return -1;
}
// 看出是一个一个的读取,fd 表示刚才文件描述的一种结构
nread = IO_Read(fd, &ret, 1);
if (nread == 0) { /* EOF */
return -1;
} else if (nread == JVM_IO_ERR) { /* error */
JNU_ThrowIOExceptionWithLastError(env, "Read error");
} else if (nread == JVM_IO_INTR) {
JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);
}
return ret & 0xFF;
}
关于IO_Read 的东西,在io_util_md.h 有定义:
/*
* HPI是一个与主机通信的并行接口
* Route the routines through HPI
*/
#define IO_Write JVM_Write
#define IO_Sync JVM_Sync
#define IO_Read JVM_Read
#define IO_Lseek JVM_Lseek
#define IO_Available JVM_Available
#define IO_SetLength JVM_SetLength
关于JVM_Read 我在jvm.h 里面看到
/*
// 从文件里面读取 a char array
* Read data from a file decriptor into a char array.
* // 文件的来源
* fd the file descriptor to read from.
// 读出来的存放位置
* buf the buffer where to put the read data.
// 读的字节数
* nbytes the number of bytes to read.
*
* This function returns -1 on error, and 0 on success.
*/
JNIEXPORT jint JNICALL
JVM_Read(jint fd, char *buf, jint nbytes);
然后在jvm.cpp 里面找到
关于这段代码,大神告诉我是宏定义,关于C和C++ 的东西,我已经无力回天啦。
当然我们知道了介绍,可以理解这里会让调系统的read 方法。
这可以参考:“操作系统read 的原理实现”,google 一下很多,这里就不解释了
jvm.cpp 里面有很多关于IO这块的,可以去看看,但是都是 宏定义。。
关于linux 下的这些代码,还有涉及一下阻塞等东西,以后在去研究一下操作系统的东西吧。
JVM_LEAF(jint, JVM_Read(jint fd, char *buf, jint nbytes))
JVMWrapper2("JVM_Read (0x%x)", fd);
//%note jvm_r6
return (jint)os::restartable_read(fd, buf, nbytes);
JVM_END
小结:
1.本想了解下底层怎么操作的,但是大概了解下了,最终到read 或者write 的时候,一些基础知识不够,还是不能透彻,但是也足够我们大致了解了。
2.按上面的思路可以看出大概的思路,虽然没写write 的过程,但是最终都是通过流,或者说字符数组在内存里面的一些操作进行,包括我们用装饰器模式搞了很多其他流,基本原理一样,仅仅为了方便加了额外的功能。
3. 有不对的地方还请指出,仅仅是个人学习,分享作用