1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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(); } if (file.isInvalid()) { throw new FileNotFoundException("Invalid file path" ); } fd = new FileDescriptor(); fd.incrementAndGetUseCount(); this .path = name; open(name); } private native void open (String name) throws FileNotFoundException
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 JNIEXPORT void JNICALL Java_java_io_FileInputStream_open(JNIEnv *env, jobject this , jstring path) { fileOpen(env, this , path, fis_fd, O_RDONLY); } void fileOpen(JNIEnv *env, jobject this , jstring path, jfieldID fid, int flags) { jlong h = winFileHandleOpen(env, path, flags); if (h >= 0 ) { SET_FD(this , h, fid); } } jlong winFileHandleOpen(JNIEnv *env, jstring path, int flags) { 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 = (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 ) { return -1 ; } h = CreateFileW( pathbuf, access, sharing, NULL , disposition, flagsAndAttributes, 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; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #define FD jlong #define SET_FD(this, fd, fid) \ if ((*env)->GetObjectField(env, (this ), (fid)) != NULL ) \ (*env)->SetLongField(env, (*env)->GetObjectField(env, (this ), (fid)), IO_handle_fdID, (fd)) #define GET_FD(this, fid) \ ((*env)->GetObjectField(env, (this ), (fid)) == NULL ) ? \ -1 : (*env)->GetLongField(env, (*env)->GetObjectField(env, (this ), (fid)), IO_handle_fdID) void fileOpen (JNIEnv *env, jobject this , jstring path, jfieldID fid, int flags) { jlong h = winFileHandleOpen(env, path, flags); if (h >= 0 ) { if ((*env)->GetObjectField(env, (this ), (fid)) != NULL ) (*env)->SetLongField(env, (*env)->GetObjectField(env, (this ), (fid)), IO_handle_fdID, (h)) } }
FileInputStream 类加载的会执行下面的代码:
1 2 3 4 5 6 7 8 9 private final FileDescriptor fd;private static native void initIDs () ;static { initIDs(); }
initIDs的实现:
1 2 3 4 5 6 7 8 9 10 11 jfieldID fis_fd; JNIEXPORT void JNICALL Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) { fis_fd = (*env)->GetFieldID(env, fdClass, "fd" , "Ljava/io/FileDescriptor;" ); }
initIDs 在 FileInputStream 被加载的时候,将其字段 fd 的 FieldID进行初始化,存储到全局变量 fis_fd 中。
同样的得到,fd 对象之后要操作,其字段:1 2 3 4 5 6 7 8 9 10 11 private int fd;private long handle;private static native void initIDs () ;static { initIDs(); }
对应的 native 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 jfieldID IO_fd_fdID; jfieldID IO_handle_fdID; JNIEXPORT void JNICALL Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) { IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd" , "I" ); IO_handle_fdID = (*env)->GetFieldID(env, fdClass, "handle" , "J" ); }
打开文件的流程总结 1 FileInputStream fis = new FileInputStream("1.txt" );
这个调用的过程如下:
创建 FileDescriptor 对象
每一个 FileInputStream 有一个 FileDescriptor,代表这个流底层的文件的handle
调用 native 方法 open, 打开文件
内部调用 CreateFile 打开文件,返回文件句柄 handle
初始化 FileDescriptor 对象
将 文件句柄 handle 设置到,FileDescriptor 对象的 handle 中
read 的实现 其实现在 FileInputStream.c 和
src/windows/native/java/io/Io_util_md.h 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 JNIEXPORT jint JNICALL Java_java_io_FileInputStream_read(JNIEnv *env, jobject this ) { return readSingle(env, this , fis_fd); } 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 ; } nread = (jint)IO_Read(fd, &ret, 1 ); if (nread == 0 ) { return -1 ; } else if (nread == JVM_IO_ERR) { JNU_ThrowIOExceptionWithLastError(env, "Read error" ); } else if (nread == JVM_IO_INTR) { JNU_ThrowByName(env, "java/io/InterruptedIOException" , NULL ); } return ret & 0xFF ; } #define IO_Append handleAppend #define IO_Write handleWrite #define IO_Sync handleSync #define IO_Read handleRead #define IO_Lseek handleLseek #define IO_Available handleAvailable #define IO_SetLength handleSetLength JNIEXPORT size_t handleRead(jlong fd, void *buf, jint len) { DWORD read = 0 ; BOOL result = 0 ; HANDLE h = (HANDLE)fd; if (h == INVALID_HANDLE_VALUE) { return -1 ; } result = ReadFile(h, buf, len, &read, NULL ); if (result == 0 ) { int error = GetLastError(); if (error == ERROR_BROKEN_PIPE) { return 0 ; } return -1 ; } return read; }
ReadFile win32 api 用来读取文件。
文件的读取模型 在windows 和 linux 中都提供了读取文件的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 BOOL WINAPI ReadFile ( _In_ HANDLE hFile, _Out_ LPVOID lpBuffer, _In_ DWORD nNumberOfBytesToRead, _Out_opt_ LPDWORD lpNumberOfBytesRead, _Inout_opt_ LPOVERLAPPED lpOverlapped ) ;DWORD WINAPI SetFilePointer ( _In_ HANDLE hFile, _In_ LONG lDistanceToMove, _Inout_opt_ PLONG lpDistanceToMoveHigh, _In_ DWORD dwMoveMethod ) ;ssize_t read(int fd, void *buf, size_t count);off_t lseek(int fildes, off_t offset, int whence);
对于这两个API,都没有提供读取的位置,那么当第一次调用的时候,应该从文件的什么位置去读取数据呢?
open & read & lseek:
The file offset is set to the beginning of the file
On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.
ReadFile & SetFilePointer
If hFile is opened with FILE_FLAG_OVERLAPPED, it is an asynchronous file handle; otherwise it is synchronous.
File pointer position for a synchronous handle is maintained by the system as data is read or written and can also be updated using the SetFilePointer or SetFilePointerEx function.
Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device.
If lpOverlapped is NULL, the read operation starts at the current file position and ReadFile does not return until the operation is complete, and the system updates the file pointer before ReadFile returns.
When an application calls CreateFile to open a file for the first time, Windows places the file pointer at the beginning of the file. As bytes are read from or written to the file, Windows advances the file pointer the number of bytes read or written.
File Pointers .aspx)
Positioning a File Pointer .aspx)
msdn: System Services > File Services > File Systems > File System Components > Files and Clusters
可以看到当使用 CreateFile 和 open 打开一个文件的时候,系统会将文件的 offset 放置到文件的开头。offset = 0, 后续的 ReadFile 和 read 的时候,offset 会被移动到下一次读取的文件的位置。所以,读取的位置 offset 是由系统来维护的。
但是,可以使用 SetFilePointer 和 lseek 来设置文件的 offset. 然后,下次的读取将使用新的 offset 来,读取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 JNIEXPORT void JNICALL Java_java_io_FileInputStream_close0(JNIEnv *env, jobject this ) { handleClose(env, this , fis_fd); } jint handleClose (JNIEnv *env, jobject this , jfieldID fid) { FD fd = GET_FD(this , fid); HANDLE h = (HANDLE)fd; if (h == INVALID_HANDLE_VALUE) { return 0 ; } SET_FD(this , -1 , fid); if (CloseHandle(h) == 0 ) { JNU_ThrowIOExceptionWithLastError(env, "close failed" ); } return 0 ; }
skip & available skip 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 JNIEXPORT jlong JNICALL Java_java_io_FileInputStream_skip(JNIEnv *env, jobject this , jlong toSkip) { jlong cur = jlong_zero; jlong end = jlong_zero; FD fd = GET_FD(this , fis_fd); if (fd == -1 ) { JNU_ThrowIOException (env, "Stream Closed" ); return 0 ; } if ((cur = IO_Lseek(fd, (jlong)0 , (jint)SEEK_CUR)) == -1 ) { JNU_ThrowIOExceptionWithLastError(env, "Seek error" ); } else if ((end = IO_Lseek(fd, toSkip, (jint)SEEK_CUR)) == -1 ) { JNU_ThrowIOExceptionWithLastError(env, "Seek error" ); } return (end - cur); } #define IO_Lseek handleLseek jlong handleLseek(jlong fd, jlong offset, jint whence) { LARGE_INTEGER pos, distance; DWORD lowPos = 0 ; long highPos = 0 ; DWORD op = FILE_CURRENT; HANDLE h = (HANDLE)fd; if (whence == SEEK_END) { op = FILE_END; } if (whence == SEEK_CUR) { op = FILE_CURRENT; } if (whence == SEEK_SET) { op = FILE_BEGIN; } distance.QuadPart = offset; if (SetFilePointerEx(h, distance, &pos, op) == 0 ) { return -1 ; } return long_to_jlong(pos.QuadPart); } BOOL SetFilePointerEx ( HANDLE hFile, LARGE_INTEGER liDistanceToMove, PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod ) ;
SetFilePointerEx win32 API:
The SetFilePointerEx function moves the file pointer of the specified file.
available 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 JNIEXPORT jint JNICALL Java_java_io_FileInputStream_available(JNIEnv *env, jobject this ) { jlong ret; FD fd = GET_FD(this , fis_fd); if (fd == -1 ) { JNU_ThrowIOException (env, "Stream Closed" ); return 0 ; } if (IO_Available(fd, &ret)) { if (ret > INT_MAX) { ret = (jlong) INT_MAX; } return jlong_to_jint(ret); } JNU_ThrowIOExceptionWithLastError(env, NULL ); return 0 ; } #define IO_Available handleAvailable int handleAvailable(jlong fd, jlong *pbytes) { HANDLE h = (HANDLE)fd; DWORD type = 0 ; type = GetFileType(h); if (type == FILE_TYPE_CHAR || type == FILE_TYPE_PIPE) { int ret; long lpbytes; HANDLE stdInHandle = GetStdHandle(STD_INPUT_HANDLE); if (stdInHandle == h) { ret = handleStdinAvailable(fd, &lpbytes); } else { ret = handleNonSeekAvailable(fd, &lpbytes); } (*pbytes) = (jlong)(lpbytes); return ret; } if (type == FILE_TYPE_DISK) { jlong current, end; LARGE_INTEGER filesize; current = handleLseek(fd, 0 , SEEK_CUR); if (current < 0 ) { return FALSE; } if (GetFileSizeEx(h, &filesize) == 0 ) { return FALSE; } end = long_to_jlong(filesize.QuadPart); *pbytes = end - current; return TRUE; } return FALSE; }
GetFileType
1 2 3 DWORD GetFileType ( HANDLE hFile ) ;
The GetFileType function retrieves the file type of the specified file.
FILE_TYPE_DISK: The specified file is a disk file.
GetFileSizeEx
1 2 3 4 BOOL GetFileSizeEx ( HANDLE hFile, PLARGE_INTEGER lpFileSize ) ;
The GetFileSizeEx function retrieves the size of the specified file.
FileOutputStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 void writeSingle(JNIEnv *env, jobject this , jint byte, jboolean append, jfieldID fid) { char c = (char ) byte; jint n; FD fd = GET_FD(this , fid); if (fd == -1 ) { JNU_ThrowIOException(env, "Stream Closed" ); return ; } if (append == JNI_TRUE) { n = (jint)IO_Append(fd, &c, 1 ); } else { n = (jint)IO_Write(fd, &c, 1 ); } if (n == JVM_IO_ERR) { JNU_ThrowIOExceptionWithLastError(env, "Write error" ); } else if (n == JVM_IO_INTR) { JNU_ThrowByName(env, "java/io/InterruptedIOException" , NULL ); } } #define IO_Append handleAppend #define IO_Write handleWrite JNIEXPORT size_t handleWrite(jlong fd, const void *buf, jint len) { return writeInternal(fd, buf, len, JNI_FALSE); } JNIEXPORT size_t handleAppend(jlong fd, const void *buf, jint len) { return writeInternal(fd, buf, len, JNI_TRUE); } static size_t writeInternal (jlong fd, const void *buf, jint len, jboolean append) { BOOL result = 0 ; DWORD written = 0 ; HANDLE h = (HANDLE)fd; if (h != INVALID_HANDLE_VALUE) { OVERLAPPED ov; LPOVERLAPPED lpOv; if (append == JNI_TRUE) { ov.Offset = (DWORD)0xFFFFFFFF ; ov.OffsetHigh = (DWORD)0xFFFFFFFF ; ov.hEvent = NULL ; lpOv = &ov; } else { lpOv = NULL ; } result = WriteFile(h, buf, len, &written, lpOv); } if ((h == INVALID_HANDLE_VALUE) || (result == 0 )) { return -1 ; } return (size_t )written; }
在 win32 平台下调用 WriteFile 来写入文件。
1 2 3 4 5 6 7 BOOL WriteFile ( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ) ;
If hFile was not opened with FILE_FLAG_OVERLAPPED and lpOverlapped is NULL, the write operation starts at the current file position and WriteFile does not return until the operation has been completed. The system updates the file pointer upon completion.
If hFile was not opened with FILE_FLAG_OVERLAPPED and lpOverlapped is not NULL, the write operation starts at the offset specified in the OVERLAPPED structure and WriteFile does not return until the write operation has been completed. The system updates the file pointer upon completion.
文件在 open 的时候,并没有使用 FILE_FLAG_OVERLAPPED 标志,所以在调用 WriteFile 的时候,遵循上面的行为。同时,由上面的描述可知,这种写入是 block 的,直到操作完成,才返回。同时,系统也会维护文件指针,使其指向下次写入的位置。
FileOutputStream 的构造函数,提供一个 append 参数,用来表示,文件的写入是从头开始(相当于覆盖),还是从文件的最后 apped 数据。这个参数,默认是 false, 也就是文件默认是覆盖写入的。
1 FileOutputStream(String name, boolean append)