.NET性能优化-使用ValueStringBuilder拼接字符串

前言

这一次要和大家分享的一个Tips是在字符串拼接场景使用的,我们经常会遇到有很多短小的字符串需要拼接的场景,在这种场景下及其的不推荐使用String.Concat也就是使用+=运算符。 目前来说官方最推荐的方案就是使用StringBuilder来构建这些字符串,那么有什么更快内存占用更低的方式吗?那就是今天要和大家介绍的ValueStringBuilder

ValueStringBuilder

ValueStringBuilder不是一个公开的API,但是它被大量用于.NET的基础类库中,由于它是值类型的,所以它本身不会在堆上分配,不会有GC的压力。 微软提供的ValueStringBuilder有两种使用方式,一种是自己已经有了一块内存空间可供字符串构建使用。这意味着你可以使用栈空间,也可以使用堆空间甚至非托管堆的空间,这对于GC来说是非常友好的,在高并发情况下能大大降低GC压力。

// 构造函数:传入一个Span的Buffer数组
public ValueStringBuilder(Span<char> initialBuffer);// 使用方式:
// 栈空间
var vsb = new ValueStringBuilder(stackalloc char[512]);
// 普通数租
var vsb = new ValueStringBuilder(new char[512]);
// 使用非托管堆
var length = 512;
var ptr = NativeMemory.Alloc((nuint)(512 * Unsafe.SizeOf<char>()));
var span = new Span<char>(ptr, length);
var vsb = new ValueStringBuilder(span);
.....
NativeMemory.Free(ptr); // 非托管堆用完一定要Free

另外一种方式是指定一个容量,它会从默认的ArrayPoolchar对象池中获取缓冲空间,因为使用的是对象池,所以对于GC来说也是比较友好的,千万需要注意,池中的对象一定要记得归还

// 传入预计的容量
public ValueStringBuilder(int initialCapacity)  
{  // 从对象池中获取缓冲区_arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);  ......
}

那么我们就来比较一下使用+=StringBuilderValueStringBuilder这几种方式的性能吧。

