☰
网络编程、正则表达式与设计模式:从原理到日志监控实战全解析
2026/10/21 13:06:14 网站建设 项目流程

先聊个现象:很多人学到“网络编程、正则表达式、设计模式”这个模块时,本能地觉得这三个东西没什么关联——网络编程在搞传输层的数据流动,正则表达式在处理字符串匹配,设计模式在管代码的类结构。但实际在项目里,它们是同一件事的三个侧面:一个能上线的网络服务,往往要用正则去解析协议报文,要用设计模式去组织处理链路,三者缺一不可。这篇文章我就围绕这个模块,把三块内容串起来讲透,讲清楚它们各自的原理、落地代码以及组合使用的完整链路,顺带把我在实际项目中踩过的坑和排查经验一并放出来。适合正在系统学习编程的开发者,也适合工作中需要补齐网络、文本处理、架构设计基本功的初级工程师。

1. 模块拆解:为什么这三个主题要绑在一起学

这个模块被放在课程靠后的位置是有道理的。它不像前面那些语法章节只解决某一个局部问题,而是把之前学过的 IO、集合、异常、多线程这些内容全部调动起来,去解决一个完整的问题。网络编程负责数据怎么传,正则负责数据长什么样,设计模式负责代码怎么组织,三者组合在一起,才构成一个可以真正交付的业务功能。

1.1 网络编程解决程序与世界的连接

先看网络编程。一个程序如果只在本地跑,它和单机游戏没有区别,一旦涉及两台以上设备之间的数据交换,就必然要面对网络编程。最基础的形态是 Socket 通信——一端监听,一端连接,然后互相收发字节流。

这里有个理解网络编程的关键点:网络传输的底层,本质上就是字节流的搬运。不管是 TCP 还是 UDP,最终都是把数据按字节拆包、封装、传输、重组。所以学网络编程的第一步不是急着写代码,而是建立“字节流”这个心智模型。你发出去的是一个字符串,底层会变成字节数组,经过网卡、路由器、交换机,再在另一端拼回来。这个过程里有延迟、有丢包、有乱序,所以上层的协议要去解决这些不确定性问题。

TCP 为什么可靠?因为它有三次握手建立连接,有确认机制,有超时重传,有滑动窗口做流量控制。UDP 为什么快?因为它不建立连接,不确认,不重传,只是尽力发送。所以在选型的时候,你要先回答一个问题:这个场景能不能容忍丢数据?不能容忍,走 TCP;能容忍少量丢失但要求低延迟,走 UDP。实时游戏里的位置同步、语音通话用 UDP 的多;文件传输、网页请求、数据库连接用 TCP 的多。这个判断做完了,再去写代码,方向就不会跑偏。很多新人一上来就纠结用什么 API,其实 API 永远是简单的,难的是对场景的理解。

1.2 正则表达式解决数据形态的识别

再说正则表达式。程序从网络里收到的原始数据,绝大多数不是干干净净的“用户名+密码”这种结构化对象,而是一串带有格式的文本。比如一行日志、一段 HTTP 报文、一条 CSV 记录。要从这些文本里提取出自己关心的字段,就得靠正则。

正则在很多人的认知里是“背语法”,比如 \d 匹配数字、\w 匹配字母数字下划线。但如果你只是背语法,遇到复杂一点的文本就完全不知道怎么组合。我的经验是,正则的本质是一种“描述字符串形态”的微型语言,它的核心是三个问题:这个位置允许出现什么字符?这个字符出现多少次?从哪里开始到哪里结束?把这三个问题想明白了,任何正则都能拆解出来。

还有一点容易被忽视:正则不是只能做“提取”,它还能做“校验”和“替换”。校验手机号格式、校验邮箱格式,是校验;把日志里的 IP 地址批量脱敏,是替换。这三种能力在真实项目里使用频率差不多高,所以学的时候不要只看“匹配”这一种用法。

1.3 设计模式解决代码生长的秩序

最后是设计模式。很多人听到设计模式就觉得是“高级内容”,其实设计模式解决的是最朴素的问题:代码量大了之后,怎么改起来不累。

