当前位置: 首页 > article >正文

JavaIO流的使用和修饰器模式(直击心灵版)

系列文章目录

   JavaIO流的使用和修饰器模式


文章目录

  • 系列文章目录
  • 前言
  • 一、字节流:
    • 1.FileInputStream(读取文件)
    • 2.FileOutputStream(写入文件)
  • 二、字符流:
    • 1..基础字符流:
    • 2.处理流:
    • 3.对象处理流:
    • 4.转换流:
  •  三、修饰器模式
  • 总结


前言

  前面我们讲解了Java文件和IO流的基础部分。把流简单的分了一下类,但是我们还不知道具体是如何是使用的,下面我们将详细的讲解一下这些个流各自的职责是什么,简言之就是各自的使用方式。然后我还想给大家强戴一下IO流当中的修饰器模式,因为这个实际上通过封装真的太牛逼了。


          我先给大家按字节流和字符流的分类方式来进行讲述:

一、字节流:

       用于处理二进制数据(如图片、视频、任何文件)​,核心类为 InputStream 和 OutputStream

        (1)FileInputStream(读取文件)

            每次调用 read() 方法从磁盘读取1字节,频繁IO操作性能差。
  适用场景:小文件读取或需要逐字节处理的场景。

try (InputStream in = new FileInputStream("test.jpg")) {
    int byteData;
    while ((byteData = in.read()) != -1) { // 每次读取1字节
        // 处理字节(例如加密、校验)
        System.out.print((char)byteData + " ");
    }
} catch (IOException e) {
    e.printStackTrace();
}

        我们要注意,这样单个字节读取,如果文件当中有汉字就不行了。                  

 所以进阶版可以用int read(byte[] b)方法来读取,这个方法底层是从该输入流中读取最多b.length字节数据到字节数组,如果读取正常,返回实际读取字节数, -1表示的是读取完毕了。但记得最后还要转换为字符串 new String(buf,0,readlen).

           (2) FileOutputStream(写入文件)

        注意点:若文件不存在会自动创建,若存在默认覆盖(通过构造参数可设置为追加模式)。

// 第二个参数 true 表示追加写入
try (OutputStream out = new FileOutputStream("log.txt", true)) {
    String logEntry = "Error occurred at " + new Date() + "\n";
    out.write(logEntry.getBytes(StandardCharsets.UTF_8)); // 显式指定编码
} catch (IOException e) {
    e.printStackTrace();
}

这里还有一处细节要注意,就是这样创建,写入内容会覆盖原来的内容,但如果是这样创建的 new FileOutputStream(filepath,true),这样再写入内容就会追加到文件后面。

二、字符流

1.基础字符流:

  (1)FileReader 读文件

  这里循环读取使用read()是单个字符读取,使用read(buf)返回的是实际取到的字符数.

  (2)FileWriter      写文件

这里面注意一定要关闭流,或者Flush才能真正的把数据写入到文件

2.处理流:

BufferedReader 和 BufferedWriter:

readLine() 可逐行读取文本。

// 读取CSV文件并解析
try (BufferedReader br = new BufferedReader(
        new FileReader("data.csv"))) {
    String line;
    while ((line = br.readLine()) != null) {
        String[] columns = line.split(",");
        // 处理每一列数据
    }
}

// 写入带换行的文本
try (BufferedWriter bw = new BufferedWriter(
        new FileWriter("output.txt"))) {
    bw.write("Line 1");
    bw.newLine();  // 跨平台换行(Windows为\r\n,Linux为\n)
    bw.write("Line 2");
}

像BufferedReader类中,有属性Reader,即可以封装一个节点流 (该节点流可以是任意的,只要是Reader的子类就行,这个我们下面讲修饰器模式再讲)。

details:

1.BufferedReader 和 BufferedWriter都是按照字符操作的。

2.不要去操作二进制文件(如声音,视频等)可能会造成文件损坏。

   总结:

场景正确流类型原因
图片、视频、EXE文件字节流直接处理原始字节,避免编解码干扰
文本文件(.txt)字符流正确处理字符编码(如UTF-8、GBK)
混合数据(如PDF)字节流PDF包含文本和二进制结构,需精确控制字节
网络传输数据字节流网络协议基于字节,而非字符

