0%

文章字数:455,阅读全文大约需要1分钟

shiro是一个可以管理项目权限的框架

springBoot中引入

maven依赖添加

1
2
3
4
5
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>

密码加密工具类

shiro提供了加密、生成随机盐的方法,封装成工具类

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
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

/**
* 密码加密工具类
*/
public class PasswordHelper{
//随机字符串生成器
private RandomNumberGenerator randomOb = new RandomNumberGenerator();

//散列算法名(md5方式)
private static final String ALGORITHM_NAME = "md5";

//散列迭代次数
private static final int HASH_ITERATION = 2;

/**
* 加密用户
* @param user 用户,用户名(name)、密码(pwd)、盐\加密因子(salt)
*/
public void encryptPassword(User user){
//生成加密因子,保存盐。
user.setSalt(randomOb.nextBytes().toHex());
//加密密码 SimpleHash(算法名,密码,盐的byte,次数).toHex()
String newPassword = new SimpleHash(ALGORITHM_NAME ,user.getPwd,ByteSource.Util.bytes(user.getSalt()),HASH_ITERATION).toHex();
//更新密码
user.setPwd(newPassword );
}
}

自定义Realm

shiro需要我们提供doGetAuthenticationInfodoGetAuthorizationInfo的实现,已完成登录认证和权限信息。
自定义Realm,继承与AuthorizingRealm

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

/*
* 为shiro提供登录验证和权限提取
*/
public class EnceladusShiroRealm extends AuthorizingRealm{
//用户操作业务层
@Autowired
private UserService userService;

/*
* 提供用户角色和权限获取的逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//需要返回的权限信息
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String userName = (String) principals.getPrimaryPrincipal();
User user = userService.findUserByName(userName);
for (SysRole role : user.getRoles()) {
//设置角色
authorizationInfo.addRole(role.getRole());
for (SysPermission permission : role.getPermissions()) {
//设置权限
authorizationInfo.addStringPermission(permission.getName());
}
}
return authorizationInfo;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//通过token获取用户名
String userName = (String) token.getPrincipal();
User user = userService.findUserByName(username);
if (user == null) return null;
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()),
getName());
return authenticationInfo;
}
}

shiro配置

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
@Configuration
public class ShiroConfig {
//配置权限
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);

Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthc");
shiroFilterFactoryBean.setSuccessUrl("/home/index");

filterChainDefinitionMap.put("/*", "anon");
filterChainDefinitionMap.put("/authc/index", "authc");
filterChainDefinitionMap.put("/authc/admin", "roles[admin]");
filterChainDefinitionMap.put("/authc/renewable", "perms[Create,Update]");
filterChainDefinitionMap.put("/authc/removable", "perms[Delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}

/*
* 提供散列算法的信息
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME); // 散列算法
hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS); // 散列次数
return hashedCredentialsMatcher;
}

/*
* 设置加密算法
*/
@Bean
public EnceladusShiroRealm shiroRealm() {
//自己实现的Realm
EnceladusShiroRealm shiroRealm = new EnceladusShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); // 设置加密算法
return shiroRealm;
}

/*
* 设置安全管理类的realm为上面写的realm
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}

@Bean
public PasswordHelper passwordHelper() {
return new PasswordHelper();
}
}
1
2
3
4
5
常用的过滤器如下:
authc:所有已登陆用户可访问
roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致
perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致
anon:所有用户可访问,通常作为指定页面的静态资源时使用

test

获取权限的操作

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
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.learnhow.springboot.web.PasswordHelper;
import com.learnhow.springboot.web.entity.User;
import com.learnhow.springboot.web.service.UserService;

@RestController
@RequestMapping
public class HomeController {
@Autowired
private UserService userService;
@Autowired
private PasswordHelper passwordHelper;

@GetMapping("login")
public Object login() {
return "Here is Login page";
}

@GetMapping("unauthc")
public Object unauthc() {
return "Here is Unauthc page";
}

@GetMapping("doLogin")
public Object doLogin(@RequestParam String username, @RequestParam String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
return "password error!";
} catch (UnknownAccountException uae) {
return "username error!";
}

User user = userService.findUserByName(username);
subject.getSession().setAttribute("user", user);
return "SUCCESS";
}

@GetMapping("register")
public Object register(@RequestParam String username, @RequestParam String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
passwordHelper.encryptPassword(user);

userService.saveUser(user);
return "SUCCESS";
}
}

权限拦截

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
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.learnhow.springboot.web.entity.User;

@RestController
@RequestMapping("authc")
public class AuthcController {

@GetMapping("index")
public Object index() {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession().getAttribute("user");
return user.toString();
}

@GetMapping("admin")
public Object admin() {
return "Welcome Admin";
}

// delete
@GetMapping("removable")
public Object removable() {
return "removable";
}

// insert & update
@GetMapping("renewable")
public Object renewable() {
return "renewable";
}
}

文章字数:632,阅读全文大约需要2分钟

该工具类主要收集了研究ssl连接时用到的相关知识点

主要内容

  1. 通过证书创建keystore
  2. keystore中取证书
  3. 通过证书、keystore等多种方式创建SSLSocketFactory
  4. 单向认证、双向认证
  5. 通过证书链创建SSLSocketFactory
  6. 通过trustStore验证证书及证书链

代码

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
import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.*;
import java.io.*;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;

/***
* ssl工具类
*
* @author colin.cheng
* @date 2021-02-03 14:39
* @since 1.0.0
*/
@Slf4j
public class SslUtil {

/**
* 读取x509格式的证书
*
* @param certPath
* @return
* @throws FileNotFoundException
* @throws CertificateException
*/
public static X509Certificate getX509Certificate(String certPath) throws Exception {
try (InputStream inStream = new FileInputStream(certPath)) {
return getX509Certificate(inStream);
}
}

/**
* 通过输入流获取证书
*
* @param inStream
* @return
* @throws Exception
*/
public static X509Certificate getX509Certificate(InputStream inStream) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(inStream);
return caCert;
}

/**
* 从keystore中获取证书
*
* @param keyStorePath keystore位置
* @param keyStorePwd keyStore密码
* @param keyStoreType keyStore类型
* @param alias 证书存储的别名
* @return
* @throws Exception
*/
public static Certificate getCertificate(String keyStorePath, String keyStorePwd, String keyStoreType, String alias)
throws Exception {
final KeyStore keyStore = getKeyStore(keyStorePath, keyStorePwd, keyStoreType);
return keyStore.getCertificate(alias);
}

/**
* 获取KeyStore
*
* @param keyStorePath 路径
* @param keyStorePwd 密码
* @param keyStoreType 类型
* @return
*/
public static KeyStore getKeyStore(String keyStorePath, String keyStorePwd, String keyStoreType) throws Exception {
try (InputStream inputStream = new FileInputStream(keyStorePath)) {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(inputStream, keyStorePwd.toCharArray());
return keyStore;
}
}

/**
* 加载DER格式的私钥证书
* @param filename
* @return
* @throws Exception
*/
public static PrivateKey getDERPrivateKey(String filename) throws Exception {
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}

/**
* 根据证书库和受信证书库构建双向认证sslSocket工厂
*
* @param p12TrustStore p12受信库
* @param p12KeyStore p12证书库
* @param ksPwd 证书库密码
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByPKCS12(String p12TrustStore, String tsPwd, String p12KeyStore,
String ksPwd) throws Exception {
final String P12_TYPE = "pkcs12";
KeyStore trustStore = getKeyStore(p12TrustStore, tsPwd, P12_TYPE);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyStore keyStore = getKeyStore(p12KeyStore, ksPwd, P12_TYPE);
// 创建管理JKS密钥库的密钥管理器 (SunX509)
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 使用密钥内容源初始化此工厂。 提供者通常使用 KeyStore 来获取在安全套接字协商期间所使用的密钥内容
kmf.init(keyStore, ksPwd.toCharArray());
// 初始sslcontext
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}

/**
* 通过证书链构造SSLSocketFactory(单向认证)
* @param keyPath
* @param pwd
* @param ca
* @param crt
* @return
* @throws Exception
*/
public static SSLSocketFactory getSocketFactoryByCrtList(String keyPath, String pwd, String ca, String crt) throws Exception {
final KeyStore keyStore = KeyStore.getInstance("pkcs12");
keyStore.load(null);
PrivateKey privateKey = getDERPrivateKey(keyPath);
keyStore.setKeyEntry("cert", privateKey, pwd.toCharArray(), new Certificate[]{getX509Certificate(crt), getX509Certificate(ca)});
// 创建管理JKS密钥库的密钥管理器 (SunX509)
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 使用密钥内容源初始化此工厂。 提供者通常使用 KeyStore 来获取在安全套接字协商期间所使用的密钥内容
kmf.init(keyStore, null);
// 初始sslcontext
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
// 单向认证,不验证证书以及ip。方便测试
final TrustManager trustManager = new X509ExtendedTrustManager(){

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {

}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {

}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
sslContext.init(kmf.getKeyManagers(), new TrustManager[] {trustManager}, new SecureRandom());
return sslContext.getSocketFactory();
}

/**
* 需要添加受信的证书
*
* @param certAlias 证书别名
* @param cert 证书
* @param trustStore 受信库路径
* @param pwd 受信库密码
* @return
*/
public static boolean trustCert(String certAlias, Certificate cert, String trustStore, String pwd,
String storeType) {
boolean isOk = true;
try {
initTrustStoreIfNotExist(trustStore, pwd, storeType);
} catch (Exception e) {
log.error("create trustStore error", e);
isOk = false;
}
if (isOk) {
try (FileInputStream ips = new FileInputStream(trustStore)) {
KeyStore ks = KeyStore.getInstance(storeType);
ks.load(ips, pwd.toCharArray());
ks.deleteEntry(certAlias);
ks.setCertificateEntry(certAlias, cert);
try (FileOutputStream ops = new FileOutputStream(trustStore)) {
ks.store(ops, pwd.toCharArray());
}
} catch (Exception e) {
log.error("authorization certificate error", e);
isOk = false;
}
}
return isOk;
}

/**
* 如果不存在创建文件
*
* @param trustStore 文件路径
* @return 是否新建
*/
public static void initTrustStoreIfNotExist(String trustStore, String pwd, String storeType) throws Exception {
File trustStoreFile = new File(trustStore);
if (!trustStoreFile.getParentFile().exists()) {
trustStoreFile.getParentFile().mkdirs();
}
if (!trustStoreFile.exists()) {
trustStoreFile.createNewFile();
KeyStore ks = KeyStore.getInstance(storeType);
try (FileOutputStream ops = new FileOutputStream(trustStoreFile)) {
ks.load(null);
ks.store(ops, pwd.toCharArray());
}
}
}

/**
* 使用TrustStore检测证书(链)是否受信
*
* @param crt
* @param certPath
* @return
*/
public static boolean authCertByTrustStore(String crt, String... certPath) {
boolean res = true;
try {
// 需要校验的证书
X509Certificate[] crts = new X509Certificate[certPath.length];
for (int i = 0; i < certPath.length; i++) {
crts[i] = getX509Certificate(certPath[i]);
}
KeyStore trustStore = KeyStore.getInstance("pkcs12");
trustStore.load(null);
trustStore.setCertificateEntry("ca", getX509Certificate(crt));
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
final TrustManager[] trustManagers = tmf.getTrustManagers();
for (int i = 0; i < trustManagers.length; i++) {
X509TrustManager manager = (X509TrustManager)trustManagers[i];
// 检测不成功会抛出异常
// manager.checkClientTrusted(new X509Certificate[] {certificate}, "RSA");
manager.checkServerTrusted(crts, "RSA");
}
} catch (Exception e) {
log.error("auth cert error ", e);
res = false;
}
return res;
}
}

