Java(I/O与NIO)

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();
                }
            }
        }
    }
}

6、NIO(会尽快更新 )

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值