当前位置: 首页 > news >正文

联通网站服务器网站推广排名收费标准

联通网站服务器,网站推广排名收费标准,正规装饰装修公司,怎么推广一个网站开始之前相比固定长度的Array#xff0c;大家可能在编程的时候经常会使用ListT#xff0c;同时可能会经常往里面Add东西#xff0c;因为List具有可扩容性#xff0c;但是注重GC的朋友会发现#xff08;比如Unity开发者#xff09;#xff0c;List.Resize会造成扩…开始之前相比固定长度的Array大家可能在编程的时候经常会使用ListT同时可能会经常往里面Add东西因为List具有可扩容性但是注重GC的朋友会发现比如Unity开发者List.Resize会造成扩容前数组长度*泛型类型所占字节长度的GC同时会造成耗时以及额外的内存占用比如List有100个元素的时候触发了扩容新容量为200但是总共一共插入了150个元素导致有50个分配的内存没被利用Stream例如MemoryStream与List一样在Resize里会分配当前容量两倍的新byte托管数组也会造成和上面提到的一样的情况导致GC和可能存在的额外内存占用以及拷贝托管数组的耗时。那么有没有什么办法能实现一个能插入元素能动态扩容扩容不造成GC能指定扩容长度包含上述内容的动态扩容数组呢让我们先看看List和Stream的原理ListT和StreamListT和Stream一样基本是内部有一个托管数组T[]或byte[]内部会记录当前总容量以及元素总数Stream还会额外记录当前的位置且内部实现了Resize方法会new一个新的托管数组长度为当前总容量的两倍紧接着会把老数组的元素复制到新数组上老数组不会再被引用且造成GCSpan和Memory最近C#提供了Span和Memory类型提供了安全操作连续内存的方法他们的内部实现是这样的记录对应泛型类型的指针记录该指针的长度多少个元素Span和Memory有一点微小的区别比如在栈上和托管堆上Span是ref structMemory则是readonly struct的缘故导致他们的用法不太一样不过本文只需要关心他们的实现原理。是不是发现和ListT以及Stream很像只是托管数组变成指针了然后少了一些成员指针指针是什么指针就是一个变量在内存里的地址所以叫做指针Pointer因为指针指向了内存内的一个变量在内存中的变量有两种情况一种是被GC托管的变量一种是不被GC托管的变量而我们的List和Stream内部的数组就是托管数组由GC托管。如果对Span和Memory熟悉的应该知道List可以直接转Span怎么做到的呢只需要把List内部托管的数组的指针传给Span的构造参数就行List转Memory也可以就是需要自己实现有点复杂那么延伸的想法就来了如果我们用非托管指针代替分配的托管数组来存我们的元素是不是就可以不被GC托管而不被产生GC了答案是没错。自行分配非托管内存如果我们需要申请非托管内存我们需要实现以下一条很重要原则手动申请的非托管内存必须用好后手动释放不然就会造成野指针C#有两种方法申请非托管内存并且任何能运行C#的平台都支持Unity也是支持的哪怕是IL2CPPMarshal.AllocHGlobal该方法会返回指定长度的非托管内存并且返回的内存有可能会有值Marshal.AllocCoTaskMem该方法会返回至少指定长度的非托管内存但是也有可能会返回超过改长度的内存且返回的内存不会有值全是0这里很明显第一个提到的方法适合我们的使用场景托管的动态扩容数组类型既然用Sturct可以避免创建时造成的GC如Span, Memory都是struct为什么我们要用托管类型Class去定义我们的动态扩容数组呢请看一下上面提到的原则手动申请的非托管内存必须用好后手动释放不然就会造成野指针只有通过托管类型我们才能做到这一点在构造函数Constructor内申请非托管内存在折构函数Finalizer内释放申请的内存折构函数就是一个对象被GC回收前调用的函数实现一个非托管类型的动态扩容数组因为非托管类型转指针比较方便所以本文我们先实现一个非托管类型的动态扩容数组根据我们上面提到的思路可以得出以下代码注此代码不是完整体/// summary /// A buffer that can dynamically extend /// /summary /// typeparam nameT/typeparam public sealed unsafe class ExtensibleBufferT where T : unmanaged {/// summary/// Init extensible buffer with a capacity/// /summary/// param namesize/param/// param nameinitialData/paramprivate ExtensibleBuffer(int size, T[] initialData) {sizeOfT (byte)sizeof(T);ExpandSize size;Data (T*)Marshal.AllocHGlobal(sizeOfT * ExpandSize);if (initialData ! null){fixed(T* ptr initialData){CopyFrom(ptr, 0, 0, initialData.Length);}}TotalLength ExpandSize;GC.AddMemoryPressure(sizeOfT * ExpandSize); }/// summary/// Free allocated memories/// /summary~ExtensibleBuffer() {Marshal.FreeHGlobal((IntPtr)Data);GC.RemoveMemoryPressure(sizeOfT * TotalLength); } }上面的代码实现了构造函数和折构函数其中构造函数的参数指定了扩容大小方法内部获取了泛型T的内存大小并且申请了类型大小*扩容数量个字节的内存并且如果有初始化数据就把初始化托管数据复制到非托管内存上同时会标记目前的总长度以及通知GC我们有申请的内存大小的内存压力促进GC多去回收折构函数内我们释放了申请的内存同时通知GC我们之前申请的内存大小的内存压力没了被我们释放了让GC不要再关系我们这个动态扩容数组了索引器索引器就是数组/List返回指定位置元素的方法/// summary /// Get element at index /// /summary /// param nameindex/param public T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)]get Data[index]; [MethodImpl(MethodImplOptions.AggressiveInlining)]set {EnsureCapacity(ref index);Data[index] value; } }我们在插入的时候检查下申请的内存就好确保插入到有效的内存里。实现扩容既然要避免每次扩容都双倍现在的长度从而造成内存浪费我们需要在构造函数里标记扩容大小然后每次扩容的时候当前总长度扩容大小就好幸运的是C#提供了一个重新分配通过Marshal.AllocHGlobal申请的内存的方法Marshal.ReAllocHGlobal这个方法需要传两个参数第一个参数是原申请的指针第二个参数是新长度转指针通过简单的封装我们得到了/// summary /// Ensure index exists /// /summary /// param nameindex/param private void EnsureCapacity(ref int index) {if (index TotalLength) return;while (index TotalLength) {TotalLength ExpandSize;GC.AddMemoryPressure(sizeOfT * ExpandSize); }Extend(); }/// summary /// Extend buffer /// /summary [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Extend() {Data (T*)Marshal.ReAllocHGlobal((IntPtr)Data, new IntPtr(TotalLength * sizeOfT)); }我们只需要定期触发比如每次插入的时候访问的话为了性能我们就不检查了因为是指针也不会导致数组越界只是会返回我们想不到的结果EnsureCapacity来检查指定的索引是否被我们申请过如果没的话就动态扩容以及通知GC即可从外部的数组/指针里复制元素我们只需要取别的数组/指针然后从指定偏移开始复制指定长度到我们申请的指针的指定位置即可/// summary /// Copy data to extensible buffer /// /summary /// param namesrc/param /// param namesrcIndex/param /// param namedstIndex/param /// param namelength/param /// exception crefInvalidOperationException/exception public void CopyFrom(T[] src, int srcIndex, int dstIndex, int length) {fixed (T* ptr src) {CopyFrom(ptr, srcIndex, dstIndex, length); } }/// summary /// Copy data to extensible buffer /// why unaligned? https://stackoverflow.com/a/72418388 /// /summary /// param namesrc/param /// param namesrcIndex/param /// param namedstIndex/param /// param namelength/param /// exception crefInvalidOperationException/exception public void CopyFrom(T* src, int srcIndex, int dstIndex, int length) {var l dstIndex length;//size checkEnsureCapacity(ref l);//copyUnsafe.CopyBlockUnaligned(Data dstIndex, src srcIndex, (uint)length); }StackOverFlow的这篇文章stackoverflow.com/a/724证明了不对齐的拷贝内存更快不过这里我们是非托管类型的非托管内存所以这样玩不会出问题复制数据到外部的数组/指针与上面的实现类似我们只需要获取需要复制到的数组/指针从我们动态扩容数组的第几个元素开始复制复制多少个即可注这里如果需要复制到指定的数组位置可以把数组转指针后偏移然后调用传指针的方法去复制/// summary /// Copy data from buffer to dst from dst[0] /// /summary /// param namedst/param /// param namesrcIndex/param /// param namelength/param /// exception crefOverflowException/exception public void CopyTo(ref T[] dst, int srcIndex, int length) {fixed (T* ptr dst) {CopyTo(ptr, srcIndex, length); } }/// summary /// Copy data from buffer to dst from dst[0] /// /summary /// param namedst/param /// param namesrcIndex/param /// param namelength/param /// exception crefOverflowException/exception public void CopyTo(T* dst, int srcIndex, int length) {var l srcIndex length;//size checkEnsureCapacity(ref l);//copyUnsafe.CopyBlockUnaligned(dst, Data srcIndex, (uint)length); }转SpanSpan特别有用在切割内存之类的地方没有什么比Span更适合的了所以我们顺便把转Span也支持吧显示转换/// summary /// convert an extensible to buffer from start index with provided length /// /summary /// param namestartIndex/param /// param namelength/param /// returns/returns public SpanT AsSpan(int startIndex, int length) {var l startIndex length;//size checkEnsureCapacity(ref l);return new SpanT(Data startIndex, length); }这样我们可以从指定位置开始讲指定长度个元素转为Span同时操作返回的Span可以直接操作到我们这个动态扩容数组内的元素上因为操作Span的元素相当于直接操作内存隐式转换/// summary /// Convert to span /// /summary /// param namebuffer/param /// returns/returns public static implicit operator SpanT(ExtensibleBufferT buffer) buffer.AsSpan(0, buffer.TotalLength);这里我们从第0个元素开始把当前总长度个元素转Span转托管数组因为有可能需要给其他接口使用所以我们需要能把非托管数组的数据复制到托管数组只需要new个托管数组然后调用复制的接口即可/// summary /// Convert buffer data to an Array (will create a new array and copy values) /// /summary /// param namestartIndex/param /// param namelength/param /// returns/returns public T[] ToArray(int startIndex, int length) {T[] ret new T[length];CopyTo(ref ret, startIndex, length);return ret; }完整代码可以在GitHub上看Nino当然我本人更希望大家来点star也可以看下面贴出的代码using System; using System.Runtime.InteropServices; using System.Runtime.CompilerServices;namespace Nino.Shared.IO {/// summary/// A buffer that can dynamically extend/// /summary/// typeparam nameT/typeparampublic sealed unsafe class ExtensibleBufferT where T : unmanaged {/// summary/// Default size of the buffer/// /summaryprivate const int DefaultBufferSize 128;/// summary/// Data that stores everything/// /summarypublic T* Data { get; private set; }/// summary/// Size of T/// /summaryprivate readonly byte sizeOfT;/// summary/// expand size for each block/// /summarypublic readonly int ExpandSize;/// summary/// Total length of the buffer/// /summarypublic int TotalLength { get; private set; }/// summary/// Init buffer/// /summarypublic ExtensibleBuffer() : this(DefaultBufferSize){}/// summary/// Init buffer/// /summarypublic ExtensibleBuffer(int expandSize) : this(expandSize, null){}/// summary/// Init extensible buffer with a capacity/// /summary/// param namesize/param/// param nameinitialData/paramprivate ExtensibleBuffer(int size, T[] initialData){sizeOfT (byte)sizeof(T);ExpandSize size;Data (T*)Marshal.AllocHGlobal(sizeOfT * ExpandSize);if (initialData ! null){fixed(T* ptr initialData){CopyFrom(ptr, 0, 0, initialData.Length);}}TotalLength ExpandSize;GC.AddMemoryPressure(sizeOfT * ExpandSize);}/// summary/// Get element at index/// /summary/// param nameindex/parampublic T this[int index]{[MethodImpl(MethodImplOptions.AggressiveInlining)]get Data[index];[MethodImpl(MethodImplOptions.AggressiveInlining)]set{EnsureCapacity(ref index);Data[index] value;}}/// summary/// Ensure index exists/// /summary/// param nameindex/paramprivate void EnsureCapacity(ref int index){if (index TotalLength) return;while (index TotalLength){TotalLength ExpandSize;GC.AddMemoryPressure(sizeOfT * ExpandSize);}Extend();}/// summary/// Extend buffer/// /summary[MethodImpl(MethodImplOptions.AggressiveInlining)]private void Extend(){Data (T*)Marshal.ReAllocHGlobal((IntPtr)Data, new IntPtr(TotalLength * sizeOfT));}/// summary/// Convert buffer data to an Array (will create a new array and copy values)/// /summary/// param namestartIndex/param/// param namelength/param/// returns/returnspublic T[] ToArray(int startIndex, int length){T[] ret new T[length];CopyTo(ref ret, startIndex, length);return ret;}/// summary/// convert an extensible to buffer from start index with provided length/// /summary/// param namestartIndex/param/// param namelength/param/// returns/returnspublic SpanT AsSpan(int startIndex, int length){var l startIndex length;//size checkEnsureCapacity(ref l);return new SpanT(Data startIndex, length);}/// summary/// Convert to span/// /summary/// param namebuffer/param/// returns/returnspublic static implicit operator SpanT(ExtensibleBufferT buffer) buffer.AsSpan(0, buffer.TotalLength);/// summary/// Copy data to extensible buffer/// /summary/// param namesrc/param/// param namesrcIndex/param/// param namedstIndex/param/// param namelength/param/// exception crefInvalidOperationException/exceptionpublic void CopyFrom(T[] src, int srcIndex, int dstIndex, int length){fixed (T* ptr src){CopyFrom(ptr, srcIndex, dstIndex, length);}}/// summary/// Copy data to extensible buffer/// why unaligned? https://stackoverflow.com/a/72418388/// /summary/// param namesrc/param/// param namesrcIndex/param/// param namedstIndex/param/// param namelength/param/// exception crefInvalidOperationException/exceptionpublic void CopyFrom(T* src, int srcIndex, int dstIndex, int length){var l dstIndex length;//size checkEnsureCapacity(ref l);//copyUnsafe.CopyBlockUnaligned(Data dstIndex, src srcIndex, (uint)length);}/// summary/// Copy data from buffer to dst from dst[0]/// /summary/// param namedst/param/// param namesrcIndex/param/// param namelength/param/// exception crefOverflowException/exceptionpublic void CopyTo(ref T[] dst, int srcIndex, int length){fixed (T* ptr dst){CopyTo(ptr, srcIndex, length);}}/// summary/// Copy data from buffer to dst from dst[0]/// /summary/// param namedst/param/// param namesrcIndex/param/// param namelength/param/// exception crefOverflowException/exceptionpublic void CopyTo(T* dst, int srcIndex, int length){var l srcIndex length;//size checkEnsureCapacity(ref l);//copyUnsafe.CopyBlockUnaligned(dst, Data srcIndex, (uint)length);}/// summary/// Free allocated memories/// /summary~ExtensibleBuffer(){Marshal.FreeHGlobal((IntPtr)Data);GC.RemoveMemoryPressure(sizeOfT * TotalLength);} } }Benchmark就这样我们理论上低GC高性能的非托管动态扩容数组就做好了让我们分析一下性能测试代码BenchmarkDotNetv0.13.1, OSmacOS Monterey 12.0.1 (21A559) [Darwin 21.1.0] Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores .NET SDK6.0.301 [Host] : .NET 6.0.6 (6.0.622.26707), X64 RyuJIT ShortRun : .NET 6.0.6 (6.0.622.26707), X64 RyuJITJobShortRun PlatformAnyCpu Runtime.NET 6.0 IterationCount1 LaunchCount1 WarmupCount1首先我们测试了ExtensibleBuffer和List的无优化版(V1不指定扩容/初始长度)以及优化版V2指定扩容/初始长度同时我们测试了byte1字节作为泛型类型以及int4字节作为泛型类型我们先看看100个元素的插入MethodtestCountMeanErrorGen 0Gen 1Gen 2AllocatedByteExtensibleBufferInsertV1100466.7 nsNA0.02770.02770.027740 BByteExtensibleBufferInsertV2100440.4 nsNA0.02190.02190.021940 BByteListInsertV1100273.6 nsNA0.0687--432 BByteListInsertV2100173.2 nsNA0.0253--160 BIntExtensibleBufferInsertV1100663.2 nsNA0.11730.11730.117340 BIntExtensibleBufferInsertV2100645.4 nsNA0.08580.08580.085840 BIntListInsertV1100299.2 nsNA0.1884--1,184 BIntListInsertV2100192.1 nsNA0.0725--456 B为什么会比List略慢因为申请内存是有耗时的虽然基本无感知。不过GC的优化是不是挺不错的我们现在看看1000个元素的插入MethodtestCountMeanErrorGen 0Gen 1Gen 2AllocatedByteExtensibleBufferInsertV110003,323.6 nsNA0.23270.23270.232740 BByteExtensibleBufferInsertV210001,560.3 nsNA0.23650.23650.236540 BByteListInsertV110001,917.9 nsNA0.3643--2,296 BByteListInsertV210001,554.6 nsNA0.1678--1,056 BIntExtensibleBufferInsertV110003,080.0 nsNA0.96890.96890.968941 BIntExtensibleBufferInsertV21000989.2 nsNA0.92510.92510.925141 BIntListInsertV110002,445.4 nsNA1.3390--8,424 BIntListInsertV210001,868.7 nsNA0.6447--4,056 B速度是不是基本一样了但是GC是不是少了特别特别多现在看看1000以上的元素的插入MethodtestCountMeanErrorGen 0Gen 1Gen 2AllocatedByteExtensibleBufferInsertV11000025,683.9 nsNA2.34992.34992.349942 BByteExtensibleBufferInsertV21000011,535.0 nsNA2.34992.34992.349942 BByteListInsertV11000017,051.6 nsNA5.2490--33,112 BByteListInsertV21000016,544.9 nsNA1.5869--10,056 BIntExtensibleBufferInsertV11000025,945.4 nsNA9.58259.58259.582546 BIntExtensibleBufferInsertV2100009,269.5 nsNA8.23978.23978.239746 BIntListInsertV11000023,988.9 nsNA20.8130--131,400 BIntListInsertV21000016,521.5 nsNA6.3477--40,056 BByteExtensibleBufferInsertV1100000276,784.6 nsNA22.949222.949222.949256 BByteExtensibleBufferInsertV2100000121,097.0 nsNA23.559623.559623.559656 BByteListInsertV1100000247,649.4 nsNA205.3223205.322334.4238262,583 BByteListInsertV2100000213,715.3 nsNA161.1328161.132826.8555100,074 BIntExtensibleBufferInsertV1100000244,882.5 nsNA93.750093.750093.7500109 BIntExtensibleBufferInsertV2100000111,195.8 nsNA86.303786.303786.303782 BIntListInsertV1100000533,471.8 nsNA619.1406619.1406233.39841,049,161 BIntListInsertV2100000326,374.4 nsNA265.6250265.625099.6094400,123 BByteExtensibleBufferInsertV110000002,656,296.6 nsNA226.5625226.5625226.5625195 BByteExtensibleBufferInsertV210000001,214,632.2 nsNA197.2656197.2656197.2656174 BByteListInsertV110000002,422,943.3 nsNA1394.53131394.5313398.43752,097,906 BByteListInsertV210000001,636,061.4 nsNA207.0313207.0313197.26561,000,185 BIntExtensibleBufferInsertV110000003,663,844.0 nsNA851.5625851.5625851.5625547 BIntExtensibleBufferInsertV21000000857,195.9 nsNA498.0469498.0469498.0469377 BIntListInsertV110000003,717,760.8 nsNA1054.68751039.06251000.00008,389,735 BIntListInsertV210000002,265,089.4 nsNA511.7188511.7188492.18754,000,381 BByteExtensibleBufferInsertV11000000029,853,310.2 nsNA1656.25001656.25001656.25001,178 BByteExtensibleBufferInsertV21000000010,881,063.5 nsNA984.3750984.3750984.3750714 BByteListInsertV11000000030,683,668.0 nsNA3312.50003312.50001625.000033,556,204 BByteListInsertV21000000016,752,229.2 nsNA593.7500593.7500437.500010,000,366 BIntExtensibleBufferInsertV11000000052,335,791.2 nsNA2500.00002500.00002500.00001,802 BIntExtensibleBufferInsertV2100000008,783,753.0 nsNA984.3750984.3750984.3750714 BIntListInsertV11000000078,802,672.6 nsNA5142.85715142.85713000.0000134,220,415 BIntListInsertV21000000033,037,550.0 nsNA937.5000937.5000937.500040,001,345 BByteExtensibleBufferInsertV1100000000297,324,344.5 nsNA5000.00005000.00005000.00003,808 BByteExtensibleBufferInsertV2100000000113,086,965.2 nsNA800.0000800.0000800.0000741 BByteListInsertV1100000000303,881,242.5 nsNA5500.00005500.00003000.0000268,438,564 BByteListInsertV2100000000172,889,432.0 nsNA666.6667666.6667666.6667100,002,269 BIntExtensibleBufferInsertV1100000000394,704,429.0 nsNA12000.000012000.000012000.00009,536 BIntExtensibleBufferInsertV210000000077,565,079.3 nsNA1000.00001000.00001000.0000848 BIntListInsertV1100000000690,861,266.0 nsNA8000.00008000.00003000.00001,073,746,576 BIntListInsertV2100000000310,024,197.0 nsNA500.0000500.0000500.0000400,001,880 B速度是不是快了好几倍毕竟直接在指针上复制会快很多也少了托管数组分配的耗时GC是不是少了几千、几万、几十万倍使用场景有人可能会问这玩意儿有使用场景吗答案是有的且很多。基本上用Stream持续写入二进制数据的使用场景都很契合这个非托管动态扩容数组如网络IO因为这种IO都是KB/MB/GB级别的而在这个量级下该动态扩容数组有着出色的性能和卓越的GC优化序列化这种需要不断写入数据的场所也很契合填充加密的使用情况也很适合比如把二进制数据每n字节之间插入m字节的假数据最后再转托管byte数组返回出去可以用这个动态扩容数组在塞入假数据期间实现无GC高性能处理TCP粘包处理也很契合类似上面提到的网络IO但是不太一样因为要不断地Enqueue二进制数据到扩容数组然后如果满足包头记录的总长度了就Dequeue出去把后面的内容移动到最前面以后会有这个方案的文章还有很多很多的用途比如通过非托管动态扩容数组写数据然后用其非托管数据的指针传递给C/C等原生代码去实现无GC的高性能功能这个以后也会有文章关于搭配这个和Zlib native的文章最后为什么不是无GC非托管动态扩容数组呢因为我们这个数组是个对象所以造成GC。特别感谢阅读到最后的朋友希望能给大家带来帮助以后我还会写一个收集对象的内存地址转IntPtr实现的低GC托管动态扩容数组。
http://www.zqtcl.cn/news/348622/

