Java 对象拷贝原理剖析及最佳实践

news2025/7/10 2:49:49

1 前言

对象拷贝,是我们在开发过程中,绕不开的过程,既存在于 Po、Dto、Do、Vo 各个表现层数据的转换,也存在于系统交互如序列化、反序列化。

Java 对象拷贝分为深拷贝和浅拷贝,目前常用的属性拷贝工具,包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct 都是浅拷贝。

1.1 深拷贝

深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容称为深拷贝。

深拷贝常见有以下四种实现方式:

  • 构造函数

  • Serializable 序列化

  • 实现 Cloneable 接口

  • JSON 序列化

1.2 浅拷贝

浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝称为浅拷贝。通过实现 Cloneabe 接口并重写 Object 类中的 clone()方法可以实现浅克隆。

2 常用对象拷贝工具原理剖析及性能对比

目前常用的属性拷贝工具,包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct。

  • Apache BeanUtils:BeanUtils 是 Apache commons 组件里面的成员,由 Apache 提供的一套开源 api,用于简化对 javaBean 的操作,能够对基本类型自动转换。

  • Spring BeanUtils:BeanUtils 是 spring 框架下自带的工具,在 org.springframework.beans 包下, spring 项目可以直接使用。

  • Cglib BeanCopier:cglib(Code Generation Library)是一个强大的、高性能、高质量的代码生成类库,BeanCopier 依托于 cglib 的字节码增强能力,动态生成实现类,完成对象的拷贝。

  • mapstruct:mapstruct 是一个 Java 注释处理器,用于生成类型安全的 bean 映射类,在构建时,根据注解生成实现类,完成对象拷贝。

2.1 原理分析

2.1.1 Apache BeanUtils

使用方式:BeanUtils.copyProperties(target, source);BeanUtils.copyProperties 对象拷贝的核心代码如下:

// 1.获取源对象的属性描述PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);PropertyDescriptor[] temp = origDescriptors;int length = origDescriptors.length;String name;Object value;
// 2.循环获取源对象每个属性,设置目标对象属性值for(int i = 0; i < length; ++i) {PropertyDescriptor origDescriptor = temp[i];name = origDescriptor.getName();// 3.校验源对象字段可读切目标对象该字段可写if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {      try {// 4.获取源对象字段值          value = this.getPropertyUtils().getSimpleProperty(orig, name);// 5.拷贝属性          this.copyProperty(dest, name, value);      } catch (NoSuchMethodException var10) {      }   }}// 1.获取源对象的属性描述PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);PropertyDescriptor[] temp = origDescriptors;int length = origDescriptors.length;String name;Object value;// 2.循环获取源对象每个属性,设置目标对象属性值for(int i = 0; i < length; ++i) {PropertyDescriptor origDescriptor = temp[i];name = origDescriptor.getName();// 3.校验源对象字段可读切目标对象该字段可写if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {      try {// 4.获取源对象字段值          value = this.getPropertyUtils().getSimpleProperty(orig, name);// 5.拷贝属性          this.copyProperty(dest, name, value);      } catch (NoSuchMethodException var10) {      }   }}

复制代码

循环遍历源对象的每个属性,对于每个属性,拷贝流程为:

  • 校验来源类的字段是否可读 isReadable

  • 校验目标类的字段是否可写 isWriteable

  • 获取来源类的字段属性值 getSimpleProperty

  • 获取目标类字段的类型 type,并进行类型转换

  • 设置目标类字段的值

由于单字段拷贝时每个阶段都会调用 PropertyUtilsBean.getPropertyDescriptor 获取属性配置,而该方法通过 for 循环获取类的字段属性,严重影响拷贝效率。获取字段属性配置的核心代码如下:

PropertyDescriptor[] descriptors = this.getPropertyDescriptors(bean);if (descriptors != null) {for (int i = 0; i < descriptors.length; ++i) {if (name.equals(descriptors[i].getName())) {return descriptors[i];}}}PropertyDescriptor[] descriptors = this.getPropertyDescriptors(bean);if (descriptors != null) {for (int i = 0; i < descriptors.length; ++i) {if (name.equals(descriptors[i].getName())) {return descriptors[i];}}}

