Java快递管理控制台:多线程与网络编程实战

1. 从单机到联网:这个快递控制台到底在解决什么问题

很多刚学完Java基础的朋友,写快递管理系统的套路基本都一样:一个ArrayList存快递单,while(true)套个switch做菜单,增删改查全在本地跑。功能是能跑通,但代码写到后面自己都不想看——所有逻辑挤在一个main方法里,想加个“多用户同时操作”的需求直接傻眼。这个“快递管理控制台简易版”要干的事情,就是在保留控制台交互的前提下,把多线程和网络编程这两块硬骨头塞进去,让一个原本只能自己跟自己玩的单机程序,变成能同时服务多个客户端、并且后台能并发处理任务的迷你系统。

说白了,它解决的是三个层面的问题。第一,并发问题:多个客户端同时查询或录入快递时,共享的快递数据怎么保证不冲突、不丢失。第二,通信问题:客户端和服务端之间怎么约定协议、怎么收发数据、怎么处理连接断开。第三,结构问题:怎么把原本一坨的代码拆成职责清晰的模块,让网络层、业务层、数据层各管各的。适合谁看?如果你已经能独立写出一个带菜单的控制台程序,但对Thread、Socket、ServerSocket这些概念只停留在“背过八股文”的阶段,那这个项目就是给你练手用的。

我先把话说在前头:这个简易版只包含方法层面的实现思路和关键代码骨架,不会给你一个能直接双击运行的完整工程。原因很简单,网络编程这东西,环境差异太大,我直接甩一个“完整版”给你,你复制过去大概率跑不起来,然后就开始怀疑人生。所以我的策略是——把每个核心方法为什么这么写、参数怎么定、异常怎么兜底讲透,你自己照着搭,踩坑的过程本身就是学习。

2. 整体架构怎么拆:三层分离不是装样子

2.1 为什么一定要做分层

我见过太多初学者的快递系统,Client类里既有Scanner读输入,又有ObjectOutputStream发数据,还顺手把快递单存进了一个static List。这种写法在单机时代勉强能跑,一旦引入网络编程,立刻变成灾难现场。因为网络连接是不可靠的,客户端随时可能断线,服务端随时可能抛IOException,如果业务逻辑和网络IO搅在一起,你根本分不清一个异常到底是“用户输入了非法字符”还是“对方把网线拔了”。

所以这个项目的架构必须切成三层。数据层负责存快递单,用一个ConcurrentHashMap或者加锁的List来保证线程安全;业务层负责处理“录入快递”“查询快递”“修改状态”这些操作,它不关心数据是从网络来的还是从控制台来的;网络层负责ServerSocket监听、Socket读写、线程池调度。三层之间通过方法调用传递数据,而不是共享一堆static变量。

提示:分层的目的不是让代码看起来“高级”,而是让每一层可以独立测试。比如你可以先写一个不联网的测试类,直接调用业务层方法,确认逻辑没问题了,再接网络层。这样排查问题时范围能缩小一半。

2.2 服务端和客户端的职责边界

服务端要干的事情:启动时绑定端口,用ServerSocket.accept()阻塞等待连接,每来一个客户端就丢给线程池处理。注意,这里不要用new Thread()无限创建线程,客户端一多直接把系统资源吃光。用Executors.newFixedThreadPool(10)或者newCachedThreadPool()都行,前者适合并发量可控的场景,后者适合连接数波动大的场景。

客户端要干的事情:用Socket连接指定IP和端口,拿到输入输出流,然后进入一个循环——读用户输入、封装成请求对象、发给服务端、接收响应、打印结果。客户端的循环里必须处理SocketException,因为服务端一旦关闭,客户端读写会立刻抛异常,你得给用户一个友好的提示,而不是甩一堆红色堆栈。

职责边界清晰之后,你会发现一个好处:客户端可以换成任何形式,控制台也好,图形界面也好,甚至你写个脚本模拟请求也行,只要遵守同一套通信协议,服务端完全不用改。

