gc方法写法_Unity3D研究院之字符串拼接0GC(一百零四)
最近在优化项目,发现字符串拼接的堆内存非常大,而且非常频繁。
大概原因如下
1.字符串就是char[] 长短发生变化必然要重新分配长度,以及拷贝之前的部分,产生GC
2.字符串+=进行拼接会产生装箱,生成GC
3.Append传入值类型数据,它会调用ToString方法,需要new string 然后把char[]拷进去,又会产生堆内存。
4.new StringBuidler 产生堆内存
5.StringBuidler.ToString 产生堆内存
5.string.format 它内部使用的就是StringBuilder,但是 1)new StringBuilder 2)Append 3)ToString都会产生堆内存。
所以我们需要优化的是
1.
int a = 100;
string b = a + “”;
禁止上述这种写法,会额外产生一次装箱操作。所以要采用如下写法。
string b = a.ToString();
2.少用或者不用string.format,提前缓存共享StringBuilder对象。避免用时候产生堆内存。
3.网上找到一个算法,挺有意思。提前定义好 0 – 9 之间的字符数组,如果传入值类型数据,从高位依次除以10算出每一位的数,然后再去预先声明的0-9字符数组中找对应的char,这样就就不会产生装箱GC了。
4.如果可以提前确定字符串的长度,例如,界面上显示玩家的等级, 我们可以确定level不可能超过3位数也就是100,那么可以提前声明一个长度为3的StringBuilder,通过反射取出来内部的_str,这样就可以避免最后的ToString产生的堆内存了。由于_str内容可能无法擦掉之前的所以需要调用GarbageFreeClear();方法。
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
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
usingSystem.Text;
usingUnityEngine;
usingUnityEngine.Profiling;
publicclassNewBehaviourScript:MonoBehaviour{
StringBuilderm_StringBuilder=newStringBuilder(100);
stringm_StringBuildertxt=string.Empty;
privatevoidStart()
{
m_StringBuildertxt=m_StringBuilder.GetGarbageFreeString();
}
privatevoidUpdate()
{
inti=Random.Range(0,100);
floatf=Random.Range(0.01f,200.01f);
floatd=Random.Range(0.01f,200.01f);
strings="yusong: "+i;
Profiler.BeginSample("string.format");
strings1=string.Format("{0}{1}{2}{3}",i,f,d,s);
Profiler.EndSample();
Profiler.BeginSample("+=");
strings2=i+""+f+""+d+""+s;
Profiler.EndSample();
Profiler.BeginSample("StringBuilder");
strings3=newStringBuilder().Append(i).Append(f).Append(d).Append(s).ToString();
Profiler.EndSample();
Profiler.BeginSample("StrExt.Format");
strings4=StrExt.Format("{0}{1:0.00}{2:0.00}{3}",i,f,d,s);
Profiler.EndSample();
Profiler.BeginSample("EmptyGC");
m_StringBuilder.GarbageFreeClear();
m_StringBuilder.ConcatFormat("{0}{1:0.00}{2:0.00}{3}",i,f,d,s);
strings5=m_StringBuildertxt;
Profiler.EndSample();
Debug.LogFormat("s1 : {0}",s1);
Debug.LogFormat("s2 : {0}",s2);
Debug.LogFormat("s3 : {0}",s3);
Debug.LogFormat("s4 : {0}",s4);
Debug.LogFormat("s5 : {0}",s5);
}
}
2.无装箱版本
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
usingSystem.Text;
usingUnityEngine;
usingUnityEngine.Profiling;
publicclassNewBehaviourScript:MonoBehaviour{
StringBuilderm_StringBuilder=newStringBuilder(100);
stringm_StringBuildertxt=string.Empty;
privatevoidStart()
{
m_StringBuildertxt=m_StringBuilder.GetGarbageFreeString();
}
privatevoidUpdate()
{
inti=Random.Range(0,100);
floatf=Random.Range(0.01f,200.01f);
floatd=Random.Range(0.01f,200.01f);
strings="yusong: "+i.ToString();
Profiler.BeginSample("string.format");
strings1=string.Format("{0}{1}{2}{3}",i.ToString(),f.ToString(),d.ToString(),s);
Profiler.EndSample();
Profiler.BeginSample("+=");
strings2=i.ToString()+f.ToString()+d.ToString()+s;
Profiler.EndSample();
Profiler.BeginSample("StringBuilder");
strings3=newStringBuilder().Append(i).Append(f).Append(d).Append(s).ToString();
Profiler.EndSample();
Profiler.BeginSample("StrExt.Format");
strings4=StrExt.Format("{0}{1:0.00}{2:0.00}{3}",i,f,d,s);
Profiler.EndSample();
Profiler.BeginSample("EmptyGC");
m_StringBuilder.GarbageFreeClear();
m_StringBuilder.ConcatFormat("{0}{1:0.00}{2:0.00}{3}",i,f,d,s);
strings5=m_StringBuildertxt;
Profiler.EndSample();
Debug.LogFormat("s1 : {0}",s1);
Debug.LogFormat("s2 : {0}",s2);
Debug.LogFormat("s3 : {0}",s3);
Debug.LogFormat("s4 : {0}",s4);
Debug.LogFormat("s5 : {0}",s5);
}
}
通通过这两张图大家可以看出来装箱和不装箱对GC有多大的影响了,还有最后的无GC版本。代码参考了下面的这两篇文章。
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
usingSystem;
usingSystem.Text;
usingUnityEngine;
publicstaticclassStringBuilderExtensions
{
// These digits are here in a static array to support hex with simple, easily-understandable code.
// Since A-Z don't sit next to 0-9 in the ascii table.
privatestaticreadonlychar[]ms_digits=newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
privatestaticreadonlyuintms_default_decimal_places=5;//< Matches standard .NET formatting dp's
privatestaticreadonlycharms_default_pad_char='0';
//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Any base value allowed.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,uintuint_val,uintpad_amount,charpad_char,uintbase_val)
{
Debug.Assert(pad_amount>=0);
Debug.Assert(base_val>0&&base_val<=16);
// Calculate length of integer when written out
uintlength=0;
uintlength_calc=uint_val;
do
{
length_calc/=base_val;
length++;
}
while(length_calc>0);
// Pad out space for writing.
string_builder.Append(pad_char,(int)Math.Max(pad_amount,length));
intstrpos=string_builder.Length;
// We're writing backwards, one character at a time.
while(length>0)
{
strpos--;
// Lookup from static char array, to cover hex values too
string_builder[strpos]=ms_digits[uint_val%base_val];
uint_val/=base_val;
length--;
}
returnstring_builder;
}
//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume no padding and base ten.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,uintuint_val)
{
string_builder.Concat(uint_val,0,ms_default_pad_char,10);
returnstring_builder;
}
//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume base ten.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,uintuint_val,uintpad_amount)
{
string_builder.Concat(uint_val,pad_amount,ms_default_pad_char,10);
returnstring_builder;
}
//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume base ten.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,uintuint_val,uintpad_amount,charpad_char)
{
string_builder.Concat(uint_val,pad_amount,pad_char,10);
returnstring_builder;
}
//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Any base value allowed.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,intint_val,uintpad_amount,charpad_char,uintbase_val)
{
Debug.Assert(pad_amount>=0);
Debug.Assert(base_val>0&&base_val<=16);
// Deal with negative numbers
if(int_val<0)
{
string_builder.Append('-');
uintuint_val=uint.MaxValue-((uint)int_val)+1;//< This is to deal with Int32.MinValue
string_builder.Concat(uint_val,pad_amount,pad_char,base_val);
}
else
{
string_builder.Concat((uint)int_val,pad_amount,pad_char,base_val);
}
returnstring_builder;
}
//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume no padding and base ten.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,intint_val)
{
string_builder.Concat(int_val,0,ms_default_pad_char,10);
returnstring_builder;
}
//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume base ten.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,intint_val,uintpad_amount)
{
string_builder.Concat(int_val,pad_amount,ms_default_pad_char,10);
returnstring_builder;
}
//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume base ten.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,intint_val,uintpad_amount,charpad_char)
{
string_builder.Concat(int_val,pad_amount,pad_char,10);
returnstring_builder;
}
//! Convert a given float value to a string and concatenate onto the stringbuilder
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,floatfloat_val,uintdecimal_places,uintpad_amount,charpad_char)
{
Debug.Assert(pad_amount>=0);
if(decimal_places==0)
{
// No decimal places, just round up and print it as an int
// Agh, Math.Floor() just works on doubles/decimals. Don't want to cast! Let's do this the old-fashioned way.
intint_val;
if(float_val>=0.0f)
{
// Round up
int_val=(int)(float_val+0.5f);
}
else
{
// Round down for negative numbers
int_val=(int)(float_val-0.5f);
}
string_builder.Concat(int_val,pad_amount,pad_char,10);
}
else
{
intint_part=(int)float_val;
// First part is easy, just cast to an integer
string_builder.Concat(int_part,pad_amount,pad_char,10);
// Decimal point
string_builder.Append('.');
// Work out remainder we need to print after the d.p.
floatremainder=Math.Abs(float_val-int_part);
// Multiply up to become an int that we can print
do
{
remainder *=10;
decimal_places--;
}
while(decimal_places>0);
// Round up. It's guaranteed to be a positive number, so no extra work required here.
remainder+=0.5f;
// All done, print that as an int!
string_builder.Concat((uint)remainder,0,'0',10);
}
returnstring_builder;
}
//! Convert a given float value to a string and concatenate onto the stringbuilder. Assumes five decimal places, and no padding.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,floatfloat_val)
{
string_builder.Concat(float_val,ms_default_decimal_places,0,ms_default_pad_char);
returnstring_builder;
}
//! Convert a given float value to a string and concatenate onto the stringbuilder. Assumes no padding.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,floatfloat_val,uintdecimal_places)
{
string_builder.Concat(float_val,decimal_places,0,ms_default_pad_char);
returnstring_builder;
}
//! Convert a given float value to a string and concatenate onto the stringbuilder.
publicstaticStringBuilderConcat(thisStringBuilderstring_builder,floatfloat_val,uintdecimal_places,uintpad_amount)
{
string_builder.Concat(float_val,decimal_places,pad_amount,ms_default_pad_char);
returnstring_builder;
}
//! Concatenate a formatted string with arguments
publicstaticStringBuilderConcatFormat(thisStringBuilderstring_builder,Stringformat_string,Aarg1)
whereA:IConvertible
{
returnstring_builder.ConcatFormat(format_string,arg1,0,0,0);
}
//! Concatenate a formatted string with arguments
publicstaticStringBuilderConcatFormat(thisStringBuilderstring_builder,Stringformat_string,Aarg1,Barg2)
whereA:IConvertible
whereB:IConvertible
{
returnstring_builder.ConcatFormat(format_string,arg1,arg2,0,0);
}
//! Concatenate a formatted string with arguments
publicstaticStringBuilderConcatFormat(thisStringBuilderstring_builder,Stringformat_string,Aarg1,Barg2,Carg3)
whereA:IConvertible
whereB:IConvertible
whereC:IConvertible
{
returnstring_builder.ConcatFormat(format_string,arg1,arg2,arg3,0);
}
//! Concatenate a formatted string with arguments
publicstaticStringBuilderConcatFormat(thisStringBuilderstring_builder,Stringformat_string,Aarg1,Barg2,Carg3,Darg4)
whereA:IConvertible
whereB:IConvertible
whereC:IConvertible
whereD:IConvertible
{
intverbatim_range_start=0;
for(intindex=0;index
{
if(format_string[index]=='{')
{
// Formatting bit now, so make sure the last block of the string is written out verbatim.
if(verbatim_range_start
{
// Write out unformatted string portion
string_builder.Append(format_string,verbatim_range_start,index-verbatim_range_start);
}
uintbase_value=10;
uintpadding=0;
uintdecimal_places=5;// Default decimal places in .NET libs
index++;
charformat_char=format_string[index];
if(format_char=='{')
{
string_builder.Append('{');
index++;
}
else
{
index++;
if(format_string[index]==':')
{
// Extra formatting. This is a crude first pass proof-of-concept. It's not meant to cover
// comprehensively what the .NET standard library Format() can do.
index++;
// Deal with padding
while(format_string[index]=='0')
{
index++;
padding++;
}
if(format_string[index]=='X')
{
index++;
// Print in hex
base_value=16;
// Specify amount of padding ( "{0:X8}" for example pads hex to eight characters
if((format_string[index]>='0')&&(format_string[index]<='9'))
{
padding=(uint)(format_string[index]-'0');
index++;
}
}
elseif(format_string[index]=='.')
{
index++;
// Specify number of decimal places
decimal_places=0;
while(format_string[index]=='0')
{
index++;
decimal_places++;
}
}
}
// Scan through to end bracket
while(format_string[index]!='}')
{
index++;
}
// Have any extended settings now, so just print out the particular argument they wanted
switch(format_char)
{
case'0':string_builder.ConcatFormatValue(arg1,padding,base_value,decimal_places);break;
case'1':string_builder.ConcatFormatValue(arg2,padding,base_value,decimal_places);break;
case'2':string_builder.ConcatFormatValue(arg3,padding,base_value,decimal_places);break;
case'3':string_builder.ConcatFormatValue(arg4,padding,base_value,decimal_places);break;
default:Debug.Assert(false,"Invalid parameter index");break;
}
}
// Update the verbatim range, start of a new section now
verbatim_range_start=(index+1);
}
}
// Anything verbatim to write out?
if(verbatim_range_start
{
// Write out unformatted string portion
string_builder.Append(format_string,verbatim_range_start,format_string.Length-verbatim_range_start);
}
returnstring_builder;
}
//! The worker method. This does a garbage-free conversion of a generic type, and uses the garbage-free Concat() to add to the stringbuilder
privatestaticvoidConcatFormatValue(thisStringBuilderstring_builder,Targ,uintpadding,uintbase_value,uintdecimal_places)whereT:IConvertible
{
switch(arg.GetTypeCode())
{
caseSystem.TypeCode.UInt32:
{
string_builder.Concat(arg.ToUInt32(System.Globalization.NumberFormatInfo.CurrentInfo),padding,'0',base_value);
break;
}
caseSystem.TypeCode.Int32:
{
string_builder.Concat(arg.ToInt32(System.Globalization.NumberFormatInfo.CurrentInfo),padding,'0',base_value);
break;
}
caseSystem.TypeCode.Single:
{
string_builder.Concat(arg.ToSingle(System.Globalization.NumberFormatInfo.CurrentInfo),decimal_places,padding,'0');
break;
}
caseSystem.TypeCode.String:
{
string_builder.Append(Convert.ToString(arg));
break;
}
default:
{
Debug.Assert(false,"Unknown parameter type");
break;
}
}
}
publicstaticvoidEmpty(thisStringBuilderstring_builder)
{
string_builder.Remove(0,string_builder.Length);
}
publicstaticstringGetGarbageFreeString(thisStringBuilderstring_builder)
{
return(string)string_builder.GetType().GetField("_str",System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance).GetValue(string_builder);
}
publicstaticvoidGarbageFreeClear(thisStringBuilderstring_builder)
{
string_builder.Length=0;
string_builder.Append(' ',string_builder.Capacity);
string_builder.Length=0;
}
}
publicstaticclassStrExt
{
//只允许拼接50个字符串
privatestaticStringBuilders_StringBuilder=newStringBuilder(50,50);
publicstaticstringFormat(Stringformat_string,Aarg1)
whereA:IConvertible
{
s_StringBuilder.Empty();
returns_StringBuilder.ConcatFormat(format_string,arg1,0,0,0).ToString();
}
publicstaticstringFormat(Stringformat_string,Aarg1,Barg2)
whereA:IConvertible
whereB:IConvertible
{
s_StringBuilder.Empty();
returns_StringBuilder.ConcatFormat(format_string,arg1,arg2,0,0).ToString();
}
publicstaticstringFormat(Stringformat_string,Aarg1,Barg2,Carg3)
whereA:IConvertible
whereB:IConvertible
whereC:IConvertible
{
s_StringBuilder.Empty();
returns_StringBuilder.ConcatFormat(format_string,arg1,arg2,arg3,0).ToString();
}
publicstaticstringFormat(Stringformat_string,Aarg1,Barg2,Carg3,Darg4)
whereA:IConvertible
whereB:IConvertible
whereC:IConvertible
whereD:IConvertible
{
s_StringBuilder.Empty();
returns_StringBuilder.ConcatFormat(format_string,arg1,arg2,arg3,arg4).ToString();
}
}
最后编辑:2019-06-10作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!
gc方法写法_Unity3D研究院之字符串拼接0GC(一百零四)相关推荐
- gc方法写法_云风的 BLOG
December 17, 2020 内存的惰性初始化 这两天和同事讨论一个问题,我写了个小玩意. 事情起因是,我们公司上海的工作室的一个 MMO 项目做服务器压力测试.谈及优化,涉及到服务器中使用的 ...
- gc方法写法_清标的内容、技巧及书写方式
概述: 众所周知,不平衡报价就是一场对清单报价的价格战,它利用对清单报价的策略性调整来实现利益的最大化,而清标恰恰像是一把封住了不平衡报价咽喉的利剑.在这场价格战中,招标人利用清标来分析投标报价是否响 ...
- oracle listagg方法,Oracle实现字符串拼接和分离功能的方法(LISTAGG函数),oraclelistagg...
Oracle实现字符串拼接和分离功能的方法(LISTAGG函数),oraclelistagg 字符串拼接(String Aggregation Techniques)是数据处理时经常需要用到一个技术, ...
- Go语言中的字符串拼接方法介绍
本文介绍Go语言中的string类型.strings包和bytes.Buffer类型,介绍几种字符串拼接方法. 目录 string类型 strings包 strings.Builder类型 strin ...
- 最优雅的Java字符串拼接是哪种方式?
title shortTitle category tag description head 最优雅的Java字符串拼接是哪种方式? Java字符串拼接 Java核心 数组&字符串 Java程 ...
- golang字符串拼接方式
字符串拼接是字符的常见操作.在golang中,遇见了字符串拼接.作为一个长期的C程序员,我第一反应是:字符串拼接函数strcat,但发现golang并无字符串拼接函数. 我想起了最简单的方法,通过+操 ...
- js字符串拼接----小知识
HTML结构: <body> <button οnclick="myClick('tom','18')">按钮1</button> <bu ...
- Guava中基础工具类Joiner的使用字符串拼接方法 joiner.on
Guava 中有一些基础的工具类,如下所列: 1,Joiner 类:根据给定的分隔符把字符串连接到一起.MapJoiner 执行相同的操作,但是针对 Map 的 key 和 value. 2,Spli ...
- python拼接字符串的方法,Python2中文字符串拼接
python 文本字符串接 python中有很多字符串连接方式,今天在写代码,顺便总结一下:最原始的字符串连接方式:str1 + str2python 新字符串连接语法:str1, str2奇怪的字符 ...
- python 字符串拼接_Python中拼接字符串的方法 | 萧小寒
摘要 在编程语言中,几乎每种语言都有关于字符串的操作方法或函数.原因很简单,字符串做为编程语言中不可或缺的数据类型,有着不可以替代的重要性.不同的是,每种编程语言对于字符串的处理方式都有自己的特色.这 ...
最新文章
- Python,Opencv cv2.Canny()边缘检测
- Log4j格式化符号
- python安装教程win8-python 2.7在win8.1上安装的方法
- [转帖]IP地址、子网掩码、网络号、主机号、网络地址、主机地址以及ip段/数字-如192.168.0.1/24是什么意思?...
- 使用 C# 9 的records作为强类型ID - 初次使用
- python 编程模型
- Net 4.0并行库实用性演练
- akka案例:统计单词个数
- linux ssh原理
- 天坑-安装salt-api安装的正确姿势
- 贷款利息及公积金知识点
- 通过名称识别和处理弹出窗口
- atitit.atitit.hb many2one relate hibernate 多对一关联配置..
- A visval git reference实践记录
- python使用Tesseract,pytesseract图片处理识别(1)
- 【python】python列表去重的5种常见方法实例
- HDFS的Shell操作和API操作
- 大话人机混合智能中深度情境意识
- 【一文读懂】Spring Bean生命周期详解
- 必读干货丨这项技能玩不转,职场终生当菜鸟
热门文章
- 大奖赛现场统分。已知某大奖赛有n个选手参赛,m(m2)个评委为参赛选手评分(最高10分,最低0分)。统分规则为:在每个选手的m个得分中,去掉一个最高分和一个最低平 每日一题--2020049--
- 小水智能-智能楼宇智慧建筑3D可视化系统,实现了数据的整合
- 经纬财富:徐州炒白银需要注意哪些技术指标
- c语言的矩阵相乘程序,C语言实现两个矩阵相乘
- Java轻量级缓存Ehcache与SpringBoot整合
- html怎么设置字体的背景颜色,html怎样设置字体的背景颜色?
- 基于LabVIEW的WIFI通信人机交互界面设计
- 苹果电脑修改MAC地址方法
- 发送短信工具类(亿美短信平台接口)
- mac转换pin计算机,MAC对应PIN码表-2012.3.4整理