最近做流量项目,系统用redis作为缓存,花了几天时间学习了redis的基本内容,整理和总结一下

Redis简介

Redis是一种非关系型数据库(即nosql),存储类型为键值(key-value),其中value支持多种数据类型,包括string、hash、list、set、zset,估计这也是流量项目使用redis的原因之一

Redis适用场景

1、取最新N个数据的操作
2、排行榜应用(好像和第一个有点重复)
3、需要精确设定过期时间的应用
4、计数器的应用(新浪微博主要使用)
5、Uniq操作,获取某段时间所有数据排重值
6、实时系统,反垃圾系统
7、Pub/Sub构建实时消息系统
8、构建队列系统
9、缓存
当前公司使用的就是Redis的第9个应用,其他功能及应用还在学习ing

Redis安装


目前Redis稳定版已经到了3.0,下载地址:http://redis.io/download
下载后解压并编译

1
2
3
$ tar xzf redis-3.0.1.tar.gz
$ cd redis-3.0.1
$ make

现在可以启动redis服务了

1
$ src/redis-server

然后另开一个窗口

1
$ src/redis-cli

显示

1
127.0.0.1:6379>

说明已经成功了

如果不想另开窗口,可将redis.conf中属性”daemonize no”中no改为yes[指后台运行]

第一个示例

1
2
3
4
127.0.0.1:6379> set first "Hello World"
OK
127.0.0.1:6379> get first
"Hello World"

Redis参数配置

折腾了3天,遇到各种超级坑的问题,总算把hexo+github pages的框架搭建好了,逃离了WordPress →_→

git客户端

git有个windows的(git for windows)可视化操作工具:msysgit,github也有一个的客户端github for windows,不知道是不是两者不兼容,两者一起装的时候,在安装hexo后,会报下面的错

1
2
$ hexo
sh.exe": hexo: command not found

找各种方法都不行,后来把两个客户端都卸载了,只安装msysgit再装hexo就OK了

github pages

这是一个疑问,到现在还没有完全弄明白,就是在新建一个github工程后,直接在master分支新建文件作为github pages的项目,包括我在网上看到各种步骤和指引都是这样,但是部署好以后,一直报github 404
我的解决办法是在项目的setting的里面,找到github pages的选项,选择Automatic page generator,自动产生一些github pages的文件,然后再git上传文件覆盖它,再部署就可以了

hexo deploy

在hexo安装好以后,把本地生成的文件传到远程github上,执行hexo deploy,不报任何错,然始终传不上去

重新配置根目录下的_config.yml即可

1
2
3
4
deploy: 
type: git
repository: git@github.com:corbam/myzchi.github.com.git
branch: gh-pages

其中第二项的repository,很多人配置成

1
repository: https://github.com/xxx/xxx.github.io.git

这种https的url配置后我是上传不上去的,只能安装第一种即ssh的url方式配置

反射是指可以获取任意一个的类或者对象(包括运行状态中)的方法和属性
1.Class是一个描述类的类,封装了描述方法的Method,描述字段的Field,描述构造器Constructor等属性

2.如何得到Class对象
2.1 Person.class
2.2 person.getclass
2.3 Class.forName(“com.person”)

3.关于Method
3.1如何获取method
1)getDeclareMethods:得到Method的数组
2)getDeclareMethod(String methodName,Class…parameterTypes)
3.2 如果调用method
1)如果方法是private,先调用method的setAccssible(true),使其变为可访问
2)method.invoke(obj…)

4.关于Field
4.1如何获取Field:getField(String fieldName)
4.2如何获取Field的值
1)setAccssible(true)
2)field.set(obj…)
4.3如何设置Field的值
field.set(obj)

安装MySQL时无法启动服务,在安装mysql时,到最后一步执行时,在start service,出现如下错误:

1
Could not start the service

在网上找了很多方法,包括要把所有相关的文件夹、服务、注册表等都要卸载干净等等,但是都无效。

后来在一个不太显眼的地方找到一个方法,解决办法如下:

使用services.msc打开服务窗口,查看MySQL service是否已经存在。如已经存在并已启动,则先停止该服务,然后到注册表

1
("HKEY_LOCAL_MACHINE/SYSTEM /CurrentControlSet/Services")

中删除对应服务,并使用命令sc delete MySQL,然后继续进行安装,就OK了。

拦截器和过滤器的区别:

  • 1.拦截器是基于java的反射机制的,而过滤器是基于函数回调
  • 2.过滤器依赖与servlet容器,而拦截器不依赖与servlet容器
  • 3.拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用
  • 4.拦截器可以访问action上下文、值栈里的对象,而过滤器不能
  • 5.在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次

拦截器 :是在面向切面编程的就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。

下面通过实例来看一下过滤器和拦截器的区别:

使用拦截器进行/admin 目录下jsp页面的过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<package name="newsDemo" extends="struts-default"   
namespace="/admin">
<interceptors>
<interceptor name="auth" class="com.test.news.util.AccessInterceptor" />
<interceptor-stack name="authStack">
<interceptor-ref name="auth" />
</interceptor-stack>
</interceptors>
<!-- action -->
<action name="newsAdminView!*" class="newsAction"
method="{1}">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="authStack">
</interceptor-ref>

下面是自己实现的Interceptor class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AccessInterceptor extends AbstractInterceptor {   
private static final long serialVersionUID = -4291195782860785705L;
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
ActionContext actionContext = actionInvocation.getInvocationContext();
Map session = actionContext.getSession();

//except login action
Object action = actionInvocation.getAction();
if (action instanceof AdminLoginAction) {
return actionInvocation.invoke();
}
//check session
if(session.get("user")==null ){
return "logout";
}
return actionInvocation.invoke();//go on
}
}

过滤器:是在javaweb中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些非法字符.
使用过滤器进行/admin 目录下jsp页面的过滤,首先在web.xml进行过滤器配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<filter>   
<filter-name>access filter</filter-name>
<filter-class>
com.test.news.util.AccessFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>access filter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
```
下面是过滤的实现类:
``` bash
public void destroy() {
}
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)arg0;
HttpServletResponse response = (HttpServletResponse)arg1;
HttpSession session = request.getSession();
if(session.getAttribute("user")== null && request.getRequestURI().indexOf("login.jsp")==-1 ){
response.sendRedirect("login.jsp");
return ;
}
filterChain.doFilter(arg0, arg1);
}
public void init(FilterConfig arg0) throws ServletException {
}
}

最近一次在eclipse启动tomcat,然后打开http://localhost:8080, 竟然404

觉得不管项目是不是正常还是失败,最起码tomcat启动起来了吧,但事实并不是如此,查资料才知道在eclipse启动tomcat,其实是eclipse调用tomcat核心的组件,内置到eclipse中,启动和部署时和真正的tomcat毫无关系,下面start.bat启动真正的tomcat

原因是因为我们在eclipse设置的tomcat的Server locations路径为: Use workspace metadata

将路径选择为第二项,即改为

server_path改为tomcat的目录,再启动就OK了

说明

HashMap有很多种遍历方式,网上很多都是建议entrySet,不建议使用其他方式,大部分的理由都是因为entrySet相比keySet而言,一次拿到了所有的key和value的集合,keySet只取到了key的集合,所以对于key而言,还要再去map集合中再查一次value,因此影响了性能,而且Java8中又出现了新的遍历方式,它的性能又如何呢?一起做一次对比测试

测试逻辑

对于方式,会针对获取key、获取value、同时获取key和value使用不同的比较;
对于数据,key存入的是包装类型Integer,value存入的是String
测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class HashMapLoop {

/**
* 第1种:最入门的方式,只能遍历value
*/
public static void forLoop(Map<Integer, String> map) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < map.size(); i++) {
System.out.print(map.get(i));
}
long costTime = System.currentTimeMillis() - startTime;
System.err.println("forLoop cost time=" + costTime);
}