2.3 通信协议怎么定:文本还是对象

这是网络编程里第一个让人纠结的选择。用ObjectOutputStream直接传Java对象,写起来爽,但要求客户端和服务端的类结构完全一致,版本稍微对不上就抛InvalidClassException。用纯文本协议(比如每行一条指令,字段用逗号分隔),通用性强,但解析起来麻烦,还得处理转义字符。

我的建议是:这个简易版用对象序列化。因为你的目标是练手,不是做生产级系统,对象序列化能让你把精力集中在多线程和连接管理上,而不是浪费在字符串分割上。但你要知道它的局限——真实项目里,跨语言通信基本都用JSON或Protobuf,Java原生序列化早就被淘汰了。

定义一个Request类,包含action(操作类型,比如"ADD""QUERY""UPDATE")和data(一个Map<String,Object>或者具体的快递对象)。再定义一个Response类,包含success(布尔)、message(提示信息)和result(返回数据)。这两个类必须实现Serializable接口,并且显式声明serialVersionUID,否则你改一个字段,旧客户端就连不上了。

3. 多线程到底加在哪里:别为了炫技而加锁

3.1 共享数据的线程安全方案

快递数据是典型的共享资源。多个客户端线程同时读写,不加控制必然出问题。最粗暴的方案是给所有业务方法加synchronized,但这样并发性能极差——一个人查快递,其他人全得等着。更好的做法是读写分离:用ReentrantReadWriteLock,读操作加读锁(允许多个线程同时读),写操作加写锁(只允许一个线程写)。

如果你不想手动管理锁,直接用ConcurrentHashMap存快递单,key用快递单号,value用快递对象。ConcurrentHashMap在JDK8之后用了CAS加synchronized锁桶的方式,并发性能很好,而且putIfAbsent、computeIfPresent这些原子方法能帮你避免大部分竞态条件。但要注意,如果你需要“先查询再修改”这种复合操作,光靠ConcurrentHashMap的单个原子方法不够,还是得加锁或者用compute方法把逻辑包起来。

// 用compute方法保证“查询+修改”的原子性 expressMap.compute(expressId, (id, express) -> { if (express == null) { return null; // 不存在,返回null } express.setStatus(newStatus); return express; });

3.2 线程池的参数怎么定

Executors.newFixedThreadPool(10)这个10不是拍脑袋来的。你要考虑两个因素:CPU核心数和任务类型。如果任务是CPU密集型的(比如大量计算),线程数设为CPU核心数 + 1比较合适;如果是IO密集型的(网络读写、文件操作),线程数可以设大一些,因为线程大部分时间在等待。快递管理系统的服务端任务主要是网络IO和简单的内存操作,属于IO密集型,线程数可以设为CPU核心数 * 2。

但更规范的做法是直接用ThreadPoolExecutor构造函数,明确指定核心线程数、最大线程数、空闲存活时间、任务队列和拒绝策略。这样你能清楚地知道线程池的行为,而不是依赖Executors的默认配置。

