IO与NIO
文章目录
IO数据流分为 字节流(二进制流,以字节为基本单位来处理信息) 和 字符流(文本流,以字符为基本单位来处理信息)。文本文件是可以看懂的。实际上,计算机中的所有文件都是以二进制形式来存储的(本质上所有的文件都是二进制文件)。但是JVM可以帮助我们在写入或读取文本IO中的字符时,自动将字符编码为字节或将字节解码为字符,而二进制IO不需要进行转换,因此处理效率比文本文件高。
装饰器(Decorator)模式: 在运行期动态给某个对象的实例增加功能的方法。Decorator模式的目的就是把一个一个的附加功能,用Decorator的方式给一层一层地累加到原始数据源上,最终,通过组合获得我们想要的功能。I/O中会经常用到装饰器设计模式,我会在下面分类进行总结。
1、字节流—InputStream&OuputStream
常用的字节流如下:(图片有点糊,建议重新打开个网页查看)。以byte
为单位读取
FilterInputStream是一个类
public class FilterInputStream extends InputStream {...}
有了装饰器,我们就可以给实际子类添加不同装饰器类的功能,这样做有什么好处呢?
装饰器设计模式把核心功能和附加功能分开了。对于I/O流来说,核心功能指FileInputStream等实际子类读取数据的源头,附加功能指 缓冲BufferedInputStream、压缩、支持操作更多数据类型(如int double)DataInputStream 等功能。
InputStream input = new GZIPInputStream( // 第二层装饰
new BufferedInputStream( // 第一层装饰
new FileInputStream("test.gz") // 核心功能
));
BufferedInputStream in = new BufferedInputStream(new FileInputStream(filePath));
1.使用字节流进行文件的读取和写入
public static void main(String[] args) throws IOException {
//读文件
//File fileName = new File("src/dir/test.txt");
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream("src/dir/test.txt");
out = new FileOutputStream("src/dir/out.txt");
//使用read()一次读取一个字节,这不高效
//int n;
//while ((n = in.read()) != -1) {
//System.out.println(n);
//}
//利用缓冲区一次读取多个字节
byte[] buffer = new byte[5*1024];
int n;
while((n = in.read(buffer)) != -1) {//read()方法会尽可能多的读取字节到缓冲区
//System.out.println(Arrays.toString(buffer));
//out.write(buffer,getBytes("UTF-8"));
//out.write(72);//H 写入一个字节
out.write(buffer,0,n);
}
}finally {
//assert in != null;
if(in != null){in.close();}
if(out != null) {out.close();}
}
}
需要注意的两点:
- 执行到read()和write()语句时,程序都会在此处阻塞
- OutputStream中的
flush()
方法,调用它可以强制将缓冲区内容输出。通常我们不需要调用它,因为缓冲区写满以及调用close()方法关闭资源时输出流会自动调用它。
**2.读取classpath资源 (如配置文件) **:把资源存储在classpath(类文件.class路径)中可以避免文件路径依赖
java项目中的classpath到底是什么
while (in == getClass().getResourceAsStream("/in.txt")); //通过Class对象的getResourceAsStream可从classpath中读取指定资源
2、序列化和反序列化
序列化
是将一个对象转换成字节序列并保存到byte[]
中,方便将对象存储和传输到文件或网络中。不过不会对静态变量进⾏序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
序列化:ObjectInputStream.readObject();
反序列化:ObjectOuputStream.writeObject();
可以序列化的类必须实现Serializable
接口,它只是⼀个标准,没有任何⽅法需要实现,但是如果不去实现它的话⽽进⾏序列化,会抛出异常。transient
关键字可以使一些属性不被序列化
public interface Serializable { //空接口
}
下面看一个例子
public class SerializableObj {
public static void main(String[] args) throws IOException, ClassNotFoundException {
byte[] b = {1,2,3};
A a1 = new A(1,"张三",b);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("src/dir/out.txt"));
out.writeObject(a1); //序列化
out.close();
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("src/dir/out.txt"));
A a2 = (A) in.readObject(); //反序列化
in.close();
System.out.println(a2);
}
}
class A implements Serializable{
private int x;
private String y;
private transient byte[] b;
public A(int x, String y, byte[] b) {
this.x = x;
this.y = y;
this.b = b;
}
@Override
public String toString() {
return "A{" +
"x=" + x +
", y='" + y + '\'' +
", b=" + Arrays.toString(b) +
'}';
}
}
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。
安全性
Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。
3、字符流—Reader&Writer
常用的字符流如下
Reader和Writer
int read()
(-1,0~65535):读取一个字符
int read(char[] c)
(-1,…):读取字符数组
void write(int c)
:写入一个字符(0~65535)
void write(char[] c)
:写入一个字符数组
void write(String s)
:写入字符串表示的所有字符
FileReader和FileWriter:当操作的文件为文本文件时推荐使用
Reader in= new FileReader (fileName / new File())
Writer out = new FileWrtier(fileName / new File(),[boolean append 是否向文件末尾追加数据])
InputStreamReade和OutputStreamReaderr:
不管是磁盘还是⽹络传输,最⼩的存储单元都是字节,⽽不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进⾏操作的⽅法。
InputStreamReader
实现从字节流解码成字符流;
OutputStreamWriter
实现字符流编码成为字节流。
- Reader本质上是一个基于InputStream的byte到char的转换器,Writer相反
Reader in = new InputStreamReader(new FileInputStream(fileName),"UTF-8");
InputStreamReader是一个转换器,它可以把任何InputStream转换为Reader。
BufferedReader和BufferedWriter:具有缓冲功能
BufferedReader in = new BufferedReader(new FileReader(fileName)); while((String line = in.readLine()) != null) {} // 支持一次读取一行文件内容 in.close(); // 在调⽤ BufferedReader 的 close() ⽅法时会去调⽤ Reader 的 close() ⽅法(装饰器模式)
4、标准I/O
1.PrintStream
public class PrintStream extends FilterOutputStream implements Appendable, Closeable{}
类中额外提供了一些输出各种数据类型的方法
而我们经常使用的System.out
(标准输出流)、System.err
便是被包装好的PrintStream对象,System.in
(标准输入流)是未经包装的InputStream对象
public final class System {
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
}
读取控制台输入
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
String s;
while ((s = in.readLine()) != null){ // 读取从控制台输入的每一行数据
System.out.println(s);
}
}
标准I/O重定向:可以将标准输入附接到文件上,并将标准输出和标准错误重定向到另一个文件。(注意重定向操纵的是字节流)
下面程序执行结果会将in.txt文件中的内容写入到out.txt中
public static void main(String[] args) throws IOException {
PrintStream console = System.out; //保存最初的标准输出流,因为下面会将标准输出重定向到文件
BufferedInputStream in = new BufferedInputStream(
new FileInputStream("src/dir/in.txt"));
PrintStream out = new PrintStream(
new FileOutputStream("src/dir/out.txt"));
System.setIn(in); //将标准输入附接到文件上
System.setOut(out); //将标准输出重定向到另一个文件
//System.setErr(out);
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)); //从文件中读数据,而不再是从控制台读
String s;
while((s = br.readLine()) != null){
System.out.println(s);
}
out.close();
System.setOut(console);//恢复最初的标准输出流
}
2.PrintWriter
public class PrintWriter extends Writer {}
构造方法
同PrintStream一样,类中额外提供了一些输出各种数据类型的方法
使用OutputStream子类创建一个PrintWriter对象,并向控制台输出一些数据,不过PrintStream最终的输出总是byte
数据,而PrintWriter最终的输出总是char
数据
StringWriter buffer = new StringWriter();
try (PrintWriter pw = new PrintWriter(buffer)) {
pw.println("Hello");
pw.println(12345);
pw.println(true);
}
System.out.println(buffer.toString());
PrintWriter out = new PrintWriter(System.out, true) //第二个参数true表示开启自动清空功能
out.println("...");
5、File类—磁盘操作
File 类可以⽤于表示⽂件或文件夹⽬录的信息,但是它不表示⽂件的内容。
1.创建目录或文件
String dirName = "C:/Users/dell/Desktop/com/kk";
//String dirName = "C:\\Users\\dell\\Desktop\\com\\kk\\test.txt";//这样写也可以
String fileName = "C:/Users/dell/Desktop/test.txt";
File file = new File(fileName);
boolean isSuccess = file.createNewFile(); //如果文件或目录已经存在,则返回false
System.out.println(isSuccess);
File dir = new File(dirName);
isSuccess = dir.mkdirs();
System.out.println(isSuccess);
2.对现有目录进行操作
/*结构
-com
--kk
---test.txt
*/
String dirName = "C:/Users/dell/Desktop/com/kk";//目录下只有test.txt文件
File dir = new File(dirName);
if(dir.exists()){
if(dir.isDirectory()){ //判断是否是一个目录
System.out.println("绝对路径:"+dir.getAbsolutePath());
String[] struct = dir.list();//将目录结构提取为列表
if(struct != null) {
for (String s : struct) {
File f = new File(dirName + "/" + s);
if(f.isDirectory()){
System.out.println("目录的绝对路径:"+f.getAbsolutePath());
//boolean isSuccess = f.delete();
//System.out.println(isSuccess);
//当删除某一目录时,必须保证该目录下没有其他文件才能正确删除,否则将删除失败。
}
else if(f.isFile()){
System.out.println("文件的绝对路径:" + f.getAbsolutePath());//C:\Users\dell\Desktop\com\kk\test.txt
System.out.println("name:" + f.getName());//test.txt
System.out.println("can read:" + f.canRead());//true
System.out.println("can write:" + f.canWrite());//true
System.out.println("parent:" + f.getParent());//C:\Users\dell\Desktop\com\kk
System.out.println("path:" + f.getPath());//相对路径
System.out.println("length:" + f.length());//0
System.out.println("最新一次更改的时间戳:" + f.lastModified());//1621219064129
boolean renameSuccess = f.renameTo(new File("test1"));
System.out.println(renameSuccess);
System.out.println("name:" + f.getName());
//f.delete();
}
}
}
}
}