开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

漫画:什么是HashMap?

发表于 2017-12-01

​

————————————

众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。

HashMap数组每一个元素的初始值都是Null。

对于HashMap,我们最常使用的是两个方法:Get 和 Put。

1.Put方法的原理

调用Put方法的时候发生了什么呢?

比如调用 hashMap.put(“apple”, 0) ,插入一个Key为“apple”的元素。这时候我们需要利用一个哈希函数来确定Entry的插入位置(index):

index = Hash(“apple”)

假定最后计算出的index是2,那么结果如下:

但是,因为HashMap的长度是有限的,当插入的Entry越来越多时,再完美的Hash函数也难免会出现index冲突的情况。比如下面这样:

这时候该怎么办呢?我们可以利用链表来解决。

HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可:

需要注意的是,新来的Entry节点插入链表时,使用的是“头插法”。至于为什么不插入链表尾部,后面会有解释。

2.Get方法的原理

使用Get方法根据Key来查找Value的时候,发生了什么呢?

首先会把输入的Key做一次Hash映射,得到对应的index:

index = Hash(“apple”)

由于刚才所说的Hash冲突,同一个位置有可能匹配到多个Entry,这时候就需要顺着对应链表的头节点,一个一个向下来查找。假设我们要查找的Key是“apple”:

第一步,我们查看的是头节点Entry6,Entry6的Key是banana,显然不是我们要找的结果。

第二步,我们查看的是Next节点Entry1,Entry1的Key是apple,正是我们要找的结果。

之所以把Entry6放在头节点,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大。

————————————

之前说过,从Key映射到HashMap数组的对应位置,会用到一个Hash函数:

index = Hash(“apple”)

如何实现一个尽量均匀分布的Hash函数呢?我们通过利用Key的HashCode值来做某种运算。

index = HashCode(Key) % Length ?

如何进行位运算呢?有如下的公式(Length是HashMap的长度):

index = HashCode(Key) & (Length** - 1)**

下面我们以值为“book”的Key来演示整个过程:

1.计算book的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001。

2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。

3.把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。

可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。

假设HashMap的长度是10,重复刚才的运算步骤:

单独看这个结果,表面上并没有问题。我们再来尝试一个新的HashCode 101110001110101110 1011 :

让我们再换一个HashCode 101110001110101110 1111 试试 :

是的,虽然HashCode的倒数第二第三位从0变成了1,但是运算的结果都是1001。也就是说,当HashMap长度为10的时候,有些index结果的出现几率会更大,而有些index结果永远不会出现(比如0111)!

这样,显然不符合Hash算法均匀分布的原则。

反观长度16或者其他2的幂,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。

喜欢本文的朋友们,欢迎长按下图关注订阅号程序员小灰,收看更多精彩内容

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Boot 整合 Thymeleaf 完整 We

发表于 2017-12-01

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!

Thymeleaf 是一种模板语言。那模板语言或模板引擎是什么?常见的模板语言都包含以下几个概念:数据(Data)、模板(Template)、模板引擎(Template Engine)和结果文档(Result Documents)。

  • 数据
    数据是信息的表现形式和载体,可以是符号、文字、数字、语音、图像、视频等。数据和信息是不可分离的,数据是信息的表达,信息是数据的内涵。数据本身没有意义,数据只有对实体行为产生影响时才成为信息。
  • 模板
    模板,是一个蓝图,即一个与类型无关的类。编译器在使用模板时,会根据模板实参对模板进行实例化,得到一个与类型相关的类。
  • 模板引擎
    模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。
  • 结果文档
    一种特定格式的文档,比如用于网站的模板引擎就会生成一个标准的HTML文档。

模板语言用途广泛,常见的用途如下:

  • 页面渲染
  • 文档生成
  • 代码生成
  • 所有 “数据+模板=文本” 的应用场景

这里案例用途自然是 页面渲染,下面在 Spring Boot 中整合 Thymeleaf 实现完整 Web 案例。

一、运行 chapter-2-spring-boot-quick-start

chapter-2-spring-boot-quick-start 工程用的是内存式数据库,不需要配置数据源。下载运行即可。

1. 下载工程

git clone 下载工程 springboot-learning-example ,项目地址见 GitHub:https://github.com/JeffLi1993/springboot-learning-example,即:

1
复制代码git clone https://github.com/JeffLi1993/springboot-learning-example.git

2. 工程结构

用 IDEA 打开工程,可以看到子工程 chapter-2-spring-boot-quick-start ,其目录如下:

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
复制代码├── pom.xml
└── src
├── main
│ ├── java
│ │ └── spring
│ │ └── boot
│ │ └── core
│ │ ├── QuickStartApplication.java
│ │ ├── domain
│ │ │ ├── User.java
│ │ │ └── UserRepository.java
│ │ ├── service
│ │ │ ├── UserService.java
│ │ │ └── impl
│ │ │ └── UserServiceImpl.java
│ │ └── web
│ │ └── UserController.java
│ └── resources
│ ├── application.properties
│ ├── static
│ │ ├── css
│ │ │ └── default.css
│ │ └── images
│ │ └── favicon.ico
│ └── templates
│ ├── userForm.html
│ └── userList.html
└── test
└── java
└── spring
└── boot
└── core
├── QuickStartApplicationTests.java
└── domain
└── UserRepositoryTests.java

对应目录:

  • org.spring.springboot.controller - Controller 层
  • org.spring.springboot.dao - 数据操作层 DAO
  • org.spring.springboot.domain - 实体类
  • org.spring.springboot.service - 业务逻辑层
  • Application - 应用启动类
  • application.properties - 应用配置文件

模板是会用到下面两个目录

  • static 目录是存放 CSS、JS 等资源文件
  • templates 目录是存放视图

3. 编译运行工程

在该工程根目录,运行 maven 指令进行编译:

1
2
复制代码cd chapter-2-spring-boot-quick-start
mvn clean install

编译工程成功后,右键运行名为 QuickStartApplication.java 应用启动类的 main 函数,然后浏览器访问 localhost:8080/users 即可:
用户列表页面:

用户编辑页面:

二、详解 chapter-2-spring-boot-quick-start

工程代码:

1. pom.xml Thymeleaf 依赖

使用模板引擎,就在 pom.xml 加入 Thymeleaf 组件依赖:

1
2
3
4
5
复制代码<!-- 模板引擎 Thymeleaf 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Thymeleaf 是什么?
Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

Thymeleaf’s main goal is to bring elegant natural templates to your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams.

Thymeleaf 是新一代 Java 模板引擎,在 Spring 4 后推荐使用。

整体个 pom.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
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
复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>spring.boot.core</groupId>
<artifactId>chapter-2-spring-boot-quick-start</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>chapter-2-spring-boot-quick-start</name>
<description>第二章快速入门案例</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>

<!-- Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 单元测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- Spring Data JPA 依赖 :: 数据持久层框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- h2 数据源连接驱动 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<!-- 模板引擎 Thymeleaf 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Spring Boot Maven 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

2. Thymeleaf 依赖配置

在 Spring Boot 项目中加入 Thymeleaf 依赖,即可启动其默认配置。如果想要自定义配置,可以在 application.properties 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码spring.thymeleaf.cache=true # Enable template caching.
spring.thymeleaf.check-template=true # Check that the template exists before rendering it.
spring.thymeleaf.check-template-location=true # Check that the templates location exists.
spring.thymeleaf.enabled=true # Enable Thymeleaf view resolution for Web frameworks.
spring.thymeleaf.encoding=UTF-8 # Template files encoding.
spring.thymeleaf.excluded-view-names= # Comma-separated list of view names that should be excluded from resolution.
spring.thymeleaf.mode=HTML5 # Template mode to be applied to templates. See also StandardTemplateModeHandlers.
spring.thymeleaf.prefix=classpath:/templates/ # Prefix that gets prepended to view names when building a URL.
spring.thymeleaf.reactive.max-chunk-size= # Maximum size of data buffers used for writing to the response, in bytes.
spring.thymeleaf.reactive.media-types= # Media types supported by the view technology.
spring.thymeleaf.servlet.content-type=text/html # Content-Type value written to HTTP responses.
spring.thymeleaf.suffix=.html # Suffix that gets appended to view names when building a URL.
spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain.
spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved.

3. Thymeleaf 使用

Controller 如何将 View 指向 Thymeleaf

用户控制层代码如下:

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
复制代码@Controller
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users
public class UserController {

@Autowired
UserService userService; // 用户服务层

/**
* 获取用户列表
* 处理 "/users" 的 GET 请求,用来获取用户列表
* 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询
*/
@RequestMapping(method = RequestMethod.GET)
public String getUserList(ModelMap map) {
map.addAttribute("userList", userService.findAll());
return "userList";
}

/**
* 显示创建用户表单
*
*/
@RequestMapping(value = "/create", method = RequestMethod.GET)
public String createUserForm(ModelMap map) {
map.addAttribute("user", new User());
map.addAttribute("action", "create");
return "userForm";
}

/**
* 创建用户
* 处理 "/users" 的 POST 请求,用来获取用户列表
* 通过 @ModelAttribute 绑定参数,也通过 @RequestParam 从页面中传递参数
*/
@RequestMapping(value = "/create", method = RequestMethod.POST)
public String postUser(@ModelAttribute User user) {
userService.insertByUser(user);
return "redirect:/users/";
}

/**
* 显示需要更新用户表单
* 处理 "/users/{id}" 的 GET 请求,通过 URL 中的 id 值获取 User 信息
* URL 中的 id ,通过 @PathVariable 绑定参数
*/
@RequestMapping(value = "/update/{id}", method = RequestMethod.GET)
public String getUser(@PathVariable Long id, ModelMap map) {
map.addAttribute("user", userService.findById(id));
map.addAttribute("action", "update");
return "userForm";
}

/**
* 处理 "/users/{id}" 的 PUT 请求,用来更新 User 信息
*
*/
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String putUser(@ModelAttribute User user) {
userService.update(user);
return "redirect:/users/";
}

/**
* 处理 "/users/{id}" 的 GET 请求,用来删除 User 信息
*/
@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
public String deleteUser(@PathVariable Long id) {

userService.delete(id);
return "redirect:/users/";
}

}

ModelMap 对象来进行数据绑定到视图。return 字符串,该字符串对应的目录在 resources/templates 下的模板名字。
@ModelAttribute 注解是用来获取页面 Form 表单提交的数据,并绑定到 User 数据对象。

Form 表单页面

核心代码:

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
复制代码<form th:action="@{/users/{action}(action=${action})}" method="post" class="form-horizontal">

<input type="hidden" name="id" th:value="${user.id}"/>

<div class="form-group">
<label for="user_name" class="col-sm-2 control-label">名称</label>
<div class="col-xs-4">
<input type="text" class="form-control" id="user_name" name="name" th:value="${user.name}" />
</div>
</div>

<div class="form-group">
<label for="user_age" class="col-sm-2 control-label">年龄:</label>
<div class="col-xs-4">
<input type="text" class="form-control" id="user_age" name="age" th:value="${user.age}"/>
</div>
</div>

<div class="form-group">
<label for="user_birthday" class="col-sm-2 control-label">出生日期:</label>
<div class="col-xs-4">
<input type="date" class="form-control" id="user_birthday" name="birthday" th:value="${user.birthday}"/>
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<input class="btn btn-primary" type="submit" value="提交"/>  
<input class="btn" type="button" value="返回" onclick="history.back()"/>
</div>
</div>
</form>

这里定义了一个 Form 表单用于新增或者更新用户。

列表页面

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码<table class="table table-hover table-condensed">
<legend>
<strong>用户列表</strong>
</legend>
<thead>
<tr>
<th>用户编号</th>
<th>名称</th>
<th>年龄</th>
<th>出生时间</th>
<th>管理</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}">
<th scope="row" th:text="${user.id}"></th>
<td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
<td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td>
</tr>
</tbody>
</table>

这里循环了用户列表。

Tymeleaf 的语法糖

我这边也就不详细展开了,大家看看人家写的 http://www.cnblogs.com/nuoyiamy/p/5591559.html
或者看看官方文档 http://www.thymeleaf.org/documentation.html

三、本文小结

该文,利用 Thymeleaf 做了个 Web 的 CRUD 案例。大家多指教~

如以上文章或链接对你有帮助的话,别忘了在文章结尾处评论哈~ 你也可以点击页面右边“分享”悬浮按钮哦,让更多的人阅读这篇文章。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JVM系列之类加载流程-自定义类加载器