ThreadPoolExecutor executor = new ThreadPoolExecutor( 4, // 核心线程数 16, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue<>(100), // 任务队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );

注意:CallerRunsPolicy的意思是当队列满了,由提交任务的线程自己执行。这在服务端场景下能起到“背压”作用,防止任务无限堆积。但如果你不希望主线程被阻塞,可以换成AbortPolicy,然后自己捕获RejectedExecutionException给客户端返回“服务繁忙”。

3.3 每个客户端连接一个处理线程

服务端的accept()方法返回一个Socket之后,把这个Socket封装成一个ClientHandler任务,提交给线程池。ClientHandler实现Runnable接口,在run()方法里循环读取客户端请求、调用业务层方法、写回响应。循环的退出条件是客户端主动断开(读到null或抛EOFException)或者发生IOException。

这里有个细节:不要在finally块里只关闭Socket而忘了关闭流。虽然关闭Socket会连带关闭流,但显式关闭流能让代码意图更清晰,也避免某些平台上资源释放延迟的问题。另外,每个ClientHandler里要有一个volatile boolean running标志,方便服务端在需要时主动通知它停止。

4. 网络编程核心方法逐个拆解

4.1 服务端启动方法:绑定端口与监听

服务端的启动方法大概长这样:创建一个ServerSocket,绑定端口,然后进入while(!Thread.currentThread().isInterrupted())循环,调用accept()。accept()是阻塞的,所以通常把整个服务端启动逻辑放在一个单独的线程里,避免阻塞主线程。

public void start(int port) throws IOException { serverSocket = new ServerSocket(port); System.out.println("服务端已启动,监听端口:" + port); while (!serverSocket.isClosed()) { try { Socket clientSocket = serverSocket.accept(); executor.execute(new ClientHandler(clientSocket, expressService)); } catch (IOException e) { if (serverSocket.isClosed()) { break; // 服务端被主动关闭,退出循环 } e.printStackTrace(); } } }

端口选择上,建议用1024以上的端口,避免和系统服务冲突。如果你在本地测试,用8888、9999这种就行。但要注意,如果端口被占用,new ServerSocket(port)会抛BindException,你得捕获它并提示用户换端口。

4.2 客户端连接方法:超时与重试

客户端的连接方法不能傻等。new Socket(ip, port)默认会一直阻塞直到连接成功或系统超时,这个时间可能长达几十秒。更好的做法是用new Socket()创建空Socket,然后调用connect(new InetSocketAddress(ip, port), timeout)指定超时时间。

public void connect(String ip, int port, int timeoutMs) throws IOException { socket = new Socket(); socket.connect(new InetSocketAddress(ip, port), timeoutMs); outputStream = new ObjectOutputStream(socket.getOutputStream()); inputStream = new ObjectInputStream(socket.getInputStream()); }

超时时间设多少?本地测试设3000毫秒足够,局域网可以设5000,公网环境建议10000以上。但你要知道,超时之后connect会抛SocketTimeoutException,你得给用户一个“连接超时,请检查服务端是否启动”的提示,而不是直接崩掉。

4.3 请求发送与响应接收:顺序不能乱

用ObjectOutputStream发送对象时,有一个经典的坑:先创建ObjectOutputStream再创建ObjectInputStream,否则两边都会阻塞。因为ObjectOutputStream的构造函数会先写一个头部(stream header),如果两边都等着对方先写,就死锁了。所以服务端和客户端的流创建顺序必须一致,通常是先output后input。

发送请求的方法很简单:outputStream.writeObject(request); outputStream.flush();。注意flush()不能省,否则数据可能留在缓冲区里没发出去。接收响应就是Response response = (Response) inputStream.readObject();,这里要捕获ClassNotFoundException,虽然理论上不会发生,但编译器强制你处理。

提示:如果你发现客户端发一次请求之后,第二次就卡住了,大概率是忘了flush(),或者服务端在循环里没有正确重置ObjectInputStream。ObjectInputStream会缓存对象引用,同一个对象第二次发送时可能只发一个引用句柄,导致反序列化出问题。解决办法是每次发送新对象,或者调用reset()。

4.4 连接关闭与资源释放

关闭连接的正确顺序是:先关输入流,再关输出流,最后关Socket。但更简单的做法是直接用try-with-resources,让Java自动帮你关。不过在网络编程里,try-with-resources有时候不够灵活,因为你需要在一个方法里保持连接打开,在另一个方法里关闭。所以还是手动关比较可控。

public void close() { try { if (inputStream != null) inputStream.close(); } catch (IOException e) { /* 忽略 */ } try { if (outputStream != null) outputStream.close(); } catch (IOException e) { /* 忽略 */ } try { if (socket != null && !socket.isClosed()) socket.close(); } catch (IOException e) { /* 忽略 */ } }

注意,关闭流的时候抛IOException是很常见的,比如对方已经断开了,你这边关闭时就会报“Socket closed”。这种异常不需要往上抛,直接忽略或者记个日志就行。很多初学者在这里纠结半天,其实完全没必要。

5. 业务层与数据层的线程安全实现

5.1 快递对象的定义与序列化

快递对象需要包含单号、寄件人、收件人、状态、创建时间这些字段。实现Serializable接口,并且给一个固定的serialVersionUID。为什么要固定?因为如果你不写,Java会根据类的结构自动生成一个,你改一个字段,这个值就变了,旧数据反序列化就会失败。虽然这个项目里数据不落盘,但养成习惯是好事。

public class Express implements Serializable { private static final long serialVersionUID = 1L; private String id; private String sender; private String receiver; private String status; private long createTime; // 省略getter/setter和构造方法 }

5.2 业务方法的并发控制

业务层的方法比如addExpress、queryExpress、updateStatus,都要考虑并发。addExpress可以用ConcurrentHashMap.putIfAbsent来保证单号不重复。queryExpress直接get就行,ConcurrentHashMap的读操作是无锁的。updateStatus用computeIfPresent来保证原子性。

但如果你用的是ArrayList而不是ConcurrentHashMap,那就必须加锁。加锁的时候要注意锁的粒度——不要锁整个List,而是锁具体的操作。比如查询的时候用读锁,修改的时候用写锁。ReentrantReadWriteLock就是干这个的。

private final ReadWriteLock lock = new ReentrantReadWriteLock(); public Express query(String id) { lock.readLock().lock(); try { return expressMap.get(id); } finally { lock.readLock().unlock(); } } public boolean updateStatus(String id, String status) { lock.writeLock().lock(); try { Express express = expressMap.get(id); if (express == null) return false; express.setStatus(status); return true; } finally { lock.writeLock().unlock(); } }

5.3 请求分发:一个方法搞定路由

服务端收到Request之后,需要根据action字段分发到不同的业务方法。最简单的做法是switch或者if-else,但这样每加一个功能就要改分发方法。稍微好一点的做法是用Map<String, Function<Request, Response>>把action和处理器映射起来,初始化的时候注册好,分发的时候直接查表。

private final Map<String, Function<Request, Response>> handlers = new HashMap<>(); private void initHandlers() { handlers.put("ADD", this::handleAdd); handlers.put("QUERY", this::handleQuery); handlers.put("UPDATE", this::handleUpdate); handlers.put("LIST", this::handleList); } public Response dispatch(Request request) { Function<Request, Response> handler = handlers.get(request.getAction()); if (handler == null) { return new Response(false, "未知操作:" + request.getAction(), null); } return handler.apply(request); }

这种写法的好处是,新增功能只需要加一个方法并在initHandlers里注册,不用动dispatch方法。而且每个handler方法可以单独测试,逻辑清晰。

6. 实操中踩过的坑与排查技巧

6.1 常见异常速查表

异常信息可能原因排查方向
BindException: Address already in use端口被占用换端口,或用netstat查占用进程
ConnectException: Connection refused服务端没启动或端口不对确认服务端已启动,检查IP和端口
SocketTimeoutException连接超时检查网络,增大超时时间
EOFException对方关闭了连接正常现象,捕获后退出循环即可
StreamCorruptedException流头部不一致检查流的创建顺序,先output后input
ClassNotFoundException类路径不一致确保客户端和服务端有相同的类
InvalidClassExceptionserialVersionUID不匹配显式声明固定的serialVersionUID

6.2 客户端断线后服务端线程不退出

这是最经典的问题。客户端直接关窗口,服务端readObject()会抛EOFException,但如果你在catch块里只打印了异常而没有break,循环会继续执行,然后再次抛异常,陷入死循环。正确的做法是在catch到EOFException或SocketException时,设置running = false并跳出循环。

while (running) { try { Request request = (Request) inputStream.readObject(); Response response = service.dispatch(request); outputStream.writeObject(response); outputStream.flush(); } catch (EOFException | SocketException e) { System.out.println("客户端断开连接"); running = false; } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); running = false; } }

6.3 多线程下控制台输出混乱

多个客户端线程同时往控制台打印日志,输出会交错在一起,根本看不清。解决办法是用一个专门的日志方法,内部加锁,保证每次打印是原子的。或者更简单——服务端只记录关键事件,不要每个请求都打印。

private static final Object logLock = new Object(); public static void log(String message) { synchronized (logLock) { System.out.println("[" + Thread.currentThread().getName() + "] " + message); } }

6.4 对象序列化的性能问题

ObjectOutputStream每次发送对象都会写完整的类描述信息,数据量大的时候很浪费带宽。如果你要传大量快递数据,建议把List<Express>包装成一个Response对象一次性发送,而不是循环发送单个对象。另外,ObjectOutputStream内部有一个缓存,同一个对象第二次发送时可能只发引用,导致接收方拿到的对象状态不对。解决办法是每次发送前调用reset(),或者每次都创建新对象。

注意:reset()会清空流的状态,但也会导致下次发送时重新写类描述信息,增加开销。所以不要每次发送都调用,只在发送可变对象且发现状态不对时才调用。

7. 从简易版到完整版还能怎么扩展

这个简易版把多线程和网络编程的骨架搭起来了,但离“能用”还有距离。如果你想继续打磨,有几个方向可以走。第一,加心跳机制:客户端每隔几秒发一个空请求,服务端收到后回复,用来检测连接是否还活着。第二,加断线重连:客户端检测到连接断开后,自动尝试重新连接,而不是直接退出。第三,加数据持久化:把快递数据定期写入文件或数据库,服务端重启后数据不丢。第四,加图形界面:用JavaFX或Swing替换控制台,体验会好很多。

但我要提醒一句:每加一个功能,复杂度都是指数级上升的。比如加了断线重连,你就要考虑重连期间用户的操作怎么缓存、重连后怎么同步状态。所以我的建议是,先把当前这个简易版跑通,把每个方法的调用链路画清楚,再考虑扩展。我在实际带新人的时候发现,很多人连ObjectOutputStream和ObjectInputStream的创建顺序都搞不明白,就急着去搞Netty,最后除了复制粘贴什么也没学到。

最后分享一个我自己的调试习惯:在服务端和客户端的每个关键方法入口和出口都加一行日志,打印线程名和当前操作。这样一旦出问题,你能清楚地看到是哪个线程卡住了、卡在哪个方法上。这个习惯帮我省了无数个加班的夜晚。

相关新闻

基于Matlab的固定床非等温稳态模拟:方程、代码与工程实践

基于Matlab的固定床非等温稳态模拟:方程、代码与工程实践

简介&#xff1a;一套面向固定床反应器一维稳态模拟的Matlab程序源码&#xff0c;适用于化学工程、催化反应及计算流体动力学方向的工程技术人员与科研人员。固定床反应器内部填充固体催化剂颗粒&#xff0c;物料在颗粒间隙流动并发生反应&#xff0c;利用该程序可求解反应器轴…

2026/10/20 16:02:18 阅读更多 →
UVM验证环境搭建指南:组件整合与TLM连接实战

UVM验证环境搭建指南:组件整合与TLM连接实战

1. 为什么要把分散的组件拼成完整的验证环境从接触UVM开始&#xff0c;大多数人会先练习写driver、写monitor、写sequence&#xff0c;等这些基础组件一个个能跑通了&#xff0c;真正头疼的问题才浮出水面——怎么把这些零件装成一台上电就能跑、跑了结果还对、报错还能定位的完…

2026/10/20 16:02:18 阅读更多 →
最小二乘相位解包裹:原理、MATLAB实现与避坑清单

最小二乘相位解包裹:原理、MATLAB实现与避坑清单

简介&#xff1a;一份基于MATLAB的相位解包裹&#xff08;unwrapping&#xff09;实现代码&#xff0c;专门面向需要恢复连续相位信息的科研与工程人员&#xff0c;适用于光谱学、雷达信号处理、地震学等多个领域&#xff0c;也适合正在学习解包裹算法的学生。资源包内共包含3个…

2026/10/20 16:02:18 阅读更多 →

最新新闻

医学图像处理代码实战:从DICOM读取到三维重建与分割

医学图像处理代码实战:从DICOM读取到三维重建与分割

前一阵有个刚转行做医学影像算法的朋友来找我&#xff0c;说他从某医院那边拿到了一批脱敏后的CT数据&#xff0c;文件夹里装着一堆后缀为 .dcm 的文件。他第一反应是像处理普通照片一样用 OpenCV 直接读&#xff0c;结果 cv2.imread 返回一大片乱码。这个场景我太熟悉了&#…

2026/10/20 16:42:44 阅读更多 →
遗传相关估计实战:从多性状混合模型到育种选择决策

遗传相关估计实战:从多性状混合模型到育种选择决策

做育种数据分析这几年&#xff0c;我越来越觉得遗传相关是个“安安静静决定成败”的参数。表型相关很容易算&#xff0c;肉眼也大概看得出来&#xff0c;但真正决定我们能不能用一个易测性状去替难测性状做间接选择、能不能在多个目标性状之间找平衡的&#xff0c;是那个看不见…

2026/10/20 16:42:44 阅读更多 →
UE4 Bounds深度解析:视锥剔除与模型消失的常见原因与对策

UE4 Bounds深度解析:视锥剔除与模型消失的常见原因与对策

前阵子在调一个动作类Demo&#xff0c;碰到一个特别迷惑的Bug&#xff1a;主角过场动画里有个体型很大的BOSS模型&#xff0c;只要摄像机稍微拉远一两个身位&#xff0c;整个模型就原地蒸发&#xff0c;走近它又出现&#xff0c;再拉远又消失。我一开始怀疑是LOD切换阈值写错了…

2026/10/20 16:42:44 阅读更多 →
基于STM32与RFID的智能仓库管理系统全流程设计与复现解析

基于STM32与RFID的智能仓库管理系统全流程设计与复现解析

很多做毕设或者想自己搞一套仓库管理系统的同学&#xff0c;第一眼看到“STM32 RFID”这个组合&#xff0c;脑子里多半是两种反应&#xff1a;要么觉得“又是老一套读写卡片的demo”&#xff0c;要么觉得“这东西能撑得起一套完整的管理系统吗”&#xff1f;我当初看到这个开源…

2026/10/20 16:42:44 阅读更多 →
AI辅助毕业论文写作全流程指南:选题、降重到答辩的实操与避坑

AI辅助毕业论文写作全流程指南:选题、降重到答辩的实操与避坑

论文写作这事&#xff0c;经历过的人都懂那种“难产”的滋味&#xff1a;选题没方向、文献看不完、框架理不清、写出来的段落自己都看不下去&#xff0c;好不容易憋出一稿&#xff0c;查重一过又傻了眼。我见过太多同学从开题一路卡到答辩&#xff0c;不是不努力&#xff0c;而…

2026/10/20 16:42:44 阅读更多 →
PyTorch实战指南:LSTM、CNN、分布式训练与图网络高频问题全解

PyTorch实战指南:LSTM、CNN、分布式训练与图网络高频问题全解

1. 这个系列到底在整理什么这个"PyTorch 秘籍"系列整理到现在&#xff0c;已经不像一份教程了&#xff0c;更像是我平时在项目里翻工具箱时的随手笔记。RNNs 和 CNNs 是深度学习里最常碰的两个结构&#xff0c;Python 工具决定了我能不能一天跑完一组对照实验&#x…

2026/10/20 16:41:43 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →