Javascript中八种遍历方法的执行速度深度对比


Posted in Javascript onApril 25, 2017

前言

遍历数组或对象是一名程序员的基本素养之一. 然而遍历却不是一件简单的事, 优秀的程序员知道怎么去选择合适的遍历方法, 优化遍历效率. 本篇将带你走进JavaScript遍历的世界, 享受分析JS循环的快感. 本篇所有代码都可以直接运行, 希望您通读本篇后, 不止是浏览, 最好是亲手去实践下.

概述

js有如下两种数据需要经常遍历

  • 数组(Array)
  • 对象(Object)

同时又提供了如下8种方法方便我们遍历元素

  • for
  • while(或do~while)
  • forEach
  • for in
  • $.each
  • $(selecter).each
  • map
  • every

最终我们将分析遍历效率选出最佳遍历选手.

本文将针对如下两种数据进行详细的分析和举栗. 下面举栗中如果不加特殊说明将会用到如下数据.

var array = ["囚徒","过客","领袖"];//职场3种人
var o = {0:"linda",1:"style",2:"nick",length:3};

for

语法: for(初始化; 循环执行条件; 每遍历一个元素后做的事情;){}

(function(){//循环置于闭包之内
 for(var i=0,length=array.length;i<length;i++){//缓存数组长度
 console.log(array[i]);//内部方法若有可能相互影响,也要置于闭包之内
 }
})();

for循环只能遍历数组, 不能遍历对象. 写for循环时有两点需要注意.

  • 其一, 为了避免遍历时执行多遍计算数组长度的操作, 影响效率, 建议在循环开始以变量的形式缓存下数组长度, 若在循环内部有可能改变数组长度, 请务必慎重处理, 避免数组越界.
  • JavaScript中并没有类似java的块级作用域, for循环内部定义的变量会直接暴露在外(如 i,循环退出后,i变量将等于数组长度, 后续代码将能访问到 i 变量的值), 因此建议将for循环置于闭包内. 特别要注意的是: 如果在循环内部, 前一个元素的遍历有可能影响到后一个元素的遍历, 那么for循环内部方法也需要置于闭包之内.

do/while

语法: do{...}while(true);

do while
(function() {
 var i = 0,
 len = array.length;
 do {
 if (i == 2) {
 break; // 循环被终止, 此处如果是continue就会造成循环无法退出
 };
 console.log('array['+ i +']:' + array[i]);
 i++;//此句建议放置循环while头部
 } while(i<len);
})();

do/while的语法简化了循环的实现, 只保留对循环条件的判断, 所以我们要在循环内部构造出循环退出的条件, 否则有可能造成死循环. 特别要注意的是: 使用 continue 跳出本次遍历时, 要保证循环能够自动进入到下一次遍历, 因此保证循环走到下一次遍历的语句需要放到 continue 前面执行, 建议置于循环头部.(如上, i++ 语句最好放置循环头部)

do/while 循环与for循环大体差不多,只支持数组遍历, 多用于对循环退出条件不是很明确的场景. 一般来说不建议使用这种方式遍历数组.

forEach

语法: array.forEach(function(item){}) , 参数item表示数组每一项的元素

array.forEach(function(item){
 if(item=="囚徒")
 return;//这里只能使用return跳过当前元素处理
 console.log(item);
});

forEach回调function默认有三个参数: item, index, array.

使用forEach循环有几点需要特别注意:

  • forEach无法遍历对象
  • forEach无法在IE中使用,只是在firefox和chrome中实现了该方法
  • forEach无法使用break,continue跳出循环,使用return时,效果和在for循环中使用continue一致

for in

语法: for(var item in array){}

for(var item in array){
 console.log(item);
}//0 1 2
for(var item in o){
 console.log(item);
}//0 1 2 length

for in 可用于遍历数组和对象, 但它输出的只是数组的索引和对象的key, 我们可以通过索引和key取到对应的值. 如下:

for(var item in array){
 console.log(array[item]);
}//"囚徒" "过客" "领袖"
for(var item in o){
 console.log(o[item]);
}//"linda" "style" "nick" "length"

$.each

语法: $.each(array|o, function(i, ele){}) 支持数组和对象