复制代码

2.1.2 Spring BeanUtils

使用方式: BeanUtils.copyProperties(source, target);BeanUtils.copyProperties 核心代码如下:

PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;PropertyDescriptor[] arr$ = targetPds;int len$ = targetPds.length;for(int i$ = 0; i$ < len$; ++i$) {    PropertyDescriptor targetPd = arr$[i$];    Method writeMethod = targetPd.getWriteMethod();    if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {        PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());        if (sourcePd != null) {            Method readMethod = sourcePd.getReadMethod();            if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {                try {                    if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {                        readMethod.setAccessible(true);                    }                    Object value = readMethod.invoke(source);                    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {                        writeMethod.setAccessible(true);                    }                    writeMethod.invoke(target, value);                } catch (Throwable var15) {                    throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);                }            }        }    }}PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;PropertyDescriptor[] arr$ = targetPds;int len$ = targetPds.length;for(int i$ = 0; i$ < len$; ++i$) {    PropertyDescriptor targetPd = arr$[i$];    Method writeMethod = targetPd.getWriteMethod();    if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {        PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());        if (sourcePd != null) {            Method readMethod = sourcePd.getReadMethod();            if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {                try {                    if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {                        readMethod.setAccessible(true);                    }                    Object value = readMethod.invoke(source);                    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {                        writeMethod.setAccessible(true);                    }                    writeMethod.invoke(target, value);                } catch (Throwable var15) {                    throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);                }            }        }    }}

复制代码

拷贝流程简要描述如下:

  • 获取目标类的所有属性描述

  • 循环目标类的属性值做以下操作

  • 获取目标类的写方法

  • 获取来源类的该属性的属性描述(缓存获取)

  • 获取来源类的读方法

  • 读来源属性值

  • 写目标属性值

与 Apache BeanUtils 的属性拷贝相比,Spring 通过 Map 缓存,避免了类的属性描述重复获取加载,通过懒加载,初次拷贝时加载所有属性描述。

2.1.3 Cglib BeanCopier

使用方式:

BeanCopier beanCopier = BeanCopier.create(AirDepartTask.class, AirDepartTaskDto.class, false); beanCopier.copy(airDepartTask, airDepartTaskDto, null);

复制代码

create 调用链如下:

BeanCopier.create-> BeanCopier.Generator.create-> AbstractClassGenerator.create->DefaultGeneratorStrategy.generate-> BeanCopier.Generator.generateClass

BeanCopier 通过 cglib 动态代理操作字节码,生成一个复制类,触发点为 BeanCopier.create

2.1.4 mapstruct

使用方式:

  • 引入 pom 依赖

  • 声明转换接口

mapstruct 基于注解,构建时自动生成实现类,调用链如下:MappingProcessor.process -> MappingProcessor.processMapperElementsMapperCreationProcessor.process:生成实现类 MapperMapperRenderingProcessor:将实现类 mapper,写入文件,生成 impl 文件使用时需要声明转换接口,例如:

@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)public interface AirDepartTaskConvert {    AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert.class);    AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);}

复制代码

生成的实现类如下:

public class AirDepartTaskConvertImpl implements AirDepartTaskConvert {
    @Override    public AirDepartTaskDto convertToDto(AirDepartTask airDepartTask) {        if ( airDepartTask == null ) {            return null;        }
        AirDepartTaskDto airDepartTaskDto = new AirDepartTaskDto();
        airDepartTaskDto.setId( airDepartTask.getId() );        airDepartTaskDto.setTaskId( airDepartTask.getTaskId() );        airDepartTaskDto.setPreTaskId( airDepartTask.getPreTaskId() );        List<String> list = airDepartTask.getTaskBeginNodeCodes();        if ( list != null ) {            airDepartTaskDto.setTaskBeginNodeCodes( new ArrayList<String>( list ) );        }        // 其他属性拷贝        airDepartTaskDto.setYn( airDepartTask.getYn() );
        return airDepartTaskDto;    }}public class AirDepartTaskConvertImpl implements AirDepartTaskConvert {    @Override    public AirDepartTaskDto convertToDto(AirDepartTask airDepartTask) {        if ( airDepartTask == null ) {            return null;        }        AirDepartTaskDto airDepartTaskDto = new AirDepartTaskDto();        airDepartTaskDto.setId( airDepartTask.getId() );        airDepartTaskDto.setTaskId( airDepartTask.getTaskId() );        airDepartTaskDto.setPreTaskId( airDepartTask.getPreTaskId() );        List<String> list = airDepartTask.getTaskBeginNodeCodes();        if ( list != null ) {            airDepartTaskDto.setTaskBeginNodeCodes( new ArrayList<String>( list ) );        }        // 其他属性拷贝        airDepartTaskDto.setYn( airDepartTask.getYn() );        return airDepartTaskDto;    }}

复制代码

2.2 性能对比

以航空业务系统中发货任务 po 到 dto 转换为例,随着拷贝数据量的增大,研究拷贝数据耗时情况

2.3 拷贝选型

经过以上分析,随着数据量的增大,耗时整体呈上升趋势

  • 整体情况下,Apache BeanUtils 的性能最差,日常使用过程中不建议使用

  • 在数据规模不大的情况下,spring、cglib、mapstruct 差异不大,spring 框架下建议使用 spring 的 beanUtils,不需要额外引入依赖包

  • 数据量大的情况下,建议使用 cglib 和 mapstruct

  • 涉及大量数据转换,属性映射,格式转换的,建议使用 mapstruct

3 最佳实践

3.1 BeanCopier

使用时可以使用 map 缓存,减少同一类对象转换时,create 次数

/**     * BeanCopier的缓存,避免频繁创建,高效复用     */    private static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIER_MAP_CACHE = new ConcurrentHashMap<String, BeanCopier>();
    /**     * BeanCopier的copyBean,高性能推荐使用,增加缓存     *     * @param source 源文件的     * @param target 目标文件     */    public static void copyBean(Object source, Object target) {        String key = genKey(source.getClass(), target.getClass());        BeanCopier beanCopier;        if (BEAN_COPIER_MAP_CACHE.containsKey(key)) {            beanCopier = BEAN_COPIER_MAP_CACHE.get(key);        } else {            beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);            BEAN_COPIER_MAP_CACHE.put(key, beanCopier);        }        beanCopier.copy(source, target, null);    }
    /**     * 不同类型对象数据copylist     *     * @param sourceList     * @param targetClass     * @param <T>     * @return     */    public static <T> List<T> copyListProperties(List<?> sourceList, Class<T> targetClass) throws Exception {        if (CollectionUtils.isNotEmpty(sourceList)) {            List<T> list = new ArrayList<T>(sourceList.size());            for (Object source : sourceList) {                T target = copyProperties(source, targetClass);                list.add(target);            }            return list;        }        return Lists.newArrayList();    }
    /**     * 返回不同类型对象数据copy,使用此方法需注意不能覆盖默认的无参构造方法     *     * @param source     * @param targetClass     * @param <T>     * @return     */    public static <T> T copyProperties(Object source, Class<T> targetClass) throws Exception {        T target = targetClass.newInstance();        copyBean(source, target);        return target;    }
    /**     * @param srcClazz 源class     * @param tgtClazz 目标class     * @return string     */    private static String genKey(Class<?> srcClazz, Class<?> tgtClazz) {        return srcClazz.getName() + tgtClazz.getName();    } /**     * BeanCopier的缓存,避免频繁创建,高效复用     */    private static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIER_MAP_CACHE = new ConcurrentHashMap<String, BeanCopier>();    /**     * BeanCopier的copyBean,高性能推荐使用,增加缓存     *     * @param source 源文件的     * @param target 目标文件     */    public static void copyBean(Object source, Object target) {        String key = genKey(source.getClass(), target.getClass());        BeanCopier beanCopier;        if (BEAN_COPIER_MAP_CACHE.containsKey(key)) {            beanCopier = BEAN_COPIER_MAP_CACHE.get(key);        } else {            beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);            BEAN_COPIER_MAP_CACHE.put(key, beanCopier);        }        beanCopier.copy(source, target, null);    }    /**     * 不同类型对象数据copylist     *     * @param sourceList     * @param targetClass     * @param <T>     * @return     */    public static <T> List<T> copyListProperties(List<?> sourceList, Class<T> targetClass) throws Exception {        if (CollectionUtils.isNotEmpty(sourceList)) {            List<T> list = new ArrayList<T>(sourceList.size());            for (Object source : sourceList) {                T target = copyProperties(source, targetClass);                list.add(target);            }            return list;        }        return Lists.newArrayList();    }    /**     * 返回不同类型对象数据copy,使用此方法需注意不能覆盖默认的无参构造方法     *     * @param source     * @param targetClass     * @param <T>     * @return     */    public static <T> T copyProperties(Object source, Class<T> targetClass) throws Exception {        T target = targetClass.newInstance();        copyBean(source, target);        return target;    }    /**     * @param srcClazz 源class     * @param tgtClazz 目标class     * @return string     */    private static String genKey(Class<?> srcClazz, Class<?> tgtClazz) {        return srcClazz.getName() + tgtClazz.getName();    }