// 一个简单的类
public class SomeClass  
{  public int Value1; public int Value2; public float Value3;  public double Value4; public string? Value5; public decimal Value6;  public DateTime Value7; public TimeOnly Value8; public DateOnly Value9;  public int[]? Value10;  
}
// Benchmark类
[MemoryDiagnoser]  
[HtmlExporter]  
[Orderer(SummaryOrderPolicy.FastestToSlowest)]  
public class StringBuilderBenchmark  
{  private static readonly SomeClass Data;  static StringBuilderBenchmark()  {  var baseTime = DateTime.Now;  Data = new SomeClass  {  Value1 = 100, Value2 = 200, Value3 = 333,  Value4 = 400, Value5 = string.Join('-', Enumerable.Range(0, 10000).Select(i => i.ToString())),  Value6 = 655, Value7 = baseTime.AddHours(12),  Value8 = TimeOnly.MinValue, Value9 = DateOnly.MaxValue,  Value10 = Enumerable.Range(0, 5).ToArray()  };  }// 使用我们熟悉的StringBuilder[Benchmark(Baseline = true)]  public string StringBuilder()  {  var data = Data;  var sb = new StringBuilder();  sb.Append("Value1:"); sb.Append(data.Value1);  if (data.Value2 > 10)  {  sb.Append(" ,Value2:"); sb.Append(data.Value2);  }  sb.Append(" ,Value3:"); sb.Append(data.Value3);  sb.Append(" ,Value4:"); sb.Append(data.Value4);  sb.Append(" ,Value5:"); sb.Append(data.Value5);  if (data.Value6 > 20)  {  sb.Append(" ,Value6:"); sb.AppendFormat("{0:F2}", data.Value6);  }  sb.Append(" ,Value7:"); sb.AppendFormat("{0:yyyy-MM-dd HH:mm:ss}", data.Value7);  sb.Append(" ,Value8:"); sb.AppendFormat("{0:HH:mm:ss}", data.Value8);  sb.Append(" ,Value9:"); sb.AppendFormat("{0:yyyy-MM-dd}", data.Value9);  sb.Append(" ,Value10:");  if (data.Value10 is null or {Length: 0}) return sb.ToString();  for (int i = 0; i < data.Value10.Length; i++)  {  sb.Append(data.Value10[i]);  }  return sb.ToString();  }// StringBuilder使用Capacity[Benchmark]  public string StringBuilderCapacity()  {  var data = Data;  var sb = new StringBuilder(20480);  sb.Append("Value1:"); sb.Append(data.Value1);  if (data.Value2 > 10)  {  sb.Append(" ,Value2:"); sb.Append(data.Value2);  }  sb.Append(" ,Value3:"); sb.Append(data.Value3);  sb.Append(" ,Value4:"); sb.Append(data.Value4);  sb.Append(" ,Value5:"); sb.Append(data.Value5);  if (data.Value6 > 20)  {  sb.Append(" ,Value6:"); sb.AppendFormat("{0:F2}", data.Value6);  }  sb.Append(" ,Value7:"); sb.AppendFormat("{0:yyyy-MM-dd HH:mm:ss}", data.Value7);  sb.Append(" ,Value8:"); sb.AppendFormat("{0:HH:mm:ss}", data.Value8);  sb.Append(" ,Value9:"); sb.AppendFormat("{0:yyyy-MM-dd}", data.Value9);  sb.Append(" ,Value10:");  if (data.Value10 is null or {Length: 0}) return sb.ToString();  for (int i = 0; i < data.Value10.Length; i++)  {  sb.Append(data.Value10[i]);  }  return sb.ToString();  }  // 直接使用+=拼接字符串[Benchmark]  public string StringConcat()  {  var str = "";  var data = Data;  str += ("Value1:"); str += (data.Value1);  if (data.Value2 > 10)  {  str += " ,Value2:"; str += data.Value2;  }  str += " ,Value3:"; str += (data.Value3);  str += " ,Value4:"; str += (data.Value4);  str += " ,Value5:"; str += (data.Value5);  if (data.Value6 > 20)  {  str += " ,Value6:"; str += data.Value6.ToString("F2");  }  str += " ,Value7:"; str += data.Value7.ToString("yyyy-MM-dd HH:mm:ss");  str += " ,Value8:"; str += data.Value8.ToString("HH:mm:ss");  str += " ,Value9:"; str += data.Value9.ToString("yyyy-MM-dd");  str += " ,Value10:";  if (data.Value10 is not null && data.Value10.Length > 0)  {  for (int i = 0; i < data.Value10.Length; i++)  {  str += (data.Value10[i]);  }     }  return str;  }  // 使用栈上分配的ValueStringBuilder[Benchmark]  public string ValueStringBuilderOnStack()  {  var data = Data;  Span<char> buffer = stackalloc char[20480];  var sb = new ValueStringBuilder(buffer);  sb.Append("Value1:"); sb.AppendSpanFormattable(data.Value1);  if (data.Value2 > 10)  {  sb.Append(" ,Value2:"); sb.AppendSpanFormattable(data.Value2);  }  sb.Append(" ,Value3:"); sb.AppendSpanFormattable(data.Value3);  sb.Append(" ,Value4:"); sb.AppendSpanFormattable(data.Value4);  sb.Append(" ,Value5:"); sb.Append(data.Value5);  if (data.Value6 > 20)  {  sb.Append(" ,Value6:"); sb.AppendSpanFormattable(data.Value6, "F2");  }  sb.Append(" ,Value7:"); sb.AppendSpanFormattable(data.Value7, "yyyy-MM-dd HH:mm:ss");  sb.Append(" ,Value8:"); sb.AppendSpanFormattable(data.Value8, "HH:mm:ss");  sb.Append(" ,Value9:"); sb.AppendSpanFormattable(data.Value9, "yyyy-MM-dd");  sb.Append(" ,Value10:");  if (data.Value10 is not null && data.Value10.Length > 0)  {  for (int i = 0; i < data.Value10.Length; i++)  {  sb.AppendSpanFormattable(data.Value10[i]);  }     }  return sb.ToString();  }// 使用ArrayPool 堆上分配的StringBuilder[Benchmark]  public string ValueStringBuilderOnHeap()  {  var data = Data;  var sb = new ValueStringBuilder(20480);  sb.Append("Value1:"); sb.AppendSpanFormattable(data.Value1);  if (data.Value2 > 10)  {  sb.Append(" ,Value2:"); sb.AppendSpanFormattable(data.Value2);  }  sb.Append(" ,Value3:"); sb.AppendSpanFormattable(data.Value3);  sb.Append(" ,Value4:"); sb.AppendSpanFormattable(data.Value4);  sb.Append(" ,Value5:"); sb.Append(data.Value5);  if (data.Value6 > 20)  {  sb.Append(" ,Value6:"); sb.AppendSpanFormattable(data.Value6, "F2");  }  sb.Append(" ,Value7:"); sb.AppendSpanFormattable(data.Value7, "yyyy-MM-dd HH:mm:ss");  sb.Append(" ,Value8:"); sb.AppendSpanFormattable(data.Value8, "HH:mm:ss");  sb.Append(" ,Value9:"); sb.AppendSpanFormattable(data.Value9, "yyyy-MM-dd");  sb.Append(" ,Value10:");  if (data.Value10 is not null && data.Value10.Length > 0)  {  for (int i = 0; i < data.Value10.Length; i++)  {  sb.AppendSpanFormattable(data.Value10[i]);  }     }return sb.ToString();  }}

结果如下所示。343eecb2ca08f1f44710b91b5a74f644.png从上图的结果中,我们可以得出如下的结论。

