Bean转换工具MapStruct看这一篇就够了

news2024/11/18 3:29:55

内容持续更新中…

文章目录

  • 1.背景
  • 2.mapstruct简介
  • 3.mapstruct与其他映射对比
  • 4.mapstruct底层原理解析
  • 5.具体使用和底层实现
    • 5.1 添加maven依赖
    • 5.2 对象转换
      • 1.UserA和UserB字段相同
      • 2.UserA和UserB字段不同
      • 3.多个源类
      • 4.子对象映射
      • 5.数据类型转换
        • 数据类型映射
        • 枚举映射
      • 6.集合映射
        • List映射
        • Set和Map映射
        • 集合映射策略
    • 5.3 依赖注入
    • 5.4 默认值
      • 3.Mapper中的自定义转换

1.背景

在我们日常开发的分层结构的应用程序中,为了各层之间互相解耦,一般都会定义不同的对象用来在不同层之间传递数据,因此,就有了各种 XXXDTO、XXXVO、XXXBO 等基于数据库对象派生出来的对象,当在不同层之间传输数据时,不可避免地经常需要将这些对象进行相互转换。

此时一般处理两种处理方式:

  • ① 直接使用 Setter 和 Getter 方法转换
  • ② 使用一些工具类进行转换(e.g. BeanUtil.copyProperties)。

第一种方式如果对象属性比较多时,需要写很多的 Getter/Setter 代码。第二种方式看起来虽然比第一种方式要简单很多,但是因为其使用了反射,性能不太好,而且在使用中也有很多陷阱。而今天要介绍的主角 MapStruct 在不影响性能的情况下,同时解决了这两种方式存在的缺点。

2.mapstruct简介

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
mapstruct是一种 实体类 映射框架,能够通过Java注解将一个实体类的属性安全地赋值给另一个实体类。它基于约定优于配置方法极大地简化了 Java bean 类型之间映射的实现,有了mapstruct,只需要定义一个映射器接口,声明需要映射的方法,在编译过程中,mapstruct会自动生成该接口的实现类,实现将源对象映射到目标对象的效果。

总的来说,有如下三个特点:

  • 基于注解
  • 在编译期自动生成映射转换代码
  • 类型安全、高性能、无依赖性

源码地址:https://github.com/mapstruct/mapstruct

mapstruct底层原理解析

3.mapstruct与其他映射对比

在第一章我们也提到,实体类映射框架大致有两种:

  • 一种是运行期通过java反射机制动态映射;
  • 另一种是编译期动态生成getter/setter,在运行期直接调用框架编译好的class类实现实体映射。

由于mapstruct映射是在编译期间实现的,因此相比运行期的映射框架有以下几个优点:

  • 1.安全性高。因为是编译期就实现源对象到目标对象的映射, 如果编译器能够通过,运行期就不会报错。
  • 2.速度快。速度快指的是运行期间直接调用实现类的方法,不会在运行期间使用反射进行转化。

此处我们先不讲原理,先看一下MapStruct具体的用法,感受一下其魅力所在,然后再看一下它的底层到底是怎么样实现的

4.mapstruct底层原理解析

mapstruct是基于JSR 269实现的,JSR 269是JDK引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。

JSR 269使用Annotation Processor在编译期间处理注解,Annotation Processor相当于编译器的一种插件,因此又称为插入式注解处理。想要实现JSR 269,主要有以下几个步骤:

  • 1.继承AbstractProcessor类,并且重写process方法,在process方法中实现自己的注解处理逻辑。
  • 2.在META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的Annotation Processor

说到此处就不得不提一下Java程序编译的流程:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
上图中Java源码到class文件的过程其实是一个比较复杂的过程。其中的经过可以用下图描述:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
上图的流程可以概括为下面几个步骤:

  • 1.生成抽象语法树。Java编译器对Java源码进行编译,生成抽象语法树(Abstract Syntax Tree,AST)。
  • 2.调用实现了JSR 269 API的程序。只要程序实现了JSR 269 API,就会在编译期间调用实现的注解处理器。
  • 3.修改抽象语法树。在实现JSR 269 API的程序中,可以修改抽象语法树,插入自己的实现逻辑。
  • 4.生成字节码。修改完抽象语法树后,Java编译器会生成修改后的抽象语法树对应的字节码文件件。

