在Java中将字符串拆分为等长的子字符串

Posted

技术标签:

【中文标题】在Java中将字符串拆分为等长的子字符串【英文标题】:Split string to equal length substrings in Java 【发布时间】:2011-04-15 04:19:43 【问题描述】:

如何在 Java 中将字符串 "Thequickbrownfoxjumps" 拆分为大小相等的子字符串。 例如。 "Thequickbrownfoxjumps" 的 4 个相等大小应该给出输出。

["Theq","uick","brow","nfox","jump","s"]

类似问题:

Split string into equal-length substrings in Scala

【问题讨论】:

你尝试了什么?为什么这不起作用? 您需要为此使用正则表达式吗?只是因为正则表达式标签而问... 他发布的@Thilo 链接是针对 Scala 的,他在 Java 中询问同样的问题 @Thilo:我在问如何在 java 中做到这一点,就像为 scala 给出的答案一样。 【参考方案1】:

这是正则表达式单行版本:

System.out.println(Arrays.toString(
    "Thequickbrownfoxjumps".split("(?<=\\G.4)")
));

\G 是一个零宽度断言,它匹配上一个匹配结束的位置。如果 没有 之前没有匹配,则匹配输入的开头,与\A 相同。封闭的lookbehind匹配距离最后一个匹配结束四个字符的位置。

lookbehind 和\G 都是高级正则表达式功能,并非所有风格都支持。此外,\G 并没有在支持它的各种风格中一致地实现。这个技巧(例如)在Java、Perl、.NET 和 JGSoft 中有效,但在 php (PCRE)、Ruby 1.9+ 或 TextMate(两者都是 Oniguruma)中无效。 javascript/y(粘性标志)不如 \G 灵活,即使 JS 确实支持后视,也不能这样使用。

我应该提到,如果您有其他选择,我不一定推荐此解决方案。其他答案中的非正则表达式解决方案可能更长,但它们也是自我记录的;这个正好与那个相反。 ;)

此外,这在 android 中不起作用,它不支持在后视中使用 \G

【讨论】:

在 PHP 5.2.4 中使用以下代码: return preg_split('/(? 作为记录,使用String.substring() 代替正则表达式,虽然需要几行额外的代码,但运行速度会快 5 倍... 在 Java 中,这不适用于带有换行符的字符串。它只会检查到第一个换行符,如果该换行符恰好在拆分大小之前,则不会拆分字符串。还是我错过了什么? 为了完整起见:将文本拆分为多行需要在正则表达式中添加前缀(?s)(?s)(?&lt;=\\G.4) @JeffreyBlattman 我怀疑你在编译时遇到了异常...【参考方案2】:

嗯,用简单的算术和字符串操作很容易做到这一点:

public static List<String> splitEqually(String text, int size) 
    // Give the list the right capacity to start with. You could use an array
    // instead if you wanted.
    List<String> ret = new ArrayList<String>((text.length() + size - 1) / size);

    for (int start = 0; start < text.length(); start += size) 
        ret.add(text.substring(start, Math.min(text.length(), start + size)));
    
    return ret;

注意:这假定 UTF-16 代码单元(char,实际上是)与“字符”的 1:1 映射。对于基本多语言平面之外的字符(例如表情符号)以及(取决于您要如何计算事物的方式)组合字符,该假设不成立。

我认为为此使用正则表达式真的不值得。

编辑:我不使用正则表达式的理由:

这不使用任何正则表达式的真正模式匹配。它只是在计数。 我怀疑上述方法会更有效,尽管在大多数情况下它并不重要 如果您需要在不同的地方使用可变大小,您可以使用重复或辅助函数来根据参数 - ick 构建正则表达式本身。 另一个答案中提供的正则表达式首先没有编译(无效转义),然后没有工作。我的代码第一次工作。这更多地证明了正则表达式与纯代码的可用性,IMO。

【讨论】:

@Emil:实际上,您没有要求使用正则表达式。它在标签中,但问题本身并没有要求使用正则表达式。你把这个方法放在一个地方,然后你可以在代码中的任何地方将字符串拆分成一个可读性很强的语句。 Emil 这不是正则表达式的用途。期间。 @Emil:如果你想要一个单行来分割字符串,我会推荐 Guava 的 Splitter.fixedLength(4),正如 seanizer 所建议的那样。 @Jay:come-on 你不必那么讽刺。我相信它可以在一行中使用正则表达式来完成。固定长度的子字符串也是一种模式。做什么你说这个答案。 ***.com/questions/3760152/…. @Emil:我不想这样粗鲁,只是异想天开。我的观点的严肃部分是,虽然是的,但我相信你可以想出一个正则表达式来做到这一点——我看到艾伦摩尔有一个他声称有效的——它是神秘的,因此对于后来的程序员来说很难理解和维护。子字符串解决方案可以直观且易读。请参阅 Jon Skeet 的第 4 条子弹:我同意 100%。【参考方案3】:

这很容易用Google Guava:

for(final String token :
    Splitter
        .fixedLength(4)
        .split("Thequickbrownfoxjumps"))
    System.out.println(token);

输出:

Theq
uick
brow
nfox
jump
s

或者如果你需要将结果作为一个数组,你可以使用这个代码:

String[] tokens =
    Iterables.toArray(
        Splitter
            .fixedLength(4)
            .split("Thequickbrownfoxjumps"),
        String.class
    );

参考:

Splitter.fixedLength() Splitter.split() Iterables.toArray()

注意:Splitter 的构造如上图所示,但由于 Splitter 是不可变且可重用的,因此最好将它们存储在常量中:

private static final Splitter FOUR_LETTERS = Splitter.fixedLength(4);

// more code

for(final String token : FOUR_LETTERS.split("Thequickbrownfoxjumps"))
    System.out.println(token);

【讨论】:

感谢您的帖子(让我了解番石榴库方法)。但我必须接受正则表达式答案***.com/questions/3760152/…,因为它不需要任何第 3 方库和一个 -班轮。 仅仅为了执行这个简单的任务而包含数百 KB 的库代码几乎肯定是不正确的。 @JeffreyBlattman 包括 Guava 只是为此可能是矫枉过正,真的。但无论如何,我在所有 Java 代码中都将它用作通用库,所以为什么不使用这一附加功能 有什么方法可以用分隔符加入回来? @AquariusPower String.join(separator, arrayOrCollection)【参考方案4】:

如果您正在使用 Google 的 guava 通用库(老实说,任何新的 Java 项目都可能应该使用),这对于 Splitter 类来说简直是微不足道的:

for (String substring : Splitter.fixedLength(4).split(inputString)) 
    doSomethingWith(substring);

这就是。简单!

【讨论】:

不适用于所有 Unicode 字符。尝试使用 Guava 30.1.1 输入,我们将 q 替换为 FACE WITH MEDICAL MASK: "The?uickbrownfoxjumps" yielding: The? ?uic kbro ...【参考方案5】:
public static String[] split(String src, int len) 
    String[] result = new String[(int)Math.ceil((double)src.length()/(double)len)];
    for (int i=0; i<result.length; i++)
        result[i] = src.substring(i*len, Math.min(src.length(), (i+1)*len));
    return result;

【讨论】:

由于src.length()len 都是ints,你的电话ceiling 没有达到你想要的效果——看看其他一些回复是如何做到的:( src.length() + len - 1) / len @Michael:说得好。我没有用非多个长度的字符串对其进行测试。现在已经修好了。【参考方案6】:
public String[] splitInParts(String s, int partLength)

    int len = s.length();

    // Number of parts
    int nparts = (len + partLength - 1) / partLength;
    String parts[] = new String[nparts];

    // Break into parts
    int offset= 0;
    int i = 0;
    while (i < nparts)
    
        parts[i] = s.substring(offset, Math.min(offset + partLength, len));
        offset += partLength;
        i++;
    

    return parts;

【讨论】:

出于兴趣,你对for循环有什么意见吗? for 循环确实是一个更“自然”的选择:-) 感谢您指出这一点。【参考方案7】:

这是一个 one-liner 版本,它使用 Java 8 IntStream 来确定切片开始的索引:

String x = "Thequickbrownfoxjumps";

String[] result = IntStream
                    .iterate(0, i -> i + 4)
                    .limit((int) Math.ceil(x.length() / 4.0))
                    .mapToObj(i ->
                        x.substring(i, Math.min(i + 4, x.length())
                    )
                    .toArray(String[]::new);

【讨论】:

【参考方案8】:

StringBuilder 版本:

public static List<String> getChunks(String s, int chunkSize)

 List<String> chunks = new ArrayList<>();
 StringBuilder sb = new StringBuilder(s);

while(!(sb.length() ==0)) 
           
   chunks.add(sb.substring(0, chunkSize));
   sb.delete(0, chunkSize);


return chunks;

【讨论】:

【参考方案9】:

您可以使用来自String.class(处理异常)或来自Apache lang commons(它为您处理异常)的substring

static String   substring(String str, int start, int end) 

把它放在一个循环中,你就可以开始了。

【讨论】:

标准String类中的substring方法有什么问题? commons 版本避免异常(越界等) 我明白了;我想说我更喜欢通过控制调用代码中的参数来“避免异常”。【参考方案10】:

我更喜欢这个简单的解决方案:

String content = "Thequickbrownfoxjumps";
while(content.length() > 4) 
    System.out.println(content.substring(0, 4));
    content = content.substring(4);

System.out.println(content);

【讨论】:

不要这样做!字符串是不可变的,因此您的代码需要每 4 个字符复制整个剩余的字符串。因此,您的 sn-p 在字符串的大小上采用二次而非线性时间。 @Tobias:即使 String 是可变的,这个 sn-p 也会执行提到的冗余副本,除了涉及到复杂的编译过程。使用这个 sn-p 的唯一原因是代码简单。 自首次发布代码以来,您是否更改了代码?最新版本实际上并没有复制 - substring() 高效运行(恒定时间,至少在旧版本的 Java 上);它保留对整个字符串的 char[] 的引用(至少在旧版本的 Java 上),但在这种情况下这很好,因为您保留了所有字符。因此,您在这里拥有的最新代码实际上是可以的(如果内容以空字符串开头,则您的代码会打印一个空行,这可能不是人们想要的)。 @Tobias:我不记得有任何变化。 @Tobias substring 实现随 Java 7 的变化而变化,2012 年年中更新 6,当时 offsetcount 字段从 String 类中删除。所以substring 的复杂性早在做出这个答案之前就变成了线性的。但是对于像示例这样的小字符串,它仍然运行得足够快,对于更长的字符串……这个任务在实践中很少发生。【参考方案11】:

我使用以下 java 8 解决方案:

public static List<String> splitString(final String string, final int chunkSize) 
  final int numberOfChunks = (string.length() + chunkSize - 1) / chunkSize;
  return IntStream.range(0, numberOfChunks)
                  .mapToObj(index -> string.substring(index * chunkSize, Math.min((index + 1) * chunkSize, string.length())))
                  .collect(toList());

【讨论】:

【参考方案12】:

如果您想将字符串等分向后,即从右到左,例如,将1010001111 拆分为[10, 1000, 1111],代码如下:

/**
 * @param s         the string to be split
 * @param subLen    length of the equal-length substrings.
 * @param backwards true if the splitting is from right to left, false otherwise
 * @return an array of equal-length substrings
 * @throws ArithmeticException: / by zero when subLen == 0
 */
public static String[] split(String s, int subLen, boolean backwards) 
    assert s != null;
    int groups = s.length() % subLen == 0 ? s.length() / subLen : s.length() / subLen + 1;
    String[] strs = new String[groups];
    if (backwards) 
        for (int i = 0; i < groups; i++) 
            int beginIndex = s.length() - subLen * (i + 1);
            int endIndex = beginIndex + subLen;
            if (beginIndex < 0)
                beginIndex = 0;
            strs[groups - i - 1] = s.substring(beginIndex, endIndex);
        
     else 
        for (int i = 0; i < groups; i++) 
            int beginIndex = subLen * i;
            int endIndex = beginIndex + subLen;
            if (endIndex > s.length())
                endIndex = s.length();
            strs[i] = s.substring(beginIndex, endIndex);
        
    
    return strs;

【讨论】:

【参考方案13】:

这是一个使用 Java8 流的单行实现:

String input = "Thequickbrownfoxjumps";
final AtomicInteger atomicInteger = new AtomicInteger(0);
Collection<String> result = input.chars()
                                    .mapToObj(c -> String.valueOf((char)c) )
                                    .collect(Collectors.groupingBy(c -> atomicInteger.getAndIncrement() / 4
                                                                ,Collectors.joining()))
                                    .values();

它给出以下输出:

[Theq, uick, brow, nfox, jump, s]

【讨论】:

这是一个可怕的解决方案,它违背了 API 的意图,使用了有状态的函数,而且比普通循环复杂得多,更不用说装箱和字符串连接的开销了。如果您想要 Stream 解决方案,请使用 String[] result = IntStream.range(0, (input.length()+3)/4) .mapToObj(i -&gt; input.substring(i *= 4, Math.min(i + 4, input.length()))) .toArray(String[]::new);【参考方案14】:

Java 8 解决方案(类似this,但更简单一些):

public static List<String> partition(String string, int partSize) 
  List<String> parts = IntStream.range(0, string.length() / partSize)
    .mapToObj(i -> string.substring(i * partSize, (i + 1) * partSize))
    .collect(toList());
  if ((string.length() % partSize) != 0)
    parts.add(string.substring(string.length() / partSize * partSize));
  return parts;

【讨论】:

【参考方案15】:

使用代码点处理所有字符

这里有一个解决方案:

适用于所有 143,859 个Unicode 字符 如果您有进一步的逻辑要处理,则允许您检查或操作每个结果字符串。

要使用所有 Unicode 字符,请避免使用过时的 char 类型。并避免使用基于char 的实用程序。而是使用code point 整数。

调用String#codePoints 以获取IntStream 对象,int 值流。在下面的代码中,我们将这些 int 值收集到一个数组中。然后我们循环数组,对于每个整数,我们将分配给该数字的字符附加到我们的StringBuilder 对象。每第 n 个字符,我们将一个字符串添加到我们的主列表中,并清空 StringBuilder

String input = "Thequickbrownfoxjumps";

int chunkSize = 4 ;
int[] codePoints = input.codePoints().toArray();  // `String#codePoints` returns an `IntStream`. Collect the elements of that stream into an array.
int initialCapacity = ( ( codePoints.length / chunkSize ) + 1 );
List < String > strings = new ArrayList <>( initialCapacity );

StringBuilder sb = new StringBuilder();
for ( int i = 0 ; i < codePoints.length ; i++ )

    sb.appendCodePoint( codePoints[ i ] );
    if ( 0 == ( ( i + 1 ) % chunkSize ) ) // Every nth code point.
    
        strings.add( sb.toString() ); // Remember this iteration's value.
        sb.setLength( 0 ); // Clear the contents of the `StringBuilder` object.
    

if ( sb.length() > 0 ) // If partial string leftover, save it too. Or not… just delete this `if` block.

    strings.add( sb.toString() ); // Remember last iteration's value.


System.out.println( "strings = " + strings );

strings = [Theq, uick, brow, nfox, jump, s]

这适用于非拉丁字符。这里我们将q替换为FACE WITH MEDICAL MASK。

String text = "The?uickbrownfoxjumps"

strings = [The?, uick, brow, nfox, jump, s]

【讨论】:

【参考方案16】:

这是我基于 RegEx 和 Java 8 流的版本。值得一提的是,Matcher.results() 方法从 Java 9 开始可用。

包括测试。

public static List<String> splitString(String input, int splitSize) 
    Matcher matcher = Pattern.compile("(?:(." + splitSize + "))+?").matcher(input);
    return matcher.results().map(MatchResult::group).collect(Collectors.toList());


@Test
public void shouldSplitStringToEqualLengthParts() 
    String anyValidString = "Split me equally!";
    String[] expectedTokens2 = "Sp", "li", "t ", "me", " e", "qu", "al", "ly";
    String[] expectedTokens3 = "Spl", "it ", "me ", "equ", "all";

    Assert.assertArrayEquals(expectedTokens2, splitString(anyValidString, 2).toArray());
    Assert.assertArrayEquals(expectedTokens3, splitString(anyValidString, 3).toArray());

【讨论】:

【参考方案17】:

最简单的解决方案是:

  /**
   * Slices string by passed - in slice length.
   * If passed - in string is null or slice length less then 0 throws IllegalArgumentException.
   * @param toSlice string to slice
   * @param sliceLength slice length
   * @return List of slices
   */
  public static List<String> stringSlicer(String toSlice, int sliceLength) 
    if (toSlice == null) 
      throw new IllegalArgumentException("Passed - in string is null");
    
    if (sliceLength < 0) 
      throw new IllegalArgumentException("Slice length can not be less then 0");
    
    if (toSlice.isEmpty() || toSlice.length() <= sliceLength) 
      return List.of(toSlice);
    
    
   return Arrays.stream(toSlice.split(String.format("(?s)(?<=\\G.%d)", sliceLength))).collect(Collectors.toList());
  

【讨论】:

【参考方案18】:

我在对accepted solution 的评论中询问了@Alan Moore 如何处理带有换行符的字符串。他建议使用 DOTALL。

根据他的建议,我创建了一个小样本来说明其工作原理:

public void regexDotAllExample() throws UnsupportedEncodingException 
    final String input = "The\nquick\nbrown\r\nfox\rjumps";
    final String regex = "(?<=\\G.4)";

    Pattern splitByLengthPattern;
    String[] split;

    splitByLengthPattern = Pattern.compile(regex);
    split = splitByLengthPattern.split(input);
    System.out.println("---- Without DOTALL ----");
    for (int i = 0; i < split.length; i++) 
        byte[] s = split[i].getBytes("utf-8");
        System.out.println("[Idx: "+i+", length: "+s.length+"] - " + s);
    
    /* Output is a single entry longer than the desired split size:
    ---- Without DOTALL ----
    [Idx: 0, length: 26] - [B@17cdc4a5
     */


    //DOTALL suggested in Alan Moores comment on SO: https://***.com/a/3761521/1237974
    splitByLengthPattern = Pattern.compile(regex, Pattern.DOTALL);
    split = splitByLengthPattern.split(input);
    System.out.println("---- With DOTALL ----");
    for (int i = 0; i < split.length; i++) 
        byte[] s = split[i].getBytes("utf-8");
        System.out.println("[Idx: "+i+", length: "+s.length+"] - " + s);
    
    /* Output is as desired 7 entries with each entry having a max length of 4:
    ---- With DOTALL ----
    [Idx: 0, length: 4] - [B@77b22abc
    [Idx: 1, length: 4] - [B@5213da08
    [Idx: 2, length: 4] - [B@154f6d51
    [Idx: 3, length: 4] - [B@1191ebc5
    [Idx: 4, length: 4] - [B@30ddb86
    [Idx: 5, length: 4] - [B@2c73bfb
    [Idx: 6, length: 2] - [B@6632dd29
     */


但我也喜欢https://***.com/a/3760193/1237974 中的@Jon Skeets 解决方案。对于不是每个人都对正则表达式都有同样经验的大型项目的可维护性,我可能会使用 Jons 解决方案。

【讨论】:

【参考方案19】:

另一种蛮力解决方案可能是,

    String input = "thequickbrownfoxjumps";
    int n = input.length()/4;
    String[] num = new String[n];

    for(int i = 0, x=0, y=4; i<n; i++)
    num[i]  = input.substring(x,y);
    x += 4;
    y += 4;
    System.out.println(num[i]);
    

代码只是通过子字符串逐步遍历字符串

【讨论】:

【参考方案20】:
    import static java.lang.System.exit;
   import java.util.Scanner;
   import Java.util.Arrays.*;


 public class string123 

public static void main(String[] args) 


  Scanner sc=new Scanner(System.in);
    System.out.println("Enter String");
    String r=sc.nextLine();
    String[] s=new String[10];
    int len=r.length();
       System.out.println("Enter length Of Sub-string");
    int l=sc.nextInt();
    int last;
    int f=0;
    for(int i=0;;i++)
        last=(f+l);
            if((last)>=len) last=len;
        s[i]=r.substring(f,last);
     // System.out.println(s[i]);

      if (last==len)break;
       f=(f+l);
     
    System.out.print(Arrays.tostring(s));
    

结果

 Enter String
 Thequickbrownfoxjumps
 Enter length Of Sub-string
 4

 ["Theq","uick","brow","nfox","jump","s"]

【讨论】:

【参考方案21】:
@Test
public void regexSplit() 
    String source = "Thequickbrownfoxjumps";
    // define matcher, any char, min length 1, max length 4
    Matcher matcher = Pattern.compile(".1,4").matcher(source);
    List<String> result = new ArrayList<>();
    while (matcher.find()) 
        result.add(source.substring(matcher.start(), matcher.end()));
    
    String[] expected = "Theq", "uick", "brow", "nfox", "jump", "s";
    assertArrayEquals(result.toArray(), expected);

【讨论】:

【参考方案22】:
public static String[] split(String input, int length) throws IllegalArgumentException 

    if(length == 0 || input == null)
        return new String[0];

    int lengthD = length * 2;

    int size = input.length();
    if(size == 0)
        return new String[0];

    int rep = (int) Math.ceil(size * 1d / length);

    ByteArrayInputStream stream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_16LE));

    String[] out = new String[rep];
    byte[]  buf = new byte[lengthD];

    int d = 0;
    for (int i = 0; i < rep; i++) 

        try 
            d = stream.read(buf);
         catch (IOException e) 
            e.printStackTrace();
        

        if(d != lengthD)
        
            out[i] = new String(buf,0,d, StandardCharsets.UTF_16LE);
            continue;
        

        out[i] = new String(buf, StandardCharsets.UTF_16LE);
    
    return out;

【讨论】:

【参考方案23】:
public static List<String> getSplittedString(String stringtoSplit,
            int length) 

        List<String> returnStringList = new ArrayList<String>(
                (stringtoSplit.length() + length - 1) / length);

        for (int start = 0; start < stringtoSplit.length(); start += length) 
            returnStringList.add(stringtoSplit.substring(start,
                    Math.min(stringtoSplit.length(), start + length)));
        

        return returnStringList;
    

【讨论】:

以上是关于在Java中将字符串拆分为等长的子字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何在android和java中将字符串拆分为句子? [复制]

在Java中将字符串拆分为字符串和int [重复]

java - 如何在java中将字符串元素拆分为不相交的组?

如何在 C++ 中将数字字符串拆分为数组? [复制]

关于在Hive中将特定字符分隔的字符串拆分成多行的应用

在 PowerShell 中将字符串拆分为多列