所以说字符流是“文本专用工具”,操作二进制文件就像用剪刀拧螺丝——不仅费力,还可能搞砸!

  3.对象处理流:

  能够将基本数据类型或者对象进行序列化和反序列化的操作。

      这里我们需要注意的是如果需要让某个对象支持序列化机制,则必须让其类是可序列化的,而为了让某个类是可序列化的,该类必须实现如下两个接口之一:

    Serializable  和   Externalizable  我们常用的是Serializable接口,因为它不用再重写方法了。

    

class User implements Serializable {
    private static final long serialVersionUID = 1L; // 版本号
    private String name;
    private transient String password; // transient字段不会被序列化
}

// 序列化对象到文件
User user = new User("Alice", "secret");
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("user.dat"))) {
    oos.writeObject(user);
}

// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("user.dat"))) {
    User restoredUser = (User) ois.readObject();
    System.out.println(restoredUser.getName()); // 输出 "Alice"
    System.out.println(restoredUser.getPassword()); // 输出 null(transient字段)
}

     注意读取(反序列化)的顺序需要和保存数据(序列化)的顺序一致,否则会出现异常。

还有最容易忽略的一点就是序列化对象时,要求里面的属性的类型也需要实现序列化接口。

  序列化对象时,默认将里面所有属性都会进序列化,除了static或者transient修饰的成员。

    4.转换流:

乱码的本质是 ​字符编码不匹配

  1. 写入时:文本按编码A(如UTF-8)转换为字节。
  2. 读取时:字节按编码B(如GBK)解码为字符。
  3. 结果:编码A和编码B的映射关系不同,导致字符显示错误。

转换流的作用

类名功能核心价值
InputStreamReader将字节流(InputStream)按指定编码转换为字符流解决读取时的编码问题
OutputStreamWriter将字符流按指定编码转换为字节流(OutputStream解决写入时的编码问题
try (Reader reader = new InputStreamReader(
        new FileInputStream("utf8_file.txt"), StandardCharsets.UTF_8)) {
    // 正确读取中文字符
    int data;
    while ((data = reader.read()) != -1) {
        System.out.print((char) data);
    }
}






try (Reader reader = new InputStreamReader(
        new FileInputStream("utf8_file.txt"), StandardCharsets.UTF_8)) {
    // 正确读取中文字符
    int data;
    while ((data = reader.read()) != -1) {
        System.out.print((char) data);
    }
}

综上可知,学习IO流我们必须要知道什么时候使用什么流。

   三、修饰器模式:

  其实我在学习的过程中也很疑惑这个修饰器模式到底有什么用,不就是像套娃一样一层套着一层吗,但是当我们真正理解了才发现Java设计者有多牛逼。

        像以BufferedInputStream举例:

BufferedInputStream 的缓冲机制

  • 内部缓冲区BufferedInputStream 维护一个字节数组(默认大小 8KB),用于临时存储从底层流读取的数据。
  • 读取逻辑
    1. 当用户调用 read() 时,BufferedInputStream 会优先从缓冲区读取数据
    2. 如果缓冲区为空,它会一次性从底层 InputStream(如 FileInputStream)读取一批数据(填满缓冲区)。
    3. 后续的 read() 直接从缓冲区返回数据,直到缓冲区耗尽,再重复步骤 2。

   

  • 数据来源BufferedInputStream 本身不连接任何数据源(如文件、网络等),它只是一个“功能增强包装器”。
  • 依赖关系:缓冲流需要底层流提供原始数据,而 FileInputStream 是唯一能直接读取文件的节点流。

   装饰器模式(Decorator Pattern)的核心思想是 ​动态地为对象添加功能,同时保持接口的一致性。

  举一个咖啡加料的例子:

     假设你经营一家咖啡店,需要灵活组合咖啡和配料(如牛奶、糖),但不想为每种组合创建子类(如 MilkSugarCoffeeSugarCoffee 等)。装饰器模式可以完美解决这个问题

     1.定义基础组件:

      

// 基础接口:咖啡
public interface Coffee {
    double getCost();
    String getDescription();
}

// 具体组件:基础咖啡
public class SimpleCoffee implements Coffee {
    @Override
    public double getCost() { return 2.0; }

    @Override
    public String getDescription() { return "基础咖啡"; }
}

     2. 定义装饰器基类:

   

// 装饰器基类:实现 Coffee 接口,并持有一个 Coffee 对象
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    // 委托给被装饰的 Coffee 对象
    @Override
    public double getCost() { return decoratedCoffee.getCost(); }

    @Override
    public String getDescription() { return decoratedCoffee.getDescription(); }
}

     3. 具体修饰器:牛奶或糖:

     

