基于spring boot的JsonSerializer 业务内容国际化

news2024/11/24 7:17:57

说起国际化,真的是老生常谈了。后端有各种i18n的依赖组件,springboot本身也支持i18n的设置,前端vue也有i18n的设置,这些常规操作就不提了,大家可以去搜索其他博客,写的都很详细。

本篇博客主要写的是业务内容国际化。举一个最常用最简单的例子,学生选课,课程有"语文","数学","英语"。这个课程也是一张业务表,随着课程的增多数据是逐渐增多的。一个学生要查看自己选择的课程时,如何根据语言进行国际化的反显"数学"还是"mathematics"。

最开始我拿到这个需求的时候,很挠头,怎么办,难得不是把这个需求做出来,这个需求实现得方式很多:

  1. 多建冗余字段,把”数学“和”mathematics“都存到表里,这样有明显得缺点,语言增多时需要一直在表里加字段。
  2. 建一张code、language、value的对应关系,查询数据的时候根据code和language进行value的匹配,这种缺陷也很明显,业务侵入性很强。

我要做的事情是让业务开发人员在无感知的情况下或侵入性很小的情况下把需求实现。提到侵入性小,大家很容易联想到切面编程AOP。我个人认为AOP最好用的地方就是能拿到自定义注解,通过在java类或者java方法上增加注解,在切面获取引入的东西并将我们相要的东西织入。

灵感一来,我们就开干。

一、建表,并存储基础数据

表的作用是能将各种code对应的各种语言的各种value进行匹配,建表比写在配置文件的好处是显而易见的,因为我们做的是业务内容的国际化,而不是定死的几个值得国际化,我们需要根据业务动态得调整内容。这个表的数据可以开一个接口,业务数据发生变化时,可以直接调用这个接口,对表中数据进行更新。

表结构如下:

 LANGUAGE_ID 主键

LANGUAGE_KEY 存在业务表中得业务标识

LANGUAGE 语言标识

LANGUAGE_VALUE 国际化后的值

MODEL 模块名称,主要防止KEY重复,同一个key在不同的业务中代表的含义不同。

以上面选课为例,该表存放的值为

1 course math en mathematics

2 course math zh-CN 数学

二、获取表中数据放入缓存

数据咱们都有了,怎么把数据拿出来用呢,每次查库?肯定不现实,我们应该提前把准备好,放在缓存中,谁想用直接取。缓存有多种方式。我们做jvm和redis两种,让大家做选择,追求效率就用jvm缓存,不求效率就用redis,对本身服务影响小一些。

1、首先定义实体类

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 系统语言ResultDTO
 */
@Getter
@Setter
@ToString
public class SysLanguageConfig {


    private Long languageId;

    private String model;

    private String languageKey;

    private String language;

    private String languageValue;
    private long currentPage;
    private long pageSize;
}

2、获取数据并缓存的配置类

package com.cnhtc.hdf.wf.common.i18n;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.util.*;
import java.util.concurrent.*;

@Configuration  
@EnableFeignClients(clients = {SysLanguageConfigService.class})
@Slf4j
public class LanguageCahceConfigration {
    public static ConcurrentHashMap<String, String> localCacheMap = new ConcurrentHashMap<>(); //本地存储缓存的map

    /**
     * 存储redis 热点数据
     */
    public static ConcurrentHashMap<String, String> redisHotspotCacheMap = new ConcurrentHashMap<>();

    public final static String CACHE_KEY_JOIN_SYMBOL = "_";

    private static Boolean i18n;

    @Value("${i18nPageSize: 5000}")
    private Integer i18nPageSize;

    @Value("${i18nEnableInitDataParallel: false}")
    private Boolean i18nEnableInitDataParallel;

    /**
     * 是否开启redis热点数据缓存,默认不开启
     */
    private static Boolean i18nEnableRedisHotspotCache;

    /**
     * 开启缓存模式
     * local MAP
     * redis
     */
    private static String i18nEnableCacheMode;

    private final static String CACHE_MODE = "local";

    /**
     * 多少个元素拆分一个List
     */
    private final Integer splitListSize = 10;


    /**
     * 批量插入 条数
     */
    private final Integer REDIS_BATCH_SAVE_SIZE = 5000;

    /**
     * 失效时间
     */
    private final long EXPIRE_SECONDS = 3600 * 1000;

    @Autowired
    private SysLanguageConfigService sysLanguageConfigService;


