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

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

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

类似的问题:

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

198868 次浏览

您可以从 String.class(处理异常)或从 阿帕奇朗公地(它为您处理异常)使用 substring

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

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

嗯,用简单的算术和字符串运算就可以很容易地做到这一点:

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。
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;
}
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;
}

这是非常容易与 谷歌番石榴:

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
);

参考文献:

注意: 上面显示了拆分器的构造,但是由于拆分器是不可变的并且可重用的,所以将它们存储在常量中是一个很好的实践:

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


// more code


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

如果你使用的是 Google 的 番石榴通用库(老实说,任何一个新的 Java 项目都可能是 应该) ,那么对于 分裂者类来说,这就是一个极其琐碎的工作:

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

那就是 简单得很!

下面是 regex 的一行程序版本:

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

\G是一个零宽度断言,与前一次匹配结束的位置匹配。如果 曾经是之前没有匹配,它匹配输入的开头,与 \A相同。封闭的后视符合从最后一次匹配结束到结束的四个字符的位置。

后向和 \G都是高级正则表达式特性,并非所有风格都支持。此外,在支持 \G的各种风格中,\G并没有得到一致的实现。这个技巧(例如)在 爪哇咖啡、 Perl、。NET 和 JGSoft,但不适用于 PHP(PCRE)、 Ruby 1.9 + 或 TextMate (都是 Oniguruma)。JavaScript 的 /y(粘性标志)不像 \G那样灵活,即使 JS 支持后向操作,也不能以这种方式使用。

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

而且,这在 Android 中不起作用,因为 Android 不支持在后视中使用 \G

    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"]

我在给 接受的解决方案的评论中问@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://stackoverflow.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://stackoverflow.com/a/3760193/1237974中的@Jon Skeets 解决方案。对于大型项目中的可维护性,如果不是每个人都具有相同的正则表达式经验,我可能会使用 Jons 解决方案。

另一个暴力解决方案可能是,

    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]);
}

代码只是用子字符串遍历字符串

我宁愿这个简单的解决办法:

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

如果你想把字符串平均地向后分割,例如从右到左,把 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;
}
@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);
}

下面是我的基于 RegEx 和 Java8流的版本。值得一提的是,Matcher.results()方法从 Java9开始就可用了。

包括测试。

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());
}

下面是一个使用 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]

我使用以下 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());
}
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;
}

Java8解决方案(类似于 这个,但更简单一些) :

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;
}
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;
}

下面是 一句话版本,它使用 爪哇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);

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;

}

使用代码点处理所有字符

这里有一个解决方案:

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

若要处理所有 Unicode 字符,请避免使用过时的 char类型。并避免使用基于 char的实用程序。相反,请使用 密码点整数。

调用 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 );

字符串 = [ Theq,uick,眉毛,nfox,跳,s ]

这适用于非拉丁字符。这里我们将 q替换为 带医用口罩的脸

String text = "The😷uickbrownfoxjumps"

字符串 = [ The,uick,眉毛,nfox,跳跃,s ]

最简单的解决办法是:

  /**
* 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());
}