举个最直观的例子。你写一个网络客户端,一开始只需要支持 HTTP 协议,直接写一个方法发请求就行。后来要支持 HTTPS、要支持 WebSocket、要支持自定义的二进制协议,如果所有逻辑都堆在一个类里,这个类就会越来越臃肿,改一个地方怕影响另一个地方。这时候如果你定义一个“协议发送器”的接口,让每种协议各自实现这个接口,调用方只依赖接口,新增协议只需要增加一个实现类,不用改动已有代码。这就是策略模式,也是设计模式里最基础、最实用的一种。

设计模式一共有二十多种,但对我来说,日常项目里真正高频出现的其实就十来种。工厂模式负责对象的创建,策略模式负责算法的替换,观察者模式负责事件的通知,模板方法模式负责流程骨架的固定,单例模式负责全局唯一实例。把这些先吃透,比把二十多个名字都背下来有用得多。

2. 网络编程实战:从Socket原理到HTTP协议

2.1 TCP与UDP的选型对照

先放一张实用对比表,方便你遇到具体场景时快速判断该用谁。

对比维度TCPUDP
连接方式面向连接,先建立连接再传输无连接,直接发送数据报
可靠性可靠:确认、重传、排序不可靠:可能丢包、乱序
传输效率相对慢,有握手和处理开销快,没有握手和确认过程
使用场景HTTP、数据库、文件传输实时音视频、游戏同步、DNS 查询

这里的核心不是“谁更好”,而是“谁更合适”。我见过一个做物联网采集的开发者,设备上报温度数据,链路偶尔抖动导致丢几包数据,他坚持要用 TCP,结果因为重传排队,数据到的反而是乱的;后来改成 UDP + 应用层序号去重,反而更稳定。所以遇到选型题,先问业务容忍度,再问技术实现。

2.2 一次完整的Java Socket通信实现

理解原理之后,写一个能跑的最小 Demo 比什么都重要。下面这个例子是服务端监听端口、客户端连接后互发消息的最简实现。

服务端代码:

import java.io.*; import java.net.*; public class ChatServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("服务端启动,监听端口 8080"); while (true) { Socket socket = serverSocket.accept(); new Thread(() -> handleClient(socket)).start(); } } private static void handleClient(Socket socket) { try (BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter( socket.getOutputStream(), true)) { String line; while ((line = reader.readLine()) != null) { System.out.println("收到客户端消息:" + line); writer.println("服务端已收到:" + line); } } catch (IOException e) { e.printStackTrace(); } } }

客户端代码:

import java.io.*; import java.net.*; public class ChatClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8080); BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter( socket.getOutputStream(), true); BufferedReader console = new BufferedReader( new InputStreamReader(System.in)); String input; while ((input = console.readLine()) != null) { writer.println(input); String response = reader.readLine(); System.out.println("服务端响应:" + response); } socket.close(); } }

这个 Demo 的完整流程是:服务端通过 ServerSocket 绑定 8080 端口,然后一直调用 accept() 阻塞等待连接;客户端 new Socket("127.0.0.1", 8080) 发起连接;连接建立后,双方从 Socket 里拿输入输出流,互相读写数据。这里有一个关键细节:PrintWriter 构造函数的第二个参数 true 代表自动 flush,否则你写完数据后必须手动 flush(),数据才能真正写进底层通道。很多初学者调试半天发现“我发了消息对方怎么没收到”,十有八九就是没有 flush。

还要提醒一个问题:这个最简单的写法是“一个连接一个线程”,在连接数只有几个的时候完全没问题,但当客户端数量到几百上千时,线程数量会暴涨,上下文切换和内存开销都扛不住。实际项目中一般用线程池限制并发,或者用 NIO 模型解决。这里的取舍逻辑后续综合实战里会再讲。

2.3 网络编程中的常见坑与排查

网络编程的坑大多集中在这几类:端口被占用、数据读不到、中文乱码、粘包半包。我给你整理成一张速查表。

现象可能原因处理方法
BindException: Address already in use端口被其他进程占用换端口,或启动前检查占用情况
连接建立了但收不到数据没 flush、缓冲区没满、线程阻塞在 read加 flush,检查流包装,确认对端真正发出了数据
收到的中文是乱码两端的字符编码不一致统一为 UTF-8,InputStreamReader 显式指定编码
读到的数据“粘”在一起应用层没有定义消息边界用固定长度、分隔符协议等方式拆包
客户端连接一直超时服务端没监听、防火墙拦截、地址写错先用 telnet 或 nc 探一下端口再排查代码