    @Bean
    public SysLanguageConfigServiceFallback sysLanguageConfigServiceFallback() {
        return new SysLanguageConfigServiceFallback();
    }

    public LanguageCahceConfigration() {
        System.out.println("------------------- 加载 LanguageCahceConfigration ----------------------------------");
    }

    @Scheduled(initialDelay = 1000, fixedRateString = "${i18nScheduledFixedRate:3600000}")
    public void setLanguageCacheMap() {
        if (i18n) {
            if (!CACHE_MODE.equals(i18nEnableCacheMode)) {
                return;
            }
            CopyOnWriteArrayList<SysLanguageConfig> allList = new CopyOnWriteArrayList<>();
            StopWatch sw = new StopWatch();
            try {
                sw.start("数据查询");
                if (i18nEnableInitDataParallel) {
                    this.selectDataCompletableFuture(allList);
                } else {
                    this.selectData(allList);
                }
                sw.stop();
            } catch (Exception e) {
                e.printStackTrace();
                allList.clear();
            }
            log.debug("allList size = {}", allList.size());
            sw.start("本地缓存");
            localCacheMap.clear();
            localCacheMap.putAll(this.getCacheDataMap(allList));
            sw.stop();
            log.warn("初始化i18n 缓存耗时 , {}", sw.prettyPrint());
            log.warn("初始化i18n 缓存总耗时 , {}", sw.getTotalTimeSeconds());
        }
    }


    /**
     * 循环查询数据
     *
     * @param allList 数据集合
     */
    private void selectData(CopyOnWriteArrayList<SysLanguageConfig> allList) {
        int page = 1;
        boolean isContinue = false;
        do {
            SysLanguageConfig sysLanguageConfig = new SysLanguageConfig();
            sysLanguageConfig.setCurrentPage(page);
            sysLanguageConfig.setPageSize(i18nPageSize);
            Page<SysLanguageConfig> result = sysLanguageConfigService.getAll(sysLanguageConfig);
            if (result != null && !CollectionUtils.isEmpty(result.getRecords())) {
                allList.addAll(result.getRecords());
                if (result.getPages() > page) {
                    isContinue = true;
                    page = page + 1;
                } else {
                    isContinue = false;
                }
            }
        } while (isContinue);
    }


