同步IO(Java)
Posted Tanglement
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同步IO(Java)相关的知识,希望对你有一定的参考价值。
File对象
- Java的标准库java.io提供了File对象来操作文件和目录
- File f = new Flie("C:Windosa.txt")
- 注意在字符串中表示一个
- 用.表示当前目录,用..表示上级目录
- getPath()返回构造方法传入的路径,getAbsolutePath(),返回绝对路径,一种是getCanonicalPath,它和绝对路径类似,但是返回的是规范路径
- 构造一个File对象,并不会导致任何磁盘操作。只有当我们调用File对象的某些方法的时候,才真正进行磁盘操作
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
File f1 = new File("C:\Windows");
File f2 = new File("C:\Windows\notepad.exe");
File f3 = new File("C:\Windows\nothing");
System.out.println(f1.isFile());
System.out.println(f1.isDirectory());
System.out.println(f2.isFile());
System.out.println(f2.isDirectory());
System.out.println(f3.isFile());
System.out.println(f3.isDirectory());
}
}
- boolean canRead():是否可读;
- boolean canWrite():是否可写;
- boolean canExecute():是否可执行;
- long length():文件字节大小。
- File表示文件,可以创建和删除文件。表示目录时,也可以创建和删除目录
- 可以使用list()和listFiles()列出目录下的文件和子目录名
InputStream
- 这是Java提供的最基本的输入流
- InputSteam并不是一个接口,而是一个抽象类。他是所有输入流的超类,这个抽象类的一个重要方法 Int read()
- FileInputStream是InputStream的一个子类。顾名思义,FileInputStream就是从文件流中读取数据。下面的代码演示了如何完整地读取一个FileInputStream的所有字节:
public void readFile() throws IOException {
// 创建一个FileInputStream对象:
InputStream input = new FileInputStream("src/readme.txt");
for (;;) {
int n = input.read(); // 反复调用read()方法,直到返回-1
if (n == -1) {
break;
}
System.out.println(n); // 打印byte的值
}
input.close(); // 关闭流
}
- InputStream和OutputStream都是通过close()方法来关闭流。关闭流就会释放对应的底层资源
- 如果读取过程中发生了IO错误,InputStream就没法正确地关闭,资源也就没法及时释放
- 因此,我们需要用try ... finally来保证InputStream在无论是否发生IO错误的时候都能够正确地关闭:
- 利用缓冲区一次性读取多个字节,效率高
- 读取多个字节时,需要先定义一个byte[]数组作为缓冲区,read()方法会尽可能多地读取字节到缓冲区, 但不会超过缓冲区的大小。read()方法的返回值不再是字节的int值,而是返回实际读取了多少个字节。如果返回-1,表示没有更多的数据了
public void readFile() throws IOException {
try (InputStream input = new FileInputStream("src/readme.txt")) {
// 定义1000个字节大小的缓冲区:
byte[] buffer = new byte[1000];
int n;
while ((n = input.read(buffer)) != -1) { // 读取到缓冲区
System.out.println("read " + n + " bytes.");
}
}
}
OutputStream
- OutputSteam也是抽象类,是所有输出流的超类
- 因为向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以OutputStream有个flush()方法,能强制把缓冲区内容输出。
- 通常情况下,我们不需要调用这个flush()方法,因为缓冲区写满了OutputSteam会自动调用。
public void writeFile() throws IOException {
OutputStream output = new FileOutputStream("out/readme.txt");
output.write("Hello".getBytes("UTF-8")); // Hello
output.close();
}
Filter模式
- ServletInputStream:从HTTP请求读取数据,是最终数据源;
- Socket.getInputStream():从TCP连接读取数据,是最终数据源;
- 通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为Filter模式(或者装饰器模式:Decorator)。它可以让我们通过少量的类来实现各种功能的组合
- 下面的例子演示了如何编写一个CountInputStream,它的作用是对输入的字节进行计数:
public class Main {
public static void main(String[] args) throws IOException {
byte[] data = "hello, world!".getBytes("UTF-8");
try (CountInputStream input = new CountInputStream(new ByteArrayInputStream(data))) {
int n;
while ((n = input.read()) != -1) {
System.out.println((char)n);
}
System.out.println("Total read " + input.getBytesRead() + " bytes");
}
}
}
class CountInputStream extends FilterInputStream {
private int count = 0;
CountInputStream(InputStream in) {
super(in);
}
public int getBytesRead() {
return this.count;
}
public int read() throws IOException {
int n = in.read();
if (n != -1) {
this.count ++;
}
return n;
}
public int read(byte[] b, int off, int len) throws IOException {
int n = in.read(b, off, len);
this.count += n;
return n;
}
}
操作zip
- 创建一个ZipInputStream,通常是传入一个FileInputStream作为数据源,然后,循环调用getNextEntry(),直到返回null,表示zip流结束。
- 一个ZipEntry表示一个压缩文件或目录,如果是压缩文件,我们就用read()方法不断读取,直到返回-1
try (ZipInputStream zip = new ZipInputStream(new FileInputStream(...))) {
ZipEntry entry = null;
while ((entry = zip.getNextEntry()) != null) {
String name = entry.getName();
if (!entry.isDirectory()) {
int n;
while ((n = zip.read()) != -1) {
...
}
}
}
}
- ZipOutputStream是一种FilterOutputStream,它可以直接写入内容到zip包。我们要先创建一个ZipOutputStream,通常是包装一个FileOutputStream,然后,每写入一个文件前,先调用putNextEntry(),然后用write()写入byte[]数据,写入完毕后调用closeEntry()结束这个文件的打包。
读取classpath资源
- 把资源存储在classpath中可以避免文件路径依赖;
- Class对象的getResourceAsStream()可以从classpath中读取指定资源;
序列化
- 序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。
- 为什么要把Java对象序列化呢?因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了
- 有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象
- Serializable接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。
public class Main {
public static void main(String[] args) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
// 写入int:
output.writeInt(12345);
// 写入String:
output.writeUTF("Hello");
// 写入Object:
output.writeObject(Double.valueOf(123.456));
}
System.out.println(Arrays.toString(buffer.toByteArray()));
}
}
- 和ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象
Reader
- Reader是Java的IO库提供的另一个输入流接口。和InputStream的区别是,InputStream是一个字节流,即以byte为单位读取,而Reader是一个字符流,即以char为单位读取
- 这个方法读取字符流的下一个字符,并返回字符表示的int,范围是0~65535。如果已读到末尾,返回-1
- FileReader是Reader的一个子类,它可以打开文件并获取Reader。下面的代码演示了如何完整地读取一个FileReader的所有字符
public void readFile() throws IOException {
// 创建一个FileReader对象:
Reader reader = new FileReader("src/readme.txt"); // 字符编码是???
for (;;) {
int n = reader.read(); // 反复调用read()方法,直到返回-1
if (n == -1) {
break;
}
System.out.println((char)n); // 打印char
}
reader.close(); // 关闭流
}
- 如果我们读取一个纯ASCII编码的文本文件,上述代码工作是没有问题的。但如果文件中包含中文,就会出现乱码,因为FileReader默认的编码与系统相关,例如,Windows系统的默认编码可能是GBK,打开一个UTF-8编码的文本文件就会出现乱码。
- 要避免乱码问题,我们需要在创建FileReader时指定编码:
- Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8);
Writer
- Reader是带编码转换器的InputStream,它把byte转换为char,而Writer就是带编码转换器的OutputStream,它把char转换为byte并输出。
PrintStream和PrintWrite
- 我们经常使用的System.out.println()实际上就是使用PrintStream打印各种数据。其中,System.out是系统默认提供的PrintStream,表示标准输出
- PrintStream最终输出的总是byte数据,而PrintWriter则是扩展了Writer接口,它的print()/println()方法最终输出的是char数据。两者的使用方法几乎是一模一样的
以上是关于同步IO(Java)的主要内容,如果未能解决你的问题,请参考以下文章