Java笔记08 - IO
基本介绍
IO
- Input/Output, 以内存为中心的输入输出
- input: 外部数据读入内存, 并且以java能够识别的形式表示
- output: 内存数据输入到外部, 避免易失性, 必须把处理后的数据以某种方式输出
- 代码在内存中运行, 只有把数据读到内存中才能操作
InputStream/OutputStream
- IO流, 以
byte
(字节)为最小单位. 故也是字节流
- InputStream: 输入字节流
- OutputStream: 输出字节流
Reader/Writer
- 按照
char
来读写:字符流
- 字符流传输的最小单位
char
Reader
和Writer
本质上是一个能自动编码的InputStream
和OutputStream
- 如果数据是文本, 使用
Reader
更方便一些
同步和异步
- 同步: 代码简单, CPU执行效率低
- 异步: 代码复杂, CPU执行料率高
- 同步IO: 输入/输出流的IO模型
File对象
- File来操作文件和目录
文件和目录
- File对象既可以操作文件, 也可以表示目录. 特别注意: 构建一个
File
对象, 即使传入的文件和目录不存在, 代码也不会出错.. 只有调用某些方法才会出错. isFile()
判断是否为一个文件isDirectory()
判断是否为一个目录boolean canRead()
: 是否可读boolean canWrite()
: 是否可写boolean canExecute()
: 是否可执行long length()
: 文件字节大小
创建和删除文件
如果当前File表示文件
createNewFile()
创建文件delete()
删除文件createTempFile()
创建临时文件deleteOnExit()
: JVM退出时自动删除文件
遍历文件和目录
如果当前File表示目录
- 使用
list()
和listFiles()
列出目录下的文件和子目录名 listFiles()
提供一系列重载方法, 可以过滤不想要的文件和目录listFiles()
, 可以接受一个Filter
对象, 筛选名称boolean mkdir()
创建当前File表示文件所在的目录boolean mkdirs()
: 不存在的父目录也创建出来boolean delete
: 删除目录
Path
- 对目录进复杂的拼接, 遍历等操作
Path p1 = Paths.get(".", "project", "study"); System.out.println(p1);
Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
System.out.println(p2);
Path p3 = p2.normalize(); // 转换为规范化路径
System.out.println(p3);
File f = p3.toFile(); // 转换为File对象
System.out.println(f);
for (Path p : Paths.get("..").toAbsolutePath()) { // 直接遍历Path
System.out.println(p);
}
InputStream
- java最基本的输入流
- 抽象类, 所有输入类的超类
read()
: 读取输入流的下一个字节, 返回字节表示的int
值.- 读到末尾, 返回
-1
表示不能再读取了 close()
关闭, 释放底层资源, 避免资源浪费.I/O
代码, 需要处理IOException
- 使用
try(resource)
自动加入 `finally { resource.close() } resource
是否实现AutoCloseable
接口
缓冲
- 一次性支持多个字符到缓冲区, 是一种高效操作.
- 缓冲重载方法:
int read(byte[] b)
: 读取若干字节到byte[]
数组, 返回读取的字节数.int read(byte[] b, int off, int len)
: 指定byte[]
数组的偏移量和最大填充数.
- 先定义一个
byte[]
数组作为缓存区,read()
尽可能多的读取字节到缓冲区, 并返回读取了多少个字节. 没有更多数据, 返回-1
阻塞
read()
方法时阻塞的, 必须等到返回后, 才能继续执行
InputStream实现类
FileInputStream
可以从文件中获取输入流.ByteArrayInputStream
可以在内存中模拟一个InputStream
, 多作为测试使用- 面向对象原则: 接受
InputStream
抽象类型, 而不是具体的FileInputStream
OutputStream
- 抽象类, 所有输出流的超类
void write(int b)
, 写入一个字节到输入流, 只写入int最低8位表示字节部分(相当于b&0xff
)close()
关闭输出流, 释放资源flush()
强制将缓冲区的内容真正输出到目的地- 一般情况下:
- 缓存区写满了,
OutputStream
自动调用 close()
关闭前, 自动调用
- 缓存区写满了,
- 手动调用情况:
- 不能等到缓冲区装满的情况
InputStream
也有缓冲区, 例如读取第一个字节, 操作系统一次性读取若干个字节到缓冲区OutputStream
中的write方法也是阻塞的ByteArrayOutputStream
可以在内存中模拟一个OutputStream
Filter模式
InputStream
根据来源可以分为:FileInputStream
, 从文件中读取, 是最终数据源ServletInputStream
: 从HTTP请求读取数据, 是最终数据源Socket.getInputStream()
: 从TCP链接读取数据, 是最终数据源
如果给
FileInputStream
添加很多功能, 至少需要3个子类三种功能的组合又需要更多子类
为了解决依赖继承导致的子类数量失控问题
首先分为: 直接提供数据的基础
InputStream
, 和提供额外功能的InputStream
1 确定数据来源:
InputStream file = new FileInputStream("test.txt")
2 提供特定的功能, 例如缓存功能:
InputStream buffered = new BufferedInputStream(file)
3 再包装一层解压:
InputStream gzip = new GZIPInputStream(buffered)
┌─────────────────────────┐
│GZIPInputStream │
│┌───────────────────────┐│
││BufferedFileInputStream││
││┌─────────────────────┐││
│││ FileInputStream │││
││└─────────────────────┘││
│└───────────────────────┘│
└─────────────────────────┘
- 通过一个基础组件叠加各种功能组件的模式: Filter模式(装饰者模式: Decorator)
- 通过少量的类, 实现很多特定的功能
OutputStream
也以这种模式来提供各种功能
编写FilterInputStream
- 实现自己的
FilterInputStream
, 以叠加到任何一个InputStream
中 - 我们只需要持有最外层的
InputStream
, 当最外层的InputStream
关闭时, 内层的close
自动调用
操作Zip
ZipInputStream
是一种FilterInputStream
, 可以直接读取zip内容jar
包就是zip
包, 只是增加了一些特定的文件JarInputStream
从ZipInputStream
派生, 读取MANIFEST.MF
读取zip包
- 创建
ZipInputStream
, 传入一个FileInputStream
作为数据源 - 循环调用
getNextEntry()
直到返回null
, 表示zip
流结束 - 一个
Entry
表示一个文件或者目录, 如果是文件, 就用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) {
// ...
}
}
}
}
写入zip包
ZipOutputStream
是一种FilterOutputStream
, 可以直接些人内容到zip
包- 首先创建一个
ZipOutputStream
, 通常是包装一个FileOutputStream
- 写入文件前, 先调用
putNextEntry()
, 然后用write()
写入byte[]
数据 - 写入完毕后调用
closeEntry
结束这个文件的打包
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream("..."))) { File[] files = ...;
for (File file : files) {
zip.putNextEntry(new ZipEntry(file.getName()));
zip.write(getFileDataAsBytes(file));
zip.close();
}
}
- 代码中没有考虑目录结构, 使用
new ZipEntry(name)
传入name
相对路径
读取classpath资源
- java存放
.class
的目录或者jar
包也可以包含其他类型的文件.properties
: 配置文件.jpg
: 图片文件.txt
, .csv
: 文本文件
- 从
classpath
读取文件, 可以避免不同环境下文件路径不一致问题 classpath
中的资源文件, 路径总是以/
开头, 先获取当前的Class
对象- 调用
getResourceAsStream()
可以直接从classpath
读取任意的资源文件 - 如果资源不存在, 返回
null
序列化
- 序列化: 是指把一个Java对象变成二进制内容, 本质上是一个
byte[]
数组 - 序列化原因: 可以把
byte[]
保存在文件中, 或者通过网络传输到远程 - 反序列化: 把二进制内容
byte[]
变化Java对象 - java对象能序列化, 需要实现接口:
java.io.Serializable
接口 Serializable
空接口, 标记接口.
- 使用
ObjectOutputStream
, 把java对象写入一个字节流 ObjectOutputStream
既可以写入基本类型, 如int
,boolean
, 也可以写入String
, 还可以写入实现了Serializable
接口的Object
- 写入
Object
时, 需要大量的类型信息, 所有写入的内容很多
反序列化
ObjectInputStream
负责从一个字节流读取Java对象- 能够读取基本类型和
String
类型,readObject()
可以直接返回一个Object
对象. 然后就可以强制转换了 readObject()
可能抛出的异常:ClassNotFoundException
: 没有找到对应的ClassInvalidClassException
: Class不匹配
- 序列化允许定义一个特殊的
seriaVersionUID
的静态变量, 用于标识Java类的序列化版本. - 反序列化时, 由JVM直接构造出Java对象, 不调用构造方法, 构造方法内部的代码, 在反序列化时不可能运行
安全
- 精心构造的
byte[]
数组被反序列化后可以执行特定的Java代码, 导致安全漏洞 - 更换的序列化犯法是通过JSON这样的数据接口, 只输出基本类型,包括(String)内容, 不存储任何代码相关信息.
Reader
Reader
是另一个输入流接口InputStream
是字节流Reader
是字符流InputStream:
- 字节流, 以
byte
为单位 - 读取字节(-1, 0-255):
int read()
- 读到字节数组:
int read(byte[] b)
- 字节流, 以
Reader
- 字符流, 以
char
为单位 - 读取字符(-1, 0-65535):
int read()
- 读到字符数组:
int read(char[] c)
- 字符流, 以
所有字符流的超类, 返回int(0 ~ 65535), 读到末尾返回
-1
FileReader
- 同样可以使用
try
进行关闭 - 可以一次性读取若干个字符并填充到
char[]
数组的方法:public int read(char[] c) throws IOException
- 设置缓存区, 尽可能填充缓冲区
CharArrayReader
- 可以模拟一个
Reader
.
StringReader
- 直接把String作为数据源, 其他和
CharArrayReader
一样
InputStreamReader
- 普通的
Reader
基于InputStream
构造 Reader
需要从InputStream
中读入字节流(byte). 根据编码设置, 再转换char
.InputStreamReader
可以转成然后InputStream
为Reader
. 需要指定编码
try (Reader reader = new InputStreamReader(new FileInputStream("src/readme.txt", "UTF-8"))) { // todo..
}
Writer
Writer:
char
转成byte
输出对比:
OutputStream
- 字节流, 以
byte
为单位 - 写入字节(0~255):
void write(int b)
- 写入字节数组:
void write(byte[] b)
- 没有写入
String
方法
- 字节流, 以
Writer
- 字符流, 以
char
为单位 - 写入字符(0~65535):
void write(int c)
- 写入字符数组:
void write(char[] c)
- 写入String:
void write(String s)
- 字符流, 以
所有字符输出流的超类
FileWriter
- 向文件中写入字符流的
Writer
.
CharArrayWriter
- 在内存中创建一个
Writer
, 作为一个缓冲区, 可以写入char
, 然后得到写入的char[]
数组 - 主要是用来测试模拟
StringWriter
- 基于内存的
Writer
, 和CharArrayWriter
类似 - 内部维护了一个
StringBuffer
接口, 并对外提供了Writer
接口
OutputStreamWriter
- 将任意的
OutputStream
转换为Writer
PrintStream和PrintWriter
PrintStream
PrintStream
是一种FilterOutStream
- 输出的都是
byte
数据 - 在
OutputStream
的接口上, 提供一些写入各种数据的方法print(int)
print(boolean)
print(String)
print(Object)
- 以及对应的一组
println()
方法, 自动加上换行符
System.out
是系统默认提供的PrintStream
System.err
系统默认提供的标准错误输出- 不会抛出
IOException
PrintWriter
- 扩展了
Writer
接口 print()
/println()
最终输出的是char
数据
以上是 Java笔记08 - IO 的全部内容, 来源链接: utcz.com/z/394379.html