$.each(array, function(i, ele){
 console.log(i,ele,this==ele);
});
//0 "囚徒" true
//1 "过客" true
//2 "领袖" true
$.each(o, function(i, ele){
 console.log(i,ele,this==ele);
});
//0 "linda" true
//1 "style" true
//2 "nick" true

这里我们注意到 this对象 指向当前属性的值,这是因为:

参考jQuery api:

$.each() 方法会迭代jQuery对象中的每一个DOM元素。每次回调函数执行时,会传递当前循环次数作为参数(从0开始计数)。更重要的是,回调函数是在当前DOM元素为上下文的语境中触发的。因此关键字 this 总是指向这个元素。
同时,上述遍历时, o 对象的属性中有一个length属性并没有被输出. 这是为什么呢? 请耐心往下看.

首先, 我们来看看遍历对象o时, 当前的this对象到底是什么?

$.each(o, function(i, ele){
 if(this=="linda"){//我们随机选取第一个属性
 console.log(this,this==ele);
 $.each(this, function(e, ele2) {
 console.log(e, ele2);
 });
 }
});
//String {0: "l", 1: "i", 2: "n", 3: "d", 4: "a", length: 5, [[PrimitiveValue]]: "linda"} true
//0 "l"
//1 "i"
//2 "n"
//3 "d"
//4 "a"

我们发现, this对象等于回调函数的第二个形参. 且它的 length 属性和 [[PrimitiveValue]] 属性并没有被打印出来, 为此我们来查看下length的内部属性.

$.each(o, function(i, ele){
 if(this=="linda")//我们还是随机选取第一个属性(这还是随机吗?)
 console.log(Object.getOwnPropertyDescriptor(this, 'length'));
});
//Object {value: 5, writable: false, enumerable: false, configurable: false}

可见, this对象的length属性的 enumerable 属性被设置成了false, 这表示该对象不能被列举或遍历, 同时还不能被配置(configurable: false) , 也不能被赋值(writable: false) .

此时, 前面遍历 o 对象时,它的 length 属性没有被打印出来的疑问似乎有解了. 让我们来看看 o.length 的内部属性吧.

console.log(Object.getOwnPropertyDescriptor(o, 'length'));
//Object {value: 3, writable: true, enumerable: true, configurable: true}

o.length 值为3, 可赋值, 可列举, 可配置. 这可不对, 刚刚不是说 enumerable 属性被设置成了false 才不会被遍历吗. 现在该值为 true, 并且还不可遍历. 这不合常理, 自然该有别的原因. 我们接着往下看.

var o = {0:"linda",1:"style",2:"nick",length:1}; // 试着改变length的值
$.each(o, function(i, ele){//再遍历一次
 console.log(i,ele);
});
//0 "linda"

var o = {0:"linda",1:"style",2:"nick",length:5}; // 坚持改变length的值
$.each(o, function(i, ele){//再遍历一次
 console.log(i,ele);
});
// 0 linda
// 1 style
// 2 nick
// length 5

var o = {0:"linda",1:"style",2:"nick"}; // 试试去掉length属性
$.each(o, function(i, ele){//再遍历一次
 console.log(i,ele);
});
// 0 linda
// 1 style
// 2 nick

现象明了, 结合jquery源码, 当对象中存在length属性时, $.each 内部使用for循环去遍历对象, 否则它将使用for in循环去遍历, 因此$.each遍历对象遵循如下规律:

  • 如果对象中存在 length 属性, 遍历深度以length属性为准, 即length多大, 遍历多少个元素.
  • 如果对象中不存在 length 属性, 遍历深度以实际内部属性个数为准.

不仅如此, $.each的具体使用过程中还有以下几点需要注意:

  • 使用 return 或者 return true 为跳过一个元素,继续执行后面的循环;
  • 使用 return false 为终止循环的执行, 这是因为在 jquery.each 中, 若返回值指定为false, 才跳出循环, 如果感兴趣请翻看 jquery.each 源码;
  • 无法使用 break 与 continue 来跳过循环.

$(selecter).each

语法: $(selecter|array|o).each(function(i, ele){}) 支持数组和对象, 该方法基本上与$.each方法相同.

