在每第n个字符处分割字符串

93

在 JavaScript 中,这是我们如何在每个第三个字符处分割一个字符串

"foobarspam".match(/.{1,3}/g)

我正在尝试在Java中解决这个问题。有什么建议吗?


我不会在这个任务中使用正则表达式。 - kennytm
3
那你有什么建议呢? - Vijay Dev
1
类似Simon的答案。 - kennytm
我赞同你的建议。不需要安装额外的库,Simon的解决方案非常好。 - harperville
10个回答

152

你可以这样做:

String s = "1234567890";
System.out.println(java.util.Arrays.toString(s.split("(?<=\\G...)")));

它产生:

[123, 456, 789, 0]

正则表达式(?<=\G...)匹配一个空字符串,该空字符串具有上一次匹配\G)后面三个字符...之前的特征((?<= ))。


3
我也会选择Simon的解决方案:如果我开始在代码库中添加我的正则表达式技巧,我的同事可能不会喜欢。 - Bart Kiers
18
我不希望有人因为不喜欢正则表达式就投反对票,让我感到很不愉快。 - William Brendel
63
非常佩服您对正则表达式的高超运用,但作为这段代码的读者,我会追踪到您的家并向您扔鸡蛋。 :) - Kevin Bourrillion
4
只要您通过正确命名的函数进行调用(即splitIntoParts),并且不直接将该行代码嵌入到您的代码中,那么一切都很好。否则,就要开始寻找错误了 :) - GreenieMeanie
9
我将此复制粘贴到我的Android Studio项目中,结果得到[123, 4567890] :( - Evren Yurtesen
显示剩余12条评论

93

Java没有提供非常全面的字符串分割工具,因此Guava库提供了这样的工具:

Iterable<String> pieces = Splitter.fixedLength(3).split(string);

看看Splitter的Javadoc吧,它非常强大。


8
这是正确的答案(也称为:“熟知和使用库”)。 - Jonik
4
我会选择这个答案,而不是正则表达式...因为它更易于维护(例如,相较于能够阅读“可读”代码的人数,使用正则表达式的人数较少)。 - sivabudh
4
只有在您已经有了Guava依赖项的情况下才是好的。否则,您需要添加另一个依赖项——在与同事/系统架构师确认之前不应这样做的内容。 - foo
1
在大多数情况下,添加整个库以便只使用一个方法并不是最佳实践,此外,在企业环境中添加库总是一个重大决定。 - GaboSampaio

57
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        for (String part : getParts("foobarspam", 3)) {
            System.out.println(part);
        }
    }
    private static List<String> getParts(String string, int partitionSize) {
        List<String> parts = new ArrayList<String>();
        int len = string.length();
        for (int i=0; i<len; i+=partitionSize)
        {
            parts.add(string.substring(i, Math.min(len, i + partitionSize)));
        }
        return parts;
    }
}

如果您保留了一个包含整个原始字符串的子字符串集合,则新的String方法实际上会浪费(n-1)*sizeof(int)的内存。新的String的char数组将占用相同的内存,但每个字符数组都将具有单独的长度字段。也就是说,如果任何子字符串稍后被丢弃,新的String可以减少内存使用量。除非原始字符串非常大,否则无论哪种方式都不用担心。 - ILMTitan
@DenisTulskiy 你能详细说明一下吗?substring方法实际上足够聪明,可以使用父字符串的char[]来存储数据;更多详情请参见此答案 - wchargin
2
@WChargin:嗯,你说得对,我不知道为什么写了那个评论。我会删除它。谢谢。 - Denis Tulskiy
我认为这个答案是正确的,就像正则表达式一样只分离一次。 - Aarush Kumar

12
作为对Bart Kiers回答的补充,我想要添加的是在正则表达式中可以使用代表三个字符的省略号...,也可以编写有相同含义的.{3}
那么代码将如下所示:
String bitstream = "00101010001001010100101010100101010101001010100001010101010010101";
System.out.println(java.util.Arrays.toString(bitstream.split("(?<=\\G.{3})")));

通过这种方式,修改字符串长度会更容易,并且现在可以使用变量输入字符串长度来创建函数。 可以像下面这样完成:


public static String[] splitAfterNChars(String input, int splitLen){
    return input.split(String.format("(?<=\\G.{%1$d})", splitLen));
}

IdeOne中的示例:http://ideone.com/rNlTj5


这是更好的解决方案,你可以告诉我正则表达式的格式吗? - mi_mo
由于我使用了Bart Kiers已经解释过的相同解决方案,因此我可以参考他的答案。%1$d将被替换为变量splitLen的十进制值。 否则,regex101.com对您也可能非常有帮助。 - Frodo

4

晚进。

下面是使用Java8 streams的简洁实现,一行代码:

String foobarspam = "foobarspam";
AtomicInteger splitCounter = new AtomicInteger(0);
Collection<String> splittedStrings = foobarspam
                                    .chars()
                                    .mapToObj(_char -> String.valueOf((char)_char))
                                    .collect(Collectors.groupingBy(stringChar -> splitCounter.getAndIncrement() / 3
                                                                ,Collectors.joining()))
                                    .values();