5.具体使用和底层实现

5.1 添加maven依赖

	<properties>
		<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
	</properties>
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct</artifactId>
			<version>${org.mapstruct.version}</version>
		</dependency>
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct-processor</artifactId>
			<version>${org.mapstruct.version}</version>
		</dependency>

5.2 对象转换

我们直接创建两个对象:
UserA,UserB,然后我们将AB进行互转

此时UserA和UserB有两种情景:

1.UserA和UserB字段相同

@Data
public class UserA {
    private Integer id;
    private String name;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
创建对象转换器(Mapper)
需要注意的是,转换器不一定都要使用 Mapper 作为结尾,只是官方示例推荐以 XXXMapper 格式命名转换器名称,这里举例的是最简单的映射情况(字段名称和类型都完全匹配),只需要在转换器类上添加 @Mapper 注解即可,转换器代码如下所示:

@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    UserB toUserB(UserA userA);

}

调用Mapper进行对象的转换

@SpringBootTest
class DemoApplicationTests {
	@Test
	void test1(){
		Integer id = 1;
		String name = "ninesun";

		UserA userA = new UserA();
		userA.setId(id);
		userA.setName(name);

		UserB userB = UserMapper.INSTANCE.toUserB(userA);
		assertEquals(id, userB.getId());
		assertEquals(name, userB.getName());
	}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
可以看到测试通过

编译之后,MapStruct注解处理器插件会识别出DoctorMapper接口并为其生成一个实现类。
,在Mapper上就可以看见其具体的实现
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
可以看到UserMapperImpl类中包含一个toUserB()方法,里面它帮我们实现了字段的映射,最终实现了UserA到UserB的转换

2.UserA和UserB字段不同

如果两者字段名称不同,MapStuct无法做到直接进行映射,需要将字段不一致的地方借助@Mappings进行对应
我们先创建一个对象UserC

@Data
public class UserC {
    private Integer id;
    private String userName;
}

和UserA相比发现name字段其实是无法映射的
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
只需要在Mapper里加入toUserC的方法即可

UserC toUserC(UserA userA);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
进行一下测试

	@Test
	void test2(){
		Integer id = 1;
		String name = "ninesun";

		UserA userA = new UserA();
		userA.setId(id);
		userA.setName(name);

		UserC userC = UserMapper.INSTANCE.toUserC(userA);
		assertEquals(id, userC.getId());
		assertEquals(name, userC.getUserName());
	}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
可以发现UserC的name并没有转换成功,原因就是UserA中的name无法和UserBC中的userName字段进行映射

怎么办呢?

方法也很简单

    @Mappings({
            @Mapping(source = "name", target = "userName"),
    })
    UserC toUserC(UserA userA);

只需要我们手动去实现映射即可,再次允许Test2方法进行测试,可以发现测试可以通过
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
上面我们已经提到了@Mapping属性中的source和target,除此之外,他还有几个基本属性:

  • ignore: 表示忽略映射当前字段
    • true:忽略该字段
    • false:不忽略,默认为false
  • defaultValue 默认值
    @Mapping(source = "UserA.specialty", target = "specialization", defaultValue = "Information Not Available")
  • expressions 可以通过表达式来构造一些简单的转化关系
    虽然设计的时候想兼容很多语言,不过目前只能写Java代码。

上面均是一对一的情况,即一个对象和一个对象之间的互转,多对一的场景我们一起看一下是什么样子的

3.多个源类

我们新增两个对象UserDto、Education

@Data
public class UserDto {
    private Integer id;
    private String name;

