从零开始 Spring Boot 29:类型转换

news2024/11/26 19:36:49

从零开始 Spring Boot 29:类型转换

spring boot

图源:简书 (jianshu.com)

PropertyEditor

Spring使用PropertyEditor进行String和具体类型之间的转换:

public interface PropertyEditor {
	void setValue(Object value);
	Object getValue();
	String getAsText();
	void setAsText(String text) throws java.lang.IllegalArgumentException;
	// ...
}

这个接口主要有这几个方法:

  • setValue,设置修改后的属性值。
  • getValue,获取属性值。
  • getAsText,获取属性值对应的字符串。
  • setAsText,用字符串设置属性值。

PropertyEditor和之后介绍的ProertyEditorSupport并不属于Spring框架,这都是java标准包的一部分,属于java.beans包。这个包定义了java bean相关的组件,更多信息可以阅读java.beans (Java Platform SE 8 ) — beans(Java Platform SE 8) (oracle.com)。

自定义PropertyEditor时并不需要直接实现PropertyEditor接口,只需要从PropertyEditorSupport继承即可:

public class PropertyEditorSupport implements PropertyEditor {
	// ...
}

下面具体举例如何在Spring Boot中使用PropertyEditor进行类型转换。

假设有这么两个实体类:

public class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog(%s)".formatted(name);
    }
}

@Data
public class Person {

    private String name;
    private int age = 0;

    public Person(String name) {
        this.name = name;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person(name:%s,age:%d)".formatted(name, age);
    }
}

我们想直接在Controller中通过传递字符串形式的参数来获取相应的实体类对象:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @GetMapping("")
    public String hello(@RequestParam Person person, @RequestParam Dog dog){
        System.out.println(person);
        System.out.println(dog);
        return Result.success().toString();
    }
}

比如我们希望能处理这样的请求:[localhost:8080/hello?person=tom:11&dog=jerry](http://localhost:8080/hello?person=tom:11&dog=jerry)

为了能让Spring将字符串转换为实体类,我们需要为实体类创建对应的PropertyEditor

public class PersonEditor extends PropertyEditorSupport {
    @Override
    public String getAsText() {
        Person person = (Person) this.getValue();
        return "%s:%d".formatted(person.getName(), person.getAge());
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (text == null || text.isEmpty()) {
            throw new IllegalArgumentException("字符串不能为空");
        }
        int index = text.indexOf(":");
        if (index <= 0) {
            throw new IllegalArgumentException("缺少:符号");
        }
        if (text.length() <= index + 1) {
            throw new IllegalArgumentException("缺少年龄信息");
        }
        String name = text.substring(0, index);
        String ageText = text.substring(index + 1);
        int age;
        try {
            age = Integer.parseInt(ageText);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("年龄不是整数");
        }
        setValue(new Person(name, age));
    }
}

public class DogEditor extends PropertyEditorSupport {
    @Override
    public String getAsText() {
        Dog dog = (Dog) this.getValue();
        return super.getAsText();
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(new Dog(text));
    }
}

将自定义PropertyEditor命名为xxxEditor是一种习惯。

主要覆盖getAsTextsetAsText方法,并实现字符串与具体类型的转换逻辑即可。

只有实体类和对应的ProertyEditor也是不行的,我们还需要将对应关系注册到Spring中:

@RestController
@RequestMapping("/hello")
public class HelloController {
    // ...
    @InitBinder
    void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Person.class, new PersonEditor());
        binder.registerCustomEditor(Dog.class, new DogEditor());
    }
}

这样做在HelloController中,所有请求处理都可以直接将字符串形式的参数转换为对应的实体类对象。

如果将实体类和对应的PropertyEntity放在同一个包下面,并且PropertyEntity类被命名为xxxEditor,Spring会自动识别检测,不需要手动进行注册。

CustomEditorConfigurer

上面示例中这种通过@InitBinder注解绑定自定义属性编辑器的做法,只针对当前Controller有效,如果要让Spring框架默认生效,需要使用CustomEditorConfigurer,这是一个BeanFactoryPostProcessor,所以可以利用它来改变IoC的行为:

