C# 高性能对象映射(表达式树实现)
前言
上篇简单实现了对象映射,针对数组,集合,嵌套类并没有给出实现,这一篇继续完善细节。
开源对象映射类库映射分析
1.AutoMapper
实现原理:主要通过表达式树Api 实现对象映射
优点: .net功能最全的对象映射类库。
缺点:当出现复杂类型和嵌套类型时性能直线下降,甚至不如序列化快
2.TinyMapper
实现原理:主要通过Emit 实现对象映射
优点:速度非常快。在处理复杂类型和嵌套类型性能也很好
缺点:相对AutoMapper功能上少一些,Emit的实现方案,在代码阅读和调试上相对比较麻烦,而表达式树直接观察 DebugView中生成的代码结构便可知道问题所在
3. 本文的对象映射库
针对AutoMapper 处理复杂类型和嵌套类型时性能非常差的情况,自己实现一个表达式树版的高性能方案
此篇记录下实现对象映射库的过程
构造测试类
public class TestA
{
public int Id { get; set; }
public string Name { get; set; } public TestC TestClass { get; set; } public IEnumerable<TestC> TestLists { get; set; }
} public class TestB
{
public int Id { get; set; }
public string Name { get; set; } public TestD TestClass { get; set; } public TestD[] TestLists { get; set; }
} public class TestC
{
public int Id { get; set; }
public string Name { get; set; } public TestC SelfClass { get; set; }
} public class TestD
{
public int Id { get; set; }
public string Name { get; set; } public TestD SelfClass { get; set; }
}
1.初步实现
利用表达式树给属性赋值 利用 Expresstion.New构造 var b=new B{};
private static Func<TSource, TTarget> GetMap<TSource, TTarget>()
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget); //构造 p=>
var parameterExpression = Expression.Parameter(sourceType, "p"); //构造 p=>new TTarget{ Id=p.Id,Name=p.Name };
var memberBindingList = new List<MemberBinding>();
foreach (var sourceItem in sourceType.GetProperties())
{
var targetItem = targetType.GetProperty(sourceItem.Name);
if (targetItem == null || sourceItem.PropertyType != targetItem.PropertyType)
continue; var property = Expression.Property(parameterExpression, sourceItem);
var memberBinding = Expression.Bind(targetItem, property);
memberBindingList.Add(memberBinding);
}
var memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList); var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression ); Console.WriteLine(lambda);
return lambda.Compile();
}
调用如下
class Program
{
static void Main(string[] args)
{
var testA = new TestA { Id = , Name = "张三" };
var func = Map<TestA, TestB>();
TestB testB = func(testA);
Console.WriteLine($"testB.Id={testB.Id},testB.Name={testB.Name}");
Console.ReadLine();
}
}
输出结果
总结:此方法需要调用前需要手动编译下,然后再调用委托没有缓存委托,相对麻烦。
2.缓存实现
利用静态泛型类缓存泛型委托
public class DataMapper<TSource, TTarget>
{
private static Func<TSource, TTarget> MapFunc { get; set; } public static TTarget Map(TSource source)
{
if (MapFunc == null)
MapFunc = GetMap();//方法在上边
return MapFunc(source);
} }
调用方法
static void Main(string[] args)
{
var testA = new TestA { Id = , Name = "张三" };
TestB testB = DataMapper<TestA, TestB>.Map(testA);//委托不存在时自动生成,存在时调用静态缓存 Console.WriteLine($"testB.Id={testB.Id},testB.Name={testB.Name}");
Console.ReadLine();
}
输出结果
总结:引入静态泛型类能解决泛型委托缓存提高性能,但是有两个问题 1.当传入参数为null时 则会抛出空引用异常 2.出现复杂类型上述方法便不能满足了
3.解决参数为空值和复杂类型的问题
首先先用常规代码实现下带有复杂类型赋值的情况
public TestB GetTestB(TestA testA)
{
TestB testB;
if (testA != null)
{
testB = new TestB();
testB.Id = testA.Id;
testB.Name = testA.Name;
if (testA.TestClass != null)
{
testB.TestClass = new TestD();
testB.TestClass.Id = testA.TestClass.Id;
testB.TestClass.Name = testA.TestClass.Name;
}
}
else
{
testB = null;
}
return testB;
}
将上面的代码翻译成表达式树
private static Func<TSource, TTarget> GetMap()
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget); //Func委托传入变量
var parameter = Expression.Parameter(sourceType); //声明一个返回值变量
var variable = Expression.Variable(targetType);
//创建一个if条件表达式
var test = Expression.NotEqual(parameter, Expression.Constant(null, sourceType));// p==null;
var ifTrue = Expression.Block(GetExpression(parameter, variable, sourceType, targetType));
var IfThen = Expression.IfThen(test, ifTrue); //构造代码块
var block = Expression.Block(new[] { variable }, parameter, IfThen, variable); var lambda = Expression.Lambda<Func<TSource, TTarget>>(block, parameter);
return lambda.Compile();
} private static List<Expression> GetExpression(Expression parameter, Expression variable, Type sourceType, Type targetType)
{
//创建一个表达式集合
var expressions = new List<Expression>(); expressions.Add(Expression.Assign(variable, Expression.MemberInit(Expression.New(targetType)))); foreach (var targetItem in targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite))
{
var sourceItem = sourceType.GetProperty(targetItem.Name); //判断实体的读写权限
if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
continue; var sourceProperty = Expression.Property(parameter, sourceItem);
var targetProperty = Expression.Property(variable, targetItem); //判断都是class 且类型不相同时
if (targetItem.PropertyType.IsClass && sourceItem.PropertyType.IsClass && targetItem.PropertyType != sourceItem.PropertyType)
{
if (targetItem.PropertyType != targetType)//不处理嵌套循环的情况
{
//由于类型是class 所以默认值是null
var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceItem.PropertyType)); var itemExpressions = GetExpression(sourceProperty, targetProperty, sourceItem.PropertyType, targetItem.PropertyType);
var ifTrueItem = Expression.Block(itemExpressions); var IfThenItem = Expression.IfThen(testItem, ifTrueItem);
expressions.Add(IfThenItem); continue;
}
} //目标值类型时 且两者类型不一致时跳过
if (targetItem.PropertyType != sourceItem.PropertyType)
continue; expressions.Add(Expression.Assign(targetProperty, sourceProperty));
} return expressions;
}
总结:此方案,运用 Expression.IfThen(testItem, ifTrueItem) 判断空值问题,通过递归调用 GetExpression()方法,处理复杂类型。 但是。。。针对嵌套类仍然不能解决。因为表达式树是在实际调用方法之前就生成的,在没有实际的
参数值传入之前,生成的表达式是不知道有多少层级的。有个比较low的方案是,预先设定嵌套层级为10层,然后生成一个有10层 if(P!=null) 的判断。如果传入的参数层级超过10层了呢,就得手动调整生成的树,此方案也否决。
最后得出的结论只能在表达式中动态调用方法。
4.最终版本
通过动态调用方法解决嵌套类,代码如下
using static System.Linq.Expressions.Expression;
public static class Mapper<TSource, TTarget> where TSource : class where TTarget : class
{
public readonly static Func<TSource, TTarget> MapFunc = GetMapFunc(); public readonly static Action<TSource, TTarget> MapAction = GetMapAction(); /// <summary>
/// 将对象TSource转换为TTarget
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static TTarget Map(TSource source) => MapFunc(source); public static List<TTarget> MapList(IEnumerable<TSource> sources)=> sources.Select(MapFunc).ToList(); /// <summary>
/// 将对象TSource的值赋给给TTarget
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
public static void Map(TSource source, TTarget target) => MapAction(source, target); private static Func<TSource, TTarget> GetMapFunc()
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
//Func委托传入变量
var parameter = Parameter(sourceType, "p"); var memberBindings = new List<MemberBinding>();
var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
foreach (var targetItem in targetTypes)
{
var sourceItem = sourceType.GetProperty(targetItem.Name); //判断实体的读写权限
if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
continue; //标注NotMapped特性的属性忽略转换
if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null)
continue; var sourceProperty = Property(parameter, sourceItem); //当非值类型且类型不相同时
if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType)
{
//判断都是(非泛型)class
if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
!sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
{
var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
memberBindings.Add(Bind(targetItem, expression));
} //集合数组类型的转换
if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
{
var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
memberBindings.Add(Bind(targetItem, expression));
} continue;
} if (targetItem.PropertyType != sourceItem.PropertyType)
continue; memberBindings.Add(Bind(targetItem, sourceProperty));
} //创建一个if条件表达式
var test = NotEqual(parameter, Constant(null, sourceType));// p==null;
var ifTrue = MemberInit(New(targetType), memberBindings);
var condition = Condition(test, ifTrue, Constant(null, targetType)); var lambda = Lambda<Func<TSource, TTarget>>(condition, parameter);
return lambda.Compile();
} /// <summary>
/// 类型是clas时赋值
/// </summary>
/// <param name="sourceProperty"></param>
/// <param name="targetProperty"></param>
/// <param name="sourceType"></param>
/// <param name="targetType"></param>
/// <returns></returns>
private static Expression GetClassExpression(Expression sourceProperty, Type sourceType, Type targetType)
{
//条件p.Item!=null
var testItem = NotEqual(sourceProperty, Constant(null, sourceType)); //构造回调 Mapper<TSource, TTarget>.Map()
var mapperType = typeof(Mapper<,>).MakeGenericType(sourceType, targetType);
var iftrue = Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty); var conditionItem = Condition(testItem, iftrue, Constant(null, targetType)); return conditionItem;
} /// <summary>
/// 类型为集合时赋值
/// </summary>
/// <param name="sourceProperty"></param>
/// <param name="targetProperty"></param>
/// <param name="sourceType"></param>
/// <param name="targetType"></param>
/// <returns></returns>
private static Expression GetListExpression(Expression sourceProperty, Type sourceType, Type targetType)
{
//条件p.Item!=null
var testItem = NotEqual(sourceProperty, Constant(null, sourceType)); //构造回调 Mapper<TSource, TTarget>.MapList()
var sourceArg = sourceType.IsArray ? sourceType.GetElementType() : sourceType.GetGenericArguments()[];
var targetArg = targetType.IsArray ? targetType.GetElementType() : targetType.GetGenericArguments()[];
var mapperType = typeof(Mapper<,>).MakeGenericType(sourceArg, targetArg); var mapperExecMap = Call(mapperType.GetMethod(nameof(MapList), new[] { sourceType }), sourceProperty); Expression iftrue;
if (targetType == mapperExecMap.Type)
{
iftrue = mapperExecMap;
}
else if (targetType.IsArray)//数组类型调用ToArray()方法
{
iftrue = Call(mapperExecMap, mapperExecMap.Type.GetMethod("ToArray"));
}
else if (typeof(IDictionary).IsAssignableFrom(targetType))
{
iftrue = Constant(null, targetType);//字典类型不转换
}
else
{
iftrue = Convert(mapperExecMap, targetType);
} var conditionItem = Condition(testItem, iftrue, Constant(null, targetType)); return conditionItem;
} private static Action<TSource, TTarget> GetMapAction()
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
//Func委托传入变量
var sourceParameter = Parameter(sourceType, "p"); var targetParameter = Parameter(targetType, "t"); //创建一个表达式集合
var expressions = new List<Expression>(); var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
foreach (var targetItem in targetTypes)
{
var sourceItem = sourceType.GetProperty(targetItem.Name); //判断实体的读写权限
if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
continue; //标注NotMapped特性的属性忽略转换
if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null)
continue; var sourceProperty = Property(sourceParameter, sourceItem);
var targetProperty = Property(targetParameter, targetItem); //当非值类型且类型不相同时
if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType)
{
//判断都是(非泛型)class
if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
!sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
{
var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
expressions.Add(Assign(targetProperty, expression));
} //集合数组类型的转换
if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
{
var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
expressions.Add(Assign(targetProperty, expression));
} continue;
} if (targetItem.PropertyType != sourceItem.PropertyType)
continue; expressions.Add(Assign(targetProperty, sourceProperty));
} //当Target!=null判断source是否为空
var testSource = NotEqual(sourceParameter, Constant(null, sourceType));
var ifTrueSource = Block(expressions);
var conditionSource = IfThen(testSource, ifTrueSource); //判断target是否为空
var testTarget = NotEqual(targetParameter, Constant(null, targetType));
var conditionTarget = IfThen(testTarget, conditionSource); var lambda = Lambda<Action<TSource, TTarget>>(conditionTarget, sourceParameter, targetParameter);
return lambda.Compile();
}
}
输出的 表达式
格式化后
p => IIF((p != null),
new TestB()
{
Id = p.Id,
Name = p.Name,
TestClass = IIF(
(p.TestClass != null),
Map(p.TestClass),
null
),
TestLists = IIF(
(p.TestLists != null),
MapList(p.TestLists).ToArray(),
null
)
},
null)
说明 Map(p.TestClass) MapList(p.TestLists).ToArray(), 完整的信息为 Mapper<TestC,TestD>.Map() Mapper<TestC,TestD>.MapList()
总结:解决嵌套类的核心代码
101 //构造回调 Mapper<TSource, TTarget>.Map()
102 var mapperType = typeof(DataMapper<,>).MakeGenericType(sourceType, targetType);
103 var mapperExecMap = Expression.Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty);
利用Expression.Call 根据参数类型动态生成 对象映射的表达式
性能测试
写了这么多最终目的还是为了解决性能问题,下面将对比下性能
1.测试类
public static class MapperTest
{
//执行次数
public static int Count = ; //简单类型
public static void Nomal()
{
Console.WriteLine($"******************简单类型:{Count / 10000}万次执行时间*****************");
var model = new TestA
{
Id =,
Name = "张三",
}; //计时
var sw = Stopwatch.StartNew();
for (int i = ; i < Count; i++)
{
if (model != null)
{
var b = new TestB
{
Id = model.Id,
Name = model.Name,
};
}
}
sw.Stop();
Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms"); Exec(model);
} //复杂类型
public static void Complex()
{
Console.WriteLine($"********************复杂类型:{Count / 10000}万次执行时间*********************");
var model = new TestA
{
Id = ,
Name = "张三",
TestClass = new TestC
{
Id = ,
Name = "lisi",
},
}; //计时
var sw = Stopwatch.StartNew();
for (int i = ; i < Count; i++)
{ if (model != null)
{
var b = new TestB
{
Id = model.Id,
Name = model.Name,
};
if (model.TestClass != null)
{
b.TestClass = new TestD
{
Id = i,
Name = "lisi",
};
}
}
}
sw.Stop();
Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms");
Exec(model);
} //嵌套类型
public static void Nest()
{
Console.WriteLine($"*****************嵌套类型:{Count / 10000}万次执行时间*************************");
var model = new TestA
{
Id = ,
Name = "张三",
TestClass = new TestC
{
Id = ,
Name = "lisi",
SelfClass = new TestC
{
Id = ,
Name = "lisi",
SelfClass = new TestC
{
Id = ,
Name = "lisi",
SelfClass = new TestC
{
Id = ,
Name = "lisi",
},
},
},
},
};
//计时
var item = model;
var sw = Stopwatch.StartNew();
for (int i = ; i < Count; i++)
{
//这里每一步需要做非空判断的,书写太麻烦省去了
if (model != null)
{
var b = new TestB
{
Id = model.Id,
Name = model.Name,
TestClass = new TestD
{
Id = model.TestClass.Id,
Name = model.TestClass.Name,
SelfClass = new TestD
{
Id = model.TestClass.SelfClass.Id,
Name = model.TestClass.SelfClass.Name,
SelfClass = new TestD
{
Id = model.TestClass.SelfClass.SelfClass.Id,
Name = model.TestClass.SelfClass.SelfClass.Name,
SelfClass = new TestD
{
Id = model.TestClass.SelfClass.SelfClass.SelfClass.Id,
Name = model.TestClass.SelfClass.SelfClass.SelfClass.Name,
},
},
},
},
};
}
}
sw.Stop();
Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms"); Exec(model);
} //集合
public static void List()
{
Console.WriteLine($"********************集合类型:{Count/10000}万次执行时间***************************"); var model = new TestA
{
Id = ,
Name = "张三",
TestLists = new List<TestC> {
new TestC{
Id = ,
Name = "张三",
},
new TestC{
Id = -,
Name = "张三",
},
}
}; //计时
var sw = Stopwatch.StartNew();
for (int i = ; i < Count; i++)
{
var item = model;
if (item != null)
{
var b = new TestB
{
Id = item.Id,
Name = item.Name,
TestLists = new List<TestD> {
new TestD{
Id = item.Id,
Name = item.Name,
},
new TestD{
Id = -item.Id,
Name = item.Name,
},
}.ToArray()
};
}
}
sw.Stop();
Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms"); Exec(model);
} public static void Exec(TestA model)
{
//表达式
Mapper<TestA, TestB>.Map(model);
var sw = Stopwatch.StartNew();
for (int i = ; i < Count; i++)
{
var b = Mapper<TestA, TestB>.Map(model);
}
sw.Stop();
Console.WriteLine($"表达式的时间:{sw.ElapsedMilliseconds}ms"); //AutoMapper
sw.Restart();
for (int i = ; i < Count; i++)
{
var b = AutoMapper.Mapper.Map<TestA, TestB>(model);
}
sw.Stop();
Console.WriteLine($"AutoMapper时间:{sw.ElapsedMilliseconds}ms"); //TinyMapper
sw.Restart();
for (int i = ; i < Count; i++)
{
var b = TinyMapper.Map<TestA, TestB>(model);
}
sw.Stop();
Console.WriteLine($"TinyMapper时间:{sw.ElapsedMilliseconds}ms");
}
}
2.调用测试
static void Main(string[] args)
{
AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<TestA, TestB>());
TinyMapper.Bind<TestA, TestB>();
Mapper<TestA, TestB>.Map(new TestA()); MapperTest.Count = ;
MapperTest.Nomal();
MapperTest.Complex();
MapperTest.Nest();
MapperTest.List(); MapperTest.Count = ;
MapperTest.Nomal();
MapperTest.Complex();
MapperTest.Nest();
MapperTest.List(); MapperTest.Count = ;
MapperTest.Nomal();
MapperTest.Complex();
MapperTest.Nest();
MapperTest.List(); MapperTest.Count = ;
MapperTest.Nomal();
MapperTest.Complex();
MapperTest.Nest();
MapperTest.List(); Console.WriteLine($"------------结束--------------------");
Console.ReadLine();
}
3.结果
1万次
10万次
100万次
1000万次
上图结果AutoMapper 在非简单类型的转换上比其他方案有50倍以上的差距,几乎就跟反射的结果一样。
作者:costyuan
GitHub地址:https://github.com/bieyuan/.net-core-DTO
地址:http://www.cnblogs.com/castyuan/p/9324088.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出,谢谢!
C# 高性能对象映射(表达式树实现)的更多相关文章
- [非专业翻译] 高性能对象映射框架 - Mapster
[非专业翻译] 高性能对象映射框架 - Mapster 系列介绍 [非专业翻译] 是对没有中文文档进行翻译的系列博客,文章由机翻和译者自己理解构成,和原文相比有所有不通,但意思基本一致. 因个人能力有 ...
- C# 高性能对象映射
1.之前在使用AutoMapper 框架感觉用着比较不够灵活,而且主要通过表达式树Api 实现对象映射 ,写着比较讨厌,当出现复杂类型和嵌套类型时性能直线下降,甚至不如序列化快. 2.针对AutoMa ...
- .net core 高性能对象映射
关于对象转换已经有不少轮子(AutoMapper,TinyMapper) .出于项目需要,手动造一个简单轮子.先贴代码 1.采用静态泛型类缓存,避免了拆箱装箱操作. 2.对于转换对象中有,字段名一样但 ...
- C# 快速高效率复制对象另一种方式 表达式树
1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } publi ...
- 【转】对象克隆(C# 快速高效率复制对象另一种方式 表达式树)
原文地址:https://www.cnblogs.com/lsgsanxiao/p/8205096.html 1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: p ...
- 对象克隆(C# 快速高效率复制对象另一种方式 表达式树转)
1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } publi ...
- 【转载】C# 快速高效率复制对象另一种方式 表达式树
1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } publi ...
- C#3.0新特性:隐式类型、扩展方法、自动实现属性,对象/集合初始值设定、匿名类型、Lambda,Linq,表达式树、可选参数与命名参数
一.隐式类型var 从 Visual C# 3.0 开始,在方法范围中声明的变量可以具有隐式类型var.隐式类型可以替代任何类型,编译器自动推断类型. 1.var类型的局部变量必须赋予初始值,包括匿名 ...
- 使用C#表达式树为两个对象的相同属性赋值
//缓存表达式树 private static Dictionary<string, object> objCache = new Dictionary<string, object ...
随机推荐
- Vue.js:计算属性
ylbtech-Vue.js:计算属性 1.返回顶部 1. Vue.js 计算属性 计算属性关键词: computed. 计算属性在处理一些复杂逻辑时是很有用的. 可以看下以下反转字符串的例子: 实例 ...
- 解决JAVA_HOME nor the JRE_HOME environment variable is defined
从别的地方复制了一个tomcat, 启动后一闪即退, 使用记事本打开 startup.bat文件, 在文件底部修改, 并追加如下内容 call "%EXECUTABLE%" run ...
- Linux学习笔记 -- Shell 数组
定义 在Shell的世界里,我们只能定义一维数组. 定义数组的时候不需要指定长度,数组的下标从0开始; Shell 数组用括号来表示,元素用"空格"符号分割开,语法格式如下: sh ...
- js中的Math
js中的Math Math.round 取最接近的整数 Math.round(-2.7) // -3 Math.ceil 向上取整 Math.ceil(1.1) // 2 Math.floor 向下取 ...
- PHP开发环境正确的错误信息处理
正确记录配置 php.ini display_errors = On error_reporting = E_ALL log_errors = On error_log = F:/data/php/e ...
- Python __getattribute__ vs __getattr__
# 例子在原来的基础上简化了一下,排除依赖和干扰,详细参见原项目 class UrlGenerator(object): def __init__(self, root_url): self.url ...
- 利用jquery mobiscroll插件选择日期、select、treeList的具体运用
体验更优排版请移步原文:http://blog.kwin.wang/programming/jquery-mobiscroll-select-treeList.html mobiscroll是个很好用 ...
- Codeforce 1004C
Description Since Sonya is interested in robotics too, she decided to construct robots that will rea ...
- 手动去除uTorrent中广告的步骤(V3.4.9依然有效)
1.开打utorrent,依次点击选项->设置->高级. 在“高级”界面中,你会看到“过滤器”,在“过滤器”右侧的框中输入“offers”. 这时会在下面框中看到“offers.left_ ...
- 【原创】5. MYSQL++ mysql_type_info类型
该类型是SQLBuffer的灵魂,它用来表示从SQL TYPE到C++ TYPE的相互转变.该类型被定义在type_info.h中.在这个头文件中,其实定义了三个类型,其中前两个都是在mysql_ty ...