    /**
     * 异步分页查询数据
     *
     * @param allList 数据集合
     * @throws Exception 异常
     */
    private void selectDataCompletableFuture(CopyOnWriteArrayList<SysLanguageConfig> allList) throws Exception {
        Page<SysLanguageConfig> result = this.getData();
        if (result != null && result.getPages() > 0) {
            allList.addAll(result.getRecords());
            if (result.getPages() > 1) {

                ForkJoinPool pool = new ForkJoinPool();
                List<Integer> pageList = new ArrayList<>();
                for (int i = 2; i <= result.getPages(); i++) {
                    pageList.add(i);
                }
                List<List<Integer>> partition = Lists.partition(pageList, splitListSize);
                for (List<Integer> pages : partition) {
                    List<CompletableFuture<Void>> futureList = new ArrayList<>();
                    for (Integer page : pages) {
                        SysLanguageConfig param = new SysLanguageConfig();
                        param.setCurrentPage(page);
                        param.setPageSize(i18nPageSize);

                        CompletableFuture<Void> future = CompletableFuture.runAsync(() ->
                                allList.addAll(sysLanguageConfigService.getAll(param).getRecords()), pool);
                        futureList.add(future);
                    }
                    CompletableFuture<Void> allSources = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()]));
                    allSources.get();
                }
            }
        }
    }

    /**
     * 获取数据
     * @return Page<SysLanguageConfig>
     */
    private Page<SysLanguageConfig> getData(){
        SysLanguageConfig sysLanguageConfig = new SysLanguageConfig();
        sysLanguageConfig.setCurrentPage(1);
        sysLanguageConfig.setPageSize(i18nPageSize);
        return sysLanguageConfigService.getAll(sysLanguageConfig);
    }

    /**
     * 批量插入并设置 失效时间,但是性能慢
     *
     * @param map 数据
     */
    private void redisPipelineInsert(ConcurrentHashMap<String, String> map) {
        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
        RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
        stringRedisTemplate.executePipelined(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                map.forEach((key, value) -> {
                    connection.set(serializer.serialize(key), serializer.serialize(value), Expiration.seconds(EXPIRE_SECONDS), RedisStringCommands.SetOption.UPSERT);
                });
                return null;
            }
        }, serializer);
    }

    /**
     * 批量插入后 异步设置失效时间
     *
     * @param map 数据
     */
    //@Async
    public void setExpire(ConcurrentHashMap<String, String> map) {
        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
        map.forEach((k, v) -> stringRedisTemplate.expire(k, EXPIRE_SECONDS, TimeUnit.SECONDS));
    }

    /**
     * 刷新redis缓存
     */
    @XxlJob("i18nRefreshRedisCache")
    public void refreshRedisCache() {
        XxlJobHelper.log("回调任务开始");
        if (i18n) {
            if (CACHE_MODE.equals(i18nEnableCacheMode)) {
                log.error("i18n国际化配置本地缓存,请勿用redis刷新缓存");
            }

            CopyOnWriteArrayList<SysLanguageConfig> allList = new CopyOnWriteArrayList<>();
            StopWatch sw = new StopWatch();
            try {
                sw.start("数据查询");
                if (i18nEnableInitDataParallel) {
                    this.selectDataCompletableFuture(allList);
                } else {
                    this.selectData(allList);
                }
                sw.stop();
            } catch (Exception e) {
                e.printStackTrace();
                allList.clear();
            }
            sw.start("redis缓存");
            StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
            if (ObjectUtils.isEmpty(stringRedisTemplate)) {
                throw new BaseException(ErrorEnum.NOTHROWABLE_ERROR, "StringRedisTemplate is null");
            }
            redisHotspotCacheMap.clear();
            ConcurrentHashMap<String, String> cacheDataMap = this.getCacheDataMap(allList);
            List<Map<String, String>> maps = splitMap(cacheDataMap, REDIS_BATCH_SAVE_SIZE);
            // multiSet 批量插入,key值存在会覆盖原值
            maps.forEach(data -> stringRedisTemplate.opsForValue().multiSet(data));
            sw.stop();
            log.warn("初始化i18n redis缓存耗时 , {}", sw.prettyPrint());
            log.warn("初始化i18n redis缓存总耗时 , {}", sw.getTotalTimeSeconds());
        }
        XxlJobHelper.log("回调任务结束");
    }

    /**
     * 刷新local缓存
     */
    //@XxlJob("i18nRefreshLocalCache")
    public ResponseDTO refreshLocalCache() {
        if (CACHE_MODE.equals(i18nEnableCacheMode)) {
            this.setLanguageCacheMap();
            return new ResponseDTO(SysErrEnum.SUCCESS);
        }
        return new ResponseDTO(SysErrEnum.ERROR.code(), "i18n国际化配置Redis缓存,请勿用本地刷新缓存");
    }

    /**
     * 获取缓存数据
     *
     * @param allList
     * @return
     */
    private ConcurrentHashMap<String, String> getCacheDataMap(List<SysLanguageConfig> allList) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        allList.parallelStream().forEach(config -> map.put(config.getModel() + CACHE_KEY_JOIN_SYMBOL + config.getLanguageKey() + CACHE_KEY_JOIN_SYMBOL + config.getLanguage(), config.getLanguageValue()));
        log.warn("map size:{}", map.size());
        return map;
    }

    /**
     * 获取缓存数据值
     *
     * @param key key
     * @return value
     */
    public static String getCacheValueByKey(String key) {
        if (i18n) {
            String value;
            if (CACHE_MODE.equals(i18nEnableCacheMode)) {
                value = localCacheMap.get(key);
                log.debug("多语言转换:本地缓存数量 = {}, key = {}", +localCacheMap.values().size(), key);
            } else {
                if (i18nEnableRedisHotspotCache) {
                    if (redisHotspotCacheMap.containsKey(key)) {
                        value = redisHotspotCacheMap.get(key);
                    } else {
                        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
                        value = stringRedisTemplate.opsForValue().get(key);
                        if (StringUtils.isNotBlank(value)) {
                            // 缓存热点数据
                            redisHotspotCacheMap.put(key, value);
                        }
                    }
                } else {
                    StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
                    value = stringRedisTemplate.opsForValue().get(key);
                }
            }
            return value;
        }
        return null;
    }

    @Value("${i18nEnableCacheMode: local}")
    private void setI18nEnableCacheMode(String i18nEnableCacheMode) {
        LanguageCahceConfigration.i18nEnableCacheMode = i18nEnableCacheMode;
    }

    @Value("${i18nEnableRedisHotspotCache: false}")
    private void setI18nEnableRedisHotspotCache(Boolean i18nEnableRedisHotspotCache) {
        LanguageCahceConfigration.i18nEnableRedisHotspotCache = i18nEnableRedisHotspotCache;
    }

    @Value("${i18n: false}")
    private void setI18n(Boolean i18n) {
        LanguageCahceConfigration.i18n = i18n;
    }

    /**
     * Map拆分 (指定分组大小)
     *
     * @param map       Map
     * @param chunkSize 每个分组的大小 (>=1)
     * @param <K>       Key
     * @param <V>       Value
     * @return 子Map列表
     */
    private <K, V> List<Map<K, V>> splitMap(Map<K, V> map, int chunkSize) {
        if (Objects.isNull(map) || map.isEmpty() || chunkSize < 1) {
            //空map或者分组大小<1,无法拆分
            return Collections.emptyList();
        }

        int mapSize = map.size(); //键值对总数
        int groupSize = mapSize / chunkSize + (mapSize % chunkSize == 0 ? 0 : 1); //计算分组个数
        List<Map<K, V>> list = Lists.newArrayListWithCapacity(groupSize); //子Map列表

        if (chunkSize >= mapSize) { //只能分1组的情况
            list.add(map);
            return list;
        }

        int count = 0; //每个分组的组内计数
        Map<K, V> subMap = Maps.newHashMapWithExpectedSize(chunkSize); //子Map

        for (Map.Entry<K, V> entry : map.entrySet()) {
            if (count < chunkSize) {
                //给每个分组放chunkSize个键值对,最后一个分组可能会装不满
                subMap.put(entry.getKey(), entry.getValue());
                count++; //组内计数+1
            } else {
                //结束上一个分组
                list.add(subMap); //当前分组装满了->加入列表

                //开始下一个分组
                subMap = Maps.newHashMapWithExpectedSize(chunkSize); //新的分组
                subMap.put(entry.getKey(), entry.getValue()); //添加当前键值对
                count = 1; //组内计数重置为1
            }
        }

        list.add(subMap);  //添加最后一个分组
        return list;
    }
}

