C/C++调用Java:JNI原理、实战与性能优化指南
1. 项目概述为什么要在C/C中调用Java在桌面应用、游戏引擎、嵌入式系统或者高性能计算的后台服务里我们常常会看到C或C的身影它们负责啃下那些对性能要求极高的硬骨头。但当我们想快速实现一个复杂的业务逻辑、接入一个成熟的网络库或者只是想用一下某个只有Java版本的开源算法包时从头用C再造一遍轮子就显得既笨重又不经济。这时候“在C/C中调用Java”就成了一个非常自然的技术选择。简单来说这就像在你的C主程序里嵌入了一个微型的Java虚拟机JVM。你的C代码是“主人”而Java代码则是被请来的“专业顾问”。主人C通过一套标准的“沟通协议”JNIJava Native Interface向顾问Java提出问题、传递数据并接收处理结果。这个能力极大地扩展了C/C程序的能力边界让你能站在Java生态这个巨人的肩膀上同时又不失C本身的性能优势。我最初接触这个技术是为了在一个用C写的实时数据处理系统中集成一个用Java写的、非常复杂的规则引擎。重写规则引擎不现实而通过JNI桥接我们只用了不到一周就完成了集成系统稳定性至今都很好。这让我深刻体会到掌握这门“跨界”技术能让你在技术选型上拥有更大的灵活性和主动权。2. 核心原理与架构拆解JNI是如何工作的要理解C/C如何调用Java核心必须吃透JNI。你可以把它想象成C世界和Java世界之间的一座精心设计的桥梁桥上不仅有通行规则还有翻译官和警卫。2.1 JNI的桥梁角色与数据类型映射JNI的核心是一套标准的API和约定。当你的C代码需要调用一个Java方法时它并不是直接跳进Java的内存空间去执行指令而是通过JNI接口向JVM发起一个“请求”。这个请求中包含了要调用的类名、方法名、参数信息等。JVM接收到请求后在其管理的Java堆中定位到对应的方法并执行然后将结果再通过JNI接口转换回C能理解的形式。这个过程里最需要关注的就是数据类型映射。Java中的int到了JNI里是jintString是jstring对象是jobject。这些j开头的类型是JNI定义的一套等价类型它们在C端只是一些句柄Handles真正的数据存储在JVM管理的Java堆中。C代码通过JNI函数如GetStringUTFChars来“锁定”并访问这些数据用完后必须显式释放如ReleaseStringUTFChars否则会导致内存泄漏。注意jstring到const char*的转换是一个高频操作点也是内存泄漏的重灾区。务必牢记“获取-使用-释放”的三部曲配对使用Get和Release系列函数。2.2 调用流程与线程模型剖析一次完整的JNI调用其内部流程可以简化为以下几个关键步骤创建JVMC程序首先需要启动一个JVM实例。这是所有后续操作的基础。获取类引用通过FindClass函数传入类的全限定名如java/lang/String获取一个jclass引用。获取方法ID通过GetMethodID或GetStaticMethodID传入类引用、方法名和方法签名获取一个jmethodID。方法签名是这里的关键它描述了方法的参数和返回类型格式如(ILjava/lang/String;)V。调用方法使用CallObjectMethod、CallIntMethod、CallVoidMethod等系列函数传入对象实例对于实例方法、方法ID和参数执行调用。处理结果与释放资源获取调用返回的JNI类型结果并在C端进行转换和使用。最后确保所有通过JNI获取的局部引用都被妥善管理避免引用累积导致内存溢出。关于线程有一个至关重要的原则只有附着Attach到JVM的本地线程才能调用JNI方法。主线程在创建JVM时自动附着。但对于你通过std::thread或系统调用创建的新线程在调用JNI前必须先用AttachCurrentThread将自己附着到JVM并在工作完成后通常是线程退出前调用DetachCurrentThread进行分离。实操心得在多线程环境下我强烈建议使用JavaVMAttachArgs设置线程组和名称便于在JVM监控工具如jstack中识别你的本地线程。同时可以考虑使用线程局部存储TLS来缓存JNIEnv*指针因为每个附着的线程都有自己的JNIEnv。3. 环境准备与工具链配置“工欲善其事必先利其器”。在开始编码之前正确的环境配置能避免一大半的奇怪错误。3.1 开发环境搭建你需要准备两套环境C编译环境和Java开发环境。C环境根据你的平台选择。Windows上常用Visual StudioLinux/macOS上用GCC或Clang。确保你的编译器支持C11或更高标准这会让智能指针等资源管理工具用起来更顺手。Java环境安装JDKJava Development Kit而不仅仅是JREJava Runtime Environment。因为我们需要javac编译器来编译Java类更需要JDK中的头文件jni.h等和链接库如jvm.lib或libjvm.so。关键步骤是让C编译器能找到JNI头文件让链接器能找到JVM库。头文件路径通常位于JAVA_HOME/include和JAVA_HOME/include/win32或linux、darwin。库文件路径Windows下是JAVA_HOME/lib/jvm.libLinux下是JAVA_HOME/lib/server/libjvm.somacOS下是JAVA_HOME/lib/server/libjvm.dylib。3.2 构建系统配置以CMake为例手动指定编译和链接参数很繁琐使用CMake可以优雅地管理。下面是一个极简的CMakeLists.txt示例它自动查找JDK并配置目标cmake_minimum_required(VERSION 3.10) project(JNIDemo) # 1. 查找JDK包这会设置一系列变量如 Java_JAVA_EXECUTABLE, Java_INCLUDE_DIRS find_package(Java REQUIRED COMPONENTS Development) # 2. 创建可执行文件目标 add_executable(jni_demo main.cpp) # 3. 为目标添加包含目录和链接库 target_include_directories(jni_demo PRIVATE ${Java_INCLUDE_DIRS}) target_link_libraries(jni_demo PRIVATE ${Java_JVM_LIBRARY}) # 4. 可选设置C标准 set_target_properties(jni_demo PROPERTIES CXX_STANDARD 11)这个配置的好处是跨平台。在Linux上find_package(Java)会找到libjvm.so在Windows上会找到jvm.lib。你不再需要硬编码路径。3.3 编写并编译Java类我们的C程序将要调用的Java类。创建一个简单的Demo.javapublic class Demo { // 一个静态方法供C调用 public static String sayHello(String name) { return Hello from Java, name !; } // 一个实例方法演示如何操作对象 public int add(int a, int b) { return a b; } // 用于测试的main方法 public static void main(String[] args) { System.out.println(sayHello(Tester)); } }使用javac Demo.java编译它得到Demo.class文件。你需要确保C程序运行时能通过类路径Classpath找到这个.class文件。通常的做法是将包含Demo.class的目录路径在启动JVM时通过-Djava.class.path参数传递进去。4. 从零实现一个完整的调用示例让我们从一个最简单的例子开始实现C启动JVM并调用上面的Demo.sayHello静态方法。4.1 启动JVM与基础调用#include jni.h #include iostream #include string int main() { JavaVM* jvm nullptr; JNIEnv* env nullptr; JavaVMInitArgs vm_args; JavaVMOption options[1]; // 1. 设置JVM启动选项指定类路径为当前目录 std::string classPathOption -Djava.class.path.; options[0].optionString const_castchar*(classPathOption.c_str()); vm_args.version JNI_VERSION_1_8; // 指定JNI版本常用1.8 vm_args.nOptions 1; vm_args.options options; vm_args.ignoreUnrecognized JNI_TRUE; // 忽略无法识别的选项 // 2. 创建JVM jint res JNI_CreateJavaVM(jvm, (void**)env, vm_args); if (res ! JNI_OK) { std::cerr Failed to create JVM, error code: res std::endl; return 1; } // 3. 查找并加载Java类 jclass demoClass env-FindClass(Demo); if (demoClass nullptr) { std::cerr Failed to find Demo class std::endl; jvm-DestroyJavaVM(); return 1; } // 4. 获取静态方法ID。方法签名为(Ljava/lang/String;)Ljava/lang/String; // 表示参数是一个String返回值也是一个String。 jmethodID sayHelloMethod env-GetStaticMethodID(demoClass, sayHello, (Ljava/lang/String;)Ljava/lang/String;); if (sayHelloMethod nullptr) { std::cerr Failed to find method sayHello std::endl; jvm-DestroyJavaVM(); return 1; } // 5. 准备参数并调用方法 jstring jName env-NewStringUTF(C Program); jstring jResult (jstring)env-CallStaticObjectMethod(demoClass, sayHelloMethod, jName); // 6. 处理返回的Java字符串 const char* cResult env-GetStringUTFChars(jResult, nullptr); if (cResult ! nullptr) { std::cout Java says: cResult std::endl; env-ReleaseStringUTFChars(jResult, cResult); // 必须释放 } // 7. 释放局部引用在简单的例子中JVM销毁时会处理但养成好习惯 env-DeleteLocalRef(jName); env-DeleteLocalRef(jResult); env-DeleteLocalRef(demoClass); // 8. 关闭JVM jvm-DestroyJavaVM(); return 0; }这个例子涵盖了最基础的流程。编译并运行它如果一切顺利你会在控制台看到Java says: Hello from Java, C Program!。4.2 处理复杂对象与异常现实中的调用不会这么简单。我们常常需要处理Java对象、数组以及捕获Java端抛出的异常。处理Java对象和调用实例方法// 假设我们已经有了 env 和 demoClass // 1. 获取构造方法ID并创建对象 jmethodID constructor env-GetMethodID(demoClass, init, ()V); jobject demoObj env-NewObject(demoClass, constructor); // 2. 获取实例方法ID jmethodID addMethod env-GetMethodID(demoClass, add, (II)I); // 3. 调用实例方法 jint sum env-CallIntMethod(demoObj, addMethod, 10, 20); std::cout 10 20 sum (calculated in Java) std::endl;异常处理JNI调用不会直接抛出C异常。如果Java方法抛出了异常它会在JNI层被挂起。你必须手动检查并处理。env-CallStaticObjectMethod(demoClass, someMethod); if (env-ExceptionCheck()) { // 1. 打印异常堆栈信息对于调试非常有用 env-ExceptionDescribe(); // 2. 清除异常否则后续的JNI调用会失败 env-ExceptionClear(); std::cerr An exception occurred in Java code. std::endl; // 处理错误... }处理数组// 在C中创建一个Java int数组并传递给Java方法 jintArray javaArray env-NewIntArray(5); jint cArray[] {1, 2, 3, 4, 5}; env-SetIntArrayRegion(javaArray, 0, 5, cArray); // 假设有一个接收 int[] 的Java方法 jmethodID processArrayMethod env-GetStaticMethodID(demoClass, processArray, ([I)V); env-CallStaticVoidMethod(demoClass, processArrayMethod, javaArray); // 从Java数组获取数据回C jint* elements env-GetIntArrayElements(javaArray, nullptr); // 使用 elements... env-ReleaseIntArrayElements(javaArray, elements, 0); // 释放4.3 性能优化与资源管理实践JNI调用是有开销的主要是查找类、方法ID以及数据转换。对于高频调用的方法性能优化至关重要。缓存IDFindClass、GetMethodID、GetFieldID等都是昂贵的操作。绝对不要在每次调用时都去查找。正确的做法是在初始化阶段如JVM启动后一次性查找并缓存这些ID。// 全局或静态变量中缓存 static jclass cachedDemoClass nullptr; static jmethodID cachedSayHelloMethod nullptr; void initCache(JNIEnv* env) { if (cachedDemoClass nullptr) { jclass localRef env-FindClass(Demo); cachedDemoClass (jclass)env-NewGlobalRef(localRef); // 提升为全局引用 env-DeleteLocalRef(localRef); } if (cachedSayHelloMethod nullptr) { cachedSayHelloMethod env-GetStaticMethodID(cachedDemoClass, sayHello, (Ljava/lang/String;)Ljava/lang/String;); } } // 后续调用直接使用 cachedDemoClass 和 cachedSayHelloMethod引用管理JNI引用分局部引用、全局引用和弱全局引用。局部引用在本地方法调用期间有效函数返回后自动释放。但如果在长时间运行的本地函数中创建大量局部引用而不删除可能导致JNI local reference table overflow错误。使用env-DeleteLocalRef()及时清理。全局引用跨线程、跨本地方法调用有效必须手动创建 (NewGlobalRef) 和删除 (DeleteGlobalRef)。用于缓存类、方法ID或对象。弱全局引用类似全局引用但不阻止垃圾回收。使用前需要检查是否已被回收 (IsSameObject(obj, nullptr))。减少跨界调用最昂贵的操作是C到Java的“来回跳”。如果可能尽量在一次JNI调用中完成更多工作而不是用多次小调用。例如设计一个Java方法接收一个复杂的数据结构或JSON字符串在Java内部处理完所有逻辑后返回一个综合结果。5. 高级主题与集成模式当基础调用掌握后你会面临更复杂的工程化问题。5.1 多线程环境下的JNI编程这是JNI编程中最容易踩坑的地方之一。核心规则每个线程在调用JNI前必须附着到JVM并且获取自己独立的JNIEnv*指针。void workerThread(JavaVM* jvm) { JNIEnv* env nullptr; // 将当前线程附着到JVM jint attachResult jvm-AttachCurrentThread((void**)env, nullptr); if (attachResult ! JNI_OK || env nullptr) { // 处理附着失败 return; } // 现在可以安全地使用env调用JNI方法了 // ... 你的JNI调用代码 ... // 线程工作完成后分离它 jvm-DetachCurrentThread(); }重要提示对于由你创建的、生命周期明确的线程如std::thread必须在退出前调用DetachCurrentThread()。对于线程池中的线程附着和分离的策略需要仔细设计有时让线程在整个生命周期内保持附着状态可能更简单。5.2 与现有框架的集成以Spring Bean为例你很可能需要调用的是一个庞大的Spring Boot应用中的某个Service Bean。直接调用MyService的某个方法是不行的因为Spring的依赖注入和上下文管理在JVM外部不存在。解决方案在Java侧创建一个“门面Facade”或“引导类Bootstrap Class”。这个类在main方法中或通过静态块初始化Spring上下文并将需要的Bean暴露为静态方法或单例。// JniBridge.java Component public class JniBridge { private static ApplicationContext context; private static MyService myService; // 静态初始化块在类加载时执行需确保在JNI调用前加载此类 static { context new SpringApplicationBuilder(YourMainApplication.class) .web(WebApplicationType.NONE) // 非Web应用 .run(); myService context.getBean(MyService.class); } // 提供给JNI调用的静态方法 public static String processViaService(String input) { return myService.complexBusinessLogic(input); } }然后在C端你只需要调用JniBridge.processViaService这个简单的静态方法背后就是完整的Spring Bean在工作。5.3 内存与生命周期的协同管理C和Java有着完全不同的内存管理模型手动/RAII vs. 垃圾回收。在JNI边界上传递对象时要格外小心。传递大数据避免在JNI边界上来回拷贝大量数据。对于大的字节数组或缓冲区可以考虑使用直接字节缓冲区DirectByteBuffer。它在堆外内存分配C端可以通过GetDirectBufferAddress直接访问其内存地址避免了拷贝。对象生命周期如果你在C端持有一个Java对象的全局引用你必须负责在C对象析构时删除这个全局引用 (DeleteGlobalRef)。最好使用C的RAII机制如自定义智能指针或析构函数来包装JNI全局引用确保资源自动释放。防止内存泄漏使用工具协助检查。在Java侧可以用-XX:PrintJNIGCStalls等JVM参数观察GC是否因本地引用而受阻。在C侧确保所有GetTypeArrayElements都有配对的Release所有NewGlobalRef都有配对的DeleteGlobalRef。6. 实战陷阱与调试技巧实录纸上得来终觉浅绝知此事要躬行。下面是我在项目中踩过的一些“坑”和总结的调试技巧。6.1 常见问题速查表问题现象可能原因排查与解决思路UnsatisfiedLinkError1. 库路径不对找不到JVM库。2. JNI函数签名错误。3. C函数名与Javanative方法声明不匹配名字混淆。1. 检查链接器参数确保jvm.lib/libjvm.so路径正确。2. 用javap -s -p ClassName反编译查看精确的方法签名。3. 对于C使用extern C防止名字改编或使用JNIEXPORT和JNICALL宏。FindClass返回nullptr1. 类名写错包路径、大小写。2. 类路径 (-Djava.class.path) 设置错误JVM找不到类文件。3. 由系统类加载器加载的本地代码无法直接查找非系统类复杂类加载器环境。1. 使用全限定名如com/example/Demo。2. 打印并检查设置的类路径。3. 尝试通过ClassLoader来查找类env-FindClass(java/lang/ClassLoader), 然后调用loadClass方法。GetMethodID返回nullptr几乎总是方法签名错误。1.黄金法则用javap -s -p ClassName获取绝对准确的方法描述符。2. 注意重载方法签名必须唯一区分。程序崩溃Segmentation Fault1. 使用了错误的JNIEnv*如在另一个线程使用了主线程的env。2. 访问了已被GC回收的Java对象弱引用未检查。3. 本地代码内存越界等错误波及JVM。1. 确保每个线程使用自己附着后获得的env。2. 对弱全局引用调用前用IsSameObject(obj, nullptr)检查。3. 使用AddressSanitizer等工具检查C端内存问题。内存缓慢增长局部引用未及时释放导致局部引用表溢出。1. 在创建大量局部引用的循环中主动调用DeleteLocalRef。2. 使用Push/PopLocalFrame进行引用管理的批量操作。性能低下在循环或高频路径中频繁调用FindClass/GetMethodID。缓存缓存缓存将所有ID查找移至初始化阶段。6.2 调试技巧与工具启用JNI检查在启动JVM时添加-Xcheck:jni参数。这会让JVM进行更严格的JNI调用检查能提前发现许多编码错误如传递错误参数类型、未检查异常等。虽然会牺牲一些性能但在调试阶段极其有用。使用ExceptionDescribe()当ExceptionCheck()返回true时立即调用env-ExceptionDescribe()。它会将Java异常的堆栈跟踪打印到标准错误输出这是定位Java端问题最直接的方法。在Java侧打印日志在你被调用的Java方法开头加入System.out.println或使用日志框架。这能直观地确认调用是否成功到达Java端以及参数是否正确。使用原生调试器用GDB或Visual Studio调试你的C程序。你可以在JNI调用前后设置断点查看JNIEnv*、jobject等变量的值。对于复杂的内存问题这是终极手段。简化与隔离当问题复杂时创建一个最小的、可复现的测试用例。剥离所有业务代码只保留最基本的JNI调用骨架。这能帮你快速判断问题是出在JNI层还是业务逻辑层。6.3 一个真实的“坑”字符串编码这是我早期遇到的一个棘手问题。在Windows上C端用NewStringUTF传递了一个中文字符串给JavaJava端收到后变成了乱码。原因NewStringUTF和GetStringUTFChars这两个函数名中的“UTF”是误导性的。它们实际上操作的是Modified UTF-8MUTF-8编码而不是标准的UTF-8。对于ASCII字符两者一致但对于非ASCII字符如中文编码方式不同。解决方案通用方案在C端将字符串如std::string或std::wstring转换为UTF-16编码然后使用NewString和GetStringChars/GetStringCritical系列函数。UTF-16是Java内部字符串的表示方式没有编码转换损失。// 假设有一个UTF-8编码的std::string input // 需要先转换为UTF-16具体转换代码依赖于你的跨平台库如iconv, ICU等 std::u16string utf16String convertUtf8ToUtf16(input); const jchar* str reinterpret_castconst jchar*(utf16String.data()); jstring jstr env-NewString(str, utf16String.length());平台相关方案在Windows上可以使用GetStringChars配合宽字符。但为了跨平台建议使用第一种方案或成熟的第三方库来处理编码转换。这个“坑”教会我处理JNI中的字符串时永远要明确当前的编码环境对于文本数据最好在接口设计初期就约定好编码格式如统一使用UTF-8并在边界进行显式转换。

