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

news2025/3/22 12:44:07

系列文章目录

   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.coloradmin.cn/o/2319570.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爬虫入门re+bs4

目录 前言 1. 导入必要的库 2. 定义获取网页HTML内容的函数 get_html 3. 定义获取数据的函数 get_data 4. 定义获取文章正文内容的函数 content_text 5. 定义获取单条课程数据的函数 get_one_course_data 6. 定义保存数据的函数 save_data 7. 定义文件名合法化处理函数 sanitiz…

MySQL身份验证的auth_socket插件

在Ubuntu 20.04 LTS上,MySQL 8.0默认使用auth_socket插件进行身份验证,可能存在意想不到的情况。 一、auth_socket插件 在使用sudo mysql或通过sudo切换用户后执行任何MySQL命令时,不需要输入密码或错误密码都可以正常登入mysql数据库&…

使用Gitee Go流水线部署个人项目到服务器指南

使用Gitee Go流水线部署个人项目到服务器指南 前言!!! 本文解决的问题: 你有一台ECS服务器,你在上面部署了一个Java服务也就是一个jar,你觉着你每次手动本地打包,上传,在通过命令去…

BlockChain.java

BlockChain 区块链,举个栗子 注意啦,列子里面的hashcode相等,但是字符串是不一样的哦,之前有记录这个问题 String.hashCode()-CSDN博客

【技术简析】触觉智能RK3506 Linux星闪网关开发板:重新定义工业物联新标杆

在工业智能化与物联网深度融合的今天,深圳触觉智能推出首款搭载瑞芯微RK3506芯片的Linux星闪网关开发板,为大家技术解析。 RK3506-国产芯的硬核实力 作为瑞芯微2024年第四季度推出的入门级工业芯片平台,RK3506以三核Cortex-A7(1.…

YOLO-UniOW: 高效通用开放世界目标检测模型【附论文与源码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

面向对象(进阶)(‘封装‘,‘多态‘,‘对象属性‘,‘类属性‘,‘类方法‘,‘对象方法‘及其应用场景)

‘封装’,多态’及其应用场景 一, 封装及其应用场景 封装的定义 属于面向对象的三大特征之一, 就是隐藏对象的属性和现实细节, 仅对外提供公共的访问方式.(我们学的 函数, 类, 都是封装的体现). 封装的格式 __属性名 __属性名()封装的好处和弊端 好处 提高代码的安全性.…

算法模型从入门到起飞系列——广度优先遍历(BFS)

文章目录 前言一、广度优先遍历(BFS)简介1.1 广度优先遍历(BFS)的特点1.2 广度优先遍历(BFS)的要素 二、广度优先遍历(BFS)& 深度优先遍历(DFS)2.1 广度优…

<项目> 主从Reactor模型的高并发服务器

目录 Reactor 概念 分类 单Reactor单线程 单Reactor多线程 多Reactor多线程 项目介绍 项目规划 模块关系 实现 TimerWheel -- 时间轮定时器 定时器系统调用 时间轮设计 通用类型Any Buffer Socket Channel Poller EventLoop(核心) eventfd 设计思路 …

注意力机制,本质上是在做什么?

本文以自注意机制为例,输入一个4*4的矩阵 如下: input_datatorch.tensor([[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16] ],dtypetorch.float) 得到Q和K的转置如下。 此时,计算QK^T ,得到如下结果 第一行第一个位置就是第一条样本和第…

使用Python在Word中创建、读取和删除列表 - 详解

目录 工具与设置 Python在Word中创建列表 使用默认样式创建有序(编号)列表 使用默认样式创建无序(项目符号)列表 创建多级列表 使用自定义样式创建列表 Python读取Word中的列表 Python从Word中删除列表 在Word中&#xff…

右键添加:新建HTML模板文件

使用注册表给Windows右键添加:新建HTML文档模板的功能_注册表右键新建-CSDN博客 新建文件有了,但是没有引用模板文件,是空文件。 默认改成 htmlfile 模板成功

Windows10配置OpenJDK11

下载 # 华为OpenJDK镜像源 https://mirrors.huaweicloud.com/openjdk/11.0.2/解压 # 解压后至于C:\Dev\Env\Java\jdk-11.0.2目录下 https://mirrors.huaweicloud.com/openjdk/11.0.2/openjdk-11.0.2_windows-x64_bin.zip编译安装 # 以管理员身份运行 CMD命令提示符 并进入JD…

统一开放世界与开放词汇检测:YOLO-UniOW无需增量学习的高效通用开放世界目标检测框架

目录 一、摘要 二、引言 三、相关工作 开放词汇对象检测 开放世界目标检测 参数高效学习 四、高效通用的开放世界目标检测 问题定义 高效的自适应决策学习 开放世界通配符学习 五、Coovally AI模型训练与应用平台 六、实验 数据集 评价指标 实施细节 定量结果 …

如何给商品一键换色?图生生AI,告别繁琐修图

在电商竞争日益激烈的今天,商品图片的视觉效果直接影响着消费者的购买决策。而商品颜色的展示,更是重中之重!传统的图片换色方式,往往需要耗费设计师大量的时间和精力,从抠图到调色,再到细节调整&#xff0…

练习-班级活动(map存储键值对)

问题描述 小明的老师准备组织一次班级活动。班上一共有 n 名 (n 为偶数) 同学,老师想把所有的同学进行分组,每两名同学一组。为了公平,老师给每名同学随机分配了一个 n 以内的正整数作为 id,第 i 名同学的 id 为 ai​。 老师希望…

OpenHarmony 开源鸿蒙北向开发——hdc工具安装

hdc(OpenHarmony Device Connector)是为开发人员提供的用于设备连接调试的命令行工具,该工具需支持部署在 Windows/Linux/Mac 等系统上与 OpenHarmony 设备(或模拟器)进行连接调试通信。简单来讲,hdc 是 Op…

buu-bjdctf_2020_babystack2-好久不见51

整数溢出漏洞 将nbytes设置为-1就会回绕,变成超大整数 从而实现栈溢出漏洞 环境有问题 from pwn import *# 连接到远程服务器 p remote("node5.buuoj.cn", 28526)# 定义后门地址 backdoor 0x400726# 发送初始输入 p.sendlineafter(b"your name…

【Java SE】抽象类/方法、模板设计模式

目录 1.抽象类/方法 1.1 基本介绍 1.2 语法格式 1.3 使用细节 2. 模板设计模式(抽象类使用场景) 2.1 基本介绍 2.2 具体例子 1.抽象类/方法 1.1 基本介绍 ① 当父类的某些方法,需要声明,但是又不确定如何实现时&#xff…

Unix 域套接字(本地套接字)

Unix 域套接字(Unix Domain Sockets),也称为本地套接字(Local Sockets),是一种用于同一主机上进程间通信(IPC)的机制。Unix 域套接字提供了一种高效的进程间通信方式,它利…