整段代码其中区分了本地缓存、redis缓存等等,还有就是查刚才数据库表里得数据,因为我们才用了微服务得架构,所以获取数据得部分是通过feign的方式获取的,大家可以替换成自己的方法。另外,开启redis缓存的部分可以取舍,没必要这么完善,保留一种即可。本地缓存的定时任务是springboot的,redis的定时任务是xxl-job的,这些技术栈都可以替换。

其中最重要的一点,redis比本地缓存慢很多,100条数据的国际化反显,速度会差20倍。为什么差怎么多,接下来就到关键内容了。

三、注解定义

注解定义的意义就是在序列化的时候,能通过注解拿到切入点并获取注解的内容


import java.lang.annotation.*;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(
        using = I18nSerializer.class
)
public @interface I18n {

    String model() default "common";
    String language() default "";
    String key() default "";
}

四、基于JsonSerializer的序列化处理,进行国际化转换

大家现在都在用springboot的restController,也就是说,前后端分离之后,前后端的交互就是json,在controller返回的内容其实就是一个实体对象或者集合,那这个实体对象或者集合是怎么转换成json的,就是通过springboot中引入的jackson来实现的,具体实现原理不多说。

我们只需要知道,写一个子类,来继承JsonSerializer和实现ContextualSerializer就能实现序列化的时候进行织入操作。

其中language是通过header从前端传递过来的。

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.stdp.hdf.wf.common.core.constants.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

