文章目录
-
- IO
- 多线程(web开发的重点)
IO
IO
- 这里主要分为两种,分别时字节流和字符流
字节流
- InputStream和OutputStream时以byte为最小单位,是字节流。
字符流
- Reader和Writer表示字符流,字符流传输的最小数据单位是char。本质上是一个自动编解码的InputStream和OutputStream。
同步和异步
- 同步IO是指,读写IO时代码等待数据返回后,才继续执行后续代码,有点事代码编写简单,缺点是CPU执行效率低
- 异步IO是指,读写IO仅发出请求,然后立刻执行后续代码,优点是CPU执行效率高,缺点是代码编写复杂。
- 上面我们讨论的InputStream、OutputStream、Reader和Writer都是同步IO的抽象类,对应的具体实现类,以文件为例,有FileInputStream、FileOutputStream、FileReader和FileWriter。
File对象
- Java中使用File对象来操作文件和目录,构造一个File对象,需要传入文件路径。
- 3种形式表示的路径
- getPath():返回构造方法传入的路径
- getCanonicalPath():它和绝对路径类似,但是返回的是规范路径。
- getAbsolutePath():返回绝对路径
文件和目录
-
File对象既可以表示文件,可以表示目录。
- 即使传入的路径不存在,也不会报错,创建File并不会有任何磁盘操作。只有调用相关函数才会对磁盘操作。
-
isFile:判断file对象是否是一个已存在的文件。
-
isDirectory:判断file对象是否是一个已经存在目录。
-
boolean canRead():是否可读;
-
boolean canWrite():是否可写;
-
boolean canExecute():是否可执行;
-
long length():文件字节大小。
创建和删除文件
- 创建文件和删除文件
File file = new File("Path");
file.createNewFile() // 创建新文件
file.delete() //删除文件
- 创建临时文件和删除临时文件,JVM退出后自动删除
File f = File.createTempFile("tmp-", ".txt"); // 提供临时文件的前缀和后缀
f.deleteOnExit(); // JVM退出时自动删除
遍历文件和目录
- 当File对象表示一个目录时,
- list列出目录下的文件
- listFiles列出目录下的子目录名
- listFiles()提供了一系列重载方法,可以过滤不想要的文件和目录
创建文件和目录
- boolean mkdir():创建当前File对象表示的目录;
- boolean mkdirs():创建当前File对象表示的目录,并在必要时将不存在的父目录也创建出来;
- boolean delete():删除当前File对象表示的目录,当前目录必须为空才能删除成功。
Path
- 和File相类似,操作更简单
- 这里就不细究了,用File就行了。
InputStream
- 并不是一个接口,而是一个抽象类,是所有输入流的超类。
- 抽象类最重要的方法,int read()
- public abstract int read() throw IOException;
- 所以定义inputStream需要捕获异常
FileInputStream文件输入流
- FileInputStream是InputSream的子类,具体的使用如下
- 这里要注意,对于InputStream的实现类来说,必须要抛出IO异常,然后这个是字节流,默认是读取字节的,如果要细致观察,需要对齐进行字符解码。
- 文件资源是由操作系统管理,进程占用之后,要及时进行释放,防止资源浪费。
- 最后一定要确保关闭资源,所以要把close写在finally中
自动关闭资源的两种写法
- 使用try-finally,确保最后始终能够关闭端口
- 使用try(resource){},这个继承了AutoCloseable接口,超过了这个会自动关闭。
缓冲
- 并非是一个字节一个字节读取,往往是一次读入多个字节,到缓冲区,然后再从缓冲区读取数据更加高效。
- 所以,不要单纯使用read函数,要创建对应的缓冲区,常见方法有以下两种
- int read(byte[] b):读取若干字节到b中,返回的是读取的字节的数量
- int read(byte[] b, int off, int len):指定b的偏移量和最大填充数,返回同上
阻塞
- read方法是阻塞的,必须等到对应的read返回之后,才能后续的语句
InputStream的实现类
- 使用ByteArrayInputStream在内存中模拟一个字节流输入,这个仅仅在测试中有用,看看代码就行了,实际应用很少。
public class Main {
public static void main(String[] args) throws IOException {
byte[] data = {
72, 101, 108, 108, 111, 33 };
try (InputStream input = new ByteArrayInputStream(data)) {
String s = readAsString(input);
System.out.println(s);
}
}
public static String readAsString(InputStream input) throws IOException {
int n;
StringBuilder sb = new StringBuilder();
while ((n = input.read()) != -1) {
sb.append((char) n);
}
return sb.toString();
}
}
OutputStream
- 这个也是一个抽象类,是所有输出流的超类,该方法有三个特殊的方法
- 一个write方法,这个方法会抛出一个IOException,所以使用这个方法需要去捕获这个异常。write方法是写一个字节到输出流。
- close():方法关闭输出流,释放资源
- flush():将缓冲区的内容输出到真正的磁盘中
- 对于IO设备而言,一次写入一个字节和1000个字节的时间是一样的,为了提高效率,所以等缓冲区满了,在一次性全部写入内存中。
- 通常情况下,不需要调用这个方法,缓存满了或者close之前,都会执行这个方法
FileOutputStream
- FileOuputStream是OutputStream的一个具体的实现类,使用write来实现写入字节到缓存中
- 同FileInputStream,都需要在最后关闭对应的对象,所以这里有两种方式,具体实现方式如下。
阻塞
- 和文件输入的情况相同,output也是阻塞的,只有当前输出执行完毕,才能执行后续的语句
ByteArrayOuputStream
- 使用这个可以模拟OutputStream,看一下这个代码就行,这个不重要,用的少
public class Main {
public static void main(String[] args) throws IOException {
byte[] data;
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
output.write("Hello ".getBytes("UTF-8"));
output.write("world!".getBytes("UTF-8"));
data = output.toByteArray();
}
System.out.println(new String(data, "UTF-8"));
}
}
Filter模式
- Filter模式是分别为InputStream和OutputStream增加额外的功能
- 分别将InputStream和FilterInputStream进行组合,可以实现不同的功能,但是仍旧是通过InputStream进行调用
- 将OutputStream和FilterOutputStream进行组合,可以实现不同的功能,但是仍旧是通过OutputStream进行调用。
- 本质上是根据不同的功能,选择不同的FilterInputStream或者FilterOuptutStream,对结果进行包装,但是引用始终是通过InputStream进行引用。
- 左侧是提供数据的基础的inputStream和OutputStream,右侧是提供额外附加功能的inputStream和OutputStream。根据所需要的功能,调用右侧对左侧的结果进行包装,即可实现不同的功能。
使用方式
- 首先确定数据来源,选择对应的基础类型的数据操作流,然后根据功能对齐进行包装。
InputStream file = new FileInputStream("test.gz");
InputStream buffered = new BufferedInputStream(file); // 实现缓冲区功能的BufferedInput Stream进行包装
InputStream gzip = new GZIPinputStream("buffered");// 实现读取压缩文件的GZIPInputStream进行包装。
对输入的字节进行计数
- 下述例子是实现一个计算输入字节数的FilterInputStream,虽然本来的InputStream就能实现这种功能,这里只是叫你一下怎么实现这个FilterInputStream,学习一下。
- 需要重写read函数,使用的话就是正常的文件输入输出流的用法。
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);
if (n != -1) {
this.count += n;
}
return n;
}
}
操作ZIP
- ZipInputStream是一种FilterInputStream,能够直接读取Zip内容。
读取Zip包
// 创建一个基础类型的文件操作流,FileInputStream,然后使用ZipInputStream这个FilterInputStream对结果对齐进行包装。
try (ZipInputStream zip = new ZipInputStream(new FileInputStream(...))) {
ZipEntry entry = null;
// 通过getNextEntry获取对应的zip的entry,可能是一个文件也可能是一个目录,然后在进行逐个操作
while ((entry = zip.getNextEntry()) != null) {
String name = entry.getName();
if (!entry.isDirectory()) {
int n;
// 如果不是目录,是文件,就调用read进行不断读取