
简介Java开发记事本完整版是一个基于Java Swing的文本编辑器项目面向初学Java GUI编程的开发者用于理解窗口组件、菜单栏、事件监听与文件读写等核心机制也能作为课程设计或练手项目参考。压缩包共36个文件大小约103KB内部包含java源码、class编译文件、可直接运行的jar包、txt说明及工程配置文件源码与运行产物分开存放便于对照阅读。当前已有4192人学习/下载。源码覆盖打开保存文件、剪切复制粘贴、查找替换、撤销重做、状态栏与快捷键等功能模块可看到JFileChooser、JTextArea、TransferHandler、Action接口等典型用法文件读写中演示了缓冲流与异常处理撤销重做则通过操作历史栈实现。通过阅读和运行该项目能完整掌握Java桌面应用的模块划分、事件处理与异常处理思路可直接修改扩展适合Java学习者深入实践。1. 项目概述与整体设计思路做Java开发绕不过去的一个经典练手项目就是“记事本”。这玩意儿看起来简单但真要做出一个“完整版”涉及的知识点一点不少GUI布局、事件监听、文件IO、文本处理、剪贴板交互、快捷键绑定甚至还能扯上编码转换和正则匹配。正因为它麻雀虽小五脏俱全我特别建议Java初学者把这个项目完完整整做一遍——它比刷一百道面试题更能帮你理解Swing的运作机制和面向对象的设计思想。1.1 为什么选择Swing而不是JavaFX现在很多人一上来就推荐JavaFX理由是界面更现代、支持CSS样式。但我的看法比较直接如果你是刚学完Java基础语法Swing是更合适的选择。原因有三第一Swing是JDK自带的库不需要额外配置任何依赖装好JDK就能跑。JavaFX从JDK 11开始已经从JDK中剥离了需要单独引入模块这对于刚入门的同学来说就是一道不必要的坎。第二Swing的API设计非常直观JFrame、JMenuBar、JTextArea这些类的命名和用途完全对应阅读理解成本低。你用Swing写完一个记事本再去学JavaFX很多概念是相通的不会白学。第三Swing的组件模型是MVC架构的经典体现——JTextArea是视图Document是模型监听器是控制器。搞清楚这层关系对理解后面Spring MVC这类框架的设计思想也大有帮助。所以我的建议是用Swing把记事本做扎实之后再去进阶JavaFX完全来得及。1.2 完整版记事本的功能范围界定网上搜“Java记事本”能找到一堆版本但大部分都是残废的——只能打几个字连文件都存不了。既然叫“完整版”功能上至少要覆盖以下几个模块文件操作新建、打开、保存、另存为、退出编辑操作剪切、复制、粘贴、全选、删除查找与替换可以输入关键词查找支持替换单个和替换全部格式设置字体选择、字号调整、加粗斜体视图辅助自动换行开关、状态栏显示行号和列号快捷键支持CtrlS保存、CtrlO打开、CtrlF查找等常规组合键这些功能全部实现下来代码量大概在600到900行之间分文件组织的话正好能练到面向对象设计。而且每一个功能背后都对应一个Java核心知识点做成简历项目也有底气。2. 核心技术点解析写记事本看起来简单但要把每一个功能都做得不露破绽有几个技术点必须吃透。这些点也是你在面试时能跟面试官聊出深度的关键。2.1 JTextArea组件与Document模型的关系记事本的核心组件就是JTextArea但这个组件有个比较隐蔽的设计它本质上只是一个“视图”真正的文本内容存放在Document对象里。你调用getText()方法和setText()方法其实都是在和Document交互。这个分离的设计有什么实际意义呢因为你做查找替换时可以直接操作Document对象来实现局部文本替换这样比拿整个字符串操作再整体setText回去要高效得多。尤其是当你打开一个几十万行的大文件时用getText()获取整个文档再replace、再setText回去内存开销极大界面还会明显卡顿。完整版记事本肯定要支持大文件的所以正确的做法是先用Document对象做局部更新或者用JTextArea内置的replaceSelection()方法配合选区操作来实现替换。2.2 Swing中的事件监听机制Swing是典型的事件驱动模型。用户点击按钮、选择菜单项、按键输入都会产生对应的事件对象。你在开发记事本时要操作的事件类型无非就这几类ActionEvent菜单点击、按钮点击、KeyEvent键盘按键、WindowEvent窗口关闭。这里要特别注意一个点很多人写监听器喜欢一坨全塞在构造器里代码又臭又长。更好的做法是定义一个Config类或者在内部类里做统一管理把菜单名称、快捷键、监听器注册都集中在一个初始化方法里。这样代码结构清晰后续加功能也容易。还需要注意的是Swing的事件处理是在事件分发线程EDT上执行的。如果你在事件回调里做了耗时的文件操作界面会直接卡死。解决思路是引入SwingWorker或SwingUtilities.invokeLater()但考虑到记事本这种轻量级应用场景只要文件不会太大直接同步处理问题不大真要支持超大文件再上异步方案。2.3 文本编码处理的底层逻辑这个东西必须展开讲因为凡是做记事本没有不踩编码坑的。Java内部字符串使用UTF-16编码但磁盘上的文件可能是GBK、UTF-8、ISO-8859-1等等。使用记事本打开一个GBK编码的txt文件如果你按UTF-8去读就会出现乱码而且这种乱码一旦显示出来你是无法通过界面操作恢复的。解决方案是在打开文件时先读取文件的原始字节流按照指定的字符集解码成字符串或者直接指定字符集创建InputStreamReader。保存文件时同理按照用户选择的编码方式把字符串转成字节数组写入文件。还有一个细节你可能没注意到Windows系统自带的记事本在保存UTF-8文件时会写入BOM头EF BB BF而Linux下创建的UTF-8文件通常没有BOM。如果你的Java记事本不处理这个差异用Windows记事本打开你保存的文件时第一行最前面会出现一个肉眼不可见但确实存在的字符导致第一行无法被正确匹配查找。解决方法是保存时默认使用UTF-8并写入BOM或者打开时自动跳过BOM头。2.4 剪贴板交互与系统级数据传递剪切、复制、粘贴这三个功能本质上是和系统剪贴板交互。Java提供了Toolkit.getDefaultToolkit().getSystemClipboard()来获取系统剪贴板实例。这里有一个使用细节当用户按CtrlC时如果你没有自己处理这个快捷键JTextArea自身会完成复制动作。但如果你在菜单项上绑定了Accelerator并且菜单项同时关联了复制动作有可能出现快捷键被菜单消费掉、JTextArea收不到复制命令的情况。所以正确做法是不要为剪切复制粘贴这些菜单项绑定CtrlC/X/V让JTextArea默认处理这些快捷键菜单只是调用textArea.cut()、copy()、paste()方法。这个细节虽然小但很多人做出来的记事本会出现“快捷键失灵”的bug排查半天发现是Accelerator和默认行为冲突了。3. 核心功能实现这一节重点讲各个功能模块应该怎么实现以及实现过程中容易踩的坑。为了方便你参考我按模块来说。3.1 界面布局与主框架构建我建议主框架用一个自定义的NotepadFrame类继承JFrame构造器里完成所有初始化逻辑。顶部用JMenuBar中间是JScrollPane包裹的JTextArea底部是JPanel做的状态栏。整体布局用BorderLayout菜单放NORTH编辑区放CENTER状态栏放SOUTH。状态栏的设计有讲究。它需要实时显示当前光标所在的行列号。实现方式是给JTextArea添加CaretListener接口光标位置变化时触发事件处理。这里要注意行列号不是直接从光标位置算出来的需要用JTextArea的getLineOfOffset()方法把字符偏移量换算成行号再计算当前行起始字符的偏移量来求得列号。字体的设置会影响整个界面的观感推荐默认字体选Monospaced或者Consolas这样中英文混合时对齐效果最好符合记事本的调性。字体大小默认12到14号中文显示比较清晰。3.2 文件读写与目录记忆功能文件操作是这个项目的灵魂实现起来却有不少细节。打开文件的推荐代码思路是通过JFileChooser选择文件后先读取整个文件为字节数组然后根据用户选择的编码格式构造字符串最后setText到文本区。保存文件则是相反的过程把文本区内容按指定编码写入文件。JFileChooser有一些值得优化的地方。默认状态下用户每次打开对话框都得手动找到上次的目录。优化做法是给JFileChooser设置一个静态变量记录当前目录每次打开对话框前先setCurrentDirectory到上次的目录体验会好很多。新建文件时还有个细节如果当前文本区内容已经被修改过了直接新建会丢失数据。所以每次新建前要检查是否有未保存的修改如果有弹出一个确认对话框让用户选择保存、不保存或取消。这个逻辑同样要应用在关闭窗口和打开新文件时。3.3 查找替换功能的实现方案查找替换是整个项目里最有含金量的模块因为它涉及正则表达式和Document操作。查找功能的实现思路是记录上一次查找的位置调用textarea的getText()获取全文用indexOf或正则匹配找下一个匹配位置找到后用setCaretPosition把光标移到那里并用select选中匹配的文本。第二次查找时需要注意要基于上一次的位置向后继续查而不是每次都从头开始。替换功能的实现思路是用textArea.getSelectionStart()和getSelectionEnd()获取当前选中的文本范围如果选中的文本和查找目标一致就用replaceSelection()替换。替换全部则简单一些遍历全文替换即可。这里有个值得优化的地方当我们用getText()获取全文来找匹配位置时如果文件很大性能会是个问题。更优雅的方案是直接操作Document对象通过javax.swing.text.Utilities的getWordStart和getWordEnd来定位单词边界不过实现复杂度会高不少。对记事本这个项目而言用getText()拿全文处理已经够用了代码还更好理解。3.4 字体设置与状态栏显示的完整实现字体设置模块需要用到JFontChooser但这个类不包含在JDK里。有两个选择一是自己写一个简易的字体选择对话框用JList列出系统支持的字体、字号和样式二是用JDialog直接做一个动态预览的字体选择器效果更好。做字体选择器时注意一个体验问题字体列表是异步加载的因为GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()在部分Windows系统上会卡顿半秒到一秒。优化方式是首次调用后缓存到静态变量里后续打开对话框直接读缓存。状态栏显示行列号的技术点在前面已经说过再补充一个体验优化如果文本区内容为空时光标位置显示为第1行第1列比第0行第0列更符合用户习惯。4. 进阶功能与界面体验优化基础版本做完以后千万别停下加上几个进阶功能项目质感会完全不一样面试讲起来也更有亮点。4.1 关键字高亮功能实现给Java记事本加上关键字高亮这个效果非常能打。实现思路是给JTextArea的Document添加DocumentListener每次文档内容变化时对全文做一次词法分析把Java关键字用高亮颜色标出来。技术上高亮用的API是JTextPane的StyledDocument它比JTextArea强大很多支持对文本的不同区间设置不同样式。当然你也可以继续用JTextArea配合Highlighter来实现高亮但Highlighter只支持背景色高亮不能改文字颜色效果会打些折扣。建议用JTextPane来做支持高亮的记事本版本注意它的setText和JTextArea有所不同Document类型需要切换成DefaultStyledDocument。关键字匹配用正则表达式把Java所有关键字列出来用|分隔构建一个正则模式匹配到的区间设置蓝色加粗。再进一步字符串常量可以设置成绿色数字设置成橙色接近IDE的效果。4.2 多个文档的标签页支持单文档的记事本实现简单但用起来总觉得不够方便。进阶版可以把主界面改成JTabbedPane布局每个标签页对应一个文档。实现思路是把原来的JTextArea和状态栏打包成一个自定义的EditorPanel类每新建一个文档就实例化一个EditorPanel放进新的标签页。菜单操作要跟当前选中的标签页联动——你得跟踪当前激活的Tab所有事件监听都发给这个活跃的EditorPanel处理。这个改动涉及到整个架构的调整如果你前期的代码是把所有内容都写在JFrame类里的这步改造会很痛苦。所以一开始就建议按模块分文件把一个完整的文档编辑界面封装成独立类这是面向对象设计的老生常谈但真正理解它的价值恰好就是在这种扩展需求出现的时候。4.3 近期文件列表与用户偏好记忆在菜单里加一个“最近打开的文件”子菜单记录最近打开的5到10个文件路径。这个功能用java.util.prefs.Preferences来实现持久化最方便它可以在Windows注册表或系统配置文件中存储小量数据不需要自己搞配置文件。用户偏好记忆同理比如用户上次设定的字体大小、是否自动换行以及窗口的位置和大小——用Preferences把这个信息存进去下次启动时自动恢复。这些细节非常提升好感度很多开源记事本都会忽略做出来之后体验会和系统自带工具拉开明显差距。5. 常见问题与排查技巧实录这部分是我做这个项目时实际踩过的坑以及帮别人debug时经常遇到的问题整理成速查表遇到可以直接定位。5.1 中文乱码问题的三种场景乱码问题基本都出在编码错配排查思路很固定。第一种代码里直接写了中文字符串编译时如果没有指定-encoding参数在中文Windows环境下源码是GBK编码但IDE默认用UTF-8编译就会乱码。解决办法是IDE里统一把项目编码设为UTF-8并在编译时指定-encoding UTF-8。第二种打开文件乱码。这是文件实际编码和读取时指定的编码不一致导致的。解决办法是打开时让用户选择编码或者更智能一点自动检测文件编码。检测原理是先看有没有UTF-8的BOM头没有的话用字节序列的统计特征去猜是GBK还是UTF-8。第三种保存后再用其他编辑器打开乱码。一般是保存时没有加BOM头Windows记事本对无BOM的UTF-8文件会按GBK解析从而出现乱码。解决办法是在保存UTF-8时把字节数组前加上EF BB BF三个字节。5.2 界面卡死与事件线程阻塞如果你在打开一个大文件时界面窗口突然变成“未响应”状态问题出在文件读取操作阻塞了事件分发线程EDT。解决思路是把文件读取放到子线程里执行读取完成后再通过SwingUtilities.invokeLater回调到EDT更新界面。在实际开发中我建议给打开文件操作加一个文件大小判断如果文件超过5MB提示用户文件较大是否继续。超过50MB建议用子线程读取并在左下角状态栏显示“加载中…”的进度提示。这样既不复杂又能覆盖掉绝大多数卡死场景。5.3 快捷键冲突与菜单焦点问题很多人做出记事本后发现CtrlC复制偶尔失灵有时第一次好用第二次没反应。这种情况十有八九是菜单项的Accelerator和JTextArea的默认快捷键发生了冲突。菜单拥有焦点时按下快捷键菜单会优先响应如果菜单里没绑定复制动作事件就被吞掉了。解决方案在前面提过复制剪切粘贴这几个菜单项不要绑定Accelerator或者绑定自定义KeyStroke并接一个“派发给当前焦点组件”的动作。我实测下来最稳的方案就是第一种——让JTextArea自己处理剪贴板快捷键菜单里只放点击触发的动作。5.4 保存后文件内容为空的问题有一个典型bug保存文件时如果用户选择的是“另存为”而且还没有输入文件名就点了取消文件会被意外清空。原因是你在获取到用户选择的文件路径之前就执行了写入操作而此时file对象为null写入会把内容写到了默认路径或者直接跳过了。正确的实现顺序是先通过JFileChooser获取用户选择的目标文件判断返回结果是否为APPROVE_OPTION如果是则执行写入不是则直接return不做任何操作。另外如果是已有文件路径的保存操作不要弹文件对话框直接覆盖原文件即可。6. 实操过程与核心环节实现最后带大家从头到尾走一遍关键实现流程。我默认你已经配置好了Java开发环境也掌握基本的Swing组件概念。6.1 搭建项目骨架项目结构建议如下src/com/notepad/Main.java —— 程序入口src/com/notepad/ui/NotepadFrame.java —— 主窗体src/com/notepad/ui/EditorPanel.java —— 单个文档的编辑面板src/com/notepad/ui/StatusBar.java —— 状态栏src/com/notepad/util/FileUtil.java —— 文件读写工具类src/com/notepad/util/PreferencesUtil.java —— 偏好设置工具类Main类就一行代码public class Main { public static void main(String[] args) { SwingUtilities.invokeLater(() - new NotepadFrame().setVisible(true)); } }注意一定要用invokeLater创建界面保证所有组件初始化和操作都在EDT上执行这是Swing线程模型的硬性规定。6.2 文件保存流程带编码选择保存的完整流程是先判断当前文件有没有路径。如果有且用户没有主动触发“另存为”直接写文件如果没有路径弹出保存对话框。下面是一个带编码选择的保存逻辑public void saveFile() { if (currentFilePath null) { showSaveFileDialog(); } else { writeFile(currentFilePath, currentEncoding); } } private void writeFile(File file, String encoding) { try { String content textArea.getText(); byte[] bytes content.getBytes(encoding); if (UTF-8.equals(encoding)) { byte[] bom new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; byte[] withBom new byte[bom.length bytes.length]; System.arraycopy(bom, 0, withBom, 0, bom.length); System.arraycopy(bytes, 0, withBom, bom.length, bytes.length); bytes withBom; } try (FileOutputStream fos new FileOutputStream(file)) { fos.write(bytes); } saved true; } catch (UnsupportedEncodingException | IOException e) { JOptionPane.showMessageDialog(this, 保存失败: e.getMessage()); } }这段代码里我特意加了BOM头处理的逻辑就是为了避免前面说的Windows记事本打开乱码的问题。6.3 查找替换核心逻辑查找功能的准确实现重点在于处理“从当前光标位置向后找”这个需求。如果每次都从文档开头找用户会觉得很别扭明明后面还有匹配项却跳到前面去了。我的做法是记录一个lastSearchIndex变量。查找时从该位置开始找到匹配后把位置更新为匹配项末尾下一次查找继续往后走。如果搜到文末还没找到就提示“未找到”并重置为从头开始搜。public void search(String query, boolean caseSensitive) { String content textArea.getText(); if (!caseSensitive) { content content.toLowerCase(); query query.toLowerCase(); } int start Math.max(lastSearchIndex, 0); int index content.indexOf(query, start); if (index 0) { textArea.setCaretPosition(index); textArea.select(index, index query.length()); lastSearchIndex index query.length(); } else { lastSearchIndex 0; JOptionPane.showMessageDialog(this, 未找到匹配内容); } }6.4 打包为可运行程序项目做完之后打包成双击就能运行的程序是最后一个必要步骤。用IDEA自带的Artifact功能把项目构建成带主类的JAR包然后在命令行里用jpackage工具JDK 14提供生成一个免安装的exe或文件夹。jpackage的使用比较简单以Windows为例jpackage --name Notepad --input lib --main-jar mynotepad.jar --main-class com.notepad.Main --type exe这样打出来的exe自带JRE运行环境用户不需要单独安装Java就能运行。整个项目到此才算真正交付可以发给同学试用或者放进自己的作品集里。做完整这一个项目你对Swing的掌握程度、对事件驱动编程的理解、对IO和编码的熟练度都会有一个质的提升。Java面试八股文里那些抽象的概念在这个项目里都能找到对应的实际应用。动手做一遍比死记硬背强太多了。本文还有配套的精品资源点击获取