关键在于轻量结构与IO协同实现日志实时显示和稳定追加:用StringBuilder高效拼接单行日志,同步输出控制台并立即flush写入FileWriter(true)文件;高并发时通过BlockingQueue解耦生产消费;统一log方法封装双路输出,避免缓存阻塞、错序和丢行。
要让日志既在控制台实时显示,又稳定追加写入文件,关键不是堆砌复杂数据结构,而是用轻量、可控的结构管理日志内容,并与基础io流协同工作——重点在于“不缓存阻塞、不错序、不丢行”。
用StringBuilder拼接单行日志再输出
日志通常按行组织,每条记录含时间、级别、消息。用StringBuilder比字符串拼接更高效,且避免频繁创建对象:
- 每次生成日志时,先用StringBuilder组装完整行(如
[2026-06-13T04:50:00] INFO: 用户登录成功) - 组装完成后,立即调用
System.out.println(sb.toString())输出到控制台 - 同时把该字符串传给文件写入器,不额外缓存整批日志,避免延迟
用FileWriter(true)开启追加模式
这是实现“追加写入”的最直接方式,无需额外数据结构参与:
- 构造
new FileWriter("app.log", true),第二个参数true表示追加,而非覆盖 - 配合
BufferedWriter提升性能,但必须在每行后调用newLine()和flush(),确保实时落盘 - 不要依赖默认缓冲行为——例如
BufferedWriter可能积压多行才刷,主动flush()才能保证控制台看到的同时文件也已写入
用线程安全队列解耦日志生产与消费
高并发场景下,多个线程同时打日志,不能让它们直接争抢同一个文件流:
- 声明一个
BlockingQueue<string></string>作为日志中转站,所有线程只往里offer()日志行 - 单独起一个守护线程,持续
take()日志并顺序写入文件(用前述FileWriter(true)+flush()) - 这样既避免了锁竞争,又保持了日志时序,还防止因I/O卡顿导致业务线程阻塞
用自定义日志方法统一控制台与文件双路输出
不靠框架也能做到简洁可靠,核心是封装一次写两处:
- 定义一个
log(String level, String msg)方法 - 内部用
StringBuilder格式化时间+级别+消息 - 同步调用
System.out.println(...)和fileWriter.write(...) + flush() - 若需支持多线程,可在该方法内加
synchronized或用ReentrantLock保护文件写入段
不需要引入Map、TreeSet等复杂结构来“管理日志”,日志本身是线性、不可变、按时间推进的文本流。真正影响实时性的,是缓冲策略、打开模式和并发控制——把这些理清楚,就足够支撑大多数场景下的可靠追加写入。

















