新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java String类深度解析:常量池、不可变性与拼接性能全攻略

发布时间:2026/9/30 4:23:13来源:尧图网络
Java String类深度解析:常量池、不可变性与拼接性能全攻略
前不久还有人在技术群里问我两个内容一模一样的字符串用比较为什么有时候返回true有时候返回false这个问题看似基础背后牵扯的却是String的存储结构、常量池机制和 JVM 内存分配。做 Java 开发这么多年我越来越发现String类就是那种“人人都认识、但没几个人真正吃透”的类。这篇博文准备把 Java 里面最基础也最容易被忽略的String类从头到尾捋一遍。不只是罗列常用 API还会聊到 JVM 层面的内存分布、JDK 不同版本下的源码实现差异、String/StringBuilder/StringBuffer的选型逻辑以及我自己在实际项目和面试中反复踩过的坑。不管你是刚开始学 Java 的新手还是正在准备 Java 面试的求职者亦或是被线上字符串拼接性能问题折磨的开发这篇文章都值得你看完。1. String类的本质不可变性到底意味着什么1.1 从源码剖析 String 的存储结构在 JDK 8 及更早版本中String类的核心就是一个被final修饰的char数组public final class String implements java.io.Serializable, ComparableString, CharSequence { private final char value[]; }从 JDK 9 开始这个内部结构做了重要调整变成了byte数组加一个编码标记public final class String implements java.io.Serializable, ComparableString, CharSequence { private final byte[] value; private final byte coder; }之所以这样改是因为实际业务中绝大多数字符串内容都是 Latin-1 字符一个字节就能表示而char占两个字节。改用byte[]存储后纯英文字符串的内存占用直接节省了一半。这个改动在 JEP 254 中有详细说明底层还引入了COMPACT_STRINGS这个 JVM 参数来控制是否启用压缩。这里有两个很容易被忽略的细节String类本身是final不能被继承。内部的value数组也是final初始化后不能替换。这意味着什么意味着一个String对象一旦创建完成它的字符内容在整个运行期间都无法被修改。你以为你在“修改字符串”比如做拼接、替换、删除实际上都是创建了一个全新的String对象原来的对象纹丝不动。举个例子String s hello; s s world;第二行执行后变量s指向了一个新的String对象内容为hello world而原来的hello对象依然存在于内存中只是不再被s引用而已。1.2 不可变带来的三大利好面试的时候经常会被问到“为什么 String 要设计成不可变”这个问题其实考的是对 Java 语言整体设计的理解。第一常量池复用成为可能。字符串常量池能够安全地缓存和复用字符串对象前提就是对象不可变。如果某个变量能偷偷修改字符串内容另一个引用同一个对象的变量就会读到“被篡改”的数据整个缓存机制就崩塌了。第二线程安全零成本。不可变对象天然线程安全多个线程同时读取同一个String完全不需要加锁。在多线程编程中String是少数几个可以放心共享的数据类型之一。第三安全性有保障。在类加载、网络协议解析、文件路径拼接等场景中字符串内容的完整性至关重要。如果String可变一个看似无害的引用修改可能导致整个类名被篡改带来严重的安全隐患。此外String的hashCode在第一次计算后会被缓存正因为内容不可变这个缓存值才永远有效String作为HashMap的 key 才能表现稳定。这里有个理解误区我得单独说一下很多人以为“不可变”就是变量完全不能动。实际上对象不可变 ≠ 引用不可变。你可以把一个String变量重新赋值为另一个字符串但原来的字符串对象本身没有任何变化。2. 字符串常量池理解内存分配的关键2.1 字符串常量池的工作机制字符串常量池String Pool是 JVM 中一块专门缓存字符串对象引用的区域。在 JDK 7 之前它位于永久代JDK 7 开始被移到了堆中。原因是永久代空间很小字符串对象一多就容易OutOfMemoryError移到堆后可以由常规的垃圾回收机制统一管理。它的工作机制用一句话概括编译期收集字面量运行期复用对象。看这段代码String s1 hello; String s2 hello; String s3 new String(hello);s1和s2都是字面量直接赋值。编译期间hello会被记录到 Class 文件的常量池中。类加载时JVM 会在堆中创建一个内容为hello的对象并把对象引用登记到字符串常量池。当s2赋值时JVM 发现常量池已经存在相同内容的字符串就直接把同一个引用交给s2。所以s1 s2的结果是true。s3的情况完全不同。new关键字强制在堆上创建一个全新的String对象不管常量池里有没有hello它都是一块新的内存。所以s1 s3的结果是false。这里有一个面试超高频问题new String(hello)到底创建了几个对象如果常量池中还没有hello答案是两个一个是类加载阶段在常量池中创建/记录的hello对象另一个是new在堆上创建的新对象。如果常量池中已经有hello那就只创建一个堆对象。这个“如果”前提很关键很多人在面试时答错就是因为默认了常量池里一定没有对应字符串。把常量池想象成一个公共图书馆的阅览室你第一次借到《Java编程思想》放进了公共书架后面所有人想读这本书都直接去书架上拿不用重复买。但new String()相当于你偏要自己掏钱再买一本放家里虽然内容一模一样但大家知道这是两本不同的实体书。2.2 intern() 方法的高频考点intern()是字符串面试题中的钉子户。它的作用很明确如果常量池中已经存在内容相同的字符串就返回常量池中那个对象的引用如果不存在就把当前字符串对象的引用放入常量池并返回。String a new String(hello) new String(world); // 此时常量池中没有 helloworld String b a.intern(); String c helloworld; System.out.println(a b); // JDK 7 下通常是 true System.out.println(b c); // true这个例子在不同 JDK 版本下的结果不一样。JDK 6 及更早版本intern()会把字符串内容拷贝到永久代并创建新对象所以a和b指向不同对象。JDK 7 之后intern()直接记录引用a和b就指向了同一个对象。面试时如果被问到这种题一定要先确认对方默认的 JDK 版本不然结论完全相反。实际开发中我基本不主动使用intern()。它虽然能节省内存但常量池膨胀带来的查表开销和潜在的 GC 压力也不容小视。如果确实需要对大量重复字符串做内存优化建议先用工具统计一下字符串重复率再决定是否值得引入。3. String常用方法全景解析3.1 内容比较equals、equalsIgnoreCase、compareToequals方法是String的核心方法重写自Object。它的比较逻辑是先看长度长度不一致直接返回false长度一致再逐个字符比较。这是一个 O(n) 操作n 是字符串长度。String a hello; String b HELLO; System.out.println(a.equals(b)); // false System.out.println(a.equalsIgnoreCase(b)); // trueequalsIgnoreCase虽然方便但因为每个字符都要做大小写转换判断性能比equals略差。在追求极致性能的场景下如果你能保证两侧字符串大小写已经统一直接用equals更稳妥。compareTo和equals类似但返回值是整数。它按字典序比较返回的是第一个不同字符的char值之差而不是单纯的1或-1。System.out.println(abc.compareTo(abd)); // 输出 -1c-d -1 System.out.println(abc.compareTo(abc)); // 输出 0实际排序时大多数人使用compareTo而不是手写循环。但要注意compareTo是区分大小写的。如果需要忽略大小写排序用compareToIgnoreCase。另一个高频考点是和equals的区别。在比较引用类型时比较的是内存地址equals默认也是比较地址但String重写后比较的是内容。所以判断两个字符串内容是否相同永远用equals不要用。3.2 截取与替换substring、replace、replaceAllsubstring方法在 JDK 7u6 前后有非常重大的实现差异。旧版本中substring生成的新字符串会直接复用原字符串内部的char[]数组通过偏移量来标记取用范围。好处是速度快、不复制内容坏处是隐患巨大如果一个很大的字符串只截取了一小段并且这一小段被长期持有那么整个大数组就一直无法被回收内存泄漏风险极高。JDK 7u6 之后官方改成了复制数组的方式。每次substring都会把范围内的字符拷贝到新数组中原数组不再被引用后可以正常被回收。代价是截取操作本身多了一次数组拷贝。所以如果你需要频繁从一个字符串中截取大量子串并把这些子串保存下来现在的实现不会造成内存泄漏但需要注意截取操作的复制开销。实际开发中substring和subSequence的区别也常被问subSequence返回的是CharSequence接口类型但底层实现原理一致。替换方法中replace和replaceAll的差异是重灾区String s a.b.c; System.out.println(s.replace(., #)); // a#b#c System.out.println(s.replaceAll(., #)); // ###因为点号是正则通配符replaceAll的第一个参数是正则表达式replace的第一个参数是普通字符串。如果你只是想把某个固定字符替换成另一个固定字符直接用replace就行既避免了正则解析的开销也规避了特殊字符的陷阱。再说一个经典场景想把字符串里的$删掉replace($, )正常工作replaceAll($, )会把字符串末尾替换成空串行为完全出乎意料。正则里的$表示行尾锚点。3.3 分割与查找split、indexOf、containssplit是日常开发中坑最多的方法之一。它的入参是正则表达式所以遇到点号、竖线这类特殊字符时必须转义String data 192.168.1.1; String[] parts data.split(\\.); // 正确 String[] parts2 data.split(.); // 错误返回空数组更隐蔽的是末尾空字符串被丢弃的问题。看这段String line a,b,c,; String[] arr line.split(,); System.out.println(arr.length); // 输出 3最后一个空串被丢掉了解决方案是给split传入负数限制String[] arr line.split(,, -1); System.out.println(arr.length); // 输出 4保留末尾空串这个细节在解析 CSV、处理日志按分隔符切分时特别重要我曾经因为忽略了这个行为导致一批数据解析后行数对不上排查了很久才发现是末尾多了一个分隔符。indexOf和contains功能上很像。indexOf返回首次出现的下标找不到返回-1contains返回布尔值内部其实就是调用indexOf(s) 0。如果只是判断“有没有”用contains更清晰如果需要定位用indexOf。还要注意indexOf有一个接受int的重载版本可以直接查找某个字符在某些高性能场景下它走的是更底层的字符匹配逻辑比传入字符串包装的版本略快。3.4 空判断与类型转换isEmpty、isBlank、toCharArrayisEmpty()判断的是length() 0。它判断不了“全是空格”的情况。用户输入的空格、制表符在isEmpty()看来都是“非空”这经常导致校验逻辑漏掉无效输入。String blank ; System.out.println(blank.isEmpty()); // false System.out.println(blank.trim().isEmpty()); // true System.out.println(blank.isBlank()); // trueJDK 11JDK 11 新增的isBlank()比trim isEmpty的组合好用在它直接判断整个字符串是否只包含空白字符空格、制表符、换行符都在内还不用创建任何临时字符串。如果你用的 JDK 版本支持优先用isBlank()。toCharArray()把字符串转成字符数组底层是System.arraycopy的完整复制返回的数组是独立副本修改数组不会影响原字符串。如果你只是想遍历字符直接用charAt(i)更轻量不需要生成一个完整的数组副本。JDK 9 之后由于内部改为byte[]存储toCharArray()还需要做字节到字符的解码转换这个成本更值得注意。字符串和数字、字节数组之间的转换也很常见。String.valueOf(int)、Integer.parseInt(String)、String.format()各自有各自的应用场景。valueOf底层会复用Integer.toString的逻辑如果只是简单拼接数字直接用 num反而会被编译器优化成StringBuilder操作并不一定是坏事。getBytes()这个方法我要特别提示一下无参版本使用平台默认编码在不同操作系统下结果可能不一致跨平台代码永远要显式指定字符集例如s.getBytes(StandardCharsets.UTF_8)。4. String、StringBuilder、StringBuffer 的选择与性能差异4.1 三者的源码级对比很多 Java 基础面试题里都会要求对比这三个类。直接上一张表把核心差异讲清楚特性StringStringBuilderStringBuffer可变性不可变可变可变线程安全安全不可变天然安全不安全安全方法级同步性能拼接时频繁创建对象单线程下最快加锁有额外开销底层结构不可变 byte[]/char[]可扩容 byte[]/char[]可扩容 byte[]/char[]适用场景常量、少量拼接、HashMap key单线程大量拼接多线程共享修改StringBuilder和StringBuffer都继承自AbstractStringBuilder内部维护一个可扩容的字符数组。append操作本质是在数组末尾追加内容数组不够时自动扩容。两者几乎一模一样唯一的核心区别是StringBuffer在所有公开方法上加了synchronized关键字。也就是说StringBuffer是线程安全的但每次方法调用都要经历锁的获取和释放。单线程环境下这个开销纯属浪费。绝大多数业务代码都是单线程处理局部变量直接用StringBuilder就好。只有在多线程共享同一个可变字符串对象、并且需要保证操作原子性时才值得考虑StringBuffer。4.2 拼接字符串的正确姿势很多人一听“字符串拼接用 StringBuilder”就认为一无是处。实际情况没这么简单。先看在编译期干了什么String a hello; String b world; String c a b;JDK 8 及之前这段代码编译后等价于String c new StringBuilder().append(a).append(b).toString();JDK 9 之后改成了invokedynamic配合StringConcatFactory实现运行时的拼接策略更灵活。这带来的结论是少量拼接用完全没问题甚至可读性更好。真正需要警惕的是循环内拼接String result ; for (int i 0; i 10000; i) { result i; // 每次循环都创建 StringBuilder、append、toString }这段代码执行 10000 次等于创建了 10000 个StringBuilder对象、10000 个临时String对象GC 压力巨大。改成这样才是正确姿势StringBuilder sb new StringBuilder(); for (int i 0; i 10000; i) { sb.append(i); } String result sb.toString();循环内拼接和循环外拼接性能差距可能是数量级的。我在实际项目中见过一个日志组装方法因为在大循环里用拼接日志内容导致一个批处理任务从 3 秒变成 25 秒改成StringBuilder后直接回到 3 秒。性能优化里最立竿见影的操作就是修循环拼接。4.3 StringBuilder 的容量优化细节StringBuilder默认初始容量是 16。每次 append 时如果容量不够会触发扩容。扩容逻辑是int newCapacity (oldCapacity 1) 2;也就是新容量 旧容量 * 2 2然后和实际需要的最小容量取较大值。扩容时会调用Arrays.copyOf底层是System.arraycopy把旧数组内容全部复制到新数组。数据量越大复制成本越高。如果预估到最终字符串长度最好直接指定初始容量// 预估最终长度大约 50000 字符 StringBuilder sb new StringBuilder(50000);这样可以减少扩容次数避免多次全量数组复制。比如要拼接 10000 个数字每个数字大多在 1 到 5 位之间可以估算出总长度大约 30000 到 40000直接设置成 40000 就很合理。相关搜索里有个很具体的问题“StringBuffer 怎么转 String”。答案非常简单就是调用toString()StringBuffer sb new StringBuffer(abc); String s sb.toString();反过来String转StringBuffer或StringBuilder用构造方法或append都行StringBuilder sb2 new StringBuilder(abc); StringBuffer sb3 new StringBuffer().append(abc);这类问题看起来简单但很多初学者确实会卡住因为StringBuffer和StringBuilder没有直接暴露value数组只能通过toString()转字符串。5. 面试高频陷阱与易错点整理5.1 用比较字符串为什么结果不稳定这是面试中出场率极高的问题。先看一组例子String s1 abc; String s2 abc; System.out.println(s1 s2); // true常量池同一个引用 String s3 new String(abc); System.out.println(s1 s3); // false堆上新对象 String s4 a bc; System.out.println(s1 s4); // true编译期常量折叠 String prefix a; String s5 prefix bc; System.out.println(s1 s5); // false运行时动态拼接很多人就是因为第一行输出true误以为可以用来比较内容。其实从头到尾都只比地址。之所以第一行是true完全是常量池的功劳两个字面量指向了同一个缓存对象。搞清楚这个原理后最佳实践就很简单了所有字符串内容比较统一用equals不要碰运气用。如果左侧对象可能为null最好把常量写在前面if (expected.equals(input)) { // 即使 input 为 null 也不会 NPE }5.2 split 和 replaceAll 的正则陷阱正则表达式给字符串处理带来了极大灵活性也带来了大量莫名其妙的 bug。split(.)分割失败、replaceAll($, )在末尾插入空串、replaceAll(\\\\, /)的转义地狱这些都是我见过的真实案例。在正则中以下字符有特殊含义. ^ $ * ? { } [ ] \ | ( )。想按这些字符原文匹配都必须加反斜杠转义。而 Java 字符串本身也有转义规则所以代码里经常出现两层转义。最稳妥的应对方法是能不用正则就不用正则。普通的分隔符和替换操作一律优先选择split的字符串字面量版本不具备所以你需要自己写一个简单的拆分逻辑或者接受转义写法。如果你只是想把固定字符串中的某个字符替换掉用replace它不涉及正则引擎。另一个容易被忽略的问题split和matches在底层都会走正则编译。如果在一个大循环里反复调用matches每次都会重新编译正则性能损耗极大。正确的做法是把正则预编译成Pattern对象循环里只做匹配Pattern pattern Pattern.compile(^\\d{4}-\\d{2}-\\d{2}$); for (String line : lines) { if (pattern.matcher(line).matches()) { // do something } }5.3 字符串参数传递值传递还是引用传递又一个超级经典的问题。看这段代码public static void change(String s) { s changed; } String str original; change(str); System.out.println(str); // 输出 original很多初学者会以为因为String是对象所以方法内修改会影响外部。但实际上 Java 的参数传递机制是值传递方法收到的s是str引用的一个副本。s changed只是让这个副本指向了常量池里另一个对象外部str根本没变。如果把参数换成StringBuilderpublic static void change(StringBuilder sb) { sb.append( changed); } StringBuilder sb new StringBuilder(original); change(sb); System.out.println(sb); // 输出 original changed结果变了因为append操作发生在同一个对象内部引用副本和外部引用同时指向同一个可变对象。把不可变性、值传递、对象引用这几个概念串起来这类题目就能彻底想通。5.4 常见面试题速查表面试复习阶段建议把下面这张表快速过一遍每一行都保证能讲出细节问题核心回答要点String 为什么不可变final 类 final char[]/byte[]安全、缓存、线程安全和equals区别比地址equals比内容new String(abc)创建几个对象常量池无该字面量时 2 个有则 1 个String / StringBuilder / StringBuffer 区别不可变 / 可变非线程安全 / 可变线程安全为什么循环里不要用拼接每轮创建新对象GC 压力大intern()的作用返回常量池中的字符串引用值传递问题Java 永远是值传递对象引用也是值6. 项目实践中的性能优化与避坑建议6.1 高频场景下的字符串处理优化把上面所有知识点放到真实项目中我提炼出几条自己一直在用的规则。第一条日志和报文组装必须用StringBuilder。日志框架虽然自己内部有优化但如果你在业务代码里做log.info(result result)这种拼接日志级别虽然可能是INFO但字符串已经先拼好了。高并发场景下这些无用拼接会白白消耗 CPU。更好的做法是用 SLF4J 的{}占位符由日志框架决定是否格式化。第二条集合转字符串避免手写循环加逗号。Java 8 之后可以用String.joinListString list Arrays.asList(a, b, c); String result String.join(,, list);也可以用 StreamString result list.stream().collect(Collectors.joining(,));如果集合特别大且性能敏感直接循环 StringBuilder反而更快因为 Stream 方式多了流对象创建和收集器的开销。几十个元素的集合随便用哪种都行百万级以上的集合则优先手动循环并预估容量。第三条字符集转换必须显式指定编码。Java 的字符串内部是 UTF-16 编码但外部数据可能是 UTF-8、GBK、ISO-8859-1。读文件、读网络流、调用第三方接口时如果依赖平台默认编码开发环境是 Windows线上是 Linux同样的代码可能读出不同的乱码。统一使用StandardCharsets.UTF_8是最省心的做法。第四条警惕正则表达式对不可信输入的性能攻击。复杂的正则表达式在处理长字符串时可能出现灾难性回溯CPU 被打满。处理用户输入、网络请求参数这类不可信内容时尽量优先使用简单的字符串查找方法例如indexOf、startsWith、endsWith、contains这些方法都是朴素的字符扫描不会掉进回溯陷阱。6.2 我这些年踩过的 String 相关坑说实话String相关的线上问题我踩过不少挑几个有代表性的聊聊帮大家提前避雷。第一个坑是substring 导致的内存问题。在旧版 JDK 上我从一个 10MB 的日志字符串里截取了前 100 个字符做摘要然后这个摘要被缓存到内存导致整个 10MB 字符串迟迟无法被 GC。排查半天才定位到是 substring 共享数组所致。JDK 7u6 以后的修复已经解决了这个问题但如果你维护的是老版本 JDK 的遗留系统要特别关注。第二个坑是split 丢末尾空串导致数据错行。当时解析一个外部系统导出的 CSV 文件某行末尾有一个空字段用默认的split(,)解析后这一行的列数比其他行少一列程序直接报数组越界。最后加上了负数限制参数才算彻底解决。现在我看到任何split调用都会下意识想一想末尾空串是否会影响业务。第三个坑是循环里用拼接 XML 报文。一个生成批量交易报文的程序原本每天跑一次每次需要 40 多分钟。后来把拼接逻辑全部改成StringBuilder时间缩短到 7 分钟。排查过程其实不复杂用 JProfiler 看了一下 CPU 和 GC 耗时发现大量时间都花在char[]数组复制上了。第四个坑是比较字符串在测试环境正常、线上环境翻车。测试环境跑的是同一个 JVM 实例字符串字面量复用让碰巧返回true线上多实例部署时不同实例的字符串对象来自不同内存区域结果就变成false了。这种问题最恼人因为现象不稳定定位成本极高。从那以后我养成了硬性习惯字符串内容比较一律equals代码审查时看到比较字符串直接打回。最后再分享一个小技巧如果你在写工具类时需要对String做频繁的判空和默认值处理可以考虑Objects.toString(obj, )或者三元表达式减少null分支代码的重复。面对从Map或接口返回值中获取的字符串永远先假设它是null再做下一步操作。这种防御式编程习惯虽然看起来啰嗦但实实在在能减少线上 NPE 告警。做 Java 开发越久我越觉得基础知识才最值得反复打磨把String这个最常用的类吃透很多线上问题其实在写代码的那一刻就可以避免。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

EtherCAT与FSoE协同实现工业功能安全 2026/9/30 6:15:12

EtherCAT与FSoE协同实现工业功能安全

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

阅读更多 →
从零入门Unity:用滚球吃金币Demo打通移动、碰撞、相机、UI与打包 2026/9/30 6:15:11

从零入门Unity:用滚球吃金币Demo打通移动、碰撞、相机、UI与打包

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

阅读更多 →
Halcon三点拟合圆标定旋转中心实战指南 2026/9/30 6:15:11

Halcon三点拟合圆标定旋转中心实战指南

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

阅读更多 →
Vue项目中axios全局配置与自定义实例详解:从入门到企业级封装实践 2026/9/30 6:15:10

Vue项目中axios全局配置与自定义实例详解:从入门到企业级封装实践

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

阅读更多 →
考研数学泰勒公式全解:从展开到应用,一篇文章彻底掌握核心考点 2026/9/30 6:15:04

考研数学泰勒公式全解:从展开到应用,一篇文章彻底掌握核心考点

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

阅读更多 →
Linux内存分配器全景解析:从伙伴系统到malloc的性能调优实战 2026/9/30 6:15:04

Linux内存分配器全景解析:从伙伴系统到malloc的性能调优实战

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

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