在JavaScript中将大字符串分割成n个字符大小的块

311

我希望将一个非常大的字符串(比如说 10,000 个字符)分割成 N 大小的块。

从性能方面考虑,最好的方法是什么?

举个例子: "1234567890" 拆分为 2 小块会变成 ["12", "34", "56", "78", "90"]

是否可以使用 String.prototype.match 来实现这样的功能?如果可以,从性能方面考虑,这是否是最佳方式?

23个回答

645
你可以像这样做:
"1234567890".match(/.{1,2}/g);
// Results in:
["12", "34", "56", "78", "90"]

该方法仍可用于大小不是块大小的精确倍数的字符串:
"123456789".match(/.{1,2}/g);
// Results in:
["12", "34", "56", "78", "9"]

通常来说,如果你想要从任意字符串中提取最多长度为n的子字符串,你可以按照以下方式处理:

str.match(/.{1,n}/g); // Replace n with the size of the substring

如果您的字符串中可能包含换行符或回车符,您可以这样做:
str.match(/(.|[\r\n]){1,n}/g); // Replace n with the size of the substring

就性能而言,我使用了大约10k个字符并在Chrome上花费了略微超过1秒钟。你的情况可能有所不同。

这也可以用于一个可重复使用的函数中:

function chunkString(str, length) {
  return str.match(new RegExp('.{1,' + length + '}', 'g'));
}

10
由于此回答现在已经快3年了,我想再次尝试由@Vivin进行的性能测试。所以请注意,使用给定的正则表达式将100k个字符两个一组拆分,在Chrome v33上是即时完成的。 - aymericbeaumet
2
@Fmstrat,“如果您的字符串包含空格,则不计入长度”是什么意思?是的,.根本不匹配换行符。我会更新答案,以便考虑\n\r - Vivin Paliath
2
类似于 var chunks = str.split("").reverse().join().match(/.{1, 4}/).map(function(s) { return s.split("").reverse().join(); });。这样可以将字符串分成长度为4的块。我不确定你所说的“更少或更多”的意思是什么。请记住,这种方法并不适用于所有情况,特别是对于包含组合字符的字符串和可能破坏Unicode字符串的情况。 - Vivin Paliath
2
根据https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp,你可以使用 [^] 匹配任何字符,包括换行符。使用这个方法,你的示例将会得到 str.match(/[^]{1,n}/g) 的结果。 - Francesc Rosas
4
如需查看在jsperf上性能基准测试的真正快速的字符串分块方法,请查看我的答案。使用正则表达式是所有分块方法中最慢的一种。 - Justin Warkentin
显示剩余4条评论

72

我创建了几个更快的变体,你可以在jsPerf上查看。我最喜欢的是这个:

function chunkSubstr(str, size) {
  const numChunks = Math.ceil(str.length / size)
  const chunks = new Array(numChunks)

  for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
    chunks[i] = str.substr(o, size)
  }

  return chunks
}

4
这段代码在长字符串(约800k-9m个字符)上运行得非常好,但是当我将大小设置为20时,最后一块未被返回...非常奇怪的行为。 - David
3
@DavidAnderton 很好的发现。我已经修复了,有趣的是它似乎运行得更快了。在确定正确的块数时,应该执行Math.ceil()而不是四舍五入。 - Justin Warkentin
2
谢谢!我将其作为一个NPM模块放在了GitHub上,并提供了可选的Unicode支持 - https://github.com/vladgolubev/fast-chunk-string - Vlad Holubiev

46

结论:

  • match 效率非常低,slice 更好,在Firefox上使用 substr/substring 效果更好
  • match 对短字符串甚至更低效(即使已缓存的正则表达式 - 可能是由于正则表达式解析设置时间)
  • match 对大块大小甚至更低效(可能是由于无法“跳转”)
  • 对于使用非常小的块大小的长字符串,match 在较旧版本的IE上性能更好,但仍然在所有其他系统上失利
  • jsperf 很棒

12
jsperf链接已损坏。 - Charles Holbrow

31
这是一个快速简单的解决方案 -

function chunkString (str, len) {
  const size = Math.ceil(str.length/len)
  const r = Array(size)
  let offset = 0
  
  for (let i = 0; i < size; i++) {
    r[i] = str.substr(offset, len)
    offset += len
  }
  
  return r
}

console.log(chunkString("helloworld", 3))
// => [ "hel", "low", "orl", "d" ]

// 10,000 char string
const bigString = "helloworld".repeat(1000)
console.time("perf")
const result = chunkString(bigString, 3)
console.timeEnd("perf")
console.log(result)
// => perf: 0.385 ms
// => [ "hel", "low", "orl", "dhe", "llo", "wor", ... ]


1
你必须使用 substr() 而不是 substring() - Leif
2
我很好奇,为什么变量名中有下划线? - Felipe Valdes
1
@FelipeValdes 我假设这样做是为了不将它们与全局/参数变量混淆,或者将它们标记为私有范围。 - Mr. Polywhirl
@Leif substr()现在已被substring()所取代。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/substring - Magne

