原文链接:http://www.cnblogs.com/fish-li/archive/2013/02/18/2916253.html 优化反射性能的总结(上) 阅读目录 开始 用Emit方法优化反射 Delegate.CreateDelegate也能创建委托 用Delegate.CreateDelegate优化反射 完整的属性优化方案 委托方案的后续问题 缓存的线程并发问题 小结 招聘信息 反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题。 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性、字段)。 那么如何得到委托呢? 目前最常见也就是二种方法:Emit, ExpressionTree 。其中ExpressionTree可认为是Emit方法的简化版本, 所以Emit是最根本的方法,它采用在运行时动态构造一段IL代码来包装需要反射调用的代码, 这段动态生成的代码满足某个委托的签名,因此最后可以采用委托的方式代替反射调用。 回到顶部 用Emit方法优化反射 如果我们需要设计自己的数据访问层,那么就需要动态创建所有的数据实体对象,尤其是还要为每个数据实体对象的属性赋值, 这里就要涉及用反射的方法对属性执行写操作,为了优化这种反射场景的性能,我们可以用下面的方法来实现: public delegate void SetValueDelegate(object target, object arg); public static class DynamicMethodFactory { public static SetValueDelegate CreatePropertySetter(PropertyInfo property) { if( property == null ) throw new ArgumentNullException("property"); if( !property.CanWrite ) return null; MethodInfo setMethod = property.GetSetMethod(true); DynamicMethod dm = new DynamicMethod("PropertySetter", null, new Type[] { typeof(object), typeof(object) }, property.DeclaringType, true); ILGenerator il = dm.GetILGenerator(); if( !setMethod.IsStatic ) { il.Emit(OpCodes.Ldarg_0); } il.Emit(OpCodes.Ldarg_1); EmitCastToReference(il, property.PropertyType); if( !setMethod.IsStatic && !property.DeclaringType.IsValueType ) { il.EmitCall(OpCodes.Callvirt, setMethod, null); } else il.EmitCall(OpCodes.Call, setMethod, null); il.Emit(OpCodes.Ret); return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate)); } private static void EmitCastToReference(ILGenerator il, Type type) { if( type.IsValueType ) il.Emit(OpCodes.Unbox_Any, type); else il.Emit(OpCodes.Castclass, type); } } 现在可以用下面的测试代码检验委托调用带来的性能改进: Console.WriteLine(System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion()); int count = 1000000; OrderInfo testObj = new OrderInfo(); PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID"); Console.Write("直接访问花费时间: "); Stopwatch watch1 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ ) testObj.OrderID = 123; watch1.Stop(); Console.WriteLine(watch1.Elapsed.ToString()); SetValueDelegate setter2 = DynamicMethodFactory.CreatePropertySetter(propInfo); Console.Write("EmitSet花费时间: "); Stopwatch watch2 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ ) setter2(testObj, 123); watch2.Stop(); Console.WriteLine(watch2.Elapsed.ToString()); Console.Write("纯反射花费时间:  "); Stopwatch watch3 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ ) propInfo.SetValue(testObj, 123, null); watch3.Stop(); Console.WriteLine(watch3.Elapsed.ToString()); Console.WriteLine("-------------------"); Console.WriteLine("{0} / {1} = {2}", watch3.Elapsed.ToString(), watch1.Elapsed.ToString(), watch3.Elapsed.TotalMilliseconds / watch1.Elapsed.TotalMilliseconds); Console.WriteLine("{0} / {1} = {2}", watch3.Elapsed.ToString(), watch2.Elapsed.ToString(), watch3.Elapsed.TotalMilliseconds / watch2.Elapsed.TotalMilliseconds); Console.WriteLine("{0} / {1} = {2}", watch2.Elapsed.ToString(), watch1.Elapsed.ToString(), watch2.Elapsed.TotalMilliseconds / watch1.Elapsed.TotalMilliseconds); 我用VS2008 (.net 3.5 , CLR 2.0) 测试可以得到以下结果: 从结果可以看出: 1. 反射调用所花时间是直接调用的2629倍, 2. 反射调用所花时间是Emit生成的Set委托代码的82倍, 3. 运行Emit生成的Set委托代码所花时间是直接调用的31倍。 虽然Emit比直接调用还有30倍的差距,但还是比反射调用快80倍左右。 有意思的是,同样的代码,如果用VS2012 ( .net 4.5 , CLR 4.0) 测试可以得到以下结果: 感谢zhangweiwen 在博客中展示了CRL 4.0对反射的性能改进, 在他的博客中还提供了一种采用表达式树的优化版本,以及包含一个泛型的强类型的版本。 回到顶部 Delegate.CreateDelegate也能创建委托 如果我们观察CreatePropertySetter的实现代码,发现这个方法的本质就是创建一个委托: public static SetValueDelegate CreatePropertySetter(PropertyInfo property) { // ..... 省略前面已贴过的代码 return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate)); } 看到这里,让我想起Delegate.CreateDelegate方法也能创建一个委托,例如: OrderInfo testObj = new OrderInfo(); PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID"); Action setter = (Action)Delegate.CreateDelegate( typeof(Action), null, propInfo.GetSetMethod()); setter(testObj, 123); 显然,这是一种很直观的方法,可以得到一个强类型的委托。 然而,这种方法仅限有一种适用场景:明确知道要访问某个类型的某个属性或者方法,因为我们要提供类型参数。 例如:我要写个关键字过滤的HttpMoudle,它需要修改HttpRequest.Form对象的IsReadOnly属性,由于IsReadOnly在NameObjectCollectionBase类型中已申明为protected访问级别, 所以我只能反射操作它了,而且还需要很频繁的设置它。 在绝大部分反射场景中,例如数据访问层中从DataReader或者DataRow加载数据实体, 我们不可能事先知道要加载哪些类型,更不可能知道要加载哪些数据成员,因此就不可能给泛型委托的类型参数赋值, 这个方法看起来也就行不通了。 如果您不信的话,可以看下面修改后的代码: OrderInfo testObj = new OrderInfo(); PropertyInfo propInfo = typeof(OrderInfo).GetProperty("OrderID"); //Action setter = (Action)Delegate.CreateDelegate( // typeof(Action), null, propInfo.GetSetMethod()); Action setter = (Action)Delegate.CreateDelegate( typeof(Action), null, propInfo.GetSetMethod()); setter(testObj, 123); Console.WriteLine(testObj.OrderID); 虽然能通过编译,但会在运行时报错: 在很多时候,我们只能在运行时得到以Type对象表示的类型,接受object类型才是通用的解决方案。 然而,前面的代码证明了我们不能简单将委托类型从Action修改为Action 。 真的没有办法了吗? 虽然Emit已是很成熟的优化方案,可我还是希望试试 Delegate.CreateDelegate ! 回到顶部 用Delegate.CreateDelegate优化反射 当我们用Delegate.CreateDelegate从一个MethodInfo对象创建委托时, 委托的签名必须和MethodInfo表示的方法签名相匹配(有可能不一致), 所以这种方法得到的委托注定是一种强类型的委托。 现在的问题是:我们在运行时构造与指定MethodInfo匹配的委托,如何将Type对象转换成泛型委托的类型参数? 为了解决这个问题,我采用了泛型类来解决泛型委托的类型参数问题: public class SetterWrapper { private Action _setter; public SetterWrapper(PropertyInfo propertyInfo) { if( propertyInfo == null ) throw new ArgumentNullException("propertyInfo"); if( propertyInfo.CanWrite == false ) throw new NotSupportedException("属性不支持写操作。"); MethodInfo m = propertyInfo.GetSetMethod(true); _setter = (Action)Delegate.CreateDelegate(typeof(Action), null, m); } public void SetValue(TTarget target, TValue val) { _setter(target, val); } 我用泛型类把Delegate.CreateDelegate的问题解决了,但是如何创建这个类型的实例呢? 可以用Type.MakeGenericType()方法来解决: public static object CreatePropertySetterWrapper(PropertyInfo propertyInfo) { if( propertyInfo == null ) throw new ArgumentNullException("propertyInfo"); if( propertyInfo.CanWrite == false ) throw new NotSupportedException("属性不支持写操作。"); MethodInfo mi = propertyInfo.GetSetMethod(true); if( mi.GetParameters().Length > 1 ) throw new NotSupportedException("不支持构造索引器属性的委托。"); Type instanceType = typeof(SetterWrapper).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType); return Activator.CreateInstance(instanceType, propertyInfo); } 现在问题并没有结束,我又如何调用那些泛型类型实例的委托呢? 这里还有另一个问题要解决:调用方法需要支持object类型(满足通用性)。 我想到了定义一个接口来解决: public interface ISetValue { void Set(object target, object val); } 然后让SetterWrapper实现ISetValue接口: public class SetterWrapper : ISetValue { // ..... 省略前面已贴过的代码 void ISetValue.Set(object target, object val) { _setter((TTarget)target, (TValue)val); } } 还有前面的CreatePropertySetterWrapper方法也需要再次调整返回值类型: public static ISetValue CreatePropertySetterWrapper(PropertyInfo propertyInfo) { // ..... 省略前面已贴过的代码 return (ISetValue)Activator.CreateInstance(instanceType, propertyInfo); } 考虑到有些特定场景下需要用反射的方式重复操作某一个属性,使用强类型的方法可以避免拆箱装箱, 所以我保留了前面的SetValue方法,让它提供更好的性能,满足一些特定场景的需要。 因此,现在的SetterWrapper类型有二种使用方法,可以提供二种性能不同的实现方法。 现在可以增加二段测试代码来测试它的性能了: Console.Write("泛型委托花费时间: "); SetterWrapper setter3 = new SetterWrapper(propInfo); Stopwatch watch4 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ ) setter3.SetValue(testObj, 123); watch4.Stop(); Console.WriteLine(watch4.Elapsed.ToString()); Console.Write("通用接口花费时间: "); ISetValue setter4 = GetterSetterFactory.CreatePropertySetterWrapper(propInfo); Stopwatch watch5 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ ) setter4.Set(testObj, 123); watch5.Stop(); Console.WriteLine(watch5.Elapsed.ToString()); 测试结果如下: 测试结果表明:强类型的泛型委托的速度比Emit生成的Set委托要快,但是基于通用接口的方法调用由于多了一层包装就比Emit方案要略慢一点。 回到顶部 完整的属性优化方案 前面介绍了为属性赋值这类反射案例的优化方案,那么怎么优化读取属性的反射操作呢? 其实思路差不多: 1. 在泛型类中调用Delegate.CreateDelegate,得到一个Func, 2. 定义一个IGetValue接口,提供一个方法: object Get(object target); 3. 让泛型类实现IGetValue接口 4. 提供一个工厂方法实例化泛型类的实例。 相关代码如下: public interface IGetValue { object Get(object target); } public static class GetterSetterFactory { public static IGetValue CreatePropertyGetterWrapper(PropertyInfo propertyInfo) { if( propertyInfo == null ) throw new ArgumentNullException("propertyInfo"); if( propertyInfo.CanRead == false ) throw new InvalidOperationException("属性不支持读操作。"); MethodInfo mi = propertyInfo.GetGetMethod(true); if( mi.GetParameters().Length > 0 ) throw new NotSupportedException("不支持构造索引器属性的委托。"); Type instanceType = typeof(GetterWrapper).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType); return (IGetValue)Activator.CreateInstance(instanceType, propertyInfo); } } public class GetterWrapper : IGetValue { private Func _getter; public GetterWrapper(PropertyInfo propertyInfo) { if( propertyInfo == null ) throw new ArgumentNullException("propertyInfo"); if( propertyInfo.CanRead == false ) throw new InvalidOperationException("属性不支持读操作。"); MethodInfo m = propertyInfo.GetGetMethod(true); _getter = (Func)Delegate.CreateDelegate(typeof(Func), null, m); } public TValue GetValue(TTarget target) { return _getter(target); } object IGetValue.Get(object target) { return _getter((TTarget)target); } } 前面的代码优化了实例属性的反射读写性能问题,但是还有极少数时候我们还需要处理静态属性,那么我们还需要再定义二个泛型类来解决: public class StaticGetterWrapper : IGetValue { private Func _getter; // ............ } public class StaticSetterWrapper : ISetValue { private Action _setter; // ............ } 前面看到的工厂方法也要调整,完整代码如下: public static ISetValue CreatePropertySetterWrapper(PropertyInfo propertyInfo) { if( propertyInfo == null ) throw new ArgumentNullException("propertyInfo"); if( propertyInfo.CanWrite == false ) throw new NotSupportedException("属性不支持写操作。"); MethodInfo mi = propertyInfo.GetSetMethod(true); if( mi.GetParameters().Length > 1 ) throw new NotSupportedException("不支持构造索引器属性的委托。"); if( mi.IsStatic ) { Type instanceType = typeof(StaticSetterWrapper<>).MakeGenericType(propertyInfo.PropertyType); return (ISetValue)Activator.CreateInstance(instanceType, propertyInfo); } else { Type instanceType = typeof(SetterWrapper).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType); return (ISetValue)Activator.CreateInstance(instanceType, propertyInfo); } } 回到顶部 委托方案的后续问题 前面的代码解决了属性的读写问题,然而使用它们还很不方便:每次都要创建一个ISetValue接口的实例,再调用它的方法。 其实这也是委托方案共有的问题:我们需要为每个属性的读写操作分别创建不同的委托,而且委托太零散了。 如何将属性与创建好的委托关联起来呢?(创建委托也是需要时间的) 我想所有人都会想到用字典来保存。 是的,好像也只有这一种方法了。 为了提高性能,我改进了工厂类,缓存了包含委托的实例, 为了方便使用前面的方法,我提供了一些扩展方法: public static class GetterSetterFactory { private static readonly Hashtable s_getterDict = Hashtable.Synchronized(new Hashtable(10240)); private static readonly Hashtable s_setterDict = Hashtable.Synchronized(new Hashtable(10240)); internal static IGetValue GetPropertyGetterWrapper(PropertyInfo propertyInfo) { IGetValue property = (IGetValue)s_getterDict[propertyInfo]; if( property == null ) { property = CreatePropertyGetterWrapper(propertyInfo); s_getterDict[propertyInfo] = property; } return property; } internal static ISetValue GetPropertySetterWrapper(PropertyInfo propertyInfo) { ISetValue property = (ISetValue)s_setterDict[propertyInfo]; if( property == null ) { property = CreatePropertySetterWrapper(propertyInfo); s_setterDict[propertyInfo] = property; } return property; } } public static class PropertyExtensions { public static object FastGetValue(this PropertyInfo propertyInfo, object obj) { if( propertyInfo == null ) throw new ArgumentNullException("propertyInfo"); return GetterSetterFactory.GetPropertyGetterWrapper(propertyInfo).Get(obj); } public static void FastSetValue(this PropertyInfo propertyInfo, object obj, object value) { if( propertyInfo == null ) throw new ArgumentNullException("propertyInfo"); GetterSetterFactory.GetPropertySetterWrapper(propertyInfo).Set(obj, value); } } 说明:我在缓存的设计上并没有使用泛型Dictionary,而是使用了Hashtable。 我承认在简单的单线程测试中,Dictionary要略快于Hashtable 。 再来测试一下FastSetValue的性能吧,毕竟大多数时候我会使用这个扩展方法。 我又在测试代码中增加了一段: propInfo.FastSetValue(testObj, 123); Console.Write("FastSet花费时间:  "); Stopwatch watch6 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ ) propInfo.FastSetValue(testObj, 123); watch6.Stop(); Console.WriteLine(watch6.Elapsed.ToString()); 测试结果如下: 测试结果表明:虽然通用接口ISetValue将反射性能优化了37倍,但是最终的FastSetValue将这个数字减少到还不到7倍(在CLR4中还不到5倍)。 看到这个结果您是否也比较郁闷:优化了几十倍的结果,最后却丢了大头,只得到一个零头! 中间那30倍的时间是哪里消耗了? 1. Hashtable的查找时间。 2. 代码的执行路径变长了。 代码的执行路径变长了,我想所有人应该都能接受:为了简化调用并配合缓存一起工作,代码的执行路径确实变长了。 Hashtable的查找时间应该很快吧? 您是不是也这样想呢? 为了看看Hashtable的查找时间,我又加了一点测试代码: Hashtable table = new Hashtable(); table[propInfo] = new object(); Console.Write("Hashtable花费时间: "); Stopwatch watch7 = Stopwatch.StartNew(); for( int i = 0; i < count; i++ ) { object val = table[propInfo]; } watch7.Stop(); Console.WriteLine(watch7.Elapsed.ToString()); 现在运行测试代码的结果如下: 确实,大部分时间消耗在Hashtable的查找上! 回到顶部 缓存的线程并发问题 集合不仅仅只有查找开销,在多线程环境中,我们还要考虑并发性。 看到许多人做性能测试时,总是喜欢写个控制台程序,然后再来个for循环,执行多少万次! 我认为 这样的结果只能反映代码在单线程环境下的性能,在多线程下,结果可能会有较大的差别, 当然了,多线程测试的确很复杂,也很难得到准确的数字。 但是我们的设计不能不考虑多线程下的并发问题。 虽然我也在单线程环境下测试过Dictionary的性能,的确要比Hashtable略好点。 但是MSDN上对Dictionary的线程安全的描述是这样的: 此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。 只要不修改该集合,Dictionary)>) 就可以同时支持多个阅读器。即便如此,从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。当出现枚举与写访问互相争用这种极少发生的情况时,必须在整个枚举过程中锁定集合。若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步。 而MSDN对Hashtable的线程安全的描述却是: Hashtable 是线程安全的,可由多个读取器线程和一个写入线程使用。多线程使用时,如果只有一个线程执行写入(更新)操作,则它是线程安全的,从而允许进行无锁定的读取(若编写器序列化为 Hashtable)。若要支持多个编写器,如果没有任何线程在读取 Hashtable 对象,则对 Hashtable 的所有操作都必须通过 Synchronized 方法返回的包装完成。 从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。 显然,二个集合都不能完全支持多线程的并发读写。 虽然Hashtable提供同步包装的线程安全版本,但是内部还是在使用锁来保证同步的! 没办法,在多线程环境中,任何复杂数据结构都有线程安全问题。 如何保证集合在并发操作中数据的同步呢? 是lock还是ReaderWriterLock? 显然前者的实现较为简单,所以它成了绝大多数人的首选。 在.net4中,ConcurrentDictionary是另一个新的首选方法。 由于Dictionary只支持并发的读操作,所以只要涉及到写操作,它就不安全了, 因此最安全地做法也只好在 读和写 操作上都加lock,否则就不安全了。 而Hashtable则不同,它的内部数据结构支持一个线程写入的同时允许多个线程并发读取,所以只要在写操作上加lock就可以实现线程同步, Hashtable的线程安全版本也就是这样实现的。 这也是我选择Hashtable的原因。 回到顶部 小结 在这篇博客中,我演示了二种不同的反射优化方法: 1. 基于Emit的动态生成符合委托签名的IL代码。 2. 使用Delegate.CreateDelegate直接创建委托。 这是二种截然不同的思路: 1. Emit方法,需要先定义一个委托签名,然后生成符合委托签名的IL代码。 2. CreateDelegate可以直接生成委托,但需要借用泛型类解决委托的类型参数问题,最后为了能通用,需要以接口方式调用强类型委托。 虽然我们可以使用任何一种方法得到委托,但是我们需要操作多少属性呢? 显然这是一个无解的问题,我们只能为每个属性创建不同的委托。所以新的问题也随之产生: 我们如何保存那些委托?如何让它们与属性关联起来? Dictionary或者Hashtable或许是较好的选择(.net 3.5),然而,这些对象内部的数据结构在查找时,并不是零成本, 它们会消耗优化的大部分成果。 另外,在实现缓存委托的问题上,并发问题也是值得我们考虑的,不高效的并发设计还会让优化的成果继续丢失! 所以,我认为优化反射是个复杂问题,至少有3个环节是需要考虑的: 1. 如何得到委托? 2. 如何缓存委托? 3. 如何支持并发? 得到委托是容易的,但它只是一个开始! 回到顶部 招聘信息 公司需要若干名 .net 方面的高级开发人员,要求熟悉以下技术领域: 1. .net framework 2. ASP.NET 3. SQL SERVER (T-SQL, SP) 4. JavaScript, jQuery 5. CSS 6. 常见的设计模式。 说明: 1. 公司名称:明源软件 2. 工作地点:武汉。 3. 我只是负责【推荐】,具体细节请发邮件给我:liqifeng0503@163.com 4. 咨询招聘相关的疑问也请发邮件给我,评论中的疑问一律不回复!