$('div').each(function(i,ele){
 console.log(this,i,this == ele);
});
//dom... 0 dom.... true
$(array).each(function(i,ele){//处理数组
 if(this == "领袖")
 console.log(this,i,this == ele);
});
//String {0: "领", 1: "袖", length: 2, [[PrimitiveValue]]: "领袖"} 2 true
$(o).each(function(i,ele){//处理对象
 if(this == "nick")
 console.log(this,i,this == ele);
});
//String {0: "n", 1: "i", 2: "c", 3: "k", length: 4, [[PrimitiveValue]]: "nick"} 2 true

dom表示div元素, 由于this恒等ele, 说明this也表示div元素, 所以this并不是jquery对象, 而是普通的DOM对象(可以在this上随意使用DOM方法). 使用$(selecter).each方法,请注意以下几点:

  • i: 即序列值 ele: 表示当前被遍历的DOM元素
  • this 表示当前被遍历的DOM元素,不能调用jQuery方法, 如需调用jquery方法需要用$符号包裹.如, $(this)

map

Array.prototype.map,该方法只支持数组

语法: array.map(callback[,thisArg]) map方法使用其提供函数的每次返回结果生成一个新的数组.

var array = [1, 4, 9];
var roots = array.map(Math.sqrt);//map包裹方法名
// roots is now [1, 2, 3], array is still [1, 4, 9]
var array = [1, 4, 9];
var doubles = array.map(function(num) {//map包裹方法实体
 return num * 2;
});
// doubles is now [2, 8, 18]. array is still [1, 4, 9]

实际上,由于map方法被设计成支持 [鸭式辨型][] , 该方法也可以用来处理形似数组的对象, 例如 NodeList.

var elems = document.querySelectorAll('select option:checked');
var values = Array.prototype.map.call(elems, function(obj) {
 return obj.value;
});

甚至还可以用来处理字符串, 如下:

var map = Array.prototype.map;
var array = map.call('Hello 中国', function(x) { 
 return x.charCodeAt(0);
});
console.log(array);
//[72, 101, 108, 108, 111, 32, 20013, 22269]

map处理字符串的方式多种多样, 例如 反转等.

var str = '12345';
var output = Array.prototype.map.call(str, function(x) {
 return x;
}).reverse().join('');
console.log(output);//54321

例如 将字符串数组转换为数字数组, 只需一条语句, 如下:

console.log(['1', '2', '3'].map(Number));//[1,2,3]

目前map方法被大部分浏览器支持, 除了IE 6,7,8.

every

Array.prototype.every, 该方法同上述map方法也只支持数组

语法: arr.every(callback[, thisArg]) every 方法用于检验数组中的每一项是否符合某个条件, 若符合则放回true, 反之则返回false.

function isBigEnough(element, index, array) {
 return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough); // false
[12, 54, 18, 130, 44].every(isBigEnough); // true

该方法还有简写方式, 如下:

[12, 5, 8, 130, 44].every(elem => elem >= 10); // false
[12, 54, 18, 130, 44].every(elem => elem >= 10); // true

以上, 遍历数组和对象的8种方法简单的介绍完, 小结如下:

  • for in , $.each , $().each 既支持对象也支持数组遍历;
  • for , do/while , forEach 只支持数组;
  • Array.prototype.map, Array.prototype.every 只支持数组和形似数组的对象;
  • forEach不能退出循环,只能通过return来进入到下一个元素的遍历中(相当于for循环的continue), 且在IE没有实现该方法;
  • $.each和$().each循环只能通过return false 来退出循环, 使用return 或 return true 将跳过一个元素, 继续执行后面的循环.

测试各方法效率

下面我们来测试下上述方法的效率.

注: array数组默认为空, 依次赋值数组长度为1 000 000, 10 000 000, 100 000 000, 分别在 Chrome, Firefox, Safari 浏览器上进行两轮测试, 取测试时间平均值作为比较对象, 时间单位为ms. 如下是测试代码:

var array = [],
 length = array.length = 10000000;//(一千万)
//for(var i=0;i<length;i++){
// array[i] = 'louis';
//}
console.log(array[0]);
//-------------------------for
var t1 = +new Date();
for(var i=0;i<length;i++){
}
var t2 = +new Date();
console.log('for:' + (t2-t1));

//-------------------------do/while
var t1 = +new Date();
var i = 0;
do {
 i++;
} while(i<length);
var t2 = +new Date();
console.log('do while:' + (t2-t1));

