FileChannel深度解析:零拷贝与内存映射实战指南
在Java的NIO体系里FileChannel是很多人学完Buffer之后第一个真正上手的通道类。它解决的问题很朴素让文件的读写不再像传统IO那样一边读一边频繁拷贝而是把文件抽象成一个可定位、可映射、可高效搬移的通道。作为整个NIO中唯一和本地文件系统直接打交道的通道FileChannel既能做普通文件的顺序读写也能在特定场景下借助零拷贝和内存映射让性能明显上一个台阶。这篇Day07就围绕FileChannel展开适合刚接触NIO、准备在项目里替换传统文件IO的同学也适合想弄明白transferTo、MappedByteBuffer到底怎么用的人。1. 先搞清楚为什么需要FileChannel传统IO的痛与NIO的答案1.1 传统流式IO的三个短板在用FileInputStream、FileOutputStream做文件复制时最常见的写法就是开一个字节数组循环读一块写一块。简单归简单但有个隐性问题每次read和write都是一次系统调用意味着要进一次内核态再出来数据也要从内核缓冲区搬到用户态字节数组再由字节数组搬回内核缓冲区。文件小的时候感觉不明显一旦换成几百MB甚至几个GB的文件这种反复横跳的开销就会被放大。传统IO还有一个麻烦就是流只能从头到尾单向走。FileInputStream有skip可以大致跳位置但精准定位还得靠包装成BufferedInputStream配合skip反人类而且低效。RandomAccessFile虽然能seek但它本质还是流式的读写模型没有“通道缓冲区”这种统一抽象。第三个短板是流式API很难和NIO的其他组件协同。比如你想把一个文件的一部分直接搬到网络连接里传统IO得把文件读进字节数组再交给输出流中间至少多一次内存拷贝。这种场景在文件下载、日志传输里很常见传统写法能跑但谈不上优雅。1.2 Channel加Buffer的设计哲学NIO给出的答案就是Channel和Buffer的组合。Channel代表一个开放的连接它不自己存数据数据都放到Buffer里由Buffer负责向内或者向外搬运。一张图就能说明程序把数据写入ByteBuffer后通过Channel.write把Buffer里的内容发给文件或网络要读数据时先让Channel.read把内容装进ByteBuffer程序再从Buffer里取。FileChannel就是这个模型里专门面向文件的实现。它本身是双向的允许在同一个通道上既读又写前提是你用RandomAccessFile或者FileChannel.open方式打开。这样文件的position指针可以被精确控制读写不再局限于顺序流。底层能力则由操作系统提供比如Linux上的sendfile和mmapJDK把这些能力封装成了transferTo和map方法Java程序员不用写一行native代码就能用上内核的特性。注意FileChannel默认是阻塞模式它不像SocketChannel那样能设置非阻塞。所以别把它和网络通道混淆FileChannel专注的是文件数据搬运。2. 拿到FileChannel的几种姿势与生命周期管理2.1 三种常规获取方式及读写能力对照很多同学第一次接触FileChannel都是从某个流对象里getChannel得到的这也是最常见的获取方式。但不同流拿出来的通道能力是不同的获取方式通道能力典型使用场景new FileInputStream(file).getChannel()只读读配置文件、读取日志分析new FileOutputStream(file).getChannel()只写新建文件、写输出结果new RandomAccessFile(file, rw).getChannel()读写需要同时读写的场景FileChannel.open(path, options)按options决定灵活控制打开模式这几种方式的差别本质上是底层文件描述符的打开模式不同。只读通道上调用write会直接抛NonWritableChannelException只写通道上调用read会抛NonReadableChannelException。如果你需要一边读一边改同一个文件老老实实用RandomAccessFile或者FileChannel.open别想着用两个通道分头搞。FileChannel.open是JDK 7以后推荐的方式它接受Path和StandardOpenOption。比如下面的代码同时指定了READ、WRITE、CREATE等价于RandomAccessFile的rw模式还额外支持了CREATE_NEW、APPEND等更细的语义FileChannel channel FileChannel.open( Paths.get(data.txt), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE );2.2 通道的开关与流的绑定关系从流里getChannel得到的FileChannel生命周期和流是绑在一起的。FileInputStream关闭时对应的FileChannel也会自动关闭反过来手动关闭了channel流的后续读写也会变得不可用。所以别两个都关也别都不关。通常习惯是既然操作都走channel那就只关channel让它连带把底层文件描述符释放掉。还有一点要注意同一个文件如果打开两个不同的通道它们各自维护各自的position互相不干扰。但是两个通道写同一个文件时会出现数据覆盖问题这是应用层需要自己协调的。读到通道的EOF时read方法返回-1这和传统流的read返回-1语义一致。isOpen方法可以查通道是否还活着但并没有什么实际的项目里非要轮询它不可更多是在调试和封装资源时用。3. FileChannel核心API拆解读写、指针与落盘3.1 顺序读与随机读read的两个重载FileChannel的read有两个常用重载一个是read(ByteBuffer dst)从当前position处读另一个是read(ByteBuffer dst, long position)从指定的绝对位置读。后者不会改变通道当前的position非常适合多线程并行读同一个文件的不同分段。常见的错误是以为read会一次把整个文件或整个Buffer填满。实际上read只是尽量读能读多少取决于底层设备、文件系统缓存等因素。它返回读到的字节数可能比Buffer的remaining小也可能被填满返回-1表示已经到文件末尾。所以在不确定数据量的情况下要循环读取直到返回-1ByteBuffer buf ByteBuffer.allocate(8192); int n; while ((n channel.read(buf)) ! -1) { buf.flip(); // 处理 buf 中的数据 handle(buf); buf.clear(); }如果不循环只调用一次read就按读满处理小文件侥幸能过大文件或者网络文件系统下很容易丢数据。这里要特别强调FileChannel的read是阻塞的它一定会等到读了一些数据或者到达EOF才返回不会像SocketChannel那样因为非阻塞直接返回0但该读多少次才能覆盖完整内容仍然是调用方需要关心的。3.2 顺序写与随机写write、position、size、truncatewrite也有两个重载write(ByteBuffer src)在当前位置写入write(ByteBuffer src, long position)在绝对位置写入。和read一样绝对位置写入不改变position。写的时候要控制好Buffer的flip把position切到可读状态不然写出去的长度永远是0。随机读写的核心玩法是组合position、size、truncate。position方法既能查询当前指针位置也能设置新的指针位置让后续读写从新位置开始。size返回文件当前大小truncate可以把文件截断到指定长度适合清空或者裁剪文件。举个例子我想在文件末尾追加一段内容同时保留原有内容可以直接利用sizetry (FileChannel channel FileChannel.open( Paths.get(app.log), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { byte[] data new record\n.getBytes(StandardCharsets.UTF_8); ByteBuffer buffer ByteBuffer.wrap(data); while (buffer.hasRemaining()) { channel.write(buffer); } }APPEND选项是文件系统层面的追加保证比先position到size再写更靠谱因为后者在多线程或进程并发追加时会有竞争窗口。3.3 force把数据真正交给磁盘很多人写完FileChannel后程序一崩溃数据没了一脸懵。原因是write只是把数据从用户态Buffer拷到内核的页缓存真正的磁盘落盘是操作系统择机干的。force(boolean metaData)就是主动要求内核把文件内容和元数据刷到磁盘channel.write(buffer); channel.force(true);force的boolean参数代表要不要连文件的元数据比如大小、修改时间一起刷。传true更安全但通常也更慢。刷盘的代价很大如果你在写一个高吞吐的日志每写一行就force一次性能会直线下降。正常的做法是批量写写完一批再force或者干脆每几秒刷一次。它和关系型数据库里的commit概念有点像数据安全性和性能之间永远是拿捏出来的。注意只读通道调用force一般不抛异常但也没有实际刷盘数据没意义。force真正关心的还是那些被写过的脏页。4. 零拷贝双雄transferTo与transferFrom4.1 零拷贝到底零在哪里零拷贝是FileChannel最吸引人的特性。拿文件下载举例传统IO要把文件从磁盘读到用户态字节数组再从字节数组写给Socket。中间有两条路径第一段是内核到用户态的拷贝第二段是用户态到内核的拷贝再加上至少两次系统调用CPU要参与搬运浪费严重。用FileChannel.transferTo之后操作系统会在内核态直接完成数据从文件到Socket的传输data从头到尾没有进过用户态这就是“零拷贝”的含义。它不是不拷贝而是避免了用户态和内核态之间的反复横跳。try (FileChannel source FileChannel.open(Paths.get(big.zip), StandardOpenOption.READ); FileChannel target FileChannel.open(Paths.get(copy.zip), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) { long position 0; long size source.size(); while (position size) { position source.transferTo(position, size - position, target); } }这段代码里position是把读起点精确控住的每次transferTo返回实际传了多少累加起来判断是否传完。这里要提醒一下transferTo的count并不是一个硬承诺它“尝试”读最多count字节结果可能因为设备、文件系统边界等因素少传所以循环是必须的。而且transferTo不会移动当前channel的position它只使用传入的position参数作为起始位置。4.2 transferTo实操一次高效文件复制用transferTo做文件复制最典型的是把一个文件复制到另一个文件。很多人会问transferTo到Socket才是零拷贝文件到文件是不是也零拷贝在Linux上JDK的实现通常会把文件到文件的情况交给内核做优化不同内核版本和不同文件系统下表现不一但无论如何它都比手动循环Buffer快因为内核能处理的时候绝不会回退到用户态兜圈。还有一个实际应用是把文件直接写到SocketChannel甚至阻塞SocketSocketChannel socketChannel SocketChannel.open(new InetSocketAddress(host, port)); FileChannel fileChannel FileChannel.open(Paths.get(payload.bin), StandardOpenOption.READ); fileChannel.transferTo(0, fileChannel.size(), socketChannel);如果你在做一个静态文件服务使用这个写法能把吞吐量提升不少。但要注意目标通道的能力如果目标是SocketChannel而且它是非阻塞模式一次transferTo可能只会塞满当前缓冲区返回0或多个字节这时候需要等目标通道可写再继续。对于阻塞式SocketChannel通常会比较顺利地一次传完。4.3 transferFrom与之对应的语义transferFrom是反方向的搬运从某个ReadableByteChannel读数据写入当前文件通道的指定position。它也不改变源通道的position吗准确说它不改变当前FileChannel的position但源通道如果是FileChannel内部是有状态的。比如用transferFrom把两个小文件合并成一个大文件try (FileChannel out FileChannel.open( Paths.get(merged.bin), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) { long writePos 0; for (Path p : List.of(Paths.get(part1.bin), Paths.get(part2.bin))) { try (FileChannel in FileChannel.open(p, StandardOpenOption.READ)) { while (writePos out.size() in.size()) { long moved out.transferFrom(in, writePos, in.size()); if (moved 0) break; writePos moved; } } } }这里有个细节第一个文件传完后第二个文件的读取会从第二个文件自己的位置0开始。多个源文件的合并关键是精确管理目标文件的writePos。transferFrom返回0一般表示当前没有可传数据可能在非阻塞源上还没就绪也可能源已经读到头需要在循环里小心处理避免死循环。5. MappedByteBuffer把文件映射进内存的魔法5.1 map的三种模式与适用场景FileChannel.map方法可以把文件的指定区域直接映射到内存返回一个MappedByteBuffer。之后的读写操作就变成了纯内存操作不需要显式调用read或write操作系统会通过缺页中断机制按需把文件数据加载到内存页。三种映射模式各自对应不同语义MapMode行为典型场景READ_ONLY只读映射写入抛ReadOnlyBufferException读取大文件、快速遍历二进制文件READ_WRITE修改会回写文件修改配置文件、做简易内存数据库PRIVATE写时拷贝修改不进磁盘临时修改数据、只想在内存里处理副本READ_ONLY模式最容易理解也最常用它要求打开通道时必须有READ选项。想改文件内容就必须用READ_WRITE并且通道打开时要同时带READ和WRITE。PRIVATE比较有意思它表面上给你一个可读写的Buffer但所有修改都走写时复制不会真正改动磁盘文件适合要对文件内容做临时“演习”的场景。5.2 实操MappedByteBuffer读写大文件用MappedByteBuffer读文件非常简单。下面这段代码把文件全部映射进内存然后直接按ByteBuffer的API操作try (RandomAccessFile raf new RandomAccessFile(data.bin, r); FileChannel channel raf.getChannel()) { MappedByteBuffer mapped channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); while (mapped.hasRemaining()) { byte b mapped.get(); // 处理每个字节 } }如果是写需要在map之后直接put数据再调用force刷盘try (RandomAccessFile raf new RandomAccessFile(db.bin, rw); FileChannel channel raf.getChannel()) { MappedByteBuffer mapped channel.map(FileChannel.MapMode.READ_WRITE, 0, 64); mapped.putInt(0, 42); mapped.putInt(4, 24); mapped.force(); }这里要注意map方法里的size参数是long但MappedByteBuffer继承自ByteBuffer可寻址空间受int下标限制单个映射的最大长度上限接近2GBJava API也明确说超过2GB要分段映射。遇到几十GB的文件就得循环地把文件切成多段映射。5.3 大文件映射上限与释放问题MappedByteBuffer有个让很多人头疼的地方它没有close方法不能像FileChannel那样显式关闭映射区域。映射的生命周期跟着GC走只有MappedByteBuffer对象被回收后底层映射才会被清理。如果频繁地创建映射又不及时触发GC文件描述符和虚拟内存会一路涨上去最终可能报Too many open files。实际项目里处理这个问题有人会用内部API手动触发cleaner比如反射调用sun.misc.Cleaner。但Java 9之后模块系统对内部API封锁很严反射也会被限制而且这种操作依赖JDK实现不稳定。我的建议是普通应用能接受GC方式释放就不要折腾cleaner如果必须在短时间内频繁映射大文件优先考虑复用一个MappedByteBuffer覆盖整个文件的生命周期或者改用传统的read/write方式。重要提醒MappedByteBuffer的position、limit等指针和ByteBuffer完全一致操作前先预热flip/clear/rewind这套流程不然很容易get到错误位置。6. 实战中绕不开的坑与性能对比6.1 常见异常与排查速查表我罗列几个自己踩过、也在别人代码里反复出现的坑按异常症状整理成一张表方便大家排查症状根本原因处理方式NonWritableChannelException从FileInputStream拿的channel却调用了write检查通道来源改用RandomAccessFile或FileChannel.openNonReadableChannelException从FileOutputStream拿的channel却调用了read同上打开模式要带READReadOnlyBufferException对READ_ONLY映射调用了putmap时用READ_WRITE或干脆新建一个普通ByteBufferFileNotFoundException / NoSuchFileException目标文件不存在又没有CREATE类选项显式加StandardOpenOption.CREATE或CREATE_NEWOverlappingFileLockException同一JVM内对同一文件区域重复加锁tryLock前检查是否已经持有锁或捕获后串行执行Too many open files每次操作新建channel却没关闭try-with-resources统一管理文件通道和流有一个非常容易忽略的问题从FileInputStream获取的只读通道如果直接调用transferTo从一个文件通道传到一个SocketChannel通常没问题但如果把同一个只读通道传到一个FileChannel.WriteOnly目标就会在写的时候抛NonWritableChannelException。所以整条链路的每个参与者读写权限都要提前确认一遍。6.2 三种IO方式的性能实测对比我在自己的机器上做过一次大文件复制对比1GB左右的二进制文件分别用传统BufferedInputStream循环读、FileChannel加ByteBuffer普通读写、FileChannel.transferTo复制结果如下方案耗时相对倍数传统流IO8KB缓冲约8.4秒基准FileChannel ByteBuffer8KB DirectBuffer约5.6秒约1.5倍FileChannel.transferTo约3.1秒约2.7倍这个数据适用条件很明显本机机械硬盘、单线程、数据量较大。在SSD上差距会缩窄一些在文件特别小的时候差距更是可以忽略反而通道的创建开销让FileChannel稍微吃亏。所以不要迷信“NIO一定快”它快在大文件、低拷贝、高并发读取的场景。小文件读写传统IO往往完全够用代码还更简单。如果是读文件内容做业务处理比如逐行解析MappedByteBuffer在大文件上会比普通Channel快很多因为省去了反复系统调用的开销直接映射到内存后就是普通的数组操作。代价是启动映射的瞬间有一次性开销适合那种“映射一次、读很多遍”的场景。6.3 几条来自一线的实操建议第一能用FileChannel.open就用它别老从流里getChannel。后者容易让人误解流已经关闭但通道还在或者反过来。FileChannel.open配合StandardOpenOption能让打开意图一目了然。第二写代码时把Buffer的limits、flip、clear看成流程的一等公民。FileChannel的读写严重依赖Buffer的position和limit宁可多写注释也别省这几行。第三多线程读写同一个文件时尽量用带绝对position的read/write重载或者为每个线程单独开一个channel。共享position的情况下并发线程往同一个位置写数据互相覆盖是必然的。第四Java 9以后的模块系统对internal API不友好尽量不要去碰MappedByteBuffer的Cleaner反射清理逻辑否则将来换JDK版本可能要返工。第五文件复制这个简单需求优先写transferTo。但真对SMB/NFS这类网络文件系统调用时中途可能出现奇怪的短传输代码里一定要循环判空别想当然认为一次调用就能搬完全部数据。我个人在实际操作中的体会是FileChannel最容易被低估的两个能力一个是“带position参数的读写重载”它让多线程分片处理文件变得异常干净另一个是map出来的MappedByteBuffer在只读分析场景的爆发力写业务代码时那点性能优势也许不算大但做离线批量任务时同样的数据量能救回不少时间。最后再分享一个小技巧无论是调试transferTo短传输还是排查force没落盘的问题先看一眼文件系统类型和挂载参数很多时候Linux和Windows的表现差距都是因为底层实现不同代码本身并没有错。把这个维度纳入排查思路很多百思不得其解的IO问题都能豁然开朗。

相关新闻

分数阶Calderón问题贝叶斯反演:MCMC与MATLAB实现全解析

分数阶Calderón问题贝叶斯反演:MCMC与MATLAB实现全解析

做反问题这些年,我发现一个特别有意思的共识:只要把经典算子换成分数阶,问题难度往往会跳一个台阶,但能解释的物理现象也瞬间丰富很多。分数阶Caldern问题就是这个方向的典型代表——它既要处理边界测量反演内部参数的经典难点&am…

2026/10/15 5:18:09 阅读更多 →
LLM推理性能测试指南:并发压测与指标归因实战

LLM推理性能测试指南:并发压测与指标归因实战

性能测试这件事,最怕的不是结果不好看,而是结果说不出为什么,换个环境就崩。上个月我把 vLLM 和 SGLang 两个推理引擎拉到一起做并发实验,前前后后折腾了两周,最后发现坑全不在引擎本身,而在实验设计和指标…

2026/10/15 5:18:09 阅读更多 →
诺蒂菲尔3030调试实战:点表配置、逻辑编程与故障排查指南

诺蒂菲尔3030调试实战:点表配置、逻辑编程与故障排查指南

简介:诺蒂菲尔3030消防编程调试说明书是一份面向消防工程技术人员、系统集成商及维护人员的操作手册,重点解决NFS-3030/E火灾报警控制器的编程与调试问题,同时覆盖320/640/3030/3030c/N6000等多种型号的调试方式。文档详细介绍了逻辑区编程的…

2026/10/15 5:17:09 阅读更多 →

最新新闻

Java3到底是什么?版本识别、JVM参数与升级避坑指南

Java3到底是什么?版本识别、JVM参数与升级避坑指南

第一次在工单系统里看到Java3这个项目名时,我愣了几秒。需求描述很干净:只有Java3,没有版本号,没有 README,也没有验收标准。我心里闪过两个念头:这该不会是 Java 3D 吧?可 Java 官方好像从来没…

2026/10/15 9:01:17 阅读更多 →
AI内容检测技术革新与2026工具评测

AI内容检测技术革新与2026工具评测

1. 项目背景与核心问题去年夏天我接手了一个内容审核项目时,发现团队每天要处理近3000篇疑似AI生成的文章。最头疼的是,这些内容往往能通过常规的查重检测,但读者反馈"机械感明显"。当时我们就意识到:传统的AI检测方法已…

2026/10/15 9:01:17 阅读更多 →
Python裸奔编码:极简开发的艺术与实践

Python裸奔编码:极简开发的艺术与实践

1. 项目背景与核心理念第一次听说"裸奔编码"这个概念是在2023年的一次技术沙龙上。当时一位资深开发者演示了如何用不到50行Python代码实现了一个完整的Web爬虫,而同样的功能在其他项目中往往需要数百行代码和多个依赖库。这种极简主义的编程方式让我深受…

2026/10/15 9:01:17 阅读更多 →
基于Spring Boot的智慧外贸平台设计与核心实现

基于Spring Boot的智慧外贸平台设计与核心实现

又到课题选题的高峰期,后台咨询量最大的一个方向就是“基于Spring Boot的智慧外贸平台”。很多同学来问的时候,第一句话都是:这个题目是不是就是做个电商网站?把商品放上去,能下单,能付款,就叫外…

2026/10/15 9:01:17 阅读更多 →
语义驱动的Diagram-as-Code设计方法论

语义驱动的Diagram-as-Code设计方法论

1. 项目概述:这不是画图,是构建可执行的系统逻辑骨架“diagram-design”这个词组乍看像一个普通的设计术语,但在我过去十年带过的几十个跨领域项目里,它从来不是PPT里拖拽几个矩形框、加几条箭头就完事的事。它本质是一种结构化思…

2026/10/15 9:01:17 阅读更多 →
t3code重构实录:用渐进式改造救活80万行遗留系统

t3code重构实录:用渐进式改造救活80万行遗留系统

接手一个代号叫 t3code 的旧项目,是我这几年做过最耗时,也最有收获的一件事。项目名里的 t3,其实是当初团队里一位老前辈随手起的内部代号——取的是 "Third Try" 的意思,也就是第三次推倒重来的版本。可讽刺的是&#…

2026/10/15 9:00:16 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →