ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ASCII码表与快速排序实战:从字符编码到高效分治排序

ASCII码表与快速排序实战:从字符编码到高效分治排序 写代码的时候总有一些基础知识点会被反复翻出来用字符放到数字环境里要查ASC码表排序打乱的数据要找快速排序。特别是字符串比较、哈希计算、网络协议解析以及面试手写算法时这两个主题几乎避不开。这篇文章不绕弯子直接处理两个实际问题ASC码表常用字符对应的十进制、十六进制、八进制码值是多少代码里怎么做字符和整数互转常见的乱码和大小写转换为什么和 ASC 码表有关。快速排序一遍基准值分区是怎么做到的C 语言怎么写Java 怎么写递归边界在哪最坏情况为什么出现以及真实项目中怎么避免退化。整篇文章会给出可直接复制运行的代码顺便讲清楚这两块知识在实际开发里的配合方式。比如按 ASCII 码值对字符数组做快速排序、用 JavaComparator实现字典序排序、用 C 语言 qsort 处理字符串等。愿意动手的读者照着代码跑一遍就能把输出结果和理论对起来。1. 核心知识速览项目说明ASC码表全称American Standard Code for Information Interchange美国信息交换标准代码简称 ASCII标准范围0~127共 128 个字符含控制字符和可打印字符扩展范围128~255通常为扩展 ASCII 或特定编码集常用字符空间数字 0~948~57、大写字母 A~Z65~90、小写字母 a~z97~122大小写差值固定为 32小写码值大于大写码值快速排序思想分治法每轮选基准值把小于基准的放左边、大于基准的放右边平均时间复杂度O(n log n)最坏 O(n^2)最坏情况原数组已经有序且每次基准值选最小或最大值空间复杂度递归版 O(log n) 到 O(n)取决于递归深度工程优化三数取中、小区间插入排序、随机基准值、双路/三路分区阅读对象建议准备笔试面试的开发者需要手写排序的在校学生以及想彻底搞懂字符编码基础的前后端工程师。不需要高配 GPU不需要模型推理环境打开任意支持 C 或 Java 的开发环境即可验证。2. 适用场景为什么要同时掌握 ASCII 和快速排序第一类场景是字符处理和编码转换。举例后端接收前端传输的字符串要把小写转大写C 语言里判断用户输入是否为数字字符浏览器 URL 编码中%41表示字符A。这些操作背后都是同一张表字符在计算机里保存的就是整数码值码值查ASC码表就能明确字符归属区间。第二类场景是算法与性能。当需要对百万级 JSON 数组里的对象按名字排序时底层默认排序可能走的就是快速排序的双轴变体。如果自己实现排序又绕不开快速排序的分区思想。字符串排序最终比较的还是字符码值所以 ASCII 和快速排序不是两个割裂的题目而是具备天然技术衔接的分区排序问题字符码值大小决定字符先后次序快速排序负责把无序字符序列变成有序字符序列。不太适合的场景也要说明如果只需要最终排序结果不要自己手写快速排序直接用 JDK 的Arrays.sort()、C 标准库的qsort()或 Python 的sorted()更可靠。如果只需要某个字符的码值直接在网上查在线 ASCII 工具即可不用背整张表。如果处理中文、Emoji单张ASC码表不够必须切换到 Unicode / UTF-8 编码模型不能拿 ASCII 强解中文字节流。3. ASC 码表核心速览3.1 0~127 控制字符和基础可打印字符ASCII 码表的设计非常规律。前 32 个字符0~31是控制字符不显示图形常用于通信协议和终端控制例如十进制缩写含义0NUL空字符9TAB水平制表符10LF换行13CR回车27ESC退出键从 32 开始进入可直接打印字符。32 本身是空格码值为0x20代码里经常用来做补位处理。3.2 数字、大小写字母区间数字 0 到 9 的 ASCII 码值是连续的 48~57大写字母 A 到 Z 是 65~90小写字母 a 到 z 是 97~122。这三段区间彼此间隔 7 和 6因此写判断逻辑时直接用范围判断即可不需要做区间映射if (ch 0 ch 9) { // 是数字字符 } if (ch A ch Z) { // 是大写字母 } if (ch a ch z) { // 是小写字母 }大小写字母之间相差 32这个规律可以用来在不调用库函数的情况下完成大小写转换char upperToLower(char c) { if (c A c Z) { return c 32; } return c; }大写转小写是32小写转大写是-32。实际库函数tolower()和toupper()内部也会做类似边界判断只是还兼容了区域设置。3.3 常见符号码值以下符号在接口开发中频繁出现建议直接记十进制字符十进制十六进制空格320x20!330x21340x22#350x23$360x24%370x25380x26390x27(400x28)410x29*420x2A430x2B,440x2C-450x2D.460x2E/470x2F:580x3A;590x3B600x3C610x3D620x3E?630x3F640x40[910x5B\920x5C]930x5D^940x5E_950x5F960x60{1230x7B|1240x7C}1250x7D~1260x7E观察排列0到9、A到Z、a到z都是连续升序所以在字典序里数字字符永远排在大写字母前面大写字母永远排在小写字母前面。给字符串排序时这个顺序会直接影响输出结果例如Z排在a前面因为90 97。3.4 扩展 ASCII128~255标准的 7 位 ASCII 只使用 0~127。128~255 通常被称为扩展 ASCII不同的字符集在这部分定义不同内容。例如 IBM 扩展字符集里包含制表符、希腊字母等Windows-1252 会把部分码位分配给欧元符号、引号等。由于扩展区域的字符没有统一标准现代应用程序处理非英文字符时更推荐直接使用 UTF-8。很多乱码问题的本质就是文本以 UTF-8 编码保存程序却按单字节 ASCII 或 GBK 去解码导致高位字节被错误解释。排查乱码时要用十六进制查看器确认字节内容再判断应该使用哪套解码表而不是只靠肉眼看。4. ASC 码表在开发中的典型使用4.1 字符和整数的相互转换字符类型本质上是整数类型C 语言中可以直接强转#include stdio.h int main() { char ch A; printf(A 的 ASCII 十进制码值: %d\n, ch); printf(A 的 ASCII 十六进制: 0x%X\n, ch); int code 66; printf(十进制 66 对应的字符: %c\n, code); return 0; }运行结果A 的 ASCII 十进制码值: 65 A 的 ASCII 十六进制: 0x41 十进制 66 对应的字符: BJava 中字符和整数直接参与运算时会自动提升为intpublic class AsciiDemo { public static void main(String[] args) { char ch A; System.out.println(A 的 ASCII 码值: (int) ch); int code 97; char lowerA (char) code; System.out.println(十进制 97 对应的字符: lowerA); } }4.2 按字符码值统计频次处理纯英文文本时长度 128 的数组可以当哈希表使用。以下代码统计字符串中英文字母出现次数数组索引直接用字符码值public class CharCount { public static void main(String[] args) { String text Hello ASCII QuickSort; int[] freq new int[128]; for (int i 0; i text.length(); i) { char c text.charAt(i); if (c 128) { freq[c]; } } for (int i 0; i freq.length; i) { if (freq[i] 0) { System.out.println((char) i - freq[i]); } } } }这种方式的时间复杂度是 O(n)空间固定 128 个计数槽适合统计纯英文的短文本。遇到中文文本则要换HashMapCharacter, Integer。4.3 判断字符类型很多登录注册模块要求密码必须包含大小写字母和数字判断逻辑就是查 ASCII 区间public static boolean isStrongPassword(String password) { boolean hasDigit false; boolean hasLower false; boolean hasUpper false; for (int i 0; i password.length(); i) { char c password.charAt(i); if (c 0 c 9) hasDigit true; else if (c a c z) hasLower true; else if (c A c Z) hasUpper true; } return hasDigit hasLower hasUpper; }5. 快速排序核心思想与实现思路快速排序使用分治法从待排序区间选择一个基准值pivot。把小于基准值的元素移到左边大于基准值的元素移到右边。递归对左右两个分区继续执行同样操作。当分区长度为 0 或 1 时递归结束。关键过程不是排序本身而是“分区”。partition函数返回基准值的最终位置后续递归都依赖这个位置。5.1 最简单的 Lomuto 分区法Lomuto 分区实现简单思路容易理解。以区间最后一个元素为基准值用i标记小于基准值的边界用j遍历数组#include stdio.h void swap(int arr[], int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; } int partition(int arr[], int low, int high) { int pivot arr[high]; int i low - 1; for (int j low; j high; j) { if (arr[j] pivot) { i; swap(arr, i, j); } } swap(arr, i 1, high); return i 1; } void quickSort(int arr[], int low, int high) { if (low high) { int pi partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi 1, high); } } void printArray(int arr[], int n) { for (int i 0; i n; i) { printf(%d , arr[i]); } printf(\n); } int main() { int arr[] {9, 7, 5, 11, 12, 2, 14, 3, 10, 6}; int n sizeof(arr) / sizeof(arr[0]); printf(排序前: ); printArray(arr, n); quickSort(arr, 0, n - 1); printf(排序后: ); printArray(arr, n); return 0; }Lomuto 分区的特点是逻辑清晰适合用来理解快速排序。但它的交换次数比 Hoare 分区多工程中不常用。5.2 性能更好的 Hoare 分区法Hoare 分区使用两个指针从左右两端向中间移动基准值通常取区间第一个元素或中间元素。无论是元素交换次数还是对大量重复元素的适应性通常都优于 Lomuto#include stdio.h void swap(int arr[], int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; } int hoarePartition(int arr[], int low, int high) { int pivot arr[low]; int i low - 1; int j high 1; while (1) { do { i; } while (arr[i] pivot); do { j--; } while (arr[j] pivot); if (i j) { return j; } swap(arr, i, j); } } void quickSortHoare(int arr[], int low, int high) { if (low high) { int pi hoarePartition(arr, low, high); quickSortHoare(arr, low, pi); quickSortHoare(arr, pi 1, high); } }Hoare 分区返回的j是左分区的右边界右递归从j 1开始这一点和 Lomuto 不同。初学者经常在这里把边界写错造成递归死循环或漏排元素。5.3 Java 实现快速排序Java 中的写法类似代码风格更适合工程维护。下面提供一个使用中间元素做基准值、双指针分区的完整实现import java.util.Arrays; public class QuickSortDemo { public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot arr[left (right - left) / 2]; int i left; int j right; while (i j) { while (arr[i] pivot) { i; } while (arr[j] pivot) { j--; } if (i j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; i; j--; } } if (left j) { quickSort(arr, left, j); } if (i right) { quickSort(arr, i, right); } } public static void main(String[] args) { int[] arr {34, 7, 23, 32, 5, 62, 32, 1, 0, -5}; System.out.println(排序前: Arrays.toString(arr)); quickSort(arr, 0, arr.length - 1); System.out.println(排序后: Arrays.toString(arr)); } }选择中间元素作为基准值后即使输入接近有序退化到 O(n^2) 的概率也会明显降低。这种写法在普通笔试题和实际项目里都足够稳定。6. 快速排序的复杂度分析与优化6.1 时间复杂度最好情况和平均情况下每次分区都能把数组分成接近均匀的两半递归深度为 O(log n)每层做 O(n) 次比较总时间复杂度为 O(n log n)。最坏情况是每次分区只分割出一个元素例如数组已经升序排列却每次取第一个元素作为基准值递归深度会变成 O(n)总时间复杂度为 O(n^2)。这种退化在处理有序数据时非常致命。6.2 空间复杂度快速排序不是严格意义上的原地排序递归调用会消耗调用栈空间。最好的空间复杂度是 O(log n)最坏是 O(n)取决于递归深度。非递归版本需要显式维护一个栈或队列来保存待排序区间能把空间复杂度稳定控制在 O(log n)如果始终先处理较短分区但工程中较少使用。6.3 工程优化方式快速排序代码很容易从理论版本进入工程版本常见优化包括随机基准值。在递归开始之前随机选取区间内的一个元素与第一个或最后一个元素交换这样可以有效规避“数据分布不均匀”导致的最坏情况Random random new Random(); public static void randomPivot(int[] arr, int left, int right) { int randIndex left random.nextInt(right - left 1); int temp arr[left]; arr[left] arr[randIndex]; arr[randIndex] temp; }三数取中。取区间最左、中间、最右三个元素的中位数作为基准值。可以用在部分有序数组上有效避免极端分布public static int medianOfThree(int[] arr, int left, int right) { int mid left (right - left) / 2; if (arr[left] arr[mid]) { swap(arr, left, mid); } if (arr[left] arr[right]) { swap(arr, left, right); } if (arr[mid] arr[right]) { swap(arr, mid, right); } return arr[mid]; }小区间插入排序。当递归区间长度小于某个阈值如 8~16时直接使用插入排序减少递归调用开销。实际排序中简单排序在数据量小时反而更快。三路分区。如果数组里出现大量重复值使用三路分区把数组划分为“小于基准值、等于基准值、大于基准值”三段可以避免重复元素进入递归区间。JDK 的DualPivotQuicksort对 byte/short/char 数组使用的就是对重复元素友好的改进版快速排序因此 Java 中对基本类型数组默认排序也很快。7. 结合 ASCII 与快速排序的实际例子7.1 按 ASCII 码值排序字符数组输入一串字符要求输出时按 ASCII 码值从小到大排列。这个例子可以同时验证 ASC 码表和快速排序#include stdio.h #include string.h void swap(char arr[], int i, int j) { char temp arr[i]; arr[i] arr[j]; arr[j] temp; } int partition(char arr[], int low, int high) { char pivot arr[high]; int i low - 1; for (int j low; j high; j) { if (arr[j] pivot) { i; swap(arr, i, j); } } swap(arr, i 1, high); return i 1; } void quickSortCharArray(char arr[], int low, int high) { if (low high) { int pi partition(arr, low, high); quickSortCharArray(arr, low, pi - 1); quickSortCharArray(arr, pi 1, high); } } int main() { char str[] HelloQuickSort123; int n strlen(str); printf(排序前: %s\n, str); quickSortCharArray(str, 0, n - 1); printf(按 ASCII 排序后: %s\n, str); return 0; }比较字符时C 语言直接比较字符对应的 ASCII 码值所以H和h的相对顺序由码值 72 和 104 决定。输出会体现出数字、大写字母、小写字母的分区界限。7.2 使用 Java 比较器实现字典序排序当对象包含字符串字段时如果库函数底层会逐个比较字符可以直接调用 Java 默认的比较逻辑或按自己的比较规则实现import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class StringSortDemo { static class User { String name; int score; User(String name, int score) { this.name name; this.score score; } Override public String toString() { return name : score; } } public static void main(String[] args) { ListUser users new ArrayList(); users.add(new User(Alice, 88)); users.add(new User(bob, 75)); users.add(new User(Charlie, 92)); users.add(new User(alice, 83)); System.out.println(按名字的 ASCII 字典序排序); users.sort(Comparator.comparing(u - u.name)); users.forEach(System.out::println); } }由于 JVM 比较字符串默认逐个比较char的 Unicode 码位而前 128 个码位直接与 ASCII 对应因此英文大小写排序顺序就等于 ASCII 码表顺序。结果中Alice会排在Charlie前面Charlie会排在alice前面因为小写a的码值 97 大于大写C的码值 67。7.3 C 标准库 qsort 使用示例C 标准库qsort内部通常采用快速排序的改进版本路径在stdlib.h可以避免自己手写复杂分区逻辑。将字符串数组排序时比较函数要对指针做两级解引用#include stdio.h #include stdlib.h #include string.h int compareStrings(const void* a, const void* b) { return strcmp(*(const char**)a, *(const char**)b); } int main() { const char* words[] {banana, Apple, cherry, apple, Banana}; int n sizeof(words) / sizeof(words[0]); qsort(words, n, sizeof(const char*), compareStrings); for (int i 0; i n; i) { printf(%s\n, words[i]); } return 0; }strcmp比较的是字符码值所以Apple会排在Banana前面因为A码值 65 小于B码值 66Banana会排在apple前面因为B码值 66 小于a码值 97。如果不希望区分大小写需要先统一转小写再调用strcasecmpPOSIX 环境或自行处理。8. 功能测试与效果验证下面给出一个可运行的 C 语言验证程序把 ASC 码表打印和快速排序放在一起方便观察过程#include stdio.h void printAsciiRange(int start, int end) { for (int i start; i end; i) { printf(%3d 0x%-2X %c\n, i, i, (i 32 i 126) ? i : ?); } } void swap(int arr[], int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; } int partition(int arr[], int low, int high) { int pivot arr[high]; int i low - 1; for (int j low; j high; j) { if (arr[j] pivot) { i; swap(arr, i, j); } } swap(arr, i 1, high); return i 1; } void quickSort(int arr[], int low, int high) { if (low high) { int pi partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi 1, high); } } int main() { printf( ASCII 码表区域示例 \n); printf(数字 0~9:\n); printAsciiRange(48, 57); printf(\n大写字母 A~Z:\n); printAsciiRange(65, 90); printf(\n小写字母 a~z:\n); printAsciiRange(97, 122); printf(\n 快速排序验证 \n); int arr[] {5, 2, 9, 1, 5, 6, 0, 3, 8, 7}; int n sizeof(arr) / sizeof(arr[0]); printf(排序前: ); for (int i 0; i n; i) { printf(%d , arr[i]); } printf(\n); quickSort(arr, 0, n - 1); printf(排序后: ); for (int i 0; i n; i) { printf(%d , arr[i]); } printf(\n); return 0; }运行程序后可以按以下标准判断快速排序的递归逻辑是否正确排序后的数组必须升序且元素个数与排序前一致。对完全随机的数组、升序数组、降序数组、全部相同的数组分别测试观察退化行为。更多测试用例建议直接写单元测试用断言判断输出等于Arrays.sort()的标准结果。如果内存允许可以用 100 万元素随机数组测试运行时间如果使用 Lomuto 分区且基准值固定取首元素将数组预先设为降序能明显感受到递归栈和耗时增长这正是最坏情况的表现。排查快速排序问题重点看分区后的边界值Lomuto 分区返回pivotIndex递归区间为[low, pi-1]和[pi1, high]。Java 双指针版本返回的不是基准值的绝对下标而是交叉后的j与i两个分区边界。很多递归死循环来自分区间隔没有缩小比如左右边界都包含基准值时始终无限循环。9. 资源占用与性能观察快速排序本身不额外消耗大量内存大家更关心的通常是“手写实现为什么比标准库慢”。观察指标主要包括三块递归调用栈深度。每次递归都会在调用栈上产生新帧。如果输入是 10 万级随机数递归深度约 17 层左右如果输入是已经有序的 10 万个数且基准值永远取第一个递归深度接近 10 万程序很容易栈溢出或运行超时。改用尾递归优化或非递归栈版本可以有效降低风险。import java.util.ArrayDeque; import java.util.Deque; public class QuickSortIterative { public static void quickSort(int[] arr) { Dequeint[] stack new ArrayDeque(); stack.push(new int[]{0, arr.length - 1}); while (!stack.isEmpty()) { int[] range stack.pop(); int left range[0]; int right range[1]; if (left right) { continue; } int pivot arr[left (right - left) / 2]; int i left; int j right; while (i j) { while (arr[i] pivot) i; while (arr[j] pivot) j--; if (i j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; i; j--; } } stack.push(new int[]{left, j}); stack.push(new int[]{i, right}); } } }比较和交换次数。Lomuto 分区在数组元素全部相等时会做大量交换时间复杂度退化为 O(n^2)。遇到多数据重复的场景三路分区或直接使用 JDK 双轴快速排序是更稳的选择。数据规模与排序时间的关系。当数据量小于 100 时插入排序反而更快因为递归调用和函数栈开销高于简单循环。很多成熟的排序实现会在递归到小区间时切换为插入排序这是提升总耗时的有效手段。以下代码展示最小可运行的小区间插入排序切换public static void insertSort(int[] arr, int left, int right) { for (int i left 1; i right; i) { int key arr[i]; int j i - 1; while (j left arr[j] key) { arr[j 1] arr[j]; j--; } arr[j 1] key; } }10. 常见问题与排查方法问题现象可能原因排查方式解决方案输出结果漏掉一个元素递归边界错误基准值位置被排除打印每次 partition 返回的下标对比区间长度Lomuto 方案递归为[low, pi-1]与[pi1, high]确保两个区间加起来不含重复且不遗漏递归无限循环导致栈溢出基准值取首元素且输入已有序双指针版本没有移动i/j打印每轮 left/right 和 pivot 值使用三数取中或随机基准双指针扫描后同时自增i自减j字符排序结果与字典序不一致忽略了大小写 ASCII 码值不同检查排序后的字符串确认小写是否排在大写后面根据业务需求调用toLowerCase()或strcasecmp中文乱码使用单字节 ASCII 存储 UTF-8 中文用十六进制编辑器查看字节流改用 UTF-8/Unicode 编码处理不按字节数截断字符串手写 qsort 比较器结果错误比较函数参数类型写错检查二级指针转换逻辑字符串数组排序时写成strcmp(*(const char**)a, *(const char**)b)数组全为相同元素时排序极慢普通分区无法有效缩小规模测试[1,1,1,...]输入使用三路分区或直接使用标准库排序程序在数据量小时反而比简单排序慢小数组递归开销大对比 10 个元素和 1000 个元素的耗时在递归中设置阈值区间小于 8~16 时切插入排序11. 最佳实践与日常学习建议先验证标准库的行为再手写算法。遇到排序需求优先使用标准库。手写快速排序的正确用途是理解原理、应对面试、处理特殊数据分布。如果自己维护一段快速排序代码建议用大量随机测试和断言验证正确性不能只拿一组数据跑通就收工。掌握 ASCII 表不需要死记硬背全部字符只需要记住四个锚点。0是 48A是 65a是 97小写区与大写区相差 32。其他字符都可以通过锚点推算。需要精确查询某个生僻符号的码值时再打开完整码表即可。排序之前先想清楚排序键。字符串排序时要明确“是否需要忽略大小写”“是否需要按拼音排序”“是否要处理中文的 Unicode 排序”。不同语言调用相同名称的排序函数结果差异可能非常明显。例如 Java 的String.compareTo()按 Unicode 码位比较和数据库常见的utf8mb4_unicode_ci排序规则并不完全相同。测试场景至少覆盖四类输入。升序数组、降序数组、随机数组、大量重复元素数组。每种输入都应断言排序结果正确。对快速排序来说升序和降序数组最容易暴露基准值选择策略的缺陷大量重复元素最容易暴露分区策略的缺陷。把四类输入跑完代码稳定性会明显提升。多练习“排序 二分”的组合题型。快速排序经常不是终点而是预处理步骤。排序完成后查找第 K 大元素、求两数之和、合并区间等题目都可以复用排序结果。真正判断是否理解快速排序不只是会默写代码而是能在需要时快速改造分区逻辑比如按某个对象字段做分区、按字符码值做稳定排序等。12. 总结与下一步这篇文章重点围绕两张技术地图展开。第一张是 ASC 码表把字符的十进制、十六进制表示法、大小写字母变换规律、字符类型判断讲清楚给出了 C 语言和 Java 的可运行示例。第二张是快速排序从 Lomuto 分区讲到 Hoare 分区再给出 Java 版递归实现、非递归实现和工程优化思路中间穿插了字符数组按 ASCII 排序的实际代码。最先应该亲手验证的三个实验是打印0、A、a三种字符的 ASCII 码值观察差值为 48、65、97。把字符数组HelloQuickSort123按快速排序排一遍观察数字、大写英文、小写英文的排列层次。用有序数组测试固定的首元素基准值快排观察 O(n^2) 退化耗时再把基准值切换为随机选择对比差异。最容易踩的坑集中在三点递归边界写错导致漏元素qsort比较器二级指针理解错误以及 ASCII 排序时没有提前确定是否区分英文大小写。这三个问题排查清楚这部分基础能力基本就稳了。后续可以继续扩展的方向包括在字符串哈希表中用 ASCII 码值做索引设计研究 JDKDualPivotQuicksort源码里对重复元素和有序输入的优化策略或者尝试用自己的分区逻辑实现求无序数组第 K 小元素的quickselect算法。建议先收藏这篇文章写代码遇到字符排序或快速排序边界问题时直接回来看对应代码块比重新翻算法书更快。
返回列表