//-------------------------forEach
var t1 = +new Date();
array.forEach(function(item){
});
var t2 = +new Date();
console.log('forEach:' + (t2-t1));

//-------------------------for in
var t1 = +new Date();
for(var item in array){
}
var t2 = +new Date();
console.log('for in:' + (t2-t1));

//------------------------- $.each
var t1 = +new Date();
$.each(array, function(i, ele){
});
var t2 = +new Date();
console.log('$.each:' + (t2-t1));

//-------------------------$().each
var t1 = +new Date();
$(array).each(function(i,ele){
});
var t2 = +new Date();
console.log('$(ele).each:' + (t2-t1));

//-------------------------map
var t1 = +new Date();
array.map(function(num){
});
var t2 = +new Date();
console.log('map:' + (t2-t1));

//-------------------------every
var t1 = +new Date();
array.every(function(e,i,arr){
});
var t2 = +new Date();
console.log('every:' + (t2-t1));

测试机器正常运行 IDE, 编辑器, 浏览器, qq, 微信等常用应用, 系统空闲. 硬件设备如下:

  • 操作系统: OSX EI Capitan 版本 10.11.5
  • MacBook Pro(13 英寸,2015 年初期)
  • 处理器: 2.7 GHz Intel Core i5
  • 内存: 8 GB 1867 MHz DDR3

以上多轮测试结果汇总如下三张表(单位:ms):

数组长度为10^6

数组长度为10^6 chrome 52.0.2743.116 (64-bit) Firefox Developer Edition 49.0a2 (2016-08-01) Safari 9.1.1 (11601.6.17)
for (16+19)/2 = 17.5 (6+7)/2 = 6.5 (6+7)/2 = 6.5
do while (24+17)/2 = 20.5 (7+5)/2 = 6 (5+5)/2 = 5
for in (19+28)/2 = 23.5 (0+0)/2 = 0 (0+0)/2 = 0
forEach (41+28)/2 = 34.5 (4+4)/2 = 4 (31+29)/2 = 30
map (26+32)/2 = 28 (4+4)/2 = 4 (32+26)/2 = 28
every (22+24)/2 = 23 (4+5)/2 = 4.5 (41+45)/2 = 43
$.each (29+27)/2 = 28 (306+311)/2 = 308.5 (111+97)/2 = 104
$(e).each (94+98)/2 = 96 (484+488)/2 = 486 (79+64)/2 = 71.5

数组长度为10^7

数组长度为10^7 chrome 52.0.2743.116 (64-bit) Firefox Developer Edition 49.0a2 (2016-08-01) Safari 9.1.1 (11601.6.17)
for (164+161)/2 = 162.5 (26+30)/2 = 28 (30+31)/2 = 30.5
do while (163+157)/2 = 160 (27+25)/2 = 26 (28+27)/2 = 27.5
for in (78+86)/2 = 82 (0+0)/2 = 0 (0+0)/2 = 0
forEach (211+205)/2 = 208 (31+30)/2 = 30.5 (291+289)/2 = 290
map (349+282)/2 = 315.5 (24+22)/2 = 23 (259+260)/2 = 259.5
every (221+219)/2 = 220 (24+24)/2 = 24 (251+257)/2 = 254
$.each (210+215)/2 = 212.5 (2868+2789)/2 = 2828.5 (699+724)/2 = 711.5
$(e).each (730+669)/2 = 699.5 (4674+4722)/2 = 4698 (523+546)/2 = 534.5

数组长度为10^8

数组长度为10^8 chrome 52.0.2743.116 (64-bit) Firefox Developer Edition 49.0a2 (2016-08-01) Safari 9.1.1 (11601.6.17)
for (1486+1583)/2 = 1534.5 (222+238)/2 = 230 (261+251)/2 = 256
do while (1548+1608)/2 = 1578 (236+247)/2 = 241.5 (272+265)/2 = 268.5
for in (0+0)/2 = 0 (0+0)/2 = 0 (0+0)/2 = 0
forEach (25838+22307)/2 = 24072.5 (212+209)/2 = 210.5 (2565+2568)/2 = 2566.5
map (23795+22787)/2 = 23291 (215+206)/2 = 210.5 (2556+2573)/2 = 2564.5
every (22393+22378)/2 = 22385.5 (212+215)/2 = 213.5 (2550+2548)/2 = 2549
$.each (14523+14776)/2 = 14649.5 (28007+27698)/2 = 27852.5 (7109+7156)/2 = 7132.5
$(e).each chrome 奔溃了... (49352+49530)/2 = 49441 (5505+4616)/2 = 5060.5