    private String degree;
}
@Data
public class Education {
    private String degreeName;
    private String institute;
    private Integer yearOfPassing;
}

我们接下来需要做的便是将UserA和Education中的属性值映射到UserDto中,对应的UserMapper中的内容如下:

    @Mappings({
            @Mapping(source = "userA.name", target = "userName"),
            @Mapping(source = "education.degreeName", target = "degree"),
    })
    UserDto toUserDto(UserA userA, Education education);

接下来继续测试

    @Test
    void test5() {
        Integer id = 1;
        String name = "ninesun";
        String degreeName = "博士";
        UserA userA = new UserA();
        userA.setId(id);
        userA.setName(name);
        Education education = new Education();
        education.setDegreeName(degreeName);
        UserDto userDto = UserMapper.INSTANCE.toUserDto(userA, education);
        System.out.println(JSON.toJSONString(userDto));
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.子对象映射

多数情况下,POJO中不会只包含基本数据类型,其中往往会包含其它类。比如说,一个User类中会有多门课程:

@Data
public class UserA {
    private Integer id;
    private String name;
    private List<Course> courseList;
}
@Data
public class Course {
    private String name;
    private Integer time;
}
@Data
public class UserDto {
    private Integer id;
    private String userName;
    private String degree;
    private List<CourseDto> courseDtoList;
}
@Data
public class CourseDto {
    private String name;
    private Integer time;
}

对应的Mapper如下:

    @Mappings({
            @Mapping(source = "name", target = "userName"),
            @Mapping(source = "courseList", target = "courseDtoList"),
    })
    UserDto toUserDto(UserA userA);

写个单元测试进行测试:

    @Test
    void test6() {
        Integer id = 1;
        String name = "ninesun";
        UserA userA = new UserA();
        userA.setId(id);
        userA.setName(name);
        Course course = new Course();
        course.setName("高等数学");
        course.setTime(12);
        List<Course> courseList = Arrays.asList(course);
        userA.setCourseList(courseList);
        UserDto userDto = UserMapper.INSTANCE.toUserDto(userA);
        System.out.println(JSON.toJSONString(userDto));
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.数据类型转换

数据类型映射

MapStruct支持source和target属性之间的数据类型转换。它还提供了基本类型及其相应的包装类之间的自动转换。
自动类型转换适用于:

  • 基本类型及其对应的包装类之间。比如, int 和 Integer, float 和 Float, long 和 Long,boolean 和 Boolean 等。
  • 任意基本类型与任意包装类之间。如 int 和 long, byte 和 Integer 等。
  • 所有基本类型及包装类与String之间。如 boolean 和 String, Integer 和 String, float 和 String 等。
  • 枚举和String之间。
  • Java大数类型(java.math.BigInteger, java.math.BigDecimal) 和Java基本类型(包括其包装类)与String之间。
  • 其它情况详见MapStruct官方文档

因此,在生成映射器代码的过程中,如果源字段和目标字段之间属于上述任何一种情况,则MapStrcut会自行处理类型转换。

我们修改 UserA ,新增一个birthdate字段:

@Data
public class UserA {
    private Integer id;
    private String name;
    private LocalDate birthdate;
    private List<Course> courseList;
}

UserDto中增加一个String类型的birthdate;

@Data
public class UserDto {
    private Integer id;
    private String userName;
    private String degree;
    private String birthdate;
    private List<CourseDto> courseDtoList;
}

Mapper映射

    @Mappings({
            @Mapping(source = "name", target = "userName"),
            @Mapping(source = "courseList", target = "courseDtoList"),
            @Mapping(source = "birthdate", target = "birthdate", dateFormat = "dd/MMM/yyyy"),
    })
    UserDto toUserDto(UserA userA);

测试:

    @Test
    void test7() {
        Integer id = 1;
        String name = "ninesun";
        UserA userA = new UserA();
        userA.setId(id);
        userA.setName(name);
        userA.setBirthdate(LocalDate.now());
        Course course = new Course();
        course.setName("高等数学");
        course.setTime(12);
        List<Course> courseList = Arrays.asList(course);
        userA.setCourseList(courseList);
        UserDto userDto = UserMapper.INSTANCE.toUserDto(userA);
        System.out.println(JSON.toJSONString(userDto));
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
除此之外,对于数字的转换,也可以使用numberFormat指定显示格式:

   // 数字格式转换示例
   @Mapping(source = "price", target = "price", numberFormat = "$#.00")
枚举映射

枚举映射的工作方式与字段映射相同。MapStruct会对具有相同名称的枚举进行映射,这一点没有问题。但是,对于具有不同名称的枚举项,我们需要使用@ValueMapping注解。同样,这与普通类型的@Mapping注解也相似。

我们先创建两个枚举。

public enum PaymentType {
    CASH,
    CHEQUE,
    CARD_VISA,
    CARD_MASTER,
    CARD_CREDIT
}
public enum PaymentTypeView {
    CASH,
    CHEQUE,
    CARD
}

单元测试:

    @Test
    void test8() {
        PaymentType paymentType1=PaymentType.CASH;
        PaymentType paymentType2=PaymentType.CARD_VISA;
        PaymentTypeView paymentTypeView1=PaymentTypeMapper.INSTANCE.paymentTypeToPaymentTypeView(paymentType1);
        PaymentTypeView paymentTypeView2=PaymentTypeMapper.INSTANCE.paymentTypeToPaymentTypeView(paymentType2);
        System.out.println(paymentTypeView1);
        System.out.println(paymentTypeView2);
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
但是,如果你要将很多值转换为一个更一般的值,这种方式就有些不切实际了。其实我们不必手动分配每一个值,只需要让MapStruct将所有剩余的可用枚举项(在目标枚举中找不到相同名称的枚举项),直接转换为对应的另一个枚举项。

可以通过 MappingConstants实现这一点:

@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CARD")
PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);

还有一种选择是使用ANY UNMAPPED:

@ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "CARD")
PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);

采用这种方式时,MapStruct不会像前面那样先处理默认映射,再将剩余的枚举项映射到target值。而是,直接将所有未通过@ValueMapping注解做显式映射的值都转换为target值。

6.集合映射

使用MapStruct处理集合映射的方式与处理简单类型相同

MapStruct将根据我们的声明自动生成映射代码。 通常,生成的代码会遍历源集合,将每个元素转换为目标类型,并将每个转换后元素添加到目标集合中。

List映射

如果我们的Mapper中饭只有下面这一个方法

List<UserDto> map(List<UserA> userAList);

测试:

    @Test
    void test9() {
        Integer id = 1;
        String name = "ninesun";
        UserA userA = new UserA();
        userA.setId(id);
        userA.setName(name);
        userA.setBirthdate(LocalDate.now());
        Course course = new Course();
        course.setName("高等数学");
        course.setTime(12);
        List<Course> courseList = Arrays.asList(course);
        userA.setCourseList(courseList);
        List<UserA> userAList = Arrays.asList(userA);
        List<UserDto> userDtoList = UserMapper.INSTANCE.map(userAList);
        System.out.println(JSON.toJSONString(userDtoList));
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
可以发现字段名称不一致的均未转换,这个时候我们把实体和实体之间的字段映射加进去

@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    @Mappings({
            @Mapping(source = "name", target = "userName"),
            @Mapping(source = "courseList", target = "courseDtoList"),
            @Mapping(source = "birthdate", target = "birthdate", dateFormat = "dd/MMM/yyyy"),
    })
    UserDto toUserDto(UserA userA);

    List<UserDto> map(List<UserA> userAList);
}

再次运行刚刚的测试用例,可以发现所有的字段映射成功
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Set和Map映射

Set与Map型数据的处理方式与List相似。按照以下方式修改UserMapper:

@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    Set<UserDto> setConvert(Set<UserA> doctor);

    Map<String, UserDto> mapConvert(Map<String, UserA> doctor);
}

同样,我们如果只要这些映射,字段名称不同的仍然无法映射,所以同样需要加入

