Java基础入门之——IO流


请添加图片描述
输入流:磁盘等数据给内存。
输出流:内存里的数据存入磁盘。
字节流:内存和磁盘之间的管道是一个一个字节相互来去
字符流:内存和磁盘之间的管道是一个一个字符相互来去

字节流

文件字节输入流

每次读取1个字节

在这里插入图片描述

public class Main {
    public static void main(String[] args) throws Exception {
        //创建文件字节输入流管道,与源文件接通
        //FileInputStream inputStream = new FileInputStream(new File("src/test.txt"));
        FileInputStream inputStream = new FileInputStream("src/test.txt");

        //读取1个字符,返回int,没有字符返回-1
        int b = inputStream.read();
        System.out.println((char) b);

        //循环读取文件地所有字符,多次访问硬盘性功能很差且遇到汉字会有乱码
        //运行时注释上面读取一个字符的代码,否则从第二个字符开始读取
        int x;
        while ((x = inputStream.read()) != -1) {
            System.out.print((char) x);
        }

        //流使用完毕后必须关闭,释放系统资源
        inputStream.close();
    }
}

中文字符乱码原因:美国最早推出ASC码(之后的编码方式基本上都在此基础上),包含数字、字母能字符,每个字符1个字节;中国推出的GBK国标码每个汉字2个字节;国际组织推出UTF-8编码汉字占3个字节。每次只读取一个字节,所以汉字会出现乱码。

每次读取多个字节

public class Main {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream("src/test.txt");

        //test.txt的内容是abcde
        int numOfWater = 3;
        byte[] buffer = new byte[numOfWater];//就像是一个水桶
        int lenOfRead = inputStream.read(buffer);
        String s = new String(buffer);
        System.out.println(s);
        System.out.println("读取字符个数:" + lenOfRead);

        //再运行一次
        int lenOfRead2 = inputStream.read(buffer);
        String s2 = new String(buffer);
        System.out.println(s2);
        System.out.println("读取字符个数:" + lenOfRead2);

        inputStream.close();
    }
}

在这里插入图片描述
第二次读取的输出有误,对后续的编程可能会有影响,String类重载了方法修改入下。

String s = new String(buffer, 0, lenOfRead);//读多少,倒多少

while循环改造,还是会有汉字乱码的问题,截断汉字的字节。

        byte[] buffer = new byte[3];
        int lenOfRead;
        while ((lenOfRead = inputStream.read(buffer)) != -1) {
            String s = new String(buffer, 0, lenOfRead);
            System.out.print(s);
        }

一次性读取全部字节

  • 方法1:定义一个和文件字节数大小一样的数组,重复以上操作
  • 方法2:调用public byte[] readAllBytes()
        byte[] bytes = inputStream.readAllBytes();
        System.out.println(new String(bytes));

两个方法本质上是一样的,JDK中封装好了方法而已。同时如果文件特别大,readAllBytes()方法会抛出内存溢出异常OutOfMemoryError

文件字节输出流

在这里插入图片描述

public class Main {
    public static void main(String[] args) throws Exception {
        //创建文件字节输入流管道,与源文件接通
        //覆盖数据管道
        FileOutputStream outputStream = new FileOutputStream("src/test.txt");

        //写一个字节
        outputStream.write(97);//a
        outputStream.write('b');//b
        outputStream.write('国');//默认写进去第一个字节,所以写进去乱码

        //写多个字节
        byte[] bytes = "我爱你中国".getBytes();
        outputStream.write(bytes);
        //使用 UTF-8 编码,每个汉字三个字节,只想输入”我爱你“要输入9个字节
        outputStream.write(bytes, 0, 9);
        
        //换行,\n支持windows操作系统,\r\n支持更过平台
        outputStream.write("\r\n".getBytes());
        
        outputStream.close();
    }
}

上面的方法每次运行会先将文件里的数据删干净,再写入。如果想追加数据,看下面。

//追加数据管道
FileOutputStream outputStream = new FileOutputStream("src/test.txt", true);

专业释放资源方式