文章字数:1850,阅读全文大约需要7分钟

bat是windows的dos界面下对于各种命令操作的一中批处理语言,可以作为胶水语言连接其他语言,也可以处理简单的操作以及执行操作系统命令。
普通文本文件后缀改成.bat就可以成为批处理文件

注释

  • REM xxx
  • :: xxx

基础美观操作

  • title newTitle设置标题
  • color 01设置颜色,0是黑色背景,1是蓝色前景。其他搭配见color /?
  • mode con cols=113 lines=15 & color 9f设置列113、行15、颜色9f
  • pause暂停,提示任意输入继续

#转义

  • ,相当空格
  • ^转义字符
  • 命令1;命令2报错后会继续执行

基础输入输出

  • echo "你想说什么"输出文字
  • echo off关闭c:\user>_ 的前缀及其他提示,只留下光标
  • echo on恢复回显
  • @echo off连自身的命令回显都不要
  • echo.输出空行,换行
  • echo "somethine">filename把信息写入文件(覆盖)
  • echo "somethine">>filename把信息写入文件(追加)
  • echo (Ctrl+G)喇叭鸣响,对个ctrl+G可响多次
  • %0 %1 %2调用批处理时传的参数,%0自身%1第1个

    例:复制自身copy %0 d:\wind.bat

  • set /p choice= 输入一个字符:获取输入

组合命令

  • 第一个命令|第二个命令管道命令,第一个的输出当成第二个的输入。

    例:dir c:\|find “txt”

  • 命令一&命令二组合执行等于
    1
    2
    3
    4
    (
    命令一
    命令二
    )
  • && 和 || 同java短路

循环for

  • for %%i in (set) do somthing有/d /l /r /f 四个属性

/D属性

  • for /d %%i in (c:\*) do echo %%i搜索C盘下的文件
  • for /d %%i in (???) do echo %%i搜索当前目录下有1-3个字母的文件

/R属性

  • FOR /R [[drive:]path] %%variable IN (set) DO command [command-parameters]/r后制定根目录,遍历目录树,如果没有路径默认当前目录。set为匹配规则
  • for /r c:\ %%i in (*.exe) do echo %%i找c盘下的exe文件

/L属性

  • for /l %%i in (0,2,6) do @echo %%i从0开始,每次加2,直到6.

/F属性

  • FOR /F “eol=; tokens=1,3* delims=,- ” %%i in (test.txt) do echo %%i %%j %%k处理文件test.txt
    1
    2
    3
    eol=;          分号开头的行为注释行
    tokens=1,3* 将每行第1段,第3段和剩余字段分别赋予变量%%i,%%j,%%k
    delims=,- (减号后有一空格)以逗号减号和空格为分隔符,空格必须放在最后

    循环if

  • 判断
    1
    2
    3
    4
    5
    6
    set /p p=请输入
    if %p% LEQ 9 (
    echo %p%
    ) else (
    echo 9
    )
  • 关系符
含义 关系符
等于 equ
大于 gtr
大于或等于 geq
小于 lss
小于或等于 leq
不等于 neq
- 存在
1
2
3
if exist %cd%\test.LOG (
echo 存在!
)
- 不存在
1
2
3
if not exist %cd%\test.LOG (
echo 不存在!
)
# 跳转
- :start标注一个叫start的点
- GOTO start跳转到start

文件关联

  • assoc设置’文件扩展名’关联,关联到’文件类型’
  • ftype设置’文件类型’关联,关联到’执行程序和参数’
  • assoc显示所有’文件扩展名’关联
  • assoc .txt显示.txt代表的’文件类型’,结果显示 .txt=txtfile
  • assoc .doc显示.doc代表的’文件类型’,结果显示 .doc=Word.Document.8
  • assoc .exe显示.exe代表的’文件类型’,结果显示 .exe=exefile
  • ftype显示所有’文件类型’关联
  • ftype exefile显示exefile类型关联的命令行,结果显示 exefile=”%1” %*
  • assoc .txt=Word.Document.8设置.txt为word类型的文档,可以看到.txt文件的图标都变了

ftype exefile=”%1” %*
恢复 exefile 的正确关联
如果该关联已经被破坏,可以运行 command.com ,再输入这条命令

变量延迟

1
2
3
4
5
6
@echo off
setlocal enabledelayedexpansion
set a=4
set a=5 & echo !a!
pause
结果:5

文件权限

1
2
3
4
5
6
7
8
9
10
11
ATTRIB [+R|-R] [+A|-A] [+S|-S] [+H|-H] [[drive:] [path] filename] [/S [/D]]
+ 设置属性。
- 清除属性。
R 只读文件属性。
A 存档文件属性。
S 系统文件属性。
H 隐藏文件属性。
[drive:][path][filename]
指定要处理的文件属性。
/S 处理当前文件夹及其子文件夹中的匹配文件。
/D 也处理文件夹。

系统变量

  • %ALLUSERSPROFILE%本地 返回“所有用户”配置文件的位置。
  • %APPDATA%本地 返回默认情况下应用程序存储数据的位置。
  • %CD%本地 返回当前目录字符串。
  • %CMDCMDLINE%本地 返回用来启动当前的 Cmd.exe 的准确命令行。
  • %CMDEXTVERSION%系统 返回当前的“命令处理程序扩展”的版本号。
  • %COMPUTERNAME%系统 返回计算机的名称。
  • %COMSPEC%系统 返回命令行解释器可执行程序的准确路径。
  • %DATE%系统 返回当前日期。使用与 date /t 命令相同的格式。由 Cmd.exe 生成。有关
    date 命令的详细信息,请参阅 Date。
  • %ERRORLEVEL%系统 返回上一条命令的错误代码。通常用非零值表示错误。
  • %HOMEDRIVE%系统 返回连接到用户主目录的本地工作站驱动器号。基于主目录值而设置。用户主目录是在“本地用户和组”中指定的。
  • %HOMEPATH%系统 返回用户主目录的完整路径。基于主目录值而设置。用户主目录是在“本地用户和组”中指定的。
  • %HOMESHARE%系统 返回用户的共享主目录的网络路径。基于主目录值而设置。用户主目录是在“本地用户和组”中指定的。
  • %LOGONSERVER%本地 返回验证当前登录会话的域控制器的名称。
  • %NUMBER_OF_PROCESSORS%系统 指定安装在计算机上的处理器的数目。
  • %OS%系统 返回操作系统名称。Windows 2000 显示其操作系统为 Windows_NT。
  • %PATH%系统 指定可执行文件的搜索路径。
  • %PATHEXT%系统 返回操作系统认为可执行的文件扩展名的列表。
  • %PROCESSOR_ARCHITECTURE%系统 返回处理器的芯片体系结构。值:x86 或 IA64 基于Itanium
  • %PROCESSOR_IDENTFIER%系统 返回处理器说明。
  • %PROCESSOR_LEVEL%系统 返回计算机上安装的处理器的型号。
  • %PROCESSOR_REVISION%系统 返回处理器的版本号。
  • %PROMPT%本地 返回当前解释程序的命令提示符设置。由 Cmd.exe 生成。
  • %RANDOM%系统 返回 0 到 32767 之间的任意十进制数字。由 Cmd.exe 生成。
  • %SYSTEMDRIVE%系统 返回包含 Windows server operating system 根目录(即系统根目录)的驱动器。
  • %SYSTEMROOT%系统 返回 Windows server operating system 根目录的位置。
  • %TEMP%%TMP%系统和用户 返回对当前登录用户可用的应用程序所使用的默认临时目录。有些应用程序需要 TEMP,而其他应用程序则需要 TMP。
  • %TIME%系统 返回当前时间。使用与 time /t 命令相同的格式。由 Cmd.exe 生成。有关
    time 命令的详细信息,请参阅 Time。
  • %USERDOMAIN%本地 返回包含用户帐户的域的名称。
  • %USERNAME%本地 返回当前登录的用户的名称。
  • %USERPROFILE%本地 返回当前用户的配置文件的位置。
  • %WINDIR%系统 返回操作系统目录的位置。

自动ftp下载

ftp -n -s:[[drive:]path]filename
filename文件内容

1
2
3
4
5
6
7
8
9
10
open 90.52.8.3   #打开ip
user iware #用户为iware
password8848 #密码
bin #二进制传输模式
prompt
cd tmp1 #切换至iware用户下的tmp1目录
pwd
lcd d:\download #本地目录
mget * #下载tmp1目录下的所有文件
bye #退出ftp