/**
* 第2种:使用ketSet,可以遍历key和value
*/
public static void keySet(Map<Integer, String> map) {
long startTime = System.currentTimeMillis();
for (Integer key : map.keySet()) {
System.out.print(map.get(key));
}
long costTime = System.currentTimeMillis() - startTime;
System.err.println("keySet cost time=" + costTime);
}

/**
* 第3种:使用entrySet,可以遍历key和value
*/
public static void entrySet(Map<Integer, String> map) {
long startTime = System.currentTimeMillis();
for (Map.Entry<Integer, String> entry : map.entrySet()) {
// entry.getKey();
System.out.print(entry.getValue());
}
long costTime = System.currentTimeMillis() - startTime;
System.err.println("entrySet cost time=" + costTime);
}

/**
* 第4种:使用迭代器遍历KeySet,可以遍历key和value
*/
public static void iteratorKeySet(Map<Integer, String> map) {
long startTime = System.currentTimeMillis();
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.print(map.get(key));
}
long costTime = System.currentTimeMillis() - startTime;
System.err.println("iteratorKeySet cost time=" + costTime);
}

/**
* 第5种:使用迭代器遍历entrySet,可以遍历key和value
*/
public static void iteratorEntrySet(Map<Integer, String> map) {
long startTime = System.currentTimeMillis();
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
// Integer key = entry.getKey();
System.out.print(entry.getValue());
}
long costTime = System.currentTimeMillis() - startTime;
System.err.println("iteratorEntrySet cost time=" + costTime);
}

/**
* 第6种:增强for循环,只能遍历value
*/
public static void values(Map<Integer, String> map) {
long startTime = System.currentTimeMillis();
for (String value : map.values()) {
System.out.print(value);
}
long costTime = System.currentTimeMillis() - startTime;
System.err.println("values cost time=" + costTime);
}

/**
* 第7种:lambda方式遍历,可以遍历key和value
*/
public static void lambda(Map<Integer, String> map) {
long startTime = System.currentTimeMillis();
map.forEach((k, v) -> System.out.print(v));
long costTime = System.currentTimeMillis() - startTime;
System.err.println("lambda foreach cost time=" + costTime);
}

public static void main(String[] args) throws InterruptedException {

Map<Integer, String> map = new HashMap();

for (int i = 0; i < 100000; i++) {
map.put(Integer.valueOf(i), "hangzhou");
}

forLoop(map);
keySet(map);
entrySet(map);
iteratorKeySet(map);
iteratorEntrySet(map);
values(map);
lambda(map);
}
}

测试结果

遍历key
keySet entrySet iteratorKeySet iteratorEntrySet lambda
248 264 305 316 321
遍历value
forLoop values keySet entrySet iteratorKeySet iteratorEntrySet lambda
227 213 250 321 209 259 325
遍历key和value
keySet entrySet iteratorKeySet iteratorEntrySet lambda
416 406 366 369 411

ps:对于lambda forEach,测试数据有点惊讶,作为Java8新特性之一,其性能绝对不会差,我后来专门查询了下,Java官方使用的是更精确的jmh测试,需要进行预热,lambda forEach性能基本和values差不多

结论

  • 只遍历key时,keySet方法更为合适,因为entrySet将无用的value也给取出来了,浪费了性能和空间;
  • 只遍历value时,使用vlaues方法和lambda forEach是最佳选择,entrySet会略好于keySet方法;
  • 同时遍历key和value时,keySet与entrySet方法的性能差异取决于key的具体情况,如复杂度(复杂对象)、离散度、冲突率等。也就是说,取决于HashMap查找value的开销。entrySet一次性取出所有key和value的操作是有性能开销的,当这个损失小于HashMap查找value的开销时,entrySet的性能优势就会体现出来。当key是最简单的数值字符串时,keySet可能反而会更高效。但总体来说还是推荐使用entrySet。因为当key很简单时,其性能或许会略低于keySet,但却是可控的;而随着key的复杂化,entrySet的优势将会明显体现出来;
  • 以上是大概情况做的选择,真实业务处理中,肯定按照实际不同情况进行选择,性能并不是唯一考虑的因素