Spring Cache缓存注解

2023-11-15

 

Spring Cache缓存注解

本篇文章代码示例在Spring Cache简单实现上的代码示例加以修改。

只有使用public定义的方法才可以被缓存,而private方法、protected 方法或者使用default 修饰符的方法都不能被缓存。 当在一个类上使用注解时,该类中每个公共方法的返回值都将被缓存到指定的缓存项中或者从中移除。

@Cacheable

@Cacheable注解属性一览:

属性名 作用与描述
cacheNames/value 指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
key 缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。
keyGenerator 缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver 和cacheManager作用一样,使用时二选一。
condition 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。
sync 是否使用异步模式进行缓存,默认false。

@Cacheable指定了被注解方法的返回值是可被缓存的。其工作原理是Spring首先在缓存中查找数据,如果没有则执行方法并缓存结果,然后返回数据。

缓存名是必须提供的,可以使用引号、Value或者cacheNames属性来定义名称。下面的定义展示了users缓存的声明及其注解的使用:

@Cacheable("users")
//Spring 3.x
@Cacheable(value = "users")
//Spring 从4.0开始新增了value别名cacheNames比value更达意,推荐使用
@Cacheable(cacheNames = "users")

键生成器

缓存的本质就是键/值对集合。在默认情况下,缓存抽象使用(方法签名及参数值)作为一个键值,并将该键与方法调用的结果组成键/值对。 如果在Cache注解上没有指定key,
则Spring会使用KeyGenerator来生成一个key。

package org.springframework.cache.interceptor;
import java.lang.reflect.Method;

@FunctionalInterface
public interface KeyGenerator {
    Object generate(Object var1, Method var2, Object... var3);
}

Sping默认提供了SimpleKeyGenerator生成器。Spring 3.x之后废弃了3.x 的DefaultKey
Generator而用SimpleKeyGenerator取代,原因是DefaultKeyGenerator在有多个入参时只是简单地把所有入参放在一起使用hashCode()方法生成key值,这样很容易造成key冲突。SimpleKeyGenerator使用一个复合键SimpleKey来解决这个问题。通过其源码可得知Spring生成key的规则。

/**
 * SimpleKeyGenerator源码的类路径参见{@link org.springframework.cache.interceptor.SimpleKeyGenerator}
 */

从SimpleKeyGenerator的源码中可以发现其生成规则如下(附SimpleKey源码):

  • 如果方法没有入参,则使用SimpleKey.EMPTY作为key(key = new SimpleKey())。
  • 如果只有一个入参,则使用该入参作为key(key = 入参的值)。
  • 如果有多个入参,则返回包含所有入参的一个SimpleKey(key = new SimpleKey(params))。
package org.springframework.cache.interceptor;

import java.io.Serializable;
import java.util.Arrays;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

public class SimpleKey implements Serializable {
    public static final SimpleKey EMPTY = new SimpleKey(new Object[0]);
    private final Object[] params;
    private final int hashCode;

    public SimpleKey(Object... elements) {
        Assert.notNull(elements, "Elements must not be null");
        this.params = new Object[elements.length];
        System.arraycopy(elements, 0, this.params, 0, elements.length);
        this.hashCode = Arrays.deepHashCode(this.params);
    }

    public boolean equals(Object other) {
        return this == other || other instanceof SimpleKey && Arrays.deepEquals(this.params, ((SimpleKey)other).params);
    }

    public final int hashCode() {
        return this.hashCode;
    }

    public String toString() {
        return this.getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
    }
}

如需自定义键生成策略,可以通过实现org.springframework.cache.interceptor.KeyGenerator接口来定义自己实际需要的键生成器。示例如下,自定义了一个MyKeyGenerator类并且实现(implements)了KeyGenerator以实现自定义的键值生成器:

package com.example.cache.springcache;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKey;
import java.lang.reflect.Method;

/**
 * @author: 博客「成猿手册」
 * @description: 为方便演示,这里自定义的键生成器只是在SimpleKeyGenerator基础上加了一些logger打印以区别自定义的Spring默认的键值生成器;
 */
public class MyKeyGenerator implements KeyGenerator {