发表于 2017-12-01

JVM系列之类加载流程-自定义类加载器

老实说,类加载流程作者还是比较熟悉而且有实战经验的,因为有过一次自定义类加载器的实战经验(文章最后会和大家分享),虽然大部分小伙伴觉得这部分对coding没什么实际意义,如果你一直写CRUD并且用现有的高级语言业务框架,我可以告诉你,确实没什么用。但话说回来,你如果想多了解底层,并且在类加载时做一些手脚,那么这一块就很有必要学了。很多框架都是利用了类加载机制里的动态加载特性来搞事情,像比较出名的OSGI模块化(一个模块一个类加载器),JSP(运行时转换为字节流让加载器动态加载),Tomcat(自定义了许多类加载器用来隔离不同工程)…这里就不一一列举了。本文还是先把类加载流程先讲一讲,然后分享一下作者的一次自定义类加载的经验心得,概要如下:

文章结构
1 类加载的各个流程讲解
2 自定义类加载器讲解
3 实战自定义类加载器

1. 类加载的各个流程讲解

作者找了下网上的图,参考着自己画了一张类生命周期流程图:

类的生命周期图

类的生命周期图

注意点:图中各个流程并不是严格的先后顺序,比如在进行1加载时,其实2验证已经开始了,是交叉进行的。

加载

加载阶段说白了,就是把我们编译后的.Class静态文件转换到内存中(方法区),然后暴露出来让程序员能访问到。具体展开:

  • 通过一个类的全限定名来获取定义此类的二进制字节流(可以是.class文件,也可以是网络上的io,也可以是zip包等)
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中(HotSpot的实现其实就是在方法区)生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

加载阶段获得的二进制字节流并不一定是来自.class文件,比如网络上发来的,那么如果不进行一定的格式校验,肯定是不能加载的。所以验证阶段实际上是为了保护JVM的。对于一般Javaer来说,俺们都是.java文件编译出来的.class文件,然后转换成相应的二进制流,没啥危害。所以不用太关心这一部分。

准备

准备阶段主要是给static变量分配内存(方法区中),并设置初始值。
比如: public static Integer value =1;在准备阶段的值其实是为0的。需要注意的是常量是在准备阶段赋值的:
public static final Integer value =1 ;在准备阶段value就被赋值为了1;

解析

解析阶段就更抽象了,稍微说一下,因为不太重要,有两个概念,符号引用,直接引用。说的通俗一点但是不太准确,比如在类A中调用了new B();大家想一想,我们编译完成.class文件后其实这种对应关系还是存在的,只是以字节码指令的形式存在,比如 “invokespecial #2” 大家可以猜到#2其实就是我们的类B了,那么在执行这一行代码的时候,JVM咋知道#2对应的指令在哪,这就是一个静态的家伙,假如类B已经加载到方法区了,地址为(#f00123),所以这个时候就要把这个#2转成这个地址(#f00123),这样JVM在执行到这时不就知道B类在哪了,就去调用了。(说的这么通俗,我都怀疑人生了).其他的,像方法的符号引用,常量的符号引用,其实都是一个意思,大家要明白,所谓的方法,常量,类,都是高级语言(Java)层面的概念,在.class文件中,它才不管你是啥,都是以指令的形式存在,所以要把那种引用关系(谁调用谁,谁引用谁)都转换为地址指令的形式。好了。说的够通俗了。大家凑合理解吧。这块其实不太重要,对于大部分coder来说,所以我就通俗的讲了讲。

初始化

这一块其实就是调用类的构造方法,注意是类的构造方法,不是实例构造函数,实例构造函数就是我们通常写的构造方法,类的构造方法是自动生成的,生成规则:
static变量的赋值操作+static代码块
按照出现的先后顺序来组装。
注意:1 static变量的内存分配和初始化是在准备阶段.2 一个类可以是很多个线程同时并发执行,JVM会加锁保证单一性,所以不要在static代码块中搞一些耗时操作。避免线程阻塞。

使用&卸载

使用就是你直接new或者通过反射.newInstance了.
卸载是自动进行的,gc在方发区也会进行回收.不过条件很苛刻,感兴趣可以自己看一看,一般都不会卸载类.

2. 自定义类加载器讲解

2.1 类加载器

类加载器,就是执行上面类加载流程的一些类,系统默认的就有一些加载器,站在JVM的角度,就只有两类加载器:

  • 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>/lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
  • 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:
    • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>/lib/ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
    • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
    • 自定义类加载器,用户根据需求自己定义的。也需要继承自ClassLoader.

2.2 双亲委派模型

如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。见下图:

双亲委派模型

双亲委派模型

需要注意的是,自定义类加载器可以不遵循双亲委派模型,但是图中红色区域这种传递关系是JVM预先定义好的,谁都更改不了。双亲委派模型有什么好处呢?举个例子,比如有人故意在自己的代码中定义了一个String类,包名类名都和JDK自带的一样,那么根据双亲委派模型,类加载器会首先传递到父类加载器去加载,最终会传递到启动类加载器,启动加载类判断已经加载过了,所以程序员自定义的String类就不会被加载。避免程序员自己随意串改系统级的类。

2.3 自定义类加载器

上面说了半天理论,我都有点迫不及待的想上代码了。下面看看如何来自定义类加载器,并且如何在自定义加载器时遵循双亲委派模型(向上传递性).其实非常简单,在这里JDK用到了模板的设计模式,向上传递性其实已经帮我们封装好了,在ClassLoader中已经实现了,在loadClass方法中:

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
复制代码protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已经加载过。
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//2 .如果没有加载过,先调用父类加载器去加载
c = parent.loadClass(name, false);
} else {
// 2.1 如果没有加载过,且没有父类加载器,就用BootstrapClassLoader去加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
//3. 如果父类加载器没有加载到,调用findClass去加载
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

从上面代码可以明显看出,loadClass(String, boolean)函数即实现了双亲委派模型!整个大致过程如下:

  1. 检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。默认的findclass毛都不干,直接抛出ClassNotFound异常,所以我们自定义类加载器就要覆盖这个方法了。
  4. 可以猜测:ApplicationClassLoader的findClass是去classpath下去加载,ExtentionClassLoader是去java_home/lib/ext目录下去加载。实际上就是findClass方法不一样罢了。

由上面可以知道,抽象类ClassLoader的findClass函数默认是抛出异常的。而前面我们知道,loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass函数,因此我们必须要在loadClass这个函数里面实现将一个指定类名称转换为Class对象.
如果是是读取一个指定的名称的类为字节数组的话,这很好办。但是如何将字节数组转为Class对象呢?很简单,Java提供了defineClass方法,通过这个方法,就可以把一个字节数组转为Class对象啦~

defineClass:将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组.

1
2
3
复制代码protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError {
return defineClass(name, b, off, len, null);

上面介绍了自定义类加载器的原理和几个重要方法(loadClass,findClass,defineClass),相信大部分小伙伴还是一脸蒙蔽,没关系,我先上一副图,然后上一个自定义的类加载器:

自定义类加载器方法调用流程图

自定义类加载器方法调用流程图

样例自定义类加载器:

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
复制代码import java.io.InputStream;
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
}
public MyClassLoader(ClassLoader parent)
{
//一定要设置父ClassLoader不是ApplicationClassLoader,否则不会执行findclass
super(parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
//1. 覆盖findClass,来找到.class文件,并且返回Class对象
try
{
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
//2. 如果没找到,return null
return null;
}
byte[] b = new byte[is.available()];
is.read(b);
//3. 讲字节数组转换成了Class对象
return defineClass(name, b, 0, b.length);
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
}

稍微说一下:
其实很简单,继承ClassLoader对象,覆盖findClass方法,这个方法的作用就是找到.class文件,转换成字节数组,调用defineClass对象转换成Class对象返回。就这么easy..
演示下效果:

1
2
3
4
5
复制代码        MyClassLoader mcl = new MyClassLoader();
Class<?> c1 = Class.forName("Student", true, mcl);
Object obj = c1.newInstance();
System.out.println(obj.getClass().getClassLoader());
System.out.println(obj instanceof Student);

返回结果:
sun.misc.Launcher$AppClassLoader@6951a712
true

1
2
3
4
5
复制代码        MyClassLoader mcl = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent());
Class<?> c1 = Class.forName("Student", true, mcl);
Object obj = c1.newInstance();
System.out.println(obj.getClass().getClassLoader());
System.out.println(obj instanceof Student);

返回结果:
MyClassLoader@3918d722
false

重点分析:
第一个代码和第二个代码唯一一点不同的就是在new MyClassLoader()时,一个传入的ClassLoader.getSystemClassLoader().getParent();(这个其实就是扩展类加载器)

  1. 当不传入这个值时,默认的父类加载器为Application ClassLoader,那么大家可以知道,在这个加载器中已经加载了Student类(ClassPath路径下的Student类),我们在调用Class.forName时传入了自定义的类加载器,会调用自定义类加载器的loadClass,判断自己之前没有加载过,然后去调用父类的(ApplicationClassLoader)的loadClass,判断结果为已经加载,所以直接返回。所以打印ClassLoader为AppClassLoader.

验证默认父类加载器为ApplicationClassLoader:

1
2
复制代码     MyClassLoader mcl = new MyClassLoader();
System.out.println(mcl.getParent().getClass());

打印结果:class sun.misc.Launcher$AppClassLoader
2. 当我们传入父类加载器为扩展类加载器时,当调用父类(扩展类加载器)的loadeClass时,由于扩展类加载器只加载java_home/lib/ext目录下的类,所以classpath路径下的它不能加载,返回null,根据loadClass的逻辑,接着会调用自定义类加载器findClass来加载。所以打印ClassLoader为MyClassLoader.
3. instanceof返回true的条件是(类加载器+类)全部一样,虽然这里我们都是一个Student类,一个文件,但是由两个类加载器加载的,当然返回false了。
4. 在JVM中判断一个类唯一的标准是(类加载器+.class文件)都一样.像instanceof和强制类型转换都是这样的标准。
5. 注意,这里所说的父类类加载器,不是以继承的方式来实现的,而是以成员变量的方式实现的。当调用构造函数传入时,就把自己的成员变量parent设置成了传入的加载器。

  • 课外衍生:这里作者是遵循了双亲委托模型,所以覆盖了findClass,没有覆盖loadClass,其实loadClass也是可以覆盖的,比如你覆盖了loadClass,实现为”直接加载文件,不去判断父类是否已经加载”,这样就打破了双亲委托模型,一般是不推荐这样干的。不过小伙伴们可以试着玩玩.

自定义类加载器就给大家说完了,虽然作者感觉已经讲清楚了,因为无非就是几个方法的问题(loadClass,findClass,defineClass),但还是给大家几个传送门,可以多阅读阅读,相互参阅一下:
www.cnblogs.com/xrq730/p/48…
www.importnew.com/24036.html

3. 实战自定义类加载器

其实上面基本已经把自定义类加载器给讲清楚了,这里和大家分享一下作者一次实际的编写自定义类加载器的经验。背景如下:
我们在项目里使用了某开源通讯框架,但由于更改了源码,做了一些定制化更改,假设更改源码前为版本A,更改源码后为版本B,由于项目中部分代码需要使用版本A,部分代码需要使用版本B。版本A和版本B中所有包名和类名都是一样。那么问题来了,如果只依赖ApplicationClassLoader加载,它只会加载一个离ClassPath最近的一个版本。剩下一个加载时根据双亲委托模型,就直接返回已经加载那个版本了。所以在这里就需要自定义一个类加载器。大致思路如下图:

双版本设计图

双版本设计图

这里需要注意的是,在自定义类加载器时一定要把父类加载器设置为ExtentionClassLoader,如果不设置,根据双亲委托模型,默认父类加载器为ApplicationClassLoader,调用它的loadClass时,会判定为已经加载(版本A和版本B包名类名一样),会直接返回已经加载的版本A,而不是调用子类的findClass.就不会调用我们自定义类加载器的findClass去远程加载版本B了。

顺便提一下,作者这里的实现方案其实是为了遵循双亲委托模型,如果作者不遵循双亲委托模型的话,直接自定义一个类加载器,覆盖掉loadClass方法,不让它先去父类检验,而改为直接调用findClass方法去加载版本B,也是可以的.大家一定要灵活的写代码。

结语

好了,JVM类加载机制给大家分享完了,希望大家在碰到实际问题的时候能想到自定义类加载器来解决 。Have a good day .

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

数据库事务与隔离级别

发表于 2017-12-01

作者:不洗碗工作室 - Hotown

文章出处: 数据库事务与隔离级别

版权归作者所有,转载请注明出处

在数据库的使用中,我们常常把一系列操作的集合看作是一个独立的单元,这种构成单一逻辑工作单元的集合被称为事务。

事务模型

一个数据库系统需要维护事务的以下四种特性,它们被合称为ACID,分别对应原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)。