   @Mappings({
            @Mapping(source = "name", target = "userName"),
            @Mapping(source = "courseList", target = "courseDtoList"),
            @Mapping(source = "birthdate", target = "birthdate", dateFormat = "dd/MMM/yyyy"),
    })
    UserDto toUserDto(UserA userA);

来处理单个对象字段不一致的情况

集合映射策略
@Data
public class UserA {
    private Integer id;
    private String name;
    private LocalDate birthdate;
    private List<Course> courseList;
}
@Data
public class UserDto {
    private Integer id;
    private String userName;
    private String degree;
    private String birthdate;
    private List<CourseDto> courseDtoList;

    public void setCourseDtoList(List<CourseDto> courseDtoList) {
        this.courseDtoList = courseDtoList;
    }

    public void addCourseDto(CourseDto courseDto) {
        if (courseDtoList == null) {
            courseDtoList = new ArrayList<>();
        }
        courseDtoList.add(courseDto);
    }

}

可以发现UserDto中多了一个set方法和add方法

    @Mappings({
            @Mapping(source = "name", target = "userName"),
            @Mapping(source = "courseList", target = "courseDtoList"),
            @Mapping(source = "birthdate", target = "birthdate", dateFormat = "dd/MMM/yyyy"),
    })
    UserDto toUserDto(UserA userA);

编译之后的实现类:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
可以看到,在默认情况下采用的策略是ACCESSOR_ONLY,使用setter方法setCourseDtoList()向UserDto对象中写入列表数据。

相对的,如果使用 ADDER_PREFERRED 作为映射策略:

@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface UserMapper {}

此时,会使用adder方法逐个将转换后的子类型DTO对象加入父类型的集合字段中。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
如果目标DTO中既没有setter方法也没有adder方法,会先通过getter方法获取子类型集合,再调用集合的对应接口添加子类型对象。

5.3 依赖注入

到目前为止,我们一直在通过getMapper()方法访问生成的映射器:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
但是,如果你使用的是Spring,只需要简单修改映射器配置,就可以像常规依赖项一样注入映射器。

@Mapper(componentModel = "spring")
public interface UserMapper {}

在@Mapper注解中添加(componentModel = “spring”),是为了告诉MapStruct,在生成映射器实现类时,我们希望它能支持通过Spring的依赖注入来创建。现在,就不需要在接口中添加 INSTANCE 字段了。

此时,生成的UserMapperImpl中会带有 @Component 注解:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
只要被标记为@Component,Spring就可以把它作为一个bean来处理,你就可以在其它类(如控制器)中通过@Autowire或@Resourece注解来使用它:

@SpringBootTest
class DemoApplicationTests {
    @Autowired
    UserMapper userMapper;
    @Test
    void test1() {
        Integer id = 1;
        String name = "ninesun";

        UserA userA = new UserA();
        userA.setId(id);
        userA.setName(name);

        UserB userB = userMapper.toUserB(userA);
        assertEquals(id, userB.getId());
        assertEquals(name, userB.getName());
    }
}

即使你不使用Spring框架(PS:我估计现在使用Java的同学们应该没有不使用Spring的了吧),MapStruct也支持Java CDI:

@Mapper(componentModel = "cdi")
public interface UserMapper {}

5.4 默认值

@Mapping 注解有两个很实用的标志就是常量 constant 和默认值 defaultValue 。无论source如何取值,都将始终使用常量值; 如果source取值为null,则会使用默认值。

3.Mapper中的自定义转换

有时候,对于某些类型,无法通过代码生成器的形式来进行处理。那么, 就需要自定义的方法来进行转换。这时候,我们可以在接口(同一个接口,后续还有调用别的 Mapper 的方法)中定义默认方法(Java8及之后)。

@Data
public class UserA {
    private Integer id;
    private String name;

