从Native层看java IO文件读写流程(以调用window API为例)

转载自:JavaIO怎么调用WindowsAPI的——从Native层剖析JavaIO文件读写

这里的java版本是1.8.0_131

RandomAccessFile

这个类就是完全模仿C语言的文件读写操作,允许随机读取,想读文件的哪个部分就可以把文件流指针指到哪儿。下面会列一张表将这个类中的常用方法和标准C语言API进行对比,然后再看一下Java在Native层是怎么实现这个类的:

JavaC
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两个接口,所以同时拥有了DataInputStreamDataOutputStream两个类的基本方法。

RandomAccessFile

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 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;

    // 转成Windows路径
    WCHAR *pathbuf = pathToNTPath(env, path, JNI_TRUE);
    if (pathbuf == NULL) {
        /* Exception already pending */
        return -1;
    }
    // 调用windows API
    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);

    if (h == INVALID_HANDLE_VALUE) {
        throwFileNotFoundException(env, path);
        return -1;
    }
    // 最后将文件句柄作为FileDescriptor返回
    return (jlong) h;
}

//
// io_util.md.h文件
#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))

点击这里可以查看CreateFile的API文档:

// 返回文件句柄
HANDLE WINAPI CreateFile(
  // 创建(或打开)的文件名(或设备名)
  _In_     LPCTSTR               lpFileName,
  // 访问属性,这个值通常是GENERIC_READ,GENERIC_WRITE,或者(GENERIC_READ | GENERIC_WRITE).
  _In_     DWORD                 dwDesiredAccess,
  // 文件共享模式: FILE_SHARE_READ,FILE_SHARE_READ,FILE_SHARE_WRITE
  _In_     DWORD                 dwShareMode,
  // SECURITY_ATTRIBUTES结构体指针,该参数可以为NULL
  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  // 文件创建的配置:
  //   CREATE_ALWAYS,总是创建新的文件
  //   CREATE_NEW,不存在就创建新文件
  //   OPEN_ALWAYS,总是打开文件
  //   OPEN_EXISTING,仅当文件存在时打开文件
  //   TRUNCATE_EXISTING,如果文件存在,删除原来的数据
  _In_     DWORD                 dwCreationDisposition,
  // 文件的属性标记:
  // FILE_ATTRIBUTE_ARCHIVE,归档文件
  // FILE_ATTRIBUTE_ENCRYPTED,加密文件
  // FILE_ATTRIBUTE_HIDDEN,隐藏文件
  // FILE_ATTRIBUTE_NORMAL,普通文件
  // FILE_ATTRIBUTE_READONLY,只读文件
  // FILE_ATTRIBUTE_TEMPORARY,临时文件
  // ...
  _In_     DWORD                 dwFlagsAndAttributes,
  // 使用某个文件的属性作为模板
  _In_opt_ HANDLE                hTemplateFile
);

read

// 所有的read方法最终都会辗转调用这两个方法
private native int read0() throws IOException; // 读取一个字节
private native int readBytes(byte b[], int off, int len) throws IOException; // 读取多个字节

Native层代码:

/
// RandomAccessFile.c文件
JNIEXPORT jint JNICALL
Java_java_io_RandomAccessFile_read(JNIEnv *env, jobject this) {
    return readSingle(env, this, raf_fd);
}

JNIEXPORT jint JNICALL
Java_java_io_RandomAccessFile_readBytes(JNIEnv *env,
    jobject this, jbyteArray bytes, jint off, jint len) {
    return readBytes(env, this, bytes, off, len, raf_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;
    }
    // 调用IO_Read
    nread = IO_Read(fd, &ret, 1);
    if (nread == 0) { /* EOF */
        return -1;
    } else if (nread == -1) { /* error */
        JNU_ThrowIOExceptionWithLastError(env, "Read error");
    }
    return ret & 0xFF;
}

/* 缓存大小 */
// 估计这也是为什么BufferedInputStream缓存大小为8192的原因
#define BUF_SIZE 8192