一个简单的事务模型

我们用T来指定这个模型,它的SQL大致是以下形式:

1
2
3
4
5
复制代码-- 操作1:扣除A账户10元
UPDATE account SET amount = amount - 10 WHERE user_name = 'A'

-- 操作2:增加B账户10元
UPDATE account SET amount = amount + 10 WHERE user_name = 'B'

T是一个最简单的转账模型,A用户将10元转到了B用户的账本上。

之后我们就用这个事务模型来解释一下事务的四种特性。

一、原子性

原子性表示,一个事务所包含的操作集合是单一的,不可拆分的。而且事务中任何一个操作失败,都要保证数据库回滚到整个事务执行前的状态。

在T事务中,有两个操作,一个是在A账户扣除10元,一个是在B账户增加10元。这个两个操作密不可分。

如果我们将这两个操作看成两个独立的事务,那么假设初始状态:

1
2
复制代码A账户:100元
B账户:100元

我们现在执行了操作A,A账户余额变为90元。然后,然后!服务器室由于某些不可控力,发生了爆炸。那么最终结果将会变成:

1
2
复制代码A账户:90元
B账户:100元

A和B就此开始了无止境的撕逼。。

B:你快给我转钱啊!

A:我转了啊!你看我的账户已经扣了10元了!

B:我这里没有收到啊!你自己看,还是100元!

……

二、一致性

一致性原则要求事务的执行不改变数据库的一致。即事务执行前如果数据库一致,事务执行后,这种一致性仍然存在。

以T事务为例,T执行前,A和B的账户余额总和为200元,那么我们要保证在T执行后,A和B的账户余额总合仍然为200元。

三、持久性

持久性的原则要求一旦事务成功完成执行,并且提交到数据库,那么这一次更新将会持久的。也就是说,只要事务成功执行,任何的系统故障都不能撤销这一次事务的提交。

这个概念可能出现一些小漏洞,比如如果事务的结果储存在内存中,那么一旦宕机,所有的数据都会消失,我们需要将数据提交到外部磁盘上,并且做好更新信息的记录,使数据库在宕机后重启凄然能恢复到之前的状态。由于这个概念不属于我们的讨论范围,这里也就不再赘述。

四、隔离性

隔离性确保事务并发执行后的系统状态与这些事务以某种次序串行执行以后的状态是等价的。

如果有多个事务并发执行,即使我们确保事务的原子性和一致性,这些操作在执行时也并不是严格的串行,而是以某种不可见的形式交叉执行,这种不可见行很可能会导致最终状态的不一致。

举个栗子,我们将之前的事务T记作事务T1,并将T1中的操作细分,他们在系统中的实际操作应该大致是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码/**
* read(x):从数据库中将x传送到执行read操作的事务的主存缓冲区中
*
* write(x):从执行write的事务的主存缓冲区中将x取出并写回数据库(其实还有一个commit过程,这里先忽略)
*/

read(A);
A := A-10;
write(A);


read(B);
B := B+10;
write(B);

除此以外,我们再定义一个T2,计算A+B的值:

1
2
3
复制代码read(A);
read(B);
A := A+B;

并行的事务会如何执行呢?如果运气好,它可能会按照T1,T2的顺序完整执行,那么最终我们得到的temp的状态应该是200。

但是如果出现一种情况,当T1中的A扣款成功,并切入数据库,而在执行给B增加余额的操作时,并没有全部完成,而是执行完B := B+10以后,开始执行T2,虽然B变量确实发生了改变,但是它还没有被写进数据库中,所以T2中计算出的temp变成了90+100=190。

大致流程会是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码-- T1
read(A):
A := A-10;
write(A); --这里A在数据库中的值变成了90

read(B);
B := B+10; --这里B确实发生了改变,但是并未提交至数据库

-- T2
read(A); --A = 90
read(B); --B = 100(不是110)
temp := A+B; --得到190

--T1
write(B) --这里B的修改被提交到数据库

为了确保隔离性,数据库系统中存在一种并发控制系统,来完成这一职责。

事务的隔离级别

在介绍事务的隔离级别前,先来介绍一下脏读,幻读,不可重复读的概念。

脏读、幻读、不可重复读

  • 脏读:脏读是指,当一个事务在访问某一数据,并且修改了这一数据,但是在commit之前,另外一个事务也访问了同一数据,然后做了修改。大致模型如下(这里用到了之前忽略的commit操作,这个操作是指事务完成后,进入提交状态):
1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码-- T1
read(A);
A := A+1;
write(A);

--T2
read(A);
A := A+2;
write(A);
commit;

-- T1
commit;
  • 不可重复读:指在一个事务中,对同一数据进行了两次读取,但是在这个事务还未结束的时候(第一次读取之后,第二次读取之前),另一事务读取了同一数据,并进行了修改,那就导致了两次重复读取的数据不一致,形成了不可重复读状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码-- 假设初始状态,A=10

-- T1
read(A); -- A = 10;

-- T2
read(A);
A = A+10;
write(A);
commit; -- A = 20;

-- T1
read(A); -- A = 20,与第一次不同
  • 幻读:幻读发生在两个事务非独立执行的情况。下面用SQL演示这种情况:
1
2
3
4
5
6
复制代码
-- T1
UPDATE users SET status = 1;

-- T2
insert users (`status`) values ('0')

然后执行T1操作的用户惊奇的发现,明明把所有的user状态都置1了啊,怎么还有一个0 ??????

事务隔离级别

  • 可串行化(Serializable):sql中最高的隔离性级别,能够避免脏读,幻读,不可重复读。代价也相对沉重,会大大影响数据库的性能。
  • 可重复读(Repeatable read):只允许读取已提交的数据,而且在一个事务两次读取一个数据项期间,其他事务不得更新该数据。这种状态不能避免幻读。
  • 已提交读(Read committed):只允许读取已提交数据,但不要求可重复读。这种状态只能避免脏读。
  • 未提交读(Read uncommitted):允许读取未提交的数据。这是最低的隔离级别,脏读,幻读,不可重复读都无法避免。

Attention:所有的隔离级别都不允许脏写,即如果一个数据项已经被另外一个尚未提交或终止的事务写入,则不允许其他事务对它进行写入。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Linux下php-fpm进程过多导致内存耗尽问题解决

发表于 2017-12-01

当个人博客数据库服务经常突然挂断,造成无法访问时我们能做什么?本篇主题就是记录博主针对这一现象时发现问题,分析问题,最后解决问题的过程。

发现问题

最近,发现个人博客的Linux服务器,数据库服务经常挂掉,导致需要重启,才能正常访问,极其恶心,于是决心开始解决问题,解放我的时间和精力(我可不想经常出问题,然后人工重启,费力费时)。

分析问题

发现问题以后,首先使用free -m指令查看当前服务器执行状况:

free -m

可以看到我的服务器内存是2G的,但是目前可用内存只剩下70M,内存使用率高达92%,很有可能是内存使用率过高导致数据库服务挂断。

继续看详细情况,使用top指令:

top

然后再看指令输出结果中详细列出的进程情况,重点关注第10列内存使用占比:

top

发现CPU使用率不算高,也排除了CPU的问题,另外可以看到数据库服务占用15.2%的内存,内存使用过高时将会挤掉数据库进程(占用内存最高的进程),导致服务挂断,所以我们需要查看详细内存使用情况,是哪些进程耗费了这么多的内存呢?

使用指令:

1
复制代码ps auxw|head -1;ps auxw|sort -rn -k4|head -40

查看消耗内存最多的前40个进程:

ps -auxw

查看第四列内存使用占比,发现除了mysql数据库服务之外,php-fpm服务池开启了太多子进程,占用超过大半内存,问题找到了,我们开始解决问题:设置控制php-fpm进程池进程数量。

解决问题

通过各种搜索手段,发现可以通过配置pm.max_children属性,控制php-fpm子进程数量,首先,打开php-fpm配置文件,执行指令:

1
复制代码vi /etc/php-fpm.d/www.conf

找到pm.max_children字段,发现其值过大:

www.conf

如图,pm.max_children值为50,每一个进程占用1%-2.5%的内存,加起来就耗费大半内存了,所以我们需要将其值调小,博主这里将其设置为25,同时,检查以下两个属性:

  1. pm.max_spare_servers: 该值表示保证空闲进程数最大值,如果空闲进程大于此值,此进行清理
  2. pm.min_spare_servers: 保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程;

这两个值均不能不能大于pm.max_children值,通常设置pm.max_spare_servers值为pm.max_children值的60%-80%。

最后,重启php-fpm

1
复制代码systemctl restart php-fpm

再次查看内存使用情况, 使用内存降低很多:

www.conf

之后经过多次观察内存使用情况,发现此次改进后,服务器内存资源消耗得到很大缓解。

原创文章,转载请注明: 转载自 熊建刚的博客

本文链接地址: Linux下php-fpm进程过多导致内存耗尽问题解决

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python中单双下划线的区别

发表于 2017-12-01

注:本文大部分内容参考自Difference between _, and __xx in Python.

在学习Python的时候,很多人都不理解为什么在方法(method)前面会加好几个下划线,有时甚至两边都会加,比如像__this__这种。在我看到上面的文章之前,我一直以为Python中这些下划线的作用就像Golang中方法/函数的大小写一样,或是一些其他语言中的private、public的作用一样,但仔细深究,这不全是Python这样设计的初衷。下面我们具体分析。

单下划线开头

我们经常看到方法或者属性前面加了单下划线,并认为它表示该方法或者属性是该类型(Python和Golang一样,不光类可以有方法,很多类型甚至基本类型也可以定义方法)的私有方法或属性。但其实在Python中不存在真正意义上的私有方法或者属性,前面加单下划线_只是表示你不应该去访问这个方法或者属性,因为它不是API的一部分。举个例子:

Python

1
2
3
4
5
6
7
8
9
10
复制代码class BaseForm(StrAndUnicode):
...

def _get_errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors

errors = property(_get_errors)

该代码片段来自Django源码(django/forms/forms.py)。这段代码的设计就是errors属性是对外API的一部分,如果你想获取错误详情,应该访问errors属性,而不是(也不应该)访问_get_errors方法。

双下划线开头

之前很多人跟我说Python中双下划线开头表示私有,我在很多地方也见到这样的说法。这样理解可能也不能说错,但这不是Python设计双下划线开头的初衷和目的,Python设计此的真正目的仅仅是为了避免子类覆盖父类的方法。我们看个例子:

Python

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
复制代码class A(object):

def __method(self):
print("I'm a method in class A")

def method_x(self):
print("I'm another method in class A\n")

def method(self):
self.__method()
self.method_x()

class B(A):

def __method(self):
print("I'm a method in class B")

def method_x(self):
print("I'm another method in class B\n")


if __name__ == '__main__':

print("situation 1:")
a = A()
a.method()

b = B()
b.method()

print("situation 2:")
# a.__method()
a._A__method()

执行结果:

1
2
3
4
5
6
7
8
9
复制代码situation 1:
I'm a method in class A
I'm another method in class A

I'm a method in class A
I'm another method in class B

situation 2:
I'm a method in class A

这里有两个点需要注意:

  1. A类中我们定义了__method()、method_x和method()三个方法;然后我们重新定义一个类B,继承自A,并且在B类中覆写(override)了其父类的__method()和method_x方法,但是从输出结果看,B对象调用method()方法时调用了其父类A的__method()方法和自己的method_x()方法。也就是说,__method()覆写没有生效,而method_x()覆写生效了。而这也正是Python设计双下划线开头的唯一目的。这一点也可在Python官方说明中得到答案:
    www.python.org/dev/peps/pe…。
  2. 前面我们就说了,Python中不存在真正意义上的私有变量。对于双下划线开头的方法和属性虽然我们不能直接引用,那是因为Python默认在其前面加了前缀_类名,所以就像situation 2下面的代码,虽然我们不能用a直接访问__method(),但却可以加上前缀去访问,即_A__method()。

