会写程序的男人博客 / zh-CN 一只专注.Net开发的程序猿 Tue, 20 Oct 2020 16:20:00 +0800 Tue, 20 Oct 2020 16:20:00 +0800 从头再来 /index.php/archives/3/ /index.php/archives/3/ Sun, 01 Mar 2020 23:16:00 +0800 会写程序的男人博客 2 /index.php/archives/3/#comments /index.php/feed/archives/3/ 【LeetCode】5.最长回文子串 /index.php/archives/42/ /index.php/archives/42/ Tue, 20 Oct 2020 16:20:00 +0800 会写程序的男人博客 Tags [`string`](https://leetcode.com/tag/string "https://leetcode.com/tag/string") | [`dynamic-programming`](https://leetcode.com/tag/dynamic-programming "https://leetcode.com/tag/dynamic-programming") 给定一个字符串 `s`,找到 `s` 中最长的回文子串。你可以假设 `s` 的最大长度为 1000。 **示例 1:** ``` 输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。 ``` **示例 2:** ``` 输入: "cbbd" 输出: "bb" ``` --- [Discussion](https://leetcode-cn.com/problems/longest-palindromic-substring/comments/ "https://leetcode-cn.com/problems/longest-palindromic-substring/comments/") | [Solution](https://leetcode-cn.com/problems/longest-palindromic-substring/solution/ "https://leetcode-cn.com/problems/longest-palindromic-substring/solution/") ## 读题 首先理解“回文”概念: **正着读反着读都一样的字符串** 用几何的角度来说就是,对称 那么对称都会有对称轴,思路就来了,一对称轴为中心,依次向两边扩散 **leetcode上的大佬称之为 中心扩散法** ## 解题 我们提到了中心扩散,但是有一个问题 奇数对称和偶数对称是不一样的,奇数时,对称轴是一个字母如"aba",对称轴是"b" 而"abba"对称轴是两个"b"中间的空隙! 不纠结,拆成两个方法 ### 奇数判断 ```C# public string findSignlePalind(string rawStr, int index, int nowMax) { //处理"a"这种单个字符的字符串,也是回文 if(rawStr.Length < 2) return rawStr; //最后偏移量 var finalOffset = 0; //转char数组,比每次substring效率高 var charArr = rawStr.ToCharArray(); for(var offset = 1;; offset++) { //数组越界检查 if(index - offset < 0 || index + offset >= rawStr.Length) break; //对称轴左字符 var leftChar = charArr[index - offset]; //对称轴右字符 var rightChar = charArr[index + offset]; if(leftChar == rightChar) { //相同,继续偏移 finalOffset = offset; continue; } break; } //计算起始位置 var left = index - finalOffset; //获取最终字符串 return rawStr.Substring(left, finalOffset * 2 + 1); } ``` ### 偶数判断 ```C# public string findDoublePalind(string rawStr, int index, int nowMax) { //处理"a"这种单个字符的字符串,也是回文 if(rawStr.Length < 2) return rawStr; //最后偏移量 var finalOffset = 0; //转char数组,比每次substring效率高 var charArr = rawStr.ToCharArray(); //记录是否有结果 var hasRes = false; for(var offset = 0;; offset++) { //数组越界检查 if(index - offset < 0 || index + offset + 1 >= rawStr.Length) break; //"abba"这种情况,把对称轴左边的字符当做对称轴 //取左字符 var leftChar = charArr[index - offset]; //取右字符 var rightChar = charArr[index + offset + 1]; if(leftChar == rightChar) { //相同,继续偏移 finalOffset = offset; hasRes = true; continue; } break; } if(!hasRes) return ""; //计算起始位置 var left = index - finalOffset; //获取最终字符串 return rawStr.Substring(left, 2 * finalOffset + 2); } ``` ### 主程序逻辑 ```C# public string LongestPalindrome(string s) { //记录最大长度 int nowMax = 0; //记录最大字符串 string nowStr = ""; for(int i = 0; i < s.Length; i++) { //奇数回文查找 string singleStr = findSignlePalind(s, i, nowMax); if(singleStr.Length > nowMax) { nowMax = singleStr.Length; nowStr = singleStr; } //偶数回文查找 string doubleStr = findDoublePalind(s, i, nowMax); if(doubleStr.Length > nowMax) { nowMax = doubleStr.Length; nowStr = doubleStr; } } return nowStr; } ``` ### 思考 查看leetcode大佬们的解题思路,竟然还有“马拉车”方案,可以神奇地将时间复杂度拉到O(n)!可以借鉴学习一下,属于进阶大佬级别! **仔细思考,其实可以使用双指针对奇偶两种情况进行统一,减少至少一半的不必要重复操作。具体修改 下回分解(那就不知道是啥时候了)** ### 膜拜大佬 如果大佬们有更好的解法,不如评论分享赐教~ [我的Leetcode解题代码库][1] [1]: https://github.com/会写程序的男人博客0929/.leetcode ]]> 0 /index.php/archives/42/#comments /index.php/feed/archives/42/ 【JDK源码解析】简单集合 /index.php/archives/40/ /index.php/archives/40/ Tue, 13 Oct 2020 10:14:00 +0800 会写程序的男人博客 本文阅读源 微信公众号 [彤哥读源码](https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzkxNDEyOTI0OQ==&scene=124#wechat_redirect) > 您可以点击 [文章系列目录](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxNDEyOTI0OQ==&action=getalbum&album_id=1538024362992254978&scene=173#wechat_redirect) 一起学习 > 建议您在此之前,先查看 [阅读建议](https://mp.weixin.qq.com/s/dn4zwUPdtoott91ttEfM5g) > > 您也可以一边查看[源代码](https://gitee.com/alan-tang-tt/yuan)一边进行阅读 > > **由于读书笔记的特殊性,大篇幅为原文复制** > > **引用部分为笔记,脚注部分为重点提示,拓展标题为根据文章后期自我总结的额外学习拓展** --- ## 笔记内容 ### ArrayList [阅读原文](https://mp.weixin.qq.com/s/EBweLpilAjW0uBTqmFAp2g) #### 继承体系 ArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable等接口。 ArrayList实现了List,提供了基础的添加、删除、遍历等操作。 ArrayList实现了RandomAccess,提供了随机访问的能力。 ArrayList实现了Cloneable,可以被克隆。 ArrayList实现了Serializable,可以被序列化。 #### 总结 (1)ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容; (2)ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1); (3)ArrayList添加元素到尾部极快,平均时间复杂度为O(1); (4)ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n); (5)ArrayList从尾部删除元素极快,时间复杂度为O(1); (6)ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n); (7)ArrayList支持求并集,调用addAll(Collection c)方法即可; (8)ArrayList支持求交集,调用retainAll(Collection c)方法即可; (7)ArrayList支持求单向差集,调用removeAll(Collection c)方法即可; > 1. ArrayList的复杂度还是很低的,其中进行中间增删的操作复杂度为O(n)是由于需要进行元素搬移 > 2. 在retainAll、removeAll中,使用了一个方法 > > **boolean batchRemove(Collection c, boolean complement)** > > **双指针遍历数组,读指针每次自增1,写指针放入元素的时候才+1,可以达到不需要额外空间在原数组上操作就可以完成批量移除** > > **方法中,在finally实现业务逻辑,证明在必要的时候,try-catch-finally中的finally也可以用来完成业务逻辑,并不是只能死板的用于异常捕获和日志记录** > 3. 在增加元素时,会检查并扩容,但是在减少元素时不会扩容 > 4. 减少元素进行圆度搬移时,最后一个索引位置赋值为null,可以帮助GC回收,这个操作很细节 #### 拓展 使用关键字 **transient** 可以使相关字段不被序列化 ### CopyOnWriteArrayList CopyOnWriteArrayList是ArrayList的线程安全版本,内部也是通过数组实现,每次对数组的修改都完全拷贝一份新的数组来修改,修改完了再替换掉老数组,这样保证了只阻塞写操作,不阻塞读操作,实现读写分离。 [阅读原文](https://mp.weixin.qq.com/s/3P3km4pHoudbR_ky29twAA) #### 继承体系 CopyOnWriteArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable等接口。 CopyOnWriteArrayList实现了List,提供了基础的添加、删除、遍历等操作。 CopyOnWriteArrayList实现了RandomAccess,提供了随机访问的能力。 CopyOnWriteArrayList实现了Cloneable,可以被克隆。 CopyOnWriteArrayList实现了Serializable,可以被序列化。 #### 总结 (1)CopyOnWriteArrayList使用ReentrantLock重入锁加锁,保证线程安全; (2)CopyOnWriteArrayList的写操作都要先拷贝一份新数组,在新数组中做修改,修改完了再用新数组替换老数组,所以空间复杂度是O(n),性能比较低下; (3)CopyOnWriteArrayList的读操作支持随机访问,时间复杂度为O(1); (4)CopyOnWriteArrayList采用读写分离[^1]的思想,读操作不加锁,写操作加锁,且写操作占用较大内存空间,所以适用于读多写少的场合; (5)CopyOnWriteArrayList只保证最终一致性,不保证实时一致性[^2] > 1. CopyOnWriteArrayList是线程安全的ArrayList实现,每次对数组的修改都完全拷贝一份新的数组来修改,修改完了再替换掉老数组 > 2. 没有size,因为每次都是拷贝新数组替换,所以不存在扩容缩容问题,有多少元素就有多大容量 > 3. 由于加锁和数组拷贝机制,空间和时间复杂度都没有ArrayList优秀 ### HashMap [阅读原文](https://mp.weixin.qq.com/s/9acWEJP1XNgD3NMgWD42Yg) #### 继承体系 HashMap实现了Cloneable,可以被克隆。 HashMap实现了Serializable,可以被序列化。 HashMap继承自AbstractMap,实现了Map接口,具有Map的所有功能。 #### 存储结构 Java中,HashMap的实现采用了(**数组 + 链表 + 红黑树**)的复杂结构,**数组的一个元素又称作桶**。 在添加元素时,会**根据hash值算出元素在数组中的位置**,如果该位置没有元素,则直接把元素放置在此处,如果该位置有元素了,则把元素**以链表的形式放置在链表的尾部**。 当一个链表的元素个数达到一定的数量(且数组的长度达到一定的长度)后,则把**链表转化为红黑树**,从而提高效率。 数组的查询效率为O(1),链表的查询效率是O(k),红黑树的查询效率是O(log k),k为桶中的元素个数,所以**当元素数量非常多的时候,转化为红黑树能极大地提高效率**。 > 1. 桶:是一个典型的单链表(包含下一个节点的地址)节点 **Node**,使用**final int hash**存储key计算得到的hash值,同时实现了**Map.Entry** > 2. TreeNode:典型的树节点 **TreeNode**(包含左节点、右节点、父节点,重点是,prev是链表中的节点,用于在删除元素的时候可以快速找到它的前置节点)继承了**LInkedHashMap.Entry** #### 总结 (1)HashMap是一种散列表,采用(数组 + 链表 + 红黑树)的存储结构; (2)HashMap的默认初始容量为16(1<<4),默认装载因子为0.75f,容量总是2的n次方; (3)HashMap扩容时每次容量变为原来的两倍; (4)当桶的数量小于64时不会进行树化,只会扩容; (5)当桶的数量大于64且单个桶中元素的数量大于8时,进行树化; (6)当单个桶中元素数量小于6时,进行反树化; (7)HashMap是非线程安全的容器; (8)HashMap查找添加元素的时间复杂度都为O(1); #### 彩蛋 红黑树知多少? 红黑树具有以下5种性质: (1)节点是红色或黑色。 (2)根节点是黑色。 (3)每个叶节点(NIL节点,空节点)是黑色的。 (4)每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) (5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。 红黑树的时间复杂度为O(log n),与树的高度成正比。 红黑树每次的插入、删除操作都需要做平衡,平衡时有可能会改变根节点的位置,颜色转换,左旋,右旋等。 > **红黑树需要掌握** #### 拓展 1. 构造方法中有一个tableSizeFor方法值得关注 ```java static final int tableSizeFor(int cap) { // 扩容门槛为传入的初始容量往上取最近的2的n次方 int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return(n < 0) ? 1: (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } ``` 该方法使用位操作达到寻找向上的最近的2的n次方的效果,非常硬核和高效,可以重点掌握。 [^1]: 因为只需要在写时保证线程安全,读无需关心线程安全问题 [^2]: 在多线操作时,只保证最终插入的数据都存在,但是并不保证先执行add操作的元素的索引一定在后执行add操作之前 ]]> 0 /index.php/archives/40/#comments /index.php/feed/archives/40/ 【LeetCode】4.寻找两个正序数组的中位数 /index.php/archives/34/ /index.php/archives/34/ Mon, 13 Jul 2020 15:12:00 +0800 会写程序的男人博客 nums1.Length - 1) { //到头了 finalArr[i] = nums2[right]; right++; continue; } if (right > nums2.Length - 1) { finalArr[i] = nums1[left]; left++; continue; } if (nums1[left] <= nums2[right]) { finalArr[i] = nums1[left]; left++; } else { finalArr[i] = nums2[right]; right++; } } if (finalArr.Length % 2 == 0) { return (finalArr[finalArr.Length / 2] + finalArr[finalArr.Length / 2 - 1]) / 2.000; } else { return finalArr[finalArr.Length / 2]; } } } // @lc code=end ``` 这种解法的思路很简单,代码里就不详细讲解了,相信你这个小机灵鬼看代码就懂了 [scode type="blue"] ```C# if (finalArr.Length % 2 == 0) { return (finalArr[finalArr.Length / 2] + finalArr[finalArr.Length / 2 - 1]) / 2.000; } else { return finalArr[finalArr.Length / 2]; } ``` [/scode] 注意到上面这段代码,利用了中位数的性质 从根本上是:取有序数组中,第k小的数【k=(m+n)/2】, 偶数情况下是 **第k小**与 **第k+1** 小数的平均值。 ::aru:discovertruth:: 二分法,第k小,oh my god! 利用中位数是k=(m+n)/2和本身排好序的特点,取k=(m+n)/4位置【保证m+n的中位数不会被切掉】,逐个二分逼近中位数! ::aru:clap:: 真是个小机灵鬼 具体图解就不上了【画图好麻烦!】 [可以参考本链接 解法三][2] 其实理解起来没有那么简单,但是也不算复杂。 实际在实现中,需要考虑几个问题 1.数组边界 2.索引转化 3.变值k的变化及其边界 4.其它我忘记说的东西 ::aru:proud:: ```C# // @lc code=start public class Solution { //题目的关键是复杂度为log(m+n) ,log(m+n)一般就要想到“二分法”,中位数的性质决定,左边一半的数组小于等于中位数,右边一般的数组大于等于中位数,本身又是排好序的,问题转化为,从两个有序数组中寻找第K小的数[k=(m+n)/2] public double FindMedianSortedArrays(int[] nums1, int[] nums2) { int n = nums1.Length; int m = nums2.Length; int len = n + m; int pre = (len + 1) / 2; int k = (len + 2) / 2; if (len % 2 == 0) return (findKTh(nums1, 0, n - 1, nums2, 0, m - 1, pre) + findKTh(nums1, 0, n - 1, nums2, 0, m - 1, k))/2; else return findKTh(nums1, 0, n - 1, nums2, 0, m - 1, k); } public double findKTh(int[] nums1, int s1, int e1, int[] nums2, int s2, int e2, int k) { //计算长度 int len1 = e1 - s1 + 1; int len2 = e2 - s2 + 1; if (len1 > len2) { //比较大小,保证1<2,防止溢出 return findKTh(nums2, s2, e2, nums1, s1, e1, k); } if (len1 == 0) { //短数组没了,直接取长数组中的值 return nums2[s2 + k - 1]; } if (k == 1) { //取第1小的数,表示取最小,两个数组的起始中最小的取出来就行了 return nums1[s1] > nums2[s2] ? nums2[s2] : nums1[s1]; } //取索引,防止数组越界,超过长度取最后一个 int i = s1 + Math.Min(len1, k / 2) - 1; int j = s2 + Math.Min(len2, k / 2) - 1; if (nums1[i] > nums2[j]) { //短数组值大于长数组,淘汰长数组前面j个数,相当于找第k + s2 - 1 - j小 //k + s2 - 1 - j的解释,k表示当前s2开头的数组为0,的第k小,所以在总数组中是第k+s2-1小,j:本次淘汰的个数 return findKTh(nums1, s1, e1, nums2, j + 1, e2, k + s2 - 1 - j); } else { return findKTh(nums1, i + 1, e1, nums2, s2, e2, k + s1 - 1 - i); } } } // @lc code=end ``` ### 思考 - 数理系大佬可能经过一波操作推导出中位数的什么不等式组,利用 **神奇的数字游戏** ::aru:surprised:: 实现更快速复杂度更低的解法 - 为啥时间和占用空间和第一个理论上更差的算法差的不多甚至更大呢?【大概是leetcode代码运行有波动,测试用例不够全面反映算法问题?亦或,新算法的某些实现其实不符合理论上的推导,存在某些没注意到的高复杂度的部分没有发现?】 ### 膜拜大佬 如果大佬们有更好的解法,不如评论分享赐教~ [我的Leetcode解题代码库][1] [1]: https://github.com/会写程序的男人博客0929/.leetcode [2]: https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/ ]]> 1 /index.php/archives/34/#comments /index.php/feed/archives/34/ 【LeetCode】3.无重复字符的最长子串 /index.php/archives/32/ /index.php/archives/32/ Sun, 28 Jun 2020 17:44:00 +0800 会写程序的男人博客 3,破纪录了,记录为4 - 数完啦! 最后的记录是4! ***卧槽!人脑实在是太聪明了*** --- 这就是传说中的,滑动窗口算法! ## 正确解法 ```c# public class Solution { public int LengthOfLongestSubstring(string s) { //特殊处理,空字符串直接返回 if (string.IsNullOrEmpty(s)) { return 0; } //非空字符串最小返回值也是1 int maxRes = 1; //当前窗口 List nowWindow = new List(); foreach (var nowChar in s) { //遍历字符串的每个字符,窗口中存在,则抛弃前一个字符以前的所有 //如 字符串abcb 当前abc 遇到第2个b 则窗口abc->c(去掉b及b以前的所有) if (nowWindow.Contains(nowChar)) { nowWindow.RemoveRange(0, nowWindow.IndexOf(nowChar) + 1); } //再加上b到末尾成为窗口成为cb nowWindow.Add(nowChar); //当前窗口是否大于历史最大尺寸,大于则刷新纪录 if (nowWindow.Count > maxRes) { maxRes = nowWindow.Count; } } return maxRes; } } ``` ### 结果 - 987/987 cases passed (92 ms) - Your runtime beats 93.12 % of csharp submissions - Your memory usage beats 40 % of csharp submissions (25 MB) ### 思考 - 可以看到速度很快,空间占用还是不如人意。 - 可否使用Dictionary 利用key不重复的结构特性实现优化? - 空间占用不行是因为使用了List,可否通过记录窗口起始index及结束index,计算子串重复更改窗口始末index位置仅使用几个int记录索引完成计算,从而减少空间占用呢? ### 膜拜大佬 如果大佬们有更好的解法,不如评论分享赐教~ [我的Leetcode解题代码库][1] [1]: https://github.com/会写程序的男人博客0929/.leetcode ]]> 0 /index.php/archives/32/#comments /index.php/feed/archives/32/ 【LeetCode】2.两数相加 /index.php/archives/31/ /index.php/archives/31/ Sun, 28 Jun 2020 17:16:00 +0800 会写程序的男人博客 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 0 -> 8 原因:342 + 465 = 807 ``` --- [Discussion](https://leetcode-cn.com/problems/add-two-numbers/comments/) | [Solution](https://leetcode-cn.com/problems/add-two-numbers/solution/) ## 解法 ### 读题 实质上类似于小学学习的加法竖式,加了有进位就进到下一位 其实是为了考察单向链表的操作和理解 以及引用类型的理解 ### 解法 按照加法竖式的思想,链表遍历,计算当前位,有则进位,无则不进位 需要注意的是,两个数的长度可能不一样比如 100+12 ```c# /** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int x) { val = x; } * } */ public class Solution { public ListNode AddTwoNumbers(ListNode l1, ListNode l2) { //结果链表 ListNode res = new ListNode(0); //前一个节点 var preNode = res; //进位 int val = 0; //有一个不为空,计算 while (l1 != null || l2 != null) { //当前和(没有进位前,有可能有一个是null,null时为0) val = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + val; //进位余数,当前与10取余 preNode.next =new ListNode(val % 10); //节点移动 preNode = preNode.next; //进位数,当前与10相除 val = val / 10; //节点移动 l1 = l1?.next; l2 = l2?.next; } //注意判断最后一次计算还有进位的情况,有的话赋值节点 if (val != 0) preNode.next = new ListNode(val); return res.next; } } ``` ### 思考 时间复杂度: O(max(m,n))【两个数中最大长度是多少就算多少次】 空间复杂度: O(max(m,n))【结果最多多一位 即max(m,n)+1】 可不可以转化为int计算后再转回去?可能是一种逻辑,但是似乎时间和空间复杂度都会在链表和int互转的时候有所提升。 【敲黑板知识点!】最后为什么返回一个res.next呢?兄弟萌想到了吧! ::aru:pouting:: ### 膜拜大佬 如果大佬们有更好的解法,不如评论分享赐教~ [我的Leetcode解题代码库][1] [1]: https://github.com/会写程序的男人博客0929/.leetcode ]]> 0 /index.php/archives/31/#comments /index.php/feed/archives/31/ 【LeetCode】1.两数之和 /index.php/archives/30/ /index.php/archives/30/ Sun, 28 Jun 2020 16:32:00 +0800 会写程序的男人博客 0 /index.php/archives/30/#comments /index.php/feed/archives/30/ [20200530][新增]增加家具查询 /index.php/archives/29/ /index.php/archives/29/ Sat, 30 May 2020 17:21:00 +0800 会写程序的男人博客 0 /index.php/archives/29/#comments /index.php/feed/archives/29/ [2020052601][新增]增加花价查询 /index.php/archives/28/ /index.php/archives/28/ Tue, 26 May 2020 12:09:00 +0800 会写程序的男人博客 0 /index.php/archives/28/#comments /index.php/feed/archives/28/ [2020033101][新增]沙盘查询 /index.php/archives/25/ /index.php/archives/25/ Tue, 31 Mar 2020 18:34:00 +0800 会写程序的男人博客 0 /index.php/archives/25/#comments /index.php/feed/archives/25/ 【手摸手系列】教你开发QQ机器人 - SQLITE工具类 /index.php/archives/24/ /index.php/archives/24/ Fri, 27 Mar 2020 16:02:00 +0800 会写程序的男人博客 (T model) { return db.Insert(model); } public int Update(T model) { return db.Update(model); } public int Delete(T model) { return db.Delete(model); } public List Query(string sql) where T : new() { return db.Query(sql); } public int Execute(string sql) { return db.Execute(sql); } } } ``` [/collapse] 引用Native.SDK类【这一步可选,如果你不需要在Sql过程中调用一些酷Q API的话】 增加一个Common.cs存储一些公共类 [collapse status="false" title="Common.cs 点我展开"] ```c# using Native.Sdk.Cqp; namespace Site.会写程序的男人博客.Demo.DB { public static class Common { public static CQApi CqApi { get; set; }//可选,如果你不需要在Sql过程中调用一些酷Q API的话 public static CQLog CqLog { get; set; }//可选,如果你不需要在Sql过程中调用一些酷Q API的话 public static string DbPath { get; set; } public static SQLiteHelper sqliteHelper { get; set; } } } ``` [/collapse] ### 2.使用Sqlite-net 首先**Site.会写程序的男人博客.Demo.DB**下新建文件夹**Model**存放我们的数据库模型 用一个简单的示例来演示Sqlite的使用 [scode type="share"] 应用场景: 一张用户表,记录了对应的用户ID和用户信息(包括 昵称、性别) 可以通过ID查询用户信息。 【其他的使用在Helper中已经集成了基本的增删改了,也可以直接执行sql语句】 [/scode] 抽象表为一个Class类,我们这边可以用"T_"来标记用于数据库表的抽象模型 [collapse status="false" title="T_User.cs 点我展开"] ```c# using SQLite; namespace Site.会写程序的男人博客.Demo.DB.Model { public class T_User { /// /// 自增主键,用作用户ID /// [PrimaryKey, AutoIncrement]//分别表示主键,自增 public int Id { get; set; } /// /// 昵称 /// public string Nick { get; set; } = "我是默认昵称"; /// /// 性别 0未知 1男 2女 /// public int Gender { get; set; } = 0; } } ``` [/collapse] ```c# using SQLite; using System.Collections.Generic; using Site.会写程序的男人博客.Demo.DB.Model; namespace Site.会写程序的男人博客.Demo.DB { public class SQLiteHelper { public SQLiteConnection db; public SQLiteHelper(string connstr) { db = new SQLiteConnection(connstr); db.CreateTable();//表已存在不会重复创建,每次新增一个表的抽象类都在这里添加db.CreateTable(),才会在程序启动的时候自动创建表。 } //下面的内容相同,省略 } ``` 在"Event_AppEnable.cs"修改如下 [collapse status="false" title="新的Event_AppEnable.cs"] ```c# using System.Linq; using System.Text; using Native.Sdk.Cqp.Interface; using Native.Sdk.Cqp.EventArgs; using System.IO; using Native.Tool.IniConfig.Linq; using Site.会写程序的男人博客.Demo.DB; namespace Site.会写程序的男人博客.Demo.Code.Event { public class Event_AppEnable : IAppEnable { public void AppEnable(object sender, CQAppEnableEventArgs e) { Common.CqApi = e.CQApi; DB.Common.CqApi = e.CQApi; DB.Common.CqLog = e.CQLog; DB.Common.DbPath = e.CQApi.AppDirectory + "yourname.db";//".db前面的文字可以修改为你想要的数据库文件名称,建议使用英文" DB.Common.sqliteHelper = new SQLiteHelper(DB.Common.DbPath); string commandPath = Common.CqApi.AppDirectory + "command.ini"; IniObject iObject; //下面的内容相同,省略 } } } ``` [/collapse] 最后在任意想使用的地方使用 DB.Common.sqliteHelper.Add(某个T_User)//增加新数据 DB.Common.sqliteHelper.Update(某个T_User)//更新某个数据(根据主键) DB.Common.sqliteHelper.Delete(某个T_User)//删除某个数据(根据主键) [1]: https://github.com/会写程序的男人博客0929/Native.Cqp.Csharp/tree/demo [2]: https://github.com/praeclarum/sqlite-net/wiki/GettingStarted [3]: https://blog.csdn.net/WuLex/article/details/78636047 [4]: https://gitee.com/DotNet会写程序的男人博客/mydatadb/raw/master/img/20200327161822.png ]]> 0 /index.php/archives/24/#comments /index.php/feed/archives/24/