前言

本文将整理腾讯 GT 各个性能测试项的测试方法,目的是为了帮助移动性能专项测试同学快速过一遍腾讯 GT 各个性能数据是如何获取的。

另外对腾讯 GT 还不了解或者不知道它能做什么的同学可以看看这篇文章:https://testerhome.com/topics/9092

一.GT 性能测试方案之 CPU 测试

1.简要流程

初始化 cpu 的数据

提供了两种方法获取 CPU 数据

getCpuUsage: 整机的 CPU 使用水平,主要用于实时刷新 GT 上的 CPU 数据。通过读取/proc/stat 的数据,将每一个核的 cpu 使用跟闲置数据提取。使用率永远是增量式计算。计算方法为 100*(cpu 忙时增量-cpu 整体增量),从计算方法来看,可能会导致负数出现。

getProcessCpuUsage:计算进程的 CPU 使用率,主要通过"/proc/" + pid + "/stat"来计算,在这里回京过一系列计算,拿到进程的 CPU 时间片

2.代码流程

cpu 数据初始化

经过初始化,让 CPU 整体使用跟进程的 CPU 占用都为 0

public CpuUtils() {

initCpuData();

}

private void initCpuData() {

pCpu = o_pCpu = 0.0;

aCpu = o_aCpu = 0.0;

}

通过不同的调用栈,来监控不同的 CPU 数据

整体 CPU 使用率:由于 getCpuUsage 是通过后台线程不断刷新来实现的,因此,o_cpu/o_idle 数据不断在实时更新

RandomAccessFile reader = null;

try {

reader = new RandomAccessFile("/proc/stat", "r");

String load;

load = reader.readLine();

String[] toks = load.split(" ");

double c_idle = Double.parseDouble(toks[5]);

double c_cpu = Double.parseDouble(toks[2])

+ Double.parseDouble(toks[3])

+ Double.parseDouble(toks[4])

+ Double.parseDouble(toks[6])

+ Double.parseDouble(toks[8])

+ Double.parseDouble(toks[7]);

if (0 != ((c_cpu + c_idle) - (o_cpu + o_idle))) {

// double value = (100.00 * ((c_cpu - o_cpu) ) / ((c_cpu +

// c_idle) - (o_cpu + o_idle)));

usage = DoubleUtils.div((100.00 * ((c_cpu - o_cpu))),

((c_cpu + c_idle) - (o_cpu + o_idle)), 2);

// Log.d("CPU", "usage: " + usage);

if (usage < 0) {

usage = 0;

}

else if (usage > 100)

{

usage = 100;

}

// BigDecimal b = new BigDecimal(Double.toString(value));

// usage = b.setScale(2,

// BigDecimal.ROUND_HALF_UP).doubleValue();

// Log.d("CPU", "usage: " + usage);

}

o_cpu = c_cpu;

o_idle = c_idle;

} catch (IOException e) {

e.printStackTrace();

} finally {

FileUtil.closeRandomAccessFile(reader);

}

进程的 CPU 使用时间片获取

public String getProcessCpuUsage(int pid) {

String result = "";

String[] result1 = null;

String[] result2 = null;

if (pid >= 0) {

result1 = getProcessCpuAction(pid);

if (null != result1) {

pCpu = Double.parseDouble(result1[1])

+ Double.parseDouble(result1[2]);

}

result2 = getCpuAction();

if (null != result2) {

aCpu = 0.0;

for (int i = 2; i < result2.length; i++) {

aCpu += Double.parseDouble(result2[i]);

}

}

double usage = 0.0;

if ((aCpu - o_aCpu) != 0) {

usage = DoubleUtils.div(((pCpu - o_pCpu) * 100.00),

(aCpu - o_aCpu), 2);

if (usage < 0) {

usage = 0;

}

else if (usage > 100)

{

usage = 100;

}

}

o_pCpu = pCpu;

o_aCpu = aCpu;

result = String.valueOf(usage) + "%";

}

p_jif = pCpu;

return result;

}

