2

第四阶段 IO

IO流

前言:

前面的学习我们只能够在已有的一定封闭范围内进行一些操作,但是这显然是无趣的,也是不支持我们实现一些复杂的需求,所以Java提供IO流这样一种概念,方便我们对数据进行操作

而使用IO流我们可以实现一些强大的功能,例如针对文件的移动复制等操作,又或者程序与外部文件之间的数据存储或者读取,又或者实现一个实时的聊天程序(网络编程),其中数据的传输也用到了我们的IO流,这些内容我们都会在后面设计,下面我就开始IO流的正式学习

(一) IO流的概述及分类

(1) 概念

IO 即 input/output(输入/输出),流的概念还是有一些陌生的

“流”从字面看来就是类似水流的概念,其具有方向性,流动性,连续性、并且可以承载一些事物,而在我们计算机中,“流”是对一种有序连续具有方向性的数据的抽象描述。其本质就是数据的传输,而根据其特点将其抽象封装为各种类,更加方便了用户的操作

(2) 分类

A:流向

  • 输入流——读取数据
  • 输出流——写出数据

B:数据类型

  • 字节流

    • 字节输入流——InputStream
    • 字节输出流——OutputStream
  • 字符流

    • 字符输入流——Reader
    • 字符输出流——Writer

注意:

a: 如果我们没有明确说明按照什么分,默认按照数据类型分。

b: 除非文件用windows自带的记事本打开我们能够读懂,才采用字符流,否则建议使用字节流。

(二) 字节流

(1) FileOutputStream 写出数据

A:操作步骤

  • 创建字节输出流对象
  • 调用writer()方法
  • 释放资源

B:代码体现

FileOutputStream fos = new FileOutputStream("fos.txt");
for.write("hello".getBytes());
fos.close;

换行操作

因为不同的系统针对不同的换行符号识别是不一样的

windows:rn linux:n Mac:r

而一些常见的高级记事本是可以识别任意换行符号的

如何实现数据的追加写入 ?

用构造方法带第二个参数是true的情况即可

FileOutputStream fos = new FileOutputStream("fos.txt", true);
(2) FileInputStream 读取数据

A:操作步骤

  • 创建字节输入流对象
  • 调用writer()方法
  • 释放资源

B:代码体现

FileInputStream fis = new FileInputStream("fos.txt");
//使用FileInputStream对指定路径下内容进行读取,可以结合FileOutputStream实现对文件的操作
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("F:\\fos.txt");

        //方式一
        int by = 0;
        while ((by = fis.read()) != -1){
            System.out.print((char)by);
        }

        //方式二(这种方式更加快,推荐)
        //数组长度一般是1024或者1024的整数倍
        byte[] bys = new byte[1024];
        int len = 0;
        while((len = fis.read(bys))!=-1){
            System.out.print(new String(bys,0,len));
        }
        //释放资源
        fis.close();
    }
}
(3) 字节缓冲流
//统计这段程序运行时间

long start = System.currentTimeMillis();
//受测试代码
long end = System.currentTimeMillis();
System.out.println("共耗时" + (end - start) + "毫秒");

字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流

//字节缓冲输出流
BuffereOutputStream
//字节缓冲输入流
BufferedInputStream
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BufferedOutputStreamDemo {
    public static void main(String[] args) throws IOException {
//        FileOutputStream fos = new FileOutputStream("F:\\fos.txt");
//        BufferedOutputStream bos = new BufferedOutputStream(fos);
        
        //简单写法
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("F:\\fos.txt"));
        
        //写数据
        bos.write("hello".getBytes());
        //释放资源,注意不需要fos.close
        bos.close();
    }
}

为什么不传递一个具体的文件或者文件路径,而是传递一个OutputStream对象呢?

原因很简单,字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是呢,真正的读写操作还得靠基本的流对象实现。

import java.io.*;

public class Test {
    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();

        method1("E:\\夜曲.mp3", "F:\\Test1.mp3");
        method2("E:\\夜曲.mp3", "F:\\Test2.mp3");
        method3("E:\\夜曲.mp3", "F:\\Test3.mp3");
        method4("E:\\夜曲.mp3", "F:\\Test4.mp3");
        long end = System.currentTimeMillis();
        System.out.println("共耗时" + (end - start) + "毫秒");
    }

    //基本字节流一次读写一个字符
    public static void method1(String srcString, String deskString) throws IOException {
        FileInputStream fis = new FileInputStream(srcString);
        FileOutputStream fos = new FileOutputStream(deskString);
        int by = 0;
        while ((by = fis.read()) != -1) {
            fos.write(by);
        }
        fis.close();
        fos.close();
    }

    //基本字节流一次读写一个字节数组
    public static void method2(String srcString, String deskString) throws IOException {
        FileInputStream fis = new FileInputStream(srcString);
        FileOutputStream fos = new FileOutputStream(deskString);
        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = fis.read(bys)) != -1) {
            fos.write(bys, 0, len);
        }
        fis.close();
        fos.close();
    }

    //高效字节流一次读写一个字节
    public static void method3(String srcString, String deskString) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
        BufferedOutputStream bos = new 
            BufferedOutputStream(new FileOutputStream(deskString));
        int by = 0;
        while ((by = bis.read()) != -1) {
            bos.write(by);
        }
        bis.close();
        bos.close();
    }

    //高效字节流一次读写一个字节数组
    public static void method4(String srcString, String deskString) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
        BufferedOutputStream bos = new 
            BufferedOutputStream(new FileOutputStream(deskString));
        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
        }
    }
}


