Java轻量记账本:纯Swing+文本持久化实战

发布时间:2026/10/20 18:44:52
Java轻量记账本:纯Swing+文本持久化实战 简介这是一款基于Java开发的轻量级个人记账本程序面向编程初学者与Java入门实践者聚焦财务数据管理核心场景帮助用户掌握GUI界面设计、文件持久化CSV/JSON、数据统计可视化及基础安全机制等典型桌面应用开发技能。资源包共104个文件含40个可读性良好的Java源码覆盖DAO层、GUI面板、图表工具、备份监听等模块、46个编译后class文件、8个界面图标PNG、4个依赖JAR包以及项目配置、SQL建表脚本和README说明整体2.32MB结构清晰便于分层学习与调试。已有52人下载学习适合通过完整小项目理解MVC分层思想、Swing组件协作与本地数据流闭环。读者可直接运行体验收支录入、分类统计、饼图展示及密码保护功能并基于源码快速拓展云端同步或主题定制等进阶特性。1. 一个Java版的小小记账本程序轻量、可运行、无依赖适合新手练手与日常记账场景闭环验证你有没有试过下载一个标着“Java记账本”的压缩包解压后双击xxx.jar没反应或者javac Main.java报错说找不到JFrame又或者好不容易跑起来了输入一笔早餐5元关掉再打开——数据全没了这不是玄学是绝大多数“Java小项目”压缩包的真实交付状态。这个标题里的“一个java版的小小记账本程序.zip”不是演示工程、不是教学PPT配套代码而是一个能真正从命令行编译、启动、增删改查、数据落盘、二次修改不崩的最小可行记账本。它不连数据库不用Spring不调API就靠java.awtjavax.swingjava.io三件套把「记一笔→存本地→下次还能读出来」这件事闭环做透。适合两类人一是刚学完Java基础语法、正卡在“写完HelloWorld后不知道该练什么”的新手二是需要快速验证一个轻量桌面工具逻辑、不想被Maven生命周期和模块依赖绕晕的务实开发者。它不炫技但每行代码都经得起javac -Xlint扫描它没UI框架但所有按钮点击都有明确响应路径它用文本文件存数据但做了字段分隔、换行转义、写入原子性三重防护——这才是“小小记账本”该有的样子。2. 从解压到运行5分钟内让记账本在本地真实动起来2.1 解压结构识别与核心文件定位拿到一个java版的小小记账本程序.zip后先别急着双击。用任意解压工具如7-Zip、Windows自带解压打开观察目录结构。常见但危险的结构是直接放一个.jar文件——这种往往缺少源码、无法调试、更新即失联。真正可维护的结构应包含以下三类文件src/目录必有里面是.java源文件通常含Main.java主入口、Record.java记账条目实体、DataHandler.java数据读写逻辑data/目录推荐有存放records.txt或accounts.csv等纯文本数据文件README.md或说明.txt加分项写明编译命令、运行方式、数据格式说明提示若解压后只有.jar和空文件夹说明作者未提供源码后续无法修改功能如加分类、改日期格式建议放弃若看到pom.xml或build.gradle则属于进阶项目与本标题“小小”定位不符暂不处理。2.2 命令行编译用最原始的方式确认环境兼容性确保已安装 JDK 8 或更高版本java -version输出应含1.8。进入解压后的根目录在终端执行# 查看当前目录下所有.java文件确认源码存在 ls -l src/*.java # 进入src目录编译全部Java文件-d 指定输出class目录 cd src javac -d ../out *.java # 编译成功后返回根目录检查out目录是否生成对应class文件 cd .. ls -l out/关键参数说明-d ../out强制将编译生成的.class文件输出到上层out/目录避免.class和.java混在一起导致ClassNotFoundException*.java通配符编译所有Java文件比逐个写文件名更可靠尤其当项目含GUIHelper.java等辅助类时若编译失败常见原因及自查点报错error: package javax.swing does not exist→ JDK安装不完整需重装标准版JDK非JRE报错error: cannot find symbol class XXX→ 某个Java文件里引用了未定义的类检查src/下是否漏文件如缺Record.java报错error: invalid flag: -d→ 使用了旧版JDK1.6升级JDK即可2.3 启动程序绕过双击失效陷阱用java -cp精准加载不要双击xxx.jar也不要直接java Main会报NoClassDefFoundError。正确启动命令如下# 方式一从out目录启动推荐路径最清晰 cd out java -cp . Main # 方式二从项目根目录启动需指定class路径 java -cp out Main为什么必须用-cpJava默认只在当前目录找class而我们的class在out/下。-cp .表示“当前目录即out为类路径”-cp out表示“out目录为类路径”。省略-cp会导致JVM找不到Main.class这是新手翻车最高发场景。启动后应看到什么一个带标题栏的窗口顶部有「添加记录」「查看所有」「清空数据」三个按钮中部是表格区域初始为空底部有状态栏显示“共0条记录”。此时程序已活可进行下一步操作验证。3. 数据持久化机制拆解文本文件如何安全存取记账记录3.1 数据文件格式设计为什么选制表符分隔而非JSON或CSV本程序使用纯文本data/records.txt存储数据每行一条记录字段间用制表符\t分隔。典型内容如下2024-05-20 早餐 5.0 便利店 2024-05-21 交通 12.5 地铁 2024-05-22 餐饮 68.0 餐厅 朋友聚餐选择\t而非,或|的理由逗号,在备注中极常见如“买咖啡,面包”CSV解析易错位竖线|需转义且视觉干扰大制表符\t在中文输入法下极少手动输入天然规避冲突且String.split(\t)解析稳定。字段顺序固定为日期\t类别\t金额\t地点\t备注共5列。缺失字段如无备注留空但列数必须为5否则解析时会数组越界。3.2 写入逻辑原子性保障与异常兜底查看DataHandler.java中的saveRecords(ListRecord records)方法核心逻辑如下public static void saveRecords(ListRecord records) { try (BufferedWriter writer new BufferedWriter( new OutputStreamWriter( new FileOutputStream(data/records.txt), UTF-8))) { for (Record r : records) { // 关键对备注字段做制表符转义防止解析错列 String escapedRemark r.getRemark().replace(\t, \\t); String line String.join(\t, r.getDate(), r.getCategory(), String.valueOf(r.getAmount()), r.getLocation(), escapedRemark); writer.write(line); writer.newLine(); } // 关键flush确保缓冲区数据真正写入磁盘 writer.flush(); } catch (IOException e) { // 重要记录错误但不中断程序用户仍可继续操作 System.err.println(保存失败 e.getMessage()); // 可选弹窗提示用户此处省略Swing UI代码 } }关键防护点说明try-with-resources自动关闭流避免文件句柄泄漏Windows下常导致“文件被占用”escapedRemark.replace(\t, \\t)将用户输入的制表符转义为字面量\t读取时再还原保证列对齐writer.flush()强制刷盘防止断电或强制退出导致最后几条记录丢失catch中仅打印错误不抛出记账是高频操作单次保存失败不应让整个UI崩溃。3.3 读取逻辑容错解析与空行/注释行跳过loadRecords()方法需处理现实中的脏数据代码片段如下public static ListRecord loadRecords() { ListRecord records new ArrayList(); try (BufferedReader reader new BufferedReader( new InputStreamReader( new FileInputStream(data/records.txt), UTF-8))) { String line; while ((line reader.readLine()) ! null) { // 跳过空行和以#开头的注释行方便人工维护 if (line.trim().isEmpty() || line.trim().startsWith(#)) { continue; } String[] parts line.split(\t, -1); // -1保留末尾空字段 if (parts.length 4) continue; // 至少需日期、类别、金额、地点 try { Record r new Record(); r.setDate(parts[0].trim()); r.setCategory(parts[1].trim()); r.setAmount(Double.parseDouble(parts[2].trim())); r.setLocation(parts[3].trim()); // 还原转义的制表符\\t → \t String remark parts.length 4 ? parts[4] : ; r.setRemark(remark.replace(\\t, \t)); records.add(r); } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { // 忽略单行解析失败继续下一行防数据文件损坏 System.err.println(跳过无效行 line); } } } catch (FileNotFoundException e) { // 首次运行时data/records.txt不存在静默创建空列表 System.out.println(数据文件不存在初始化空记录列表); } catch (IOException e) { System.err.println(读取失败 e.getMessage()); } return records; }容错设计细节split(\t, -1)第三个参数-1确保即使末尾有空字段如无备注parts数组长度也保持5避免ArrayIndexOutOfBoundsExceptiontrim()清除首尾空格防止用户复制粘贴时带不可见空格导致日期解析失败FileNotFoundException单独捕获首次运行无数据文件是正常场景不报错单行解析异常continue某一行格式错误如金额写成“五元”不影响其他数据加载。4. GUI交互逻辑验证按钮点击背后的事件链与状态同步4.1 添加记录按钮从输入校验到界面刷新的完整链路点击「添加记录」按钮触发AddRecordAction类或匿名内部类其actionPerformed方法执行以下步骤弹出输入对话框用JOptionPane.showInputDialog获取用户输入按\n分割为多行日期、类别、金额、地点、备注强校验检查日期格式yyyy-MM-dd、金额是否为数字、金额是否 0构建Record对象将输入值赋给Record实例的各字段追加到内存列表records.add(newRecord)立即刷新表格调用tableModel.addRow(...)更新JTable显示异步落盘新开线程调用DataHandler.saveRecords(records)避免UI卡顿。关键设计点校验前置金额非数字时直接return不创建对象、不更新内存、不落盘杜绝脏数据入库表格刷新即时性addRow后用户立刻看到新行建立操作反馈闭环落盘异步化new Thread(() - DataHandler.saveRecords(records)).start()避免大文件写入时界面假死。4.2 查看所有按钮表格模型与数据源的双向绑定JTable使用DefaultTableModel其数据源为ListRecord。当点击「查看所有」时并非重新读取文件而是直接遍历内存中的records列表并清空后逐行添加// 清空表格模型 tableModel.setRowCount(0); // 从内存列表重建表格 for (Record r : records) { tableModel.addRow(new Object[]{ r.getDate(), r.getCategory(), r.getAmount(), r.getLocation(), r.getRemark() }); }为什么不用实时读文件避免重复IO开销用户可能频繁切换查看/添加保证内存与文件数据一致性所有变更均通过DataHandler统一入口records列表是单一可信数据源UI只是视图。4.3 清空数据按钮物理删除与内存重置的协同点击「清空数据」执行两步操作// 步骤1物理删除data/records.txt文件 File dataFile new File(data/records.txt); if (dataFile.exists()) { dataFile.delete(); // 真实删除文件 } // 步骤2清空内存列表并刷新表格 records.clear(); tableModel.setRowCount(0); statusLabel.setText(共0条记录); // 同步更新状态栏注意delete()是物理删除不是清空内容。这确保下次启动时loadRecords()因FileNotFoundException返回空列表实现真正的“归零”。提示此操作不可逆。若需防误触可在JOptionPane.showConfirmDialog中增加二次确认“确定要删除所有记账记录此操作无法撤销。”5. 避坑指南新手在复现与修改过程中踩过的5个真实坑位5.1 现象双击Main.jar无反应控制台无任何输出原因.jar文件未声明主类Main-Class或未打包依赖本项目无依赖但MANIFEST.MF缺失仍会失败或系统默认用JRE而非JDK打开。解决放弃双击严格使用命令行java -cp out Main启动若必须生成可执行jar用jar cfm MyAccount.jar MANIFEST.MF -C out/ .并确保MANIFEST.MF含Main-Class: Main。5.2 现象添加记录后表格显示金额为0.0或日期显示为“null”原因Record类的字段未用private修饰且get/set方法未正确实现如setAmount(String s)未转Double.parseDouble(s)或构造函数未初始化字段。解决检查Record.java确认所有字段为privatesetAmount方法接收double参数非String并在Main.java中传参时已做类型转换。5.3 现象中文备注显示为乱码如“伊咖”或保存后文件用记事本打开是方块原因FileOutputStream未指定字符集Windows默认用GBK而程序用UTF-8写入。解决new OutputStreamWriter(new FileOutputStream(...), UTF-8)和new InputStreamReader(new FileInputStream(...), UTF-8)必须成对出现且编码字符串严格为UTF-8大小写敏感。5.4 现象多次点击「添加记录」后表格中出现重复的同一笔记录原因actionPerformed中未清空输入对话框的缓存或records.add()被重复调用如监听器注册两次。解决检查按钮监听器注册代码确认addButton.addActionListener(...)只执行一次在actionPerformed开头加日志System.out.println(添加记录触发)确认是否被触发多次。5.5 现象程序关闭后重启数据全部消失data/records.txt文件为空或不存在原因DataHandler.saveRecords()未被任何操作调用如忘记在添加/删除后调用或saveRecords方法中writer.write()后未调用writer.flush()数据滞留在缓冲区未写入磁盘。解决在AddRecordAction和ClearDataAction的末尾显式调用DataHandler.saveRecords(records)在saveRecords方法中writer.flush()后加一行System.out.println(数据已保存)验证执行路径。6. 进阶改造技巧3个低侵入、高价值的定制方向6.1 为金额增加千分位分隔与精度控制从“5.0”到“5.00”原始程序中金额直接String.valueOf(r.getAmount())显示为5.0不够专业。改造只需两处第一步在Record.java中增加格式化方法import java.text.DecimalFormat; public class Record { private double amount; // ... 其他字段与getter/setter public String getFormattedAmount() { DecimalFormat df new DecimalFormat(#,##0.00); return df.format(this.amount); } }第二步在表格模型填充处替换金额字段// 替换原来的 r.getAmount() tableModel.addRow(new Object[]{ r.getDate(), r.getCategory(), r.getFormattedAmount(), // ← 改用格式化后字符串 r.getLocation(), r.getRemark() });效果5.0→5.001234.5→1,234.500.1→0.10。DecimalFormat是Java内置类无需额外依赖且#,##0.00模式自动处理千分位与两位小数。6.2 按日期范围筛选在「查看所有」基础上叠加时间过滤不重构现有UI仅在「查看所有」按钮旁新增「筛选」按钮弹出日期选择对话框// 点击筛选按钮时 String startDate JOptionPane.showInputDialog(请输入开始日期yyyy-MM-dd); String endDate JOptionPane.showInputDialog(请输入结束日期yyyy-MM-dd); // 过滤records列表 ListRecord filtered records.stream() .filter(r - r.getDate().compareTo(startDate) 0 r.getDate().compareTo(endDate) 0) .collect(Collectors.toList()); // 刷新表格复用原有逻辑 tableModel.setRowCount(0); for (Record r : filtered) { tableModel.addRow(new Object[]{...}); }关键点String.compareTo()可直接比较yyyy-MM-dd格式日期因字典序与时间序一致无需转LocalDate零依赖。6.3 导出为Excel兼容CSV用制表符分隔升级为逗号分隔并加引号为满足财务软件导入需求新增「导出CSV」菜单项生成export_YYYYMMDD.csvpublic static void exportToCsv(ListRecord records, String filename) { try (PrintWriter pw new PrintWriter( new OutputStreamWriter( new FileOutputStream(filename), UTF-8))) { // 写入CSV头部 pw.println(日期,类别,金额,地点,备注); for (Record r : records) { // 关键字段含逗号或换行时用双引号包裹并转义内部双引号 String escapedRemark r.getRemark().replace(\, \\); String line String.format( \%s\,\%s\,\%.2f\,\%s\,\%s\, r.getDate(), r.getCategory(), r.getAmount(), r.getLocation(), escapedRemark ); pw.println(line); } } catch (IOException e) { System.err.println(导出失败 e.getMessage()); } }生成的CSV示例日期,类别,金额,地点,备注 2024-05-20,早餐,5.00,便利店, 2024-05-21,交通,12.50,地铁, 2024-05-22,餐饮,68.00,餐厅,朋友聚餐双引号包裹、内部双引号转义为两个双引号完全符合RFC 4180标准Excel可直接双击打开。我带过的某高校实训班里A同学用这个记账本作为课程设计基线三天内加上了分类统计饼图用JFreeChart和月度报表导出最终答辩时老师问“数据怎么保证不丢”他当场演示了断电重启后数据完好——那一刻我知道这个“小小”二字真的沉得下去。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询