/* 越界判断 */
static int
outOfBounds(JNIEnv *env, jint off, jint len, jbyteArray array) {
    return ((off < 0) ||
            (len < 0) ||
            // We are very careful to avoid signed integer overflow,
            // the result of which is undefined in C.
            ((*env)->GetArrayLength(env, array) - off < len));
}

jint
readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
          jint off, jint len, jfieldID fid)
{
    jint nread;
    // 在栈中申请的默认缓存空间
    char stackBuf[BUF_SIZE];
    char *buf = NULL;
    FD fd;

    if (IS_NULL(bytes)) {
        JNU_ThrowNullPointerException(env, NULL);
        return -1;
    }

    if (outOfBounds(env, off, len, bytes)) {
        JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
        return -1;
    }

    // 根据想要读取的字节数,申请对应大小的缓冲区
    if (len == 0) {
        return 0;
    } else if (len > BUF_SIZE) {
        // 申请缓冲区内存
        buf = malloc(len);
        if (buf == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return 0;
        }
    } else {
        // 如果需要读取的字节数小于默认缓存大小
        // 则使用栈缓存区
        buf = stackBuf;
    }

    // 获取文件句柄
    fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        nread = -1;
    } else {
        // 也是调用IO_Read方法读取文件
        nread = IO_Read(fd, buf, len);
        if (nread > 0) {
            (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);
        } else if (nread == -1) {
            JNU_ThrowIOExceptionWithLastError(env, "Read error");
        } else { /* EOF */
            nread = -1;
        }
    }

    // 释放缓冲区内存
    if (buf != stackBuf) {
        free(buf);
    }
    return nread;
}


// io_util_md.h文件
#define IO_Read handleRead


// io_util_md.c文件
JNIEXPORT
jint
handleRead(FD fd, void *buf, jint len)
{
    DWORD read = 0;
    BOOL result = 0;
    // Windows上FileDescriptor就是Handle文件句柄
    HANDLE h = (HANDLE)fd;
    if (h == INVALID_HANDLE_VALUE) {
        return -1;
    }
    // 调用Windows API
    result = ReadFile(h,          /* File handle to read */
                      buf,        /* address to put data */
                      len,        /* number of bytes to read */
                      &read,      /* number of bytes read */
                      NULL);      /* no overlapped struct */
    if (result == 0) {
        int error = GetLastError();
        if (error == ERROR_BROKEN_PIPE) {
            return 0; /* EOF */
        }
        return -1;
    }
    return (jint)read;
}

点击这里查看ReadFile的API文档

BOOL WINAPI ReadFile(
  // 文件句柄
  _In_        HANDLE       hFile,
  // 接收数据的字节缓冲区
  _Out_       LPVOID       lpBuffer,
  // 读取数据的长度
  _In_        DWORD        nNumberOfBytesToRead,
  // 接受数据读取的长度,当lpOverlapped参数不为NULL,该参数可以为NULL
  _Out_opt_   LPDWORD      lpNumberOfBytesRead,
  // OVERLAPPED结构体指针,用于使用FILE_FLAG_OVERLAPPED标记打开的文件
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);

write

// 所有的write方法最终都会调用这两个方法
private native void write0(int b) throws IOException; // 写入一个字节
private native void writeBytes(byte b[], int off, int len) throws IOException; // 写入多个字节

Native层源码:

//
// RandomAccessFile.c文件
JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_write(JNIEnv *env, jobject this, jint byte) {
    writeSingle(env, this, byte, JNI_FALSE, raf_fd);
}

JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_writeBytes(JNIEnv *env,
    jobject this, jbyteArray bytes, jint off, jint len) {
    writeBytes(env, this, bytes, off, len, JNI_FALSE, raf_fd);
}

//
// io_util.c文件
void
writeSingle(JNIEnv *env, jobject this, jint byte, jboolean append, jfieldID fid) {
    // Discard the 24 high-order bits of byte. See OutputStream#write(int)
    // 在OutputStream.write(int)方法中已经通过位运算处理了int的高24位字节
    // 所以这里可以直接强转
    char c = (char) byte;
    jint n;
    // 获取文件句柄(Windows)
    FD fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        return;
    }
    if (append == JNI_TRUE) {
        // 向后追加
        n = IO_Append(fd, &c, 1);
    } else {
        // 覆盖写入
        n = IO_Write(fd, &c, 1);
    }
    if (n == -1) {
        JNU_ThrowIOExceptionWithLastError(env, "Write error");
    }
}


