IO流
文件概念
什么是文件?
就是保存数据的地方
文件流

常用文件操作
new File(String pathname)  // 根据路径构建一个File对象(一个File对象就是一个文件)
new File(File parent,String child)  // 根据父目录文件+子路径构建
new File(String parent,String child)  // 根据父目录+子路径构成
createNewFile // 创建新文件 

我们上代码展示
package javaSEStudy.IO.File_;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
// 创建文件
public class FileCreate {
    public static void main(String[] args) {
    }
    // 方式一,根据路径构建
    @Test
    public void create1() {
        // 注意,这里的 \\ 可以换成 /
        File file = new File("D:\\IdeaProject\\xie\\new1.txt");
        try {
            // 执行创建文件
            file.createNewFile();
            System.out.println("创建文件成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 方式二,根据父目录文件加子路径构建
    @Test
    public void create2() {
        File parentFile = new File("D:\\IdeaProject\\xie");
        String fileName = "new2.txt";
        // 这里的File对象,在java程序中,只是一个对象
        File file = new File(parentFile, fileName);
        // 只有执行下面语句才会真正把文件创建出来
        try {
            file.createNewFile();
            System.out.println("创建文件成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 方式三,根据父目录加子路径构建
    @Test
    public void create3() {
        String parentPath = "D:\\IdeaProject\\xie";
        String fileName = "new3.txt";
        File file = new File(parentPath, fileName);
        try {
            file.createNewFile();
            System.out.println("创建文件成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
操作方法
- getName 获取文件名字
 - getAbsolutePath 获取绝对路径
 - getParent 获取文件的父目录
 - length 获取文件大小,以字节方式显示
 - exists 文件是否存在
 - isFile 是否是个文件
 - isDirectory 是否是个目录
 
我们上代码展示
package javaSEStudy.IO.File_;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
// 文件信息
public class FileInfromation {
    public static void main(String[] args) {
    }
    // 获取文件信息
    @Test
    public void info() {
        // 先创建文件对象
        File file = new File("D:\\IdeaProject\\xie\\NO1.txt");
        try {
            file.createNewFile();
            System.out.println("文件创建成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 调用相应方法,获取对应信息
        System.out.println("文件名字: " + file.getName());
        // 获取绝对路径
        System.out.println("绝对路径: " + file.getAbsolutePath());
        // 获取文件父目录
        System.out.println("父级目录: " + file.getParent());
        // 获取文件大小(字节计算)
        System.out.println("文件大小: " + file.length());
        // 判断文件是否存在
        System.out.println("是否存在: "+file.exists());
        // 判断是否是个文件
        System.out.println("是否是个文件: "+ file.isFile());
        // 是否是个目录
        System.out.println("是否是个目录: "+ file.isDirectory());
    }
}
常用文件操作
目录的操作和文件删除
mkdir 创建一级目录, mkdirs 创建多级目录, delete 删除空目录或文件
package javaSEStudy.IO.File_;
import org.junit.jupiter.api.Test;
import java.io.File;
// 文件操作
public class Directory_ {
    public static void main(String[] args) {
    }
    // 判断 D:\IdeaProject\xie\new1.txt 是否存在,存在就删除
    @Test
    public void Method1() {
        String path = "D:\\IdeaProject\\xie\\new1.txt";
        File file = new File(path);
        if (file.exists()) {
            // file.delete 返回的是布尔值
            if (file.delete()) {
                System.out.println(path + " 删除成功");
            } else {
                System.out.println("删除失败");
            }
        } else {
            System.out.println("该文件不存在...");
        }
    }
    // 判断目录 D:\IdeaProject\xie\Demo01 是否存在,存在就删除
    // 注意,在Java编程中,目录也被当做文件,因为这里可以被删除
    @Test
    public void Method2() {
        String path = "D:\\IdeaProject\\xie\\Demo01";
        File file = new File(path);
        if (file.exists()) {
            // file.delete 返回的是布尔值
            if (file.delete()) {
                System.out.println(path + " 删除成功");
            } else {
                System.out.println("删除失败");
            }
        } else {
            System.out.println("该目录不存在...");
        }
    }
    // 判断 D:\IdeaProject\xie\Demo02\a\b\c 是否存在,否则就创建
    @Test
    public void Method3() {
        String path = "D:\\IdeaProject\\xie\\Demo01\\a\\b\\c";
        File file = new File(path);
        // 创建一级目录 mkdir(),注意这里的区别
        if (file.mkdirs()){
            System.out.println("创建成功");
        }else {
            System.out.println("创建失败,该目录已存在");
        }
    }
}
IO流
原理
1.I/O是Input和Output的缩写,I/O技术是非常实用的技术,用于处理数据传输,如读/写文件,网络通讯
2.Java程序中,对于数据的输出/输出操作以"流(stream)"的方式进行
3.java.io 包下提供了各种"流"类和接口,用以获取不同种类的数据,并通过方法输入或输出数据

分类
- 按操作数据单位的不同分为: 字节流(8bit) 二进制,字符流(字符) 文本文件
 - 按数据流的流向不同分为: 输入流,输出流
 - 按流的角色不同分为: 节点流,处理流/包装流
 
声音,照片,视频等,都是二进制文件
下面四种都是抽象类
| (抽象基类) | 字节流 | 字符流 | 
|---|---|---|
| 输入流 | InputStream | Reader | 
| 输出流 | OutputStream | Writer | 
ps:
1.java的IO流总共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的
2.由这4个类派生出来的子类名称都是以其父类名作为子类名后缀

InputStream
字节输入流
InputStream 常用子类
1.FileInputStream: 文件输入流
2.BufferedInputStream: 缓冲字节输入流
3.ObjectInputStream: 对象字节输入流
关系:

方法摘要

我们直接谁上代码展示
package javaSEStudy.IO.inputStream_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
// FileInputStream(字节输入流 文件-->程序) 用法详解
@SuppressWarnings({"all"})
public class FileInputStream_ {
    public static void main(String[] args) {
    }
    /**
    * 读取文件 read()
    * 单个字节的读取,效率较低
    * 且不能读取中文,会显示乱码
     */
    @Test
    public void readFile01() {
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\hello.txt";
        int readData = 0;
        InputStreamReader isr = null;
        try {
            // 创建FileInputStream对象,用于读取文件
            FileInputStream fis = new FileInputStream(filePath);
            // 不使用下面这个方法,会导致控制台输出乱码
            isr = new InputStreamReader(fis, "UTF-8");
            // readData() 从输入流读取一个字节的数据,如果没有输入可用,此方法将阻止,到达文件末尾,读取完毕则返回-1
            while ((readData = isr.read()) != -1) {
                // 在读取的时候是int类型,显示的时候需要将其转换为char显示
                System.out.print((char) readData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭文件流,释放资源
            try {
                // 注意在这里使用irs,是在try代码块中,所以作用域仅限于try中,因此需要扩大其作用域
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 读取文件 read(byte[] b),提高效率
     * 不能读取中文
     */
    @Test
    public void readFile02() {
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\hello.txt";
        int readData = 0;
        int readLength = 0;
        // 字节数组
        // 一次读取4096个字节,大概率可以避免中文字节被截断导致显示乱码的错误
        byte[] buffer = new byte[4096];
        FileInputStream fis = null;
        try {
            // 创建FileInputStream对象,用于读取文件
            fis = new FileInputStream(filePath);
            // 该读取流最多读取b.length字节的数据到字节数组,如果返回-1表示读取完毕
            // 如果读取正常,返回实际读取的字节数
            while ((readLength=fis.read(buffer)) != -1) {
                // 在读取的时候是int类型,显示的时候需要将其转换为char显示
                // 这里new String()将字节转换为字符串解码
                // buffer:读取的文件内容(原始二进制数据) 0:起始偏移量 readLength:有效数据长度
                System.out.print(new String(buffer, 0, readLength));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭文件流,释放资源
            try {
                // 注意在这里使用fis,是在try代码块中,所以作用域仅限于try中,因此需要扩大其作用域
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
FileOutputStream
字节输出流


类图关系

话不多说,我们直接上代码
package javaSEStudy.IO.outputStream;
import org.junit.jupiter.api.Test;
import java.io.FileOutputStream;
import java.io.IOException;
// FileOutputStream 详解
@SuppressWarnings({"all"})
public class FileOutputStream_ {
    public static void main(String[] args) {
    }
    /**
     * 在test01.txt文件中写入数据
     * 如果文件不存在会创建文件,前提是目录已经存在
     * new FileOutputStream(filePath);该方式创建,当写入内容时,是覆盖原文件
     * new FileOutputStream(filePath,true); 该方式创建,是以追加的形式创建
     */
    @Test
    public void writeFile01() {
        // 创建FileOutputStream对象
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\test01.txt";
        FileOutputStream fos = null;
        try {
//            fos = new FileOutputStream(filePath);
            fos = new FileOutputStream(filePath, true);
            // 写入一个字节 char --> int
            fos.write('a');
            // 写入多个字节(字符串)
            String str1 = " hello,world! ";
            // str.getBytes() 将String转换成byte[] 也就是字节数组
            fos.write(str1.getBytes());
            // 只将字符串部分写入
            String str2 = " abcdefghij ";
            // 全部写入
            fos.write(str2.getBytes(), 0, str2.length());
            // 部分写入
            fos.write(str2.getBytes(), 0, 6);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
来个小测试
完成拷贝任务
package javaSEStudy.IO.test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
// 完成图片/音乐的拷贝
@SuppressWarnings({"all"})
public class FileCopy {
    public static void main(String[] args) {
        /**
         * 将data中的图片复制到test下
         * 在完成程序时,应读取部分数据,就写入到指定文件中 (使用循环)
         */
        String inputPath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\picture1.png";
        String outputPath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\test\\picture1.png";
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(inputPath);
            fos = new FileOutputStream(outputPath);
            // 定义一个字节数组,提高读取效率
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = fis.read(buffer)) != -1) {
                // 边读边写
                // 一定要使用write(byte[] b,int off,int len)方法
                // write(byte[]),如果里面是1024个字节,那么每一次写入都会写入1024个字节,即使数据长度没有1024个,不够使用0代替
                fos.write(buffer, 0, length);
            }
            System.out.println("拷贝成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
FileReader
字符流,按照字符操作io
方法
- new FileReader(File/String)
 - read: 每次读取单个字符,返回该字符,如果到文件末尾返回-1
 - read(char[]): 批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
相关API - new String(char[]): 将char[] 转换成String
 - new String(char[],off,len): 将char[]的指定部分转换成String
 
类图关系

话不多说,我们直接上代码
package javaSEStudy.IO.Reader;
import org.junit.jupiter.api.Test;
import java.io.FileReader;
import java.io.IOException;
// FileReader 详解
@SuppressWarnings({"all"})
public class FileReader_ {
    public static void main(String[] args) {
    }
    /**
     * 单个字符读取
     * 读取速率较慢
     */
    @Test
    public void readFile01() {
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\story.txt";
        FileReader reader = null;
        int data = 0;
        // 1.创建操作对象
        try {
            reader = new FileReader(filePath);
            // 2.循环读取,使用read(),单个字符读取
            while ((data = reader.read())!= -1){
                System.out.print((char)data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 字符数组读取
     */
    @Test
    public void readFile02() {
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\story.txt";
        FileReader reader = null;
        int readLength = 0;
        char[] buffer = new char[1024];
        // 1.创建操作对象
        try {
            reader = new FileReader(filePath);
            // 2.循环读取,使用read(buffer),返回的是实际读取到的字符数
            // 如果返回-1,证明文件读取完毕
            while ((readLength = reader.read(buffer))!= -1){
                System.out.print(new String(buffer, 0, readLength));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
FileWriter
字符流,按照字符操作io
类图关系

方法
- new FileWriter(File/String): 覆盖模式,相当于流的指针在首端
 - new FileWriter(File/String,true): 追加模式,相当于流的指针在尾端
 - write(int): 写入单个字符
 - writer(char[]): 写入指定数组
 - writer(char[],off,len): 写入指定数组的指定部分
 - writer(String): 写入整个字符串
 - writer(String,off,len): 写入字符串的指定部分
相关API:
String类 --> toCharArray: 将String转换成char[] 
注意: FileWriter使用后,必须要关闭(close)或者刷新(flush),否则写入不到指定的文件
我们直接上代码
package javaSEStudy.IO.Writer;
import java.io.FileWriter;
import java.io.IOException;
// FileWriter 详解
@SuppressWarnings({"all"})
public class FileWriter_ {
    public static void main(String[] args) {
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\note.txt";
        // 创建操作对象
        FileWriter fw = null;
        char[] chars = {'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            // 默认覆盖输入
            // 倘若想以追加的形式输入: fw = new FileWriter(filePath, true);
            fw = new FileWriter(filePath);
//            // 写入单个字符
//            fw.write('A');
//
//            // 写入数组
//            fw.write(chars);
//
//            // 写入指定数组的指定部分
//            fw.write("哎哟你干嘛".toCharArray(), 0, 2);
//
//            // 写入字符串
//            fw.write(" 鸡你太美");
//
//            // 写入字符串的指定部分
//            fw.write("把篮球和鸡结合起来会发生什么", 0, 10);
            // 在实际开发中,数据量大的情况下,可能会循环操作
            String txt = "雨下得很大,林小夏躲在便利店屋檐下,没带伞。忽然,一把黑伞递了过来。\n" +
                    "“一起走吧。”陌生男孩笑着说。\n" +
                    "他们并肩走着,聊起喜欢的书和电影,竟发现彼此住同一栋楼。\n" +
                    "“明天还在这里等吗?”分别时,男孩问。\n" +
                    "“嗯!”她点头。\n" +
                    "第二天,雨停了,阳光很好。林小夏站在便利店门口,手里拿着两杯热咖啡。\n" +
                    "而他,也如约而至。";
            for (int i = 0; i < 1; i++) {
                fw.write(txt);
                fw.write("\n\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 注意,写完之后必须要关闭或者刷新,否则无法写入内容
            try {
                // 关闭
//                fw.close();
                // 刷新
                fw.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("程序结束...");
    }
}
节点流和处理流
基本介绍:
1.节点流是可以从一个特定的数据源读写数据(固定只能对单一处理),如:FileReader,FileWriter

2.处理流(也叫包装流)是"连接"在已存在的流(节点流或处理流)之上(对节点流进行包装),为程序提供更为强大的读写能力,也更加灵活,如:BufferedReader,BufferedWriter

一览图

详细说明

使用包装流,可以增强功能,从而能操作更多数据,这种模式也叫修饰器模式
处理流设计模式
节点流和处理流的区别和联系
- 1.节点流是底层流/低级流,直接跟数据源相接
 - 2.处理流包装节点流,既可以消除不同节点流实现差异,也可以提供更方便的方法来完成输入输出
 - 3.处理流(包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
 
处理流的功能主要体现在以下两个方面
- 1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率
 - 2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
 
修饰器模式本质:本质:通过组合和委托,在保留原有功能的基础上动态扩展新行为,而非通过继承硬编码。这是修饰器模式的核心.
我们直接上代码展示
package javaSEStudy.IO;
// 修饰器模式详解
// 本质:通过组合和委托,在保留原有功能的基础上动态扩展新行为,而非通过继承硬编码。这是修饰器模式的核心思想,也是其优于继承的关键所在。
// 抽象类
public abstract class Reader_ {
    public void readFile(){}
    public void readString(){}
}
// 看做处理流/包装流
class BufferedReader_ extends Reader_ {
    // 属性是 Reader_ 类型
    private Reader_ reader_;
    // 用于接受Reader_的子类
    public BufferedReader_(Reader_ reader_) {
        this.reader_ = reader_;
    }
    // 封装一层,只在BufferedReader_中封装一层,未嵌套,通过reader_委托调用节点流
    public void readFile() {
        reader_.readFile();
    }
    // 封装一层
    public void readString() {
        reader_.readString();
    }
    // 让方法更灵活,多次读取文件,或者加缓冲byte[]...
    public void readFiles(int count) {
        for (int i = 0; i < count; i++) {
            reader_.readFile();
        }
    }
    // 扩展,批量处理字符串
    public void readStrings(int count) {
        for (int i = 0; i < count; i++) {
            reader_.readString();
        }
    }
}
// 可以看成节点流
class FileReader_ extends Reader_ {
    public void readFile() {
        System.out.println("对文件进行读取");
    }
    @Override
    public void readString() {
    }
}
// 看做节点流
class StringReader_ extends Reader_ {
    public void readString() {
        System.out.println("读取字符串");
    }
}
// 测试类
class Test_{
    public static void main(String[] args) {
        BufferedReader_ br1 = new BufferedReader_(new FileReader_());
        // 调取只读一次的方法
        br1.readFile();
        // 调取读多次的方法
//        br1.readFiles(2);
        BufferedReader_ br2 = new BufferedReader_(new StringReader_());
        // 通过bufferedReader_ 操作字符串
        br2.readString();
//        br2.readStrings(2);
    }
}
上述方法还不足以将该设计模式完整展示,让我们将其完善一下
package javaSEStudy.IO.modifier.testPlus;
// 将代码优化后展示
// 抽象组件:统一用read()方法管理所有节点流
public abstract class Reader_ {
    public abstract void read(); // 改为单一抽象方法
}
// 具体组件:文件读取节点流
class FileReader_ extends Reader_ {
    @Override
    public void read() {
        System.out.println(" 对文件进行读取");
    }
}
// 具体组件:字符串读取节点流
class StringReader_ extends Reader_ {
    @Override
    public void read() {
        System.out.println(" 读取字符串");
    }
}
// 修饰器:增强功能(缓冲/批量读取)
class BufferedReader_ extends Reader_ {
    // 持有被包装对象
    private Reader_ reader_; 
    public BufferedReader_(Reader_ reader_) {
        this.reader_  = reader_;
    }
    @Override
    public void read() {
        reader_.read(); // 动态绑定到具体组件
    }
    // 扩展功能:批量读取
    public void readMultiple(int count) {
        for (int i = 0; i < count; i++) {
            reader_.read(); // 利用动态绑定调用具体实现
        }
    }
}
// 测试类
class Test_ {
    public static void main(String[] args) {
        // 动态绑定示例
        Reader_ fileReader = new FileReader_();
        fileReader.read();  // 输出:对文件进行读取
        Reader_ stringReader = new StringReader_();
        stringReader.read();  // 输出:读取字符串
        // 修饰器增强功能
        BufferedReader_ bufferedFileReader = new BufferedReader_(new FileReader_());
        // 输出3次文件读取
        bufferedFileReader.readMultiple(3);  
    }
}
处理流
BufferedReader和BufferedWriter
- BufferedReader和BufferedWriter属于字符流,是按照字符读取数据的
 - 关闭时,只需要关闭外层流即可
也就是说外层流也就是BufferedReader去调用FileReader时,将BufferedReader关闭,JVm会自动关闭FileReader 
话不多说,我们直接上代码
BufferedReader
package javaSEStudy.IO.Reader;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
// 使用BufferedReader 读取文本文件
public class BufferedReader_ {
    public static void main(String[] args) throws IOException {
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\story.txt";
        // 创建BufferedReader对象
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        // 读取文件
        // 按行读取
        // 当返回为null时,表示读取完毕
        String line;
        while((line = br.readLine()) != null) {
            System.out.print(line+"\n");
        }
        // 关闭流
        // 注意,这里应该关闭的是FileReader,但是实际上只需要关闭BufferedReader,底层会自动关闭节点流
        br.close();
    }
}
BufferedWriter
package javaSEStudy.IO.Writer;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
// BufferedWriter 详解
public class BufferedWriter_ {
    public static void main(String[] args) throws IOException {
        // 目标输入路径
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\test01.txt";
        String str = "暮色沉沉。暴雨倾盆的傍晚,林医生刚结束一台手术,发现医院门口蜷缩着一只湿透的流浪狗,\n" +
                "前爪血迹斑斑。她想起今日是已故父亲生日——他生前总说“生命无价”。她脱下白大褂裹住小狗冲进雨幕,\n" +
                "身后护士喊:“林医生!伞!”她回头一笑:“它等不及了。”\n" +
                "\n" +
                "(注:故事暗合今日农历“十”的圆满之意,蛇年主题通过“医者仁心”呼应蛇的智慧与治愈象征。)";
        // 创建BufferedWriter对象
        // 注意,如果想以追加的形式将其写入 new FileWriter(filePath,true)
        BufferedWriter bw = new BufferedWriter(new FileWriter(filePath));
        bw.write(str, 0, str.length());
        // 插入一个和系统相关的换行符
        bw.newLine();
        System.out.println("写入成功");
        // 这里关闭同BufferedReader
        bw.close();
    }
}
综合案例
使用BufferedReader和BufferedWriter完成文件拷贝
package javaSEStudy.IO.test;
import java.io.*;
// 使用BufferedReader和BufferedWriter完成文本拷贝(带时间统计)
public class TxtCopy {
    public static void main(String[] args) throws IOException {
        /**
         * 说明
         *  BufferedReader和BufferedWriter是按照字符操作的
         *  不要去操作二进制文件[声音,图片,视频,doc,pdf...],可能造成文件损坏
         */
        String filePath = "D:\\IdeaProject\\Xie\\src\\javaSEStudy\\IO\\data\\DouPoCangQiong.txt";
        String copyPath = "D:\\IdeaProject\\Xie\\src\\javaSEStudy\\IO\\test\\data\\DouPoCangQiongCopy.txt";
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        BufferedReader reader = new BufferedReader(new FileReader(filePath));
        BufferedWriter writer = new BufferedWriter(new FileWriter(copyPath));
        String line;
        while ((line = reader.readLine()) != null) {
            writer.write(line + "\n");
            // 这里换行可以写成 writer.newLine()
        }
        // 记录结束时间并计算耗时
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        System.out.println(" 拷贝完成");
        System.out.println(" 复制耗时: " + duration + " 毫秒");
        if (reader != null) {
            reader.close();
        }
        if (writer != null) {
            writer.close();
        }
    }
}
BufferedInputStream和BufferedOutputStream


BufferedInputStream和BufferedOutputStream的使用
package javaSEStudy.IO.test;
import java.io.*;
// BufferedInputStream和BufferedOutputStream复制图片
public class BufferedStreamCopy {
   public static void main(String[] args) throws IOException {
       // 目标路径
       String inputPath = "D:\\IdeaProject\\Xie\\src\\javaSEStudy\\IO\\data\\茶园少女.mp4";
       String outputPath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\test\\data\\茶园少女(copy).mp4";
       // 记录开始时间
       long startTime = System.currentTimeMillis();
       BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inputPath));
       BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputPath));
       // 循环读取
       byte[] buf = new byte[1024];
       int len;
       // 当返回-1时表示文件读取完毕
       while ((len = bis.read(buf)) != -1) {
           bos.write(buf, 0, len);
       }
       // 记录结束时间并计算耗时
       long endTime = System.currentTimeMillis();
       long duration = endTime - startTime;
       System.out.println(" 拷贝完成");
       System.out.println(" 复制耗时: " + duration + " 毫秒");
       if (bis != null) {
           bis.close();
       }
       if (bos != null) {
           bos.close();
       }
   }
}
对象流ObjectInputStream和ObjectOutputStream
首先我们来看一个需求:
- 将
int num = 10这个 int 数据保存到文件中,注意不是数字100,而是int 100,并且能够从文件中直接恢复int 100 - 将
Dog dog = new Dog("小黄",3)这个dog对象保存到文件中,并且能从文件中国恢复 - 上面的要求,就是能够将 基本数据类型 或对象 进行序列化或反序列化
 
序列化和反序列化
- 序列化就是在保存数据的时候,保存数据的值和数据类型
 - 反序列化就是在恢复数据的时候,恢复数据的值和数据类型
 - 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现下面两个接口之一
Serializablle // 这是一个标记接口(推荐,仅用于声明,没有方法)
Externalizable // 该接口有两个方法需要实现 
基本介绍
- 功能: 提供了对基本数据类型或对象类型的序列化和反序列化方法
 - ObjectInputStream 负责反序列化(读取对象)
 - ObjectOutputStream 负责序列化(写入对象)
这里序列化和反序列化的实现方法注意别弄混了 
其本质还是修饰器模式


ObjectOutputStream
先创建一个公有的Dog类
package javaSEStudy.IO.outputStream;
import java.io.Serializable;
// 如果需要序列化某个类的对象,需要实现Serializable 接口
public class Dog implements Serializable {
    private String name;
    private int age;
    // seriaVersionUID 序列化的版本号,可以提高兼容性
    // 如果添加新的属性,他不会认为这是一个新的类,而是原来Dog类的升级版
    private static final long seriaVersionUID = 1L;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return  "name='" + name + '\'' +
                ", age=" + age;
    }
}
package javaSEStudy.IO.outputStream;
import java.io.*;
// 演示 ObjectOutputStream_ 的使用,完成数据的序列化
public class ObjectOutputStream_ {
    public static void main(String[] args) throws IOException {
        // 序列化后保存的文件格式不是纯文本格式,而是按照序列化的格式保存
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\data.tat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
        /**
         * 序列化数据到文件中
         */
        // 八大基本数据类型的包装类都是实现了Serializable接口
        // int --> Integer(实现了 Serializable 接口) 在底层自动装箱
        oos.writeInt(100);
        // boolean --> Boolean
        oos.writeBoolean(true);
        // char --> Character
        oos.writeChar('a');
        // double --> Double
        oos.writeDouble(3.14);
        // 存放字符串注意,使用的是writeUTF  String 实现了Serializable接口
        oos.writeUTF("基尼太美");
        // 保存一个dog对象
        oos.writeObject(new Dog("大黄",3));
        // 关闭流
        oos.close();
        System.out.println("序列化数据完毕");
    }
}
ObjectInoutStream
package javaSEStudy.IO.inputStream_;
import javaSEStudy.IO.outputStream.Dog;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
// ObjectInputStream 读取数据并反序列化恢复数据
public class ObjectInputStream_ {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 指定反序列化文件
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\data.tat";
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
        /**
         * 读取
         * 1.反序列化顺序(读取)需要和序列化顺序(保存)一致
         * 2.否则会出现异常
         */
        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readChar());
        System.out.println(ois.readDouble());
        System.out.println(ois.readUTF());
        // 在底层 Object --> Dog 所以这里会抛出一个类型转换异常
        // dog的编译类型是Object,但是运行类型是Dog
        Object dog = ois.readObject();
        System.out.println("运行信息 " + dog.getClass());
        System.out.println("Dog类 "+dog);
        // 这里有个特别重要的细节
        // 1.如果我们希望调用Dog的方法,,需要向下转型
        // 2.需要我们将Dog类的定义,拷贝到可以引用的地方,或者导包
        // 这里的包不能是某个类下私有的,必须是public公有的才能访问到
        Dog d = (Dog)dog;
        System.out.println(d.getName());
        // 关闭流,关闭外层流,底层自动关FileInputStream
        ois.close();
    }
}
对象处理流使用细节
- 
- 读写顺序要一致
 
 - 
- 要求实现序列化或反序列化对象,需要实现Serializable接口
 
 - 
- 序列化的类中建议添加SerialVersionUID,为了提高版本兼容性
 
 
    // seriaVersionUID 序列化的版本号,可以提高兼容性
    // 如果添加新的属性,他不会认为这是一个新的类,而是原来Dog类的升级版
    private static final long seriaVersionUID = 1L;
- 
- 序列化对象时,默认将里面所有属性都进行序列化,但是除了static或transient修饰的成员
 
 - 
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
 
 
// 如果此时有个共有的类 Master,将其在Dog类中new出来
private String name;
private int age;
private Master master = new Master();
// 这样就会报错,显示不能序列化,此时就要去Master类让他实现Serializable接口
- 
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它所有的子类也已经默认实现了序列化
 
 
标准输入输出流
介绍
| 类型 | 默认设备 | |
|---|---|---|
| System.in 标准输入 | InputStream | 键盘 | 
| System.out 标准输出 | PrintStream | 显示器 | 
PrintStream: 打印流
话不多说,直接上代码
package javaSEStudy.IO.standard;
import java.util.Scanner;
// 标准输入输出流
public class InputAndOutput {
    public static void main(String[] args) {
        /**
         * 标准输入 键盘
         * System.in 是System类中的一个属性 public static final InputStream in = null;
         * 编译类型 InputStream
         * 运行类型 BufferedInputStream
         */
        System.out.println(System.in.getClass());
        /**
         * 标准输出 显示器
         * System.out 是System类中的一个属性 public static final PrintStream out = null;
         * 编译类型 PrintStream (打印流)
         * 运行类型 PrintStream
         */
        System.out.println(System.out.getClass());
        /**
         * 案例
         */
        // 表示Scanner从键盘获取数据
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入内容:");
        System.out.println(sc.nextLine());
        // 表示标准输出到显示器上
        System.out.println("Hello World");
    }
}
转换流
InputStreamReader 和 OutputStreamWriter
见名知其意:字节流 --> 字符流
代码问题
package javaSEStudy.IO.transformation;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
// 中文乱码问题
public class CodeQuestion {
    public static void main(String[] args) throws IOException {
        /**
         * 读取data目录中的new1.txt 文件
         * 1.创建字符流 BufferedReader (处理流)
         * 2.使用BufferedReader 对象读取new1.txt文件
         * 3.在默认情况下是按照 utf-8  编码
         * 4.倘若文件不是 utf-8 编码,就会产生乱码
         */
        String filePath ="D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\new1.txt";
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        String line;
        while((line = br.readLine()) != null) {
            System.out.println("读取到的内容: "+line);
        }
        br.close();
    }
}
这里将new1.txt编码改为gbk,就产生了乱码问题

介绍
- InputStreamReader ---> Reader 的子类,可以将InputStream(字节流)包装(转换)成Reader(字符流)
 - OutputStreamWriter ---> Writer 的子类,实现将OutputStream(字节流)包装成Writer(字符流)
 - 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效处理中文问题,所以建议将字节流转换成字符流
 - 可以在使用时指定编码格式(如:utf-8,gbk,gb2312,ISO8859-1等)
 


使用案例:
将字节流(InputStream) 包装成 字符流 (InputStreamReader),对文件进行读取(utf-8/gbk)格式,进而包装成 BufferedReader
package javaSEStudy.IO.transformation;
import java.io.*;
// 使用InputStreamReader 转换流解决中文乱码问题
// 将字节流FileInputStream转换为字符流 InputStreamReader,编码格式为GBK/utf-8
public class InputStreamReader_ {
    public static void main(String[] args) throws IOException {
        // 文件路径
        String filePath ="D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\new1.txt";
        // 将 FileInputStream 转成了 InputStreamReader
        // 指定编码为 GBK
        InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath),"GBK");
        // 最终用 BufferReader 处理流进行操作
        BufferedReader br = new BufferedReader(isr);
        // 读取
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        br.close();
        // 压缩写法,并且该写法会自动关闭所有流
        try(BufferedReader br2 = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"GBK"))) {
            br2.lines().forEach(System.out::println);
        }
    }
}
最终将乱码成功输出

使用案例:
将FileOutputStream(字节流) 转成 OutputStreamWriter(字符流),指定编码GBK/utf-8
package javaSEStudy.IO.transformation;
import java.io.*;
// OutputStreamWriter 使用,将FileOutputStream(字节流) 转成 OutputStreamWriter(字符流),指定编码GBK/utf-8
// 要求虽说是件字节流转换成字符流,但是其本质是 使用字符编码将字符转换成字节,最后将字节写入底层的字节输出流
// 所以该转换其实是将 字符流转换成字节流
public class OutputStreamWriter_ {
    public static void main(String[] args) throws IOException {
        // 文件路径
        String filePath ="D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\new2.txt";
        // 编码格式
        String charSet = "GBK";
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath),charSet);
        osw.write("hello,你在干嘛");
        osw.close();
        System.out.println("GBK编码成功");
    }
}
打印流
PS: 打印流只有输出流,没有输入流
PrintStream
本质上是一个字节流

不仅可以将打印内容打印到显示器上,也可以打印到文件中
package javaSEStudy.IO.outputStream;
import java.io.IOException;
import java.io.PrintStream;
// 演示 字节打印流
public class PrintStream_ {
    public static void main(String[] args) throws IOException {
        // 最简单用法
        PrintStream out = System.out;
        out.println("Hello World");
        // 因为print的底层使用的是write,所以我们可以直接调用write直接进行打印输出/输出
        out.write("你好".getBytes());
        out.close();
        // 我们可以去修改打印流输出的位置/设备
        // 修改位置
        System.setOut(new PrintStream("D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\new3.txt"));
        // 下面这段话就会被输入到new3.txt文件中
        System.out.println("基尼太美");
    }
}
PrintWriter
本质上是一个字符流

package javaSEStudy.IO.Writer;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
// 演示PrintWriter使用方法
public class PrintWriter_ {
    public static void main(String[] args) throws IOException {
        // 这种写法就会将其输出到控制台上
//        PrintWriter pw = new PrintWriter(System.out);
        // 将传入的内容输出到该文件中
         String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\NO1.txt";
        PrintWriter pw = new PrintWriter(new FileWriter(filePath));
        pw.println("Hello World");
        // 如果不关闭是无法将内容输入到目录或文件中
        // 相当于 flush + 关闭流
        pw.close();
    }
}
Properties类
在集合中有说过,这是一个配置类
看需求:
如下一个配置文件 mysql.properties
ip=192.168.0.13
user=root
pwd=123456
问题: 编程读取ip,user和pwd的值是多少
传统解决方式
package javaSEStudy.IO.properties_;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
// 读取配置文件传统方法
public class PropertiesTradition {
    public static void main(String[] args) throws IOException {
        // 数据路径
        String filePath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\data\\mysql.properties";
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        String line;
        while ((line = br.readLine()) != null) {
            String[] split = line.split("=");
//            System.out.println(split[0] + " 的值是: " + split[1]);
            // 如果只需要ip
            if ("ip".equals(split[0])) {
                System.out.println(split[1]);
            }
        }
        br.close();
    }
}
介绍
- 
1.专门用于读写配置文件的集合类
配置文件格式
键=值
键=值 - 
2.注意:键值对不需要有空格,值不需要用引号括起来,默认类型是String
 
常见方法
- load:加载配置文件的键值对到Properties对象
 - list:将数据显示到指定设备/流对象
 - getProperties(key):根据键获取值
 - setProperties(key,value):设置键值对到Properties对象(如果键值对不存在就是创建,存在就是修改)
 - store:将Properties中的键值对存储到配置文件中,在idea中,信息保存到配置文件,如果含有中文,会存储为unicode码
 
读取配置文件
package javaSEStudy.IO.properties_;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
// 使用Properties类读取mysql.properties文件
public class PropertiesRead {
    public static void main(String[] args) throws IOException {
        // 1.创建对象
        Properties properties = new Properties();
        // 2.加载指定的配置文件
        properties.load(new FileReader("src\\javaSEStudy\\IO\\data\\mysql.properties"));
        // 3.将键值对显示到控制台
        properties.list(System.out);
        // 4.根据key获取对应的值
        System.out.println("用户名: "+properties.getProperty("user"));
        System.out.println("密码: "+properties.getProperty("pwd"));
    }
}
使用Properties类方法对k-v修改等
package javaSEStudy.IO.properties_;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
// 使用Properties类方法详解
public class PropertiesMethod {
    public static void main(String[] args) throws IOException {
        // 使用Properties类来创建,配置文件,修改配置文件内容
        Properties properties = new Properties();
        // 1.创建配置信息
        // 如果该文件有key,就是修改,没有key就是创建
        // Properties的父类就是HashTable,所以底层就是HashTable
        properties.setProperty("charset", "utf8");
        // 保存时,是中文的unicode码
        properties.setProperty("user", "汤姆");
        properties.setProperty("pwd", "111");
        properties.setProperty("pwd", "666");
        // 2.将信息存储到文件中,在这里comments是注释,如果需要就 "这是一个注释"该写法,不需要则null,依旧中文使用unicode编码
        properties.store(new FileOutputStream("src\\javaSEStudy\\IO\\data\\mysql2.properties"),"这是一个注释");
        System.out.println("保存配置文件成功");
    }
}
测试
第一问

package javaSEStudy.IO.homeWork;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
// 判断homeWork文件夹中是否有myTemp,没有就创建
// 在该目录下创建test1.txt
// 如果已经存在,就提示不要重复创建该文件
public class Test1 {
    public static void main(String[] args) throws IOException {
        String directoryPath = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\homeWork\\myTemp";
        File file = new File(directoryPath);
        if (!file.exists()) {
            file.mkdir();
            System.out.println("创建目录成功");
        } else {
            System.out.println("创建目录失败");
        }
        String filePath = directoryPath + "\\test1.txt";
        File file1 = new File(filePath);
        if (file1.exists()) {
            System.out.println("该文件已存在,请勿重复创建");
        } else {
            if (file1.createNewFile()) {
                System.out.println("目录创建成功");
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file1));
                bos.write("hello world".getBytes());
                bos.close();
            } else {
                System.out.println("目录创建失败");
            }
        }
    }
}
第二问

package javaSEStudy.IO.homeWork;
import java.io.*;
// 使用BufferedReader读取文本文件,并为每行加上行号,在连同内容一并输出到控制台
public class Test2 {
    public static void main(String[] args) throws IOException {
        // 文件编码为默认utf-8
        String filePath1 = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\homeWork\\myTemp\\test2.txt";
        BufferedReader br1 = new BufferedReader(new FileReader(filePath1));
        String line;
        int count = 0;
        while ((line = br1.readLine()) != null) {
            ++count;
            System.out.println(count + " " + line);
        }
        br1.close();
        System.out.println("----------------------------------------------------------");
        // 当文件编码发生变化,需要指定默认编码
        String filePath2 = "D:\\IdeaProject\\xie\\src\\javaSEStudy\\IO\\homeWork\\myTemp\\test2(copy).txt";
        InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath2), "GBK");
        BufferedReader br2 = new BufferedReader(isr);
        while ((line = br2.readLine()) != null) {
            ++count;
            System.out.println(count + " " + line);
        }
        br1.close();
    }
}
第三问

package javaSEStudy.IO.homeWork;
import java.io.*;
import java.util.Properties;
// 编写dog.Properties,再完成一个Dog类,读取dog.Properties类相应内容完成初始化
public class Test3 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String path1 = "src\\javaSEStudy\\IO\\homeWork\\myTemp\\dog.properties";
        Properties properties = new Properties();
        properties.load(new FileReader(path1));
        Dog dog = new Dog(properties.getProperty("name"),
                Integer.parseInt(properties.getProperty("age")),
                properties.getProperty("color"));
        System.out.println(dog);
        // 将其序列化
        String path2 = "src\\javaSEStudy\\IO\\homeWork\\myTemp\\dog.dat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path2));
        oos.writeObject(dog);
        System.out.println("序列化成功");
        oos.close();
        System.out.println("-------------------------------------");
        // 反序列化查看是否存储正确
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path2));
        System.out.println("查看序列化的内容");
        System.out.println(ois.readObject());
        ois.close();
    }
}
class Dog implements Serializable {
    private String name;
    private int age;
    private String color;
    public Dog(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}
                    
                
                
            
        
浙公网安备 33010602011771号