其中粘包半包是我被问过最多的问题。TCP 是字节流协议,它不认为你每次 write 的数据包就是一个完整的业务消息。它可能把两次 write 合并成一个包发出去(粘包),也可能把一次 write 拆成两个包分开发(半包)。解决方案就是在应用层自己定义消息边界,比如每条消息以换行结尾,或者开头固定 4 字节放长度。我上面那个 Demo 用 readLine 就是最简单的“换行分界”方式。

3. 正则表达式:真正会用而不是背语法

3.1 匹配原理:从NFA到回溯

很多人看不懂正则为啥会“慢”,尤其是处理大文件时越跑越卡。这就要说到正则引擎的底层工作方式。

Java 的 Pattern 底层用的是 NFA(非确定有限自动机)引擎。它的工作方式有点像你在迷宫里走岔路:每到一个分岔口,它会先选一条路走下去,走不通再退回来换一条。这个“退回来再换”的过程,就叫回溯。

比如模式 a(b|c)d 匹配字符串 abd:引擎先匹配 a,然后进入分组,先试 b,成功;再匹配 d,成功,结束。这个过程很快。但如果模式写成 (a+)+b,去匹配一串很长的 a 且末尾没有 b,事情就麻烦了。因为每一个 a 都可以被归到不同的组里,引擎会枚举大量分组方式,每次最后都发现没有 b,再回溯到上一步换一种分子方式。字符串稍微长一点,计算量就是爆炸式的增长,这就是所谓的“灾难性回溯”。

实践里的应对办法有几个:少写嵌套的量词、能确定边界就加锚点、用非贪婪模式减少探索范围、复杂场景把正则拆成多个小正则分步处理。记住一点,正则不是处理文本的唯一手段,遇到结构特别复杂的文本,先按行、按分隔符切分,比硬写一个超长正则要清爽得多。

3.2 分组、贪婪与懒惰的实战选择

正则里有三个词天天用到:分组、贪婪、懒惰。它们直接影响你能提取出什么结果。

先说分组。括号 () 除了把一部分模式括起来,还会把匹配到的内容存起来,之后可以用 matcher.group(n) 取出来。这是“提取字段”能力的核心。比如匹配日志里的 IP,模式里写成 ^(\S+) ,group(1) 就能取到 IP。如果是纯分组不想捕获,用 (?:...) 可以省掉存储的开销。

再说贪婪与懒惰。默认的量词都是贪婪的,比如 a.b 匹配 a...b...,它会尽量往长了匹配,直到最后一个 b;而 a.?b 是懒惰模式,会尽量往短了匹配,找到第一个 b 就停。我见过一个真实案例:想从 HTML 里提取每个

块,用贪婪写法把整页内容都吞进了一个匹配结果;改成 <div.*?
后才正确地把每个块单独切出来。所以遇到这种“相隔最近的一对边界”场景,记住用懒惰模式。

这里还有个细节:group 的编号从 1 开始,group(0) 是整体匹配内容;分组嵌套时编号按左括号出现的顺序排,别想当然地去数右括号。

3.3 日志解析里的正则实战

纸上谈兵讲了这么多,直接落到最常见的场景:解析一条访问日志。假设你有这样一行数据:

192.168.1.1 - - [10/Oct/2023:13:55:36 +0800] "GET /index.html HTTP/1.1" 200 2326

要用正则把它拆成 IP、时间、请求方法、路径、状态码、响应字节数这几个字段,模式可以写成这样:

import java.util.regex.*; public class LogParserDemo { private static final Pattern LOG_PATTERN = Pattern.compile( "^(\\S+) - - \\[([^\\]]+)\\] \"(\\w+) (\\S+) HTTP/\\S+\" (\\d{3}) (\\d+)$"); public static void main(String[] args) { String line = "192.168.1.1 - - [10/Oct/2023:13:55:36 +0800] \"GET /index.html HTTP/1.1\" 200 2326"; Matcher matcher = LOG_PATTERN.matcher(line); if (matcher.matches()) { System.out.println("IP: " + matcher.group(1)); System.out.println("时间: " + matcher.group(2)); System.out.println("方法: " + matcher.group(3)); System.out.println("路径: " + matcher.group(4)); System.out.println("状态码: " + matcher.group(5)); System.out.println("字节数: " + matcher.group(6)); } } }