相关文章:

  • 济南企业网站推广方法wordpress 类别 排序
  • 深圳网站建设开发公司哪家好wordpress 删除主题作者
  • 网站怎么登陆后台wordpress卡蜜 插件
  • wordpress安装微信登录插件青岛网站seo技巧
  • 燕郊个人做网站超变传奇手游刀刀切割无会员散人
  • 有没有可以做兼职的网站网站建设发展方向有哪些
  • php网站后台上传图片有没有推荐到首页的功能客户求购平台
  • 大型网站的标准莱芜市官网
  • 建站用Wordpress还是青州网站建设青州
  • 百度网站收录更新建网站的公司赚钱吗
  • 哪种语言做网站最快网站大全app下载
  • 手机营销网站制作网站建设备案和免备案的区别
  • 浙江省住房和城乡建设厅网站打不开中国建设银行官网站纪念币预约
  • 推广软件的网站安徽省城乡建设网站
  • 用网站做淘宝客怎么样珍爱网
  • 龙岩建设局招聘网站网站dns解析失败
  • 音乐网站的音乐怎么做深圳美容网站建设
  • 贵阳市观山湖区网站建设wordpress博客vieu模板
  • 怎么区分网站的好坏网站建设营销型号的区别
  • wordpress固定链接 中文建设网站优化
  • 东莞地产网站建设简述建设iis网站的基本过程
  • 外贸网站建设 公司价格怎样在手机上制作网站
  • 网站建设电话销售录音企业做网站有什么用
  • 网站布局设计软件软件工程大学排名
  • 自己的网站做防伪码深圳软件开发公司招聘
  • 网上购物网站大全wordpress文本悬停变色
  • 科技类公司网站设计如何做各大网站广告链接
  • 深圳做h5网站制作奢侈品网站设计
  • 用什么程序做网站佛山网站建设慕枫
  • 萍乡网站建设哪家公司好惠州开发做商城网站建设哪家好