复制代码

3.2 mapstruct

mapstruct 支持多种形式对象的映射,主要有下面几种

  • 基本映射

  • 映射表达式

  • 多个对象映射到一个对象

  • 映射集合

  • 映射 map

  • 映射枚举

  • 嵌套映射

@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)public interface AirDepartTaskConvert {    AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert.class);
    // a.基本映射    @Mapping(target = "createTime", source = "updateTime")    // b.映射表达式    @Mapping(target = "updateTimeStr", expression = "java(new SimpleDateFormat( \"yyyy-MM-dd\" ).format(airDepartTask.getCreateTime()))")    AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);}
@Mapperpublic interface AddressMapper {    AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
 // c.多个对象映射到一个对象    @Mapping(source = "person.description", target = "description")    @Mapping(source = "address.houseNo", target = "houseNumber")    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);}
@Mapperpublic interface CarMapper {  // d.映射集合    Set<String> integerSetToStringSet(Set<Integer> integers);
    List<CarDto> carsToCarDtos(List<Car> cars);
    CarDto carToCarDto(Car car);  // e.映射map  @MapMapping(valueDateFormat = "dd.MM.yyyy")  Map<String,String> longDateMapToStringStringMap(Map<Long, Date> source);
 // f.映射枚举    @ValueMappings({        @ValueMapping(source = "EXTRA", target = "SPECIAL"),        @ValueMapping(source = "STANDARD", target = "DEFAULT"),        @ValueMapping(source = "NORMAL", target = "DEFAULT")    })    ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);    // g.嵌套映射    @Mapping(target = "fish.kind", source = "fish.type")    @Mapping(target = "fish.name", ignore = true)    @Mapping(target = "ornament", source = "interior.ornament")    @Mapping(target = "material.materialType", source = "material")    @Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")    FishTankDto map( FishTank source );}@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)public interface AirDepartTaskConvert {    AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert.class);    // a.基本映射    @Mapping(target = "createTime", source = "updateTime")    // b.映射表达式    @Mapping(target = "updateTimeStr", expression = "java(new SimpleDateFormat( \"yyyy-MM-dd\" ).format(airDepartTask.getCreateTime()))")    AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);}@Mapperpublic interface AddressMapper {    AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class); // c.多个对象映射到一个对象    @Mapping(source = "person.description", target = "description")    @Mapping(source = "address.houseNo", target = "houseNumber")    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);}@Mapperpublic interface CarMapper {  // d.映射集合    Set<String> integerSetToStringSet(Set<Integer> integers);    List<CarDto> carsToCarDtos(List<Car> cars);    CarDto carToCarDto(Car car);  // e.映射map  @MapMapping(valueDateFormat = "dd.MM.yyyy")  Map<String,String> longDateMapToStringStringMap(Map<Long, Date> source); // f.映射枚举    @ValueMappings({        @ValueMapping(source = "EXTRA", target = "SPECIAL"),        @ValueMapping(source = "STANDARD", target = "DEFAULT"),        @ValueMapping(source = "NORMAL", target = "DEFAULT")    })    ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);    // g.嵌套映射    @Mapping(target = "fish.kind", source = "fish.type")    @Mapping(target = "fish.name", ignore = true)    @Mapping(target = "ornament", source = "interior.ornament")    @Mapping(target = "material.materialType", source = "material")    @Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")    FishTankDto map( FishTank source );}

复制代码

4 总结

以上就是我在使用对象拷贝过程中的一点浅谈。在日常系统开发过程中,要深究底层逻辑,哪怕发现一小点的改变能够使我们的系统更加稳定、顺畅,都是值得我们去改进的。

最后,希望随着我们的加入,系统会更加稳定、顺畅,我们会变得越来越优秀。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/36863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【外卖项目实战开发二】

文章目录1、完善登录功能问题分析代码实现2、新增员工需求分析数据模型代码开发3、员工信息分页查询需求分析代码开发4、启用/禁用员工账号需求分析代码开发代码修复5、编辑员工信息需求分析代码开发1、完善登录功能 问题分析 前面我们已经完成了后台系统的员工登录功能开发&…

zabbix集群搭建分布式监控的操作步骤

作用&#xff1a; 分担server的集中式压力解决多机房之间的网络延迟问题环境准备&#xff1a; 服务器1&#xff1a;zabbix-server 服务器2&#xff1a;zabbix-proxy 服务器3&#xff1a;zabbix-agent 关系&#xff1a;zabbix-agent发送数据到代理&#xff0c;代理汇总数据发送…

Linux多核运行机制(SMP)

一、Linux内核兼容多处理器要求 有多个 CPU 处理器 的 系统中 , Linux 内核需要处理的问题 : 1、公平共享 : CPU 的负载 , 需要公平地共享 , 不能出现某个CPU空闲 , 造成资源浪费。 2、可设置进程 与 CPU 亲和性 : 可以为 某些类型的 进程 与 指定的 处理器设置亲和性 , 可以针…

QT:debug,打不开头文件以及qDebug和Q_CLASSINFO的使用

这个是因为链接器在给定路径上搜索不到对应的头文件&#xff0c;而大多数的Qt相关的头文件都集中在一个include文件夹里&#xff1a; 我电脑上的路径是&#xff1a;C:\Qt\Qt5.9.7\5.9.7\msvc2017_64\include 然后我们在项目设置里&#xff1a; 注意&#xff0c;这边要加上\*&…

【Java】Assert.assertEquals断言

Assert.assertEquals 1.概述 在开发中&#xff0c;我们需要测试时候&#xff0c;不可能把全部程序运行一次&#xff0c;在此我们就需要通过编写单元测试来对程序进行测试了。在 Assert 类里面有大量的静态方法&#xff0c;本篇的主角就是 Assert.assertEquals 这个静态方法。该…

day12_类中成员之方法

成员变量是用来存储对象的数据信息的&#xff0c;那么如何表示对象的行为功能呢&#xff1f;就要通过方法来实现 方法 概念&#xff1a; 方法也叫函数&#xff0c;是一个独立功能的定义&#xff0c;是一个类中最基本的功能单元。把一个功能封装为方法的目的是&#xff0c;可…

【知识网络分析】 一模网络(one node)

一模网络(one node) 1 本地文献读取并构建一模网络数据集2 网络数据集精简3 网络数据集中节点信息大小写转化4 获取一模网络中可使用的mode标签5 网络数据集清洗(以武汉大学信息管理学院为例)5.1 创建映射5.2 求解节点中count属性数值5.3 处理网络数据中的连线信息5.4 处理…

中文版:Spread .NET 16.0 -Winform-WPF-ASP.NET

Spread .NET 是一个功能、布局与 Excel 高度类似的 .NET表格控件&#xff0c;可全面满足 WinForm、ASP.NET、XAML 和 WinRT 等平台下表格数据处理、数据可视化开发需求。Spread .NET 支持 462 种 Excel 公式&#xff0c;提供可嵌入系统的类Excel设计器和全面开放的 API&#xf…

使用Python进行交易策略和投资组合分析

我们将在本文中衡量交易策略的表现。并将开发一个简单的动量交易策略&#xff0c;它将使用四种资产类别:债券、股票和房地产。这些资产类别的相关性很低&#xff0c;这使得它们成为了极佳的风险平衡选择。 动量交易策略 这个策略是基于动量的的&#xff0c;因为交易者和投资者…

美食杰项目 -- 编辑个人资料(六)

目录前言&#xff1a;具体实现思路&#xff1a;步骤&#xff1a;1. 展示美食杰编辑个人资料效果2. 引入element-ui3. 代码总结&#xff1a;前言&#xff1a; 本文给大家讲解&#xff0c;美食杰项目中 实现编辑个人资料页的效果&#xff0c;和具体代码。 具体实现思路&#xff…

【Java面经】阿里三面被挂、幸获内推,历经5轮终于拿到口碑offer

每一个互联网人心中都有一个大厂梦&#xff0c;百度、阿里巴巴、腾讯是很多互联网人梦寐以求的地方&#xff0c;而我也不例外。但是&#xff0c;BAT等一线互联网大厂并不是想进就能够进的&#xff0c;它对人才的技术能力和学历都是有一定要求的&#xff0c;所以除了学历以外&am…

面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了

由于现在大多计算机都是多核CPU&#xff0c;多线程往往会比单线程更快&#xff0c;更能够提高并发&#xff0c;但提高并发并不意味着启动更多的线程来执行。更多的线程意味着线程创建销毁开销加大、上下文非常频繁&#xff0c;你的程序反而不能支持更高的TPS。 时间片 多任务…

Java项目:JSP员工出差请假考勤管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目为后台管理系统&#xff1b; 管理员角色包含以下功能&#xff1a; 登录,首页,考勤记录增删改查,假期申请记录增删改查,出差申请记录增删…

maven学习:maven 的入门

2.maven 的入门 到目前为止&#xff0c;我们已经大概了解并安装好了Maven&#xff0c;现在&#xff0c;我们开始创建一个最简单的Hello World项目。 2.1 在Idea创建maven项目 创建一个Maven项目也十分简单&#xff0c;选择”Create New Project" 选择”maven”选项,创…

留学推荐信如何写好个人优点和缺点?

留学推荐信是出国申请文书的重要文件之一&#xff0c;是一个从第三方&#xff08;通常是以前的导师&#xff09;从学术权威的角度对申请者的客观评价。评价的内容包括学术能力&#xff08;Academic competence&#xff09;、性格特点&#xff08;Personal characteristics&…

如何使用 MySQL 做全文检索这件事

​前言 这有朋友聊到他们的系统中要接入全文检索&#xff0c;这让我想起了很久以前为一个很古老的项目添加搜索功能的事儿。 一提到全文检索&#xff0c;我们首先就会想到搜索引擎。也就是用一个词、一段文本搜索出匹配的内容。一般这种技术都有对应的实现方式&#xff0c;ES&…

从01背包说起(上)

目录 引入 1.什么是动态规划? 2.什么是背包问题&#xff1f; 3.什么是01背包&#xff1f; 模板题 1.题面 2.思路 Ⅰ为何不可用贪心 Ⅱ状态转移方程 3.代码 下期预告 引入 1.什么是动态规划? 动态规划&#xff08;英语&#xff1a;Dynamic programming&#xff0…

MQTT,JSON,VSCODE(C语言编程环境)心得

VSCODE&#xff08;C语言编程环境&#xff09;心得 心得基于linux虚拟机和SSH方式&#xff0c;编辑基于VSCODE&#xff0c;编译基于GCC或G&#xff0c;调试基于GDB的插件&#xff0c;代码管理基于git。 安装GIT&#xff1a;sudo apt-get install git 配置GIT&#xff1a; git…

Mysql时间类型

多个timestamp 默认只对第一个timestamp自动更新时间

element-ui在项目当中的引入以及按需引入使用

目录 1.项目当中引入element-ui A.先使用npm安装 B.在main.js当中引入文件 C.在App.vue当中可以引用button相关的UI组件 2.element-ui配合脚手架按需引入 A.首先安装按需引入的插件 B.修改 .babelrc C.按需引入的好处 1.项目当中引入element-ui A.先使用npm安装 npm i …