23

惊喜!你可以使用split函数来分割字符串。

var parts = "1234567890 ".split(/(.{2})/).filter(O=>O)

结果为[ '12', '34', '56', '78', '90', ' ' ]


5
filter(o => o) 是用来做什么的? - Ben Carp
3
当前的正则表达式在块之间创建空数组元素。使用filter(x=>x)来过滤掉这些空元素。 - artemnih
5
简洁但需要多次迭代输入。这个答案比本主题中其他解决方案慢了4倍以上。 - Mulan
28
是摩托车手掌控的。它可以让摩托车跑得更快 ;) - Fozi
1
.filter(Boolean)会起作用 - kirill.buga

13

你绝对可以做这样的事情

let pieces = "1234567890 ".split(/(.{2})/).filter(x => x.length == 2);

要获得这个:

[ '12', '34', '56', '78', '90' ]
如果你想动态输入/调整块大小,使得块的大小为n,你可以这样做:
n = 2;
let pieces = "1234567890 ".split(new RegExp("(.{"+n.toString()+"})")).filter(x => x.length == n);

要在原始字符串中找到所有可能的大小为n的块,请尝试以下方法:

let subs = new Set();
let n = 2;
let str = "1234567890 ";
let regex = new RegExp("(.{"+n.toString()+"})");     //set up regex expression dynamically encoded with n

for (let i = 0; i < n; i++){               //starting from all possible offsets from position 0 in the string
    let pieces = str.split(regex).filter(x => x.length == n);    //divide the string into chunks of size n...
    for (let p of pieces)                 //...and add the chunks to the set
        subs.add(p);
    str = str.substr(1);    //shift the string reading frame
}

你应该最终得到:

[ '12', '23', '34', '45', '56', '67', '78', '89', '90', '0 ' ]

8
var str = "123456789";
var chunks = [];
var chunkSize = 2;

while (str) {
    if (str.length < chunkSize) {
        chunks.push(str);
        break;
    }
    else {
        chunks.push(str.substr(0, chunkSize));
        str = str.substr(chunkSize);
    }
}

alert(chunks); // chunks == 12,34,56,78,9

6

包括左右版本,并进行预分配。 对于小块数据来说,这与 RegExp 实现一样快,但是随着块大小的增加速度更快。而且它非常节省内存。

function chunkLeft (str, size = 3) {
  if (typeof str === 'string') {
    const length = str.length
    const chunks = Array(Math.ceil(length / size))
    for (let i = 0, index = 0; index < length; i++) {
      chunks[i] = str.slice(index, index += size)
    }
    return chunks
  }
}

function chunkRight (str, size = 3) {
  if (typeof str === 'string') {
    const length = str.length
    const chunks = Array(Math.ceil(length / size))
    if (length) {
      chunks[0] = str.slice(0, length % size || size)
      for (let i = 1, index = chunks[0].length; index < length; i++) {
        chunks[i] = str.slice(index, index += size)
      }
    }
    return chunks
  }
}

console.log(chunkRight())  // undefined
console.log(chunkRight(''))  // []
console.log(chunkRight('1'))  // ["1"]
console.log(chunkRight('123'))  // ["123"]
console.log(chunkRight('1234'))  // ["1", "234"]
console.log(chunkRight('12345'))  // ["12", "345"]
console.log(chunkRight('123456'))  // ["123", "456"]
console.log(chunkRight('1234567'))  // ["1", "234", "567"]

PS:我发现 slice 比 substr 快一点。 - clarkttfu
非常快,谢谢。 - Aman Gupta

5

我已经编写了一个扩展函数,因此块长度还可以是数字数组,例如[1,3]

String.prototype.chunkString = function(len) {
    var _ret;
    if (this.length < 1) {
        return [];
    }
    if (typeof len === 'number' && len > 0) {
        var _size = Math.ceil(this.length / len), _offset = 0;
        _ret = new Array(_size);
        for (var _i = 0; _i < _size; _i++) {
            _ret[_i] = this.substring(_offset, _offset = _offset + len);
        }
    }
    else if (typeof len === 'object' && len.length) {
        var n = 0, l = this.length, chunk, that = this;
        _ret = [];
        do {
            len.forEach(function(o) {
                chunk = that.substring(n, n + o);
                if (chunk !== '') {
                    _ret.push(chunk);
                    n += chunk.length;
                }
            });
            if (n === 0) {
                return undefined; // prevent an endless loop when len = [0]
            }
        } while (n < l);
    }
    return _ret;
};

代码
"1234567890123".chunkString([1,3])

将返回:

[ '1', '234', '5', '678', '9', '012', '3' ]

5
const getChunksFromString = (str, chunkSize) => {
    var regexChunk = new RegExp(`.{1,${chunkSize}}`, 'g')   // '.' represents any character
    return str.match(regexChunk)
}

根据需要调用

console.log(getChunksFromString("Hello world", 3))   // ["Hel", "lo ", "wor", "ld"]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接