综上, 我们发现for in 循环的性能不稳定, 猜测它可能没有进入循环. 因此将数组各元素进行如下赋值. 重新进行如下两轮测试.

var array = [],
 length = array.length = 1000000;
for(var i=0;i<length;i++){
 array[i] = 'louis';
}

数组赋值后, 数组长度为10^6

数组长度为10^6 chrome 52.0.2743.116 (64-bit) Firefox Developer Edition 49.0a2 (2016-08-01) Safari 9.1.1 (11601.6.17)
for (21+22)/2 = 21.5 (8+10)/2 = 9 (6+5)/2 = 5.5
do while (22+19)/2 = 20.5 (6+6)/2 = 6 (6+5)/2 = 5.5
for in (178+184)/2 = 181 (318+268)/2 = 293 (413+464)/2 = 438.5
forEach (42+45)/2 = 43.5 (4+4)/2 = 4 (21+24)/2 = 22.5
map (137+153)/2 = 145 (9+8)/2 = 8.5 (38+43)/2 = 40.5
every (0+0)/2 = 0 (0+0)/2 = 0 (0+0)/2 = 0
$.each (85+84)/2 = 84.5 (15+19)/2 = 17 (37+25)/2 = 31
$(e).each (81+83)/2 = 82 (34+31)/2 = 32.5 (37+46)/2 = 41.5

数组赋值后, 数组长度为10^7

数组长度为10^7 chrome 52.0.2743.116 (64-bit) Firefox Developer Edition 49.0a2 (2016-08-01) Safari 9.1.1 (11601.6.17)
for (171+157)/2 = 164 (27+26)/2 = 26.5 (26+28)/2 = 27
do while (168+158)/2 = 163 (27+27)/2 = 27 (28+29)/2 = 28.5
for in (1469+1715)/2 = 1592 (2922+3123)/2 = 3022.5 (5755+5742)/2 = 5748.5
forEach (347+329)/2 = 338 (32+36)/2 = 34 (171+174)/2 = 172.5
map (1320+1335)/2 = 1327.5 (147+137)/2 = 142 (448+469)/2 = 458.5
every (0+0)/2 = 0 (0+0)/2 = 0 (0+0)/2 = 0
$.each (438+441)/2 = 439.5 (142+141)/2 = 141.5 (254+248)/2 = 251
$(e).each (876+935)/2 = 905.5 (315+328)/2 = 321.5 (450+402)/2 = 426

可见, 对数组进行赋值后, 代码运行基本稳定.(every还不清楚为什么执行时间为0.欢迎大神告知原因.)

分析总结

通过以上 30 次运行测试(实际上为了得到比较稳定的数据, 摈弃了许多异常的测试数据), 我们发现在数组长度为10^6, 10^7, 10^8 时, 代码运行基本稳定. 各方法运行需要的时间大致排序如下:

for ~= do while < forEach ~= map ~= every < $.each < $(e).each < for in

根据统计数据, 可得这8个方法的运行速度大致排序为:

  1. for 与 do while
  2. forEach map every (这3个不相上下,可认为运行速度差不多)
  3. $.each
  4. $(e).each
  5. for in

我们翻看jquery代码就会知道, $.each方法内部通过调用for循环来实现, 而$().each是先用jquery包裹数组对象, 然后再调用for循环, 因此后者效率略低于前者.

综上, 最佳遍历选手是 for/do while循环, 推荐大家优先考虑使用它. ( Firefox浏览器由于对forEach循环做了底层优化, 效率接近native,不在我们考虑范围内 ).

基于测试结果的两点思考

从测试数据上猜测, Firefox 与 Safari 似乎对于 for, do while 等都进行了底层优化. 循环执行效率明显优于Chrome.

每次浏览器执行到 for in 循环处, 便会出现卡顿, 猜测浏览器可能正在预加载循环所需资源(后续我将专门分析此处).

想要进一步优化循环效率, 推荐您阅读下篇 《JS作用域链及闭包》.

