├── MySQL命令 笔记.pdf ├── Pythonic.py ├── README.md ├── README.pdf ├── img ├── img1.png ├── img10.png ├── img11.png ├── img2.png ├── img3.png ├── img4.png ├── img5.png ├── img6.jpg ├── img7.png ├── img8.png ├── img9.png ├── vim.png └── web7.jpeg ├── process-parallel ├── process.py ├── processPool.py ├── process_Manager.py ├── process_pipe.py └── process_queue.py ├── server-program ├── mult-client.py └── mult-server.py ├── thread-parallel ├── GIL.py ├── thread-status-change.png ├── thread.py ├── threadPool.py ├── thread_Event.py ├── thread_Lock.py ├── thread_Semaphare.py └── thread_queue.py ├── 精选 TOP 面试题 ├── 1.两数之和.py ├── 10.正则表达式匹配.py ├── 11.盛最多水的容器.py ├── 13.罗马数字转整数.py ├── 14.最长公共前缀.py ├── 15.三数之和.py ├── 17.电话号码的字母组合.py ├── 19.删除链表的倒数第n个节点.py ├── 2.两数相加.py ├── 20.有效的括号.py ├── 21.合并两个有序链表.py ├── 22.括号生成.py ├── 23.合并k个排序链表.py ├── 26.删除排序数组中的重复项.py ├── 28.实现str-str.py ├── 3.无重复字符的最长子串.py ├── 33.搜索旋转排序数组.py ├── 34.在排序数组中查找元素的第一个和最后一个位置.py ├── 36.有效的数独.py ├── 38.报数.py ├── 4.寻找两个有序数组的中位数.py ├── 41.缺失的第一个正数.py ├── 42.接雨水.py ├── 46.全排列.py ├── 5.最长回文子串.py ├── 7.整数反转.py └── 8.字符串转换整数-atoi.py └── 计算机网络概述.md /MySQL命令 笔记.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/MySQL命令 笔记.pdf -------------------------------------------------------------------------------- /Pythonic.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import parse_qs 2 | from random import randint 3 | # 1.确认python版本 4 | # 2.遵循PEP8风格 5 | # 3.了解bytes、str与unicode的区别 6 | # 接受bytes或str,总是返回str 7 | def to_str(bytes_or_str): 8 | if isinstance(bytes_or_str, bytes): 9 | value = bytes_or_str.decode('utf-8') 10 | else: 11 | value = bytes_or_str 12 | return value 13 | 14 | # 接受bytes或str,总是返回bytes 15 | def to_bytes(bytes_or_str): 16 | if isinstance(bytes_or_str, str): 17 | value = bytes_or_str.encode('utf-8') 18 | else: 19 | value = bytes_or_str 20 | return value 21 | 22 | # 4.用辅助函数取代复杂的表达式 23 | my_values = parse_qs('red=5&blue=4&green=', keep_blank_values=True) 24 | print(my_values) 25 | # 待查询参数不存在或者查询参数的值为空白时返回0 26 | def get_first_int(values, key, default=0): 27 | found = values.get(key, ['']) 28 | if found[0]: 29 | found = int(found[0]) 30 | else: 31 | found = default 32 | return found 33 | print(get_first_int(my_values, 'blue', 0)) 34 | 35 | # 5.了解切割序列的方法(1.注意索引省略的情况;2.切片不考虑越界;3.利用切片操作对list赋值) 36 | # 6.在单次切片操作内,不要同时指定start、end和stride(先做范围切割,再做步进切割,反过来也行,stride尽量为正) 37 | # 7.用列表推导(list comprehension)来取代map和filter 38 | 39 | a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 40 | squares = [x**2 for x in a if x % 2 == 0] 41 | print(squares) 42 | 43 | chile_ranks = {'ghost': 1, 'habanero': 2, 'cayene': 3} 44 | rank_dict = {rank: name for name, rank in chile_ranks.items()} 45 | chile_len_set = {len(name) for name in rank_dict.values()} 46 | print(rank_dict) 47 | print(chile_len_set) 48 | 49 | # 8.不要使用含有两个以上表达式(两个条件,两个循环或者一个循环搭配一个条件)的列表推导 50 | # 9.用生成器表达式(generator expression)来改写数据量较大(内存量激增,消耗大量内存)的列表推导 51 | 52 | it = (x for x in a) 53 | print(it) 54 | print(next(it)) 55 | # 串在一起的生成器表达式执行速度很快 56 | # 把某个声称其表达式所返回的迭代器,放在另一个生成器表达式的for子表达式中 57 | roots = ((x, x**0.5) for x in it) 58 | print(next(roots)) # 迭代器是有状态的 59 | print(next(roots)) 60 | 61 | # 10.尽量用enumerate取代range(元素与下标相结合的序列遍历代码) 62 | 63 | random_bits = 0 64 | for i in range(8): 65 | if randint(0,1): 66 | random_bits |= 1 << i 67 | print(random_bits) 68 | 69 | flavor_list = ['vanilla', 'pecan', 'chocolate', 'strawbetery'] 70 | for i, flavor in enumerate(flavor_list,2): 71 | print('%d: %s'%(i, flavor)) 72 | 73 | # 11.用zip函数同时遍历两个迭代器(1.python3中的zip相当于生成器,会在遍历过程中逐次产生元组;2.如果提供的迭代器长度不等,zip会自动提前终止) 74 | 75 | names = ['Cecilia', 'Lise', 'Marie'] 76 | letters = [len(n) for n in names] 77 | longest_name = None 78 | max_letters = 0 79 | for name, count in zip(names, letters): 80 | if count > max_letters: 81 | longest_name = name 82 | max_letters = count 83 | print(longest_name, max_letters) 84 | 85 | # 12. 不要在for和while循环后面写else块 86 | # 13. 合理利用try/except/else/finally结构中的每个代码块 87 | def load_json_key(data, key): 88 | try: 89 | result_dict = json.loads(data) 90 | except ValueError as e: 91 | raise KeyError from e 92 | else: 93 | return result_dict[key] 94 | 95 | UNDEFINED = object() 96 | def divide_json(path): 97 | handle = open(path,'r+') 98 | try: 99 | data = handle.read() 100 | op = json.loads(data) 101 | value = ( 102 | op['numerator'] / 103 | op['demominator'] 104 | ) 105 | except ZeroDivisionError as e: 106 | return UNDEFINED 107 | else: 108 | op['result'] = value 109 | result = json.dumps(op) 110 | handle.seek(0) 111 | handle.write(result) 112 | return value 113 | finally: 114 | handle.close() 115 | from itertools import islice 116 | from datetime import datetime 117 | import json 118 | # 第一类对象(First-Class Object) 119 | # 函数作为第一类对象(First-Class Object),函数作为第一类对象,支持赋值给变量,作为参数传递给其它函数,作为其它函数的返回值,支持函数的嵌套,实现了__call__方法的类实例对象也可以当做函数被调用。是 Python 函数的一大特性。 120 | # 函数身为一个对象,拥有对象模型的三个通用属性:id、类型、和值, 121 | def foo(text): 122 | print(len(text)) 123 | 124 | foo("im ok") 125 | print(id(foo)) 126 | print(type(foo)) 127 | print(foo) 128 | ''' 129 | 1746434188968 130 | 131 | 132 | ''' 133 | # 函数赋值给更多的变量,唯一变化的是该函数对象的引用计数不断地增加,本质上这些变量最终指向的都是同一个函数对象。 134 | # 14.尽量用异常表示特殊情况,而不要返回None 135 | 136 | def divide(a, b): 137 | try: 138 | return a/b 139 | except ZeroDivisionError as e: 140 | raise ValueError('invalid inputs') from e 141 | divide(1, 1) 142 | 143 | # 15.了解如何在闭包里使用外围作用域中的变量 144 | 145 | def sort_priority(numbers, group): 146 | found = False 147 | def helper(x): 148 | #nonlocal found 149 | if x in group: 150 | found = True #作用域bug(scoping bug),,python语言故意设计,防止函数的局部变量污染函数外面的模块 151 | return (0, x) 152 | return(1, x) 153 | numbers.sort(key=helper) 154 | return found 155 | numbers = [8, 3, 1, 2, 5, 4, 7, 6] 156 | group = {2, 3, 5, 7} 157 | print(sort_priority(numbers, group)) 158 | print(numbers) 159 | 160 | # 16.考虑用生成器(generator)来改写直接返回列表的函数 161 | 162 | def index_file(handle): 163 | offset = 0 164 | for line in handle: 165 | if line: 166 | yield offset 167 | for letter in line: 168 | offset += 1 169 | if letter == ' ': 170 | yield offset 171 | with open(r'C:\Users\ASUS\Documents\我的坚果云\python学习笔记\Effective Python\address.txt', 'r') as f: 172 | it = index_file(f) 173 | results = islice(it, 0, 3) 174 | print(list(results)) 175 | 176 | # 17.在参数上面迭代时,要多加小心 177 | # (Python的迭代器协议,描述了容器和迭代器应该如何与iter和next内置函数、for循环及相关表达式互相配合) 178 | # (把__iter__方法实现为生成器,即可定义自己的容器类型) 179 | class ReadVisits(object): 180 | def __init__(self, data_path): 181 | self.data_path = data_path 182 | def __iter__(self): 183 | with open(self.data_path) as f: 184 | for line in f: 185 | yield int(line) 186 | 187 | def normalized(numbers): 188 | if iter(numbers) is iter(numbers): 189 | raise TypeError('必须提供容器类型') 190 | total = sum(numbers) 191 | result = [] 192 | for value in numbers: 193 | percent = 100 * value / total 194 | result.append(percent) 195 | return result 196 | print(normalized([1,2,3])) 197 | 198 | # 18.用数量可变的位置参数(*args,星号参数) 199 | def log(message, *values): 200 | if not values: 201 | print(message) 202 | else: 203 | values_str = ','.join(str(x) for x in values) 204 | print('%s: %s'%(message, values_str)) 205 | log('my birth',9,12,98) 206 | log('no values') 207 | 208 | # 19.用关键字参数来表示可选的行为 209 | # 20.用None和文档字符串来描述具有动态默认值的参数 210 | #(例如打印日志消息时,要把相关事件的记录时间也标注在这条消息中) 211 | def log(message, when = None): 212 | ''' 213 | args: 214 | message: message to print 215 | when: datetime of when the message occurred> 216 | Default to the percent time. 217 | ''' 218 | when = datetime.now() if when is None else when 219 | print('%s: %s'%(when, message)) 220 | log('hi there') 221 | 222 | def decode(data, default=None): 223 | ''' 224 | Load JSON data from a string. 225 | 226 | Args: 227 | data: JSON data to decode 228 | default: Value to return if decoding fails 229 | Default to an empty dictinary 230 | ''' 231 | if default is None: # if 动态生成过程 232 | default = {} 233 | try: 234 | return json.loads(data) 235 | except ValueError: 236 | return default 237 | 238 | # 21.用只能以关键字形式指定的参数来确保代码明晰 239 | def safe_division(number, divisor, *, ignore_overflow = False, ignore_zero_division = False): 240 | print(ignore_overflow) 241 | safe_division(1,2) 242 | # 2.函数注释 243 | def fun1(a:'spam',b:(1,2),c:float)->int: 244 | return a+b+c 245 | print(fun1(1,2,3)) 246 | # 函数对象有一个名为 __annotations__ 的属性,它是一个映射(dict), 247 | # 用于将每个参数名(key)映射到其相关的注释(value)。 248 | print(fun1.__annotations__) 249 | # 3.匿名函数:lambda是单一的表达式, 250 | # 程序一次行使用,不需要定义函数名,节省内存中变量定义空间 251 | L = [ 252 | lambda x: x**2, 253 | lambda x: x**3, 254 | lambda x: x**4 255 | ] 256 | for f in L: 257 | print(f(2)) 258 | # 4.函数式编程工具filter与reduce,map 259 | from functools import reduce 260 | print(reduce((lambda x,y:x+y),[1,2,3,4,5],5)) 261 | 262 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | - [写在前面](#写在前面) 7 | - [公司选择](#公司选择) 8 | - [简历投递](#简历投递) 9 | - [面试策略](#面试策略) 10 | - [技术部分](#技术部分) 11 | - [语言特性篇](#语言特性篇) 12 | - [谈谈 Python 和其他语言的区别](#谈谈-python-和其他语言的区别) 13 | - [Python 2和3的区别](#python-2和3的区别) 14 | - [闭包](#闭包) 15 | - [装饰器](#装饰器) 16 | - [谈谈GC](#谈谈gc) 17 | - [GIL的理解](#gil的理解) 18 | - [Python传参](#python传参) 19 | - [深拷贝和浅拷贝](#深拷贝和浅拷贝) 20 | - [鸭子类型](#鸭子类型) 21 | - [猴子补丁](#猴子补丁) 22 | - [Python中的作用域](#python中的作用域) 23 | - [函数式编程](#函数式编程) 24 | - [lambda函数](#lambda函数) 25 | - [map函数](#map函数) 26 | - [reduce函数](#reduce函数) 27 | - [filter函数](#filter函数) 28 | - [迭代器和生成器](#迭代器和生成器) 29 | - [协程](#协程) 30 | - [Python 面对对象编程](#python-面对对象编程) 31 | - [封装](#封装) 32 | - [继承](#继承) 33 | - [多态](#多态) 34 | - [类成员](#类成员) 35 | - [类成员的修饰符](#类成员的修饰符) 36 | - [类的特殊成员](#类的特殊成员) 37 | - [Python自省指南](#python自省指南httpswwwibmcomdeveloperworkscnlinuxl-pyintindexhtml) 38 | - [Python 中的元编程](#python-中的元编程httpswwwibmcomdeveloperworkscnanalyticslibraryba-metaprogramming-pythonindexhtml) 39 | - [设计模式](#设计模式) 40 | - [单例模式](#单例模式) 41 | - [工厂模式](#工厂模式httpssegmentfaultcoma1190000013053013) 42 | - [数据结构与算法篇](#数据结构与算法篇) 43 | - [排序算法](#排序算法) 44 | - [链表算法](#链表算法httpsmpweixinqqcoms__bizmzuynjqxnjyymgmid2247484830idx1sn9d24fc787da4b49b82ac01c7f8de257bchksmfa0e6a1fcd79e309a2e7f3e09ec9913a55f1c077287c907f13528578b7785831a2effb3104e0scene21wechat_redirect) 45 | - [栈和队列算法](#栈和队列算法) 46 | - [二叉树算法](#二叉树算法) 47 | - [四种遍历方式](#四种遍历方式) 48 | - [二叉树中的一些重要属性](#二叉树中的一些重要属性) 49 | - [字符串算法](#字符串算法) 50 | - [哈希算法](#哈希算法) 51 | - [常见算法基本思想](#常见算法基本思想) 52 | - [回溯](#回溯) 53 | - [分而治之](#分而治之) 54 | - [动态规划](#动态规划) 55 | - [分支界限](#分支界限) 56 | - [操作系统篇](#操作系统篇) 57 | - [进程与线程区别](#进程与线程区别) 58 | - [谈谈多线程并发](#谈谈多线程并发) 59 | - [线程同步方式(Python代码实现)](#线程同步方式python代码实现httpsgithubcommorsolipython-interview-guidetreemasterthread-parallel) 60 | - [线程状态切换](#线程状态切换) 61 | - [分页机制](#分页机制) 62 | - [分页和分段有什么区别(内存管理)](#分页和分段有什么区别内存管理) 63 | - [什么是虚拟内存?](#什么是虚拟内存) 64 | - [颠簸(抖动)](#颠簸抖动) 65 | - [进程调度算法](#进程调度算法) 66 | - [经典进程同步问题](#经典进程同步问题) 67 | - [进程的状态转换](#进程的状态转换) 68 | - [进程间通信方式](#进程间通信方式) 69 | - [僵尸进程和孤儿进程](#僵尸进程和孤儿进程httpswwwcnblogscomankerp3271773html3777880) 70 | - [fork进程](#fork进程httpsblogcsdnnetu013851082articledetails76902046) 71 | - [Socket编程](#socket编程) 72 | - [Linux的五种IO模型](#linux的五种io模型) 73 | - [IO 多路复用模型](#io-多路复用模型) 74 | - [select](#select) 75 | - [poll](#poll) 76 | - [epoll](#epoll) 77 | - [select、poll、epoll区别](#select-poll-epoll区别) 78 | - [谈谈死锁](#谈谈死锁) 79 | - [死锁的概念](#死锁的概念) 80 | - [死锁产生的原因](#死锁产生的原因) 81 | - [死锁产生的四个必要条件](#死锁产生的四个必要条件) 82 | - [解决死锁的基本方法](#解决死锁的基本方法) 83 | - [计算机网络篇](#计算机网络篇) 84 | - [从URL输入到页面展现到底发生什么?](#从url输入到页面展现到底发生什么httpsmpweixinqqcoms__bizmzg5oda5ntm1mwmid2247483803idx1sn460597ae3bd3ec10ad3426b7db605074chksmc066800df711091b2b6e005a922fff4a6a8e4b7930928a1c60fc0ac657c3ea5061cf1b51563dmpshare1scene1srcidrd) 85 | - [ARP 协议工作原理](#arp-协议工作原理) 86 | - [TCP 三次握手](#tcp-三次握手) 87 | - [TCP 四次挥手](#tcp-四次挥手) 88 | - [TCP 怎样保证可靠性](#tcp-怎样保证可靠性) 89 | - [流量控制和拥塞控制](#流量控制和拥塞控制) 90 | - [TCP 与 UDP 的区别](#tcp-与-udp-的区别) 91 | - [HTTP协议](#http协议) 92 | - [HTTP 和 HTTPS 的区别](#http-和-https-的区别) 93 | - [URL详解](#url详解) 94 | - [HTTP 常见方法(GET/PUT)](#http-常见方法getput) 95 | - [安全性和幂等性](#安全性和幂等性) 96 | - [GET和POST 区别](#get和post-区别) 97 | - [常见 HTTP 状态码](#常见-http-状态码) 98 | - [HTTP 长连接与短连接](#http-长连接与短连接) 99 | - [长连接](#长连接) 100 | - [短连接](#短连接) 101 | - [cookie 和 session 的区别](#cookie-和-session-的区别) 102 | - [JSON Web Token](#json-web-token) 103 | - [IP 地址的分类](#ip-地址的分类) 104 | - [OSI七层参考模型](#osi七层参考模型) 105 | - [数据库篇](#数据库篇) 106 | - [数据库系统概念](#数据库系统概念) 107 | - [数据库范式](#数据库范式) 108 | - [视图](#视图) 109 | - [游标](#游标) 110 | - [触发器](#触发器) 111 | - [存储过程](#存储过程) 112 | - [MySQL 基础问题](#mysql-基础问题) 113 | - [常用 SQL 语句](#常用-sql-语句) 114 | - [in与not in,exists与not exists的区别](#in与not-inexists与not-exists的区别) 115 | - [drop、delete 与 truncate 的区别](#drop-delete-与-truncate-的区别) 116 | - [数据库事务](#数据库事务) 117 | - [事务的特征(ACID)](#事务的特征acid) 118 | - [事务并发带来的问题](#事务并发带来的问题) 119 | - [事务的隔离级别](#事务的隔离级别) 120 | - [MySQL 的事务支持](#mysql-的事务支持) 121 | - [数据库索引](#数据库索引) 122 | - [索引的优点和缺点](#索引的优点和缺点) 123 | - [B 树和 B+ 树](#b-树和-b-树) 124 | - [索引的分类](#索引的分类) 125 | - [MySQL 中的锁](#mysql-中的锁) 126 | - [乐观锁和悲观锁](#乐观锁和悲观锁) 127 | - [MySQL 行级锁](#mysql-行级锁) 128 | - [存储引擎 MyISAM 和 InnoDB 区别](#存储引擎-myisam-和-innodb-区别) 129 | - [实现MVCC](#实现mvcchttpsliuzhengyanggithubio20170418innodb-mvcc) 130 | - [实践中如何优化 MySQL](#实践中如何优化-mysql) 131 | - [SQL 语句的优化](#sql-语句的优化) 132 | - [索引的优化](#索引的优化) 133 | - [数据表结构的优化](#数据表结构的优化) 134 | - [Redis](#redis) 135 | - [Redis数据类型](#redis数据类型) 136 | - [Redis的应用场景](#redis的应用场景) 137 | - [Redis 持久化](#redis-持久化) 138 | - [缓存使用过程中的坑](#缓存使用过程中的坑) 139 | - [其他问题](#其他问题) 140 | - [最佳实践](#最佳实践) 141 | - [编码规范](#编码规范) 142 | - [正确的流程开发](#正确的流程开发httpswwwzhihucomquestion300762444answer529335326appzhihuliteutm_sourcezhihusignmtu2mda2nzmzmzyznwutm_mediumsocialutm_oi758575502888824800invite_code7jv2ek) 143 | - [单元测试](#单元测试) 144 | - [CI/CD工具篇](#cicd工具篇) 145 | - [Git飞行规则(Flight Rules)](#git飞行规则flight-ruleshttpsgithubcomk88hudsongit-flight-rulesblobmasterreadme_zh-cnmd) 146 | - [Docker 篇](#docker-篇) 147 | - [Web 扩展](#web-扩展) 148 | - [Web安全篇](#web安全篇) 149 | - [DDos 攻击](#ddos-攻击) 150 | - [XSS 攻击](#xss-攻击) 151 | - [SQL 注入攻击](#sql-注入攻击) 152 | - [加密](#加密) 153 | - [Nginx篇](#nginx篇) 154 | - [Vue篇](#vue篇) 155 | - [Django篇](#django篇) 156 | - [WSGI、uwsgi、uWSGI 区别](#wsgi-uwsgi-uwsgi-区别) 157 | - [正则表达式篇](#正则表达式篇) 158 | - [其他](#其他) 159 | 160 | 161 | 162 | ### 写在前面 163 | * 这些都是平时逛技术社区时看到的我认为不错的内容,顺手记录在印象笔记中,所以分类总结下来。能够找到来源出处的都有所注明,如果涉及侵权可以联系我删除或进行来源标注。 164 | * 本来我认为将自己使用过的技术掌握扎实就行,技术面试靠“面试总结”有点死记硬背应付的赶脚,但实习时的 mentor 告诉我针对面试常见问题进行记忆也是为了提升面试效果,再说技术广度如此大,要是都用过会被严重怀疑仅仅是个码农,不能从底层发现问题,只会照着 api 和框架 code,这也是为自己写这份总结的原因。 165 | * 总之面试是一场持久战,要软硬兼备。硬性条件:面试官对候选人的技术广度、技术深度、基础功底、系统设计、项目经验几个角度来进行的考察,软性条件:一些软素质比如说学习能力、表达能力、沟通能力都比较重要。哦,对了,绝对不能忽略运气成分😄 166 | #### 公司选择 167 | * 写 Python 可供选择的公司本来不多,但中小公司还是很多,但找到小而美的就很少,说说个人心中“小而美”的标准😃: 168 | - 团队互补能力强,虽小但是能够cover的业务能力广; 169 | - 有明锐的嗅觉和创新能力,并且能够专注于某个行业领域或者发展方向; 170 | - 快或缓,有清晰的发展规划; 171 | - 最后一条,商业模式应该在很小的规模下就能验证,据说这句话是雷军说的 172 | - 多抓鱼,长亭科技,待补充。。。(欢迎提交issue或推荐公司😂) 173 | #### 简历投递 174 | * 岗位对上三条及以上就可以投了,要不要实习都可以尝试一下!😂 175 | * 自己设计一张简历投递状态记录表,投递中 -> 笔试-> 一面 -> 二面-> hr面 -> 面试结果(被拒绝/get offer) 176 | * 找正确的投递渠道,拉勾上找到几页满足岗位要求的公司,然后去官网,一般都有加入我们,没有的就不用考虑啦(官网连这些基本都做不到,还招啥人,可以看看一面数据,连招过的实习生都有展示),当然还有没有官网的公司😂,不想放过的话,去V2EX、知乎搜关键字会发现惊喜。 177 | * thank you letter!面试结束后发感谢信! 178 | #### 面试策略 179 | * 项目回答 180 | - 任何一个公司的面试,都一定会涉及到作为一个工程师最核心的价值——解决问题的能力,具体来说就是你做过的项目,这块是面试准备时的重中之重,应该作为最高优先级来对待。面试官反复的追问项目的各个地方的技术实现细节,就想看看有没有哪个地方是有一定的技术难度的,可以体现出这个候选人的一些项目上的亮点,但是如果说来说去总是从业务的角度去说,就说有哪些子系统组成,如何交互的,没有技术含量的东西在项目里体现出来,就会有点本末倒置了。 181 | - 自己至少应该反复思考,你目前负责的系统应该引入什么样的技术架构,采用何种技术方案,才能抗住各种冲击。同时搜一下国内大型互联网公司的技术架构,他们使用了哪些技术,对于某个技术难点采用了什么技术方案。可以对面试官阐述一下你对这个项目一些问题的思考,这样设计可以解决什么技术问题,有没有更好的方案选择。 182 | * 投递策略 183 | - 第一阶段练手:当你觉得自己充分的准备好了面试之后,你应该先找几个感觉对自己会有一定技术挑战的公司,但是并不是自己特别意向想要去的公司去投递简历面试一下,这个阶段一般会暴露出来很多问题。只要面试官的技术实力比你强,那么在面试的过程中,一定会问到你一些问题,是你之前没注意以及没准备好的。此时你很可能会发现,刚开始面试的头三四家公司,每家公司聊的都不太顺畅,每家公司总有那么几个问题没回答好,然后都没拿到offer。但是这个阶段的好处是,你发现了自己很多薄弱环节,这个时候你应该尽快通过上网查资料的方式填补好自己对一些薄弱问题的弱项,然后迅速总结,内化为自己的语言,并且能落地到纸上画图实现。 184 | - 第二个阶段冲刺:经过了第一个阶段的被虐之后,每个人的面试能力都会加强很多,而且找到了一些面试的感觉,对面试的节奏、对答都有了更好的把控。这个时候就可以尝试去冲刺一下心仪的大厂了,在这个阶段里,需要全力以赴去面试。 185 | ### 技术部分 186 | #### 语言特性篇 187 | * 学习书籍推荐:[《流畅的Python》](https://book.douban.com/subject/27028517/) 188 | ##### 谈谈 Python 和其他语言的区别 189 | * Python是强类型、动态类型的语言 190 | ##### Python 2和3的区别 191 | * python2中,print是个特殊语句,python3中print是函数。 192 | * python中有两种字符类型:字节字符串和文本字符串。 193 | 194 | | 版本 | python2| python3 | 195 | | -----| ---- | ---- | 196 | | 字节字符串 | str | bytes | 197 | | 文本字符串 | Unicode | str | 198 | * python2中默认的字符串类型默认是ASCII,python3中默认的字符串类型是Unicode。 199 | * python2中除法`/`的结果是整型,python3中是浮点类型。 200 | * python2中默认类是旧式类,需要显式继承新式类(object)来创建新式类。python3中完全移除旧式类,所有类都是新式类,但仍可显式继承object类。 201 | * 兼容问题:six库,2to3,__future__包 202 | ##### 闭包 203 | * 闭包指延伸(延伸的意思是seriers在average函数用,但在average_nums中定义的)了作用域的函数,访问定义体之外定义的非全局变量。创建一个闭包必须满足以下几点: 204 | - 必须有一个内嵌函数 205 | - 内嵌函数必须引用外部函数中的变量 206 | - 外部函数的返回值必须是内嵌函数 207 | ```python 208 | def average_num(): 209 | series=[] 210 | def average(num): 211 | # series自由变量,未在本地作用域绑定 212 | series.append(num) 213 | return sum(series)/len(series) 214 | return average 215 | aver=average_num() 216 | ``` 217 | * nonlocal 关键字将变量count,total 手动标记为自由变量 218 | ```python 219 | def average_num(): 220 | count=0 221 | total=0 222 | def average(num): 223 | nonlocal total,count 224 | total+=num 225 | count+=1 226 | return total/count 227 | return average 228 | aver=average_num() 229 | print(aver(10)) 230 | ``` 231 | * 顺便提下 global 关键字,Python 默认函数定义体中赋值的变量是局部变量,所以要在函数中使用全局变量,须用global声明。(下面这句去掉global就会报错,`local variable 'b' referenced before assignment`) 232 | ```python 233 | b=9 234 | def test(a): 235 | print(a) 236 | global b 237 | print(b) 238 | b=6 239 | test(3) 240 | ``` 241 | ##### 装饰器 242 | * 主要参考 243 | * [理解 Python 装饰器看这一篇就够了](https://foofish.net/python-decorator.html) 244 | * 闭包的一个重要应用就是装饰器,函数装饰器在导入模块时立即执行,被装饰的函数只在明确调用时执行。装饰器采用语法糖,使用方便,用途多样,既可以自定义在装饰器中指定日志级别,也可以使用官方提供的预激协程装饰器等等。 245 | ``` python 246 | # 手动实现一个带参数的装饰器 247 | import time 248 | 249 | def rate_limit(max_requests, time_window): 250 | def decorator(func): 251 | requests = [] 252 | 253 | def wrapper(*args, **kwargs): 254 | now = time.time() 255 | # 移除超出时间窗口的请求记录 256 | while requests and now - requests[0] > time_window: 257 | requests.pop(0) 258 | if len(requests) >= max_requests: 259 | print("Rate limit exceeded. Please try again later.") 260 | return 261 | requests.append(now) 262 | return func(*args, **kwargs) 263 | 264 | return wrapper 265 | 266 | return decorator 267 | 268 | # 限制每5秒内只能有2次请求 269 | @rate_limit(max_requests=2, time_window=5) 270 | def process_request(request): 271 | print("Processing request:", request) 272 | 273 | # 测试 274 | for i in range(5): 275 | process_request(i) 276 | time.sleep(1) 277 | ``` 278 | ##### 谈谈GC 279 | * 主要参考 280 | * [Python 进阶:浅析「垃圾回收机制」(上篇)](https://hackpython.com/blog/2019/07/05/Python%E8%BF%9B%E9%98%B6%EF%BC%9A%E6%B5%85%E6%9E%90%E3%80%8C%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%E3%80%8D-%E4%B8%8A%E7%AF%87/) 281 | * 主要使用**引用计数**进行垃圾回收 282 | * 通过**标记-清理**解决容器对象产生循环引用的问题 283 | * 通过**分代回收**以空间换时间的方式来提高垃圾回收的效率 284 | ##### GIL的理解 285 | * 主要参考 286 | * [python中的GIL详解](https://www.cnblogs.com/SuKiWX/p/8804974.html) 287 | * GIL是一个防止多线程并发执行机器码的Mutex:cpython解释器的内存管理不是线程安全的,保护多线程情况下对python对象的访问,cpython使用简单的锁机制避免多个线程同时执行字节码。 288 | * 缺陷 :限制程序的多核执行,同一时间只能有一个线程执行字节码,CPU密集型程序(大量时间花在计算上)难以利用多核优势;但是IO期间会释放GIL,对IO密集型程序(大量时间花在网络传输上,资源调度)影响小。 289 | * 规避影响策略:CPU密集型使用多进程+进程池,IO密集型采用多线程/协程的策略;cython扩展(将python转换为c代码) 290 | * GIL释放:Python会计算当前已执行的微代码数量,达到一定阈值后强制释放GIL;系统分配的时间片用完释放;IO操作时释放。 291 | * 一个操作如果是一个字节码指令可以完成就是原子的,原子操作是可以保证线程安全的,有了GIL之后仍然要关注线程安全,必要时候需人为加锁。 292 | ```python 293 | import threading 294 | lock=threading.Lock() 295 | n=[0] 296 | def demo(): 297 | #with lock: 298 | n[0]=n[0]+1 299 | threads=[] 300 | for i in range(5000): 301 | t=threading.Thread(target=demo) 302 | threads.append(t) 303 | for t in threads: 304 | t.start() 305 | print(n) 306 | ``` 307 | * 剖析程序性能:内置profile/cprofile工具 308 | ##### Python传参 309 | * 主要参考 310 | - [Python传入参数的几种方法](https://blog.csdn.net/abc_12366/article/details/79627263) 311 | * Python中参数传递方式:位置传递、默认参数、可变参数、关键字参数、命名关键字参数 312 | * Python 函数参数传递实际叫传对象(call by object),可以把不可变对象传参理解为传值,可变对象参数理解为传引用。 313 | ##### 深拷贝和浅拷贝 314 | * 浅拷贝(copy)拷贝父对象,不会拷贝对象的内部的子对象。深拷贝(deepcopy)完全拷贝了父对象及其子对象。 315 | ```python 316 | import copy 317 | a = [1, 2, 3, 4, ['a', 'b']] #原始对象 318 | b = a #赋值,传对象的引用 319 | c = copy.copy(a) #对象拷贝,浅拷贝 320 | d = copy.deepcopy(a) #对象拷贝,深拷贝 321 | a.append(5) #修改对象a 322 | a[4].append('c') #修改对象a中的['a', 'b']数组对象 323 | ``` 324 | ##### 鸭子类型 325 | * 鸭子类型就是:如果走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子(If it walks like a duck and quacks like a duck, it must be a duck)。鸭子类型是编程语言中动态类型语言中的一种设计风格,一个对象的特征不是由父类决定,而是通过对象的方法决定的。(重点关注接口,不关注对象) 326 | ```python 327 | class Dog(Animal): 328 | def run(self): 329 | print('Dog is running...') 330 | 331 | class Cat(Animal): 332 | def run(self): 333 | print('Cat is running...') 334 | ``` 335 | ##### 猴子补丁 336 | * Monkey patch就是在运行时对已有的代码进行修改,达到hot patch的目的。运行时动态替换模块的方法,运行时动态增加模块的方法(慎用) 337 | ```python 338 | class Foo(object): 339 | def bar(self): 340 | print('Foo.bar') 341 | def bar(self): 342 | print('Modified bar') 343 | Foo().bar() 344 | Foo.bar = bar 345 | Foo().bar() 346 | ``` 347 | ##### Python中的作用域 348 | * Python的作用域一共有4种: L (Local) 局部作用域,E (Enclosing) 闭包函数外的函数中,G (Global) 全局作用域,B (Built-in) 内建作用域。以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。 349 | ##### 函数式编程 350 | ###### lambda函数 351 | * lambda 定义了一个匿名函数,lambda 并不会带来程序运行效率的提高,只会使代码更简洁。使用lambda内不要包含循环,如果有,定义函数来完成,使代码获得可重用性和更好的可读性。lambda 是为了减少单行函数的定义而存在的。 352 | ```python 353 | list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) 354 | # [1, 4, 9, 16, 25, 36, 49, 64, 81] 355 | ``` 356 | ###### map函数 357 | * map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。 358 | ###### reduce函数 359 | * reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是: 360 | `reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)` 361 | ###### filter函数 362 | * filter()函数用于过滤序列。filter()接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。 363 | ```python 364 | def is_odd(n): 365 | return n % 2 == 1 366 | list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) 367 | # 结果: [1, 5, 9, 15] 368 | ``` 369 | ##### 迭代器和生成器 370 | * 主要参考 371 | - [Python中的可迭代对象、迭代器和生成器的异同点](https://blog.csdn.net/SL_World/article/details/86507872) 372 | ![relationships.png](https://i.loli.net/2019/08/25/a9HQ6d1vxPNzeSl.png) 373 | * 可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。 374 | * 迭代器有一种具体的迭代器类型,它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果(不同于容器)。任何实现了__iter__和__next__()方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。迭代器每次调用next()方法的时候做两件事:为下一次调用next()方法修改状态,生成当前调用的返回结果。 375 | * 生成器其实是一种特殊的迭代器,这种迭代器更加优雅,它不需要写__iter__()和__next__()方法了,只需要一个yiled关键字, 生成器一定是迭代器(反之不成立)。 376 | ##### 协程 377 | 协程(Coroutines)是一种轻量级的并发编程方式,用于在单线程中实现多个函数的并发执行。协程允许在函数执行过程中暂停,并在之后恢复执行,从而实现非抢占式的多任务处理。在Python中,协程可以使用`async`和`await`关键字来定义和管理。 378 | 379 | 适用场景: 380 | 381 | 1. **高并发网络应用:** 协程适用于高并发网络应用,如Web服务器。通过使用协程,可以在一个线程内处理多个客户端请求,从而减少线程切换和线程开销,提高并发处理能力。 382 | 383 | 2. **异步I/O操作:** 协程适用于需要大量I/O操作的情况,如文件读写、数据库查询等。协程可以在等待I/O操作时释放控制权,允许其他协程执行,从而提高程序的吞吐量。 384 | 385 | 3. **事件驱动编程:** 协程适用于事件驱动的编程模型,如图形界面应用、游戏引擎等。协程可以处理事件的回调逻辑,而不需要创建大量的回调函数。 386 | 387 | 4. **批量处理任务:** 协程适用于批量处理任务的情况,如数据处理、爬虫等。协程可以并发执行多个任务,从而加速任务的处理过程。 388 | 389 | 为何还需要协程: 390 | 391 | 尽管线程提供了一种并发处理方式,但线程存在一些问题,如线程切换开销高、线程安全等。相比之下,协程有以下优势: 392 | 393 | 1. **轻量级:** 协程比线程更加轻量级,占用的内存和资源更少。 394 | 395 | 2. **无需线程锁:** 在多线程环境下,需要考虑线程安全和锁的问题。而协程在单线程中执行,不需要额外的线程同步机制。 396 | 397 | 3. **更容易管理:** 由于协程在单线程中执行,因此避免了多线程中的竞争条件和死锁问题,使得并发编程更容易管理和调试。 398 | 399 | 4. **高效的I/O操作:** 协程可以充分利用I/O操作的等待时间,从而实现高效的非阻塞I/O操作。 400 | 401 | **示例:网络爬虫程序** 402 | 403 | 1. **含义:** 协程是一种非抢占式的并发编程方式,可以在一个线程中实现多个函数的并发执行。在网络爬虫中,协程允许你同时发送多个HTTP请求并处理响应。 404 | 405 | 2. **适用场景:** 网络爬虫程序需要从多个网页上获取数据,而网页的下载和处理是I/O密集型操作。协程可以在等待一个网页下载时切换到另一个协程,从而充分利用等待时间。 406 | 407 | 3. **为何需要协程:** 尽管可以使用多线程实现并发下载,但线程切换和线程安全的问题可能会导致复杂的编程和调试。而协程可以在单线程中实现高并发下载,避免线程切换开销和线程安全问题。 408 | 409 | ```python 410 | import asyncio 411 | import aiohttp 412 | 413 | async def fetch_url(url): 414 | async with aiohttp.ClientSession() as session: 415 | async with session.get(url) as response: 416 | return await response.text() 417 | 418 | async def main(): 419 | urls = ['https://example.com', 'https://google.com', 'https://github.com'] 420 | tasks = [fetch_url(url) for url in urls] 421 | responses = await asyncio.gather(*tasks) 422 | 423 | for url, response in zip(urls, responses): 424 | print(f"Response from {url}: {len(response)} bytes") 425 | 426 | if __name__ == "__main__": 427 | asyncio.run(main()) 428 | ``` 429 | 在上面的示例中,`fetch_url`函数使用`aiohttp`库发送异步HTTP请求,而`main`函数创建了多个协程来并发执行。每个协程在等待HTTP响应时会切换到其他协程,从而实现了高并发的网络爬虫。 430 | ##### Python 面对对象编程 431 | ###### 封装 432 | * 将内容封装到某处 433 | * 从某处调用被封装的内容 434 | ```python 435 | class Person: 436 | def __init__(self,name,age): 437 | self.name=name 438 | self.age=age 439 | 440 | # 初始化一个实例时调用__init__方法 441 | tim=Person('Tim',18) 442 | # 将Bob,16分布封装到bob的name和age属性中 443 | bob=Person('Bob',16) 444 | ``` 445 | ###### 继承 446 | * 对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。Python的类如果继承了多个类,那么其寻找方法的方式(即MRO,method resolution order (方法解析顺序),在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了)有两种,可以分别粗略理解为:深度优先和广度优先。具体实现细节可以看这篇[python多重继承C3算法](https://blog.csdn.net/fmblzf/article/details/52512145) 447 | * 当类是经典类时,多继承情况下,会按照深度优先方式查找,Python 2.x中默认都是经典类,只有显式继承了object才是新式类。 448 | * 当类是新式类时,多继承情况下,会按照广度优先方式查找Python 3.x中默认都是新式类,不必显式的继承object。 449 | * 继承中super的调用顺序是与MRO-C3的类方法查找顺序一样的,super作用如下: 450 | - 如果子类继承父类不做初始化,那么会自动继承父类属性。 451 | - 如果子类继承父类做了初始化,且不调用super初始化父类构造函数,那么子类不会自动继承父类的属性。 452 | - 如果子类继承父类做了初始化,且调用了super初始化了父类的构造函数,那么子类也会继承父类的属性。 453 | ```python 454 | class A: 455 | def __init__(self): 456 | print('A') 457 | class B(A): 458 | def __init__(self): 459 | print('B') 460 | super().__init__() 461 | class C(A): 462 | def __init__(self): 463 | print('C') 464 | super().__init__() 465 | class D(B, C): 466 | def __init__(self): 467 | print('D') 468 | super().__init__() 469 | ``` 470 | ###### 多态 471 | * Pyhon不支持多态并且也用不到多态,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。 472 | ###### 类成员 473 | ![leichengyuan.png](https://i.loli.net/2019/08/16/i4GtE972ScAjQhJ.png) 474 | * 字段 475 | * 静态字段在内存中只保存一份 476 | * 普通字段在每个实例对象中都要保存一份 477 | * 应用场景: 通过类创建实例对象时,如果每个实例对象都具有相同的字段,那么就使用静态字段 478 | ```python 479 | class Province: 480 | # 静态字段 481 | country = 'China' 482 | def __init__(self,name): 483 | # 普通字段 484 | self.name = name 485 | obj1 =Province('BeiJing') 486 | print(obj1.name) 487 | print(Province.country) 488 | ``` 489 | * 方法 490 | - 普通实例方法:由实例对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的实例对象赋值给self; 491 | - 类方法:由类调用;至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls; 492 | - 静态方法:由类调用;无默认参数; 493 | ```python 494 | class Foo: 495 | def __init__(self,name): 496 | self.name =name 497 | # 定义普通实例方法,至少一个self参数 498 | def ord_func(self): 499 | print(self.name) 500 | # 定义类方法,至少一个cls参数 501 | @classmethod 502 | def class_func(cls): 503 | print('类方法') 504 | # 定义静态方法,无默认参数 505 | @staticmethod 506 | def static_fun(): 507 | print('静态方法') 508 | f = Foo('ord_func') 509 | f.ord_func() 510 | Foo.class_func() 511 | Foo.static_fun() 512 | ``` 513 | * 属性 514 | - 定义时,在普通方法的基础上添加@property装饰器,属性仅有一个self参数,调用时无需括号。@property装饰器可以实现其他语言所拥有的getter,setter和deleter的功能(例如实现获取,设置,删除隐藏的属性);通过@property装饰器可以对属性的取值和赋值加以控制,提高代码的稳定性。 515 | ```python 516 | class Goods: 517 | # 查看属性值 518 | @property 519 | def price(self): 520 | print('@property') 521 | # 修改、设置属性 522 | @price.setter 523 | def price(self, value): 524 | print('@price.setter') 525 | # 删除属性 526 | @price.deleter 527 | def price(self): 528 | print('@price.deleter') 529 | obj = Goods() 530 | # 自动执行 @property 修饰的 price 方法,并获取方法的返回值 531 | obj.price 532 | # 自动执行 @price.setter 修饰的 price 方法,并将2000赋值给方法的参数 533 | obj.price = 2000 534 | # 自动执行 @price.deleter 修饰的 price 方法 535 | del obj.price 536 | ``` 537 | ###### 类成员的修饰符 538 | * _xxx 表示这是一个保护成员(属性或者方法),它不能用from module import * 导入,其他方面和公有一样访问; 539 | * __xxx 这表示这是一个私有成员,它无法直接像公有成员一样随便访问,但可以通过`实例对象名._类名__xxx`这样的方式可以访问; 540 | * __xxx__表示这是一个特殊成员,用来区别其他用户自定义的命名以防冲突,就是例如`__init__()、 __del__()` 这些特殊方法 541 | ###### 类的特殊成员 542 | ![魔法函数.png](https://i.loli.net/2019/08/16/aeglES6LYfQIz3U.png) 543 | 1. __doc __输出类的描述信息 544 | 2. __module __输出当前操作对象所在模块 545 | 3. __class __输出当前操作对象的类 546 | 4. __init __构造方法,通过类创建实例对象时,自动触发执行 547 | 5. __del __析构方法,当对象在内存中被释放时,自动触发执行,此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。 548 | 6. __call__对象后面加括号,触发执行,构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()() 549 | ```python 550 | class Foo: 551 | def __init__(self): 552 | pass 553 | def __call__(self,*args,**kwargs): 554 | print('__call__') 555 | # 执行 __init__ 556 | obj6 = Foo() 557 | # 执行 __call__ 558 | obj6() 559 | ``` 560 | 7. __dict__类或对象中的所有成员 561 | ```python 562 | class Province: 563 | country = 'China' 564 | def __init__(self, name, count): 565 | self.name = name 566 | self.count = count 567 | def func(self, *args, **kwargs): 568 | print('func') 569 | # 获取类的成员,即:静态字段、方法、 570 | # 输出:{'__module__': '__main__', 'country': 'China', '__init__': , 571 | # 'func': , '__dict__': , 572 | # '__weakref__': , '__doc__': None} 573 | print(Province.__dict__) 574 | # 输出:{'name': 'HeBei', 'count': 1000} 575 | obj1 = Province('HeBei',1000) 576 | print(obj1.__dict__) 577 | obj2 = Province('HeNan', 3) 578 | # 输出:{'name': 'HeNan', 'count': 3} 579 | print(obj2.__dict__) 580 | ``` 581 | 8. __str__如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值。 582 | ```python 583 | class Foo: 584 | def __init__(self): 585 | pass 586 | def __str__(self): 587 | return '__str__' 588 | obj=Foo() 589 | print(obj) 590 | ``` 591 | 9. `__getitem__、__setitem__、__delitem__`用于索引操作,如字典。以上分别表示获取、设置、删除数据。 592 | 9. `__getslice__、__setslice__、__delslice__`该三个方法用于分片操作,如列表。 593 | 10. __iter__用于迭代器,之所以列表、字典、元组可以进行for循环。 594 | 11. `__new__和__init__` 595 | `__new__`是一个静态方法,而`__init__`是一个实例方法,`__new__`方法会返回一个创建的实例,而`__init__`什么都不返回,当创建一个新实例时调用`__new__`,初始化一个实例时用`__init__`。 596 | ##### [Python自省指南](https://www.ibm.com/developerworks/cn/linux/l-pyint/index.html) 597 | * 自省(Introspection)指运行时判断一个对象类型的能力,常见type,id,isinstance 等获取对象类型信息或 ispect 模块中的函数获取详细信息。 598 | ##### [Python 中的元编程](https://www.ibm.com/developerworks/cn/analytics/library/ba-metaprogramming-python/index.html) 599 | * 元类是创建类的类,元类允许我们控制类的生成(修改类的属性等),用type来定义元类,元类最常见的使用场景是 ORM 框架。 600 | ```python 601 | # 通过元编程实现类自定义属性大写 602 | class Base(type): 603 | def __new__(cls,name,bases,attrs): 604 | upper_attrs={} 605 | for k,v in attrs.items(): 606 | if not k.startswith("__"): 607 | upper_attrs[k.upper()]=v 608 | else: 609 | upper_attrs[k]=v 610 | return type.__new__(cls,name,bases,upper_attrs) 611 | class Foo(metaclass=Base): 612 | foo=True 613 | def hello(self): 614 | print("world") 615 | def hello(): 616 | print("world") 617 | print(dir(Foo)) 618 | ``` 619 | ##### 设计模式 620 | ###### 单例模式 621 | * 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。(None就是单例) 622 | 1. metaclass 实现 623 | ```python 624 | class Singleton(type): 625 | _instances = {} 626 | def __call__(cls, *args, **kwargs): 627 | if cls not in cls._instances: 628 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 629 | return cls._instances[cls] 630 | 631 | class Foo(metaclass=Singleton): 632 | pass 633 | foo1=Foo() 634 | foo2=Foo() 635 | print(id(foo1)==id(foo2)) 636 | ``` 637 | 2. 函数装饰器实现 638 | ```python 639 | def singleton(cls): 640 | _instance = {} 641 | def wrapper(): 642 | if cls not in _instance: 643 | _instance[cls] = cls() 644 | return _instance[cls] 645 | return wrapper 646 | 647 | @singleton 648 | class Cls(object): 649 | def __init__(self): 650 | pass 651 | cls1 = Cls() 652 | cls2 = Cls() 653 | print(id(cls1) == id(cls2)) 654 | ``` 655 | 3. import 方法 656 | * Python 的模块就是天然的单例模式 657 | ###### [工厂模式](https://segmentfault.com/a/1190000013053013) 658 | * 工厂模式是说调用方可以通过调用一个简单函数就可以创建不同的对象。工厂模式一般包含工厂方法和抽象工厂两种模式。 659 | #### 数据结构与算法篇 660 | * 主要参考 661 | - [problem-solving-with-algorithms-and-data-structure-using-python 中文版](https://facert.gitbooks.io/python-data-structure-cn/) 662 | - 五分钟算法公众号 663 | * 常见数据结构[戳这里](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247485737&idx=1&sn=82fa3f103ef485a8ce28c5e1c5e012ea&chksm=fa0e66a8cd79efbeb88cb175fcc21a71964d975a47d3c87a8335506466937e811a066f8a1889&mpshare=1&scene=1&srcid=&sharer_sharetime=1564711256674&sharer_shareid=d8d88a642b42a12a54cd09298c974b8c#rd) 664 | ##### 排序算法 665 | * 快排,堆排,归并排序详细原理参考这篇[这或许是东半球分析十大排序算法最好的一篇文章](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247485556&idx=1&sn=344738dd74b211e091f8f3477bdf91ee&chksm=fa0e67f5cd79eee3139d4667f3b94fa9618067efc45a797b69b41105a7f313654d0e86949607&scene=21#wechat_redirect),下面我将用 Python 快速实现出来。话不多说,Show Me Code! 666 | * 冒泡排序 667 | ```python 668 | def bubble_sort(alist): 669 | for i in range(len(alist)-1): 670 | for j in range(len(alist)-1-i): 671 | if alist[j]>alist[j+1]: 672 | alist[j],alist[j+1]=alist[j+1],alist[j] 673 | alist=[54,26,93,17,77,31,44,55,20] 674 | bubble_sort(alist) 675 | print(alist) 676 | ``` 677 | * 选择排序 678 | ```python 679 | def select_sort(alist): 680 | for i in range(len(alist)-1): 681 | min_index=i 682 | for j in range(i+1,len(alist)): 683 | if alist[j](n ~ n^2,不稳定) 707 | def shell_sort(alist): 708 | gap=len(alist)//2 709 | while gap>=1: 710 | for i in range(gap,len(alist)): 711 | j=i 712 | while(j-gap)>=0: 713 | if alist[j]= end: 756 | return 757 | #基准 758 | mark = alist[start] 759 | left = start 760 | right = end 761 | while left < right: 762 | while leftmark: 763 | right-=1 764 | alist[left]=alist[right] 765 | while left ListNode: 781 | second = fist = head 782 | for i in range(n): # 第一个指针先走 n 步 783 | fist = fist.next 784 | 785 | if fist == None: # 如果现在第一个指针已经到头了,那么第一个结点就是要删除的结点。 786 | return second.next 787 | 788 | while fist.next: # 然后同时走,直到第一个指针走到头 789 | fist = fist.next 790 | second = second.next 791 | second.next = second.next.next # 删除相应的结点 792 | return head 793 | ``` 794 | * 有序链表合并 795 | ```python 796 | # leetcode 21 797 | # 解题思路:合并后的链表仍然是有序的,可以同时遍历两个链表, 798 | # 每次选取两个链表中较小值的节点,依次连接起来,就能得到最终的链表 799 | class Solution: 800 | def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: 801 | if not l1 or not l2: 802 | return l1 or l2 803 | head=cur=ListNode(0) 804 | while l1 and l2: 805 | if l1.val ListNode: 853 | flag=0 854 | root=n=ListNode(0) 855 | while l1 or l2 or flag: 856 | v1=v2=0 857 | if l1: 858 | v1=l1.val 859 | l1=l1.next 860 | if l2: 861 | v2=l2.val 862 | l2=l2.next 863 | flag,val=divmod(v1+v2+flag,10) 864 | n.next=ListNode(val) 865 | n=n.next 866 | return root.next 867 | ``` 868 | * 删除链表中节点,要求时间复杂度为O(1) 869 | ```python 870 | # leetcode 237 871 | class Solution: 872 | def deleteNode(self, node): 873 | """ 874 | :type node: ListNode 875 | :rtype: void Do not return anything, modify node in-place instead. 876 | """ 877 | next_node=node.next 878 | next_nextnode=next_node.next 879 | node.val=next_node.val 880 | node.next=next_nextnode 881 | ``` 882 | * 从尾到头打印链表 883 | ```python 884 | # 根据栈的特性 885 | class Solution: 886 | def printListFromTailToHead(self, listNode): 887 | if listNode is None: 888 | return [] 889 | sta=list() 890 | res=list() 891 | while listNode: 892 | sta.append(listNode.val) 893 | listNode=listNode.next 894 | while sta: 895 | res.append(sta.pop()) 896 | return res 897 | # 递归 898 | class Solution: 899 | def printListFromTailToHead(self, listNode): 900 | if listNode is None: 901 | return [] 902 | return self.printListFromTailToHead(listNode.next)+[listNode.val] 903 | ``` 904 | * 反转链表 905 | ```python 906 | # leetcode 206 907 | class Solution: 908 | def reverseList(self, head: ListNode) -> ListNode: 909 | pre=None 910 | # 不断取出和向后移动头节点,并将头节点连接到新头节点后面 911 | while head: 912 | next_node=head.next 913 | head.next=pre 914 | pre=head 915 | head=next_node 916 | return pre 917 | 918 | ``` 919 | * LRU缓存机制 920 | ```python 921 | # leetcode 146: 922 | # 字典(哈希)+双端链表 923 | class Node(object): 924 | def __init__(self, key, val): 925 | self.key = key 926 | self.val = val 927 | self.next = None 928 | self.prev = None 929 | 930 | class LRUCache(object): 931 | 932 | def __init__(self, capacity): 933 | self.dic = {} 934 | self.capacity = capacity 935 | self.dummy_head = Node(0, 0) 936 | self.dummy_tail = Node(0, 0) 937 | self.dummy_head.next = self.dummy_tail 938 | self.dummy_tail.prev = self.dummy_head 939 | 940 | def get(self, key): 941 | if key not in self.dic: 942 | return -1 943 | node = self.dic[key] 944 | self.remove(node) 945 | self.append(node) 946 | return node.val 947 | 948 | def put(self, key, value): 949 | if key in self.dic: 950 | self.remove(self.dic[key]) 951 | node = Node(key, value) 952 | self.append(node) 953 | self.dic[key] = node 954 | 955 | if len(self.dic) > self.capacity: 956 | head = self.dummy_head.next 957 | self.remove(head) 958 | del self.dic[head.key] 959 | 960 | def append(self, node): 961 | tail = self.dummy_tail.prev 962 | tail.next = node 963 | node.prev = tail 964 | self.dummy_tail.prev = node 965 | node.next = self.dummy_tail 966 | 967 | def remove(self, node): 968 | prev = node.prev 969 | next = node.next 970 | prev.next = next 971 | next.prev = prev 972 | ``` 973 | ##### 栈和队列算法 974 | * [以下几个算法原理详解](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484846&idx=2&sn=e508da06e9f7a0b3d00db5415d7ce622&chksm=fa0e6a2fcd79e3397fe8083f9493ae639f47c9448ac2a0026026494d098c47ecc6e08f1f8e28&scene=21#wechat_redirect) 975 | * 有效的括号 976 | ```python 977 | # leetcode 20 978 | class Solution: 979 | def isValid(self, s: str) -> bool: 980 | chars={'(':')','[':']','{':'}'} 981 | stack=[] 982 | for i in s: 983 | if i in chars: 984 | stack.append(i) 985 | else: 986 | if not stack or chars[stack.pop()]!=i: 987 | return False 988 | return not stack 989 | ``` 990 | * 用两个栈实现队列 991 | ```python 992 | # leetcode 232: 一个栈作为缓存区 993 | class MyQueue: 994 | 995 | def __init__(self): 996 | """ 997 | Initialize your data structure here. 998 | """ 999 | self.stack1=[] 1000 | self.stack2=[] 1001 | 1002 | 1003 | def push(self, x: int) -> None: 1004 | """ 1005 | Push element x to the back of queue. 1006 | """ 1007 | self.stack1.append(x) 1008 | 1009 | 1010 | def pop(self) -> int: 1011 | """ 1012 | Removes the element from in front of queue and returns that element. 1013 | """ 1014 | if len(self.stack2)==0: 1015 | while(self.stack1): 1016 | self.stack2.append(self.stack1.pop()) 1017 | return self.stack2.pop() 1018 | 1019 | def peek(self) -> int: 1020 | """ 1021 | Get the front element. 1022 | """ 1023 | if len(self.stack2)==0: 1024 | while(self.stack1): 1025 | self.stack2.append(self.stack1.pop()) 1026 | return self.stack2[-1] 1027 | 1028 | def empty(self) -> bool: 1029 | """ 1030 | Returns whether the queue is empty. 1031 | """ 1032 | return not self.stack1 and not self.stack2 1033 | ``` 1034 | * 栈的压入、弹出序列 1035 | ```python 1036 | # leetcode 946:借用一个辅助的栈tmp,遍历压栈顺序 1037 | class Solution: 1038 | def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: 1039 | tmp=[] 1040 | while pushed: 1041 | tmp.append(pushed.pop(0)) 1042 | while tmp and tmp[-1]==popped[0]: 1043 | popped.pop(0) 1044 | tmp.pop() 1045 | return not tmp 1046 | ``` 1047 | * 包含 min 函数的栈 1048 | ```python 1049 | # leetcode 155:实际上就是实现最小栈(重点是连续弹出最小值时的问题) 1050 | class MinStack: 1051 | 1052 | def __init__(self): 1053 | """ 1054 | initialize your data structure here. 1055 | """ 1056 | self.stack=[] 1057 | self.minstack=[] 1058 | 1059 | def push(self, x: int) -> None: 1060 | self.stack.append(x) 1061 | if self.minstack: 1062 | self.minstack.append(x) 1063 | else: 1064 | if x None: 1070 | self.stack.pop() 1071 | self.minstack.pop() 1072 | 1073 | def top(self) -> int: 1074 | return self.stack[-1] 1075 | 1076 | def getMin(self) -> int: 1077 | return self.minstack[-1] 1078 | ``` 1079 | * 验证栈序列 1080 | ```python 1081 | # leetcode 946 1082 | class Solution: 1083 | def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: 1084 | tmp=[] 1085 | while pushed: 1086 | tmp.append(pushed.pop(0)) 1087 | while tmp and tmp[-1]==popped[0]: 1088 | popped.pop(0) 1089 | tmp.pop() 1090 | return not tmp 1091 | ``` 1092 | * 约瑟夫环(双端队列实现) 1093 | ```python 1094 | from pythonds.basic.queue import Queue 1095 | def hotPotato(namelist,num): 1096 | simple_queue = Queue() 1097 | for name in namelist: 1098 | simple_queue.enqueue(name) 1099 | while simple_queue.size()>1: 1100 | for i in range(num): 1101 | simple_queue.enqueue(simple_queue.dequeue()) 1102 | simple_queue.dequeue() 1103 | return simple_queue.dequeue() 1104 | 1105 | print(hotPotato(["Bill","David","Susan","Jane","Kent","Brad"],2)) 1106 | ``` 1107 | * 合并K个排序链表(我刚开始用的暴力解法2333) 1108 | * 这个[解析](https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode/)不错,我直接贴了其中采用最优队列的方式。 1109 | ```python 1110 | # leetcode 23 1111 | from Queue import PriorityQueue 1112 | class Solution(object): 1113 | def mergeKLists(self, lists): 1114 | head = point = ListNode(0) 1115 | q = PriorityQueue() 1116 | for l in lists: 1117 | if l: 1118 | q.put((l.val, l)) 1119 | while not q.empty(): 1120 | val, node = q.get() 1121 | point.next = ListNode(val) 1122 | point = point.next 1123 | node = node.next 1124 | if node: 1125 | q.put((node.val, node)) 1126 | return head.next 1127 | ``` 1128 | ##### 二叉树算法 1129 | ###### 四种遍历方式 1130 | * 二叉树的四种遍历方式分别是:前序、中序、后序和层次。它们的时间复杂度都是O(n),其中n是树中节点个数,因为每一个节点在递归的过程中,只访问了一次。 1131 | * 三种深度优先遍历方法的空间复杂度是O(h),其中h是二叉树的深度,额外空间是函数递归的调用栈产生的,而不是显示的额外变量。`空间复杂度,通常是指完成算法所用的辅助空间的复杂度,而不用管算法前置的空间复杂度。比如在树的遍历算法中,整棵树肯定要占O(n)的空间,n是树中节点的个数,这部分空间是“平凡”的,即肯定存在的,我们不讨论它。` 1132 | * 层次遍历的空间复杂度是O(w),其中w是二叉树的宽度(拥有最多节点的层的节点数)。 1133 | ![树的遍历.png](https://i.loli.net/2019/06/27/5d142fcec291f76765.png) 1134 | * 前序遍历 1135 | ```python 1136 | # 递归解法 1137 | class Solution: 1138 | def preorderTraversal(self, root: TreeNode) -> List[int]: 1139 | ret=[] 1140 | if root: 1141 | ret=ret+[root.val] 1142 | ret=ret+self.preorderTraversal(root.left) 1143 | ret=ret+self.preorderTraversal(root.right) 1144 | return ret 1145 | # 迭代算法思路:使用栈的思想,从根节点开始以此使用ret添加根节点值,stack添加右节点, 1146 | # curr=左节点,如果左节点为None,则获取其上一个右节点(一直输出根节点,添加其右节点, 1147 | # 遍历左节点,右节点的输出顺序为从下往上) 1148 | class Solution: 1149 | def preorderTraversal(self, root: TreeNode) -> List[int]: 1150 | ret=[] 1151 | if root==None: 1152 | return ret 1153 | stack=[] 1154 | curr=root 1155 | while curr or stack: 1156 | if curr: 1157 | ret.append(curr.val) 1158 | stack.append(curr.right) 1159 | curr=curr.left 1160 | else: 1161 | curr=stack.pop() 1162 | return ret 1163 | ``` 1164 | * 中序遍历 1165 | ```python 1166 | # 递归解法同上 1167 | # 迭代解法 1168 | class Solution: 1169 | def inorderTraversal(self, root: TreeNode) -> List[int]: 1170 | res=[] 1171 | if root==None: 1172 | return res 1173 | stack=[] #添加根节点 1174 | curr=root 1175 | while curr or stack: 1176 | if curr: 1177 | stack.append(curr) 1178 | curr=curr.left 1179 | else: 1180 | curr=stack.pop() 1181 | res.append(curr.val) 1182 | curr=curr.right 1183 | return res 1184 | ``` 1185 | * 后序遍历 1186 | ```python 1187 | # 递归解法同上 1188 | # 迭代算法思路:后序遍历方式为:左右中,将其进行反转 中右左, 1189 | # 那么我们可以实现一个中右左,其原理与前序遍历一样 1190 | class Solution: 1191 | def postorderTraversal(self, root: TreeNode) -> List[int]: 1192 | ret=[] 1193 | if root==None: 1194 | return ret 1195 | stack=[] 1196 | curr=root 1197 | while curr or stack: 1198 | if curr: 1199 | ret.append(curr.val) 1200 | stack.append(curr.left) 1201 | curr=curr.right 1202 | else: 1203 | curr=stack.pop() 1204 | return ret[::-1] 1205 | ``` 1206 | * 层次遍历 1207 | ``` 1208 | class Solution: 1209 | def levelOrder(self, root: TreeNode) -> List[List[int]]: 1210 | level=[root] 1211 | ret=[] 1212 | if root==None: 1213 | return [] 1214 | while level: 1215 | ret.append([i.val for i in level]) 1216 | level=[kid for node in level for kid in (node.left,node.right) if kid] 1217 | return ret 1218 | ``` 1219 | ###### 二叉树中的一些重要属性 1220 | * 二叉树的最小深度 1221 | ```python 1222 | class Solution: 1223 | def minDepth(self, root: TreeNode) -> int: 1224 | if not root: 1225 | return 0 1226 | if root.left==None or root.right==None: 1227 | return self.minDepth(root.left)+self.minDepth(root.right)+1 1228 | else: 1229 | return min(map(self.minDepth,(root.left,root.right)))+1 1230 | ``` 1231 | * 二叉树的层平均值 1232 | ```python 1233 | class Solution: 1234 | def averageOfLevels(self, root: TreeNode) -> List[float]: 1235 | average=[] 1236 | if root: 1237 | level=[root] 1238 | while level: 1239 | average.append(sum(i.val for i in level)/len(level)) 1240 | level=[kid for node in level for kid in (node.left,node.right) if kid] 1241 | return average 1242 | ``` 1243 | * 二叉树的直径 1244 | * 采用分治和递归的思想:根节点为root的二叉树的直径 = max(root-left的直径, root->right的直径,root->left的最大深度+root->right的最大深度+1) 1245 | * 分两种情况,1,最大直径经过根节点,则直径为左子树最大深度+右子树最大深度 2.如果不经过根节点,则取左子树或右子树的最大深度 1246 | ```python 1247 | class Solution: 1248 | def diameterOfBinaryTree(self, root: TreeNode) -> int: 1249 | self.diameter=0 1250 | def dfs(root): 1251 | if root==None: 1252 | return 0 1253 | left=dfs(root.left) 1254 | right=dfs(root.right) 1255 | self.diameter=max(self.diameter,left+right) 1256 | return max(left,right)+1 1257 | dfs(root) 1258 | return self.diameter 1259 | ``` 1260 | * 两个二叉树的最低公共祖先节点 1261 | ```python 1262 | ''' 1263 | 思路:递归,如果当前节点就是p或q,说明当前节点就是最近的祖先;如果当前节点不是p或p, 1264 | 就试着从左右子树里找pq;如果pq分别在一左一右被找到,那么当前节点还是最近的 1265 | 祖先返回root就好了;否则,返回它们都在的那一边。 1266 | ''' 1267 | class Solution: 1268 | def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': 1269 | if root==None: 1270 | return 1271 | if root==p or root==q: 1272 | return root 1273 | left=self.lowestCommonAncestor(root.left,p,q) 1274 | right=self.lowestCommonAncestor(root.right,p,q) 1275 | if left and right: 1276 | return root 1277 | if left: 1278 | return left 1279 | if right: 1280 | return right 1281 | else: 1282 | return 1283 | ``` 1284 | ##### 字符串算法 1285 | * 最长公共前缀 1286 | ```python 1287 | # leetcode 14 1288 | class Solution: 1289 | def longestCommonPrefix(self, strs: List[str]) -> str: 1290 | temp_str="" 1291 | zip_obj = zip(*strs) 1292 | for obj in zip_obj: 1293 | if(len(set(obj)))>1: 1294 | break 1295 | temp_str+=obj[0] 1296 | return temp_str 1297 | ``` 1298 | * 最长回文子串 1299 | ```python 1300 | # leetcode 5 1301 | class Solution: 1302 | def longestPalindrome(self, s: str) -> str: 1303 | start=0 1304 | maxstr=0 1305 | for i in range(len(s)): 1306 | if i-maxstr>=1 and s[i-maxstr-1:i+1]==s[i-maxstr-1:i+1][::-1]: 1307 | start = i-maxstr-1 1308 | maxstr+=2 1309 | continue 1310 | if i-maxstr>=0 and s[i-maxstr:i+1]==s[i-maxstr:i+1][::-1]: 1311 | start = i-maxstr 1312 | maxstr+=1 1313 | return s[start:start+maxstr] 1314 | ``` 1315 | * 无重复字符的最长子串 1316 | ```python 1317 | # leetcode 3 :滑动窗口法 1318 | class Solution: 1319 | def lengthOfLongestSubstring(self, s: str) -> int: 1320 | max_str=0 1321 | for i in range(len(s)): 1322 | if len(s[i-max_str:i+1])==len(set(s[i-max_str:i+1])): 1323 | max_str+=1 1324 | return max_str 1325 | ``` 1326 | ##### 哈希算法 1327 | * 哈希函数特性:确定性、哈希冲突,不可逆性,混淆特性 1328 | * 哈希表的 load factor(负载因子),越大表明哈希表中填的元素越多,越容易发生冲突 1329 | * 解决哈希冲突:开放寻址法(线性探测、二次探测、双重哈希),链表法 1330 | * 哈希洪水攻击 1331 | * 实现哈希表 1332 | ```python 1333 | class HashMap: 1334 | def __init__(self): 1335 | self.size = 11 1336 | self.slots = [None]*self.size 1337 | self.data = [None]*self.size 1338 | 1339 | def put(self, key, data): 1340 | hash_value = self.hash(key, len(self.slots)) 1341 | if self.slots[hash_value] == None: 1342 | self.slots[hash_value] = key 1343 | self.data[hash_value] = data 1344 | else: 1345 | # 键一样时替换 1346 | if self.slots[hash_value] == key: 1347 | self.data[hash_value] = data 1348 | else: 1349 | # 重新散列,+1线性探测 1350 | next_slot = self.rehash(hash_value, len(self.slots)) 1351 | while self.slots[next_slot] != None and self.slots[next_slot] != key: 1352 | next_slot = self.rehash(next_slot, len(self.slots)) 1353 | if self.slots[next_slot] != None: 1354 | self.slots[next_slot] = key 1355 | self.data[next_slot] = data 1356 | else: 1357 | self.data[next_slot] = data 1358 | 1359 | def get(self, key): 1360 | start_slot = self.hash(key, len(self.slots)) 1361 | found = False 1362 | data = None 1363 | stop = False 1364 | position = start_slot 1365 | while self.slots[position] != None and not stop and not found: 1366 | if self.slots[position] == key: 1367 | data = self.data[position] 1368 | found = True 1369 | else: 1370 | position = self.rehash(position,len(self.slots)) 1371 | if position==start_slot: 1372 | stop = True 1373 | return data 1374 | 1375 | def hash(self, key, size): 1376 | return key % size 1377 | 1378 | def rehash(self, oldhash, size): 1379 | return (oldhash+1) % size 1380 | 1381 | def __getitem__(self, key): 1382 | return self.get(key) 1383 | 1384 | def __setitem__(self, key, data): 1385 | return self.put(key, data) 1386 | hashmap=HashMap() 1387 | hashmap[0]='cat' 1388 | hashmap[1]='dog' 1389 | hashmap[2]='bird' 1390 | print(hashmap.slots) 1391 | print(hashmap.data ) 1392 | ``` 1393 | * 两数之和 1394 | ```python 1395 | # leetcode 1 1396 | class Solution: 1397 | def twoSum(self, nums: List[int], target: int) -> List[int]: 1398 | #enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串) 1399 | # 组合为一个索引序列,同时列出数据和数据下标 1400 | demo_dict = dict((num,i)for i,num in enumerate(nums)) 1401 | for i,num in enumerate(nums): 1402 | #get()方法和[key]方法的主要区别在于[key]在遇到不存在的key时会抛出KeyError错 1403 | j=demo_dict.get(target-num) 1404 | if j and i!=j: 1405 | return [i,j] 1406 | ``` 1407 | * 三数之和 1408 | ```python 1409 | # leetcode 15 1410 | class Solution: 1411 | def threeSum(self, nums: List[int]) -> List[List[int]]: 1412 | nums.sort() 1413 | res = [] 1414 | # 排序后遍历 1415 | for t in range(len(nums)- 2): 1416 | # 跳过相同的情况 1417 | if t > 0 and nums[t] == nums[t - 1]: 1418 | continue 1419 | # i向后判断,j向前判断 1420 | i, j = t + 1, len(nums) - 1 1421 | while i < j: 1422 | _sum = nums[t] + nums[i] + nums[j] 1423 | if _sum == 0: 1424 | res.append([nums[t], nums[i], nums[j]]) 1425 | i += 1 1426 | j -= 1 1427 | # 跳过相同的情况 1428 | while i < j and nums[i] == nums[i - 1]: 1429 | i += 1 1430 | while i < j and nums[j] == nums[j + 1]: 1431 | j -= 1 1432 | elif _sum < 0: 1433 | i += 1 1434 | else: 1435 | j -= 1 1436 | return res 1437 | ``` 1438 | * 重复的 DNA 序列 1439 | ```python 1440 | # leetcode 187 1441 | class Solution: 1442 | def findRepeatedDnaSequences(self, s: str) -> List[str]: 1443 | res=dict() 1444 | for i in range(len(s)-9): 1445 | res[s[i:i+10]]=res.get(s[i:i+10],0)+1 1446 | return [k for k,v in res.items() if v>=2] 1447 | ``` 1448 | * 两个数组的交集 1449 | ```python 1450 | # leetcode 350 1451 | class Solution: 1452 | def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: 1453 | res=[] 1454 | num_dict={} 1455 | for num in nums1: 1456 | if num not in num_dict: 1457 | num_dict[num]=1 1458 | else: 1459 | num_dict[num]+=1 1460 | for num in nums2: 1461 | if num in num_dict and num_dict[num]>0: 1462 | num_dict[num]-=1 1463 | res.append(num) 1464 | return res 1465 | ``` 1466 | ##### 常见算法基本思想 1467 | ###### 回溯 1468 | * 主要参考 1469 | * [Backtracking回溯法(又称DFS,递归)全解](https://segmentfault.com/a/1190000006121957) 1470 | * leetcode 22 / leetcode 39 / leetcode 40 /leetcode 46 / leetcode 79 1471 | ```python 1472 | # leetcode 17 1473 | class Solution(object): 1474 | def letterCombinations(self, digits): 1475 | """ 1476 | :type digits: str 1477 | :rtype: List[str] 1478 | """ 1479 | if not digits: 1480 | return [] 1481 | d = {'2':"abc", 1482 | '3':"def", 1483 | '4':"ghi", 1484 | '5':"jkl", 1485 | '6':"mno", 1486 | '7':"pqrs", 1487 | '8':"tuv", 1488 | '9':"wxyz" 1489 | } 1490 | res = [] 1491 | self.dfs(d, digits, "", res) 1492 | return res 1493 | 1494 | def dfs(self, d, digits, tmp , res): 1495 | if not digits: 1496 | res.append(tmp) 1497 | else: 1498 | for num in d[digits[0]]: 1499 | self.dfs(d, digits[1:], tmp + num, res) 1500 | ``` 1501 | ###### 分而治之 1502 | * leetcode 4 / leetcode 215 / leetcode 241 1503 | ```python 1504 | # leetcode 215 1505 | ''' 1506 | 任意选定数组内一个数mark,将比其大和比其小的值分别放在左右两个数组里,接着判断两边数字 1507 | 的数量,如果左边的数量大于k个,说明第k大的数字存在于mark及mark左边的区域,对左半区执行 1508 | partition函数;如果左边的数量小于k个,说明第k大的数字在mark和mark右边的区域之内,对右 1509 | 半区执行partition函数。直到左半区刚好有k-1个数,那么第k大的数就已经找到了。 1510 | ''' 1511 | class Solution(object): 1512 | def findKthLargest(self, nums, k): 1513 | mark = random.choice(nums) 1514 | num1,num2=list(),list() 1515 | for num in nums: 1516 | if num>mark: 1517 | num1.append(num) 1518 | if numlen(nums)-len(num2): 1523 | return self.findKthLargest(num2,k-(len(nums)-len(num2))) 1524 | return mark 1525 | ``` 1526 | ###### 动态规划 1527 | * 硬币找零 1528 | ```python 1529 | # leetcode 322 1530 | class Solution(object): 1531 | def coinChange(self, coins, amount): 1532 | # 建一个长度是 amount + 1的dp数组,构成面额从0到 amount需要使用的最少硬币数量。 1533 | dp = [0] + [-1] * amount 1534 | for x in range(amount): 1535 | if dp[x] < 0: 1536 | continue 1537 | for c in coins: 1538 | if x + c > amount: 1539 | continue 1540 | if dp[x + c] < 0 or dp[x + c] > dp[x] + 1: 1541 | dp[x + c] = dp[x] + 1 1542 | return dp[amount] 1543 | ``` 1544 | ###### 分支界限 1545 | #### 操作系统篇 1546 | * 学习资料推荐 1547 | - [哈工大李治军老师的OS](https://www.icourse163.org/course/HIT-1002531008) 1548 | - [配套的实验环境](https://www.shiyanlou.com/courses/115) 1549 | - [gitbook和源码](https://github.com/hoverwinter/HIT-OSLab) 1550 | - [Linux内核完全注释](https://book.douban.com/subject/1231236/) 1551 | ##### 进程与线程区别 1552 | 1. **内存和资源分配**: 1553 | - 进程拥有独立的内存空间,每个进程有自己的代码、数据、堆栈等资源,进程间的通信需要使用特定的通信机制,如管道、消息队列等。 1554 | - 线程共享进程的内存空间,它们之间可以直接访问相同的数据和变量,线程间的通信相对较为简单。 1555 | 2. **执行环境**: 1556 | - 进程是一个独立的执行环境,具有独立的程序计数器和寄存器等,进程间切换代价较高。 1557 | - 线程是在进程内部运行的,共享了进程的执行环境,线程间切换代价较低。 1558 | 3. **创建和销毁**: 1559 | - 创建和销毁进程需要较多的资源和时间,代价较高。 1560 | - 创建和销毁线程相对较轻量,代价较低。 1561 | 4. **并发性**: 1562 | - 进程之间并发执行,各自独立,进程间互不干扰。 1563 | - 线程之间共享进程的资源,因此线程之间并发执行时需要考虑同步和互斥问题。 1564 | 使用场景举例: 1565 | - **进程**:适合需要隔离资源、具有独立执行环境的任务。例如,操作系统中的各个应用程序就是独立的进程,每个进程都有自己的内存空间,可以并发执行不同的应用。 1566 | - **线程**:适合需要共享资源、执行环境的任务。例如,Web 服务器中,每个客户端请求可以由一个单独的线程处理,多个线程可以共享服务器的资源,提高并发处理能力。又如在游戏中,可以使用一个线程负责渲染,另一个线程负责用户输入的响应。 1567 | ##### 谈谈多线程并发 1568 | 1. **分离执行逻辑与调度机制:** 在多线程环境下,你可以将程序的执行逻辑与线程的调度机制分离,使不同线程能够交替执行,从而提高程序的响应能力和效率。例如,在图形界面应用程序中,可以将UI响应和后台任务处理分开,在后台线程中执行耗时操作,而不会阻塞用户界面。 1569 | 2. **异步 I/O:** 当需要处理异步 I/O 操作时,多线程能够让程序在等待 I/O 完成时执行其他任务,避免阻塞。一个常见的场景是网络服务器,其中每个客户端连接可以由一个单独的线程处理,保持服务器的高并发性。 1570 | 3. **资源利用率提升:** 在多核处理器上,多线程能够并行执行,充分利用硬件资源,加快任务的执行速度。例如,在数据处理应用中,可以将数据划分为多个部分,由不同线程同时处理,提高数据处理效率。 1571 | 4. **分解复杂任务:** 复杂任务可以被拆解为多个简单且同步的工作流,在不同的线程中执行,然后在同步位置进行交互。这在游戏开发中很常见,不同线程负责渲染、物理模拟、用户输入等,以提高游戏性能。 1572 | 5. **IO密集型任务:** 当任务涉及大量的IO操作,如文件读写、网络请求等,多线程可以让一个线程在等待IO时,其他线程继续执行,提高系统的资源利用率和响应速度。 1573 | 6. **多核CPU优势:** 多核处理器系统中,多线程可以更好地利用多个核心,提高并发处理能力。在科学计算、图像处理等领域,多线程能够并行处理复杂计算任务。 1574 | 7. 当线程之间如果要共享资源或数据的时候,可能变的非常复杂。Python的threading模块提供了很多同步原语,包括信号量,条件变量,事件和锁。 1575 | ```python 1576 | import threading 1577 | import time 1578 | class GetHtml(threading.Thread): 1579 | def __init__(self,name): 1580 | super().__init__(name=name) 1581 | def run(self): 1582 | print('get html start') 1583 | time.sleep(2) 1584 | print('get html end') 1585 | class GetUrl(threading.Thread): 1586 | def __init__(self,name): 1587 | super().__init__(name=name) 1588 | def run(self): 1589 | print('get url start') 1590 | time.sleep(4) 1591 | print('get url end') 1592 | if __name__ == "__main__": 1593 | thread1=GetHtml(name='html') 1594 | thread2=GetUrl(name='url') 1595 | start_time=time.time() 1596 | thread1.start() 1597 | thread2.start() 1598 | thread1.join() 1599 | thread2.join() 1600 | end_time=time.time() 1601 | total_time=end_time-start_time 1602 | print('total running tim:{}'.format(total_time)) 1603 | ``` 1604 | ##### 线程同步方式([Python代码实现](https://github.com/MorsoLi/python-interview-guide/tree/master/thread-parallel)) 1605 | * 主要参考 1606 | - [四种线程同步(或互斥)方式小结](https://blog.csdn.net/ebowtang/article/details/29905309) 1607 | * 线程同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的! 1608 | * 线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。 1609 | * **互斥量 Mutex**:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问,当前拥有互斥对象的线程处理完任务后必须将互斥对象交出,以便其他线程访问该资源。(RLock 和 Lock) 1610 | * **信号量 Semaphare**:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1 ,只要当前可用资源计数是大于0 的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离 开的同时通过Release将当前可用资源计数加1,在任何时候当前可用资源计数决不可能大于最大资源计数。 1611 | * **事件对象 Event**:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。 1612 | ##### 线程状态切换 1613 | ![thread-status-change.png](https://i.loli.net/2019/08/18/g6zZDk5iLaYCMcR.png) 1614 | ##### 分页机制 1615 | * 主要参考 1616 | - [分页机制图文详解](https://www.jianshu.com/p/3558942fe14f) 1617 | * 一级页表的弊端: 1618 | - 每存取一个数据,需两次访问内存,第一次访问内存中的页表,第二次访问内存中的数据,效率较低。改进:增设一个具有并行查寻能力的特殊高速缓冲寄存器,称为“联想寄存器”或“快表”,IBM中称为TLB,用于存放当前访问的那些页表项。 1619 | - IA-32体系结构中,处理器为32位,可寻址 2^32 =4GB的虚拟地址空间,若每页大小为4KB,则共分为4GB/4KB= 2^20 =1048576页,因此页表中应有1048576项,每个页表项为4B,则一个页表需要4MB的连续的物理内存,每个进程都需要自身的页表占4MB,将导致大量内存用于保存进程的页表。 1620 | - 改进:采用两级页表,每页中存2^10 项,共分为2^10 页,并新增一个页目录表来记录这 2^10 页表的地址与信息,因此页目录表大小为2^10*4B=4KB放在内存中,需要具体的表再由此读入。采用离散分配方式代替原来页表需要的连续物理内存,将当前需要的部分页表项调入内存,其余页表项仍驻留在磁盘上,需要时再调入。 1621 | ##### 分页和分段有什么区别(内存管理) 1622 | * 主要参考 1623 | - [分段和分页内存管理](https://blog.csdn.net/bupt_tinyfisher/article/details/8939689) 1624 | * 段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中,将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如 4k 的段换5k 的段,会产生 1k 的外碎片) 1625 | * 页式存储管理方案是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的帧,程序加载时,可以将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。页式存储管理的优点是:没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满) 1626 | * 两者的不同点: 1627 | - 目的不同:分页是由于系统管理的需要而不是用户的需要,它是信息的物理单位;分段的目的是为了能更好地满足用户的需要,它是信息的逻辑单位,它含有一组其意义相对完整的信息; 1628 | - 大小不同:页的大小固定且由系统决定,而段的长度却不固定,由其所完成的功能决定; 1629 | - 地址空间不同: 段向用户提供二维地址空间;页向用户提供的是一维地址空间; 1630 | - 信息共享:段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制; 1631 | - 内存碎片:页式存储管理的优点是没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满);而段式管理的优点是没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如 4k 的段换 5k 的段,会产生1k 的外碎片)。 1632 | ##### 什么是虚拟内存? 1633 | * 主要参考 1634 | - [BAT面试之操作系统内存详解](http://www.imooc.com/article/11015) 1635 | * 内存的发展历程:没有内存抽象 (单进程,除去操作系统所用的内存之外,全部给用户程序使用) —> 有内存抽象(多进程,进程独立的地址空间,交换技术 (内存大小不可能容纳下所有并发执行的进程))—> 连续内存分配 (固定大小分区 (多道程序的程度受限),可变分区 (首次适应,最佳适应,最差适应),碎片) —> 不连续内存分配(分段,分页,段页式,虚拟内存)。 1636 | 虚拟内存是计算机操作系统中的一种内存管理技术,它允许程序使用比实际物理内存更大的内存空间。虚拟内存通过将物理内存和硬盘空间结合起来,为每个进程提供一个虚拟的地址空间,使得每个进程认为自己拥有独立的连续内存空间,而实际上其数据和指令可以分布在物理内存和硬盘上。 1637 | 1638 | 虚拟内存的主要目的是提供更大的地址空间,以便能够运行更大的程序,而不受物理内存大小的限制。它还可以实现以下几个重要功能: 1639 | 1640 | 1. **内存隔离:** 每个进程都有独立的虚拟地址空间,相互之间不会干扰。这种隔离性使得不同进程可以在不同的地址空间中运行,增加了系统的稳定性和安全性。 1641 | 1642 | 2. **地址空间共享:** 虚拟内存可以让多个进程共享同一个物理内存页面,从而减少了内存占用。共享的页面可以包含代码、只读数据等。 1643 | 1644 | 3. **内存映射:** 虚拟内存可以将文件映射到内存中,使得文件的内容可以直接在内存中操作,提高了文件读写的性能。 1645 | 1646 | 4. **页面置换:** 当物理内存不足时,虚拟内存系统可以将不常用的页面从物理内存中置换到硬盘上,以便为更重要的页面腾出空间。这种页面置换策略可以提高系统的资源利用率。 1647 | 1648 | 5. **内存保护:** 虚拟内存可以为每个页面设置访问权限,从而实现内存保护,防止程序越界访问或者恶意修改其他进程的内存数据。 1649 | 1650 | * 局部性原理 1651 | - 时间上的局部性:最近被访问的页在不久的将来还会被访问; 1652 | - 空间上的局部性:内存中被访问的页周围的页也很可能被访问。 1653 | * 页面置换算法 1654 | - FIFO 先进先出算法:在操作系统中经常被用到,比如作业调度(主要实现简单,很容易想到); 1655 | - LRU(Least recently use)最近最少使用算法:根据使用时间到现在的长短来判断; 1656 | - LFU(Least frequently use)最少使用次数算法:根据使用次数来判断; 1657 | - OPT(Optimal replacement)最优置换算法:理论的最优,理论;就是要保证置换出去的是不再被使用的页,或者是在实际内存中最晚使用的算法。 1658 | ##### 颠簸(抖动) 1659 | * 颠簸本质上是指频繁的页调度行为,具体来讲,进程发生缺页中断,这时,必须置换某一页。然而,其他所有的页都在使用,它置换一个页,但又立刻再次需要这个页。因此,会不断产生缺页中断,导致整个系统的效率急剧下降,这种现象称为颠簸(抖动)。 1660 | * 内存颠簸的解决策略包括: 1661 | - 如果是因为页面替换策略失误,可以修改替换算法来解决这个问题; 1662 | - 如果是因为运行的程序太多,造成程序无法同时将所有频繁访问的页面调入内存,则要降低多道程序的数量; 1663 | ##### 进程调度算法 1664 | * FCFS(先来先服务,队列实现,非抢占的):先请求CPU的进程先分配到CPU 1665 | * SJF(最短作业优先调度算法):平均等待时间最短,但难以知道下一个CPU区间长度 1666 | * 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿; 1667 | * 时间片轮转调度算法(可抢占的):队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。 1668 | * 多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。 1669 | * 多级反馈队列调度算法:与多级队列调度算法相比,其允许进程在队列之间移动:若进程使用过多CPU时间,那么它会被转移到更低的优先级队列;在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,以防止饥饿发生。 1670 | ##### 经典进程同步问题 1671 | * 主要参考 1672 | - [进程同步的基本概念](https://www.kancloud.cn/hanghanghang/os/116760) 1673 | * 信号量(PV操作)、管程 1674 | ##### 进程的状态转换 1675 | * 主要参考 1676 | - [操作系统之进程的状态和转换详解](https://blog.csdn.net/qwe6112071/article/details/70473905) 1677 | * 进程的三种状态情况:运行态(占用CPU);就绪态(可运行,但暂时没有CPU分配给它);阻塞态(除非某种外部条件发生,就算CPU空闲,也不能执行)。unix用block阻塞。状态转换表 1678 | * 就绪状态 :一个进程获得了除处理机外的一切所需资源,一旦得到处理机即可运行,则称此进程处于就绪状态。 1679 | * 执行状态:当一个进程在处理机上运行时,则称该进程处于运行状态。 1680 | * 阻塞状态:一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。 1681 | ##### 进程间通信方式 1682 | * 主要参考 1683 | * [进程间通信(IPC)介绍](https://www.cnblogs.com/CheeseZH/p/5264465.html) 1684 | * 1685 | 进程间通信(Inter-Process Communication,简称IPC)是指在多进程环境中,不同进程之间进行数据交换、信息共享和协调工作的机制和技术。进程间通信允许不同进程之间在相互独立的内存空间中进行数据传递,从而实现进程之间的合作和协同。 1686 | 常见的进程间通信方式包括: 1687 | 1688 | 1. **管道(Pipe):** 管道是一种单向通信方式,允许一个进程写入数据到管道,另一个进程从管道读取数据。管道通常用于父子进程之间或者具有共同祖先的进程之间进行通信。 1689 | 1690 | 2. **命名管道(Named Pipe):** 类似于管道,但命名管道是一个有名字的FIFO文件,可以在不相关的进程之间进行通信。 1691 | 1692 | 3. **消息队列(Message Queue):** 消息队列允许不同进程之间通过发送和接收消息进行通信。每个消息都有一个特定的类型,进程可以选择接收特定类型的消息。 1693 | 1694 | 4. **信号量(Semaphore):** 信号量用于进程之间的同步和互斥操作。进程可以使用信号量来控制对临界资源的访问,以避免竞争条件。 1695 | 1696 | 5. **共享内存(Shared Memory):** 共享内存允许多个进程访问相同的内存区域,从而实现高效的数据交换。但需要特殊的同步机制来保证数据一致性和并发性。 1697 | 1698 | 6. **套接字(Socket):** 套接字是一种在网络编程中广泛使用的通信机制,不仅适用于本地进程间通信,还可以在不同主机上的进程之间进行通信。 1699 | 1700 | 7. **RPC(Remote Procedure Call):** RPC允许一个进程调用另一个进程中的函数,就像调用本地函数一样,从而实现远程进程之间的通信。 1701 | 1702 | 然而,进程间通信也可能引入复杂性和竞争条件,因此在设计和实现时需要考虑进程同步、数据一致性和安全性等问题。 1703 | 1704 | ##### [僵尸进程和孤儿进程](https://www.cnblogs.com/Anker/p/3271773.html#3777880) 1705 | * 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 1706 | * 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。 1707 | ##### [fork进程](https://blog.csdn.net/u013851082/article/details/76902046) 1708 | * 由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id。对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid;也可以调用getppid()来获取父进程的id。(进程id 0总是由交换进程使用,所以一个子进程的进程id不可能为0 )。 1709 | * fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置。 1710 | ##### Socket编程 1711 | * [Socket Programming in Python (Guide)](https://realpython.com/python-sockets/) 1712 | ![20190507170534122.png](https://i.loli.net/2019/08/13/e1MknSJRAbXI3du.png) 1713 | ##### Linux的五种IO模型 1714 | * 主要参考 1715 | * [聊聊IO多路复用之select、poll、epoll详解](https://mp.weixin.qq.com/s?__biz=MzAxODI5ODMwOA==&mid=2666538922&idx=1&sn=e6b436efd6a4f53dcbf20f4ce11a986a&scene=23&srcid=0425xFfzV9LmmVrdeEQ4He1W#rd) 1716 | * [漫话:如何给女朋友解释什么是Linux的五种IO模型](https://juejin.im/post/5b94e93b5188255c672e901e#comment) 1717 | 1718 | * 在Linux(UNIX)操作系统中,共有五种IO模型,分别是:阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型以及异步IO模型。 I/O操作,以一次磁盘文件读取为例,读取的文件是存储在磁盘上的,我们的目的是把它读取到内存中,可以把这个步骤简化成(真正的文件读取还涉及到缓存等细节)把数据从硬件(硬盘)中读取到用户空间中。关于用户空间、内核空间以及硬件等的关系如可以通过钓鱼的例子理解。鱼塘就可以映射成磁盘,中间过渡的鱼钩可以映射成内核空间,最终放鱼的鱼篓可以映射成用户空间。一次完整的钓鱼(IO)操作,是鱼(文件)从鱼塘(硬盘)中转移(拷贝)到鱼篓(用户空间)的过程。 1719 | 1. **阻塞IO模型**。应用进程通过系统调用 recvfrom 接收数据,但由于内核还未准备好数据报,应用进程就会阻塞住,直到内核准备好数据报,recvfrom 完成数据报复制工作,应用进程才能结束阻塞状态。(并发低,时效性要求低) 1720 | ![utf-8165bde9e5723181b.jpg](https://i.loli.net/2019/08/16/PbuHlOjvmwNdTV4.jpg) 1721 | 2. **非阻塞IO模型**。应用进程通过 recvfrom 调用不停的去和内核交互,直到内核准备好数据。如果没有准备好,内核会返回error,应用进程在得到error后,过一段时间再发送recvfrom请求。在两次发送请求的时间段,进程可以先做别的事情。 1722 | ![65bde9e699d0713.jpg](https://i.loli.net/2019/08/16/48SqhVp672HUZG3.jpg) 1723 | 3. **IO多路复用模型**。IO多路转接是多了一个select函数,多个进程的IO可以注册到同一个select上,当用户进程调用该select,select会监听所有注册好的IO,如果所有被监听的IO需要的数据都没有准备好时,select调用进程会阻塞。当任意一个IO所需的数据准备好之后,select调用就会返回,然后进程在通过recvfrom来进行数据拷贝。这里的IO复用模型,并没有向内核注册信号处理函数,所以,他并不是非阻塞的。进程在发出select后,要等到select监听的所有IO操作中至少有一个需要的数据准备好,才会有返回,并且也需要再次发送请求去进行文件的拷贝。PS: 这种方式的钓鱼,通过增加鱼竿的方式,可以有效的提升效率。 1724 | ![165bde9e6e6da275.jpg](https://i.loli.net/2019/08/16/MFGRjAe8ontQ2U1.jpg) 1725 | 4. **信号驱动IO模型**。应用进程预先向内核注册一个信号处理函数,然后用户进程返回,并且不阻塞,当内核数据准备就绪时会发送一个信号给进程,用户进程便在信号处理函数中开始把数据拷贝的用户空间中。PS: 这种方式钓鱼,和前几种相比,所使用的工具有了一些变化,需要有一些定制(实现复杂)。但是钓鱼的人就可以在鱼儿咬钩之前彻底做别的事儿去了。等着报警器响就行了。 1726 | ![ut165bde9e6c6552e2.jpg](https://i.loli.net/2019/08/16/7hV4NISfOwTktWE.jpg) 1727 | 5. **异步IO模型**。用户进程发起aio_read操作之后,给内核传递描述符、缓冲区指针、缓冲区大小等,告诉内核当整个操作完成时,如何通知进程,然后就立刻去做其他事情了。当内核收到aio_read后,会立刻返回,然后内核开始等待数据准备,数据准备好以后,直接把数据拷贝到用户控件,然后再通知进程本次IO已经完成。(应用进程把IO请求传给内核后,完全由内核去操作文件拷贝。内核完成相关操作后,会发信号告诉应用进程本次IO已经完成。) 1728 | ![](https://i.imgsafe.org/60/6095077381.jpeg) 1729 | * 五种IO模型比较:以上前四种都是同步的IO模型。因为,无论以上那种模型,真正的数据拷贝过程,都是同步进行的。信号驱动难道不是异步的么? 信号驱动,内核是在数据准备好之后通知进程,然后进程再通过recvfrom操作进行数据拷贝。我们可以认为数据准备阶段是异步的,但是,数据拷贝操作是同步的。所以,整个IO过程也不能认为是异步的。 1730 | ![](https://i.imgsafe.org/60/609e06b39f.jpeg) 1731 | ##### IO 多路复用模型 1732 | * I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,pselect,poll,epoll本质上都是同步I/O(epoll是Linux所特有,而select则应该是POSIX所规定的),因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。 1733 | * IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合: 1734 | 1. 当客户处理多个描述符时(一般是交互式输入和网络套接口),必须使用I/O复用。 1735 | 2. 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。 1736 | 3. 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用 1737 | 4. 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。 1738 | 5. 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。 1739 | * [用Python实现多用户/多服务器模型](https://github.com/MorsoLi/python-interview-guide/tree/master/server-program) 1740 | ###### select 1741 | * select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。 1742 | ###### poll 1743 | * poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。 1744 | ###### epoll 1745 | * epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。 1746 | ###### select、poll、epoll区别 1747 | * 支持一个进程所能打开的最大连接数 1748 | ![Snipaste_2019-05-29_10-08-08.png](https://i.loli.net/2019/08/16/YDrUzSZBdHOCFeN.png) 1749 | * FD剧增后带来的IO效率问题 1750 | ![Snipaste_2019-05-29_10-08-18.png](https://i.loli.net/2019/08/16/jdLBwPryzn5Mp2W.png) 1751 | * 消息传递方式 1752 | ![Snipaste_2019-05-29_10-08-28.png](https://i.loli.net/2019/08/16/koPVBeFp1XIYGfS.png) 1753 | * 最佳实践:并发性不高但连接活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调;并发性高但连接不活跃情况下,epoll的性能更好,select低效是因为每次它都需要轮询。 1754 | ##### 谈谈死锁 1755 | ###### 死锁的概念    1756 | * 在两个或者多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗的讲,就是两个或多个进程无限期的阻塞、相互等待的一种状态。 1757 | ###### 死锁产生的原因 1758 | * 系统中的资源可以分为两类: 1759 | * 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源; 1760 | * 不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。 1761 | * 产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞) 1762 | * 产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁。 1763 | ###### 死锁产生的四个必要条件 1764 | * 互斥:至少有一个资源必须属于非共享模式,即一次只能被一个进程使用;若其他申请使用该资源,那么申请进程必须等到该资源被释放为止; 1765 | * 占有并等待:一个进程必须占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有; 1766 | * 非抢占:进程不能被抢占,即资源只能被进程在完成任务后自愿释放 1767 | * 循环等待:若干进程之间形成一种头尾相接的环形等待资源关系 1768 | ###### 解决死锁的基本方法 1769 | * 解决死锁的基本方法主要有 预防死锁、避免死锁、解除死锁 1770 | * **预防死锁**的基本思想是 只要**确保死锁发生的四个必要条件中至少有一个不成立,就能预防死锁的发生**,具体方法包括: 1771 | * 打破互斥条件:允许进程同时访问某些资源。但是,有些资源是不能被多个进程所共享的,这是由资源本身属性所决定的,因此,这种办法通常并无实用价值。 1772 | * 打破占有并等待条件:可以实行资源预先分配策略(进程在运行前一次性向系统申请它所需要的全部资源,若所需全部资源得不到满足,则不分配任何资源,此进程暂不运行;只有当系统能满足当前进程所需的全部资源时,才一次性将所申请资源全部分配给该线程)或者只允许进程在没有占用资源时才可以申请资源(一个进程可申请一些资源并使用它们,但是在当前进程申请更多资源之前,它必须全部释放当前所占有的资源)。但是这种策略也存在一些缺点:在很多情况下,无法预知一个进程执行前所需的全部资源,因为进程是动态执行的,不可预知的;同时,会降低资源利用率,导致降低了进程的并发性。 1773 | * 打破非抢占条件:允许进程强行从占有者哪里夺取某些资源。也就是说,但一个进程占有了一部分资源,在其申请新的资源且得不到满足时,它必须释放所有占有的资源以便让其它线程使用。这种预防死锁的方式实现起来困难,会降低系统性能。 1774 | * 打破循环等待条件:实行资源有序分配策略。对所有资源排序编号,所有进程对资源的请求必须严格按资源序号递增的顺序提出,即只有占用了小号资源才能申请大号资源,这样就不回产生环路,预防死锁的发生。 1775 | * **避免死锁**的基本思想是动态地检测资源分配状态,以确保循环等待条件不成立,从而确保系统处于安全状态。所谓安全状态是指:如果系统能按某个顺序为每个进程分配资源(不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态。资源分配图算法和银行家算法是两种经典的死锁避免的算法,其可以确保系统始终处于安全状态。其中,资源分配图算法应用场景为每种资源类型只有一个实例(申请边,分配边,需求边,不形成环才允许分配),而银行家算法应用于每种资源类型可以有多个实例的场景。 1776 | * **解除死锁** 1777 | * 终止进程(简单粗暴),就是字面上的,你们死锁了,我就把你们一起杀掉,缺点就是如果一个进程跑了很长时间,但是被杀了,还得从头来。 1778 | * 逐个终止进程,按照某种顺序,挨个杀死进程,每杀一个进程就去看看死锁解除了没有(每杀一个进程都会释放一些资源,如果释放好粗来的资源解决了死锁问题,就没必要再滥杀无辜了),没解除就继续杀。 1779 | #### 计算机网络篇 1780 | * 主要参考 1781 | - [协议森林](https://www.cnblogs.com/vamei/archive/2012/12/05/2802811.html) 1782 | - [HTTP协议详解](https://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html) 1783 | ##### [计算机网络概述](./计算机网络概述.md) 1784 | ##### [从URL输入到页面展现到底发生什么?](https://mp.weixin.qq.com/s?__biz=Mzg5ODA5NTM1Mw==&mid=2247483803&idx=1&sn=460597ae3bd3ec10ad3426b7db605074&chksm=c066800df711091b2b6e005a922fff4a6a8e4b7930928a1c60fc0ac657c3ea5061cf1b51563d&mpshare=1&scene=1&srcid=#rd) 1785 | * DNS查询,ARP解析,TCP三次握手连接,HTTP请求,反向代理Nginx,uwsgi/gunicorn(WSGI服务器),服务器响应,浏览器页面渲染,TCP四次挥手。 1786 | ##### ARP 协议工作原理 1787 | * 网络层的 ARP 协议完成了 IP 地址与物理地址的映射。首先,每台主机都会在自己的 ARP 缓冲区中建立一个 ARP列表,以表示 IP 地址和 MAC 地址的对应关系。当源主机需要将一个数据包要发送到目的主机时,会首先检查自己 ARP 列表中是否存在该 IP 地址对应的 MAC 地址:如果有,就直接将数据包发送到这个 MAC 地址;如果没有,就向本地网段发起一个 ARP 请求的广播包,查询此目的主机对应的 MAC 地址。此 ARP 请求数据包里包括源主机的 IP 地址、硬件地址、以及目的主机的 IP 地址。网络中所有的主机收到这个 ARP 请求后,会检查数据包中的目的 IP 是否和自己的 IP 地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的 MAC 地址和 IP 地址添加到自己的 ARP 列表中,如果 ARP 表中已经存在该 IP 的信息,则将其覆盖,然后给源主机发送一个 ARP 响应数据包,告诉对方自己是它需要查找的 MAC 地址;源主机收到这个 ARP 响应数据包后,将得到的目的主机的 IP 地址和 MAC 地址添加到自己的 ARP 列表中,并利用此信息开始数据的传输。如果源主机一直没有收到 ARP 响应数据包,表示 ARP 查询失败。 1788 | ##### TCP 三次握手 1789 | * 重点记住每个阶段以后客户和服务器的状态变化 1790 | ![20180717202520531.png](https://i.loli.net/2019/08/15/xpwUj2QYqrEz5a3.png) 1791 | * 为什么不能用两次握手进行连接? 1792 | 答:为了防止 已失效的链接请求报文突然又传送到了服务端,因而产生错误。客户端发出的连接请求报文并未丢失,而是在某个网络节点长时间滞留了,以致延误到链接释放以后的某个时间才到达 Server。这是,Server 误以为这是 Client 发出的一个新的链接请求,于是就向客户端发送确认数据包,同意建立链接。若不采用 “三次握手”,那么只要 Server 发出确认数据包,新的链接就建立了。由于 client 此时并未发出建立链接的请求,所以其不会理睬 Server 的确认,也不与 Server通信;而这时 Server 一直在等待 Client 的请求,这样Server 就白白浪费了一定的资源。若采用 “三次握手”,在这种情况下,由于 Server 端没有收到来自客户端的确认,则就会知道 Client 并没有要求建立请求,就不会建立链接。 1793 | ##### TCP 四次挥手 1794 | ![20180717204202563.png](https://i.loli.net/2019/08/15/yqCMJ3jxzErW4it.png) 1795 | * 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态? 1796 | 答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。 1797 | ##### TCP 怎样保证可靠性 1798 | * 数据包校验:目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段并且不给出响应,这时 TCP 发送数据端超时后会重发数据; 1799 | * 对失序数据包重排序:既然 TCP 报文段作为 IP 数据报来传输,而 IP 数据报的到达可能会失序,因此TCP 报文段的到达也可能会失序。TCP 将对失序数据进行重新排序,然后才交给应用层; 1800 | * 丢弃重复数据:对于重复数据,能够丢弃重复数据; 1801 | * 应答机制:当 TCP 收到发自 TCP 连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒; 1802 | * 超时重发:当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段; 1803 | * 流量控制:TCP 连接的每一方都有固定大小的缓冲空间。TCP 的接收端只允许另一端发送接收端缓冲区所能接纳的数据,这可以防止较快主机致使较慢主机的缓冲区溢出,这就是流量控制。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 1804 | ##### 流量控制和拥塞控制 1805 | * 主要参考 1806 | * [TCP的流量控制和拥塞控制](https://blog.csdn.net/yechaodechuntian/article/details/25429143) 1807 | * 区别注意,拥塞控制和流量控制不同,前者是一个全局性的过程,而后者指点对点通信量的控制。 1808 | - 拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;常用的方法就是:慢开始、拥塞避免;快重传、快恢复。 1809 | - 流量控制:流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。 1810 | ##### TCP 与 UDP 的区别 1811 | * TCP 是面向连接的,UDP 是无连接的; 1812 | * TCP 是可靠的,UDP 是不可靠的; 1813 | * TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多的通信模式; 1814 | * TCP 是面向字节流的,UDP 是面向报文的; 1815 | * TCP 有拥塞控制机制; UDP 没有拥塞控制,适合媒体通信; 1816 | * TCP 首部开销 (20 个字节) 比 UDP 的首部开销 (8 个字节) 要大 1817 | ##### HTTP协议 1818 | * 超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器,目前我们使用的是HTTP/1.1 版本。 1819 | * HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对http服务器来说,它并不知道这两个请求来自同一个客户端。 为了解决这个问题, Web程序引入了Cookie机制和session机制来维护状态。 1820 | * HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。 1821 | ###### HTTP 和 HTTPS 的区别 1822 | 1. https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。 1823 | 2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。 1824 | 3. http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。 1825 | 4. http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。 1826 | ###### URL详解 1827 | * URL(Uniform Resource Locator) 地址用于描述一个网络上的资源,  基本格式如下: 1828 | ``` 1829 | schema://host[:port#]/path/.../[?query-string][#anchor] 1830 | >schema               指定低层使用的协议(例如:http, https,ftp) 1831 | host                   HTTP服务器的IP地址或者域名 1832 | port#         HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明 1833 | path                 访问资源的路径 1834 | query-string       发送给http服务器的数据 1835 | anchor             锚 1836 | ``` 1837 | ##### HTTP 常见方法(GET/PUT) 1838 | * Http协议定义了很多与服务器交互的方法,最基本的有4种,分别是GET,POST,PUT,DELETE. 一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,改,增,删4个操作。 1839 | ###### 安全性和幂等性 1840 | * 安全意味着该操作用于获取信息而非修改信息。幂等意味着对同一 URL 的多个请求应该返回同样的结果。 1841 | ![Snipaste_2019-05-09_08-39-20.png](https://i.loli.net/2019/08/13/Z74rHxcqbkM93A8.png) 1842 | ###### GET和POST 区别 1843 | * 最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息. 1844 | * GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连?name=test1&id=123456.  POST方法是把提交的数据放在HTTP包的Body中. 1845 | * GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制。 1846 | * GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码。 1847 | ##### 常见 HTTP 状态码 1848 | * Response 消息中的第一行叫做状态行,由HTTP协议版本号,状态码,状态消息三部分组成。状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response。HTTP/1.1中定义了5类状态码,状态码由三位数字组成,第一个数字定义了响应的类别 1849 | ``` 1850 | 1XX  提示信息 - 表示请求已被成功接收,继续处理 1851 | 2XX  成功 - 表示请求已被成功接收,理解,接受 1852 | 200 OK : 表明该请求被成功地完成,所请求的资源发送回客户端 1853 | 3XX  重定向 - 要完成请求必须进行更进一步的处理 1854 | 302 Found : 重定向,新的URL会在response 中的Location中返回,浏览器将会 1855 | 自动使用新的URL发出新的Request 1856 | 304 Not Modified : 代表上次的文档已经被缓存了, 还可以继续使用 1857 | (提示:如果你不想使用本地缓存可以用Ctrl+F5 强制刷新页面) 1858 | 4XX  客户端错误 -  请求有语法错误或请求无法实现 1859 | 400 Bad Request : 客户端请求与语法错误,不能被服务器所理解 1860 | 403 Forbidden : 服务器收到请求,但是拒绝提供服务 1861 | 404 Not Found : 请求资源不存在(还有可能是输错了URL) 1862 | 5XX  服务器端错误 -   服务器未能实现合法的请求 1863 | 500 Internal Server Error:服务器发生了不可预期的错误 1864 | 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常 1865 | ``` 1866 | ##### HTTP 长连接与短连接 1867 | ###### 长连接 1868 | * **长连接定义**:client方与server方先建立连接,连接建立后不断开,然后再进行报文发送和接收。这种方式下由于通讯连接一直存在。此种方式常用于P2P点对点的通信。长连接的操作步骤是:建立连接——数据传输...(保持连接)...数据传输——关闭连接 1869 | * **长连接适用场景**:以下这些连接,如果每次操作都要建立连接然后再操作的话处理速度会降低。所以操作时第一次连接上以后,以后每次直接发送数据就可以了,不用再建立TCP连接。常连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。 1870 | * 即时通信系统:其它用户登录、发送信息; 1871 | * 即时报价系统:后台数据库内容发生变化; 1872 | * 数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,频繁的socket创建也是对资源的浪费。 1873 | ###### 短连接 1874 | * **短连接定义**: Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此方式常用于一点对多点通讯。短连接的操作步骤是:建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接 1875 | * **短连接的适用场景**:每个TCP连接的建立都需要三次握手,每个TCP连接的断开要四次握手。web网站的http服务一般都用短连接。因为长连接对于服务器来说要耗费一定的资源。像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接更省一些资源。  1876 | ##### cookie 和 session 的区别 1877 | * 会话状态保存在客户端。客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器颁发一个 Cookie,而客户端浏览器会把Cookie 保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该 Cookie 一同提交给服务器,服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie 的内容。 1878 | * 会话状态也可以保存在服务器端。客户端请求服务器,如果服务器记录该用户状态,就获取 Session 来保存状态,这时,如果服务器已经为此客户端创建过 session,服务器就按照 sessionid 把这个 session 检索出来使用;如果客户端请求不包含 sessionid,则为此客户端创建一个session 并且生成一个与此 session 相关联的 sessionid,并将这个 sessionid 在本次响应中返回给客户端保存。保存这个 sessionid 的方式可以采用 cookie 机制 ,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器;若浏览器禁用 Cookie 的话,可以通过 URL 重写机制将sessionid 传回服务器。 1879 | * 实现机制:Session 的实现常常依赖于 Cookie 机制,通过 Cookie 机制回传 SessionID; 1880 | * 安全性:Cookie 存在安全隐患,通过拦截或本地文件找得到 cookie 后可以进行攻击,而 Session 由于保存在服务器端,相对更加安全; 1881 | * 大小限制:Cookie 有大小限制并且浏览器对每个站点也有 cookie 的个数限制,Session 没有大小限制,理论上只与服务器的内存大小有关; 1882 | * 服务器资源消耗:Session 是保存在服务器端上会存在一段时间才会消失,如果 session 过多会增加服务器的压力。 1883 | ##### JSON Web Token 1884 | * 主要参考 1885 | - [JSON Web Token 入门教程](http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html) 1886 | ![1821058-2e28fe6c997a60c9.png](https://i.loli.net/2019/08/25/EKdXuH2ib9gIzyn.png) 1887 | * JWT 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准,该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 1888 | ##### IP 地址的分类 1889 | * IP 地址是指互联网协议地址,是 IP 协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。IP 地址编址方案将 IP 地址空间划分为 A、B、C、D、E 五类,其中 A、B、C 是基本类,D、E 类作为多播和保留使用,为特殊地 1890 | 址。 1891 | * 每个 IP 地址包括两个标识码(ID),即网络 ID 和主机ID。同一个物理网络上的所有主机都使用同一个网络 ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机 ID 与其对应。A~E 类地址的特点如下: 1892 | - A 类地址:以 0 开头,第一个字节范围:0~127; 1893 | - B 类地址:以 10 开头,第一个字节范围:128~191; 1894 | - C 类地址:以 110 开头,第一个字节范围:192~223; 1895 | - D 类地址:以 1110 开头,第一个字节范围为: 224~239; 1896 | - E 类地址:以 1111 开头,保留地址 1897 | ##### OSI七层参考模型 1898 | ![OSI网络参考模型功能表示.png](https://i.loli.net/2019/08/15/HKS5TkxRfrbMVpC.png) 1899 | #### 数据库篇 1900 | * 学习书籍推荐 1901 | - [知乎龚子捷的回答](https://www.zhihu.com/question/34840297/answer/272185020) 1902 | - [《高性能MySQL》](https://book.douban.com/subject/23008813/) 1903 | ##### 数据库系统概念 1904 | ###### 数据库范式 1905 | * 第一范式:列不可分,eg:【联系人】(姓名,性别,电话),一个联系人有家庭电话和公司电话,那么这种表结构设计就没有达到 1NF; 1906 | * 第二范式:有主键,保证完全依赖。eg:订单明细表【OrderDetail】(OrderID,ProductID,UnitPrice,Discount,Quantity,ProductName),Discount(折扣),Quantity(数量)完全依赖(取决)于主键(OderID,ProductID),而 UnitPrice,ProductName 只依赖于 ProductID,不符合2NF; 1907 | * 第三范式:无传递依赖 (非主键列 A 依赖于非主键列B,非主键列 B 依赖于主键的情况),eg: 订单表【Order】(OrderID,OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity)主键是(OrderID),CustomerName,CustomerAddr,CustomerCity直接依赖的是 CustomerID(非主键列),而不是直接依赖于主键,它是通过传递才依赖于主键,所以不符合 3NF。 1908 | ###### 视图 1909 | * 视图是一种虚拟的表,通常是有一个表或者多个表的行或列的子集,具有和物理表相同的功能,可以对视图进行增,删,改,查等操作。特别地,对视图的修改不影响基本表。相比多表查询,它使得我们获取数据更容易。 1910 | ###### 游标 1911 | * 游标是对查询出来的结果集作为一个单元来有效的处理。游标可以定在该单元中的特定行,从结果集的当前行检索一行或多行,可以对结果集当前行做修改。一般不使用游标,但是需要逐条处理数据的时候,游标显得十分重要。 MySQL 检索操作返回一组称为结果集的行,这组返回的行都是与 SQL 语句相匹配的行(零行或多行),使用简单的 SELECT 语句,不存在每次一行地处理所有行的简单方法(相对于成批地处理它们)。有时需要在检索出来的行中前进或后退一行或多行,这就是使用游标的原因。游标(cursor)是一个存储在 MySQL 服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。 1912 | ###### 触发器 1913 | * 触发器是与表相关的数据库对象,在满足定义条件时触发,并执行触发器中定义的语句集合。触发器的这种特性可以协助应用在数据库端确保数据库的完整性。 1914 | ###### 存储过程 1915 | * 存储过程是事先经过编译并存储在数据库中的一段 SQL语句的集合。存储过程是由一些 T-SQL 语句组成的代码块,这些 T-SQL 语句代码像一个方法一样实现一些功能(对单表或多表的增删改查),然后再给这个代码块取一个名字,在用到这个功能的时候调用他就行了。存储过程具有以下特点: 1916 | - 存储过程只在创建时进行编译,以后每次执行存储过程都不需再重新编译,而一般 SQL 语句每执行一次就编译一次,所以使用存储过程可提高数据库执行效率; 1917 | - 当 SQL 语句有变动时,可以只修改数据库中的存储过程而不必修改代码; 1918 | - 减少网络传输,在客户端调用一个存储过程当然比执行一串 SQL 传输的数据量要小; 1919 | - 通过存储过程能够使没有权限的用户在控制之下间接地存取数据库,从而确保数据的安全。 1920 | ##### MySQL 基础问题 1921 | ###### 常用 SQL 语句 1922 | * 内连接,左连接,全连接 1923 | * union 和union all的区别:union会对结果集进行处理排除掉相同的结果,union all 不会对结果集进行处理,不会处理掉相同的结果。 1924 | * MySQL 常用数据类型:字符串、数值、日期 1925 | * 数据库查询语言分类 1926 | - DQL(Data Query Language)数据查询语言DQL由SELECT子句,FROM子句,WHERE子句组成 1927 | - DML(Data Manipulation Language)数据操纵语言DML包含INSERT,UPDATE,DELETE 1928 | - DDL(Data Definition Language)数据定义语言DDL用来创建数据库中的各种对象-----表、视图、 索引、同义词、聚簇等,如:CREATE TABLE/VIEW/INDEX/SYN/CLUSTER。DDL操作是隐性提交的!不能rollback 1929 | - DCL(Data Control Language)数据控制语言(DCL)是用来设置或者更改数据库用户或角色权限的语句,这些语句包括GRANT、DENY、REVOKE等语句,在默认状态下,只有sysadmin、dbcreator、db_owner或db_securityadmin等角色的成员才有权利执行数据控制语言。 1930 | ###### in与not in,exists与not exists的区别 1931 | * exist会针对子查询的表使用索引 1932 | * not exist会对主子查询都会使用索引 1933 | * in与子查询一起使用的时候,只针对主查询使用索引 1934 | * not in则不会使用任何索引 1935 | * 如果查询的两个表大小相当,那么用in和exists差别不大;如果两个表中一个较小一个较大,则子查询表大的用exists,子查询表小的用in,所以无论哪个表大,用not exists都比not in 要快。 1936 | ###### drop、delete 与 truncate 的区别 1937 | * delete 用来删除表的全部或者一部分数据行,执行delete 之后,用户需要提交 (commmit) 或者回滚(rollback) 来执行删除或者撤销删除, delete 命令会触发这个表上所有delete 触发器; 1938 | * truncate 删除表中的所有数据,这个操作不能回滚,也不会触发这个表上的触发器,truncate 比 delete 更快,占用的空间更小; 1939 | * drop 命令从数据库中删除表,所有的数据行,索引和权限也会被删除,所有的 DML 触发器也不会被触发,这个命令也不能回滚。 1940 | * 因此,在不再需要一张表的时候,用 drop;在想删除部分数据行时候,用 delete;在保留表而删除所有数据的时候用 truncate。 1941 | ##### 数据库事务 1942 | * 事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。 1943 | ###### 事务的特征(ACID) 1944 | * 原子性 (Atomicity):事务所包含的一系列数据库操作要么全部成功执行,要么全部回滚; 1945 | * 一致性 (Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态; 1946 | * 隔离性 (Isolation):并发执行的事务之间不能相互影响; 1947 | * 持久性 (Durability):事务一旦提交,对数据库中数据的改变是永久性的。 1948 | ###### 事务并发带来的问题 1949 | * 脏读:一个事务读取了另一个事务未提交的数据;(eg:事务A:张三妻子给张三转账100元。事务B:张三查询余额。事务A转账后(还未提交),事务B查询多了100元。事务A由于某种问题,比如超时,进行回滚。事务B查询到的数据是假数据。) 1950 | * 不可重复读:不可重复读的重点是修改,同样条件下两次读取结果不同;(eg:事务A:张三妻子给张三转账100元。事务B:张三两次查询余额。事务B第一次查询余额,事务A还没有转账,第二次查询余额,事务A已经转账了,导致一个事务中,两次读取同一个数据,读取的数据不一致。) 1951 | * 幻读:幻读的重点在于新增或者删除,一个事务两次读取一个范围的记录,两次读取的记录数不一致。(eg:事务A:张三妻子两次查询张三有几张银行卡。事务B:张三新办一张银行卡。事务A第一次查询银行卡数的时候,张三还没有新办银行卡,第二次查询银行卡数的时候,张三已经新办了一张银行卡,导致两次读取的银行卡数不一样。) 1952 | ###### 事务的隔离级别 1953 | * 参考[数据库事务隔离级别-- 脏读、幻读、不可重复读(清晰解释)](https://blog.csdn.net/JIESA/article/details/51317164) 1954 | * 数据库事务的隔离级别有4个,由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable(最高级别的隔离,只允许事务串行执行。),后三个级别可以逐个解决脏读 、不可重复读 、幻读这几类问题,MySQL 的 InnoDB存储引擎都支持,MySQL 默认的隔离级别是 Repeatable read。 1955 | ![Snipaste_2019-09-05_15-29-32.png](https://i.loli.net/2019/09/05/ajrmGC2HAhT1Lqf.png) 1956 | ###### MySQL 的事务支持 1957 | * MySQL 的事务支持不是绑定在 MySQL 服务器本身,而是与存储引擎相关: 1958 | - MyISAM:不支持事务,用于只读程序提高性能; 1959 | - InnoDB:支持 ACID 事务、行级锁、并发; 1960 | - Berkeley DB:支持事务。 1961 | ##### 数据库索引 1962 | * 索引是对数据库表中一个或多个列的值进行排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用 B树和B+树,索引加速了数据访问,因为存储引擎不会再去扫描整张表得到需要的数据;相反,它从根节点开始,根节点保存了子节点的指针,存储引擎会根据指针快速寻找数据。 1963 | * 在数据结构中,我们最为常见的搜索结构就是二叉搜索树和 AVL 树 (高度平衡的二叉搜索树,为了提高二叉搜索树的效率,减少树的平均搜索长度) 了。然而,无论二叉搜索树还是 AVL 树,当数据量比较大时,都会由于树的深度过大而造成 I/O 读写过于频繁,进而导致查询效率低下,因此对于索引而言,B 树(平衡多路查找树)各操作能使 B 树保持较低的高度,从而保证高效的查找效率。 1964 | ###### 索引的优点和缺点 1965 | * 优点 1966 | * 大大加快数据的检索速度,这也是创建索引的最主要的原因; 1967 | * 加速表和表之间的连接; 1968 | * 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间; 1969 | * 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性; 1970 | * 缺点 1971 | * 时间方面:创建索引和维护索引要耗费时间,具体地对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度; 1972 | * 空间方面:索引需要占物理空间; 1973 | ###### B 树和 B+ 树 1974 | * 参考[重温数据结构:理解 B 树、B+ 树特点及使用场景](https://blog.csdn.net/u011240877/article/details/80490663) 1975 | * B+ 树相比 B 树的优势 1976 | - B+ 树的磁盘读写代价更低:B+树的内部结点并没有指向关键字具体信息的指针,因此其内部结点相对 B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多,相对来说 IO 读写次数也就降低了; 1977 | - B+ 树的查询效率更加稳定:由于内部结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引,所以,任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当; 1978 | - B+ 树只要遍历叶子节点就可以实现整棵树的遍历,而且在数据库中基于范围的查询是非常频繁的,而 B 树只能中序遍历所有节点,效率太低。 1979 | ###### 索引的分类 1980 | * 普通索引和唯一性索引:索引列的值的唯一性 1981 | * 主键索引:指的就是主键,主键是索引的一种,是唯一索引的特殊类型((唯一索引允许空值,主键不允许有空值)。创建主键的时候,数据库默认会为主键创建一个唯一索引;InnoDB 作为 MySQL 存储引擎时,默认按照主键进行聚集,如果没有定义主键,InnoDB 会试着使用唯一的非空索引来代替。如果没有这种索引,InnoDB 就会定义隐藏的主键然后在上面进行聚集。所以,对于聚集索引来说,你创建 1982 | 主键的时候,自动就创建了主键的聚集索引。 1983 | * 单个索引和复合索引:索引列所包含的列数 1984 | * 聚集索引按照数据的物理存储进行划分的。对于一堆记录来说,使用聚集索引就是对这堆记录进行堆划分,即主要描述的是物理上的存储。聚集索引可以帮助把很大的范围,迅速减小范围,然后查找指定记录;而非聚集索引是把一个很大的范围,转换成一个小的地图,然后你需要在这个小地图中找你要寻找的信息的位置,最后通过这个位置,再去找你所需要的记录。参考自[快速理解聚集索引和非聚集索引](https://blog.csdn.net/zc474235918/article/details/50580639) 1985 | * 聚集和非聚集指:B+树叶节点存的指针还是数据记录,myISAM 非聚集(指针)innoDB聚集(指针+记录) 1986 | ##### MySQL 中的锁 1987 | ###### 乐观锁和悲观锁 1988 | * 悲观锁的特点是先获取锁,再进行业务操作,即 “悲观” 的认为所有的操作均会导致并发安全问题,因此要先确保获取锁成功再进行业务操作。通常来讲,在数据库上的悲观锁需要数据库本身提供支持,即通过常用的 select … for update 操作来实现悲观锁。当数据库执行 select … for update 时会获取被 select 中的数据行的行锁,因此其他并发执行的 select … for update 如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update 获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。另外还有个问题是: select… for update 语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此,如果在 mysql 中用悲观锁务必要确定使用了索引,而不是全表扫描。(先上锁,再操作,一锁二查三更新) 1989 | * 乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部 update 同一行的时候是不允许并发的,即数据库每次执行一条 update 语句时会获取被 update 行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这其间没有发生并发的修改。如果更新失败,即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。(先修改,更新时发现数据变化就会滚) 1990 | * 悲观锁与乐观锁的应用场景:一般情况下,读多写少更适合用乐观锁,读少写多更适合用悲观锁。乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能。 1991 | ###### MySQL 行级锁 1992 | * MyISAM 只支持表级锁,用户在操作MyISAM 表时,select、update、delete 和 insert语句都会给表自动加锁,如果加锁以后的表满足insert 并发的情况下,可以在表的尾部插入新的数据。InnoDB 支持事务和行级锁,行锁大幅度提高了多用户并发操作的新能,但是 InnoDB 的行锁只是在 WHERE 的主键是有效的,非主键的 WHERE 都会锁全表。 1993 | * 共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。SELECT ... LOCK IN SHARE MODE 会对查询出的每一条数据加共享锁,如果其它线程再加排它锁就会阻塞。 1994 | * 排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的锁,获准排他锁的事务既能读数据,又能修改数据。SELECT ... FOR UPDATE 中会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。 1995 | ##### 存储引擎 MyISAM 和 InnoDB 区别 1996 | * 事务支持:MyISAM 强调的是性能,每次查询具有原子性,其执行速度比 InnoDB 更快,但是不支持事务。InnoDB 提供事务、外键等高级数据库功能,具有事务提交、回滚能力。 1997 | * AUTO_INCREMENT:对AUTO_INCREMENT的处理方式不一样。如果将某个字段设置为INCREMENT,InnoDB中规定必须包含只有该字段的索引,如果是组合索引也必须是组合索引的第一列。但是在MyISAM中,可以将该字段和其他字段一起建立组合索引,可以不是第一列。 1998 | * 表主键:MyISAM 允许没有任何索引和主键的表存在,索引都是保存行的地址。对于 InnoDB,如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键 (用户不可见),数据是主索引的一部分。 1999 | * 表的具体行数:MyISAM内置了一个计数器来存储表的行数。执行 select count(*)时直接从计数器中读取,速度非常快。而 InnoDB不保存这些信息,如果使用 select count( *) from table 就会遍历整个表,消耗相当大。(在加了wehre 条件后,myisam 和 innodb 处理的方式都一样) 2000 | * 全文索引:MyISAM支持 FULLTEXT类型的全文索引,InnoDB不支持FULLTEXT类型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。 2001 | * 外键:MyISAM 不支持外键,而 InnoDB 支持外键。 2002 | * 存储结构:每个 MyISAM 在磁盘上存储成三个文件:第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表定义,数据文件的扩展名为. MYD (MYData),索引文件的扩展名是. MYI(MYIndex)。InnoDB 所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB 表的大小只受限于操作系统文件的大小,一般为 2GB。 2003 | * CURD操作:如果执行大量的SELECT,MyISAM是更好的选择。如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表。DELETE 从性能上InnoDB更优,但DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除,在innodb上如果要清空保存有大量数据的表,最好使用truncate table这个命令。 2004 | ##### [实现MVCC](https://liuzhengyang.github.io/2017/04/18/innodb-mvcc/) 2005 | * MVCC 全称是Multi-Version Concurrent Control,即多版本并发控制,在MVCC协议下,每个读操作会看到一个一致性的snapshot,并且可以实现非阻塞的读。MVCC允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务ID,在同一个时间点,不同的事务看到的数据是不同的。 2006 | * innodb会为每一行添加两个字段,分别表示该行创建的版本和删除的版本,填入的是事务的版本号,这个版本号随着事务的创建不断递增。在repeated read的隔离级别下,具体各种数据库操作的实现: 2007 | * select:满足以下两个条件innodb会返回该行数据:该行的创建版本号小于等于当前版本号,用于保证在select操作之前所有的操作已经执行落地。该行的删除版本号大于当前版本或者为空。删除版本号大于当前版本意味着有一个并发事务将该行删除了。 2008 | * insert:将新插入的行的创建版本号设置为当前系统的版本号。 2009 | * delete:将要删除的行的删除版本号设置为当前系统的版本号。 2010 | * update:不执行原地update,而是转换成insert + delete。将旧行的删除版本号设置为当前版本号,并将新行insert同时设置创建版本号为当前版本号。 2011 | * 其中,写操作(insert、delete和update)执行时,需要将系统版本号递增。由于旧数据并不真正的删除,所以必须对这些数据进行清理,innodb会开启一个后台线程执行清理工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge。通过MVCC很好的实现了事务的隔离性,可以达到repeated read级别,要实现serializable还必须加锁。 2012 | ##### 实践中如何优化 MySQL 2013 | * 实践中,MySQL 的优化主要涉及 SQL 语句的优化,索引的优化,数据表结构的优化。其他包括系统配置和硬件的优化。 2014 | ###### SQL 语句的优化 2015 | * 发现有问题的 SQL:MySQL 的慢查询日志是 MySQL 提供的一种日志记录,用来记录在 MySQL 中响应时间超过阀值的语句,具体指运行时间超过 long_query_time(long_query_time 的默认值为 10,意思是运行10s 以上的语句。) 值的 SQL,则会被记录到慢查询日志中。通过 MySQL 的慢查询日志可以查询出执行次数多、占用时间长的 SQL,用 pt_query_disgest(一种 mysql 慢日志分析工具) 分析 Rows examine(MySQL 执行器需要检查的行数) 项去找出 IO 大的 SQL 以及发现未命中索引的 SQL,对其进行优化。 2016 | * 分析 SQL 的执行计划:使用 EXPLAIN 关键字可以知道 MySQL 是如何处理 SQL 语句的,以便分析查询语句或是表结构的性能瓶颈。通过 explain 命令可以得到表的读取顺序、哪些索引被实际使用、表之间的引用以及每张表有多少行被优化器查询等问题。当扩展列 extra 出现 Using filesort 和 Using temporay,则往往表示SQL 需要优化了。具体参考[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) 2017 | * 优化 SQL 语句: 2018 | - 优化 insert 语句:一次插入多值 2019 | - 应尽量避免在 where 子句中使用!= 或 <> 操作符,则将引擎放弃使用索引而进行全表扫描; 2020 | - 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描; 2021 | - 优化嵌套查询:子查询可以被更有效率的连接 (Join)替代; 2022 | - 很多时候用 exists 代替 in 是一个好的选择。 2023 | ###### 索引的优化 2024 | * 适合创建索引的字段 2025 | - 经常作查询选择的字段 2026 | - 经常作表连接的字段 2027 | - 经常出现在 order by, group by, distinct 后面的字段 2028 | * 创建索引时需要注意什么 2029 | - 非空字段:应该指定列为 NOT NULL,除非你想存储NULL。在 MySQL 中含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。应该用 0、一个特殊的值或者一个空串代替空值; 2030 | - 取值离散大的字段(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过 count() 函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高; 2031 | - 索引字段越小越好:数据库的数据存储以页为单位,一页存储的数据越多,一次 IO 操作获取的数据越多,效率越高。 2032 | * 索引失效的情况 2033 | - 以 “%(表示任意 0 个或多个字符)” 开头的 LIKE 语句,模糊匹配; 2034 | - OR 语句前后没有同时使用索引; 2035 | - 数据类型出现隐式转化(如 varchar 不加单引号的话可能会自动转换为 int 型); 2036 | - 对于多列索引,必须满足 最左匹配原则 (eg:多列索引 col1、col2 和 col3,则 索引生效的情形包括 col1或 col1,col2 或 col1,col2,col3)。 2037 | ###### 数据表结构的优化 2038 | * 选择合适数据类型 2039 | - 使用较小的数据类型解决问题; 2040 | - 使用简单的数据类型 (mysql 处理 int 要比 varchar容易); 2041 | - 尽可能的使用 not null 定义字段; 2042 | - 尽量避免使用 text 类型,非用不可时最好考虑分表。 2043 | * 表的范式的优化,一般情况下表的设计应该遵循三大范式。 2044 | * 表的垂直拆分 2045 | - 把不常用的字段单独放在同一个表中; 2046 | - 把大字段独立放入一个表中; 2047 | - 把经常使用的字段放在一起; 2048 | * 表的水平拆分 2049 | - 表的水平拆分用于解决数据表中数据过大的问题,水平拆分每一个表的结构都是完全一致的。一般地,将数据平分到 N 张表中的常用方法。包括以下两种:对 ID 进行 hash 运算,如果要拆分成 5 个表,mod(id,5) 取出 0~4 个值;针对不同的 hashID 将数据存入不同的表中; 2050 | - 表分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,提高查询速度; 2051 | - 表中的数据本来就有独立性,例如表中分别记录各个地区的数据或不同时期的数据,特别是有些数据常用,而另外一些数据不常用。 2052 | ##### Redis 2053 | * 参考 [面试中关于Redis的问题看这篇就够了](https://blog.csdn.net/qq_34337272/article/details/80012284) 2054 | * Redis 是一个使用 C 语言写成的,开源的 key-value 数据库。和Memcached类似,但支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型),这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序,与memcached一样,为了保证效率,数据都是缓存在内存中,区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 2055 | * 适合存储在非关系型数据库中的数据:关系不是很密切的的数据,比如用户信息,班级信息,评论数量等等;量比较大的数据,如访问记录等;访问比较频繁的数据,如用户信息,访问数量,最新微博等。 2056 | ###### Redis数据类型 2057 | * string 常用命令: set,get,decr,incr,mget ,是最基本的数据类型,一个键对应一个值,需要注意是一个键值最大存储512MB。用于常规计数:微博数,粉丝数等。 2058 | * hash 常用命令:hget,hset,hgetall 等,redis hash是一个键值对的集合,是一个string类型的field和value的映射表,适用于存储用户信息,商品信息。 2059 | * List 常用命令: lpush,rpush,lpop,rpop,lrange等,是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,最新消息排行等功能都可以用Redis的list结构来实现。Redis list的实现为一个双向链表,可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 2060 | * Set常用命令:sadd,spop,smembers,sunion 等,对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动去重的,并且set提供了判断某个成员是否在一个set集合内的重要接口。在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同喜好、二度好友等功能。 2061 | * Sorted Set常用命令: zadd,zrange,zrem,zcard 等,和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按 score 进行有序排列。(直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用Redis中的SortedSet结构进行存储。) 2062 | ###### Redis的应用场景 2063 | * 访问热点数据,减少响应时间,提升吞吐量 2064 | * 会话缓存(最常用) 2065 | * 消息队列,比如支付 2066 | * 活动排行榜或计数 2067 | * 发布,订阅消息(消息通知) 2068 | * 商品列表,评论列表等 2069 | ###### Redis 持久化 2070 | * 参考[redis的持久化和缓存机制](https://blog.csdn.net/tr1912/article/details/70197085?foxhandler=RssReadRenderProcessHandler) 2071 | * 快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。可以配置 redis在 n 秒内如果超过 m 个 key 被修改就自动做快照,下面是默认的快照保存配置: 2072 | ``` 2073 | 保存 900 1 # 900秒内如果超过1个Key被修改,则启动快照保存 2074 | 保存300 10 # 300秒内如果超过10个Key被修改,则启动快照保存 2075 | 保存60 10000 # 60秒内如果超过10000个Key被修改,则启动快照保存 2076 | ``` 2077 | * aof 比快照方式有更好的持久化性,是由于在使用 aof 持久化方式时,redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是 appendonly.aof)。当 redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容,AOF持久化存储方式参数说明 2078 | ``` 2079 | appendonly yes # 开启AOF持久化存储方式 2080 | appendfsync always # 收到写命令后就立即写入磁盘,效率最差,效果最好 2081 | appendfsync everysec # 每秒写入磁盘一次,效率与效果居中 2082 | appendfsync no # 完全依赖操作系统,效率最佳,效果没法保证 2083 | ``` 2084 | ###### 缓存使用过程中的坑 2085 | * 引用自 [缓存穿透,缓存击穿,缓存雪崩解决方案分析](https://blog.csdn.net/zeb_perfect/article/details/54135506) 2086 | * 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。解决方案:如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 2087 | * 缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。解决方案:将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。 2088 | * 缓存击穿,对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。 2089 | ###### 其他问题 2090 | * 高并发场景下数据重复插入的问题 2091 | * 使用关系型数据库的唯一索引 2092 | * Redis 实现分布式锁: 2093 | * Redis常见性能问题和解决方案 2094 | * Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 2095 | * 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 2096 | * 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 2097 | * 尽量避免在压力很大的主库上增加从库 2098 | ### 最佳实践 2099 | * [Phodal的全栈增长工程师实战](http://growth-in-action.phodal.com/) 2100 | ![](./img/web7.jpeg) 2101 | #### 编码规范 2102 | * PEP8 2103 | * Python 之禅 2104 | * 了解 dosctring 2105 | * 了解类型注解 2106 | * Python 对象的命名规范,例如方法或者类 2107 | * Python 中的注释 2108 | * 优雅的给一个函数加注释 2109 | * 给变量加注释 2110 | * Python 代码缩进中是否支持 Tab 键和空格混用 2111 | * 是否可以在一句 import 中导入多个库 2112 | * 在给 Py 文件命名的时候需要注意什么 2113 | * 例举几个规范 Python 代码风格的工具 2114 | #### [正确的流程开发](https://www.zhihu.com/question/300762444/answer/529335326?app=zhihulite&utm_source=zhihu&sign=MTU2MDA2NzMzMzYzNw==&utm_medium=social&utm_oi=758575502888824800&invite_code=7JV2EK) 2115 | 1. 用GitHub或类似的现代平台 2116 | 2. 平台上设置禁止直接push到主干,所有的修改必须fork后走Pull Request 2117 | 3. 启用CI(持续集成),提PR时平台自动执行CI步骤,失败的不能被合并(不准开任何后门) 2118 | 4. CI加入linter,确保代码规范;所有代码规范必须要可由linter检测,代码规范/linter配置规则也要针对实践中发现的问题不断补充细化和更新 2119 | 5. CI加入单元测试,代码的测试覆盖率至少60%以上,核心模块测试覆盖率必须90%以上;所有发现的bug必须由造成bug的人负责补上单元测试 2120 | 6. 每个PR强制要求改动代码行数小于100行,新人要求小于60行,以利code review 2121 | 7. 每个PR在CI通过后必须有其他人进行过code review并approve,否则不能被merge,新人的代码必须至少有两人review和approve(比如新人的mentor和相关代码文件或目录的owner) 2122 | 8. 针对每个PR自动部署一份到测试环境,方便自测或提供给测试团队进行必要的测试 2123 | 9. 每2周检查近期bug,总结经验教训,特别是重复犯的错误一定要建立机制去防范 2124 | #### 单元测试 2125 | * 单元测试可以防止回归的时候出现问题,一个函数,一个类的逐次开始验证,自底向上测试,易测试的代码往往是高内聚低耦合的。 2126 | * [Django测试](https://docs.djangoproject.com/zh-hans/2.2/intro/tutorial05/) 2127 | * 单元测试相关的库 2128 | * nose/pytest较为常用 2129 | * mock模块用来模拟替换网络请求 2130 | * coverage 统计测试覆盖率 2131 | * 测试用例设计原则: 2132 | * 正常值功能测试 2133 | * 边界值(最大最小,或者最左最右) 2134 | * 异常值(none,空值,非法值) 2135 | #### CI/CD工具篇 2136 | * [谁才是世界上最好的 CI/CD 工具?](https://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650721534&idx=1&sn=1db6a5f66ee4d6b3d336c082ed04f1e0&chksm=bea6bd2d89d1343be60df41638d0b2992684b1a140d15ee25ac332ab2cbd9708a0fb675fe90a&mpshare=1&scene=1&srcid=#rd) 2137 | #### Vim 编辑器 2138 | ![Vim](./img/vim.png) 2139 | #### [Git飞行规则(Flight Rules)](https://github.com/k88hudson/git-flight-rules/blob/master/README_zh-CN.md) 2140 | #### Docker 篇 2141 | * [用Docker部署一个Web应用](https://zhuanlan.zhihu.com/p/26418829) 2142 | ### Web 扩展 2143 | #### Web安全篇 2144 | ##### DDos 攻击 2145 | * 客户端向服务端发送请求链接数据包,服务端向客户端发送确认数据包,客户端不向服务端发送确认数据包,服务器一直等待来自客户端的确认。 2146 | ##### XSS 攻击 2147 | * XSS 是指恶意攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些脚本代码嵌入到 web 页面中去,使别的用户访问都会执行相应的嵌入代码,从而盗取用户资料、利用用户身份进行某种动作或者对访问者进行病毒侵害的一种攻击方式。 2148 | * 主要原因:过于信任客户端提交的数据!解决办法:不信任任何客户端提交的数据,只要是客户 2149 | 端提交的数据就应该先进行相应的过滤处理然后方可进行下一步的操作。 2150 | * 反射性 XSS 攻击 (非持久性 XSS 攻击)和持久性 XSS 攻击 (留言板场景) 2151 | ##### SQL 注入攻击 2152 | * SQL 注入就是通过把 SQL 命令插入到 Web 表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令。 2153 | * SQL 注入攻击的总体思路:寻找到 SQL 注入的位置,判断服务器类型和后台数据库类型,针对不同的服务器和数据库特点进行 SQL 注入攻击。 2154 | ##### 加密 2155 | * 对称密钥加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即如何安全地将密钥发给对方;而非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。 2156 | * 由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,它非常的慢,所以我们还是要用对称加密来传送消息,但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。 2157 | * [用于消息验证的hash算法:HMAC](https://www.biaodianfu.com/hmac.html) 2158 | ![](https://b2.bmp.ovh/imgs/2019/08/a94b2ab7387fe433.png) 2159 | #### 正则表达式篇 2160 | * [可能是最好的正则表达式的教程笔记了吧...](https://juejin.im/post/5b5db5b8e51d4519155720d2) 2161 | ### 其他 2162 | * [优质 Python 播客推荐](https://mp.weixin.qq.com/s?__biz=MzUyOTk2MTcwNg==&mid=2247484132&idx=1&sn=fd88c8c3b993eedb2f5b0b62052476f2&chksm=fa584561cd2fcc777f2c4a268a0dc56bf2df1cdd5242d124ba50285b56e1d37834b620006cdb&mpshare=1&scene=1&srcid=#rd) 2163 | * [PYCON CHINA 2019](https://cn.pycon.org/) 2164 | * [JetBrains IDE 基本快捷键](https://nextfe.com/jetbrains-ide-shortcuts/) 2165 | -------------------------------------------------------------------------------- /README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/README.pdf -------------------------------------------------------------------------------- /img/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img1.png -------------------------------------------------------------------------------- /img/img10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img10.png -------------------------------------------------------------------------------- /img/img11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img11.png -------------------------------------------------------------------------------- /img/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img2.png -------------------------------------------------------------------------------- /img/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img3.png -------------------------------------------------------------------------------- /img/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img4.png -------------------------------------------------------------------------------- /img/img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img5.png -------------------------------------------------------------------------------- /img/img6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img6.jpg -------------------------------------------------------------------------------- /img/img7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img7.png -------------------------------------------------------------------------------- /img/img8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img8.png -------------------------------------------------------------------------------- /img/img9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/img9.png -------------------------------------------------------------------------------- /img/vim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/vim.png -------------------------------------------------------------------------------- /img/web7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/img/web7.jpeg -------------------------------------------------------------------------------- /process-parallel/process.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | class MyProcess(multiprocessing.Process): 4 | def run(self): 5 | print('called run method in process:%s'%self.name) 6 | 7 | if __name__ == "__main__": 8 | jobs=[] 9 | for i in range(5): 10 | p=MyProcess() 11 | jobs.append(p) 12 | p.start() 13 | p.join() -------------------------------------------------------------------------------- /process-parallel/processPool.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Pool, cpu_count 2 | import os 3 | import time 4 | def long_time_task(i): 5 | print('子进程: {} - 任务{}'.format(os.getpid(), i)) 6 | time.sleep(2) 7 | print("结果: {}".format(8 ** 20)) 8 | 9 | 10 | if __name__=='__main__': 11 | print("CPU内核数:{}".format(cpu_count())) 12 | print('当前母进程: {}'.format(os.getpid())) 13 | start = time.time() 14 | p = Pool(4) 15 | for i in range(5): 16 | p.apply_async(long_time_task, args=(i,)) 17 | print('等待所有子进程完成。') 18 | p.close() 19 | p.join() 20 | end = time.time() 21 | print("总共用时{}秒".format((end - start))) -------------------------------------------------------------------------------- /process-parallel/process_Manager.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | def worker(dictionary, key, item): 4 | dictionary[key] = item 5 | print("key = %d value = %d" % (key, item)) 6 | 7 | if __name__ == '__main__': 8 | mgr = multiprocessing.Manager() 9 | dictionary = mgr.dict() 10 | jobs = [multiprocessing.Process(target=worker, args=(dictionary, i, i*2)) for i in range(4)] 11 | for j in jobs: 12 | j.start() 13 | for j in jobs: 14 | j.join() 15 | print('Results:', dictionary) -------------------------------------------------------------------------------- /process-parallel/process_pipe.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | def create_items(pipe): 4 | output_pipe, _ = pipe 5 | for item in range(4): 6 | output_pipe.send(item) 7 | output_pipe.close() 8 | 9 | def multiply_items(pipe_1, pipe_2): 10 | close, input_pipe = pipe_1 11 | close.close() 12 | output_pipe, _ = pipe_2 13 | try: 14 | while True: 15 | item = input_pipe.recv() 16 | output_pipe.send(item * item) 17 | except EOFError: 18 | output_pipe.close() 19 | 20 | if __name__== '__main__': 21 | # 第一个进程管道发出数字 22 | pipe_1 = multiprocessing.Pipe(True) 23 | process_pipe_1 = multiprocessing.Process(target=create_items, args=(pipe_1,)) 24 | process_pipe_1.start() 25 | # 第二个进程管道接收数字并计算 26 | pipe_2 = multiprocessing.Pipe(True) 27 | process_pipe_2 = multiprocessing.Process(target=multiply_items, args=(pipe_1, pipe_2,)) 28 | process_pipe_2.start() 29 | pipe_1[0].close() 30 | pipe_2[0].close() 31 | try: 32 | while True: 33 | print(pipe_2[1].recv()) 34 | except EOFError: 35 | print("End") -------------------------------------------------------------------------------- /process-parallel/process_queue.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import random 3 | import time 4 | 5 | class Producer(multiprocessing.Process): 6 | def __init__(self, queue): 7 | super().__init__() 8 | self.queue = queue 9 | 10 | def run(self): 11 | for i in range(3): 12 | item = random.randint(0, 256) 13 | self.queue.put(item) 14 | print("Process Producer : item %d appended to queue %s" % (item, self.name)) 15 | time.sleep(1) 16 | print("The size of queue is %s" % self.queue.qsize()) 17 | 18 | class Consumer(multiprocessing.Process): 19 | def __init__(self, queue): 20 | super().__init__() 21 | self.queue = queue 22 | 23 | def run(self): 24 | while True: 25 | if self.queue.empty(): 26 | print("the queue is empty") 27 | break 28 | else: 29 | time.sleep(2) 30 | item = self.queue.get() 31 | print('Process Consumer : item %d popped from by %s \n' % (item, self.name)) 32 | time.sleep(1) 33 | 34 | if __name__ == '__main__': 35 | queue = multiprocessing.Queue() 36 | process_producer = Producer(queue) 37 | process_consumer = Consumer(queue) 38 | process_producer.start() 39 | process_consumer.start() 40 | process_producer.join() 41 | process_consumer.join() -------------------------------------------------------------------------------- /server-program/mult-client.py: -------------------------------------------------------------------------------- 1 | # mult-client.py 2 | import sys 3 | import socket 4 | import selectors 5 | import types 6 | 7 | sel = selectors.DefaultSelector() 8 | messages = [b"Message 1 from client.", b"Message 2 from client."] 9 | 10 | 11 | def start_connections(host, port, num_conns): 12 | server_addr = (host, port) 13 | for i in range(0, num_conns): 14 | connid = i + 1 15 | print("starting connection", connid, "to", server_addr) 16 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | sock.setblocking(False) 18 | sock.connect_ex(server_addr) 19 | events = selectors.EVENT_READ | selectors.EVENT_WRITE 20 | data = types.SimpleNamespace( 21 | connid=connid, 22 | msg_total=sum(len(m) for m in messages), 23 | recv_total=0, 24 | messages=list(messages), 25 | outb=b"", 26 | ) 27 | sel.register(sock, events, data=data) 28 | 29 | 30 | def service_connection(key, mask): 31 | sock = key.fileobj 32 | data = key.data 33 | if mask & selectors.EVENT_READ: 34 | recv_data = sock.recv(1024) # Should be ready to read 35 | if recv_data: 36 | print("received", repr(recv_data), "from connection", data.connid) 37 | data.recv_total += len(recv_data) 38 | if not recv_data or data.recv_total == data.msg_total: 39 | print("closing connection", data.connid) 40 | sel.unregister(sock) 41 | sock.close() 42 | if mask & selectors.EVENT_WRITE: 43 | if not data.outb and data.messages: 44 | data.outb = data.messages.pop(0) 45 | if data.outb: 46 | print("sending", repr(data.outb), "to connection", data.connid) 47 | sent = sock.send(data.outb) # Should be ready to write 48 | data.outb = data.outb[sent:] 49 | 50 | 51 | if len(sys.argv) != 4: 52 | print("usage:", sys.argv[0], " ") 53 | sys.exit(1) 54 | 55 | host, port, num_conns = sys.argv[1:4] 56 | start_connections(host, int(port), int(num_conns)) 57 | 58 | try: 59 | while True: 60 | events = sel.select(timeout=1) 61 | if events: 62 | for key, mask in events: 63 | service_connection(key, mask) 64 | # Check for a socket being monitored to continue. 65 | if not sel.get_map(): 66 | break 67 | except KeyboardInterrupt: 68 | print("caught keyboard interrupt, exiting") 69 | finally: 70 | sel.close() 71 | -------------------------------------------------------------------------------- /server-program/mult-server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | import selectors 4 | import types 5 | 6 | sel = selectors.DefaultSelector() 7 | 8 | 9 | def accept_wrapper(sock): 10 | conn, addr = sock.accept() # Should be ready to read 11 | print("accepted connection from", addr) 12 | # 调用 sock.accept() 后立即再立即调 conn.setblocking(False) 来让 socket 进入非阻塞模式 13 | conn.setblocking(False) 14 | # 使用了 types.SimpleNamespace 类创建了一个对象用来保存我们想要的 socket 和数据 15 | data = types.SimpleNamespace(addr=addr, inb=b"", outb=b"") 16 | events = selectors.EVENT_READ | selectors.EVENT_WRITE 17 | sel.register(conn, events, data=data) 18 | 19 | def service_connection(key, mask): 20 | # key 包含了 socket 对象「fileobj」和数据对象; 21 | sock = key.fileobj 22 | data = key.data 23 | # mask 包含了就绪的事件 24 | if mask & selectors.EVENT_READ: 25 | ''' 26 | 如果 socket 就绪而且可以被读取,mask & selectors.EVENT_READ 就为真,sock.recv() 会被调用。 27 | 所有读取到的数据都会被追加到 data.outb 里面 28 | ''' 29 | recv_data = sock.recv(1024) # Should be ready to read 30 | if recv_data: 31 | data.outb += recv_data 32 | else: 33 | print("closing connection to", data.addr) 34 | sel.unregister(sock) 35 | sock.close() 36 | if mask & selectors.EVENT_WRITE: 37 | if data.outb: 38 | print("echoing", repr(data.outb), "to", data.addr) 39 | # 任何接收并被 data.outb 存储的数据都将使用 sock.send() 方法打印出来 40 | sent = sock.send(data.outb) 41 | data.outb = data.outb[sent:] 42 | 43 | 44 | if len(sys.argv) != 3: 45 | print("usage:", sys.argv[0], " ") 46 | sys.exit(1) 47 | 48 | host, port = sys.argv[1], int(sys.argv[2]) 49 | lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 50 | lsock.bind((host, port)) 51 | lsock.listen() 52 | print("listening on", (host, port)) 53 | lsock.setblocking(False) # 配置 socket 为非阻塞模式 54 | # data 来跟踪 socket 上发送或者接收的东西 55 | sel.register(lsock, selectors.EVENT_READ, data=None) 56 | 57 | try: 58 | while True: 59 | # 事件循环,不断请求 socket 状态,并调用对应的回调函数 60 | events = sel.select(timeout=None) # 阻塞直到 socket I/O 就绪 61 | """ 62 | sel.select(timeout=None)返回一个(key, events) 元组; 63 | key 包含了 socket 对象「fileobj」和数据对象; 64 | mask 包含了就绪的事件 65 | """ 66 | for key, mask in events: 67 | # 新的客户端, 利用accept_wrapper()来接受新的 socket 对象并注册到 selector 上 68 | if key.data is None: 69 | accept_wrapper(key.fileobj) #key.fileobj=socket对象 70 | # 如果 key.data 不为空,旧的被接受的客户端,我们需要为它服务,接着 service_connection() 会传入 key 和 mask 参数并调用 71 | else: 72 | service_connection(key, mask) 73 | except KeyboardInterrupt: 74 | print("caught keyboard interrupt, exiting") 75 | finally: 76 | sel.close() -------------------------------------------------------------------------------- /thread-parallel/GIL.py: -------------------------------------------------------------------------------- 1 | import threading 2 | lock=threading.Lock() 3 | n=[0] 4 | def demo(): 5 | with lock: 6 | n[0]=n[0]+1 7 | threads=[] 8 | for i in range(5000): 9 | t=threading.Thread(target=demo) 10 | threads.append(t) 11 | for t in threads: 12 | t.start() 13 | print(n) -------------------------------------------------------------------------------- /thread-parallel/thread-status-change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morsoli/python-interview-guide/6bbc330af9efb2cfbed958f1bf5898df91c12644/thread-parallel/thread-status-change.png -------------------------------------------------------------------------------- /thread-parallel/thread.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | # 简单程序的多线程编程 4 | """ def get_html(): 5 | print('get html start') 6 | time.sleep(2) 7 | print('get html end') 8 | def get_url(): 9 | print('get url start') 10 | time.sleep(4) 11 | print('get url end') """ 12 | 13 | # 复杂程序的多线程编程 14 | class GetHtml(threading.Thread): 15 | def __init__(self,name): 16 | super().__init__(name=name) 17 | def run(self): 18 | print('get html start') 19 | time.sleep(2) 20 | print('get html end') 21 | class GetUrl(threading.Thread): 22 | def __init__(self,name): 23 | super().__init__(name=name) 24 | def run(self): 25 | print('get url start') 26 | time.sleep(4) 27 | print('get url end') 28 | if __name__ == "__main__": 29 | # thread1=threading.Thread(target=get_html) 30 | # thread2=threading.Thread(target=get_url) 31 | thread1=GetHtml(name='html') 32 | thread2=GetUrl(name='url') 33 | start_time=time.time() 34 | thread1.start() 35 | thread2.start() 36 | thread1.join() 37 | thread2.join() 38 | end_time=time.time() 39 | total_time=end_time-start_time 40 | print('total running tim:{}'.format(total_time)) -------------------------------------------------------------------------------- /thread-parallel/threadPool.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | import urllib.request 3 | 4 | URLS = ['https://www.baidu.com/', 5 | 'https://www.yimian.com.cn', 6 | 'https://app.mokahr.com', 7 | 'http://www.zhangyue.com', 8 | 'http://www.hexinedu.com/'] 9 | 10 | def load_url(url, timeout): 11 | with urllib.request.urlopen(url, timeout=timeout) as conn: 12 | return conn.read() 13 | 14 | with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: 15 | future_to_url = {executor.submit(load_url, url, 60): url for url in URLS} 16 | for future in concurrent.futures.as_completed(future_to_url): 17 | url = future_to_url[future] 18 | try: 19 | data = future.result() 20 | except Exception as exc: 21 | print('%r generated an exception: %s' % (url, exc)) 22 | else: 23 | print('%r page is %d bytes' % (url, len(data))) -------------------------------------------------------------------------------- /thread-parallel/thread_Event.py: -------------------------------------------------------------------------------- 1 | import time 2 | from threading import Thread, Event 3 | import random 4 | items = [] 5 | event = Event() 6 | class consumer(Thread): 7 | def __init__(self, items, event): 8 | Thread.__init__(self) 9 | self.items = items 10 | self.event = event 11 | 12 | def run(self): 13 | while True: 14 | time.sleep(2) 15 | self.event.wait() 16 | item = self.items.pop() 17 | print('Consumer notify : %d popped from list by %s' % (item, self.name)) 18 | 19 | class producer(Thread): 20 | def __init__(self, integers, event): 21 | Thread.__init__(self) 22 | self.items = items 23 | self.event = event 24 | 25 | def run(self): 26 | global item 27 | for i in range(3): 28 | time.sleep(2) 29 | item = random.randint(0, 256) 30 | self.items.append(item) 31 | print('Producer notify : item N° %d appended to list by %s' % (item, self.name)) 32 | print('Producer notify : event set by %s' % self.name) 33 | self.event.set() 34 | print('Produce notify : event cleared by %s '% self.name) 35 | self.event.clear() 36 | 37 | if __name__ == '__main__': 38 | t1 = producer(items, event) 39 | t2 = consumer(items, event) 40 | t1.start() 41 | t2.start() 42 | t1.join() 43 | t2.join() -------------------------------------------------------------------------------- /thread-parallel/thread_Lock.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | shared_resource_with_lock = 0 4 | shared_resource_with_no_lock = 0 5 | COUNT = 500000 6 | shared_resource_lock = threading.Lock() 7 | 8 | # 有锁的情况 9 | def increment_with_lock(): 10 | global shared_resource_with_lock 11 | for i in range(COUNT): 12 | shared_resource_lock.acquire() 13 | shared_resource_with_lock += 1 14 | shared_resource_lock.release() 15 | 16 | def decrement_with_lock(): 17 | global shared_resource_with_lock 18 | for i in range(COUNT): 19 | shared_resource_lock.acquire() 20 | shared_resource_with_lock -= 1 21 | shared_resource_lock.release() 22 | 23 | # 没有锁的情况 24 | def increment_without_lock(): 25 | global shared_resource_with_no_lock 26 | for i in range(COUNT): 27 | shared_resource_with_no_lock += 1 28 | 29 | def decrement_without_lock(): 30 | global shared_resource_with_no_lock 31 | for i in range(COUNT): 32 | shared_resource_with_no_lock -= 1 33 | 34 | if __name__ == "__main__": 35 | t1 = threading.Thread(target=increment_with_lock) 36 | t2 = threading.Thread(target=decrement_with_lock) 37 | t3 = threading.Thread(target=increment_without_lock) 38 | t4 = threading.Thread(target=decrement_without_lock) 39 | t1.start() 40 | t2.start() 41 | t3.start() 42 | t4.start() 43 | t1.join() 44 | t2.join() 45 | t3.join() 46 | t4.join() 47 | print ("the value of shared variable with lock management is %s" % shared_resource_with_lock) 48 | print ("the value of shared variable with race condition is %s" % shared_resource_with_no_lock) -------------------------------------------------------------------------------- /thread-parallel/thread_Semaphare.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | import random 4 | semaphare=threading.Semaphore(0) 5 | def producer(): 6 | global item 7 | time.sleep(2) 8 | item=random.randint(0,1000) 9 | print('Producer notify: produced item number %s'%item) 10 | semaphare.release() 11 | def consumer(): 12 | print('comsumer is waiting') 13 | semaphare.acquire() 14 | print('Consumer notify: consumed item number %s'%item) 15 | 16 | if __name__ == "__main__": 17 | for i in range(5): 18 | t1=threading.Thread(target=producer) 19 | t2=threading.Thread(target=consumer) 20 | t1.start() 21 | t2.start() 22 | t1.join() 23 | t2.join() 24 | print('end!!!') -------------------------------------------------------------------------------- /thread-parallel/thread_queue.py: -------------------------------------------------------------------------------- 1 | from threading import Thread, Event 2 | from queue import Queue 3 | import time 4 | import random 5 | class producer(Thread): 6 | def __init__(self, queue): 7 | Thread.__init__(self) 8 | self.queue = queue 9 | 10 | def run(self) : 11 | for i in range(10): 12 | item = random.randint(0, 256) 13 | self.queue.put(item) 14 | print('Producer notify: item N° %d appended to queue by %s' % (item, self.name)) 15 | time.sleep(1) 16 | 17 | class consumer(Thread): 18 | def __init__(self, queue): 19 | Thread.__init__(self) 20 | self.queue = queue 21 | 22 | def run(self): 23 | while True: 24 | item = self.queue.get() 25 | print('Consumer notify : %d popped from queue by %s' % (item, self.name)) 26 | self.queue.task_done() 27 | 28 | if __name__ == '__main__': 29 | queue = Queue() 30 | t1 = producer(queue) 31 | t2 = consumer(queue) 32 | t3 = consumer(queue) 33 | t4 = consumer(queue) 34 | t1.start() 35 | t2.start() 36 | t3.start() 37 | t4.start() 38 | t1.join() 39 | t2.join() 40 | t3.join() 41 | t4.join() -------------------------------------------------------------------------------- /精选 TOP 面试题/1.两数之和.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=1 lang=python3 3 | # 4 | # [1] 两数之和 5 | # 6 | class Solution: 7 | def twoSum(self, nums: List[int], target: int) -> List[int]: 8 | hash_map={} 9 | for i,value in enumerate(nums): 10 | if target-value in hash_map: 11 | return [i,hash_map[target-value]] 12 | hash_map[value]=i 13 | # 解题思路: 14 | # 这里采用一遍hash的方式:新建一个空的字典,然后遍历数组,如果target-x在字典里面,则返回x和target-x的索引值, 15 | # 如果不在则将该数值加入字典中。 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /精选 TOP 面试题/10.正则表达式匹配.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=10 lang=python3 3 | # 4 | # [10] 正则表达式匹配 5 | # 6 | class Solution: 7 | def isMatch(self, s: str, p: str) -> bool: 8 | import re 9 | return re.match('^'+p+'$',s)!=None 10 | 11 | -------------------------------------------------------------------------------- /精选 TOP 面试题/11.盛最多水的容器.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=11 lang=python3 3 | # 4 | # [11] 盛最多水的容器 5 | # 6 | class Solution: 7 | def maxArea(self, height: List[int]) -> int: 8 | if height==[]: 9 | return 0 10 | area=0 11 | # 定义两个指针 12 | p1=0 13 | p2=len(height)-1 14 | while p1 int: 8 | result=0 9 | chars={'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000} 10 | for i in range(len(s)-1): 11 | # 在左边的情况 12 | if chars[s[i]] str: 8 | result='' 9 | zip_obj=zip(*strs) 10 | for obj in zip_obj: 11 | if(len(set(obj)))==1: 12 | result+=obj[0] 13 | else: 14 | break 15 | return result 16 | -------------------------------------------------------------------------------- /精选 TOP 面试题/15.三数之和.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=15 lang=python3 3 | # 4 | # [15] 三数之和 5 | # 6 | class Solution: 7 | def threeSum(self, nums: List[int]) -> List[List[int]]: 8 | nums.sort() 9 | res = [] 10 | # 排序后遍历 11 | for t in range(len(nums)- 2): 12 | # 跳过相同的情况 13 | if t > 0 and nums[t] == nums[t - 1]: 14 | continue 15 | # i向后判断,j向前判断 16 | i, j = t + 1, len(nums) - 1 17 | while i < j: 18 | _sum = nums[t] + nums[i] + nums[j] 19 | if _sum == 0: 20 | res.append([nums[t], nums[i], nums[j]]) 21 | i += 1 22 | j -= 1 23 | # 跳过相同的情况 24 | while i < j and nums[i] == nums[i - 1]: 25 | i += 1 26 | while i < j and nums[j] == nums[j + 1]: 27 | j -= 1 28 | elif _sum < 0: 29 | i += 1 30 | else: 31 | j -= 1 32 | return res 33 | -------------------------------------------------------------------------------- /精选 TOP 面试题/17.电话号码的字母组合.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=17 lang=python3 3 | # 4 | # [17] 电话号码的字母组合 5 | # 6 | class Solution: 7 | def letterCombinations(self, digits: str) -> List[str]: 8 | if digits=='': 9 | return [] 10 | kvmaps = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl', '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'} 11 | result = [''] 12 | for i in range(len(digits)): 13 | # 使用结果数组表示遍历到当前的位置已有的结果,那么再遍历下一个位置的时候, 14 | # 把这个位置能形成的所有结果和原来的进行两两组合。 15 | result=[c+w for w in kvmaps[digits[i]] for c in result] 16 | return result 17 | -------------------------------------------------------------------------------- /精选 TOP 面试题/19.删除链表的倒数第n个节点.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=19 lang=python3 3 | # 4 | # [19] 删除链表的倒数第N个节点 5 | # 6 | # Definition for singly-linked list. 7 | # class ListNode: 8 | # def __init__(self, x): 9 | # self.val = x 10 | # self.next = None 11 | 12 | class Solution: 13 | def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: 14 | second = fist = head 15 | for i in range(n): # 第一个指针先走 n 步 16 | fist = fist.next 17 | 18 | if fist == None: # 如果现在第一个指针已经到头了,那么第一个结点就是要删除的结点。 19 | return second.next 20 | 21 | while fist.next: # 然后同时走,直到第一个指针走到头 22 | fist = fist.next 23 | second = second.next 24 | second.next = second.next.next # 删除相应的结点 25 | return head 26 | -------------------------------------------------------------------------------- /精选 TOP 面试题/2.两数相加.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=2 lang=python3 3 | # 4 | # [2] 两数相加 5 | # 6 | # Definition for singly-linked list. 7 | # class ListNode: 8 | # def __init__(self, x): 9 | # self.val = x 10 | # self.next = None 11 | 12 | class Solution: 13 | def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: 14 | flag=0 15 | root=n=ListNode(0) 16 | while l1 or l2 or flag: 17 | v1=v2=0 18 | if l1: 19 | v1=l1.val 20 | l1=l1.next 21 | if l2: 22 | v2=l2.val 23 | l2=l2.next 24 | flag,val=divmod(v1+v2+flag,10) 25 | n.next=ListNode(val) 26 | n=n.next 27 | return root.next 28 | 29 | -------------------------------------------------------------------------------- /精选 TOP 面试题/20.有效的括号.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=20 lang=python3 3 | # 4 | # [20] 有效的括号 5 | # 6 | class Solution: 7 | def isValid(self, s: str) -> bool: 8 | if len(s)%2==1: 9 | return False 10 | map_dict={'{':'}','[':']','(':')'} 11 | stack=[] 12 | for i in s: 13 | # 左半边字符全入栈 14 | if i in map_dict: 15 | stack.append(i) 16 | else: 17 | # 第一个字符为右边符号或者一出现不匹配就直接返回false 18 | if not stack or map_dict[stack.pop()]!=i: 19 | return False 20 | return stack==[] 21 | -------------------------------------------------------------------------------- /精选 TOP 面试题/21.合并两个有序链表.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=21 lang=python3 3 | # 4 | # [21] 合并两个有序链表 5 | # 6 | # Definition for singly-linked list. 7 | # class ListNode: 8 | # def __init__(self, x): 9 | # self.val = x 10 | # self.next = None 11 | 12 | class Solution: 13 | def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: 14 | if not l1 or not l2: 15 | return l1 or l2 16 | head=cur=ListNode(0) 17 | while l1 and l2: 18 | if l1.val List[str]: 8 | res=[] 9 | self.dfs(res,n,n,'') 10 | return res 11 | def dfs(self,res,left,right,path): 12 | if left==0 and right==0: 13 | res.append(path) 14 | return 15 | if left>0: 16 | self.dfs(res,left-1,right,path+'(') 17 | if left ListNode: 14 | k_heap=[] 15 | for i in lists: 16 | while i: 17 | k_heap.append(i.val) 18 | i=i.next 19 | if k_heap==[]: 20 | return [] 21 | k_heap.sort() 22 | head=cur=ListNode(0) 23 | while k_heap: 24 | cur.next=ListNode(k_heap.pop(0)) 25 | cur=cur.next 26 | return head.next 27 | -------------------------------------------------------------------------------- /精选 TOP 面试题/26.删除排序数组中的重复项.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=26 lang=python3 3 | # 4 | # [26] 删除排序数组中的重复项 5 | # 6 | class Solution: 7 | def removeDuplicates(self, nums: List[int]) -> int: 8 | curr_p=0 9 | while curr_p int: 8 | for i in range(len(haystack)-len(needle)+1): 9 | if haystack[i:i+len(needle)]==needle: 10 | return i 11 | return -1 12 | -------------------------------------------------------------------------------- /精选 TOP 面试题/3.无重复字符的最长子串.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=3 lang=python3 3 | # 4 | # [3] 无重复字符的最长子串 5 | # 6 | class Solution: 7 | def lengthOfLongestSubstring(self, s: str) -> int: 8 | max_str=0 9 | for i in range(len(s)): 10 | if len(s[i-max_str:i+1])==len(set(s[i-max_str:i+1])): 11 | max_str+=1 12 | return max_str 13 | # 解题思路:滑动窗口法 14 | 15 | 16 | -------------------------------------------------------------------------------- /精选 TOP 面试题/33.搜索旋转排序数组.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=33 lang=python3 3 | # 4 | # [33] 搜索旋转排序数组 5 | # 6 | class Solution: 7 | def search(self, nums: List[int], target: int) -> int: 8 | if not nums: return -1 9 | left, right = 0, len(nums) - 1 10 | while left <= right: 11 | mid = int((left + right) / 2) 12 | if nums[mid] == target: 13 | return mid 14 | # 如果A[m] nums[mid] and target <= nums[right]: 17 | left = mid + 1 18 | else: 19 | right = mid - 1 20 | else: 21 | if target < nums[mid] and target >= nums[left]: 22 | right = mid - 1 23 | else: 24 | left = mid + 1 25 | return -1 26 | # 解题思路:二分查找,重要的不是通过mid与左右指针指向的值的比较来移动指针, 27 | # 而是通过判断那一部分是有序的,target是否在这个有序的切片之中来实现的。 28 | -------------------------------------------------------------------------------- /精选 TOP 面试题/34.在排序数组中查找元素的第一个和最后一个位置.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=34 lang=python3 3 | # 4 | # [34] 在排序数组中查找元素的第一个和最后一个位置 5 | # 6 | class Solution: 7 | def searchRange(self, nums: List[int], target: int) -> List[int]: 8 | if not nums: 9 | return [-1,-1] 10 | left,right=0,len(nums)-1 11 | while left<=right: 12 | mid=(left+right)//2 13 | if nums[mid]target: 16 | right=mid-1 17 | else: 18 | # 从小区间向内探查,然后确定范围 19 | while nums[left]!=target: 20 | left+=1 21 | while nums[right]!=target: 22 | right-=1 23 | return [left,right] 24 | return [-1,-1] 25 | -------------------------------------------------------------------------------- /精选 TOP 面试题/36.有效的数独.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=36 lang=python3 3 | # 4 | # [36] 有效的数独 5 | # 6 | class Solution: 7 | def isValidSudoku(self, board: List[List[str]]) -> bool: 8 | # 元素以一个字典储存,key是数字,value统一为1 9 | dic_row=[{} for i in range(9)] 10 | dic_col=[{} for i in range(9)] 11 | dic_box=[{} for i in range(9)] 12 | for i in range(len(board)): 13 | for j in range (len(board)): 14 | num=board[i][j] 15 | if num=='.': 16 | continue 17 | if num not in dic_row[i] and num not in dic_col[j] and num not in dic_box[3*(i//3)+(j//3)]: 18 | dic_row[i][num]=1 19 | dic_col[j][num]=1 20 | # 利用地板除,向下取余。巧妙地将矩阵划分为九块 21 | dic_box[3*(i//3)+(j//3)][num]=1 22 | else: 23 | return False 24 | return True 25 | 26 | -------------------------------------------------------------------------------- /精选 TOP 面试题/38.报数.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=38 lang=python3 3 | # 4 | # [38] 报数 5 | # 6 | class Solution: 7 | def countAndSay(self, n: int) -> str: 8 | if n==1: 9 | return '1' 10 | res='1' 11 | while n>1: 12 | s,res,count=res,'',0 13 | for i in range(len(s)): 14 | count+=1 15 | if i==len(s)-1 or s[i]!=s[i+1]: 16 | res+=str(count) 17 | res+=s[i] 18 | count=0 19 | n-=1 20 | return res 21 | -------------------------------------------------------------------------------- /精选 TOP 面试题/4.寻找两个有序数组的中位数.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=4 lang=python3 3 | # 4 | # [4] 寻找两个有序数组的中位数 5 | # 6 | class Solution: 7 | def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: 8 | m,n=len(nums1),len(nums2) 9 | if m>n: 10 | nums1,nums2,m,n=nums2,nums1,n,m # 保持 n 始终大于 m 11 | if n==0: 12 | return None 13 | imin,imax,half_len=0,m,(n+m+1)/2 14 | while imin<=imax: 15 | # 确定 i j 两个值 16 | i=(imin+imax)/2 17 | j=half_len-i 18 | # i太小,应该增加 19 | if inums1[i]: 20 | imin=i+1 21 | # i太大,应该减小 22 | elif i>0 and nums1[i-1]>nums2[j]: 23 | imax=i-1 24 | # i 的值已经确定,现在找中间值 25 | else: 26 | # 确定左边界情况 27 | if i==0: 28 | max_of_left=nums2[j-1] 29 | elif j==0: 30 | max_of_left=nums1[i-1] 31 | else: 32 | max_of_left=max(nums1[i-1],nums2[j-1]) 33 | # 奇数情况下 34 | if (m+n)%2==1: 35 | return max_of_left 36 | # 确定右边界情况 37 | if i==m: 38 | min_of_right=nums2[j] 39 | elif j==n: 40 | min_of_right=nums1[i] 41 | else: 42 | min_of_right=min(nums1[i],nums2[j]) 43 | return (max_of_left+min_of_right)/2 44 | -------------------------------------------------------------------------------- /精选 TOP 面试题/41.缺失的第一个正数.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=41 lang=python3 3 | # 4 | # [41] 缺失的第一个正数 5 | # 6 | class Solution: 7 | def firstMissingPositive(self, nums: List[int]) -> int: 8 | for i in range(len(nums)): 9 | while nums[i]>0 and nums[i]<=len(nums) and nums[nums[i]-1]!=nums[i]: 10 | nums[nums[i]-1],nums[i] = nums[i],nums[nums[i]-1] 11 | for i in range(0,len(nums)): 12 | if nums[i]!=(i+1): 13 | return i+1 14 | return len(nums)+1 15 | # 解题思路:将每个数放在它正确的位置,前提是该数是正数,并且该数小于序列长度,并且它正确位置上的那个数不是它, 16 | # 也就是说,把4要放在第4个位置,要保证第4个位置上的数不是4,如果是4的话,交换前后没什么变换,把两个4移来移去,还会造成死循环。 17 | # 从前往后看,发现在第n个位置本该出现的n没有出现,所有该序列缺失的第一个正数是n。 18 | -------------------------------------------------------------------------------- /精选 TOP 面试题/42.接雨水.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=42 lang=python3 3 | # 4 | # [42] 接雨水 5 | # 6 | class Solution: 7 | def trap(self, height: List[int]) -> int: 8 | if not height: 9 | return 0 10 | left,right=0,len(height)-1 11 | # 用 left_max, right_max 记录走过的最高的边 12 | left_max=right_max=result=0 13 | while leftleft_max: 16 | left_max=height[left] 17 | else: 18 | result+=(left_max-height[left]) 19 | # 继续移动左指针 20 | left+=1 21 | else: 22 | if height[right]>right_max: 23 | right_max=height[right] 24 | else: 25 | result+=(right_max-height[right]) 26 | right-=1 27 | return result 28 | # 解题思路:https://blog.csdn.net/XX_123_1_RJ/article/details/81048041 29 | -------------------------------------------------------------------------------- /精选 TOP 面试题/46.全排列.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=46 lang=python3 3 | # 4 | # [46] 全排列 5 | # 6 | class Solution: 7 | def permute(self, nums: List[int]) -> List[List[int]]: 8 | visited = [0] * len(nums) 9 | res = [] 10 | def dfs(path): 11 | if len(path) == len(nums): 12 | res.append(path) 13 | else: 14 | for i in range(len(nums)): 15 | if not visited[i]: 16 | visited[i] = 1 17 | dfs(path + [nums[i]]) 18 | visited[i] = 0 19 | 20 | dfs([]) 21 | return res 22 | 23 | -------------------------------------------------------------------------------- /精选 TOP 面试题/5.最长回文子串.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=5 lang=python3 3 | # 4 | # [5] 最长回文子串 5 | # 6 | class Solution: 7 | def longestPalindrome(self, s: str) -> str: 8 | start=0 9 | maxstr=0 10 | for i in range(len(s)): 11 | # 头和尾相同时,最长回文子串是去头去尾之后的部分的最长回文子串加上头和尾 12 | if i-maxstr>=1 and s[i-maxstr-1:i+1]==s[i-maxstr-1:i+1][::-1]: 13 | start=i-maxstr-1 14 | maxstr+=2 15 | continue 16 | # 头和尾不同,最长回文子串是去头的部分的最长回文子串和去尾的部分的最长回文子串中的较长的那一个。 17 | if s[i-maxstr:i+1]==s[i-maxstr:i+1][::-1]: 18 | start=i-maxstr 19 | maxstr+=1 20 | return s[start:start+maxstr] 21 | -------------------------------------------------------------------------------- /精选 TOP 面试题/7.整数反转.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=7 lang=python3 3 | # 4 | # [7] 整数反转 5 | # 6 | class Solution: 7 | def reverse(self, x: int) -> int: 8 | if x>0: 9 | temp=int(str(x)[::-1]) 10 | else: 11 | temp=-int(str(-x)[::-1]) 12 | if temp<2**31-1 and temp>-2**31: 13 | return temp 14 | else: 15 | return 0 16 | -------------------------------------------------------------------------------- /精选 TOP 面试题/8.字符串转换整数-atoi.py: -------------------------------------------------------------------------------- 1 | # 2 | # @lc app=leetcode.cn id=8 lang=python3 3 | # 4 | # [8] 字符串转换整数 (atoi) 5 | # 6 | class Solution: 7 | def myAtoi(self, str: str) -> int: 8 | # 去除前面的空格 9 | str=str.strip() 10 | if len(str)==0: 11 | return 0 12 | # flag 正负标志, number 为返回整数 13 | number,flag=0,1 14 | if str[0]=='-': 15 | str=str[1:] 16 | flag=-1 17 | elif str[0]=='+': 18 | str=str[1:] 19 | for c in str: 20 | if c>='0' and c<='9': 21 | number=10*number+ord(c)-ord('0') 22 | else: 23 | break 24 | number=number*flag 25 | if number>2**31-1: 26 | return 2**31-1 27 | if number<-2**31: 28 | return -2**31 29 | return number 30 | -------------------------------------------------------------------------------- /计算机网络概述.md: -------------------------------------------------------------------------------- 1 | ### 计算机网络概述 2 | ![img1.png](./img/img1.png) 3 | 4 | * 我在知乎看到了一段非常不错的总结,就直接丢过来用了,感谢回答者花潇! 5 | 我们的因特网,肯定是基于物理电路的,因此,我们需要一个将数据转化为物理信号的层,于是,物理层诞生啦! 6 | 有了处理物理信号的物理层,可我们还得知道,信号发给谁啊,你肯定知道,每个主机都有一个全球唯一的MAC地址吧,所以我们可以通过MAC地址来寻址啊,恭喜你,链路层诞生了! 7 | 别急,你知道MAC地址是扁平化的吧,也就是说,MAC地址的空间分布,是无规律的!!!如果你有十万台主机,要通过MAC地址来寻址,简直无F**K可说。不管怎样说,这么大的数据量,我们需要有个解决办法所以我们引入IP地址,网络层应运而生然而! 8 | 一台主机不能只和一台服务器通信啊,毕竟下小电影,也要同时货比三家啊。那如何实现并行通信呢?嘿嘿,我们有端口号啊,基于不同的需求,产生了UDP&TCP,运输层也诞生啦! 9 | 别急,你知道的吧,不同应用、不同的传输需求,比如请求网页、发邮件什么的,为了方便开发者,我们对这些常用需求进行了封装,这样就有了应用层的诞生! 10 | 这算是自底而上的讲述了计网,由此我们可以更好地理解每层layer的含义 11 | #### 几个重要概念 12 | * **Packet Switching分组交换技术**(packet switching)也称包交换,是将用户传送的数据划分成一定的长度,每个部分叫做一个分组,通过传输分组的方式传输信息的一种技术。**Circuit Switching电路交换方式**(circuit switching)是指在同一电信网用户群中任意两个或多个用户终端之间建立电路暂时连接的交换方式。 13 | ![img1.png](./img/img2.png) 14 | 15 | * **时延**及四种时延的关系 : 16 | 结点时延=处理时延+排队时延( 丢包问题,平均速率α,分组长度L,发送速率R, αL/R≤1)+发送时延( 取决于分组长度与结点端口网卡的发送速率)+传播时延 17 | 18 | ![img1.png](./img/img3.png) 19 | 20 | * 数据通信系统5个组成部分:**报文 发送方 接受方 传输介质 协议** 21 | * 数据传输单元相应是:**message->segment->datagram->frame->bit** 22 | ### 物理层 23 | ### 数据链路层 24 | * 数据链路层设计的初衷就是顺利为网络层提供数据服务,不考虑可靠性,可靠性的部分由传输层的TCP协议实现为了使数据链路层能更好地适应多种局域网标准,802 委员会就将局域网的数据链路层拆成两个子层:逻辑链路控制 LLC (Logical Link Control)子层和介质访问控制 MAC (Medium Access Control)子层。 25 | * 数据链路层使用的信道主要有以下两种类型: 26 | - 广播信道(多用于LAN) 27 | - 点对点信道,点对点信道的数据链路层的协议数据单元为帧(frame),点对点信道的数据链路层在进行通信时的主要步骤: 28 | (1)结点A的数据链路层把网络层交下来的IP数据报添加首部和尾部封装成帧 29 | (2)结点A把封装好的帧发送给结点B的数据链路层。 30 | (3)若结点B的数据链路层收到的帧无差错,则从收到的帧中提取出IP数据报上交给上面的网络层;否则丢弃这个帧。 31 | * 以太网是链路层的协议,我们先来看看以太网的帧。 32 | ![img1.png](./img/img4.png) 33 | 34 | * **头部** 35 | > * 帧的最初7个byte被称为序言(preamble)。它的每个byte都是0xAA。通常,我们都会预定好以一定的频率发送0/1序列(比如每秒10bit)。如果接收设备以其他频率接收(比如每秒5bit),那么就会错漏掉应该接收的0/1信息。但是,由于网卡的不同,发送方和接收方即使预订的频率相同,两者也可能由于物理原因发生偏差。序言是为了让接收设备调整接收频率,以便与发送设备的频率一致,这个过程就叫做时钟复原(recover the clock)。 36 | >* 时钟调整好之后,我们等待帧的起始信号(SFD, start frame delimiter)。SFD是固定的值0xAB。 37 | > * 紧随SFD之后的是6 byte的目的地(DST, destination)和6 byte的发出地(SRC, source),也就是MAC地址,MAC地址是物理设备自带的序号。 38 | > * 头部的最后一个区域是Type,用以说明数据部分的类型。(比如0x0800为IPv4,0x0806为ARP) 39 | 40 | * **数据** 41 | >* 数据一般包含有符合更高层协议的数据,比如IP包。链路层协议本身并不在乎数据是什么,它只负责传输。注意,数据尾部可能填充有一串0(PAD区域)。原因是数据需要超过一定的最小长度。 42 | * **尾部** 43 | >* 跟随在数据之后的是校验序列(FCS, Frame Check Sequence)。校验序列是为了检验数据的传输是否发生错误。在物理层,我们通过一些物理信号来表示0/1序列(比如高压/低压,高频率/低频率等),但这些物理信号可能在传输过程中受到影响,以致于发生错误。数据链路层检测数据传输错误的方法一般是通过对差错编码进行校验来实现,常见的有奇偶校验码和循环冗余校验(CRC),FCS采用了CRC(Cyclic Redundancy Check)算法 44 | > * n位CRC算法取一个n bit的因子,比如下面的1011。数据序列结尾增加n-1个0。因子与数据序列的不断进行XOR运算,直到得到n-1位的余数,也就是100。该余数各位取反(011),然后存储在FCS的位置。 45 | ![img1.png](./img/img5.png) 46 | 47 | > * 在Ethernet中使用的因子为32位的,以达到更好的检测效果。 48 | ### 网络层 49 | * 在网络层上,各个局域网根据IP协议相互连接,最终构成覆盖全球的Internet。 50 | ![img1.png](./img/img6.jpg) 51 | 52 | * 网络层有三个主要组件: 53 | - IP协议 54 | - 路由选择部分(计算和维护转发表) 55 | - ICMP(报告数据报中的差错和对默写网络层信息请求进行相应的设施) 56 | #### IP协议 57 | * 无论是TCP还是UDP,必须通过网络层的IP数据包(datagram)来传递信息。操作系统也会提供该层的socket,从而允许用户直接操作IP包,IP数据包是符合IP协议的信息。IP包分为头部(header)和数据(Data)两部分。数据部分是要传送的信息,头部是为了能够实现传输而附加的信息,与以太网帧的头部功能相类似。 58 | * **ipv4 头部的前一部分是固定长度的共20字节,是所有IP分组必须具有的**,在头部固定部分的后面是一些可选字段,其长度是可变的,用来提供错误检测及安全等机制。IPv4的地址为4 bytes的长度(也就是32位)。我们通常将IPv4的地址分为四个十进制的数,每个数的范围为0-255,比如192.0.0.1就是一个IP地址。填写在IP包头部的是该地址的二进制形式。 59 | ![img1.png](./img/img7.png) 60 | * **协议详解** 61 | * 黄色区域 (同名区域) 62 | - 三个黄色区域跨越了IPv4和IPv6。Version(4位)用来表明IP协议版本,是IPv4还是IPv6(IPv4, Version=0100; IPv6, Version=0110)。Source Adrresss和Destination Address分别为发出地和目的地的IP地址。 63 | * 蓝色区域 (名字发生变动的区域) 64 | - Time to Live 存活时间(Hop Limit in IPv6)。记录一个整数(比如30),表示在IP包接力过程中最多经过30个路由接力,如果超过30个路由接力,那么这个IP包就作废。IP包每经过一个路由器,路由器就给Time to Live减一。当一个路由器发现Time to Live为0时,就不再发送该IP包。IPv6中的Hop Limit区域记录的也是最大路由接力数,与IPv4的功能相同。Time to Live/Hop Limit避免了IP包在互联网中无限接力。 65 | - Type of Service 服务类型(Traffic Class in IPv6)。Type of Service被实际分为两部分:Differentiated Service Field (DS, 前6位)和Explicit Congestion Notification (ECN, 后2位),前者依然用来区分服务类型,而后者用于表明IP包途径路由的交通状况。IPv6的Traffic Class也被如此分成两部分。通过IP包提供不同服务的想法,并针对服务进行不同的优化,比如ECN区域,它用来表示IP包经过路径的交通状况。如果接收者收到的ECN区域显示路径上的很拥挤,那么接收者应该作出调整。但在实际上,许多接收者都会忽视ECN所包含的信息。交通状况的控制往往由更高层的比如TCP协议实现。 66 | - Protocol 协议(Next Header in IPv6)。Protocol用来说明IP包Payload部分所遵循的协议,也就是IP包之上的协议是什么。它说明了IP包封装的是一个怎样的高层协议包(TCP? UDP?)。 67 | - IPv4中整个IP包 Total Length=IHL+数据部分;IPv6中整个IP包 40 bytes + Payload Length。 68 | * 红色区域 (IPv6中删除的区域) 69 | - 不考虑options的话,整个IPv4头部有20 bytes(上面每行为4 bytes)。但由于有options的存在,整个头部的总长度是变动的。我们用**IHL(Internet Header Length**)来记录头部的总长度,用**Total Length**记录整个IP包的长度。**IPv6没有options**,它的头部是固定的长度40 bytes,所以**IPv6中并不需要IHL区域**。Payload Length用来表示IPv6的数据部分的长度。整个IP包为40 bytes + Payload Length。 70 | - IPv4中还有一个**Header Checksum**区域。这个checksum用于校验IP包的头部信息。IPv6则没有checksum区域。IPv6包的校验依赖高层的协议来完成,这样的好处是免去了执行checksum校验所需要的时间,减小了网络延迟 (latency)。 71 | - **Identification, flags和fragment offset,这三个包都是为分片(fragmentation)服务的。分片会给路由器和网络带来很大的负担。最好在IP包发出之前探测整个路径上的最小MTU,IP包的大小不超过该最小MTU,就可以避免分片。IPv6在设计上避免分片。每一个IPv6局域网的MTU都必须大于等于1280 bytes。IPv6的默认发送IP包大小为1280 bytes。** 72 | * IPv6新增区域:Flow Label是IPv6中新增的区域。它被用来提醒路由器来重复使用之前的接力路径。这样IP包可以自动保持出发时的顺序。这对于流媒体之类的应用有帮助。Flow label的进一步使用还在开发中。 73 | * **IP数据包分片(fragmentation)**,一个链路层数据报能承载的最大数据量称为**最大传送单元(MTU**)。因为IP数据报被封装在链路层数据报中,故链路层的MTU严格地限制着IP数据报的长度,而且在IP数据报的源于目的地路径上的各段链路可能使用不同的链路层协议,有不同的MTU。当IP数据报的总长度大于链路MTU,就需要将IP数据报中的数据分装在两个或更多个较小的IP数据报中,这些较小的数据报叫做片。**注意**:为坚持网络内核保持简单的原则,IPv4的设计者决定数据报的重新组装工作放到端系统中,而不是在网络路由器中。分片举例: 74 | ![img1.png](./img/img8.png) 75 | 76 | * **IP子网划分**,连接在Internet中的每一台主机(或路由器)都分配一个32比特的全球唯一的标识符,即IP地址。传统的IP地址是分类的地址,分为A、B、C、D、E五类(A:1-126 B:128-191 C:192-223 D:224-239 E:240-255),每个IP地址的32位分为前后两部分,第一部分用来区分局域网,第二个部分用来区分该局域网的主机。 77 | - **子网掩码**(Subnet Mask)告诉我们这两部分的分界线,比如255.0.0.0(也就是8个1和24个0)表示前8位用于区分局域网,后24位用于区分主机。由于A、B、C分类是已经规定好的,所以当一个IP地址属于B类范围时,我们就知道它的前16位和后16位分别表示局域网和主机。 78 | ![img1.png](./img/img9.png) 79 | - **网络地址转换(NAT**)是通过将专用网络地址转换为公用地址,从而对外隐藏了内部管理的IP地址。它使得整个专用网只需要一个全球IP地址就可以与因特网连通,由于专用网本地IP地址是可重用的,所以NAT大大节省了IP地址的消耗。同时,它隐藏了内部网络结构,从而降低了内部网络收到攻击的风险。 80 | * **DHCP(the Dynamic Host Configuration Protocol)**,动态主机配置协议)它可以为客户机自动分配IP地址、子网掩码以及缺省网关、DNS服务器的IP地址等TCP/IP参数,简单来说,就是在DHCP服务器上有一个数据库,存放着IP地址、网关、DNS等参数。当客户端请求使用时,服务器则负责将相应的参数分配给客户端。以避免客户端手动指定IP地址。**DHCP是基于UDP的应用层协议**,来看下DHCP的工作过程: 81 | >1. DHCP DISCOVER: 寻找服务器  当DHCP客户端第一次登录网络的时候或者是开机时,此计算机发现本机上没有任何IP地址设定,就会向网络广播去寻找DHCP服务器。该数据包的来源地址会为0.0.0.0,而目的地址则为255.255.255.255。   82 | >2. DHCP OFFER 分配IP地址  当无线设备监听到客户端发出的寻找服务器的数据包后,它会从那些还没有分配出的IP地址里,选择最前面的的空闲IP,给客户端一个分配IP地址,但这里仅仅是分配,客户端还没有真正应用上。   83 | >3. DHCP REQUEST 请求使用  客户端收到无线设备发送回来的分配IP地址数据包,客户端会向网络发送一个**ARP数据包**,确认网络中没有其他机器使用该IP地址,如果已经有,则重复发送步骤1中的动作;如果没有,则接受该IP地址,并发送一个DHCP request数据包给无线路由器,也就是DHCP服务器,请求使用此地址。   84 | >4. DHCP ACK IP地址分配确认  当无线设备接收到客户端的DHCP request数据包之后,会向客户端发出一个DHCP ACK回应,以确认IP地址的正式生效,也就结束了一个完整的DHCP工作过程。 85 | #### **ARP协议** 86 | * IP地址与MAC地址的对应是通过ARP协议传播到局域网的每个主机和路由。每一台主机或路由中都有一个ARP cache,用以存储局域网内IP地址和MAC地址如何对应。 87 | * ARP协议(ARP介于链路层和网络层之间,ARP包需要包裹在一个帧中)的工作方式如下:主机会发出一个ARP包,该ARP包中包含有自己的IP地址和MAC地址。通过ARP包,主机以广播的形式询问局域网上所有的主机和路由:我是IP地址xxxx,我的MAC地址是xxxx,有人知道199.165.146.4的MAC地址吗?拥有该IP地址的主机会回复发出请求的主机:哦,我知道,这个IP地址属于我的一个NIC,它的MAC地址是xxxxxx。由于发送ARP请求的主机采取的是广播形式,并附带有自己的IP地址和MAC地址,其他的主机和路由会同时检查自己的ARP cache,如果不符合,则更新自己的ARP cache。这样,经过几次ARP请求之后,ARP cache会达到稳定。如果局域网上设备发生变动,ARP重复上面过程。 88 | #### [ICMP协议](http://www.cnblogs.com/vamei/archive/2012/12/05/2801991.html) 89 | * **ICMP(Internet Control Message Protocol)** 是介于网络层和传输层的协议。它的主要功能是传输网络诊断信息,ICMP基于IP协议。也就是说,一个ICMP包需要封装在IP包中,然后在互联网传送。ICMP是IP套装的必须部分,也就是说,任何一个支持IP协议的计算机,都要同时实现ICMP。 90 | * 一类是错误(error)信息,这一类信息可用来诊断网络故障。如果IP包没有被传送到目的地,或者IP包发生错误,IP协议本身不会做进一步的努力。但上游发送IP包的主机和接力的路由器并不知道下游发生了错误和故障,它们可能继续发送IP包。通过ICMP包,下游的路由器和主机可以将错误信息汇报给上游,从而让上游的路由器和主机进行调整。需要注意的是,ICMP只提供特定类型的错误汇报,它不能帮助IP协议成为“可靠”(reliable)的协议。 91 | * 另一类信息是咨询(Informational)性质的,比如某台计算机询问路径上的每个路由器都是谁,然后各个路由器同样用ICMP包回答。 92 | ![img1.png](./img/img10.png) 93 | * ICMP包都会有Type, Code和Checksum三部分。Type表示ICMP包的大的类型,而Code是一个Type之内细分的小类型。针对不同的错误信息或者咨询信息,会有不同的Type和Code。 94 | Checksum与IP协议的header checksum相类似,但与IP协议中checksum只校验头部不同,这里的Checksum所校验的是整个ICMP包(包括头部和数据)。 95 | * ICMP协议是实现ping命令和traceroute命令的基础。这两个工具常用于网络排错。 96 | 97 | #### 路由选择部分 98 | * 网卡是计算机的一个硬件,它在接收到网路信息之后,将信息交给计算机(处理器/内存),路由器(router)实际上就是一台配备有多个网卡的专用电脑,它让网卡接入到不同的网络中。路由器的内部结构和工作原理,它有四种组件构成: 99 | - Input ports [输入端口] 100 | - Output ports [输出端口] 101 | - Switching fabric [交换结构] 102 | - Routing processor [路由选择处理器] 103 | * **IP包的传输要通过路由器的路由**。每一个主机和路由器中都存有一个**路由表(routing table)**。路由表根据目的地的IP地址,**规定了等待发送的IP包所应该走的路线(不得不提路由算法,)**。中间的router在收到IP包之后(实际上是收到以太协议的帧,然后从帧中的payload读取IP包),提取目的地IP地址,然后对照自己的routing table,送到下一个目的ip地址。整个过程中,IP包不断被主机和路由封装入帧并拆开,然后借助链路层,在局域网的各个NIC之间传送帧。整个过程中,我们的IP包的内容保持完整,没有发生变化。最终的效果是一个IP包从一个主机传送到另一个主机。利用IP包,我们不需要去操心底层发生了什么。 104 | * **路由算法**,即已知一组路由器及连接路由器链路的情况下,找出一条由源节点到目标节点的最佳路径。 105 | 106 | 几种主要的路由算法:     107 | * 静态路由算法(非自适应路由算法):       108 | - 1.最短路径路由算法(Shortest Path Routing).      109 | - 2.扩散算法     110 | * 动态路由算法(自适应路由算法):       111 | - 1.距离矢量路由算法(Distance-Vector)       112 | - 2.链路状态路由算法(Link-State) 113 | * **因特网的路由协议** 114 | - **RIP(Routing Information Protocol)路由信息协议** 是内部网关协议中最先得到广泛应用的协议,是一种分布式的基于距离向量的路由选择协议,其最大优点就是简单。缺点是RIP限制了网络的规模,它能使用的最大距离为15(16为不可达),除此之外开销很大。 115 | - **OSPF (Open Shortest Path First)**,开放最短路径优先协议是使用分布式链路状态路由算法的典型代表。OSPF收敛速度快,适应各种规模,将协议自身的开销控制到最小,同时也具有良好的安全性。**OSPF是网络层协议,它不使用UDP或TCP而是直接IP数据报传送。** 116 | - **BGP (Border Gateway Protocol)**: 边界网关协议是不同自治系统的路由器之间交换路由信息的协议。边界网关协议常常应用于互联网的网关之间。路由表包含已知路由器的列表、路由器能够达到的地址以及到达每个路由器的路径的跳数。**边界网关协议BGP只能是力求寻找一条能够到达目的网络且比较好的路由,而并非要寻找一条最佳路由**。BGP采用的是路径向量路由选择协议,它与距离向量协议和链路状态协议有很大区别。BGP协议是应用层协议,基于TCP的。 117 | #### ICMP 118 | * **ICMP(Internet Control Message Protocol)** 是介于网络层和传输层的协议。它的主要功能是传输网络诊断信息。 119 | 120 | ### 传输层 121 | * 网络上主机与主机之间的通信,实质上是主机上运行的应用进程之间的通信。例如,当你通过Http上网浏览网页时,实质上是你所访问的主机的服务器进程与你本机的浏览器进程在进行通信。试想一下,当你在上网的同时,还挂着QQ,还使用ftp下载大文件,这时就有三个网络上的进程与你的主机上的三个进程进行通信,那么系统是怎么样正确地把接收到的数据定位到指定的进程中的呢?也就是说,系统是怎么把从ftp服务器发送过来的数据交付到ftp客户端,而不把这些数据交付到你的QQ上的呢?反过来考虑,系统又是如何精确地把来自各个应用进程的数据发到网络上指定上的主机(服务器)上的对应进程的呢?这就是多路分解与多路复用的作用了。每个运输层的报文段中设置了几个字段,包括源端口号和目的端口号等。多路分解就是,在接收端,运输层检查这些字段并标识出接收套接字,然后将该报文定向到该套接字。多路复用就是从源主机的不同套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层中去。 122 | * **UDP** 的数据包同样分为头部(header)和数据(payload)两部分。UDP是传输层(transport layer)协议,这意味着UDP的数据包需要经过IP协议的封装(encapsulation),然后通过IP协议传输到目的电脑。随后UDP包在目的电脑拆封,并将信息送到相应端口的缓存中。 123 | ![img1.png](./img/img11.png) 124 | * source port和destination port分别为UDP包的出发端口和目的地端口。Length为整个UDP包的长度,UDP的checksum所校验的序列包括了整个UDP数据包,以及封装的IP头部的一些信息(主要为出发地IP和目的地IP)。这样,checksum就可以校验**IP:端口**的正确性了。在IPv4中,checksum可以为0,意味着不使用checksum。IPv6要求必须进行checksum校验。 --------------------------------------------------------------------------------