通过ping实现延迟,完成进度条

1
2
3
4
5
6
7
8
9
10
11
12
@echo off
mode con cols=113 lines=15 &color 9f
cls
echo.
echo 程序正在初始化…
echo.
echo ┌──────────────────────────────────────┐
set/p= ■<nul
for /L %%i in (1 1 38) do set /p a=■<nul&ping /n 1 127.0.0.1>nul
echo 100%%
echo └──────────────────────────────────────┘
pause

随机数

生成5个100以内的随机数

1
2
3
4
5
6
@echo off
setlocal enabledelayedexpansion
for /L %%i in (1 1 5) do (
set /a randomNum=!random!%%100
echo 随机数:!randomNum!
)

文章字数:2606,阅读全文大约需要10分钟

Zookeeper

序号 参数名 说明
7 snapCount 设置多少次事务日志输出后,触发一次快照(snapshot),此时ZK会生产一个snapshot.*文件,同时创建一个新的事务日志文件log.*,默认100000,真实实现会增加一定的随机数,避免所有服务器同一时间快照影响性能

节点

  • create /aaa val创建节点aaa并赋予值val。节点必须有值,否则不能创建

  • zk视图结构和标准unix文件系统类似,从/根节点出发。

  • 节点成为ZNode,每个节点可存储数据,也可以挂载子节点。因此可以成为树

  • 节点类型(不同类型节点名也不能重复)

  1. 持久节点,和客户端连接,断开后数据还在(ZNode
  2. 临时节点,和客户端断开后,数据不在
  3. 顺序节点,临时节点和持久节点都能创建顺序节点,每次创建节点名都会自动递增(名字+自动生成的序列)

节点状态属性

序号 属性 数据结构 描述
1 czxid long 节点被创建的Zxid
2 mzxid long 节点被修改的Zxid
3 paxid long 节点最后一次被修改时的事务ID
4 ctime long 节点被创建的时间
5 mtime long 节点最后一次被修改时间
6 dataVersoin long 节点被修改的版本号(每次修改+1)(CAS保证分布式数据原子性)
7 cversion long 节点所拥有子节点被修改的版本号
8 aversion long 节点的ACL被修改的版本号
9 emphemeralOwner long 如果此阶段为临时节点,这个值就是节点拥有者会话ID,否则0
10 dataLength long 节点数据域长度
umChildren long 节点拥有的子节点个数

ACL

  • getAcl /xxx查看xxx的权限

    1
    2
    'world',anyone
    :cdrwa
  • scheme授权机制, id用户id给谁授权, permissions权限,只读、读写、管理等。

    • create(c)
    • delete(d)
    • read(r)
    • write(w)
    • admin(a) 是否能给子节点设置权限

    机制有

  • world,下面只有一个id,叫anyone, world:anyone代表任何人,ZK中对搜有人有权限的节点就是属于world:anyone

  • auth, 它不需要id, 只需要通过authenticationuser都有权限。ZK支持通过kerberos来进行authencation,也支持username/password形式的authentication(明文)

  • digest, 通过对应的id为username:BASE64(SHA1(password)), 它需要先通过username:password形式的authentication(密文)

  • ip, 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16,表示匹配16bitIP

常用ACL命令

  • getAcl获取指定节点的ACL信息

  • setAcl设置指定节点的ACL信息

    1
    2
    3
    addauth digest username:pwd # 添加用户(会话级别的,退出需要重新操作)
    setAcl /xxx/zzz auth:username:pwd:crwa #给用户添加 auth机制的crwa权限
    setAcl /xxx/zzz digest:username:xxxxmd5:crwa #digest机制的权限添加,密文生产在zookeeper自带的java类 DigestAuthenticationProvider里
  • addauth注册绘画授权信息

  • 注册超级管理员用户可以解决没有权限不能删除的问题

使用

  1. zkCli.sh -server ip连接ZooKeeper服务,连接成功后系统会输出相关环境及配置
  2. 基本操作
    • 显示根目录下、文件:ls / 查看当前ZooKeeper包含的内容
    • 显示根目录下、文件:ls2 /查看当前节点数据并能看到更新次数等数据
    • 创建文件,并设置初始内容。create /zk "val" 创建一个新的znode节点zk ,以及初始化内容 -e 临时节点(客户端端口就删除) -s 顺序节点(名字自增)
    • 获取内容get /zk 确认znode是否包含我们所创建的字符串
    • 修改set /zk "val2"修改节点内容
    • 删除delete /zk将指定znode删除,如果有子节点,删除失败
    • 递归删除rmr /zk删除节点及子节点
    • 退出quit

四字命令

zk支持使用某些特点四字的命令交互获取服务当前状态,可通过telnetnc提交命令

  1. echo stat|nc ip port查看那个节点被选择作为follower或者leader
  2. 使用echo ruok|nc ip port测试是否启动了该server若回复imok表示已经启动
  3. echo dump|nc ip port列出未经处理的会话和临时节点
  4. echo kill |nc ip port关闭server
  5. echo conf | nc ip port输出相关服务配置的详细信息
  6. echo cons | nc ip port列出所有连接到服务器的客户端完全的连接/会话的详细信息
  7. echo envi|nc ip port输出关于服务环境的详细信息
  8. echo reqs|nc ip port列出未经处理的请求
  9. echo wchs|nc ip port列出服务器watch的详细信息
  10. echo wchc|nc ip port通过session列出服务器的watch详细信息,输出的是一个与watch相关会话的列表
  11. echo wchp|nc ip port通过路径列出服务器的watch的详细信息,输出的是与session相关的路径

可视化

  1. 事务日志可视化LogFormatter

    1
    java -cp ../../zookeeper-3.4.5.jar;../../lib/slf4j-api-1.6.1.jar org.apache.zookeeper.server.logFormatter log.xxx
  1. 数据快照可视化SnapshotFormatter

    1
    java -cp ../../zookeeper-3.4.5.jar;../../lib/slf4j-api-1.6.1.jar org.apache.zookeeper.server.SnapshotFormatter snapshot.xxx

一致性原理

2pc

2pc 两段提交,强一致性算法。常用于分布式数据库中。

  • 术语
  1. undo记录原始数据,用于回滚。
  2. redo正常提交数据
  • 流程

    1. 第一阶段,所有资源数据库都写入undoredo到事务日志
      1. 第二阶段,所有资源都返回ok,则全部执行commit,否则rollback
  • 缺点

    1. 同步阻塞,所有都成功才能成功。
      1. 单点故障,一个返回失败,都失败
      2. 数据不一致,网络延迟导致一个资源commit了,另一个没有commit
      3. 容错机制不完善,一个失败都失败

3pc

先询问资源是否可以访问再进行2pc相同步骤

不一样的是第三阶段协调者如果网络超时或异常,参与者也会commit

优点

1. 改善同步阻塞(不会因为某些访问超时占用时间)
   2. 解决单点故障

paxos算法

少数服从多数,角色轮换避免单点故障

第一阶段,提议者订一个K值,然后访问所有资源(prepare请求),多数回应ok就进行下一阶段,否则k+1再重新请求

第二阶段, 提交数据,绝大部分返回ok则整体成功,否则重新进行第一阶段

问题

  1. 主导者故障(单点故障)
  2. 最终一致性

协议要求

  1. 资源端必须接受第一个prepare
  2. 第一个prepare的数据必须要接受

多提议者情况下(解决单点故障)

若干提议者发起prepare,若多数资源同意则进入下一阶段。若同意没超过半数,则k增加再进行prepare。资源会同意更高K的prepare。当提议者认为自己的支持者超过半数就会进行第二阶段,

提交accept,如果资源在次期间遇到更高k的prepare,则会拒绝当前accept,等待最高k发起的提议者的accept。accept接受超过半数则成功,否则k增加,重新prepare。成功后全体接受成功的accept。

ZK使用

Zookeeper原生客户端

1
2
3
4
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
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
private final static String CONNECT = "192.168.1.1:8088,xxxx";// 多个用逗号隔开
private static CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper(CONNECT, 5000, new Watch(){// 5000超时
public void process(WatchedEvent watchedEvent) {
// 如果获取到了连接
if(watchedEvent.getState() == Event.keeperState.SyncConnected) {
countDownLatch.countDown();
}
}
});

countDownLatch.watch();//等待连接
zookeeper.create("/path", "val".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
Stat stat = new Stat();// 节点状态
// watch为true代表监听此节点,节点内容发生变化会回调连接时注册的watch。watch是一致性的
// watch之后只会返回当前session最后一次修改此节点的内容,即多个setData,只会最后一次回调
byte[] data = zookeeper.getData("/path", true, stat);// 返回值

List<String> childrens = zookeeper.getChildren("/path", true);//[a,b,c]的形式返回子节点

ACL acl = new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvicer.generateDigest("root:root")));
ACL acl2 = new ACL(ZooDefs.Perms.CREATE, new Id("ip", "192.168.1.1"));
List<ACL> acls = new ArrayList<>();
acls.add(acl);
acls.add(acl2);
// 创建持久节点
zookeeper.create("path", "val".getBytes(), acls, CreateMode.PERSISTENT);
// 添加digest方案的权限
zookeeper.addAuthInfo("digest", "root:root".getBytes());

问题

  1. 会话连接是异步的
  2. watch需要重复注册,一次watch只能监听一个
  3. 缺少session重连机制
  4. 复杂,缺少很多功能,例如级联新增

ZkClient

1
2
3
4
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
</dependency>
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
private final static String CONNECT_HOST = "192.168.1.1:8000";
// zkClient不用监听
ZkClient zkClient = new ZkClient(CONNECT_HOST, 4000);
// 提供了递归创建父节点的功能,true代表级联新增,false代表不允许(父节点不存在就会报错)
zkClient.createPersistent("/zk/zkclient/zkclient1", true);
// 获取子节点 [node1,node2,node3]
List<String> list = zkClient.getChildren("/zkclient");
// 监听
// 节点内容修改
zkClient.subscribeDataChange("/nodeName", new IZkDataListener() {
public void handleDataChange(String nodeName, Object newVal) throws Exception {
// nodeName节点名称 newVal修改后的值
}

public void handleDataDeleted(String nodeName) throws Exception {
// ...
}
});

