
xlua中lua对象到c#对象的转型 lua中的类型基础类型 define LUA_TNIL 0 define LUA_TBOOLEAN 1 define LUA_TLIGHTUSERDATA 2 define LUA_TNUMBER 3 define LUA_TSTRING 4 define LUA_TTABLE 5 define LUA_TFUNCTION 6 define LUA_TUSERDATA 7 define LUA_TTHREAD 8 变体(或者说子类型) /*** tags for Tagged Values have the following use of bits:* bits 0-3: actual tag (a LUA_T value)** bits 4-5: variant bits** bit 6: whether value is collectable*/ /*** LUA_TFUNCTION variants:** 0 - Lua function** 1 - light C function** 2 - regular C function (closure)*/ / Variant tags for functions / define LUA_TLCL (LUA_TFUNCTION | (0 << 4)) / Lua closure / define LUA_TLCF (LUA_TFUNCTION | (1 << 4)) / light C function / define LUA_TCCL (LUA_TFUNCTION | (2 << 4)) / C closure / / Variant tags for strings / define LUA_TSHRSTR (LUA_TSTRING | (0 << 4)) / short strings / define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) / long strings / / Variant tags for numbers / define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) / float numbers / define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) / integer numbers / / Bit mark for collectable types / define BIT_ISCOLLECTABLE (1 << 6) lua中的对象都是用TValue来描述的,TValue中的tt_成员变量代表着这个TValue的类型。关于类型的具体定义,上面贴的代码中的注释中已经讲的比较清楚了。一个lua对象的类型是由一个7位的bits描述的。比如一个整数,这个对象的类型就是0011000(24)表示这个对象是数字类型中的整形,是一个不可回收对象。 C#如何获取lua对象和c语言和lua交互其实没啥本质区别,就是通过lua提供的c函数操作lua栈,直接从栈中取就可以了。区别在于如何把取到的值转换为c#认识的值。 如何在C#端描述这些类型简介lua的类型中boolean、string、number这几个类型是clr所认识的类型,所以clr就可以直接把这些类型拿过来用。具体就是直接调用Lua提供的lua_tonumber之类的c接口。lightUserData、table、function、userData、thread是C#不认识的类,需要通过某种标识(lua自带的reference系统)来表示。 boolean、string、number类这三个类上面已经说过了,直接用提供的接口转就可以,下面写几个需要注意的点: string虽然也是一个引用类型,但是clr在拿到这个string的指针时,还需要将这个string的数据直接复制进clr中才算转型结束(xlua也已经封装好了,不用我们自己去复制)。大部分类型转型失败的时候都不会报错,而是会返回一个默认值。就拿将一个lua对象转为int来说,最终是通过lua_tointegerx函数调用的,当lua对象不是number类型时,返回0:LUA_API lua_Integer lua_tointegerx (lua_State L, int idx, int pisnum) { lua_Integer res; const TValue *o = index2addr(L, idx); int isnum = tointeger(o, &res); if (!isnum) res = 0; /* call to 'tointeger' may change 'n' even if it fails */ if (pisnum) *pisnum = isnum; return res;}当一个number类型是浮点数时,转型整数不会进行取整操作,而是会直接返回0。因为lua默认对float转int的操作模式LUA_FLOORN2I是0,代表碰见float转int时返回0。/*** try to convert a value to an integer, rounding according to 'mode':** mode == 0: accepts only integral values** mode == 1: takes the floor of the number** mode == 2: takes the ceil of the number*/int luaV_tointeger (const TValue obj, lua_Integer p, int mode) { TValue v; again: if (ttisfloat(obj)) { lua_Number n = fltvalue(obj); lua_Number f = l_floor(n); if (n != f) { /* not an integral value? */ if (mode == 0) return 0; /* fails if mode demands integral value */ else if (mode > 1) /* needs ceil? */ f += 1; /* convert floor to ceil (remember: n != f) */ } return lua_numbertointeger(f, p); } else if (ttisinteger(obj)) { *p = ivalue(obj); return 1; } else if (cvt2num(obj) && luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) { obj = &v; goto again; /* convert result from 'luaO_str2num' to an integer */ } return 0; / conversion failed /}userDatauserData主要是lua对c#对象的引用,这里只简单说一下。代表c#对象的userData主要分两种。 把c#对象存在ObjectTranslator中,用下标作为引用(类似于lua中的reference)。经过GC优化的结构体和枚举,不存在ObjectTranslator中,而是把所有内容都打包到userdata中一起传入lua中。比如一个Vector3,那么xlua会把这个Vector3的x、y、z作为3个连续的float一起打包到userdata中。这样就避免了c#层的装箱、拆箱和gc操作。对table与function的引用简介这两个类型都是通过lua的reference系统来让c#持有对lua对象的引用。 lua reference系统c#就是通过这个系统来持有不认识的lua对象的。一共就两个接口: luaL_ref:把栈顶元素加入一个lua的表中,并返回下标。luaL_unref:把一个下标所代表元素从表中删除。这样就可以用一个整数来让lua外的环境持有这个lua对象。具体可以看下官方说明lua References luaBase类所有lua对象在c#中的基类,在初始化时通过luaL_ref生成lua对象的引用,在析构时通过luaL_unref移除引用。 对table的引用LuaTable一般情况下table在C#中被包装为LuaTable类,没啥特别的,只是在LuaBase的基础上增加了几个常用的函数。比如Get、Set之类的,让开发者可以避开一些不直观的栈操作。 Array、List、Dictionary这几个都差不多。都是把table中的key和value全部拿出来,组成Array或Dictionaray。 接口、其他类这两种转型是尝试把这个table看作对应的接口或类。比如将一个table转为IEnumberator就是把table转为SystemCollectionsIEnumeratorBridge类(继承了LuaBase、实现了IEnumerator的类,由Xlua生成),这个类实现了MoveNext和Reset。实现方法就是调用一下table中对应名称的函数。 对function的引用lua函数在c#中有两种表示: LuaFunctionLuaFunction和luaTable差不多,也是在LuaBase的基础上增加了几个常用函数,Call、Action之类的。 DelegateBridge为什么已经有LuaFunction还要一个DelegateBridge类?因为我们在c#中拿到一个lua函数时,大多数时候是要作为一个委托来时用的。DelegateBridge就是用来化简这个转型操作的。DelegateBridge的功能就是在持有lua函数引用的同时,将这个函数包装成各种各样的委托,让整个转型过程对开发人员无感知。下面是一个不使用DelegateBridge,自己转型的例子,比较繁琐: //将一个LuaFunction作为一个Action使用//其实LuaFunction.Cast就是干这个的,这里只是用简单的方式表达出来public static Action LuaFunctionToActionInt(XLua.LuaFunction luaFunction){ //由于luaFunction已经提供了Call操作封装了函数调用的各种栈操作,所以我们这里只需要用一个Action<int>把这个操作包装起来即可 return (x) => { luaFunction.Call(x); }; } public static void Test(){ XLua.LuaEnv luaEnv = new XLua.LuaEnv(); object[] rets = luaEnv.DoString("return function(x) CS.UnityEngine.Debug.LogError(\"print x: \"..x) end"); var luaFunction = (XLua.LuaFunction)rets[0]; Action<int> actionInt = LuaFunctionToActionInt(luaFunction); actionInt(10); }DelegateBridge重要成员 xlua在将lua函数转型的时候做了什么 Tips通过ObjectTranslator.getDelegateUsingGeneric生成委托时,会对返回值和参数进行不为值类型的约束。因为值类型在il2cpp下会有jit异常。这也是为什么我们发现有的委托类型不用注册也可以使用,但是有的就不行。在编辑器模式下,没有进行代码生成时,会通过Emit直接生成一个XLuaGenDelegateImplx类,内容和通过代码生成后的DelegateBridge一样,而不是全部通过反射来进行转型。让没有进行代码生成时的环境和真机环境更接近。DelegateBridge一般不会被直接引用,而是被bindto中的委托生成的闭包引用和被delegate_bridges作为弱引用持有。当一个DelegateBridge的bindto中的委托没有被任何对象引用时,这个DelegateBridge就会在下次gc时被gc掉。其他这里主要写了常用lua类型转型的简介和一些关键点。可能不够全面和细节。如果有什么错误或者问题可以在下面留言。 原文地址https://www.cnblogs.com/blueberryzzz/p/13066922.html
MongoDB 4.X CRUD基本操作 mangoDB目录 创建(Create Operations)db.collection.insert()db.collection.insertOne()db.collection.insertMany()关于返回确认信息查询(Read Operations)db.collection.find()db.collection.findOne()条件查询操作符更新(Update Operations)db.collection.update()db.collection.updateOne()db.collection.updateMany()删除(Delete Operations)db.collection.deleteOne()db.collection.deleteMany()总结参考正文 本文总结了MongoDB 4.X在mongo shell客户端涉及的对文档一些基本的增删改查操作,即CRUD操作。主要结合了自己平时使用MongoDB的操作命令,更详细的命令可以参考官方文档: https://docs.mongodb.com/manual/crud/ 。 回到顶部创建(Create Operations)创建(Create Operations)也叫插入操作,当集合不存在时,插入操作同时也会创建集合。MongoDB提供以下几种插入文档方法: db.collection.insert():在指定集合中插入单个或多个文档。db.collection.insertOne():在指定集合中插入单个文档(版本3.2新增)。db.collection.insertMany():在指定集合中插入多个文档(版本3.2新增)。回到顶部db.collection.insert()在平时的使用当中,db.collection.insert()是我用得最多的文档插入方式,具体的语法格式如下: db.collection.insert(, { writeConcern: <document>, ordered: <boolean> })参数说明: document:指定一个或多个文档;writeConcern:文档写入确认级别(可选),关于读写策略确认级别,以后再进行讨论;ordered:指定文档是否按顺序插入(可选),默认为true;当指定为true时,插入多个文档时将文档排序保存在一个数组中进行插入,如果其中有一个文档插入失败,则会导致数组中余下的文档不进行插入操作;当指定为false时,插入多个文档时将文档不进行排序保存在一个数组中进行插入,如果其中有一个文档插入失败,则不影响数组中余下的文档进行插入操作。如果插入的文档当中没有指定_id字段,则MongoDB会自动为文档生成具有唯一ObjectId值的字段_id。 使用示例: // 没有指定_id字段的插入单个文档db.products.insert( { item: "card", qty: 15 } ); // 指定_id字段的插入单个文档db.products.insert( { _id: 10, item: "box", qty: 20 } ); // 插入多个文档,不进行排序,多个文档包含在数组[]中db.products.insert( [ { _id: 11, item: "pencil", qty: 50, type: "no.2" }, { item: "pen", qty: 20 }, { item: "eraser", qty: 25 } ]); // 插入多个文档,并进行排序db.products.insert( [ { _id: 20, item: "lamp", qty: 50, type: "desk" }, { _id: 21, item: "lamp", qty: 20, type: "floor" }, { _id: 22, item: "bulk", qty: 100 } ], { ordered: false }); 回到顶部db.collection.insertOne()语法格式如下: db.collection.insertOne(, { writeConcern: <document> })参数说明: 参考db.collection.insert()的参数说明。 使用示例: // 单行插入文档,关于_id字段指定与否也与db.collection.insert()一致db.products.insertOne( { item: "card", qty: 15 } );回到顶部db.collection.insertMany()语法格式如下: db.collection.insertMany( [ , , ... ], { writeConcern: <document>, ordered: <boolean> })参数说明: 参考db.collection.insert()的参数说明。 使用示例: 参考db.collection.insert()的参数说明。 回到顶部关于返回确认信息db.collection.insert()在插入文档成功之后返回的信息相对较为简洁: db.products.insert( { item: "card", qty: 15 } );WriteResult({ "nInserted" : 1, "writeConcernError" : [ ] })db.collection.insertOne()和db.collection.insertMany()返回的信息较为详细: db.products.insertOne( { item: "card", qty: 15 } );{ "acknowledged": true, "insertedId": ObjectId("5eccbd214139000074003be8") } db.products.insertMany( [ { _id: 10, item: "large box", qty: 20 }, { _id: 11, item: "small box", qty: 55 }, { _id: 12, item: "medium box", qty: 30 } ] );{ "acknowledged": true, "insertedIds": [ 10, 11, 12 ] }回到顶部查询(Read Operations)查询(Read Operations)读操作,是对集合中已存在的文档进行查询,即对应关系型数据库当中的select操作,比如MySQL,MongoDB提供以下几种主要查询文档方法: db.collection.find():查询指定集合中满足条件的一个或多个文档和视图;db.collection.findOne():查询指定集合中满足条件的第一个文档,并以格式化方式展现,通过pretty()方法。来自官方文档的测试数据: db.inventory.insertMany([ { item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" }, { item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" }, { item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" }, { item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" }, { item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }]);回到顶部db.collection.find()db.collection.find()可以说是使用频率最高的方法了,可以用来查询数据库集合当中的文档。 语法格式如下: db.collection.find(, )query:查询表达式;projection:指定查询结果集中需要显示的字段。Col_name:1|true 代表显示该字段;Col_name:0 | false 代表不显示该字段。_id字段是默认显示的,如果不想显示,则显式指定{"_id" : 0}。 查询所有文档: db.inventory.find()或 db.inventory.find({})回到顶部db.collection.findOne()db.collection.findOne()方法显示符合条件查询的第一条文档,接受的参数与db.collection.find()方法一致。 回到顶部条件查询操作符通常对文档的查询,是需要带条件的,而很少使用到不带条件的全文档检索,以下总结了几种常使用的查询操作符: 比较操作符比较操作符涉及的操作如下表所示: 名称 说明$eq 与指定值相等$gt 大于指定的值$gte 大于或等于指定的值$in 指定的值在数组中$lt 小于指定的值$lte 小于或等于指定的值$ne 所有不等于指定的值$nin 指定的值不在数组中使用示例: // $eq:等值查询 SQL: SELECT * FROM inventory WHERE status = "D";db.inventory.find( { status: "D" } ) // $ne 同$eq // $gt:范围查询(以大于为例) SQL: SELECT * FROM inventory WHERE qty > 30;db.inventory.find( { qty: { $gt: 30 } } ) // $gte、$lt、$lte 同$gt // $in:或查询,可使用or代替 SQL: SELECT * FROM inventory WHERE status in ("A", "D")db.inventory.find( { status: { $in: [ "A", "D" ] } } ) // $nin 同$in逻辑操作符逻辑操作符涉及的操作如下表所示: 名称 说明$and 指定查询同时满足多个条件查询子句$not 指定查询不满足条件查询子句$nor 指定查询无法满足多个条件查询子句$or 指定查询满足其中某个条件查询子句使用示例: // $and: 逻辑与查询 SQL: SELECT * FROM inventory WHERE status = "A" AND qty < 30;db.inventory.find( { $and: [ { status: { $eq: "A" }, qty: { $lt: 30 } } ] } ) // $not: 不符合查询 SQL: SELECT * FROM inventory WHERE status <> "A";db.inventory.find( { status: { $not: { $eq: "A" } } } ) /*$nor: 无法同时满足多个条件查询,字段不存在时也符合 SQL: SELECT * FROM inventory WHERE status <> "A" AND qty > 30; 符合以下条件之一都会出现在结果集中:1.文档包含status和qty字段并且符合条件;2.文档包含status字段并且符合条件,不包含qty字段;3.文档不包含status字段,包含qty字段并且符合条件;4.文档不包含status字段和qty字段。*/db.inventory.find( { $nor: [ { status: { $eq: "A" } }, { qty: { $lt: 30 } } ] } ) // $or: 逻辑或查询 SQL: SELECT * FROM inventory WHERE status = "A" OR qty < 30;db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } )元素操作符元素操作符主要涉及的操作如下表所示: 名称 说明$exists 指定查询文档是否有对应的字段$type 指定查询文档的某个字段是否是对应类型使用示例: // $exists: 是否存在指定字段查询db.inventory.find( { price: { $exists: true } } ) // $type: 字段是否是指定类型查询db.inventory.find( { "qty": { $type: "double" } } )评估操作符评估操作符主要涉及的操作如下表所示,更多操作符可以参考官方文档:https://docs.mongodb.com/manual/reference/operator/query-evaluation/。 名称 说明$expr 为同一个文档中的字段指定表达式并且符合条件的查询,比如比较同一文档当中两个字段的值$mod 为字段值取模并且符合条件的查询为了更好的使用这两个主要的操作符,额外创建个文档: db.monthlyBudget.insertMany([ { "_id" : 1, "category" : "food", "budget": 400, "spent": 450 }, { "_id" : 2, "category" : "drinks", "budget": 100, "spent": 150 }, { "_id" : 3, "category" : "clothes", "budget": 100, "spent": 50 }, { "_id" : 4, "category" : "misc", "budget": 500, "spent": 300 }, { "_id" : 5, "category" : "travel", "budget": 200, "spent": 650 } ]);使用示例: // $expr: 允许使用聚合表达式,这里以$gt为例,更多表达式参考 https://docs.mongodb.com/manual/meta/aggregation-quick-reference/#aggregation-expressionsdb.monthlyBudget.find( { $expr: { $gt: [ "$spent" , "$budget" ] } } ) // $mod: 对字段所在值进行取模运算,显示符合条件的查询,如qty字段值对4取模,并且余数为0db.inventory.find( { qty: { $mod: [ 4, 0 ] } } )回到顶部更新(Update Operations)更新(Update Operations)是对已存在的文档进行修改操作,MongoDB提供以下几种主要更新文档方法: db.collection.update():更新或替换集合中符合条件的一个或多个文档;db.collection.updateOne():只更新集合中符合条件的第一个文档,即使有多个文档(版本3.2新增);db.collection.updateMany():更新集合中所有符合条件的文档(版本3.2新增)。回到顶部db.collection.update()根据update指定的表达式可以修改文档中符合条件的字段或代替整个文档。具体的语法格式如下: db.collection.update(, //查询表达式, //更新表达式 { upsert: <boolean>, multi: <boolean>, writeConcern: <document>, collation: <document>, arrayFilters: [ <filterdocument1>, ... ], hint: <document|string> // 版本4.2新增 })参数说明: query:更新文档的查询表达式;如果指定了参数upsert: true并且集合中没有符合查询条件的文档,查询条件中有关于字段_id指定了.分隔符的,并不会插入新的文档;update:主要包含三种格式1.更新文档:只包含更新操作符表达式;2.替换文档:只包含: 对;3.聚合管道:版本4.2新增,详细参考官方文档。upsert:当query查询条件没符合更新的文档,就新创建文档(可选),默认值为false;multi:是否更新多个符合条件的文档(可选),默认值为false,只更新符合条件的第一个文档;writeConcern:参考db.collection.insert()相同参数说明;collation:指定校对规则(可选,版本3.4新增);arrayFilters:文档数组更新过滤操作符(可选,版本3.6新增);详细参考:https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-array-update-operations hint:采用文档或字符串的形式指定适用于查询表达式的索引,如果索引不存在则报错(可选,版本4.2新增)。使用示例: 使用示例将通过使用两种场景进行,一是没有使用参数选项upsert,二是使用参数选项upsert。 不使用选项upsert// 测试数据db.books.remove({}); db.books.insertMany([ { "_id" : 1, "item" : "TBD", "stock" : 0, "info" : { "publisher" : "1111", "pages" : 430 }, "tags" : [ "technology", "computer" ], "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "lmn", "rating" : 5 } ], "reorder" : false }, { "_id" : 2, "item" : "XYZ123", "stock" : 15, "info" : { "publisher" : "5555", "pages" : 150 }, "tags" : [ ], "ratings" : [ { "by" : "xyz", "rating" : 5 } ], "reorder" : false }]); /* 使用选项参数 upsert: true1、如果查询表达式找到匹配的文档,则执行更新操作;2、如果查询表达式没有找到匹配的文档,则执行插入操作;*/db.books.update( { item: "ZZZ135" }, // 查询表达式 { // 更新或替换文档 item: "ZZZ135", stock: 5, tags: [ "database" ] }, { upsert: true }); // 1.使用更新操作表达式/* $set操作符1、查询表达式指定需要更新的文档 _id;2、$inc操作符: stock的字段值+5;3、$set操作符: 替换item字段值,替换嵌入文档info的publisher字段值,替换tags字段值,替换数组ratings的第二个元素值*/db.books.update( { _id: 1 }, { $inc: { stock: 5 }, $set: { item: "ABC123", "info.publisher": "2222", tags: [ "software" ], "ratings.1": { by: "xyz", rating: 3 } } });更新之后的文档:{ "_id" : 1, "item" : "ABC123", "stock" : 5, "info" : { "publisher" : "2222", "pages" : 430 }, "tags" : [ "software" ], "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "xyz", "rating" : 3 } ], "reorder" : false} // 2.为已存在的数组添加元素// $push操作符: 为指定文档数组ratings添加一个元素db.books.update( { _id: 2 }, { $push: { ratings: { "by" : "jkl", "rating" : 2 } } });更新之后的文档:{ "_id" : 2, "item" : "XYZ123", "stock" : 15, "info" : { "publisher" : "5555", "pages" : 150 }, "tags" : [ ], "ratings" : [ { "by" : "xyz", "rating" : 5 }, { "by" : "jkl", "rating" : 2 } ], "reorder" : false } // 3.文档移除字段// $unset操作符: 移除文档的指定字段,为_id:1文档移除tags字段db.books.update( { _id: 1 }, { $unset: { tags: 1 } } );更新后的文档:{ "_id" : 1, "item" : "TBD", "stock" : 0, "info" : { "publisher" : "1111", "pages" : 430 }, "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "lmn", "rating" : 5 } ], "reorder" : false } // 4.替换整个文档// 替换_id:2的文档db.books.update( { _id: 2 }, { item: "XYZ123", stock: 10, info: { publisher: "2255", pages: 150 }, tags: [ "baking", "cooking" ] });更新后的文档:{ "_id" : 2, "item" : "XYZ123", "stock" : 10, "info" : { "publisher" : "2255", "pages" : 150 }, "tags" : [ "baking", "cooking" ]} // 5.更新多个文档db.books.update( { stock: { $lte: 10 } }, { $set: { reorder: true } }, { multi: true });更新后的全部文档:[ { "_id" : 1, "item" : "ABC123", "stock" : 5, "info" : { "publisher" : "2222", "pages" : 430 }, "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "xyz", "rating" : 3 } ], "reorder" : true } { "_id" : 2, "item" : "XYZ123", "stock" : 10, "info" : { "publisher" : "2255", "pages" : 150 }, "tags" : [ "baking", "cooking" ], "reorder" : true }]使用upserts选项/* 使用选项参数 upsert: true1、如果查询表达式找到匹配的文档,则执行更新操作;2、如果查询表达式没有找到匹配的文档,则执行插入操作;*/ // 1.插入未符合更新条件的文档db.books.update( { item: "ZZZ135" }, { item: "ZZZ135", stock: 5, tags: [ "database" ] }, { upsert: true } );因为集合并未满足条件的文档,则插入的文档为:{ "_id" : ObjectId("5da78973835b2f1c75347a83"), "item" : "ZZZ135", "stock" : 5, "tags" : [ "database" ]} // 2.插入未符合更新条件并且基于更新操作符的文档// 如果没有符合更新查询条件,并且使用的是更新操作符,则会基于当前的查询条件和更新操作符字段插入新的文档db.books.update( { item: "BLP921" }, { $set: { reorder: false }, $setOnInsert: { stock: 10 } }, { upsert: true } );新插入的文档为:{ "_id" : ObjectId("5da79019835b2f1c75348a0a"), "item" : "BLP921", "reorder" : false, "stock" : 10} // 3.插入未符合更新条件并且基于聚合管道的文档// 关于聚合管道请参考官方文档:https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline // 4.插入未符合更新条件并且同时联合多文档操作符的文档如果不符合查询条件,则只会插入单个文档db.books.update( { "info.publisher": "Self-Published" }, { $set: { reorder: false, tags: [ "literature", "hardcover" ], stock: 25 } }, { upsert: true, multi: true } );新插入的文档:{ "_id" : ObjectId("5db337934f670d584b6ca8e0"), "info" : { "publisher" : "Self-Published" }, "reorder" : false, "stock" : 25, "tags" : [ "literature", "hardcover" ]}回到顶部db.collection.updateOne()根据update指定的参数可以修改文档中符合条件的字段或代替整个文档,与db.collection.update()不同的是每次只更新单个文档。 语法格式如下: db.collection.updateOne(,, { upsert: <boolean>, writeConcern: <document>, collation: <document>, arrayFilters: [ <filterdocument1>, ... ], hint: <document|string> })参数说明: 参考db.collection.update()的参数说明。 使用示例: // 参考db.collection.update()回到顶部db.collection.updateMany()根据update指定的参数可以修改文档中符合条件的字段或代替整个文档,与db.collection.updateOne()不同的是更新所有符合条件的文档。 语法格式如下: db.collection.updateMany(,, { upsert: <boolean>, writeConcern: <document>, collation: <document>, arrayFilters: [ <filterdocument1>, ... ], hint: <document|string> })参数说明: 参考db.collection.update()的参数说明。 使用示例: // 参考db.collection.update()回到顶部删除(Delete Operations)删除是指对集合当中已存在的文档进行清除操作,MongoDB提供以下几种主要删除文档方法: db.collection.deleteOne():只删除集合中符合条件的一个文档;db.collection.deleteMany():删除集合中所有符合条件的文档;db.collection.remove():删除集合中符合条件的一个或多个文档。回到顶部db.collection.deleteOne()根据filter选项条件删除集合中的单个文档,具体语法格式如下: db.collection.deleteOne(, { writeConcern: <document>, collation: <document> })参数说明: filter:指定基于查询表达式的过滤条件,关于查询表达式可以查看db.collecion.find()中的;writeConcern:参考db.collection.insert()相同参数说明;collation:指定校对规则(可选,版本3.4新增);使用示例: // 删除指定条件的单个文档db.orders.deleteOne( { "_id" : 1 } );{ "acknowledged" : true, "deletedCount" : 1 }回到顶部db.collection.deleteMany()根据filter选项条件删除集合中的单个文档,具体语法格式如下: db.collection.deleteMany(, { writeConcern: <document>, collation: <document> })参数说明: 参考db.collection.deleteOne()的参数说明。 使用示例: // 删除指定条件的多个文档db.orders.deleteMany( {"cust_id" : "Cam Elot"} );{ "acknowledged" : true, "deletedCount" : 2 }注意: 如果是对固定集合进行删除文档操作则会报错,固定集合的清除操作使用方法db.collection.drop()。 回到顶部总结本文简单梳理了在Mongo Shell下基本的CRUD操作,主要适用于DBA的运维管理,如果是研发同学,根据不同的编程语言使用不同客户端驱动进行操作,详细同样可以参考官方文档;针对CRUD各个方面还有其他一些额外的方法,比如查询修改文档方法db.collection.findAndModify(),这里只是总结每个文档操作中一些最基础的方法,对于额外高级的方法这里不再赘述;掌握了这些基本的CRUD操作,就可以对MongoDB文档进行操作了,但还是需要控制好权限,毕竟数据安全不是小事,做变更之前做好数据的备份,以防万一。回到顶部参考https://docs.mongodb.com/manual/crud/ https://docs.mongodb.com/manual/reference/operator/query-evaluation/ https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-array-update-operations https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline ☆〖本人水平有限,文中如有错误还请留言批评指正!〗☆ 作者: H_Johnny出处: http://www.cnblogs.com/dbabd/
Java 异常(一) 异常概述及其架构 一、异常概述(一)、概述Java异常是Java提供的一种识别及响应错误的一致性机制。异常指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答 what, where, why 这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出。 (二)、异常体系异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Error与java.lang.Exception,平常所说的异常指java.lang.Exception。 Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理。 1、Throwable:Throwable是 Java 语言中所有错误或异常的超类。Throwable包含两个子类: Error 和 Exception。它们通常用于指示发生了异常情况。 Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。 Throwable常用API: public void printStackTrace() // 打印异常的详细信息。包含了异常的类型,异常的原因,还包括异常出现的位置。public String getMessage() // 获取发生异常的原因。public String toString() // 获取异常的类型和异常描述信息。2、Error:严重错误Error,无法通过处理的错误,只能事先避免。 3、Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。 (三)、异常分类我们平常说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。 异常主要分为两大类:编译期异常和运行期异常。 编译期异常:checked异常。在编译期,就会检查,如果没有处理异常,则编译失败(如日期格式化异常)。 特点: Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。被检查异常通常都是可以恢复的。 运行期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)(如数学异常)。 特点: Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。 虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。 如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生! 二、异常处理Java异常处理机制用到的几个关键字:try、catch、finally、throw、throws。 try:用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。catch:用于捕获异常。catch用来捕获try语句块中发生的异常。finally:finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。throw:用于抛出异常。throws:用在方法签名中,用于声明该方法可能抛出的异常。三、异常注意点(一)、finally中的异常会覆盖(消灭)前面try或者catch中的异常不要在fianlly中使用return。不要在finally中抛出异常。减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。将尽量将所有的return写在函数的最后面,而不是try ... catch ... finally中。(二)、多个异常使用捕获多个异常分别处理。多个异常一次捕获,多次处理。多个异常一次捕获一次处理。一般情况下,一般我们是使用一次捕获多次处理方式,如下代码 try{ // 编写可能会出现异常的代码 }catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获. // 处理异常的代码 //记录日志/打印异常信息/继续抛出异常 }catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获. // 处理异常的代码 //记录日志/打印异常信息/继续抛出异常 } 注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。 三、实例(一)、try - catch用法try - catch 必须搭配使用,不能单独使用。 public class ExceptionDemo { public static void main(String[] args) { try { int i = 10 / 0;// 除数不能为0,此行会抛出 ArithmeticException 异常 System.out.println("i = " + i);// 抛出异常后,此行不会执行 }catch(ArithmeticException e) { // 捕获 ArithmeticException 异常 // 在catch 代码块处理异常 e.printStackTrace(); // 异常最详细信息 System.out.println("e.getMessage() : " + e.getMessage());// 发生异常的原因 System.out.println("e.toString() : " + e.toString()); // 获取异常的类型和异常描述信息 } } } (二)、finally 用法try - catch - finally搭配使用,或者 try - finally 搭配使用。 public class ExceptionDemo { public static void main(String[] args) { // try-catch-finally搭配使用 try { int[] arr = {1,2,3}; int i = arr[3];// 数组索引越界,此行会抛出 ArrayIndexOutOfBoundsException 异常 System.out.println("i = " + i);// 抛出异常后,此行不会执行 }catch(ArithmeticException e) { // 捕获 ArithmeticException System.out.println(e.getMessage());// 发生异常的原因 System.exit(0); // 程序强制退出,finally 代码块不会执行 }finally {// 除了程序强制退出,如(System。exit(0)),无论是否发生异常,finally 代码块总会执行 System.out.println("this is finally"); } // try-finally搭配使用 try { int[] arr = {1,2,3}; int i = arr[3];// 数组索引越界,此行会抛出 ArrayIndexOutOfBoundsException 异常 System.out.println("i = " + i);// 抛出异常后,此行不会执行 }finally { // 无论是否发生异常,finally 代码块总会执行 System.out.println("this is finally"); } } } 注意点: try-catch-finally 搭配:这种形式捕获异常时,开发者可以在 catch 代码块中处理异常(如打印日志、日志记录等),异常处理权在开发者。try-finally 搭配:这种形式捕获异常时,默认抛出给 JVM 处理,JVM默认处理时调用 e.printStackTrace() 方法打印异常详细信息。finally 代码块:除非程序强制退出,否则无论程序是否发生异常,finally 代码块总会执行。finally 中抛出异常会覆盖(消灭)前面 try 或者 catch 中的异常,尽量避免在 finally 代码块中抛出异常。如果 try 中和 finally 中都有 return 语句,程序会先执行 finally 中的 return 语句,然后程序块运行结束,而不会执行 try 中的 return 语句。所以尽量不在finally中使用 return 语句。(三)、throw 用法throw 是用于抛出异常,将这个异常对象传递到调用者处,并结束当前方法的执行 public static void main(String[] args) { try { int i = 10 / 0; System.out.println("i = " + i); }catch(ArithmeticException e) { // 抛出异常,传递自定义异常信息提示 // 默认抛出给 JVM 处理打印异常详细信息 throw new ArithmeticException("除数不能为0"); } } (四)、throws 用法throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)。 public class ExceptionDemo { public static void main(String[] args) { demo(); } public static void demo() throws ArrayIndexOutOfBoundsException{ try { int[] arr = {1,2,3}; int i = arr[3]; System.out.println("i = " + i); }catch(ArrayIndexOutOfBoundsException e) { System.out.println(e.toString()); } } } 原文地址https://www.cnblogs.com/lingq/p/12942895.html
C#网络编程入门之TCP 目录:C#网络编程入门系列包括三篇文章: (一)C#网络编程入门之UDP (二)C#网络编程入门之TCP (三)C#网络编程入门之HTTP 一、概述UDP和TCP是网络通讯常用的两个传输协议,C#一般可以通过Socket来实现UDP和TCP通讯,由于.NET框架通过UdpClient、TcpListener 、TcpClient这几个类对Socket进行了封装,使其使用更加方便, 本文就通过这几个封装过的类讲解一下相关应用。 二、基本应用:连接、发送、接收服务端建立侦听并等待连接: TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9000);tcpListener.Start();if (tcpListener.Pending()){ TcpClient client = tcpListener.AcceptTcpClient(); Console.WriteLine("Connected"); } 服务端是通过AcceptTcpClient方法获得TcpClient对象,而客户端是直接创建TcpClient对象。 TcpClient tcpClient = new TcpClient();tcpClient.Connect("127.0.0.1", 9000);发送数据TcpClient对象创建后,发送接收都通过TcpClient对象完成。 发送数据: TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 9000); NetworkStream netStream = tcpClient.GetStream(); int Len = 1024; byte[] datas = new byte[Len]; netStream.Write(datas, 0, Len); netStream.Close(); tcpClient.Close(); 接收数据: TcpClient client = tcpListener.AcceptTcpClient();Console.WriteLine("Connected"); NetworkStream stream = client.GetStream(); var remote = client.Client.RemoteEndPoint; byte[] data = new byte[1024]; while (true) { if (stream.DataAvailable) { int len = stream.Read(data, 0, 1024); Console.WriteLine($"From:{remote}:Received ({len})"); } Thread.Sleep(1); } 三、 粘包问题和UDP不太一样,TCP连接不会丢包,但存在粘包问题。(严格来说粘包这个说法是不严谨的,因为TCP通讯是基于流的,没有包的概念,包只是使用者自己的理解。) 下面分析一下粘包产生的原因及解决办法。 TCP数据通讯是基于流来实现的,类似一个队列,当有数据发送过来时,操作系统就会把发送过来的数据依次放到这个队列中,对发送者而言,数据是一片一片发送的,所以自然会认为存在数据包的概念,但对于接收者而言,如果没有及时去取这些数据,这些数据依次存放在队列中,彼此之间并无明显间隔,自然就粘包了。 还有一种情况粘包是发送端造成的,有时我们调用发送代码时,操作系统可能并不会立即发送,而是放到缓存区,当缓存区达到一定数量时才真正发送。 要解决粘包问题,大致有以下几个方案。 1、 约定数据长度,发送端的数据都是指定长度,比如1024;接收端取数据时也取同样长度,不够长度就等待,保证取到的数据和发送端一致; 2、 接收端取数据的频率远大于发送端,比如发送端每1秒发送一段数据,接收端每0.1秒去取一次数据,这样基本可以保证数据不会粘起来; 以上两个方案都要求发送端需要立即发送,不可缓存数据。而且这两种方案都有缺陷:首先,第一种方案:如果要包大小一致的话,如果约定的包比较大,肯定有较多数据冗余,浪费网络资源,如果包较小,连接就比较频繁,效率不高。 其次,第二种方案:这个方案只能在理想环境下可以实现,当服务端遭遇一段时间的计算压力时可能会出现意外,不能完全保证。 比较完善的解决方案就是对接收到的数据进行预处理:首先通过定义特殊的字符组合作为包头和包尾,如果传输ASCII字符,可以用0x02表示开始(STX),用0x03表示结束(ETX),比如:STX ‘H’ ‘e’ ‘l’ ‘l’ ‘o’ ETX (二进制数据: 02 48 65 6C 6C 6F 03)。如果数据较长可以在包头留出固定位置存放包长度, 如: 02 00 05 48 65 6C 6C 6F 03 其中02 05 就表示正文长度为5个字节,可以进行校验。 虽然第三种方案比较严谨,但相对复杂,在传输比较可靠、应用比较简单的场景下,也可以采用前面两种解决方案。 四、 一个完整的例程服务端: View Code客户端: View Code原文地址https://www.cnblogs.com/seabluescn/p/12972632.html
Java 集合排序策略接口 Comparator 前言最近用到了集合排序(基于 Java 8)。现在我能用 Stream 的就用 Stream ,真香!排序可以这么写: List peoples = new ArrayList<>(); // 中间省略 // 按照年龄从小到大排序peoples.sort(Comparator.comparing(People::getAge));这里排序用到了一个关键接口 java.util.Comparator。排序比较作为业务中经常出现的需求,我们有必要研究一下这个接口。 Comparator 概念Comparator 是一个函数式接口。它经常用于没有天然排序的集合进行排序,如 Collections.sort 或 Arrays.sort。或者对于某些有序数据结构的排序规则进行声明,如 TreeSet 、TreeMap 。也就是该接口主要用来进行集合排序。 Comparator 中的方法Comparator 作为一个函数式接口只有一个抽象方法,但是它有很多的默认方法,我们来认识一下这些方法们。 3.1 compare 抽象方法作为Comparator 唯一的抽象方法,int compare(T o1,T o2) 比较两个参数的大小, 返回负整数,零,正整数 ,分别代表 o1o2,通常分别返回 -1、0 或 1。伪表达式: // 输入两个同类型的对象 ,输出一个比较结果的int数字(x1,x2)-> int实现该方法一定要注意以下事项: 必须保证compare(x,y) 和compare(y,x) 的值的和必须为 0 。必须保证比较的顺序关系是可传递的,如果compare(x,y)>0 而且compare(y,z)>0 则 compare(x,z)>0。如果存在 compare(x,y)=0,则对于 z 而言,存在 compare(x, z)==compare(y, z)。然而并不 严格要求(compare(x, y)==0) == (x.equals(y))。一般说来,任何违背这个条件的 Comparator 实现都应该明确指出这一事实情况。 3.2 comparing 系列方法从 Java 8 开始,Comparator 提供了一系列的静态方法,并通过函数式的风格赋予 Comparator 更加强大和方便的功能,我们暂且称它们为 comparing系列方法。 public static Comparator comparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { Objects.requireNonNull(keyExtractor); Objects.requireNonNull(keyComparator); return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2)); } 该方法是该系列方法的基本方法。是不是看上去很难懂的样子?我们来分析一下该方法。它一共两个参数都是函数式接口。 第一个参数 Function<? super T, ? extends U> keyExtractor 表示输入一个是 T 类型对象,输出一个 U 类型的对象,举个例子,输入一个 People 对象返回其年龄 Integer 数值: // people -> people.getAge(); 转换为下面方法引用Function getAge = People::getAge;第二个参数 keyComparator就很好理解了,表示使用的比较规则。 对 c1,c2 按照 第一个参数 keyExtractor 提供的规则进行提取特征,然后第二个参数keyComparator对这两个特征进行比较。下面的式子其实可以概括为 3.1 的 (x1,x2)-> int (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2)) Comparator & Serializable 为 Java 8 新特性:同时满足这两个类型约束 理解了这个方法后,其它该系列的方法就好理解了,这里不再赘述。目前 comparing 系列方法使用更加广泛。我们举一些例子: List peoples = new ArrayList<>();// ………………// 按照年龄从低到高排序peoples.sort(Comparator.comparing(People::getAge));// 按照年龄从高到低排序peoples.sort(Comparator.comparing(People::getAge, (x, y) -> -x.compareTo(y))); 同样你可以使用 java.util.Collections 或者 Stream 提供的排序方法来使用Comparator。 小结今天对 Comparator进行了简单的分析,它用于构建集合排序的规则,在日常开发中非常有用。下一篇 我们将对另一个和它十分相似的接口 Comparable 进行分析和比较它们的不同,敬请关注。 原文地址https://www.cnblogs.com/felordcn/p/12921857.html