    private static final Logger logger =  LoggerFactory.getLogger(MyKeyGenerator.class);

    @Override
    public Object generate(Object o, Method method, Object... objects) {
        logger.info("执行自定义键生成器");
        return generateKey(objects);
    }

    public static Object generateKey(Object... params) {
        if (params.length == 0) {
            logger.debug("本次缓存键名称:{}", SimpleKey.EMPTY);
            return SimpleKey.EMPTY;
        } else {
            if (params.length == 1) {
                Object param = params[0];
                if (param != null && !param.getClass().isArray()) {
                    logger.debug("本次缓存键名称:{}", params);
                    return param;
                }
            }
            SimpleKey simpleKey = new SimpleKey(params);
            logger.debug("本次缓存键名称:{}", simpleKey.toString());
            return simpleKey;
        }
    }
}

同时在Spring配置文件中配置:

<!-- 配置键生成器Bean -->
<bean id = "myKeyGenerator" class="com.example.cache.springcache.MyKeyGenerator" />

使用示例如下:

@Cacheable(cacheNames = "userId",keyGenerator = "myKeyGenerator")
public User getUserById(String userId)

执行的打印结果如下:

first query...
14:50:29.901 [main] INFO com.example.cache.springcache.MyKeyGenerator - 执行自定义键生成器
14:50:29.902 [main] DEBUG com.example.cache.springcache.MyKeyGenerator - 本次键名称:test001
14:50:29.904 [main] INFO com.example.cache.springcache.MyKeyGenerator - 执行自定义键生成器
14:50:29.904 [main] DEBUG com.example.cache.springcache.MyKeyGenerator - 本次键名称:test001
query user by userId=test001
querying id from DB...test001
result object: com.example.cache.customize.entity.User@1a6c1270
second query...
14:50:29.927 [main] INFO com.example.cache.springcache.MyKeyGenerator - 执行自定义键生成器
14:50:29.927 [main] DEBUG com.example.cache.springcache.MyKeyGenerator - 本次键名称:test001
result object: com.example.cache.customize.entity.User@1a6c1270

@CachePut

@CachePut注解属性与@Cacheable注解属性相比少了sync属性。其他用法基本相同:

属性名 作用与描述
cacheNames/value 指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
key 缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。
keyGenerator 缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver 和cacheManager作用一样,使用时二选一。
condition 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。

如果一个方法使用了@Cacheable注解,当重复(n>1)调用该方法时,由于缓存机制,并未再次执行方法体,其结果直接从缓存中找到并返回,即获取还的是第一次方法执行后放进缓存中的结果。

但实际业务并不总是如此,有些情况下要求方法一定会被调用,例如数据库数据的更新,系统日志的记录,确保缓存对象属性的实时性等等。

@CachePut注解就确保方法调用即执行,执行后更新缓存。

示例代码清单:

package com.example.cache.springcache;

import com.example.cache.customize.entity.User;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
@Service(value = "userServiceBean2")
public class UserService2 {

    /**
     * 声明缓存名称为userCache
     * 缓存键值key未指定默认为userNumber+userName组合字符串
     *
     * @param userId 用户Id
     * @return 返回用户对象
     */
    @Cacheable(cacheNames = "userCache")
    public User getUserByUserId(String userId) {
        // 方法内部实现不考虑缓存逻辑,直接实现业务
        return getFromDB(userId);
    }

    /**
     * 注解@CachePut:确保方法体内方法一定执行,执行完之后更新缓存;
     * 使用与 {@link com.example.cache.springcache.UserService2#getUserByUserId(String)}方法
     * 相同的缓存userCache和key(缓存键值使用spEl表达式指定为userId字符串)以实现对该缓存更新;
     *
     * @param user 用户参数
     * @return 返回用户对象
     */
    @CachePut(cacheNames = "userCache", key = "(#user.userId)")
    public User updateUser(User user) {
        return updateData(user);
    }

    private User updateData(User user) {
        System.out.println("real updating db..." + user.getUserId());
        return user;
    }

    private User getFromDB(String userId) {
        System.out.println("querying id from db..." + userId);
        return new User(userId);
    }
}

测试代码清单:

package com.example.cache.springcache;