// nodeName中的子节点发升变化触发
zkClient.subscribeChildChanges("/nodeName", new IZkChildListener()) {
public void handleChildChange(String nodeName, List<String> list) throws Exception {
// nodeName节点名 list节点列表
}
}
// 监听器
// subscriptStateChanges
// 权限
public void addAuthInfo(String scheme, final byte[] auth);
public void setAcl(final String path, final List<ACL> acl);

Curator

1
2
3
4
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
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
private final static String CONNECT_HOST="192.168.1.1:8000";
// 尝试三次连接,失败1000毫秒后重试,第二次重试时间2*1000毫秒,第三次3*1000
CuratorFramework curatorFramework = CuratorFrameworkFactory.
newCliebt(CONNECT_HOST, 5000, 5000, new ExponentialBackoffRetry(1000, 3));
curatorFramework.start();// 启动连接

// 另一种写法
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString(CONNECT_HOST).sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
curatorFramework.start();// 启动连接
// 创建节点,返回节点路径
String val = curatorFramework.create()
.createingParentsIfNeeded()//级联创建父节点
.withMode(CreateMode.PERSISTENT)//持久节点
.forPath("/path/path1/path2", "val".getBytes());
// 删除节点,级联删除
curatorFramework.delete().deleteChildrenIfNeed().forPath("/path");

// 异步执行
final CountDownLatch countDownLatch = new CountDownLatch(1);
ExcutorService service = Executors.newFixedThreadPool(1);
curatorFramework.create().createingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
.inBackground(new BackgroundCallback(){
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throw Exception {
curatorFramework.getResultCode();// 结果
curatorFramework.getType();
countDownLatch.countDown();
}
},server).forPath("/path/path1/path2", "val".getBytes());
// 事务, 同时成功才成功
Collection<CuratorTransactionResult> resultCollections = curatorFramework.inTransaction()
.create().forPath("/path/path1/path2", "val".getBytes()).and()
.setData().forPath("/path/path1/path2", "v1".getBytes()).and().commit();

for(CuratorTransactionResult res: resultCollections) {
res.getForPath();// 节点路径
res.getType();// 结果
}

// watch机制
// Pathcache 监听一个路径下子节点的创建、删除、数据更新
// NodeCache 节点的创建、删除、更新
// TreeCache Pathcache+NodeCache
NodeCache nodeCache = new NodeCache(curatorFramework, "/curator", false);
nodeCache.start(true);
nodeCache.getListenable().addListener(()->System.out.println("节点变化,变成了" + new String(nodeCache.getCurrentData().getData())));

pathCache.getListenable().addListener((framework, event)->{
event.getType();//CHILD_ADD CHILD_REMOVED CHILD_UPDATE 子节点增加、删除、更新
});

集群

特点

  1. 顺序一致性,命令执行顺序一致
  2. 原子性,集群中所有机器都成功,否则失败
  3. 单一视图,连接集群任意一个机器数据一致
  4. 可靠性,一个更新被操作之前,数据不变
  5. 实时性,一个节点更改,其他节点很短时间内同步
  6. 角色轮换,避免故障

角色

  1. leader任务调度,事务处理(增,删,改)
  2. follower非事务请求,读。参与投票
  3. observer观察者,读,不参与投票。(3.30以上提供,增加效率)

配置集群

zoo.cfg

1
2
3
server.0=192.168.1.2:2333:2444 # 选举端口:通讯端口
server.1=192.168.1.3:2333:2444 # 1代表 myid ,集群名称,必须是数字
server.2=192.168.1.4:2333:2444

ZAB协议

类似paxos,zk自己实现的协议


文章字数:1887,阅读全文大约需要7分钟

简介

动态规划是运筹学的一个分支。(管理学的重要专业基础课,利用统计学、数学模型、算法等寻找复杂问题的最佳或近似最佳的答案)
动态规划的核心是以局部最优解求全局最优解
重点

  1. 子问题
    原问题由多个子问题组成
    例如 1+2+4+8,原问题是求四个数的和,子问题是当前数+之前数的和
  2. 动态规划状态
    即子问题可能的结果,最优解
  3. 边界状态值
    无法用子问题概括的值,例如 1+2+4+8中第一个和第二个值,需要先1+2之后的值才能用当前数+之前数的和来概括
  4. 状态转移方程
    上一个子问题解如何转换成另一个子问题的解

一、最大子序和

  • 题目
    给定一个整数数组nums,找到一个具有最大和的连续子数组(子数组至少包含一个元素),返回其最大和。

  • 分析

    1. 子问题,以i个位置上的数结尾的子数组最大和,求出每个位置的最大和,最大子数组就是其中最大的值。
    2. 动态规划状态
      i个位置结尾的子数组最大和
    3. 边界状态
      1位置结尾的数组最大和
    4. 状态转移方程
      对于每一个位置结尾的子数组,其最大值只有两种可能
      第一种,上一个位置结尾的连续数组最大值+当前位置值(连续之前的数组)
      第二种,当前位置的值(从新开始子数组)
      求这两种操作的最大值就是当前位置结尾的连贯子数组最大值
      dp[i] = max(dp[i - 1] + num[i], mun[i])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int maxSubArray(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
}
int max = dp[0];
for (int i : dp) {
max = Math.max(i, max);
}
return max;
}
}

二、爬楼梯

  • 题目
    假设你在爬楼梯,需要n(整数)阶才能到达楼顶。
    每次可以爬1或者2个台阶。
    问:
    有多少种方法爬到楼顶

  • 分析

    1. 子问题:原问题是到达n阶有多少种走法,n阶的走法由n-1阶台阶走法数量和n-2阶台阶走法数量组成。因为一次只能走1或者2个台阶,所以第n个台阶只能是由n-1或者n-2台阶走上来的,且n-1n-2走上来都只有一种方法。所以子问题就是登上n-1个台阶走法数量。
    2. 动态规划状态,第i个状态就是i个阶梯的走法数量
    3. 边界值状态,第1和第2阶台阶的走法
    4. 状态转移方程,dp[i] = dp[i-1] + dp[i-2]
  1. 设置递推数组 dp[n],dp[i]代表到达第i阶时有多少种走法
  2. 设置dp[1]dp[2]的值,即第一阶和第二阶的走法数量
  3. 利用i循环递推,从3n阶计算结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int climbStairs(int n) {
if(n == 1) {
return 1;
}
if(n == 2) {
return 2;
}
int[] dp = new int[n];
dp[0] = 1;
dp[1] = 2;
for(int i=2; i<n ;i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n - 1];
}
}

三、打家劫舍

  • 问题
    你是一个专业的小偷,计划沿街窃取钱财。街上排列有若干房屋,相邻房屋装有相同的防盗系统,如果两个房屋都失窃会自动报警。
    问:
    给定一个代表存放金额的非负数数组,计算不触发报警情况下能达到最高金额

  • 分析

    1. 子问题,原问题是盗窃到最后一家或倒数第二家时能够获得的最大财宝数量。子问题就是求每个房间的最优解
    2. 动态规划状态,i个房间的状态就是该房间能获得财宝的最大值
    3. 边界, 第一个房间,第二个房间能获得的最大财宝
    4. 转移方程,i个房间最大值有两种可能,一种是选择当前房间,另一种是不选择当前房间
      选择:不能盗窃相邻房间,所以值为i-2间房最大值+当前房间财宝值
      不选择:上一间的最大值
      再挑选选择和不选择中的最大值就是当前房间的最大值
      dp[i] = max(dp[i-2] + num[i], dp[i-1])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int rob(int[] nums) {
// 边界情况
if (nums.length == 0) {
return 0;
} else if (nums.length == 1) {
return nums[0];
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.length - 1];
}
}

四、零钱兑换

  • 题目
    给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
    coins[1, 2, 5], amount = 11

  • 分析

    1. 子问题,1...amount最少硬币组合数,例如amount = 11时最小组合就是amount = 10amount = 9amount = 6时组合数+1中最小的。子问题就是总金额1到amount的最优解
    2. 动态规划状态, amount = i时的最小组合数
    3. 边界,当硬币数额等于amountdp[amount] = 1,即一枚硬币就能组合成总金额
    4. 转移方程,min(dp[i] = dp[i-coin])coins数组中元素带入方程后的最小值
      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
      public static int coinChange(int[] coins, int amount) {
      if (amount == 0) {
      return 0;
      }
      int[] dp = new int[amount + 1];
      int coinsMin = coins[0];
      for (int i : coins) {
      if (i <= amount) {
      dp[i] = 1;
      coinsMin = Math.min(coinsMin, i);
      }
      }
      for (int i = coinsMin * 2; i <= amount; i++) {
      for (int j : coins) {
      if (i - j > 0 && i - j <= amount && dp[i - j] > 0) {
      if (dp[i] == 0) {
      dp[i] = dp[i - j] + 1;
      } else {
      dp[i] = Math.min(dp[i], dp[i - j] + 1);
      }
      }
      }
      }
      return dp[amount] == 0 ? -1 : dp[amount];
      }