  • 使用StringConcat是最慢的,这种方式是无论如何都不推荐的。

  • 使用StringBuilder要比使用StringConcat快6.5倍,这是推荐的方法。

  • 设置了初始容量的StringBuilder要比直接使用StringBuilder快25%,正如我在你应该为集合类型设置初始大小[1]一样,设置初始大小绝对是相当推荐的做法。

  • 栈上分配的ValueStringBuilderStringBuilder要快50%,比设置了初始容量的StringBuilder还快25%,另外它的GC次数是最低的。

  • 堆上分配的ValueStringBuilderStringBuilder要快55%,他的GC次数稍高与栈上分配。 从上面的结论中,我们可以发现ValueStringBuilder的性能非常好,就算是在栈上分配缓冲区,性能也比StringBuilder快25%。

源码解析

ValueStringBuilder的源码不长,我们挑几个重要的方法给大家分享一下,部分源码如下。

// 使用 ref struct 该对象只能在栈上分配
public ref struct ValueStringBuilder
{// 如果从ArrayPool里分配buffer 那么需要存储一下// 以便在Dispose时归还private char[]? _arrayToReturnToPool;// 暂存外部传入的bufferprivate Span<char> _chars;// 当前字符串长度private int _pos;// 外部传入bufferpublic ValueStringBuilder(Span<char> initialBuffer){// 使用外部传入的buffer就不使用从pool里面读取的了_arrayToReturnToPool = null;_chars = initialBuffer;_pos = 0;}public ValueStringBuilder(int initialCapacity){// 如果外部传入了capacity 那么从ArrayPool里面获取_arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);_chars = _arrayToReturnToPool;_pos = 0;}// 返回字符串的Length 由于Length可读可写// 所以重复使用ValueStringBuilder只需将Length设置为0public int Length{get => _pos;set{Debug.Assert(value >= 0);Debug.Assert(value <= _chars.Length);_pos = value;}}......[MethodImpl(MethodImplOptions.AggressiveInlining)]public void Append(char c){// 添加字符非常高效 直接设置到对应Span位置即可int pos = _pos;if ((uint) pos < (uint) _chars.Length){_chars[pos] = c;_pos = pos + 1;}else{// 如果buffer空间不足,那么会走GrowAndAppend(c);}}[MethodImpl(MethodImplOptions.AggressiveInlining)]public void Append(string? s){if (s == null){return;}// 追加字符串也是一样的高效int pos = _pos;// 如果字符串长度为1 那么可以直接像追加字符一样if (s.Length == 1 && (uint) pos < (uint) _chars .Length){_chars[pos] = s[0];_pos = pos + 1;}else{// 如果是多个字符 那么使用较慢的方法AppendSlow(s);}}private void AppendSlow(string s){// 追加字符串 空间不够先扩容// 然后使用Span复制 相当高效int pos = _pos;if (pos > _chars.Length - s.Length){Grow(s.Length);}s
#if !NETCOREAPP.AsSpan()
#endif.CopyTo(_chars.Slice(pos));_pos += s.Length;}// 对于需要格式化的对象特殊处理[MethodImpl(MethodImplOptions.AggressiveInlining)]public void AppendSpanFormattable<T>(T value, string? format = null, IFormatProvider? provider = null)where T : ISpanFormattable{// ISpanFormattable非常高效if (value.TryFormat(_chars.Slice(_pos), out int charsWritten, format, provider)){_pos += charsWritten;}else{Append(value.ToString(format, provider));}}[MethodImpl(MethodImplOptions.NoInlining)]private void GrowAndAppend(char c){// 单个字符扩容在添加Grow(1);Append(c);}// 扩容方法[MethodImpl(MethodImplOptions.NoInlining)]private void Grow(int additionalCapacityBeyondPos){Debug.Assert(additionalCapacityBeyondPos > 0);Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos,"Grow called incorrectly, no resize is needed.");// 同样也是2倍扩容,默认从对象池中获取bufferchar[] poolArray = ArrayPool<char>.Shared.Rent((int) Math.Max((uint) (_pos + additionalCapacityBeyondPos),(uint) _chars.Length * 2));_chars.Slice(0, _pos).CopyTo(poolArray);char[]? toReturn = _arrayToReturnToPool;_chars = _arrayToReturnToPool = poolArray;if (toReturn != null){// 如果原本就是使用的对象池 那么必须归还ArrayPool<char>.Shared.Return(toReturn);}}// [MethodImpl(MethodImplOptions.AggressiveInlining)]public void Dispose(){char[]? toReturn = _arrayToReturnToPool;this = default; // 为了安全,在释放时置空当前对象if (toReturn != null){// 一定要记得归还对象池ArrayPool<char>.Shared.Return(toReturn);}}
}

从上面的源码我们可以总结出ValueStringBuilder的几个特征:

  • 比起StringBuilder来说,实现方式非常简单。

  • 一切都是为了高性能,比如各种Span的用法,各种内联参数,以及使用对象池等等。

  • 内存占用非常低,它本身就是结构体类型,另外它是ref struct,意味着不会被装箱,不会在堆上分配。

适用场景

ValueStringBuilder是一种高性能的字符串创建方式,针对于不同的场景,可以有不同的使用方式。1.非常高频次的字符串拼接的场景,并且字符串长度较小此时可以使用栈上分配ValueStringBuilder。 大家都知道现在ASP.NET Core性能非常好,在其依赖的内部库UrlBuilder[2]中,就使用栈上分配,因为栈上分配在当前方法结束后内存就会回收,所以不会造成任何GC压力。8406d8374815a3f4b598ecb9d156d31a.png2.非常高频次的字符串拼接场景,但是字符串长度不可控此时使用ArrayPool指定容量ValueStringBuilder。比如在.NET BCL库中有很多场景使用,比如动态方法的ToString[3]实现。从池中分配虽然没有栈上分配那么高效,但是一样的能降低内存占用和GC压力。9c16b9fc17670536f49e8678ed76311c.png3. 非常高频次的字符串拼接场景,但是字符串长度可控,此时可以栈上分配和ArrayPool分配联合使用,比如正则表达式[4]解析类中,如果字符串长度较小那么使用栈空间,较大那么使用ArrayPool。d79671fe682a5abe8f963c1b0fe60b6d.png

需要注意的场景

1.在async\await中无法使用ValueStringBuilder。原因大家也都知道,因为ValueStringBuilderref struct它只能在栈上分配async\await会编译成状态机拆分await前后的方法,所以ValueStringBuilder不好在方法内传递,不过编译器也会警告。558667c34a3163bab84a844e66bbccea.png2.无法将ValueStringBuilder作为返回值返回,因为在当前栈上分配,方法结束后它会被释放,返回它将指向未知的地址。这个编译器也会警告。3a858dacc12a8ef6964711367cabbb26.png3.如果要将ValueStringBuilder传递给其它方法,那么必须使用ref传递,否则发生值拷贝会存在多个实例。这个编译器不会警告,但是你必须非常注意。97a24949bc7a6e890521dbf1a2953ed4.png4. 如果使用栈上分配,那么Buffer大小控制在5KB内比较稳妥,至于为什么需要这样,后面有机会在讲一讲。

总结

今天和大家分享了一下高性能几乎无内存占用的字符串拼接结构体ValueStringBuilder,在大多数的场景还是推荐大家使用。但是要非常注意上面提到的[5]的几个场景,如果不符合条件,那么大家还是可以使用高效的StringBuilder来进行字符串拼接。

本文源码链接

https://github.com/InCerryGit/BlogCode-Use-ValueStringBuilder

参考资料

[1]

你应该为集合类型设置初始大小: https://www.cnblogs.com/InCerry/p/Dotnet-Opt-Perf-You-Should-Set-Capacity-For-Collection.html

[2]

UrlBuilder: https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.Uri/src/System/UriBuilder.cs#L284-L362

[3]

ToString: https://github.com/dotnet/runtime/blob/43dd0a74ab524278620d8c6a9d33a9b73b2d2228/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs#L137

[4]

正则表达式: https://github.com/dotnet/runtime/blob/43dd0a74ab524278620d8c6a9d33a9b73b2d2228/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs#L150

[5]

上面提到的: #需要注意的场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Buildroot 龙芯1C支持指南

本文转载自&#xff1a;https://github.com/pengphei/smartloong-sphinx/blob/master/source/cn/loongson1c_buildroot_guide.rst Buildroot 龙芯1C支持指南 引子&#xff1a;从龙芯1C预订拿到板子已经很长一段时间了&#xff0c;因为各种事情&#xff0c;一直让它呆在角落的冷…

STOLUCK:经济下行的当下 ,STO或将帮助中小企业度过寒冬

2018年被称为创业阵亡率特别高的一年&#xff0c;相关报道称有近20%的创业团队面临“后续融资跟不上&#xff0c;可能死在春天来临之前”的窘境。经济不景气的当下&#xff0c;上下游资金不足&#xff0c;信贷机构没钱&#xff0c;风投业捉襟见肘。实际今年3月份开始&#xff0…

[ 转载 ] Java面试精选【Java基础第一部分】

http://www.cnblogs.com/hnlshzx/p/3491587.html 转载于:https://www.cnblogs.com/ILoke-Yang/p/8137326.html

html如何自动调整边框大小,html – Chrome与大小调整:显示中的边框:表格

我正在使用display&#xff1a;table做一个小的2窗格布局.对于间距(也来自背景图像),我使用填充.因为我需要孩子们有一个确切的宽度&#xff1a;50&#xff05;来自可用空间(考虑到父div的填充),我使用Box-sizing&#xff1a;border-Box.这在Opera中运行良好,但在Chrome中,框大…

浅析C# Dictionary实现原理

一、前言二、理论知识1、Hash 算法2、Hash 桶算法3、解决冲突算法三、Dictionary 实现1. Entry 结构体2. 其它关键私有变量3. Dictionary - Add 操作4. Dictionary - Find 操作5. Dictionary - Remove 操作6. Dictionary - Resize 操作(扩容)7. Dictionary - 再谈 Add 操作8. C…

对特朗普获胜感到意外? 那你是被社交媒体迷惑了

北京时间11月10日消息&#xff0c;据外媒报道&#xff0c;昨天旷日持久的美国总统选战终于告一段落&#xff0c;特朗普的获胜让民调彻底成了一张废纸&#xff0c;而早就在Facebook上提前欢庆希拉里胜利的人则彻底蒙圈了&#xff0c;就连万里之外的中国吃瓜群众们也开始追着许多…

猫晚流量再创记录,阿里云直播方案护航优酷2500万用户体验

2019独角兽企业重金招聘Python工程师标准>>> 对“剁手党而言&#xff0c;天猫双11早已经超越了简单的“买买买”&#xff0c;更是一场边看边玩的狂欢盛宴。今年的天猫双11狂欢夜晚会&#xff08;简称“猫晚”&#xff09;在上海举办&#xff0c;这台兼具年轻潮流与国…

python实现二叉树和它的七种遍历

介绍&#xff1a; 树是数据结构中非常重要的一种&#xff0c;主要的用途是用来提高查找效率&#xff0c;对于要重复查找的情况效果更佳&#xff0c;如二叉排序树、FP-树。另外可以用来提高编码效率&#xff0c;如哈弗曼树。 代码&#xff1a; 用python实现树的构造和几种遍历算…

.NET性能系列文章二:Newtonsoft.Json vs System.Text.Json

微软终于追上了&#xff1f;图片来自 Glenn Carstens-Peters[1]Unsplash[2]欢迎来到.NET 性能系列的另一章。这个系列的特点是对.NET 世界中许多不同的主题进行研究、基准和比较。正如标题所说的那样&#xff0c;重点在于使用最新的.NET7 的性能。你将看到哪种方法是实现特定主…

android gpu平板 推荐,性能强的不像话,最强安卓平板华为平板M6上手

原标题&#xff1a;性能强的不像话&#xff0c;最强安卓平板华为平板M6上手你为什么买平板电脑&#xff1f;当这一问题问出以后&#xff0c;许多朋友的表情都很微妙&#xff0c;随后大概率的回答则相当统一&#xff1a;"我买平板干嘛&#xff1f;"。其实得到这样一个…

【Python】HackBack(获取暴力破解服务器密码的IP来源)

1、前言 又在0x00sec上翻到好东东。 https://0x00sec.org/t/python-hackback-updated/882 帖子里的脚本会得到那些暴力服务器密码失败的IP和用户名&#xff0c;并且使用shodan api做一个溯源定位。 #!/usr/bin/python3.4 import re import urllib.request import json log_path…

企业应用“数据优先”革命的下一个主战场:安全与运营

根据IDC发布的2015年全球CIO日程预测&#xff0c;80%的CIO将提供一个实现创新和改善业务决策的新体系架构。 大数据时代&#xff0c;企业软件市场正在经历一次大迁移&#xff0c;数以十亿计的企业IT支出预算将投向“数据优先”应用&#xff0c;而不是长久以来以业务流程和工作流…

给Web开发人员的以太坊入坑指南

以太坊现在各种学习资料数不胜数&#xff0c;但由于以太坊正处于飞速发展阶段&#xff0c;有些学习资料很快就过时了。所以想找到有价值的资料无异于大海捞针。我费了很大功夫&#xff0c;才建立起对以太坊的整体认识&#xff0c;搞清楚它的工作机制。我相信很多跃跃欲试的开发…

和硕看重物联网大势 程建中:从擅长领域出发

物联网(IoT)前景可期已是全球科技产业的共识&#xff0c;但是如何真正找出到位的商机&#xff0c;却考验产业链业者的智能。苹果iPhone代工厂和硕联合科技执行长程建中表示&#xff0c;物联网与大数据相关应用商机看俏&#xff0c;物联网筑的梦比网际网路还大&#xff0c;当年网…

html选择文本框后提示消失,两种方法实现文本框输入内容提示消失

第一种方法&#xff1a;基于HTML5 input标签的新特性 - placeholder 。另外&#xff0c;x-webkit-speech 属性可以实现语音输入功能。第二种方法&#xff1a;用span模拟&#xff0c;定位span&#xff0c;借助JS键盘事件判断输入&#xff0c;确定span里的内容显示隐藏。无标题文…

TensorFlow基本计算单元——变量

# -*- coding: utf-8 -*- import tensorflow as tf a 3 # 创建变量 w tf.Variable([[0.5, 1.0]]) #行向量 x tf.Variable([[2.0], [1.0]]) y tf.matmul(w, x) #矩阵相乘 print(y) # Tensor("MatMul:0", shape(1, 1), dtypefloat32)init_op tf.global_variables…

程序人生:织梦dedecms后台/会员验证码关闭

dedecms默认是所有的功能几乎只要用到验证码的地方我们都需要验证的&#xff0c;如果要关闭一些验证功能我们可以参考下面的教程&#xff0c;这里介绍了关闭后台&#xff0c;留言板&#xff0c;会员系统等验证码功能关闭了。提示&#xff1a;支持DedeCMS V5.6 以上的所有版本取…

html中图片的属性优化,Html标签元素在SEO中的优化方式(二)

接上html标签元素在SEO中的优化方式(一)中对HTML界面的介绍&#xff0c;我们今天继续补充HTML标签的SEO优化方式在内容中有几个值得去研究一下的优化元素--导航和内部链接&#xff1a;很明显的一点&#xff0c;建立导航会使搜索引擎可以容易的确定网站结构&#xff0c;但是很多…

Gartner认为安全性将取代成本和敏捷性成为政府部门采用云服务的首要原因

全球领先的信息技术研究和顾问公司Gartner表示&#xff0c;公有云如今具备可扩展性、计算威力、海量存储和安全性&#xff0c;可打造更好的政府数字化平台并满足对业绩和价值不断增长的期望值。 Gartner预计到2018年&#xff0c;提升的安全性将取代成本节约和敏捷性成为政府部门…

一款简单的缩放拖拽图片控件

本文介绍一个针对 .NET 桌面应用程序的独立图片缩放拖拽显示控件 SQPhoto[1]。SQPhoto 是一个 Windows 桌面应用的组件&#xff0c;支持 .NET6 和 .NET Framework 4.6 。基于 PictureBox 的图片展示工具&#xff0c;增加了拖动和缩放功能&#xff0c;便于在某些场景下的图片展…