Redisson同时使用jackson、fastjson、kryo、protostuff序列化(含效率对比)

news2024/9/17 8:33:12

原创不易,转载请注明出处:

https://blog.csdn.net/q258523454/article/details/129988906


前言

我们项目中会用到各种序列化工具,到底哪一种是最适合我们的? 需要从序列化、反序列化、存储大小各个方法面来衡量,我之前有实测过jackson、fastjson、kryo、protostuff序列化的数据,大概情况如下:

CPU:  i7 2.80GHz
内存: 16GB
系统: 64位

备注:每次插入1个 StudentObject, 每个 StudentObject中含有 100000 个 Student 对象
600次,共6000万个对象,实测数据.

方式序列化反序列化存储大小
fastjson70 ms43 ms4.3 M
jackson68 ms135 ms7.6 M
kryo19 ms20 ms1.7 M
protoStuff14 ms30 ms2.1 M

首选: kryo,其次 protoStuff 

代码实现

下面的代码将会实现,一个项目中,Redisson同时使用多个序列化工具,让Redisson用不同的序列化来作客户端。文末也有总结。

maven包

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- ——————————————————mybatis 数据库 BEGIN —————————————————————————— -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.13</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>mybatis</artifactId>
                    <groupId>org.mybatis</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.46</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.17.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.1.3</version>
        </dependency>

        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>kryo</artifactId>
            <version>5.4.0</version>
        </dependency>
    </dependencies>

Redisson抽象类

为了让各个序列化工具复用代码,我们先定义 RedissonClientInterface

public interface RedissonClientInterface {

    RedissonClient getRedissonClient();

}

将公共能力抽象到 AbstractRedissonService

public abstract class AbstractRedissonService implements RedissonClientInterface {

    /**
     * 默认保存时间
     */
    private static final long DEFAULT_EXPIRE_TIME_SECONDS = 3600L;


    public RLock getLock(String key) {
        return getRedissonClient().getLock(key);
    }

