StackExchange.Redis 之 hash 类型示例
StackExchange.Redis 的组件封装示例网上有很多,自行百度搜索即可。
这里只演示如何使用Hash类型操作数据:
// 在 hash 中存入或修改一个值 并设置order_hashkey有效期1分钟,过期自动删除;null为不过期
stopwatch.Start();
var isok = RedisCacheHelper.Instance.HashSet("order_hashkey", "order_hashfield", "", TimeSpan.FromMinutes());
stopwatch.Stop();
Console.WriteLine("在hash中存入一个值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
//判断该字段是否存在 hash 中
var isexists = RedisCacheHelper.Instance.HashExists("order_hashkey", "order_hashfield");
Console.WriteLine("判断该字段是否存在hash中:" + isexists); //返回 True 或 False
//从hash中移除指定字段
var isdel = RedisCacheHelper.Instance.HashDelete("order_hashkey", "order_hashfield");
Console.WriteLine("从hash中移除指定字段:" + isdel); //返回 True 或 False
//从hash中递增 默认按1递增
var getincre = RedisCacheHelper.Instance.HashIncrement("order_hashkey", "order_hashfield");
Console.WriteLine("从hash中递增:" + getincre); //返递增后的值 11 //从hash中递减 默认按1递减
var getdecre = RedisCacheHelper.Instance.HashDecrement("order_hashkey", "order_hashfield");
Console.WriteLine("从hash中递减:" + getdecre); //返递减后的值 10
//保存一个字符串类型集合
string[] strarray = { "", "", "", "", "" };
RedisCacheHelper.Instance.HashSetList("orderlist_hashkey", strarray, m => { return "hashfield_" + m.ToString(); }, new TimeSpan(, , )); //从数据库获取10条数据
stopwatch.Start();
var getlist = TestRedis.GetOrderFormAll();
stopwatch.Stop();
Console.WriteLine("从数据库获取10条数据消耗时间:" + stopwatch.ElapsedMilliseconds.ToString()); //保存多个对象集合 非序列化
stopwatch.Start();
RedisCacheHelper.Instance.HashSetObjectList("objlist_hashkey", getlist, r => { return r.ordercode.ToString(); }, );
stopwatch.Stop();
Console.WriteLine("将10条数据存入HashRedis 消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
//保存或修改一个hash对象(序列化) 对key设置过期时间有时候起作用,有时候不起作用
stopwatch.Start();
RedisCacheHelper.Instance.HashSet<OrderForm>("orderform_hashkey", "orderform_hashfield", getlist.FirstOrDefault(), new TimeSpan(, , ), );
stopwatch.Stop();
Console.WriteLine("保存或修改一个hash对象(序列化)消耗时间:" + stopwatch.ElapsedMilliseconds.ToString()); //保存Hash对象集合 序列化 对key设置过期时间可以起作用
stopwatch.Start();
RedisCacheHelper.Instance.HashSet<OrderForm>("orderformlist_hashkey", getlist, m=> { return m.ordercode.ToString(); }, new TimeSpan(, , ), );
stopwatch.Stop();
Console.WriteLine("保存Hash对象集合(序列化)消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
//从Hash Redis获取某一条订单信息 反序列化
stopwatch.Start();
var getorderinfo = RedisCacheHelper.Instance.HashGet<OrderForm>("orderform_hashkey", "orderform_hashfield", );
stopwatch.Stop();
Console.WriteLine("从Hash Redis获取某一条订单信息消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
Console.WriteLine(getorderinfo.ordercode);
Console.WriteLine(getorderinfo.Area);
Console.WriteLine(getorderinfo.City);
Console.WriteLine(getorderinfo.Province);
Console.WriteLine(getorderinfo.PostTime);
//根据hashkey 获取所有的值 反序列化
stopwatch.Start();
var getorderlist = RedisCacheHelper.Instance.HashGetAll<OrderForm>("orderformlist_hashkey", );
stopwatch.Stop();
Console.WriteLine("根据hashkey 获取所有的值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getorderlist)
{
Console.WriteLine("获取反序列化得到的ordercode: " + item.ordercode);
}
//根据hashkey获取单个字段hashField的值
stopwatch.Start();
var getvalue = RedisCacheHelper.Instance.HashGet("objlist_hashkey1", "City", );
stopwatch.Stop();
Console.WriteLine("获取单个字段hashField的值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
Console.WriteLine("获取City: " + getvalue); Console.WriteLine("------------------");
Console.WriteLine(); //获取多个字段hashField的值
List<RedisValue> fieldlist = new List<RedisValue>() { "ordercode", "Province", "City", "Area" };
stopwatch.Start();
var getlist = RedisCacheHelper.Instance.HashGet("objlist_hashkey1", fieldlist.ToArray(), );
stopwatch.Stop();
Console.WriteLine("获取多个字段hashField的值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getlist)
{
Console.WriteLine("获取到的值: " + item);
}
//根据hashkey返回所有的HashFiled
stopwatch.Start();
var getkeylist = RedisCacheHelper.Instance.HashKeys("objlist_hashkey1", );
stopwatch.Stop();
Console.WriteLine("消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getkeylist)
{
Console.WriteLine("获取到的HashFiled: " + item);
} //根据hashkey返回所有HashFiled值
stopwatch.Start();
var getvaluelist = RedisCacheHelper.Instance.HashValues("objlist_hashkey1", );
stopwatch.Stop();
Console.WriteLine("消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getvaluelist)
{
Console.WriteLine("获取到的HashFiled值: " + item);
}
//模拟并发场景 使用HashDecrement递减功能 使用分布式锁,但是线程线程排队处理,有可能照成Redis超时
该示例会照成1个用户抢多个
stopwatch.Start();
List<Task> taskList = new List<Task>();
for (int i = ; i <= ; i++) //模拟多个用户同时请求
{
var task = Task.Run(() =>
{
try
{
//*************************开始****************
RedisValue token = Environment.MachineName;
while (true)
{
if (RedisCacheHelper.Instance.LockAndDo("lock_key", token,
() =>
{
//在这里处理并发逻辑 //先获取库存数
var getvalues = (int)RedisCacheHelper.Instance.HashGet("order_hashkey", "order_hashfield");
if (getvalues > )
{
//先自减,获取自减后的值
int order_Num = (int)RedisCacheHelper.Instance.HashDecrement("order_hashkey", "order_hashfield", null);
if (order_Num >= )
{
//下面执行订单逻辑(这里不考虑业务出错的情况)
Console.WriteLine("我抢到了,还剩余数量:" + order_Num);
}
else
{
Console.WriteLine("A商品已经被抢光了");
}
return true;
}
else
{
Console.WriteLine("B商品已经被抢光了");
return true;
} }, new TimeSpan(, , )))
{
break;
} } }
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
});
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
stopwatch.Stop();
Console.WriteLine("模拟并发场景消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
抢购前库存30个
抢购后库存0
针对上边的分布式做抢购固然可以,但是相对的会牺牲一部分性能,还有一个更好的解决办法,那就是Lua脚本:
List<Task> taskList = new List<Task>();
for (int i = ; i <= ; i++) //模拟多个用户同时请求
{
var task = Task.Run(() =>
{
var getguid = Guid.NewGuid().ToString("N"); //模拟用户名
var script = " local isexist= redis.call('hexists',@hid_1,@hfiled_1) " + //检查该用户是否已经抢到
" if tonumber(isexist) == 1 then " + //1 表示已经存在 0表示不存在
" return isexist " + //直接返回
" end " +
" local getstocknumber = redis.call('hget',@hid_2,@hfiled_2) " + //获取库存剩余数量
" if tonumber(getstocknumber) > 0 then " + //如果库存>0
" local decrtystock = redis.call('hincrby',@hid_2,@hfiled_2,@hvalue_2) " + //减去1个库存
" local setuser = redis.call('hset',@hid_1,@hfiled_1,@hvalue_1) " + //记录抢到的用户
" if tonumber(setuser) ==1 then " + //保存成功返回1 0失败
" redis.log(redis.LOG_WARNING,decrtystock) " + //打印redis日志
" end " +
" return setuser " + //返回1 表示true
" end "; //参数
object obj = new
{
@hid_1 = "orderSuccessList",
@hfiled_1 = getguid.ToString(),
@hvalue_1 = "",
@hid_2 = "order_hashkey",
@hfiled_2 = "order_hashfield",
@hvalue_2 = -
}; var getresult = RedisCacheHelper.Instance.HashOrderPut(script, obj);
Console.WriteLine("返回值:" + getresult); });
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
先设置库存数量为10个
操作日志及剩余库存数量
记录的用户数据
应用场景:
我们简单举个实例来描述下 Hash 的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息:
用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息,如果用普通的 key/value 结构来存储,主要有以下 2 种存储方式:
第一种方式将用户 ID 作为查找 key, 把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化 / 反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入 CAS 等复杂问题。
第二种方法是这个用户信息对象有多少成员就存成多少个 key-value 对儿,用用户 ID+ 对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户 ID 为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。
那么 Redis 提供的 Hash 很好的解决了这个问题,Redis 的 Hash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口,如下图:
也就是说,Key 仍然是用户 ID, value 是一个 Map,这个 Map 的 key 是成员的属性名,value 是属性值,这样对数据的修改和存取都可以直接通过其内部 Map 的 Key(Redis 里称内部 Map 的 key 为 field), 也就是通过 key(用户 ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。
这里同时需要注意,Redis 提供了接口 (hgetall) 可以直接取到全部的属性数据, 但是如果内部 Map 的成员很多,那么涉及到遍历整个内部 Map 的操作,由于 Redis 单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。
实现方式:
上面已经说到 Redis Hash 对应 Value 内部实际就是一个 HashMap,实际这里会有 2 种不同实现,这个 Hash 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 value redisObject 的 encoding 为 zipmap, 当成员数量增大时会自动转成真正的 HashMap, 此时 encoding 为 ht。
这里附上RedisHelp
/// <summary>
/// 判断该字段是否存在 hash 中
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public bool HashExists(string redisKey, string hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashExists(redisKey, hashField);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 从 hash 中移除指定字段
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public bool HashDelete(string redisKey, string hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashDelete(redisKey, hashField);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 从 hash 中移除多个指定字段
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public long HashDelete(string redisKey, IEnumerable<RedisValue> hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashDelete(redisKey, hashField.ToArray());
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 递增 默认按1递增 可用于计数
/// </summary>
/// <param name="key"></param>
/// <param name="span"></param>
/// <param name="db"></param>
/// <returns></returns>
public long HashIncrement(string redisKey, string hashField, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
var result = _db.HashIncrement(redisKey, hashField);
//设置过期时间
if (span != null)
{
this.KeyExpire(redisKey, span);
}
return result;
}
catch (Exception)
{ throw;
}
} /// <summary>
/// 递减 默认按1递减 可用于抢购类的案例
/// </summary>
/// <param name="key"></param>
/// <param name="span"></param>
/// <param name="db"></param>
/// <returns></returns>
public long HashDecrement(string redisKey, string hashField, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
var result = _db.HashDecrement(redisKey, hashField);
//设置过期时间
if (span != null)
{
this.KeyExpire(redisKey, span);
}
return result;
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 在 hash 中保存或修改一个值 字符类型
/// set or update the HashValue for string key
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <param name="value"></param>
/// <param name="span">过期时间,可空</param>
/// <returns></returns>
public bool HashSet(string redisKey, string hashField, string value, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
return _db.HashSet(redisKey, hashField, value);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 保存一个字符串类型集合
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey">Redis Key</param>
/// <param name="list">数据集合</param>
/// <param name="getFiledKey">字段key</param>
public void HashSetList(string redisKey, IEnumerable<string> list, Func<string, string> getFiledKey, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
List<HashEntry> listHashEntry = new List<HashEntry>();
foreach (var item in list)
{
listHashEntry.Add(new HashEntry(getFiledKey(item), item));
}
_db.HashSet(redisKey, listHashEntry.ToArray()); if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
}
catch (Exception ex)
{
throw;
}
} /// <summary>
/// 保存多个对象集合 非序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tableName">表名前缀</param>
/// <param name="list">数据集合</param>
/// <param name="getModelId">字段key</param>
public void HashSetObjectList<T>(string tableName, IEnumerable<T> list, Func<T, string> getModelId, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
foreach (var item in list)
{
List<HashEntry> listHashEntry = new List<HashEntry>();
Type t = item.GetType();//获得该类的Type
foreach (PropertyInfo pi in t.GetProperties())
{
string name = pi.Name; //获得属性的名字,后面就可以根据名字判断来进行些自己想要的操作
var value = pi.GetValue(item, null); //用pi.GetValue获得值
listHashEntry.Add(new HashEntry(name, value.ToString()));
}
_db.HashSet(tableName + getModelId(item), listHashEntry.ToArray());
}
}
catch (Exception ex)
{
throw;
}
} /// <summary>
/// 保存或修改一个hash对象(序列化)
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool HashSet<T>(string redisKey, string hashField, T value, TimeSpan? span = null, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
var json = JsonConvert.SerializeObject(value);
if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
return _db.HashSet(redisKey, hashField, json);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 保存Hash对象集合 序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey">Redis Key</param>
/// <param name="list">数据集合</param>
/// <param name="getModelId"></param>
public void HashSet<T>(string redisKey, IEnumerable<T> list, Func<T, string> getModelId, TimeSpan? span = null, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
List<HashEntry> listHashEntry = new List<HashEntry>();
foreach (var item in list)
{
string json = JsonConvert.SerializeObject(item);
listHashEntry.Add(new HashEntry(getModelId(item), json));
}
_db.HashSet(redisKey, listHashEntry.ToArray()); if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
}
catch (Exception ex)
{
throw;
}
} /// <summary>
/// 从 hash 中获取对象(反序列化)
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public T HashGet<T>(string redisKey, string hashField, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
return JsonConvert.DeserializeObject<T>(_db.HashGet(redisKey, hashField));
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 根据hashkey获取所有的值 序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey"></param>
/// <param name="db"></param>
/// <returns></returns>
public List<T> HashGetAll<T>(string redisKey, int db = -) where T : class
{
List<T> result = new List<T>();
try
{
_db = GetDatabase(db);
HashEntry[] arr = _db.HashGetAll(redisKey);
foreach (var item in arr)
{
if (!item.Value.IsNullOrEmpty)
{
var t = JsonConvert.DeserializeObject<T>(item.Value);
if (t != null)
{
result.Add(t);
}
}
}
}
catch (Exception)
{
throw;
}
return result;
} /// <summary>
/// 根据hashkey获取所有的值 非序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey"></param>
/// <param name="db"></param>
/// <returns></returns>
public IEnumerable<Dictionary<RedisValue, RedisValue>> HashGetAll(IEnumerable<string> redisKey, int db = -)
{
List<Dictionary<RedisValue, RedisValue>> diclist = new List<Dictionary<RedisValue, RedisValue>>();
try
{
_db = GetDatabase(db);
foreach (var item in redisKey)
{
HashEntry[] arr = _db.HashGetAll(item);
foreach (var he in arr)
{
Dictionary<RedisValue, RedisValue> dic = new Dictionary<RedisValue, RedisValue>();
if (!he.Value.IsNullOrEmpty)
{
dic.Add(he.Name, he.Value);
}
diclist.Add(dic);
}
}
}
catch (Exception)
{
throw;
}
return diclist;
} /// <summary>
/// 根据hashkey获取单个字段hashField的值
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public RedisValue HashGet(string redisKey, string hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashGet(redisKey, hashField);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 根据hashkey获取多个字段hashField的值
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public RedisValue[] HashGet(string redisKey, RedisValue[] hashField, int db = -)
{
_db = GetDatabase(db);
return _db.HashGet(redisKey, hashField);
} /// <summary>
/// 根据hashkey返回所有的HashFiled
/// </summary>
/// <param name="redisKey"></param>
/// <returns></returns>
public IEnumerable<RedisValue> HashKeys(string redisKey, int db = -)
{
_db = GetDatabase(db);
return _db.HashKeys(redisKey);
} /// <summary>
/// 根据hashkey返回所有HashValue值
/// </summary>
/// <param name="redisKey"></param>
/// <returns></returns>
public RedisValue[] HashValues(string redisKey, int db = -)
{
_db = GetDatabase(db);
return _db.HashValues(redisKey);
}
StackExchange.Redis 之 hash 类型示例的更多相关文章
- StackExchange.Redis 之 String 类型示例
String类型很简单,就不做示例演示了,这里只贴出Helper类 /// <summary> /// 判断key是否存在 /// </summary> /// <par ...
- StackExchange.Redis 之 SortedSet 类型示例
1,增加操作 RedisCacheHelper.Instance.ZSortadd(); RedisCacheHelper.Instance.ZSortadd(); RedisCacheHelper. ...
- redis之Hash类型常用方法总结
redis之Hash类型常用方法总结 格式: 存--HMGET key field [field ...] 取--HMGET key field [field ...] M:表示能取多个值,many ...
- 怎样在Redis通过StackExchange.Redis 存储集合类型List
StackExchange 是由StackOverFlow出品, 是对Redis的.NET封装,被越来越多的.NET开发者使用在项目中. 绝大部分原先使用ServiceStack的开发者逐渐都转了过来 ...
- redis的Hash类型以及其操作
hashes类型 hashes类型及操作Redis hash是一个string类型的field和value的映射表.它的添加.删除操作都是0(1)(平均).hash特别适合用于存储对象.相较于将对象的 ...
- 二:redis 的hash类型相关操作
=====================二种:hash类型================== 介绍:redis -> hash是一个string类型的field和value的映射表 hash ...
- redis的hash类型
1.简单描述 hash是一个string类型的field和value的映射表.添加和删除操作都是O(1)(平均)的复杂度.hash类型特别适合用于存储对象.在field的数量在限制的范围内以及valu ...
- redis的hash类型!!!!
Hash类型 redsi的hash是基本类型之一,键值本身又是一对键值结构,是string类型的field和value的映射表,或者说是集合,适合存储对象. Hash的增操作 127.0.0.1:63 ...
- Redis之Hash类型操作
接口IRedisDaoHash: package com.net.test.redis.base.dao; import com.net.test.redis.base.entity.UserPsg; ...
随机推荐
- spring-boot内嵌三大容器https设置
spring-boot内嵌三大容器https设置 spring-boot默认的内嵌容器为tomcat,除了tomcat之前还可以设置jetty和undertow. 1.设置https spring-b ...
- php5升php7代码修改整理
1. 为什么升级至php7 a.安全需要 过早以至于不维护的php版本难免会有未修补的漏洞,安全性要求较高的行业是要及时升级的. b.性能提升(开启opcache hugepage) 据说性能提升一倍 ...
- 一个动态扩展表格控件列和行的 jQuery 插件
一个动态扩展表格控件列和行的 jQuery 插件 不过这并不影响使用鸭! 看这里:https://github.com/zhuwansu/table-ext.js 一个简单的示范 html <t ...
- python字典的遍历
遍历字典: keys() .values() .items() 1. xxx.keys() : 返回字典的所有的key 返回一个序列,序列中保存有字典的所有的键 效果图: 代码: ...
- Kafka日志压缩剖析
1.概述 最近有些同学在学习Kafka时,问到Kafka的日志压缩(Log Compaction)问题,对于Kafka的日志压缩有些疑惑,今天笔者就为大家来剖析一下Kafka的日志压缩的相关内容. 2 ...
- 异数OS 织梦师-云(五)-- 容器服务化,绿色拯救未来。
. 异数OS 织梦师-云(五)– 容器服务化,绿色拯救未来. 本文来自异数OS社区 github: https://github.com/yds086/HereticOS 异数OS社区QQ群: 652 ...
- java集合与数组之间转换
数组转换为集合 采用java中集合自带的asList()方法就可以完成转换了 String[] array = new String[] {"zhu", "wen&quo ...
- JS实现 JSON扁平数据转换树状数据
后台我拿的数据是这样的格式: [ {id:1 , parentId: 0, name: '', level: 0}, {id:2 , parentId: 0, name: '', level: 0}, ...
- selenium,CSS定位法应用
如图,下载按钮 查看其元素,是无法直接定位的,通过xpath也无法定位,转为firepath获取的CSS也是无效的#downloadItems>a 但是为经过firepath,直接在html下复 ...
- [HNOI2008]明明的烦恼(prufer序列,高精度,质因数分解)
prufer序列 定义 Prufer数列是无根树的一种数列.在组合数学中,Prufer数列由有一个对于顶点标过号的树转化来的数列,点数为n的树转化来的Prufer数列长度为n-2. 描述 eg 将 ...