@Configuration
public class AppConfig {
    @Bean
    public CustomEditorConfigurer customEditorConfigurer(){
        CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
        Map<Class<?>, Class<? extends PropertyEditor>> editors = new HashMap<>();
        editors.put(Dog.class, DogEditor.class);
        editors.put(Person.class, PersonEditor.class);
        customEditorConfigurer.setCustomEditors(editors);
        customEditorConfigurer.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return customEditorConfigurer;
    }
}

关于更多BeanFactoryPostProcessor的内容,可以阅读从零开始 Spring Boot 27:IoC - 红茶的个人站点 (icexmoon.cn)。

这样做了之后,就像在从零开始 Spring Boot 28:资源 - 红茶的个人站点 (icexmoon.cn)中展示的那样,即使是通过@Value注解从配置文件中注入对象,也可以被正确解析,比如application.properties文件中有如下配置信息:

my.person=tom:11

在Controller中使用@Value注解直接将配置信息注入为Person对象:

@RestController
@RequestMapping("/home")
public class HomeController {
    @Value("${my.person}")
    private Person person;
    // ...
    @GetMapping("/prop")
    public String prop(){
        System.out.println(this.person);
        return Result.success().toString();
    }
}

需要特别说明的是,即使创建了CustomEditorConfigurer这个bean,通过Web传入的请求参数依然不会被处理,除非像之前那样在@InitBinder方法中绑定了相应的属性编辑器

这点并没有在Spring官方文档中指出。

也就是说下面这样的类型转换尝试会报错:

@RestController
@RequestMapping("/home")
public class HomeController {
	//...
    @GetMapping("")
    public String home(@RequestParam("person") Person person){
        System.out.println(person);
        return Result.success().toString();
    }
	//...
}

相关的错误信息是缺少Converter或类型处理器,这很容易让人迷惑,因为实际上我们通过CustomEditorConfigurer注入了属性编辑器相关的内容。只能是认为这是Spring MVC对通过Web传入信息的特殊处理。

CustomPropertyEditorRegistrar

如果在项目中经常需要用到一组属性编辑器,可以定义一个PropertyEditorRegistrar类:

public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(Person.class, new PersonEditor());
        registry.registerCustomEditor(Dog.class, new DogEditor());
    }
}

定义之后可以更方便地配置和使用属性编辑器:

@Configuration
public class AppConfig {
    @Bean
    public CustomEditorConfigurer customEditorConfigurer(){
        CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
        customEditorConfigurer.setPropertyEditorRegistrars(new PropertyEditorRegistrar[]{customPropertyEditorRegistrar()});
        customEditorConfigurer.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return customEditorConfigurer;
    }

    @Bean
    public CustomPropertyEditorRegistrar customPropertyEditorRegistrar(){
        return new CustomPropertyEditorRegistrar();
    }
}

为了方便在其他地方复用,这里直接将CustomPropertyEditorRegistrar定义为bean。如果不需要这么做,也可以直接new

也可以像之前那样,在@InitBinder方法中使用:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    private CustomPropertyEditorRegistrar customPropertyEditorRegistrar;
	// ...
    @InitBinder
    void initBinder(WebDataBinder binder) {
        customPropertyEditorRegistrar.registerCustomEditors(binder);
    }
}

ConfigurableWebBindingInitializer

之前说了,CustomEditorConfigurer并不能提供对Web传入参数的自定义类型转换,所以要定义一个ConfigurableWebBindingInitializer类型的bean:

@Configuration
public class AppConfig {
	// ...
    @Bean
    public ConfigurableWebBindingInitializer configurableWebBindingInitializer(){
        ConfigurableWebBindingInitializer configurableWebBindingInitializer = new ConfigurableWebBindingInitializer();
        configurableWebBindingInitializer.setPropertyEditorRegistrar(customPropertyEditorRegistrar());
        return configurableWebBindingInitializer;
    }
}

关于ConfigurableWebBindingInitializer可以阅读Web (springdoc.cn)。

现在即使Controller中没有@InitBinder方法,也可以正常将Web传入的字符串参数转换为对应类型:

@RestController
@RequestMapping("/home")
public class HomeController {
	//...
	@GetMapping("")
    public String home(@RequestParam("person") Person person){
        System.out.println(person);
        return Result.success().toString();
    }
	//...
}

这种方式可以看做是设置了全局的WebDataBinder

预设PropertyEditor

Spring提供了一些预设的PropertyEditor,有的是默认开启的,有的则不是。详细内容可以参考核心技术 (springdoc.cn)。

Converter

除了使用PropertyEditor,还可以使用Converter实现类型转换,这里同样用String转换Person的例子:

public class StringToPersonConverter implements Converter<String, Person> {

    @Override
    public Person convert(String source) {
        return PersonEditor.str2Person(source);
    }
}

这里的PersonEditor.str2Person方法是对已有代码的重构,以避免代码重复出现。

ConversionService

Spring使用一个ConversionService来管理Converter,可以利用一个工厂类ConversionServiceFactoryBean来创建ConversionService,并添加自定义转换器:

@Configuration
public class AppConfig {
	// ...
	@Bean
    public ConversionServiceFactoryBean conversionService(){
        ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        HashSet<Object> converters = new HashSet<>();
        converters.add(new StringToPersonConverter());
        conversionServiceFactoryBean.setConverters(converters);
        return conversionServiceFactoryBean;
    }
}

之后就可以在需要转换时注入conversionService这个bean,然后调用convert方法转换:

@RestController
@RequestMapping("/converter")
public class ConverterController {
    @Resource
    private ConversionService conversionService;

    @GetMapping("")
    public String converter(@RequestParam("person") String personText){
        Person person = conversionService.convert(personText, Person.class);
        System.out.println(person);
        return Result.success().toString();
    }
}

和使用PropertyEditor时一样,如果要让系统自动转换Web入参,需要在ConfigurableWebBindingInitializer中添加ConversionService

@Configuration
public class AppConfig {
	// ...
    @Bean
    public ConfigurableWebBindingInitializer configurableWebBindingInitializer(ConversionService conversionService){
        ConfigurableWebBindingInitializer configurableWebBindingInitializer = new ConfigurableWebBindingInitializer();
        configurableWebBindingInitializer.setConversionService(conversionService);
        return configurableWebBindingInitializer;
    }
	// ...
}

现在就可以在Controller中直接转换入参:

@RestController
@RequestMapping("/converter")
public class ConverterController {
	// ...
    @GetMapping("/auto")
    public String converter(@RequestParam("person") Person person){
        System.out.println(person);
        return Result.success().toString();
    }
}

除了ConfigurableWebBindingInitializer以外,还可以通过WebMvcConfigurer将自定义转换器添加到全局设置:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        WebMvcConfigurer.super.addFormatters(registry);
        registry.addConverter(new StringToPersonConverter());
    }
}

这样同样对处理Web入参是有效的。

预定义Converter

查看ConversionServiceFactoryBean的源码就能发现,通过这种方式创建的ConversionService实际上包含很多预定义的Converter

public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
	// ...
    public void afterPropertiesSet() {
        this.conversionService = this.createConversionService();
        ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
    }

    protected GenericConversionService createConversionService() {
        return new DefaultConversionService();
    }
}

public class DefaultConversionService extends GenericConversionService {
	@Nullable
    private static volatile DefaultConversionService sharedInstance;

    public DefaultConversionService() {
        addDefaultConverters(this);
    }
    
    public static void addDefaultConverters(ConverterRegistry converterRegistry) {
        //这里添加的常用的转换器
        addScalarConverters(converterRegistry);
        addCollectionConverters(converterRegistry);
        converterRegistry.addConverter(new ByteBufferConverter((ConversionService)converterRegistry));
        // ...
    }
    
      public static void addCollectionConverters(ConverterRegistry converterRegistry) {
        //这里添加了容器类相关的转换器
        ConversionService conversionService = (ConversionService)converterRegistry;
        converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
		// ...
     }

    private static void addScalarConverters(ConverterRegistry converterRegistry) {
        //这里添加的和数字相关的转换器
        converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());
        converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
        converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());
		// ...
    }
}

所以默认情况下Spring会处理一般的类型转换,比如默认情况下Spring会将枚举类型的字面量转换为对应的枚举值,这是因为有StringToEnumConverterFactory

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
    StringToEnumConverterFactory() {
    }

    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnumConverterFactory.StringToEnum(ConversionUtils.getEnumType(targetType));
    }

    private static class StringToEnum<T extends Enum> implements Converter<String, T> {
        private final Class<T> enumType;

        StringToEnum(Class<T> enumType) {
            this.enumType = enumType;
        }

        @Nullable
        public T convert(String source) {
            return source.isEmpty() ? null : Enum.valueOf(this.enumType, source.trim());
        }
    }
}

因此Spring默认会将枚举和字符串之间的转换都是针对字面量的,如果想要实现其他形式的转换,比如一个定义的整形值和枚举类型之间的转换,可以参考从零开始 Spring Boot 16:枚举 - 红茶的个人站点 (icexmoon.cn)。

Formatter

Formatter是Spring中负责格式化字符串的接口:

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

这个接口由PrinterParser接口扩展而来:

public interface Printer<T> {

    String print(T fieldValue, Locale locale);
}

import java.text.ParseException;

public interface Parser<T> {

    T parse(String clientValue, Locale locale) throws ParseException;
}

下面用一个示例说明。

假如有一个表示姓名的实体类:

@Getter
public class Name {

    private String firstName;
    private String lastName;

    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "Name(firstName:%s, lastName:%s)".formatted(firstName, lastName);
    }
}

创建与之对应的Formatter

public class NameFormatter implements Formatter<Name> {

    @Override
    public Name parse(String text, Locale locale) throws ParseException {
        if (text == null || text.isEmpty()) {
            throw new ParseException("字符串不能为空", 0);
        }
        int spaceIndex = text.indexOf(" ");
        if (spaceIndex <= 0 || spaceIndex == text.length() - 1) {
            throw new ParseException("缺少空格作为分隔符", spaceIndex);
        }
        String firstName = text.substring(0, spaceIndex);
        String lastName = text.substring(spaceIndex + 1);
        if (!"zh".equals(locale.getLanguage())){
            //非中文地区,姓在前名在后
            String temp = firstName;
            firstName = lastName;
            lastName = temp;
        }
        return new Name(firstName, lastName);
    }

    @Override
    public String print(Name object, Locale locale) {
        if ("zh".equals(locale.getLanguage())){
            return "%s %s".formatted(object.getFirstName(), object.getLastName());
        }
        //非中文地区,名在前姓在后
        return "%s %s".formatted(object.getLastName(), object.getFirstName());
    }
}

之后通过Spring MVC的配置将自定义Formatter加入默认配置:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
        registry.addFormatter(new NameFormatter());
    }
}

之后就可以以具体类型而不是String接收参数:

@RestController
@RequestMapping("/name")
public class NameController {
    @GetMapping("")
    public String name(@RequestParam Name name) {
        System.out.println(name);
        return Result.success().toString();
    }
}

可以看到,FormatterPropertyEditor的用法与用途类似,实际上都可以将String与具体类型之间进行转换。区别是后者更单纯,仅仅是类型转换,而前者附加了一种本地化的功能,更强调是在客户端的字符串和服务端的业务对象之间的转换,并且可以根据具体客户端的地区和语言的不同来以不同的方式处理数据或拼接字符串。

这种本地化体现在编程上就是方法中的Local参数,具体可以参考上面的示例。

注解驱动的格式化

Formatter可以搭配注解使用,实际上Spring已经定义了一些用于格式化的注解,比如:

package cn.icexmoon.books2.book.controller;
// ...
@RestController
@RequestMapping("/book/coupon")
public class CouponController {
    // ...
    @PostMapping("/params-add")
    Result addCouponWithParams(@RequestParam Integer addUserId,
                               @RequestParam Double amount,
                               @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
                               @RequestParam LocalDateTime expireTime,
                               @RequestParam Double enoughAmount,
                               @RequestParam CouponType type){
        // ...
    }
}

这里的@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")就是一个这样的注解,用它标记的参数或属性会如果原始数据是String,就会被Spring用时间相关的Formatter进行格式化。

  • 关于@DateTimeFormat注解的更多应用和说明可以阅读从零开始 Spring Boot 24:处理时间 - 红茶的个人站点 (icexmoon.cn)。
  • 类似的注解还有@NumberFormat等。

要想让自定义的Formatter实现类似的功能,就需要定义一个注解,并且实现一个AnnotationFormatterFactory接口:

package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);
}

接口中的泛型参数A是注解的类型,getFieldTypes返回的是所有注解可以处理的类型。

下面以为NameFormatter创建对应的注解处理进行说明。

先创建一个与NameFormatter对应的自定义注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface NameFormat {
}

实现AnnotationFormatterFactory接口:

public class NameFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<NameFormat> {
    private static final Set<Class<?>> FIELD_TYPES = Set.of(Name.class);

    @Override
    public Set<Class<?>> getFieldTypes() {
        return FIELD_TYPES;
    }

    @Override
    public Printer<?> getPrinter(NameFormat annotation, Class<?> fieldType) {
        return new NameFormatter();
    }

    @Override
    public Parser<?> getParser(NameFormat annotation, Class<?> fieldType) {
        return new NameFormatter();
    }
}

最后通过Spring MVC配置设置,使其生效:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
        registry.addFormatterForFieldAnnotation(new NameFormatAnnotationFormatterFactory());
    }
}

现在,即使默认没有启用NameFormatter,也可以直接通过注解来处理Name类型的入参:

@RestController
@RequestMapping("/name")
public class NameController {
    @GetMapping("")
    public String name(@NameFormat @RequestParam Name name) {
        System.out.println(name);
        return Result.success().toString();
    }
}

当然,可以根据需要在注解中添加属性,实现一些更复杂的逻辑,这里不再展示。

日期和时间

默认情况下,Spring处理时间的格式是这样的:

@RestController
@RequestMapping("/time")
public class TimeController {

    public TimeController(FormattingConversionService conversionService) {
        String converted = conversionService.convert(LocalDate.of(2023, 5, 16), String.class);
        System.out.println(converted);
        converted = conversionService.convert(LocalDateTime.of(2023,5,16,20,15), String.class);
        System.out.println(converted);
    }
    // ...
}

可以看到如下输出:

2023/5/16
2023/5/16 下午8:15

这里的FormattingConversionService是一个管理Formatter的类,它也是一个ConversionService。在这里通过注入它来利用Formatter进行类型转换。

如果要变更这种行为,可以在Spring MVC配置中设置:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
    	// ...
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ISO_LOCAL_DATE);
        registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        registrar.registerFormatters(registry);
    }
}

会变成如下的输出:

2023-05-16
2023-05-16 20:15:00
  • 即使不更改,Spring也能正常将2023-05-16这样的内容转换为DateLocalDate类型。但是无法处理2023-05-16 20:15:00这样的时间,无法将其转换为TimeLocalDateTime
  • DateTimeFormatter.ISO_LOCAL_DATE_TIME对应的时间字符串是类似2023-05-16T20:30:01这样的,所以只能通过DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"的方式自定义。

这样就可以正常接收和处理类似2023-05-16 20:15:00这样的时间字符串了:

@RestController
@RequestMapping("/time")
public class TimeController {
	// ...
    @GetMapping("")
    public String time(@RequestParam LocalDate date,
                       @RequestParam LocalDateTime time) {
        // ...
    }
}

请求类似下面这样:

http://localhost:8080/time?date=2018-03-09&time=2023-05-16%2008:15:11

其中的空格被url编码。

本文所有的示例代码可以通过ch29/validator · 魔芋红茶/learn_spring_boot - 码云 - 开源中国 (gitee.com)获取。