    /**
     * 设置key-value
     */
    public void set(String key, Object value, long seconds) {
        if (Objects.isNull(value)) {
            return;
        }
        if (seconds <= 0) {
            seconds = DEFAULT_EXPIRE_TIME_SECONDS;
        }
        getRedissonClient().getBucket(key).set(value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 批量设置key-value
     */
    public <T> void setBatch(Map<String, T> map, long seconds) {
        if (seconds <= 0) {
            seconds = DEFAULT_EXPIRE_TIME_SECONDS;
        }
        RBatch batch = getRedissonClient().createBatch();
        long finalSeconds = seconds;
        map.forEach((k, v) -> {
            batch.getBucket(k).setAsync(v, finalSeconds, TimeUnit.SECONDS);
        });
        batch.execute();
    }


    /**
     * 获取value
     * 没有则返回 null
     */
    public Object get(String key) {
        return getRedissonClient().getBucket(key).get();
    }

    /**
     * 批量获取value
     */
    public List<?> getBatch(List<String> keys) {
        RBatch batch = getRedissonClient().createBatch();
        keys.forEach(key -> batch.getBucket(key).getAsync());
        BatchResult<?> execute = batch.execute();
        return execute.getResponses();
    }


    /**
     * 设置key-value,同时返回旧值
     * 旧值不存在则返回 null
     */
    public Object getAndSet(String key, Object value, long seconds) {
        if (seconds <= 0) {
            seconds = DEFAULT_EXPIRE_TIME_SECONDS;
        }
        return getRedissonClient().getBucket(key).getAndSet(value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 获取所有的指定前缀 keys
     * 例如: "test:*"
     */
    public Set<String> getKeys(String prefix) {
        Iterable<String> keysByPattern = getRedissonClient().getKeys().getKeysByPattern(prefix);
        Set<String> keys = new HashSet<>();
        for (String key : keysByPattern) {
            keys.add(key);
        }
        return keys;
    }

    /**
     * 删除key
     */
    public boolean removeKey(String key) {
        return getRedissonClient().getBucket(key).delete();
    }

    /**
     * 删除key
     */
    public void removeKeyAsync(String key) {
        getRedissonClient().getBucket(key).deleteAsync();

    }

    /**
     * 批量删除
     */
    public void removeKeyBatch(Collection<String> keys) {
        RBatch batch = getRedissonClient().createBatch();
        keys.forEach(key -> batch.getBucket(key).deleteAsync());
        batch.execute();
    }
}

定义FastJson序列化编码

继承实现Redisson的BaseCodec,自定义fastjson编码器


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;

import org.redisson.client.codec.BaseCodec;
import org.redisson.client.handler.State;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;

import java.io.IOException;

public class FastJsonCodec extends BaseCodec {

    private static final Schema SCHEMA = RuntimeSchema.getSchema(ProtoStuffDataWrapper.class);

    @Override
    public Decoder<Object> getValueDecoder() {
        return decoder;
    }

    @Override
    public Encoder getValueEncoder() {
        return encoder;
    }

    private final Encoder encoder = new Encoder() {
        @Override
        public ByteBuf encode(Object in) throws IOException {
            ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
            try {
                ByteBufOutputStream os = new ByteBufOutputStream(out);
                JSON.writeJSONString(os, in, SerializerFeature.WriteClassName);
                return os.buffer();
            } catch (IOException e) {
                // ByteBuf 只有在出现异常的时候才需要主动释放,因为否则重复释放会报错: IllegalReferenceCountException: refCnt: 0
                out.release();
                throw e;
            } catch (Exception e) {
                out.release();
                throw new IOException(e);
            }
        }
    };

    private final Decoder<Object> decoder = new Decoder<Object>() {
        public Object decode(ByteBuf buf, State state) throws IOException {
            // 添加待序列化对象的扫描包,如果不添加扫描包,无法实现序列化和反序列化
            // 另外待序列化的对象必须有一个默认的构造器,否则也无法实现序列化和反序列化。
            ParserConfig.getGlobalInstance().addAccept("redisson.entity");
            return JSON.parseObject(new ByteBufInputStream(buf), Object.class);
        }
    };
}

定义ProtoStuff序列化编码

注意,我们要先定义Protostuff 包装类,Protostuff 是基于POJO进行序列化和反序列化操作, 有时候对非定义POJO要用这个类辅助.

**
 * Protostuff 包装类
 * Protostuff 是基于POJO进行序列化和反序列化操作, 有时候对非定义POJO要用这个类辅助.
 * 作用:
 * 1.主要用于对 Map、List、String、Enum 等进行序列化/反序列化
 * 2.对未知的 Object.class 可以用这个通用包装类
 */
class ProtoStuffDataWrapper<T> {
    private T data;

    public ProtoStuffDataWrapper() {
    }

    public ProtoStuffDataWrapper(T data) {
        super();
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

实现Protostuff序列化


import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import lombok.extern.slf4j.Slf4j;

import org.redisson.client.codec.BaseCodec;
import org.redisson.client.handler.State;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;

import java.io.IOException;

@Slf4j
public class ProtoStuffCodec extends BaseCodec {

    /**
     * 继承 Redisson Codec 实现后, 因为没有Object.Class信息,因此必须用一个封装类来定义 Schema, 否则无法 prostuff 序列化
     */
    private static final Schema SCHEMA = RuntimeSchema.getSchema(ProtoStuffDataWrapper.class);

    @Override
    public Decoder<Object> getValueDecoder() {
        return decoder;
    }

    @Override
    public Encoder getValueEncoder() {
        return encoder;
    }


    @SuppressWarnings({"unchecked", "rawtypes"})
    private final Encoder encoder = new Encoder() {
        @Override
        public ByteBuf encode(Object in) throws IOException {
            if (null == in) {
                throw new NullPointerException();
            }
            LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
            ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
            ByteBufOutputStream os = new ByteBufOutputStream(out);
            try {
                ProtostuffIOUtil.writeTo(os, new ProtoStuffDataWrapper(in), SCHEMA, buffer);
                return os.buffer();
            } catch (Exception e) {
                out.release();
                throw e;
            } finally {
                // 注意:finally 不要调用 release() 释放 ByteBuf, 因为重复释放会报错: io.netty.util.IllegalReferenceCountException: refCnt: 0
                // ProtostuffIOUtil.writeTo() 会调用 ByteBufOutputStream.write() 已经进行'软'置空
                buffer.clear();
            }
        }
    };

    @SuppressWarnings({"unchecked", "rawtypes"})
    private final Decoder<Object> decoder = new Decoder<Object>() {
        @Override
        public Object decode(ByteBuf buf, State state) throws IOException {
            ProtoStuffDataWrapper ProtoStuffDataWrapper = new ProtoStuffDataWrapper<>();
            ProtostuffIOUtil.mergeFrom(new ByteBufInputStream(buf), ProtoStuffDataWrapper, SCHEMA);
            return ProtoStuffDataWrapper.getData();
        }
    };
}

定义Jackson序列化编码

无需实现,Redisson自带:org.redisson.codec.JsonJacksonCodec

定义Kryo序列化编码

无需实现,Redisson自带:org.redisson.codec.Kryo5Codec

注入客户端 RedissonClient

每个序列化单独用一个客户端


import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import redisson.codec.FastJsonCodec;
import redisson.codec.ProtoStuffCodec;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.codec.Kryo5Codec;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.CollectionUtils;

import java.util.List;

@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class RedissonConfig {

    private Cluster cluster;

    private String host;

    private String port;

    private String password;

    @Data
    public static class Cluster {

        private List<String> nodes;

        /**
         * Maximum number of redirects to follow when executing commands across the cluster.
         */
        private Integer maxRedirects;
    }

    /**
     * RedissonClient bean
     */
    @Primary
    @Bean(name = "redissonClient", destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        return getRedissonClient(new JsonJacksonCodec());
    }

    @Bean(name = "kryoRedisson", destroyMethod = "shutdown")
    public RedissonClient kryoRedisson() {
        return getRedissonClient(new Kryo5Codec());
    }

    @Bean(name = "protoStuffRedisson", destroyMethod = "shutdown")
    public RedissonClient protoStuffRedisson() {
        return getRedissonClient(new ProtoStuffCodec());
    }

    @Bean(name = "fastJsonRedisson", destroyMethod = "shutdown")
    public RedissonClient fastJsonRedisson() {
        return getRedissonClient(new FastJsonCodec());
    }

    /**
     * 根据 codec 创建 RedissonClient
     *
     * @param codec codec
     * @return RedissonClient
     */
    public RedissonClient getRedissonClient(Codec codec) {
        Config redissonConfig = new Config();
        redissonConfig.setCodec(codec);
        // 设置'看门狗'续命时间, 默认30秒
        redissonConfig.setLockWatchdogTimeout(30 * 1000);

        if (null != cluster && !CollectionUtils.isEmpty(cluster.getNodes())) {
            List<String> nodes = cluster.getNodes();
            String[] clusterNodes = new String[nodes.size()];
            for (int i = 0; i < nodes.size(); i++) {
                clusterNodes[i] = "redis://" + nodes.get(i);
            }
            // 集群模式
            ClusterServersConfig clusterServersConfig = redissonConfig.useClusterServers();
            clusterServersConfig.addNodeAddress(clusterNodes)
                    .setPassword(password)
                    .setSlaveConnectionMinimumIdleSize(24)
                    .setMasterConnectionMinimumIdleSize(24);

        } else {
            // 单点模式
            SingleServerConfig singleServerConfig = redissonConfig.useSingleServer();
            singleServerConfig.setAddress("redis://" + host + ":" + port)
                    .setPassword(password);
        }
        return Redisson.create(redissonConfig);
    }
}

注入客户端Service

以 kryoRedisson 为例其他的一样,把Qualifier的名字改一下即可(redissonClient、 kryoRedisson、 protoStuffRedisson、 fastJsonRedisson)

import redisson.service.base.AbstractRedissonService;

import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;


@Component
public class RedissonKryoService extends AbstractRedissonService {

    @Autowired
    @Qualifier("kryoRedisson")
    private RedissonClient redissonClient;

    @Override
    public RedissonClient getRedissonClient() {
        return redissonClient;
    }
}

结果测试

测试代码如下,运行即可,其他的序列化工具测试写法一样,替换service即可。


import lombok.extern.slf4j.Slf4j;
import redisson.entity.Student;
import redisson.entity.StudentListObject;
import redisson.service.RedissonKryoService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Set;
import java.util.UUID;

@Slf4j
@RestController
public class KryoController {

    private static final long EXPIRE_TIME_SECONDS = 600;

    public static final String PREFIX = "test:kryo:studentList:";

    @Autowired
    private RedissonKryoService redissonKryoService;

    @GetMapping(value = "/redisson/kryo/set")
    public String studentSet() {
        Student student = new Student();
        student.setId(1);
        student.setAge(19);
        student.setName("zhangsan");
        redissonKryoService.set("kryo_student", student, EXPIRE_TIME_SECONDS);
        return "ok";
    }

    @GetMapping(value = "/redisson/kryo/get")
    public String studentGet() {
        return "" + redissonKryoService.get("kryo_student");
    }

    /**
     * 测试平均速度:
     * 序列化效率 比 JackSon、FastJson 好
     * 100次插入,总耗时:2184,平均耗时:21
     * 100次插入,总耗时:1714,平均耗时:17
     * 100次插入,总耗时:1627,平均耗时:16
     * 100次插入,总耗时:1641,平均耗时:16
     * 100次插入,总耗时:2451,平均耗时:24
     * 100次插入,总耗时:1995,平均耗时:19
     */
    @GetMapping(value = "/redisson/kryo/set/list")
    public String studentSetList() {
        // 为了测试准确, 每次插入前, 先清空旧数据
        Set<String> keys = redissonKryoService.getKeys(PREFIX + "*");
        redissonKryoService.removeKeyBatch(keys);
        log.info("清空 " + PREFIX + keys.size() + "个");

        // 创建插入对象(每个对象List有n个)
        int n = 100000;
        StudentListObject studentListObject = new StudentListObject();
        studentListObject.setName("studentListObject");
        studentListObject.setStudentList(StudentUtil.getRandomStudentList(n));

        // 循环插入100次,测试效率
        int count = 100;
        StopWatch stopWatch = new StopWatch();
        for (int i = 0; i < count; i++) {
            String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 10);
            stopWatch.start("" + i);
            redissonKryoService.set(PREFIX + uuid, studentListObject, EXPIRE_TIME_SECONDS);
            stopWatch.stop();
        }
        // log.info(stopWatch.prettyPrint());
        log.info(count + "次插入,总耗时:{},平均耗时:{}", stopWatch.getTotalTimeMillis(), stopWatch.getTotalTimeMillis() / stopWatch.getTaskCount());
        return stopWatch.getTotalTimeMillis() + "";
    }

    /**
     * 测试平均速度:
     * 反序列化效率 比 JackSon、FastJson 好
     * 100次查询,总耗时:2657,平均耗时:26
     * 100次查询,总耗时:1962,平均耗时:19
     * 100次查询,总耗时:2055,平均耗时:20
     * 100次查询,总耗时:1980,平均耗时:19
     * 100次查询,总耗时:1910,平均耗时:19
     * 100次查询,总耗时:1968,平均耗时:19
     */
    @GetMapping(value = "/redisson/kryo/get/list")
    public String studentGetList() {
        // 获取所有key
        Set<String> keys = redissonKryoService.getKeys(PREFIX + "*");

        Object list = null;
        StopWatch stopWatch = new StopWatch();
        // 循环依次查询
        int i = 0;
        for (String key : keys) {
            stopWatch.start("" + i);
            list = redissonKryoService.get(key);
            stopWatch.stop();
            i++;
        }
        // log.info(stopWatch.prettyPrint());
        log.info(keys.size() + "次查询,总耗时:{},平均耗时:{}", stopWatch.getTotalTimeMillis(), stopWatch.getTotalTimeMillis() / stopWatch.getTaskCount());
        return list + "";
    }
}

总结

各个序列化效率总结,可以看我这篇文章:

https://blog.csdn.net/q258523454/article/details/129953047

jackson、fastjson、kryo、protobuf等序列化效率对比【全】_kryo jackson-CSDN博客

我们通过同时使用多个序列化工具,以及实际数据测试的效果来看。

综合性能推荐使用:kryo 和 protoStuff


原创不易,转载请注明出处:

https://blog.csdn.net/q258523454/article/details/129988906

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

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

相关文章

H5漂流瓶社交系统源码

一个非常有创意的H5漂流瓶社交系统源码&#xff0c;带完整前端h5和后台管理系统。 环境&#xff1a;Nginx 1.20.1-MySQL 5.6.50-PHP-7.3 代码下载

python简单计算入门教程|加减法

python通过调用numpy模块&#xff0c;非常擅长数学计算。再通过调用matplotlib模块&#xff0c;可以自由自在地输出numpy计算的结果。 今天&#xff0c;我们就尝试一些基本计算。 下述是正弦函数和余弦函数的加法和减法计算结果。 图1 代码为&#xff1a; import matplotli…

【stata】处理城市名和城市代码

写了两个简单的外部命令&#xff0c;在这里分享一下&#xff0c;希望能帮到大家 1.citycode_mutate 第一个命令是citycode_mutate&#xff0c;用于识别字符串中可能存在的城市信息&#xff0c;并生成城市代码&#xff08;图1图2&#xff09;。 2.cityname_mutate 第二个命令…

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_word(struct pci_…

C++(一)----C++基础

1.C的发展史 C语言诞生后&#xff0c;很快普及使用&#xff0c;但是随着编程规模增大且越来越复杂&#xff0c;并且需要高度的抽象和建模时&#xff0c;C语言的诸多短板便表现了出来&#xff0c;为了解决软件危机&#xff0c;上世纪八十年代&#xff0c;计算机界提出了oop&…

拓扑排序-广度优先遍历思路

本质&#xff1a; 【广度优先遍历 】【贪心算法】应用于【有向图】的专有名词 应用场景&#xff1a;任务调度&#xff0c;课程安排 作用&#xff1a; 得到一个不唯一的【拓扑序】检测【有向图】是否有环&#xff0c;使用数据【并查集】 使用&#xff1a;先找度为0的前驱节点…

Linux运维排查常见故障_在tmp目录下有大量包含picture_ 的临时文件,每天晚上2 30需要对一天前的文件进行

echo“”>>/etc/security/limits.conf echo“*softnproc65535″>>/etc/security/limits.conf echo“*hardnproc65535″>>/etc/security/limits.conf echo“*softnofile65535″>>/etc/security/limits.conf echo“*hardnofile65535″>>/etc/secur…

【自动驾驶】控制算法(八)横向控制Ⅲ | 代码与模型

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

以太网--TCP/IP协议(一)

概述 以太网是局域网的一种&#xff0c;其他的比如还有令牌环、FDDI。和局域网对应的就是广域网&#xff0c;如Internet&#xff0c;城域网等。 从网络层次看&#xff0c;局域网协议主要偏重于低层&#xff08;业内一般把物理层、数据链路层归为低层&#xff09;。以太网协议…

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP…

vue动态统计图的绘画

效果图&#xff1a; 实现&#xff1a; 一、导入依赖 import echarts from echarts 二、vue的代码实现 1.在main.js导入文件 // 引入 echarts 插件 import echarts from echarts // 配置成全局组件 Vue.prototype.$echarts echarts2.代码实现 <template><!--为echa…

韩国火烧车影响出现,浙江出现限制电车进入地下车库,车主难受了

韩国电动汽车起火&#xff0c;烧毁140辆汽车&#xff0c;还导致大楼损坏以及居民受伤的后果&#xff0c;如今在中国市场也产生了影响&#xff0c;《华商报》旗下的《大风新闻》报道指&#xff0c;浙江多地的饭店、大厦禁止电动汽车进入地下车库&#xff0c;这下子电动汽车车主又…

滑动窗口在算法中的应用

滑动窗口是一种经典的算法技巧&#xff0c;就像在处理一系列动态数据时&#xff0c;用一扇可以滑动的“窗口”来捕捉一段连续的子数组或子字符串。通过不断地移动窗口的起点或终点&#xff0c;我们能够以较低的时间复杂度来解决一系列问题。在这篇文章中&#xff0c;我们将通过…

图形视频处理软件Adobe After Effects(AE)2024WIN/MAC下载及系统要求

目录 一、Adobe AE软件简介 1.1 什么是Adobe AE软件 1.2 AE软件的发展历程 1.3 AE软件的应用领域 二、Adobe AE软件下载 2.1 下载 2.2 下载注意事项 三、Adobe AE软件系统要求 3.1 最低配置要求 3.2 推荐配置要求 3.3 显示器和分辨率 四、Adobe AE软件安装与使用 …

【MacOS】mac定位服务中删除已经卸载的软件

mac定位服务中删除已经卸载的软件 网上的帖子真不靠谱 直接右键 WeTypeSettings &#xff0c;查找位置&#xff0c;丢废纸篓即可&#xff01;会提示你卸载的&#xff01;

Pyramid: Real-Time LoRa Collision Decoding with Peak Tracking技术思考与解读

一点点个人的论文解读、技术理解&#xff0c;难免会有错误&#xff0c;欢迎大家一起交流和学习~~ &#x1f600;作者关于lora的系列文章从问题陈述到方法论的提出&#xff0c;再到实验评估&#xff0c;文章结构条理清晰&#xff0c;逻辑性强&#xff0c;并深入分析了LoRa信号处…

力扣刷题(5)

整数转罗马数字 整数转罗马数字-力扣 思路&#xff1a; 把各十百千位可能出现的情况都列出来&#xff0c;写成一个二维数组找出该数的各十百千位&#xff0c;与数组中的罗马元素对应 const char* ch[4][10]{{"", "I", "II", "III"…

webpack - 五大核心概念和基本配置(打包一个简单HTML页面)

// 五大核心概念 1. entry&#xff08;入口&#xff09; 指示Webpack从哪个文件开始打包2. output&#xff08;输出&#xff09; 指示Webpack打包完的文件输出到哪里去&#xff0c;如何命名等3. loader&#xff08;加载器&#xff09; webpack本身只能处理js&#xff0c;json等…

Bev pool 加速(2):自定义c++扩展

文章目录 1. c++扩展2. 案例2.1 案例12. 1.1 代码实现(1) c++ 文件(2) setup.py编写(3) python 代码编写2.1 案例1在bevfusion论文中,将bev_pooling定义为view transform中的效率瓶颈,bevfusion 主要就是对bev_pooling进行了加速,使得视图转换的速度提高了40倍,延迟从500ms…

charles配置安卓抓包(避坑版)

下载Charleshttps://www.charlesproxy.com/安装&#xff0c;疯狂点击下一步即可注册&#xff1a;打开Charles&#xff0c;选择“Help”菜单中的“Register Charles”&#xff0c;进网站生成密钥&#xff1a;https://www.zzzmode.com/mytools/charles/,将生成的密钥填入注册重启…