目前安全框架shiro使用较为广泛,其功能也比较强大。为了分布式session共享,通常的做法是将session存储在redis中,实现多个节点获取同一个session。此实现可以实现session共享,但session的特点是内存存储,就是为了高速频繁访问,每个请求都必须验证session是否存在是否过期,也从session中获取数据。这样导致一个页面刷新过程中的数十个请求会同时访问redis,在几毫秒内同时操作session的获取,修改,更新,保存,删除等操作,从而造成redis的并发量飙升,刷新一个页面操作redis几十到几百次。

为了解决由于session共享造成的redis高并发问题,很明显需要在redis之前做一次短暂的session缓存,如果该缓存存在就不用从redis中获取,从而减少同时访问redis的次数。如果做session缓存,主要有两种种方案,其实原理都相同:

  1>重写sessionManager的retrieveSession方法。首先从request中获取session,如果request中不存在再走原来的从redis中获取。这样可以让一个请求的多次访问redis问题得到解决,因为request的生命周期为浏览器发送一个请求到接收服务器的一次响应完成,因此,在一次请求中,request中的session是一直存在的,并且不用担心session超时过期等的问题。这样就可以达到有多少次请求就几乎有多少次访问redis,大大减少单次请求,频繁访问redis的问题。大大减少redis的并发数量。此实现方法最为简单。

 package cn.uce.web.login.filter;

 import java.io.Serializable;

 import javax.servlet.ServletRequest;

 import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey; public class ShiroSessionManager extends DefaultWebSessionManager {
/**
* 获取session
* 优化单次请求需要多次访问redis的问题
* @param sessionKey
* @return
* @throws UnknownSessionException
*/
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey); ServletRequest request = null;
if (sessionKey instanceof WebSessionKey) {
request = ((WebSessionKey) sessionKey).getServletRequest();
} if (request != null && null != sessionId) {
Object sessionObj = request.getAttribute(sessionId.toString());
if (sessionObj != null) {
return (Session) sessionObj;
}
} Session session = super.retrieveSession(sessionKey);
if (request != null && null != sessionId) {
request.setAttribute(sessionId.toString(), session);
}
return session;
}
}
 <!-- session管理器 -->
<bean id="sessionManager" class="cn.uce.web.login.filter.ShiroSessionManager">
<!-- 超时时间 -->
<property name="globalSessionTimeout" value="${session.global.timeout}" />
<!-- session存储的实现 -->
<property name="sessionDAO" ref="redisSessionDAO" />
<!-- <property name="deleteInvalidSessions" value="true"/> -->
<!-- 定时检查失效的session -->
<!-- <property name="sessionValidationSchedulerEnabled" value="true" /> -->
<!-- <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionIdCookieEnabled" value="true"/> -->
<property name="sessionIdCookie" ref="sessionIdCookie" />
</bean>

  2>session缓存于本地内存中。自定义cacheRedisSessionDao,该sessionDao中一方面注入cacheManager用于session缓存,另一方面注入redisManager用于session存储,当createSession和updateSession直接使用redisManager操作redis.保存session.当readSession先用cacheManager从cache中读取,如果不存在再用redisManager从redis中读取。注意:该方法最大的特点是session缓存的存活时间必须小于redis中session的存活时间,就是当redus的session死亡,cahe中的session一定死亡,为了保证这一特点,cache中的session的存活时间应该设置为s级,设置为1s比较合适,并且存活时间固定不能刷新,不能随着访问而延长存活。