开头结尾双下划线

一般来说像__this__这种开头结尾都加双下划线的方法表示这是Python自己调用的,你不要调用。比如我们可以调用len()函数来求长度,其实它后台是调用了__len__()方法。一般我们应该使用len,而不是直接使用__len__():

Python

1
2
3
4
5
6
7
复制代码a = [1, 2, 3]
print(len(a))
print(a.__len__()) # 和上面等效

num = 10
print(num + 10)
print(num.__add__(10)) # 和上面等效

我们一般称__len__()这种方法为magic methods,一些操作符后台调用的也是也是这些magic methods,比如+后台调用的是__add__,-调用的是__sub__,所以这种机制使得我们可以在自己的类中覆写操作符(见后面例子)。另外,有的时候这种开头结尾双下划线方法仅仅是某些特殊场景的回调函数,比如__init__()会在对象的初始化时调用,__new__()会在构建一个实例的时候调用等等。下面我们看两个例子:

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码class CrazyNumber(object):
def __init__(self, n):
self.n = n
def __add__(self, other):
return self.n - other
def __sub__(self, other):
return self.n + other
def __str__(self):
return str(self.n)

num = CrazyNumber(10)
print(num) # output is: 10
print(num + 5) # output is: 5
print(num - 20) # output is: 30

在上面这个例子中,我们覆写了+和-操作符,将他们的功能交换了。再看个例子:

Python

1
2
3
4
5
6
7
8
9
10
11
复制代码class Room(object):
def __init__(self):
self.people = []
def add(self, person):
self.people.append(person)
def __len__(self):
return len(self.people)

room = Room()
room.add("Igor")
print len(room) # output is: 1

这个例子中,因为我们实现了__len__(),所以Room对象也可以使用len函数了。

所有此类的方法都在这里有说明:documentation.

结论

  1. 使用单下划线(_one_underline)开头表示方法不是API的一部分,不要直接访问(虽然语法上访问也没有什么问题)。
  2. 使用双下划线开头(__two_underlines)开头表示子类不能覆写该方法。除非你真的知道你在干什么,否则不要使用这种方式。
  3. 当你想让自己定义的对象也可以像Python内置的对象一样使用Python内置的一些函数或操作符(比如len、add、+、-、==等)时,你可以定义该类方法。
  4. 当然还有些属性只在末尾加了但下划线,这仅仅是为了避免我们起的一些名字和Python保留关键字冲突,没有特殊含义。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何利用Shodan API和Python自动扫描高危设备

发表于 2017-12-01

Shodan称自己是“互联网连接设备的搜索引擎”。有这么多设备连接到互联网,具有不同的安全级别,这个搜索引擎的特殊功能意味着它可以提供一个设备列表来测试和攻击。在本教程中,我们将使用Python来定位特定的软件漏洞,并从Shodan中提取易受攻击的目标IP地址。

任何连接到互联网的设备都必须显示某种关于自己的信息。这可能相对有限,因为聪明的系统配置可以阻止大多数不需要的请求。在某些设备上,可以扫描端口来显示网络服务器上运行的服务或连接到无线网络的网络摄像头的名称。

步骤1使用Shodan

Shodan可以像大多数其他搜索引擎一样访问,通过在浏览器中导航到shodan.io。

如何利用Shodan API和Python自动扫描高危设备

除了使用传统的搜索术语来搜索公开索引的网站内容之外,在搜索Shodan时,我们通常会寻找设备头部或者除了由传统搜索引擎索引的设备的HTTP网页内容之外的其他信息。

在Mr. Robot中,名字字符搜索下面的字符串。

1
org:"Evil Corp" product:"Apache Tomcat"

虽然我们可以搜索相同的搜索命令,但虚构的公司Evil Corp.很可能不会返回任何结果(或者是否?)。字符串的第二个组成部分,“产品”过滤器仍然是一个功能和有用的搜索。此搜索字符串确实需要使用过滤器,一个选项仅适用于注册用户。可以通过点击主页右上方的“登录/注册”按钮或直接访问
account.shodan.io/register来注册Shodan账户。

如何利用Shodan API和Python自动扫描高危设备

Shodan账户注册后,将有多种附加搜索功能以过滤器的形式提供。其中一些过滤器显示在下面的列表中。

· 国家:过滤到特定的国家

· 城市:过滤到特定的城市

· 地理:按坐标过滤

· 主机名:查找匹配的主机名

· net:限制为IP /前缀

· 操作系统:基于操作系统的过滤器

· 端口:基于开放端口进行过滤

这些过滤器可以使用与先生机器人中使用的示例相同的格式应用,其中筛选器包含在搜索中,后跟冒号和搜索项。下面显示的格式可以用于Shodan中的任何过滤器。

1
filter:"Keyword"”

在这个例子中,“过滤器”是所使用的过滤器的名称,“关键字”是在过滤器类别内搜索的搜索术语。只要用空格隔开,就可以应用多个过滤器。

如show中所示,“Apache Tomcat”搜索确实会在Shodan上使用合法结果。我们可以通过搜索下面显示的字符串来测试。

1
product:"Apache Tomcat"

如何利用Shodan API和Python自动扫描高危设备

搜索后,我们可以看到超过140万个结果被返回。这个搜索提供了一些有关使用Apache Tomcat的位置和组织的有趣数据,但是对于黑客来说,这些结果可能具有不同的效用。

攻击者可能会使用具有已知漏洞的过时软件专门搜索服务器或连接到网络的设备,以便查找要利用的设备。这个过程可以通过在Web浏览器中从Shodan搜索复制结果手动完成,并选择地址进行手动攻击。然而,这个过程也可以通过使用脚本语言和Shodan的API来实现。

步骤2检索Shodan API密钥

为了使用Shodan的API在绕过Web界面的同时直接请求和接收数据,我们需要使用我们的API密钥。此API密钥可以通过导航到Shodan网站的“我的账户”部分进行检索,链接在主页的右上角,或者只需打开account.shodan.io即可。

如何利用Shodan API和Python自动扫描高危设备

此密钥将被插入到用于进行API调用的Python代码中,所以将其复制到剪贴板或将其保存到文件可能会很有用。

第3步用Python调用Shodan API

为了使用Python来使用Shodan API进行请求,我们需要安装一个功能Python环境以及Shodan Python模块。在本教程中使用的示例中,使用了Python 2.7。Python 3也可以使用该模块,但是需要使用本教程中显示的脚本进行一些语法更改。在基于Debian的Linux操作系统上,可以通过打开终端仿真器并运行下面的命令来安装Python 2.7。

1
sudo apt-get update && sudo apt-get install python2.7

如何利用Shodan API和Python自动扫描高危设备

安装Python后,我们也可以安装Shodan Python模块。这可以使用点或使用简易安装。使用下面的命令也可以使用apt-get安装Pip
。

1
sudo apt-get install python-pip

安装pip之后,我们可以使用pip来安装Shodan Python模块。

1
sudo pip install shodan

如果您的设备上存在多个Python版本,并且可能存在多个pip版本,则可能需要指定您希望为Python 2.7安装模块,而不是使用指定的pip2.7,而是使用下面的命令。

1
sudo pip2.7 install shodan

如果这些技术都不成功,则可以通过运行下面的命令来安装库。

1
easy_install shodan

一旦安装了Python和Shodan库,我们就可以开始编写一个新的Python脚本。在Linux命令行中,我们可以创建一个新文件并使用nano开始编辑它。一定要选择“shodan”以外的文件名,这样在引用库 和脚本之间没有冲突。下面,我们将创建一个名为“search.py”的文件。

1
nano search.py

我们要添加到这个文件中的第一件事是一个将加载Shodan库的行。我们可以使用Python 的导入功能来做到这一点,如下所示。

1
import shodan

接下来,我们可以定义我们的Shodan API密钥,以便脚本可以使用它来进行API查询。添加以下行来执行此操作。

1
2
SHODAN_API_KEY = "insert your API key here"
api = shodan.Shodan(SHODAN_API_KEY)

用您从Shodan网站检索到的API密钥替换“在此处插入您的API密钥”,留下引号将字段包含在内。

如何利用Shodan API和Python自动扫描高危设备

接下来,我们可以使用try声明来定义脚本应该尝试的内容。接下来,我们可以添加一个使用Shodan API 的api.search单元来实际请求搜索结果的命令。

1
2
try:
results = api.search('apache')

在这个例子中,搜索字符串只是简单的apache,但是,这可以用任何需要的搜索替换,包括带有过滤器的搜索,比如前面的教程。我们可以使用下面代码末尾附近显示的一组打印命令来返回此搜索的结果。

1
2
3
4
5
6
7
8
# Show the results
print 'Results found: %s' % results'total'
for result in results'matches':
print 'IP: %s' % result'ip_str'
print result'data'
print ''
except shodan.APIError, e:
print 'Error: %s' % e

该脚本现在应该与下面的图像中显示的代码类似。有关Shodan API和此代码的更多信息,请参阅其文档页面。

如何利用Shodan API和Python自动扫描高危设备

脚本现在可以保存和测试。在nano中,我们可以用Ctrl + O保存脚本,并用Ctrl + X退出nano 。在同一个目录下,我们可以使用下面的命令来运行脚本。

1
python2 script.py

运行脚本应返回一些IP地址和一些与之相关的信息,包括Shodan索引的HTTP状态,位置和其他设备信息。该信息的格式与在Web界面中搜索时显示的数据非常相似。

如何利用Shodan API和Python自动扫描高危设备

虽然这些附加信息可能允许其他脚本和工具处理其他标准,但如果希望自动收集和测试针对IP地址的攻击的过程,则此格式在很大程度上是不必要的。

为了只返回IP地址,我们可以改变Python脚本的格式。首先,我们可以从下面的行中删除IP:前缀。

1
2
3
4
5
6
7
print 'IP: %s' % result'ip_str'
Such that it looks like this line instead:
print '%s' % result'ip_str'
We can also delete the line which precedes it, and the two lines which follow it.
print 'Results found: %s' % results'total'
print result'data'
print ''

该脚本现在应该显示如下所示。

如何利用Shodan API和Python自动扫描高危设备

当我们运行这个脚本的时候,它会返回一个IP地址列表,而没有其他不必要的内容。

如何利用Shodan API和Python自动扫描高危设备

这个列表对于自动攻击列表来说非常有用,但是我们需要一个有效的方法来保存它。我们可以使用shell操作来将输出直接发送到日志文件。在运行脚本时,请包含>>运算符,后跟您希望将输出发送到的文件的名称。

1
python2 search.py >> log.txt

现在我们有一个包含IP列表的文本文件,我们可以用它来测试各种攻击,这取决于我们用来识别特定类型的易受攻击的系统的搜索术语。

步骤4使用Shodan结果自动执行任务

对于可以使用IP地址作为参数的命令行实用程序的示例,我们将使用ping。使用积极尝试攻击或利用Shodan发现的设备的东西将是非法和不负责任的,所以如果您实际上利用任何东西,请确保只运行您有权在目标上使用的工具。首先,我们将使用nano创建一个新的shell脚本,就像我们之前所做的一样。

1
nano ping.sh

