我需要在Java中合并两个String数组。
void f(String[] first, String[] second) {
String[] both = ???
}
最简单的方法是什么?
我需要在Java中合并两个String数组。
void f(String[] first, String[] second) {
String[] both = ???
}
最简单的方法是什么?
我从老牌Apache Commons Lang库中找到了一行解决方案。
代码如下:
String[] both = ArrayUtils.addAll(first, second);
以下是一个简单的方法,可以将两个数组连接起来并返回结果:
public <T> T[] concatenate(T[] a, T[] b) {
int aLen = a.length;
int bLen = b.length;
@SuppressWarnings("unchecked")
T[] c = (T[]) Array.newInstance(a.getClass().getComponentType(), aLen + bLen);
System.arraycopy(a, 0, c, 0, aLen);
System.arraycopy(b, 0, c, aLen, bLen);
return c;
}
请注意,它不能处理原始数据类型,只能处理对象类型。
以下稍微复杂一些的版本适用于对象和原始数组。它使用T而不是T[]作为参数类型来实现这一点。
它还可以通过选择最通用的类型作为结果的组件类型来连接两种不同类型的数组。
public static <T> T concatenate(T a, T b) {
if (!a.getClass().isArray() || !b.getClass().isArray()) {
throw new IllegalArgumentException();
}
Class<?> resCompType;
Class<?> aCompType = a.getClass().getComponentType();
Class<?> bCompType = b.getClass().getComponentType();
if (aCompType.isAssignableFrom(bCompType)) {
resCompType = aCompType;
} else if (bCompType.isAssignableFrom(aCompType)) {
resCompType = bCompType;
} else {
throw new IllegalArgumentException();
}
int aLen = Array.getLength(a);
int bLen = Array.getLength(b);
@SuppressWarnings("unchecked")
T result = (T) Array.newInstance(resCompType, aLen + bLen);
System.arraycopy(a, 0, result, 0, aLen);
System.arraycopy(b, 0, result, aLen, bLen);
return result;
}
以下是一个例子:
Assert.assertArrayEquals(new int[] { 1, 2, 3 }, concatenate(new int[] { 1, 2 }, new int[] { 3 }));
Assert.assertArrayEquals(new Number[] { 1, 2, 3f }, concatenate(new Integer[] { 1, 2 }, new Number[] { 3f }));
在Java 8中使用Stream:
String[] both = Stream.concat(Arrays.stream(a), Arrays.stream(b))
.toArray(String[]::new);
或者像这样,使用flatMap:
String[] both = Stream.of(a, b).flatMap(Stream::of)
.toArray(String[]::new);
为了对泛型类型执行此操作,您需要使用反射:
@SuppressWarnings("unchecked")
T[] both = Stream.concat(Arrays.stream(a), Arrays.stream(b)).toArray(
size -> (T[]) Array.newInstance(a.getClass().getComponentType(), size));
Stream.concat 作为参数时它们的流需要使用.boxed()方法转换成 Stream 类型,而不是使用 IntStream 等类型。 - Will Hardwick-Smitha 和 b 是 int[] 类型,可以使用以下代码:int[] both = IntStream.concat(Arrays.stream(a), Arrays.stream(b)).toArray(); - HolgerSystem.arrayCopy那样快,但也不是特别慢。你可能需要在非常性能敏感的上下文中使用巨大数组进行很多次操作才会对执行时间产生影响。 - Lii可以编写一个完全通用的版本,甚至可以扩展以连接任意数量的数组。这些版本需要Java 6,因为它们使用Arrays.copyOf()。
两个版本都避免创建任何中间List对象,并使用System.arraycopy()来确保复制大型数组的速度尽可能快。
对于两个数组,代码如下:
public static <T> T[] concat(T[] first, T[] second) {
T[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
对于任意数量的数组(≥1),它看起来像这样:
public static <T> T[] concatAll(T[] first, T[]... rest) {
int totalLength = first.length;
for (T[] array : rest) {
totalLength += array.length;
}
T[] result = Arrays.copyOf(first, totalLength);
int offset = first.length;
for (T[] array : rest) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
T替换为byte(并删除<T>)。 - Joachim Sauerconcat(ai, ad),其中ai是Integer[],而ad是Double[],那么这种方法中会出现一个错误(在这种情况下,编译器将类型参数<T>解析为<? extends Number>)。通过Arrays.copyOf创建的数组将具有第一个数组的组件类型,例如在这个例子中是Integer。当函数要复制第二个数组时,将抛出一个ArrayStoreException。解决办法是增加一个额外的Class<T> type参数。 - T-Bull或者使用备受喜爱的Guava:
String[] both = ObjectArrays.concat(first, second, String.class);
此外,还有适用于原始数组的版本:
Booleans.concat(first, second)Bytes.concat(first, second)Chars.concat(first, second)Doubles.concat(first, second)Shorts.concat(first, second)Ints.concat(first, second)Longs.concat(first, second)Floats.concat(first, second)你可以用两行代码将这两个数组连接起来。
String[] both = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, both, first.length, second.length);
这是一个快速高效的解决方案,适用于原始数据类型,因为这两种方法都被重载了。
您应该避免使用涉及ArrayList、streams等的解决方案,因为它们需要为没有任何用处的临时内存分配空间。
对于大数组,您应该避免使用for循环,因为它们效率并不高。内置的方法使用块复制函数,非常快速。
both = array1 + array2 - Carson Holzheimer使用Java API:
String[] f(String[] first, String[] second) {
List<String> both = new ArrayList<String>(first.length + second.length);
Collections.addAll(both, first);
Collections.addAll(both, second);
return both.toArray(new String[both.size()]);
}
both.toArray(new String[0])比both.toArray(new String[both.size()])更快,即使它与我们的直觉相矛盾。这就是为什么在优化时测量实际性能如此重要的原因。或者当无法证明更复杂的变体的优势时,只需使用更简单的结构即可。 - Holgerstatic String[] concat(String[]... arrays) {
int length = 0;
for (String[] array : arrays) {
length += array.length;
}
String[] result = new String[length];
int pos = 0;
for (String[] array : arrays) {
for (String element : array) {
result[pos] = element;
pos++;
}
}
return result;
}
null检查。并且或许将一些变量设置为final。 - Tripp Kineticsnull 检查会隐藏 NPE 而不是显示它们,而在本地变量中使用 final 没有任何好处(至少目前还没有)。 - Maarten Bodewesfor 循环,而不是 for-each。这极不可能被重构为使用列表(你只需编写另一个方法),而且通过索引直接访问数组很可能比 for-each 更慢。挑剔一点:pos 应该在第一个 for 循环内部定义,而不是在外部定义。 - Maarten Bodewes最近我遇到了过多的内存旋转问题。如果a和/或b通常为空,这里是silvertab代码的另一种适应方式(也进行了泛型化):
private static <T> T[] concatOrReturnSame(T[] a, T[] b) {
final int alen = a.length;
final int blen = b.length;
if (alen == 0) {
return b;
}
if (blen == 0) {
return a;
}
final T[] result = (T[]) java.lang.reflect.Array.
newInstance(a.getClass().getComponentType(), alen + blen);
System.arraycopy(a, 0, result, 0, alen);
System.arraycopy(b, 0, result, alen, blen);
return result;
}
编辑:此前的版本中提到,像这样重复使用数组应该有明确的文档记录。正如Maarten在评论中指出的那样,通常最好只是删除if语句,从而避免需要文档记录。但是再说一遍,这些if语句是这种优化的整个重点。我将保留此答案,但要小心!
System.arraycopy只是复制数组的内容呢? - Rosdi KasimFunctional Java库提供了一个数组包装类,它为数组提供了方便的方法,如连接。
import static fj.data.Array.array;
...然后
Array<String> both = array(first).append(array(second));
要获取未包装的数组,请调用:
String[] s = both.array();
array1 + array2连接函数。 - JollyJoker