二.GT 性能测试方案之内存测试

1.简要流程

内存测试主要通过 handleMessage 来触发,根据 msg 参数来决定任务执行

内存数据获取:仅仅简单通过 dumpsys meminfo 来获取内存数据,然后通过解析来得到 pss_Native/naticeHeapSize/naticeAllocated/pss_OtherDev/pss_graphics/pss_gl/pss_UnKnown/pss_total 数据

dumpHeap:通过 am dumpheap 来获取 heap 文件

GC:直接 kill -10 $pid 来完成 GC

2.测试方法

内存数据获取

public static MemInfo getMemInfo(String packageName)

{

MemInfo result = null;

String resultString = null;

try {

resultString = runCMD("dumpsys meminfo " + packageName);

} catch (Exception e) {

e.printStackTrace();

return MemInfo.EMPTY;

}

if(Env.API < 14)

{

result = parseMemInfoFrom2x(resultString);

}

else if (Env.API < 19)

{

result = parseMemInfoFrom4x(resultString);

}

else

{

result = parseMemInfoFrom44(resultString);

}

return result;

}

dumpHeap

private void dumpHeap() {

String pid = String.valueOf(ProcessUtils

.getProcessPID(AUTManager.pkn.toString()));

if (!pid.equals("-1")) {

boolean isSucess = true;

ProcessBuilder pb = null;

String sFolder = Env.S_ROOT_DUMP_FOLDER + AUTManager.pkn.toString() + "/";

File folder = new File(sFolder);

if (!folder.exists())

{

folder.mkdirs();

}

String cmd = "am dumpheap " + pid + " "// 命令

+ Env.S_ROOT_DUMP_FOLDER + AUTManager.pkn.toString() + "/"// 输出路径

+ "dump_" + pid + "_" + GTUtils.getSaveDate() + ".hprof"; // 输出文件名

pb = new ProcessBuilder("su", "-c", cmd);

Process exec = null;

pb.redirectErrorStream(true);

try {

exec = pb.start();

InputStream is = exec.getInputStream();

BufferedReader reader = new BufferedReader(

new InputStreamReader(is));

while ((reader.readLine()) != null) {

isSucess = false;

}

} catch (Exception e) {

e.printStackTrace();

isSucess = false;

}

// 至此命令算是执行成功

if (isSucess)

{

handler.sendEmptyMessage(6);

}

} else {

Log.d("dump error", "pid not found!");

}

}

GC

private void gc() {

String pid = String.valueOf(ProcessUtils

.getProcessPID(AUTManager.pkn.toString()));

if (!pid.equals("-1")) {

boolean isSucess = true;

ProcessBuilder pb = null;

String cmd = "kill -10 " + pid;

pb = new ProcessBuilder("su", "-c", cmd);

Process exec = null;

pb.redirectErrorStream(true);

try {

exec = pb.start();

InputStream is = exec.getInputStream();

BufferedReader reader = new BufferedReader(

new InputStreamReader(is));

while ((reader.readLine()) != null) {

isSucess = false;

}

} catch (Exception e) {

e.printStackTrace();

isSucess = false;

}

// 至此命令算是执行成功

if (isSucess)

{

handler.sendEmptyMessage(5);

}

} else {

Log.d("gc error", "pid not found!");

}

}

三.GT 性能测试方案之内存填充

1.简要流程

内存填充,主要是通过调用系统的 malloc 来分配内存。内存释放,则是通过系统 free 来释放。

2. 代码流程

内存分配以及释放的函数

const int BASE_SIZE = 1024*1024; // 1M

int fill(int blockNum)

{

int memSize = blockNum * BASE_SIZE;

p = (char *)malloc(memSize);

int i;

for (i = 0; i < memSize; i++)

{

p[i] = 0;

}

return 0;

}

int freeMem()

{

free(p);

return 0;

}

加载 com_tencent_wstt_gt_api_utils_MemFillTool.c,并提供度应对操作接口