拆开看一下这个模式:^(\S+) 匹配行首的非空白串,也就是 IP;[([^]]+)] 匹配中括号里的内容,用排除法把右括号排除掉;"(\w+) (\S+) HTTP/\S+" 匹配引号里的请求行;最后 (\d{3}) 和 (\d+) 分别抓状态码和字节数。

这里有一个非常重要的工程习惯:Pattern.compile 一定要放在方法外面,最好定义为 static final。因为编译一个正则是一个相对昂贵的过程,如果你在循环里重复编译,大日志量下性能会肉眼可见地下降。这个点我在带新人的时候反复强调过,写出来希望大家一开始就养成好习惯。

4. 设计模式:在代码里识别“套路”

4.1 面向接口而不是面向实现

设计模式学了那么多,骨子里就一句话:面向接口编程,而不是面向具体实现。这句话听多了容易麻木,但你看下面这个例子就明白了。

假设系统里要发通知,一开始只有短信渠道,你写一个 SmsSender 类,里面有个 send 方法,所有用得到的地方直接 new SmsSender().send(msg)。过了一个月产品说还要支持邮件,你改完全部调用点,过了两个月又加 App 推送,你又改一轮。每加一个渠道就动一次旧代码,费时而且容易改出新 bug。

换个思路,先定义接口:

public interface Notifier { void send(String message); }

然后短信、邮件、App 推送各自实现 Notifier。调用方只持有 Notifier 这个接口类型,具体用哪个实现由工厂或者配置决定。以后再加企业微信机器人,只需要新增一个实现类,旧代码一行都不用动。这就是策略模式,也是开闭原则的直观体现:对扩展开放,对修改关闭。

4.2 网络场景里最常见的设计模式

把设计模式硬生生背下来没有意义,你得在代码里认出它们。其实你整天都在用,只是没意识到名字。

比如你写 new BufferedReader(new InputStreamReader(socket.getInputStream())),这行代码本身就是装饰者模式。原始的字节流只负责吐字节,BufferedReader 在外面包了一层,给它加了“按行读取字符”的能力,而底层能力没变。这就跟手机壳一样,手机还是那个手机,套上壳子就多了防摔属性。

再比如网络框架里的事件机制,底层服务收到消息后,把消息转发给所有注册过的监听器,这就是观察者模式。我在实现消息推送模块时就是这么做的:连接层拿到数据,交给事件总线,各个业务模块各自订阅关心的事件,松耦合,互不干扰。

还有适配器模式也很常见。老系统返回的是 A 接口的数据结构,新模块要的是 B 接口的数据结构,中间写一个类把 A 翻译成 B,这就是适配器。单例模式则大量用在连接池、配置管理器这类“全局只能有一份”的对象上,注意线程安全问题。

4.3 如何避免过度设计

聊完这些,必须泼一盆冷水:设计模式不是越多越好。见过不少开发者学了几个模式就手痒,哪怕一个只有两个方法的小功能,也要硬套上工厂加策略加单例,最后代码量翻了三倍,可读性断崖式下跌。

我的判断标准很简单:当你能明确说出“这里加一层抽象是为了解决哪个具体变化点”时,才值得加;如果只是“可能以后会变”,那就先不要加。代码和需求一样,讲究够用就好。你现在写一个固定协议的客户端,就不要提前设计一个抽象工厂去应对十条未来协议,等第二条协议真的出现了,再重构也不迟。日常重构的成本远低于过度设计带来的长期维护成本。这个模块里设计模式的定位是“让你有识别套路的能力”,而不是“让你到处套套路”。

5. 综合实战:把三块知识拧成一个网络日志监控小工具

5.1 需求梳理与架构决策

理论讲完,下面重点来了。我把前面三块内容组合起来,做一个真实的小工具:一个通过网络传输日志、用正则解析、用设计模式分发处理的小型监控服务。这个例子不复杂,但闭环非常完整。