就到这里了,谢谢阅读。

参考资料

  • Spring Custom Property Editor | Baeldung
  • SpringMVC多种数据类型转换器 Day30 2018-12-20 - 简书 (jianshu.com)
  • Guide to DateTimeFormatter | Baeldung
  • 核心技术 (springdoc.cn)
  • 从零开始 Spring Boot 24:处理时间 - 红茶的个人站点 (icexmoon.cn)
  • Locale (Java Platform SE 8 ) (oracle.com)
  • Web (spring.io)
  • Properties (The Java™ Tutorials > JavaBeans™ > Writing JavaBeans Components) (oracle.com)
  • java.beans (Java Platform SE 8 ) (oracle.com)
  • PropertyEditor (Java Platform SE 8 ) (oracle.com)

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

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

相关文章

第五章 面向对象-7.hashCode()和toString()

hashCode()和toString() hashCode() hashCoed 的特性&#xff1a; &#xff08;1&#xff09;HashCode的存在主要是用于查找的快捷性&#xff0c;如Hashtable&#xff0c;HashMap等&#xff0c;HashCode经常用于确定对象的存储地址&#xff1b; &#xff08;2&#xff09;如果…

华为OD机试真题 Java 实现【统一限载货物数最小值】【2023Q1 200分】

一、题目描述 火车站附近的货物中转站负责将到站货物运往仓库&#xff0c;小明在中转站负责调度 2K 辆中转车(K辆干货中转车&#xff0c;K 辆湿货中转车)货物由不同供货商从各地发来&#xff0c;各地的货物是依次进站&#xff0c;然后小明按照卸货顺序依次装货到中转车&#x…

智能床垫市场调研分析报告

文章目录 一、简介&#xff08;1&#xff09;电动床&#xff08;2&#xff09;气垫床 二、使用人群三、睡姿四、实用性 一、简介 &#xff08;1&#xff09;电动床 电动床之下又分成了分体、连体和床头分体。分体电动床是指床垫与床底座分开的电动床&#xff1b;连体的则是床垫…

数据结构-外部排序-(多路归并排序、败者树、置换选择排序、最佳归并树)

目录 一、外部归并排序 二、败者树 三、置换选择排序 四、最佳归并树 一、外部归并排序 16个块&#xff0c;先每个块读入内存进行排序在输出回来&#xff0c;进行16次读和16次写 两两归并&#xff0c;第一趟如下 在两两归并 时间分析 外部排序时间开销读写外存时间内存排序时…

C语言基础知识:函数的声明和使用

目录 函数的声明 1.定义顺序 2.函数的声明 3.函数的声明格式 多源文件开发 1.为什么要有多个源文件 2.将sum函数写到其他源文件中 3.在main函数中调用sum函数 4.编译所有的源文件 5.链接所有的目标文件 #include 1.#include的作用 2.#include可以使用绝对路径 3.#…

Linux免交互操作

免交互操作 Here DocumentExpect工具 Here Document Here Document概述 使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp 、cat 或 read 命令。Here Document 是标准输入的一种替代品&#xff0c;可以帮助脚本开发人员不必使用临时文件来构建输入信息…

docker搭建Elasticsearch集群

这里写目录标题 1.拉取es镜像2.配置配置文件3.启动容器4.启动过程中遇到的问题5.查看容器启动情况 1.拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.0版本根据自己需求进行拉取&#xff0c;我这边选择的是7.17.0&#xff0c;不同版本配置可能稍有…

ANR原理篇 - Input超时机制

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、事件分发流程1.1 事件分发流程概览1.2 InputDispatcher 三、ANR触发流程超时重…

ANR原理篇 - service/broadcast/provider超时机制

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、Service超时机制1.1 埋炸弹1.1.1 AS.realStartServiceLocked1.1.2 AS.bumpSer…

三大基础排序算法——我欲修仙(功法篇)

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️我欲修仙】 学习名言&#xff1a;莫等闲、白了少年头&#xff0c;空悲切。——岳飞 系列文章目录 第一章 ❤️ 学习前的必知知识 第二章 ❤️ 二分查找 文章目录 系列文章目录前言&#x1f697;&…