声明: 本文所有数据均为单机测试, 难免存在误差, 如果发现本文测试数据不对之处, 欢迎批评斧正.

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
js 连接数据库如何操作数据库中的数据
Nov 23 Javascript
Javascript中判断变量是数组还是对象(array还是object)
Aug 14 Javascript
js判断移动端是否安装某款app的多种方法
Dec 18 Javascript
jquery实现折叠菜单效果【推荐】
Mar 08 Javascript
Bootstrap Tree View简单而优雅的树结构组件实例解析
Jun 15 Javascript
微信小程序的分类页面制作
Jun 27 Javascript
微信小程序实现tab切换效果
Nov 21 Javascript
React 使用Hooks简化受控组件的状态绑定
Mar 18 Javascript
微信小程序 腾讯地图显示偏差问题解决
Jul 27 Javascript
JavaScript中使用Spread运算符的八种方法总结
Jun 18 Javascript
vue中的.$mount('#app')手动挂载操作
Sep 02 Javascript
js+canvas实现画板功能
Sep 13 Javascript
详谈js使用in和hasOwnProperty获取对象属性的区别
Apr 25 #Javascript
3分钟掌握常用的JS操作JSON方法总结
Apr 25 #Javascript
vue.js父组件使用外部对象的方法示例
Apr 25 #Javascript
ES6学习笔记之正则表达式和字符串正则方法分析
Apr 25 #Javascript
Javascript中类式继承和原型式继承的实现方法和区别之处
Apr 25 #Javascript
整理一些最近经常遇到的前端面试题
Apr 25 #Javascript
Vue.js 2.0学习教程之从基础到组件详解
Apr 24 #Javascript
You might like
山进SANGEAN ATS-909X电路分析
2021/03/02 无线电
php引用计数器进行垃圾收集机制介绍
2012/09/19 PHP
一个简单的php加密解密函数(动态加密)
2013/06/19 PHP
PHP chunk_split()函数讲解
2019/02/12 PHP
检测是否已安装 .NET Framework 3.5的js脚本
2009/02/14 Javascript
Mootools 1.2教程(2) DOM选择器
2009/09/14 Javascript
匹配任意字符的正则表达式写法
2010/04/29 Javascript
前端开发必须知道的JS之原型和继承
2010/07/06 Javascript
JSON字符串转JSON对象
2015/07/31 Javascript
BootStrap Validator使用注意事项(必看篇)
2016/09/28 Javascript
javascript匀速动画和缓冲动画详解
2016/10/20 Javascript
JavaScript自定义文本框光标
2017/03/05 Javascript
基于JavaScript实现焦点图轮播效果
2017/03/27 Javascript
JS设计模式之状态模式概念与用法分析
2018/02/05 Javascript
解决ng-repeat产生的ng-model中取不到值的问题
2018/10/02 Javascript
解决echarts echarts数据动态更新和dataZoom被重置问题
2020/07/20 Javascript
[01:39]2014DOTA2国际邀请赛 Newbee经理CU专访队伍火力全开
2014/07/15 DOTA
Python线程的两种编程方式
2015/04/14 Python
Python实现好友全头像的拼接实例(推荐)
2017/06/24 Python
深入理解Python分布式爬虫原理
2017/11/23 Python
pygame游戏之旅 按钮上添加文字的方法
2018/11/21 Python
在Python中append以及extend返回None的例子
2019/07/20 Python
Python3批量移动指定文件到指定文件夹方法示例
2019/09/02 Python
PHP如何去执行一个SQL语句
2016/03/05 面试题
妇产医师自荐信
2014/01/29 职场文书
情况说明书格式范文
2014/05/06 职场文书
工作建议书范文
2014/05/13 职场文书
篮球比赛策划方案
2014/06/05 职场文书
高中课前三分钟演讲稿
2014/09/13 职场文书
开展党的群众路线教育实践活动情况汇报
2014/11/05 职场文书
行政经理岗位职责
2015/04/15 职场文书
搞笑婚庆主持词
2015/06/29 职场文书
pygame面向对象的飞行小鸟实现(Flappy bird)
2021/04/01 Python
Java方法重载和方法重写的区别到底在哪?
2021/06/11 Java/Android
JAVA API 实用类 String详解
2021/10/05 Java/Android
Apache SkyWalking 监控 MySQL Server 实战解析
2022/09/23 Servers