/**
*
*/
package com.uc56.web.omg.authentication; import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set; import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.crazycake.shiro.SerializeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.uc56.web.omg.shiroredis.CustomRedisManager; /**
* 将从redis读取的session进行本地缓存,本地缓存失效时重新从redis读取并更新最后访问时间,解决shiro频繁读取redis问题
*/
public class CachingShiroSessionDao extends CachingSessionDAO { private static final Logger logger = LoggerFactory.getLogger(CachingShiroSessionDao.class); /** 保存到Redis中key的前缀 */
private String keyPrefix = ""; /**
* jedis 操作redis的封装
*/
private CustomRedisManager redisManager; /**
* 如DefaultSessionManager在创建完session后会调用该方法;
* 如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;
* 返回会话ID;主要此处返回的ID.equals(session.getId());
*/
@Override
protected Serializable doCreate(Session session) {
// 创建一个Id并设置给Session
Serializable sessionId = this.generateSessionId(session);
assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
} /**
* 重写CachingSessionDAO中readSession方法,如果Session中没有登陆信息就调用doReadSession方法从Redis中重读
*/
@Override
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session session = getCachedSession(sessionId);
if (session == null
|| session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
session = this.doReadSession(sessionId);
if (session == null) {
throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
} else {
// 缓存
cache(session, session.getId());
}
}
return session;
} /**
* 根据会话ID获取会话
*
* @param sessionId 会话ID
* @return
*/
@Override
protected Session doReadSession(Serializable sessionId) {
ShiroSession shiroSession = null;
try {
shiroSession = (ShiroSession)SerializeUtils.deserialize(redisManager.get(this.getByteKey(sessionId)));
if (shiroSession != null
&& shiroSession.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) != null) {
//检查session是否过期
shiroSession.validate();
// 重置Redis中Session的最后访问时间
shiroSession.setLastAccessTime(new Date());
this.saveSession(shiroSession);
logger.info("sessionId {} name {} 被读取并更新访问时间", sessionId, shiroSession.getClass().getName());
}
} catch (Exception e) {
if (!(e instanceof ExpiredSessionException)) {
logger.warn("读取Session失败", e);
}else {
logger.warn("session已失效:{}", e.getMessage());
}
} return shiroSession;
} //扩展更新缓存机制,每次请求不重新更新session,更新session会延长session的失效时间
@Override
public void update(Session session) throws UnknownSessionException {
doUpdate(session);
if (session instanceof ValidatingSession) {
if (((ValidatingSession) session).isValid()) {
//不更新ehcach中的session,使它在设定的时间内过期
//cache(session, session.getId());
} else {
uncache(session);
}
} else {
cache(session, session.getId());
}
} /**
* 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
*/
@Override
protected void doUpdate(Session session) {
//如果会话过期/停止 没必要再更新了
try {
if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
return;
}
} catch (Exception e) {
logger.error("ValidatingSession error");
} try {
if (session instanceof ShiroSession) {
// 如果没有主要字段(除lastAccessTime以外其他字段)发生改变
ShiroSession shiroSession = (ShiroSession) session;
if (!shiroSession.isChanged()) {
return;
}
shiroSession.setChanged(false);
this.saveSession(session);
logger.info("sessionId {} name {} 被更新", session.getId(), session.getClass().getName()); } else if (session instanceof Serializable) {
this.saveSession(session);
logger.info("sessionId {} name {} 作为非ShiroSession对象被更新, ", session.getId(), session.getClass().getName());
} else {
logger.warn("sessionId {} name {} 不能被序列化 更新失败", session.getId(), session.getClass().getName());
}
} catch (Exception e) {
logger.warn("更新Session失败", e);
}
} /**
* 删除会话;当会话过期/会话停止(如用户退出时)会调用
*/
@Override
protected void doDelete(Session session) {
try {
redisManager.del(this.getByteKey(session.getId()));
logger.debug("Session {} 被删除", session.getId());
} catch (Exception e) {
logger.warn("修改Session失败", e);
}
} /**
* 删除cache中缓存的Session
*/
public void uncache(Serializable sessionId) {
Session session = this.readSession(sessionId);
super.uncache(session);
logger.info("取消session {} 的缓存", sessionId);
} /**
*
* 统计当前活动的session
*/
@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = new HashSet<Session>(); Set<byte[]> keys = redisManager.keys(this.keyPrefix + "*");
if(keys != null && keys.size()>0){
for(byte[] key:keys){
Session s = (Session)SerializeUtils.deserialize(redisManager.get(key));
sessions.add(s);
}
} return sessions;
} /**
* save session
* @param session
* @throws UnknownSessionException
*/
private void saveSession(Session session) throws UnknownSessionException{
if(session == null || session.getId() == null){
logger.error("session or session id is null");
return;
} byte[] key = getByteKey(session.getId());
byte[] value = SerializeUtils.serialize(session);
session.setTimeout(redisManager.getExpire() * 1L);
this.redisManager.set(key, value, redisManager.getExpire());
} /**
* 将key转换为byte[]
* @param key
* @return
*/
private byte[] getByteKey(Serializable sessionId){
String preKey = this.keyPrefix + sessionId;
return preKey.getBytes();
} public CustomRedisManager getRedisManager() {
return redisManager;
} public void setRedisManager(CustomRedisManager redisManager) {
this.redisManager = redisManager; /**
* 初使化RedisManager
*/
this.redisManager.init();
} /**
* 获取 保存到Redis中key的前缀
* @return keyPrefix
*/
public String getKeyPrefix() {
return keyPrefix;
} /**
* 设置 保存到Redis中key的前缀
* @param keyPrefix 保存到Redis中key的前缀
*/
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
} }
/**
*
*/
package com.uc56.web.omg.authentication; import java.io.Serializable;
import java.util.Date;
import java.util.Map; import org.apache.shiro.session.mgt.SimpleSession; /**
* 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,
* 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回
*/
public class ShiroSession extends SimpleSession implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L; // 除lastAccessTime以外其他字段发生改变时为true
private boolean isChanged; public ShiroSession() {
super();
this.setChanged(true);
} public ShiroSession(String host) {
super(host);
this.setChanged(true);
} @Override
public void setId(Serializable id) {
super.setId(id);
this.setChanged(true);
} @Override
public void setStopTimestamp(Date stopTimestamp) {
super.setStopTimestamp(stopTimestamp);
this.setChanged(true);
} @Override
public void setExpired(boolean expired) {
super.setExpired(expired);
this.setChanged(true);
} @Override
public void setTimeout(long timeout) {
super.setTimeout(timeout);
this.setChanged(true);
} @Override
public void setHost(String host) {
super.setHost(host);
this.setChanged(true);
} @Override
public void setAttributes(Map<Object, Object> attributes) {
super.setAttributes(attributes);
this.setChanged(true);
} @Override
public void setAttribute(Object key, Object value) {
super.setAttribute(key, value);
this.setChanged(true);
} @Override
public Object removeAttribute(Object key) {
this.setChanged(true);
return super.removeAttribute(key);
} //更新最后访问时间不更新redis
@Override
public void touch() {
this.setChanged(false);
super.touch();
} /**
* 停止
*/
@Override
public void stop() {
super.stop();
this.setChanged(true);
} /**
* 设置过期
*/
@Override
protected void expire() {
this.stop();
this.setExpired(true);
} public boolean isChanged() {
return isChanged;
} public void setChanged(boolean isChanged) {
this.isChanged = isChanged;
} @Override
public boolean equals(Object obj) {
return super.equals(obj);
} @Override
protected boolean onEquals(SimpleSession ss) {
return super.onEquals(ss);
} @Override
public int hashCode() {
return super.hashCode();
} @Override
public String toString() {
return super.toString();
}
}
/**
*
*/
package com.uc56.web.omg.authentication; import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionFactory; public class ShiroSessionFactory implements SessionFactory { @Override
public Session createSession(SessionContext initData) {
ShiroSession session = new ShiroSession();
return session;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd"> <!-- 自定义权限定义 -->
<bean id="permissionsRealm" class="com.uc56.web.omg.realm.PermissionsRealm">
<!-- 缓存管理器 -->
<property name="cacheManager" ref="shiroRedisCacheManager" />
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 缓存管理器 -->
<property name="cacheManager" ref="shiroEhcacheManager" />
<!-- session 管理器 -->
<property name="sessionManager" ref="sessionManager" />
<property name="realm" ref="permissionsRealm"/>
</bean>
<!-- redis 缓存管理器 -->
<bean id="shiroRedisCacheManager" class="com.uc56.web.omg.shiroredis.CustomRedisCacheManager">
<property name="redisManager" ref="shiroRedisManager" />
</bean>
<bean id="shiroRedisManager" class="com.uc56.web.omg.shiroredis.CustomRedisManager">
<property name="host" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.password}" />
<property name="expire" value="${session.maxInactiveInterval}" />
<property name="timeout" value="${redis.timeout}" />
</bean>
<!-- 提供单独的redis Dao -->
<!-- <bean id="redisSessionDAO" class="com.uc56.web.omg.shiroredis.CustomRedisSessionDAO">
<property name="redisManager" ref="shiroRedisManager" />
<property name="keyPrefix" value="${session.redis.namespace}"></property>
</bean> -->
<bean id="sessionDao" class="com.uc56.web.omg.authentication.CachingShiroSessionDao">
<property name="keyPrefix" value="${session.redis.namespace}"/>
<property name="redisManager" ref="shiroRedisManager" />
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login/loginAuthc.do"></property>
<property name="successUrl" value="login/loginIndex.do"></property>
<property name="unauthorizedUrl" value="login/forbidden.do" />
<property name="filters">
<map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="LoginFailureCheck" value-ref="LoginFailureCheckFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<value>
/login/login.do=anon
/login/loginAuthc.do=anon
/login/authCheck.do=anon
/login/forbidden.do=anon
/login/validateUser.do=anon
/city/**=anon
/easyui-themes/**=anon
/images/**=anon
/jquery-easyui-1.5.1/**=anon
/scripts/**=anon
/users/**=anon
/**=LoginFailureCheck,authc,user
</value>
</property>
</bean>
<!-- 用户授权信息Cache, 采用EhCache,本地缓存最长时间应比中央缓存时间短一些,以确保Session中doReadSession方法调用时更新中央缓存过期时间 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:springShiro/spring-shiro-ehcache.xml"/>
</bean> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/>
<bean id="LoginFailureCheckFilter" class="com.uc56.web.omg.filter.LoginFailureCheckFilter">
<property name="casService" ref="casService"></property>
<property name="loginUserService" ref="loginUserService"></property>
</bean>
<bean id="loginUserService" class="com.uc56.web.omg.control.LoginUserService"/>
<bean id="passwordEncoder" class="com.uc56.core.security.MD5PasswordEncoder"/> <!-- session管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 超时时间 -->
<property name="globalSessionTimeout" value="${session.global.timeout}" />
<property name="sessionFactory" ref="sessionFactory"/>
<!-- session存储的实现 -->
<property name="sessionDAO" ref="sessionDao" />
<!-- 定时检查失效的session -->
<property name="sessionValidationSchedulerEnabled" value="true" />
<!-- <property name="sessionValidationInterval" value="180000"/> -->
<property name="sessionIdCookie" ref="sharesession" />
<property name="sessionListeners">
<list>
<bean class="com.uc56.web.omg.authentication.listener.ShiroSessionListener"/>
</list>
</property>
</bean> <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
<bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的name,对应的默认是JSESSIONID -->
<constructor-arg name="name" value="redisManager.sessionname" />
<!-- jsessionId的path为/用于多个系统共享jsessionId -->
<property name="path" value="/" />
<property name="httpOnly" value="false"/>
</bean> <!-- 自定义Session工厂方法 返回会标识是否修改主要字段的自定义Session-->
<bean id="sessionFactory" class="com.uc56.web.omg.authentication.ShiroSessionFactory"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shirocache">
<!-- <diskStore path="java.io.tmpdir"/>
登录记录缓存 锁定10分钟
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro_cache"
maxElementsInMemory="2000"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
maxElementsOnDisk="0"
overflowToDisk="true"
memoryStoreEvictionPolicy="FIFO"
statistics="true">
</cache> -->
<!-- <defaultCache
在内存中最大的对象数量
maxElementsInMemory="10000"
设置元素是否永久的
eternal="false"
设置元素过期前的空闲时间
timeToIdleSeconds="60"
缓存数据的生存时间(TTL)
timeToLiveSeconds="60"
是否当memory中的数量达到限制后,保存到Disk
overflowToDisk="false"
diskPersistent="false"
磁盘失效线程运行时间间隔,默认是120秒
diskExpiryThreadIntervalSeconds="10"
缓存满了之后的淘汰算法: LRU(最近最少使用)、FIFO(先进先出)、LFU(较少使用)
memoryStoreEvictionPolicy="LRU"
/> -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToLiveSeconds="60"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="10"
/>
</ehcache>

此设计中最重要的一点就是:

  1.cache中的session只存储不更新,也就是说每次访问不会刷新缓存中的session,cache中的session一定会在设定的时间中过期
  2.cache中设置的session的时间一定要短于redis中存储的session,保证redis中session过期是,cache中的session一定过期

  3.redis中的session更新会清楚cache中的session保证session一直性

  

  

shiro之redis频繁访问问题的更多相关文章

  1. Shiro经过Redis管理会话实现集群(转载)

    原文:http://www.myexception.cn/software-architecture-design/1815507.html Shiro通过Redis管理会话实现集群 写在前面 1.在 ...

  2. Shiro权限管理框架(二):Shiro结合Redis实现分布式环境下的Session共享

    首发地址:https://www.guitu18.com/post/2019/07/28/44.html 本篇是Shiro系列第二篇,使用Shiro基于Redis实现分布式环境下的Session共享. ...

  3. 【nodejs代理服务器四】代理服务器增加频繁访问的ip加入黑名单

    问题 渗透者在扫站的时候会频繁请求,我们可以做一些策略来封堵这些频繁访问的ip,把ip加入黑名单. 策略 2秒之内访问次数超过100,加入黑名单. 实现思路 初次访问把访问Ip作为键,访问ip,时间, ...

  4. PHP禁止同一IP频繁访问以防止网站被防攻击或采集的代码

    PHP禁止同一IP频繁访问以防止网站被防攻击或采集的代码 <?php /* *通过禁止IP频繁访问防止网站被防攻击代码*design by www.scutephp.com*/header('C ...

  5. shiro使用redis作为缓存,出现要清除缓存时报错 java.lang.Exception: Failed to deserialize at org.crazycake.shiro.SerializeUtils.deserialize(SerializeUtils.java:41) ~[shiro-redis-2.4.2.1-RELEASE.jar:na]

    shiro使用redis作为缓存,出现要清除缓存时报错 java.lang.Exception: Failed to deserialize at org.crazycake.shiro.Serial ...

  6. .Net频繁访问数据库的优化探究(一)

    知识点:DataTable.Linq.lamda表达式.Cache 场景:查询部门的所有员工和管理员,并分配相应的权限 实现过程一般为:查询部门,遍历部门(查询员工.分配权限.查询管理员.分配权限) ...

  7. C基础 redis缓存访问

    引言 先说redis安装, 这里采用的环境是. Linux version --generic (buildd@lgw01-) (gcc version (Ubuntu -14ubuntu2) ) # ...

  8. linux下redis安装访问

    下载编译安装 wget http://download.redis.io/releases/redis-3.0.1.tar.gz tar xvf redis-3.0.1.tar.gz mv redis ...

  9. Docker---(8)Docker启动Redis后访问不了

    原文:Docker---(8)Docker启动Redis后访问不了 版权声明:欢迎转载,请标明出处,如有问题,欢迎指正!谢谢!微信:w1186355422 https://blog.csdn.net/ ...

随机推荐

  1. Chisel3 - Tutorial - VendingMachine

    https://mp.weixin.qq.com/s/tDpUe9yhwC-2c1VqisFzMw   演示如何使用状态机.   参考链接: https://github.com/ucb-bar/ch ...

  2. JavaScript (四) js的基本语法 - - 函数练习、arguments、函数定义、作用域、预解析

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.函数练习 1.求最值 // - 求2个数中的最大值 function getMax(num1, nu ...

  3. Java 第十一届 蓝桥杯 省模拟赛 合法括号序列

    合法括号序列 题目 问题描述 由1对括号,可以组成一种合法括号序列:(). 由2对括号,可以组成两种合法括号序列:()().(()). 由4对括号组成的合法括号序列一共有多少种? 答案提交 这是一道结 ...

  4. Java实现 蓝桥杯 算法训练 1的个数

    试题 算法训练 1的个数 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 输入正整数n,判断从1到n之中,数字1一共要出现几次.例如1123这个数,则出现了两次1.例如15,那么从1 ...

  5. Android studio环境配置(运行报错)

    报错的种类有很多,下面的方法能解决大多数: 所有路径不能用中文,不能有空格,逗号句号也不能用中文,项目文件路径也不行 首先要配置Java环境,这个就不多说了, 这里有以下JAVA_HOME的配置,下图 ...

  6. WinForm通用自动更新AutoUpdater项目实战

    目前我们做的上位机项目还是以Winform为主,在实际应用过程中,可能还会出现一些细节的修改.对于这种情况,如果上位机带有自动更新功能,我们只需要将更新后的应用程序打包放在指定的路径下,可以让用户自己 ...

  7. is ==小数据池编码解码

    ==      比较      比较的是两边的值    is      比较      比较的是内存地址   判断两个东西指向的是不是同一个对象         取内存地址 id() 小数据池     ...

  8. Spring:工厂模式哪里解耦了?

    菜瓜:我一定是太菜了,为什么别人说Spring屏蔽了new关键字创建对象就很丝滑?我完全get不到这个操作的好处啊,我自己写new它也很香啊 水稻:emmmm,换个角度想啊,如果把现在用的注解@Aut ...

  9. C++ 网教通直播刷屏反制 (思路启发)

    前言 那些手动刷屏的你们弱爆了! 直播间的讨论区是用来讨论的, 下次谁再在上课时间大量刷屏,就以暴制暴! 思路启发 #define VK_CTRL 0x11 //... keybd_event(VK_ ...

  10. 当小程序的flex布局遇到button时,justify-content不起作用的原因及解决方案

    当小程序的flex布局遇到button时 发现justify-content不起作用,无论怎么设置都是space-around的效果. 经过排查,发现原因是小程序button中的默认样式中的margi ...