//运行结果
共耗时125961毫秒
共耗时143毫秒
共耗时1356毫秒
共耗时29毫秒

由此可见在上述四种方式中,效率最高的还是最后一种——高效字节流一次读写一个字节数组!

(三) 字符流

我们在开发中,如果想要对我们所能识别的文本内容进行数据的传输,如果我们继续使用我们上面所学习的字节流,我们就会发现显示出来的内容是乱码,这是因为编码出现了问题,而这个时候我们就会用到我们的字符流,我们可以先简单的认识 字符流 = 字节流 + 编码
(1) 编码解码
编码 说明
ASCII 美国标准信息交换码,用一个字节的7位可以表示。
ISO8859-1 拉丁码表。欧洲码表,用一个字节的8位表示。
GB2312 中国的中文编码表。
GBK 中国的中文编码表升级,融合了更多的中文文字符号。
GB18030 GBK的取代版本
BIG-5码 行于台湾、香港地区的一个繁体字编码方案,俗称“大五码”。
Unicode 国际标准码,融合了多种文字, 有文字都用两个字节来表示,Java语言使用的就是unicode
UTF-8 最多用三个字节来表示一个字符。UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:它将Unicode编码为00000000-0000007F的字符,用单个字节来表示它将Unicode编码为00000080-000007FF的字符用两个字节表示 它将Unicode编码为00000800-0000FFFF的字符用3字节表示

字节流我们前面已经有了一定的认识,那么什么是编码和解码呢?

编码是信息从一种形式或格式转换为另一种形式的过程;解码则是编码的逆过程。

我们先通过一个例子来了解一下它的流程

//通过指定的字符集解码字节数组
String(byte[] bytes, String charsetName)
//使用指定的字符集合把字符串编码为字节数组
byte[] getBytes(String charsetName)
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class EncodingDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "理想";

        //String - byte[] - 编码
        byte[] bys = s.getBytes();  //[-25, -112, -122, -26, -125, -77]
//        byte[] bys = s.getBytes("UTF-8");   //[-25, -112, -122, -26, -125, -77]
//        byte[] bys = s.getBytes("GBK"); //[-64, -19, -49, -21]
        System.out.println(Arrays.toString(bys));
        
        //byte[] - String - 解码
        String string = new String(bys);  //理想
//        String string = new String(bys,"UTF-8");    //理想
//        String string = new String(bys,"GBK");    //鐞嗘兂
        System.out.println(string);
    }
}

发送过程:“理想” —— 数字 —— 二进制 —— 发送

接收过程:接收 —— 二进制 —— 十进制 —— 数值 —— 字符 —— “理想”

其实简单的总结就是:

编码:把看得懂的变成看不懂的

解码:把看不懂的变成看得懂的

(2) 字符输入输出流

OutputStreamWriter 字符输出流(写出)

public OutputStreamWriter(OutputStream out)
public OutputStreamWriter(OutputStream out,String charsetName)

InputStreamReader 字符输入流(读取)

public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in,String charsetName)

OutputStreamWriter写数据方法

//写一个字符
public void write(int c)

//写一个字符数组
public void write(char[] cbuf)

//写一个字符数组的一部分
public void write(char[] cbuf,int off,int len)

//写一个字符串
public void write(String str)

//写一个字符串的一部分
public void write(String str,int off,int len)

OutputStreamWriter读数据方法

//读一个字符
public int read()

//第一个字符数组
public int read(char[] cbuf)

字符流操作要注意的问题

flush()和close()的区别 ?

close:关闭流对象,但是先刷新一次缓冲区,关闭之后,流对象就不能继续使用了

flush:仅仅刷新缓冲区,刷新之后,流对象还可以继续使用

(2) 字符流的简单写法

转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化我们的书写,转换流提供了对应的子类

//输出流
FileWriter
//输入流
FileReader
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"))
//等价
FileWriter fw = new FileWriter("b.txt"); (写出)

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"))
//等价 
FileReader fr = new FileReader("a.txt"); (读取)
(3) 字符缓冲流

 BufferedWriter:字符缓冲输出流

将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入

可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。