以上我们讲到,使用完io流后要及时关闭流以达到释放资源的目的,但是在复杂的情形中,关闭流之前出现异常,那么流就一直没有被关闭,一直占用着资源,这一点不好。主要以下两种方式解决。

  • try-catch-finally
    finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
    作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。
public class Main {
    public static void main(String[] args) {
        FileOutputStream outputStream = null;
        try {
            //创建文件字节输入流管道,与源文件接通
            outputStream = new FileOutputStream("src/test.txt", true);

            //写多个字节
            byte[] bytes = "我爱你中国".getBytes();
            outputStream.write(bytes);
            //使用 UTF-8 编码,每个汉字三个字节,只想输入”我爱你“要输入9个字节
            outputStream.write(bytes, 0, 9);

            //换行,\n支持windows操作系统,\r\n支持更过平台
            outputStream.write("\r\n".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 防止创建流对象之前有异常,或者流在try中已经关闭
            try {
                if (outputStream != null) outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • try-with-resource
    在这里插入图片描述
    以上关闭资源方式的finally中的代码略显臃肿,JDK7之后提供了更简便的释放资源方式。
    注意:try之后的括号里只能放资源对象,像定义一个整形变量是不可以的。
    资源:实现了AutoCloseable接口,每个资源实现了close()方法,资源对象被使用完成后会自动去调用它的close()方法实现资源释放。
public class Main {
    public static void main(String[] args) {
        try (//创建文件字节输入流管道,与源文件接通
             FileOutputStream outputStream = new FileOutputStream("src/test.txt", true)) {
            //写多个字节
            byte[] bytes = "我爱你中国".getBytes();
            outputStream.write(bytes);
            //使用 UTF-8 编码,每个汉字三个字节,只想输入”我爱你“要输入9个字节
            outputStream.write(bytes, 0, 9);

            //换行,\n支持windows操作系统,\r\n支持更过平台
            outputStream.write("\r\n".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符流

在这里插入图片描述

public class Main {
    public static void main(String[] args) {
        try(
                Reader reader = new FileReader("src/test.txt");
            ) {
            char[] buffer = new char[3];
            int len;
            while ((len = reader.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, len));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
跟字节流差不多,不做演示了。
注意:字符流写出数据后,必须刷新流(.flush())或者关闭流,不然没写。因为字符流先把数据写到了缓冲区,刷新和关闭操作通知缓冲区写道文件里。

缓冲流

对原始流经行包装,提升原始流的性能。
在这里插入图片描述
先来讲讲缓冲流是如何提高原始流的读取性能的,缓冲流的原理。
举一个例子从D盘复制文件到C盘,用字节输入输出流实现逻辑如下。在内存里创建一个1KB的数组,16次从D盘输入,16次向C盘输出,一共32次访问磁盘
在这里插入图片描述
字节缓冲输入输出流先是包装了字节输入输出流,它就会在内存中开辟一个8KB的缓冲池。D盘数据到输入缓冲池只需要2次,也就是2次输入;同理,2次输出。也就是4次访问磁盘
输入缓冲池的数据通过数组放到输出缓冲池中,这一操作在内存中实现,是相当快的。
在这里插入图片描述

字节缓冲流

在这里插入图片描述
方法也就是功能和字节流一样,只是性能上得到了提升。

public class Main {
    public static void main(String[] args) {
        try(
                InputStream inputStream = new FileInputStream("src/test.txt");
                //创建字节输入缓冲流,可传入int类型作为第二参数定义缓冲池大小
                InputStream bis = new BufferedInputStream(inputStream);
                OutputStream outputStream = new FileOutputStream("src/test_copy.txt");
                //创建字节输出缓冲流,可传入int类型作为第二参数定义缓冲池大小
                OutputStream bos = new BufferedOutputStream(outputStream);
            ) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

字符缓冲流

在这里插入图片描述
字符缓冲输入流还新增了一个功能
在这里插入图片描述
在这里插入图片描述
字符缓冲输出流新增功能
在这里插入图片描述

转换流

解决代码编码和文本文件编码不一致的情况,避免乱码。
在这里插入图片描述

  • 字符输入转换流
    在这里插入图片描述
public class Main {
    public static void main(String[] args) {
        try(
                //创建原始字节流(GBK编码)
                InputStream inputStream = new FileInputStream("src/test.txt");
                //把原始字节流变成字符转换流
                Reader reader = new InputStreamReader(inputStream, "GBK");
                //字符转换流包装成字符缓冲流
                BufferedReader br = new BufferedReader(reader);
            ) {
            char[] buffer = new char[20];
            int len = br.read(buffer);
            System.out.println(new String(buffer,0,len));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 字符输出转换流
    在这里插入图片描述
public class Main {
    public static void main(String[] args) {
        try(
                //创建原始字节流(GBK编码)
                OutputStream outputStream = new FileOutputStream("src/test.txt");
                //把原始字节流变成字符转换流
                Writer writer = new OutputStreamWriter(outputStream, "GBK");
                //字符转换流包装成字符缓冲流
                BufferedWriter bw = new BufferedWriter(writer);
            ) {
            bw.write("我爱中国");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印流

PrintStream/PrintWriter (打印流)
作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
在这里插入图片描述

  • PrintStream
    在这里插入图片描述
public class Main {
    public static void main(String[] args) {
        try(
                PrintStream ps = new PrintStream("src/test.txt")
            ) {
            ps.println("你好");
            ps.println(true);
            ps.println(123);
            ps.println(3.14);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • PrintWriter
    在这里插入图片描述
        try(
                PrintWriter pw = new PrintWriter("src/test.txt")
            ) {
            pw.println("你好");
            pw.println(true);
            pw.println(123);
            pw.println(3.14);
        } catch (Exception e) {
            e.printStackTrace();
        }

以上两个流属于高级流,创建时在后面加true不能实现追加字符。那如果要实现追加流,需要包装一个实现追加流的低级流。

PrintWriter pw = new PrintWriter(new FileWriter("src/test.txt", true))
  • 打印流的应用:输出重定向

平时开发中,我们通常把数据输出到控制台,而程序在运行的时候通常用户是在文件里看数据或者异常。

public class Main {
    public static void main(String[] args) {
        try(
                PrintStream ps = new PrintStream(new FileOutputStream("src/test.txt", true))
            ) {

            //改变系统默认的打印对象
            System.setOut(ps);

            System.out.println(123);
            System.out.println("你好");
            System.out.println(1132.543);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

数据流

应用场景,输出数据的同时,把数据的类型也输出记录下来。
在这里插入图片描述
数据输出流
在这里插入图片描述
数据输入流
在这里插入图片描述
可以看出来数据输入流和数据输出流的方法是相互对应的,使用时也应该相互对应使用。

public class Main {
    public static void main(String[] args) {
        try(
                DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/test.txt"));
            ) {
            dos.writeInt(123);
            dos.writeDouble(12.3);
            dos.writeUTF("我爱中国");
        } catch (Exception e) {
            e.printStackTrace();
        }

        try(
                DataInputStream dis = new DataInputStream(new FileInputStream("src/test.txt"));
        ) {
            System.out.println(dis.readInt());
            System.out.println(dis.readDouble());
            System.out.println(dis.readUTF());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

而text中的数据
在这里插入图片描述

序列化流

序列化:把Java对象写道文件中去
反序列化:把文件里的Java对象读出来

序列化流就是解决把把Java对象写道文件中去的。
在这里插入图片描述
对象字节输出流
在这里插入图片描述
对象字节输入流
在这里插入图片描述

public class Main {
    public static void main(String[] args) {
        try(
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/test.txt"));
            ) {
            //需要序列化的对象,必须实现可序列化接口Serializable
            oos.writeObject(new Student("张三", 20));
        } catch (Exception e) {
            e.printStackTrace();
        }

        try(
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/test.txt"));
        ) {
            Student student = (Student) ois.readObject();
            System.out.println(student);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
注意:需要序列化的对象,必须实现可序列化接口Serializable

如果想要对象的某个成员变量(一般密码不参与)不参与序列化,在变量前加transient修饰符即可。

private transient String password;

总结

读取文本更适合使用字符流,而字节流跟适用于数据转移,如文件的复制。
使用缓冲流可以提高性能。
转换流解决编码问题。
使用资源后要及时关闭资源,用try-with-resource更方便。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值