void
writeBytes(JNIEnv *env, jobject this, jbyteArray bytes,
           jint off, jint len, jboolean append, jfieldID fid)
{
    jint n;
    // 栈内存缓冲区
    char stackBuf[BUF_SIZE];
    char *buf = NULL;
    FD fd;

    if (IS_NULL(bytes)) {
        JNU_ThrowNullPointerException(env, NULL);
        return;
    }

    if (outOfBounds(env, off, len, bytes)) {
        JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
        return;
    }

    if (len == 0) {
        return;
    } else if (len > BUF_SIZE) {
        buf = malloc(len);
        if (buf == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return;
        }
    } else {
        buf = stackBuf;
    }

    // 将Java层字节数组,转成Native层字节数组
    // Java中数组是一个对象,对象头部有部分其他信息(比如数组的长度)
    // 不像C语言的字节数组那么单纯
    (*env)->GetByteArrayRegion(env, bytes, off, len, (jbyte *)buf);

    if (!(*env)->ExceptionOccurred(env)) {
        off = 0;
        // 这里要循环,IO_Append,IO_Write底层调用的WriteFile API
        // 不是让写多少就写多少
        while (len > 0) {
            fd = GET_FD(this, fid);
            if (fd == -1) {
                JNU_ThrowIOException(env, "Stream Closed");
                break;
            }
            if (append == JNI_TRUE) {
                // 向后追加
                n = IO_Append(fd, buf+off, len);
            } else {
                // 覆盖写入
                n = IO_Write(fd, buf+off, len);
            }
            if (n == -1) {
                JNU_ThrowIOExceptionWithLastError(env, "Write error");
                break;
            }
            // 已写入n个字节
            off += n;
            len -= n;
        }
    }
    // 释放内存
    if (buf != stackBuf) {
        free(buf);
    }
}


// io_util_md.h
#define IO_Append handleAppend
#define IO_Write handleWrite


// io_util_md.c
jint handleWrite(FD fd, const void *buf, jint len) {
    return writeInternal(fd, buf, len, JNI_FALSE);
}

jint handleAppend(FD fd, const void *buf, jint len) {
    return writeInternal(fd, buf, len, JNI_TRUE);
}

static jint writeInternal(FD fd, const void *buf, jint len, jboolean append)
{
    BOOL result = 0;
    DWORD written = 0;
    HANDLE h = (HANDLE)fd;// Windows中文件句柄就是FileDescriptor
    if (h != INVALID_HANDLE_VALUE) {
        OVERLAPPED ov;
        LPOVERLAPPED lpOv;
        if (append == JNI_TRUE) {
            // 构造OVERLAPPED结构体
            ov.Offset = (DWORD)0xFFFFFFFF;
            ov.OffsetHigh = (DWORD)0xFFFFFFFF;
            ov.hEvent = NULL;
            lpOv = &ov;
        } else {
            lpOv = NULL;
        }
        // 调用Windows API
        result = WriteFile(h,                /* File handle to write */
                           buf,              /* pointers to the buffers */
                           len,              /* number of bytes to write */
                           &written,         /* receives number of bytes written */
                           lpOv);            /* overlapped struct */
    }
    if ((h == INVALID_HANDLE_VALUE) || (result == 0)) {
        return -1;
    }
    return (jint)written;
}

点击这里查看WriteFile的API文档

BOOL WINAPI WriteFile(
  // 文件句柄
  _In_        HANDLE       hFile,
  // 要写入的字节数据
  _In_        LPCVOID      lpBuffer,
  // 要写入多少数据
  _In_        DWORD        nNumberOfBytesToWrite,
  // 实际写入了多少数据,作为返回值
  _Out_opt_   LPDWORD      lpNumberOfBytesWritten,
  // OVERLAPPED结构体指针,对FILE_FLAG_OVERLAPPED方式打开的文件有效
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);

seek

private native void seek0(long pos) throws IOException;

Native层源码:

/
// RandomAccessFile.c文件
JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_seek0(JNIEnv *env,
                    jobject this, jlong pos) {

    FD fd;

    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) {
        // 调用IO_Lseek函数
        JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
    }
}

///
// io_util_md.h文件
#define IO_Lseek handleLseek

///
// io_util_md.c文件
jlong
handleLseek(FD 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;
    // 调用SetFilePointerEx API
    if (SetFilePointerEx(h, distance, &pos, op) == 0) {
        return -1;
    }
    // 返回移动后的位置
    return long_to_jlong(pos.QuadPart);
}

点击这里查看SetFilePointEx的API文档

BOOL WINAPI SetFilePointerEx(
  // 文件句柄
  _In_      HANDLE         hFile,
  // 要移动的距离
  _In_      LARGE_INTEGER  liDistanceToMove,
  // 移动后的文件指针位置
  _Out_opt_ PLARGE_INTEGER lpNewFilePointer,
  // 移动的相对起点:
  // FILE_BEGIN 文件开头
  // FILE_END 文件结尾
  // FILE_CURRENT 当前位置
  _In_      DWORD          dwMoveMethod
);

getFilePointer

public native long getFilePointer() throws IOException;

Native层源码:

JNIEXPORT jlong JNICALL
Java_java_io_RandomAccessFile_getFilePointer(JNIEnv *env, jobject this) {
    FD fd;
    jlong ret;

    fd = GET_FD(this, raf_fd);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        return -1;
    }
    // 仍然是调用IO_Lseek方法,但是最后参数(相对位置),设置为当前位置
    if ((ret = IO_Lseek(fd, 0L, SEEK_CUR)) == -1) {
        JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
    }
    return ret;
}

RandomAccessFile类最终调用的是Windows的四个API:OpenFile,ReadFile,WriteFile,GetFilePointer

FileInputStream和FileOutputStream

FileInputStream和FileOutputStream与C++的STL中的文件流API类似:面向对象,RandomAccessFile仅仅以面向对象方式封装了文件读写。Java的文件流功能上肯定不如C++,要知道C++的运算符重载,模板类等语言特性让C++的文件操作简单了很多(相反调试也变得更加困难)。

C++中std::basic_ifstream代表了文件输入流,std::basic_ofstream代表了文件输出流,又因为C++有多继承的特性所以还有一个std::basic_fstream融合了前两者的功能(实际上并非继承自前两个类而是继承自basic_iostream类)。

C++中的IO流

不过C++虽好,但Java的native实现仍使用C语言(实际上Java API几乎都是C语言实现,而JVM的实现Hotspot才使用到了C++)。

事实上FileInputStream和FileOutputStream的实现和RandomAccessFile几近一致:

FileInputStream的native方法

private native void open0(String name) throws FileNotFoundException;
private native int read0() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
public native long skip(long n) throws IOException;
public native int available() throws IOException;

Native层源码:

JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open(JNIEnv *env, jobject this, jstring path) {
    // fileOpen,这和RandomAccessFile打开文件调用同一个函数
    fileOpen(env, this, path, fis_fd, O_RDONLY);
}

JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_read(JNIEnv *env, jobject this) {
    // readSingle,读取单个字节
    return readSingle(env, this, fis_fd);
}

JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
        jbyteArray bytes, jint off, jint len) {
    // readBytes,读取多个字节
    return readBytes(env, this, bytes, off, len, fis_fd);
}

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;
    }
    // IO_Lseek函数,和RandomAccessFile的seek方法实现类似
    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);
}

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;
    }
    // 唯一出了点新花样的可能就是这个IO_Available函数
    if (IO_Available(fd, &ret)) {
        if (ret > INT_MAX) {
            ret = (jlong) INT_MAX;
        } else if (ret < 0) {
            ret = 0;
        }
        return jlong_to_jint(ret);
    }
    JNU_ThrowIOExceptionWithLastError(env, NULL);
    return 0;
}

///
// io_util_md.h文件
#define IO_Available handleAvailable