相关新闻

PySide6 + Qt Widgets 系统开发

PySide6 + Qt Widgets 系统开发

1

2026/10/22 22:26:49 阅读更多 →
《万亿参数内卷下,国产大模型的理论桎梏与破局思考》

《万亿参数内卷下,国产大模型的理论桎梏与破局思考》

《万亿参数内卷下,国产大模型的理论桎梏与破局思考》 引言 2026年,以Kimi K3为代表的国产万亿级MoE大模型集中落地,从工程层面来看,国内AI团队已经完整掌握超大稀疏模型分布式训练、百万上下文长文本优化、开源私有化部署、多模态…

2026/10/21 3:12:23 阅读更多 →
数字签名技术原理与Windows实践指南

数字签名技术原理与Windows实践指南

1. 数字签名基础概念解析数字签名是现代密码学中确保数据完整性和身份认证的核心技术。简单来说,它就像纸质文件上的手写签名,但采用了数学方法实现更高的安全性。数字签名解决了三个关键问题:身份认证:确认消息确实来自声称的发送…

2026/10/21 20:40:10 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

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

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

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

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

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

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

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

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

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

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 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/22 22:56:44 阅读更多 →
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/22 22:56:12 阅读更多 →
黑夜航拍船只数据集训练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/22 19:06:46 阅读更多 →