Files should be a common data source in development, so FileInputStream and FileOutputStream are important implementation classes in Java IO byte stream.
Before I talk about FileInputStream and FileOutputStream, I have to mention File classes.
File is also a class in the java.io package. It is mainly used to describe the path of files and file directories. It is not expanded here. In FileInputStream and FileOutputStream, the main purpose is to inform their data sources when constructing FileInputStream and FileOutputStream.
FileInputStream has two more common constructors:
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
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);
}
From the above two constructors, we can see that some of the previous statements are related to control and attribute assignment, and the core is the last sentence open(String name) method.
Another constructor is also derived:
public FileInputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if(fdObj == null) {
throw new NullPointException();
}
if(security != null) {
security.checkRead(fdObj);
}
fd = fdObj;
path = null;
fd.incrementAndGetUseCount();
}
But open(String name) is actually a native method implemented by JVM itself:
private native void open(String name) throws FileNotFouondException;
The three most important methods are the rewriting of InputStream's three read() methods:
public ind read() throws IOException
public ind read() throws IOException {
Object traceContext = IoTrace.fileReadBegin(path);
int b = 0;
try {
b = read0();
} finally {
IoTrace.fileReadEnd(traceContext, b == -1 ? 0 : 1);
}
return b;
}
private native int read0() throws IOException;
Actually, it just calls the native read0() method
public int read(byte b[]) throws IOException
public int read(byte b[], int off, int len) throws IOException
public int read(byte b[]) throws IOException {
Object traceContext = IoTrace.fileReadBegin(path);
int bytesRead = 0;
try {
bytesRead = readBytes(b, 0, b.length);
} finally {
IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead);
}
return bytesRead;
}
public int read(byte b[], int off, int len) throws IOException {
Object traceContext = IoTrace.fileReadBegin(path);
int bytesRead = 0;
try {
bytesRead = readBytes(b, off, len);
} finally {
IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead);
}
return bytesRead;
}
private native int readBytes(byte b[], int off, int len) throws IOException;
Actually, it just calls the native readBytes(byte b[], int off, int len) method
Several others are also native methods, such as skip(), available(), open(String name), and so on.
It is worth mentioning that an additional method is added to obtain the NIO object FileChannel:
public FileChannel getChannel() {
synchronized (this) {
if(channel == null) {
channel = FileChannelImpl.open(fd, path, true, false, this);
fd.incrementAndGetUseCount();
}
return channel;
}
}
Wait for the NIO content to be added. This method is very similar to the singleton pattern.
The corresponding FileOutput Stream has a similar structure:
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
public FileOutputStream(String name, boolean append) throws FileNotFoundException {
this(name != null ? new File(name) : null, append);
}
public FileOutputStream(File file) throws FileNotFoundException {
this(file, false);
}
publid FileOutputStream(File file, boolean append) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if(security != null) {
security.checkWrite(name);
}
if(name == null) {
throw new NullPointerException();
}
if(file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
this.fd = new FileDescriptor();
this.append = append;
this.path = name;
fd.incrementAndGetUseCount();
open(name, append);
}
public FileOutputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkWrite(fdObj);
}
this.fd = fdObj;
this.path = null;
this.append = false;
fd.incrementAndGetUseCount();
}
The constructor of FileOutputStream has one more boolean append parameter than FileInputStream, so the constructor has one more overloaded constructor.
The boolean append parameter denotes whether to append from the end of the file content, which is not appended by default. The override mode is adopted, that is, if the file is content, once the FileOutputStream for the file is created, the content of the file is overwritten, because once the FileOutputStream object is instantiated through the constructor, it has invoked the open method:
private native void open(String name, boolean append) throws FileNotFoundException;
The three most important methods are also the three write methods that rewrite OutputStream:
public void write(int b) throws IOException {
Object traceContext = IoTrace.fileWriteBegin(path);
int bytesWritten = 0;
try {
write(b, append);
bytesWritten = 1;
} finally {
IoTrace.fileWriteEnd(traceContext, bytesWritten);
}
}
private native void write(int b, boolean append) throws IOException;
private native void writeBytes(byte b[], int off, int len, boolean append) throws IOException;
public void write(byte b[]) throws IOException {
Object traceContext = IoTrace.fileWriteBegin(path);
int bytesWritten = 0;
try {
writeBytes(b, 0, b.length, append);
bytesWritten = b.length;
} finally {
IoTrace.fileWriteEnd(traceContext, bytesWritten);
}
}
public void write(byte b[], int off, int len) throws IOException {
Object traceContext = IoTrace.fileWriteBegin(path);
int bytesWritten = 0;
try {
writeBytes(b, off, len, append);
bytesWritten = len;
} finally {
IoTrace.fileWriteEnd(traceContext, bytesWritten);
}
}
Correspondingly, the relevant native methods are invoked
A method for obtaining NIO object FileChannel is also added:
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, false, true, append, this);
fd.incrementAndGetUseCount();
}
return channel;
}
}
Other methods like close() are also natvie close 0 () methods that are ultimately called.
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
fd.decrementAndGetUseCount();
channel.close();
}
int useCount = fd.decrementAndGetUseCount();
if ((useCount <= 0) || !isRunningFinalize()) {
close0();
}
}
private native void close0() throws IOException;
These two classes are here for the time being.