int
handleAvailable(FD fd, jlong *pbytes) {
    HANDLE h = (HANDLE)fd;
    DWORD type = 0;

    // 获取文件类型,调用Windows API
    type = GetFileType(h);
    /* Handle is for keyboard or pipe */
    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); /* keyboard */
        } else {
            ret = handleNonSeekAvailable(fd, &lpbytes); /* pipe */
        }
        (*pbytes) = (jlong)(lpbytes);
        return ret;
    }
    /* Handle is for regular file */
    if (type == FILE_TYPE_DISK) {
        jlong current, end;

        LARGE_INTEGER filesize;
        current = handleLseek(fd, 0, SEEK_CUR);
        if (current < 0) {
            return FALSE;
        }
        // 调用Windows API获取文件大小
        if (GetFileSizeEx(h, &filesize) == 0) {
            return FALSE;
        }
        end = long_to_jlong(filesize.QuadPart);
        *pbytes = end - current;
        return TRUE;
    }
    return FALSE;
}

查看GetFileTypeGetFileSizeEx的API文档

// FILE_TYPE_CHAR  字符文件,典型的如:打印设备或控制台
// FILE_TYPE_DISK  磁盘文件
// FILE_TYPE_PIPE  管道文件,如Socket,命名管道,匿名管道
// FILE_TYPE_REMOTE 未使用
// FILE_TYPE_UNKNOWN  未知设备,或者函数调用出错
DWORD WINAPI GetFileType(
  // 文件句柄
  _In_ HANDLE hFile
);

BOOL WINAPI GetFileSizeEx(
  // 文件句柄
  _In_  HANDLE         hFile,
  // 接收文件大小的长整型指针
  _Out_ PLARGE_INTEGER lpFileSize
);

FileOutputStream的native方法

FileOutputStream的native实现就更简单了

private native void open0(String name, boolean append) throws FileNotFoundException;
private native void write(int b, boolean append) throws IOException;
private native void writeBytes(byte b[], int off, int len, boolean append) throws IOException;

Native源码:

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this,
                                   jstring path, jboolean append) {
    // fileOpen:打开文件
    fileOpen(env, this, path, fos_fd,
             O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC));
}
JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_write(JNIEnv *env, jobject this, jint byte, jboolean append) {
    // writeSingle:写入单个字节
    writeSingle(env, this, byte, append, fos_fd);
}

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_writeBytes(JNIEnv *env,
    jobject this, jbyteArray bytes, jint off, jint len, jboolean append)
{
    // writeBytes:写入多个字节
    writeBytes(env, this, bytes, off, len, append, fos_fd);
}

===========================

转载自:左潇龙 JNI探秘-----FileInputStream的read方法详解

   上一章我们已经分析过FileInputStream的构建过程,接下来我们就来看一下read方法的读取过程。

             我们先来看下FileInputStream中的四个有关read的方法的源码,如下。

[java]  view plain  copy
  1.    public native int read() throws IOException;  
  2.   
  3.    private native int readBytes(byte b[], int off, int len) throws IOException;  
  4.   
  5.    public int read(byte b[]) throws IOException {  
  6. return readBytes(b, 0, b.length);  
  7.    }  
  8.   
  9.    public int read(byte b[], int off, int len) throws IOException {  
  10. return readBytes(b, off, len);  
  11.    }  
             可以看到,其中有两个本地方法,两个不是本地方法,但是还是内部还是调用的本地方法,那么我们研究的重点就是这两个本地方法到底是如何实现的。

             下面是这两个本地方法的源码,非常简单,各位请看。

[cpp]  view plain  copy
  1. JNIEXPORT jint JNICALL  
  2. Java_java_io_FileInputStream_read(JNIEnv *env, jobject this) {  
  3.     return readSingle(env, this, fis_fd);//每一个本地的实例方法默认的两个参数,JNI环境与对象的实例  
  4. }  
  5.   
  6. JNIEXPORT jint JNICALL  
  7. Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,  
  8.         jbyteArray bytes, jint off, jint len) {//除了前两个参数,后三个就是readBytes方法传递进来的,字节数组、起始位置、长度三个参数  
  9.     return readBytes(env, this, bytes, off, len, fis_fd);  
  10. }  
             可以看到,这两个本地方法的实现只是将任务又转给了两个方法,readSingle和ReadBytes,请注意,在调用这两个方法时,除了常用的env和this对象,以及从JAVA环境传过来的参数之外,还多了一个参数fis_fd,这个对象就是上一章中FileInputStream类中的fd属性的内存地址偏移量了。

             那么下面我们首先来看readSingle方法的实现,如下。