五、地下城游戏

  • 题目
    一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
    骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
    有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
    为了尽快到达公主,骑士决定每次只向右或向下移动一步。

  • 分析
    看到题目首先想到的是从 dungeon[0][0]开始,计算向右或向下过程中扣除或增加的总血量,但是制定转移方程的时候却有个问题,判断向右或是向下的时候需要考虑两个变量,一个是总血量,另一个是最低血量。需要在追求总血量最小的时候保证路径中任意一格时血量都在0之上。
    看了官方解答,正确的解法是从dungeon[m - 1][n - 1]也就是公主所在处规划。计算每个格子达到终点需要多少初始值,则有转移方程dp[i][j] = min(1 - dp[i - 1][j], dp[i][j - 1])。意为从下一步需要的初始值推算当前需要的初始值。又因为初始值必须大于1,所以调整一下转移方程dp[i][j] = max(1, min(1 - dp[i - 1][j], dp[i][j - 1])),使其小于1则初始值为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int calculateMinimumHP(int[][] dungeon) {
int n = dungeon.length;
int m = dungeon[0].length;
int[][] dp = new int[n][m];
dp[n - 1][m - 1] = Math.max((1 - dungeon[n - 1][m - 1]), 1);
for (int i = m - 2; i >= 0; i--) {
dp[n - 1][i] = Math.max(dp[n - 1][i + 1] - dungeon[n - 1][i], 1);
}
// 只有一行的情况下,直接返回
if (dungeon.length == 1) {
return dp[0][0];
}
for (int i = n - 2; i >= 0; i--) {
dp[i][m - 1] = Math.max(dp[i + 1][m - 1] - dungeon[i][m - 1], 1);
}
for (int i = n - 2; i >= 0; i--) {
for (int j = m - 2; j >= 0; j--) {
dp[i][j] = Math.max(1, Math.min(dp[i + 1][j] - dungeon[i][j], dp[i][j + 1] - dungeon[i][j]));
}
}
return dp[0][0];
}
}

文章字数:120,阅读全文大约需要1分钟

  • SocketChannelProcessorThread.class
  • 使用Select监听socketChannel的通用类
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
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Deque;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

/**
* @author colin.cheng
* @date 2021-10-30
* @since 1.0.0
*/
public class SocketChannelProcessorThread extends Thread {

private int bufferSize;
private Selector selector;
private ExecutorService workerPool;
private Function<String, String> dealPackage;
private Charset defaultCharset = StandardCharsets.UTF_8;
private volatile boolean flag = true;

public SocketChannelProcessorThread(Function<String, String> dealPackage, int bufferSize, int workerSize, Selector selector) {
super("TcpServer-MessageProcessor-Thread");
this.selector = selector;
this.dealPackage = dealPackage;
this.bufferSize = bufferSize;
this.workerPool = Executors.newFixedThreadPool(workerSize, new ThreadFactory() {
final AtomicInteger index = new AtomicInteger();
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "TcpServer-MessageProcessor-Worker" + index.getAndIncrement());
}
});
}

