Java Arrays工具类全解:从sort排序到parallelPrefix,一文吃透数组操作
凡是写过Java的人基本都绕不过数组。不管是学习阶段做的图书管理系统还是工作里处理批量数据、做算法题、写接口返回结构数组都是最底层、最常用的一块积木。但很多人在实际开发里对数组的处理方式其实非常原始复制一个数组要写循环比较两个数组要写循环想把数组转成List要先遍历add打印数组更是直接一个地址糊在脸上。明明JDK自带的Arrays工具类里就集成了这些操作而且是官方优化过、经过生产环境验证过的实现很多人却只停留在用过sort()和toString()的程度连parallelPrefix、spliterator这些高阶API听都没听过。这篇就把java.util.Arrays从常用到冷门的方法一次性讲透。不光是告诉你“这个方法是干什么的”更多是结合我自己的踩坑经历讲清楚为什么官方要这么设计、底层大概是怎么实现的、在什么场景下该用哪个方法、换了Java版本之后行为有没有变化。无论是刚学Java基础准备面试的后端新人还是写了好几年业务代码想把手头工具用得更有章法的工程师这篇都值得认真看完。1. Arrays工具类整体认知与定位1.1 一个纯静态方法的集合根本不需要实例化Arrays的定位和Collections很像一个工具类为对应的数据结构提供成套操作。Collections管的是集合框架Arrays管的是数组。这个类的构造方法是private的所以没法new Arrays()出来所有方法都是static的直接Arrays.xxx()调用就行。这意味着什么意味着这个类没有任何内部状态方法之间也不共享数据。它是一堆纯函数输入一个数组或者几个数组输出结果或者对数组做原地修改不会产生什么全局副作用。这也是我推荐新手去读源码的原因Arrays源码大概是整个JDK里最容易读懂的类之一没有异步、没有锁、没有复杂的继承结构大部分方法几十行就能看完读一遍对理解Java内存模型中的引用传递也有帮助。1.2 为什么有了数组语法还得有Arrays有些同学可能觉得int[] arr {1,2,3};不是挺好用的吗下标访问、循环遍历、arr.length拿长度原生语法已经很舒服了为什么要一个工具类因为原生语法只解决了“创建数组”和“按下标操作”这两个最基础的需求剩下的高频操作全部都是缺失的。比如把数组内容转成字符串原生语法做不到直接System.out.println(arr)打印出来的是[I1b6d3586这种类型标识加哈希码完全不是内容。两个数组做相等比较原生语法里arr1 arr2比较的是引用而equals()方法数组也没有正确重写两个内容相同的数组用equals判断也是false。数组扩容原生语法根本没有这个能力数组一旦创建长度就固定了扩容只能新建数组再复制元素而Arrays.copyOf把这个过程封装好了。快速填充、批量替换、排序、二分查找、转流这些全部是原生语法不覆盖的。所以要理解Arrays的价值不能把它当成一个“能帮我省几行代码的小工具”而要把它理解成数组这个数据结构在JDK层面的完整方法配套。你用数组就该用它配套的工具方法否则就是在重复造轮子而且大概率造得不如人家好。1.3 和其他容器的桥梁集合、流、数组互相转换数组在Java里是一种独立的数据结构但实际写代码的时候经常需要跟List、Stream互相打通。Arrays在中间承担了桥梁的作用。数组转ListArrays.asList(T... a)但这个转换有不少限制后面我会专门讲坑。数组转StreamArrays.stream(T[] array)基础类型数组还有专门的IntStream、LongStream、DoubleStream方法重载。List转数组这头则是集合那边提供的list.toArray()配合Arrays.copyOf的内部逻辑正好形成闭环。理解了定位之后下面把方法按功能分组逐个拆解。先看最常用的排序和查找。2. 排序与查找日常开发最常用的两组方法2.1 sort基本类型走双轴快排对象类型走TimSortArrays.sort()可能是所有方法里出场率最高的。但绝大多数人只停留在“调用就完事”的层面不清楚它背后为什么分了两套完全不同实现的策略。看一眼源码就能发现sort(int[])和sort(Object[])走的根本不是一条路。基本类型数组int、long、double等用的是Dual-Pivot Quicksort即双轴快速排序JDK 7引入的优化版本在大量实际数据上表现比传统单轴快排更稳定最坏情况下也不会退化到O(n²)那么夸张。对象类型数组用的则是TimSort一种稳定的自适应归并排序由Tim Peters在Python里提出后JVM也借鉴了过来。为什么要分开处理因为对象排序通常需要维持相等元素之间的相对位置比如先按姓名排序再按年龄排序两次排序下来如果第一次排序的次序在第二次排序时被破坏用户就会困惑。所以对象排序必须“稳定”而基本类型只需要数值顺序不需要保证稳定于是可以用更快但可能不稳定的快排。这个区别在实际开发中是有意义的。我自己就踩过这样一次坑一个订单列表先按时间排了一次然后按状态排用的是Arrays.sort(orderList, comparator)主观认为第二次排序不会打乱时间顺序结果发现状态相同的订单时间乱了。后来查文档确认了Arrays.sort(Object[])是稳定排序问题出在我自己写的Comparator上我给的比较器在状态相同的时候直接返回了0而不是继续比时间。所以如果你有“二次排序保持第一次排序结果”的需求写Comparator的时候必须记得在字段相同的情况下继续比下一字段而不是偷懒返回0。基本类型排序没有Comparator重载如果想对int[]按降序排不能直接传一个Comparator因为泛型不支持基本类型。常见的办法是先升序排再自己反转数组或者把元素装箱成Integer[]再传入Comparator.reverseOrder()代价是多了装箱和拆箱开销。数据量小无所谓数据量大我建议用反转或者干脆用IntStream做自定义处理没必要为了几行代码背性能债。2.2 parallelSort数据量足够大时才是好选择Java 8开始Arrays多了一组parallelSort方法底层用ForkJoin池把数组拆分排序再合并。很多人一看到“并行”两个字就兴奋觉得凡是排序都应该用这个方法这是误区。源码里有个阈值arrays.length小于某个值实际上内部是MIN_ARRAY_SORT_GRAN8192的时候parallelSort会直接调用sort走串行逻辑。也就是说数组不够长的时候你调用parallelSort根本不会并行还多了一层方法调用的开销写这段代码的人等于白激动。只有元素数量足够大的时候并行切分排序才有意义。并行排序适合什么场景比如日志分析、上报数据预处理、大规模数值计算里对几十万甚至上百万元素的数组排序。普通业务开发里一个页面最多几百条数据老老实实用sort就好加parallelSort并不会更快反而可能因为线程池调度增加延迟。另一个注意点是并行排序受ForkJoin公共池影响如果系统的公共池有其他任务在跑排序的速度也可能被拖慢。2.3 binarySearch返回负数不是“出错了”是插入点编码Arrays.binarySearch给很多初学者带来的困惑可能是所有数组方法里最多的。明明数组里有这个元素返回的下标是对的但查一个不存在的元素时返回值经常是负的而且负的那个数字看起来毫无规律。比如数组{2, 4, 6, 8}查5返回-3初学的人可能以为API坏了。其实这是官方刻意设计的返回值规则。先看结论如果找到了返回元素下标如果没找到返回值是-(插入点) - 1。插入点指的是该元素如果存在它应该被插入的位置下标。比如上面那个例子5应该插在4后面也就是下标2的位置所以返回-(2) - 1 -3。这个设计的好处是调用方拿到任何返回值都可以无歧义地恢复出插入位置如果返回值 0找到了如果返回值 0实际插入点就是-ret - 1。用这个规则写“不存在就插入”的代码就非常自然int idx Arrays.binarySearch(arr, 5); if (idx 0) { int insertPos -idx - 1; // 执行插入逻辑 }使用二分查找有一个极其重要的前提数组必须是有序的。如果数组没有排过序binarySearch的行为是未定义的可能碰巧对但更多时候返回的结果莫名其妙。我自己刚工作的时候就在这上面吃过亏一个去重逻辑里忘了排序结果某些数据能命中某些数据返回了错误的负数排查了半小时才发现问题。记住一个口诀先sort再binarySearch顺序不能反。对象数组还有带Comparator的重载版本同样要求数组按该比较器排序。3. 数组拷贝与填充边界情况多到值得专门写一篇3.1 copyOf和copyOfRange扩容的核心机制数组长度不可变是Java数组设计里最让新手抓狂的特性。实际写代码的时候经常需要一个“动态容量”的数组比如收集不确定数量的数据。Arrays.copyOf就是为了这个场景设计的。看签名copyOf(T[] original, int newLength)。它做的事情很朴素创建一个长度为newLength的新数组把original里的元素尽量复制过去。如果新数组更短就只复制前newLength个元素相当于截断如果新数组更长复制完原数组之后剩余位置填默认值基本类型是0、false、0.0引用类型是null。这个“填默认值”的细节特别有用因为这意味着你手写循环扩容时必须自己处理空位而官方方法已经把这一步做完了。copyOfRange是在一个数组里按起止下标截取一段同样的边界规则。浅拷贝这一点必须时刻记住如果数组存的是引用类型对象copyOf拷贝的是引用本身不是对象内容。修改拷贝出来的对象内部状态原对象也会变。基本类型数组则不存在这个问题复制的就是实实在在的值。我实际开发里最常见的用途之一是实现“数组栈”或者“数组队列”比如手写一个按需扩容的容器时直接在内部调用Arrays.copyOf比手动new数组加System.arraycopy要简洁得多。还有一个高频场景是配合反射做泛型数组String[] arr list.toArray(new String[0]);早年很多老程序员推荐new String[list.size()]但后来JDK官方实测new String[0]性能更好因为这个写法会触发Arrays.copyOf走JVM优化的复制路径。虽然这是Collection.toArray那边的逻辑但底层的优化思路是相通的copyOf是一个高度优化的基础方法与其传一个“正好够用”的数组不如传一个空数组让JDK用自己擅长的方式去扩容。3.2 fill和setAll批量赋值的两种姿势Arrays.fill用来把数组的某个区间全部赋成同一个值。最的简单场景是初始化一个int数组为-1或者把布尔数组全部置为true。使用fill时有个很容易忽略的坑当数组是引用类型时fill填进去的是同一个引用。比如你要创建一个长度为n的对象数组并填充一个新的Person对象写Person[] people new Person[5]; Arrays.fill(people, new Person(张三));这样写出的结果是一个拥有5个“同一个Person对象”的数组而不是5个独立的Person。你改people[0]的名字people[1]到people[4]的名字全跟着变。要创建5个独立对象得用循环或Arrays.setAll。setAll是Java 8加入的方法它不填固定值而是根据索引生成值签名是Arrays.setAll(T[] array, IntFunctionT generator)输入的IntFunction接收下标返回该下标位置要放的元素。这样就能优雅地生成一个下标和值有映射关系的数组比如arr[i] i * i。还有parallelSetAll版本数据量大时用并行方式生成值。注意别对太小的数组用parallelSetAll并行开销可能反而比串行大。3.3 二维数组和“假填充”问题fill用在二维数组上时坑更深。int[][] matrix new int[3][3]; Arrays.fill(matrix, new int[]{1,2,3});这里填充的还是引用三行其实指向了同一个一维数组。修改matrix[0][0] 99三行的第0列全变99。我第一次意识到这个问题是在写一个简单的矩阵算法时结果矩阵的行全部“共享”了排查了半天才发现问题出在fill上。正确的二维数组初始化方式是对每一行单独处理int[][] matrix new int[3][3]; for (int i 0; i matrix.length; i) { Arrays.fill(matrix[i], 0); }或者直接用Arrays.setAll给每一行生成独立的新数组。4. 数组与集合互转asList的三宗罪与toString的真相4.1 asList返回的不是java.util.ArrayListArrays.asList可能是整个Arrays类里最多人用错、也最多人面试栽跟头的方法。先明确一个事实这个方法返回的确实是List的实例但它的真实类型是Arrays$ArrayList一个Arrays内部定义的私有静态类不是我们日常用的java.util.ArrayList。两者最大的区别是Arrays$ArrayList没有重写add和remove它的底层直接引用了传入的数组大小完全固定。所以第一个坑就出现了对Arrays.asList的返回值调用add或remove会直接抛出UnsupportedOperationException。很多人在写代码的时候不以为意觉得都是List凭什么不能add结果一运行就炸。不是说完全不能用这个结果如果你只是需要只读列表、遍历、get、set它是可以用的而且因为底层是数组的视图set会直接写回原数组。但任何改变元素个数的操作想都不要想。第二个坑更隐蔽当你用基本类型数组去调用Arrays.asList时结果会出乎意料。比如int[] arr {1, 2, 3}; Listint[] list Arrays.asList(arr);返回的居然是一个元素类型为int[]、长度为1的列表。因为泛型不支持基本类型T被推断为int[]整个数组被当成了一个对象塞进了列表。要正确得到ListInteger得用Integer[]包装Integer[] arr {1, 2, 3};然后Arrays.asList(arr)才是三个元素。这是面试里非常高频的一个考点本质上考的是对Java泛型擦除和基本类型自动装箱机制的理解。第三个坑是直接使用asList之后试图修改元素个数来“变相扩容”数组结果全是UnsupportedOperationException。正确做法是拐个弯ListInteger list new ArrayList(Arrays.asList(1, 2, 3)); list.add(4); // 正常这也是实际开发里最常见的写法先通过Arrays.asList快速生成一个不可变列表再用它作为ArrayList构造器的参数copy出一个真正的可变列表。Java 9之后还可以用List.of(1, 2, 3)直接创建不可变列表语义更加明确避免了asList的泛型陷阱。4.2 toString与deepToString关于数组打印的真相数组直接打印为什么会是一串地址因为数组是一个对象但它没有重写toString()。Object.toString()的默认实现是“类名十六进制哈希”所以直接System.out.println(arr)输出的是[I1b6d3586这种东西。方括号里的I代表int类型这是JVM规范的数组类型描述符。Arrays.toString(arr)重载了所有基本类型数组和引用类型数组输出类似[1, 2, 3]的格式这是日常调试用的。但它有个局限如果数组的元素是对象而这些对象的类没重写toString()输出出来的就是一堆对象地址。对于多维数组Arrays.toString同样不够用因为它只调用了第一层数组元素的toString()而第一层元素本身就是一维数组输出还是地址。这时候要用Arrays.deepToString。这个方法会递归处理嵌套的数组结构把每一层元素都转成可读字符串。所以结论很简单一维数组用Arrays.toString二维及以上的嵌套数组用Arrays.deepToString对象数组元素对象的类必须自己重写toString才能得到有意义的内容我调试一些算法的中间结果时经常要用这两兄弟。还有一个小技巧把数组转成字符串之后再用String.format拼日志不愁打日志把内存打爆的写法是System.out.println(Arrays.toString(arr).substring(0, Math.min(Arrays.toString(arr).length(), 200)));不过这个也暴露了另一个问题直接反复调用toString可能造成重复计算调试时图省事可以正式代码里需要注意。4.3 equals与deepEquals数组比较的正确姿势前面说了数组没有重写equals()所以arr1.equals(arr2)跟arr1 arr2在效果上完全一样都是比较引用。那怎么比较两个数组内容是否相等用Arrays.equals。Arrays.equals有基本类型和引用类型的重载比较逻辑是先看长度再看每个位置的元素值。对于基本类型数组直接比较值对于引用类型数组用Objects.equals判断每个元素也就是说如果元素自己的equals写得有问题这个比较也会不可信。和deepToString类似Arrays.equals面对嵌套数组时第一层元素的equals显然不会去比较子数组内容因为数组的equals是引用比较所以比较二维数组必须用Arrays.deepEquals(a1, a2)它递归处理嵌套数组的比较。一个很容易踩的坑是想比较两个int[][]是否相同用了Arrays.equals结果永远返回false。甚至两个都从同一个源复制出来的二维数组Arrays.equals也返回false。原因就是这一层递归没有做。所以记住嵌套数组一律用deep系列。Java 9之后还增加了Arrays.compare和Arrays.mismatch。compare用来按字典序比较两个数组返回负数、0、正数类似于字符串的compareTo。mismatch返回两个数组第一个不同元素的下标如果完全一样返回-1。这两个方法在处理多维数组、做数组字段排序时非常方便比手写循环优雅得多。5. Stream、并行与原语进阶API的实战价值5.1 stream数组与函数式编程的桥梁Java 8引入Stream之后Arrays也顺势加了一组stream()方法。基本类型数组有专门的重载Arrays.stream(int[])返回IntStream还有long[]和double[]的版本引用类型数组则返回对应的StreamT。这意味着处理数组时可以直接串起各种流式操作而不用先把数组转成List再list.stream()。比如对一个int[]求最大值int max Arrays.stream(arr).max().orElseThrow();统计元素个数、过滤、映射、求和、转Set、分组统统可以在流上完成。更妙的是IntStream还自带sum()、average()、summaryStatistics()这些聚合方法日常数据分析不要太爽。数据量大时还可以在stream()之后加.parallel()转并行流配合多核CPU加速。不过要提醒一下流式操作对初学者来说调试体验比循环差一些。循环里可以随手打日志流里面打日志就得靠peek。实际开发我的建议是简单循环能处理的别硬上Stream但凡是过滤、聚合、分组这类高表达力的活儿Stream写出来的代码可读性远好过一堆forEach。5.2 setAll、parallelSetAll、parallelPrefix来自源码的宝藏setAll前面已经提到过Java 8加的用生成器函数按索引赋值。还有个parallelSetAll它用并行方式对每个位置执行生成函数适合超长数组的初始化比如生成100万长度的斐波那契数列。不过先别急这种涉及的只是“读索引、算值”的过程如果生成函数本身很简单比如i - i * 2并行未必快因为拆分任务和合并结果的开销可能超过计算本身。parallelPrefix是另一个冷门但极其强大的方法。它的作用是用指定的二元运算符对数组连续元素做累计计算结果原地写回。也就是前缀和prefix sum// 输入 [1, 2, 3, 4]求和前缀 Arrays.parallelPrefix(arr, Integer::sum); // 输出 [1, 3, 6, 10]这个方法在数值计算里用途很广比如范围查询、累计涨幅、实时统计等。名字里的parallel不是装饰它是真的并行执行的初始时扫描前段任务再用ForkJoin框架分块计算每个块维护自己的局部前缀最后再合并调整。如果数组不够长它会直接退化成串行。这里有个经验教训如果你想对数组做“每个元素等于前面所有元素之和”的累计操作手写循环做前缀和是O(n)看起来也不差但parallelPrefix在大数组上的并行化能把耗时压下去很多。生成的代码也比手写循环少还不容易下标越界。5.3 spliterator与Spliterator特性装叉但有用的知识点Arrays.spliterator(T[] array)返回一个Spliterator这是并行流切分任务的底层机制。日常业务代码里直接操作Spliterator的机会不多但理解它有助于明白parallelStream是怎么把数组切给多个线程的。Spliterator的几个关键特性标志ORDERED表示元素顺序有意义SIZED表示元素数量已知SUBSIZED表示每个切分出的子部分大小也已知。数组天然满足这些条件所以Arrays.spliterator返回的实例在这几个特性位上都置了1JVM可以据此做更优化的并行切分策略。如果你要写自己的并发处理框架或者想更精细地控制并行流的拆分策略可以用Spliterator.trySplit手动拆数组。不过大部分人知道这个概念就够了真到了要用的时候再去细看源码也来得及。5.4 Java 9的compare和mismatch别再用老办法比较了前面提过Java 9给Arrays增加了compare和mismatch两个系列方法。它们分别覆盖基本类型和引用类型而且还支持通过Comparator自定义比较规则。compare的典型场景是对一组数组字段做排序比如一个列表里的记录每人都带一串指标数据你想按指标数组的字典序排序Arrays.sort(records, Comparator.comparing(r - Arrays.compare(r.metrics1, r.metrics2)));这个写法很清晰。如果不用compare你得手写一个循环去比两个数组的每一个下标。mismatch则适合做数据校验、找出两个数组第一个不一致的位置在测试断言中也挺好用。JDK版本这事儿值得多说两句。很多团队目前的主流版本还是Java 8或者Java 11所以Arrays的API差异在实际开发中是真实的令人困惑的点mismatch在Java 8里根本不存在编译器直接报红List.of也是Java 9的。如果你的项目编译环境是JDK 8写代码时踩到API不存在的坑就明白了。所以用Arrays之前先确认自己项目的JDK版本别写着写着用了一个当前版本没有的方法。6. 实战案例与常见问题排查6.1 合并两个数组的三种写法日常开发里经常要把两个数组合并成一个。手写for循环是最原始的一版但用工具类可以写得非常简洁String[] a {a, b}; String[] b {c, d}; String[] result new String[a.length b.length]; System.arraycopy(a, 0, result, 0, a.length); System.arraycopy(b, 0, result, a.length, b.length);或者更简单用Stream.concatString[] result Stream.concat(Arrays.stream(a), Arrays.stream(b)) .toArray(String[]::new);写法上后者要优雅很多但拼接数组本身是个O(n)操作用哪种差别不大。如果是要合并很多个数组每一轮都concat会产生中间对象性能一般。大数据量合并建议固定大小后System.arraycopy这是JVM最擅长的内存复制方式。6.2 数组去重、求交集、统计频次去重一般第一反应是转Set但如果是基本类型数组手动处理起来稍微多几步int[] arr {1, 2, 3, 2, 1}; int[] distinct Arrays.stream(arr).distinct().toArray();这一行就能搞定。求两个数组的交集int[] a {1, 2, 3}; int[] b {2, 3, 4}; SetInteger setB Arrays.stream(b).boxed().collect(Collectors.toSet()); int[] intersection Arrays.stream(a) .filter(setB::contains) .toArray();统计频次就更明显了MapInteger, Long freq Arrays.stream(arr) .boxed() .collect(Collectors.groupingBy(i - i, Collectors.counting()));可以看到只要把数组转成流所有集合里能用的技巧全部都能无缝接上这就是Arrays.stream最大的价值。6.3 典型问题速查10个Arrays使用高频坑我整理一个表格几乎把日常用到Arrays的大坑都涵盖了进去问题现象原因正确做法/规避方式Arrays.asList后调用add/remove抛UnsupportedOperationException返回的是内部固定大小List包一层new ArrayList()或使用List.of基本类型数组传给asList得到的List长度为1元素是int[]泛型无法接收基本类型用Integer[]包装binarySearch结果负数明明存在却返回-1数组未排序或比较器不一致先sort再binarySearch数组直接打印输出[Ixxxx地址数组未重写toString用Arrays.toString或deepToString二维数组用equals比较内容相同返回false递归层未对比子数组用deepEqualsfill填充引用类型所有元素指向同一对象fill拷贝引用逐项setAll或循环newparallelSort和数据量太小排序性能反而下降小于8192退化为串行数据量大才用并排序copyOf修改新数组原数组也被改了引用类型浅拷贝深拷贝需自行递归复制Arrays.stream后忘boxed()无法直接collect(toList())基础类型流没有Collector需要引用类型时先boxed()sort后想降序排列直接用Comparator报错基本类型数组不支持泛型Comparator装箱Integer[]或手动反转这些坑我几乎一个个踩过。有些是初学时就踩的比如直接打印数组有些是工作了好几年才吃过的亏比如在并行流里用parallelSetAll没看数据量。把它们放在一起是希望后面的人少走几步弯路。6.4 面试常见考点Arrays类背后的原理问题在Java面试里Arrays类相关的考点也很容易碰到。除了前面提到的asList的坑和parallelSort的阈值还有几个常见问题Arrays.sort为什么有快排和TimSort两套实现本质是为了稳定性需求不同和数据结构特性有关。Arrays.copyOf和System.arraycopy的区别是什么arraycopy是native方法只负责复制copyOf除了复制还能扩容并填充默认值。binarySearch为什么找不到时返回值是-insertionPoint - 1为了保证所有负数返回值都能唯一映射回插入点。数组为什么不能直接equals因为数组没有重写Object.equals它是引用比较。理解这些问题的关键不在于背答案而在于理解JDK设计者的取舍逻辑。比如二分查找返回值的编码方式如果不这么设计调用方就得通过其他途径知道“没找到”和“插哪”返回单个int就同时携带这两个信息属于非常漂亮的设计。结尾写到这里Arrays工具类的主体内容基本说完了。如果只让我总结一个使用原则那就是数组相关的操作先想一下JDK有没有现成的方法有就优先用。我见过太多人一上来就手写循环既慢又容易边界出错其实官方工具类把边界已经处理好了源码就放在那还能翻着学设计。最后分享一个我自己的习惯每次升级JDK版本后我都会抽个时间过一遍java.util.Arrays的类文档看看这个版本新增了哪些方法。JDK 8的stream三件套、JDK 9的compare和mismatch都是靠这个习惯才发现的好东西。新的工具方法往往能简化掉一大片低价值代码。这个类不大但吃透它你写Java的日常体验会舒服很多。

相关新闻

110kV线路三段式相间距离保护整定计算与Simulink仿真实践

110kV线路三段式相间距离保护整定计算与Simulink仿真实践

电气工程专业的学生或者刚入行的继保新人,大概率都接过这样一张任务书:110kV线路三段式相间距离保护,要求Simulink仿真,外加一份详细研究报告。这题目看着经典,但真要动手,会发现卡壳的点全藏在细节里——整…

2026/10/9 4:21:45 阅读更多 →
无需开发板:STM32驱动DS18B20与OLED仿真实战

无需开发板:STM32驱动DS18B20与OLED仿真实战

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

2026/10/9 4:21:45 阅读更多 →
PS5手柄驱动与固件解析技术指南

PS5手柄驱动与固件解析技术指南

我无法根据当前输入生成符合要求的博文。原因如下:项目标题“AnyPS5”缺乏明确指向性,未说明其性质(是工具、项目、社区、改装方案、模拟器相关?);项目正文为空,无任何功能描述、技术背景或使用…

2026/10/9 4:21:45 阅读更多 →

最新新闻

LRE框架:重构AI智能体的时间感知与因果记忆机制

LRE框架:重构AI智能体的时间感知与因果记忆机制

1. 这不是“给AI加个备忘录”,而是重构智能体的时间感知能力很多人第一次看到“AI智能体记忆管理”这个词,下意识会想:不就是让大模型多存点上下文、加个向量数据库当外挂硬盘吗?我试过——在某个模拟项目X里,给一个任…

2026/10/9 4:48:03 阅读更多 →
10分钟上手 douyin-downloader:抖音批量下载与无水印提取完整指南

10分钟上手 douyin-downloader:抖音批量下载与无水印提取完整指南

10分钟上手 douyin-downloader:抖音批量下载与无水印提取完整指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fal…

2026/10/9 4:48:03 阅读更多 →
answer-me-with-html 完整参考指南:从 Markdown 稿件格式到代码块、自定义主题与 STE 写作检查

answer-me-with-html 完整参考指南:从 Markdown 稿件格式到代码块、自定义主题与 STE 写作检查

【免费下载链接】answer-me-with-html Answer me with HTML — an agent skill that answers hard questions with a one-page HTML you can actually read. 让 AI Agent 用一页 HTML 回答复杂问题。 项目地址: https://gitcode.com/gh_mirrors/an/answer-me-with-h…

2026/10/9 4:48:03 阅读更多 →
ponytail插件使用指南:skill模块配置与效率提升实践

ponytail插件使用指南:skill模块配置与效率提升实践

1. 从“ponytail”这个标题说起:它到底是什么第一次看到“ponytail”这个词,大多数人脑子里蹦出来的画面是扎在脑后的那束马尾辫。但如果它出现在技术社区、插件市场或者效率工具的讨论里,那它大概率不是发型教程,而是一个被开发者…

2026/10/9 4:48:03 阅读更多 →
Java电商系统实战:JSP+JavaBean+SQL Server全链路可运行方案

Java电商系统实战:JSP+JavaBean+SQL Server全链路可运行方案

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

2026/10/9 4:48:03 阅读更多 →
HermesWorkspace Playground 可选 3D NPC 模型替换指南:基于 GLB 的 Voxel 身体升级方案

HermesWorkspace Playground 可选 3D NPC 模型替换指南:基于 GLB 的 Voxel 身体升级方案

【免费下载链接】hermes-workspace Native web workspace for Hermes Agent — chat, terminal, memory, skills, inspector. 项目地址: https://gitcode.com/gh_mirrors/he/hermes-workspace 点击查看 免费下载 本文依据仓库 public/avatars-3d/README.md 编写&am…

2026/10/9 4:47:02 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/8 15:26: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/7 13:34:55 阅读更多 →