Java IO原理总结
Java IO Overview
文件相关的类
Files are a common source or destination of data in Java applications.
在Java应用程序中,文件是一种通用的数据源或者存储数据的媒介。
通过Java IO读文件
-
FileInputStream
二进制文件 输入流
可以从文件开始到文件末尾一次读取一个字节
-
FileReader
文本文件 输入流
可以从文件开始到文件末尾一次读取一个字符
你也不必一次性读取整个文件,相反你可以按顺序地读取文件中的字节和字符。
-
RandomAccessFile
跳跃式地读取文件其中的某些部分
通过Java IO写文件
FileOutputStream
FileWriter
通过Java IO随机存取文件
-
RandomAccess
随机存取并不意味着你可以在真正随机的位置进行读写操作,它只是意味着你可以跳过文件中某些部分进行操作,并且支持同时读写,不要求特定的存取顺序。
这使得
RandomAccessFile
可以覆盖一个文件的某些部分、或者追加内容到它的末尾、或者删除它的某些内容,当然它也可以从文件的任何位置开始读取文件。
Stream(流)
Java IO流是既可以从中读取,也可以写入到其中的数据流。流通常会与数据源、数据流向目的地相关联,比如文件、网络等等。
流的特点
-
流和数组不一样,不能通过索引(下标)读写数据
在流中,你也不能像数组那样前后移动读取数据,除非使用
RandomAccessFile
处理文件。流仅仅只是一个连续的数据流。 -
Java IO流通常是基于字节或者基于字符的。
-
字节流通常以“stream”命名,比如
InputStream
和OutputStream
除了
DataInputStream
和DataOutputStream
还能够读写int, long, float和double类型的值以外,其他流在一个操作时间内只能读取或者写入一个原始字节。 -
字符流通常以“
Reader
”或者“Writer
”命名
-
字节流
InputStream
If you are writing a component that needs to read input from a stream, try to make our component depend on an
InputStream
, rather than any of it's subclasses (e.g.FileInputStream
).Doing so makes your code able to work with all types of input streams, instead of only the concrete subclass.
java.io.InputStream
类是所有Java IO输入流的基类。如果你正在开发一个从流中读取数据的组件,请尝试用InputStream
替代任何它的子类(比如FileInputStream
)进行开发。
这么做能够让你的代码兼容任何类型而非某种确定类型的输入流。(更好的兼容性)
read()
Java Doc: Reads the next byte of data from the input stream. The value byte is returned as an
int
in the range0
to255
.If no byte is available because the end of the stream has been reached, the value
-1
is returned.This method blocks until input data is available, the end of the stream is detected,or an exception is thrown.
读数据:通常使用InputStream
中的read()
方法读取数据。
read()方法返回一个整数,代表了读取到的字节的内容(0 ~ 255)。当达到流末尾没有更多数据可以读取的时候,read()方法返回-1
try (InputStream in = new FileInputStream(
"D:\\gitRepository\\organized-learning\\deep-in-java\\stage-8\\helloworld.txt");
OutputStream out = new FileOutputStream(
"D:\\gitRepository\\organized-learning\\deep-in-java\\stage-8\\output.txt")) {
int data;
while ((data = in.read()) != -1) {
out.write(data);
}
}
FileInputStream
重点了解构造器
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
public FileInputStream(File file) throws FileNotFoundException {
...
}
// 传入文件对象 如果文件本身不存在,FileInputStream创建时会报错的
File file = new File("c:\\data\\input-text.txt");
InputStream input = new FileInputStream(file);
// 或者传入路径
InputStream inputStream=new FileInputStream("c:\\data\\input-text.txt");
BufferedInputStream
BufferedInputStream
能为输入流提供缓冲区,能提高很多IO的速度。
你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。
InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"));
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
读的时候会一次性将byte[]数组填满
OutputStream
java.io.OutputStream
是Java IO中所有输出流的基类。
write(int)
用于把单个字节写入到输出流中。
OutputStream
的write(int)
方法将一个包含了待写入数据的int变量作为参数进行写入。
只有int类型的第一个字节会被写入,其余位会被忽略。(写入低8位,忽略高24位)。
write(byte[])
-
write(byte[])
把字节数组中所有数据写入到输出流中。 -
write(byte[], int offset, int length)
把字节数据中从offset位置开始,length个字节的数据写入到输出流。
flush()
将所有写入到OutputStream
的数据冲刷到相应的目标媒介中。
比如,如果输出流是FileOutputStream
,那么写入到其中的数据可能并没有真正写入到磁盘中。即使所有数据都写入到了FileOutputStream
,这些数据还是有可能保留在内存的缓冲区中。
通过调用flush()方法,可以把缓冲区内的数据刷新到磁盘(或者网络,以及其他任何形式的目标媒介)中。
it does not guarantee that they are actually written to a physical device such as a disk drive.(不能确保一定写入磁盘这样的物理设备中)
FileOutputStream
文件内容覆盖&&追加
OutputStream output = new FileOutputStream("c:\\data\\output-text.txt", true);//appends to file
// 默认情况下 是覆盖
OutputStream output = new FileOutputStream("c:\\data\\output-text.txt", false); //overwrites file
BufferedOutputStream
与BufferedInputStream
类似,BufferedOutputStream
可以为输出流提供缓冲区。
OutputStream output = new BufferedOutputStream(new FileOutputStream("c:\\data\\output-file.txt"));
字符流
Reader
Reader
是字符输入流的基类,主要有下列子类。
BufferedReader
InputStreamReader
FileReader
FilterReader
PushbackReader
PipedReader
StringReader
CharArrayReader
Reader
与InputStream类似,不同点在于,Reader
基于字符而非基于字节。换句话说,Reader
用于读取文本,而InputStream
用于读取原始字节。
Reader#read
InputStream#read()
方法返回一个字节,意味着这个返回值的范围在0到255之间(当达到流末尾时,返回 -1);
Reader#read()
方法返回一个字符,这个返回值的范围是在0到65535之间(当达到流末尾时,同样返回-1)。
这并不意味着
Reade
r只会从数据源中一次读取2个字节,Reader
会根据文本的编码,一次读取一个或者多个字节。
InputStreamReader
原文:A Java
Reader
can be combined with anInputStream
. If you have anInputStream
and want to read characters from it, you can wrap it in anInputStreamReader
.
一个Reader
可以和一个InputStream
相结合。如果你有一个InputStream
输入流,并且想从其中读取字符,可以把这个InputStream
包装到InputStreamReader
中。
Reader reader = new InputStreamReader(inputStream);
Writer
Writer
是所有字符输出流的父类。
子类:
BufferedWriter
CharArrayWriter
FilterWriter
OutputStreamWriter
FileWriter
PipedWriter
PrintWriter
StringWriter
write(int c)
Writer的write(int c)方法,会将传入参数的低16位写入到Writer中,忽略高16位的数据。
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
OutputStreamWriter
tips: A Java Writer
can be combined with an OutputStream
just like Readers
and InputStream
's. Wrap the OutputStream
in an OutputStreamWriter
and all characters written to the Writer
are passed on to the OutputStream
.
such as :
Writer writer = new OutputStreamWriter(outputStream);
与Reader
和InputStream
类似,一个Writer
可以和一个OutputStream
相结合。把OutputStream
包装到OutputStreamWriter
中,所有写入到OutputStreamWriter
的字符都将会传递给OutputStream
。(字符流 -> 字节流)。
组合流(Combining Streams)
原文:You can combine streams into chains to achieve more advanced input and output operations. For instance, reading every byte one at a time from a file is slow. It is faster to read a larger block of data from the disk and then iterate through that block byte for byte afterwards.
翻译:我们可以将流整合起来以便实现更高级的输入和输出操作。
比如,一次读取一个字节是很慢的(每次都会触发磁盘访问、网络活动或其他一些相对昂贵的操作),所以可以从磁盘中一次读取一大块数据,然后从读到的数据块中获取字节。
InputStream input = new BufferedInputStream(
new FileInputStream("d:\\data\\input-file.txt"));
缓冲同样可以应用到OutputStream
中。我们也可以实现将大块数据批量地写入到磁盘(或者相应的流)中,这个功能由BufferedOutputStream
实现。
序列化与反序列化
被序列化和反序列化的对象 对应的类,必须实现
Serializable
接口。
核心类:ObjectInputStream
&& ObjectOutputStream
-
ObjectInputStream
能够让你从输入流中读取Java对象,而不需要每次读取一个字节。(反序列化)你可以把
InputStream
包装到ObjectInputStream
中,然后就可以从中读取对象了。 -
ObjectOutputStream
能够让你把对象写入到输出流中,而不需要每次写入一个字节。(序列化)你可以把
OutputStream
包装到ObjectOutputStream
中,然后就可以把对象写入到该输出流中了。
传统Java文件系统
文件对象API - java.io.File
-
检查文件是否存在
-
获取文件长度
-
删除文件
-
检查路径 对应的是 文件还是目录
- true表示该路径存在并且是一个目录
- 如果返回false也有可能是路径不存在而不是指向文件。
-
读取目录中的文件列表
String printInfoPrefix = "[" + TEST_SOURCE_FILE_PATH + "]路径对应的文件";
File file = new File(TEST_SOURCE_FILE_PATH);
boolean exists = file.exists();
String existInfo = exists ? "存在" : "不存在";
// [D:\gitRepository\organized-learning\deep-in-java\stage-8\helloworld.txt]目录对应的文件存在
System.out.println(printInfoPrefix + existInfo);
System.out.println(printInfoPrefix + "长度为:[" + file.length() + "]字节");
// true表示该路径存在并且是一个目录,
// 如果返回false也有可能是路径不存在而不是指向文件。
String pathInfo = file.isDirectory() ? "目录" : "文件";
System.out.println("[" + TEST_SOURCE_FILE_PATH + "]路径 对应的是一个" + pathInfo);
// 获取上一层路径
File parentFile = file.getParentFile();
// 读取路径直接对应的 文件或目录
String[] childFileArr = parentFile.list();
for (String childFile : childFileArr) {
// 遍历 打印
System.out.println(childFile);
}
[D:\gitRepository\organized-learning\deep-in-java\stage-8\helloworld.txt]路径对应的文件存在 [D:\gitRepository\organized-learning\deep-in-java\stage-8\helloworld.txt]路径对应的文件长度为:[12612]字节 [D:\gitRepository\organized-learning\deep-in-java\stage-8\helloworld.txt]路径 对应的是一个文件
file.test file.txt helloworld.txt output.txt outputchar.txt pom.xml stage-8.iml stage8-lesson1
File
类的不足之处
Prior to the Java SE 7 release, the
java.io.File
class was the mechanism used for file I/O, but it had several drawbacks.
- Many methods didn't throw exceptions when they failed, so it was impossible to obtain a useful error message. For example, if a file deletion failed, the program would receive a "delete fail" but wouldn't know if it was because the file didn't exist, the user didn't have permissions, or there was some other problem.
- The
rename
method didn't work consistently across platforms.- There was no real support for symbolic links(快捷方式).
- More support for metadata was desired, such as file permissions, file owner, and other security attributes.
- Accessing file metadata was inefficient.
- Many of the
File
methods didn't scale. Requesting a large directory listing over a server could result in a hang. Large directories could also cause memory resource problems, resulting in a denial of service.- It was not possible to write reliable code that could recursively walk a file tree and respond appropriately if there were circular symbolic links.
文件系统API - java.io.FileSystem
java.io.WinNTFileSystem
文件描述符 - java.io.FileDescriptor
public static void main(String[] args) throws Exception {
displayFileDescriptor(FileDescriptor.in);
displayFileDescriptor(FileDescriptor.out);
displayFileDescriptor(FileDescriptor.err);
}
private static void displayFileDescriptor(FileDescriptor fileDescriptor)
throws NoSuchFieldException, IllegalAccessException {
Integer fd = getFieldValue(fileDescriptor, "fd");
Long handle = getFieldValue(fileDescriptor, "handle");
Boolean closed = getFieldValue(fileDescriptor, "closed");
System.out.printf("FileDescriptor=[ fd: %d , handle : %d , closed: %s]\n",fd,handle,closed);
}
private static <T> T getFieldValue(FileDescriptor fileDescriptor, String fieldName)
throws NoSuchFieldException, IllegalAccessException {
Field field = FileDescriptor.class.getDeclaredField(fieldName);
field.setAccessible(true);
return (T)field.get(fileDescriptor);
}
文件输入输出流
FileInputStream
FileOutputStream
文件过滤器
java.io.FileFilter
**
* 计算一个目录占据的空间
*
* @author ajin
*/
public class DirectorySpaceDemo {
/**
* 根目录
* */
private final File rootDirectory;
/**
* {@link Predicate}
* */
private final Predicate<File> filter;
public DirectorySpaceDemo(File rootDirectory, FileFilter... fileFilters) {
this.rootDirectory = rootDirectory;
this.filter = new FilePredicate(fileFilters);
}
private class FilePredicate implements Predicate<File> {
private final FileFilter[] fileFilters;
public FilePredicate(FileFilter[] fileFilters) {
this.fileFilters = fileFilters;
}
@Override
public boolean test(File file) {
for (FileFilter fileFilter : fileFilters) {
if (!fileFilter.accept(file)) {
return false;
}
}
return true;
}
}
private interface FilePredicateAdapter extends Predicate<File>, FileFilter {
@Override
default boolean accept(File pathname) {
return test(pathname);
}
}
/**
* 获取路径对应 的 大小 比如:d:\\test 占据内存 128kb
*/
public long getSpace() {
if (rootDirectory.isFile()) {
return rootDirectory.length();
} else if (rootDirectory.isDirectory()) {
File[] subFiles = rootDirectory.listFiles();
long space = 0L;
if (null == subFiles) {
return space;
}
// 累加当前目录的文件
space += Stream.of(subFiles).filter(File::isFile).filter(filter).map(File::length).reduce(Long::sum).orElse(
0L);
// 递归当前子目录
space += Stream.of(subFiles).filter(File::isDirectory).filter(filter).map(DirectorySpaceDemo::new).map(
DirectorySpaceDemo::getSpace).reduce(Long::sum).orElse(0L);
return space;
}
return -1L;
}
public static long calculateSpace(File file) {
Objects.requireNonNull(file);
if (file.isFile()) {
return file.length();
} else if (file.isDirectory()) {
long filesSpace = 0L;
File[] subFiles = file.listFiles();
if (null == subFiles) {
return filesSpace;
}
for (File subFile : subFiles) {
filesSpace += calculateSpace(subFile);
}
return filesSpace;
}
return -1L;
}
public static void main(String[] args) {
System.out.println(
new DirectorySpaceDemo(new File(System.getProperty("user.home")), file -> file.getName().endsWith(".log"))
.getSpace() / 1024);
}
}
java.io.FileNameFilter
@FunctionalInterface
public interface FilenameFilter {
boolean accept(File dir, String name);
}
函数式接口,没什么特殊的。