BufferedReader:字符缓冲输入流

从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取

可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

特殊功能

BufferedWriter:

//根据系统来决定换行符
public void newLine()

BufferedReader:

//一次读取一行数据,包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
public String readLine()

(四) IO流案例

字节流案例

案例一:复制单级文件夹

import java.io.*;

/*
 * 需求:复制单级文件夹
 *
 * 数据源:f:\\demo
 * 目的地:f:\\test
 *
 * 分析:
 *         A:封装目录
 *         B:获取该目录下的所有文本的File数组
 *         C:遍历该File数组,得到每一个File对象
 *         D:把该File进行复制
 */


public class CopyFolderDemo {
    public static void main(String[] args) throws IOException {
        File srcFloder = new File("F:\\demo");
        File deskFloder = new File("F:\\test");

        if (!deskFloder.exists()) {
            deskFloder.mkdirs();
        }

        File[] fileArray = srcFloder.listFiles();
        for (File file : fileArray) {
            String name = file.getName();
            //拼凑出每一个文件的路径
            File newFile = new File(deskFloder, name);
            copyFloder(file, newFile);
        }
    }

    public static void copyFloder(File file, File newFile) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream bos = 
            new BufferedOutputStream(new FileOutputStream(newFile));

        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read()) != -1) {
            bos.write(bys, 0, len);
        }

        bis.close();
        bos.close();

    }
}

案例二:复制指定目录下的指定文件,并修改后缀名

import java.io.*;

/*
 * 需求:复制指定目录下的指定文件,并修改后缀名。
 * 指定的文件是:.txt文件。
 * 指定的后缀名是:.bat
 * 指定的目录是:test
 *
 * 数据源:f:\\demo\\A.txt
 * 目的地:f:\test\\A.bat
 *
 * 分析:
 *         A:封装目录
 *         B:获取该目录下的java文件的File数组
 *         C:遍历该File数组,得到每一个File对象
 *         D:把该File进行复制
 *         E:在目的地目录下改名
 */
public class CopyFolderDemo2 {
    public static void main(String[] args) throws IOException {
        File srcFloder = new File("F:\\demo");
        File destFloder = new File("F:\\test");

        if (!destFloder.exists()) {
            destFloder.mkdirs();
        }

        File[] fileArray = srcFloder.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return new File(dir, name).isFile() && name.endsWith(".txt");
            }
        });


        for (File file : fileArray) {
            String name = file.getName();
            File newFile = new File(destFloder, name);
            copyFile(file, newFile);
        }

        File[] deskFileArray = destFloder.listFiles();
        for (File destFile : deskFileArray) {
            String name = destFile.getName();
            String newName = name.replace(".txt", ".bat");

            File newFile = new File(destFloder, newName);
            destFile.renameTo(newFile);
        }
    }

    public static void copyFile(File file, File newFile) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream bos = 
            new BufferedOutputStream(new FileOutputStream(newFile));

        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
        }
        bis.close();
        bos.close();

    }
}

案例三:复制多级文件夹

import java.io.*;

/*
 * 需求:复制多极文件夹
 *
 * 数据源:F:\\admin
 * 目的地:E:\\
 *
 * 分析:
 *         A:封装数据源File
 *         B:封装目的地File
 *         C:判断该File是文件夹还是文件
 *             a:是文件夹
 *                 就在目的地目录下创建该文件夹
 *                 获取该File对象下的所有文件或者文件夹File对象
 *                 遍历得到每一个File对象
 *                 回到C
 *             b:是文件
 *                 就复制(字节流)
 */
public class CopyFloderDemo3 {
    public static void main(String[] args) throws IOException {
        File srcFile = new File("F:\\admin");
        File destFile = new File("E:\\");
        copyFolder(srcFile, destFile);
    }


    private static void copyFolder(File srcFile, File destFile) throws IOException {
        if (srcFile.isDirectory()) {
            File newFolder = new File(destFile, srcFile.getName());
            newFolder.mkdirs();

            //获取该File对象下的所有文件或者文件夹File对象
            File[] fileArray = srcFile.listFiles();
            for (File file : fileArray) {
                //递归,继续判断
                copyFolder(file, newFolder);
            }

        } else {
            File newFile = new File(destFile, srcFile.getName());
            copyFile(srcFile, newFile);
        }
    }

    private static void copyFile(File srcFile, File newFile) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
        BufferedOutputStream bos = 
            new BufferedOutputStream(new FileOutputStream(newFile));

        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
        }

        bos.close();
        bis.close();
    }
}
字符流案例

案例一:随机获取文本文件中的姓名案例

import java.io.*;
import java.util.ArrayList;
import java.util.Random;

/*
 * 随机获取文本文件中的姓名案例
 *      需求:我有一个文本文件中存储了几个名称
 *      请大家写一个程序实现随机获取一个人的名字。
 *
 * 分析:
 *        A:把文本文件中的数据存储到集合中
 *        B:随机产生一个索引
 *        C:根据该索引获取一个值
 */