转载于:https://www.cnblogs.com/ppcompany/articles/2916602.html

优化反射性能的总结(上)相关推荐

  1. 41 | 案例篇:如何优化 NAT 性能?(上)

    上一节,探究了网络延迟增大问题的分析方法,并通过一个案例,掌握了如何用 hping3.tcpdump.Wireshark.strace 等工具,来排查和定位问题的根源. 简单回顾一下,网络延迟是最核心 ...

  2. 推理芯片的性能建立在优化的存储子系统设计上

    推理芯片的性能建立在优化的存储子系统设计上 Inference chip performance builds on optimized memory subsystem design 好的推断芯片可 ...

  3. 如何优化WebAPP性能:从五个层面上彻底优化前端项目性能

    如何优化WebAPP性能:从五个层面上彻底优化前端项目性能 资源层面上的优化 该项措施可以帮助我们优化 FP.FCP.LCP 指标. 压缩文件.使用 Tree-shaking 删除无用代码 服务端配置 ...

  4. 如何优化WebAPP性能:从四个层面上彻底优化前端项目性能

    如何优化WebAPP性能:从四个层面上彻底优化前端项目性能 资源层面上的优化 该项措施可以帮助我们优化 FP.FCP.LCP 指标. 压缩文件.使用 Tree-shaking 删除无用代码 服务端配置 ...

  5. gpu服务器性能测试用例,多目标测试用例预优化方法及其在GPU上的应用研究

    摘要: 在软件回归测试中,由于客观因素(例如时间.成本等)的制约,庞大的测试用例集不可能全部被执行.测试用例预优化是一种通过调整测试用例的执行顺序来优化回归测试过程的技术.传统的测试用例预优化技术主要 ...

  6. c 与java 反射性能_谈谈Java 反射的快慢

    [相关学习推荐:java基础教程] 反射到底是好是坏 说到Java 中的反射,初学者在刚刚接触到反射的各种高级特性时,往往表示十分兴奋,甚至会在一些不需要使用反射的场景中强行使用反射来「炫技」.而经验 ...

  7. lambda表达式优化反射_反射选择器表达式

    lambda表达式优化反射 Java :: Geci是一个在单元测试期间运行的代码生成器. 如果生成的代码适合源代码的实际版本,则测试不会失败. 如果需要进行任何修改,则测试将修改源代码并失败. 例如 ...

  8. 轻量级渲染管线_轻量级渲染管道:优化实时性能

    轻量级渲染管线 Update: LWRP is now out of preview and production-ready. Get more info in our 2019.1 release ...

  9. java 提高反射性能_java反射性能

    项目中用到了java的反射,可以大大减少代码量.但是反射的性能却不容乐观,做了个简单的测试,如下. public void noreflect() { Person p = new Person(); ...

最新文章

  1. 【Android 应用开发】Android之Bluetooth编程
  2. 给Ubuntu添加清华的软件源
  3. java访问jar中的资源问题代码
  4. 我的mongoose代码备份
  5. win7关机快捷键_电脑快捷键大全(上)
  6. 【牛客 - 301哈尔滨理工大学软件与微电子学院第八届程序设计竞赛同步赛(高年级)】小乐乐搭积木(状压dp)
  7. 华为机试HJ57:高精度整数加法
  8. android 颜色测试,颜色 大师 (Color Sense Master) - 颜色测试
  9. 腕能助手android9,腕间应用助手(com.gmf.watchapkassistant) - 1.7 - 应用 - 酷安
  10. 微信小程序(一):微信小程序与服务器的简单链接
  11. php百度蜘蛛劫持,技术教程:php伪造ip访问一个网站,可以伪造百度蜘蛛ip
  12. 3、一层、二层、交换机原理、Cisco软件及命令
  13. 华杉讲透《孙子兵法》阅读有感(一)
  14. raspberry pi pico|在raspberry pi pico上玩nes游戏(1)(开源树莓派pico NES模拟器)-效果演示及介绍
  15. thinkphp3.2 七牛 bad token
  16. 【文件监控】之一:理解 ReadDirectoryChangesW part1
  17. JAVA后台对接苹果APNS(VOIP)实现推送
  18. 台式计算机设置事带麦耳机,win10系统的台式电脑耳机(带麦克风的那种)怎么设置?...
  19. 传真存储转发工作过程
  20. matlab 秒数转换时分秒,将秒转换为天时分秒格式字符串(如是0天0时0分就显示秒)...

热门文章

  1. 伯乐发卡系统源码 可用
  2. PHP开发的爱情盲盒交友系统网站源码
  3. 志汇超级外卖餐饮 5.9.2 + 超级跑腿 v1.9.5 打包下载 小程序模块
  4. java版苹果免签源码超级签名免签源码
  5. SpringCloud Hoxton版微服务-RestTempalte + @LoadBlanced 实现负载均衡
  6. VMware虚拟机的联网(图)
  7. MongoDB 教程五: MongoDB固定集合和性能优化 (索引Indexes, 优化器, 慢查询profile)
  8. Bootstrap 3 移除输入框聚焦线 How to remove border (outline) around text/input boxes?
  9. moodle升级完整过程
  10. javascript实现代码高亮