import com.example.cache.customize.entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
public class UserMain2 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService2 userService2 = (UserService2) context.getBean("userServiceBean2");
        //第一次查询,缓存中没有,从数据库查询
        System.out.println("first query...");
        User user1 = userService2.getUserByUserId("user001");
        System.out.println("result object: " + user1);

        user1.setAge(20);
        userService2.updateUser(user1);
        //调用即执行,然后更新缓存
        user1.setAge(21);
        userService2.updateUser(user1);

        System.out.println("second query...");
        User user2 = userService2.getUserByUserId("user001");
        System.out.println("result object: " + user2);
        System.out.println("result age: " + user2.getAge());
    }
}

测试打印结果如下:

first query...
querying id from db...user001
result object: com.example.cache.customize.entity.User@6d1ef78d
real updating db...user001
real updating db...user001
second query...
result object: com.example.cache.customize.entity.User@6d1ef78d
result age: 21

结果表明,执行了两次模拟调用数据库的方法。需要注意的是,在这个简单示例中,两次setAge()方法并不能够证明确实更新了缓存:把updateData()方法去掉也可以得到最终的用户年龄结果,因为set操作的仍然是getUserByName()之前获取的对象。

应该在实际操作中将getFromDBupdateData调整为更新数据库的具体方法,再通过加与不加@CachePut来对比最后的结果判断是否更新缓存。

@CacheEvict

@CacheEvict注解属性一览:

属性名 作用与描述
cacheNames/value 指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
key 缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。
keyGenerator 缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver 和cacheManager作用一样,使用时二选一。
condition 指定删除缓存的条件(对参数判断,满足什么条件时才删除缓存),可用SpEL表达式,例如:入参为字符userId的方法删除缓存条件设定为当入参不是user001就删除缓存,则表达式可以写为condition = "!('user001').equals(#userId)"
allEntries allEntries是布尔类型的,用来表示是否需要清除缓存中的所有元素。默认值为false,表示不需要。当指定allEntries为true时,Spring Cache将忽略指定的key,清除缓存中的所有内容。
beforeInvocation 清除操作默认是在对应方法执行成功后触发的(beforeInvocation = false),即方法如果因为抛出异常而未能成功返回时则不会触发清除操作。使用beforeInvocation属性可以改变触发清除操作的时间。当指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

@CacheEvict注解是@Cachable注解的反向操作,它负责从给定的缓存中移除一个值。大多数缓存框架都提供了缓存数据的有效期,使用该注解可以显式地从缓存中删除失效的缓存数据。该注解通常用于更新或者删除用户的操作。下面的方法定义从数据库中删除-一个用户,而@CacheEvict 注解也完成了相同的工作,从users缓存中删除了被缓存的用户。

在上面的实例中添加删除方法:

@CacheEvict(cacheNames = "userCache")
public void delUserByUserId(String userId) {
    //模拟实际业务中的删除数据操作
    System.out.println("deleting user from db..." + userId);
}

测试代码清单:

package com.example.cache.springcache;

import com.example.cache.customize.entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
public class UserMain3 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService2 userService2 = (UserService2) context.getBean("userServiceBean2");
        String userId = "user001";
        //第一次查询,缓存中没有,执行数据库查询
        System.out.println("first query...");
        User user1 = userService2.getUserByUserId(userId);
        System.out.println("result object: " + user1);

        //第二次查询从缓存中查询
        System.out.println("second query...");
        User user2 = userService2.getUserByUserId(userId);
        System.out.println("result object: " + user2);

        //先移除缓存再查询,缓存中没有,执行数据库查询
        userService2.delUserByUserId(userId);
        User user3 = userService2.getUserByUserId(userId);
        System.out.println("result object: " + user3);
    }
}

执行的打印结果如下:

first query...
querying id from db...user001
result object: com.example.cache.customize.entity.User@6dee4f1b
second query...
result object: com.example.cache.customize.entity.User@6dee4f1b
deleting user from db...user001
querying id from db...user001
result object: com.example.cache.customize.entity.User@31bcf236

