1.概述
FileInputStream即文件输入流,FileOutputStream即文件输出流,内部采用二进制字节数组的数据传输形式,实现本地文件的读写。这俩个对象是java.io包下最基本的对象,java大量采用装饰者模式、适配器模式对这俩个类的功能进行增强和扩展,衍生出了其他众多字节流、字符流对象,因此了解这俩对象对学习IO流非常重要。
2.1 构造器
FileInputStream对象提供了3个构造方法,String参数就不提了,还剩下File和FileDescriptor参数构造器,最终都是调用操作系统函数拿到一个文件描述符:
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
| 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.attach(this);
path = name;
open(name); }
public FileInputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager(); if (fdObj == null) { throw new NullPointerException(); } if (security != null) { security.checkRead(fdObj); }
fd = fdObj;
path = null;
fd.attach(this); }
|
2.2 成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private final FileDescriptor fd;
private final String path;
private FileChannel channel = null;
private final Object closeLock = new Object();
private volatile boolean closed = false;
|
2.3 native方法
FileInputStream类内部的native方法,底层都是对操作系统的IO函数进行调用,而这些IO函数都是围绕某个文件描述符操作的,虽然java层面没有作为参数传递,但native方法内部会使用this获取到文件描述符,然后作为参数调用系统IO函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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;
private native void close0() throws IOException;
|
关于native方法的具体逻辑,可以下载openjdk源码查看。
2.4 普通方法
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
|
private void open(String name) throws FileNotFoundException { open0(name); }
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); }
public void close() throws IOException {
synchronized (closeLock) { if (closed) { return; } closed = true; }
if (channel != null) { channel.close(); }
fd.closeAll(new Closeable() { public void close() throws IOException { close0(); } }); }
public final FileDescriptor getFD() throws IOException { if (fd != null) { return fd; } throw new IOException(); }
public FileChannel getChannel() { synchronized (this) { if (channel == null) { channel = FileChannelImpl.open(fd, path, true, false, this); } return channel; } }
protected void finalize() throws IOException { if ((fd != null) && (fd != FileDescriptor.in)) { close(); } }
|
2.5 简单使用
对于一个稍大的文件来说,通过单字节的形式挨个读取,应用程序对系统内核的调用、内核对设备管理器的调用都太频繁,所以都会采用缓冲区批量读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) throws Exception{
FileInputStream fis = new FileInputStream("you file path");
StringBuilder result = new StringBuilder();
byte[] bytes = new byte[1024];
int length = 0; while((length = fis.read(bytes)) != -1){ result.append(new String(bytes, 0, length)); } System.out.println(result.toString()); }
|
3.FileOutputStream
3.1 内部源码
FileOutputStream的构造器和成员变量和FileInputStream几乎一样,仅仅多了一个append概念,主要用于区分写入的形式是将原来的内容覆盖,还是在后面追加。方法也几乎一样,只不过read变成了write。
3.2 简单使用
1 2 3 4
| public static void main(String[] args) throws Exception{ FileOutputStream fos = new FileOutputStream("you file path"); fos.write("hello world".getBytes()); }
|