[cpp]  view plain  copy
  1. /* 
  2.     env和this参数就不再解释了 
  3.     fid就是FileInputStream类中fd属性的内存地址偏移量 
  4.     通过fid和this实例可以获取FileInputStream类中fd属性的内存地址 
  5. */  
  6. jint  
  7. readSingle(JNIEnv *env, jobject this, jfieldID fid) {  
  8.     jint nread;//存储读取后返回的结果值  
  9.     char ret;//存储读取出来的字符  
  10.     FD fd = GET_FD(this, fid);//这个获取到的FD其实就是之前handle属性的值,也就是文件的句柄  
  11.     if (fd == -1) {  
  12.         JNU_ThrowIOException(env, "Stream Closed");  
  13.         return -1;//如果文件句柄等于-1,说明文件流已关闭  
  14.     }  
  15.     nread = (jint)IO_Read(fd, &ret, 1);//读取一个字符,并且赋给ret变量  
  16.     //以下根据返回的int值判断读取的结果  
  17.     if (nread == 0) { /* EOF */  
  18.         return -1;//代表流已到末尾,返回-1  
  19.     } else if (nread == JVM_IO_ERR) { /* error */  
  20.         JNU_ThrowIOExceptionWithLastError(env, "Read error");//IO错误  
  21.     } else if (nread == JVM_IO_INTR) {  
  22.         JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);//被打断  
  23.     }  
  24.     return ret & 0xFF;//与0xFF做按位的与运算,去除高于8位bit的位  
  25. }  
         可以看到,这个方法其实最关键的就是IO_Read这个宏定义的处理,而IO_Read其实只是代表了一个方法名称叫handleRead,我们去看一下handleRead的源码。

[cpp]  view plain  copy
  1. /* 
  2.     fd就是handle属性的值 
  3.     buf是收取读取内容的数组 
  4.     len是读取的长度,可以看到,这个参数传进来的是1 
  5.     函数返回的值代表的是实际读取的字符长度 
  6. */  
  7. JNIEXPORT  
  8. size_t  
  9. handleRead(jlong fd, void *buf, jint len)  
  10. {  
  11.     DWORD read = 0;  
  12.     BOOL result = 0;  
  13.     HANDLE h = (HANDLE)fd;  
  14.     if (h == INVALID_HANDLE_VALUE) {//如果句柄是无效的,则返回-1  
  15.         return -1;  
  16.     }  
  17.     //这里ReadFile又是一个现有的函数,和上一章的CreateFile是一样的  
  18.     //都是WIN API的函数,可以百度搜索它的作用与参数详解,理解它并不难  
  19.     result = ReadFile(h,          /* File handle to read */  //文件句柄  
  20.                       buf,        /* address to put data */  //存放数据的地址  
  21.                       len,        /* number of bytes to read */  //要读取的长度  
  22.                       &read,      /* number of bytes read */  //实际读取的长度  
  23.                       NULL);      /* no overlapped struct */  //只有对文件进行重叠操作时才需要传值  
  24.     if (result == 0) {//如果没读取出来东西,则判断是到了文件末尾返回0,还是报错了返回-1  
  25.         int error = GetLastError();  
  26.         if (error == ERROR_BROKEN_PIPE) {  
  27.             return 0; /* EOF */  
  28.         }  
  29.         return -1;  
  30.     }  
  31.     return read;  
  32. }  
         到此,基本上就完全看完了无参数的read方法的源码,它的原理其实很简单,就是利用handle这个句柄,使用ReadFile的WIN API函数读取了一个字符,不过值得注意的是,这些都是windows系统下的实现方式,所以不可认为这些源码代表了所有系统下的情况。

         然而对于带有参数的read方法,其原理与无参read方法是一样的,而且最终也是调用的handleRead这个方法,只是读取的长度不再是1而已。


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值