最近在优化项目,发现字符串拼接的堆内存非常大,而且非常频繁。

大概原因如下

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(一百零四)相关推荐

  1. gc方法写法_云风的 BLOG

    December 17, 2020 内存的惰性初始化 这两天和同事讨论一个问题,我写了个小玩意. 事情起因是,我们公司上海的工作室的一个 MMO 项目做服务器压力测试.谈及优化,涉及到服务器中使用的 ...

  2. gc方法写法_清标的内容、技巧及书写方式

    概述: 众所周知,不平衡报价就是一场对清单报价的价格战,它利用对清单报价的策略性调整来实现利益的最大化,而清标恰恰像是一把封住了不平衡报价咽喉的利剑.在这场价格战中,招标人利用清标来分析投标报价是否响 ...

  3. oracle listagg方法,Oracle实现字符串拼接和分离功能的方法(LISTAGG函数),oraclelistagg...

    Oracle实现字符串拼接和分离功能的方法(LISTAGG函数),oraclelistagg 字符串拼接(String Aggregation Techniques)是数据处理时经常需要用到一个技术, ...

  4. Go语言中的字符串拼接方法介绍

    本文介绍Go语言中的string类型.strings包和bytes.Buffer类型,介绍几种字符串拼接方法. 目录 string类型 strings包 strings.Builder类型 strin ...

  5. 最优雅的Java字符串拼接是哪种方式?

    title shortTitle category tag description head 最优雅的Java字符串拼接是哪种方式? Java字符串拼接 Java核心 数组&字符串 Java程 ...

  6. golang字符串拼接方式

    字符串拼接是字符的常见操作.在golang中,遇见了字符串拼接.作为一个长期的C程序员,我第一反应是:字符串拼接函数strcat,但发现golang并无字符串拼接函数. 我想起了最简单的方法,通过+操 ...

  7. js字符串拼接----小知识

    HTML结构: <body> <button οnclick="myClick('tom','18')">按钮1</button> <bu ...

  8. Guava中基础工具类Joiner的使用字符串拼接方法 joiner.on

    Guava 中有一些基础的工具类,如下所列: 1,Joiner 类:根据给定的分隔符把字符串连接到一起.MapJoiner 执行相同的操作,但是针对 Map 的 key 和 value. 2,Spli ...

  9. python拼接字符串的方法,Python2中文字符串拼接

    python 文本字符串接 python中有很多字符串连接方式,今天在写代码,顺便总结一下:最原始的字符串连接方式:str1 + str2python 新字符串连接语法:str1, str2奇怪的字符 ...

  10. python 字符串拼接_Python中拼接字符串的方法 | 萧小寒

    摘要 在编程语言中,几乎每种语言都有关于字符串的操作方法或函数.原因很简单,字符串做为编程语言中不可或缺的数据类型,有着不可以替代的重要性.不同的是,每种编程语言对于字符串的处理方式都有自己的特色.这 ...

最新文章

  1. Python,Opencv cv2.Canny()边缘检测
  2. Log4j格式化符号
  3. python安装教程win8-python 2.7在win8.1上安装的方法
  4. [转帖]IP地址、子网掩码、网络号、主机号、网络地址、主机地址以及ip段/数字-如192.168.0.1/24是什么意思?...
  5. 使用 C# 9 的records作为强类型ID - 初次使用
  6. python 编程模型
  7. Net 4.0并行库实用性演练
  8. akka案例:统计单词个数
  9. linux ssh原理
  10. 天坑-安装salt-api安装的正确姿势
  11. 贷款利息及公积金知识点
  12. 通过名称识别和处理弹出窗口
  13. atitit.atitit.hb many2one relate hibernate 多对一关联配置..
  14. A visval git reference实践记录
  15. python使用Tesseract,pytesseract图片处理识别(1)
  16. 【python】python列表去重的5种常见方法实例
  17. HDFS的Shell操作和API操作
  18. 大话人机混合智能中深度情境意识
  19. 【一文读懂】Spring Bean生命周期详解
  20. 必读干货丨这项技能玩不转,职场终生当菜鸟

热门文章

  1. 大奖赛现场统分。已知某大奖赛有n个选手参赛,m(m2)个评委为参赛选手评分(最高10分,最低0分)。统分规则为:在每个选手的m个得分中,去掉一个最高分和一个最低平 每日一题--2020049--
  2. 小水智能-智能楼宇智慧建筑3D可视化系统,实现了数据的整合
  3. 经纬财富:徐州炒白银需要注意哪些技术指标
  4. c语言的矩阵相乘程序,C语言实现两个矩阵相乘
  5. Java轻量级缓存Ehcache与SpringBoot整合
  6. html怎么设置字体的背景颜色,html怎样设置字体的背景颜色?
  7. 基于LabVIEW的WIFI通信人机交互界面设计
  8. 苹果电脑修改MAC地址方法
  9. 发送短信工具类(亿美短信平台接口)
  10. mac转换pin计算机,MAC对应PIN码表-2012.3.4整理