public class GetRandName {
    public static void main(String[] args) throws IOException {
        String path = "F:\\test.txt";
//        BufferedReader br = new BufferedReader(new FileReader(path));
        //默认记事本以ansi编码保存,但是使用FileReader默认使用UTF-8输出,所以使用上面语句会乱码
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path), "gb2312"));

        ArrayList<String> array = new ArrayList<>();
        String line = null;
        while ((line = br.readLine()) != null) {
            array.add(line);
        }
        br.close();

        Random r = new Random();
        int index = r.nextInt(array.size());

        String name = array.get(index);
        System.out.println("该幸运儿是:" + name);
    }
}

案例二:键盘录入学生信息按照总分排序并写入文本文件案例

//Student类自行补充
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;

public class StudentDemo {
    public static void main(String[] args) throws IOException {
        TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                int num = s2.getSum() - s1.getSum();
                int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
                int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;
                int num4 = num3 == 0 ? s1.getEnglish() - s2.getEnglish() : num3;
                int num5 = num4 == 0 ? s1.getName().compareTo(s2.getName()) : num4;
                return num5;
            }
        });

        for (int x = 1; x <= 3; x++) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入第" + x + "个学生成绩信息");
            System.out.println("姓名:");
            String name = sc.nextLine();
            System.out.println("语文成绩:");
            int chinese = sc.nextInt();
            System.out.println("数学成绩:");
            int math = sc.nextInt();
            System.out.println("英语成绩:");
            int english = sc.nextInt();

            Student s = new Student();
            s.setName(name);
            s.setChinese(chinese);
            s.setMath(math);
            s.setEnglish(english);
            ts.add(s);

            BufferedWriter bw = new BufferedWriter(new FileWriter("F:\\students.txt"));
            bw.write("学生成绩信息如下");

            bw.newLine();
            bw.flush();
            bw.write("姓名-语文成绩-数学成绩-英语成绩");
            bw.newLine();
            bw.flush();
            for (Student stu : ts) {
                StringBuilder sb = new StringBuilder();
                sb.append(stu.getName() + "-" + stu.getChinese() + "-" + stu.getMath() + "-" + stu.getEnglish());
                bw.write(sb.toString());
                bw.newLine();
                bw.flush();
            }

            bw.close();
            System.out.println("学生成绩信息录入完毕");
        }
    }
}

案例三:登陆注册案例(使用IO)

在前几篇中集合实现的基础上,其余文件不变,只需要对 UserDaoImpl.java 文件进行重写

由于篇幅较长,其余dao、pojo、test层代码请翻阅前几篇中 集合框架——List篇

package cn.bwh_05_LoginDemo.dao.impl;

import cn.bwh_05_LoginDemo.dao.UserDao;
import cn.bwh_05_LoginDemo.pojo.User;

import java.io.*;

/**
 * 这是用户操作的具体实现类 (IO)
 *
 * @author BWH_Steven
 * @version v1.1
 */
public class UserDaoImpl implements UserDao {
    private static File file = new File("User.txt");

    static {
        try {
            file.createNewFile();
        } catch (IOException e) {
            System.out.println("创建文件失败");
            e.printStackTrace();
        }
    }

    @Override
    public boolean isLogin(String username, String password) {
        boolean flag = false;
        BufferedReader br = null;
        String path = "user.txt";

        try {
            br = new BufferedReader(new FileReader(file));
            String line = null;
            while ((line = br.readLine()) != null) {
                //用户名--密码
                String[] datas = line.split("--");
                if (datas[0].equals(username) && datas[1].equals(password)) {
                    flag = true;
                    break;
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println("找不到登录所需要的信息文件");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("用户登录失败");
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    System.out.println("用户登录释放资源失败");
                    e.printStackTrace();
                }
            }
        }
        return flag;
    }

    @Override
    public void regist(User user) {
        /*
         *  为注册的数据定义一个规则: 用户名--密码
         */
        BufferedWriter bw = null;
        String path = "user.txt";
        try {
            //为了保证数据是追加写入,所以必须加true
            bw = new BufferedWriter(new FileWriter(file, true));
            //bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path,true),"gb2312"));
            bw.write(user.getUsername() + "--" + user.getPassword());
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            System.out.println("用户注册失败");
            e.printStackTrace();
        } finally {
            try {
                bw.close();
            } catch (IOException e) {
                System.out.println("用户注册释放资源失败");
                e.printStackTrace();
            }
        }
    }

}

结尾:

如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家 !^_^

如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

在这里的我们素不相识,却都在为了自己的梦而努力 ❤

一个坚持推送原创Java技术的公众号:理想二旬不止


二境志
191 声望26 粉丝