diff --git a/README.md b/README.md index a755f639..850bc4ca 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ |[LeetCode-链表](./leetcode_linked_list/main.md)|leetcode链表类算法练习| |[LeetCode-哈希表](./leetcode_hash/main.md)|leetcode哈希表类算法练习| |[LeetCode-栈+队列](./leetcode_stack/main.md)|leetcode栈+队列类算法练习| -|[LeetCode-树](./leetcode_tree/main.md)|leetcode树类算法练习| +|[LeetCode-二叉树](./leetcode_tree/main.md)|leetcode二叉树类算法练习| +|[LeetCode-其他](./leetcode_others/main.md)|leetcode其他分类算法练习| |[Django学习](./django_note/main.md)|Django学习笔记。| |[其他](./others/main.md)|redis,kafka,ZeroMQ等| diff --git "a/data_analysis/Numpy345円237円272円347円241円200円.md" "b/data_analysis/Numpy345円237円272円347円241円200円.md" new file mode 100644 index 00000000..f7fa5db5 --- /dev/null +++ "b/data_analysis/Numpy345円237円272円347円241円200円.md" @@ -0,0 +1 @@ +# Numpy基础 diff --git a/data_analysis/main.md b/data_analysis/main.md index b458bb24..198cc926 100644 --- a/data_analysis/main.md +++ b/data_analysis/main.md @@ -1,4 +1,4 @@ ## 数据分析 1. [《深入浅出统计学》笔记上](深入浅出统计学笔记上.md) - 2. [《深入浅出统计学》笔记下](深入浅出统计学笔记下.md) +3. [Python Numpy基础](Numpy基础.md) diff --git a/leetcode_others/main.md b/leetcode_others/main.md new file mode 100644 index 00000000..58ae9cbb --- /dev/null +++ b/leetcode_others/main.md @@ -0,0 +1,410 @@ +## 牛客网刷题 +### 1.字符串最后一个单词的长度 +输入一行,代表要计算的字符串,非空,长度小于5000。 +输出一个整数,表示输入字符串最后一个单词的长度。 +```python +s_ = input() +last_word = s_.split(' ')[-1] +print(len(last_word)) +``` + +### 2.计算某字母出现次数 +写出一个程序,接受一个由字母、数字和空格组成的字符串,和一个字母,然后输出输入字符串中该字母的出现次数。不区分大小写。 +第一行输入一个由字母和数字以及空格组成的字符串,第二行输入一个字母。 +输出输入字符串中含有该字符的个数。 +```python +s_ = input() +word_ = input().upper() +times = 0 +for w in s_.upper(): + if w == word_: + times += 1 +print(times) +``` + +### 3.明明的随机数 +明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了N个1到1000之间的随机整数(N≤1000),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成"去重"与"排序"的工作(同一个测试用例里可能会有多组数据(用于不同的调查),希望大家能正确处理)。 + +第一组是3个数字,分别是:2,2,1。 +第二组是11个数字,分别是:10,20,40,32,67,40,20,89,300,400,15。 + +**先输入N,再输入N个整数,然后对N个整数去重排序后输出** +```python +while True: + try: + N = int(input()) + input_set = set() + for _ in range(N): + random_ = int(input()) + input_set.add(random_) + input_list = list(input_set) + input_list.sort() + for s in input_list: + print(s) + except: + break +``` + +### 4.字符串分隔 +* 连续输入字符串,请按长度为8拆分每个字符串后输出到新的字符串数组; +* 长度不是8整数倍的字符串请在后面补数字0,空字符串不处理。 + +```python +while True: + try: + s = input() + for i in range(0, len(s), 8): + s_sub = s[i:i+8] + if len(s_sub) < 8: + s_sub += '0' * (8-len(s_sub)) + print(s_sub) + except: + break +``` + +### 5.进制转换 +写出一个程序,接受一个十六进制的数,输出该数值的十进制表示。输入一个十六进制的数值字符串。输出该数值的十进制字符串。 +```python +while True: + try: + s_16 = input() + print(eval(s_16)) + except: + break +``` + +### 6.质数因子 +功能:输入一个正整数,按照从小到大的顺序输出它的所有质因子(重复的也要列举)(如180的质因子为2 2 3 3 5 ) +最后一个数后面也要有空格 +```python +def find(number): + flag = True + for i in range(2, int(number ** 0.5 + 2)): + if number % i == 0: + flag = False + print(str(i), end=" ") + find(int(number / i)) + break + if flag: + print(number, end=' ') + + +num = int(input()) +find(num) +``` + +### 7.取近似值 +写出一个程序,接受一个正浮点数值,输出该数值的近似整数值。如果小数点后数值大于等于5,向上取整;小于5,则向下取整。 +```python +num = eval(input()) +if num - int(num)>=0.5: + print(int(num) + 1) +else: + print(int(num)) +``` + +### 8.合并表记录 +数据表记录包含表索引和数值(int范围的正整数),请对表索引相同的记录进行合并,即将相同索引的数值进行求和运算,输出按照key值升序进行输出。 + +先输入键值对的个数 +然后输入成对的index和value值,以空格隔开 + +输出合并后的键值对(多行) + +```python +N = int(input()) +dict_ = {} +for _ in range(N): + k, v = input().split(' ') + dict_[int(k)] = dict_.get(int(k), 0) + int(v) + +for item in sorted(dict_): + print('{} {}'.format(item, dict_[item])) +``` + +### 9.提取不重复的整数 +输入一个int型整数,按照从右向左的阅读顺序,返回一个不含重复数字的新的整数。 +保证输入的整数最后一位不是0。 +```python +s = input() +ans = list() + +for i in range(len(s)-1, -1, -1): + if s[i] not in ans: + ans.append(s[i]) +print(''.join(ans)) +``` + +### 10.字符个数统计 +编写一个函数,计算字符串中含有的不同字符的个数。字符在ACSII码范围内(0~127),换行表示结束符,不算在字符里。不在范围内的不作统计。多个相同的字符只计算一次 +例如,对于字符串abaca而言,有a、b、c三种不同的字符,因此输出3。 +```python +s = input() +ans = set() +for item in s: + if ord(item)>= 0 and ord(item) <= 127: + ans.add(item) +print(len(ans)) +``` +### 11.数字颠倒 +输入一个整数,将这个整数以字符串的形式逆序输出 +程序不考虑负数的情况,若数字含有0,则逆序形式也含有0,如输入为100,则输出为001 +```python +s = input() +ans = [] +for i in range(len(s)-1, -1, -1): + ans.append(s[i]) +print(''.join(ans)) +``` + +### 12.字符串反转 +接受一个只包含小写字母的字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000) +```python +s = input() +ans = [] +for i in range(len(s)-1, -1, -1): + ans.append(s[i]) +print(''.join(ans)) +``` + +### 13.句子逆序 +将一个英文语句以单词为单位逆序排放。例如"I am a boy",逆序排放后为"boy a am I" +所有单词之间用一个空格隔开,语句中除了英文字母外,不再包含其他字符 +```python +s = input() +s = s.split(' ') +ans = [] +for i in range(len(s)-1, -1, -1): + ans.append(s[i]) +print(' '.join(ans)) +``` + +### 14.字符串排序 +给定n个字符串,请对n个字符串按照字典序排列。 + +输入第一行为一个正整数n(1≤n≤1000),下面n行为n个字符串(字符串长度≤100),字符串中只含有大小写字母。 + +数据输出n行,输出结果为按照字典序排列的字符串。 + +```python +N = int(input()) +ans = [] +for i in range(N): + s = input() + ans.append(s) +ans.sort() +for item in ans: + print(item) +``` + +### 15.二进制中1的个数 +输入一个int型的正整数,计算出该int型数据在内存中(这个数转换成2进制后,输出1的个数)存储时1的个数。 +```python +num = int(input()) +s = str(bin(num)) +times = 0 +for i in range(2, len(s)): + if s[i] == '1': + times+=1 +print(times) +``` + +### 16.购物单 +### 17.坐标移动 +### 18.识别有效的IP地址和掩码并进行分类统计 +### 19.简单错误记录 +### 20.密码验证合格程序 +### 21.简单密码破解 +密码是我们生活中非常重要的东东,我们的那么一点不能说的秘密就全靠它了。哇哈哈. 接下来渊子要在密码之上再加一套密码,虽然简单但也安全。 + +假设渊子原来一个BBS上的密码为zvbo9441987,为了方便记忆,他通过一种算法把这个密码变换成YUANzhi1987,这个密码是他的名字和出生年份,怎么忘都忘不了,而且可以明目张胆地放在显眼的地方而不被别人知道真正的密码。 + +他是这么变换的,大家都知道手机上的字母: 1--1, abc--2, def--3, ghi--4, jkl--5, mno--6, pqrs--7, tuv--8 wxyz--9, 0--0,就这么简单,渊子把密码中出现的小写字母都变成对应的数字,数字和其他的符号都不做变换, + +声明:密码中没有空格,而密码中出现的大写字母则变成小写之后往后移一位,如:X,先变成小写,再往后移一位,不就是y了嘛,简单吧。记住,z往后移是a哦。 +```python +dict_ = {'1': '1', '0': '0', 'a': '2', 'b': '2', 'c': '2', 'd': '3', 'e': '3', 'f': '3', 'g': '4', 'h': '4', 'i': '4', 'j': '5', 'k': '5', 'l': '5', 'm': '6', 'n': '6', 'o': '6', 'p': '7', 'q': '7', 'r': '7', 's': '7', 't': '8', 'u': '8', 'v': '8', 'w': '9', 'x': '9', 'y': '9', 'z': '9'} +ans = [] +password = input() +for s in password: + if s.isupper(): + # 大写转换为小写 向后推一位 + s = s.lower() + if s == 'z': + s = 'a' + else: + s = chr(ord(s) + 1) + ans.append(s) + elif s.islower(): + # 小写转换为数字 + s = dict_.get(s) + ans.append(s) + elif s.isdigit(): + # 数字不变 + ans.append(s) +print(''.join(ans)) +``` + +### 22.汽水瓶 +有这样一道智力题:"某商店规定:三个空汽水瓶可以换一瓶汽水。小张手上有十个空汽水瓶,她最多可以换多少瓶汽水喝?"答案是5瓶,方法如下:先用9个空瓶子换3瓶汽水,喝掉3瓶满的,喝完以后4个空瓶子,用3个再换一瓶,喝掉这瓶满的,这时候剩2个空瓶子。然后你让老板先借给你一瓶汽水,喝掉这瓶满的,喝完以后用3个空瓶子换一瓶满的还给老板。如果小张手上有n个空汽水瓶,最多可以换多少瓶汽水喝? +```python +while True: + try: + nums = int(input()) + ans = 0 + while nums>= 3: + m = nums % 3 + n = nums // 3 + ans += n + nums = m + n + if nums == 2: + ans += 1 + if ans: + print(ans) + except: + break +``` +### 23.删除字符串中出现最少的字符 +实现删除字符串中出现次数最少的字符,若多个字符出现次数一样,则都删除。输出删除这些单词后的字符串,字符串中其它字符保持原来的顺序。 +注意每个输入文件有多组输入,即多个字符串用回车隔开 +```python +输入 +abcdd +aabcddd + +输出 +dd +aaddd +``` +**统计字符串中每个字符出现的次数,对字典进行value排序,确定要删除的字符** +```python +import sys +lines = sys.stdin.readlines() +for line in lines: + line = line.replace('\n', '') + s_count = {} + for s in line: + s_count[s] = s_count.get(s, 0) + 1 + + l = sorted(s_count.items(), key=lambda x: x[1]) + min_ = l[0][1] + for item in l: + if min_ != item[1]: + break + line = line.replace(item[0], '') + print(line) +``` +### 24.合唱队 +### 25.数据分类处理 +### 26.字符串排序 +### 27.查找兄弟单词 +定义一个单词的"兄弟单词"为:交换该单词字母顺序,而不添加、删除、修改原有的字母就能生成的单词。 +兄弟单词要求和原来的单词不同。例如:ab和ba是兄弟单词。ab和ab则不是兄弟单词。 +现在给定你n个单词,另外再给你一个单词str,让你寻找str的兄弟单词里,字典序第k大的那个单词是什么? +注意:字典中可能有重复单词。本题含有多组输入数据。 +```python +输入 +3 abc bca cab abc 1 +输出 +2 +bca +``` + +```python +line = input() +list_ = line.split(' ') +K = int(list_[-1]) +s_ = list_[-2] + +def is_brother(s1, s2): + if len(s1) != len(s2): + return False + if s1 == s2: + return False + s1_dict = {} + for s in s1: + s1_dict[s] = s1_dict.get(s, 0) + 1 + s2_dict = {} + for s in s2: + s2_dict[s] = s2_dict.get(s, 0) + 1 + if s1_dict == s2_dict: + return True + return False + +ans = [] +for word in list_[1:-2]: + if is_brother(word, s_): + ans.append(word) +ans.sort() +print(len(ans)) + +# 非常重要,注意审题 +if len(ans)>= K: + print(ans[K-1]) +``` + +### 28.素数伴侣 +### 29.字符串加密 +1、对输入的字符串进行加解密,并输出。 + +2、加密方法为: + +当内容是英文字母时则用该英文字母的后一个字母替换,同时字母变换大小写,如字母a时则替换为B;字母Z时则替换为a; + +当内容是数字时则把该数字加1,如0替换1,1替换2,9替换0; + +其他字符不做变化。 + +3、解密方法为加密的逆过程。 + +**注意奇数输入为加密,偶数输入为解密** +```python +def encode_s(s): + ans = [] + for item in s: + if item.isdigit(): + ans.append(str((int(item) + 1) % 10)) + elif item.isupper(): + if item == 'Z': + ans.append('a') + else: + ans.append(chr(ord(item) + 1).lower()) + elif item.islower(): + if item == 'z': + ans.append('A') + else: + ans.append(chr(ord(item) + 1).upper()) + return ''.join(ans) + +def decode_s(s): + ans = [] + for item in s: + if item.isdigit(): + if item == '0': + ans.append('9') + else: + ans.append(str(int(item) - 1)) + elif item.isupper(): + if item == 'A': + ans.append('z') + else: + ans.append(chr(ord(item) - 1).lower()) + elif item.islower(): + if item == 'a': + ans.append('Z') + else: + ans.append(chr(ord(item) - 1).upper()) + return ''.join(ans) + +count = 0 +while True: + try: + s = input() + count += 1 + if count % 2 == 0: + print(decode_s(s)) + else: + print(encode_s(s)) + except: + break +``` diff --git a/leetcode_stack/main.md b/leetcode_stack/main.md index f0f3ce2d..aba97d96 100644 --- a/leetcode_stack/main.md +++ b/leetcode_stack/main.md @@ -16,6 +16,8 @@ * [用队列实现栈](用队列实现栈.md) * [删除字符串中的所有相邻重复项](删除字符串中的所有相邻重复项.md) * [逆波兰表达式求值](逆波兰表达式求值.md) +* [滑动窗口最大值(困难)](滑动窗口最大值.md) +* [前K个高频元素](前K个高频元素.md) diff --git "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円344円270円255円345円272円217円351円201円215円345円216円206円.md" "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円344円270円255円345円272円217円351円201円215円345円216円206円.md" new file mode 100644 index 00000000..c60f7678 --- /dev/null +++ "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円344円270円255円345円272円217円351円201円215円345円216円206円.md" @@ -0,0 +1,59 @@ +# 94.二叉树的中序遍历 +## 题目 +给定一个二叉树的根节点 root ,返回它的 中序 遍历。 + +## 分析 +#### 递归 +中序遍历是按照 左子树-根节点-右子树的方式遍历 ,在访问左子树或右子树的时候,也是同样的方式遍历 + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +def inorderTraversal(root): + def inorder(root): + if not root: + return + inorder(root.left) + ans.append(root.val) + inorder(root.right) + + ans = list() + inorder(root) + return ans + +if __name__ == '__main__': + root = TreeNode(1) + node_1 = TreeNode(2) + node_2 = TreeNode(3) + root.left = node_1 + root.right = node_2 + + res = inorderTraversal(root) + print(res) +``` + +#### 迭代 +![](../pic/leetcode_tree/94_1.gif) + +```python +def inorderTraversal(root): + ans = list() + if not root: + return ans + + stack = [] + node = root + while stack or node: + while node: + stack.append(node) + node = node.left + node = stack.pop() + ans.append(node.val) + node = node.right + return ans +``` diff --git "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円211円215円345円272円217円351円201円215円345円216円206円.md" "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円211円215円345円272円217円351円201円215円345円216円206円.md" new file mode 100644 index 00000000..4b867557 --- /dev/null +++ "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円211円215円345円272円217円351円201円215円345円216円206円.md" @@ -0,0 +1,59 @@ +# 144.二叉树的前序遍历 +## 题目 +给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 + +## 分析 +#### 递归 +前序遍历是按照 根节点-左子树-右子树的方式遍历 ,在访问左子树或右子树的时候,也是同样的方式遍历 + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +def preorderTraversal(root): + def preorder(root): + if not root: + return + ans.append(root.val) + preorder(root.left) + preorder(root.right) + + ans = list() + preorder(root) + return ans +``` + +#### 迭代 +* 利用栈,每遍历到非空节点,就记录节点的值(根)就入栈,然后继续遍历该节点的左节点(左) +* 如果左节点为空,说明节点的做节点遍历完毕,结束循环,弹栈,开始遍历右节点,重复第一步 + +![](../pic/leetcode_tree/144_1.gif) + +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +def preorderTraversal(root): + ans = list() + if not root: + return ans + + stack = [] + node = root + while stack or node: + while node: + ans.append(node.val) + stack.append(node) + node = node.left + node = stack.pop() + node = node.right + return ans +``` diff --git "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円220円216円345円272円217円351円201円215円345円216円206円.md" "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円220円216円345円272円217円351円201円215円345円216円206円.md" new file mode 100644 index 00000000..59d99584 --- /dev/null +++ "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円220円216円345円272円217円351円201円215円345円216円206円.md" @@ -0,0 +1,47 @@ +# 145.二叉树的后序遍历 +## 题目 +给定一个二叉树,返回它的 后序 遍历。 + +## 分析 +#### 递归 +```python +def postorderTraversal(root): + def postorder(root): + if not root: + return + postorder(root.left) + postorder(root.right) + ans.append(root.val) + + ans = list() + postorder(root) + return ans +``` + +## 迭代 +```python +def postorderTraversal(root): + ans = list() + if not root: + return ans + + stack = [] + node = root + prev = None + while stack or node: + while node: + # 遍历左节点 + stack.append(node) + node = node.left + node = stack.pop() + if not node.right or node.right == prev: + # 根节点 + ans.append(node.val) + prev = node + node = None + else: + # 遍历右节点 + stack.append(node) + node = node.right + return ans +``` diff --git "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円261円202円345円272円217円351円201円215円345円216円206円.md" "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円261円202円345円272円217円351円201円215円345円216円206円.md" new file mode 100644 index 00000000..b6d1d3df --- /dev/null +++ "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円345円261円202円345円272円217円351円201円215円345円216円206円.md" @@ -0,0 +1,34 @@ +# 102.二叉树的层序遍历 +## 题目 +给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 + +## 分析 +* 队列存放当前层所有节点 +* 每次拓展下一层的时候,需要将当前队列中所有节点都取出来进行拓展 +* 保证每次拓展完时候,队列中存放的都是同一层的所有节点 +* 用列表保存每层的元素值 + +```python +from queue import Queue +def levelOrder(root): + ans = list() + if not root: + return ans + q = Queue() + q.put(root) + while q.qsize(): + size = q.qsize() + tmp = [] + for _ in range(size): + # 取出这一层所有的元素 + node = q.get() + if node: + tmp.append(node.val) + if node.left: + q.put(node.left) + if node.right: + q.put(node.right) + if tmp: + ans.append(tmp) + return ans +``` diff --git "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円211円200円346円234円211円350円267円257円345円276円204円.md" "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円211円200円346円234円211円350円267円257円345円276円204円.md" new file mode 100644 index 00000000..2470a37a --- /dev/null +++ "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円211円200円346円234円211円350円267円257円345円276円204円.md" @@ -0,0 +1,70 @@ +# 257.二叉树的所有路径 +## 题目 +给定一个二叉树,返回所有从根节点到叶子节点的路径。 + +说明: 叶子节点是指没有子节点的节点。 + +```python +输入: + + 1 + / \ +2 3 + \ + 5 + +输出: ["1->2->5", "1->3"] + +解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3 +``` + +## 分析 +#### 迭代 + +* 定义两个队列,一个队列存储节点,一个队列存储根到该节点的路径 +* 遍历每一个节点的同时,也在记录路径,直到叶子节点,将路径添加到返回列表中 + +![](../pic/leetcode_tree/257_1.gif) + +```python +from queue import Queue +def binaryTreePaths(root): + if not root: + return [] + node_queue = Queue() + path_queue = Queue() + node_queue.put(root) + path_queue.put(str(root.val)) + + ans = [] + while node_queue.qsize(): + node = node_queue.get() + path = path_queue.get() + if node.left: + node_queue.put(node.left) + path_queue.put(path + '->' + str(node.left.val)) + if node.right: + node_queue.put(node.right) + path_queue.put(path + '->' + str(node.right.val)) + if node.left is None and node.right is None: + ans.append(path) + return ans +``` + +#### 递归 +```python +def binaryTreePaths(root): + def construct_paths(root, path): + if root: + path += str(root.val) + if root.left is None and root.right is None: + ans.append(path) + else: + path += '->' + construct_paths(root.left, path) + construct_paths(root.right, path) + + ans = [] + construct_paths(root, '') + return ans +``` diff --git "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円244円247円346円267円261円345円272円246円.md" "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円244円247円346円267円261円345円272円246円.md" index 9d6457c9..a90d853b 100644 --- "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円244円247円346円267円261円345円272円246円.md" +++ "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円244円247円346円267円261円345円272円246円.md" @@ -1,4 +1,5 @@ -## 二叉树的最大深度 +# 104.二叉树的最大深度 +## 题目 给定一个二叉树,找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 @@ -15,42 +16,39 @@ 15 7 ``` -#### 代码 +## 分析 +#### 递归 递归: 最大深度 = max(左子树最大深度,右子树最大深度) + 1 + ```python def maxDepth(root): if not root: return 0 - - max_depth_of_subtree = max(maxDepth(root.left), maxDepth(root.right)) - return 1 + max_depth_of_subtree + return 1 + max(maxDepth(root.left), maxDepth(root.right)) ``` -广度优先搜索: +#### 迭代 * 队列存放当前层所有节点 * 每次拓展下一层的时候,需要将当前队列中所有节点都取出来进行拓展 * 保证每次拓展完时候,队列中存放的都是当前层的所有节点 * 用一个变量维护拓展的次数 ```python +from queue import Queue def maxDepth(root): if not root: return 0 - depth = 0 - queue_ = Queue() - queue_.put(root) - - while queue_.qsize(): - depth += 1 - size = queue_.qsize() + q = Queue() + q.put(root) + ans = 0 + while q.qsize(): + ans += 1 + size = q.qsize() for _ in range(size): - node = queue_.get() - left = node.left - right = node.right - if left: - queue_.put(left) - if right: - queue_.put(right) - - return depth + node = q.get() + if node.left: + q.put(node.left) + if node.right: + q.put(node.right) + return ans ``` diff --git "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円260円217円346円267円261円345円272円246円.md" "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円260円217円346円267円261円345円272円246円.md" index 03c672b3..6e77da31 100644 --- "a/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円260円217円346円267円261円345円272円246円.md" +++ "b/leetcode_tree/344円272円214円345円217円211円346円240円221円347円232円204円346円234円200円345円260円217円346円267円261円345円272円246円.md" @@ -1,4 +1,5 @@ -## 二叉树的最小深度 +# 111.二叉树的最小深度 +## 题目 给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 @@ -11,30 +12,28 @@ 输出:2 ``` -#### 代码 -广度优先搜素,迭代,与[二叉树的最大深度](二叉树的最大深度.md)类似, -只是碰到叶子结点(没有子节点)直接返回深度。 -```python -class Solution: - def minDepth(self, root: TreeNode) -> int: - from queue import Queue - if not root: - return 0 - queue_ = Queue() - queue_.put(root) +## 分析 +与[二叉树的最大深度](二叉树的最大深度.md)逻辑一样,只是碰到叶子节点直接返回深度,不再继续向下遍历 - depth = 0 - while queue_.qsize(): - depth += 1 - size = queue_.qsize() - for _ in range(size): - node = queue_.get() - left = node.left - right = node.right - if not left and not right: - return depth - if left: - queue_.put(left) - if right: - queue_.put(right) +#### 迭代 +```python +from queue import Queue +def minDepth(root): + if not root: + return 0 + q = Queue() + q.put(root) + depth = 0 + while q.qsize(): + depth += 1 + size = q.qsize() + for _ in range(size): + node = q.get() + if node.left is None and node.right is None: + return depth + if node.left: + q.put(node.left) + if node.right: + q.put(node.right) + return depth ``` diff --git "a/leetcode_tree/345円217円215円350円275円254円344円272円214円345円217円211円346円240円221円.md" "b/leetcode_tree/345円217円215円350円275円254円344円272円214円345円217円211円346円240円221円.md" new file mode 100644 index 00000000..815d3252 --- /dev/null +++ "b/leetcode_tree/345円217円215円350円275円254円344円272円214円345円217円211円346円240円221円.md" @@ -0,0 +1,59 @@ +# 226.反转二叉树 +## 题目 +翻转一棵二叉树。 +```python +输入 + 4 + / \ + 2 7 + / \ / \ +1 3 6 9 + +输出 + 4 + / \ + 7 2 + / \ / \ +9 6 3 1 +``` + +## 分析 +#### 递归 +* 新树的左子树,是旧树的右子树的反转树 +* 新树的右子树,是旧树的左子树的反转树 + +![](../pic/leetcode_tree/226_1.png) +```python +def invertTree(root): + if not root: + return root + left = invertTree(root.right) + right = invertTree(root.left) + root.left = left + root.right = right + return root +``` + +#### 迭代 +* 遍历所有节点,交换每个节点的左右节点的位置 + +![](../pic/leetcode_tree/226_1.gif) + +```python +def invertTree(root): + if not root: + return root + q_ = [root] + while q_: + node = q_.pop() + left = node.left + right = node.right + # 左右两个子节点交换 + node.left = right + node.right = left + if node.left: + q_.append(node.left) + if node.right: + q_.append(node.right) + return root +``` diff --git "a/leetcode_tree/345円217円246円344円270円200円344円270円252円346円240円221円347円232円204円345円255円220円346円240円221円.md" "b/leetcode_tree/345円217円246円344円270円200円344円270円252円346円240円221円347円232円204円345円255円220円346円240円221円.md" new file mode 100644 index 00000000..c60c22a6 --- /dev/null +++ "b/leetcode_tree/345円217円246円344円270円200円344円270円252円346円240円221円347円232円204円345円255円220円346円240円221円.md" @@ -0,0 +1,51 @@ +# 572.另一个树的子树 +## 题目 +给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。 +```python +给定的树 s: + 3 + / \ + 4 5 + / \ + 1 2 +给定的树 t: + 4 + / \ +1 2 +返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。 +``` + +## 分析 +迭代,遍历s树,如果子树的根节点与t树的根节点相等,再去判断两个数是否相等,[相同的树](相同的树.md) +```python +from queue import Queue +def isSubtree(s, t): + def isSame(p, q): + if p is None and q is None: + return True + if not p or not q: + return False + if p.val != q.val: + return False + else: + return isSame(p.left, q.left) and isSame(p.right, q.right) + + Q = Queue() + Q.put(s) + while Q.qsize(): + node = Q.get() + if node.val == t.val: + if isSame(node, t): + return True + else: + if node.left: + Q.put(node.left) + if node.right: + Q.put(node.right) + else: + if node.left: + Q.put(node.left) + if node.right: + Q.put(node.right) + return False +``` diff --git "a/leetcode_tree/345円256円214円345円205円250円344円272円214円345円217円211円346円240円221円347円232円204円350円212円202円347円202円271円344円270円252円346円225円260円.md" "b/leetcode_tree/345円256円214円345円205円250円344円272円214円345円217円211円346円240円221円347円232円204円350円212円202円347円202円271円344円270円252円346円225円260円.md" new file mode 100644 index 00000000..d390d7ef --- /dev/null +++ "b/leetcode_tree/345円256円214円345円205円250円344円272円214円345円217円211円346円240円221円347円232円204円350円212円202円347円202円271円344円270円252円346円225円260円.md" @@ -0,0 +1,36 @@ +# 222.完全二叉树的节点个数 +## 题目 +给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。 + +## 分析 +#### 迭代 +与[二叉树的层序遍历](二叉树的层序遍历.md)逻辑一样,每次碰到一个节点就累加 +```python +from queue import Queue +def countNodes(root): + if not root: + return 0 + q = Queue() + q.put(root) + ans = 0 + while q.qsize(): + size = q.qsize() + for _ in range(size): + node = q.get() + ans += 1 + if node.left: + q.put(node.left) + if node.right: + q.put(node.right) + return ans +``` + +#### 递归 +```python +def countNodes(root): + if not root: + return 0 + left_nodes = countNodes(root.left) + right_nodes = countNodes(root.right) + return left_nodes + right_nodes + 1 +``` diff --git "a/leetcode_tree/345円257円271円347円247円260円344円272円214円345円217円211円346円240円221円.md" "b/leetcode_tree/345円257円271円347円247円260円344円272円214円345円217円211円346円240円221円.md" index 3d4039a5..48caa41e 100644 --- "a/leetcode_tree/345円257円271円347円247円260円344円272円214円345円217円211円346円240円221円.md" +++ "b/leetcode_tree/345円257円271円347円247円260円344円272円214円345円217円211円346円240円221円.md" @@ -1,4 +1,5 @@ -## 对称二叉树 +# 101.对称二叉树 +## 题目 给定一个二叉树,检查它是否是镜像对称的。 #### 示例 @@ -19,73 +20,64 @@ 3 3 ``` -#### 代码 -**迭代** +## 分析 +#### 递归 +* 如果一个树镜像对称,则左子树和右子树互为镜像 +* 只需要判断左子树和右子树是不是互为镜像 -和[相同的树](相同的树.md)类似,遍历树节点,保存到队列中,然后依次比较 - -* 根节点为None,直接返回True -* 两个队列分别存储根节点的左子节点与右子节点 -* 分别从两个队列中取出一个节点,进行比较 -* 如果两个节点一个为None,一个不为None,则说明不对称,直接返回False -* 如果两个节点都不为None,比较节点值,如果值不相等,则说明不对称,直接返回False -* 如果值相等,按照顺序,添加节点的子节点到队列中,等待下一轮比较 +![](../pic/leetcode_tree/101_1.png) ```python -class Solution: - def isSymmetric(self, root: TreeNode) -> bool: - if not root: +def isSymmetric(root): + def check(node_1, node_2): + if node_1 is None and node_2 is None: + # 两个节点都是None return True - left = root.left - right = root.right - - queue1 = deque([left]) - queue2 = deque([right]) - - while queue1 and queue2: - node1 = queue1.popleft() - node2 = queue2.popleft() - - if (not node1) ^ (not node2): - # 两个子节点一个为None,一个不为None - return False - - if node1 is None and node2 is None: - # 两个子节点都是None - continue - - if node1.val != node2.val: - return False - - left1 = node1.left - right1 = node1.right - left2 = node2.left - right2 = node2.right - - queue1.append(left1) - queue2.append(right2) - queue1.append(right1) - queue2.append(left2) + if not node_1 or not node_2: + # 一个为None,一个不为None + return False + if node_1.val != node_2.val: + return False + return check(node_1.left, node_2.right) and check(node_1.right, node_2.left) - return not queue1 and not queue2 + res = check(root.left, root.right) + return res ``` -**递归** +#### 迭代 +* 两个队列分别存储根节点的左子节点与右子节点 +* 分别从两个队列中取出一个节点,进行比较 +* 如果两个节点一个为None,一个不为None,则说明不对称,直接返回False +* 如果两个节点都不为None,比较节点值,如果值不相等,则说明不对称,直接返回False +* 如果值相等,按照顺序,添加节点的子节点到队列中,等待下一轮比较 ```python -class Solution: - - def check(self, p, q): - if (not p) ^ (not q): +from queue import Queue +def isSymmetric(root): + left = root.left + right = root.right + queue_1 = Queue() + queue_2 = Queue() + queue_1.put(left) + queue_2.put(right) + + while queue_1.qsize() and queue_2.qsize(): + node_1 = queue_1.get() + node_2 = queue_2.get() + if node_1 is None and node_2 is None: + continue + if not node_1 or not node_2: return False - if p is None and q is None: - return True - - if p.val != q.val: + if node_1.val != node_2.val: return False + queue_1.put(node_1.left) + queue_2.put(node_2.right) - return self.check(p.left, q.right) and self.check(p.right, q.left) + queue_1.put(node_1.right) + queue_2.put(node_2.left) - def isSymmetric(self, root: TreeNode) -> bool: - return self.check(root, root) + if queue_1.qsize() or queue_2.qsize(): + # 两个队列必须同时为空才说明对称 + return False + return True ``` diff --git "a/leetcode_tree/345円267円246円345円217円266円345円255円220円344円271円213円345円222円214円.md" "b/leetcode_tree/345円267円246円345円217円266円345円255円220円344円271円213円345円222円214円.md" new file mode 100644 index 00000000..337083a0 --- /dev/null +++ "b/leetcode_tree/345円267円246円345円217円266円345円255円220円344円271円213円345円222円214円.md" @@ -0,0 +1,70 @@ +# 404.左叶子之和 +## 题目 +计算给定二叉树的所有左叶子之和。 +```python + 3 + / \ + 9 20 + / \ + 15 7 + +在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 +``` + +## 分析 +#### 迭代 +遍历树,如果一个节点的左节点是叶子节点,就累加 + +```python +from queue import Queue +def sumOfLeftLeaves(root): + def isLeavesNode(node): + # 判断一个节点是不是叶子节点 + if node.left is None and node.right is None: + return True + return False + + if not root: + return 0 + ans = 0 + q = Queue() + q.put(root) + while q.qsize(): + node = q.get() + if node.left: + if isLeavesNode(node.left): + ans += node.left.val + else: + q.put(node.left) + + if node.right: + if not isLeavesNode(node.right): + q.put(node.right) + return ans +``` + +#### 深度优先搜索 +```python +def sumOfLeftLeaves(root): + def isLeavesNode(node): + # 判断一个节点是不是叶子节点 + if node.left is None and node.right is None: + return True + return False + + def dfs(root): + ans = 0 + if root.left: + if isLeavesNode(root.left): + ans += root.left.val + else: + ans += dfs(root.left) + if root.right: + if not isLeavesNode(root.right): + ans += dfs(root.right) + return ans + + if not root: + return 0 + return dfs(root) +``` diff --git "a/leetcode_tree/345円271円263円350円241円241円344円272円214円345円217円211円346円240円221円.md" "b/leetcode_tree/345円271円263円350円241円241円344円272円214円345円217円211円346円240円221円.md" index be3f2838..a3028c32 100644 --- "a/leetcode_tree/345円271円263円350円241円241円344円272円214円345円217円211円346円240円221円.md" +++ "b/leetcode_tree/345円271円263円350円241円241円344円272円214円345円217円211円346円240円221円.md" @@ -1,4 +1,5 @@ -## 平衡二叉树 +# 110.平衡二叉树 +## 题目 给定一个二叉树,判断它是否是高度平衡的二叉树。一棵高度平衡二叉树定义为: **一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。** @@ -9,39 +10,22 @@ 输出:true ``` -#### 代码 -自顶向下递归 +## 分析 * 左右两个子树的高度差小于等于1 * 平衡二叉树的两个子树也是平衡二叉树 -```python -class Solution: - def isBalanced(self, root: TreeNode) -> bool: - def maxHeight(node): - if not node: - return 0 - return max(maxHeight(node.left), maxHeight(node.right)) + 1 - if not root: - return True - left = root.left # 左子树 - right = root.right # 右子树 - if self.isBalanced(left) and self.isBalanced(right) and abs(maxHeight(left) - maxHeight(right)) <= 1: - return True - return False -``` - -自底向上(没懂) - ```python def isBalanced(root): - def heigh(root): + def depth(root): + # 计算树的深度 if not root: return 0 - left_height = heigh(root.left) - right_height = heigh(root.right) - if left_height == -1 or right_height == -1 or abs(left_height-right_height)> 1: - return -1 - else: - return max(left_height, right_height) + 1 - return heigh(root)>= 0 + return 1 + max(depth(root.left), depth(root.right)) + + if not root: + return True + left = root.left + right = root.right + return isBalanced(left) and isBalanced(right) and abs(depth(left) - depth(right)) <= 1 + ``` diff --git "a/leetcode_tree/346円211円276円346円240円221円345円267円246円344円270円213円350円247円222円347円232円204円345円200円274円.md" "b/leetcode_tree/346円211円276円346円240円221円345円267円246円344円270円213円350円247円222円347円232円204円345円200円274円.md" new file mode 100644 index 00000000..aa4e901a --- /dev/null +++ "b/leetcode_tree/346円211円276円346円240円221円345円267円246円344円270円213円350円247円222円347円232円204円345円200円274円.md" @@ -0,0 +1,41 @@ +# 513.找树左下角的值 +## 题目 +给定一个二叉树,在树的最后一行找到最左边的值。 +```python +输入: + + 1 + / \ + 2 3 + / / \ + 4 5 6 + / + 7 + +输出: +7 +``` + +## 分析 +#### 迭代 +层序遍历,队列中存放的都是同一层的节点,每一层第一个节点就是最左边的节点 + +```python +from queue import Queue +def findBottomLeftValue(root): + q = Queue() + q.put(root) + while q.qsize(): + ans = None + size = q.qsize() + for _ in range(size): + node = q.get() + if not ans: + # 记录每一层最左边的节点 + ans = node + if node.left: + q.put(node.left) + if node.right: + q.put(node.right) + return ans.val +``` diff --git "a/leetcode_tree/347円233円270円345円220円214円347円232円204円346円240円221円.md" "b/leetcode_tree/347円233円270円345円220円214円347円232円204円346円240円221円.md" index 38c5a8e4..059fb79b 100644 --- "a/leetcode_tree/347円233円270円345円220円214円347円232円204円346円240円221円.md" +++ "b/leetcode_tree/347円233円270円345円220円214円347円232円204円346円240円221円.md" @@ -1,4 +1,5 @@ -## 相同的树 +# 100.相同的树 +## 题目 给定两个二叉树,编写一个函数来检验它们是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 @@ -14,94 +15,65 @@ 输出: true ``` -#### 代码 -递归
+## 分析 +#### 递归 * 如果两个二叉树都为空,则两个二叉树相同 * 如果两个二叉树中有且只有一个为空,则两个二叉树一定不相同 * 如果两个二叉树都不为空,那么首先判断它们的根节点的值是否相同 * 若相同,再分别判断两个二叉树的左子树是否相同以及右子树是否相同 ```python - -class TreeNode: - def __init__(self, val=0, left=None, right=None): - self.val = val - self.left = left - self.right = right - - def isSameTree(p, q): if p is None and q is None: return True - - if p and q: - if p.val == q.val: - return isSameTree(p.left, q.left) and isSameTree(p.right, q.right) - else: - return False - - return False - - -if __name__ == '__main__': - p = TreeNode(1) - p.left = TreeNode(2) - p.right = TreeNode(3) - - q = TreeNode(1) - q.left = TreeNode(2) - q.right = TreeNode(3) - - res = isSameTree(p, q) - print(res) + if not p or not q: + return False + if p.val != q.val: + return False + else: + return isSameTree(p.left, q.left) and isSameTree(p.right, q.right) ``` -迭代
+#### 迭代 * 使用两个队列分别存储两个二叉树的节点 * 初始时将两个二叉树的根节点分别加入两个队列。每次从两个队列各取出一个节点,进行比较操作 * 比较两个节点的值,如果两个节点的值不相同则两个二叉树一定不同 -* 如果两个节点的值相同,则判断两个节点的子节点是否为空,如果一个节点的左节点为空,另一个节点的子节点不为空,则两个二叉树的结构不同,因此两个二叉树一定不同 +* 如果两个节点的值相同,则判断两个节点的子节点是否为空,如果一个节点的子节点为空,另一个节点的子节点不为空,则两个二叉树的结构不同,因此两个二叉树一定不同 * 如果两个节点的子节点的结构相同,则将两个节点的非空子节点分别加入两个队列 ```python -from collections import deque +from queue import Queue def isSameTree(p, q): - if p is None and q is None: - # 根节点全为None - return True - - if not p or not q: - # 根节点一个为None,一个不为None - return False - - queue1 = deque([p]) - queue2 = deque([q]) - - while queue1 and queue2: - node1 = queue1.popleft() - node2 = queue2.popleft() - # 比较两个节点的值 - if node1.val != node2.val: + queue_1 = Queue() + queue_2 = Queue() + if p: + queue_1.put(p) + if q: + queue_2.put(q) + + while queue_1.qsize() and queue_2.qsize(): + node_1 = queue_1.get() + node_2 = queue_2.get() + if node_1.val != node_2.val: + # 两个节点都不为None,比较节点值 return False - left1 = node1.left - right1 = node1.right - left2 = node2.left - right2 = node2.right - - # 比较两个子节点的结构,如果一个左子节点为None,另一个左子节点不为None,则返回False - # 加not是因为 节点对象不能直接进行异或运算,需要转换成布尔对象再进行异或运算 + left1 = node_1.left + right1 = node_1.right + left2 = node_2.left + right2 = node_2.right if (not left1) ^ (not left2): + # 一个节点为None,一个节点不为None return False if (not right1) ^ (not right2): return False if left1: - queue1.append(left1) + queue_1.put(left1) + queue_2.put(left2) if right1: - queue1.append(right1) - if left2: - queue2.append(left2) - if right2: - queue2.append(right2) - # 如果两个树相同,最后两个队列都为空 - return not queue1 and not queue2 + queue_1.put(right1) + queue_2.put(right2) + + if queue_1.qsize() or queue_2.qsize(): + return False + return True ``` diff --git "a/leetcode_tree/350円267円257円345円276円204円346円200円273円345円222円214円.md" "b/leetcode_tree/350円267円257円345円276円204円346円200円273円345円222円214円.md" new file mode 100644 index 00000000..28d8d37d --- /dev/null +++ "b/leetcode_tree/350円267円257円345円276円204円346円200円273円345円222円214円.md" @@ -0,0 +1,55 @@ +# 112.路径总和 +## 题目 +给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。 + +![](../pic/leetcode_tree/112_1.jpg) +```python +输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 +输出:true +``` + +## 分析 +#### 迭代 +与[二叉树的所有路径](二叉树的所有路径.md)思路相似,用两个队列,分别存节点,以及到该节点的总和 +![](../pic/leetcode_tree/112_1.gif) +```python +from queue import Queue +def hasPathSum(root, targetSum): + if not root: + return False + + node_queue = Queue() + sum_queue = Queue() + node_queue.put(root) + sum_queue.put(root.val) + + while node_queue.qsize(): + node = node_queue.get() + sum_ = sum_queue.get() + if node.left is None and node.right is None: + if sum_ == targetSum: + return True + else: + continue + if node.left: + node_queue.put(node.left) + sum_queue.put(sum_ + node.left.val) + if node.right: + node_queue.put(node.right) + sum_queue.put(sum_ + node.right.val) + return False +``` + +#### 递归 +假定从根节点到当前节点的值之和为val,可以将这个大问题转化为一个小问题:是否存在从当前节点的子节点到叶子的路径,满足其路径和为 sum - val。 +```python +def hasPathSum(root, targetSum): + if not root: + return False + + if root.left is None and root.right is None: + return root.val == targetSum + + return hasPathSum(root.left, targetSum-root.val) or hasPathSum(root.right, targetSum-root.val) + +``` diff --git a/linux/main.md b/linux/main.md index 81869551..cedac38d 100644 --- a/linux/main.md +++ b/linux/main.md @@ -1,5 +1,6 @@ ## Linux相关 1. [Linux常用操作命令(更新......)](Linux常用操作命令.md) +2. [tmux常用操作命令](tmux常用操作命令.md) ## Linux基础 2. [Linux文件属性与权限](Linux文件权限.md) diff --git "a/linux/tmux345円270円270円347円224円250円346円223円215円344円275円234円345円221円275円344円273円244円.md" "b/linux/tmux345円270円270円347円224円250円346円223円215円344円275円234円345円221円275円344円273円244円.md" new file mode 100644 index 00000000..fb345f73 --- /dev/null +++ "b/linux/tmux345円270円270円347円224円250円346円223円215円344円275円234円345円221円275円344円273円244円.md" @@ -0,0 +1,23 @@ +## tmux 命令 +#### 会话 session +* tmux:直接创建一个默认的会话session +* tmux ls: 查看所有的会话,快捷键:ctrl+b s +* tmux new -s session_name: 指定会话名称创建会话 +* tmux detach: 离开会话,快捷键:ctrl+b d +* tmux a -t session_name: 进入会话 +* tmux kill-session -t session_name: 杀掉会话,快捷键:ctrl+d +* tmux rename-session -t old_name new_name: 重命名session + +#### 窗口 windows +* tmux new-window -n window_name: 新建一个窗口 +* **ctrl+b w: 查看所有窗口** +* tmux select-window -t window_name: 切换窗口 +* tmux kill-window -t window_name: 关闭窗口,快捷键:ctrl+b & 关闭当前窗口 + +#### 窗格 pane +* tmux split-window: 上下切割,快捷键:ctrl+b % +* tmux split-window -h: 左右切割,快捷键:ctrl+b " +* ctrl+b 方向键:切换窗格 +* ctrl+b x: 关闭窗格 + + diff --git "a/network_protocol/HTTPS345円216円237円347円220円206円.md" "b/network_protocol/HTTPS345円216円237円347円220円206円.md" new file mode 100644 index 00000000..d250c500 --- /dev/null +++ "b/network_protocol/HTTPS345円216円237円347円220円206円.md" @@ -0,0 +1,69 @@ +# HTTPS原理 +## 1.HTTP通信存在的问题 +* HTTP通信数据传输使用的是明文,传输过程中数据可能被窃听 +* HTTP无法验证数据的完整性,数据可能在传输过程中被篡改(没有办法确认发送的请求/响应和接收到的请求/响应前后是否完全一致) +* 无法验证通信方的身份,可能遭遇伪装,任何人都可以伪装成服务器欺骗客户端 + +## 2.HTTPS +HTTPS是HTTP+SSL/TSL,在HTTP的基础上添加了一个安全层,SSL,Secure Socket Layer,对所有的数据进行加密后传输,是安全版本的HTTP。通常HTTP直接和TCP通信,当使用SSL时,则变成HTTP先和SSL层通信,再由SSL和TCP通信。 + +![](../pic/network_protocol/https_1.png) + +## 3.HTTPS的主要作用 +* 对数据进行加密,并建立一个信息安全通道,来保证传输过程中数据安全 +* 对网站服务器进行身份认证 + +## 4.加密算法 +#### 4.1. 对称加密 +**加密和解密使用的是同一个秘钥。如AES,DES加密算法。** + +通过对称加密算法加密时,必须要把秘钥告诉给对方,不然对方无法解密。但是在互联网上转发秘钥时,如果通信被窃听,秘钥也会被第三方获取,这时也就失去了加密的意义。 + +![](../pic/network_protocol/https_2.png) +#### 4.2. 非对称加密 +非对称加密有两个秘钥:公钥与私钥,公钥是公开的,如果用公钥对数据进行加密,只有对应的私钥才可以解密;如果用私钥进行加密,只有对应的公钥才能解密。如RSA加密算法。 + +缺点: +* 公钥是公开的,所以针对私钥加密的数据,第三方截获后,可以利用公钥解密,获取其中的内容 +* 非对称加密在数据加密解密过程中相对耗时,传输效率低 + +## 5.HTTPS使用对称加密+非对称加密相结合 +如果使用对称加密算法,对数据进行加密传输,但是对称加密的秘钥在互联网传输过程中会被窃取,这样就导致数据传输并不安全。 + +如果使用非对称加密算法 + 对称加密算法相结合,也就是在传输过程中利用非对称加密算法,将对称加密算法的秘钥进行加密,这样就算传输过程中秘钥被截获,别人也无法解密获取到秘钥,相对来说就很安全了。流程如下: + +* 服务器将自己的公钥(非对称加密的公钥)告诉给客户端 +* 客户端随机生成一个秘钥(对称加密的秘钥),客户端利用非对称加密的公钥 对 对称加密算法的秘钥进行加密,发送给服务器 +* 服务器利用自己的私钥解密,得到对称加密算法的秘钥 +* 这样客户端与服务器都知道了对称加密算法的秘钥,并且就算该秘钥被第三方获取,也无法解密 +* 这样客户端与服务器就可以利用对称加密算法进行加密传输通信了 + +![](../pic/network_protocol/https_3.png) + +#### 以上流程仍然不太安全 +非对称加密的公钥,如果直接通过互联网传输给客户端,容易被不法分子劫持,篡改为自己的公钥,如果这样的话,数据传输过程中又不安全了。 + +![](../pic/network_protocol/https_4.png) + +## 6.客户端如何获取服务器的公钥? +服务器的公钥不能在不安全的网络中直接发送给客户端,这时就引入了证书颁发机构(Certificate Authority,简称CA) +#### 数字证书认证机构的业务流程 +1. 服务器的运营人员向第三方机构CA提供自己的公钥,组织信息,域名信息等申请认证 +2. CA审核通过后,会给服务器颁发签名证书,证书包含:申请者的公钥,申请者的组织信息,域名信息,签发机构的CA信息,有效时间等,同时还包含一个**数字签名** +3. **数字签名**是通过对证书中的明文信息进行哈希计算,得到数字摘要,然后采用CA的私钥对数字摘要进行加密,进而得到数字签名 +![](../pic/network_protocol/https_5.png) +4. 客户端向服务器发起https请求时,服务器返回给客户端的是CA颁发的证书 +5. 客户端读取证书中的明文,通过哈希计算得到数字摘要;同时利用CA的公钥(操作系统内置)对数字签名进行解密得到数字摘要,如果两个摘要相同,说明证书内容没有被篡改,证书中的秘钥是可以信赖的 +![](../pic/network_protocol/https_6.png) +6. 客户端还会验证证书相关的域名信息、有效时间等信息; 客户端会内置信任CA的证书信息(包含公钥),如果CA不被信任,则找不到对应CA的证书,证书也会被判定非法 + +**以上可知,通过第三方证书传输公钥,可以防止公钥被第三方篡改的问题,这样这个通信流程就非常安全了。** + +## 7.HTTPS工作流程 +1. 客户端发起HTTPS请求,连接服务器的443端口 +2. 服务器把配置好的证书发送给客户端 +3. 客户端对证书的合法性进行验证,比如是否在有效期内,证书的用途是不是匹配客户端请求的站点等,如果验证不通过会警告 +4. 如果证书验证通过,客户端从证书中获取服务器的非对称加密的公钥 +5. 客户端随机生成一个对称加密秘钥,利用公钥进行加密,发送给服务器 +6. 服务器使用自己的私钥解密,得到对称加密算法的秘钥 +7. 之后客户端与服务器就通过对称加密算法进行数据传输 diff --git "a/network_protocol/HTTP346円246円202円345円277円265円.md" "b/network_protocol/HTTP346円246円202円345円277円265円.md" new file mode 100644 index 00000000..726fb527 --- /dev/null +++ "b/network_protocol/HTTP346円246円202円345円277円265円.md" @@ -0,0 +1,15 @@ +# HTTP概念 +## 1.定义 +HTTP,HyperText Transfer Protocol,超文本(文本,图片,视频等)传输协议,是基于TCP协议的应用层传输协议。 + +## 2.HTTP的特性 +* 灵活:Http允许传输多种类型的数据对象,传输类型在请求头Content-Type中标记 +* 无连接:每次连接只处理一个请求,请求时建立连接,请求后释放连接,尽快将资源释放出来服务其他客户端,但随着网页越来越复杂,里面可能嵌套的很多图片,每次访问图片都需要建立一次TCP连接这样就很低效,后来的Keep-Alive解决这个问题,Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。 +* 无状态:HTTP协议不记录客户端的状态,客户端的每次请求对服务端来说都是独立的。 + +## 3.URL与URI +* URI:Uniform Resource Identifier,统一资源标识符 +* URL:Uniform Resource Locator,统一资源定位符,一段字符串标识资源的定位地址(协议名称+服务器IP+服务器端口+服务器上路径) +* URN:Uniform Resource Name,统一资源名称 + +**URI重点是对资源的唯一标识,可以通过地址标识资源(URL),也可以通过唯一名称标识资源(URN).** diff --git a/network_protocol/main.md b/network_protocol/main.md index 9702d7b1..e4d0c717 100644 --- a/network_protocol/main.md +++ b/network_protocol/main.md @@ -1,4 +1,7 @@ ## 计算机网络 +针对B站上湖科大教书匠的《计算机网络微课堂》视频所做的学习笔记,很不错的计算机网络教程。 +https://www.bilibili.com/video/BV1c4411d7jb?share_source=copy_web + 1. [电路交换与分组交换](电路交换与分组交换.md) 2. [计算机网络分类](计算机网络分类.md) 3. [计算机网络的性能指标](计算机网络的性能指标.md) @@ -16,5 +19,11 @@ 15. [TCP可靠传输的实现](TCP可靠传输的实现.md) 16. [TCP连接的建立与释放](TCP连接的建立与释放.md) 17. [TCP/IP总结](TCPIP总结.md) -18. [HTTP状态码、网关、代理、隧道](状态码网关代理隧道.md) -19. [HTTP首部字段.md](http首部字段.md) + +## HTTP +1. [HTTP概念](HTTP概念.md) +2. [HTTP状态码、网关、代理、隧道](状态码网关代理隧道.md) +3. [HTTP首部字段](http首部字段.md) +4. [HTTPS原理](HTTPS原理.md) + +* [总结](总结.md) diff --git "a/network_protocol/346円200円273円347円273円223円.md" "b/network_protocol/346円200円273円347円273円223円.md" new file mode 100644 index 00000000..660cf355 --- /dev/null +++ "b/network_protocol/346円200円273円347円273円223円.md" @@ -0,0 +1,53 @@ +### 1.UDP和TCP的特点和区别 +#### UDP:User Datagram Protocol,用户数据报协议 +* 面向无连接 +* 尽最大努力交付,不可靠传输,没有流量控制和拥塞控制 +* 支持一对一,一对多和多对一通信 +* 面向报文(对于应用层传下来的报文直接打包) +* 首部开销小,只有8字节 + +#### TCP:Transmission Control Protocol,传输控制协议 +* 面向连接 +* 提供可靠传输,有流量控制和拥塞控制 +* 只能是一对一通信 +* 面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块) +* 首部开销大,最小20字节,最大60字节 + +### 2.UDP和TCP的首部 +**UDP的首部只有8个字节,源端口号,目的端口号,长度,校验和** + +**TCP的首部比较复杂,[TCP首部](TCP首部.md)** + +### 3.TCP的三次握手与四次挥手过程,为什么是三报文建立连接,为什么是四报文释放连接 +[TCP连接的建立与释放](TCP连接的建立与释放.md) + +### 4.TCP短连接和长连接的区别 +#### 短连接 +Client向Server发送消息,Server回应Client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是 Client先发起close操作。短连接一般只会在Client/Server间传递一次读写操作。 + +短连接管理起来比较简单,建立存在的连接都是有用的连接,不需要额外的控制手段。 + +#### 长连接 +Client与Server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。 + +在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server端的压力会越来越大,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免客户端连累后端服务。 + +### 5.TCP的粘包,拆包 +#### UDP没有粘包问题 +TCP是基于字节流的传输,虽然应用层和传输层之间数据交互是大小不同的数据块,但是TCP并没有对这些数据块区分边界,仅仅是一连串没有结构的字节流;UDP是基于报文传输的,有区分边界,不会发生粘包情况 +#### 为什么会发生粘包拆包 +TCP是基于字节流传输的,在传输过程中,对于应用层的一个数据包,在传输层可能会拆分成多个字节数据块传输(拆包),也可能是多个数据包合并成一个字节数据块传输(粘包) +* 要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包 +* 待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包 +* 要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包 +* 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包 + +#### 解决办法 +在传输层无法保证数据包不被拆分和合并,只能通过应用层制定相关的协议,在应用层接收数据的时候根据协议解决粘包和拆包的问题,比如应用层协议规定消息头标识(边界),定义消息头与消息体,定义数据长度等。应用层在接收数据的时候根据头标识接收指定长度的消息头,然后解析获取消息长度,再接收指定长度的消息体,这样就能解决粘包和拆包的问题了 + +### 6.TCP的流量控制 +[TCP的流量控制](TCP的流量控制.md) +### 7.TCP的拥塞控制 +[TCP的拥塞控制](TCP的拥塞控制.md) +### 8.TCP的可靠传输实现 +[TCP可靠传输的实现](TCP可靠传输的实现.md) diff --git "a/network_protocol/350円256円241円347円256円227円346円234円272円347円275円221円347円273円234円347円232円204円346円200円247円350円203円275円346円214円207円346円240円207円.md" "b/network_protocol/350円256円241円347円256円227円346円234円272円347円275円221円347円273円234円347円232円204円346円200円247円350円203円275円346円214円207円346円240円207円.md" index 612c73a5..9aa48712 100644 --- "a/network_protocol/350円256円241円347円256円227円346円234円272円347円275円221円347円273円234円347円232円204円346円200円247円350円203円275円346円214円207円346円240円207円.md" +++ "b/network_protocol/350円256円241円347円256円227円346円234円272円347円275円221円347円273円234円347円232円204円346円200円247円350円203円275円346円214円207円346円240円207円.md" @@ -9,7 +9,7 @@ * 1 TB = 1024 GB,1T等于2的10次方GB ## 1.速率 -定义:连接在计算机上的主机在数字信道上传送bit的速率,也叫比特率,单位是bit/s,或者bps。 +定义:连接在网络上的主机在数字信道上传送bit的速率,也叫比特率,单位是bit/s,或者bps。 * 1kb/s = 1000bit/s * 1mb/s = 1000kb/s = 1000 * 1000bit/s diff --git a/others/main.md b/others/main.md index 77deb323..2f9d8353 100644 --- a/others/main.md +++ b/others/main.md @@ -3,3 +3,4 @@ 2. [ZeroMQ基础](ZeroMQ基础.md) 3. [redis基础操作](redis基础操作.md) +4. [redis基础理论](redis基础理论.md) diff --git "a/others/redis345円237円272円347円241円200円346円223円215円344円275円234円.md" "b/others/redis345円237円272円347円241円200円346円223円215円344円275円234円.md" index 8c774526..8c74b906 100644 --- "a/others/redis345円237円272円347円241円200円346円223円215円344円275円234円.md" +++ "b/others/redis345円237円272円347円241円200円346円223円215円344円275円234円.md" @@ -12,6 +12,8 @@ Redis是一个基于内存的高性能key-value数据库。 #### 4.redis是单进程单线程的 redis利用队列将并发访问变为串行访问。 +redis连接指定数据库:redis-cli -n 1 + ## key * keys * : 查看所有的key * del key : 删除指定的key diff --git "a/others/redis345円237円272円347円241円200円347円220円206円350円256円272円.md" "b/others/redis345円237円272円347円241円200円347円220円206円350円256円272円.md" new file mode 100644 index 00000000..44ada245 --- /dev/null +++ "b/others/redis345円237円272円347円241円200円347円220円206円350円256円272円.md" @@ -0,0 +1,119 @@ +# redis基础理论 +### 1.什么是redis +* 全称Remote Dictionary Server +* redis是一个开源的,基于内存的,key-value类型的**数据结构存储服务器** +* redis的键(key)只能是字符串,值(value)支持多种数据结构,如字符串(string),列表(list),哈希表(hash),集合(set),有序集合(sorted set),value的最大限制是1G +* redis的读写都是在内存中操作,性能出色,并且定期进行持久化操作,将数据保存在硬盘上 + +### 2.redis支持那几种数据类型 +字符串(string)最大512M,列表(list),哈希表(hash),集合(set),有序集合(sorted set) + +### 3.redis主要消耗什么物理资源 +内存 + +### 4.redis的优缺点 +#### 优点: +* 速度快,在内存中读写 +* 数据结构丰富,string,list,hash,set,sorted set +* 支持事务,操作原子性,对数据的更改要么全部执行,要么全部不执行 +* 支持数据持久化 +* 灵活,可以设置key的过期时间 + +#### 缺点: +* 数据库容量受内存大小限制 + +### 5.redis快的原因 +* 完全基于内存 +* 数据结构简单 +* 单线程,避免了多进程多线程的切换导致CPU的开销,不用考虑锁的问题 +* 使用多路IO复用模型,非阻塞IO + +### 6.为什么用redis做缓存 +1. 性能高,用户第一次访问数据库中的数据是读取硬盘,过程比较慢,将该用户访问的数据在内存中缓存,下次读取就是直接在内存中读取,所以速度比较快 +2. 高并发,直接操作缓存能够承受的请求是远大于直接访问数据库的 +3. 支持分布式与持久化,即使服务器出现故障不会导致数据丢失 + +### 7.redis的应用场景 +* 计数器:对于频繁读写的计数量,可以用redis缓存 +* 缓存:将热点数据放到内存中,设置内存的最大使用量及淘汰策略保证缓存的命中率 +* 会话缓存:用redis统一保存多台应用服务器的session会话信息,应用服务器不再存储session信息,不具备状态,一个用户可以请求任意一台应用服务器,实现分布式 +* 消息队列:list可以使用lpush和rpop写入与读取数据 + +### 8.什么是redis持久化 +持久化就是把内存中的数据写到硬盘中,防止服务器宕机了内存数据丢失 + +### 9.redis的持久化机制 +redis提供两种持久化机制,RDB(默认)和AOF、 +#### 9.1. RDB持久化,redis datebase快照 +RDB是redis默认的持久化方式,按照一定的时间将内存的数据以快照的形式保存到硬盘上,对应产生的数据文件dump.rdb,通过配置文件中的save参数来定义快照的周期。 + +![](../pic/others/redis_1.jpg) +**RDB优点** +* 只有一个dump.rdb文件,方便持久化 +* 性能最大化,使用单独的子进程来进行持久化操作,主进程不受影响,保证了redis的性能 +* 对于数据集较大时,比AOF启动效率高 +* 容灾性好,将数据保存在安全的硬盘上 + +**RDB缺点** +* RDB是间隔一段时间进行持久化,如果这段时间内发生故障,会丢数据 + +#### 9.2. AOF持久化,Append Only File +AOF是将redis执行的每次写命令,追加到缓存区aof_buf记录,再同步到单独的日志文件中,当重启redis会重新从持久化的日志文件中恢复数据,当两种持久化机制同时开启,redis会优先选择AOF恢复。 + +**AOF优点** +* 数据安全,aof 持久化可以配置appendfsync属性,有always,每进行一次命令操作就记录到aof文件中一次 +* AOF比RDB更新数据的频率高,也更安全 + +**缺点:** +* AOF的文件比RDB文件大,恢复速度慢 +* 数据集较大时,比RDB启动效率低 + +### 10.redis过期键删除策略 +**定时过期:** +每个设置过期时间的key都有一个定时器,到过期时间就会立即清除,该策略可以立即清除过期的数据,对内存友好,但是会占用大量的cpu资源去处理过期的数据,影响redis效率 + +**惰性过期:** +只有当访问一个key时,才判断是否过期,如果过期了再清除。该策略可以节省cpu资源,但是对内存不友好,如果过期的数据没有被再次访问,会一直占用内存。 + +**定期过期:** +每隔一定时间,会扫描一定数量的expiresz字典中的key,并清除过期的数据。通过调整扫描的时间间隔和每次扫描的时间,可以使cpu资源和内存占用达到平衡。 + +**Redis中同时使用了惰性过期和定期过期两种过期策略。** + +### 11.redis的内存淘汰策略 +内存淘汰策略是指在redis用于缓存的内存不够用的情况下,对于新写入的数据怎么申请额外的空间 +* noeviction:当内存不够容纳新写入的数据时,新的写入操作会报错 +* allkeys-lru:当内存不够容纳新写入的数据时,在键空间中,移除最近最少使用的key(常用) +* allkeys-random:当内存不够容纳新写入的数据时,在键空间中随机删除某个key +* volatile-lru:当内存不够容纳新写入的数据时,在设置了过期时间的键空间中,移除最近最少使用的key +* volatile-random:当内存不够容纳新写入的数据时,在设置了过期时间的键空间中,随机移除某个key +* volatile-ttl:当内存不够容纳新写入的数据时,优先移除更早过期的key + +### 12.redis的内存用完了会发生什么 +如果内存的使用达到了设置的上限,新数据的写入会报错(读取命令可以正常执行),或者配置内存淘汰策略,当内存达到上限冲刷掉旧内容 + +### 数据库里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据? +redis内存占用到上限,会使用内存淘汰策略 + +### 13.redis如何做内存优化 +合理利用五种数据结构存储数据 + +### 14.缓存雪崩 +缓存层承载着大量请求压力,有效保护了数据库层面,如果缓存在同一时间大面积失效(大部分key在同一时间过期),导致大量请求落在数据库层面上,造成数据库短时间内承受大量请求而崩溃。 +**解决方案:** +* 缓存的过期时间设置随机,防止同一时间大量数据同时过期 + +### 15.缓存穿透 +查询一个根本不存在的数据,缓存层和数据库层面都不会命中,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 +**解决方案:** +从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用) + +### 16.缓存击穿 +缓存中没有,数据库中有的数据,这个时间段内对某一个热点key的并发量非常大,同时读取缓存没有读取到数据,于是又同时去查询数据库,引起数据库压力瞬间增大。和缓存雪崩不同,缓存击穿是大量请求查询同一条数据,缓存雪崩是大量的缓存数据都过期了,很多数据都查不到导致查询数据库。 + +以下情况可能会造成缓存击穿: +* 当前key是一个热点key(例如一个秒杀活动),并发量非常大 +* 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。 + +**解决方案:** +设置热点数据永不过期,或者加锁,只允许有一个线程重建缓存,其他线程等待缓存重建完成后,重新从缓存中获取数据。 diff --git a/pic/leetcode_stack/239_1.gif b/pic/leetcode_stack/239_1.gif new file mode 100644 index 00000000..b9978cec Binary files /dev/null and b/pic/leetcode_stack/239_1.gif differ diff --git a/pic/leetcode_stack/239_1.png b/pic/leetcode_stack/239_1.png new file mode 100644 index 00000000..e8287729 Binary files /dev/null and b/pic/leetcode_stack/239_1.png differ diff --git a/pic/leetcode_tree/101_1.png b/pic/leetcode_tree/101_1.png new file mode 100644 index 00000000..2be58373 Binary files /dev/null and b/pic/leetcode_tree/101_1.png differ diff --git a/pic/leetcode_tree/112_1.gif b/pic/leetcode_tree/112_1.gif new file mode 100644 index 00000000..804a7980 Binary files /dev/null and b/pic/leetcode_tree/112_1.gif differ diff --git a/pic/leetcode_tree/112_1.jpg b/pic/leetcode_tree/112_1.jpg new file mode 100644 index 00000000..06a2640c Binary files /dev/null and b/pic/leetcode_tree/112_1.jpg differ diff --git a/pic/leetcode_tree/144_1.gif b/pic/leetcode_tree/144_1.gif new file mode 100644 index 00000000..12238c72 Binary files /dev/null and b/pic/leetcode_tree/144_1.gif differ diff --git a/pic/leetcode_tree/226_1.gif b/pic/leetcode_tree/226_1.gif new file mode 100644 index 00000000..ffaeae5d Binary files /dev/null and b/pic/leetcode_tree/226_1.gif differ diff --git a/pic/leetcode_tree/226_1.png b/pic/leetcode_tree/226_1.png new file mode 100644 index 00000000..f10d53ed Binary files /dev/null and b/pic/leetcode_tree/226_1.png differ diff --git a/pic/leetcode_tree/257_1.gif b/pic/leetcode_tree/257_1.gif new file mode 100644 index 00000000..44ce2404 Binary files /dev/null and b/pic/leetcode_tree/257_1.gif differ diff --git a/pic/leetcode_tree/94_1.gif b/pic/leetcode_tree/94_1.gif new file mode 100644 index 00000000..3a534b0e Binary files /dev/null and b/pic/leetcode_tree/94_1.gif differ diff --git a/pic/leetcode_tree/bianlitree.png b/pic/leetcode_tree/bianlitree.png new file mode 100644 index 00000000..b858f820 Binary files /dev/null and b/pic/leetcode_tree/bianlitree.png differ diff --git a/pic/leetcode_tree/manerchashu.png b/pic/leetcode_tree/manerchashu.png new file mode 100644 index 00000000..fddffea7 Binary files /dev/null and b/pic/leetcode_tree/manerchashu.png differ diff --git a/pic/leetcode_tree/paixutree.png b/pic/leetcode_tree/paixutree.png new file mode 100644 index 00000000..45b92c05 Binary files /dev/null and b/pic/leetcode_tree/paixutree.png differ diff --git a/pic/leetcode_tree/wanquantree.png b/pic/leetcode_tree/wanquantree.png new file mode 100644 index 00000000..1e0805ea Binary files /dev/null and b/pic/leetcode_tree/wanquantree.png differ diff --git a/pic/network_protocol/https_1.png b/pic/network_protocol/https_1.png new file mode 100644 index 00000000..f001f9a6 Binary files /dev/null and b/pic/network_protocol/https_1.png differ diff --git a/pic/network_protocol/https_2.png b/pic/network_protocol/https_2.png new file mode 100644 index 00000000..639399e6 Binary files /dev/null and b/pic/network_protocol/https_2.png differ diff --git a/pic/network_protocol/https_3.png b/pic/network_protocol/https_3.png new file mode 100644 index 00000000..1856f2e4 Binary files /dev/null and b/pic/network_protocol/https_3.png differ diff --git a/pic/network_protocol/https_4.png b/pic/network_protocol/https_4.png new file mode 100644 index 00000000..18ef78bb Binary files /dev/null and b/pic/network_protocol/https_4.png differ diff --git a/pic/network_protocol/https_5.png b/pic/network_protocol/https_5.png new file mode 100644 index 00000000..f7148259 Binary files /dev/null and b/pic/network_protocol/https_5.png differ diff --git a/pic/network_protocol/https_6.png b/pic/network_protocol/https_6.png new file mode 100644 index 00000000..f66436ab Binary files /dev/null and b/pic/network_protocol/https_6.png differ diff --git a/pic/others/redis_1.jpg b/pic/others/redis_1.jpg new file mode 100644 index 00000000..5e05d6a6 Binary files /dev/null and b/pic/others/redis_1.jpg differ diff --git a/pic/python_advance/gc_1.png b/pic/python_advance/gc_1.png new file mode 100644 index 00000000..4e7cad6f Binary files /dev/null and b/pic/python_advance/gc_1.png differ diff --git a/pic/python_basic/tuple3.png b/pic/python_basic/tuple3.png new file mode 100644 index 00000000..68512190 Binary files /dev/null and b/pic/python_basic/tuple3.png differ diff --git "a/python_advance/Git345円237円272円347円241円200円.md" "b/python_advance/Git345円237円272円347円241円200円.md" index 6dbd60e9..bfe8a59f 100644 --- "a/python_advance/Git345円237円272円347円241円200円.md" +++ "b/python_advance/Git345円237円272円347円241円200円.md" @@ -40,6 +40,17 @@ git官网下载windows版本的, https://git-scm.com/downloads ![](../pic/gitdoc/1_3.png) +* 查看所有配置: git config -l +* 查看系统配置:git config --system --list +* 查看本地配置:git config --global --list +* 配置用户名: git config --global user.name "XXX" +* 配置邮箱: git config --global user.email "XXX@qq.com" +* 新建一个分支:git branch [branch-name] +* 新建一个分支并切换到新的分支上:git checkout -b [branch] +* 合并指定分支到当前分支:git merge [branch] +* 删除分支: git branch -d [branch-name] +* 删除远程分支:git push origin --delete [branch-name] + ## 二、git使用 ### 2.1、用git创建版本库 学习三个git命令: diff --git "a/python_advance/Python344円270円212円344円270円213円346円226円207円347円256円241円347円220円206円345円231円250円344円270円216円with350円257円255円345円217円245円.md" "b/python_advance/Python344円270円212円344円270円213円346円226円207円347円256円241円347円220円206円345円231円250円344円270円216円with350円257円255円345円217円245円.md" new file mode 100644 index 00000000..af46b352 --- /dev/null +++ "b/python_advance/Python344円270円212円344円270円213円346円226円207円347円256円241円347円220円206円345円231円250円344円270円216円with350円257円255円345円217円245円.md" @@ -0,0 +1,95 @@ +# 上下文管理器与with语句 +## 1.上下文管理器 +* 上下文管理器的任务是:代码块执行前的准备工作,代码块执行后的收尾操作,比如文件关闭,数据库连接关闭操作 +* 上下文管理器类实现了__enter__和__exit__两个方法 +* 上下文管理器与with语句结合 +* with语句开始运行时,会在上下文管理器上调用__enter__方法,with语句运行结束后,会在上下文管理器上调用__exit__方法 + +## 2.with语句 +**with语句的目的是为了简化try...finally模式。** + +读取文件内容的操作,需要先打开文件,然后读取文件内容,最后关闭文件,为了保证文件一定关闭,需要用try...finally..语句。 +```python +try: + f = open('zhibi.py', 'r', encoding='utf-8') + f.read(10) + print(f) +except Exception as e: + print('文件读取异常:{}'.format(e)) +finally: + f.close() +``` + +可以将打开文件操作(open),和关闭文件操作(close),交给上下文管理器去处理 +* with语句执行的时候,上下文管理器打开文件,并将返回值与as绑定 +* with语句执行结束后,上下文管理器执行关闭文件的操作 + +```python +with open('zhibi.py', 'r', encoding='utf-8') as f: + f.read(10) + print(f) +``` + +## 3.上下文管理器类实现 +```python +class MyOpen: + def __init__(self, filename): + self.f = open(filename, 'r', encoding='utf-8') + + def __enter__(self): + print('2.执行上下文管理器的__enter__') + return self.f + + def __exit__(self, exc_type, exc_val, exc_tb): + print('4.执行上下文管理器的__exit__') + self.f.close() + +if __name__ == '__main__': + print('1.start...') + with MyOpen('zhibi.py') as f: + f.read(10) + print('3.执行代码块内容') + print('5.over!!!') +``` +```python +1.start... +2.执行上下文管理器的__enter__ +3.执行代码块内容 +4.执行上下文管理器的__exit__ +5.over!!! +``` + +## 4.contextmanager装饰器实现上下文管理器 +使用@contextmanager装饰器实现的上下文管理器中,yield语句的作用是把函数体分为两部分 +* yield前面的语句在with开始的时候执行(等同于__enter__方法) +* yield后面的语句在with结束时执行(等同于__exit__方法) + +```python +from contextlib import contextmanager + +@contextmanager +def open_file(filename): + + print('2.代码块执行前') + f = open(filename, 'r', encoding='utf-8') + + yield f + + print('4.代码块执行后') + f.close() + +if __name__ == '__main__': + print('1.start...') + with open_file('zhibi.py') as f: + f.read(10) + print('3.执行代码块内容') + print('5.over!!!') +``` + +```python +1.start... +2.代码块执行前 +3.执行代码块内容 +4.代码块执行后 +5.over!!! +``` diff --git "a/python_advance/Python344円270円255円347円232円204円344円270円213円345円210円222円347円272円277円.md" "b/python_advance/Python344円270円255円347円232円204円344円270円213円345円210円222円347円272円277円.md" index b110529b..87f40cf4 100644 --- "a/python_advance/Python344円270円255円347円232円204円344円270円213円345円210円222円347円272円277円.md" +++ "b/python_advance/Python344円270円255円347円232円204円344円270円213円345円210円222円347円272円277円.md" @@ -2,26 +2,29 @@ #### 单下划线 * _XXX: "单下划线" 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;单下划线开头(_foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用"from xxx import *"而导入. +**@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。** + ```python -class Person(object): - def __init__(self,name,age): - self.name = name - self._age = age - def play(self): - print(self.name,self._age) -def main(): - p = Person('王大锤',12) - p.play() +class Person(object): + def __init__(self,name,age): + self.name = name + self._age = age + + @property + def age(self): + return self._age + +if __name__ == '__main__': + p = Person('王大锤', 12) """ '单下划线'的保护变量 大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的, 本类之外的代码在访问这样的属性时应该要保持慎重。 这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻 - """ - print(p._age) - -if __name__ == '__main__': - main() + """ + print(p._age) + # 通过定义的接口访问保护变量 + print(p.age) ``` #### 双下划线 * \_\_XXX: "双下划线" 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。通过对象名._类名__xxx这样的方式可以访问. diff --git "a/python_advance/Python345円236円203円345円234円276円345円233円236円346円224円266円346円234円272円345円210円266円.md" "b/python_advance/Python345円236円203円345円234円276円345円233円236円346円224円266円346円234円272円345円210円266円.md" new file mode 100644 index 00000000..ebe8d9b0 --- /dev/null +++ "b/python_advance/Python345円236円203円345円234円276円345円233円236円346円224円266円346円234円272円345円210円266円.md" @@ -0,0 +1,83 @@ +# Python垃圾回收机制 +**引用计数+标记清除+分代回收** +## 1.通过引用计数来进行垃圾回收 +Python中每个对象内部都有一个引用计数器,程序在运行过程中会实时更新"引用计数"的值,当某个对象的引用计数值为0时,这个对象的内存就会被立即释放掉。**Python 使用标记清除(mark-sweep)算法和分代回收(generational),来启用针对循环引用的自动垃圾回收。** +#### 增加引用计数的情况 +* 对象被创建的时候,a={} +* 对象被引用的时候, b=a +* 对象被作为参数传入到一个函数中func(a) +* 对象作为元素存储在容器中 +* 可以通过sys.getrefcount()方法查看对象的引用计数值 + +#### 减小引用计数的情况 +* 对象别名被显示销毁 del +* 对象别名被赋予新的对象 +* 一个对象离开他的作用域,函数执行结束对象引用减一 +* 对象所在的容器被销毁或者是从容器中删除对象 + +```python +import sys + +def func(a): + pass + +if __name__ == '__main__': + a = [] # +1 + b = a # +1 + func(a) # 这里函数被调用的时候+1,函数执行结束引用计数-1 + dict_ = {'a': a} # +1 + print(sys.getrefcount(a)) # 使用getrefcount()方法本身也会+1,所以最终打印是4 + + b = None # -1 + dict_.clear() # -1 + print(sys.getrefcount(a)) # 4-1-1 = 2 + del a # del只是删除对象的别名a,并不删除对象本身,引用计数-1 +``` + +#### 引用计数的优点 +* 逻辑简单,效率高 +* 实时性,一旦一个对象的引用计数为0了,内存就直接释放了。(有的内存回收机制是有时间等待的) + +#### 引用计数的缺点 +* 每个对象需要分配单独的空间统计引用计数,增加了内存负担 +* 当需要释放一个大的对象时,如字典,需要对引用的所有对象进行计算,花费时间比较长 +* 没办法处理循环引用的情况,必须要使用其它的垃圾回收算法对其进行补充 + +#### 循环引用 +a和b相互引用而再没有外部引用a与b中的任何一个,它们的引用计数都为1,但是如果按照引用计数法的话,永远不为0,永远无法回收。 +```python +a = {'b': None} +b = {'a': None} +a['b'] = b +b['a'] = a +del a +del b +``` + +## 2.标记清除处理循环引用的情况 + +**只有容器才会产生循环引用的情况,如列表,字典,元组等,所以"标记清除"处理的也是容器循环引用的问题** +* 标记阶段:遍历所有对象,如果是可达的,说明还有对象引用,就标记。 +* 清除阶段:再次遍历所有对象,如果说没有被标记,就将其回收 + +对象之间的引用通过指针连接在一起,构成一个有向图,每个对象是有向图的节点,引用关系构成有向图的变,从根对象出发,沿着有向边遍历对象,可以到达的标记为活动对象,不可到达的就是要被清除的对象。根节点就是全局变量、调用栈、寄存器。 + +如图所示,因为执行了 del a, del b,a和b都是互相引用,没有别的外部引用指向a和b,所以从根节点遍历无法到达ab节点,因此循环引用的ab对象会被回收。 + +![](../pic/python_advance/gc_1.png) + +## 3.分代回收 +* 循环引用对象的回收过程中,程序是暂停的,所以应该控制"标记清除"的频次,而且不可能每次"标记清除"都遍历所有的对象。 +* 分代回收用空间换取时间 +* Python将所有对象分为三代,刚创建的对象是第一代,经过一次垃圾回收仍然存在的对象会放入到第二代,第二代对象经过一次垃圾回收会放入到第三代 +* 当垃圾回收器中新增的对象减去要删除的对象到达一定阈值时,就执行一次垃圾回收 +```python +import gc +print(gc.get_threshold()) # (700, 10, 10) +# 700:当垃圾回收器中新增的对象减去释放的对象差值大于700时,就执行一次一代回收 +# 10次一代回收执行一次二代回收 +# 10次二代回收执行一次三代回收 +# 所以越后代的对象说明越重要,越不容易被回收 +``` + +**在程序中,大部分(80%-90%)对象的生存周期比较短,只有少量对象生命周期比较长,甚至伴随着整个程序,所以,对象存在的时间越长,越不可能是垃圾,"标记清除"越应该减少去遍历这类对象,所以应该将这类对象放入到第二代第三代,以此来提高垃圾回收的效率。** diff --git "a/python_advance/Python345円256円236円347円216円260円345円215円225円344円276円213円346円250円241円345円274円217円.md" "b/python_advance/Python345円256円236円347円216円260円345円215円225円344円276円213円346円250円241円345円274円217円.md" new file mode 100644 index 00000000..06f30931 --- /dev/null +++ "b/python_advance/Python345円256円236円347円216円260円345円215円225円344円276円213円346円250円241円345円274円217円.md" @@ -0,0 +1,74 @@ +# Python实现单例模式 +**单例模式的主要目的是保证在系统中,某个类只能有一个实例存在。比如保存系统基本配置信息的类,在很多地方都要用到,没有必要频繁创建实例与销毁实例,只需要保存一个全局的实例对象即可,这样可以减少对内存资源的占用。** + +## 1.Python模块实现单例 +Python模块在第一次被导入的时候会生成.pyc文件,当第二次导入的时候就会直接加载.pyc文件而不会再次执行模块代码,所以可以在模块中定义单例类并实例化对象,在用的时候直接导入这个模块的实例对象即可。 +```python +class GlobalConfig: + host = 'xxx.xxx' + port = 3306 + username = 'username' + password = '123123' + +g = GlobalConfig() +``` + +## 2.利用\_\_new__()方法实现单例 +**\_\_new__()是类的构造方法,在实例创建前被调用,它的作用就是创建实例并返回实例对象,是一个静态方法** + +**多线程下创建单例对象需要加锁** + +```python +import threading + +class Singleton: + _instance_lock = threading.Lock() + + def __new__(cls, *args, **kwargs): + # 加锁 + with cls._instance_lock: + if not hasattr(cls, 'instance'): + cls.instance = super().__new__(cls) + return cls.instance + +def func(): + s = Singleton() + print('s:{}'.format(id(s))) + +if __name__ == '__main__': + for _ in range(5): + td = threading.Thread(target=func) + td.start() +``` + +## 3.利用装饰器实现单例 +```python +import threading + +def singleton(cls): + isntance_dict = {} + lock_ = threading.Lock() + + def wrap(*args, **kwargs): + with lock_: + if 'instance' not in isntance_dict: + isntance_dict['instance'] = cls(*args, **kwargs) + return isntance_dict['instance'] + + return wrap + + +@singleton +class Singleton: + pass + + +def func(): + s = Singleton() + print(s) + +if __name__ == '__main__': + for _ in range(5): + td = threading.Thread(target=func) + td.start() +``` diff --git a/python_advance/main.md b/python_advance/main.md index 7a225e60..e07cf254 100644 --- a/python_advance/main.md +++ b/python_advance/main.md @@ -1,20 +1,25 @@ ## Python进阶 -1. [Python中的下划线](Python中的下划线.md) - -2. [可迭代对象、迭代器、生成器](可迭代对象迭代器生成器.md) -3. [位置参数、默认参数、可变参数、关键字参数](位置参数默认参数可变参数关键字参数.md) -4. [模块和包](模块和包.md) -5. [通用日志模块](通用日志模块.md) -6. [变量的作用域与闭包](变量的作用域.md) -7. [装饰器](装饰器.md) -8. [进程与线程概念](进程与线程概念.md) -9. [Python中的多线程](Python中的多线程.md) -10. [Python中的多进程](Python中的多进程.md) -11. [socket网络编程](socket网络编程.md) -12. [Python实现TFTP文件传输](Python实现TFTP文件传输.md) -13. [Python连接数据库](Python连接数据库.md) -14. [Git基础](Git基础.md) -15. [Python之AES加密解密](Python之AES加密解密.md) -16. [MongoDB存储图片](MongoDB存储图片.md) -17. [python中的协程](python中的协程.md) -18. [concurrent.futures模块](concurrentfutures模块.md) +* [Python中的下划线](Python中的下划线.md) +* [可迭代对象、迭代器、生成器](可迭代对象迭代器生成器.md) +* [位置参数、默认参数、可变参数、关键字参数](位置参数默认参数可变参数关键字参数.md) +* [类变量、实例变量、类方法、实例方法、静态方法](类实例静态方法.md) +* [模块和包](模块和包.md) +* [通用日志模块](通用日志模块.md) +* [变量的作用域与闭包](变量的作用域.md) +* [Python命名空间与作用域](命名空间与作用域.md) +* [装饰器](装饰器.md) +* [进程与线程概念](进程与线程概念.md) +* [Python中的多线程](Python中的多线程.md) +* [Python中的多进程](Python中的多进程.md) +* [socket网络编程](socket网络编程.md) +* [Python实现TFTP文件传输](Python实现TFTP文件传输.md) +* [Python连接数据库](Python连接数据库.md) +* [Git基础](Git基础.md) +* [Python之AES加密解密](Python之AES加密解密.md) +* [MongoDB存储图片](MongoDB存储图片.md) +* [python中的协程](python中的协程.md) +* [concurrent.futures模块](concurrentfutures模块.md) +* [Python垃圾回收机制](Python垃圾回收机制.md) +* [Python上下文管理器与with语句](Python上下文管理器与with语句.md) +* [Python实现单例模式](Python实现单例模式.md) +* [Python opencv基础](opencv-python基础.md) diff --git "a/python_advance/opencv-python345円237円272円347円241円200円.md" "b/python_advance/opencv-python345円237円272円347円241円200円.md" new file mode 100644 index 00000000..02608bd5 --- /dev/null +++ "b/python_advance/opencv-python345円237円272円347円241円200円.md" @@ -0,0 +1,95 @@ +# opencv-python基础 +* pip install opencv-python + +## 1.图像的读取、显示、保存 +```python +import cv2 + +if __name__ == '__main__': + + # 1:加载彩色图像 0:灰度模式 -1:加载图像,包括alpha通道 + img = cv2.imread("test.jpg", -1) + cv2.namedWindow('window1', cv2.WINDOW_NORMAL) # 创建一个默认大小的窗口 + cv2.imshow('window1', img) # 窗口显示图像 + key_event = cv2.waitKey(0) # 键盘绑定,点击键盘程序继续执行 + if key_event == 27: + # ESC按键 + cv2.destroyAllWindows() # 关闭所有窗口 + elif key_event == ord('s'): + # 按键s + cv2.imwrite('new_test.jpg', img) # 写图片 + cv2.destroyAllWindows() +``` + +## 2.绘制直线、圆、矩形、多边形,文本 +```python +import cv2 +import numpy as np + + +if __name__ == '__main__': + # 创建一个高512,宽512的黑色图片 + img = np.zeros((512, 512, 3), np.uint8) + # 从左上角到右下角画一条直线,厚度为5像素,蓝色(255, 0, 0) + cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5) + + # 画矩形,指定矩形的左上角和右下角,绿色(0, 255, 0) + cv2.rectangle(img, (300, 0), (500, 100), (0, 255, 0), 3) + + # 画一个圆,指定圆心和半径,红色(0, 0, 255) + cv2.circle(img, (300, 100), 150, (0, 0, 255), 3) + + # 画一个椭圆 + cv2.ellipse(img, (300, 100), (100, 50), 0, 0, 180, 255, -1) + + # 画多边形,首先需要顶点的坐标,将这些点组成形状为rowsx1x2的数组,其中rows是顶点数,并且其类型应为int32。 + pts = np.array([[10, 10], [100, 10], [100, 150], [10, 200]], np.int32) + pts = pts.reshape((-1, 1, 2)) + # True是闭合的折线,False不闭合 + cv2.polylines(img, [pts], False, (0, 255, 255)) + + # 添加文本,指定文本内容,左下角坐标,字体,大小,颜色,厚度,线性 + font = cv2.FONT_HERSHEY_SIMPLEX + cv2.putText(img, '123321123ddd', (0, 500), font, 1, (255, 255, 255), 2, cv2.LINE_AA) + + cv2.imshow('window1', img) + key_event = cv2.waitKey(0) + cv2.destroyAllWindows() +``` + +## 3.访问修改像素值 +```python +import time +import cv2 +import numpy as np + +if __name__ == '__main__': + img = cv2.imread('roi.jpg') + # 通过行列坐标访问像素,BGR图像返回一个[蓝色,绿色,红色值]的数组,灰度图像仅返回相应的强度 + px = img[100, 100] + print(px) + # 修改像素 + img[100, 100] = [255, 255, 255] + print(img[100, 100]) + # 图像的形状,数据类型 + print(img.shape, img.dtype) + + # 替换指定区域图像 + ball = img[220:280, 270:330] + img[100:160, 100:160] = ball + + # 分割合并图像通道,一般用numpy索引 或者:b = img[:, :, 0] + b, g, r = cv2.split(img) + img = cv2.merge((b, g, r)) + + cv2.imshow('window1', img) + key_event = cv2.waitKey(0) + cv2.destroyAllWindows() + + # 将红色通道全部设置为0 + img[:, :, 2] = 0 + + cv2.imshow('window1', img) # 窗口显示图像 + key_event = cv2.waitKey(0) + cv2.destroyAllWindows() +``` diff --git "a/python_advance/345円221円275円345円220円215円347円251円272円351円227円264円344円270円216円344円275円234円347円224円250円345円237円237円.md" "b/python_advance/345円221円275円345円220円215円347円251円272円351円227円264円344円270円216円344円275円234円347円224円250円345円237円237円.md" new file mode 100644 index 00000000..7aee3d31 --- /dev/null +++ "b/python_advance/345円221円275円345円220円215円347円251円272円351円227円264円344円270円216円344円275円234円347円224円250円345円237円237円.md" @@ -0,0 +1,106 @@ +# 命名空间与作用域 +## 命名空间(namespace) +* **Python的命名空间是一个字典,字典内保存了变量名称与对象之间的映射关系。** +* 一个变量名称可以定义在多个不同的命名空间下,之间互不影响 +* 同一个命名空间下不存在有两个相同的变量名 +* 顾名思义,namespace理解为为变量名称分组划分的空间区域 + +#### 一般有三种命名空间 +* 内置名称:built-in names,python内置的名称,如函数名abs,sum,异常名Exception等 +* 全局名称:global names, python模块中定义的名称,如模块中的类,函数,模块级别的变量与常量 +* 局部名称:local names, 函数中定义的名称,函数内部声明的变量名称 + +## 作用域 +**作用域是python程序可以直接访问的命名空间的文本区域。** +* 在python程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,LEGB +* L:local,当前函数内部作用域 +* E:Enclosing Functions,外部嵌套函数的作用域 +* G:global,全局作用域 +* B:built-in,python内建(内置模块)作用域 + +```python +g_count = 0 # 全局作用域 G +def outer(): + o_count = 1 # 闭包函数外的函数中 E + def inner(): + i_count = 2 # 局部作用域 L +``` + +#### 查看python的内置名称 +```python +import builtins +# dir():返回模块的属性列表 +print(dir(builtins)) + +['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'] + +``` + +#### 内部作用域要修改外部作用域的变量,需要用到global,nonlocal +具体参看[变量的作用域与闭包](变量的作用域.md) + +* 函数内部修改全局变量,用global + +```python +def func(): + a = 99 # 此时a只是函数内部的定义局部变量,与全局变量a没有任何关系 + +if __name__ == '__main__': + a = 1 + func() + print(a) # 1 +``` + +```python +def func(): + # 此时python在函数内部作用域中没找到对a的定义 + # 然后去全局空间找到了a + print(a) # 1 + +if __name__ == '__main__': + a = 1 + func() + print(a) # 1 +``` + +```python +def func(): + # 该代码无法编译通过 + # 此时认为a是函数内部的局部变量,但是在函数内部并没有找到对a的定义声明 + print(a) + a = 99 + +if __name__ == '__main__': + a = 1 + func() + print(a) +``` + +```python +def func(): + # 声明a是引用的全局变量a,不是函数局部变量 + global a + a = 99 + +if __name__ == '__main__': + a = 1 + func() + print(a) # 99 +``` + +* 嵌套函数内函数修改外函数中的变量,用 nonlocal + +```python +def out_(): + a = 1 + + def in_(): + nonlocal a + a = 99 + + in_() + print(a) # 99 + +if __name__ == '__main__': + out_() +``` diff --git "a/python_advance/347円261円273円345円256円236円344円276円213円351円235円231円346円200円201円346円226円271円346円263円225円.md" "b/python_advance/347円261円273円345円256円236円344円276円213円351円235円231円346円200円201円346円226円271円346円263円225円.md" new file mode 100644 index 00000000..f37c2526 --- /dev/null +++ "b/python_advance/347円261円273円345円256円236円344円276円213円351円235円231円346円200円201円346円226円271円346円263円225円.md" @@ -0,0 +1,95 @@ +# 类变量、实例变量、类方法 实例方法 静态方法 +## 类变量 +类的所有实例之间共享的值,它们不是单独分配给每个实例的。 + +## 实例变量 +实例化之后,每个实例对象单独拥有的变量。 +```python +class Test: + nums_of_instance = 0 # 类变量 + + def __init__(self, name): + self.name = name # 实例变量 + Test.nums_of_instance += 1 + + +if __name__ == '__main__': + t1 = Test('实例1') + t2 = Test('实例2') + print(Test.nums_of_instance) # 2 + print(t1.name) # 实例1 + print(t2.name) # 实例2 +``` + +## 实例方法 +类中定义的普通方法,第一个参数是self(约定好的),指的是实例对象本身,通过self来传递实例对象的属性和方法。**实例方法只能由实例对象调用。** + +## 类方法 +使用装饰器@classmethod定义,第一个参数cls,表示当前类,用cls来传递类的属性和方法。**类方法可以通过实例对象调用,也可以直接通过类名调用**, 类方法主要应用在统一修改类变量,也就是这个类下所有实例的属性。 + +```python +class Goods: + __discount = 1 # 类变量,私有,不允许外部直接访问 + + def __init__(self, name, price): + self.name = name + self.__price = price # 实例变量,私有 + + @classmethod + def get_discount(cls): + return cls.__discount + + @classmethod + def set_discount(cls, new_discount): + cls.__discount = new_discount + + @property + def price(self): + return self.__price * self.__discount + +if __name__ == '__main__': + g1 = Goods('苹果', 10) + g2 = Goods('香蕉', 15) + print('全场商品折扣:{}'.format(Goods.get_discount())) + print('苹果的价格:{}'.format(g1.price)) + print('香蕉的价格:{}'.format(g2.price)) + + Goods.set_discount(0.5) # 通过类方法修改类变量 + print('全场商品折扣:{}'.format(Goods.get_discount())) + print('苹果的价格:{}'.format(g1.price)) + print('香蕉的价格:{}'.format(g2.price)) + + 全场商品折扣:1 + 苹果的价格:10 + 香蕉的价格:15 + 全场商品折扣:0.5 + 苹果的价格:5.0 + 香蕉的价格:7.5 +``` + +## 静态方法 +**使用装饰器@staticmethod定义,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。静态方法可以通过实例对象调用,也可以通过类名调用** + +实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个"三角形"类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题. + +```python +import math +class Triangle(object): + def __init__(self, a, b, c): + self._a = a + self._b = b + self._c = c + + @staticmethod + def is_valid(a,b,c): + return a+b>c and a+c>b and b+c>a + + def d(self): + return self._a + self._b + self._c + +if __name__ == '__main__': + a,b,c = 3,4,5 + if Triangle.is_valid(a,b,c): + tri = Triangle(a,b,c) + print('周长',tri.d()) +``` diff --git "a/python_advance/350円243円205円351円245円260円345円231円250円.md" "b/python_advance/350円243円205円351円245円260円345円231円250円.md" index 4c196843..98ac1241 100644 --- "a/python_advance/350円243円205円351円245円260円345円231円250円.md" +++ "b/python_advance/350円243円205円351円245円260円345円231円250円.md" @@ -1,90 +1,217 @@ -**装饰器:本质上是一个Python函数(嵌套函数),它的参数是另一个函数(被装饰函数),他可以让其他函数在不做任何代码变动的情况下增加额外的功能,装饰器的返回值也是一个函数对象。有了装饰器,可以抽离出大量与函数功能本身无关的代码并重复使用。需求场景如:插入日志、性能测试、权限校验等。** - -#### 1.最简单的装饰器 -被装饰函数没有参数: - -```python -import time -def count_time(func): - def deco(): - start = time.time() - func() - end = time.time() - print('{} 耗时: {}'.format(func.__name__, end-start)) - return deco -@count_time -def func_a(): - time.sleep(3) - -if __name__ == '__main__': - # func_a 耗时: 3.0008597373962402 - func_a() -``` -#### 2.被装饰函数带参数 -可变参数+关键字参数可以表示所有的参数情况。 - -```python -import time -def count_time(func): - def deco(*args, **kwargs): - start = time.time() - func(*args, **kwargs) - end = time.time() - print('{} 耗时: {}'.format(func.__name__, end-start)) - return deco -@count_time -def func_a(t): - time.sleep(t) - -if __name__ == '__main__': - # func_a 耗时: 1.0008749961853027 - func_a(1) - # func_a 耗时: 2.0003809928894043 - func_a(2) -``` -#### 3.带参数的装饰器 -实现一个装饰器,控制被装饰函数的执行次数。三层嵌套。 - -```python -def repate(num=3): - def actual_deco(func): - def deco(*args, **kwargs): - for i in range(num): - print('{}执行第{}次'.format(func.__name__, i+1)) - func() - return deco - return actual_deco -@repate(num=3) -def foo(): - pass - -if __name__ == '__main__': - # foo执行第1次 - # foo执行第2次 - # foo执行第3次 - foo() -``` - -#### 4.装饰器有一个关键的特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时) +# 装饰器 +## 1.装饰器定义 +**装饰器的本质是一个闭包(嵌套函数中内函数引用了外函数的局部变量,外函数的返回包含对内函数的引用,这样就形成了一个闭包)。[变量的作用域与闭包](变量的作用域.md)** + +* 所以装饰器也可以理解为一个嵌套函数,它的参数是被装饰的函数,返回值也是一个函数对象 +* 装饰器可以在原函数代码没有任何变动的基础上,增加额外的功能 +* 装饰器的应用场景有:性能测试(计算函数运行时间),插入日志,权限校验(Django的@login_required),缓存(缓存函数的计算结果,避免重复计算,提高效率) + +## 2.装饰器的实现 +#### 2.1. 最简单的装饰器 +计算函数运行时间 ```python -registry = [] +import time + +def count_time(func): + def wrap(): + start = time.time() + func() + end = time.time() + print('函数名:{} 耗时:{}'.format(func.__name__, end - start)) + return wrap + +@count_time +def func(): + print('func正在运行...') + time.sleep(3) + print('func运行结束') + +if __name__ == '__main__': + func() +``` +```python +func正在运行... +func运行结束 +函数名:func 耗时:3.0006725788116455 +``` + +#### 2.2. 被装饰的函数带参数 +可变参数+关键字参数可以表示所有的参数情况 +```python +import time + +def count_time(func): + def wrap(*args, **kwargs): + start = time.time() + func(*args, **kwargs) + end = time.time() + print('函数名:{} 耗时:{}'.format(func.__name__, end - start)) + return wrap + +@count_time +def func(seconds): + print('func正在运行...') + time.sleep(seconds) + print('func运行结束') + +if __name__ == '__main__': + func(3) + func(5) +``` + +#### 2.3. 装饰器带参数 +定义一个装饰器,控制被装饰函数运行次数 +```python +def run(times): + def deco(func): + def wrap(*args, **kwargs): + for _ in range(times): + func(*args, **kwargs) + return wrap + return deco + +@run(times=3) +def func(): + print('func正在运行...') + print('func运行结束') + +if __name__ == '__main__': + func() +``` +```python +func正在运行... +func运行结束 +func正在运行... +func运行结束 +func正在运行... +func运行结束 +``` + +#### 2.4. 装饰器用类实现(无参数) +CountTime()相当于调用CountTime类的\_\_call__()方法 + +```python +import time + +class CountTime: + def __call__(self, func): + def wrap(*args, **kwargs): + start = time.time() + func(*args, **kwargs) + end = time.time() + print('函数名:{} 耗时:{}'.format(func.__name__, end - start)) + return wrap + +@CountTime() +def func(): + print('func正在运行...') + time.sleep(3) + print('func运行结束') + +if __name__ == '__main__': + func() +``` +```python +func正在运行... +func运行结束 +函数名:func 耗时:3.0221292972564697 +``` + +#### 2.5. 装饰器用类实现(有参数) +通过init初始化方法传参 +```python +class Run: + def __init__(self, times): + self.times = times + + def __call__(self, func): + def wrap(*args, **kwargs): + for _ in range(self.times): + func(*args, **kwargs) + return wrap + +@Run(times=3) +def func(): + print('func正在运行...') + print('func运行结束') + +if __name__ == '__main__': + func() +``` +```python +func正在运行... +func运行结束 +func正在运行... +func运行结束 +func正在运行... +func运行结束 +``` + +## 3.functools.wraps +以上装饰器存在一个小问题,被装饰函数虽然并没有改动,但是被装饰函数的元信息不见了,如下: +* 被装饰函数的函数名是func,但是经过装饰器装饰后,函数名变为了wrap + +```python +import time + +def count_time(func): + def wrap(): + start = time.time() + func() + end = time.time() + print('函数名:{} 耗时:{}'.format(func.__name__, end - start)) + return wrap + +@count_time +def func(): + print('func正在运行...') + time.sleep(3) + print('func运行结束') +if __name__ == '__main__': + print(func.__name__) # wrap +``` + +**通过functools.wraps来避免这种问题** + +```python +import functools +import time + +def count_time(func): + @functools.wraps(func) + def wrap(): + start = time.time() + func() + end = time.time() + print('函数名:{} 耗时:{}'.format(func.__name__, end - start)) + return wrap + +@count_time +def func(): + print('func正在运行...') + time.sleep(3) + print('func运行结束') + +if __name__ == '__main__': + print(func.__name__) # func +``` +## 4.装饰器有一个关键的特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时) +```python +registry = [] def register(func): print('running register({})'.format(func)) registry.append(func) return func - @register def f1(): print('running f1()') - def f2(): print('running f2()') - if __name__ == '__main__': print(registry) print('running main') @@ -101,7 +228,7 @@ running f1() running f2() ``` -#### 5.标准库中的装饰器 functools.lru_cache() +## 5.标准库中的装饰器 functools.lru_cache() functools.lru_cache是非常实用的装饰器,它实现了备忘功能。这是一项优化技术,它把耗时的函数的结果保存 起来,避免传入相同的参数时重复计算。 * 参数maxsize:指定缓存的最大结果数量 @@ -198,7 +325,7 @@ if __name__ == '__main__': 执行函数fibonacci(5)耗时: 0.0 ``` -#### 6.标准库中的装饰器 functools.singledispatch +## 6.标准库中的装饰器 functools.singledispatch Python3.4新增的 functools.singledispatch 装饰器可以把整体方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。使用@singledispatch 装饰的普通函数会变成泛函数(generic function):根据第一个参数的类型,以不同方式执行相同操作的一组函数. diff --git "a/python_basic/Python345円207円275円346円225円260円345円217円202円346円225円260円347円232円204円344円274円240円351円200円222円346円234円272円345円210円266円.md" "b/python_basic/Python345円207円275円346円225円260円345円217円202円346円225円260円347円232円204円344円274円240円351円200円222円346円234円272円345円210円266円.md" new file mode 100644 index 00000000..ef3bcc0b --- /dev/null +++ "b/python_basic/Python345円207円275円346円225円260円345円217円202円346円225円260円347円232円204円344円274円240円351円200円222円346円234円272円345円210円266円.md" @@ -0,0 +1,35 @@ +# Python函数参数的传递机制 +## 不可变对象是值传递 +**python中不可变对象,函数实际参数(实参)传递给形式参数(形参)的过程,实际上是把实际参数值的副本(复制品)传入函数,参数本身不会收到任何影响。** + +```python +def func(a, b): + a, b = b, a + # 函数内 a:2 b:1 + print('函数内 a:{} b:{}'.format(a, b)) + +if __name__ == '__main__': + a = 1 + b = 2 + # 可以理解为是将a, b的值复制一份传入 + func(a, b) + # 函数外 a:1 b:2 + print('函数外 a:{} b:{}'.format(a, b)) +``` + +## 可变对象是引用传递(地址传递) +对于可变对象如字典,列表等,参数传递的方式是引用传递,也就是将可变对象的引用(内存地址)传递给函数,参数会受到影响。 + +```python +def func(a): + a.append(4) + a.append(5) + # 函数内 a:[1, 2, 3, 4, 5] + print('函数内 a:{}'.format(a)) + +if __name__ == '__main__': + a = [1, 2, 3] + func(a) + # 函数内 a:[1, 2, 3, 4, 5] + print('函数外 a:{}'.format(a)) +``` diff --git "a/python_basic/Python345円234円250円345円206円205円345円255円230円344円270円255円350円257円273円345円206円231円346円225円260円346円215円256円.md" "b/python_basic/Python345円234円250円345円206円205円345円255円230円344円270円255円350円257円273円345円206円231円346円225円260円346円215円256円.md" new file mode 100644 index 00000000..67c6194d --- /dev/null +++ "b/python_basic/Python345円234円250円345円206円205円345円255円230円344円270円255円350円257円273円345円206円231円346円225円260円346円215円256円.md" @@ -0,0 +1,50 @@ +# Python在内存中读写数据 +有时候数据不一定要读写到文件中,可以再内存中进行读写操作。 + +有些场景本地不保存数据,从远程读取文件数据到本地内存中进行处理后,再把数据流推送到远程,从头到尾本地并不生成文件。 + +**比如读取服务器一个加密文件数据流到内存中,在内存中进行解析处理,最终将解密后的数据流推送到服务器,中间不需要在本地保存加密文件,直接在内存中处理,减少了IO操作。** +## StringIO +```python +from io import StringIO + +if __name__ == '__main__': + s = StringIO() + s.write('123\n') + s.write('456\n') + s.write('aaa\n') + s.write('bbb') + print(s) # <_io.stringio object at 0x0000022F7440F9D8> + # 获取写入的内容 + print(s.getvalue()) # '123\n456\naaa\nbbb' + # tell()方法返回文件指针当前位置 + print(s.tell()) # 15 + + # seek(offset, whence)方法用于移动文件读取指针到指定位置 + # offset:代表需要移动偏移的字节数 + # whence:默认0,表示要从哪个位置开始偏移;0代表从文件开头开始算起,1代表从当前位置开始算起,2代表从文件末尾算起。 + s.seek(0, 0) + + print(s.tell()) # 0 + print(s.readlines()) # ['123\n', '456\n', 'aaa\n', 'bbb'] + + print(s.tell()) # 15 + print(s.readlines()) # [] +``` + +## BytesIO +```python +from io import BytesIO + +if __name__ == '__main__': + f = BytesIO() + + fr = open('test.mp4', 'rb') + f.write(fr.read()) + fr.close() + + # 对视频流解析处理 + + with open('local_test.mp4', 'wb') as fw: + fw.write(f.getvalue()) +``` diff --git "a/python_basic/Python346円240円207円345円207円206円346円225円260円346円215円256円347円261円273円345円236円213円.md" "b/python_basic/Python346円240円207円345円207円206円346円225円260円346円215円256円347円261円273円345円236円213円.md" new file mode 100644 index 00000000..0d9a354a --- /dev/null +++ "b/python_basic/Python346円240円207円345円207円206円346円225円260円346円215円256円347円261円273円345円236円213円.md" @@ -0,0 +1,39 @@ +# Python标准数据类型 +python3中有六种标准数据类型 + +* Number:数字,包含int,float,bool,complex(复数) +```python +# bool型也是数字 +print(True + True) # 2 +``` +* String:字符串 +* List:列表 +* Tuple:元组 +* Set:集合 +* Dict:字典 + +## 可变类型与不可变类型 +* 可变类型:在id(内存地址)不变的情况下,值可以变化 +* 不可变类型:值一旦发生变化,id(内存地址)也改变,意味着开辟了新的内存空间。 +#### 可变数据类型:List,Set,Dict +#### 不可变数据类型:Number,String,Tuple + +## type与isinstance的区别 +* type()不认为子类是一种父类类型 +* isinstance()认为子类是一种父类类型 + +```python +class A: + pass + +class B(A): + pass + +if __name__ == '__main__': + a = A() # 父类 + b = B() # 子类 + print(type(a) is A) # True + print(type(b) is A) # False, type()不认为子类是一种父类类型 + print(isinstance(a, A)) # True + print(isinstance(b, A)) # True, isinstance()认为子类是一种父类类型 +``` diff --git "a/python_basic/Python350円257円273円345円217円226円345円244円247円346円226円207円346円234円254円346円226円207円344円273円266円.md" "b/python_basic/Python350円257円273円345円217円226円345円244円247円346円226円207円346円234円254円346円226円207円344円273円266円.md" new file mode 100644 index 00000000..a6c169ab --- /dev/null +++ "b/python_basic/Python350円257円273円345円217円226円345円244円247円346円226円207円346円234円254円346円226円207円344円273円266円.md" @@ -0,0 +1,37 @@ +# Python读取大文本文件 +* read():一次读取文件的全部内容,可以指定size去读取 +* readline():每次读取一行 +* readlines():一次读取所有内容并按行返回list + +## 按行读取 +```python +with open('test.txt', 'r', encoding='utf-8') as fr: + for line in fr: + print(line) +``` + +## 按块读取 +```python +def read_file_chunked(fr, block_size=1024): + while True: + chunk = fr.read(block_size) + if not chunk: + break + yield chunk + + +if __name__ == '__main__': + with open('test.txt', 'r', encoding='utf-8') as fr: + for chunk in read_file_chunked(fr): + print(chunk) +``` + +或者 +```python +from functools import partial +def read_file_chunked(fr, block_size=1024 * 8): + # 首先使用 partial(fr.read, block_size) 构造一个新的无需参数的函数 + # 循环将不断返回 fr.read(block_size) 调用结果,直到其为 '' 时终止 + for chunk in iter(partial(fr.read, block_size), ''): + yield chunk +``` diff --git a/python_basic/main.md b/python_basic/main.md index 86343fef..6c30271b 100644 --- a/python_basic/main.md +++ b/python_basic/main.md @@ -1,23 +1,30 @@ ## Python基础 -1. [时间格式转换](时间格式转换.md) - -2. [字节串与十六进制转换](字节串与十六进制转换.md) -3. [struct-序列化与反序列化](序列化与反序列化.md) -4. [文件路径处理](文件路径处理.md) -5. [创建临时文件](创建临时文件.md) -6. [文件目录扫描](文件目录扫描.md) -7. [XML文件处理](XML文件处理.md) -8. [utf8-BOM文件处理](utf8BOM文件处理.md) -9. [解析命令行参数(getopt)](解析命令行参数getopt.md) -10. [等分字符串](等分字符串.md) -11. [读取配置文件(configparser)](读取配置文件configparser.md) -12. [集合删除元素](集合删除元素.md) -13. [网络字节序(大端)与主机字节序(小端)转换](网络字节序与主机字节序转换.md) -14. [文件目录事件监控](文件目录事件监控.md) -15. [车牌格式校验](车牌格式校验.md) -16. [关于tuple](关于tuple.md) -17. [json文件处理](json文件处理.md) -18. [Excel文件处理](Excel文件处理.md) -19. [深拷贝与浅拷贝](深拷贝与浅拷贝.md) -20. [列表推导式,字典推导式,生成器表达式,map,filter,reduce](列表推导式.md) -21. [== 与 is的区别](is.md) +* [时间格式转换](时间格式转换.md) +* [字节串与十六进制转换](字节串与十六进制转换.md) +* [struct-序列化与反序列化](序列化与反序列化.md) +* [文件路径处理](文件路径处理.md) +* [创建临时文件](创建临时文件.md) +* [文件目录扫描](文件目录扫描.md) +* [XML文件处理](XML文件处理.md) +* [utf8-BOM文件处理](utf8BOM文件处理.md) +* [解析命令行参数(getopt)](解析命令行参数getopt.md) +* [等分字符串](等分字符串.md) +* [读取配置文件(configparser)](读取配置文件configparser.md) +* [集合删除元素](集合删除元素.md) +* [网络字节序(大端)与主机字节序(小端)转换](网络字节序与主机字节序转换.md) +* [文件目录事件监控](文件目录事件监控.md) +* [车牌格式校验](车牌格式校验.md) +* [关于tuple](关于tuple.md) +* [json文件处理](json文件处理.md) +* [Excel文件处理](Excel文件处理.md) +* [Python读取大文本文件](Python读取大文本文件.md) +* [Python标准数据类型](Python标准数据类型.md) +* [深拷贝与浅拷贝](深拷贝与浅拷贝.md) +* [匿名函数,列表推导式,字典推导式,生成器表达式,map,filter,reduce](列表推导式.md) +* [== 与 is的区别](is.md) +* [python异常处理机制](python异常处理机制.md) +* [\_\_new__()与\_\_init__()方法](new和init方法.md) +* [@property 保护变量的访问与设置](保护变量的访问与设置.md) +* [Python函数参数的传递机制](Python函数参数的传递机制.md) +* [Python猴子补丁 monkey patch](猴子补丁.md) +* [Python在内存中读写数据StringIO/BytesIO](Python在内存中读写数据.md) diff --git "a/python_basic/new345円222円214円init346円226円271円346円263円225円.md" "b/python_basic/new345円222円214円init346円226円271円346円263円225円.md" new file mode 100644 index 00000000..ac1c5b8d --- /dev/null +++ "b/python_basic/new345円222円214円init346円226円271円346円263円225円.md" @@ -0,0 +1,83 @@ +# \_\_new__()和\_\_init__()方法 +* \_\_new__():是类的构造方法,在实例创建前被调用,它的作用就是创建实例并返回实例对象,是一个静态方法 +* \_\_init__():是类的初始化方法,当实例对象被创建后调用,然后初始化实例对象的属性值,是一个实例方法 +* new()方法先被调用,创建实例对象,然后将实例对象传递给init()方法的self,进行初始化操作 + +## \_\_new__()实现单例 +```python +class Singleton: + + def __new__(cls, *args, **kwargs): + if not hasattr(cls, 'instance'): + cls.instance = super().__new__(cls) + return cls.instance + +if __name__ == '__main__': + s1 = Singleton() + s2 = Singleton() + print('s1:{}'.format(id(s1))) + print('s2:{}'.format(id(s2))) + print('s1 is s2:{}'.format(s1 is s2)) +``` +```python +s1:1891632871352 +s2:1891632871352 +s1 is s2:True +``` + +**以上看似实现了单例模式,但实际在多线程的情况下会有问题,多个线程同时创建单例对象,如果不加锁的情况下是不安全的。** + +```python +import time +import threading + +class Singleton: + + def __new__(cls, *args, **kwargs): + if not hasattr(cls, 'instance'): + time.sleep(0.05) + cls.instance = super().__new__(cls) + return cls.instance + +def func(): + s = Singleton() + print('s:{}'.format(id(s))) + +if __name__ == '__main__': + for _ in range(5): + # 开启5个线程同时创建对象 + td = threading.Thread(target=func) + td.start() +``` +```python +s:1230297165896 +s:1230297245008 +s:1230297372152 +s:1230297270648 +s:1230297296176 +``` + +**多线程下创建单例对象需要加锁** + +```python +import threading + +class Singleton: + _instance_lock = threading.Lock() + + def __new__(cls, *args, **kwargs): + # 加锁 + with cls._instance_lock: + if not hasattr(cls, 'instance'): + cls.instance = super().__new__(cls) + return cls.instance + +def func(): + s = Singleton() + print('s:{}'.format(id(s))) + +if __name__ == '__main__': + for _ in range(5): + td = threading.Thread(target=func) + td.start() +``` diff --git "a/python_basic/python345円274円202円345円270円270円345円244円204円347円220円206円346円234円272円345円210円266円.md" "b/python_basic/python345円274円202円345円270円270円345円244円204円347円220円206円346円234円272円345円210円266円.md" new file mode 100644 index 00000000..4546afad --- /dev/null +++ "b/python_basic/python345円274円202円345円270円270円345円244円204円347円220円206円346円234円272円345円210円266円.md" @@ -0,0 +1,77 @@ +# python异常处理机制 +python主要支持5种异常机制 +## 1.默认的异常处理 +如果没有对异常做任何处理,程序执行过程中,发生了异常,就会中断,调用python默认的异常处理器,并在终端输出异常信息。 +```python +if __name__ == '__main__': + a = [1, 2] + a[10] = 10 + print(a) + +Traceback (most recent call last): + File "C:/Users/daacheng.py", line 3, in + a[10] = 10 +IndexError: list assignment index out of range +``` + +## 2.try...except... +利用try-except语句捕捉可能产生的异常并做相应的处理,之后程序继续向下执行 +```python +if __name__ == '__main__': + try: + a = [1, 2] + a[10] = 10 + print(a) + except IndexError as e: + # 捕捉异常并处理 + print('出现索引异常') + print('over') + +出现索引异常 +over +``` + +## 3.try...except...finally... +无论是否发生异常,finally都会被执行 +```python +if __name__ == '__main__': + try: + a = [1, 2] + a[10] = 10 + print(a) + except IndexError: + print('出现索引异常') + finally: + print('执行finally') + print('over') + +出现索引异常 +执行finally +over +``` + +## 4.assert语句 +assert(断言)用于判断一个表达式,在表达式条件为 False 的时候触发异常,中断程序,表达式为True时继续向下执行。 + +```python +assert 表达式 + +或者 + +# 当表达式为False时,执行代码块,抛出异常 +assert 表达式, 代码块 +``` + +```python +if __name__ == '__main__': + assert 1 == 2, print('1不等于2') + +1不等于2 +Traceback (most recent call last): +File "C:/Users/daacheng.py", line 2, in + assert 1 == 2, print('1不等于2') +AssertionError: None +``` + +## 5.with...as... +不管使用过程中是否发生异常都会执行必要的"清理"操作,释放资源,比如文件使用后自动关闭/线程中锁的自动获取和释放等 diff --git "a/python_basic/344円277円235円346円212円244円345円217円230円351円207円217円347円232円204円350円256円277円351円227円256円344円270円216円350円256円276円347円275円256円.md" "b/python_basic/344円277円235円346円212円244円345円217円230円351円207円217円347円232円204円350円256円277円351円227円256円344円270円216円350円256円276円347円275円256円.md" new file mode 100644 index 00000000..e50572b6 --- /dev/null +++ "b/python_basic/344円277円235円346円212円244円345円217円230円351円207円217円347円232円204円350円256円277円351円227円256円344円270円216円350円256円276円347円275円256円.md" @@ -0,0 +1,25 @@ +# 保护变量的访问与设置 +对于私有变量(双下划线)或者保护变量(单下划线),不允许外部直接访问,类似于Java的private,可以通过对外提供get,set接口来访问和修改这类变量,便于控制。 + +* @property装饰器会将方法转换为相同名称的只读属性,相当于实现get方法 +* @xxx.setter装饰器使得可以直接通过 对象.xxx来修改保护变量的值,相当于实现set方法 + +```python +class Student(object): + def __init__(self, name): + self.name = name + self._score = None + + @property + def score(self): + return self._score + + @score.setter + def score(self, value): + self._score = value + +if __name__ == '__main__': + s = Student('王大锤') + s.score = 100 # 设置保护变量的值 + print(s.score) # 访问保护变量 +``` diff --git "a/python_basic/345円205円263円344円272円216円tuple.md" "b/python_basic/345円205円263円344円272円216円tuple.md" index b9dafb09..1cbbb53a 100644 --- "a/python_basic/345円205円263円344円272円216円tuple.md" +++ "b/python_basic/345円205円263円344円272円216円tuple.md" @@ -1,4 +1,19 @@ -#### 不要把可变对象放在元组中 +**元组不可变指的是tuple数据结构中的物理内容不变,也就是元组保存的引用是不可变的。** + +**所以如果元组引用的元素是可变的,那即使元组本身包含的引用不可变,元素依然可变** +```python +# 元组内保存的三个元素的内存地址是不可变的 +t1 = (1, 2, [30, 40]) +print(id(t1[-1])) # 1742619956424 +t1[-1].append(50) +print(t1) # (1, 2, [30, 40, 50]) +print(id(t1[-1])) # 1742619956424 +``` + +![](../pic/python_basic/tuple3.png) + +#### 尽量不要把可变对象放在元组中 #### 增量赋值不是一个原子操作,虽然程序抛出异常,但还是会添加成功 ++= 是赋值操作,相当于要改变引用地址,元组内引用地址是不可变的,所以会报错 答案:D ![](../pic/python_basic/tuple2.png) diff --git "a/python_basic/345円210円227円350円241円250円346円216円250円345円257円274円345円274円217円.md" "b/python_basic/345円210円227円350円241円250円346円216円250円345円257円274円345円274円217円.md" index b060a54c..337c8b8a 100644 --- "a/python_basic/345円210円227円350円241円250円346円216円250円345円257円274円345円274円217円.md" +++ "b/python_basic/345円210円227円350円241円250円346円216円250円345円257円274円345円274円217円.md" @@ -1,4 +1,16 @@ -#### map(__func, __iter) +## 匿名函数 +python中的匿名函数,也就是lambda函数,通常用来替代函数体比较简单的函数,不需要函数名。 + +**有些函数在代码中只用一次,函数体比较简单,可以考虑匿名函数,减少代码量,相对优雅** +```python +lambda 参数: 表达式 + +func = lambda x, y: x+y +res = func(1, 2) +print(res) # 3 +``` + +## map(__func, __iter) map会将一个函数映射到一个输入列表的所有元素上. ```python list_x = [1, 2, 3, 4, 5, 6, 7, 8] @@ -9,7 +21,7 @@ print(list_x_multi_two) print(list(list_x_multi_two)) ``` -#### filter(__func, __iter) +## filter(__func, __iter) 过滤列表中的元素 ```python list_x = [1, 2, 3, 4, 5, 6, 7, 8] @@ -20,7 +32,7 @@ print(list_x_more_than_5) print(list(list_x_more_than_5)) ``` -#### 列表推导式部分情况可以替代map,filter函数 +## 列表推导式部分情况可以替代map,filter函数 ```python # [2, 4, 6, 8, 10, 12, 14, 16] print([x*2 for x in list_x]) @@ -29,7 +41,7 @@ print([x*2 for x in list_x]) print([x for x in list_x if x> 5]) ``` -#### reduce(func, sequence) +## reduce(func, sequence) 对参数序列中元素进行累计计算。用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。 ```python from functools import reduce @@ -38,14 +50,14 @@ sum_ = reduce((lambda x, y:x+y), list_x) print(sum_) # 36 ``` -#### 生成器表达式 +## 生成器表达式 与列表推导式不同的是,生成器表达式逐个产出元素,不会一次产生出所有的结果。这样可以避免额外的内存占用。 ```python for item in (x*2 for x in list_x): print(item) ``` -#### 字典推导式 +## 字典推导式 ```python data = [('China', '86'), ('USA', '1'), ('XXX', '87')] data_dict = {country: areacode for country, areacode in data} diff --git "a/python_basic/346円200円273円347円273円223円.md" "b/python_basic/346円200円273円347円273円223円.md" new file mode 100644 index 00000000..e69de29b diff --git "a/python_basic/347円214円264円345円255円220円350円241円245円344円270円201円.md" "b/python_basic/347円214円264円345円255円220円350円241円245円344円270円201円.md" new file mode 100644 index 00000000..01ff6160 --- /dev/null +++ "b/python_basic/347円214円264円345円255円220円350円241円245円344円270円201円.md" @@ -0,0 +1,60 @@ +# 猴子补丁 +**monkey patch允许在运行期间动态修改一个类或模块** + +* 在运行时替换方法、属性等 +* 在不修改第三方代码的情况下增加原来不支持的功能 +* 在运行时为内存中的对象增加patch而不是在磁盘的源代码中增加 + +```python +class A: + def func(self): + print('这是A类下的func方法') + +# arg 这个参数是没有用到的,因为func有一个参数,如果这个函数没有参数的话不能这样直接赋值 +def monkey_func(arg): + print('这是猴子补丁方法') + +if __name__ == '__main__': + a = A() + # 运行原类下的方法 + a.func() # 这是A类下的func方法 + + # 在不改变原类代码的情况下,动态修改原类的方法,打补丁 + A.func = monkey_func + + # 运行替换后的方法 + a.func() # 这是猴子补丁方法 +``` + +## 应用 +gevent通过打补丁的方式,利用自己的socket替换了python的标准socket模块,利用gevent协程处理高并发的情况 + +```python +from gevent import monkey +monkey.patch_all() +import gevent +from socket import * + + +def talk(conn): + while 1: # 循环通讯 + try: + from_client_msg = conn.recv(1024) + if not from_client_msg:break + print("来自客户端的消息:%s" %(from_client_msg)) + conn.send(from_client_msg.upper()) + except: + break + conn.close() + + +if __name__ == '__main__': + server = socket() + ip_port = ("127.0.0.1", 8001) + server.bind(ip_port) + server.listen(5) + while 1: # 循环连接 + conn, addr = server.accept() + gevent.spawn(talk, conn) # 开启一个协程 + server.close() +``` diff --git a/spiders/main.md b/spiders/main.md index 72c6a474..9b0d5ae3 100644 --- a/spiders/main.md +++ b/spiders/main.md @@ -1,11 +1,11 @@ ## Python爬虫 -1. [Python爬虫之requests库](Python爬虫之requests库.md) - -2. [Python爬虫之BeautifulSoup](Python爬虫之BeautifulSoup.md) -3. [Python爬虫之xpath解析](Python爬虫xpath解析.md) -4. [Python爬虫之selenium自动化](Python爬虫之selenium自动化.md) -5. [Python爬虫之构建自己的代理IP池](Python爬虫之构建自己的代理IP池.md) -6. [Python爬虫之知乎钓鱼贴图片爬取](Python爬虫之知乎钓鱼贴图片爬取.md) -7. [Python爬虫之uiautomator2常用操作](Python爬虫之uiautomator2常用操作.md) -8. [python爬虫之一尘论坛发帖数据爬取](python爬虫之一尘论坛发帖数据爬取.md) -9. [python爬虫之网易云音乐歌单歌曲列表及热门评论数据抓取](python爬虫之网易云音乐.md) +* [Python爬虫之requests库](Python爬虫之requests库.md) +* [Python爬虫之BeautifulSoup](Python爬虫之BeautifulSoup.md) +* [Python爬虫之xpath解析](Python爬虫xpath解析.md) +* [Python爬虫之selenium自动化](Python爬虫之selenium自动化.md) +* [Python爬虫之构建自己的代理IP池](Python爬虫之构建自己的代理IP池.md) +* [Python爬虫之知乎钓鱼贴图片爬取](Python爬虫之知乎钓鱼贴图片爬取.md) +* [Python爬虫之uiautomator2常用操作](Python爬虫之uiautomator2常用操作.md) +* [python爬虫之一尘论坛发帖数据爬取](python爬虫之一尘论坛发帖数据爬取.md) +* [python爬虫之网易云音乐歌单歌曲列表及热门评论数据抓取](python爬虫之网易云音乐.md) +* [一些概念](概念.md) diff --git "a/spiders/346円246円202円345円277円265円.md" "b/spiders/346円246円202円345円277円265円.md" new file mode 100644 index 00000000..63268a5c --- /dev/null +++ "b/spiders/346円246円202円345円277円265円.md" @@ -0,0 +1,58 @@ +## 1.robots协议 +也叫robots.txt,是存放在网站根目录下的文本文件,用来告诉搜索引擎该网站哪些内容是不应该被抓取的,哪些是可以抓取的。 + +如https://www.csdn.net/robots.txt +```python +User-agent: * +Disallow: /scripts +Disallow: /public +Disallow: /css/ +Disallow: /images/ +Disallow: /content/ +Disallow: /ui/ +Disallow: /js/ +Disallow: /scripts/ +Disallow: /article_preview.html* +Disallow: /tag/ +Disallow: /*?* +Disallow: /link/ + +Sitemap: https://www.csdn.net/sitemap-aggpage-index.xml +Sitemap: https://www.csdn.net/article/sitemap.txt +``` +## 2.常见的反爬虫措施 +#### 1.请求头校验 +一般网站会对请求头进行校验,比如Host,UA,Content-Type字段等,模拟请求的时候,这些常见的请求头最好是带上。 +#### 2.IP访问次数控制 +同一个IP地址短时间内大量发起请求,会引起IP限制,解决方法是用代理IP,或者构建自己的代理IP池。 +#### 3.接口请求频率限制 +有的网站会控制接口访问的频率,比如有些查询接口,控制两三秒访问一次。 +#### 4.接口访问次数限制 +每天限制某个IP或账号访问接口的次数,达到上限后出现二次验证或者直接封账号/IP.比如登录接口 +#### 5.行为认证 +请求次数过多会出现人工认证,如图片验证码,滑动认证,点击认证等,可以对接打码平台。 +#### 6,自动化环境检测 +selenium自动化工具有的网站会检测出来,大部分可以通过下面两种方式跳过检测,下面两种方式无法处理,还可以尝试把页面改为移动端页面(手机模式),最后还有一种方法就是代理服务器拦截修改js代码,把检测selenium的js修改掉。 +```python +options = webdriver.ChromeOptions() +# 躲避部分网站selenium检测 +options.add_experimental_option('excludeSwitches', ['enable-automation']) +options.add_experimental_option("useAutomationExtension", False) + +driver = webdriver.Chrome(executable_path=chromedriver_path, options=options) + +# 躲避部分网站selenium检测 +script = "Object.defineProperty(navigator, 'webdriver', {get: () => undefined});" +driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": script}) +``` + +对于移动端appium的检测,可以尝试替换为uiautomator2实现自动化 + +#### 7.数据动态加载 +有的数据不是通过html页面的接口请求返回的,抓包分析请求,找到正确的数据接口。 +#### 8.请求参数加密 +网易云音乐的post请求的请求体就是前端经过js加密后计算得到的,需要逆向js代码 +#### 9.返回数据加密 +需要逆向js代码,分析如何解密。还有一种像大众点评的评论,需要通过定位去找到文本。 +#### 10.动态更新cookies +华为手机云服务,每次请求接口都会重新设置cookies,并且请求头参数也需要跟着cookies一起变化

AltStyle によって変換されたページ (->オリジナル) /