// 牛奶装饰器
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double getCost() { return super.getCost() + 0.5; }

    @Override
    public String getDescription() { return super.getDescription() + "+牛奶"; }
}

// 糖装饰器
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double getCost() { return super.getCost() + 0.2; }

    @Override
    public String getDescription() { return super.getDescription() + "+糖"; }
}

     4.使用修饰器的动态组合: 

public class Main {
    public static void main(String[] args) {
        // 基础咖啡
        Coffee coffee = new SimpleCoffee();
        System.out.println(cost: " + coffee.getCost() + ", desc: " + coffee.getDescription());

        // 加牛奶
        coffee = new MilkDecorator(coffee);
        System.out.println(cost: " + coffee.getCost() + ", desc: " + coffee.getDescription());

        // 再加糖
        coffee = new SugarDecorator(coffee);
        System.out.println(cost: " + coffee.getCost() + ", desc: " + coffee.getDescription());
    }
}

而在IO流中:

  • 组件接口InputStream(所有输入流的基类)。
  • 具体组件FileInputStream(直接操作文件的节点流)。
  • 装饰器基类FilterInputStream(实现 InputStream,并持有 InputStream 对象)。
  • 具体装饰器BufferedInputStream(扩展 FilterInputStream,添加缓冲功能)。
// 节点流:直接读取文件
InputStream fileStream = new FileInputStream("data.txt");

// 装饰器:添加缓冲功能
InputStream bufferedStream = new BufferedInputStream(fileStream);

// 可以继续装饰:例如添加解密功能(假设有 DecryptInputStream)
InputStream decryptedStream = new DecryptInputStream(bufferedStream);

当调用 bufferedStream.read() 时:

  1. 检查缓冲区:如果有数据,直接返回。
  2. 缓冲区为空:调用底层 fileStream.read(byte[]) 批量读取数据到缓冲区。
  3. 返回数据:从缓冲区返回一个字节。

其实吧,处理流(如 BufferedInputStream)需要传入 InputStream 对象的核心目的,正是为了在自己的成员方法中调用底层流的 read 方法,并在其基础上添加额外功能(如缓冲、编码转换等)。这是装饰器模式的精髓所在。


总结

以上就是今天要讲的内容,本文仅简单的讲述了IO流分类后的使用和例子,然后讲了一下修饰器模式,接下来我会一直持续更新,谢谢大家。


http://www.kler.cn/a/596196.html

相关文章:

  • 解释什么是受控组件和非受控组件
  • git 查看某个函数的所有提交日志
  • Web爬虫利器FireCrawl:全方位助力AI训练与高效数据抓取。本地部署方式
  • 【入门初级篇】报表基础操作与功能介绍
  • 大数据处理最容易的开源平台
  • 基于Python编程语言实现“机器学习”,用于车牌识别项目
  • Android Audio基础(52)—— ASoC的PCM逻辑设备
  • AGI成立的条件
  • jieba中文分词模块,详细使用教程
  • 基于 PyTorch 的 MNIST 手写数字分类模型
  • 学习笔记:黑马程序员JavaWeb开发教程(2025.3.21)
  • 卷积神经网络 - 汇聚层
  • 使用Three.js渲染器创建炫酷3D场景
  • m4i.22xx-x8系列-PCIe总线直流耦合5G采集卡
  • 基于Django的动物图像识别分析系统
  • 阿里云平台Vue项目打包发布
  • EtherCAT 八口交换机方案测试介绍,FCE1100助力工业交换机国产芯快速移植。
  • 《Python实战进阶》No26: CI/CD 流水线:GitHub Actions 与 Jenkins 集成
  • DeepSeek 3FS 与 JuiceFS:架构与特性比较
  • C++《红黑树》