【问题标题】:Fastest way to duplicate an array in JavaScript - slice vs. 'for' loop在 JavaScript 中复制数组的最快方法 - 切片与“for”循环
【发布时间】:2011-04-28 01:38:37
【问题描述】:

为了在 JavaScript 中复制一个数组:以下哪个使用起来更快?

Slice方法

var dup_array = original_array.slice();

For循环

for(var i = 0, len = original_array.length; i < len; ++i)
   dup_array[i] = original_array[i];

我知道这两种方式都只做浅拷贝:如果original_array 包含对对象的引用,则不会克隆对象,但只会复制引用,因此两个数组都将具有引用相同的对象。 但这不是这个问题的重点。

我只问速度。

【问题讨论】:

标签: javascript arrays duplicates copy slice


【解决方案1】:

? 克隆数组的最快方法

我制作了这个非常简单的实用函数来测试克隆数组所需的时间。它不是 100% 可靠的,但它可以让您大致了解克隆现有阵列需要多长时间:

function clone(fn) {
  const arr = [...Array(1000000)];
  console.time('timer');
  fn(arr);
  console.timeEnd('timer');
}

并测试了不同的方法:

1)   5.79ms -> clone(arr => Object.values(arr));
2)   7.23ms -> clone(arr => [].concat(arr));
3)   9.13ms -> clone(arr => arr.slice());
4)  24.04ms -> clone(arr => { const a = []; for (let val of arr) { a.push(val); } return a; });
5)  30.02ms -> clone(arr => [...arr]);
6)  39.72ms -> clone(arr => JSON.parse(JSON.stringify(arr)));
7)  99.80ms -> clone(arr => arr.map(i => i));
8) 259.29ms -> clone(arr => Object.assign([], arr));
9) Maximum call stack size exceeded -> clone(arr => Array.of(...arr));

更新

  1. 测试是在 2018 年进行的,因此今天您很可能会在当前浏览器上得到不同的结果。
  2. 在所有这些中,深度克隆数组的唯一方法是使用JSON.parse(JSON.stringify(arr))

    也就是说,如果您的数组可能包含函数,请不要使用上述方法,因为它将返回 null
    感谢 @GilEpshtain 提供此更新

我在 Chrome 和 Firefox 中运行 @mesqueeb 的基准测试。 slice() 似乎仍然是一个不错的选择。

Chrome 95.0:

火狐 94.0:

【讨论】:

  • 我尝试对您的答案进行基准测试,结果却大不相同:jsben.ch/o5nLG
  • @mesqueeb,测试可能会改变,当然取决于你的机器。但是,请随时使用您的测试结果更新答案。干得好!
  • 我非常喜欢你的回答,但是我尝试了你的测试,发现arr =&gt; arr.slice() 是最快的。
  • @LiorElrom,您的更新不正确,因为方法不可序列化。例如:JSON.parse(JSON.stringify([function(){}])) 将输出[null]
  • 不错的基准测试。我已经在我的 Mac 上的 2 个浏览器中对此进行了测试:Chrome 版本 81.0.4044.113 和 Safari 版本 13.1 (15609.1.20.111.8),最快的是传播操作:[...arr]4.653076171875ms 在 Chrome 和 8.565ms 在 Safari。 Chrome 中第二快的是切片函数 arr.slice()6.162109375ms,Safari 中第二快的是 [].concat(arr)13.018ms
【解决方案2】:

有几种方法可以克隆数组。基本上,克隆分为两种:

  1. 浅拷贝
  2. 深拷贝

浅拷贝仅覆盖数组的第 1 层,其余为 参考。如果你想要数组中嵌套元素的真实副本,你需要一个 深度克隆。

例子:

const arr1 = [1,2,3,4,5,6,7]           
// Normal Array (shallow copy is enough)     
const arr2 = [1,2,3,[4],[[5]],6,7]          
// Nested Array  (Deep copy required) 


Approach 1 : Using (...)Spread Operator  (Shallow copy enough)
const newArray = [...arr1] // [1,2,3,4,5,6,7]

Approach 2 : Using Array builtIn Slice method (Deep copy)  
const newArray = arr1.slice()  // [1,2,3,4,5,6,7]