我们可以用“crunchbang”(#!符号)和shell声明开始脚本。这表明它是一个shell脚本,由bash shell运行。

1
2
3
4
cat log.txt | while read line
do
ping $line
done

该脚本现在应该看起来像下图所示。

如何利用Shodan API和Python自动扫描高危设备

我们现在可以用Ctrl + O保存这个脚本,然后用Ctrl + X再次退出nano 。为了能够运行该脚本,我们需要通过使用chmod赋予它这个特权来将它标记为我们的操作系统的可执行文件。

1
chmod + x ping.sh

现在,我们可以从命令行运行脚本。

1
./ping.sh

脚本应该迭代地址中的每个IP,并向每个IP发送一个ping。

如何利用Shodan API和Python自动扫描高危设备

如果这个工作,你现在已经成功检索Shodan结果,并单独处理它们!Ping不是一个攻击媒介,但是有一些小的变化,攻击者可以使用类似的脚本来达到恶意的目的。

步骤5武器化攻击

在shell脚本中启动“ping”的字符串将是一个简单的区域来操作用从Shodan检索到的IP完成的操作。原始的Python脚本也可以被更新以用于更复杂的攻击或测试。下面显示了shell脚本的“while”迭代的基本ping命令。

1
ping $line

该命令中的$ line变量表示IP列表文件log.txt的每一行。我们可以用任何其他包含IP地址作为参数的字符串替换这个ping命令。我们可以使用nmap通过使用下面的命令端口扫描目标IP,使用-sS参数
进行服务扫描。

1
nmap -sS $line

如何利用Shodan API和Python自动扫描高危设备

虽然每个网络连接的设备有多种方式,它可以通过扫描服务,如初段进行索引,人们可以通过一个网站就像检查自己的外部IP检查他们的本地网络和路由器的安全whatsmyip.org和搜索这在Shodan上查看知识产权信息。

其他应用

这种格式可以扩展到任何可以从命令行启动并包含IP的任何其他攻击。这种对多个目标进行扫描和攻击的方法,对于发现易受攻击的系统而言,是一种非常有效的方法,无需花时间手动单独查找和攻击。

这种方法可以应用于各种不同的攻击,使用Shodan,Python,shell脚本或其他工具,只要它们能够在没有用户输入的情况下查找设备并攻击设备。

如若转载,请注明原文地址: www.4hou.com/web/8803.ht…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

你所不知道的的Java之整数

发表于 2017-12-01

以下内容为作者辛苦原创,版权归作者所有,如转载演绎请在“光变”微信公众号留言申请,转载文章请在开始处显著标明出处。

实参形参

前些天看到朋友圈分享了一片文章《Java函数的传参机制——你真的了解吗?》

有些触发,之前也研究过Java的Integer,所以写下本文,希望对你有所帮助。

交换

首先来看一个示例。

请用Java完成swap函数,交换两个整数类型的值。

1
2
3
4
5
6
7
8
9
复制代码public static void test() throws Exception {
Integer a = 1, b = 2;
swap(a, b);
System.out.println("a=" + a + ", b=" + b);
}

static void swap(Integer a, Integer b){
// 需要实现的部分
}

第一次
如果你不了解Java对象在内存中的分配方式,以及方法传递参数的形式,你有可能会写出以下代码。

1
2
3
4
5
复制代码public static void swapOne(Integer a, Integer b) throws Exception {
Integer aTempValue = a;
a = b;
b = aTempValue;
}

运行的结果显示a和b两个值并没有交换。 那么让我们来看一下上述程序运行时,Java对象在内存中的分配方式:

对象地址分配
由此可以看到,在两个方法的局部变量表中分别持有的是对a、b两个对象实际数据地址的引用。
上面实现的swap函数,仅仅交换了swap函数里局部变量a和局部变量b的引用,并没有交换JVM堆中的实际数据。
所以main函数中的a、b引用的数据没有发生交换,所以main函数中局部变量的a、b并不会发生变化。

那么要交换main函数中的数据要如何操作呢?

第二次
根据上面的实践,可以考虑交换a和b在JVM堆上的数据值?
简单了解一下Integer这个对象,它里面只有一个对象级int类型的value用以表示该对象的值。
所以我们使用反射来修改该值,代码如下:

1
2
3
4
5
6
7
复制代码public static void swapTwo(Integer a1, Integer b1) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
int tempAValue = valueField.getInt(a1);
valueField.setInt(a1, b1.intValue());
valueField.setInt(b1, tempAValue);
}

运行结果,符合预期。

惊喜

上面的程序运行成后,如果我在声明一个Integer c = 1, d = 2;会有什么结果

示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码
public static void swapTwo(Integer a1, Integer b1) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
int tempAValue = valueField.getInt(a1);
valueField.setInt(a1, b1.intValue());
valueField.setInt(b1, tempAValue);
}

public static void testThree() throws Exception {
Integer a = 1, b = 2;
swapTwo(a, b);
System.out.println("a=" + a + "; b=" + b);
Integer c = 1, d = 2;
System.out.println("c=" + c + "; d=" + d);
}

输出的结果如下:

1
2
复制代码a=2; b=1
c=2; d=1

惊喜不惊喜!意外不意外!刺激不刺激!

惊喜不惊喜

深入

究竟发生了什么?让我们来看一下反编译后的代码:

作者使用IDE工具,直接反编译了这个.class文件

1
2
3
4
5
6
7
8
9
复制代码public static void testThree() throws Exception {
Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(2);
swapTwo(a, b);
System.out.println("a=" + a + "; b=" + b);
Integer c = Integer.valueOf(1);
Integer d = Integer.valueOf(2);
System.out.println("c=" + c + "; d=" + d);
}

