深入解析Java中的数组复制:System.arraycopy、Arrays.copyOf和Arrays.copyOfRange

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 当涉及到在Java中处理数组时,有许多方法可供选择,其中一些包括`System.arraycopy()`、`Arrays.copyOf()`和`Arrays.copyOfRange()`。这些方法允许您在不同的数组之间复制数据,但它们之间有一些细微的差异。在本篇博客文章中,我们将深入探讨这些方法,以便您了解何时使用它们以及如何正确使用它们。

当涉及到在Java中处理数组时,有许多方法可供选择,其中一些包括System.arraycopy()Arrays.copyOf()Arrays.copyOfRange()。这些方法允许您在不同的数组之间复制数据,但它们之间有一些细微的差异。在本篇博客文章中,我们将深入探讨这些方法,以便您了解何时使用它们以及如何正确使用它们。

System.arraycopy()

System.arraycopy方法是Java中的本地方法,其实际实现是由Java虚拟机的底层实现提供的。

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

参数说明:

  • src:源数组
  • srcPos:源数组中的起始位置
  • dest:目标数组
  • destPos:目标数组中的起始位置
  • length:要复制的元素个数

System.arraycopy()方法的性能非常高,因为它是由底层代码实现的,并且能够利用硬件的特性来进行快速的数据复制。它通常比使用循环逐个复制数组元素要快得多。

System.arraycopy() 可以用于向上或向下转型,但在使用时要谨慎,确保数据类型兼容性和运行时类型检查。如果数据类型不匹配,虽然可通过编译,但运行时会跑出运行时异常java.lang.ArrayStoreException。最好的做法是尽量避免不必要的类型转换,以保持代码的清晰性和可维护性。

示例:

public static void main(String[] args) {

    String[] strArray = new String[]{"xj1","xj2","xj3","xj4","xj5"};
    String[] strArrayCopy = new String[5];
    System.arraycopy(strArray,0,strArrayCopy,0,3);

    //向下转型
    TestEntity[] testArray = new TestChildEntity[]{new TestChildEntity("xiuji","xj")} ;
    TestChildEntity[] testChildArrayCopy = new TestChildEntity[2];

    System.arraycopy(testArray,0,testChildArrayCopy,0,1);

    System.out.println(Arrays.toString(strArrayCopy));
    System.out.println(Arrays.toString(testChildArrayCopy));
}

运行结果

[xj1, xj2, xj3, null, null]
[TestChildEntity{nickName='xj'name='xiuji'}, null]

Arrays.copyOf()

语法:

不转换类型

copyOf(T[] original, int newLength)

转换类型

copyOf(U[] original, int newLength, Class<? extends T[]> newType)

参数说明:

  • original:要复制的原始数组。
  • newLength:新数组的长度,它可以比原始数组的长度长或短。
  • newType:新数组的类型,是一个Class对象,通常是一个数组类。它用于确定新数组的类型。

源码:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {

    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);这是一个三元条件运算符,
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
  • @SuppressWarnings("unchecked"):这个注解,用于告诉编译器忽略未经检查的转换警告。因为在这个方法中进行了类型转换,所以使用这个注解来抑制警告。

  • T[] copy = ((Object)newType == (Object)Object[].class): 这个三元条件运算符,它根据newType的类型创建一个新的数组copy。如果newType是Object[].class,则创建一个Object类型的新数组;否则,使用Array.newInstance()方法创建一个新数组,其类型由newType的组件类型确定。

  • System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength)): System.arraycopy()方法用于将原始数组的元素复制到新数组中。它的参数包括原始数组、原始数组的起始位置(0表示从第一个元素开始)、目标数组(即新数组)、目标数组的起始位置(0表示从第一个位置开始复制),以及要复制的元素数量,数量由原始数组长度和newLength中较小的那个确定。

示例:

public static void main(String[] args) {

    String[] strArray = new String[]{"xj1","xj2","xj3","xj4","xj5"};
    String[] strArrayCopy = Arrays.copyOf(strArray,8);

    //向下转型
    TestEntity[] testArray = new TestChildEntity[]{new TestChildEntity("xiuji","xj")} ;
    TestChildEntity[] testChildArrayCopy =Arrays.copyOf(testArray,3,TestChildEntity[].class);

    System.out.println(Arrays.toString(strArrayCopy));
    System.out.println(Arrays.toString(testChildArrayCopy));

}

运行结果:

[xj1, xj2, xj3, xj4, xj5, null, null, null]
[TestChildEntity{nickName='xj'name='xiuji'}, null, null]

Arrays.copyOfRange()

语法:

不转换类型

copyOfRange(U[] original, int from, int toe)

转换类型

copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType)
  • original:这是要复制元素的原始数组。
  • from:这是要复制的范围的起始索引。
  • to:这是要复制的范围的结束索引(不包括在内)。
  • newType:这是新数组的类型,通常是一个数组类。

源码:

public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
    int newLength = to - from;
    if (newLength < 0)
        throw new IllegalArgumentException(from + " > " + to);
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));
    return copy;
}
  • int newLength = to - from;:此行计算新数组的长度,基于指定的from和to索引。

  • if (newLength < 0) throw new IllegalArgumentException(from + " > " + to);:此行检查newLength是否为负数(即from是否大于to)。如果满足此条件,它将抛出IllegalArgumentException,指示from索引大于to索引。

  • @SuppressWarnings("unchecked"):此注解用于抑制未检查的类型转换警告。

  • T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    此行根据指定的newType创建一个新数组copy。它使用反射来创建所需类型的数组。
    如果newType等于Object[].class,则创建一个新的Object数组,其长度为newLength。
    否则,它使用Array.newInstance创建一个新的数组,该数组的组件类型与newType的组件类型相同,长度为newLength。这使您能够创建特定类型的数组。

  • System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
    此行使用System.arraycopy执行实际的数组复制操作。它将元素从original数组的from索引开始复制到copy数组的0索引开始的位置。要复制的元素数量由Math.min(original.length - from, newLength)确定,确保仅复制指定的范围。

示例:

public static void main(String[] args) {

    String[] strArray = new String[]{"xj1","xj2","xj3","xj4","xj5"};
    String[] strArrayCopy = Arrays.copyOfRange(strArray,2,4);

    //向下转型
    TestEntity[] testArray = new TestChildEntity[]{new TestChildEntity("xiuji","xj")} ;
    TestChildEntity[] testChildArrayCopy =Arrays.copyOfRange(testArray,0,1,TestChildEntity[].class);

    System.out.println(Arrays.toString(strArrayCopy));
    System.out.println(Arrays.toString(testChildArrayCopy));

}

运行结果:

[xj3, xj4]
[TestChildEntity{nickName='xj'name='xiuji'}]

注意事项

在使用数组复制时,需要注意以下几点:

  • 如果新数组的长度小于源数组的长度,那么新数组将截取源数组的前几个元素。

  • 如果源数组中的元素是对象引用,那么新数组中的元素将仍然引用相同的对象,这意味着对新数组的修改可能会影响到源数组。

  • 如果源数组包含基本数据类型(如int、char等),新数组将包含这些基本数据类型的默认值,如0或'\0'

总结

在处理数组时,选择合适的复制方法取决于您的具体需求。以下是一些使用这些方法的一些建议:

  • 如果您需要高效的底层复制操作,并且能够手动计算起始位置和元素数量,那么System.arraycopy可能是一个不错的选择。
  • 如果您想要创建一个新数组,其长度与源数组相同,并且将源数组的内容全部复制到新数组中,那么Arrays.copyOf是一个方便的选择。
  • 如果您需要复制源数组的一部分内容到一个新数组中,那么Arrays.copyOfRange是最适合的。

不管您选择哪种方法,都可以确保在处理数组时能够更加灵活、高效和安全地进行操作。希望本文能够帮助您更好地理解和使用这些数组复制方法。

目录
相关文章
|
28天前
|
存储 Java 计算机视觉
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
44 15
|
6天前
|
XML JSON Java
Java中Log级别和解析
日志级别定义了日志信息的重要程度,从低到高依次为:TRACE(详细调试)、DEBUG(开发调试)、INFO(一般信息)、WARN(潜在问题)、ERROR(错误信息)和FATAL(严重错误)。开发人员可根据需要设置不同的日志级别,以控制日志输出量,避免影响性能或干扰问题排查。日志框架如Log4j 2由Logger、Appender和Layout组成,通过配置文件指定日志级别、输出目标和格式。
|
28天前
|
存储 Java C++
Java数组:静态初始化与动态初始化详解
本文介绍了Java中数组的定义、特点及初始化方式。
61 12
|
28天前
|
算法 搜索推荐 Java
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
58 6
|
28天前
|
存储 算法 搜索推荐
【潜意识Java】期末考试可能考的高质量大题及答案解析
Java 期末考试大题整理:设计一个学生信息管理系统,涵盖面向对象编程、集合类、文件操作、异常处理和多线程等知识点。系统功能包括添加、查询、删除、显示所有学生信息、按成绩排序及文件存储。通过本题,考生可以巩固 Java 基础知识并掌握综合应用技能。代码解析详细,适合复习备考。
21 4
|
28天前
|
存储 Java
【潜意识Java】期末考试可能考的选择题(附带答案解析)
本文整理了 Java 期末考试中常见的选择题,涵盖数据类型、控制结构、面向对象编程、集合框架、异常处理、方法、流程控制和字符串等知识点。每道题目附有详细解析,帮助考生巩固基础,加深理解。通过这些练习,考生可以更好地准备考试,掌握 Java 的核心概念和语法。
32 1
|
28天前
|
Java 编译器 程序员
【潜意识Java】期末考试可能考的简答题及答案解析
为了帮助同学们更好地准备 Java 期末考试,本文列举了一些常见的简答题,并附上详细的答案解析。内容包括类与对象的区别、多态的实现、异常处理、接口与抽象类的区别以及垃圾回收机制。通过这些题目,同学们可以深入理解 Java 的核心概念,从而在考试中更加得心应手。每道题都配有代码示例和详细解释,帮助大家巩固知识点。希望这些内容能助力大家顺利通过考试!
19 0
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
33 14
|
5天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
34 13
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。

热门文章

最新文章

推荐镜像

更多