二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。

上一章简单介绍了SpringBoot整合Redis_Jedis版(二十),如果没有看过,请观看上一章

一. SpringCache

一.一 SpringCache 的出现

在SpringBoot 整合 Redis 时,无论是使用 Lettuce 还是使用 Jedis 连接池, 在查询单个对象,查询全部对象的时候,都是我们自己手动进行判断缓存的信息。

SpringBoot 使用 Lettuce 连接池时:

 @Overridepublic User findById(int id) {log.info("先从缓存中查询用户编号为{} 是否存在",id);User user=redisUtil.get(KEY_PRE+id);if(user!=null){log.info(">>>>>>>>>>使用的是缓存中的数据");return user;}log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");user= userMapper.findById(id);redisUtil.set(KEY_PRE+id,user);return user;}@Overridepublic List<User> findAll() {log.info("先从缓存中查询用户列表是否存在");List<User> userList= (List<User>) redisUtil.range(KEY_PRE+"ALL");if(!CollectionUtils.isEmpty(userList)){log.info(">>>>>>>>>>使用的是缓存中的数据");return userList;}log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");userList= userMapper.findAll();redisUtil.leftPushAll(KEY_PRE+"ALL",userList);return userList;}

SpringBoot 使用 Jedis 连接池时:

 @Overridepublic User findById(int id) {log.info("先从缓存中查询用户编号为{} 是否存在",id);User user=BeanConvertUtil.stringToBean(redisUtil.get(KEY_PRE+id,redisDB),User.class);if(user!=null){log.info(">>>>>>>>>>使用的是缓存中的数据");return user;}log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");user= userMapper.findById(id);redisUtil.set(KEY_PRE+id,BeanConvertUtil.beanToString(user),redisDB);return user;}@Overridepublic List<User> findAll() {log.info("先从缓存中查询用户列表是否存在");List<String> userStringList= (List<String>) redisUtil.lrange(KEY_PRE+"ALL",0,-1,redisDB);List<User> userList=new ArrayList<>();if(!CollectionUtils.isEmpty(userStringList)){log.info(">>>>>>>>>>使用的是缓存中的数据");for(String userString:userStringList){userList.add(BeanConvertUtil.stringToBean(userString,User.class));}return userList;}log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");userList= userMapper.findAll();for(User user:userList){redisUtil.lpush(redisDB,KEY_PRE+"ALL",BeanConvertUtil.beanToString(user));}return userList;}

可以发现, 两个都需要开发者自己手动处理缓存的信息。

并且,如果缓存的工具不同,处理的方式也不同。

实际上,这些与业务是没有太大的联系的。

我们希望能够有一种方式,能够通过简单的配置+注解,达到以前原生的写法就完美了。

    @Overridepublic User findById(int id) {return userMapper.findById(id);}@Overridepublic List<User> findAll() {return userMapper.findAll();}

在这两个方法上,添加某个注解, 能够达到 有缓存走缓存,没有缓存走数据库查询,
然后将查询结果放置在缓存中,下一次查询时走缓存的结果,
并且与缓存的实现方式无关 (无论是 Lettuce 还是 Jedis)

有这么一种技术, 叫做SpringCache

一.二 SpringCache 的简单使用

按照 SpringBoot_Redis 项目,创建 SpringBoot_Cache 项目。

Redis服务器打开,使用的仍然是 database 15 数据库。

采用的是 springboot 2.2.13 版本。

目前数据库表 user 里面有三条记录

一.二.一 pom.xml 添加依赖

        <!--依赖 data-redis的依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--不能忘记这个依赖--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!--添加cache的依赖信息-->

一.二.二 application.yml 进行配置

与redis整合时一样,没有改变。

# 引入 数据库的相关配置
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=trueusername: rootpassword: abc123# 配置Redis的使用redis:database: 15 # 所使用的数据库  默认是0host: 127.0.0.1  #所使用的redis的主机地址port: 6379  # 端口号  默认是 6379password: zk123 # 密码timeout: 5000 # 超时时间  5000毫秒# 连接池 lettuce 的配置lettuce:pool:max-active: 100min-idle: 10max-wait: 100000
#整合mybatis时使用的
mybatis:#包别名type-aliases-package: top.yueshushu.learn.pojo#映射文件路径mapper-locations: classpath:mybatis/mapper/**/*.xmlconfiguration:#日志信息log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