在Java对原始类型int自动装箱到Integer类型的过程中使用了Integer.valueOf(int)这个方法了。
肯定是这个方法在内部封装了一些操作,使得我们修改了Integer.value后,产生了全局影响。
所有这涉及该部分的代码一次性粘完(PS:不拖拉的作者是个好码农):

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
复制代码public class Integer{
/**
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

}

如上所示Integer内部有一个私有静态类IntegerCache,该类静态初始化了一个包含了Integer.IntegerCache.low到java.lang.Integer.IntegerCache.high的Integer数组。
其中java.lang.Integer.IntegerCache.high的取值范围在[127~Integer.MAX_VALUE

  • (-low) -1]之间。
    在该区间内所有的Integer.valueOf(int)函数返回的对象,是根据int值计算的偏移量,从数组Integer.IntegerCache.cache中获取,对象是同一个,不会新建对象。

所以当我们修改了Integer.valueOf(1)的value后,所有Integer.IntegerCache.cache[ 1 - IntegerCache.low ]的返回值都会变更。

我相信你们的智商应该理解了,如果不理解请在评论区call 10086。

好了,那么不在[IntegerCache.low~IntegerCache.high)的部分呢?
很显然,它们是幸运的,没有被IntegerCache缓存到,法外之民,每次它们的到来,都会new一边,在JVM上分配一块土(内)地(存)。

遐想

如果我把转换的参数换成类型换成int呢?

1
2
3
4
5
6
7
8
9
复制代码public static void testOne() throws Exception {
int a = 1, b = 2;
swapOne(a, b);
System.out.println("a=" + a + ", b=" + b);
}

static void swapOne(int a, int b){
// 需要实现的部分
}

以作者目前的功力,无解。高手可以公众号留言,万分感谢!
至此swap部分已经讲完了。

1 + 1

首先让我们来看一下代码:

1
2
3
4
5
复制代码public static void testOne() {
int one = 1;
int two = one + one;
System.out.printf("Two=%d", two);
}

请问输出是什么?
如果你肯定的说是2,那么你上面是白学了,请直接拨打95169。
我可以肯定的告诉你,它可以是[Integer.MIN_VALUE~Integer.MAX_VALUE]区间的任意一个值。

惊喜不惊喜!意外不意外!刺激不刺激!

惊喜不惊喜
让我们再撸(捋)一(一)串(遍)烧(代)烤(码)。

作者使用IDE工具,直接反编译了这个.class文件

1
2
3
4
5
复制代码public static void testOne() {
int one = 1;
int two = one + one;
System.out.printf("Two=%d", two);
}

这里的变量two竟然没有调用Integer.valueOf(int),跟想象的不太一样,我怀疑这是IDE的锅。
所以果断查看编译后的字节码。以下为摘录的部分字节码:

1
2
3
4
5
6
7
8
9
10
复制代码LDC "Two=%d"
ICONST_1
ANEWARRAY java/lang/Object
DUP
ICONST_0
ILOAD 2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
INVOKEVIRTUAL java/io/PrintStream.printf (Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
POP

可以看出确实是IDE的锅,这里不仅调用了一次Integer.valueOf(int),而且还创建一个Object的数组。
完整的Java代码应该是如下所示:

1
2
3
4
5
6
复制代码public static void testOne() {
int one = 1;
int two = one + one;
Object[] params = { Integer.valueOf(two) };
System.out.printf("Two=%d", params);
}

所以只要在方法调用前修改Integer.IntegerCache.cache[2+128]的值就可以了,所以在类的静态初始化部分加些代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码public class OnePlusOne {
static {
try {
Class<?> cacheClazz = Class.forName("java.lang.Integer$IntegerCache");
Field cacheField = cacheClazz.getDeclaredField("cache");
cacheField.setAccessible(true);
Integer[] cache = (Integer[]) cacheField.get(null);
//这里修改为 1 + 1 = 3
cache[2 + 128] = new Integer(3);
} catch (Exception e) {
e.printStackTrace();
}
}

public static void testOne() {
int one = 1;
int two = one + one;
System.out.printf("Two=%d", two);
}
}

two == 2 ?

在修改完Integer.IntegerCache.cache[2 + 128]的值后,变量two还等于2么?

1
2
3
4
5
6
复制代码public static void testTwo() {
int one = 1;
int two = one + one;
System.out.println(two == 2);
System.out.println(Integer.valueOf(two) == 2);
}

上述代码输出如下

true false

因为two == 2不涉及到Integer装箱的转换,还是原始类型的比较,所以原始类型的2永远等于2。
Integer.valueOf(two)==2的真实形式是Integer.valueOf(two).intValue == 2,即3==2,所以是false。

这里可以看到如果拿一个值为null的Integer变量和一个int变量用双等号比较,会抛出NullPointException。

这里的方法如果换成System.out.println(“Two=” + two)的形式会有怎样的输出?你可以尝试一下。

后记

XCache

类 是否有Cache 最小值 最大值
Boolean 无 – –
Byte ByteCache -128 127(固定)
Short ShortCache -128 127(固定)
Character CharacterCache 0 127(固定)
Integer IntegerCache -128 java.lang.Integer.IntegerCache.high
Long LongCache -128 127(固定)
Float 无 – –
Double 无 – –

java.lang.Integer.IntegerCache.high

看了IntegerCache类获取high的方法sun.misc.VM.getSavedProperty,可能大家会有以下疑问,我们不拖沓,采用一个问题一解答的方式。

1. 这个值如何如何传递到JVM中?

和系统属性一样在JVM启动时,通过设置-Djava.lang.Integer.IntegerCache.high=xxx传递进来。

2. 这个方法和System.getProperty有什么区别?

为了将JVM系统所需要的参数和用户使用的参数区别开,
java.lang.System.initializeSystemClass在启动时,会将启动参数保存在两个地方:
2.1 sun.misc.VM.savedProps中保存全部JVM接收的系统参数。
JVM会在启动时,调用java.lang.System.initializeSystemClass方法,初始化该属性。
同时也会调用sun.misc.VM.saveAndRemoveProperties方法,从java.lang.System.props中删除以下属性:

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag
    以上罗列的属性都是JVM启动需要设置的系统参数,所以为了安全考虑和隔离角度考虑,将其从用户可访问的System.props分开。

2.2 java.lang.System.props中保存除了以下JVM启动需要的参数外的其他参数。

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag

PS:作者使用的JDK 1.8.0_91

Java 9的IntegerCache

幻想一下,如果以上淘气的玩法出现在第三方的依赖包中,绝对有一批程序员会疯掉(请不要尝试这么恶劣的玩法,后果很严重)。

庆幸的是Java 9对此进行了限制。可以在相应的module中编写module-info.java文件,限制了使用反射来访问成员等,按照需要声明后,代码只能访问字段、方法和其他用反射能访问的信息,只有当类在相同的模块中,或者模块打开了包用于反射方式访问。详细内容可参考一下文章:
在 Java 9 里对 IntegerCache 进行修改?

感谢Lydia和飞鸟的宝贵建议和辛苦校对。

关注公众号

如果文章对你有所帮助,请给作者块糖吃。 可以关注我们的公众号,定期发布高质量文章。

光变:微信公众号

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

逆向分析某APP并使用JAVA与PHP语言实现RC4加解密

发表于 2017-12-01

近期在使用“实时公交”软件,其有北京大部分公交车的GPS数据,但是软件比较偏国企化,做的并没有那么人性化,大山子路口这里剧堵是远近闻名的,基本上每辆车都要堵一段时间,那么冬天出去等车就会很遭罪,但是晚出去有可能眼睁睁的看着公交到站开走,所以打算逆向分析一下北京实时公交软件,加入对堵车时间的预测。

逆向分析

该软件如其外观一样简单,内部没有做混淆,甚至还有测试信息打印,System.out.println等输出,所以直接dex转jar(https://sourceforge.net/projects/dex2jar/),然后使用jd-gui(http://jd.benow.ca/)进行查看,导出来后放入IDE查看。

代码分析

以下为其主要目录结构

model层应该是可以比较直观的看出其结构(其model层写了一个错别字?)

分析看到需要解密的字段,其使用RC4加解密,一个不是特别常用的对称加解密算法

RC4加解密简介

RC4不是对明文进行分组处理,而是字节流的方式依次加密明文中的每一个字节,解密的时候也是依次对密文中的每一个字节进行解密。
算法简单,运行速度快,而且密钥长度是可变的,可变范围为1-256字节(8-2048比特)

1、密钥流:RC4算法的关键是根据明文和密钥生成相应的密钥流,密钥流的长度和明文的长度是对应的,也就是说明文的长度是500字节,那么密钥流也是500字节。当然,加密生成的密文也是500字节,因为密文第i字节=明文第i字节^密钥流第i字节;
2、状态向量S:长度为256,S[0],S[1]…..S[255]。每个单元都是一个字节,算法运行的任何时候,S都包括0-255的8比特数的排列组合,只不过值的位置发生了变换;
3、临时向量T:长度也为256,每个单元也是一个字节。如果密钥的长度是256字节,就直接把密钥的值赋给T,否则,轮转地将密钥的每个字节赋给T;
4、密钥K:长度为1-256字节,注意密钥的长度keylen与明文长度、密钥流的长度没有必然关系,通常密钥的长度趣味16字节(128比特)。

RC4加解密PHP实现

实现:

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
复制代码function rc4($key, $pt)
{
    $s = array();
    for ($i=0; $i<256; $i++) {
        $s[$i] = $i;
    }
 
    $j = 0;
    $key_len = strlen($key);
    for ($i=0; $i<256; $i++) {
        $j = ($j + $s[$i] + ord($key[$i % $key_len])) % 256;
        //swap
        $x = $s[$i];
        $s[$i] = $s[$j];
        $s[$j] = $x;
    }
    $i = 0;
    $j = 0;
    $ct = '';
    $data_len = strlen($pt);
    for ($y=0; $y< $data_len; $y++) {
        $i = ($i + 1) % 256;
        $j = ($j + $s[$i]) % 256;
        //swap
        $x = $s[$i];
        $s[$i] = $s[$j];
        $s[$j] = $x;
        $ct .= $pt[$y] ^ chr($s[($s[$i] + $s[$j]) % 256]);
    }
    return $ct;
}

测试验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码$str = '{"root":{"status":"200","message":"success","encrypt":"1","num":"6","lid":"949","data":{"bus":[{"gt":"1511789342","id":"75544","t":"0","ns":"MeACxHFmZLhE","nsn":"5kU=","nsd":"1706","nsrt":"197","nst":"1511789539","sd":"+Uc=","srt":"+Uc=","st":"+Uc=","x":"5UewD9XUuDbzQw==","y":"4EaoEdPStDT4","lt":"0","ut":"1511789353"},{"gt":"1511789343","id":"75537","t":"0","ns":"9kh/iiYwnXfFvgPc4r4gjGxZ","nsn":"Ldk=","nsd":"652","nsrt":"79","nst":"1511789422","sd":"MtA=","srt":"MtA=","st":"MtA=","x":"LtDlQqCIRdFZaQ==","y":"K9H9XKaJRtVY","lt":"0","ut":"1511789354"},{"gt":"1511789334","id":"75611","t":"0","ns":"z60iAGP9avHwPh8dLCVw17Rv","nsn":"Gzw=","nsd":"-1","nsrt":"-1","nst":"-1","sd":"Bzg=","srt":"Bzg=","st":"Bzg=","x":"Gzizy+Z0tmpX4g==","y":"GTCr3Op6uW5S","lt":"0","ut":"1511789342"},{"gt":"1511789335","id":"75538","t":"0","ns":"q2CWQiHZ52ohG3yEEOiW","nsn":"fMs=","nsd":"321","nsrt":"52","nst":"1511789387","sd":"YM0=","srt":"YM0=","st":"YM0=","x":"fM07iK9COfuC","y":"fsUjn6JDNvg=","lt":"0","ut":"1511789343"},{"gt":"1511789256","id":"75549","t":"0","ns":"GlwsiqkbEEag","nsn":"wcI=","nsd":"515","nsrt":"71","nst":"1511789327","sd":"3sQ=","srt":"3sQ=","st":"3sQ=","x":"wsS2QRG8zfQcfA==","y":"x8WuXxa/y/kU","lt":"0","ut":"1511789270"},{"gt":"1511789107","id":"75553","t":"0","ns":"ZZQTCHtyjvXT3sBLOCJLdjq3","nsn":"sws=","nsd":"-1","nsrt":"-1","nst":"-1","sd":"rQg=","srt":"rQg=","st":"rQg=","x":"sQi8wPzyX0BIDw==","y":"tAmk3vr4X0NG","lt":"0","ut":"1511789130"}]}}}';
$arr = json_decode($str,true);
$arr = $arr['root']['data']['bus'];
 
foreach ($arr as $key=>$val){
    echo date('Y-m-d H:i:s',$val['gt'])."\n";
    echo $val['id']."\n";
    echo $val['t']."\n";
    echo rc4(md5("aibang".$val['gt']), base64_decode($val['ns']))."\n";
    echo rc4(md5("aibang".$val['gt']), base64_decode($val['nsn']))."\n";
    echo $val['nsd']."\n";
    echo $val['nsrt']."\n";
    echo $val['nst']."\n";
    echo $val['sd']."\n";
    echo $val['srt']."\n";
    echo $val['nst']."\n";
    echo rc4(md5("aibang".$val['gt']), base64_decode($val['x']))."\n";
    echo rc4(md5("aibang".$val['gt']), base64_decode($val['y']))."\n";
    echo date('Y-m-d H:i:s',$val['ut'])."\n";
    echo date('Y-m-d H:i:s',$val['lt'])."\n";
    echo "******************************************************\n";
}

RC4加解密Java实现

实现:

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
复制代码package testest;
 
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
 
public class RC4
{
    public static String decry_RC4(byte[] data, String key) {
        if (data == null || key == null) {
            return null;
        }
        return asString(RC4Base(data, key));
    }
 
 
    public static String decry_RC4(String data, String key) {
        if (data == null || key == null) {
            return null;
        }
        return new String(RC4Base(HexString2Bytes(data), key));
    }
 
 
    public static byte[] encry_RC4_byte(String data, String key) {
        if (data == null || key == null) {
            return null;
        }
        byte b_data[] = data.getBytes();
        return RC4Base(b_data, key);
    }
 
 
    public static String encry_RC4_string(String data, String key) {
        if (data == null || key == null) {
            return null;
        }
        return toHexString(asString(encry_RC4_byte(data, key)));
    }
 
 
    private static String asString(byte[] buf) {
        StringBuffer strbuf = new StringBuffer(buf.length);
        for (int i = 0; i < buf.length; i++) {
            strbuf.append((char) buf[i]);
        }
        return strbuf.toString();
    }
 
 
    private static byte[] initKey(String aKey) {
        byte[] b_key = aKey.getBytes();
        byte state[] = new byte[256];
 
        for (int i = 0; i < 256; i++) {
            state[i] = (byte) i;
        }
        int index1 = 0;
        int index2 = 0;
        if (b_key == null || b_key.length == 0) {
            return null;
        }
        for (int i = 0; i < 256; i++) {
            index2 = ((b_key[index1] & 0xff) + (state[i] & 0xff) + index2) & 0xff;
            byte tmp = state[i];
            state[i] = state[index2];
            state[index2] = tmp;
            index1 = (index1 + 1) % b_key.length;
        }
        return state;
    }
 
    private static String toHexString(String s) {
        String str = "";
        for (int i = 0; i < s.length(); i++) {
            int ch = (int) s.charAt(i);
            String s4 = Integer.toHexString(ch & 0xFF);
            if (s4.length() == 1) {
                s4 = '0' + s4;
            }
            str = str + s4;
        }
        return str;// 0x表示十六进制
    }
 
 
    private static byte[] HexString2Bytes(String src) {
        int size = src.length();
        byte[] ret = new byte[size / 2];
        byte[] tmp = src.getBytes();
        for (int i = 0; i < size / 2; i++) {
            ret[i] = uniteBytes(tmp[i * 2], tmp[i * 2 + 1]);
        }
        return ret;
    }
 
    private static byte uniteBytes(byte src0, byte src1) {
        char _b0 = (char)Byte.decode("0x" + new String(new byte[] { src0 }))
                .byteValue();
        _b0 = (char) (_b0 << 4);
        char _b1 = (char)Byte.decode("0x" + new String(new byte[] { src1 }))
                .byteValue();
        byte ret = (byte) (_b0 ^ _b1);
        return ret;
    }
 
    private static byte[] RC4Base (byte [] input, String mKkey) {
        int x = 0;
        int y = 0;
        byte key[] = initKey(mKkey);
        int xorIndex;
        byte[] result = new byte[input.length];
 
        for (int i = 0; i < input.length; i++) {
            x = (x + 1) & 0xff;
            y = ((key[x] & 0xff) + y) & 0xff;
            byte tmp = key[x];
            key[x] = key[y];
            key[y] = tmp;
            xorIndex = ((key[x] & 0xff) + (key[y] & 0xff)) & 0xff;
            result[i] = (byte) (input[i] ^ key[xorIndex]);
        }
        return result;
    }
 
  private static String encode(String paramString1, String paramString2)
  {
    try
    {
      MessageDigest localMessageDigest = MessageDigest.getInstance(paramString2);
      localMessageDigest.update(paramString1.getBytes());
      byte[] arrayOfByte = localMessageDigest.digest();
      StringBuilder localStringBuilder = new StringBuilder(2 * arrayOfByte.length);
      for (int i = 0; i < arrayOfByte.length; i++)
      {
        localStringBuilder.append(Integer.toHexString((0xF0 & arrayOfByte[i]) >>> 4));
        localStringBuilder.append(Integer.toHexString(0xF & arrayOfByte[i]));
      }
      String str = localStringBuilder.toString();
      return str;
    }
    catch (NoSuchAlgorithmException localNoSuchAlgorithmException)
    {
      localNoSuchAlgorithmException.printStackTrace();
    }
    return "";
  }
}

验证

1
2
3
4
5
6
7
8
9
复制代码    public static void main(String[] args) throws NoSuchAlgorithmException {
        String md5 = encode("aibang1511793087","MD5");
        System.out.println(md5);
        byte[] b = Base64.decode("jT2f7w3uYkWWKvnwI4XpuD1X",0);
        System.out.println(new String(b));
        String test = null;
        test = new String(RC4.RC4Base(b, md5));
        System.out.println(test);
    }

附Base64实现

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
复制代码package testest;
 
import java.io.UnsupportedEncodingException;
 
/**
* Utilities for encoding and decoding the Base64 representation of
* binary data.  See RFCs <a
* href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
* href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
*/
public class Base64 {
    /**
     * Default values for encoder/decoder flags.
     */
    public static final int DEFAULT = 0;
 
    /**
     * Encoder flag bit to omit the padding '=' characters at the end
     * of the output (if any).
     */
    public static final int NO_PADDING = 1;
 
    /**
     * Encoder flag bit to omit all line terminators (i.e., the output
     * will be on one long line).
     */
    public static final int NO_WRAP = 2;
 
    /**
     * Encoder flag bit to indicate lines should be terminated with a
     * CRLF pair instead of just an LF.  Has no effect if {@code
     * NO_WRAP} is specified as well.
     */
    public static final int CRLF = 4;
 
    /**
     * Encoder/decoder flag bit to indicate using the "URL and
     * filename safe" variant of Base64 (see RFC 3548 section 4) where
     * {@code -} and {@code _} are used in place of {@code +} and
     * {@code /}.
     */
    public static final int URL_SAFE = 8;
 
    /**
     * Flag to pass to {@link Base64OutputStream} to indicate that it
     * should not close the output stream it is wrapping when it
     * itself is closed.
     */
    public static final int NO_CLOSE = 16;
 
    //  --------------------------------------------------------
    //  shared code
    //  --------------------------------------------------------
 
    /* package */ static abstract class Coder {
        public byte[] output;
        public int op;
 
        /**
         * Encode/decode another block of input data.  this.output is
         * provided by the caller, and must be big enough to hold all
         * the coded data.  On exit, this.opwill be set to the length
         * of the coded data.
         *
         * @param finish true if this is the final call to process for
         *        this object.  Will finalize the coder state and
         *        include any final bytes in the output.
         *
         * @return true if the input so far is good; false if some
         *         error has been detected in the input stream..
         */
        public abstract boolean process(byte[] input, int offset, int len, boolean finish);
 
        /**
         * @return the maximum number of bytes a call to process()
         * could produce for the given number of input bytes.  This may
         * be an overestimate.
         */
        public abstract int maxOutputSize(int len);
    }
 
    //  --------------------------------------------------------
    //  decoding
    //  --------------------------------------------------------
 
    /**
     * Decode the Base64-encoded data in input and return the data in
     * a new byte array.
     *
     * <p>The padding '=' characters at the end are considered optional, but
     * if any are present, there must be the correct number of them.
     *
     * @param str    the input String to decode, which is converted to
     *               bytes using the default charset
     * @param flags  controls certain features of the decoded output.
     *               Pass {@code DEFAULT} to decode standard Base64.
     *
     * @throws IllegalArgumentException if the input contains
     * incorrect padding
     */
    public static byte[] decode(String str, int flags) {
        return decode(str.getBytes(), flags);
    }
 
    /**
     * Decode the Base64-encoded data in input and return the data in
     * a new byte array.
     *
     * <p>The padding '=' characters at the end are considered optional, but
     * if any are present, there must be the correct number of them.
     *
     * @param input the input array to decode
     * @param flags  controls certain features of the decoded output.
     *               Pass {@code DEFAULT} to decode standard Base64.
     *
     * @throws IllegalArgumentException if the input contains
     * incorrect padding
     */
    public static byte[] decode(byte[] input, int flags) {
        return decode(input, 0, input.length, flags);
    }
 
    /**
     * Decode the Base64-encoded data in input and return the data in
     * a new byte array.
     *
     * <p>The padding '=' characters at the end are considered optional, but
     * if any are present, there must be the correct number of them.
     *
     * @param input  the data to decode
     * @param offset the position within the input array at which to start
     * @param len    the number of bytes of input to decode
     * @param flags  controls certain features of the decoded output.
     *               Pass {@code DEFAULT} to decode standard Base64.
     *
     * @throws IllegalArgumentException if the input contains
     * incorrect padding
     */
    public static byte[] decode(byte[] input, int offset, int len, int flags) {
        // Allocate space for the most data the input could represent.
        // (It could contain less if it contains whitespace, etc.)
        Decoder decoder = new Decoder(flags, new byte[len*3/4]);
 
        if (!decoder.process(input, offset, len, true)) {
            throw new IllegalArgumentException("bad base-64");
        }
 
        // Maybe we got lucky and allocated exactly enough output space.
        if (decoder.op == decoder.output.length) {
            return decoder.output;
        }
 
        // Need to shorten the array, so allocate a new one of the
        // right size and copy.
        byte[] temp = new byte[decoder.op];
        System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
        return temp;
    }
 
    /* package */ static class Decoder extends Coder {
        /**
         * Lookup table for turning bytes into their position in the
         * Base64 alphabet.
         */
        private static final int DECODE[] = {
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
            -1,  0,  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, -1, -1, -1, -1, -1,
            -1, 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, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        };
 
        /**
         * Decode lookup table for the "web safe" variant (RFC 3548
         * sec. 4) where - and _ replace + and /.
         */
        private static final int DECODE_WEBSAFE[] = {
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
            -1,  0,  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, -1, -1, -1, -1, 63,
            -1, 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, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        };
 
        /** Non-data values in the DECODE arrays. */
        private static final int SKIP = -1;
        private static final int EQUALS = -2;
 
        /**
         * States 0-3 are reading through the next input tuple.
         * State 4 is having read one '=' and expecting exactly
         * one more.
         * State 5 is expecting no more data or padding characters
         * in the input.
         * State 6 is the error state; an error has been detected
         * in the input and no future input can "fix" it.
         */
        private int state;   // state number (0 to 6)
        private int value;
 
        final private int[] alphabet;
 
        public Decoder(int flags, byte[] output) {
            this.output = output;
 
            alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
            state = 0;
            value = 0;
        }
 
        /**
         * @return an overestimate for the number of bytes {@code
         * len} bytes could decode to.
         */
        public int maxOutputSize(int len) {
            return len * 3/4 + 10;
        }
 
        /**
         * Decode another block of input data.
         *
         * @return true if the state machine is still healthy.  false if
         *         bad base-64 data has been detected in the input stream.
         */
        public boolean process(byte[] input, int offset, int len, boolean finish) {
            if (this.state == 6) return false;
 
            int p = offset;
            len += offset;
 
            // Using local variables makes the decoder about 12%
            // faster than if we manipulate the member variables in
            // the loop.  (Even alphabet makes a measurable
            // difference, which is somewhat surprising to me since
            // the member variable is final.)
            int state = this.state;
            int value = this.value;
            int op = 0;
            final byte[] output = this.output;
            final int[] alphabet = this.alphabet;
 
            while (p < len) {
                // Try the fast path:  we're starting a new tuple and the
                // next four bytes of the input stream are all data
                // bytes.  This corresponds to going through states
                // 0-1-2-3-0.  We expect to use this method for most of
                // the data.
                //
                // If any of the next four bytes of input are non-data
                // (whitespace, etc.), value will end up negative.  (All
                // the non-data values in decode are small negative
                // numbers, so shifting any of them up and or'ing them
                // together will result in a value with its top bit set.)
                //
                // You can remove this whole block and the output should
                // be the same, just slower.
                if (state == 0) {
                    while (p+4 <= len &&
                           (value = ((alphabet[input[p] & 0xff] << 18) |
                                     (alphabet[input[p+1] & 0xff] << 12) |
                                     (alphabet[input[p+2] & 0xff] << 6) |
                                     (alphabet[input[p+3] & 0xff]))) >= 0) {
                        output[op+2] = (byte) value;
                        output[op+1] = (byte) (value >> 8);
                        output[op] = (byte) (value >> 16);
                        op += 3;
                        p += 4;
                    }
                    if (p >= len) break;
                }
 
                // The fast path isn't available -- either we've read a
                // partial tuple, or the next four input bytes aren't all
                // data, or whatever.  Fall back to the slower state
                // machine implementation.
 
                int d = alphabet[input[p++] & 0xff];
 
                switch (state) {
                case 0:
                    if (d >= 0) {
                        value = d;
                        ++state;
                    } else if (d != SKIP) {
                        this.state = 6;
                        return false;
                    }
                    break;
 
                case 1:
                    if (d >= 0) {
                        value = (value << 6) | d;
                        ++state;
                    } else if (d != SKIP) {
                        this.state = 6;
                        return false;
                    }
                    break;
 
                case 2:
                    if (d >= 0) {
                        value = (value << 6) | d;
                        ++state;
                    } else if (d == EQUALS) {
                        // Emit the last (partial) output tuple;
                        // expect exactly one more padding character.
                        output[op++] = (byte) (value >> 4);
                        state = 4;
                    } else if (d != SKIP) {
                        this.state = 6;
                        return false;
                    }
                    break;
 
                case 3:
                    if (d >= 0) {
                        // Emit the output triple and return to state 0.
                        value = (value << 6) | d;
                        output[op+2] = (byte) value;
                        output[op+1] = (byte) (value >> 8);
                        output[op] = (byte) (value >> 16);
                        op += 3;
                        state = 0;
                    } else if (d == EQUALS) {
                        // Emit the last (partial) output tuple;
                        // expect no further data or padding characters.
                        output[op+1] = (byte) (value >> 2);
                        output[op] = (byte) (value >> 10);
                        op += 2;
                        state = 5;
                    } else if (d != SKIP) {
                        this.state = 6;
                        return false;
                    }
                    break;
 
                case 4:
                    if (d == EQUALS) {
                        ++state;
                    } else if (d != SKIP) {
                        this.state = 6;
                        return false;
                    }
                    break;
 
                case 5:
                    if (d != SKIP) {
                        this.state = 6;
                        return false;
                    }
                    break;
                }
            }
 
            if (!finish) {
                // We're out of input, but a future call could provide
                // more.
                this.state = state;
                this.value = value;
                this.op = op;
                return true;
            }
 
            // Done reading input.  Now figure out where we are left in
            // the state machine and finish up.
 
            switch (state) {
            case 0:
                // Output length is a multiple of three.  Fine.
                break;
            case 1:
                // Read one extra input byte, which isn't enough to
                // make another output byte.  Illegal.
                this.state = 6;
                return false;
            case 2:
                // Read two extra input bytes, enough to emit 1 more
                // output byte.  Fine.
                output[op++] = (byte) (value >> 4);
                break;
            case 3:
                // Read three extra input bytes, enough to emit 2 more
                // output bytes.  Fine.
                output[op++] = (byte) (value >> 10);
                output[op++] = (byte) (value >> 2);
                break;
            case 4:
                // Read one padding '=' when we expected 2.  Illegal.
                this.state = 6;
                return false;
            case 5:
                // Read all the padding '='s we expected and no more.
                // Fine.
                break;
            }
 
            this.state = state;
            this.op = op;
            return true;
        }
    }
 
    //  --------------------------------------------------------
    //  encoding
    //  --------------------------------------------------------
 
    /**
     * Base64-encode the given data and return a newly allocated
     * String with the result.
     *
     * @param input  the data to encode
     * @param flags  controls certain features of the encoded output.
     *               Passing {@code DEFAULT} results in output that
     *               adheres to RFC 2045.
     */
    public static String encodeToString(byte[] input, int flags) {
        try {
            return new String(encode(input, flags), "US-ASCII");
        } catch (UnsupportedEncodingException e) {
            // US-ASCII is guaranteed to be available.
            throw new AssertionError(e);
        }
    }
 
    /**
     * Base64-encode the given data and return a newly allocated
     * String with the result.
     *
     * @param input  the data to encode
     * @param offset the position within the input array at which to
     *               start
     * @param len    the number of bytes of input to encode
     * @param flags  controls certain features of the encoded output.
     *               Passing {@code DEFAULT} results in output that
     *               adheres to RFC 2045.
     */
    public static String encodeToString(byte[] input, int offset, int len, int flags) {
        try {
            return new String(encode(input, offset, len, flags), "US-ASCII");
        } catch (UnsupportedEncodingException e) {
            // US-ASCII is guaranteed to be available.
            throw new AssertionError(e);
        }
    }
 
    /**
     * Base64-encode the given data and return a newly allocated
     * byte[] with the result.
     *
     * @param input  the data to encode
     * @param flags  controls certain features of the encoded output.
     *               Passing {@code DEFAULT} results in output that
     *               adheres to RFC 2045.
     */
    public static byte[] encode(byte[] input, int flags) {
        return encode(input, 0, input.length, flags);
    }
 
    /**
     * Base64-encode the given data and return a newly allocated
     * byte[] with the result.
     *
     * @param input  the data to encode
     * @param offset the position within the input array at which to
     *               start
     * @param len    the number of bytes of input to encode
     * @param flags  controls certain features of the encoded output.
     *               Passing {@code DEFAULT} results in output that
     *               adheres to RFC 2045.
     */
    public static byte[] encode(byte[] input, int offset, int len, int flags) {
        Encoder encoder = new Encoder(flags, null);
 
        // Compute the exact length of the array we will produce.
        int output_len = len / 3 * 4;
 
        // Account for the tail of the data and the padding bytes, if any.
        if (encoder.do_padding) {
            if (len % 3 > 0) {
                output_len += 4;
            }
        } else {
            switch (len % 3) {
                case 0: break;
                case 1: output_len += 2; break;
                case 2: output_len += 3; break;
            }
        }
 
        // Account for the newlines, if any.
        if (encoder.do_newline && len > 0) {
            output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
                (encoder.do_cr ? 2 : 1);
        }
 
        encoder.output = new byte[output_len];
        encoder.process(input, offset, len, true);
 
        assert encoder.op == output_len;
 
        return encoder.output;
    }
 
    /* package */ static class Encoder extends Coder {
        /**
         * Emit a new line every this many output tuples.  Corresponds to
         * a 76-character line length (the maximum allowable according to
         * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>).
         */
        public static final int LINE_GROUPS = 19;
 
        /**
         * Lookup table for turning Base64 alphabet positions (6 bits)
         * into output bytes.
         */
        private static final byte ENCODE[] = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
        };
 
        /**
         * Lookup table for turning Base64 alphabet positions (6 bits)
         * into output bytes.
         */
        private static final byte ENCODE_WEBSAFE[] = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
        };
 
        final private byte[] tail;
        /* package */ int tailLen;
        private int count;
 
        final public boolean do_padding;
        final public boolean do_newline;
        final public boolean do_cr;
        final private byte[] alphabet;
 
        public Encoder(int flags, byte[] output) {
            this.output = output;
 
            do_padding = (flags & NO_PADDING) == 0;
            do_newline = (flags & NO_WRAP) == 0;
            do_cr = (flags & CRLF) != 0;
            alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
 
            tail = new byte[2];
            tailLen = 0;
 
            count = do_newline ? LINE_GROUPS : -1;
        }
 
        /**
         * @return an overestimate for the number of bytes {@code
         * len} bytes could encode to.
         */
        public int maxOutputSize(int len) {
            return len * 8/5 + 10;
        }
 
        public boolean process(byte[] input, int offset, int len, boolean finish) {
            // Using local variables makes the encoder about 9% faster.
            final byte[] alphabet = this.alphabet;
            final byte[] output = this.output;
            int op = 0;
            int count = this.count;
 
            int p = offset;
            len += offset;
            int v = -1;
 
            // First we need to concatenate the tail of the previous call
            // with any input bytes available now and see if we can empty
            // the tail.
 
            switch (tailLen) {
                case 0:
                    // There was no tail.
                    break;
 
                case 1:
                    if (p+2 <= len) {
                        // A 1-byte tail with at least 2 bytes of
                        // input available now.
                        v = ((tail[0] & 0xff) << 16) |
                            ((input[p++] & 0xff) << 8) |
                            (input[p++] & 0xff);
                        tailLen = 0;
                    };
                    break;
 
                case 2:
                    if (p+1 <= len) {
                        // A 2-byte tail with at least 1 byte of input.
                        v = ((tail[0] & 0xff) << 16) |
                            ((tail[1] & 0xff) << 8) |
                            (input[p++] & 0xff);
                        tailLen = 0;
                    }
                    break;
            }
 
            if (v != -1) {
                output[op++] = alphabet[(v >> 18) & 0x3f];
                output[op++] = alphabet[(v >> 12) & 0x3f];
                output[op++] = alphabet[(v >> 6) & 0x3f];
                output[op++] = alphabet[v & 0x3f];
                if (--count == 0) {
                    if (do_cr) output[op++] = '\r';
                    output[op++] = '\n';
                    count = LINE_GROUPS;
                }
            }
 
            // At this point either there is no tail, or there are fewer
            // than 3 bytes of input available.
 
            // The main loop, turning 3 input bytes into 4 output bytes on
            // each iteration.
            while (p+3 <= len) {
                v = ((input[p] & 0xff) << 16) |
                    ((input[p+1] & 0xff) << 8) |
                    (input[p+2] & 0xff);
                output[op] = alphabet[(v >> 18) & 0x3f];
                output[op+1] = alphabet[(v >> 12) & 0x3f];
                output[op+2] = alphabet[(v >> 6) & 0x3f];
                output[op+3] = alphabet[v & 0x3f];
                p += 3;
                op += 4;
                if (--count == 0) {
                    if (do_cr) output[op++] = '\r';
                    output[op++] = '\n';
                    count = LINE_GROUPS;
                }
            }
 
            if (finish) {
                // Finish up the tail of the input.  Note that we need to
                // consume any bytes in tail before any bytes
                // remaining in input; there should be at most two bytes
                // total.
 
                if (p-tailLen == len-1) {
                    int t = 0;
                    v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
                    tailLen -= t;
                    output[op++] = alphabet[(v >> 6) & 0x3f];
                    output[op++] = alphabet[v & 0x3f];
                    if (do_padding) {
                        output[op++] = '=';
                        output[op++] = '=';
                    }
                    if (do_newline) {
                        if (do_cr) output[op++] = '\r';
                        output[op++] = '\n';
                    }
                } else if (p-tailLen == len-2) {
                    int t = 0;
                    v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
                        (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
                    tailLen -= t;
                    output[op++] = alphabet[(v >> 12) & 0x3f];
                    output[op++] = alphabet[(v >> 6) & 0x3f];
                    output[op++] = alphabet[v & 0x3f];
                    if (do_padding) {
                        output[op++] = '=';
                    }
                    if (do_newline) {
                        if (do_cr) output[op++] = '\r';
                        output[op++] = '\n';
                    }
                } else if (do_newline && op > 0 && count != LINE_GROUPS) {
                    if (do_cr) output[op++] = '\r';
                    output[op++] = '\n';
                }
 
                assert tailLen == 0;
                assert p == len;
            } else {
                // Save the leftovers in tail to be consumed on the next
                // call to encodeInternal.
 
                if (p == len-1) {
                    tail[tailLen++] = input[p];
                } else if (p == len-2) {
                    tail[tailLen++] = input[p];
                    tail[tailLen++] = input[p+1];
                }
            }
 
            this.op = op;
            this.count = count;
 
            return true;
        }
    }
 
    private Base64() { }   // don't instantiate
}