@Override
public void run() {
while (flag) {
try {
int readyChannels = this.selector.select(100);
if(readyChannels == 0) {
continue;
}
Set<SelectionKey> selectedKeys = this.selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
final SelectionKey selectionKey = keyIterator.next();
try {
dealSelectedKey(selectionKey);
} catch (IOException e) {
e.printStackTrace();
}
keyIterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

public void close() throws IOException {
this.flag = false;
this.selector.close();
this.workerPool.shutdown();
}

private void dealSelectedKey(SelectionKey key) throws IOException {
if(key.isConnectable()) {

} else if (key.isReadable()) {
final byte[] bytes = new byte[bufferSize];
bytes[0] = 1;
bytes[1] = 1;
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
SocketChannel channel = (SocketChannel)key.channel();
int len = channel.read(buffer);
if(len > 0) {
final String str = new String(buffer.array(), defaultCharset);
workerPool.execute(()->{
final String res = dealPackage.apply(str);
if(res != null) {
((Deque<String>)key.attachment()).addLast(res);
}
});
}
} else if(key.isWritable()) {
SocketChannel channel = (SocketChannel)key.channel();
Deque<String> messList = (Deque<String>)key.attachment();
final Iterator<String> iterator = messList.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println("send >> " + str);
channel.write(ByteBuffer.wrap(str.getBytes(defaultCharset)));
iterator.remove();
}
}
}
}
  • TcpClient.class
  • 客户端
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
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Function;

/**
* @author colin.cheng
* @date 2021-10-30
* @since 1.0.0
*/
public class TcpClient {

public SocketChannel socketChannel;
private Selector selector;
private SelectionKey selectionKey;
private SocketChannelProcessorThread socketChannelProcessorThread;

public TcpClient(String ip, int port, Function<String, String> dealPackage, int bufferSize, int workerSize) throws IOException {
this.socketChannel = SocketChannel.open();
this.socketChannel.connect(new InetSocketAddress(ip, port));
this.socketChannel.configureBlocking(false);
this.selector = Selector.open();
this.selectionKey = this.socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
this.selectionKey.attach(new ConcurrentLinkedDeque<>());
this.socketChannelProcessorThread = new SocketChannelProcessorThread(dealPackage, bufferSize, workerSize, selector);
this.socketChannelProcessorThread.start();
}

public void write(String mess) throws IOException {
Deque<String> list = (Deque<String>)this.selectionKey.attachment();
list.add(mess);
}

public void close() throws IOException {
this.socketChannelProcessorThread.close();
this.socketChannel.close();
this.selector.close();
}
}
  • TcpServer.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
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Function;

/**
* @author colin.cheng
* @date 2021-10-30
* @since 1.0.0
*/
@Slf4j
public class TcpServer {

private Selector selector;
private BindPortThread bindThread;
private SocketChannelProcessorThread messageProcessorThread;
private ServerSocketChannel serverSocketChannel;

public TcpServer(int port, Function<String, String> dealPackage, int bufferSize, int workerSize) throws IOException {
this.selector = Selector.open();
this.serverSocketChannel = ServerSocketChannel.open();
this.serverSocketChannel.socket().bind(new InetSocketAddress(port));
this.serverSocketChannel.configureBlocking(false);
this.messageProcessorThread = new SocketChannelProcessorThread(dealPackage, bufferSize, workerSize, selector);
this.messageProcessorThread.start();
this.bindThread = new BindPortThread(selector, serverSocketChannel);
this.bindThread.start();
}

public void close() throws IOException {
this.bindThread.close();
this.messageProcessorThread.close();
this.serverSocketChannel.close();
this.selector.close();
}

private class BindPortThread extends Thread {

private Selector selector;
private ServerSocketChannel serverSocketChannel;
private volatile boolean flag = true;

public BindPortThread(Selector selector, ServerSocketChannel serverSocketChannel) {
super("TcpServer-BindPort-Thread");
this.selector = selector;
this.serverSocketChannel = serverSocketChannel;
}

@Override
public void run() {
while (flag) {
try {
if(serverSocketChannel.isOpen()) {
final SocketChannel accept = serverSocketChannel.accept();
if(accept != null) {
accept.configureBlocking(false);
accept.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE).attach(new ConcurrentLinkedDeque<>());
}
}
} catch (ClosedChannelException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

public void close() throws IOException {
this.serverSocketChannel.close();
this.flag = false;
}
}
}
  • 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
try {
TcpServer server = new TcpServer(8111, mess->{
System.out.println("server收到 [" + mess + "]");
return "来自server的回复=》" + mess;
}, 1024, 2);

TcpClient client = new TcpClient("127.0.0.1", 8111, mess->{
System.out.println("client收到 [" + mess + "]");
return null;
}, 1024, 2);

client.write("test1");
client.write("test2");
} catch (Exception e) {
e.printStackTrace();
}
}

文章字数:1392,阅读全文大约需要5分钟

根据复利计算总数

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>复利增长模型</title>
<style>
html, body {
height: 100%;
margin: 0px;
padding: 0px;
background-color: #f5faff;
}
input{
width: 190px;
height: 27px;
background:none;
outline:none;
border:none;
font-size: 14px;
font-weight: 700;
font-family: "Microsoft soft";
}

.inputContent {
overflow: hidden;
background-color: white;
border-radius: 3px;
outline-style: none ;
border: 1px solid #ccc;
margin: 5px auto;
width: 300px;
height: 30px;
}

.inputLabel {
padding: 0 5px;
font-size: 14px;
font-weight: 700;
color: grey;
}

.inputContentClick{
border-color: #66afe9 !important;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)!important;
}

.flexBottom{
position: fixed !important;
bottom: 0px;
}
.fullScreen {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #f5faff;
position: fixed !important;
bottom: 0px;
z-index: 100;
}
.closeFullScreen {
position: fixed;
bottom: 0.5rem;
right: 0.5rem
}
.openFullScreen {
font-size: 1.5rem;
user-select:none;
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 99;
}
.fullScreenShowDiv {
height: calc(100% - 3rem);
width: calc(100% - 2rem);
margin: 1rem;
}

.button{
width: 140px;
line-height: 38px;
text-align: center;
font-weight: bold;
color: #fff;
text-shadow:1px 1px 1px #333;
border-radius: 5px;
margin:0 20px 20px 0;
position: relative;
overflow: hidden;
}
.button:nth-child(6n){
margin-right: 0;
}
.button.gray{
color: #8c96a0;
text-shadow:1px 1px 1px #fff;
border:1px solid #dce1e6;
box-shadow: 0 1px 2px #fff inset,0 -1px 0 #a8abae inset;
background: -webkit-linear-gradient(top,#f2f3f7,#e4e8ec);
background: -moz-linear-gradient(top,#f2f3f7,#e4e8ec);
background: linear-gradient(top,#f2f3f7,#e4e8ec);
}
.round,.side,.tags{
padding-right: 30px;
}
.tags:after{
font-weight: normal;
position: absolute;
display: inline-block;
content: "ADD TO";
top:-3px;
right: -33px;
color: #fff;
text-shadow:none;
width: 85px;
height:25px;
line-height: 28px;
-webkit-transform:rotate(45deg) scale(.7,.7);
-moz-transform:rotate(45deg) scale(.7,.7);
transform:rotate(45deg) scale(.7,.7);
}
.gray.tags:after{
background: #8c96a0;
border:2px solid #fff;
}
</style>
</head>
<body>
<div class="fullScreen">
<div class="fullScreenShowDiv">
<div id="fullScreenMain" style="transform:rotate(90deg);transform-origin:0 100%;position: relative;"></div>
</div>
<span class="closeFullScreen" onclick="document.getElementsByClassName('fullScreen')[0].style.display='none'">
<svg class="icon" style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="569"><path d="M354.133333 682.666667H256v-42.666667h170.666667v170.666667H384v-98.133334L243.2 853.333333l-29.866667-29.866666L354.133333 682.666667z m358.4 0l140.8 140.8-29.866666 29.866666-140.8-140.8V810.666667h-42.666667v-170.666667h170.666667v42.666667h-98.133334zM354.133333 384L213.333333 243.2l29.866667-29.866667L384 354.133333V256h42.666667v170.666667H256V384h98.133333z m358.4 0H810.666667v42.666667h-170.666667V256h42.666667v98.133333L823.466667 213.333333l29.866666 29.866667L712.533333 384z" fill="#444444" p-id="570"></path></svg>
</span>
</div>
<span class="openFullScreen" onclick="document.getElementsByClassName('fullScreen')[0].style.display=''">
<svg class="icon" style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="533"><path d="M285.866667 810.666667H384v42.666666H213.333333v-170.666666h42.666667v98.133333l128-128 29.866667 29.866667-128 128z m494.933333 0l-128-128 29.866667-29.866667 128 128V682.666667h42.666666v170.666666h-170.666666v-42.666666h98.133333zM285.866667 256l128 128-29.866667 29.866667-128-128V384H213.333333V213.333333h170.666667v42.666667H285.866667z m494.933333 0H682.666667V213.333333h170.666666v170.666667h-42.666666V285.866667l-128 128-29.866667-29.866667 128-128z" fill="#444444" p-id="534"></path></svg>
</span>
<div id="main" style="width: 100%; height:calc(100% - 300px);clear: both"></div>
<div style = "width: 100%; height: 256px" class="flexBottom">
<div style = "width: 305px; margin: auto">
<div class="inputContent">
<label for="name" class="inputLabel">组合名</label>
<input id="name" onfocus="inputfocus(this)" onblur="inputBlur(this)"/>
</div>
<div class="inputContent">
<label for="principal" class="inputLabel">初始值</label>
<input id="principal" onfocus="inputfocus(this)" onblur="inputBlur(this)"/>
</div>
<div class="inputContent">
<label for="interestRate" class="inputLabel">利率/年</label>
<input id="interestRate" onfocus="inputfocus(this)" onblur="inputBlur(this)"/>
</div>
<div class="inputContent">
<label for="time" class="inputLabel">周期/月</label>
<input id="time" onfocus="inputfocus(this)" onblur="inputBlur(this)"/>
</div>
<div class="inputContent">
<label for="append" class="inputLabel">周期追加</label>
<input id="append" onfocus="inputfocus(this)" onblur="inputBlur(this)"/>
</div>
<div style="width: 325px">
<div style="margin: auto">
<button type="button" class="button gray" onclick="defaultShow()">重新展示</button>
<button type="button" class="button gray tags" onclick="defaultAppend()">追加展示</button>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js"></script>
<script>
var fullScreenH = document.getElementsByClassName('fullScreenShowDiv')[0].offsetHeight;
var fullScreenW = document.getElementsByClassName('fullScreenShowDiv')[0].offsetWidth;
document.getElementById("fullScreenMain").style.height = fullScreenW + 'px';
document.getElementById("fullScreenMain").style.bottom = fullScreenW + 'px';
document.getElementById("fullScreenMain").style.width = fullScreenH + 'px';

document.getElementsByClassName('fullScreen')[0].style.display='none';

var nameIndex = 1;
var myChart = echarts.init(document.getElementById('main'));
var fullScreenChart = echarts.init(document.getElementById('fullScreenMain'));
window.currentData = [];

/**
* 计算周期值
*/
function getCompoundInterest(principal, interestRate, time, append) {
interestRate = formatRate(interestRate);
//interestRate = interestRate + 1;
interestRate = Math.pow(interestRate + 1, 1/12);
append = isNumber(append) ? append : 0;
var principals = [principal];
var title = [0];
var calculationProcess = [principal];
for (let i = 1; i <= time; i++) {
calculationProcess[i] = principal + "*" + interestRate + "+" + append + "=" + principal;
principal = principal * interestRate + append;
principals[i] = Math.floor(principal * 100) / 100;
title[i] = i;
}
var data = {};
data.legend = ["周期"];
data.xAxis = title;
data.value = principals;
return [principals, calculationProcess];
}

/**
* 显示值到视图,覆盖之前的
*/
function showCompoundInterest(principal, interestRate, time, append, name){
nameIndex = 1;
var res = getCompoundInterest(principal, interestRate, time, append);
window.currentData = null;
window.currentData = [resToData(res[0], name, principal, interestRate, time, append)];
myChart.setOption(getOptionByData(window.currentData), true);
fullScreenChart.setOption(getOptionByData(window.currentData), true);
}

/**
* 追加值到视图
*/
function appendShowCompoundInterest(principal, interestRate, time, append, name){
var res = getCompoundInterest(principal, interestRate, time, append);
var appendData = resToData(res[0], name, principal, interestRate, time, append);
var flag = true;
for (var i = 0; i < window.currentData.length; i++) {
if(window.currentData[i].name == appendData.name) {
window.currentData[i] = appendData;
flag = false;
}
}
if(flag) {
window.currentData.push(appendData);
}
myChart.setOption(getOptionByData(window.currentData));
fullScreenChart.setOption(getOptionByData(window.currentData));
}

/**
* 构造值
*/
function resToData(res, name, principal, interestRate, time, append){
if(name==null){
name = "default"
}
return {
name: name,
data: res,
principal: principal,
interestRate: interestRate,
time: time,
append: append
};
}

/**
* 检测是否是数值型
*/
function isNumber(value) {
return typeof value === "number";
}

/**
* 格式化利率
*/
function formatRate(rate) {
return rate == null? 1 :rate;
}

/**
* 构造Echart配置
*/
function getOptionByData(data) {
var maxAxis = 0;
var legends = [];
var datas = [];
var dataXAxis = [];
for(let index=0; index<data.length; index++){
legends[index] = data[index].name;
let one = data[index];
one.type = 'line';
datas[index] = one;
maxAxis = one.data.length > maxAxis ? one.data.length : maxAxis;
}
for(let i=0;i<maxAxis;i++){
dataXAxis[i] = i;
}

option = {
title: {
text: '增长趋势'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: legends
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {show: false}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: dataXAxis
},
yAxis: {
type: 'value'
},
series: datas
};

console.log(option);
return option;
}

/**
* 获取对象的值,空则返回默认值
*/
function getInputValOrElse(id, defaultVal){
var ob = document.getElementById(id);
var value = ob.value;
ob.value = "";
if(value == null || value == ""){
value = defaultVal + "";
}
return execStringExpression(value)
}

/**
* 获取名称的值,空则给默认值
*/
function getNameOrDefault() {
var ob = document.getElementById("name");
var value = ob.value;
ob.value = "";
if(value == null || value == ""){
value = "未命名组合" + nameIndex++;
}
return value;
}

/**
* 将字符当做命令执行(输入框可以输入简单公式)
*/
function execStringExpression(expr, info) {
let exprF = expr.replace(/\$\{([^}]+)\}/g, '${this[\'$1\'] || \'\'}');
const fn = new Function(`return \`${exprF}\``).bind(info || {});
try {
return eval(fn())
} catch(e) { console.log(e); }
return ''
}

/**
* 获取输入值并执行回调函数
*/
function getValueOrDefault(doFun){
var name = getNameOrDefault();
var principal = getInputValOrElse("principal", 0);
var interestRate = getInputValOrElse("interestRate", 0);
var time = getInputValOrElse("time", 0);
var append = getInputValOrElse("append", 0);
doFun(parseInt(principal), parseFloat(interestRate), parseInt(time), parseFloat(append), name);
}

/**
* 展示方法入口
*/
function defaultShow(){
getValueOrDefault(showCompoundInterest);
}

/**
* 追加方法入口
*/
function defaultAppend(){
getValueOrDefault(appendShowCompoundInterest);
}

/**
* 点击趋势数据的时候回填输入信息
*/
myChart.on('click', function (param) {
var name = param.seriesName;
for (var i = 0; i < window.currentData.length; i++) {
if(window.currentData[i].name == name) {
var crt = window.currentData[i];
document.getElementById("name").value = crt.name;
document.getElementById("principal").value = crt.principal;
document.getElementById("interestRate").value = crt.interestRate;
document.getElementById("time").value = crt.time;
document.getElementById("append").value = crt.append;
break;
}
}
});

// 输入框样式
function inputfocus(ob) {
ob.parentNode.className = "inputContent inputContentClick";
}

function inputBlur(ob) {
ob.parentNode.className = "inputContent";
}

// 初始化显示
showCompoundInterest(100, 0.8, 10, 0, "演示组合");
</script>
</html>

文章字数:510,阅读全文大约需要2分钟

一、分类

  • 对称加密:DES3DESAES等(安全等级中,速度很快,每秒数M比特)
  • 非对称加密:RSADSA等(安全等级高,速度比较慢,适合小数据量或数据签名)
  • 散列算法:SHA-1MD5

二、散列

2.1 MD5

  • 使用哈希函数,对信息进行摘要
  • 无论多长,都会输出一个长度为128bits的串(通常用 16 进制 表示为 32 个字符)
1
2
3
4
5
6
7
8
public static final byte[] computeMD5(byte[] content) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
return md5.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

2.2 SHA1算法

  • 相对于MD5更安全
  • 对于长度小于2^64位的消息,会产生一个160位的信息摘要
1
2
3
4
5
6
7
8
public static byte[] computeSHA1(byte[] content) {
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
return sha1.digest(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

2.3 HMAC算法

  • 相当于对称加密+散列
  • 发送发通过密钥和原文计算出加密的散列,接收方也需要通过原文密钥解密得到正确的散列
  • 多个线程同时使用一个实例会导致线程不安全的问题,需要加锁或者使用ThreadLocal
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
@NotThreadSafe
public class HMacHelper {
private static final Logger logger = LoggerFactory.getLogger(HMacHelper.class);
private Mac mac;

/**
* MAC算法可选以下多种算法
* HmacMD5/HmacSHA1/HmacSHA256/HmacSHA384/HmacSHA512
*/
private static final String KEY_MAC = "HmacMD5";
public HMacHelper(String key) {
try {
SecretKey secretKey = new SecretKeySpec(key.getBytes(ConstField.UTF8), KEY_MAC);
mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
} catch (Exception e) {
logger.error("create hmac helper failed.", e);
}
}
public byte[] sign(byte[] content) {
return mac.doFinal(content);
}

public boolean verify(byte[] signature, byte[] content) {
try {
byte[] result = mac.doFinal(content);
return Arrays.equals(signature, result);
} catch (Exception e) {
logger.error("verify sig failed.", e);
}
return false;
}
}

三、对称加密

3.1 AES

  • 高级别的加密标准,区块加密标准
  • 对称分组密码机制
  • 分组密码,128位是一个分组。
  • 秘钥长度最少支持为128位、192位、256
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
@NotThreadSafe
public class AesHelper {
private SecretKeySpec keySpec;
private IvParameterSpec iv;

public AesHelper(byte[] aesKey, byte[] iv) {
if (aesKey == null || aesKey.length < 16 || (iv != null && iv.length < 16)) {
throw new RuntimeException("错误的初始密钥");
}
if (iv == null) {
iv = Md5Util.compute(aesKey);
}
keySpec = new SecretKeySpec(aesKey, "AES");
this.iv = new IvParameterSpec(iv);
}

public AesHelper(byte[] aesKey) {
if (aesKey == null || aesKey.length < 16) {
throw new RuntimeException("错误的初始密钥");
}
keySpec = new SecretKeySpec(aesKey, "AES");
this.iv = new IvParameterSpec(Md5Util.compute(aesKey));
}

public byte[] encrypt(byte[] data) {
byte[] result = null;
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
result = cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}

public byte[] decrypt(byte[] secret) {
byte[] result = null;
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
result = cipher.doFinal(secret);
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}

public static byte[] randomKey(int size) {
byte[] result = null;
try {
KeyGenerator gen = KeyGenerator.getInstance("AES");
gen.init(size, new SecureRandom());
result = gen.generateKey().getEncoded();
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
}

四、非对称加密

4.1 RSA

  • 目前最有影响力的公钥加密算法,可同时用于加密和数字签名
  • 能抵抗已知所有的密码攻击
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
@NotThreadSafe
public class RsaHelper {
private static final Logger logger = LoggerFactory.getLogger(RsaHelper.class);
private RSAPublicKey publicKey;
private RSAPrivateCrtKey privateKey;

static {
Security.addProvider(new BouncyCastleProvider()); //使用bouncycastle作为加密算法实现
}

public RsaHelper(String publicKey, String privateKey) {
this(Base64Util.decode(publicKey), Base64Util.decode(privateKey));
}

public RsaHelper(byte[] publicKey, byte[] privateKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
if (publicKey != null && publicKey.length > 0) {
this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
}
if (privateKey != null && privateKey.length > 0) {
this.privateKey = (RSAPrivateCrtKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public RsaHelper(String publicKey) {
this(Base64Util.decode(publicKey));
}

public RsaHelper(byte[] publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
if (publicKey != null && publicKey.length > 0) {
this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public byte[] encrypt(byte[] content) {
if (publicKey == null) {
throw new RuntimeException("public key is null.");
}

if (content == null) {
return null;
}

try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int size = publicKey.getModulus().bitLength() / 8 - 11;
ByteArrayOutputStream baos = new ByteArrayOutputStream((content.length + size - 1) / size * (size + 11));
int left = 0;
for (int i = 0; i < content.length; ) {
left = content.length - i;
if (left > size) {
cipher.update(content, i, size);
i += size;
} else {
cipher.update(content, i, left);
i += left;
}
baos.write(cipher.doFinal());
}
return baos.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public byte[] decrypt(byte[] secret) {
if (privateKey == null) {
throw new RuntimeException("private key is null.");
}

if (secret == null) {
return null;
}

try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int size = privateKey.getModulus().bitLength() / 8;
ByteArrayOutputStream baos = new ByteArrayOutputStream((secret.length + size - 12) / (size - 11) * size);
int left = 0;
for (int i = 0; i < secret.length; ) {
left = secret.length - i;
if (left > size) {
cipher.update(secret, i, size);
i += size;
} else {
cipher.update(secret, i, left);
i += left;
}
baos.write(cipher.doFinal());
}
return baos.toByteArray();
} catch (Exception e) {
logger.error("rsa decrypt failed.", e);
}
return null;
}

public byte[] sign(byte[] content) {
if (privateKey == null) {
throw new RuntimeException("private key is null.");
}
if (content == null) {
return null;
}
try {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(privateKey);
signature.update(content);
return signature.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public boolean verify(byte[] sign, byte[] content) {
if (publicKey == null) {
throw new RuntimeException("public key is null.");
}
if (sign == null || content == null) {
return false;
}
try {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initVerify(publicKey);
signature.update(content);
return signature.verify(sign);
} catch (Exception e) {
logger.error("rsa verify failed.", e);
}
return false;
}
}

文章字数:232,阅读全文大约需要1分钟

后端使用java截取视频关键帧,并组装成带进度的图片。前端用js视频内容展示

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
package com.colin.tool.img;

import java.awt.*;
import java.util.List;
import java.awt.image.BufferedImage;

/**
* @author colin.cheng
* @date 2022-01-17
* @since 1.0.0
*/
public class ImageUtil {

public static ImageBuilder getComicStrip(int outWidth, int outHeight, List<BufferedImage> imageList) {
int showW = outWidth, showH = outHeight;
double outScale = outHeight / outWidth;
final ImageBuilder imageBuilder = new ImageBuilder(showW * imageList.size(), showH, Color.BLACK);
for (int i = 0; i < imageList.size(); i++) {
BufferedImage image = imageList.get(i);
final double height = image.getHeight();
final double width = image.getWidth();
double scale = height / width;
if(scale == outScale) {
imageBuilder.drawImg(image, i * showW, 0, showW, showH);
} else {
if(scale > outHeight) {
double n = height / showH;
double w = width / n;
imageBuilder.drawImg(image, (int)(showW * i + (showW - w) / 2), 0, (int)w, showH);
} else {
double n = width / showW;
double h = height / n;
imageBuilder.drawImg(image, showW * i, (int)((showH - h) / 2), showW, (int)h);
}
}
}
return imageBuilder;
}
}
  • 使用ffmpeg截取关键帧并调用上面的方法
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
package com.colin.ffm;

import com.colin.tool.img.ImageBuilder;
import com.colin.tool.img.ImageUtil;
import org.opencv.core.Mat;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.stream.Collectors;

/**
* @author colin.cheng
* @date 2021-11-17
* @since 1.0.0
*/
public class Test {

public static void main(String[] args) throws IOException {
String base = "F:\\test\\vid\\resource";
String name = "1.mp4";
String path = base + File.separator + name;
int frameIndexLength = 5;
final int count = VidUtil.countFrame(path);
final LinkedList<Mat> mats = new LinkedList<>();
VidUtil.getFrameMatByVideo(path, count / 5, mat -> mats.add(VidUtil.copy(mat)));
if(mats.size() > frameIndexLength) {
mats.removeLast();
}
ImageBuilder imageBuilder = ImageUtil.getComicStrip(200, 120,mats.stream().map(mat->VidUtil.mat2BufImg(mat, ".png")).collect(Collectors.toList()));
imageBuilder.writeToFile(new File(base + File.separator + "res.png"), "png");
}
}

前端

2.1 通用样式

  • 200 * 120
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
.vidContent {
display: flex;
justify-content: flex-start;
}
.vidItem {
width: 200px;
overflow-x: hidden;
margin: 5px;
cursor: pointer;
}
.vidComicStrip {
height: 120px
}
.vidComicStrip img {
height: 100%;
position: relative;
top: 0px;
}
.vidTitle {
text-align: center;
font-size: 12px;
display: inherit;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.progressBar {
width: 0;
height: 21px;
background-color: rgba(30,144,254,0.5);
position: relative;
top: -25px;
}

2.2 两种展示方式

  1. 通过鼠标移动改变进度
  • comicStrip.js
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
var frameLength = 4;
var itemWidth = 200;

var currIndex = 0;
var vidContentSelect = ".vidContent";

function appendVidComicStrip(id, img, title) {
var vidItem = document.createElement("div");
vidItem.setAttribute("class", "vidItem");
var vidComicStrip = document.createElement("div");
vidComicStrip.setAttribute("class", "vidComicStrip");
var vidTitle = document.createElement("div");
vidTitle.setAttribute("class", "vidTitle");
vidItem.appendChild(vidComicStrip);
vidItem.appendChild(vidTitle);
var vidImg = document.createElement("img");
vidImg.src = img;
vidComicStrip.appendChild(vidImg);
var progressBar = document.createElement("div");
progressBar.setAttribute("class", "progressBar");
vidComicStrip.appendChild(progressBar);
vidTitle.innerText = title;
vidItem.onclick = function() {
clickVidComicStrip(id);
}
document.querySelector(vidContentSelect).appendChild(vidItem);
// 事件
vidImg.onmousemove = function(event) {
showVidComicStrip(event, this);
event.stopPropagation();
}
vidImg.onmouseout = function() {
stopVidComicStrip(this);
}
}

function showVidComicStrip(event, ob) {
var offset = event.pageX - ob.parentNode.offsetLeft;
var index = (offset / itemWidth) * frameLength
showFrame(ob, parseInt(index));
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = offset + "px";
}

function showFrame(ob, i) {
if(i <= frameLength && currIndex != i) {
currIndex = i;
ob.style.left = (0 - i * itemWidth) + "px";
}
}

function stopVidComicStrip(ob) {
ob.style.left = 0;
currIndex = 0;
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = "0px";
}

function clickVidComicStrip(id) {
console.log(id);
}
  1. 鼠标移入后自动展示
  • comicStripAuto.js
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
var frameLength = 4;
var itemWidth = 200;
var frameInterval = 1000;
//var progressBarShow = true;

var vidContentSelect = ".vidContent";
var timeOut = null;
var currentShow = null;

function appendVidComicStrip(id, img, title) {
var vidItem = document.createElement("div");
vidItem.setAttribute("class", "vidItem");
var vidComicStrip = document.createElement("div");
vidComicStrip.setAttribute("class", "vidComicStrip");
var vidTitle = document.createElement("div");
vidTitle.setAttribute("class", "vidTitle");
vidItem.appendChild(vidComicStrip);
vidItem.appendChild(vidTitle);
var vidImg = document.createElement("img");
vidImg.src = img;
vidComicStrip.appendChild(vidImg);
var progressBar = document.createElement("div");
progressBar.setAttribute("class", "progressBar");
vidComicStrip.appendChild(progressBar);
vidTitle.innerText = title;
vidItem.onclick = function() {
clickVidComicStrip(id);
}
document.querySelector(vidContentSelect).appendChild(vidItem);
// 事件
vidImg.onmouseover = function() {
showVidComicStrip(this);
}
vidImg.onmouseout = function() {
stopVidComicStrip(this);
}
}

function showVidComicStrip(ob) {
currentShow = ob;
showFrame(ob, 1);
}

function showFrame(ob, i) {
if(currentShow == ob && i <= frameLength) {
ob.style.left = (0 - i * itemWidth) + "px";
console.log(ob.parentNode.getElementsByClassName("progressBar")[0])
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = i / frameLength * itemWidth + "px";
timeOut = setTimeout(function() {
showFrame(ob, i + 1);
}, frameInterval)
} else {
timeOut = null;
}
}

function stopVidComicStrip(ob) {
ob.style.left = 0;
if(timeOut != null) {
clearTimeout(timeOut);
}
currentShow = null;
ob.parentNode.getElementsByClassName("progressBar")[0].style.width = "0px";
}

function clickVidComicStrip(id) {
console.log(id);
}

2.3 使用

  1. 添加一个vidContent
  2. 调用appendVidComicStrip方法添加一个视频图标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="./comicStrip.css">
</head>
<body>
<div class="vidContent"></div>
<script type="text/javascript" src="./comicStrip.js"></script>
<script>
appendVidComicStrip("id", "imgUrl", "title");
</script>
</body>
</html>

文章字数:577,阅读全文大约需要2分钟

网上复制的 原文链接

经检验,此方法在不同的操作系统下不稳定。有些参数获取失败,目前使用的方式是OSHI开源工具获取信息

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
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.CompilationMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;

public class HeapMain {

private static NumberFormat fmtI = new DecimalFormat("###,###", new DecimalFormatSymbols(Locale.ENGLISH));
private static NumberFormat fmtD = new DecimalFormat("###,##0.000", new DecimalFormatSymbols(Locale.ENGLISH));

public static void main(String[] args) {
//运行时情况
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
//操作系统情况
OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
//线程使用情况
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
//堆内存使用情况
MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
//非堆内存使用情况
MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
//类加载情况
ClassLoadingMXBean cl = ManagementFactory.getClassLoadingMXBean();
//内存池对象
List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
//编译器和编译情况
CompilationMXBean cm = ManagementFactory.getCompilationMXBean();
//获取GC对象(不好用)
List<GarbageCollectorMXBean> gcmList = ManagementFactory.getGarbageCollectorMXBeans();



//运行时情况
System.out.printf("jvm.name (JVM名称-版本号-供应商):%s | version: %s | vendor: %s %n", runtime.getVmName(), runtime.getVmVersion(), runtime.getVmVendor());
System.out.printf("jvm.spec.name (JVM规范名称-版本号-供应商):%s | version: %s | vendor: %s %n", runtime.getSpecName(), runtime.getSpecVersion(), runtime.getSpecVendor());
System.out.printf("jvm.java.version (JVM JAVA版本):%s%n", System.getProperty("java.version"));
System.out.printf("jvm.start.time (Java虚拟机的启动时间):%s%n", toDuration(runtime.getStartTime()));
System.out.printf("jvm.uptime (Java虚拟机的正常运行时间):%s%n", toDuration(runtime.getUptime()));

System.out.println("------------------------------------------------------------------------------------------------------");

//编译情况
System.out.printf("compilation.name(编译器名称):%s%n",cm.getName());
System.out.printf("compilation.total.time(编译器耗时):%d毫秒%n",cm.getTotalCompilationTime());
boolean isSupport=cm.isCompilationTimeMonitoringSupported();
if(isSupport){
System.out.println("支持即时编译器编译监控");
}else{
System.out.println("不支持即时编译器编译监控");
}
System.out.printf("------------------------------------------------------------------------------------------------------");
//JVM 线程情况
System.out.printf("jvm.threads.total.count (总线程数(守护+非守护)):%d%n", threads.getThreadCount());
System.out.printf("jvm.threads.daemon.count (守护进程线程数):%d%n", threads.getDaemonThreadCount());
System.out.printf("jvm.threads.peak.count (峰值线程数):%d%n", threads.getPeakThreadCount());
System.out.printf("jvm.threads.total.start.count(Java虚拟机启动后创建并启动的线程总数):%d%n", threads.getTotalStartedThreadCount());
for(Long threadId : threads.getAllThreadIds()) {
System.out.printf("threadId: %d | threadName: %s%n", threadId, threads.getThreadInfo(threadId).getThreadName());
}
System.out.println("------------------------------------------------------------------------------------------------------");
//获取GC信息

System.out.println("------------------------------------------------------------------------------------------------------");
//堆内存情况
System.out.printf("jvm.heap.init (初始化堆内存):%s %n", bytesToMB(heapMemoryUsage.getInit()));
System.out.printf("jvm.heap.used (已使用堆内存):%s %n", bytesToMB(heapMemoryUsage.getUsed()));
System.out.printf("jvm.heap.committed (可使用堆内存):%s %n", bytesToMB(heapMemoryUsage.getCommitted()));
System.out.printf("jvm.heap.max (最大堆内存):%s %n", bytesToMB(heapMemoryUsage.getMax()));

System.out.println("------------------------------------------------------------------------------------------------------");

//非堆内存使用情况
System.out.printf("jvm.noheap.init (初始化非堆内存):%s %n", bytesToMB(nonHeapMemoryUsage.getInit()));
System.out.printf("jvm.noheap.used (已使用非堆内存):%s %n", bytesToMB(nonHeapMemoryUsage.getUsed()));
System.out.printf("jvm.noheap.committed (可使用非堆内存):%s %n", bytesToMB(nonHeapMemoryUsage.getCommitted()));
System.out.printf("jvm.noheap.max (最大非堆内存):%s %n", bytesToMB(nonHeapMemoryUsage.getMax()));

System.out.println("------------------------------------------------------------------------------------------------------");

//系统概况
System.out.printf("os.name(操作系统名称-版本号):%s %s %s %n", os.getName(), "version", os.getVersion());
System.out.printf("os.arch(操作系统内核):%s%n", os.getArch());
System.out.printf("os.cores(可用的处理器数量):%s %n", os.getAvailableProcessors());
System.out.printf("os.loadAverage(系统负载平均值):%s %n", os.getSystemLoadAverage());

System.out.println("------------------------------------------------------------------------------------------------------");

//类加载情况
System.out.printf("class.current.load.count(当前加载类数量):%s %n", cl.getLoadedClassCount());
System.out.printf("class.unload.count(未加载类数量):%s %n", cl.getUnloadedClassCount());
System.out.printf("class.total.load.count(总加载类数量):%s %n", cl.getTotalLoadedClassCount());

System.out.println("------------------------------------------------------------------------------------------------------");

for(MemoryPoolMXBean pool : pools) {
final String kind = pool.getType().name();
final MemoryUsage usage = pool.getUsage();
System.out.println("内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(pool.getName()) + ", jvm." + pool.getName() + ".init(初始化):" + bytesToMB(usage.getInit()));
System.out.println("内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(pool.getName()) + ", jvm." + pool.getName() + ".used(已使用): " + bytesToMB(usage.getUsed()));
System.out.println("内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(pool.getName()) + ", jvm." + pool.getName()+ ".committed(可使用):" + bytesToMB(usage.getCommitted()));
System.out.println("内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(pool.getName()) + ", jvm." + pool.getName() + ".max(最大):" + bytesToMB(usage.getMax()));
System.out.println("------------------------------------------------------------------------------------------------------");
}

}

protected static String getKindName(String kind) {
if("NON_HEAP".equals(kind)) {
return "NON_HEAP(非堆内存)";
}else {
return "HEAP(堆内存)";
}
}

protected static String getPoolName(String poolName) {
switch (poolName) {
case "Code Cache":
return poolName +"(代码缓存区)";
case "Metaspace":
return poolName +"(元空间)";
case "Compressed Class Space":
return poolName +"(类指针压缩空间)";
case "PS Eden Space":
return poolName +"(伊甸园区)";
case "PS Survivor Space":
return poolName +"(幸存者区)";
case "PS Old Gen":
return poolName +"(老年代)";
default:
return poolName;
}
}


protected static String bytesToMB(long bytes) {
return fmtI.format((long)(bytes / 1024 / 1024)) + " MB";
}

protected static String printSizeInKb(double size) {
return fmtI.format((long) (size / 1024)) + " kbytes";
}

protected static String toDuration(double uptime) {
uptime /= 1000;
if (uptime < 60) {
return fmtD.format(uptime) + " seconds";
}
uptime /= 60;
if (uptime < 60) {
long minutes = (long) uptime;
String s = fmtI.format(minutes) + (minutes > 1 ? " minutes" : " minute");
return s;
}
uptime /= 60;
if (uptime < 24) {
long hours = (long) uptime;
long minutes = (long) ((uptime - hours) * 60);
String s = fmtI.format(hours) + (hours > 1 ? " hours" : " hour");
if (minutes != 0) {
s += " " + fmtI.format(minutes) + (minutes > 1 ? " minutes" : " minute");
}
return s;
}
uptime /= 24;
long days = (long) uptime;
long hours = (long) ((uptime - days) * 24);
String s = fmtI.format(days) + (days > 1 ? " days" : " day");
if (hours != 0) {
s += " " + fmtI.format(hours) + (hours > 1 ? " hours" : " hour");
}
return s;
}

}