用properties+纯bio实现商品配置持久化,以goods.conf文本文件存储键值对,通过fileinputstream/fileoutputstream完成加载与保存,配合scanner构建控制台cli,支持set/get/list命令,每次save全量重写文件,零依赖、易读易改、适合教学与轻量场景。

用最纯粹的 BIO(Blocking I/O)读写思维实现商品配置持久化,核心是不依赖任何框架、ORM 或高级抽象,只靠 java.io 包中的 FileInputStream / FileOutputStream、BufferedReader / BufferedWriter 等基础类,以“一行一商品”或“键值对”形式在控制台脚手架中完成读取、修改、保存。
这种方式适合教学演示、轻量级本地工具、或嵌入式调试场景——它不追求并发、不处理网络、不抽象模型,只聚焦“人敲命令 → 程序读文件 → 改内存对象 → 写回文件”这一条清晰路径。
1. 商品配置的数据格式要简单直接
选纯文本、易读易改的格式,比如 properties 风格(推荐)或 CSV:
# goods.conf apple.id=1001 apple.name=红富士苹果 apple.price=8.5 apple.stock=120 banana.id=1002 banana.name=进口香蕉 banana.price=12.0 banana.stock=85
✅ 优点:
- 可用记事本直接编辑
-
Properties类原生支持加载/存储 - 无需 JSON 解析库,零依赖
2. 用 Properties + BIO 完成加载与保存
import java.io.*;
import java.util.Properties;
public class GoodsConfig {
private static final String CONFIG_PATH = "goods.conf";
private Properties props;
public GoodsConfig() {
this.props = new Properties();
load(); // 启动时自动读取
}
private void load() {
try (FileInputStream fis = new FileInputStream(CONFIG_PATH)) {
props.load(fis);
} catch (IOException e) {
System.out.println("⚠️ 配置文件不存在或读取失败,将创建空配置");
// 不抛异常,静默初始化
}
}
public void save() {
try (FileOutputStream fos = new FileOutputStream(CONFIG_PATH)) {
props.store(fos, "商品配置 - " + new java.util.Date());
} catch (IOException e) {
System.err.println("❌ 保存失败:" + e.getMessage());
}
}
// 示例:根据商品名获取价格(字符串形式)
public String getPrice(String sku) {
return props.getProperty(sku + ".price", "0.0");
}
// 示例:更新库存
public void updateStock(String sku, int newStock) {
props.setProperty(sku + ".stock", String.valueOf(newStock));
save(); // 立即落盘
}
}
? 关键点:
- 每次
save()都是完整重写整个文件(BIO 的典型做法,非增量) -
load()和save()都用try-with-resources确保流关闭 - 不做线程同步 —— BIO 脚手架默认单线程交互(如 Scanner 控制台输入)
3. 在控制台脚手架中接入增删改查
配合 Scanner 实现简易 CLI:
public class GoodsCLI {
private final GoodsConfig config = new GoodsConfig();
private final Scanner scanner = new Scanner(System.in);
public void run() {
System.out.println("? 商品配置控制台(BIO 原生版)");
while (true) {
System.out.print("> ");
String cmd = scanner.nextLine().trim();
if ("quit".equalsIgnoreCase(cmd)) break;
handleCommand(cmd);
}
System.out.println("? 已退出");
}
private void handleCommand(String cmd) {
String[] parts = cmd.split("\s+", 3);
if (parts.length == 0) return;
switch (parts[0]) {
case "set":
if (parts.length >= 4) {
String sku = parts[1];
String field = parts[2];
String value = parts[3];
config.props.setProperty(sku + "." + field, value);
config.save();
System.out.println("✅ 已保存:" + sku + "." + field + "=" + value);
}
break;
case "get":
if (parts.length == 2) {
String val = config.props.getProperty(parts[1]);
System.out.println(parts[1] + " = " + (val != null ? val : "(未设置)"));
}
break;
case "list":
config.props.stringPropertyNames().stream()
.filter(k -> k.endsWith(".name"))
.map(k -> k.substring(0, k.length() - 5))
.forEach(sku ->
System.out.printf("%s → %s ¥%s ×%s
",
sku,
config.props.getProperty(sku + ".name"),
config.props.getProperty(sku + ".price"),
config.props.getProperty(sku + ".stock")
)
);
break;
default:
System.out.println("支持命令:set <sku><field><value> | get <key> | list | quit");
}
}
}</key></value></field></sku>
运行效果示例:
> set apple price 9.8 ✅ 已保存:apple.price=9.8 > get apple.name apple.name = 红富士苹果 > list apple → 红富士苹果 ¥9.8 ×120
4. 注意事项与边界处理
- 文件路径用相对路径即可,确保
goods.conf和 jar 在同一目录 - 第一次运行时
goods.conf不存在 →load()捕获异常并继续,后续save()会自动创建 - 不校验数据类型(如 price 是否为数字),这是“纯粹 BIO”的取舍:把校验逻辑留给上层或人工
- 如需支持中文,
props.store()默认 ISO-8859-1,可改用storeToXML()或自定义 UTF-8 writer(若必须)
如果坚持完全手动解析(不用 Properties),也可用 BufferedReader.readLine() 逐行切分 =,但 Properties 是 JDK 原生、稳定、符合 BIO 纯粹性的最佳选择。
不复杂但容易忽略。











