Lc

lichong博客


  • 首页

  • 归档

《我的第一本算法书》读后感

发表于 2019-07-15

《我的第一本算法书(图灵图书) (Chinese Edition)》

  • 作者:石田保辉,宮崎修一

  • 序章 算法的基本知识

  • 108
    选 择 排 序

    第 1 章 数据结构

  • 210
    N o . 1 1   什 么 是 数 据 结 构 决 定 了 数 据 的 顺 序 和 位 置 关 系

  • 396
    栈 这 种 最 后 添 加 的 数 据 最 先 被 取 出 , 即 “ 后 进 先 出 ” 的 结 构 , 我 们 称 为 L a s t I n F i r s t O u t , 简 称 L I F O 。

  • 397
    与 链 表 和 数 组 一 样 , 栈 的 数 据 也 是 线 性 排 列

  • 407
    队 列 中 的 数 据 也 呈 线 性 排 列

  • 419
    队 列 这 种 最 先 进 去 的 数 据 最 先 被 取 来 , 即 “ 先 进 先 出 ” 的 结 构 , 我 们 称 为 F i r s t I n F i r s t O u t , 简 称 F I F O 。

  • 427
    哈 希 表

  • 430
    哈 希 表 存 储 的 是 由 键 ( k e y ) 和 值 ( v a l u e ) 组 成 的 数 据

  • 465
    存 储 位 置 重 复 了 的 情 况 便 叫 作 “ 冲 突 ”

  • 489
    在 哈 希 表 中 , 我 们 可 以 利 用 哈 希 函 数 快 速 访 问 到 数 组 中 的 目 标 数 据 。 如 果 发 生 哈 希 冲 突 , 就 使 用 链 表 进 行 存 储 。 这 样 一 来 , 不 管 数 据 量 为 多 少 , 我 们 都 能 够 灵 活 应 对

  • 494
    存 储 数 据 的 过 程 中 , 如 果 发 生 冲 突 , 可 以 利 用 链 表 在 已 有 数 据 的 后 面 插 入 新 数 据 来 解 决 冲 突 。 这 种 方 法 被 称 为 “ 链 地 址 法 ”

  • 501
    因 为 哈 希 表 在 数 据 存 储 上 的 灵 活 性 和 数 据 查 询 上 的 高 效 性 , 编 程 语 言 的 关 联 数 组 等 也 常 常 会 使 用 它

  • 502
    堆 是 一 种 图 的 树 形 结 构 , 被 用 于 实 现 “ 优 先 队 列 ”

  • 503
    优 先 队 列 是 一 种 数 据 结 构 , 可 以 自 由 添 加 数 据 , 但 取 出 数 据 时 要 从 最 小 值 开 始 按 顺 序 取 出 。

  • 510
    堆 中 存 储 数 据 时 必 须 遵 守 这 样 一 条 规 则 : 子 结 点 必 定 大 于 父 结 点 。

  • 539
    堆 中 最 顶 端 的 数 据 始 终 最 小 , 所 以 无 论 数 据 量 有 多 少 , 取 出 最 小 值 的 时 间 复 杂 度 都 为 。

  • 541
    所 以 取 出 数 据 需 要 的 运 行 时 间 和 树 的 高 度 成 正 比 。 假 设 数 据 量 为 , 根 据 堆 的 形 状 特 点 可 知 树 的 高 度 为 , 那 么 重 构 树 的 时 间 复 杂 度 便 为 。

  • 548
    狄 克 斯 特 拉 算 法 , 每 一 步 都 需 要 从 候 补 顶 点 中 选 择 距 离 起 点 最 近 的 那 个 顶 点 。 此 时 , 在 顶 点 的 选 择 上 就 可 以 用 到 堆 。

  • 550
    二 叉 查 找 树 ( 又 叫 作 二 叉 搜 索 树 或 二 叉 排 序 树 ) 是 一 种 数 据 结 构 , 采 用 了 图 的 树 形 结 构 ( 关 于 树 形 结 构 的 详 细 说 明 请 参 考 4 2 节 ) 。 数 据 存 储 于 二 叉 查 找 树 的 各 个 结 点 中

  • 557
    二 叉 查 找 树 有 两 个 性 质 。 第 一 个 是 每 个 结 点 的 值 均 大 于 其 左 子 树 上 任 意 一 个 结 点 的 值

  • 561
    第 二 个 是 每 个 结 点 的 值 均 小 于 其 右 子 树 上 任 意 一 个 结 点 的 值
    第 2 章 排序

  • 692
    冒 泡 排 序

  • 722
    选 择 排 序

  • 744
    插 入 排 序 是 一 种 从 序 列 左 端 开 始 依 次 对 数 据 进 行 排 序 的 算

  • 777
    堆 排 序 的 特 点 是 利 用 了 数 据 结 构 中 的 堆

  • 809
    归 并 排 序 算 法 会 把 序 列 分 成 长 度 相 同 的 两 个 子 序 列 , 当 无 法 继 续 往 下 分 时 ( 也 就 是 每 个 子 序 列 中 只 有 一 个 数 据 时 ) , 就 对 子 序 列 进 行 归 并 。 归 并 指 的 是 把 两 个 排 好 序 的 子 序 列 合 并 成 一 个 有 序 序 列

  • 845
    快 速 排 序 算 法 首 先 会 在 序 列 中 随 机 选 择 一 个 基 准 值 ( p i v o t ) , 然 后 将 除 了 基 准 值 以 外 的 数 分 为 “ 比 基 准 值 小 的 数 ” 和 “ 比 基 准 值 大 的 数 ” 这 两 个 类 别 , 再 将 其 排 列 成 以 下 形 式

  • 885
    快 速 排 序 是 一 种 “ 分 治 法 ”
    第 3 章 数组的查找

  • 904
    线 性 查 找 是 一 种 在 数 组 中 查 找 数 据 的 算 法

  • 919
    二 分 查 找 也 是 一 种 在 数 组 中 查 找 数 据 的 算 法
    ###第 4 章 图的搜索

  • 1027
    深 度 优 先 搜 索

  • 1060
    贝 尔 曼 福 特 ( B e l l m a n F o r d ) 算 法 是 一 种 在 图 中 求 解 最 短 路 径 问 题 的 算 法

  • 1117
    , 狄 克 斯 特 拉 ( D i j k s t r a ) 算 法 也 是 求 解 最 短 路 径 问 题 的 算 法 , 使 用 它 可 以 求 得 从 起 点 到 终 点 的 路 径 中 权 重 总 和 最 小 的 那 条 路 径 路 径

  • 1182
    A * ( A S t a r ) 算 法 也 是 一 种 在 图 中 求 解 最 短 路 径 问 题 的 算 法 , 由 狄 克 斯 特 拉 算 法 发 展 而 来
    第 5 章 安全算法

  • 1288
    哈 希 函 数

    第 7 章 其他算法

  • 1775
    欧 几 里 得 算 法 ( 又 称 辗 转 相 除 法 ) 用 于 计 算 两 个 数 的 最 大 公 约 数 , 被 称 为 世 界 上 最 古 老 的 算 法

  • 1822
    费 马 测 试 被 称 为 概 率 性 素 性 测 试 , 它 判 断 的 是 “ 某 个 数 是 素 数 的 概 率 大 不 大 ” 。

  • 1833
    “ 费 马 小 定 理 ”

  • 1843
    的 “ 米 勒 拉 宾 ( M i l l e r R a b i n ) 素 性 测 试 ” 。 用 这 个 方 法 重 复 进 行 测 试 后 , 当 数 不 是 素 数 的 概 率 小 于 时 , 就 可 以 大 致 判 断 该 数 为 素 数 。

  • 1851
    “ 卡 迈 克 尔 数 ” ( C a r m i c h a e l n u m b e r s ) , 也 叫 “ 绝 对 伪 素 数 ”

  • 1856
    “ A K S 算 法

  • 1858
    网 页 排 名 ( P a g e R a n k , 也 叫 作 佩 奇 排 名 ) 是 一 种 在 搜 索 网 页 时 对 搜 索 结 果 进 行 排 序 的 算 法 。

  • 1880
    随 机 游 走 模 型 ” ( r a n d o m w a l k m o d e l )

  • 1927
    汉诺塔