需求是这样:若干客户端会通过 TCP 连接向服务端持续上报访问日志,每行一条;服务端需要实时解析每一条日志,提取 IP、时间、请求方法、路径、状态码和字节数;然后根据状态码走不同的处理逻辑,比如状态码大于等于 400 的算异常日志,需要打印到错误输出并计数,正常日志打印到标准输出。

架构决策上,我做了三个选择。第一,接收入口用线程池而不是一连接一线程,防止客户端一多就把线程资源耗尽。第二,正则表达式编译成 static final 常量,避免每条日志都重新编译。第三,日志处理用策略模式加一个简单工厂,让“什么样的日志走什么样的处理”这件事可以独立扩展。

5.2 核心实现:网络接收加正则解析加工厂封装

先写解析器,它只干一件事:把一行文本解析成一个结构化的日志对象。

import java.util.regex.*; public class LogInfo { private final String ip; private final String time; private final String method; private final String path; private final int status; private final int bytes; public LogInfo(String ip, String time, String method, String path, int status, int bytes) { this.ip = ip; this.time = time; this.method = method; this.path = path; this.status = status; this.bytes = bytes; } // 省略 getter public int getStatus() { return status; } public String getIp() { return ip; } public String getPath() { return path; } } public class LogParser { private static final Pattern LOG_PATTERN = Pattern.compile( "^(\\S+) - - \\[([^\\]]+)\\] \"(\\w+) (\\S+) HTTP/\\S+\" (\\d{3}) (\\d+)$"); public LogInfo parse(String line) { Matcher matcher = LOG_PATTERN.matcher(line); if (matcher.matches()) { return new LogInfo( matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4), Integer.parseInt(matcher.group(5)), Integer.parseInt(matcher.group(6))); } throw new IllegalArgumentException("无法解析的日志行: " + line); } }

接着定义处理接口和两个策略实现。

public interface LogHandler { void handle(LogInfo log); } public class ErrorLogHandler implements LogHandler { @Override public void handle(LogInfo log) { System.err.println("[异常日志] " + log.getIp() + " -> " + log.getStatus()); } } public class NormalLogHandler implements LogHandler { @Override public void handle(LogInfo log) { System.out.println("[正常日志] " + log.getIp() + " " + log.getPath()); } } public class LogHandlerFactory { public static LogHandler getHandler(int status) { if (status >= 400) { return new ErrorLogHandler(); } return new NormalLogHandler(); } }

最后是服务端主程序。它把网络接收和前面这些组件拼起来:每收到一行数据就解析,解析完根据状态码从工厂拿处理策略,再交给策略执行。

import java.io.*; import java.net.*; import java.util.concurrent.*; public class LogMonitorServer { private final LogParser parser = new LogParser(); public void start(int port) throws IOException { ServerSocket serverSocket = new ServerSocket(port); ExecutorService pool = Executors.newFixedThreadPool(8); System.out.println("日志监控服务启动,端口 " + port); while (true) { Socket socket = serverSocket.accept(); pool.submit(() -> handle(socket)); } } private void handle(Socket socket) { try (BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8"))) { String line; while ((line = reader.readLine()) != null) { try { LogInfo log = parser.parse(line); LogHandler handler = LogHandlerFactory.getHandler(log.getStatus()); handler.handle(log); } catch (IllegalArgumentException e) { System.err.println("跳过异常数据: " + e.getMessage()); } } } catch (IOException e) { System.err.println("连接处理失败: " + e.getMessage()); } } public static void main(String[] args) throws IOException { new LogMonitorServer().start(9090); } }

测试用的客户端就简单多了,建立连接后把两条示例日志发过去:

import java.io.*; import java.net.*; public class LogSenderClient { public static void main(String[] args) throws Exception { try (Socket socket = new Socket("127.0.0.1", 9090); PrintWriter writer = new PrintWriter( socket.getOutputStream(), true)) { writer.println("192.168.1.1 - - [10/Oct/2023:13:55:36 +0800] \"GET /index.html HTTP/1.1\" 200 2326"); writer.println("192.168.1.2 - - [10/Oct/2023:13:56:01 +0800] \"GET /api/data HTTP/1.1\" 500 512"); System.out.println("日志已发送"); } } }

5.3 实测运行与优化记录

先启动服务端,再运行客户端,控制台输出大概是:

日志监控服务启动,端口 9090 [正常日志] 192.168.1.1 /index.html [异常日志] 192.168.1.2 -> 500

到这里,三块知识就形成闭环了:Socket 负责把日志从客户端搬到服务端,正则负责把日志文本变成结构化对象,工厂和策略负责决定每条日志该走哪条处理链路。这个例子我建议你亲手敲一遍,运行通了之后再自己扩展。比如把 NormalLogHandler 改成把数据写入数据库,或者给 ErrorLogHandler 加一个计数器和告警通知,你会发现所有扩展点都非常自然。

再从性能角度复盘一下这个实现。线程池把并发数限制在了 8 个,避免连接数暴涨拖垮服务;Pattern 只在类加载时编译一次,循环里只做匹配;每条日志解析后立刻丢给处理器,不攒批,延迟很低。如果日志量特别大,下一步可以考虑批量写入、用 NIO 替代阻塞 IO,或者引入消息队列削峰。但作为这个模块的综合练习,现在的架构已经够用,重要的是把链路跑通,而不是一开始就把架构堆上天。

6. 常见问题与排查技巧实录

6.1 高频问题速查表

把我在实际项目和教学过程中遇到的高频问题整理成一张表,你可以直接当备忘录用。

问题可能原因解决办法
正则越跑越慢,大文件处理时长不可接受灾难性回溯,嵌套量词过多简化正则、拆成多步、用非贪婪模式、必要时不用正则
正则匹配不到多行文本默认模式不匹配换行符用 Pattern.DOTALL 或显式匹配 \n
提取到的分组不是想象中那个值分组编号数错或分组嵌套顺序弄错用命名分组 (? ...) ,代码里用 matcher.group("name")
客户端连不上服务端,报 ConnectException服务端未启动、端口被占用、防火墙拦截先用 telnet 探测端口,再查监听状态
客户端发送数据后服务端阻塞不返回服务端在等换行符而客户端没发换行统一协议边界,发送后加 \n 并 flush
想扩展新的日志类型,改了很多旧代码设计上没有用接口接缝引入策略模式或工厂模式,把变化点隔离出来

6.2 排查思路与调试习惯

最后分享几个我长期使用的排查习惯。第一,先验证网络连通性再查代码。遇到“连不上”,不要一上来就翻代码,先用 nc -vz 127.0.0.1 9090 或者 telnet 连一下端口,能通说明网络层没问题,问题在应用层;不通就先解决监听和防火墙问题。这个顺序能帮你省掉大量无效定位时间。

第二,调试正则时不要直接上很长的表达式。先用一个最小测试字符串验证关键片段,比如先只测 IP 的匹配,再慢慢把整条表达式拼起来。这样出问题的时候,你永远知道是刚加的哪一段出了问题。

第三,生产环境里的数据永远比你想的脏。我的习惯是每一条数据解析都包一层异常捕获,解析失败就跳过并记录原始内容,而不是让整个服务挂掉。日志监控服务里我专门写了 try-catch 跳过非法行,就是这个原因。数据脏不是异常情况,而是默认情况,代码要按这个前提写。

6.3 想深入下去该怎么继续学

这个模块学完之后,下一步的延伸方向其实很清晰。网络编程可以往 NIO、Netty 方向走,理解 Reactor 模型和零拷贝;正则表达式可以深入引擎实现原理,看看自动机理论,同时把常用工具链里的正则语法差异摸一遍;设计模式则可以配合源码阅读来学,挑几个开源项目,看看里面是怎么组合使用模式的。学编程一直是这样,知识点的深度是学不完的,但如果你能把这个模块里“数据怎么传、数据长什么样、代码怎么组织”这三条线串起来,后面看任何项目都不会觉得是一座孤岛。

我在实际带教过程中最大的感受是:这三个主题单独拎出来都不难,难的是它们组合起来的那一瞬间——当一条字节流从网络进来,经过正则的拆解变成结构,再通过工厂找到正确的处理策略,那种“闭环打通”的感觉,会一下子改变你对整个项目运作方式的理解。如果你也正在学这个模块,建议你把上面的监控小工具完整跑一遍,然后试着加一种新的日志类型,比如把 JSON 格式的日志加进来。你会发现,之前学的 IO、集合、异常、多线程全都被调动起来了,这才是这个模块放在课程末尾的真正价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询