通过打印结果验证了@CacheEvict移除缓存的效果。需要注意的是,在相同的方法上使用@Caheable@CacheEvict注解并使用它们指向相同的缓存没有任何意义,因为这相当于数据被缓存之后又被立即移除了,所以需要避免在同一方法上同时使用这两个注解。

@Caching

@Caching注解属性一览:

属性名 作用与描述
cacheable 取值为基于@Cacheable注解的数组,定义对方法返回结果进行缓存的多个缓存。
put 取值为基于@CachePut注解的数组,定义执行方法后,对返回方的方法结果进行更新的多个缓存。
evict 取值为基于@CacheEvict注解的数组。定义多个移除缓存。

总结来说,@Caching是一个组注解,可以为一个方法定义提供基于@Cacheable@CacheEvict或者@CachePut注解的数组。

示例定义了User(用户)、Member(会员)和Visitor(游客)3个实体类,它们彼此之间有一个简单的层次结构:User是一个抽象类,而Member和Visitor类扩展了该类。

User(用户抽象类)代码清单:

package com.example.cache.springcache.entity;

/**
 * @author: 博客「成猿手册」
 * @description: 用户抽象类
 */
public abstract class User {
    private String userId;
    private String userName;

    public User(String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }
    //todo:此处省略get和set方法
}

Member(会员类)代码清单:

package com.example.cache.springcache.entity;

import java.io.Serializable;

/**
 * @author: 博客「成猿手册」
 * @description: 会员类
 */
public class Member extends User implements Serializable {
    public Member(String userId, String userName) {
        super(userId, userName);
    }
}

Visitor(游客类)代码清单:

package com.example.cache.springcache.entity;

import java.io.Serializable;

/**
 * @author: 博客「成猿手册」
 * @description: 访客类
 */
public class Visitor extends User implements Serializable {
    private String visitorName;

    public Visitor(String userId, String userName) {
        super(userId, userName);
    }
}

UserService3类是一个Spring服务Bean,包含了getUser()方法。
同时声明了两个@Cacheable注解,并使其指向两个不同的缓存项: members和visitors。然后根据两个@Cacheable注解定义中的条件对方法的参数进行检查,并将对象存储在
members或visitors缓存中。

UserService3代码清单:

package com.example.cache.springcache;

import com.example.cache.springcache.entity.Member;
import com.example.cache.springcache.entity.User;
import com.example.cache.springcache.entity.Visitor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
@Service(value = "userServiceBean3")
public class UserService3 {

    private Map<String, User> users = new HashMap<>();

    {
        //初始化数据,模拟数据库中数据
        users.put("member001", new Member("member001", "会员小张"));
        users.put("visitor001", new Visitor("visitor001", "访客小曹"));
    }

    @Caching(cacheable = {
            /*
              该condition指定的SpEl表达式用来判断方法传参的类型
              instanceof是Java中的一个二元运算符,用来测试一个对象(引用类型)是否为一个类的实例
             */
            @Cacheable(value = "members", condition = "#user instanceof T(" +
                    "com.example.cache.springcache.entity.Member)"),
            @Cacheable(value = "visitors", condition = "#user instanceof T(" +
                    "com.example.cache.springcache.entity.Visitor)")
    })
    public User getUser(User user) {
        //模拟数据库查询
        System.out.println("querying id from db..." + user.getUserId());
        return users.get(user.getUserId());
    }
}

UserService3类是-一个Spring服务Bean,包含了getUser()方法。同时声明了两个@Cacheable注解,并使其指向两个不同的缓存项: members 和visitors。
然后根据两个@Cacheable注解定义中的条件对方法的参数进行检查,并将对象存储在
members或visitors缓存中。

测试代码清单:

package com.example.cache.springcache;

import com.example.cache.springcache.entity.Member;
import com.example.cache.springcache.entity.User;
import com.example.cache.springcache.entity.Visitor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
public class UserService3Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService3 userService3 = (UserService3) context.getBean("userServiceBean3");

        Member member = new Member("member001", null);

        //会员第一次查询,缓存中没有,从数据库中查询
        User member1 = userService3.getUser(member);
        System.out.println("member userName-->" + member1.getUserName());
        //会员第二次查询,缓存中有,从缓存中查询
        User member2 = userService3.getUser(member);
        System.out.println("member userName-->" + member2.getUserName());

        Visitor visitor = new Visitor("visitor001", null);
        //游客第一次查询,缓存中没有,从数据库中查询
        User visitor1 = userService3.getUser(visitor);
        System.out.println("visitor userName-->" + visitor1.getUserName());
        //游客第二次查询,缓存中有,从缓存中查询
        User visitor2 = userService3.getUser(visitor);
        System.out.println("visitor userName-->" + visitor2.getUserName());
    }
}