结果

最终可以看到其站名公交车GPS速度等内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码******************************************************
2017-11-27 21:28:54
75611
0
大山子路口南
15
-1
-1
-1
-1
-1
-1
116.489674
39.986622
2017-11-27 21:29:02
0
******************************************************

相关文章

  • 解决Windows磁盘爆满却不知如何清理问题
  • 利用ping、nslookup、route、traceroute、mtr等工具排查网络异常
  • 针对包含签名加密随机生成特殊请求参数等情况使用JMeter进行压力测试
  • CentOS_MINI配置方案3_更换更新源_安装常用软件_创建管理用户_服务启动配置
  • CentOS_MINI配置方案2_配置网卡
  • CentOS_MINI配置方案1_关闭SELinux
  • PHP框架之Yaf框架入门

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

中小型研发团队架构实践:如何用好消息队列RabbitMQ?

发表于 2017-12-01

一、写在前面

使用过分布式中间件的人都知道,程序员使用起来并不复杂,常用的客户端 API 就那么几个,比我们日常编写程序时用到的 API 要少得多。但是分布式中间件在中小研发团队中使用得并不多,为什么会这样呢?

原因是中间件的职责相对单一,客户端的使用虽然简单,但整个环境搭起来却不容易。所以对于系列中的几篇中间件文章,我们重点放在解决门槛问题,把服务端环境搭好(后期可云或运维解决),把中间件的基本职责和功能介绍好,把客户端 Demo 写好,让程序员抬抬脚,在调试代码中即可轻松入门。