输出:

[foo, bar, spa, m]

12
一句话概括。 ;) - Chris

1
这是一个晚回答,但我还是想让新程序员看到:
如果您不想使用正则表达式,并且不希望依赖第三方库,您可以使用此方法,该方法在2.80 GHz CPU上需要89920至100113纳秒(少于一毫秒)。它不像Simon Nickerson的示例那样漂亮,但它有效。
   /**
     * Divides the given string into substrings each consisting of the provided
     * length(s).
     * 
     * @param string
     *            the string to split.
     * @param defaultLength
     *            the default length used for any extra substrings. If set to
     *            <code>0</code>, the last substring will start at the sum of
     *            <code>lengths</code> and end at the end of <code>string</code>.
     * @param lengths
     *            the lengths of each substring in order. If any substring is not
     *            provided a length, it will use <code>defaultLength</code>.
     * @return the array of strings computed by splitting this string into the given
     *         substring lengths.
     */
    public static String[] divideString(String string, int defaultLength, int... lengths) {
        java.util.ArrayList<String> parts = new java.util.ArrayList<String>();

        if (lengths.length == 0) {
            parts.add(string.substring(0, defaultLength));
            string = string.substring(defaultLength);
            while (string.length() > 0) {
                if (string.length() < defaultLength) {
                    parts.add(string);
                    break;
                }
                parts.add(string.substring(0, defaultLength));
                string = string.substring(defaultLength);
            }
        } else {
            for (int i = 0, temp; i < lengths.length; i++) {
                temp = lengths[i];
                if (string.length() < temp) {
                    parts.add(string);
                    break;
                }
                parts.add(string.substring(0, temp));
                string = string.substring(temp);
            }
            while (string.length() > 0) {
                if (string.length() < defaultLength || defaultLength <= 0) {
                    parts.add(string);
                    break;
                }
                parts.add(string.substring(0, defaultLength));
                string = string.substring(defaultLength);
            }
        }

        return parts.toArray(new String[parts.size()]);
    }

1

使用纯Java:

    String s = "1234567890";
    List<String> list = new Scanner(s).findAll("...").map(MatchResult::group).collect(Collectors.toList());
    System.out.printf("%s%n", list);

生成以下输出:

[123, 456, 789]

请注意,这会丢弃剩余的字符(在本例中为0)。


0

我会从类似这样的东西开始

public List<String> split(String str, int interval) {
    if (str.length() <= interval) {
        return List.of(str);
    }
    var subStrings = new ArrayList<String>();
    int pointer = 0;
    while (str.length() > pointer) {
        String substring = str.substring(pointer, pointer + interval);
        subStrings.add(substring);
        pointer += interval;
    }
    return subStrings;
}

0

您还可以将字符串在每个第n个字符处拆分,并将它们放入列表的每个索引中:

这里我创建了一个名为Sequence的字符串列表:

List < String > Sequence

然后,我基本上是通过每2个字母来拆分字符串“KILOSO”。因此,“KI”、“LO”和“SO”将被合并到名为Sequence的列表的单独索引中。

String S = KILOSO

Sequence = Arrays.asList(S.split("(?<=\G..)"));

所以当我执行以下操作时:

System.out.print(Sequence)

它应该打印:

[KI,LO,SO]

以验证我可以编写:

System.out.print(Sequence.get(1))

它将打印出:

LO


0

我最近遇到了这个问题,这是我想出的解决方案

final int LENGTH = 10;
String test = "Here is a very long description, it is going to be past 10";

Map<Integer,StringBuilder> stringBuilderMap = new HashMap<>();
for ( int i = 0; i < test.length(); i++ ) {
    int position = i / LENGTH; // i<10 then 0, 10<=i<19 then 1, 20<=i<30 then 2, etc.

    StringBuilder currentSb = stringBuilderMap.computeIfAbsent( position, pos -> new StringBuilder() ); // find sb, or create one if not present
    currentSb.append( test.charAt( i ) ); // add the current char to our sb
}

List<String> comments = stringBuilderMap.entrySet().stream()
        .sorted( Comparator.comparing( Map.Entry::getKey ) )
        .map( entrySet -> entrySet.getValue().toString() )
        .collect( Collectors.toList() );
//done



// here you can see the data
comments.forEach( cmt -> System.out.println( String.format( "'%s' ... length= %d", cmt, cmt.length() ) ) );
// PRINTS:
// 'Here is a ' ... length= 10
// 'very long ' ... length= 10
// 'descriptio' ... length= 10
// 'n, it is g' ... length= 10
// 'oing to be' ... length= 10
// ' past 10' ... length= 8

// make sure they are equal
String joinedString = String.join( "", comments );
System.out.println( "\nOriginal strings are equal " + joinedString.equals( test ) );
// PRINTS: Original strings are equal true

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