总结

本书结合大量的图解详细的解析了各种算法的渐进演变。
适合人群:了解算法但不了解算法里常用的名词
复习指数:#

串口工具

发表于 2019-07-15

完整代码

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
import android.text.TextUtils;
import android.util.Log;

import com.deemons.serialportlib.SerialPort;
import com.glodon.tool.DataUtils;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* 串口帮助类
* <p>
* 使用简述
* <p>
* 添加库:implementation "com.deemons.serialport:serialport:1.2.0"
*
* @see
* <a href=“https://github.com/Deemonser/AndroidSerialPort”>https://github.com/Deemonser/AndroidSerialPort</a>
* <p>
* @see
* <a href=“http://pm.glodon.com/newjira/browse/GLW-1234”>通信协议:http://pm.glodon.com/newjira/browse/GLW-1234</a>
* <p>
* Created by lichongmac@163.com on 2019-07-12.
*/
public class SerialPortHelper {
private static final String TAG = SerialPortHelper.class.getSimpleName();

private SerialPort mSerialPort;

private String filePath = "/dev/ttyS2";
private int baudRate = 115200;

private static SerialPortHelper sSerialPortHelper;

private SerialPortHelper() {
openSerialPort();
}

public void openSerialPort() {
closeSerialPort();
try {
mSerialPort = new SerialPort(filePath, baudRate);
} catch (IOException e) {
e.printStackTrace();
}

new ReceiveThread().start();
}

public static SerialPortHelper getInstance() {
if (sSerialPortHelper == null) {
sSerialPortHelper = new SerialPortHelper();
}
return sSerialPortHelper;
}

public void closeSerialPort() {
if (mSerialPort != null) {
isStart = false;
mSerialPort.close();
}
}

private boolean isStart = false;

/**
* 接收串口数据的线程
*/
private class ReceiveThread extends Thread {

public ReceiveThread() {
isStart = true;
}

private String dataBuffer = "";
private String CMD_HEADER = "BB";

@Override
public void run() {
super.run();
//条件判断,只要条件为true,则一直执行这个线程
while (isStart) {
if (mSerialPort.getInputStream() == null) {
Log.e(TAG, "数据流出现未知异常");
return;
}

byte[] readData = new byte[1024];
try {
int size = mSerialPort.getInputStream().read(readData);
if (size > 0) {

String data = DataUtils.ByteArrToHex(readData, 0, size);
if (!TextUtils.isEmpty(data)) {
Log.i(TAG, data);
dataBuffer += data;
}
while (dataBuffer.contains(CMD_HEADER) && dataBuffer.length() > 10) {
// Log.i(TAG, "start dataBuffer = " + dataBuffer);
int start = dataBuffer.indexOf(CMD_HEADER) + CMD_HEADER.length();
String msgType = dataBuffer.substring(start, start + 2);
String msgComm = dataBuffer.substring(start + 2, start + 4);
String contentLength = dataBuffer.substring(start + 4, start + 8);

int length = DataUtils.HexToInt(contentLength);

if (start + 8 + length * 2 > dataBuffer.length())
break;

String content = dataBuffer.substring(start + 8,
start + 8 + length * 2);
Log.i(TAG, "消息类型:" + msgType
+ ",指令:" + msgComm
+ ",数据长度:" + length
+ ",content:" + content
);
//通知观察者或监听者消息内容
SerialPortHelper.getInstance().notify(msgType, msgComm, content);

dataBuffer = dataBuffer.substring(start + 8 + length * 2);
if (dataBuffer.contains(CMD_HEADER)) {//剔除尾部和校验位
dataBuffer = dataBuffer.substring(dataBuffer.indexOf(CMD_HEADER));
}
// Log.i(TAG, "end dataBuffer = " + dataBuffer);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

}
}

public interface SerialPortListener {
void onReceive(String msgType, String msgComm, String content);
}

List<SerialPortListener> mListeners = new CopyOnWriteArrayList();

public void addListener(SerialPortListener listener) {
if (!mListeners.contains(listener)) {
mListeners.add(listener);
}
}

public void removeListener(SerialPortListener listener) {
if (mListeners.contains(listener)) {
mListeners.remove(listener);
}
}

public void notify(String msgType, String msgComm, String content) {
for (SerialPortListener listener : mListeners)
listener.onReceive(msgType, msgComm, content);
}
}

辅助类

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
import android.text.TextUtils;

import java.util.ArrayList;
import java.util.List;

/**
* 串口数据转换工具类
* Created by Administrator on 2016/6/2.
*/
public class DataUtils {
//-------------------------------------------------------
// 判断奇数或偶数,位运算,最后一位是1则为奇数,为0是偶数
public static int isOdd(int num) {
return num & 1;
}

//-------------------------------------------------------
//Hex字符串转int
public static int HexToInt(String inHex) {
if(TextUtils.isEmpty(inHex)) {
return 0;
}
try {

return Integer.parseInt(inHex, 16);
}catch (NumberFormatException e){
e.printStackTrace();
}
return 0;
}

public static String IntToHex(int intHex){
return Integer.toHexString(intHex);
}

//-------------------------------------------------------
//Hex字符串转byte
public static byte HexToByte(String inHex) {
return (byte) Integer.parseInt(inHex, 16);
}

//-------------------------------------------------------
//1字节转2个Hex字符
public static String Byte2Hex(Byte inByte) {
return String.format("%02x", new Object[]{inByte}).toUpperCase();
}

//-------------------------------------------------------
//字节数组转转hex字符串
public static String ByteArrToHex(byte[] inBytArr) {
StringBuilder strBuilder = new StringBuilder();
for (byte valueOf : inBytArr) {
strBuilder.append(Byte2Hex(Byte.valueOf(valueOf)));
strBuilder.append(" ");
}
return strBuilder.toString();
}

//-------------------------------------------------------
//字节数组转转hex字符串,可选长度
public static String ByteArrToHex(byte[] inBytArr, int offset, int byteCount) {
StringBuilder strBuilder = new StringBuilder();
int j = byteCount;
for (int i = offset; i < j; i++) {
strBuilder.append(Byte2Hex(Byte.valueOf(inBytArr[i])));
}
return strBuilder.toString();
}

// ascii byte数 组转成String字符串,可选长度
public static String byteOfASCIIToString(byte[] inBytArr, int offset, int byteCount){
StringBuilder strBuilder = new StringBuilder();
int j = byteCount;
for (int i = offset; i < j; i++) {
char c=(char)inBytArr[i];
strBuilder.append(c);
}
return strBuilder.toString();
}


//-------------------------------------------------------
//转hex字符串转字节数组
public static byte[] HexToByteArr(String inHex) {
byte[] result;
int hexlen = inHex.length();
if (isOdd(hexlen) == 1) {
hexlen++;
result = new byte[(hexlen / 2)];
inHex = "0" + inHex;
} else {
result = new byte[(hexlen / 2)];
}
int j = 0;
for (int i = 0; i < hexlen; i += 2) {
result[j] = HexToByte(inHex.substring(i, i + 2));
j++;
}
return result;
}

/**
* 按照指定长度切割字符串
*
* @param inputString 需要切割的源字符串
* @param length 指定的长度
* @return
*/
public static List<String> getDivLines(String inputString, int length) {
List<String> divList = new ArrayList<>();
int remainder = (inputString.length()) % length;
// 一共要分割成几段
int number = (int) Math.floor((inputString.length()) / length);
for (int index = 0; index < number; index++) {
String childStr = inputString.substring(index * length, (index + 1) * length);
divList.add(childStr);
}
if (remainder > 0) {
String cStr = inputString.substring(number * length, inputString.length());
divList.add(cStr);
}
return divList;
}

/**
* 计算长度,两个字节长度
*
* @param val value
* @return 结果
*/
public static String twoByte(String val) {
if (val.length() > 4) {
val = val.substring(0, 4);
} else {
int l = 4 - val.length();
for (int i = 0; i < l; i++) {
val = "0" + val;
}
}
return val;
}

/**
* 校验和
*
* @param cmd 指令
* @return 结果
*/
public static String sum(String cmd) {
List<String> cmdList = DataUtils.getDivLines(cmd, 2);
int sumInt = 0;
for (String c : cmdList) {
sumInt += DataUtils.HexToInt(c);
}
String sum = DataUtils.IntToHex(sumInt);
sum = DataUtils.twoByte(sum);
cmd += sum;
return cmd.toUpperCase();
}

public static int str8ToInt(String[] str){
int sum=0;
for(int i=0;i<str.length;i++){

int value=computerBinary(str[i],str.length-1-i);

sum+=value;
}
return sum;
}
public static int str8ToInt(String str){
int sum=0;
for(int i=0;i<str.length();i++){
String strIndex=str.charAt(i)+"";
int value=computerBinary(strIndex,str.length()-1-i);

sum+=value;
}
return sum;
}
private static int computerBinary(String c, int index) {
int vaule=Integer.valueOf(c);
for(int i=0;i<index;i++){
vaule*=2;
}
return vaule;
}

}

参考

GitHub地址

【win】Flutter安装记录

发表于 2019-04-27

官网

官网

https://flutterchina.club/setup-windows/

SDK下载

GitHub下载

Flutter中文网下载

环境配置

  • 在c盘新建 C:\src\flutter
  • 在Flutter安装目录的flutter文件下找到flutter_console.bat,双击运行并启动flutter命令行,接下来,你就可以在Flutter命令行运行flutter命令了

环境变量

  • flutter doctor

开发IDE

  • Android Strudio
  • VS Code
  • IntelliJ IDEA

Jenkins搭建设计大纲

发表于 2019-04-27

Jenkins+AndroidSDK+Node.js+http-server+qr.jar+163邮箱服务大纲

目录

JDK
Android SDK
Git
Jenkins
Gradle
Node
配置Job
二维码生成
集成RSS
apk版本设置为名称

Apk版本设置为名称

参考
https://juejin.im/entry/5adaa9ecf265da0ba062b9a1

JDK

安装

下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html

根据电脑环境安装合适的版本

配置

在"用户变量"中设置3项属性,JAVA_HOME,PATH,CLASSPATH(不区分大小写),若已存在则点击"编辑",注意用分号与前面的隔开,不存在则点击"新建"。

变量设置参数如下:

变量名: JAVA_HOME
变量值: C:\Program Files\Java\jdk1.8.0_111
变量名: Path
变量值: %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
变量名: CLASSPATH
变量值: .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;       
注意:这前面有一个点‘.’

测试

command(window键)+R,然后输入cmd,确定后输入命令: java、javac 几个命令

Android SDK

sdk安装
下载地址:Android Studio

下载Android studio即可在安装流程里下载AndroidSDK
studio

环境配置(可选)

ANDROID_HOME=“安装目录”

测试
使用adb进行命令测试如下图

参考

Android Studio

Git

安装
安装教程
https://www.jianshu.com/p/414ccd423efc

测试
在终端输入 “git –version”如下图:

ssh
ssh-keygen -t rsa -C “youremail@example.com“

验证

例子:ssh -T git@code.aliyun.com
返回 Welcome to GIT, 用户名! 成功

参考地址
官网安装教程

Jenkins

Jenkins特性:

1、易于安装-本文提供四种配置方式。

2、易于配置-所有配置都是通过其提供的web界面实现。

3、集成RSS/E-mail通过RSS发布构建结果或当构建完成时通过e-mail通知。

4、生成JUnit/TestNG测试报告。

5、分布式构建支持Jenkins能够让多台计算机一起构建/测试。

6、文件识别:Jenkins能够跟踪哪次构建生成哪些jar,哪次构建使用哪个版本的jar等。

7、插件支持:支持扩展插件,你可以开发适合自己团队使用的工具。

8、Jenkins一切配置都可以在web界面上完成。有些配置如MAVEN_HOME和Email,只需要配置一次,所有的项目就都能用。当然也可以通过修改XML进行配置。

9、支持Maven的模块(Module),Jenkins对Maven做了优化,因此它能自动识别Module,每个Module可以配置成一个job。相当灵活。

10、测试报告聚合,所有模块的测试报告都被聚合在一起,结果一目了然,使用其他CI,这几乎是件不可能完成的任务。

11、构件指纹(artifact fingerprint),每次build的结果构件都被很好的自动管理,无需任何配置就可以方便的浏览下载。

平台安装

jenkins官网

  • mac

brew install jenkins

  • window

官网下载Jenkin.war包

安装方式参考

各种插件配置见Jenkins插件列表

测试运行

  • mac

    jenkins

  • window

    java -jar jenkins.war

Jenkins插件列表

  • Build Timeout
  • description setter plugin
  • Email Extension Plugin
  • GitHub Branch Source Plugin
  • Gradle Plugin
  • SSH Slaves plugin
  • Timestamper
  • Publish over ssh

参考地址
https://www.jianshu.com/p/400b4516b98e
https://blog.csdn.net/RonnyJiang/article/details/51208009
https://segmentfault.com/a/1190000015391418

Gradle(可选)

Gradle官网

各平台安装

  • mac

brew install gradle

  • liunx、unbuntu和centos

sdk install gradle 4.6

  • 环境变量配置

$ export PATH=$PATH:/opt/gradle/gradle-4.6/bin

测试

gradle -v

出现如下:

1
2
3
4
5
6
7
8
9
10
------------------------------------------------------------
Gradle 4.6
------------------------------------------------------------
Build time: 2018-02-28 13:36:36 UTC
Revision: 8fa6ce7945b640e6168488e4417f9bb96e4ab46c

Groovy: 2.4.12
Ant: Apache Ant(TM) version 1.9.9 compiled on February 2 2017
JVM: 1.8.0_161 (Oracle Corporation 25.161-b12)
OS: Mac OS X 10.13.3 x86_64
  • window

scoop install gradle

注意:

  • windows可以使用scoop安装
  • windows还可以使用Chocolatey包管理器安装

    另:手动安装就不介绍了

二维码生成

安装

配置
测试

注:忘记在哪下载的qr.jar包。如有需要发邮件联系我

Node

安装
配置
测试
npm install http-server -g

配置Job

构建触发器

时间设置说明

* * * * *
(五颗星,中间用空格隔开)
第一颗*表示分钟,取值0~59
第二颗*表示小时,取值0~23
第三颗*表示一个月的第几天,取值1~31
第四颗*表示第几月,取值1~12
第五颗*表示一周中的第几天,取值0~7,其中0和7代表的都是周日

例子如下:

每15分钟构建一次:H/15 * * * *   或*/5 * * * *
每天8点构建一次:0 8 * * *
每天8点~17点,两小时构建一次:0 8-17/2 * * *
周一到周五,8点~17点,两小时构建一次:0 8-17/2 * * 1-5
每月1号、15号各构建一次,除12月:H H 1,15 1-11 *
*/5 * * * * (每5分钟检查一次源码变化)
0 2 * * * (每天2:00 必须build一次源码)

构建操作

echo %JOB_NAME%
echo %BUILD_NUMBER%
echo %WORKSPACE%
copy %WORKSPACE%\app\build\outputs\apk\debug\app-debug.apk C:\Users\{user}\public\apk\%JOB_NAME%-debug-%BUILD_NUMBER%.apk
cd C:\jenkins
java -jar qr.jar url=http://10.1.17.54:8000/apk/%JOB_NAME%-debug-%BUILD_NUMBER%.apk image=%JOB_NAME%-debug-%BUILD_NUMBER%.jpg save=C:\Users\{user}\public\qr_img

Set build desc

<img src='http://10.1.17.54:8000/qr_img/${JOB_NAME}-debug-${BUILD_NUMBER}.jpg' width="200px" height="200px" >

集成RSS

邮件通知格式

163邮箱通知设置

https://blog.csdn.net/yamingwu/article/details/44142635

模板一

1
Jenkins构建通知:$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<hr/>

(本邮件是程序自动下发的,请勿回复!)<br/><hr/>

项目名称:$PROJECT_NAME<br/><hr/>

构建编号:$BUILD_NUMBER<br/><hr/>

触发原因:${CAUSE}<br/><hr/>

构建状态:$BUILD_STATUS<br/><hr/>

扫描二维码
<img src="http://10.1.90.39:8000/qr_img/${JOB_NAME}-${BUILD_NUMBER}.jpg" height="200px" width="200px"/><br/>
下载地址:<a href="http://10.1.90.39:8000/apk/${JOB_NAME}-${BUILD_NUMBER}.apk">http://10.1.90.39:8000/apk/${JOB_NAME}-${BUILD_NUMBER}.apk</a><br>

<hr/>

构建日志地址:<a href="${BUILD_URL}console">${BUILD_URL}console</a><br/><hr/>

构建地址:<a href="$BUILD_URL">$BUILD_URL</a><br/><hr/>

变更集:${JELLY_SCRIPT,template="html"}<br/><hr/>

模板二

1
Jenkins构建通知:$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!
1
2
3
4
5
6
7
8
9
10
(邮件由Jenkins自动发出,请勿回复~)<br>
项目名称:$PROJECT_NAME<br>
构建编号:$BUILD_NUMBER<br>
构建状态:$BUILD_STATUS<br>
触发原因:${CAUSE}<br>
构建地址:<A HREF="${BUILD_URL}">${BUILD_URL}</A><br>
构建输出日志:<a href="${BUILD_URL}console">${BUILD_URL}console</a><br>
下载地址:<a href="http://10.1.67.54:8000/apk/${JOB_NAME}-debug-${BUILD_NUMBER}.apk">http://10.1.67.54:8000/apk/${JOB_NAME}-debug-${BUILD_NUMBER}.apk</a><br><br>
二维码下载:<img src='http://10.1.67.54:8000/qr_img/${JOB_NAME}-debug-${BUILD_NUMBER}.jpg' width="200px" height="200px" ><br>
最近修改:<br>${CHANGES, showPaths=false, format="%a:\"%m\"<br>", pathFormat="\n\t- %p"}

建议使用“邮箱大师”|“Foxmail”接收邮件

疑难解决

553 Mail from must equal authorized user

有些人错误为501 mail from address must be same as authorization user;其实问题是一样的

需要在Jenkins Location中配置系统管理员邮件地址

解决方式如下:
https://blog.csdn.net/yamingwu/article/details/44142635

SSLHandshakeException


There were errors checking the update sites: SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target


附一 参考

https://blog.csdn.net/u013066244/article/details/78665075

附二 常见问题汇总

Jenkins默认会在Build结束后Kill掉所有的衍生进程,导致后台无法运行应用程序。

在Post Steps ->Execute Shell 中配置的应用程序启动脚本启动后,当Build结束,进程会被Jenkins杀掉。为了在退出Build时继续运行程序,需要进行以下配置,才能避免此类情况发生。

7.1 方式一

a、重设环境变量build_id

在execute shell输入框中加入BUILD_ID=DONTKILLME,即可防止jenkins杀死启动的应用程序进程

b、在启动jenkins 的时候禁止jenkins杀死衍生进程

修改/etc/sysconfig/jenkins配置,在JENKINS_JAVA_OPTIONS中加入-Dhudson.util.ProcessTree.disable=true。需要重启jenkins生效。此方法配置一次后,所有的job都无需设置BUILD_ID,就能够防止jenkins杀死启动的应用程序进程。

7.2、方式二

添加 Post build task插件,在 Post build task 处执行脚本。

每当检测到项目编译信息中包含log text中的文本即执行script中的shell脚本。此处执行脚本启动的进程,jenkins不会自动kill。

联系我

QQ:94297366

Email: lichongmac@163.com

数据结构和算法

发表于 2019-03-08

数据结构和算法

目录

  1. 简单的数据结构
    • 栈、队列、链表、数组、哈希表
    • 栈和队列的相同和不同之处
    • 栈通常采用的两种存储结构
  2. 树

    • 二叉树、字典树、平衡树、排序树
    • B 树、B+ 树、R 树、多路树、红黑树
  3. 堆

    • 大根堆、小根堆
  4. 图

    • 有向图、无向图、拓扑
  5. 排序算法

    • 稳定的排序
      • 冒泡排序
      • 插入排序
      • 鸡尾酒排序
      • 桶排序
      • 计数排序
      • 归并排序
      • 原地归并排序
      • 二叉排序树排序
      • 鸽巢排序
      • 基数排序
      • 侏儒排序
      • 图书馆排序
      • 块排序
    • 不稳定的排序
      • 选择排序
      • 希尔排序
      • Clover 排序算法
      • 梳排序
      • 堆排序
      • 平滑排序
      • 快速排序
      • 内省排序
      • 耐心排序
  6. 两个栈实现队列,和两个队列实现栈

  7. 深度优先和广度优先搜索
  8. 全排列、贪心算法、KMP 算法、hash 算法
  9. 海量数据处理
    • 分治,hash 映射,堆排序,双层桶划分,Bloom Filter,bitmap,数据库索引,mapreduce

数据结构

栈

栈是一种“先进后出”的一种数据结构,有压栈出栈两种操作方式

栈的分类

栈主要分为两类:

  • 静态栈
  • 动态栈

【静态栈】

静态栈的核心是数组,类似于一个连续内存的数组,我们只能操作其栈顶元素。

【动态栈】

静态栈的核心是数组,类似于一个连续内存的数组,我们只能操作其栈顶节点。
MacDown logo

栈的算法

首先要明白以下思路:

  • 栈操作的是一个一个节点
  • 栈本身也是一种存储的数据结构
  • 栈有初始化、压栈、出栈、判空、遍历、清空等主要方法

栈的应用

  • 数制转换
  • 括号匹配的检验

    栈通常采用的两种存储结构

    参考地址

  • https://www.cnblogs.com/xiaoyouPrince/p/8082640.html

队列

栈和队列的相同与不同

链表

数组

哈希表

【Jenkins】构建后生成下载二维码并邮件通知

发表于 2018-05-13

二维码生成配置

http-server(MAC)

检测 node -v、npm -v 然后安装http-server

npm install http-server -g

启动服务“http-server -p 8000” 设置端口为8000.避免jenkins端口冲突
在/Users/XXX/Public 目录下面建立jenkins目录,把构建后的apk复制到/Users/XXX/Public/jenkins/apk/目录下面
然后把二维码图片也复制到/Users/XXX/Public/jenkins

为什么要http-server服务

因为在jenkins里的图片在邮件里是没有办法直接显示的。必须要有一个没有拦截的访问路径。以避开jenkins的用户验证。同理apk的下载也是如此
不过一般都是部署在局域网内。安全性还是有保障的。如果要发布到外网的可以使用第三方服务上传apk包生成公网下载二维码。

安装pip(MAC)(python>3.1)[如果版本不够看”其他“]

sudo easy_install pip

mac下安装pip

windows下面安装Python和pip终极教程

安装myqr

命令1:pip install Pillow (失败)
命令2:pip install -I --no-cache-dir -v Pillow (成功)
至于为什么失败,解释颇多也不想深究。暂且都记上多试试。
安装qrcode 命令:pip install myqr

生成二维码命令
myqr http://localhost:8000/jenkins/apk/XXXX.apk -n jenkins-qrcode-${JOB_NAME}.png -v 1 -l L -d /Users/XXX/Public/jenkins

Jenkins邮件通知配置

在邮件配置content字段里添加

<img src="http://localhost:8000/jenkins/jenkins-qrcode-${JOB_NAME}.png " width="200px" height="200px" /></br></hr>
<a href='http://localhost:8000/jenkins/apk/XXXX.apk'>点击下载apk</a>

邮件通知配置样例


(本邮件是程序自动下发的,请勿回复!)<br/><hr/>

项目名称:$PROJECT_NAME<br/><hr/>

构建编号:$BUILD_NUMBER<br/><hr/>

构建状态:$BUILD_STATUS<br/><hr/>

触发原因:${CAUSE}<br/><hr/>

扫描二维码下载<img src="http://192.168.10.214:8000/JenkinsApk/jenkins-qrcode-${JOB_NAME}.png" width="200px" height="200px"/> <br/><hr/>
<a href="http://192.168.10.214:8000/JenkinsApk/apk/${JOB_NAME}-Debug-${BUILD_NUMBER}.apk">点击下载${JOB_NAME}-Debug-${BUILD_NUMBER}.apk</a><br/><hr/>
构建日志地址:<a href="${BUILD_URL}console">${BUILD_URL}console</a><br/><hr/>

构建地址:<a href="$BUILD_URL">$BUILD_URL</a><br/><hr/>

变更集:${JELLY_SCRIPT,template="html"}<br/><hr/>

参考:
https://www.jianshu.com/p/915c1ae69144

其他

【MAC】

“OSError: [Errno 13] Permission denied: ‘/Library/Python/2.7/site-packages/PIL’”

使用 sudo pip install -I –no-cache-dir -v Pillow 命令安装

mac OSError: [Errno 1] Operation not permitted: ‘/tmp/pip-ZaNR4Q-uninstall/System/Library/Frameworks

升级python 版本

$ pip install --upgrade pip
$ sudo pip install numpy --upgrade --ignore-installed
$ sudo pip install scipy --upgrade --ignore-installed
$ sudo pip install scikit-learn --upgrade --ignore-installed

参考https://www.jianshu.com/p/7a18c78b5982

OSError: [Errno 13] Permission denied: ‘/Library/Python/2.7/site-packages/imageio-2.3.0.dist-info’
解决方法:
sudo pip install myqr

matplotlib 1.3.1 requires nose, which is not installed.
matplotlib 1.3.1 requires tornado, which is not installed.

解决方法:运行sudo pip install matplotlib

联系我

Email:lichongmac@163.com

支付宝打赏:https://pan.baidu.com/s/1UMWjU1FHv7hYpdlcCQrJ3A

微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【Jenkins】自动构建配置

发表于 2018-05-13

时间设置说明

* * * * *
(五颗星,中间用空格隔开)
第一颗*表示分钟,取值0~59
第二颗*表示小时,取值0~23
第三颗*表示一个月的第几天,取值1~31
第四颗*表示第几月,取值1~12
第五颗*表示一周中的第几天,取值0~7,其中0和7代表的都是周日

例子如下:

每15分钟构建一次:H/15 * * * *   或*/5 * * * *
每天8点构建一次:0 8 * * *
每天8点~17点,两小时构建一次:0 8-17/2 * * *
周一到周五,8点~17点,两小时构建一次:0 8-17/2 * * 1-5
每月1号、15号各构建一次,除12月:H H 1,15 1-11 *
*/5 * * * * (每5分钟检查一次源码变化)
0 2 * * * (每天2:00 必须build一次源码)

触发远程构建 例如,使用脚本

Build after other projects are built

Projects to watch “XXXXX”

* Trigger only if build is stable:只有在构建稳定时才触发
* 
* Trigger even if the build is unstable:即使构建不稳定,也要触发
* 
* Trigger even if the build fails:即使构建失败,也要触发
* 

Build periodically

周期性进行项目构建,这个是到指定的时间必须触发构建任务
比如我想在每天的9点,17点,朝九晚五各构建一次,在Build periodically里设置如下

Build when a change is pushed to GitLab. GitLab CI Service URL: XXXXX

  • Enabled GitLab triggers :启用GitLab触发器
  • Push Events:push 事件
  • Opened Merge Request Events:打开合并请求事件
  • Accepted Merge Request Events :接受合并请求事件
  • Closed Merge Request Events :关闭合并请求事件
  • Rebuild open Merge Requests :重建开放合并请求
  • Comments :评论
  • Comment for triggering a build:注释触发构建

GitHub hook trigger for GITScm polling

GitHub钩子触发GITScm轮询

Poll SCM

1.Poll SCM:定时检查源码变更(根据SCM软件的版本号),如果有更新就checkout最新code下来,然后执行构建动作

2.如果我想每隔30分钟(H/30 )检查一次源码变化,有变化就执行

Ignore post-commit hooks(可选项):忽略post-commit钩子

联系我

Email:lichongmac@163.com

支付宝打赏:https://pan.baidu.com/s/1UMWjU1FHv7hYpdlcCQrJ3A

微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【Unity3D】Android真机断点调试

发表于 2017-11-18

【Unity3D】Android真机断点调试

  1. 首先在手机上开启USB调试功能,并安装驱动(这一步很多手机助手都可以完成)
  2. 用USB电缆连接手机和电脑
  3. 确保手机和电脑在一个局域网内,简单的说就是电脑和手机共用一个路由器,网段一样。
  4. 打开电脑上CMD窗口,输入以下命令:

    Adb tcpip 5555(该命令打开手机adb网络调试功能)
    正常情况下输入命令后控制台会出现回显
    restarting in TCP mode port: 5555
    打开手机查看手机的IP地址(不会请百度)假设手机的地址是192.168.1.x输入命令
    adb connect 192.168.1.x
    如果一切正常控制台会回显以下内容
    connected to 192.168.1.x:5555
    如果你想查看是否连接成功请输入以下内容
    adb devices
    控制台会回显连接的设备

  5. 如果一切连接成功,请拔掉USB电缆,选择File->Build&Run,在编译之前要勾选上
    Development Build 和Script Debugging这两项在build setting里面勾选不要忘记否则是不能调试的)电脑会自动编译文件并将APK推送至手机,在手机上同意并安装

  6. 当程序运行后再Monodevelop里面打开Run->Attach to process 会发现你手机的选项,选择手机,在脚本里面添加断点,你发现可以调试了,那叫一个爽!出现问题再也不用去瞎猜,或者添加Debuglog了

【设计模式】Java状态模式(1)

发表于 2017-05-20

State的定义:不同的状态,不同的行为;或者说,每个状态有着相应的行为

何时使用状态模式

State模式在实际使用中比较多,适合”状态的切换”。因为我们经常会使用If elseif else 进行状态切换, 如果针对状态的这样判断切换反复出现,我们就要联想到是否可以采取State模式了。

不只是根据状态,也有根据属性。如果某个对象的属性不同,对象的行为就不一样,这点在数据库系统中出现频率比较高,我们经常会在一个数据表的尾部,加上property属性含义的字段,用以标识记录中一些特殊性质的记录,这种属性的改变(切换)又是随时可能发生的,就有可能要使用State。

在实际使用,类似开关一样的状态切换是很多的,但有时并不是那么明显,取决于你的经验和对系统的理解深度。

这里要阐述的是”开关切换状态” 和” 一般的状态判断”是有一些区别的,” 一般的状态判断”也是有 if..elseif结构,例如:

if (which==1) state="hello";
else if (which==2) state="hi";
else if (which==3) state="bye";

这是一个 “ 一般的状态判断”,state值的不同是根据which变量来决定的,which和state没有关系。如果改成:

if (state.euqals("bye")) state="hello";
 else if (state.euqals("hello")) state="hi";
 else if (state.euqals("hi")) state="bye";

这就是 “开关切换状态”,是将state的状态从”hello”切换到”hi”,再切换到””bye”;在切换到”hello”,好象一个旋转开关,这种状态改变就可以使用State模式了。

如果单纯有上面一种将”hello”–>”hi”–>”bye”–>”hello”这一个方向切换,也不一定需要使用State模式,因为State模式会建立很多子类,复杂化,但是如果又发生另外一个行为:将上面的切换方向反过来切换,或者需要任意切换,就需要State了。

请看下例:

public class Context{
  private Color state=null;
  public void push(){
    //如果当前red状态 就切换到blue
    if (state==Color.red) state=Color.blue;
    //如果当前blue状态 就切换到green
    else if (state==Color.blue) state=Color.green;
    //如果当前black状态 就切换到red
    else if (state==Color.black) state=Color.red;
    //如果当前green状态 就切换到black
    else if (state==Color.green) state=Color.black;
    Sample sample=new Sample(state);
    sample.operate();
  }
  public void pull(){
    //与push状态切换正好相反
    if (state==Color.green) state=Color.blue;
    else if (state==Color.black) state=Color.green;
    else if (state==Color.blue) state=Color.red;
    else if (state==Color.red) state=Color.black;
    Sample2 sample2=new Sample2(state);
    sample2.operate();
  }
}

在上例中,我们有两个动作push推和pull拉,这两个开关动作,改变了Context颜色,至此,我们就需要使用State模式优化它。

另外注意:但就上例,state的变化,只是简单的颜色赋值,这个具体行为是很简单的,State适合巨大的具体行为,因此在,就本例,实际使用中也不一定非要使用State模式,这会增加子类的数目,简单的变复杂。

例如:银行帐户,经常会在Open 状态和Close状态间转换。

例如:经典的TcpConnection,Tcp的状态有创建 侦听 关闭三个,并且反复转换,其创建 侦听 关闭的具体行为不是简单一两句就能完成的,适合使用State。

例如:信箱POP帐号,会有四种状态,start HaveUsername Authorized quit,每个状态对应的行为应该是比较大的,适合使用State。

例如:在工具箱挑选不同工具,可以看成在不同工具中切换,适合使用State。如 具体绘图程序,用户可以选择不同工具绘制方框 直线 曲线,这种状态切换可以使用State。

如何使用状态模式

State需要两种类型实体参与:
state manager 状态管理器,就是开关,如上面例子的Context实际就是一个state manager,在state manager中有对状态的切换动作。
用抽象类或接口实现的父类,不同状态就是继承这个父类的不同子类。
以上面的Context为例,我们要修改它,建立两个类型的实体。

第一步,首先建立一个父类:

package top.lc951.state1;

public abstract class  State {
    public abstract void handlepush(Context c);
    public abstract  void handlepull(Context c);
    public abstract String getColor();

}

父类中的方法要对应state manager中的开关行为,在state manager中 本例就是Context中,有两个开关动作push推和pull拉.那么在状态父类中就要有具体处理这两个动作:handlepush() handlepull();同时还需要一个获取push或pull结果的方法getcolor()。

下面是具体子类的实现:

package top.lc951.state1;

public class BlueState extends State {


@Override
public void handlepush(Context c) {
    // TODO Auto-generated method stub
    c.setState(new GreenState());

}

@Override
public void handlepull(Context c) {
    // TODO Auto-generated method stub
    c.setState(new RedState());

}

@Override
public String getColor() {
    // TODO Auto-generated method stub
    return "blue";

}

}

同样,其他状态的子类实现如blue一样。

第二步,要重新改写State manager 也就是本例的Context

public class Context{
 private Sate state=null; //我们将原来的 Color state 改成了新建的State state;
 //setState是用来改变state的状态 使用setState实现状态的切换
 pulic void setState(State state){
    this.state=state;
 }
 public void push(){
  //状态的切换的细节部分,在本例中是颜色的变化,已经封装在子类的handlepush中实现,这里无需关心
  state.handlepush(this);
  //因为sample要使用state中的一个切换结果,使用getColor()
  Sample sample=new Sample(state.getColor());
  sample.operate();
 }
 public void pull(){
  state.handlepull(this);
  Sample2 sample2=new Sample2(state.getColor());
  sample2.operate();
 }
}

至此,我们也就实现了State的refactorying过程。

源码地址https://github.com/lichong951/designpattern-lichong951.git

参考:http://www.cnblogs.com/Coda/p/4312281.html

面试总结记录

发表于 2017-04-27

1、 Android性能分析工具

1.1 TraceView:
    最简单的方式就是直接打开DDMS,选择一个进程,然后按上面的“Start Method Profiling”按钮,
    第2种方式就是使用android.os.Debug.startMethodTracing();和android.os.Debug.stopMethodTracing();方法
    重要指标 Calls+Recur Calls/Total(方法执行的次数);Cpu Time/Call(方法耗时)
1.2 Eclipse Memory Analyzer Tool 内存使用

1.3 Dump UI Hierarchy for UI Atomator 分析UI层级

1.4 systrace
    Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统(如surfaceflinger、WindowManagerService等Framework部分关键模块、服务)的运行信息,从而帮助开发者更直观的分析系统瓶颈,改进性能。
    Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中,它主要由3部分组成:
    内核部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用Systrace的话,必须开启kernel中和ftrace相关的模块。
    数据采集部分:Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时,Android还有一个atrace程序,它可以从ftrace中读取统计信息然后交给数据分析工具来处理。
    数据分析工具:Android提供一个systrace.py(python脚本文件,位于Android SDK目录/tools/systrace中,其内部将调用atrace程序)用来配置数据采集的方式(如采集数据的标签、输出文件名等)和收集ftrace统计数据并生成一个结果网页文件供用户查看。

1.5 Oprofile:性能数据采集和分析工具

1.6 Hierarchy Viewer(层级查看器)

参考:http://www.jianshu.com/p/da2a4bfcba68

1.7 Battery Historian 
    Google出品, 通过Android系统的bugreport文件来做电量使用分析的工具
    https://github.com/google/battery-historian
1.8 NetEase/Emmagee
    针对Android App的CPU, 内存, 网络, 电量等多项综合的测试分析.
    https://github.com/NetEase/Emmagee

1.9 Square
    Square出品, 必属精品.
    类似与App探针的内存泄露监测工具.
    https://github.com/square/leakcanary

1.10 AndroidDevMetrics
    一个library, 用来检测Activity生命周期执行性能, Dagger2注入性能以及帧率性能的工具
    https://github.com/frogermcs/AndroidDevMetrics

2、 Android内存优化方案

2.1 Android应用String/StringBuilder/StringBuffer优化建议
字符串操作在Android应用开发中是十分常见的操作,也就是这个最简单的字符串操作却也暗藏很多潜在的性能问题,下面我们实例来说说。
先看下面这个关于String和StringBuffer的对比例子:
//性能差的实现
String str1 = "Name:";
String str2 = "GJRS";
String Str = str1 + str2;
//性能好的实现
String str1 = "Name:";
String str2 = "GJRS";
StringBuffer str = new StringBuilder().append(str1).append(str2);

通过这个例子可以看出来,String对象(记得是对象,不是常量)和StringBuffer对象的主要性能区别在于String对象是不可变的,所以每次对String对象做改变操作(譬如“+”操作)时其实都生成了新的String对象实例,所以会导致内存消耗性能问题;而StringBuffer对象做改变操作每次都会对自己进行操作,所以不需要消耗额外的内存空间。

我们再看一个关于String和StringBuffer的对比例子:
//性能差的实现
StringBuffer str = new StringBuilder().append("Name:").append("GJRS");
//性能好的实现
String Str = "Name:" + "GJRS";
在这种情况下你会发现StringBuffer的性能反而没有String的好,原因是在JVM解释时认为 
String Str = "Name:" + "GJRS";就是String Str = "Name:GJRS";,所以自然比StringBuffer快了。
可以发现,如果我们拼接的是字符串常量则String效率比StringBuffer高,如果拼接的是字符串对象,则StringBuffer比String效率高,我们在开发中要酌情选择。当然,除过注意StringBuffer和String的效率问题,我们还应该注意另一个问题,那就是StringBuffer和StringBuilder的区别,其实StringBuffer和StringBuilder都继承自同一个父类,只是StringBuffer是线程安全的,也就是说在不考虑多线程情况下StringBuilder的性能又比StringBuffer高。
PS:如果想追究清楚他们之间具体细节差异,麻烦自己查看实现源码即可

2.2 Android应用HashMap与ArrayMap及SparseArray优化建议
在Android开发中涉及到数据逻辑部分大部分用的都是Java的API(譬如HashMap),但是对于Android设备来说有些Java的API并不适合,可能会导致系统性能下降,好在Google团队已经意识到这些问题,所以他们针对Android设备对Java的一些API进行了优化,优化最多就是使用了ArrayMap及SparseArray替代HashMap来获得性能提升。

HashMap:
HashMap内部使用一个默认容量为16的数组来存储数据,数组中每一个元素存放一个链表的头结点,其实整个HashMap内部结构就是一个哈希表的拉链结构。HashMap默认实现的扩容是以2倍增加,且获取一个节点采用了遍历法,所以相对来说无论从内存消耗还是节点查找上都是十分昂贵的。

SparseArray:
SparseArray比HashMap省内存是因为它避免了对Key进行自动装箱(int转Integer),它内部是用两个数组来进行数据存储的(一个存Key,一个存Value),它内部对数据采用了压缩方式来表示稀疏数组数据,从而节约内存空间,而且其查找节点的实现采用了二分法,很明显可以看见性能的提升。

ArrayMap:
ArrayMap内部使用两个数组进行数据存储,一个记录Key的Hash值,一个记录Value值,它和SparseArray类似,也会在查找时对Key采用二分法。
有了上面的基本了解我们可以得出结论供开发时参考,当数据量不大(千位级内)且Key为int类型时使用SparseArray替换HashMap效率高;当数据量不大(千位级内)且数据类型为Map类型时使用ArrayMap替换HashMap效率高;其他情况下HashMap效率相对高于二者

2.3 Android应用ContentProviderOperation优化建议
ContentProvider是Android应用开发的核心组件之一,有时候在开发中需要使用ContentProvider对多行数据进行操作,我们的做法一般是多次调运相关操作方法,殊不知这种实现方式是非常低性能的,取而代之的做法应该是使用批量操作,具体为了使批量更新、插入、删除数据操作更加方便官方提供了ContentProviderOperation工具类。所以在我们开发中遇到类似情景时请务必使用批量操作,具体的优势如下:
所有的操作都在一个事务中执行,可以保证数据的完整性。
批量操作在一个事务中执行,所以只用打开、关闭一个事务。
减轻应用程序与ContentProvider间的多次频繁交互,提升性能。
可以看见,这对于数据库操作来说是一个非常有用的优化措施,烦请务必重视(我们项目优化过,的确有很大提升)

2.4 其他逻辑优化
避免在Android中使用Java的枚举类型,因为编译后不但占空间,加载也费时,完全没有static final的变量好用、高效。

Handler发送消息时尽量使用obtain去获取已经存在的Message对象进行复用,而不是新new Message对象,这样可以减轻内存压力。

在使用后台Service时尽量将能够替换为IntentService的地方替换为此,这样可以减轻系统压力、省电、省内存、省CPU占用率。

在当前类内部尽量不要通过自己的getXXX、setXXX对自己内部成员进行操作,而是直接使用,这样可以提高代码执行效率。

不要一味的为了设计模式而过分的抽象代码,因为代码抽象系数与代码加载执行时间成正比。

尽量减少锁个数、减小锁范围,避免造成性能问题。

合理的选择使用for循环与增强型for循环,譬如不要在ArrayList上使用增强型for循环等。

3、 Android UI性能优化方案

4、 设计模式

5、 Android网络访问框架选型和优化

5.1 volley
5.2 retrofit
5.3 andrid-async-http

6、 Bug解决系统:Bugly;BugTags;GT;iTest;Emmagee;

7、 Android应用移动设备电池耗电性能分析

有了UI性能优化、内存性能优化、代码编写优化之后我们在来说说应用开发中很重要的一个优化模块—–电量优化。

7.1 Android应用耗电量概念

在盒子等开发时可能电量优化不是特别重视(视盒子待机真假待机模式而定),但是在移动设备开发中耗电量是一个非常重要的指标,如果用户一旦发现我们的应用非常耗电,不好意思,他们大多会选择卸载来解决此类问题,所以耗电量是一个十分重要的问题。

关于我们应用的耗电量情况我们可以进行定长时间测试,至于具体的耗电量统计等请参考此文,同时我们还可以直接通过Battery Historian Tool来查看详细的应用电量消耗情况。最简单常用办法是通过命令直接查看,如下:

adb shell dumpsys batterystats

其实我们一款应用耗电量最大的部分不是UI绘制显示等,常见耗电量最大原因基本都是因为网络数据交互、GPS定位、大量内存性能问题、冗余的后台线程和Service等造成。

7.2 Android应用耗电量优化建议

优化电量使用情况我们不仅可以使用系统提供的一些API去处理,还可以在平时编写代码时就养成好的习惯。具体的一些建议如下:

在需要网络的应用中,执行某些操作前尽量先进行网络状态判断。

在网络应用传输中使用高效率的数据格式和解析方法,譬如JSON等。

在传输用户反馈或者下载OTA升级包等不是十分紧急的操作时尽量采用压缩数据进行传输且延迟到设备充电和WIFI状态时进行。

在有必要的情况下尽量通过PowerManager.WakeLock和JobScheduler来控制一些逻辑操作达到省电优化。

对定位要求不太高的场景尽量使用网络定位,而不是GPS定位。

对于定时任务尽量使用AlarmManager,而不是sleep或者Timer进行管理。

尽可能的减少网络请求次数和减小网络请求时间间隔。

后台任务要尽可能少的唤醒CPU,譬如IM通信的长连接心跳时间间隔、一些应用的后台定时唤醒时间间隔等要设计合理。

特殊耗电业务情况可以进行弹窗等友好的交互设计提醒用户该操作会耗用过多电量。

可以看见,上面只是一些常见的电量消耗优化建议。总之,作为应用开发者的我们要意识到电量损耗对于用户来说是非常敏感的,只有我们做到合理的电量优化才能赢得用户的芳心。

12

lichong

读书笔记;移动开发;node.js

20 日志
19 标签
© 2019 lichong
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4