执行的打印结果如下:

querying id from db...member001
member userName-->会员小张
member userName-->会员小张
querying id from db...visitor001
visitor userName-->访客小曹
visitor userName-->访客小曹

@CacheConfig

@CacheConfig注解属性一览:

属性名 作用与描述
cacheNames/value 指定类级别缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
keyGenerator 类级别缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager 指定类级别缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver 和cacheManager作用一样,使用时二选一。

前面我们所介绍的注解都是基于方法的,如果在同一个类中需要缓存的方法注解属性都相似,则需要重复增加。Spring 4.0之后增加了@CacheConfig类级别的注解来解决这个问题。

一个简单的实例如下所示:

package com.example.cache.springcache;

import com.example.cache.springcache.entity.User;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
@CacheConfig(cacheNames = "users",keyGenerator = "myKeyGenerator")
public class UserService4 {
    @Cacheable
    public User findA(User user){
        //todo:执行一些操作
    }
        
    @CachePut
    public User findB(User user){
        //todo:执行一些操作
    }
}

可以看到,在@CacheConfig注解中定义了类级别的缓存users和自定义键生成器,
那么在findA0和findB(方法中不再需要重复指定,而是默认使用类级别的定义。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Cache缓存注解 的相关文章

随机推荐

  • 素数环

    昨天晚上 突然想读刘汝佳老师书中的例题 素数环 但是突然自己就有了思路 于是便自己实现了一下 但是 由于昨晚时间比较晚 程序是写完了 但是没调试出来 今天一大早就开始调试 花了半小时终于调试出来了 好开心 中心思想是 回溯 上代码 incl
  • sqli-labs布尔盲注和时间盲注相结合的脚本实现

    import time import requests 定义一个变量 用于存放cookie值 HEADER cookie Idea 69a0360c 059291e0 9967 4fe4 bb5e 2524cdcf69d4 security
  • 多元共进|探索社区故事,助力开发者成长

    技术社区中的多元交流 带来丰富的灵感思维 为开发者的发展成长注入不竭的动力 谷歌凭借多种开发者生态项目和社区活动 辅助开发者在学习和成长的道路上稳步前行 中国的开发市场目前正处于全球视野中的重要位置 与各地的开发者交流互动也成为了中国开发者
  • 利用leapmotion实现抓取一个立方体

    1 基础设置 2 拖进来LeapHandController 显示为蓝色 其中 Edit Time Pose选择的是不同的模式 是桌面模式还是VR模式 3 拖进来HandModels 此部分为手势模型 为新建空物体 然后加上Hand Mod
  • 关于visual studio中的$(ConfigurationName)疑问

    关于visual studio中的 ConfigurationName 疑问 2012 12 02 16 09 15 转载 标签 it 分类 程序员之路 关于vs中的各种路径的值de查看方法 来源 http social msdn micr
  • Python---三大流程控制

    三大流程控制语句 1 顺序 按顺序去执行步骤 是最基本的代码执行规则 不做过多的解释 2 分支 单分支 if 条件 满足条件做什么 双分支 if 条件 满足条件做什么 else 不满足条件做什么 三元运算符 双份支的简化版 result 满
  • 关于scanf和printf的一些问题&&EOF和~的问题

    1 scanf和printf的返回值 在C语言中 scanf和printf这两个函数是标准输入输出库中的函数 它们在使用时不返回具体的值 而是通过输入输出参数来完成相应的功能 scanf函数 scanf函数用于从标准输入读取数据并根据提供的
  • KNN 原理及参数总结

    文章目录 前言 1 KNN 原理 2 KNN 优缺点 3 KNN 算法三要素 4 KNN 算法实现 5 sklearn实现KNN算法 前言 针对一个完整的机器学习框架目前还没有总结出来 所以目前只能总结每一个单独的算法 由于现在研究的重点是
  • 详解虚短、虚断以及在运算放大器中的应用

    详解虚短 虚断以及在运算放大器中的应用 一 运算放大器 运算放大器 后续简称运放 是一种集成电路 内部有很多三极管类晶体管的组合 外围接很少的电子元器件就能够实现放大信号的作用 并且信号干净 漂亮 1 1 开环 闭环 运算放大器电路 开环电
  • 2021年新版-编程基础训练32题-附提示和答案

    2021年新版 编程基础训练32题 附提示和答案 1 用级数法求圆周率 题目 圆周率十分重要 不仅仅是在数学理论上 即便在千年前的古代 工程上的需求 也迫切需要我们知道圆周率的尽量精确的数值 求圆周率 有很多种方法 级数法就是简便易行的方法
  • 牛客网Python篇入门编程习题

    目 录 一 输入输出 二 类型转换 三 字符类型 四 列表类型 五 运算符号 六 条件语句 七 循环语句 八 元组类型 九 字典类型 十 内置函数 十一 面向对象 十二 正则表达 本文题库非常适合刚刚接触Python编程的同学 有兴趣的同学
  • STlink V2 烧录器使用教学 【STM32篇】

    STlink V2 是一款支持STM32 STM8 烧录的常规工具 本帖主要讲解STM32 的烧录过程 STM32有2种烧录接口 分别为古老的Jtag接口和目前最常规的SWD接口 由于SWD只需要4条线就能烧录 目前STM32硬件工程师用S
  • Unity使用C#实现简单Scoket连接及服务端与客户端通讯

    简介 网络编程是个很有意思的事情 偶然翻出来很久之前刚开始看Socket的时候写的一个实例 贴出来吧 Unity中实现简单的Socket连接 c 中提供了丰富的API 直接上代码 服务端代码 Thread connectThread 当前服
  • idea 编码扫描插件_4款好用的IDEA插件

    刚开始安装使用的IDEA是没有灵魂的 所以我们要通过插件来给 它注入灵魂 Codota 这是一款代码提示工具 根据你敲击的代码进行提示 这样再敲一些长代码时会方便很多 安装方法 点击file gt settings 选择plugins 搜索
  • 悟空CRM9从零开始搭建详细步骤——肯定成功

    悟空CRM9从零开始搭建详细步骤 欢迎留言 欢迎各位一起加入开源 愿意共享分享学习经验 特别感谢打赏点赞的朋友 我们一起努力分享更多学习经验吧 可参考其他论坛 码云https gitee com wukongcrm 72crm java 悟
  • 用户态--fork函数创建进程

    我们一般使用Shell命令行来启动一个程序 其中首先是创建一个子进程 但是由于Shell命令行程序比较复杂 为了便于理解 我们简化了Shell命令行程序 用如下一小段代码来看怎样在用户态创建一个子进程 include
  • 网上经常看到的冒泡排序的动图如何制作

    今天博主想要和大家分享如何实现动态图 经常在其他博主的文章中可以看到各式各样的动图 搜索一下 网上冒泡排序的动图怎么制作出来 可以看到 很迷 全是告诉冒泡排序的原理 以及动图解析 并没有告知动图是如何制作的 结合博主目前正在学习的前端技术
  • Linux Ubuntu16.04 安装lmdb问题

    LMDB的全称是Lightning Memory Mapped Database 快如闪电的内存映射数据库 它的文件结构简单 包含一个数据文件和一个锁文件 LMDB文件可以同时由多个进程打开 具有极高的数据存取速度 访问简单 不需要运行单独
  • ubuntu下新建txt文档的快捷方式

    进入模板文件夹 Templates 右键打开终端 输入如下命令 sudo gedit txt文档 txt 点击右上角保存 退出 即可通过右键新建txt模板
  • Spring Cache缓存注解

    目录 Spring Cache缓存注解 Cacheable 键生成器 CachePut CacheEvict Caching CacheConfig Spring Cache缓存注解 本篇文章代码示例在Spring Cache简单实现上的代