public class MemFillTool {

public MemFillTool() {

}

public static MemFillTool instance = null;

public static MemFillTool getInstance() {

if (instance == null) {

System.loadLibrary("mem_fill_tool");

instance = new MemFillTool();

}

return instance;

}

// 填充xxxMB内存

public native int fillMem(int blockNum);

// 释放刚才填充的内存

public native int freeMem();

}

四.GT 性能测试方案之帧率测试

1.简要流程

FPS 数据收集是一个定时任务(4.3 后 1s 一次),通过异步线程中不断获取 FPS 数据来刷新到前端页面。而广播模式调用,则直接从缓存的 field 中获取数据即可。

在这里 GT 获取 fps 数据,也是通过采用 surfaceflinger 来获取,但我感觉好像是有问题的。因为,一般 surfaceflinger 数据获取的命令是adb shell dumpsys SurfaceFlinger --latency ,在这里直接定义了把"service call SurfaceFlinger 1013"字符串写到流里,没看明白这个操作跟帧率获取有什么关系。刚去了解了下,Runtime.getRuntime()原来执行多条命令时后续只要拿到process的DataOutputStream对象,继续writeBytes就可以保证是在同一个上下文中执行多条命令了。

2.代码流程

判断当前 root 状态,如果没有 root 直接返回,避免消耗系统资源

if (! GTFrameUtils.isHasSu())

{

return;

}

计算一个周期内的帧率数据,由于比较简单(除了在 1 中 surfaceflinger 数据存疑外),直接把核心代码发出来

startTime = System.nanoTime();

if (testCount == 0) {

try {

lastFrameNum = getFrameNum();

} catch (IOException e) {

e.printStackTrace();

}

}

int currentFrameNum = 0;

try {

currentFrameNum = getFrameNum();

} catch (IOException e) {

e.printStackTrace();

}

int FPS = currentFrameNum - lastFrameNum;

if (realCostTime > 0.0F) {

int fpsResult = (int) (FPS * 1000 / realCostTime);

defaultClient.setOutPara("FPS", fpsResult);

}

lastFrameNum = currentFrameNum;

testCount += 1;

帧率获取的部分也发一下。另外感谢@codeskyblue 的指点,service call SurfaceFlinger 1013这个命令是获取系统的总的刷新帧率(返回的是 16 进制)

public static synchronized int getFrameNum() throws IOException {

String frameNumString = "";

String getFps40 = "service call SurfaceFlinger 1013";

if (process == null)

{

process = Runtime.getRuntime().exec("su");

os = new DataOutputStream(process.getOutputStream());

ir = new BufferedReader(

new InputStreamReader(process.getInputStream()));

}

os.writeBytes(getFps40 + "\n");

os.flush();

String str = "";

int index1 = 0;

int index2 = 0;

while ((str = ir.readLine()) != null) {

if (str.indexOf("(") != -1) {

index1 = str.indexOf("(");

index2 = str.indexOf(" ");

frameNumString = str.substring(index1 + 1, index2);

break;

}

}

int frameNum;

if (!frameNumString.equals("")) {

frameNum = Integer.parseInt(frameNumString, 16);

} else {

frameNum = 0;

}

return frameNum;

}

五.GT 性能测试方案之流畅度测试

1.简要流程

腾讯的流畅度测试比较简单粗暴,测试方式是通过初始化 choreographer 日志级别,生成 Choreographer 日志来得到当前操作的丢帧。通过一系列计算后来计算流畅度。

2.测试方法

执行setprop debug.choreographer.skipwarning 1

View.OnClickListener button_write_property = new View.OnClickListener() {

@Override

public void onClick(View v) {

String cmd = "setprop debug.choreographer.skipwarning 1";

ProcessBuilder execBuilder = new ProcessBuilder("su", "-c", cmd);

execBuilder.redirectErrorStream(true);

try {

execBuilder.start();

} catch (IOException e) {

e.printStackTrace();

}

}

};

