spring-data-redis时效设置
本人转自http://hbxflihua.iteye.com/blog/2320584#bc2396403
spring目前在@Cacheable和@CacheEvict等注解上不支持缓存时效设置,只允许通过配置文件设置全局时效。这样就很不方便设定时间。比如系统参数和业务数据的时效是不一样的,这给程序开发造成很大的困扰。不得已,我重写了spring的这两个注解。以下是具体实现。
首先定义@Cacheable和@CacheEvict注解类。
- package com.lh.common.annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- import com.rd.ifaes.common.dict.ExpireTime;
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD})
- public @interface Cacheable {
- public String key() default ""; // 缓存key
- public ExpireTime expire() default ExpireTime.NONE; // 缓存时效,默认无限期
- }
- package com.lh.common.annotation;
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Inherited;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- /**
- * 缓存清除
- * @author lh
- * @version 3.0
- * @since 2016-8-28
- *
- */
- @Target({ ElementType.METHOD })
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @Documented
- public @interface CacheEvict {
- String key() default "";// 缓存key
- }
具体的切面代码(CacheAspect.java)如下:
- package com.lh.common.annotation;
- import java.util.List;
- import java.util.Set;
- import java.util.concurrent.TimeUnit;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.core.ValueOperations;
- import org.springframework.stereotype.Component;
- import org.springframework.util.CollectionUtils;
- import com.rd.ifaes.common.util.ReflectionUtils;
- import com.rd.ifaes.common.util.StringUtils;
- import com.rd.ifaes.core.core.util.CacheUtils;
- @Aspect
- @Component
- public class CacheAspect {
- @SuppressWarnings("rawtypes")
- @Autowired
- private RedisTemplate redisTemplate;
- @Around("@annotation(cache)")
- public Object cacheable(final ProceedingJoinPoint pjp, Cacheable cache) throws Throwable {
- String key = getCacheKey(pjp, cache.key());
- // //方案一:使用自定义缓存工具类操作缓存
- // Object value = CacheUtils.getObj(key);// 从缓存获取数据
- // if (value != null) {
- // return value; // 如果有数据,则直接返回
- // }
- // value = pjp.proceed(); // 缓存,到后端查询数据
- // if (value != null) {
- // CacheUtils.set(key, value, cache.expire());
- // }
- // 方案二:使用redisTemplate操作缓存
- @SuppressWarnings("unchecked")
- ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
- Object value = valueOper.get(key); // 从缓存获取数据
- if (value != null) {
- return value; // 如果有数据,则直接返回
- }
- value = pjp.proceed(); // 缓存,到后端查询数据
- CacheUtils.set(key, value, cache.expire());
- if (cache.expire().getTime() <= 0) { // 如果没有设置过期时间,则无限期缓存
- valueOper.set(key, value);
- } else { // 否则设置缓存时间
- valueOper.set(key, value, cache.expire().getTime(), TimeUnit.SECONDS);
- }
- return value;
- }
- @SuppressWarnings("unchecked")
- @Around("@annotation(evict)")
- public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict evict) throws Throwable {
- Object value = pjp.proceed(); // 执行方法
- String key = getCacheKey(pjp, evict.key());
- // //方案一:使用自定义缓存工具类操作缓存
- // CacheUtils.del(key);
- // 方案二:使用redisTemplate操作缓存
- if (evict.key().equals(key)) {// 支持批量删除
- Set<String> keys = redisTemplate.keys(key.concat("*"));
- redisTemplate.delete(keys);
- }else{
- redisTemplate.delete(key);
- }
- return value;
- }
- /**
- * 获取缓存的key值
- *
- * @param pjp
- * @param key
- * @return
- */
- private String getCacheKey(ProceedingJoinPoint pjp, String key) {
- StringBuilder buf = new StringBuilder();
- Object[] args = pjp.getArgs();
- if(StringUtils.isNotBlank(key)){
- buf.append(key);
- List<String> annoParamNames = AopUtils.getAnnoParams(key);
- String[] methodParamNames = AopUtils.getMethodParamNames(AopUtils.getMethod(pjp));
- if(!CollectionUtils.isEmpty(annoParamNames)){
- for (String ap : annoParamNames) {
- String paramValue = "";
- for (int i = 0; i < methodParamNames.length; i++) {
- if(ap.startsWith(methodParamNames[i])){
- Object arg = args[i];
- if (ap.contains(".")) {
- paramValue = String.valueOf(ReflectionUtils.invokeGetter(arg, ap.substring(ap.indexOf(".") + 1)));
- } else {
- paramValue = String.valueOf(arg);
- }
- }
- }
- int start = buf.indexOf("{" + ap);
- int end = start + ap.length() + 2;
- buf = buf.replace(start, end, paramValue);
- }
- }
- }else{
- buf.append(pjp.getSignature().getDeclaringTypeName()).append(":").append(pjp.getSignature().getName());
- for (Object arg : args) {
- buf.append(":").append(arg.toString());
- }
- }
- return buf.toString();
- }
- }
里面使用到AopUtils.java和ExpireTime.java两个类,具体代码如下:
- package com.lh.common.annotation;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.Signature;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.springframework.asm.*;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.reflect.Method;
- import java.lang.reflect.Modifier;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- * 切面编程工具类
- * @author lh
- * @version 3.0
- * @since 2016-8-26
- */
- public class AopUtils {
- /**
- * <p>获取方法的参数名</p>
- *
- * @param m
- * @return
- */
- public static String[] getMethodParamNames(final Method m) {
- final String[] paramNames = new String[m.getParameterTypes().length];
- final String n = m.getDeclaringClass().getName();
- final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
- String className = m.getDeclaringClass().getSimpleName();
- ClassReader cr = null;
- InputStream resourceAsStream = null;
- try {
- // cr = new ClassReader(n);
- // String filePathName = Class.forName(n).getResource("EDayHqbProcessManagerImpl.class").getPath();
- resourceAsStream = Class.forName(n).getResourceAsStream(className + ".class");
- cr = new ClassReader(resourceAsStream);
- // cr = new ClassReader(ClassLoader.getSystemResourceAsStream(n + ".class"));
- } catch (IOException e) {
- //e.printStackTrace();
- // Exceptions.uncheck(e);
- } catch (ClassNotFoundException e) {
- //e.printStackTrace();
- } finally {
- if (resourceAsStream != null) {
- try {
- resourceAsStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- assert cr != null;
- cr.accept(new ClassVisitor(Opcodes.ASM4, cw) {
- @Override
- public MethodVisitor visitMethod(final int access,
- final String name, final String desc,
- final String signature, final String[] exceptions) {
- final Type[] args = Type.getArgumentTypes(desc);
- // 方法名相同并且参数个数相同
- if (!name.equals(m.getName())
- || !sameType(args, m.getParameterTypes())) {
- return super.visitMethod(access, name, desc, signature,
- exceptions);
- }
- MethodVisitor v = cv.visitMethod(access, name, desc, signature,
- exceptions);
- return new MethodVisitor(Opcodes.ASM4, v) {
- @Override
- public void visitLocalVariable(String name, String desc,
- String signature, Label start, Label end, int index) {
- int i = index - 1;
- // 如果是静态方法,则第一就是参数
- // 如果不是静态方法,则第一个是"this",然后才是方法的参数
- if (Modifier.isStatic(m.getModifiers())) {
- i = index;
- }
- if (i >= 0 && i < paramNames.length) {
- paramNames[i] = name;
- }
- super.visitLocalVariable(name, desc, signature, start,
- end, index);
- }
- };
- }
- }, 0);
- return paramNames;
- }
- /**
- * <p>比较参数类型是否一致</p>
- *
- * @param types asm的类型({@link Type})
- * @param clazzes java 类型({@link Class})
- * @return
- */
- private static boolean sameType(Type[] types, Class<?>[] clazzes) {
- // 个数不同
- if (types.length != clazzes.length) {
- return false;
- }
- for (int i = 0; i < types.length; i++) {
- if (!Type.getType(clazzes[i]).equals(types[i])) {
- return false;
- }
- }
- return true;
- }
- /**
- * 取得切面调用的方法
- * @param pjp
- * @return
- */
- public static Method getMethod(ProceedingJoinPoint pjp){
- Signature sig = pjp.getSignature();
- MethodSignature msig = null;
- if (!(sig instanceof MethodSignature)) {
- throw new IllegalArgumentException("该注解只能用于方法");
- }
- msig = (MethodSignature) sig;
- Object target = pjp.getTarget();
- Method currentMethod = null;
- try {
- currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
- } catch (NoSuchMethodException e) {
- } catch (SecurityException e) {
- }
- return currentMethod;
- }
- public static List<String> getMatcher(String regex, String source) {
- List<String> list = new ArrayList<String>();
- Pattern pattern = Pattern.compile(regex);
- Matcher matcher = pattern.matcher(source);
- while (matcher.find()) {
- list.add(matcher.group());
- }
- return list;
- }
- /**
- * 取得注解参数
- (?=exp) 匹配exp前面的位置
- (?<=exp) 匹配exp后面的位置
- (?!exp) 匹配后面跟的不是exp的位置
- (?<!exp) 匹配前面不是exp的位置
- * @param managers
- * @return
- */
- public static List<String> getAnnoParams(String source){
- String regex = "(?<=\\{)(.+?)(?=\\})";
- return getMatcher(regex, source);
- }
- }
- package com.lh.common.dict;
- /**
- * 失效时间枚举类
- * @author lh
- * @version 3.0
- * @since 2016-8-25
- *
- */
- public enum ExpireTime {
- /**
- * 无固定期限
- */
- NONE(0, "无固定期限")
- /**
- * 1秒钟
- */
- ,ONE_SEC(1, "1秒钟")
- /**
- * 5秒钟
- */
- ,FIVE_SEC(5, "5秒钟")
- /**
- * 10秒钟
- */
- ,TEN_SEC(10, "10秒钟")
- /**
- * 30秒钟
- */
- ,HALF_A_MIN(30, "30秒钟")
- /**
- * 1分钟
- */
- ,ONE_MIN(60, "1分钟")
- /**
- * 5分钟
- */
- ,FIVE_MIN(5 * 60, "5分钟")
- /**
- * 10分钟
- */
- ,TEN_MIN(10 * 60, "10分钟")
- /**
- * 20分钟
- */
- ,TWENTY_MIN(20 * 60, "20分钟")
- /**
- * 30分钟
- */
- ,HALF_AN_HOUR(30 * 60, "30分钟")
- /**
- * 1小时
- */
- ,ONE_HOUR(60 * 60, "1小时")
- /**
- * 1天
- */
- ,ONE_DAY(24 * 60 * 60, "1天")
- /**
- * 1个月
- */
- ,ONE_MON(30 * 24 * 60 * 60, "1个月")
- /**
- * 1年
- */
- ,ONE_YEAR(365 * 24 * 60 * 60, "1年")
- ;
- /**
- * 时间
- */
- private final int time;
- /**
- * 描述
- */
- private final String desc;
- ExpireTime(int time, String desc) {
- this.time = time;
- this.desc = desc;
- }
- /**
- * 获取具体时间
- * @return
- */
- public int getTime() {
- return time;
- }
- /**
- * 获取时间描述信息
- * @return
- */
- public String getDesc() {
- return desc;
- }
- /**
- * 根据时间匹配失效期限
- * @param time
- * @return
- */
- public static ExpireTime match(int time){
- if(NONE.getTime() == time){
- return NONE;
- }else if(ONE_SEC.getTime() == time){
- return ONE_SEC;
- }else if(FIVE_SEC.getTime() == time){
- return FIVE_SEC;
- }else if(TEN_SEC.getTime() == time){
- return TEN_SEC;
- }else if(HALF_A_MIN.getTime() == time){
- return HALF_A_MIN;
- }else if(ONE_MIN.getTime() == time){
- return ONE_MIN;
- }else if(FIVE_MIN.getTime() == time){
- return FIVE_MIN;
- }else if(TEN_MIN.getTime() == time){
- return TEN_MIN;
- }else if(TWENTY_MIN.getTime() == time){
- return TWENTY_MIN;
- }else if(HALF_AN_HOUR.getTime() == time){
- return HALF_AN_HOUR;
- }else if(ONE_HOUR.getTime() == time){
- return ONE_HOUR;
- }else if(ONE_DAY.getTime() == time){
- return ONE_DAY;
- }else if(ONE_MON.getTime() == time){
- return ONE_MON;
- }else if(ONE_YEAR.getTime() == time){
- return ONE_YEAR;
- }
- return HALF_AN_HOUR;
- }
- }
配置中的RdRedisCache.java 代码如下:
- package com.lh.common.jedis;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import net.sf.ehcache.Element;
- import org.springframework.cache.Cache;
- import org.springframework.cache.support.SimpleValueWrapper;
- import org.springframework.dao.DataAccessException;
- import org.springframework.data.redis.connection.RedisConnection;
- import org.springframework.data.redis.core.RedisCallback;
- import org.springframework.data.redis.core.RedisTemplate;
- public class RdRedisCache implements Cache {
- private RedisTemplate<String, Object> redisTemplate;
- private String name;
- public RedisTemplate<String, Object> getRedisTemplate() {
- return redisTemplate;
- }
- public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
- this.redisTemplate = redisTemplate;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String getName() {
- return this.name;
- }
- @Override
- public Object getNativeCache() {
- return this.redisTemplate;
- }
- @Override
- public ValueWrapper get(Object key) {
- final String keyf = obj2Str(key);
- Object object = null;
- object = redisTemplate.execute(new RedisCallback<Object>() {
- public Object doInRedis(RedisConnection connection)
- throws DataAccessException {
- byte[] key = keyf.getBytes();
- byte[] value = connection.get(key);
- if (value == null) {
- return null;
- }
- return toObject(value);
- }
- });
- return (object != null ? new SimpleValueWrapper(object) : null);
- }
- @Override
- public void put(Object key, Object value) {
- final String keyf = obj2Str(key);
- final Object valuef = value;
- final long liveTime = 86400;
- redisTemplate.execute(new RedisCallback<Long>() {
- public Long doInRedis(RedisConnection connection)
- throws DataAccessException {
- byte[] keyb = keyf.getBytes();
- byte[] valueb = toByteArray(valuef);
- connection.set(keyb, valueb);
- if (liveTime > 0) {
- connection.expire(keyb, liveTime);
- }
- return 1L;
- }
- });
- }
- public String obj2Str(Object key){
- String keyStr = null;
- if(key instanceof Integer){
- keyStr = ((Integer)key).toString();
- }else if(key instanceof Long){
- keyStr = ((Long)key).toString();
- }else {
- keyStr = (String)key;
- }
- return keyStr;
- }
- /**
- * 描述 : <Object转byte[]>. <br>
- * <p>
- * <使用方法说明>
- * </p>
- *
- * @param obj
- * @return
- */
- private byte[] toByteArray(Object obj) {
- byte[] bytes = null;
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- try {
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(obj);
- oos.flush();
- bytes = bos.toByteArray();
- oos.close();
- bos.close();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- return bytes;
- }
- /**
- * 描述 : <byte[]转Object>. <br>
- * <p>
- * <使用方法说明>
- * </p>
- *
- * @param bytes
- * @return
- */
- private Object toObject(byte[] bytes) {
- Object obj = null;
- try {
- ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
- ObjectInputStream ois = new ObjectInputStream(bis);
- obj = ois.readObject();
- ois.close();
- bis.close();
- } catch (IOException ex) {
- ex.printStackTrace();
- } catch (ClassNotFoundException ex) {
- ex.printStackTrace();
- }
- return obj;
- }
- @Override
- public void evict(Object key) {
- final String keyf = obj2Str(key);
- redisTemplate.execute(new RedisCallback<Long>() {
- public Long doInRedis(RedisConnection connection)
- throws DataAccessException {
- return connection.del(keyf.getBytes());
- }
- });
- }
- @Override
- public void clear() {
- redisTemplate.execute(new RedisCallback<String>() {
- public String doInRedis(RedisConnection connection)
- throws DataAccessException {
- connection.flushDb();
- return "ok";
- }
- });
- }
- @Override
- public <T> T get(Object key, Class<T> type) {
- ValueWrapper wrapper = get(key);
- return wrapper == null ? null : (T) wrapper.get();
- }
- @Override
- public ValueWrapper putIfAbsent(Object key, Object value) {
- synchronized (key) {
- ValueWrapper wrapper = get(key);
- if (wrapper != null) {
- return wrapper;
- }
- put(key, value);
- return toWrapper(new Element(key, value));
- }
- }
- private ValueWrapper toWrapper(Element element) {
- return (element != null ? new SimpleValueWrapper(element.getObjectValue()) : null);
- }
- }
spring配置文件的相关配置如下:
- <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
- <property name="maxIdle" value="${redis.pool.maxIdle}" /> <!-- 最大能够保持idel状态的对象数 -->
- <property name="maxTotal" value="${redis.pool.maxTotal}" /> <!-- 最大分配的对象数 -->
- <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> <!-- 当调用borrow Object方法时,是否进行有效性检查 -->
- </bean>
- <!-- jedisPool init -->
- <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
- <constructor-arg index="0" ref="jedisPoolConfig" />
- <constructor-arg index="1" value="${redis.host}" type="String" />
- <constructor-arg index="2" value="${redis.port}" type="int" />
- </bean>
- <!-- jedis单机配置 -->
- <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
- <property name="hostName" value="${redis.host}" />
- <property name="port" value="${redis.port1}" />
- <property name="timeout" value="${redis.timeout}" />
- <property name="poolConfig" ref="jedisPoolConfig" />
- </bean>
- <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
- <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
- p:connectionFactory-ref="jedisConnFactory" p:keySerializer-ref="stringRedisSerializer" />
- <!-- spring自己的缓存管理器 -->
- <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
- <property name="caches">
- <set>
- <bean class="com.lh.common.jedis.RdRedisCache" p:redis-template-ref="redisTemplate" p:name="sysCache"/>
- </set>
- </property>
- </bean>
- <!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 -->
- <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true" />
spring-data-redis时效设置的更多相关文章
- spring data redis RedisTemplate操作redis相关用法
http://blog.mkfree.com/posts/515835d1975a30cc561dc35d spring-data-redis API:http://docs.spring.io/sp ...
- Spring Data Redis—Pub/Sub(附Web项目源码)
一.发布和订阅机制 当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher). 而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE ...
- Spring Data Redis—Pub/Sub(附Web项目源码) (转)
一.发布和订阅机制 当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher). 而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE ...
- Spring Data Redis 详解及实战一文搞定
SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能.它提供了与商店互动的低级别和高级别抽象,使用户免受 ...
- Spring Data Redis 让 NoSQL 快如闪电(2)
[编者按]本文作者为 Xinyu Liu,文章的第一部分重点概述了 Redis 方方面面的特性.在第二部分,将介绍详细的用例.文章系国内 ITOM 管理平台 OneAPM 编译呈现. 把 Redis ...
- Spring Data Redis 让 NoSQL 快如闪电 (1)
[编者按]本文作者为 Xinyu Liu,详细介绍了 Redis 的特性,并辅之以丰富的用例.在本文的第一部分,将重点概述 Redis 的方方面面.文章系国内 ITOM 管理平台 OneAPM 编译呈 ...
- spring data redis使用1——连接的创建
spring data redis集成了几个Redis客户端框架,Jedis , JRedis (Deprecated since 1.7), SRP (Deprecated since 1.7) a ...
- Spring Data Redis学习
本文是从为知笔记上复制过来的,懒得调整格式了,为知笔记版本是带格式的,内容也比这里全.点这里 为知笔记版本 Spring Data Redis 学习 Version 1.8.4.Release 前言 ...
- Spring Data Redis 2.x 中 RedisConfiguration 类的新编写方法
在 Spring Data Redis 1.x 的时候,我们可能会在项目中编写这样一个RedisConfig类: @Configuration @EnableCaching public class ...
- Spring Boot使用Spring Data Redis操作Redis(单机/集群)
说明:Spring Boot简化了Spring Data Redis的引入,只要引入spring-boot-starter-data-redis之后会自动下载相应的Spring Data Redis和 ...
随机推荐
- poj 2226 Muddy Fields(水二分图)
Rain has pummeled the cows' field, a rectangular grid of R rows and C columns (1 <= R <= 50, 1 ...
- MT【254】值域包含值域
已知函数$f(x)=x-\dfrac{1}{1+x},g(x)=x^2-2ax+4,$若对任意$x_1\in[0,1]$,存在$x_2\in[1,2]$,使得$f(x_1)=g(x_2)$,则实数$a ...
- 【POJ1037】A decorative fence(DP)
BUPT2017 wintertraining(15) #6C 题意 给长度n的数列,1,2,..,n,按依次递增递减排序,求字典序第k小的排列. 题解 dp. up[i][j]表示长度为j,以第i小 ...
- Python将是人工智能时代的最佳编程语言
Python将是人工智能时代的最佳编程语言 移动互联网取代PC互联网领跑在互联网时代的最前沿,Android和iOS一度成为移动互联网应用平台的两大霸主,成为移动开发者首选的两门技术,HTML5以其跨 ...
- python中lambda的使用
为什么我们需要lambda? 既然有了def可以用来定义函数,我们为什么还需要lambda来定义.根据我的使用情况我认为lambda的优点在于: 非常适合用来构造只使用一次的函数,可以是匿名函数 有利 ...
- [luogu5003]跳舞的线【动态规划】
题目描述 线现在在一个地图上,它正在(1,1)上(左上角),最终要去到(M,N)上.它不但只能往下或往右走,还只能在整数格子上移动. Imakf有的时候想要炫技,又有时想偷懒,所以他会告诉你这张地图的 ...
- JS日期选择器
浏览器自带 浏览器自带日期控件,使用<input type="date">时,点击后会弹出. 1:EDGE 2:火狐 3:谷歌 三种都不一样.略胜于无 练习 模仿火狐 ...
- 「HNOI2016」树 解题报告
「HNOI2016」树 事毒瘤题... 我一开始以为每次把大树的子树再接给大树,然后死活不知道咋做,心想怕不是个神仙题哦 然后看题解后才发现是把模板树的子树给大树,虽然思维上难度没啥了,但是还是很难写 ...
- cf351B Jeff and Furik (树状数组)
逆序对数=0的时候,这个数列是有序的 然后交换相邻的,看哪个比较大,逆序对数会加1或减1 Jeff用的是最优策略所以他肯定让逆序对数-1 设f[i]表示Jeff操作前,逆序对数为i,最终的期望次数 那 ...
- [luogu5077][Tweetuzki 爱等差数列]
题目链接 思路 数学题 首先列出等差数列求和的式子. \[S = \frac{(n + m)(n - m + 1)}{2}(n为末项,m为首项)\] \[S * 2= (n + m)(n - m + ...