# Java-IO流

# 文件

# 创建文件

三种重载方式

new File(String pathname); // 根据路径构建一个file对象
new File(File parent,String child); // 根据父目录文件+子路径创建
new File(String parent,String child); // 根据父目录+子路径创建

注意:在java中目录也是一种文件,这样可以提供一致的API

# 文件的常见操作

文件的常用信息:

  • getName

  • getAbsolutePath

  • getParent

  • getPath(创建文件时传入的路径)

  • length

  • isFile

  • isDirectoryexists

创建与删除文件夹:

  • mkdir:创建单级目录
  • mkdirs:创建多级目录(推荐)
  • delete:删除空目录或文件

# IO流原理及分类

# 流的分类

  • 按操作数据单位的不同分为:字节流(8bit)二进制文件,字符流(按字符)文本文件
  • 按数据流的流向不同分为:输入流、输出流
  • 按流的角色不同分为:节点流,处理流和包装流
抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

Java的IO涉及40多个类,都是派生自上面的4个基类

# 流的体系图

io-stream

# IO流体系图-常用类

# FileInputStream

  • 单个字节读取
/**
 * 单个字节读取,效率比较低
 */
@Test
public void test01(){
    String path = "g:/hello.txt";
    int readData = 0;
    FileInputStream inputStream = null;
    try {
        inputStream = new FileInputStream(path);
        // 每次读取一个字节,如果到文件末尾会返回-1
        while( (readData = inputStream.read()) != -1){
            System.out.print((char)readData);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流,释放资源
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 每次读取多个字节
/**
 * 使用read(byte[] b)方法,每次读取多个字节
 */
@Test
public void test02(){
    String path = "g:/hello.txt";
    int readLen = 0;
    // 字节数组
    byte[] buffer = new byte[8];

    FileInputStream inputStream = null;
    try {
        inputStream = new FileInputStream(path);
        // 每次读取buffer的长度的数据,存入buffer
        // 如果读取正常,返回实际读取的字节数
        // 返回-1表示读取完毕
        while( (readLen = inputStream.read(buffer)) != -1){
            // String构造器可以传入字节数组,偏移量,数组长度
            System.out.print(new String(buffer,0,readLen));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流,释放资源
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

# FileOutputStream

@Test
public void writeFile(){
    // 创建Fileout
    String path = "g:/a.txt";
    FileOutputStream fileOutputStream = null;
    try {
        // 得到FileInputStream对象
        // 1. new FileOutputStream(path)  写入内容会覆盖原来的内容
        // 2. new FileOutputStream(path,true)  写入内容会追加原来的内容
        fileOutputStream = new FileOutputStream(path,true);
        // 写入一个字节
        //fileOutputStream.write('H');

        // 写入一个字符串
        String str = "hello,world!";
        // str.getBytes()把字符串转为字节数组
        //fileOutputStream.write(str.getBytes());

        // write(byte[] b, int off, int len) 从off位置开始读取len长度的字节到输出流
        fileOutputStream.write(str.getBytes(),0,str.length());

    } catch (IOException e){
        e.printStackTrace();
    }finally {
        try {
            fileOutputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

注意:

  • UTF8编码:一个英文字符占1个字节,一个中文字符占3个字节(扩展汉字可能为4个)
  • GBK编码:一个英文字符占1个字节,一个中文字符占2字节

# FileReader

  • 每次读取单个字符
@Test
public void test01(){
    String filePath = "g:/a.txt";
    FileReader fileReader = null;
    int data = 0;
    try {
        fileReader = new FileReader(filePath);
        // 每次读取一个字符
        while((data = fileReader.read()) != -1){
            System.out.print((char) data);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (fileReader != null) {
                fileReader.close();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 每次读取多个字符
@Test
public void test02(){
    String filePath = "g:/a.txt";
    FileReader fileReader = null;
    char[] buffer = new char[8];
    int readLen = 0;
    try {
        fileReader = new FileReader(filePath);
        // 每次读取一个字符数组/字符串
        while((readLen = fileReader.read(buffer)) != -1){
            System.out.print(new String(buffer,0,readLen));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (fileReader != null) {
                fileReader.close();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

# FileWriter

@Test
public void test01(){
    String filePath = "g:/note.txt";
    // 创建FileWriter
    FileWriter fileWriter = null;

    char[] chars = {'h','u','y','a'};
    try {
        fileWriter = new FileWriter(filePath,true);
        fileWriter.write('H');
        fileWriter.write(chars);
        fileWriter.write("你好,未来的自己");
        fileWriter.write("孤独的鹿角虫".toCharArray(),0,6);
        fileWriter.write("上海天津",2,2);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // FileWriter必须关闭或刷新,数据才能写入文件
        try {
            //fileWriter.close();
            fileWriter.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

# 节点流和处理流

# 概念

节点流:就是从一个特定的数据源读写数据,如FileReader,FileWriter

处理流:又称包装流,可连接在已存在的流(节点流或处理流),为程序提供更强大的读写能力,也更加灵活,如BufferedReader、BufferedWriter

处理流的作用:

  • 性能提高:增加缓冲来提高输入输出的效率
  • 操作的便捷:处理流可能提供了一系列便捷的方法,一次性输入输出大批量数据,使用更加方便灵活

处理流使用了修饰器设计模式,不会直接与数据源相连

# 字符处理流

  • BufferedReader

缓冲区的作用:减少类对底层输入流的访问频率,提高读取效率

@Test
public void test01() throws Exception {
    String path = "g:/note.txt";
    // 创建
    BufferedReader br = new BufferedReader(new FileReader(path));
    // 读取
    String line;
    // 当返回空表示文件读取完毕
    while((line = br.readLine()) != null){
        System.out.println(line);
    };
    // 关闭流,只需要关闭外层的BufferedReader,底层会自动关闭节点流
    br.close();
}
  • BufferedWriter
@Test
public void test01() throws IOException {
    String path = "g:/ok.txt";
    // 创建
    BufferedWriter bw = new BufferedWriter(new FileWriter(path));
    bw.write("hello,未来的自己!");
    // 插入一个换行,和系统相关
    bw.newLine();
    bw.write("hello1,未来的自己!");
    bw.newLine();
    bw.write("hello2,未来的自己!");
    bw.newLine();
    
    // 关闭外层流即可
    bw.close();
}

# 字节处理流

  • BufferedInputStream
  • BufferedOutputStream
@Test
public void test(){
    String oriPath = "f:/fugang.png";
    String tarPath = "f:/test.png";
    BufferedInputStream is = null;
    BufferedOutputStream os = null;
    byte[] b = new byte[1024];
    int readLen = 0;
    try {
        is = new BufferedInputStream(new FileInputStream(oriPath));
        os = new BufferedOutputStream(new FileOutputStream(tarPath));
        while((readLen = is.read(b)) != -1){
            os.write(b,0,readLen);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流
        try {
            if(is != null){
                is.close();
            }
            if(os != null){
                os.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:字节流既可以处理二进制文件,也能处理文本文件

# 对象处理流

对象处理流是一种字节流

序列化就是指把保存数据时,同时保存数据值和数据类型;反序列化,就是指恢复数据时能同时恢复数据的值和数据类型

需要序列化的类必须实现以下两个接口之一:

  • Serializable
  • Externalizable

案例:

  • ObjectInputStream
public class ObjectOutputStream_ {
    @Test
    public void  test() throws IOException {
        String filePath = "f:/data.dat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

        oos.writeInt(100);
        oos.writeBoolean(true);
        oos.writeChar('a');
        oos.writeDouble(9.5);
        oos.writeUTF("未来可期");
        oos.writeObject(new Dog("旺财",10));
        oos.close();
        System.out.println("数据保存完毕");
    }
}
  • ObjectOutputStream
public class ObjectInputStream_ {
    @Test
    public void test() throws Exception {
        String filePath = "f:/data.dat";
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));

        // 读取
        // 反序列化的顺序需要和序列化的顺序一致,否会出现异常
        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 = ois.readObject();
        System.out.println("运行类型="+dog.getClass());
        System.out.println("对象内容="+dog);
        ois.close();
        System.out.println("数据读取完毕");

        // 对象在序列化和反序列化是所在的包必须保持一致,可以将Dog类的定义放在可以引用的位置
        // 要调用dog的方法,需要向下转型
        Dog dogObj = (Dog )dog;
        System.out.println(dogObj.getName());
    }
}
  • Dog类
public class Dog implements Serializable {
    private String name;
    private Integer age;

    public Dog(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

注意:

  1. 读写顺序要一致
  2. 序列化和反序列化对象需要实现,Serializable
  3. 序列化的类建议添加SerialVersionUID,为了提高版本兼容性
  4. 序列化对象时,默认将所有属性序列化,除了statictransient修饰的成员
  5. 序列化对象时,要求对象属性的类型也要实现序列化接口
  6. 序列化具备可继承性,如果某个类已经实现了序列化,则它的所有子类也默认实现了序列化

# 标准输入输出流

标准流也是一种字节流

public class InputAndOutput {
    public static void main(String[] args) {
        // System.in 的编译类型 InputStream
        // System.in 的运行类型 BufferedInputStream
        // 表示标准输入 键盘
        System.out.println(System.in.getClass());

        // System.out 的编译类型 PrintStream
        // System.out 的运行类型 PrintStream
        // 表示标准输出 显示器
        System.out.println(System.out.getClass());
    }
}

# 转换流

可以把字节流转字符流

  • InputStreamReader:

20240730113538

@Test
public void test01() throws IOException {
    String filePath = "f:/a.txt";
    // 字节流转字符流,并制定编码为gbk
    InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath),"gbk");
    // 转BufferedReader
    BufferedReader br = new BufferedReader(isr);
    // 读取
    String s = br.readLine();
    System.out.println("读取到的内容:"+s);
    br.close();
}
  • OutputStreamWriter:可以传入一个输出流,并指定字符集

20240730113328

@Test
public void test() throws IOException {
    String filePath = "f:/rong.txt";
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), "gbk");
    osw.write("hello,你好呀!");
    osw.close();
    System.out.println("按照gbk保存文件");
}

# 打印流

打印流只有输出流

# PrintStream

@Test
public void test() throws IOException {
    PrintStream out = System.out;
    out.print("huya dish");
    // print底层调用的write
    out.write("你好".getBytes());
    out.close();

    // 可以控制输出位置
    System.setOut(new PrintStream("f:/sout.txt"));
    // 这句话会输出到文件中
    System.out.println("hello 鹿角虫");
}

# PrintWriter

@Test
public void test() throws IOException {
    PrintWriter pw = new PrintWriter(new FileWriter("f:/out.txt"));
    pw.print("你好,未来");
    pw.close();
}

# Properties类

用于读取.properties配置文件

@Test
public void test() throws IOException {
    // 使用Properties类读取配置文件
    Properties properties = new Properties();
    // 加载指定配置文件
    properties.load(new FileReader("src/mysql.properties"));
    // 把k-v打印到控制台
    properties.list(System.out);
    // 根据key获取指定值
    System.out.println(properties.getProperty("user"));
    // 根据key设置指定值
    properties.setProperty("password","鹿角虫");
    properties.setProperty("ip","192.168.0.1");
    // 重新输出
    properties.list(System.out);
    // 输出到文件,如果有中文,默认会保存为unicode,第二个参数为注释
    properties.store(new FileOutputStream("src/test.pproperties"),"hello world");
    System.out.println("保存配置文件成功");
}
Last Updated: 12/23/2024, 4:18:13 AM