Java四种拷贝方式实战总结(一文扫清所有拷贝问题)

 更新时间:2025年07月11日 10:43:10   作者:小W求学之旅  
在Java中文件拷贝可以通过多种方式实现,不同方式的性能和适用场景有所差异,文中通过代码介绍的非常详细,这篇文章主要介绍了Java四种拷贝方式的相关资料,需要的朋友可以参考下

前言

作为Java开发者,日常开发中经常会遇到数据拷贝的需求。最近在面试中也有被问到一次大文件拷贝,抽空专门总结一下,从基础概念到实战技巧,配合流程图,让原理和流程一目了然~

一、浅拷贝VS深拷贝

一开始做spring项目时,就被浅拷贝坑过。当时想复制一个对象,结果改了新对象的属性,老对象也跟着变了。后来才知道,这就是浅拷贝的“锅”。

浅拷贝就像拍证件照,照片上的人看着和你一模一样,但本质上还是两张纸。Java里的浅拷贝只会复制对象的基本属性,遇到引用类型(比如自定义类),就直接把地址抄过来。所以你改新对象里的引用属性,老对象也会跟着遭殃。

深拷贝就靠谱多了,它会把对象里里外外都复制一遍,就像克隆人,新对象和老对象完全独立。虽然麻烦点,但胜在安全,适合处理复杂对象。

举个例子,有个Person类包含nameAddress属性:

class Address {
    String city;
}

class Person {
    String name;
    Address address;

    // 浅拷贝
    Person shallowCopy() {
        Person copy = new Person();
        copy.name = this.name;
        copy.address = this.address;
        return copy;
    }

    // 深拷贝
    Person deepCopy() {
        Person copy = new Person();
        copy.name = this.name;
        copy.address = new Address();
        copy.address.city = this.address.city;
        return copy;
    }
}

二、大文件拷贝:别让你的程序“龟速运行”

之前做文件上传功能,用传统IO方式拷贝大文件,结果服务器直接卡住。后来换成NIO,速度直接起飞!

传统IO就像蚂蚁搬家,一次搬一点,频繁读写磁盘;NIO则像卡车运输,直接把数据从一个地方搬到另一个地方,速度快得多。

// 传统IO方式,适合小文件
public static void copyFileByIO(File source, File dest) throws IOException {
    try (InputStream is = new FileInputStream(source);
         OutputStream os = new FileOutputStream(dest)) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
    }
}

// NIO方式,适合大文件
public static void copyFileByNIO(File source, File dest) throws IOException {
    try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
         FileChannel destChannel = new FileOutputStream(dest).getChannel()) {
        destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
    }
}

三、对象拷贝的进阶方案:使用造好的轮子

手动写深拷贝代码太麻烦?别慌,Java提供给我们有不少写好的办法。

1. 序列化与反序列化

就像把对象打包成快递,寄出去再拆开。虽然速度慢点,但能保证完全独立的拷贝。不过要注意,所有相关类都得实现Serializable接口。

public static <T extends Serializable> T deepCopyBySerialization(T obj) {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(obj);
        try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
             ObjectInputStream ois = new ObjectInputStream(bais)) {
            return (T) ois.readObject();
        }
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

2. JSON序列化

用Jackson或Gson把对象转成JSON字符串,再转回来,简单粗暴。不过这种方式要求对象的属性都能被JSON正确解析。

import com.fasterxml.jackson.databind.ObjectMapper;

public static <T> T deepCopyByJSON(T obj, Class<T> clazz) {
    try {
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(obj);
        return mapper.readValue(json, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

四、数组和集合的拷贝

数组的clone()方法看似简单,实则暗藏玄机:基本类型数组用它是深拷贝,对象数组就是浅拷贝。

int[] intArray = {1, 2, 3};
int[] copiedIntArray = intArray.clone(); // 深拷贝

Person[] personArray = new Person[2];
Person[] copiedPersonArray = personArray.clone(); // 浅拷贝

集合类的构造函数也是浅拷贝,比如new ArrayList<>(originalList)。想深拷贝集合,得手动遍历每个元素。

List<Person> originalList = new ArrayList<>();
List<Person> deepCopiedList = originalList.stream()
        .map(Person::deepCopy)
        .collect(Collectors.toList());

五、第三方库:站在巨人的肩膀上

不想自己造轮子?Apache Commons Lang和Dozer能帮你大忙。前者提供了便捷的序列化拷贝工具,后者擅长对象属性映射,用起来超方便。

<!-- Apache Commons Lang依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

// 使用示例
Person copiedPerson = SerializationUtils.clone(originalPerson);

六、总结:看不同场景选择合适的拷贝方式

  • 简单对象用浅拷贝,复杂对象用深拷贝
  • 大文件优先用NIO,小文件用传统IO
  • 能偷懒就偷懒,善用第三方库
  • 多写测试用例,别让拷贝“埋雷”

到此这篇关于Java四种拷贝方式实战文章就介绍到这了,更多相关Java四种拷贝方式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于BindingResult的使用总结及注意事项

    关于BindingResult的使用总结及注意事项

    这篇文章主要介绍了关于BindingResult的使用总结及注意事项,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java多个线程同时执行的方法

    Java多个线程同时执行的方法

    这篇文章主要介绍了Java多线程处理文件详解与代码示例,通过本文的介绍和代码示例,我们了解了如何使用Java多线程来处理文件,使用多线程技术可以显著提高文件处理的效率,特别是对于大量文件的处理任务,需要的朋友可以参考下
    2024-12-12
  • SpringBoot中配置Redis连接池的完整指南

    SpringBoot中配置Redis连接池的完整指南

    这篇文章主要为大家详细介绍了SpringBoot中配置Redis连接池的完整指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-04-04
  • 哲学家就餐问题中的JAVA多线程学习

    哲学家就餐问题中的JAVA多线程学习

    哲学家就餐问题是1965年由Dijkstra提出的一种线程同步的问题,下面我们就看一下JAVA多线程如何做
    2013-11-11
  • Springboot 2.x集成kafka 2.2.0的示例代码

    Springboot 2.x集成kafka 2.2.0的示例代码

    kafka近几年更新非常快,也可以看出kafka在企业中是用的频率越来越高。本文主要为大家介绍了Springboot 2.x集成kafka 2.2.0的示例代码,需要的可以参考一下
    2022-04-04
  • java ExecutorService CompletionService线程池区别与选择

    java ExecutorService CompletionService线程池区别与选择

    这篇文章主要为大家介绍了java ExecutorService CompletionService线程池区别与选择使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • springcloud gateway设置context-path的操作

    springcloud gateway设置context-path的操作

    这篇文章主要介绍了springcloud gateway设置context-path的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java使用迭代器Iterator遍历集合

    Java使用迭代器Iterator遍历集合

    Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。本文就来和大家详细聊聊Java如何使用迭代器Iterator实现遍历集合,感兴趣的可以跟随小编一起学习一下
    2022-12-12
  • SpringBoot中给指定接口加上权限校验的实现

    SpringBoot中给指定接口加上权限校验的实现

    本文介绍了使用SpringSecurity为接口添加权限校验,以防止外部访问并确保安全性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-12-12
  • SpringBoot整合Activiti工作流框架的使用

    SpringBoot整合Activiti工作流框架的使用

    本文主要介绍了SpringBoot整合Activiti工作流框架的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02

最新评论