根据我们以往几年的经验,初次接触也可以自主快速学习,文章和 Demo 以实用为主,以下是消息队列 RabbitMQ 的快速入门及应用。

二、为什么要用消息队列 MQ

1、业务系统往往要求响应能力特别强,能够起到削峰填谷的作用。

2、解耦:如果一个系统挂了,则不会影响另外个系统的继续运行。

3、业务系统往往有对消息的高可靠要求,以及有对复杂功能如 Ack 的要求。

4、增强业务系统的异步处理能力,减少甚至几乎不可能出现并发现象:

使用消息队列,就好比为了防汛而建葛洲坝,有大量数据的堆积能力,然后可靠地进行异步输出。例如:

传统做法存在如下问题,请见上图:

  1. 一旦业务处理时间超过了定时器时间间隔,就会导致漏单。
  2. 如果采用新开线程的方式获取数据,那么由于大量新开线程处理,会容易造成服务器宕机。
  3. 数据库压力大,易并发。

使用 MQ 后的好处,请见上图:

  1. 业务可注册、可配置。
  2. 获取数据规则可配置。
  3. 成功消费 MQ 中的消息才会被 Ack,提高可靠性。
  4. 大大增强了异步处理业务作业的能力:

定时从数据库获取数据后,存入 MQ 消息队列,然后 Job 会定期扫描 MQ 消息队列,假设 Job 扫描后先预取 5 条消息,然后异步处理这 5 条消息,也就是说这 5 条消息可能会同时被处理。

三、RabbitMQ 简介

RabbitMQ 是基于 AMQP 实现的一个开源消息组件,主要用于在分布式系统中存储转发消息,由因高性能、高可用以及高扩展而出名的 Erlang 语言写成。

其中,AMQP(Advanced Message Queuing Protocol,即高级消息队列协议),是一个异步消息传递所使用的应用层协议规范,为面向消息的中间件设计。

RabbitMQ 特点如下:

高可靠:RabbitMQ 提供了多种多样的特性让你在可靠性和性能之间做出权衡,包括持久化、发送应答、发布确认以及高可用性。

高可用队列:支持跨机器集群,支持队列安全镜像备份,消息的生产者与消费者不论哪一方出现问题,均不会影响消息的正常发出与接收。

灵活的路由:所有的消息都会通过路由器转发到各个消息队列中,RabbitMQ 内建了几个常用的路由器,并且可以通过路由器的组合以及自定义路由器插件来完成复杂的路由功能。

支持多客户端:对主流开发语言(如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等)都有客户端实现。

集群:本地网络内的多个 Server 可以聚合在一起,共同组成一个逻辑上的 broker。

扩展性:支持负载均衡,动态增减服务器简单方便。

权限管理:灵活的用户角色权限管理,Virtual Host 是权限控制的最小粒度。

插件系统:支持各种丰富的插件扩展,同时也支持自定义插件,其中最常用的插件是 Web 管理工具 RabbitMQ_Management,其 Web UI 访问地址:

http://139.198.13.12:6233/,

登录账号:flight,密码:yyabc123。

四、RabbitMQ 工作原理

消息从发送端到接收端的流转过程即 RabbitMQ 的消息工作机制,请见下图:

消息发送与接收的工作机制

五、RabbitMQ 基本用法

共有 6 种基本用法:单对单、单对多、发布订阅模式、按路由规则发送接收、主题、RPC(即远程存储调用)。我们将介绍单对单、单对多和主题的用法。

1、单对单:单发送、单接收。请见下图。

2、单对多:一个发送端,多个接收端,如分布式的任务派发。请见下图:

3、主题:Exchange Type 为 topic,发送消息时需要指定交换机及 Routing Key,消费者的消息队列绑定到该交换机并匹配到 Routing Key 实现消息的订阅,订阅后则可接收消息。只有消费者将队列绑定到该交换机且指定的 Routing Key 符合匹配规则,才能收到消息。

其中 Routing Key 可以设置成通配符,如:*或 #(*表示匹配 Routing Key 中的某个单词,# 表示任意的 Routing Key 的消息都能被收到)。如果 Routing Key 由多个单词组成,则单词之间用. 来分隔。

命名规范:

交换机名的命名建议:Ex{AppID}.{自定义 ExchangeName},队列名的命名建议:MQ{AppID}.{自定义 QueueName} 。

六、Demo 下载及更多资料

RabbitMQDemo 下载地址:github.com/das2017/Rab…

RabbitMQ 的官方网址:www.rabbitmq.com

本系列文章涉及内容清单如下(并不按这顺序发布),其中有感兴趣的,欢迎关注:

  • 开篇:中小型研发团队架构实践三要点
  • 缓存 Redis
  • 消息队列 RabbitMQ
  • 集中式日志 ELK
  • 任务调度 Job
  • 应用监控 Metrics
  • 微服务框架 MSA
  • 搜索利器 Solr
  • 分布式协调器 ZooKeeper
  • 小工具:Dapper.NET/EmitMapper/AutoMapper/Autofac/NuGet
  • 发布工具 Jenkins
  • 总体架构设计:电商如何做企业总体架构?
  • 单个项目架构设计
  • 统一应用分层:如何规范公司所有应用分层?
  • 调试工具 WinDbg
  • 单点登录
  • 企业支付网关
  • 结篇

作者介绍

张辉清,10 多年的 IT 老兵,先后担任携程架构师、古大集团首席架构、中青易游 CTO 等职务,主导过两家公司的技术架构升级改造工作。现关注架构与工程效率,技术与业务的匹配与融合,技术价值与创新。

杨丽,拥有多年互联网应用系统研发经验,曾就职于古大集团,现任职中青易游系统架构师,主要负责公司研发中心业务系统的架构设计以及新技术积累和培训。现阶段主要关注开源软件、软件架构、微服务以及大数据。

感谢雨多田光对本文的审校。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…922923924…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%