执行getprop debug.choreographer.skipwarning判断,为 1 则可以进行测试

View.OnClickListener button_check_status = new View.OnClickListener() {

@Override

public void onClick(View v) {

String cmd = "getprop debug.choreographer.skipwarning";

ProcessBuilder execBuilder = new ProcessBuilder("sh", "-c", cmd);

execBuilder.redirectErrorStream(true);

try {

TextView textview = (TextView) findViewById(R.id.textviewInformation);

Process p = execBuilder.start();

InputStream is = p.getInputStream();

InputStreamReader isr = new InputStreamReader(is);

BufferedReader br = new BufferedReader(isr);

Boolean flag = false;

String line;

while ((line = br.readLine()) != null) {

if (line.compareTo("1") == 0) {

flag = true;

break;

}

}

if (flag) {

textview.setText("OK");

} else {

textview.setText("NOT OK");

}

} catch (IOException e) {

e.printStackTrace();

}

}

};

执行adb logcat -v time -s Choreographer:I *:S

过滤获取当前 pid 丢帧值

protected void onHandleIntent(Intent intent) {

try {

String str = intent.getStringExtra("pid");

int pid = Integer.parseInt(str);

List args = new ArrayList(Arrays.asList("logcat", "-v", "time", "Choreographer:I", "*:S"));

dumpLogcatProcess = RuntimeHelper.exec(args);

reader = new BufferedReader(new InputStreamReader(dumpLogcatProcess.getInputStream()), 8192);

String line;

while ((line = reader.readLine()) != null && !killed) {

// filter "The application may be doing too much work on its main thread."

if (!line.contains("uch work on its main t")) {

continue;

}

int pID = LogLine.newLogLine(line, false).getProcessId();

if (pID != pid){

continue;

}

line = line.substring(50, line.length() - 71);

Integer value = Integer.parseInt(line.trim());

SMServiceHelper.getInstance().dataQueue.offer(value);

}

} catch (IOException e) {

Log.e(TAG, e.toString() + "unexpected exception");

} finally {

killProcess();

}

}

数据处理得到 sm 值

腾讯这边的处理方案是:当丢帧<60 时,流畅度 SM =60-frame; 当丢帧 frame>60 时,流畅度 SM = 60-frame%60。不过这种处理方式是有问题的。在这里要先说下流畅度计算的原理:

VSync 机制可以通过其 Loop 来了解当前 App 最高绘制能力,固定每隔 16.6ms 执行一次,这样最高的刷新的帧率就控制在 60FPS 以内,Choreographer 日志可以打印当前丢帧数,因此通过计算,得到当前 APP 的流畅度。

而计算这样来计算可能会更加准确(个人看法,欢迎讨论):

SM= 60-丢帧 frame/每两行同一线程的丢帧时间差(单位:s),如果只关心 UI 线程,那就只需要统计 UI 线程即可。