    private Date birthdate;
}
@Data
public class UserB {
    private Integer id;
    private String name;
    private Date birthdate;
}

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

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

相关文章

C语言KR圣经笔记 2.1变量名 2.2 数据类型和大小

第2章 类型、操作符和表达式 变量和常量是程序里操作的基本数据对象。声明列出了要使用的变量&#xff0c;并指出它们的类型&#xff0c;还可能赋初始值。而操作符指定了要对它们做什么。表达式把变量和常量结合起来产生新的值。一个对象的类型决定了它的取值范围以及能对它做…

Linux部署Redis哨兵集群 一主两从三哨兵(这里使用Redis6,其它版本类似)

目录 一、哨兵集群架构介绍二、下载安装Redis2.1、选择需要安装的Redis版本2.2、下载并解压Redis2.3、编译安装Redis 三、搭建Redis一主两从集群3.1、准备配置文件3.1.1、准备主节点6379配置文件3.1.2、准备从节点6380配置文件3.1.3、准备从节点6381配置文件 3.2、启动Redis主从…

【Kotlin精简】第6章 反射

1 反射简介 反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff0c;对于任意一个对象&#xff0c;都能够调用它的任意一个方法和属性。 1.1 Kotlin反射 我们对比Kotlin和Java的反射类图。 1.1.1 Kotlin反射常用的数据结…

【反悔贪心】2022ICPC南京 G

Problem - G - Codeforces 题意 思路 首先容易发现&#xff0c;合并操作对平均攻击力有贡献&#xff0c;但是加一个1就没有贡献&#xff0c;因此首先考虑每次遇到0的时候都合并 但是很快发现如果这样的话&#xff0c;遇到-1就不一定有足够的1给你合并&#xff0c;因此在遇到…

Vite创建vue3+ts+pinia+vant项目起步流程

pnpm介绍&安装 本质上他是一个包管理工具&#xff0c;和npm/yarn没有区别&#xff0c;主要优势在于 包安装速度极快磁盘空间利用效率高 安装&#xff1a; npm i pnpm -g使用&#xff1a; npm命令pnpm等效npm installpnpm installnpm i axiospnpm add axiosnpm i webpa…

springboo单机多线程高并发防止重复消费的redis方案

springboo单机多线程高并发防止重复消费的redis方案 仅提供方案与测试。 想法&#xff1a;第一次收到userCode时&#xff0c;检查是否在redis中有&#xff0c;如果有&#xff0c;就表明已经消费了&#xff0c;返回抢单失败&#xff1b;否则&#xff0c;就去消费&#xff0c;顺…

EfficientFormerV2:全新的轻量级视觉Transformer

期刊&#xff1a;2023 IEEE/CVF International Conference on Computer Vision (ICCV) 标题&#xff1a;Rethinking&#xff08;重新审视&#xff09; Vision Transformers&#xff08;ViT&#xff09; for MobileNet Size and Speed&#xff08;MobileNet的规模和速度&#xf…

计算机组成原理-存储器概念

计算机组成原理-存储器 存储系统的基本概念 1.层次结构 可以直接被CPU读取: 高速缓存:cache主存储器: 主存和内存 辅助存储器: 辅存和外存 2.分类 1.按层次结构划分 如上面所示 2.按存储介质 半导体存储器磁表面存储器光存储器 3.按信息可更改性 r/w存储器ROM(只读存储器) 4…

Vue2 + Echarts实现3D地图下钻

一、npm安装组件&#xff1a; "echarts": "5.4.0","echarts-gl": "^2.0.9","element-china-area-data": "^5.0.2", 二、Vue页面 <template><div class"Map3D" id"Map3D" ref"…

Python基础入门例程9-NP9 十六进制数字的大小

目录 描述 输入描述&#xff1a; 输出描述&#xff1a; 示例1 解答&#xff1a; 说明&#xff1a; 描述 计算的世界&#xff0c;除了二进制与十进制&#xff0c;使用最多的就是十六进制了&#xff0c;现在使用input读入一个十六进制的数字&#xff0c;输出它的十进制数字…

30 个常用的 Linux 命令!

作者&#xff1a;JackTian 来源&#xff1a;公众号「杰哥的IT之旅」 ID&#xff1a;Jake_Internet 链接&#xff1a;30 个常用的 Linux 命令&#xff01; 命令 1&#xff1a;last用于显示用户最近登录信息&#xff0c;包括用户名、登录时间、登录来源等信息 单独执行last命令&…

开发直播带货APP:用户体验设计策略

在当今数字化时代&#xff0c;直播带货APP已经成为了电子商务领域的一股重要力量。这种形式的电子商务结合了实时直播和购物&#xff0c;吸引了数百万用户。然而&#xff0c;为了确保直播带货APP的成功&#xff0c;关键在于提供出色的用户体验。本文将探讨开发直播带货APP的用户…

iframe嵌入报表滚动条问题

当在iframe中嵌入报表时&#xff0c;可能会遇到滚动条的问题。下面是一个详细的介绍 1. 了解iframe&#xff1a; - iframe是HTML中的元素&#xff0c;用于在当前页面中嵌入另一个页面。 - 嵌入报表时常使用iframe&#xff0c;以便将报表以独立的方式展示&#xff0c;并与其他页…

MT4教程新手指南:一步步开启你的金融交易之旅!

本文将为您详细介绍如何使用MT4(MetaTrader 4)平台进行金融交易。MT4是全球最受欢迎的在线交易平台之一&#xff0c;它拥有强大的功能&#xff0c;包括图表分析工具、交易执行、订单管理等&#xff0c;可以帮助你更好地理解和参与金融市场。那么&#xff0c;让我们开始吧! **步…

基于正余弦算法的无人机航迹规划-附代码

基于正余弦算法的无人机航迹规划 文章目录 基于正余弦算法的无人机航迹规划1.正余弦搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用正余弦算法来优化无人机航迹规划。 1.正余弦…

31一维信号滤波(限幅滤波、中值滤波、均值滤波、递推平均滤波),MATLAB程序已调通,可直接运行。

一维信号滤波&#xff08;限幅滤波、中值滤波、均值滤波、递推平均滤波&#xff09;&#xff0c;MATLAB程序已调通&#xff0c;可直接运行。 31matlab、中值滤波、信号处理 (xiaohongshu.com)

structs2 重构成SpringBoot架构

# 目录 structs2 重构成SpringBoot架构 1.1 structs2架构&#xff1a; 1.2 springboot 架构 1.3 演化要点&#xff1a; 1.基于前端的展示层不需要修改 2.HttpServlet 将会有SpringBoot annotation 来处理 3.构建前置的Structs url 转发器&#xff0c;适配 4.ActionSupport将由…

9篇论文速览股票预测高分经典方案

作为一直以来的烫门&#xff0c;股票预测因其非线性、高度波动性和复杂性等原因&#xff0c;成为了金融量化领域的一大难题。以往的解决方案主要围绕机器学习展开&#xff0c;如今&#xff0c;基于深度学习的股票预测方法有了许多新的突破。 为了帮助大家更深入地了解股票预测…

防止消息丢失与消息重复——Kafka可靠性分析及优化实践

系列文章目录 上手第一关&#xff0c;手把手教你安装kafka与可视化工具kafka-eagle Kafka是什么&#xff0c;以及如何使用SpringBoot对接Kafka 架构必备能力——kafka的选型对比及应用场景 Kafka存取原理与实现分析&#xff0c;打破面试难关 防止消息丢失与消息重复——Kafka可…

ToDesk等远程软件连接主机无法更改分辨率 - 解决方案

问题 使用ToDesk等远程软件连接自己的Linux或Windows主机时&#xff0c;若主机已连接显示器&#xff0c;则可通过系统设置更改显示分辨率。但如果主机没有连接显示器或显示器的电源关闭&#xff0c;则无法正常调整分辨率。下文介绍解决方案。 解决方案 方案1&#xff1a;连接…