@Slf4j
public class I18nSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private String model;

    private String language;

    private String key;

    public I18nSerializer(String model, String language, String key) {
        this.model = model;
        this.language = language;
        this.key = key;
    }

    public I18nSerializer() {
    }

    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        String requestLanguage = null;
        String mapkey = s;
        if (StringUtils.isBlank(language)) {
            requestLanguage = getLanguage();
        } else {
            requestLanguage = language;
        }
        if (StringUtils.isNotBlank(requestLanguage)) {
            if (StringUtils.isNotBlank(key)) {
                Object o = jsonGenerator.getCurrentValue();
                mapkey = getPropertyValue(o, key).toString();
            }
            String keyString = model + LanguageCahceConfigration.CACHE_KEY_JOIN_SYMBOL + mapkey + LanguageCahceConfigration.CACHE_KEY_JOIN_SYMBOL + requestLanguage;
            String keyName = LanguageCahceConfigration.getCacheValueByKey(keyString);
            if (StringUtils.isBlank(keyName)) {
                keyName = s;
            }
            jsonGenerator.writeString(keyName);
        } else {
            jsonGenerator.writeString(s);
        }

    }


    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) { // 为空直接跳过
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { // 非 String 类直接跳过
                I18n i18n = beanProperty.getAnnotation(I18n.class);
                if (i18n == null) {
                    i18n = beanProperty.getContextAnnotation(I18n.class);
                }
                if (i18n != null) { // 如果能得到注解,就将注解的 value 传入 I18nSerializer
                    return new I18nSerializer(i18n.model(), i18n.language(), i18n.key());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(beanProperty);
    }

    public String getLanguage() {
        //直接从request中获取language信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }

        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

        return request.getHeader(Constants.LANGUAGE);
    }

    public Object getPropertyValue(Object t, String objProperty) {
        Map<String, String> objMap = null;
        try {
            objMap = BeanUtils.describe(t);
            if (objMap.get(objProperty) != null) {
                return objMap.get(objProperty);
            }
            return "";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

五、使用

还是以选课为例,返回的json信息,CourseName自动就转成了对应的语言。

@Getter
@Setter
@ToString
public Course implements Serializable {
    private String courseCode; //课程编号 

    @I18n(model = "course",key = "courseCode")
    private String courseName; //课程名称

}

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

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

相关文章

5大技巧,实现视频号预约直播人数暴涨!

两个体量相当的视频号&#xff0c;为什么别人的直播间人数过万&#xff0c;而自己的直播间却寥寥无几&#xff1f;这其中有一个非常重要的原因&#xff0c;就是预约直播的工作没有做好。 通常情况下&#xff0c;视频号直播预约人数和最终场观呈现1:10的比例&#xff0c;换言之…

聊聊Redis中的跳跃表

Redis 大家项目中应该都用过&#xff0c;哪怕没有分布式锁、幂等校验的一些逻辑使用场景&#xff0c;缓存数据这个大家肯定都用过吧&#xff1f;最简单的key-value格式&#xff0c;直接存储String类型。 当然&#xff0c;针对越来越复杂的业务场景&#xff0c;后续也可能用到li…

合宙Air724UG Cat.1模块硬件设计指南--数字语音接口

数字语音接口 简介 数字音频接口DAI&#xff0c;即Digital Audio Interfaces&#xff0c;表示在板级或板间传输数字音频信号的方式。相比于模拟接口&#xff0c;数字音频接口抗干扰能力更强&#xff0c;硬件设计简单&#xff0c;DAI在音频电路设计中得到越来越广泛的应用。 特…

【学习日记2023.6.20】之 分布式事务_CAP定理_BASE理论_微服务集成Seata_Seata的四种事务模式_高可用架构模型

文章目录 1. 分布式事务问题1.1 本地事务1.2 分布式事务1.3 演示分布式事务问题 2. 理论基础2.1 CAP定理2.1.1 一致性2.1.2 可用性2.1.3 分区容错2.1.4 矛盾 2.2 BASE理论2.3 解决分布式事务的思路 3. 初识Seata3.1 Seata的架构3.2 部署TC服务3.2.1 下载3.2.2 解压3.2.3 修改配…

数据库系统概述——第七章 数据库设计(知识点复习+练习题)

&#x1f31f;博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;离散数学考前复习&#xff08;知识点题&#xff09; &#x1f353;专栏&#xff1a;概率论期末速成&#xff08;一套卷&#xff09; &#x1f433;专栏&#xff1a;数字电路考前复习 &#x1f99a;专栏&am…

p7付费课程笔记:jvm基础知识、字节码、类加载器

编程语言 演化&#xff1a; 机器语言->编程语言->高级语言&#xff08;java&#xff0c;c,Go,Rust等&#xff09; 面向过程–面向对象-面向函数 java是一种面向对象、静态类型、编译执行&#xff0c;有VM&#xff08;虚拟机&#xff09;/GC和运行时、跨平台的高级语言…

第二章 视觉感知与视觉通道(复习)

大纲 视觉感知 认知 视觉通道 色彩* 可视化致力于外部认知&#xff0c;也就是说&#xff0c;怎样利用大脑以外的资源来增强大脑本身的认知能力。 感知是指客观事物通过人的感觉器官在人脑中形成的直接反映 感觉器官&#xff1a;眼、耳、口、鼻、神经末梢 视觉感知就是客观事物通…

世界史上五个横跨亚欧非三大洲的超强帝国

古代地中海和西亚地区文明出现的很早&#xff0c;经济文化社会都比较先进&#xff0c;其中古埃及早在四千多年前就建立了庞大的帝国&#xff0c;给世人留下了不朽的金字塔&#xff1b;两河流域、希腊半岛也很早就出现了城邦制的国家&#xff0c;也创造了灿烂的文明。同时&#…

架构设计我们要注意什么?

这几天我正在做一个新项目的架构设计&#xff0c;关于动态流程引擎平台的搭建&#xff0c;涉及到了系统架构的设计&#xff0c;里面涉及了方方面面&#xff0c;所以就想着结合自己的实际经验&#xff0c;遇到的问题&#xff0c;以及自己的理解&#xff0c;为大家做一个简单的分…

损失函数:IoU、GIoU、DIoU、CIoU、EIoU、alpha IoU、SIoU、WIoU超详细精讲及Pytorch实现

前言 损失函数是用来评价模型的预测值和真实值不一样的程度&#xff0c;损失函数越小&#xff0c;通常模型的性能越好。不同的模型用的损失函数一般也不一样。 损失函数的使用主要是在模型的训练阶段&#xff0c;如果我们想让预测值无限接近于真实值&#xff0c;就需要将损…

献给蓝初小白系列(二)——Liunx应急响应

1、Linux被入侵的症状​​ ​​https://blog.csdn.net/weixin_52351575/article/details/131221720​​ 2、Linux应急措施 顺序是&#xff1a;隔离主机--->阻断通信--->清除病毒--->可疑用户--->启动项和服务--->文件与后门--->杀毒、重装系统、恢复数据 …

python代码加密方案

为何要对代码加密&#xff1f; python的解释特性是将py编译为独有的二进制编码pyc 文件&#xff0c;然后对pyc中的指令进行解释执行&#xff0c;但是pyc的反编译却非常简单&#xff0c;可直接反编译为源码&#xff0c;当需要将产品发布到外部环境的时候&#xff0c;源码的保护尤…

Guitar Pro是什么软件 Guitar Pro有什么用

相信玩吉他的朋友多多少少都听说过Guitar Pro这款软件&#xff0c;那大家知道Guitar Pro是什么软件&#xff1f;Guitar Pro有什么用呢&#xff1f;今天小编就和大家分享一下关于Guitar Pro这款吉他软件的相关内容。 一、Guitar Pro是什么软件 简单说Guitar Pro是一款吉他谱软…

Vue实现元素沿着坐标数组移动,超出窗口视图时页面跟随元素滚动

一、实现元素沿着坐标数组移动 现在想要实现船沿着下图中的每个河岸移动。 实现思路&#xff1a; 1、将所有河岸的位置以 [{x: 1, y: 2}, {x: 4, y: 4}, …] 的形式保存在数组中。 data() {return {coordinateArr: [{ x: 54, y: 16 }, { x: 15, y: 31 }, { x: 51, y: 69 }…

leetcode77. 组合(回溯算法-java)

组合 leetcode77. 组合题目描述解题思路代码演示 递归专题 leetcode77. 组合 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/combinations 题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个…

量化交易:止盈策略与回测

我们买基金或股票的时候通常用最简单的策略进行决策&#xff1a;低买高卖&#xff0c;跌的多了就加仓拉低持有成本&#xff0c;达到收益率就卖出。 那么如何用代码表示这个策略呢&#xff1f;首先定义交易信号则是&#xff1a;0.5%时买入&#xff0c;目标止盈线是1.5%&#xf…

Java官方笔记12异常

Exception Definition: An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the programs instructions. the checked exception 比如&#xff0c;java.io.FileNotFoundException the error 比如&#xff0c;java.i…

Flink流批一体计算(2):Flink关键特性

目录 流式处理 丰富的状态管理 流处理 自定义时间流处理 有状态流处理 通过状态快照实现的容错 流式处理 在自然环境中&#xff0c;数据的产生原本就是流式的。无论是来自 Web 服务器的事件数据&#xff0c;证券交易所的交易数据&#xff0c;还是来自工厂车间机器上的…

优先级队列建立小根堆来解决前K个高频元素(TOP K问题)

目录 场景一&#xff1a;解决前K个高频元素需要解决如下几个问题&#xff1a; 优先级队列PriorityQueue 堆的定义 题目链接 场景二&#xff1a;亿万级数据取前TOP K / 后TOP K 数据 场景一&#xff1a;解决前K个高频元素需要解决如下几个问题&#xff1a; 1.记录每一个元…

【C++】4.工具:读取ini配置信息

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍读取ini配置信息。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&…