while (true) {

if (pause) {

break;

}

int x = count.getAndSet(0);

// 卡顿大于60时,要将之前几次SM计数做修正

if (x > 60) {

int n = x / 60;

int v = x % 60;

TagTimeEntry tte = OpPerfBridge.getProfilerData(key);

int len = tte.getRecordSize();

// 补偿参数

int p = n;

//Math.min(len, n);

/*

* n > len是刚启动测试的情况,日志中的亡灵作祟,这种情况不做补偿;

* 并且本次也记为60。本逻辑在两次测试间会清理数据的情况生效。

*/

if (n > len) {

globalClient.setOutPara(key, 60);

// globalClient.setOutPara(SFKey, 0);

} else {

for (int i = 0; i < p; i++) {

TimeEntry te = tte.getRecord(len - 1 - i);

te.reduce = 0;

}

globalClient.setOutPara(key, v);

// globalClient.setOutPara(SFKey, 60 - v);

}

} else {

int sm = 60 - x;

globalClient.setOutPara(key, sm);

// globalClient.setOutPara(SFKey, x);

}

六.GT 性能测试方案之流量测试

1.简要流程

流量测试有三种方案,默认采用方案 1

通过读取"/proc/uid_stat/" + uid + "/tcp_snd"获取发送跟接收流量

直接调用 android 的api:TrafficStats.getUidTxBytes(uid)来获取流量数据(该方法号称是获取到指定 uid 发送流量的总和,但实测情况是只有 tcp 层的流量)

第三种方案居然空在那里,那实际上只有两种方案

2.代码流程

初始化流量

public void initProcessNetValue(String pName) {

p_t_base = getOutOctets(pName);

p_r_base = getInOctets(pName);

p_t_add = 0;

p_r_add = 0;

}

其中 getOutOctets/getInOctets 具体对应什么方法,需要看设备是不是支持 uid 流量数据获取

获取增加的流量

public String getProcessNetValue(String pName) {

StringBuffer sb = new StringBuffer();

java.text.DecimalFormat df = new java.text.DecimalFormat("#.##");

p_t_cur = getOutOctets(pName);

p_r_cur = getInOctets(pName);

p_t_add = (p_t_cur - p_t_base) / B2K;

p_r_add = (p_r_cur - p_r_base) / B2K;

sb.append("t");

sb.append(df.format(p_t_add));

sb.append("KB|r");

sb.append(df.format(p_r_add));

sb.append("KB");

return sb.toString();

}

矫正处理

// modify on 20120616 过滤有的手机进程流量偶尔输出负数的情况

if ((nowT != lastT || nowR != lastR) && nowT >= 0 && nowR >= 0) {

OpPerfBridge.addHistory(op, value, new long[]{(long) nowT, (long) nowR});

}

return value;

七.GT 性能测试方案之电量测试

1.简单流程

关注指标:

电量测试关注的指标有四个: 电流,电压,电量跟温度。

数据获取方式:

通过ReadPowerTimerTask任务去 set 关注的电量指标,当 update 方法调用时,才把数据 set 进去。电量数据调用的系统命令/sys/class/power_supply/battery/uevent

具体流程:

接收"com.tencent.wstt.gt.plugin.battery.startTest"广播后,update 各个指标

注册跟设置出参,并设置刷新频率跟初始化屏幕电量

开启定时任务ReadPowerTimerTask,这个任务的作用就是去获取/sys/class/power_supply/battery/uevent下的电量数据并解析,最后设置出参。刷新频率默认是 250ms 一次

最后 stop 时,销毁异步定时任务

2.代码流程

整个生命周期如下,当BATTERY_START_TEST行为被捕获时,开始执行电量测试

String action = intent.getAction();

if (action == null) return;

if (action.equals(BATTERY_START_TEST)) {

int refreshRate = intent.getIntExtra("refreshRate", 250);

int brightness = intent.getIntExtra("brightness", 100);

boolean updateI = intent.getBooleanExtra("I", true);

GTBatteryEngine.getInstance().updateI(updateI);

boolean updateU = intent.getBooleanExtra("U", false);

GTBatteryEngine.getInstance().updateU(updateU);

boolean updateT = intent.getBooleanExtra("T", false);

GTBatteryEngine.getInstance().updateT(updateT);

boolean updateP = intent.getBooleanExtra("P", false);

GTBatteryEngine.getInstance().updateP(updateP);

GTBatteryEngine.getInstance().doStart(refreshRate, brightness);

} else if (action.equals(BATTERY_END_TEST)) {

GTBatteryEngine.getInstance().doStop();

}

update 操作很简单,只是注册跟 set 出参

public void updateI(boolean isChecked)

{

if (isChecked)

{

globalClient.registerOutPara(GTBatteryEngine.OPI, "I");

globalClient.setOutparaMonitor(GTBatteryEngine.OPI, true);

}

else

{

globalClient.unregisterOutPara(GTBatteryEngine.OPI);

}

state_cb_I = isChecked;

GTPref.getGTPref().edit().putBoolean(GTBatteryEngine.KEY_I, isChecked).commit();

for (BatteryPluginListener listener : listeners)

{

listener.onUpdateI(isChecked);

}

}

初始化操作略过,这里展示异步任务,处理流程直接看下面的关键代码即可,方法在GTBatteryEngine.java中

timer = new Timer(true);

timer.schedule(new ReadPowerTimerTask(), refreshRate, refreshRate);

@Override

public void run() {

BufferedReader br = null;

try {

FileReader fr = new FileReader(f);

br = new BufferedReader(fr);

String line = "";

while((line = br.readLine()) != null){

int found = 0;

if (line.startsWith("POWER_SUPPLY_VOLTAGE_NOW="))

{

U = line.substring(line.lastIndexOf("=") + 1);

// since 2.1.1 从μV转成mV

long volt = Long.parseLong(U) / 1000;

globalClient.setOutPara(OPU, volt + "mV");

OutPara op = globalClient.getOutPara(OPU);

if (null != op)

{

OpPerfBridge.addHistory(op, U, volt);

}

found++;

}

if (line.startsWith("POWER_SUPPLY_CURRENT_NOW="))

{

I = line.substring(line.lastIndexOf("=") + 1);

// since 2.1.1 从μA转成mA since 2.2.4 华为本身就是mA

long current = Long.parseLong(I);

if (isHuawei)

{

current = -current;

}

else if (isLGg3)

{

current = current >> 1; // 经验值估算LG g3的数据除以2后比较接近真实

}

else

{

current = current / 1000;

}

globalClient.setOutPara(OPI, current + "mA");

OutPara op = globalClient.getOutPara(OPI);

if (null != op)

{

OpPerfBridge.addHistory(op, I, current);

}

found++;

}

if (line.startsWith("POWER_SUPPLY_CAPACITY="))

{

String lastBattery = POW;

POW = line.substring(line.lastIndexOf("=") + 1);

if (! lastBattery.equals(POW)) // 电池百分比变化了

{

if (startBattry != -1)

{

lastBatteryChangeTime = (System.currentTimeMillis() - startBattry)/1000 + "s";

String tempValue = POW + "% | -1% time:" + lastBatteryChangeTime;

globalClient.setOutPara(OPPow, tempValue);

GTLog.logI(LOG_TAG, tempValue);

// 将电量加入历史记录

OutPara op = globalClient.getOutPara(OPPow);

if (null != op)

{

OpPerfBridge.addHistory(op, tempValue, Long.parseLong(POW));

}

}

startBattry = System.currentTimeMillis();

}

globalClient.setOutPara(OPPow, POW + "% | -1% time:" + lastBatteryChangeTime);

found++;

}

if (line.startsWith("POWER_SUPPLY_TEMP="))

{

TEMP = line.substring(line.lastIndexOf("=") + 1);

int iTemp = Integer.parseInt(TEMP);

iTemp = iTemp/10;

if (iTemp > -273)

{

TEMP = iTemp + "℃";

}

globalClient.setOutPara(OPTemp, TEMP);

OutPara op = globalClient.getOutPara(OPTemp);

if (null != op && iTemp != INT_TEMP)

{

OpPerfBridge.addHistory(op, TEMP, iTemp);

GTLog.logI(LOG_TAG, TEMP);

INT_TEMP = iTemp;

}

found++;

}

if (found >= 4)

{

return;

}

}

} catch (Exception e) {

doStop();

}

finally

{

FileUtil.closeReader(br);

}

}

android 腾讯 gt,源码解读腾讯 GT 的性能测试方案相关推荐

  1. Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)

    不要心急,一点一点的进步才是最靠谱的. 读完本文你将了解: 前言 Message 如何获取一个消息 Message.obtain() 消息的回收利用 MessageQueue MessageQueue ...

  2. 源码解读腾讯 GT 的性能测试方案

    前言 本文将整理腾讯GT各个性能测试项的测试方法,目的是为了帮助移动性能专项测试同学快速过一遍腾讯GT各个性能数据是如何获取的. 一.GT性能测试方案之CPU测试 1.简要流程 初始化cpu的数据 提 ...

  3. Android 开源框架之 Android-async-http 源码解读

    开源项目链接 Android-async-http仓库:https://github.com/loopj/android-async-http android-async-http主页:http:// ...

  4. PackageManagerService Android 8.1 源码解读 02

    接上文:PackageManagerService Android 8.1 源码解读 01 d.第三步细节:PKMS.main(),main函数主要工作: [检查]Package编译相关系统属性 [调 ...

  5. android 普通蓝牙源码解读

    一:概述 上一篇博客讲了下蓝牙4.0在android中的应用,这里讲讲普通蓝牙之间如何操作. 我记得在大二的时候还做了个比较烂的游戏,当时喜欢打dota就做了个蓝牙之间对战坦克的游戏,可以去看看,确实 ...

  6. PackageManagerService Android 8.1 源码解读 01

    一.PackageManagerService 是什么? 答: PackageManagerService(简称 [PKMS]),是 Android 系统中核心服务之一,负责应用程序的安装,卸载,信息 ...

  7. 【Android】OkHttp源码解读逐字稿(1)-拦截器

    目录 0.前言 1.OkHttp的简单使用 2.浅析开始 拦截器 链式调用流程示意图 第 0 个拦截器 第一个 拦截器 RetryAndFollowUpInterceptor 第二个拦截器 Bridg ...

  8. 【Android】OkHttp源码解读逐字稿(2)-OkHttpClient

    目录 0.前言 1.各个属性浅析 01.dispatcher 02.connectionPool 03.interceptors&networkInterceptors 04.eventLis ...

  9. Feflow 源码解读

    Feflow 源码解读 Feflow(Front-end flow)是腾讯IVWEB团队的前端工程化解决方案,致力于改善多类型项目的开发流程中的规范和非业务相关的问题,可以让开发者将绝大部分精力集中在 ...

最新文章

  1. 如何设置Java Spring Boot JWT授权和认证
  2. 递归和函数栈与setjmp和longjmp的关系
  3. java基础知识回顾之javaIO类总结
  4. ACM Smallest Difference
  5. Linux字符驱动程序的基本结构与函数
  6. java计算字符串中字符出现的次数_java – 计算字符串中字符出现次数
  7. C语言的格式控制符问题
  8. 信奥中的数学:孙子定理 中国剩余定理
  9. 关于Rxjava的几个问题
  10. 拷贝网页内容增加版权信息的 JavaScript 代码示例
  11. linux进阶之Tomcat服务篇
  12. ubuntu10下Eclipse中无法输入中文
  13. 大牛学习爬虫经验,转自知乎
  14. 建设智能机房--动环监控系统你不能不知道的事
  15. java开发一年后学习计划
  16. python 修改文件名有特殊符号_Linux删除包含特殊符号文件名的文件
  17. PHP之实现 家谱树,子孙树
  18. 【FinE】资产组合理论(2) 均值方差模型
  19. NVIDIA BERT推理解决方案Faster Transformer开源了!
  20. 如何解决error: failed to push some refs to ‘git@github.com:......git pull冲突问题

热门文章

  1. Python 数据结构与算法——拓扑排序
  2. 保护眼睛的电脑设置_专为长期玩电脑的你准备的3种护眼模式,你学到了吗?...
  3. python画三维图-Python 绘制酷炫的三维图步骤详解
  4. python代码写好了怎么运行-教你如何编写、保存与运行 Python 程序
  5. python简单爬虫代码-使用Python3.5写简单网络爬虫
  6. python程序员工资-被Python程序员高工资惊到!报告却显示Python热度降了?
  7. python工资一般多少p-为什么这么多人喜欢Python?Python的就业方向是什么?
  8. 用python画小黄人-怎么用python画小黄人
  9. 21个php常用方法汇总
  10. bag of words matlab,Bag of words(matlab实现)