Netty实战(三)

Netty的组件和设计 一、Channel、EventLoop 和 ChannelFuture1.1 Channel 接口1.2 EventLoop 接口1.3 ChannelFuture 接口 二、ChannelHandler 和 ChannelPipeline2.1 ChannelHandler 接口2.2 ChannelPipeline 接口2.3 编码器和解码器2.4 抽象类 SimpleChannelInboundHandler 三…

suricata中DPDK收发包源码分析2

《suricata中DPDK收发包源码分析1》中分析了整体的DPDK收发包框架代码&#xff0c;今天我们继续来深入了解一下一些细节方面的问题。 目录 Q1&#xff1a;收发包线程模式在代码中是怎样确定的&#xff1f; Q2: DPDK库的初始化rte_eal_init在哪里调用的&#xff1f; Q3: 对网…

Linux中LV Status的状态为NOT available

今天下午有现场反馈备份磁盘找不到了&#xff0c;使用lvm方式的。提供了todesk帮忙看下&#xff0c; 首先使用 blkid查看&#xff0c;确实看不到备份磁盘的UUID&#xff0c;使用lvdisplay查看状态&#xff0c;状态不对了 [rootdb1 ~]# lvdisplay --- Logical volume --- …

.Vue3项目初始化

文章目录 1.Vue3项目初始化1.1 创建vue项目1.2 vue 初始化1.3 git 项目管理1.4 配置iconfig.json1.5 element 按需引入1.6 element 主题色的定制1.7 axios的基础配置1.8 router路由的配置 1.Vue3项目初始化 1.1 创建vue项目 npm init vuelatest1.2 vue 初始化 npm install1.…

【2023/05/16】MonteCarlo

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第11天。 Share O Beauty,find theyself in love,not in the flattery of thymirror. 译文&#xff1a; 啊&#xff0c;美啊&#xff0c;在爱中找你自己吧&#xff0c;不要到你镜子的诌谀中去寻找。 M…

[遗传学]转座因子的结构与功能

本篇文章主要带你了解:转座因子的发现和分类;原核生物以及真核生物种的转座子;转座作用的分子机制以及转座因子的遗传学效应和应用. &#x1f9ec;转座因子的发现和分类 &#x1f9ec;转座因子的概念 转座因子(transposable element)是在转座酶&#xff08;transposase&#xf…

Class 03 - R语言的 Vectors(向量) 与 lists(列表)

Class 03 - R语言的 Vector与 列表 list R语言语法脚本文件的创建、保存、和修改名称第一个函数使用帮助功能查看函数详细说明语法问题变量与赋值定义变量名称格式调用变量 R中的数据结构Vectors (向量)创建向量查看向量的性质查看数据类型 typeof()查看数据长度 length()检查…

Elasticsearch 核心技术(十):GEO 地理查询(geo_bounding_box、geo_distance、geo_shape)

❤️ 博客主页&#xff1a;水滴技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; &#x1f338; 订阅专栏&#xff1a;大数据核心技术从入门到精通 文章目录 一、地理数据类型1.1、geo_point 地理点类型1.1.1、创建一个含有 geo_point 字…

opencv_c++学习(八)

一、两张图像的像素比较 比较最大最小 最小&#xff1a; min(lnputArray src1, InputArray src2, outputArray dst)最大&#xff1a; max(lnputArray src1, InputArray src2, outputArray dst)src1 :第一个图像矩阵&#xff0c;可以是任意通道数的矩阵。 src2:第二个图像矩…

电源电压过冲

前言&#xff1a; 前段时间突然想起来以前的一个问题&#xff0c;这个问题相信大家也都遇到过&#xff0c;甚至是解决过&#xff0c;或者没解决&#xff0c;也就不了了之&#xff0c;今天这篇文章&#xff0c;主要来讲下这个问题&#xff0c;看完喜欢的欢迎给我留言或者点赞&a…