Approach 3 : Using Array builtIn Concat method (Deep a copy)
const newArray = [].concat(arr1)  // [1,2,3,4,5,6,7]

Approach 4 : Using JSON.stringify/parse. (Deep a copy & fastest)
const newArray = JSON.parse(JSON.stringify(arr2));)  // [1,2,3,[4],[[5]],6,7]

Approach 5: Using own recursive function or using loadash's __.cloneDeep method. (Deep copy)

【讨论】:

    【解决方案3】:

    技术上slice 最快的方式。 不过,如果添加0 begin 索引会更快。

    myArray.slice(0);
    

    myArray.slice();
    

    https://web.archive.org/web/20170824010701/https://jsperf.com/cloning-arrays/3

    【讨论】:

    • 而且myArray.slice(0,myArray.length-1);myArray.slice(0); 快吗?
    • @jave.web 你刚刚删除了数组的最后一个元素。完整副本为 array.slice(0) 或 array.slice(0, array.length)
    • 这是不正确的,至少在我的机器上并且根据你自己的基准测试。
    • 链接已失效。
    • jsben.ch/56xWo - 有时,slice() 更快,有时slice(0),两者都只有一点点(在 Firefox 56 和基于 Chrome 的最新 Vivaldi 中)。但是slice(0, length) 总是明显变慢(除了它是 Firefox 87 中最快的)。
    【解决方案4】:

    克隆对象数组的最快方法是使用扩展运算符

    var clonedArray=[...originalArray]
    

    但该克隆数组中的对象仍将指向旧内存位置。因此更改 clonedArray 对象也会更改 orignalArray。

    var clonedArray = originalArray.map(({...ele}) => {return ele})
    

    这不仅会创建新数组,还会克隆对象。

    【讨论】:

      【解决方案5】:

      如果你想要一个真正的 JS 中的克隆对象/数组,其中包含所有属性和子对象的克隆引用:

      export function clone(arr) {
          return JSON.parse(JSON.stringify(arr))
      }
      

      所有其他操作都不会创建克隆,因为它们只是更改根元素的基地址,而不是包含对象的基地址。

      除非你递归遍历对象树。

      对于简单的副本,这些都可以。对于存储地址相关的操作,我建议(在大多数其他情况下,因为这很快!)将类型转换为字符串并返回一个完整的新对象。

      【讨论】:

        【解决方案6】:

        至少有 6 (!) 种克隆数组的方法:

        • 循环
        • 切片
        • Array.from()
        • 连续
        • 扩展运算符(最快)
        • 地图A.map(function(e){return e;});

        已经有一个huuugeBENCHMARKS thread,提供如下信息:

        • 对于 blink 浏览器,slice() 是最快的方法,concat() 稍慢,while loop 慢 2.4 倍。

        • 对于其他浏览器while loop 是最快的方法,因为这些浏览器没有针对sliceconcat 的内部优化。

        2016 年 7 月仍然如此。

        以下是一些简单的脚本,您可以将它们复制粘贴到浏览器的控制台中,然后运行多次以查看图片。它们输出毫秒,越低越好。

        while 循环

        n = 1000*1000;
        start = + new Date();
        a = Array(n); 
        b = Array(n); 
        i = a.length;
        while(i--) b[i] = a[i];
        console.log(new Date() - start);
        

        切片

        n = 1000*1000;
        start = + new Date();
        a = Array(n); 
        b = a.slice();
        console.log(new Date() - start);
        

        请注意,这些方法会克隆 Array 对象本身,但数组内容是通过引用复制的,不会被深度克隆。

        origAr == clonedArr //returns false
        origAr[0] == clonedArr[0] //returns true
        

        【讨论】:

        • @cept0 没有情绪,只是基准测试jsperf.com/new-array-vs-splice-vs-slice/31
        • @Dan 那又怎样?您的测试用例结果:Firefox 30 nightly 仍然比 Chrome 快约 230%。查看splice 的 V8 源代码,您会感到惊讶(同时...)
        • Sadly for short arrays the answer is vastly different。例如,在调用每个侦听器之前克隆一组侦听器。这些数组通常很小,通常只有 1 个元素。
        • 你错过了这个方法:A.map(function(e){return e;});
        • 您正在撰写关于 blink 浏览器的文章。 blink 不就是一个布局引擎,主要影响 HTML 渲染,因此不重要吗?我想我们宁愿在这里谈论 V8、Spidermonkey 和朋友们。只是一件让我困惑的事情。如果我错了,请启发我。
        【解决方案7】:

        在 ES6 中,您可以简单地使用 the Spread syntax

        例子:

        let arr = ['a', 'b', 'c'];
        let arr2 = [...arr];
        

        请注意,扩展运算符会生成一个全新的数组,因此修改一个不会影响另一个。

        例子:

        arr2.push('d') // becomes ['a', 'b', 'c', 'd']
        console.log(arr) // while arr retains its values ['a', 'b', 'c']
        

        【讨论】:

          【解决方案8】:

          您可以按照此代码进行操作。不可变方式数组克隆。这是阵列克隆的完美方式

          
          const array = [1, 2, 3, 4]
          
          const newArray = [...array]
          newArray.push(6)
          console.log(array)
          console.log(newArray)
          

          【讨论】:

            【解决方案9】:

            在 JavaScript 中按顺序复制数组的快速方法:

            #1: array1copy = [...array1];

            #2: array1copy = array1.slice(0);

            #3: array1copy = array1.slice();

            如果您的数组对象包含一些 JSON 不可序列化的内容(函数、Number.POSITIVE_INFINITY 等)更好地使用

            array1copy = JSON.parse(JSON.stringify(array1))

            【讨论】:

              【解决方案10】:

              基准时间!

              function log(data) {
                document.getElementById("log").textContent += data + "\n";
              }
              
              benchmark = (() => {
                time_function = function(ms, f, num) {
                  var z = 0;
                  var t = new Date().getTime();
                  for (z = 0;
                    ((new Date().getTime() - t) < ms); z++)
                    f(num);
                  return (z)
                }
              
                function clone1(arr) {
                  return arr.slice(0);
                }
              
                function clone2(arr) {
                  return [...arr]
                }
              
                function clone3(arr) {
                  return [].concat(arr);
                }
              
                Array.prototype.clone = function() {
                  return this.map(e => Array.isArray(e) ? e.clone() : e);
                };
              
                function clone4(arr) {
                  return arr.clone();
                }
              
              
                function benchmark() {
                  function compare(a, b) {
                    if (a[1] > b[1]) {
                      return -1;
                    }
                    if (a[1] < b[1]) {
                      return 1;
                    }
                    return 0;
                  }
              
                  funcs = [clone1, clone2, clone3, clone4];
                  results = [];
                  funcs.forEach((ff) => {
                    console.log("Benchmarking: " + ff.name);
                    var s = time_function(2500, ff, Array(1024));
                    results.push([ff, s]);
                    console.log("Score: " + s);
              
                  })
                  return results.sort(compare);
                }
                return benchmark;
              })()
              log("Starting benchmark...\n");
              res = benchmark();
              
              console.log("Winner: " + res[0][0].name + " !!!");
              count = 1;
              res.forEach((r) => {
                log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
              });
              log("\nWinner code:\n");
              log(res[0][0].toString());
              &lt;textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"&gt;&lt;/textarea&gt;

              自您单击按钮后,基准测试将运行 10 秒。

              我的结果:

              Chrome(V8 引擎):

              1. clone1 score: 100% *winner* (4110764)
              2. clone3 score: 74.32% speed of winner. (3055225)
              3. clone2 score: 30.75% speed of winner. (1264182)
              4. clone4 score: 21.96% speed of winner. (902929)
              

              火狐(蜘蛛猴引擎):

              1. clone1 score: 100% *winner* (8448353)
              2. clone3 score: 16.44% speed of winner. (1389241)
              3. clone4 score: 5.69% speed of winner. (481162)
              4. clone2 score: 2.27% speed of winner. (192433)
              

              中奖代码:

              function clone1(arr) {
                  return arr.slice(0);
              }
              

              获胜引擎:

              蜘蛛猴 (Mozilla/Firefox)

              【讨论】:

                【解决方案11】:
                        const arr = ['1', '2', '3'];
                
                         // Old way
                        const cloneArr = arr.slice();
                
                        // ES6 way
                        const cloneArrES6 = [...arr];
                
                // But problem with 3rd approach is that if you are using muti-dimensional 
                 // array, then only first level is copied
                
                        const nums = [
                              [1, 2], 
                              [10],
                         ];
                
                        const cloneNums = [...nums];
                
                // Let's change the first item in the first nested item in our cloned array.
                
                        cloneNums[0][0] = '8';
                
                        console.log(cloneNums);
                           // [ [ '8', 2 ], [ 10 ], [ 300 ] ]
                
                        // NOOooo, the original is also affected
                        console.log(nums);
                          // [ [ '8', 2 ], [ 10 ], [ 300 ] ]
                

                所以,为了避免这些情况发生,请使用

                        const arr = ['1', '2', '3'];
                
                        const cloneArr = Array.from(arr);
                

                【讨论】:

                • 在您的示例中指出更改 cloneNums[0][0] 如何将更改传播到 nums[0][0] 是一件有效的事情 - 但这是因为 nums[0][0] 实际上是一个对象,其引用被复制到 @987654326 @ 由扩展运算符。也就是说,这种行为不会影响我们按值(int、字符串等文字)复制的代码。
                【解决方案12】:

                es6 方式呢?

                arr2 = [...arr1];
                

                【讨论】:

                • 如果用 babel 转换:[].concat(_slice.call(arguments))
                • 不确定arguments 来自哪里...我认为您的 babel 输出混合了一些不同的功能。更有可能是arr2 = [].concat(arr1)
                • @SterlingArcher arr2 = [].conact(arr1)arr2 = [...arr1] 不同。 [...arr1] 语法将孔转换为undefined。例如,arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3
                • 我在浏览器(Chrome 59.0.3071.115)中针对 Dan 的上述回答对此进行了测试。它比 .slice() 慢 10 倍以上。 n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
                • 仍然不会克隆这样的东西:[{a: 'a', b: {c: 'c'}}]。如果 c 的值在“复制”数组中发生更改,它将在原始数组中更改,因为它只是一个引用副本,而不是克隆。
                【解决方案13】:

                带有Spread 运算符的ECMAScript 2015 方式:

                基本示例:

                var copyOfOldArray = [...oldArray]
                var twoArraysBecomeOne = [...firstArray, ..seccondArray]
                

                在浏览器控制台中尝试:

                var oldArray = [1, 2, 3]
                var copyOfOldArray = [...oldArray]
                console.log(oldArray)
                console.log(copyOfOldArray)
                
                var firstArray = [5, 6, 7]
                var seccondArray = ["a", "b", "c"]
                var twoArraysBecomOne = [...firstArray, ...seccondArray]
                console.log(twoArraysBecomOne);
                

                参考文献

                【讨论】:

                • 可能唯一快速传播的就是键入它。与其他方法相比,它的性能要差得多。
                • 请提供一些关于你的论点的链接。
                【解决方案14】:

                a.map(e =&gt; e) 是这项工作的另一种选择。截至今天,.map() 在 Firefox 中非常快(几乎与 .slice(0) 一样快),但在 Chrome 中则不然。

                另一方面,如果一个数组是多维的,因为数组是对象,而对象是引用类型,所以 slice 或 concat 方法都不能解决问题……所以克隆数组的一种正确方法是Array.prototype.clone()的发明如下。

                Array.prototype.clone = function(){
                  return this.map(e => Array.isArray(e) ? e.clone() : e);
                };
                
                var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ],
                    brr = arr.clone();
                brr[4][2][1] = "two";
                console.log(JSON.stringify(arr));
                console.log(JSON.stringify(brr));

                【讨论】:

                • 不错,但不幸的是,如果您的数组中有 Object ,这将不起作用:\ JSON.parse(JSON.stringify(myArray)) 在这种情况下效果更好。
                【解决方案15】:

                记住 .slice() 不适用于二维数组。你需要一个这样的函数:

                function copy(array) {
                  return array.map(function(arr) {
                    return arr.slice();
                  });
                }
                

                【讨论】:

                • 在 Javascript 中没有二维数组。只有包含数组的数组。您正在尝试做的是问题中不需要的 深拷贝
                【解决方案16】:

                这取决于数组的长度。如果数组长度 slice 和 concat 方法所用的时间大致相同。但是当你给出更大的范围时,concat 方法会胜出。

                例如,试试这个代码:

                var original_array = [];
                for(var i = 0; i < 10000000; i ++) {
                    original_array.push( Math.floor(Math.random() * 1000000 + 1));
                }
                
                function a1() {
                    var dup = [];
                    var start = Date.now();
                    dup = original_array.slice();
                    var end = Date.now();
                    console.log('slice method takes ' + (end - start) + ' ms');
                }
                
                function a2() {
                    var dup = [];
                    var start = Date.now();
                    dup = original_array.concat([]);
                    var end = Date.now();
                    console.log('concat method takes ' + (end - start) + ' ms');
                }
                
                function a3() {
                    var dup = [];
                    var start = Date.now();
                    for(var i = 0; i < original_array.length; i ++) {
                        dup.push(original_array[i]);
                    }
                    var end = Date.now();
                    console.log('for loop with push method takes ' + (end - start) + ' ms');
                }
                
                function a4() {
                    var dup = [];
                    var start = Date.now();
                    for(var i = 0; i < original_array.length; i ++) {
                        dup[i] = original_array[i];
                    }
                    var end = Date.now();
                    console.log('for loop with = method takes ' + (end - start) + ' ms');
                }
                
                function a5() {
                    var dup = new Array(original_array.length)
                    var start = Date.now();
                    for(var i = 0; i < original_array.length; i ++) {
                        dup.push(original_array[i]);
                    }
                    var end = Date.now();
                    console.log('for loop with = method and array constructor takes ' + (end - start) + ' ms');
                }
                
                a1();
                a2();
                a3();
                a4();
                a5();
                

                如果您将 original_array 的长度设置为 1,000,000,则 slice 方法和 concat 方法所用的时间大致相同(3-4 毫秒,具体取决于随机数)。

                如果将 original_array 的长度设置为 10,000,000,则 slice 方法会占用 60 毫秒,concat 方法会占用 20 毫秒。

                【讨论】:

                • dup.pusha5 中是错误的,应该使用dup[i] =
                【解决方案17】:

                有一个更清洁的解决方案:

                var srcArray = [1, 2, 3];
                var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);
                

                需要进行长度检查,因为Array 构造函数在仅使用一个参数调用时表现不同。

                【讨论】:

                • 但它是最快的吗?
                • 也许比 splice() 更语义化。但实际上,applythis 几乎是直观的。
                • 在 chrome 上显示最慢的性能-jsperf.com/new-array-vs-splice-vs-slice/113
                • 可以使用Array.of,忽略长度:Array.of.apply(Array, array)
                【解决方案18】:

                我整理了一个快速演示:http://jsbin.com/agugo3/edit

                我在 Internet Explorer 8 上的结果是 156、782 和 750,这表明 slice 在这种情况下要快得多。

                【讨论】:

                • 如果您必须非常快速地执行此操作,请不要忘记垃圾收集器的额外成本。我正在使用 slice 为我的元胞自动机中的每个单元复制每个相邻数组,它比重用以前的数组和复制值要慢得多。 Chrome 显示大约 40% 的总时间用于垃圾收集。
                【解决方案19】:

                这取决于浏览器。如果您查看博文 Array.prototype.slice vs manual array creation,则有一个粗略的性能指南:

                结果:

                【讨论】:

                • arguments 不是一个正确的数组,他正在使用call 强制slice 在集合上运行。结果可能具有误导性。
                • 是的,我的意思是在我的帖子中提到,这些统计数据现在可能会随着浏览器的改进而改变,但它提供了一个总体思路。
                • @diugalde 我认为唯一可以接受将代码作为图片发布的情况是代码具有潜在危险并且不应复制粘贴。但在这种情况下,这很荒谬。
                【解决方案20】:

                正如@Dan 所说“这个答案很快就会过时。使用benchmarks 检查实际情况”,jsperf 有一个具体的答案,它自己没有答案:while

                var i = a.length;
                while(i--) { b[i] = a[i]; }
                

                有 960,589 次操作/秒,亚军 a.concat() 为 578,129 次/秒,即 60%。

                这是最新的 Firefox (40) 64 位。


                @aleclarson 创建了一个新的、更可靠的基准。

                【讨论】:

                • 你真的应该链接jsperf。您正在考虑的那个已损坏,因为在每个测试用例中都会创建一个新数组,但“while 循环”测试除外。
                • 我做了一个更准确的新jsperf:jsperf.com/clone-array-3
                • 60% 什么?快 60%?
                • @PeterMortensen:587192 约为 960589 的 60% (61.1...)。
                【解决方案21】:

                一个简单的解决方案:

                original = [1,2,3]
                cloned = original.map(x=>x)
                

                【讨论】:

                  【解决方案22】:
                  var cloned_array = [].concat(target_array);
                  

                  【讨论】:

                  • 请解释这是做什么的。
                  • 虽然这段代码 sn-p 可以回答这个问题,但它没有提供任何上下文来解释如何或为什么。考虑添加一两句话来解释你的答案。
                  • 我讨厌这种cmets。它的作用很明显!
                  • 一个简单问题的简单答案,没有什么大故事可读。我喜欢这种答案+1
                  • “我只问速度” - 这个答案没有给出速度指示。这是被问到的主要问题。 Brandonscript 有一个很好的观点。需要更多信息才能将其视为答案。但如果这是一个更简单的问题,这将是一个很好的答案。
                  【解决方案23】:

                  深度克隆数组或对象的最简单方法:

                  var dup_array = JSON.parse(JSON.stringify(original_array))
                  

                  【讨论】:

                  • 对初学者的重要提示:因为这依赖于 JSON,这也继承了它的局限性。除此之外,这意味着您的数组不能包含undefined 或任何functions。在JSON.stringify 过程中,这两个都将为您转换为null。其他策略,例如(['cool','array']).slice() 不会更改它们,但也不会深度克隆数组中的对象。所以有一个权衡。
                  • 性能非常差,不适用于 DOM、日期、正则表达式、函数等特殊对象或原型对象。不支持循环引用。永远不要使用 JSON 进行深度克隆。
                  • 最糟糕的方法!仅在某些问题所有其他问题都不起作用时才使用。它很慢,资源密集,并且具有 cmets 中已经提到的所有 JSON 限制。无法想象它是如何获得 25 票赞成的。
                  • 它深度复制带有原语的数组,其中属性是带有更多原语/数组的数组。为此没关系。
                  • 我在浏览器(Chrome 59.0.3071.115)中针对 Dan 的上述回答对此进行了测试。它比 .slice() 慢了近 20 倍。 n = 1000*1000; start = + new Date(); a = Array(n); var b = JSON.parse(JSON.stringify(a)) console.log(new Date() - start); // 221
                  【解决方案24】:

                  看看:link。这不是关于速度,而是关于舒适。此外,如您所见,您只能在 原始类型上使用 slice(0)

                  要制作数组的独立副本而不是对其引用的副本,您可以使用数组切片方法。

                  例子:

                  要制作数组的独立副本而不是对其引用的副本,您可以使用数组切片方法。

                  var oldArray = ["mip", "map", "mop"];
                  var newArray = oldArray.slice();
                  

                  复制或克隆一个对象:

                  function cloneObject(source) {
                      for (i in source) {
                          if (typeof source[i] == 'source') {
                              this[i] = new cloneObject(source[i]);
                          }
                          else{
                              this[i] = source[i];
                    }
                      }
                  }
                  
                  var obj1= {bla:'blabla',foo:'foofoo',etc:'etc'};
                  var obj2= new cloneObject(obj1);
                  

                  来源:link

                  【讨论】:

                  • primitive types 注释也适用于问题中的for 循环。
                  • 如果我正在复制一个对象数组,我希望新数组引用相同的对象而不是克隆对象。
                  猜你喜欢
                  • 1970-01-01
                  • 2021-08-06
                  • 2011-06-07
                  • 2010-11-03
                  • 1970-01-01
                  • 2011-07-17
                  • 2018-05-21
                  • 1970-01-01
                  相关资源
                  最近更新 更多