一.二.三 启动类上 添加 @EnableCaching 注解

需要在启动类上 添加 @EnableCaching 注解, 开启缓存。

@MapperScan("top.yueshushu.learn.mapper")
@SpringBootApplication
//开启缓存
@EnableCaching
public class RedisApplication {public static void main(String[] args) {SpringApplication.run(RedisApplication.class,args);System.out.println("运行 Redis Cache缓存");}
}

一.二.四 不使用缓存时处理

一.二.四.一 查询 findById 实现

    @Overridepublic User findById(int id) {return userMapper.findById(id);}

一.二.四.二 查询测试

  @Testpublic void findByIdTest(){User user=userService.findById(40); //id随时更换log.info(user);}

运行测试方法 findByIdTest()

第一次查询

发现查询了数据库

第二次查询

依然走的是数据库查询.

这是以前的常规的写法。

一.二.五 使用SpringCache 缓存时处理

一.二.五.一 查询 findById 实现

    @Override// 指定了参数为 id:  变成了:  value::id 的key值@Cacheable(value=KEY_PRE,key = "#id")public User findById(int id) {return userMapper.findById(id);}

在方法上 添加了一个注解 @Cacheable ,补充属性信息

value 表示使用的缓存组, key 表示缓存的值。

一.二.五.二 查询测试

  @Testpublic void findByIdTest(){User user=userService.findById(40); //id随时更换log.info(user);}

运行测试方法 findByIdTest()

第一次查询

发现查询了数据库

第二次查询

发现,并没有查询数据库,走的是缓存里面的数据。

查看 Redis客户端

发现存储的数据乱码了.

一.二.六 处理存储信息乱码问题

除了 RedisConfig.java 配置之外 ,再添加一个 CacheConfig.java 的配置信息

package top.yueshushu.learn.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;import javax.annotation.Resource;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;/*** @author :zk_yjl* @description:Cache的缓存配置信息,可以解决乱码问题* @date :2021/09/23 17:09*/
@Log4j2
@Configuration
public class CacheConfig extends CachingConfigurerSupport {@Resourceprivate RedisConnectionFactory factory;/*** 自定义生成redis-key** @return*/@Override@Beanpublic KeyGenerator keyGenerator() {return (o, method, objects) -> {StringBuilder sb = new StringBuilder();sb.append(o.getClass().getName()).append(".");sb.append(method.getName()).append(".");for (Object obj : objects) {sb.append(obj.toString());}log.info("keyGenerator=" + sb.toString());return sb.toString();};}@Bean@Overridepublic CacheResolver cacheResolver() {return new SimpleCacheResolver(cacheManager());}@Bean@Overridepublic CacheErrorHandler errorHandler() {// 用于捕获从Cache中进行CRUD时的异常的回调处理器。return new SimpleCacheErrorHandler();}@Bean@Overridepublic CacheManager cacheManager() {return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(factory),this.getRedisCacheConfigurationWithTtl(30*60), // 默认策略,未配置的 key 会使用这个this.getRedisCacheConfigurationMap() // 指定 key 策略);}private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();//DayCache和SecondsCache进行过期时间配置translates缓存丢弃改为了redisredisCacheConfigurationMap.put("translates", this.getRedisCacheConfigurationWithTtl(12*60*60));redisCacheConfigurationMap.put("strategies", this.getRedisCacheConfigurationWithTtl(60));return redisCacheConfigurationMap;}private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofSeconds(seconds));return redisCacheConfiguration;}}

重新运行测试 (此时缓存信息并没有清空)


出现了异常.

将缓存信息 key 清空后再执行, 运行是成功的,

从数据库里面查询, 将查询结果放置到Redis缓存里面,并且缓存信息正常展示。

二. Spring Cache 的概念

参考文章: https://www.cnblogs.com/morganlin/p/12000223.html (转载很多,不知道原版是哪一个)

Spring Cache 介绍

二.一 几个重要概念&缓存注解

名称 解释
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其进行缓存.不保证方法被调用
@CacheEvict 清空缓存 常用于删除
@CachePut 保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都会调用方法,常用于更新
@EnableCaching 开启基于注解的缓存 在启动类上进行配置
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略
@CacheConfig 统一配置本类的缓存注解的属性
@Caching 同一个方法,操作多个缓存时使用

二.二 @Cacheable/@CachePut/@CacheEvict 主要的参数

名称 解释
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#id”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存/清除缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
unless 否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”)
allEntries (@CacheEvict ) 是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation (@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true)

二.三 SpEL上下文数据

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

注意:

1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如

@Cacheable(key = "targetClass + methodName +#p0")

2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:

@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")

SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,||,!,and,or,not,between,instanceof
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],$[…]

三. SpringCache 的注解用法

通常情况下, 传入的参数为 key, 返回的结果为 value

三.一 @Cacheable

    @Override// 指定了参数为 id:  变成了:  value::id 的key值@Cacheable(value=KEY_PRE,key = "#id")public User findById(int id) {return userMapper.findById(id);}

进行测试 传入的参数 是 40

当参数有多个时

 @Cacheable(value = KEY_PRE)@Overridepublic List<User> findByNameAndSex(String name, String sex) {return userMapper.findByNameAndSex(name,sex);}

不指定 key 时 (传入参数 name为 周小欢 sex为 女 时), 默认的生成的缓存 key为:

user_::top.yueshushu.learn.service.UserServiceImpl.findByNameAndSex.周小欢女

可以 通过 key 进行指定

 @Cacheable(value = KEY_PRE,key = "#name")@Overridepublic List<User> findByNameAndSex(String name, String sex) {return userMapper.findByNameAndSex(name,sex);}

生成 后的 key 为: user_::周小欢

key值 可以进行拼接

 @Cacheable(value = KEY_PRE,key = "#name+#sex")

生成后的key 为: user_::周小欢女

也可以使用 #p+参数序号 来指定

@Cacheable(value = KEY_PRE,key = "#p0+#p1")

生成的key 是: user_::周小欢女

也可以使用 SpEL 上下文进行处理

    @Override@Cacheable(value = KEY_PRE,key="#root.targetClass+#root.methodName+#id")public User findById(int id) {return userMapper.findById(id);}

传入参数是 40 的话,

生成的 key 是: user_::class top.yueshushu.learn.service.UserServiceImplfindById40

也可以指定 条件 condition 当条件满足时,才使用缓存。

传入 id 为 40, <30为false, 即条件为 false

@Cacheable(value = KEY_PRE,key="#root.targetClass+#root.methodName+#id",
condition ="#id<30" )

重新运行

发生会查询数据库,不走缓存。 即使Redis里面有这个 key

unless 表示条件不满足时,使用缓存

    @Override@Cacheable(value = KEY_PRE,key="#root.targetClass+#root.methodName+#id",unless ="#id<30" )public User findById(int id) {return userMapper.findById(id);}

三.二 @CachePut 缓存更新

常用于修改缓存里面的内容。

设置 id=40 的用户的缓存, key为: user_::40

    @Override@Cacheable(value = KEY_PRE,key="#id" )public User findById(int id) {return userMapper.findById(id);}

修改用户的信息, 注意, 这个修改方法有返回值 User, 并不是以前的 void

    @Override@CachePut(value = KEY_PRE,key = "#user.id")public User updateUser(User user) {userMapper.updateUser(user);//更新全部的缓存信息return user;}

将返回值 放置到缓存里面。

    @Testpublic void updateTest(){User user=userService.findById(40);  //id随时更换user.setName("我换新的名字了");userService.updateUser(user);log.info("修改成功{}",user);findByIdTest();;}

运行处理

发现缓存里面的内容 也同步进行更新了.

添加方法时

注意, 方法有返回值, 为 User userMapper.addUser() 方法,会自动回显 id.
所以 key值用的是 #result 结果里面的id

   @CachePut(value=KEY_PRE,key = "#result.id")@Overridepublic User addUser(User user) {userMapper.addUser(user);return user;}

测试方法

    @Testpublic void insertTest(){//1. 构建对象User user=new User();user.setName("岳泽霖");user.setAge(26);user.setSex("男");user.setDescription("一个快乐的程序员");//2. 添加方法userService.addUser(user);log.info("添加成功,{}",user);}

查看控制台输出

新添加的 用户 id 为56

根据 id=56 查询的话, 也是直接从缓存里面获取数据.

三.三 @CacheEvict 清空缓存

根据id 清空缓存 返回值可以是 void

    @Override@CacheEvict(value = KEY_PRE,key = "#id")public void deleteUser(int id) {userMapper.deleteById(id);}

清空缓存

   @Testpublic void deleteTest(){userService.deleteUser(56); //id随时更换}

发现数据库里面没有 id=56 的记录了, redis缓存里面也没有 key为 user_:: 56 的记录了。

findById 中 id=40 和 findAll() 生成两个缓存

删除时, 使用 allEntries=true 属性

    @Override@CacheEvict(value = KEY_PRE,key = "#id",allEntries = true)public void deleteUser(int id) {userMapper.deleteById(id);}

运行删除 id=56(已经不存在这条记录信息了)

发现, 会清空当前数据库下所有的缓存信息。 所以,这个属性 allEntries 不要乱用。

三.四 @CacheConfig 在类上统一设置

我们发现, 我们设置缓存时, 每一个方法,无论是 findById , 还是 deleteUser, updateUser , 都使用了一个前缀 value=KEY_PRE, 这个值是 user_ 可不可以将这个前缀统一设置呢?

可以使用 @CacheConfig 注解在类上来简化缓存的开发.

@Service
@Log4j2
@CacheConfig(cacheNames ={"user_"})
public class UserServiceImpl implements UserService {...
}

这样在 方法上,就可以省略掉 以前的 value 属性。

    @Override@Cacheable(key="#id" )public User findById(int id) {return userMapper.findById(id);}@Override@Cacheable(key = "#root.targetClass+#root.methodName")public List<User> findAll() {return userMapper.findAll();}@Cacheable(key = "#root.targetClass+#root.methodName")@Overridepublic List<User> findByNameAndSex(String name, String sex) {return userMapper.findByNameAndSex(name,sex);}

与以前是相同的效果。

可以一个实体类,设置一个相应的前缀信息。

三.五 @Caching 多注解组合

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {Cacheable[] cacheable() default {};CachePut[] put() default {};CacheEvict[] evict() default {};}

里面包含了三个常用的注解

    @Caching(cacheable = {@Cacheable(key = "#name"),  // 放置缓存到 name@Cacheable(key="#sex")     // 放置缓存到 sex , id的缓存用  put更新},put = {@CachePut(key="#id")    //同时更新 id缓存})@Overridepublic List<User> findByNameAndSexAndId(String name, String sex, Integer id) {return userMapper.findByNameAndSexAndId(name,sex,id);}

会同时将查询的信息 放置到 name 属性的缓存, sex属性的缓存里面, 同时更新 id属性的缓存信息。

测试类

    @Testpublic void findNameAndSexAndIdTest(){log.info(">>>>>>>>目前数据库中存在的用户信息:");List<User> userList=userService.findByNameAndSexAndId("欢欢","女",40);userList.forEach(n->log.info(n));}

查看缓存的信息

这些就是 Spring Cache 的基本用法.

本章节的代码放置在 github 上:

https://github.com/yuejianli/springboot/tree/develop/SpringBoot_Cache

谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!

SpringBoot整合Cache缓存技术(二十一)相关推荐

  1. SpringBoot集成Cache缓存(Ehcache缓存框架,注解方式)

    1.说明 Spring定义了CacheManager和Cache接口, 用来统一不同的缓存技术, 例如JCache,EhCache,Hazelcast,Guava,Redis等. 本文通过Spring ...

  2. SpringBoot整合Redis缓存

    SpringBoot整合Redis缓存 一.缓存概念知识 1.是什么缓存 2.缓存的优缺点 3.为什么使用缓存 二.Redis概念知识 1.Redis简介 2.为什么用Redis作为缓存 3.Redi ...

  3. springboot整合持久层技术

    springboot整合持久层技术 整合jdbcTemplate 1.依赖导入 <?xml version="1.0" encoding="UTF-8"? ...

  4. Spring学习笔记(三十二)——SpringBoot中cache缓存的介绍和使用

    目录 Spring Boot与缓存 什么是cache java cache:JSR107 Spring缓存抽象 redis和cache的使用场景和区别 SpringBoot缓存的使用 0. 开启缓存的 ...

  5. .Net MVC Cache 缓存技术总结

    一.细说 ASP.NET Cache 及其高级用法 二..Net环境下的缓存技术介绍 (转) 三.asp.net中缓存的使用介绍一 四.HttpContext.Current.Cache 过期时间

  6. SpringBoot集成Cache缓存(Redis缓存,RedisTemplate方式)

    1.说明 SpringBoot集成Redis缓存, 首先创建一个Spring Boot工程, 使用Maven向导方式创建:SpringBoot集成Maven工程 然后引入redis的spring bo ...

  7. Spring Cache缓存技术,Cacheable、CachePut、CacheEvict、Caching、CacheConfig注解的使用

    前置知识: 在Spring Cache缓存中有两大组件CacheManager和Cache.在整个缓存中可以有多个CacheManager,他们负责管理他们里边的Cache.一个CacheManage ...

  8. 【SpringBoot】SpringBoot——整合持久层技术

    文章目录 5. 整合持久层技术 5.1 整合JdbcTemplate 5.2 整合MyBatis 5.3 Spring Data JPA 5.3.1 JPA.Spring Data.Spring Da ...

  9. 深度理解springboot集成cache缓存之源码解析

    一.案例准备 1.创建数据表(employee表) 2.创建Employee实体类封装数据库中的数据 @AllArgsConstructor @NoArgsConstructor @Data @ToS ...

  10. SpringBoot整合redis缓存(一)

    一. 准备工作 1.Linux系统 2.安装redis(也可以安装docker,然后再docker中装redis,本文章就直接用Linux安装redis做演示)redis下载地址:http://dow ...

最新文章

  1. 媒体行业注册什么企业邮箱比较好?
  2. CentOS安装MongoDB
  3. 《Oracle数据库管理与维护实战》——2.11 Oracle数据字典
  4. 奇偶数判断(信息学奥赛一本通-T1041)
  5. Autolayout屏幕适配——代码实现(苹果公司 / VFL语言 / 第三方框架Masonry)
  6. python函数式编程:apply, map, lambda和偏函数
  7. 4200有linux版本么,如何检查Linux版本
  8. SGuard64.exe(SGuardwnd) ACE-Guard Client EXE:造成磁盘经常读写,游戏卡顿,及解决方案
  9. 1.2 说说大学这滩泥淖——《逆袭大学》连载
  10. 华为盒子联网后显示无法连接服务器,【当贝市场】华为盒子连上无线后不能上网怎么办?...
  11. Selenium调用使用360浏览器,QQ浏览器,遨游浏览器,猎豹浏览器,Chromium
  12. matlab二维正弦曲线
  13. linux物联网项目,6个开源项目提升物联网开发效率
  14. Android 手机灭屏流程分析详解
  15. html格式显示图标异常,HTM或HTML图标变成无法显示和识别的解决方法大全
  16. 大数据24小时:地质局发布地质大数据共享平台,科大讯飞将语音识别植入腾讯小Q机器人
  17. 延时加载技术-----仿照手机淘宝网站图片延时加载
  18. JavaScript中的数组方法和循环
  19. 阻塞状态和等待状态的区别
  20. Rock5 KubeSphere常规部署

热门文章

  1. Linux下wm8978调试指南
  2. java 对错代厔_将汉语转换成拼音,实现拼音和中文双重登录
  3. 传奇世界修改服务器时间,《传奇世界手游时长版》测试结束公告
  4. 美团java笔试题_美团笔试题目(Java后端5题2小时)
  5. 串口(串行接口)相关概念
  6. 四象限法推导lm曲线_数据分析四象限法详解
  7. win10系统pyCharm安装及最新2018激活码
  8. vs2015中工具箱不显示DevExpress控件的解决办法
  9. Finalize()、Dispose(bool disposing)和Dispose()的使用场景与对比
  10. GPIO推挽输出和开漏输出模式区别详解