Python教程12:列表进阶与推导式高级

Python教程12:列表进阶与推导式高级 “熟能生巧,巧能生精。” 在第8课我们学习了列表推导式的基础,今天我们深入探讨列表推导式的高级技巧和列表的进阶操作,让你的代码更加Pythonic和高效。 1. 回顾:列表推导式基础 1# 基础语法 2squares = [x**2 for x in range(10)] 3 4# 带条件 5evens = [x for x in range(10) if x % 2 == 0] 6 7# if-else 8result = [x if x > 0 else 0 for x in [-1, 2, -3, 4]] 2. 嵌套列表推导式 二维列表展平 1# 传统方法 2matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 3flat = [] 4for row in matrix: 5 for num in row: 6 flat.append(num) 7 8# 列表推导式 9flat = [num for row in matrix for num in row] 10print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9] 理解技巧:从左到右阅读,就像嵌套的for循环。 创建二维列表 1# 创建3×3矩阵 2matrix = [[0 for _ in range(3)] for _ in range(3)] 3print(matrix) 4# [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 5 6# 创建乘法表 7table = [[i*j for j in range(1, 10)] for i in range(1, 10)] 8 9# 注意:不要这样创建二维列表 10# bad = [[0] * 3] * 3 # 错误!所有行是同一个对象 多重嵌套with条件 1# 找出两个列表的所有组合(有条件) 2a = [1, 2, 3] 3b = [3, 4, 5] 4 5# 找出和大于5的组合 6result = [(x, y) for x in a for y in b if x + y > 5] 7print(result) # [(2, 4), (2, 5), (3, 3), (3, 4), (3, 5)] 3. 列表推导式vs传统循环 1# 性能对比示例 2import time 3 4# 方法1:传统for循环 5start = time.time() 6result1 = [] 7for i in range(100000): 8 result1.append(i**2) 9time1 = time.time() - start 10 11# 方法2:列表推导式 12start = time.time() 13result2 = [i**2 for i in range(100000)] 14time2 = time.time() - start 15 16print(f"传统循环:{time1:.4f}秒") 17print(f"列表推导式:{time2:.4f}秒") 18# 列表推导式通常快20-30% 4. 字典和集合推导式进阶 字典推导式高级用法 1# 统计字符出现次数 2text = "hello world" 3char_count = {char: text.count(char) for char in set(text) if char != ' '} 4 5# 从列表创建索引字典 6fruits = ['apple', 'banana', 'cherry'] 7fruit_index = {fruit: i for i, fruit in enumerate(fruits)} 8 9# 嵌套字典推导式 10students = ['Alice', 'Bob'] 11subjects = ['Math', 'English'] 12grades = { 13 student: {subject: 0 for subject in subjects} 14 for student in students 15} 集合推导式妙用 1# 去重并转换 2numbers = [1, -2, 3, -4, 5] 3abs_unique = {abs(n) for n in numbers} # {1, 2, 3, 4, 5} 4 5# 找差异 6list1 = [1, 2, 3, 4, 5] 7list2 = [4, 5, 6, 7, 8] 8diff = {x for x in list1 if x not in list2} # {1, 2, 3} 5. 生成器表达式深入 1# 列表推导式:立即生成,占内存 2squares_list = [x**2 for x in range(1000000)] 3 4# 生成器表达式:按需生成,省内存 5squares_gen = (x**2 for x in range(1000000)) 6 7# 使用生成器 8total = sum(x**2 for x in range(1000000)) 9 10# 生成器只能遍历一次 11gen = (x for x in range(5)) 12print(list(gen)) # [0, 1, 2, 3, 4] 13print(list(gen)) # [](已耗尽) 6. 列表的高级操作 zip和enumerate进阶 1# zip并行遍历 2names = ['Alice', 'Bob', 'Charlie'] 3ages = [25, 30, 35] 4cities = ['Beijing', 'Shanghai', 'Guangzhou'] 5 6# 创建字典 7people = [ 8 {'name': n, 'age': a, 'city': c} 9 for n, a, c in zip(names, ages, cities) 10] 11 12# enumerate with start 13for i, name in enumerate(names, start=1): 14 print(f"{i}. {name}") filter和map结合推导式 1# 虽然有filter和map,但推导式更清晰 2numbers = range(1, 11) 3 4# filter + map方式 5result1 = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, numbers))) 6 7# 推导式方式(更清晰) 8result2 = [x**2 for x in numbers if x % 2 == 0] 7. 实战案例 案例1:矩阵转置 1matrix = [ 2 [1, 2, 3], 3 [4, 5, 6], 4 [7, 8, 9] 5] 6 7# 转置 8transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))] 9print(transposed) 10# [[1, 4, 7], [2, 5, 8], [3, 6, 9]] 11 12# 或使用zip 13transposed = [list(col) for col in zip(*matrix)] 案例2:笛卡尔积 1colors = ['红', '黑'] 2sizes = ['S', 'M', 'L'] 3products = [f"{color}-{size}" for color in colors for size in sizes] 4# ['红-S', '红-M', '红-L', '黑-S', '黑-M', '黑-L'] 案例3:数据清洗 1# 清洗CSV数据 2raw_data = [ 3 " Alice, 25 ", 4 "Bob,30", 5 " Charlie, 35 " 6] 7 8cleaned = [ 9 [item.strip() for item in row.split(',')] 10 for row in raw_data 11] 8. 何时不用推导式 虽然推导式简洁,但有时不适合: ...

2024-04-28 · 4 min · 744 words · 老墨

自由与梦想

今天终于读完了《我在北京送快递》一书,这本15万字的书前前后后读完居然花了两个多月的时间——习惯使然,我读什么书都喜欢字斟句酌,这一点让我有些苦恼:一方面是因为进度非常缓慢,想象一下,读一本人物传记我还在拿着笔一边阅读一边给不认识的字词标注笔记……,另一方面,由于阅读的周期很长,导致容易半途而废,可能前半部分阅读的很仔细,后半部分就走马观花甚至放弃了。不过这本书我还算比较认真的读完了,我想谈一谈我对“自由”的理解。 作者在尾声的“后记”一章中阐述了自己对“自由”的观点:这些年来自己经常处于一边工作、一边写作的生活状态中,工作时无法全心投入写作,而写作时也无法积极投身工作,作者自己希望的自由是“在高度发展上的个人意识的个人追求和自我实现”。他认为,追求自己热衷的事情,形成与别人不同的自我意识上的区别,那么这种追求是自由的;相反,表面上看似自由,却并没有实现自我价值,只是迫于某种原因而不得不去做,又或者是安于现状,非个人追求或无法实现自我,那么这些都不能算是真正的自由。 作者写到,在迫于生计而不得不付诸大量时间和精力在工作上时,自己便无法专注于自己喜欢的事情——写作,自身也被疲倦、困意、复杂的人际关系所折磨,,身心俱疲,就算挣到了钱但自己并不快乐,这种生活不能算是自由;而之后回到老家,全身心投入写作,不必为生计而担忧,每天作者自己喜欢的事情——阅读、写作、与其他作者交流写作创意等等,自己的阅读量、写作水平都有很大的提升,这在知识的高度上与其他人拉开了差距、形成了区别,自身的追求有了一定的成果,自己也觉得开心、快乐,这样的生活是自由的。 我很喜欢作者的这个观点。简而言之,作者所述的自由,在我看来是“做自己想做的事,走自己想走的路”。 每个人心中都会有梦想,那就是我们最想做的事情,最想走的路,只是多数时候我们为生活所迫东奔西走,仿佛随波逐流的沙粒,被生活磨平了棱角,早就忘记了自己的梦想是什么了。**“人们只有两条路,一条需要用心走,叫做梦想;另一条需要用脚走,叫做现实。”**诚然,现实与梦想仿佛总是相对立的两面。对于大多数人而言,梦想往往是高不可攀的,只能深埋于心,为了生计不得不放弃梦想而从事自己并不喜欢的工作的人不计其数,为追逐梦想而奋斗此生最终获得成功的人寥寥无几。 在人生的道路上,现实与梦想常常交织着,而在这交织之中,自由似乎成了一种奢侈。对许多人而言,梦想是一种遥不可及的幻影,而现实却是残酷的枷锁,将他们紧紧束缚。在这样的环境下,自由似乎只存在于幻想之中。 普通的农民每天的工作就是完成农务,整日与农田、二十四节气打交道,农忙时努力播种、收获,农闲时与朋友们一起打打牌、聊聊天。我相信,他们大多觉得生活本就如此,自给自足也挺好,除非生活实在拮据迫不得已才会跑出去打工赚钱。此时,或许他们早已习惯了这样的生活,早已忘记年轻时候自己想要做的事情了。对于他们而言,无论忙碌或是清闲,可能都不重要,年复一年重复着同样的事情,循规蹈矩地过着自己的生活,这就是最简单的自由和幸福吧。 在车水马龙的城里,白领们的知识水平比普通农民要高出许多,他们终日奔波忙碌,所选择的工作就是自己所追求的梦想吗?我相信大多连“事业”都称不上。他们所做的只是“为老板打工”,赚取自己的劳动报酬罢了,他们的工作心态总是“尽力完成我能力范围内的工作”的得过且过,很少有人会将自己的工作真正的当成一份事业来打拼,因为就算你再努力,那也是为别人赚钱,对自己并没有多大意义。所以,大多数白领会默默地选择平庸,只是在偶尔看到少数人成功之后才能激起心中的激情,也许那只是害怕自己被周围人超越的恐慌,然后疯狂地努力一段时间,随后逐渐发现无论自己多努力似乎都无法提升自己到一个新的高度,慢慢难以坚持最后果断放弃并又再次平庸下去,这就样反反复复,最终一无所成。 当我们回首自己的生活时,是否还记得小时候那个满怀梦想的自己?那个对未来充满信心、对世界充满好奇的自己是否还在内心深处?然而,随着年岁的增长,生活的压力和社会的现实往往会让我们逐渐放弃那些美好的梦想。我们被迫在现实的洪流中奔波,为了生计为了生活,不得不选择一条看似安稳但缺少激情的道路。 梦想的实现谈何容易?马云当初创业时,初创团队“十八罗汉”吃住都其家里,十几人就挤在几十平方的房子里,他们没日没夜的工作,饿了就吃泡面,困了就打地铺小睡一会儿……但是,团队人人都充满着激情,没有一个人轻言放弃,因为他们知道他们现在是在为了梦想而奋斗,奋斗着并奋不顾身。就在阿里巴巴上市的那一天,这十八罗汉人人都身价过亿、功成名就、梦想成真。回望这一路坎坷,我想他们应该是无怨无悔了吧?至少,为了梦想,再苦再累也都是甘之如饴吧。这样的生活,看似处处是桎梏,却总能令人无限神往,这又何尝不是一种自由呢?马云当然属于成功者,这些年义无反顾的追梦,等到如今大获成功之后,可能他才发现原来自己的人生还有其他更想做的事情,所以他开办学校、拍电影、搞演唱会……有钱真任性,但是他有任性的资本,试问此时,为所欲为的马云是否获得了真正的自由呢?答案只有他自己知道,看似风光无限,可能高处不胜寒的寂寞只有他自己懂得了。 当我们审视自己的生活,回顾梦想与现实的交织,或许我们会发现,自由并非遥不可及的幻影,而是一种内心的坚定和勇气。在生活的征途中,或许我们会因为生计而忙碌,会因为社会的压力而迷失,但只要我们保持对梦想的信念,不忘初心,奋力前行,那梦想的光芒就会一直照亮前行的路。或许成功并非是终点,而是一种过程,而真正的自由,其实就是坚持不懈地追寻内心的信仰,敢于实现自己的梦想,敢于选择一条不一样的道路。因为只有在追逐梦想的路上,我们才能找到真正属于自己的自由和幸福。愿我们都能保持对梦想的热爱,勇敢追寻内心的声音,活出真正的自己,活出自由的精彩人生。

2024-04-26 · 1 min · 11 words · 老墨

Python教程11:列表基础

Python教程11:列表基础 “工欲善其事,必先利其器。” 列表(List)是Python中最常用的数据结构之一,就像一个可以随意增删改查的购物清单。今天我们深入学习列表的基础操作,为后续学习打好基础。 1. 什么是列表 列表是一个有序的、可变的元素集合,可以存储不同类型的数据。 1# 创建列表 2empty_list = [] # 空列表 3numbers = [1, 2, 3, 4, 5] # 整数列表 4mixed = [1, "hello", 3.14, True] # 混合类型 5nested = [[1, 2], [3, 4], [5, 6]] # 嵌套列表 6 7# 使用list()函数 8from_string = list("Python") # ['P', 'y', 't', 'h', 'o', 'n'] 9from_range = list(range(5)) # [0, 1, 2, 3, 4] 2. 访问列表元素 索引访问 1fruits = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"] 2 3# 正向索引(从0开始) 4print(fruits[0]) # 苹果 5print(fruits[1]) # 香蕉 6 7# 负向索引(从-1开始) 8print(fruits[-1]) # 西瓜(最后一个) 9print(fruits[-2]) # 葡萄(倒数第二个) 10 11# 索引越界会报错 12# print(fruits[10]) # IndexError 切片访问 1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 3# [start:stop:step] 4print(numbers[2:5]) # [2, 3, 4] 5print(numbers[:5]) # [0, 1, 2, 3, 4] 6print(numbers[5:]) # [5, 6, 7, 8, 9] 7print(numbers[::2]) # [0, 2, 4, 6, 8](步长为2) 8print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0](反转) 9 10# 负索引切片 11print(numbers[-3:]) # [7, 8, 9] 12print(numbers[:-3]) # [0, 1, 2, 3, 4, 5, 6] 3. 修改列表 列表是可变的,可以直接修改元素: ...

2024-04-15 · 4 min · 750 words · 老墨

Rust 学习笔记 02:变量与可变性

Rust 学习笔记 02:变量与可变性 “Stability is not immobility.” – Prince Metternich 习惯了 Go 语言的 var 和 :=,第一次写 Rust 时,最让人抓狂的报错一定是 cannot assign twice to immutable variable。 在 Go 里,变量生来就是可变的(除了 const),但在 Rust 里,变量生来就是不可变的。 这就像是:Go 是一个自由奔放的游乐场,你想去哪就去哪;而 Rust 是一个戒备森严的博物馆,除非你申请了"触摸许可"(mut),否则只能看,不能摸。 1. 默认不可变 (Immutability) 在 Rust 中,当你写下: 1let x = 5; 注意: Rust 必须在语句末尾加上 “;",与 Java 一样,但是 Go 却不需要,这需要 Go 开发者习惯很久。 这不仅是定义了一个变量,更是立下了一个誓言:“x 的值就是 5,永远不会变。” 如果你试图打破誓言: 1x = 6; // 编译报错! 编译器会无情地告诉你:cannot assign twice to immutable variable x。 ...

2024-04-12 · 2 min · 350 words · 老墨

这本区块链爱好者必读作品终于迎来第三版

学习区块链的书籍我推荐这两本,我在这里已经推荐过一次: Mastering Bitcoin Mastering Ethereum 比特币开创了一门新的技术——区块链,而以太坊则在比特币的理念基础上进行再创新和升华,二者是近十多年来区块链技术的代表。因此,学习区块链技术,绝对离不开它们。恰好,这两本书就完全阐述了比特币和以太坊的技术。 这两本书都由同一名作者编写,他是一名有十多年区块链研究经验的爱好者,两本书的传播度非常广,是学习、研究区块链技术的必读作品。 《Mastering Ethereum》翻译为《精通以太坊》,目前该书可以在 github 上免费阅读,地址在这里,翻译版在这里。 《Mastering Bitcoin》中文译作《精通比特币》,本书详细介绍了比特币底层的实现逻辑,目前版本是第二版。但是随着比特币2017、2021两次的重大升级,添加了隔离见证、Taproot、Schnorr签名等重大改进,不论是逻辑上还是技术上都有着很大的学习难度,第二版是在2017升级之前刊印的,现在未免显得有点过时了。如果你想学习比特币的底层知识,可以在这里阅读到本书的中文版。 让人兴奋的是,2023年《Mastering Bitcoin》迎来了它的第三个版本,它由原作者 Andreas Antonopoulos 和另一位区块链爱好者 David Harding 联合编著,在第二版的基础上补全了这几年比特币的多个升级技术。可惜的是,本书目前还没有中文版本,但你可以直接在 github 上阅读英文版,地址在这里。 另外,为了便于阅读,我也fork了本书,编译并搭建了在线阅读网站,打开即可直接阅读,可以选择单页阅读整书也可以分章节阅读。 区块链技术的学习是一个枯燥和费力的过程,但历史的车轮总是向前推进的,技术在进步,坚持下来者方能迎接蜕变。 相关阅读: Bitcoin’s Taproot Upgrade: What You Should Know https://github.com/ajtowns/taproot-review

2024-03-20 · 1 min · 34 words · 老墨

Python教程10:第一个实用程序

Python 教程 10:第一个实用程序 “纸上得来终觉浅,绝知此事要躬行。” 经过前面 9 课的学习,我们已经掌握了 Python 的基础知识。今天,让我们把这些知识串起来,开发一个真正实用的程序:批量文件重命名工具。 1. 项目需求 开发一个命令行工具,能够: 批量重命名文件:支持添加前缀、后缀、替换文本 过滤文件:支持按扩展名、文件名模式过滤 预览模式:先预览修改,确认后再执行 撤销功能:记录操作,支持撤销 这个工具很实用,能解决日常工作中的真实问题。 2. 项目结构 file_renamer/ ├── file_renamer.py # 主程序 ├── renamer.py # 核心重命名逻辑 ├── utils.py # 工具函数 └── history.json # 操作历史记录 3. 核心功能实现 3.1 列出目录中的文件 1# utils.py 2import os 3 4def list_files(directory, extension=None, pattern=None): 5 """ 6 列出目录中的文件 7 8 Args: 9 directory: 目标目录 10 extension: 文件扩展名过滤(如'.txt') 11 pattern: 文件名模式(简单的包含匹配) 12 13 Returns: 14 文件路径列表 15 """ 16 files = [] 17 18 for filename in os.listdir(directory): 19 filepath = os.path.join(directory, filename) 20 21 # 只处理文件,忽略目录 22 if not os.path.isfile(filepath): 23 continue 24 25 # 扩展名过滤 26 if extension and not filename.endswith(extension): 27 continue 28 29 # 文件名模式过滤 30 if pattern and pattern not in filename: 31 continue 32 33 files.append(filepath) 34 35 return files 3.2 重命名逻辑 1# renamer.py 2import os 3import re 4 5class FileRenamer: 6 """文件重命名器""" 7 8 def __init__(self, directory): 9 self.directory = directory 10 self.changes = [] # 记录修改 11 12 def add_prefix(self, files, prefix): 13 """添加前缀""" 14 for filepath in files: 15 dirname = os.path.dirname(filepath) 16 filename = os.path.basename(filepath) 17 new_name = prefix + filename 18 new_path = os.path.join(dirname, new_name) 19 self.changes.append((filepath, new_path)) 20 21 def add_suffix(self, files, suffix): 22 """添加后缀(在扩展名前)""" 23 for filepath in files: 24 dirname = os.path.dirname(filepath) 25 filename = os.path.basename(filepath) 26 name, ext = os.path.splitext(filename) 27 new_name = name + suffix + ext 28 new_path = os.path.join(dirname, new_name) 29 self.changes.append((filepath, new_path)) 30 31 def replace_text(self, files, old_text, new_text): 32 """替换文件名中的文本""" 33 for filepath in files: 34 dirname = os.path.dirname(filepath) 35 filename = os.path.basename(filepath) 36 new_name = filename.replace(old_text, new_text) 37 new_path = os.path.join(dirname, new_name) 38 if filepath != new_path: # 只记录有变化的 39 self.changes.append((filepath, new_path)) 40 41 def preview(self): 42 """预览修改""" 43 if not self.changes: 44 print("没有要修改的文件") 45 return 46 47 print(f"\n将要进行 {len(self.changes)} 项修改:") 48 print("-" * 60) 49 for i, (old, new) in enumerate(self.changes, 1): 50 old_name = os.path.basename(old) 51 new_name = os.path.basename(new) 52 print(f"{i}. {old_name} -> {new_name}") 53 print("-" * 60) 54 55 def execute(self): 56 """执行重命名""" 57 if not self.changes: 58 print("没有要执行的操作") 59 return 60 61 success_count = 0 62 for old_path, new_path in self.changes: 63 try: 64 os.rename(old_path, new_path) 65 success_count += 1 66 except Exception as e: 67 print(f"错误:{old_path} -> {e}") 68 69 print(f"\n成功重命名 {success_count}/{len(self.changes)} 个文件") 70 71 # 保存操作历史 72 self.save_history() 73 74 def save_history(self): 75 """保存操作历史(简化版)""" 76 import json 77 from datetime import datetime 78 79 history_file = "history.json" 80 81 # 读取现有历史 82 history = [] 83 if os.path.exists(history_file): 84 with open(history_file, 'r', encoding='utf-8') as f: 85 history = json.load(f) 86 87 # 添加新记录 88 history.append({ 89 'time': datetime.now().isoformat(), 90 'changes': [(old, new) for old, new in self.changes] 91 }) 92 93 # 保存 94 with open(history_file, 'w', encoding='utf-8') as f: 95 json.dump(history, f, indent=2, ensure_ascii=False) 3.3 主程序 1# file_renamer.py 2#!/usr/bin/env python3 3# -*- coding: utf-8 -*- 4 5""" 6文件批量重命名工具 7 8用法: 9 python file_renamer.py 10""" 11 12import os 13from renamer import FileRenamer 14from utils import list_files 15 16def main(): 17 print("=" * 60) 18 print("文件批量重命名工具") 19 print("=" * 60) 20 21 # 获取目录 22 directory = input("\n请输入目录路径(留空使用当前目录):").strip() 23 if not directory: 24 directory = "." 25 26 if not os.path.exists(directory): 27 print(f"错误:目录 '{directory}' 不存在") 28 return 29 30 # 获取文件过滤条件 31 extension = input("文件扩展名过滤(如.txt,留空跳过):").strip() 32 if not extension: 33 extension = None 34 35 # 列出文件 36 files = list_files(directory, extension) 37 38 if not files: 39 print("没有找到符合条件的文件") 40 return 41 42 print(f"\n找到 {len(files)} 个文件") 43 44 # 创建重命名器 45 renamer = FileRenamer(directory) 46 47 # 操作菜单 48 while True: 49 print("\n请选择操作:") 50 print("1. 添加前缀") 51 print("2. 添加后缀") 52 print("3. 替换文本") 53 print("4. 预览修改") 54 print("5. 执行重命名") 55 print("0. 退出") 56 57 choice = input("\n请输入选择:").strip() 58 59 if choice == "1": 60 prefix = input("请输入前缀:") 61 renamer.add_prefix(files, prefix) 62 print("✓ 已添加前缀规则") 63 64 elif choice == "2": 65 suffix = input("请输入后缀:") 66 renamer.add_suffix(files, suffix) 67 print("✓ 已添加后缀规则") 68 69 elif choice == "3": 70 old_text = input("请输入要替换的文本:") 71 new_text = input("请输入新文本:") 72 renamer.replace_text(files, old_text, new_text) 73 print("✓ 已添加替换规则") 74 75 elif choice == "4": 76 renamer.preview() 77 78 elif choice == "5": 79 renamer.preview() 80 confirm = input("\n确认执行?(y/N):").strip().lower() 81 if confirm == 'y': 82 renamer.execute() 83 break 84 else: 85 print("已取消") 86 87 elif choice == "0": 88 print("再见!") 89 break 90 91 else: 92 print("无效的选择") 93 94if __name__ == "__main__": 95 main() 4. 使用示例 场景 1:照片重命名 假设有一批照片: ...

2024-03-18 · 4 min · 787 words · 老墨

Rust 学习笔记 01:简介与环境搭建

Rust 学习笔记 01:简介与环境搭建 “A language that doesn’t affect the way you think about programming, is not worth knowing.” – Alan Perlis 作为一名写了几年 Go 的程序员,习惯了 GC 的安逸,也忍受了 if err != nil 的繁琐。一直听说 Rust 有多强,但每次看到那陡峭的学习曲线和满屏的生命周期引用,都默默劝退。 2024 年了,是时候走出舒适区,挑战一下这个"编译器教你做人"的语言了。 这系列笔记不是官方教程的复读机,而是从一个 Go/Java 开发者的视角,记录学习过程中的困惑、对比和感悟。 1. 为什么要折腾自己? 如果 Go 是一把瑞士军刀,简单实用;那 Rust 就像是一把手术刀,精准锋利,但由于太锋利,很容易割到手。 对于 Go 开发者来说,Rust 的吸引力在于: 零成本抽象:不用担心封装会带来性能损耗。 没有 GC:从此告别 STW (Stop The World),虽然 Go 的 GC 已经很快了,但有些场景下,完全控制内存是刚需。 安全性:编译器会在编译阶段就拦下绝大多数内存错误和并发数据竞争问题。 2. 环境搭建 Rust 的安装体验和 Go 差不多,甚至更现代。 macOS/Linux 一行命令搞定: ...

2024-03-05 · 1 min · 203 words · 老墨

Python教程09:Python编码规范(PEP 8)

Python 教程 09:Python 编码规范(PEP 8) “代码的阅读次数远远多于编写次数。” PEP 8 是 Python 官方的编码规范,定义了如何写出"Pythonic"的代码。遵循这些规范,你的代码会更专业、更易读、更容易被其他 Python 程序员理解。 1. 什么是 PEP 8 PEP (Python Enhancement Proposal) 是 Python 增强提案。PEP 8 专门定义了 Python 代码的风格指南。 核心思想: 代码更多是被阅读,而不是被编写 一致性很重要 可读性至上 完整文档:https://peps.python.org/pep-0008/ 2. 缩进和空格 使用 4 个空格缩进 1# 正确 2def hello(): 3 print("Hello") 4 if True: 5 print("World") 6 7# 错误:使用Tab或2个空格 8def hello(): 9 print("Hello") # 2个空格,不推荐 续行对齐 1# 方法1:对齐左括号 2result = some_function(argument1, argument2, 3 argument3, argument4) 4 5# 方法2:悬挂缩进 6result = some_function( 7 argument1, argument2, 8 argument3, argument4 9) 10 11# 列表、字典的续行 12my_list = [ 13 1, 2, 3, 14 4, 5, 6, 15] # 末尾逗号是好习惯 3. 空行 类和函数之间 1# 顶层函数和类之间空2行 2def function1(): 3 pass 4 5 6def function2(): 7 pass 8 9 10class MyClass: 11 pass 12 13 14class AnotherClass: 15 pass 方法之间 1class MyClass: 2 # 类中的方法之间空1行 3 def method1(self): 4 pass 5 6 def method2(self): 7 pass 函数内部逻辑分组 1def complex_function(): 2 # 初始化部分 3 x = 10 4 y = 20 5 6 # 计算部分 7 result = x + y 8 9 # 返回结果 10 return result 4. 最大行长度 每行不超过 79 个字符(文档字符串/注释不超过 72 个字符)。 ...

2024-03-02 · 4 min · 748 words · 老墨

Python教程08:列表推导式入门

Python 教程 08:列表推导式入门 “简洁是智慧的灵魂。” —— 莎士比亚 列表推导式是 Python 最具特色的语法之一,它让你用一行代码完成原本需要多行循环才能实现的功能。这不仅是代码的简化,更是思维方式的提升。 1. 什么是列表推导式 列表推导式(List Comprehension)是一种简洁的创建列表的方式。 传统方法: 1# 生成1-10的平方 2squares = [] 3for i in range(1, 11): 4 squares.append(i ** 2) 5print(squares) # [1, 4, 9, 16, ..., 100] 列表推导式: 1# 一行搞定 2squares = [i ** 2 for i in range(1, 11)] 3print(squares) # [1, 4, 9, 16, ..., 100] 代码从 3 行变成 1 行,清晰简洁,这就是 Python 的魅力。 2. 基本语法 1[表达式 for 变量 in 序列] 执行过程: 遍历序列中的每个元素 将元素赋值给变量 计算表达式 将结果添加到新列表 1# 示例 2numbers = [1, 2, 3, 4, 5] 3 4# 每个数乘以2 5doubled = [n * 2 for n in numbers] 6print(doubled) # [2, 4, 6, 8, 10] 7 8# 转换为字符串 9str_list = [str(n) for n in numbers] 10print(str_list) # ['1', '2', '3', '4', '5'] 11 12# 调用方法 13names = ['alice', 'bob', 'charlie'] 14capitalized = [name.capitalize() for name in names] 15print(capitalized) # ['Alice', 'Bob', 'Charlie'] 3. 带条件的列表推导式 可以添加 if 条件进行过滤: ...

2024-02-28 · 4 min · 816 words · 老墨

Python教程07:字符串深入

Python 教程 07:字符串深入 “语言是思维的外壳。” 字符串是编程中最常用的数据类型之一,几乎每个程序都要处理文本。今天我们深入学习 Python 字符串的各种操作,从格式化到正则表达式,让你处理文本得心应手。 1. 字符串的创建 Python 中创建字符串有多种方式: 1# 单引号 2s1 = 'Hello' 3 4# 双引号 5s2 = "World" 6 7# 三引号(多行字符串) 8s3 = """这是一个 9多行 10字符串""" 11 12s4 = '''也可以用 13单引号''' 14 15# 原始字符串(忽略转义字符) 16path = r"C:\Users\name\documents" # \n不会被解释为换行 17 18# 字符串拼接 19full = s1 + " " + s2 # "Hello World" 2. 字符串格式化:三种武器 方法 1:%格式化(老式,不推荐) 1name = "张三" 2age = 25 3print("我叫%s,今年%d岁" % (name, age)) 4 5# 格式控制 6pi = 3.14159 7print("π = %.2f" % pi) # 保留2位小数 方法 2:format()方法 1# 位置参数 2print("{}+{}={}".format(1, 2, 3)) 3 4# 索引 5print("{0}+{1}={2}".format(1, 2, 3)) 6print("{2}+{1}={0}".format(3, 2, 1)) # 调换顺序 7 8# 关键字参数 9print("{name}今年{age}岁".format(name="李四", age=30)) 10 11# 格式控制 12print("{:.2f}".format(3.14159)) # 3.14 13print("{:0>5}".format(42)) # 00042(左侧填充0,总宽度5) 14print("{:*^10}".format("Hi")) # ****Hi****(居中,宽度10,填充*) 方法 3:f-string(Python 3.6+,最推荐) 1name = "王五" 2age = 28 3city = "北京" 4 5# 简洁直观 6print(f"{name}今年{age}岁,来自{city}") 7 8# 表达式 9print(f"明年我{age + 1}岁") 10print(f"2的10次方是{2 ** 10}") 11 12# 格式控制 13pi = 3.14159 14print(f"π ≈ {pi:.2f}") 15 16# 对齐和填充 17num = 42 18print(f"{num:0>5}") # 00042 19print(f"{num:*^10}") # ****42**** 20 21# 调试输出(Python 3.8+) 22x = 10 23print(f"{x=}") # x=10 推荐:新代码统一使用 f-string,简洁且高效。 ...

2024-02-27 · 3 min · 622 words · 老墨

Python教程06:控制流程-循环语句

Python 教程 06:控制流程-循环语句 “重复是力量之母。” 如果说条件语句让程序会"选择",那循环语句就让程序会"重复"。想象一下,如果要打印 1 到 100 的数字,难道要写 100 行print()吗?循环语句就是为了解决这类重复性工作而生的。 1. for 循环:遍历序列 for 循环用于遍历序列(列表、字符串、范围等)中的每个元素。 基本语法 1# 遍历列表 2fruits = ["苹果", "香蕉", "橙子"] 3for fruit in fruits: 4 print(f"我喜欢吃{fruit}") 5 6# 遍历字符串 7for char in "Python": 8 print(char) 9 10# 遍历字典 11user = {"name": "张三", "age": 25, "city": "北京"} 12for key in user: 13 print(f"{key}: {user[key]}") 语法要点: for 变量 in 序列: 循环体必须缩进 每次循环,变量会依次取序列中的每个值 range()函数 range()生成数字序列,是 for 循环的好搭档。 1# range(stop):从0到stop-1 2for i in range(5): 3 print(i) # 0, 1, 2, 3, 4 4 5# range(start, stop):从start到stop-1 6for i in range(1, 6): 7 print(i) # 1, 2, 3, 4, 5 8 9# range(start, stop, step):指定步长 10for i in range(0, 10, 2): 11 print(i) # 0, 2, 4, 6, 8 12 13# 倒序 14for i in range(10, 0, -1): 15 print(i) # 10, 9, 8, ..., 1 enumerate():带索引的遍历 有时候需要同时获取元素和索引: ...

2024-01-21 · 4 min · 687 words · 老墨

ios如何同步obsidian笔记仓库?

对于技术人而言,经常需要记录、整理大量的笔记内容,形成自己的知识库。一款好的笔记软件我认为需要具备以下几个条件: 必须:跨平台,同时支持桌面电脑(Windows,Mac,Linux)和手机(Android,iOS) 必须:支持同步,在多台设备中打开任何一台都能接接着编写笔记 必须:实时存储,就算突然断电、司机也不会丢失已写笔记内容 必须:支持代码高亮,更便于友好地阅读代码 必须:支持 Markdown 格式,快速编写文档必备格式,谁用谁知道 必须:支持多重备份,最好是本地一份、远端一份,首选支持git同步的 可选:支持双向链接,这样笔记与笔记之间就可以形成关联关系,慢慢积累后就形成了自己的知识库 可选:支持笔记导出,比如导出 pdf 等格式,便于分享,如果能一键发布到常见博客如 hexo、wordpress、jekyll 更好 我用过诸多笔记软件,但都存在或多或少的问题,无法满足上述要求,后来一直使用网易的有道笔记,它支持一键保存笔记,不过编辑器实在难用,markdown 的图片要自己搭建图床。 阮一峰推荐的笔记软件是 github.dev,可以看看他的这篇文章。不过我更喜欢原生的 app 软件,我选择 obsidian,因为它免费而且支持上述的大部分需求,尤其是第 6 点,对于不想将笔记存储保存给笔记软件厂商而言非常好,直接使用git同步。 现在,我基本上都是用 obsidian 来记笔记和写文章,它支持双向链接,很容易形成知识体系,而且具备丰富的插件支持,具体特性可以自己咨询网络。如果对于格式非常多的文章,我也会使用 AsciiDoc 格式来编写,相比于 Markdown 它更加强大,但是语法也更复杂。Obsidian 目前并不支持,非常遗憾。 在使用 Obsidian 时,最大的问题就是手机端的同步。我是用 Github 存储笔记,手机端没有很好的同步方案,官方的同步方式无法满足需求,而且需要付费。我的需求是,macOS、windows、iPhone、iPad 四种设备上需要从 github 同步我的笔记,没有安卓端同步的需要,所以我使用 iSH 这个 app,它开源免费,支持 ios,完美的解决了我的问题。 什么是 iSH 它是一个在 ios 上模拟 Linux 环境的 app,使用的是 alpine linux,在你的 iPhone、iPad 上都可以运行并创建 Linux Shell 环境。正好我可以使用它来同步 git 仓库到我的 ios 设备上。 安装 ish app 从 ios 应用商店下载 iSH Shell,注意看名称和 logo: ...

2024-01-06 · 2 min · 366 words · 老墨

2023年终回顾和2024新年展望

每至年终,总慨时光飞逝,悔不该浪费光阴而不自知。欲提笔总结一年之种种,却感人生多琐事、岁月少芳华,生活不过柴米油盐酱醋茶。常人者,虽一年之大事鲜有,却亦欲略举一二。 2023年,整个国际形势依旧不太妙,俄乌冲突、巴以冲突、缅甸内战,各国都在想法设法为自己牟利,战乱频发,新冠疫情之后本来就衰弱的国际经济形势更是雪上加霜。逐年变暖的气候,给我们的家园带来了严峻的挑战:火灾、洪水、泥石流、地震、台风…… 2023国际十大新闻 2023中国十大新闻 作为一介庶民,本该独善其身,”各人自扫门前雪,莫管他人网上霜“,然人是社会人,践行之何其难也! 博客 这一年,撰写的文章倒是很少。一方面,感觉没什么可写的,可能自己正慢慢变得懒散了;另一方面,每每要提笔写些什么,发现对要写的东西还了解的不够透彻,只好搁浅。新的一年,得试着多见多听多写,多思考。这是本年的文章列表: 又一个大佬辞世,技术人该何去何从? Redis集群中的 CROSSSLOT Keys Error Nginx Bad Gateway和no live upstreams错误分析 依赖注入库wire使用入门 使用cli框架开发CLI程序 (译)Go1.13中处理错误 (译)Go错误是值 (译)Go模块:管理依赖项 (译)Go 中的字符串、字节、符文和字符 (译)数组、切片和字符串 - “append” 原理 技能 2023除了继续 golang 之外,也复习了一下 python3,包括: 阅读了《Python编程 从入门到实践 第2版》,里边基础讲解不是很透彻,实例倒是不错 廖雪峰的Python教程 语言都是互通的,但建议技术人至少掌握一门静态语言(Java、Go、Rust等)和一门动态脚本语言(Python,Ruby等),以便应对不同的场景,比如使用脚本语言快速开发一些小工具。2024年的工作中应该会大量使用 Go 和 Python。 2024打算加强区块链的课程,包括: 北大肖臻区块链课程 精读《精通以太坊》,之前只是泛读了一遍 精读《精通比特币》 读书是充实自我、提升自我最好的方式。 2023年读了几本书: 《Go语言精进之路1》:掌握go语言编程思想的必要书籍,尤其是从 java 转 go,很多套路与 java 不同,参考本书可以快速掌握 go 的一些编程技巧,本书还有下册,准备2024研究 《朱元璋传》:描写朱元璋的一生,讲述其如何从一个被逼进入寺庙混口饭吃的和尚,到建国当皇帝开辟传奇道路。本书内容较多,适合泛读。 《任正非传》:讲述了任正非与华为的故事,想要了解华为、了解任正非的人必读。 《苏东坡传》:林语堂的作品,讲述大才苏东坡传奇的人生故事。读此书除了了解苏轼一生大才却屡遭贬谪、坎坷的一生,更应该学习其豁达、乐观的人生境界。 《乡土中国》:目前正在读,看费孝通用浅显的文字讲述什么是中国乡土社会。 2024年打算读这几本书: ...

2024-01-02 · 1 min · 77 words · 老墨

又一个大佬辞世,技术人该何去何从?

陈皓,网名左耳朵耗子,技术圈亲切地称他为“耗子叔”、“皓哥”,是一位资深技术大咖、骨灰级程序员,前阿里云资深架构师、天猫开发总监、亚马逊高级研发经理、汤森路透基础架构师和高级研发经理,他的博客酷壳每篇文章的阅读量高达数十万,其发布的专栏课程购买量也近20万,可见其影响力惊人! 然而,这位技术大佬在2023年5月13日因心梗而与世长辞,享年47岁。英年早逝,无不令听者痛心、见者流泪! 我很早都在拜读他的文章了,之前他一直在csdn撰写了大量的技术分享类博文,后来又自建博客。其博文范围广泛,涉及架构、技术、管理、生活方方面面,其文风犀利、见解独到、个性鲜明,每每读之均令人深受启发。这位技术传道者除了爱写作、爱分享,而且还一直处于不断学习、提升自己的状态,其生前博客还发表着GoLang、Rust、区块链等等技术的文章,实乃吾辈之楷模! 有段时间没有翻阅老师的博客了,昨天一打开发现老师并没有更新文章,最近的一篇仍然停留在《是微服务架构不香还是云不香?》 翻阅博客之时,偶然看到评论中说老师此时的消息,难以置信,无比痛心,不得不感叹命运多变、世事无常。 作为技术人的我们,用技术改变着这个世界,却无法避免被这个世界所抛弃。一方面,技术人要被公司体制所折磨,公司的利益高于一切,技术只为公司的利益服务,只要有利可图,随时可以要求技术人加班加点干活,而不管需求是否合理、技术是否可达;另一方面,技术人大多是一个隐藏于后台的微小角色,他们不受重视、不被尊重,肩负着强于身体承受能力数倍的工作量,却因为一些技术问题被公司批评,甚至辞退。加班、熬夜、饮食不规律、运动少、职业病、亚健康等等词汇都是贴在每一个技术人身上的标签,年轻时不断地过度消耗着身体,殊不知随着年龄的增长,这些“负债”总是要一点点偿还,甚至是付出生命的代价。 正如陈皓在其痛恨手册中所述:“ 痛恨各种不从研发团队出发,不从团队和项目出发的流程、方法论、咨询师、SQA、流程部门。 痛恨那些为所欲为的,为了自己商业目标牺牲用户利益的中国IT企业。 痛恨中国的C2C式的那种简单的抄袭和复制。 ……” 在这喧杂的社会,我们不得不沉下心来思考:我们会不会是下一个陈皓?我们又该何去何从呢?推荐大家阅读《软技能:代码之外的生存指南》一书,该书从职业规划、自我营销、自我学习提升个人专业技能,到理财、健身、精神来修炼自身体质等多个层面向我们展示了技术人应该如何进行技能、身心双修。 这里大致罗列作者的观点,值得我们深思: 职业:每一位技术人都需要学会管理自己的职业生涯。树立明确的目标,指定可靠、周密的计划,什么时候做什么都经过精心的安排,然后朝着目标努力。 自我营销:你的营销手段决定了你的营销对象是受益还是受损。营销需要人们的关 注,以便让人们关注你,关注你的产品。优秀的营销会将人们的需要或者期待与能够满足此愿望的产品或服务关联起来。“实现价值在先,要求回报在后”。做自己的产品、写博客、发视频,让跟多的人认识你。 学习:“活到老,学到老”。尤其是技术人,在这个飞速变化的世界里,学习的能力是至关重要的。技术人绝不能固步自封,忽视自己的技能发展,否则就会被社会淘汰。 生产力:“外行静坐等待灵感,其他人则唤起激情努力工作”。如何克服拖延症,“做该做的事”,我们需要运用方法、使用工具、培养习惯,提升自己的生产力。 理财:技术人耗费青春拿到较高的薪酬,但是如果不会理财,纵使有万贯家财,最终也可能毁于一旦。 健身:“身体是革命的本钱”,长期的久坐令技术人大多处于亚健康的状态,我们需要健身,只有强健的身体才能更好的发展自己。我认为这是大部分技术人忽略的一项,但它确实重中之重! 精神:我们并不是简单的机器——我们是人类。我们不只是一个与思想相连的躯壳。我们不能只下达指示然后就期望身体能完成这些指令。这个世界存在着另一股很强大的力量,它能带领我们走 上成功之路,把我们推向成功。这种力量为——精神。身体强健了,还要求我们保持良好的精神,乐观、豁达的心态,积极上下的生活态度,都是技术人所必须的。我们不只是只在电脑上敲敲打打的码农,我们更是父母、儿女、丈夫和妻子,积极地融入生活、享受生活吧。 陈皓老师走了,仍然有无数个陈皓前仆后继地为技术而奋斗着。我们执着与技术的同时,别忘了我们仍然是这个社会的一份子,更需要积极、乐观的去享受生活的美好! 谨以此文悼念陈皓老师,一路走好!

2023-12-27 · 1 min · 24 words · 老墨

Python教程05:控制流程-条件语句

Python 教程 05:控制流程-条件语句 “人生处处是选择。” 程序和人生一样,也需要做出选择。条件语句就是让程序具备"决策"能力的工具,就像十字路口的红绿灯,告诉你该往哪走。 1. if 语句:单向选择 最简单的条件语句,满足条件就执行,不满足就跳过。 1age = 20 2 3if age >= 18: 4 print("你已经成年了") 5 print("可以独立做决定") 语法要点: if后面跟条件表达式,以冒号结尾 条件代码块必须缩进(通常 4 个空格) 缩进的代码属于 if 块,一起执行或跳过 2. if-else:双向选择 两条路,必须选一条。 1age = 15 2 3if age >= 18: 4 print("成年人,可以投票") 5else: 6 print("未成年,不能投票") 就像走到岔路口,往左或往右,总要选一个方向。 3. if-elif-else:多向选择 当选择超过两个时,使用elif(else if 的缩写)。 1score = 85 2 3if score >= 90: 4 grade = "A" 5elif score >= 80: 6 grade = "B" 7elif score >= 70: 8 grade = "C" 9elif score >= 60: 10 grade = "D" 11else: 12 grade = "F" 13 14print(f"分数:{score},等级:{grade}") 执行顺序: 从上到下依次判断 遇到第一个为 True 的条件就执行,然后跳出整个 if-elif-else 结构 如果所有条件都是 False,执行 else 块(如果有的话) 这就像走迷宫,找到第一个出口就出去了,不会继续找其他出口。 ...

2023-12-06 · 3 min · 566 words · 老墨

Python教程04:运算符

Python 教程 04:运算符 “巧妇难为无米之炊。” 有了数据类型,接下来就要学会如何操作这些数据。运算符就是操作数据的工具,就像厨房里的刀、铲、勺,每种工具各有用途。 1. 算术运算符 最基础的运算符,用于数学计算。 基本运算 运算符 说明 示例 结果 + 加法 5 + 3 8 - 减法 5 - 3 2 * 乘法 5 * 3 15 / 除法 5 / 2 2.5 // 整除 5 // 2 2 % 取模(余数) 5 % 2 1 ** 乘方 2 ** 3 8 1# 算术运算示例 2a = 10 3b = 3 4 5print(f"{a} + {b} = {a + b}") # 13 6print(f"{a} - {b} = {a - b}") # 7 7print(f"{a} * {b} = {a * b}") # 30 8print(f"{a} / {b} = {a / b}") # 3.333... 9print(f"{a} // {b} = {a // b}") # 3 10print(f"{a} % {b} = {a % b}") # 1 11print(f"{a} ** {b} = {a ** b}") # 1000 有趣的细节 除法的"历史遗留问题" ...

2023-12-04 · 5 min · 878 words · 老墨

Python教程03:数据类型基础

Python 教程 03:数据类型基础 “万物皆有类。” 在 Python 的世界里,所有数据都有自己的类型。了解数据类型,就像认识食材,知道哪些能一起烹饪,哪些会"水火不容"。 1. Python 的基础数据类型 Python 有几种基础数据类型,今天我们先学习最常用的四种: 类型 英文名 示例 说明 整数 int 42, -100, 0 没有小数点的数字 浮点数 float 3.14, -0.5, 2.0 带小数点的数字 字符串 str "Hello", 'Python' 文本数据 布尔值 bool True, False 真或假 还有一个特殊的值:None,表示"空"或"无值"。 2. 整数(int) 整数就是没有小数部分的数字,可正可负可为零。 1# 整数示例 2age = 25 3temperature = -10 4zero = 0 5 6print(age, type(age)) # 25 <class 'int'> 7print(temperature) # -10 8 9# Python 3的整数可以无限大(只要内存够) 10big_number = 1234567890123456789012345678901234567890 11print(big_number) # 正常输出,不会溢出 12 13# 不同进制的整数 14binary = 0b1010 # 二进制,等于十进制的10 15octal = 0o12 # 八进制,等于十进制的10 16hexadecimal = 0x1F # 十六进制,等于十进制的31 17print(binary, octal, hexadecimal) # 10 10 31 整数运算 1a = 10 2b = 3 3 4# 基本运算 5print(a + b) # 13 加法 6print(a - b) # 7 减法 7print(a * b) # 30 乘法 8 9# 除法:注意Python 3的除法很特别 10print(a / b) # 3.3333... 除法,结果是浮点数 11print(a // b) # 3 整除,结果是整数 12print(a % b) # 1 取模(求余数) 13 14# 乘方 15print(a ** 2) # 100 (10的2次方) 16print(2 ** 10) # 1024 这里有个有趣的现象:在 Python 3 中,10 / 3的结果是3.333...(浮点数),而不是像 Go/Java 那样得到3。如果你想要整除,必须用//。 ...

2023-11-20 · 3 min · 633 words · 老墨

Python教程02:基础语法

Python 教程 02:基础语法 “纸上得来终觉浅,绝知此事要躬行。” 学编程和学游泳一样,看再多教程也不如下水扑腾几下。今天我们来学习 Python 的基础语法,这些是写代码的"规矩"。 1. 注释:给代码写"旁白" 注释就像电影里的旁白,是写给人看的,不会被 Python 执行。写注释有两个好处: 提醒自己:三个月后回头看代码,如果没有注释,你可能会问"这是谁写的垃圾代码?"(然后发现是自己写的) 方便别人:团队协作时,注释能让其他人快速理解你的思路 单行注释 用#开头,从#开始到行尾的所有内容都是注释: 1# 这是一个单行注释 2print("Hello") # 这也是注释,可以放在代码后面 3 4# 计算圆的面积 5radius = 5 6area = 3.14 * radius ** 2 多行注释 Python 没有专门的多行注释语法,但可以用三引号('''或""")来实现: 1""" 2这是一个多行注释 3可以写很多行 4通常用于写文档字符串(docstring) 5""" 6 7''' 8单引号也可以 9但更推荐用双引号 10''' 11 12def calculate_area(radius): 13 """ 14 计算圆的面积 15 16 参数: 17 radius: 圆的半径 18 19 返回: 20 圆的面积 21 """ 22 return 3.14 * radius ** 2 最佳实践:注释要写"为什么",而不是"是什么"。比如# 计算圆的面积这种注释意义不大,因为代码本身已经很清楚了。更好的注释是:# 使用简化的π值,精确度够用且计算更快 ...

2023-11-10 · 3 min · 522 words · 老墨

Python教程01:Python简介与环境搭建

Python 教程 01:Python 简介与环境搭建 “工欲善其事,必先利其器。” 在开始学习 Python 之前,我们先要把环境搭建好。别担心,这比组装宜家家具简单多了。 1. Python 是什么? Python 是一种高级编程语言,由荷兰程序员 Guido van Rossum 于 1991 年创建。有趣的是,这个名字并非来自那条盘在树上的蟒蛇,而是源自英国喜剧团体"Monty Python"(蒙提·派森)。Guido 在开发 Python 时正在看这个喜剧团的剧集,觉得这名字"简短、独特、略带神秘",于是就用了。 不过大家还是约定俗成地用蛇做 Logo,毕竟这样看起来更酷一些。 Python 的三大特点 简单易学:语法接近自然语言,读代码就像读英文句子 功能强大:从网站开发到人工智能,从自动化脚本到科学计算,几乎无所不能 生态丰富:拥有数十万个第三方库,就像一个超级大工具箱 用一句话概括:Python 是程序员界的瑞士军刀——简单好用,功能齐全。 2. 为什么学 Python? 应用领域广泛 Web 开发:Django、Flask、FastAPI 框架让你快速搭建网站 数据分析:Pandas、NumPy、Matplotlib 是数据科学家的标配 人工智能:TensorFlow、PyTorch 支撑着深度学习的发展 自动化脚本:批量处理文件、爬虫、运维工具,让重复劳动自动化 游戏开发:Pygame 虽然小众,但也很有趣 科学计算:SciPy 在科研领域广泛应用 市场需求大 根据 TIOBE 编程语言排行榜,Python 常年稳居前三。这意味着无论你是找工作、做副业,还是只是想提升技能,Python 都是一个非常好的选择。 就像学外语,你可以学世界语(优雅但没人用),也可以学英语(实用且吃香)。Python 就是编程界的"英语"。 代码简洁优雅 Python 崇尚"用最少的代码做最多的事"。同样的功能,Python 代码往往比其他语言短 50%以上。比如打印 1 到 10: 1# Python:简洁明了 2for i in range(1, 11): 3 print(i) 非常的简洁。 3. Python 2 vs Python 3:历史的遗留问题 目前 Python 有两个主要版本: ...

2023-10-18 · 2 min · 369 words · 老墨

Redis集群中的 CROSSSLOT Keys Error

场景 Redis单节点没有问题,切换到Redis Cluster后业务上某一功能出错: CROSSSLOT Keys in request don't hash to the same slot 出错的代码: 1var ctx = context.TODO() 2_, err := uq.queueImpl.rc.TxPipelined(ctx, func(pip redis.Pipeliner) error { 3 cmd := pip.LPush(ctx, uq.key, dest...) 4 if cmd.Err() != nil { 5 return cmd.Err() 6 } 7 return pip.SAdd(context.Background(), uq.setkey, dest...).Err() 8}) 这段代码的逻辑是向 list 中push一条数据,再向一个 set 加入数据,两步操作通过 pipline 在同一个事务中完成。 问题分析 错误的大概意思就是: 请求中跨槽的key没有被hash到相同的hash槽中。通过代码分析,事务中的两次操作的key并不相同,他们没有被hash到同一个hash槽从而出现上述错误。 什么是hash槽 Redis Cluster 规范中的 Key distribution model(key分布模型)说明如下: Redis集群中将key的存储空间划分为16384个slot,整个集群可以支持最大16384个主节点,实际上建议不超过1000个节点。每一个主节点又可以处理16384个 hash slot。整个集群将hash slot分布于不同的节点组成高可用集群,单个节点有多个副本以便在节点故障时将重新平衡节点,达到高可用的目的。关于可用性保证,可以看这里。 如下的公式用于计算key的hash slot: HASH_SLOT = CRC16(key) mod 16384 Redis将相同hash值的slot分布到同一个node下,如下图所示: 图片出自这里 可以看出,hash槽(slot)就是一个整数,通过key计算得来,它的作用就是决定key存储于哪一个节点中。 为什么会出现CROSSSLOT错误 主要原因是应用程序尝试在多个键上运行命令,但操作中涉及的键不在同一个哈希槽中,这会导致集群中不允许的“CROSSSLOT”操作。 比如,使用Set的SUION命令时,如果多个key的 hash slot 不在集群中的同一个node上,则会出现CROSSSLOT错误。 前边场景中,在同一个事务中操作多个key,集群环境下必须要保证这些被操作的key必须被hash到同一个slot,否则同样会抛出CROSSSLOT错误。 Redis这么做的主要原因还是在于避免分布式数据被破坏的风险,而且在同一个事务下或者同一个命令中操作多个跨节点的key,会因网络等因素带来性能损耗,所以Redis禁止这么做。如果有这种场景,Redis也提供了解决方案:使用Hash Tags。 问题解决 那么,什么是 Hash Tag? ...

2023-10-17 · 1 min · 128 words · 老墨

Nginx Bad Gateway和no live upstreams错误分析

最近项目的生产环境中客户端出现大量的Nginx 502 Bad Gateway错误,逐步排查最终定位到是由于被ddos攻击造成服务器资源耗尽无法响应造成的问题。遂整理过程著文以记之。 场景 线上4个节点,每个节点都有两个相同服务通过Nginx作负载均衡,均采用Nginx默认值,未做过多配置,配置类似: 1upstream test-server { 2 server 127.0.0.1:8002; 3 server 127.0.0.1:8001; 4} 客户端出现大量的 502 Bad Gateway 信息,查看Nginx错误日志,信息如下: no live upstreams while connecting to upstream 初步定位问题,发现后台出现了很多莫名其妙的错误,查看Nginx错误日志发现也打印了很多上述错误。怀疑是后台某个接口请求出错导致返回了 500,导致Nginx将这个服务下线,后经过排查,后台确实存在一些错误信息,但是出错上述错误的时间不匹配。 后整理思路并认真思考,发现可能思路存在偏差:后台http状态码怎么会影响Nginx将服务下线呢? 因为Http状态码表示http响应的状态,也就是表示响应结果的正确与否,比如 2xx 表示服务端正确处理并响应了数据,4xx 表示客户端存在错误妨碍了服务端的处理,5xx 表示服务端错误导致处理失败了。但是,能够返回这些状态码说明服务端连接正常,只是由于特定原因导致服务端响应错误了。返回了这些状态码Nginx当真会将服务下线吗?试想一下,某一个客户端请求查询一条不存在的数据导致服务端处理时抛出异常并响应 500 状态码,然后Nginx认为服务不可用并将其下线,导致一定时间内所有的请求都返回上述错误,那么罪魁祸首到底是服务端还是客户端?Nginx这么处理是不是太过了呢?Nginx肯定不会这么来设计。 所以,我猜测Nginx不可能如此是非不分,应该是别的原因导致的。 查阅upstream官方文档,看到这么两个参数配置: fail_timeout=time: 设置多少时间内不能与下游服务成功通信 max_fails 次后可以认为下游服务不可用,这是一个周期时间,即每隔 fail_timeout 时间都会进行统计,默认为 10 秒。 max_fails=number: 设置在 fail_timeout 时间内与下游服务通信失败的次数,达到该次数后认为服务不可用,默认为1。 按照Nginx的默认设置,也就是说每个10秒统计下有服务的通信失败次数,达到1次就认为服务不可用,此时Ngingx会将其踢下线,后续所有转发到该服务的请求都会返回 no live upstreams while connecting to upstream,直到下一个10秒再重新处理(max_fails 为0又重新将服务上线)。 关键在于这个 通信失败 的理解。通信失败,表示Nginx转发请求给服务,但是服务没有任何响应,而不是一开始怀疑的 HTTP 状态不是200,能成功响应,不论是什么状态码,都应该认为与服务通信成功。 实践才能出真知,为了验证我的猜想,必须进行实验。 实验 使用golang编写一个http服务,代码如下: 1var inErr bool 2func main() { 3 port := os.Args[1] 4 node := os.Args[2] 5 r := gin.Default() 6 go func() { 7 for { 8 if node == "node1" { 9 break 10 } 11 inErr = !inErr 12 time.Sleep(3 * time.Second) 13 } 14 }() 15 r.GET("/", func(ctx *gin.Context) { 16 if inErr { 17 ctx.String(http.StatusInternalServerError, "error: "+node) 18 } else { 19 ctx.JSON(http.StatusOK, gin.H{"msg": "ok, " + node}) 20 } 21 }) 22 r.GET("/err", func(ctx *gin.Context) { 23 ctx.String(http.StatusInternalServerError, "error: "+node) 24 ctx.Abort() 25 }) 26 r.GET("/timeout", func(ctx *gin.Context) { 27 time.Sleep(time.Second * 10) 28 ctx.String(http.StatusOK, "after 10s responsed") 29 }) 30 _ = r.Run(":" + port) // 参数0为执行文件本身信息,真正的参数下标为1 31} 上述代码使用了 gin 框架,大致的逻辑: ...

2023-10-07 · 5 min · 922 words · 老墨

依赖注入库wire使用入门

依赖注入(Dependency injection)不是一个新概念了,早在2004年 Martin Fowler 就在其文章Inversion of Control Containers and the Dependency Injection pattern提出了依赖注入模式。在Java的世界中,随处可见依赖注入的使用,如Spring框架的核心之一就是控制反转(IoC),而依赖注入就是 IoC 思想的一种技术实现,比如我们常用的 Spring 的注解 @Autowire,还有 Jsr250规范定义的 @Resource 注解都实现了依赖注入。 简单而言,依赖注入就是以构建松散耦合的程序为目的、以控制反转为基本思想而在软件工程实现的一种编程技术,它使得程序不需要关注对象的创建逻辑,只是通过函数或者属性告诉程序自己需要什么对象,关注所依赖对象的使用逻辑,从而将对象的创建和使用过程分离开。可见,依赖注入技术遵循依赖倒置原则。 虽然 GO 不是面向对象的语言,但是它也有依赖注入实现,常用的依赖注入框架包括 google 自身出品的 wire、Uber的dig、Facebook的inject等等。本文将介绍 wire 的基本使用。 不使用依赖注入时 在开始介绍 wire 之前,我们来编写一个简单的程序,该程序可以创建问候者并向您问好。 我们创建一个 greeter.go 文件,它包含以下几个结构体: 1type Message string 2 3type Greeter struct { 4 Msg Message 5} 6 7type Event struct { 8 Greeter Greeter 9} Message 表示消息,Greeter 表示问候者,它需要一个 Message,Event 表示一个事件,它用来触发问候。 现在,我们需要增加创建这些结构体的方法: 1func NewMessage(name string) Message { 2 return Message(fmt.Sprintf("hello, %s!", name)) 3} 4 5func NewGreeter(msg Message) Greeter { 6 return Greeter{Msg: msg} 7} 8 9func NewEvent(g Greeter) Event { 10 return Event{Greeter: g} 11} 12 13func (g Greeter) Greet() Message { 14 return g.Msg 15} 16 17func (e Event) Start() { 18 msg := e.Greeter.Greet() 19 fmt.Println(msg) 20} 通过 NewMessage 创建消息,通过 NewGreeter 创建一个问候者,通过 NewEvent 创建事件,然后就可以调用 Start 方法来发起问候了,其实底层最终调用的是 Greeter 的 Greet 方法。 ...

2023-08-06 · 4 min · 680 words · 老墨

(译)Go1.13中处理错误

本文翻译自GoLang官方文档, 见原文地址. 介绍 在过去的十年中,Go 将 “错误作为值” 来处理 ,这对我们很有帮助。尽管标准库对错误的支持很少 —— 只有 errors.New 和 fmt.Errorf 函数,它们产生的错误只包含一条消息 —— 内置 error 接口允许 Go 程序员添加他们想要的任何信息。它所需要的只是一个实现 Error 方法的类型: 1type QueryError struct { 2 Query string 3 Err error 4} 5 6func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() } 像这样的错误类型无处不在,它们存储的信息千差万别,从时间戳到文件名再到服务器地址。通常,该信息包括另一个较低级别的错误以提供额外的上下文。 一个错误包含另一个错误的模式在 Go 代码中非常普遍,经过 广泛讨论 后,Go 1.13 添加了对它的明确支持。这篇文章描述了提供该支持的标准库的新增内容:errors 包中的三个新函数,以及 fmt.Errorf. 在详细描述更改之前,让我们回顾一下在该语言的先前版本中如何检查和构造错误。 Go 1.13 之前的错误 检查错误 Go 错误是值。程序以几种方式根据这些值做出决策。最常见的是比较错误以 nil 查 看操作是否失败。 1if err != nil { 2 // something went wrong 3} 有时我们将错误与已知的 标记 值进行比较,以查看是否发生了特定错误。 1var ErrNotFound = errors.New("not found") 2 3if err == ErrNotFound { 4 // something wasn't found 5} 错误值可以是满足语言定义 error 接口的任何类型。程序可以使用类型断言或类型开关将错误值视为更具体的类型。 ...

2023-05-05 · 4 min · 743 words · 老墨

(译)Go错误是值

Rob Pike,2015 年 1 月 12 日 ,原文地址: https://go.dev/blog/errors-are-values Go 程序员(尤其是刚接触该语言的程序员)讨论的一个共同点是如何处理错误,这些讨论最终往往都将回归于无数次出现的代码片段: 1if err != nil { 2 return err 3} 如上所示,但我们最近扫描了所有我们能找到的开源项目,发现这个片段每页或每两页只出现一次,比某些人认为的要少。尽管如此,许多程序员仍然认为必须键入如下代码来处理错误: 1if err != nil 一直以来,人们觉得一定有什么地方出了问题,而明显的目标就是 Go 本身。 这是不幸的、误导性的,而且很容易纠正。也许刚接触 Go 的程序员会问,“我该如何处理错误?”,然后学习这种模式,最后就此打住。在其他语言中,可能会使用 try-catch 块或其他类似机制来处理错误。因此,程序员认为,我在我的旧语言中可以使用 try-catch 时,但在 go 中我只能输入 if err != nil 来处理错误。随着时间的推移,Go 代码收集了许多这样的片段,结果感觉很笨拙。 不管这种解释是否成立,很明显这些 Go 程序员忽略了关于错误的一个基本观点: 错误是值。 可以对值进行编程,并且由于错误是值,因此可以对错误进行编程。 当然,涉及错误值的常见语句是测试它是否为 nil,但是错误值可以做无数其他事情,应用其中一些其他事情可以使您的程序更好,从而消除大部分样板代码,避免多次使用 if 语句检查每个错误。 一个简单示例是 bufio 包中的 https://go.dev/pkg/bufio/#Scanner[`Scanner`]。它的 https://go.dev/pkg/bufio/#Scanner.Scan[`Scan`] 方法执行底层 I/O,这当然可能导致错误。然而,该 Scan 方法根本不会暴露错误。相反,它返回一个布尔值和一个单独的方法,在扫描结束时运行,报告是否发生错误。客户端代码如下所示: 1scanner := bufio.NewScanner(input) 2for scanner.Scan() { 3 token := scanner.Text() 4 // process token 5} 6if err := scanner.Err(); err != nil { 7 // process the error 8} 当然,有一个 nil 检查错误,但它只出现并执行一次。假设将该 Scan 方法改为: ...

2023-04-30 · 2 min · 389 words · 老墨

(译)Go模块:管理依赖项

文章翻译自Golang官方文档,见原文地址。 当您的代码使用外部包时,这些包(作为模块分发)成为依赖项。随着时间的推移,您可能需要升级或更换它们。Go 提供了依赖管理工具,可帮助您在合并外部依赖项时确保 Go 应用程序的安全。 本文介绍如何执行一些任务来管理您代码中的依赖项,您可以使用 Go tools 执行其中的大部分操作。本主题还介绍了如何执行其他一些您可能会觉得有用的依赖相关任务。 相关阅读: 如果您不熟悉模块和依赖,请查看 入门教程 以获得简要介绍。 使用该 go 命令管理依赖项有助于确保您的需求保持一致,并且您的 go.mod 文件的内容是有效的。有关命令的参考,请参阅 go命令文档。您还可以通过键入 go help 命令名称从命令行获取帮助,如go help mod tidy`. 使用 Go 命令更改依赖项时会编辑 go.mod 文件。有关文件内容的更多信息,请参阅 go.mod 文件参考文档。 让您的编辑器或 IDE 能够感知 Go 模块可以让您更轻松地管理它们。有关支持 Go 的编辑器的更多信息,请参阅 编辑器插件和 IDE。 本主题不描述如何开发、发布和版本模块以供其他人使用。有关更多信息,请参阅 开发和发布模块。 使用和管理依赖项的工作流程 您可以通过 Go tools 获取和使用有用的包。在 pkg.go.dev 上,您可以搜索您觉得有用的包,然后使用 go 命令将这些包导入您自己的代码中以调用它们的功能。 下面列出了最常见的依赖管理步骤: 在 pkg.go.dev 上 «查找和导入包, 查找有用的包»。 «查找和导入包, 在代码中导入所需的包»。 将您的代码添加到模块以进行依赖跟踪(如果它不在模块中)。请参阅 «代码中启用依赖项跟踪, 启用依赖项跟踪» [«添加依赖项, 添加外部包作为依赖项»,以便您可以管理它们。 随着时间的推移,根据需要 «升级或降级依赖项, 升级或降级依赖版本»。 依赖项作为模块管理 在 Go 中,依赖项作为包含导入包的模块来管理。此过程由以下功能支持: ...

2023-04-19 · 3 min · 628 words · 老墨

(译)Rob Pike 带你深入理解 Go 字符串:字节、符文与 Unicode 编码

大家好,我是极客老墨! 今天这篇文章是早期老墨翻译自 Golang 官方博客文章,有一定的深度,写的非常好,读完你会对字符串的设计和底层原理有一个明确的认识,建议收藏后细细品味。 原文地址: https://go.dev/blog/strings, Rob Pike 简介 这篇文章讨论了 Go 中的字符串。起初,字符串对于一篇博文来说似乎太简单了,但要很好地使用它们,不仅需要了解它们的工作原理,还需要了解字节、字符和符文之间的区别,Unicode 和 UTF- 8、字符串和字符串字面量的区别,以及其他更细微的区别。 处理该话题的一种方法首先是回答这个问题:“当我在位置 n 检索 Go 字符串时,为什么我没有得到第 n 个字符?” 正如您将看到的,这个问题引导我们了解有关文本在现代世界中如何工作的许多细节。 什么是字符串? 让我们从一些基础知识开始。 在 Go 中,字符串实际上是只读的字节切片。如果您完全不确定字节切片是什么或它是如何工作的,请阅读 数组、切片和字符串 一文。 重要的是首先要明确一个字符串包含_任意_多个字节,不论字符串是否包含 Unicode 文本、UTF-8 文本或任何其他预定义格式。就字符串的内容而言,它完全等价于一个字节切片([]byte)。 下边是一个字符串(稍后详述),它使用 \xNN 符号来定义一个字符串常量,其中包含一些特殊的字节值(字节的取值范围从十六进制值 00 到 FF)。 const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" 打印字符串 由于上边我们的示例字符串 sample 中的某些字节不是有效的 ASCII,甚至不是有效的 UTF-8,所以直接打印字符串会产生奇怪的输出。简单的打印语句如下: 1fmt.Println(sample) 产生这种乱码(输出可能因环境而异)输出: ��=� ⌘ 为了找出 sample 字符串底层到底是什么,我们需要把它拆开检查一下。有几种方法可以做到这一点。最明显的是循环其内容并单独提取字节,如以下for循环所示: 1for i := 0; i < len(sample); i++ { 2 fmt.Printf("%x ", sample[i]) // 输出为十六进制格式 3} 正如前文所述,索引字符串访问的是单个字节,而不是单个字符,我们将在下面详细阐述该主题。现在,让我们只使用字节,这是逐字节循环的十六进制输出: bd b2 3d bc 20 e2 8c 98 请注意各个字节如何与定义字符串的十六进制转义匹配。 为凌乱的字符串生成可呈现输出的一种更简便的方法是使用fmt.Printf方法的%x(十六进制)格式,它只是将字符串的每两个连续字节转储为十六进制数字: ...

2023-03-25 · 4 min · 714 words · 老墨

(译)Go创造者Rob Pike带你深入了解数组、切片和字符串 底层的 “append” 原理

大家好,我是极客老墨! 今天这篇文章是早期老墨翻译自 Golang 官方博客文章,有一定的深度,非常经典,读完你会深入了解数组、切片和字符串的 Append 原理,建议收藏后细细品味。 原文地址: https://go.dev/blog/slices,Rob Pike 介绍 过程编程语言最常见的特征之一是数组的概念。数组看起来很简单,但在将它们添加到语言时必须回答许多问题,例如: 固定尺寸还是可变尺寸? 大小是类型的一部分吗? 多维数组是什么样的? 空数组有意义吗? 这些问题的答案会影响数组是否只是语言的一个特性还是其设计的核心部分。 在 Go 的早期开发中,在设计感觉正确之前,花了大约一年的时间来确定这些问题的答案。关键步骤是引入 slices(切片),它建立在固定大小的 array (数组)之上,以提供灵活、可扩展的数据结构。然而,直到今天,刚接触 Go 的程序员经常对切片的工作方式感到困惑,也许是因为其他语言的经验影响了他们的思维。 在这篇文章中,我们将尝试消除混淆。我们将通过构建片段来解释 append 内置函数是如何工作的,以及为什么它会以这种方式工作。 数组 数组是 Go 中的一个重要构建块,但就像建筑物的基础一样,它们通常隐藏在更可见的组件之下。在我们继续讨论更有趣、更强大、更突出的切片概念之前,我们必须简单地讨论一下它们。 数组在 Go 程序中并不常见,因为数组的大小是其类型的一部分,这限制了它的表达能力。 以下代码: 1var buffer [256]byte 声明了一个数组变量 buffer ,[256]byte 表示它持有的数据类型为 byte,长度为 256。如果想声明 512 个字节的数组可以这样: [512]byte。 与数组关联的数据就是 数组中的元素。上边声明的数组缓冲区在内存中看起来像这样: 1buffer: byte byte byte ... 256 times ... byte byte byte 也就是说,该变量只保存 256 个字节的数据,仅此而已。我们可以使用熟悉的索引语法 buffer[0]、buffer[1] 到 buffer[255] 来访问它的元素。(索引范围 0 到 255 涵盖 256 个元素)尝试使用超出此范围的索引值访问 buffer 会使程序崩溃。 内置函数 len 会返回数组、切片或其他一些数据类型中的元素数量。在我们的示例中,len(buffer) 返回固定值 256。 数组有它们适合的使用场景 —— 例如,它们是转换矩阵的良好表示,但它们在 Go 中最常见的用途是为切片保存存储空间。 ...

2023-02-09 · 8 min · 1613 words · 老墨

(译)进入Go模糊测试的世界

原文地址: https://go.dev/doc/tutorial/fuzz[] 本教程介绍了 Go 中模糊测试的基础知识。模糊测试会针对您的测试准备一些随机数据然后运行测试时使用它们,以尝试找出漏洞或导致崩溃的输入。可以通过模糊测试发现的一些漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击(XSS)。 在本教程中,您将为一个简单的函数编写一个模糊测试,运行 go 命令,并调试和修复代码中的问题。 有关本教程中术语的帮助,请参阅 “词汇表”。 您将逐步完成以下部分: «为您的代码创建一个文件夹» «添加代码进行测试» «添加单元测试» «添加模糊测试» «修复无效字符串错误» «修复双反错误» «结论» 注意 更多 Go 教程,请参阅 https://go.dev/doc/tutorial/index.html[教程]。 Go fuzzing 当前支持 https://go.dev/security/fuzz/#requirements[Go Fuzzing 文档] 中列出的内置类型的子集,并支持将来添加的更多内置类型。 先决条件 Go 1.18 或更高版本的安装。 有关安装说明,请参阅 https://go.dev/doc/install[安装 Go]。 用于编辑代码的工具。 您拥有的任何文本编辑器都可以正常工作。 一个命令终端。 Go 在 Linux 和 Mac 上的任何终端以及 Windows 中的 PowerShell 或 cmd 上都能很好地工作。 支持模糊测试的环境。 目前仅在 AMD64 和 ARM64 架构上使用覆盖检测进行模糊测试。 [[为您的代码创建一个文件夹]] 为您的代码创建一个文件夹 首先,为您要编写的代码创建一个文件夹。 1、 打开命令提示符并切换到您的主目录。 在 Linux 或 Mac 上: ...

2022-12-19 · 9 min · 1893 words · 老墨

(译)初始Go模糊测试

原文地址: https://go.dev/security/fuzz/ 从 Go 1.18 开始,Go 在其标准工具链中支持模糊测试。Native Go 模糊测试受 https://google.github.io/oss-fuzz/getting-started/new-project-guide/go-lang/#native-go-fuzzing-support[OSS-Fuzz 支持]。 Go模糊测试详细教程见:进入Go模糊测试的世界 一文。 概述 Fuzzing 是一种自动化测试,它不断地操纵程序的输入以查找错误。Go fuzzing 使用覆盖率指导来智能地不断重复执行模糊测试的代码,以发现并向用户报告问题。由于它可以覆盖人类经常错过的边缘情况,因此模糊测试对于发现安全漏洞特别有价值。 下面是一个 «fuzztarget, 模糊测试» 的例子,突出了它的主要组成部分。 上图显示整体模糊测试的示例代码,其中包含一个模糊目标( «corpus, fuzz target» )。 在模糊目标之前调用 f.Add 添加种子语料库,模糊目标的参数高亮显示为fuzzing参数。 编写模糊测试 要求 以下是模糊测试必须遵循的规则。 模糊测试必须是一个形如 FuzzXxx 的函数,以 Fuzz 作为前缀,它只接受一个 *testing.F 参数并且没有返回值。 模糊测试必须在 *_test.go 文件中才能运行。 调用 https://pkg.go.dev/testing#F.Fuzz[(*testing.F).Fuzz] 方法时的匿名函数参数称之为 «fuzztarget, 模糊目标»,形如 func(t *testing.T, xxx)*, 它必须是一个函数,接受一个 *testing.T 作为第一个参数,其他后续参数称为模糊参数,且该函数没有返回值。 每个模糊测试必须只有一个模糊目标。 所有 «seedcoprus, 种子语料库» 条目必须具有与 «fuzzing, 模糊参数» 相同的类型,并且顺序相同。这适用于调用 https://pkg.go.dev/testing#F.Add[(*testing.F).Add] 添加的种子语料库和 testdata/fuzz 目录中已有的语料库文件。 模糊测试参数只能是以下类型: string, []byte int, int8, int16, int32/rune, int64 uint, uint8/byte, uint16, uint32, uint64 float32, float64 bool 建议 以下建议将帮助您充分利用模糊测试。 ...

2022-10-26 · 3 min · 435 words · 老墨

[GoLang避坑实战-05] 玩转 iota:优雅定义常量的黑科技,你真的会用吗?

大家好,我是极客老墨。 写 Go 之前,我在 Java 里定义枚举都是老老实实写 enum。转到 Go 发现没有 enum 关键字,心想"这不得手写 0、1、2、3?" 结果发现 Go 给了个更骚的东西:iota。自动递增,省掉一堆重复代码。 更绝的是 Go 的"无类型常量"设计,直接让你不用管类型转换的破事儿。这篇就聊聊 Go 常量的几个巧妙设计。 常量基础:用 const 声明 常量就是编译期就确定值、运行时不能改的东西。声明很简单: 1const c1 = 1 2const c2 = -100 3const c3 = "hello" 跟变量一样,可以用括号批量声明: 1const ( 2 c4 = 3.14 3 c5 = 1.2 + 12i // Go 支持复数,数学系的福音 4) ⚠️ 小坑:不赋值会复制上一个值 批量声明时,如果某个常量不赋值,它会复制上一个非空表达式的值: 1const ( 2 m = 1 // 1 3 n // 1,复制 m 的值 4 k // 1,继续复制 5 l = m + 1 // 2,新表达式 6 i // 2,复制 l 的表达式结果 7) 8fmt.Println(m, n, k, l, i) // 输出:1 1 1 2 2 这个特性配合 iota 用起来很爽,后面会讲。 Go 的神设计:无类型常量 你注意到没?前面声明常量时都没写类型。这是 Go 的一个巧妙设计:无类型常量。 有类型常量的麻烦 Go 是强类型语言,类型不同就不能直接运算,哪怕底层类型一样: ...

2022-09-10 · 7 min · 1381 words · 老墨

[GoLang避坑实战-04] 变量也玩"躲猫猫"?揭秘 Go 类型系统里那些隐蔽的坑

大家好,我是极客老墨! 从 Java 或 Python 转 Go 的时候,我在变量声明这块卡了好一会儿。 不是说 Go 的变量有多复杂,而是它给了你好几种声明方式。var 和 := 到底啥区别?类型推导怎么玩?指针和 Java 有啥不同?常量还能玩出花来? 这篇文章一次讲透。 变量声明:var 和 := Go 有两种主要声明变量的方式。 用 var 声明 这是最"正统"的写法,适合先声明、后赋值的场景: 1// 声明单个变量 2var name string 3name = "Go" 4 5// 声明时直接赋值(类型可省略) 6var age = 18 // Go 自动推导为 int 7 8// 批量声明 9var ( 10 x int 11 y float64 12 z bool 13 s string 14) 用 := 声明 这是 Go 的语法糖,声明 + 赋值一步到位: 1// 必须初始化,Go 自动推导类型 2message := "Hello, Go!" 3fmt.Println(message) 4 5// 接收函数返回值 6result, err := someFunc() 什么时候用哪个? 用 var 的场景: ...

2022-08-02 · 7 min · 1281 words · 老墨

[GoLang避坑实战-03] 专业选手的养成:用 golangci-lint 直抵企业级代码规范

大家好,我是极客老墨。 Go作为编程语言中的后起之秀,在语言规范上吸收了诸多语言的优点,并形成了自身独特的语言风格。本文探讨一下Go语言的代码风格。 main方法 main 方法同样是go程序的入门函数,但是其定义非常简单,比如上一篇中的: 1func main() { 2 fmt.Println("Hello, world!") 3} 直接用 func 关键字定义一个名为 main 的方法即可,没有任何参数。 那么,如何获取传递给 main 的参数呢,可以使用 os.Args: 1func main() { 2 fmt.Println("hello go!") 3 for i, arg := range os.Args { 4 fmt.Println(i, "=", arg) 5 } 6} os.Args 获取命令行传递的参数,第一个始终为执行程序的名称,一个示例执行结果如下: 1$ go run main_func.go a b 2hello go! 30 = /var/folders/8b/y3pklwbs1wj_cq7hm_yjwgpr0000gn/T/go-build3458283994/b001/exe/main_func 41 = a 52 = b 当然,通常情况不会使用 os.Args 来解析命令行参数,而是使用 go 标准库的 flag 包。 go的关键字 go的内置关键字有25个: 1break default func interface select 2case defer go map struct 3chan else goto package switch 4const fallthrough if range type 5continue for import return var 这25个关键字不能作为标识符使用。 标识符命名 Go中的标识符同java一样,必须由字母、数字和下划线"_“组成,而且第一个不能为数字: 1func main() { 2 _a := 1 3 fmt.Println(_a) 4 b := 2 5 fmt.Println(b) 6 // 3c := 3 // 不能编译 7 你好 := 5 8 println(你好) 9} 有意思的是,除了25个关键字之外的合法标识符,只要不存在冲突,都可以作为标识符,比如包名也可以作为标识符: ...

2022-07-30 · 7 min · 1438 words · 老墨

[GoLang避坑实战-02] 初识 Go:除了高并发,它还有哪些让你尖叫的特性?

大家好,我是极客老墨。这是本教程的第一篇,我们讲介绍 Go 语言的发展历史,并完成安装、运行 HelloWorld 程序。 Go 语言是最近几年发展最快、最火热的编程语言之一,由 Google 公司出品,其学习成本低、天生支持高并发、语法简洁高效、强大的标准库以及日益丰富的生态特性等使得其非常适合开发后端服务程序,并逐渐挑战着 Java 在服务端编程语言中的领导地位。 Go语言的诞生 Go 语言的前身被认为是一种名为 Limbo 的编程语言,它是由 Unix 之父、C语言之父 肯·汤普逊(Ken Thompson) 和 丹尼斯·里奇(Dennis Ritchie) 这两位计算机灵魂人物领衔并在一个名为 Plan 9 的操作系统研究项目中发明的。 Go语言起源于2007年,当时还是身在 Google 的 Plan 9 项目原班人马在业余时间编写完成的,后来 Google 意识到了 Go 的巨大潜力并全力支持这个项目。Go 在2009年11月正式对外开放,并在2012年3月28日发布了第一个正式版本。 Go 语言的主要开发者有 肯·汤普逊(Ken Thompson)、罗布·派克(Rob Pike)、罗伯特·格里泽默(Robert Griesemer)等,每一位都是赫赫有名的大师级人物: 肯·汤普逊:与丹尼斯·里奇在贝尔实验室发明了Unix操作系统,以及用于该系统的 C编程语言 罗布·派克:Unix小组成员,参与了Unix后续的Plan 9和Inferno操作系统,同时也是Limbo语言和Go语言共同的发明者 罗伯特·格里泽默:协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8,Go语言的共同创造者 Go的特点 Go 始于 2007 年 9 月,当时 Robert Griesemer、Ken Thompson 和我开始讨论一种新语言,以解决我们和 Google 的同事在日常工作中面临的工程挑战。 ...

2022-07-21 · 5 min · 1061 words · 老墨

(译)Go并发模式——Context

原文地址: https://go.dev/blog/context[] 作者:Sameer Ajmani 时间:29 July 2014 简介 footnote:ddns[这篇文章是go官网博客中的一篇,尽管文章比较早,但是较详细的描述了 context 出现的原因、使用方式,仍然值得一读。] 在 Go 服务器中,每个传入的请求都在其自己的 goroutine 中处理。请求处理程序通常会启动额外的 goroutine 来访问数据库和 RPC 服务等后端。处理请求的一组 goroutine 通常需要访问特定于请求的值,例如最终用户的身份、授权令牌和请求的截止日期。当请求被取消或超时时,所有处理该请求的 goroutines 都应该快速退出,以便系统可以回收它们正在使用的任何资源。 在 Google,我们开发了一个 context 包,可以轻松地将请求范围的值、取消信号和截止日期等跨 API 边界传递给正在处理请求的所有 goroutine。 https://go.dev/pkg/context[该软件包] 作为context公开可用 。本文介绍了如何使用该包并提供了一个完整的工作示例。 Context context 包的核心是 Context 类型: 1type Context interface { // <1> 2 Done() <-chan struct{} // <2> 3 4 Err() error // <3> 5 6 Deadline() (deadline time.Time, ok bool) // <4> 7 8 Value(key interface{}) interface{} // <5> 9} <1> Context 携带截止日期、取消信号和请求范围的跨越 API 边界的值,多个 goroutine 同时使用它的方法是安全的。 <2> Done 返回一个在此 Context 取消或超时时的通道(chan) <3> Err 错误信息说明 context 为什么被取消, 在 Done 返回的 chan 被关闭之后获取 <4> Deadline 返回 Context 被取消的时间 <5> Value 返回参数 key 关联的值,没有则返回 nil ...

2022-07-19 · 5 min · 1012 words · 老墨

以太坊入门之交易

"上一篇" 介绍了MetaMask钱包的安装和使用,并成功获取到部分测试以太币(ETH)并向另外一个账户发起了一笔转账,这笔转账称之为"交易",我们将在本篇来介绍它。 1. 以太坊的账户 首先,在学习交易之前,我们需要了解以太坊的账户体系。 以太坊的账户分为两种:外部账户和合约账户。 外部账户:外部账户一般由钱包创建,具有私钥,通过私钥控制和区块链网络和智能合约的访问; 合约账户:以太坊中智能合约部署后对应着一个合约账户,合约账户拥有智能合约代码,并受其逻辑控制。 简单而言,外部账户就是区块链的用户通过钱包创建的账户,而合约账户由合约代码编写者所有,对应着一个智能合约,由 EVM 执行。 合约账户和外部账户地址没有明显的区别,都是长度 20字节、160 位、转为十六进制(0x开头)后为 40 个字符的一个串,形如 0x829BD824B016326A401D083B33D092293333A830 两者的区别: 启动交易者(首先发起交易的一方)必须是拥有私钥的外部账户,也就是说只有用户可以启动交易;但是智能合约可以通过调用别的智能合约来对交易做出响应,比如,用户A通过智能合约A发起了一笔交易,虽然智能合约A调用智能合约B也可以发起了一笔交易,但是这整个过程中交易的启动者还是用户A的外部账户 外部账户和合约账户都可以发送和接收区块链货币(如以太币),但是交易目标如果是合约账户,则会使得合约账户拥有的智能合约代码在 EVM 中执行,外部账户则不会 合约账户没有私钥,智能合约编写者通过代码逻辑来保证安全性,外部账户拥有私钥,用户需要通过妥善的保管私钥来保证账户的安全性 2. 以太币单位 交易与货币息息相关,以太坊发行的货币为以太币(Ether, ETH,符号 Ξ),其基本单位为 wei,ETH与wei的关系如下: 1 ETH = 10^18 wei 此外,还有比较常用的 gwei (gigawei): 1 ETH = 10^9 gwei 1 gwei = 10^9 wei Table 1. 以太币的面额和单位 值(以wei为单位) 指数 通用名称 标准名称 1 1 wei wei 1,000 103 babbage kilowei or femtoether 1,000,000 106 lovelace megawei or picoether 1,000,000,000 109 shannon gigawei or nanoether 1,000,000,000,000 1012 szabo microether or micro 1,000,000,000,000,000 1015 finney milliether or milli 1,000,000,000,000,000,000 1018 ether ether 1,000,000,000,000,000,000,000 1021 grand kiloether 1,000,000,000,000,000,000,000,000 1024 megaether ...

2022-07-16 · 2 min · 373 words · 老墨

[GoLang避坑实战-01] Go 1.24 安装排雷指南:拒绝被"环境变量"折磨

大家好,我是极客老墨! 在正式开始这套 Golang 基础教程之前,老墨要先跟你聊聊一个很多人容易忽略的问题:环境配置。 你可能会想:“不就是装个 Go 嘛,有啥好说的?” 但老墨见过太多同学,拿着 Go 1.11 的版本跑 Go 1.18 的泛型代码,然后在群里问:“为什么我的代码报错?” 所以,磨刀不误砍柴工,咱们先把环境搞对,后面学起来才能一路畅通。 关于本教程 这套 Golang 教程分为三个部分: 基础教程(本系列):从零开始学习 Go 语言的核心语法和基础特性 高级教程(规划中):工程实践、性能优化、并发模式等进阶内容 Web 开发(规划中):Gin、GORM 等 Web 框架的实战应用 目前你正在阅读的是基础教程部分,适合 Go 语言初学者或想系统学习 Go 基础的开发者。高级教程和 Web 开发部分老墨会在后续完善后陆续发布,敬请期待! 1. Go 版本选择 1.1 本教程使用的版本 本教程所有代码基于 Go 1.24+ 编写和测试。 由于老墨编写这一套教程时间跨度较大,可能教程中的 Go 版本较旧,但是老墨建议大家都使用最新的 LTS 版本即可,GoLang 官方保证了兼容性,所以理论上最新版本的 Go 都可以跑本教程的代码。 1.2 如何查看你的 Go 版本 1go version 如果输出类似这样,就说明安装成功了: go version go1.24.0 darwin/arm64 1.3 版本兼容性说明 Go 版本 本教程兼容性 说明 1.24+ 完全兼容 推荐使用,支持所有最新特性 1.21-1.23 完全兼容 支持泛型、slog 等特性,可以正常使用 1.18-1.20 部分兼容 泛型相关代码可以跑,但 slog 等新特性不可用 1.16-1.17 不兼容 不支持泛型,部分代码无法运行 < 1.16 不兼容 太老了,建议升级 2. 安装 Go 2.1 macOS 安装 方法一:使用 Homebrew(推荐) ...

2022-07-10 · 3 min · 449 words · 老墨

以太坊入门之MetaMask钱包的安装和使用

区块链中,与用户直接相关的一个重要组件是钱包。用户通过钱包来访问链上的资金、查看账户地址、转账交易等操作。可以说,没有钱包,用户就不能正常访问链上资金,可见其重要程度。而诸多钱包中,MetaMask是一款可以直接在浏览器中使用的、实现了钱包标准 BIP-39 的钱包,也是大多数使用者选择的入门级钱包。 1. 区块链钱包简介 与普通的实物钱包不同,区块链钱包的核心目的是用来保存用户的公钥和私钥,从而保证访问区块链中账户资金的权限,即:证明钱是你的,而不是存储资金。 Figure 1. 区块链钱包(图片来源网络) 钱包的一个重要的误区是:钱包存储资金。其实,钱包并不会直接存储区块链上的资金。可以将其看做一个银行账户,当你需要取钱时,只需告诉银行要取多少钱,然后银行就可以检查账户的余额是否充足并进行取款,账户只是一个逻辑划分,钱始终是存在银行。同理,区块链钱包也只是在区块链上开了一个账户,钱始终在区块链网络中。 开通银行账户,我们必须设置交易密码,已验证你有访问账户的权限;同样的,区块链钱包中,私钥就相当于账户密码,每产生一笔交易时,都需要通过钱包的私钥来进行签名,已验证你拥有操作账户资金的权限。 但是,与银行账户不同,银行是中心化的资金机构,除了验证账户密码,还有其他手段比如身份证来验证取款人身份,忘记密码还可以重置。但在区去中心化的区块链中,私钥是你唯一的凭证,一旦私钥泄露,那么任何人都可以访问你的账户。 因此,钱包可以看成是一个私钥圈,保存了多组公钥私钥对。现在的钱包大多采用随机串生成助记词,再加密生成种子密钥,最后再以种子密钥为根生成密钥树的方式来生成密钥。这里仅说明钱包的概念,关于钱包的底层原理,后续在详细讨论。 Figure 2. 钱包中的密钥树(图片来源网络) 2. 什么是MetaMask MetaMask 是一款简单易用的区块链钱包,除了 App 版本的钱包,它还提供了基于浏览器的插件,包括 Chrome、Firefox等,而且它内置了 Web3,可以直接与以太坊区块链交互并开发 DApp 网页应用,这也是大多开发者选择它的原因。 3. 安装MetaMask 安装 MetaMask 很简单,以 Chrome 为例,直接从 Google 商店安装即可,前提是需要自带梯子。步骤如下: 1、进入google应用商店,直达地址在 这里,也可以自己搜索MetaMask,第一个小狐狸头像的就是 2、点击 "添加至Chrome",然后弹出框点击"添加至扩展"即可,然后会下载 Chrome 插件,耐心等待 3、下载完成后,Chrome会自动安装,完成后会进入扩展插件地址 点击开始使用,进入钱包创建页面 4、创建钱包 如果您以前有钱包,并记得助记词,那么可以直接通过助记词导入钱包,没有则点击"创建钱包"按钮新建,下一步点击"我同意",进入密码设置页面: 设置并牢记自己的密码,以后登录钱包是需要用到,然后点击"创建"按钮后,会出现一个介绍的视频页面,直接点击"下一步" 5、备份助记词 这一步很重要,助记词作为恢复秘钥的唯一手段,需要十分安全地做备份,一般建议手抄写在纸上并保存,其他电子存储方式均存在泄漏风险 什么是助记词? 按照密钥生成策略分,钱包分为两类:非确定性钱包和确定性钱包,前者的每一个私有都是由不同的随机数生成的,私钥间没有任何关联,备份和恢复非常麻烦;而确定性钱包则是通过一个随机串作为种子密钥来生成各种私钥,只要备份种子密钥就可以恢复出所有的私钥。 但是,由于种子密钥长度长而且毫无规律,非常难以记录和输入,因此人们为这个种子密钥按照一定的算法来为其匹配单词表中的一组顺序固定、数量固定(与种子密钥长度有关)的单词,这样便于备份和恢复,这有点类似密码本,这些单词就成为助记词(mnemonic)。 因此,助记词可以看做种子密钥的别名,其安全性与之等同,必须安全保存。 ...

2022-06-11 · 1 min · 123 words · 老墨

什么是以太坊

上一篇 简单介绍了什么是区块链,也介绍了以太坊的产生和基本概念,其中提到: 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。作为开放平台和”世界超级计算机”,以太坊以智能合约为核心为去中心化应用(DApp)建设提供了整套解决方案。这一篇我们将详细介绍什么是以太坊。 1. 为什么要学习以太坊 以太坊是目前最大的区块链开发平台,也是“世界计算机”。以太坊是区块链开发工作的必经之路,原因如下: 以太坊是目前最大的区块链开发平台,拥有庞大的用户群和社区 以太坊发展早,技术相对成熟,资料、文档众多 以太坊生态体系完备,除了智能合约、DApp、DAO、DeFi,还提供了 ENS、Swarm等DApp构建所需技术体系 以太坊减缓了区块链陡峭的学习曲线,以应用入手,使得区块链的开发上手快速、简单 以太坊与 JavaScript 结合紧密,开发者学习难度小 总之,以太坊好比微信开放平台,DApp开发好比基于微信公众号、小程序的开发,我们不用太关注底层的东西,而是使用以太坊和微信已经提供好的功能来开发自己的DApp和微信公众号,只是以太坊是基于去中心化的区块链,而微信则是中心化应用。 2. 以太坊的诞生 上一篇 介绍了什么是区块链,其中提到, Vitalik Buterin (V神)在2013年发表了《以太坊:一个下一代智能合约和去中心化应用平台》,提出了以太坊的基本构想。 V神是比特币的狂热粉丝,他不断思考着如何扩展比特币和其智能合约协议 Mastercoin,并提出了一种可替代 Mastercoin 的专用合约语言的更灵活和脚本的智能合约机制,这只是一个他的初步构想,尽管比特币团队非常感兴趣,但是这种想法过于超前,而且与比特币的开发计划存在冲突,所以并没有得到支持。 V神 2013年12月,V神发表了 以太坊白皮书,勾勒出以太坊背后的思想:一个图灵完备的通用目的的区块链。以太坊白皮书发布后,不断有区块链爱好者向V神提交并反馈自己的想法,帮助其不断完善这个想法。其中,Gavin Wood(Mastering Ethereum的作者之一)在C++语言上对以太坊给与了极大地帮助,后来成为了以太坊的联合创始人和CTO,并编写了 以太坊黄皮书。 Gavin Wood 白皮书与黄皮书 白皮书由V神编写,旨在提出改进比特币背后区块链技术的一种构想,使其成为一种通用的、适用于多个方面而不仅仅是虚拟货币的区块链技术,这仅仅是一种理论,在以太坊的成长过程中得以逐步实现; 黄皮书是由以太坊CTO Gavin Wood 博士编写,旨在定义以太坊的技术规范,详细描述了以太坊背后的技术实现细节,其中包含了大量的数学公式,比如,黄皮书定义了产生每一笔交易包含的通用字段、如何验证交易,以太坊虚拟机规范(EVM)等等。黄皮书着重于以太坊技术规范的制定,包含大量数学公式,专业性很强,比较难懂,这里[1]有一个面向普通大众的浅黄版本。 Figure 1. 白皮书 vs 黄皮书 V神与Gavin Wood一起,不断完善以太坊的协议层和技术体系,逐步实现“可编程的、安全的、通用的区块链”这一目标。2015年7月30日,第一个以太坊区块被成功挖出,以太坊作为“世界计算机”正式运行并对外提供服务。 3. 以太坊发展的四大阶段 以太坊的发展并不是一帆风顺的,以太坊的发展整体上分为四个阶段 [2],代号分别为:Frontier(边疆)、Homestead(家园)、Metropolis(都会) 和 Serenity(宁静),每一个阶段都以"硬分叉"的方式发布,因此新版本不能与老版本兼容。并且,每一个阶段都在不断完善其新功能和安全性,比如2016年以太坊遭受DAO攻击,而分叉了 "以太坊经典" 和 "以太坊" 这样的相互竞争、并行的以太坊版本。 阶段和硬分叉按照区块高度编号并标注日期,包括: Frontier: 区块高度为0,以太坊的初始阶段,持续时间为2015年7月30日至2016年3月。 Ice Age: 冰河期,区块高度为 200,000,引入指数难度增加的硬分叉,以便在准备就绪时将激励机制过度到Pos(权益证明)。 Homestead:家园,区块高度 1,150,000, 以太坊的第二阶段,发布于2016年3月。 DAO: 区块高度 1192000,以太坊遭受严重的攻击而不得不硬分叉,从而产生了 以太坊经典(Ethereum Classic, ETC) 和 以太坊(ETH) 两个相互竞争的系统 [3]。 Tangerine Whistle:橘子口哨,区块高度 2,463,000,改变某些IO密集操作的燃气计算方法和清除拒绝服务攻击(利用这些操作的低燃气成本)累积状态的硬分叉。 Spurious Dragon:伪龙,区块高度 2,675,000,2016 年 11 月 22 日发生的另一次计划外的硬分叉,包括四项提案,用以解决一些攻击问题: EIP 155: 简单重放攻击保护 EIP 160: 提升EXP操作码的费用 EIP 161: 状态树清理 (不变量保持替代) EIP 170: 调整智能合约的最大字节数限制 ...

2022-05-09 · 2 min · 238 words · 老墨

什么是区块链

区块链是这几年非常火热的一个话题,越来越多的人加入到”区块“大军之中。他们其中一些人,积极投入到挖矿、炒币行列之中,甚至发行自己的数字货币来创收,这部分人社称之为”币圈“;另一部分人,深入探究区块链底层技术,期望不断改进并将区块链技术运用到更多领域中,这部分人社称之为”链圈“。越来越多的人积极群涌入区块链,游弋于“币圈"和"链圈"之间,忙的不亦乐乎。 本文将简单介绍区块链的一些入门知识,并介绍区块链开发所需要具备的基础知识,希望对准备进入区块链的开发者们会有一些帮助。 1. 什么是区块链 区块链(Blockchain)源于 比特币,它其实是对比特币的底层技术的一种扩展和创新。2008年,一位网名“中本聪”的日本大佬发布了一篇名为 《比特币:一种点对点的电子现金系统 的论文(比特币 白皮书),文中总结了之前的数字货币发明,提出了一种完全去中心化的数字货币系统,即比特币。文中并没有提及“区块链”一词,而是使用了“区块”(Block)和“链”(Chain)来描述比特币底层技术,后来人们将区块、链合并在一起,正式提出“区块链”(Blockchian)这一概念。 区块链是一系列技术的组合,包括P2P动态组网、密码学、基于密码学的共享账本、共识机制、智能合约等。简单而言,区块链可以看做一个全球互联的超级账本,链中的每一台计算机通过P2P组网连接并保留了账本的副本,这样,链中的计算机都有一份账本。每一笔交易都需要记录到账本中,当一笔交易产生后,产生这笔交易的计算机将交易信息广播出去,其他链上的计算机就会收到交易信息,它们会进行一系列复杂计算,最终由率先计算完成的计算机将这笔交易信息记录到账本上。具体的计算机制其实是由不同的区块链设计好的,与该区块链的结构相关。比如按照比特币的设计,计算机需要将10分钟内的全部交易打包,再加上一个随机数,然后穷举随机数计算一个256位的哈希值,使得其符合一定的条件,这样就可以获得交易记账权,一旦计算完成并记账,该计算机要快速将计算的区块广播出去,由其他计算机来验证计算的正确性,防止他人篡改。 每产生一笔交易并不是立即就要被区块的各个节点记账,而是按设计要求被打包到一起,比特币设计为每10分钟将所有新交易打包(而以太坊时间更短,大约9秒左右),形成了一个区块(Block),然后将区块广播出去,区块链中的计算机都需要验证区块的正确性,并将其写入自己的账本中,完成记账。区块链则是由一个个相互连接的区块组成的,每一个区块都记录了上一个区块的 HASH 值,第一个区块没有上一个区块的 HASH 值,称之为”创世区块“。计算哈希值竞争记账权的计算机称为“矿工”,计算的过程称为”挖矿“,挖矿成功则获得交易记账权,记录打包好的交易信息并写入最新的区块,称为”出块“,最新出块的矿工可以获得一定的手续费收入。 区块链中的计算机都有账本,那么如果确定各个账本数据的一致性呢?区块链采用”共识机制“来保证大部分诚实计算机的账本一致性,这样就实现了完全去中心化。常用的共识机制有Pow(工作量证明机制,例如挖矿机制)、PoS(权益证明)、DPos(股权授权证明)和分布式一致性算法(如拜占庭容错算法、Paxos、Raft)。 与传统的中心化应用相比,区块链具有如下特点: 去中心化:区块链本质是基于P2P对等网络的分布式系统,链上的每一个节点都存有相同的数据,通过共识机制来决定交易数据的同步,不需要中心化服务器。但是目前,还不能做到完全的去中心化,某些场景还是具备中心化的特征。 高度开放:公有链上的区块链系统通常都是开源的,任何人都可以查询区块链上的数据,智能合约代码高度公开、透明,数据存储与链上所有节点中,不用担心中心化应用的资金安全等一系列问题。 高度可用:区块链应用由区块链上的每一个节点共同维护,任何一个或几个节点宕机不影响整个应用的可用性。 高度安全:区块链技术采用一系列加密措施(如非对称加密、哈希算法、椭圆曲线算法等)来对交易进行加密和签名,保证交易不能被伪造、篡改,然后借助分布式系统中各个节点的共识算法形成强大算力来抵御破坏者的攻击,从而保证链上数据的安全性。 匿名性:区块链上的用户信息都是匿名的,仅对外暴露钱包公钥和地址,就可以完成交易,完全不需要用户的真实身份信息。 2. 什么是以太坊 比特币作为一种虚拟数字货币,为金融领域开创了一种全新的去中心化货币系统。比特币的狂热粉们一直在探索,比特币技术能否使用到其他非金融领域呢? 以太坊 的出现,为区块链技术迎来了崭新的时代。 2013年,比特币的忠实拥护者 Vitalik Buterin (人称V神)提出了一种构想:区块链不应该仅用于金融领域,而是应该开放出来形成一个开放平台,供人们开发更多的去中心化应用,为此他提出了以太坊,并发表了《以太坊:一个下一代智能合约和去中心化应用平台》,也就是后来的 《以太坊白皮书》。 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。 首先,以太坊是一条发行有自己货币(ether,以太币)的区块链;其次,以太坊具备开发去中心化应用功能,它具有能够在以太坊虚拟机(EVM)上运行的 智能合约 功能,开发者可以编写智能合约来自主控制去中心化应用;第三,以太坊优化了PoW(工作量证明机制),并提供了PoS权益证明共识机制,大大加快了区块链交易确认速度(比特币确定一笔交易需要10分钟,而以太坊减少到9秒左右),为去中心化应用开发提供了必要条件。 总之,以太坊是一个区块链开放平台,通过它可以开发自己的区块链 去中心化(Decentralization)应用(Dapps)。 以太坊被称为"第二代的区块链平台", 这个网站 用车站的例子动态、形象地展示了以太坊和比特币之间的一些差别,非常生动有趣。 3. 区块链中的一些基本概念 了解了区块链和以太坊,现在我们来看看区块链中的一些概念。 区块链:Blockchain,共享的分布式账本,交易附加到区块上存储并记录。 区块:Block,多个交易被打包为区块并存储,区块之间相互连接,形成一个链状结构,每一个区块都有一个哈希值加以区分,区块在链中的长度称为区块高度。 钱包:Wallet,存储用户虚拟货币的地方,对外暴露公共地址和公钥,内部包含有绝对私密的私钥信息。 以太坊:Ethereum,一个基于区块链的去中心化运行智能合约的平台,旨在解决与审查,欺诈和第三方干扰相关的问题。 主链:Mainnet,正式运行的区块链的主网络,一个区块链项目经过前期的技术开发后,最终都会发布到区块链主网上线,比如 以太坊主网。 侧链:Sidechains,侧链实质上不是特指某个区块链,而是指遵守侧链协议的所有区块链,该名词是相对于主链而言的。侧链协议是指可以让主币安全地从区块链主链转移到其他区块链,又可以从其他区块链安全地返回主链的一种协议。侧链与主链共存,并可以扩展主链的功能、提高交易速度等,比如ETM就是以太坊ETH的侧链。 测试链:Testnet,与主链功能相同的测试网络,以测试为目的搭建,可以免费从水龙头获得测试币在测试链上进行开发测试,如 币安测试链。 交易:由原始帐户签署的提交到以太坊区块链的数据,并以特定地址为目标。交易包含元数据,例如交易的燃气限额(Gas limit)。 区块链浏览器:查询区块链中数据的工具网站,可以详细查询区块链上的详细信息,如 以太坊浏览器、 币安链浏览器等等。 Dapp:去中心化应用,基于区块链技术开发的应用程序,数据存储于区块链中。 DAO:去中心化自治组织,一种将组织的管理和运营规则以智能合约的形式编码在区块链上,从而在没有集中控制或第三方干预的情况下自主运行的组织形式。DAO 有望成为应对不确定、多样、复杂环境的一种新型有效组织。 以太币:Ether,以太坊生态系统中使用的本地货币,在执行智能合约时承担gas费用,它的符号是 Ξ. 以太坊虚拟机:EVM,Ethereum Virtual Machine, 基于栈的、执行智能合约字节码的虚拟机。 智能合约:Smart Contract,在以太坊的虚拟机中执行的程序,有开发者编写以控制链上业务逻辑。 水龙头:Faucet,一个网站,用来向测试用户提供一定数量的测试币,供测试使用,测试币除了测试并没有真正的价值。 Solidity:过程式(命令式)编程语言,语法类似于 Javascript, C++ 或 Java,以太坊智能合约最流行和最常使用的语言。由以太坊架构师 Gavin Wood 发明。 图灵完备:Turing Complete,在计算理论中,如果数据操纵规则(如计算机的指令集,程序设计语言或细胞自动机)可用于模拟任何图灵机,则它被称为图灵完备或计算上通用的。这个概念是以英国数学家和计算机科学家阿兰图灵命名的。 ...

2022-04-17 · 1 min · 213 words · 老墨

Typora For Mac、Windows破解版和免费版下载

Typora是一个所见即所得(WYSIWYG)的markdown编辑器,由于其快捷、方便,并且配备强大的快捷键来快速编写markdown文档,一经发布就深受大批用户(尤其是程序猿)的喜爱,也是迄今为止我用过的最好用的markdown编辑器了! Typora有如下特性: 轻量级,性能非常高,启动时间在秒级 所见即所得,也是其最大的特性,它只需要一个界面就可以完美展示渲染后的markdown格式,而不需要像大多数markdown编辑器需要两个界面,一个显示源码,一个显示渲染结果 支持表格、UML图、数学公式,编写方便 支持大量展示主题,可以下载使用他人发布的主体,也支持自定义主题 支持markdown自动目录 支持图片拖拽调整位置,支持调整图片显示大小,支持搭建自定义图床 支持导出PDF格式文件 Typora在1.0之前的版本都是免费使用的,从1.0开始,官方宣布Typora收费,价格14.99$,人民币89元,价格不算太贵,有条件请支持正版! 本站从网络上搜索好了一些平台的破解版本和最后的免费版本,请根据需要自行下载,下载地址如下: Typora for Mac 1.2.3破解版下载:链接: https://pan.baidu.com/s/1g-xT_AaagWBvzvKVBDGkmg 密码: grvk Typora for Windows 1.2.4破解版下载:链接: https://pan.baidu.com/s/1-tfx_Bz0ScExaqrixlFVzg 密码: 5it7 Typora for Mac 0.11.18免费版下载:链接: https://pan.baidu.com/s/1Xlt6fhEF_TLJpXBryPRI6w 密码: mrei Typora for Windows 0.11.18免费版下载:链接: https://pan.baidu.com/s/1DRorRI4gX9b_sgk0WdhxOA 提取码: ttsx Typora for Linux 0.11.18免费版下载:链接: https://pan.baidu.com/s/1O1aA0p_DTzzWkMCAD0g5cQ 提取码: hm84 下载声明 本站所有下载资源均来自互联网,由站长搜集整理,版本归原作者所有,如有侵权请联系站长删除。 本站所有下载资源仅供个人学习和研究使用,请勿用于商业用途,请在下载后24小时内删除。

2022-04-14 · 1 min · 49 words · 老墨

读《Java并发》— Java内存模型

"上一章" 介绍了线程安全性,这一章称为"对象的共享",书中重点介绍了如何共享和发布对象,这两章是并发编程中非常基础却很重要的部分。在本章,首先介绍了什么是可见性问题,然后介绍了Java内存模型,讨论什么是内存可见性以及java保证内存可见性的方式,在此基础上介绍如何设计线程安全的对象,如何使用线程封闭技术和设计不可变对象来避免同步,最后再重点探讨如何安全地发布对象。由于内容较多,我将这一章分拆为几篇来阐述自己对本章的理解,这是第一篇。 Java的并发机制基于共享内存,要理解对象间的共享关系,则离不开对象间的内存关系,这涉及到本章要介绍的一个重要概念:Java内存模型,又称 JMM。 1. 内存可见性 上边提到,Java的并发机制是采用的是 共享内存模型,因此,在并发环境中保证对象间的内存可见性是并发编程解决的主要问题。 什么是内存可见性?可见性是一个复杂的问题,它表示程序中的变量在写入值后是否能够立即读取到。在单线程环境中,由于写入变量和读取变量都是在单线程中进行的,因此能够保证总能读取到修改后的值。但在多线程环境下,却无法保证,可能一个线程修改变量的值,而另外的线程并不能正确读取被修改的变量的值,除非我们使用变量同步等机制来保证可见性。 为什么多线程环境下变量就不能保证可见性了呢?稍后介绍JMM时再来讨论,先看一个示例。 内存可见性(Memory Visibility):某些线程修改了变量的值,正在读该变量的线程能够立即读取到修改后的值。 @NotThreadSafe public class UnsafeSequence { private int count; public void increment() { count++; } public int getCount() { return count; } } 上边的示例,count变量被多个线程共享,因此不能保证 getCount() 总能读取到 increment() 增加后的值。那是不是在 increment() 方法上使用 synchronized 进行同步就能保证可见性了呢?答案是不行。虽然使用同步能够保证只有一个线程修改count的值,但是其他多个线程仍然可能读到 失效的值,因此必须在 getCount() 上也使用同步,见 "这里"。 再看一个示例,如下边的代码: @NotThreadSafe public class NoVisibility { private static boolean ready; private static int anInt; private static class VariableReader implements Runnable { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(anInt); } } public static void main(String[] args) { new Thread(new VariableReader(), "read").start(); anInt = 47; (1) ready = true; (2) } } ...

2022-03-28 · 3 min · 578 words · 老墨

读《Java并发》— 线程安全性

上一章 介绍过,并发编程会带来诸多挑战,最基本的就是 线程安全性,但这又是一个非常复杂的主题。这一章重点介绍了什么是线程安全性、原子性、竞态条件等概念,以及在Java中如何通过加锁来确保线程安全性。 1. 什么是线程安全性 1.1. 正确性 要理解什么是线程安全性,必须先明白什么是正确性,正确性是线程安全性的核心概念。 正确性的含义是,某个类的行为与其规范完全一致。 — Java并发编程实战 这个定义从静态视角出发强调了类的规范重要性。通常,我们不会定义类的详细规范,但是我们应该为类和方法提供文档注释,来说明类是否是线程安全的,以及对于线程安全性如何保证。尤其在方法上,应该明确规定该方法是否已经保证了线程安全,调用者是否应该在同步机制内调用该方法等等。 下边是 ArrayList 的类文档注释的节选,它告诉调用者关于线程安全的内容: Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. ...

2022-01-25 · 6 min · 1177 words · 老墨

读《Java并发》— 并发简史

这一章主要讲述线程的发展历史,以及并发编程带来的优势和挑战。 1. 线程的发展 1946年第一台计算机诞生,一直到20世纪50年代中期,这时候的计算机没有操作系统的概念,采用手工操作的方式工作,每次只能有一个人使用计算机。此时的手工操作方式,用户独占全机,昂贵的计算机资源得不到充分利用。 后来,随着计算机的发展,出现了批处理系统、多道程序系统,它们都提升了计算机的资源利用率。1961年,分时系统(Time Sharing System)出现,此时的一台计算机可以供多个用户终端同时连接并使用,就好像自己在独占计算机一样。分时系统将CPU的运行时间分成很短的时间片,按时间片分配给不同的连接终端使用,这充分利用了计算机资源,看起来就好像多个用户在同时使用计算机一样。 再后来,通用操作系统出现(General Operating System),它隐藏了硬件调用接口,为应用程序员提供调用硬件资源的更好、更简单、更清晰的系统调用接口,程序员就可以直接面向操作系统来编写程序,而不用关注硬件。 计算机的发展 [1],都在试图解决三个问题: 资源利用率:尽可能的利用昂贵的计算机资源,如CPU、内存,而不是使其大部分时间处于等待状态 公平性:不同的用户对计算机都有同等使用权,而不是排队等待 便利性:可以同时执行多个程序,每个程序执行一个任务并在必要时相互通信 这三个问题促使进程和线程的出现。计算机可以同时运行多个程序,每个程序一个进程;而同一个进程中同样存在多个线程(轻量级进程),它们共享进程范围内的资源,如内存句柄和文件句柄,每个线程都有自己的程序计数器、局部变量等,而且同一个进程的多个线程都可以被调度到CPU上运行。但是,线程之间存在着共享资源(进程的内存),如果没有协同机制,那么多个线程在同时处理共享内存时会存在安全问题,比如一个线程读某个变量而一个线程同时在写这个变量,如果没有线程同步机制,则该变量的值变得不可预测。 举个例子,假设进程为多条高速公路,它们之间彼此独立,而线程就好比高速公路的多个车道,它们共享这条高速路的资源,如指示牌、测速设备等,线程中执行的任务就是一辆小汽车,它们相安无事的飞驰在自己的车道上,一旦它们开始抢夺道路资源,不遵守交通规则,超速、违规变道,那么后果可想而知。 2. 多线程的优势 Java是门支持并发编程的语言,充分利用其提供的并发特性来编写高并发程序,是信息化时代发展所需。多线程程序的优势可以从几个方面来说明: 多线程程序能够极大的发挥处理器的强大能力。现代的计算机,CPU核心越来越多、处理频率越来越快、制造工艺越来越精,计算机的价格也越来越亲民,个人普通电脑都普遍是四核心、八核心甚至配置更高。在单核心CPU中,可能多线程程序不但没能提升处理性能,反而由于线程上下文切换的开销导致程序性能降低;但是,在多核心时代,多线程能够发挥处理器的强大能力,每个核心都能同时处理一个任务而不会造成上下文切换,如果还停留在单线程程序,这无疑是极大的浪费计算机资源,如果有100个CPU,那么单线程程序会造成99%的CPU都处于空闲状态。 建模更简单。首先要明确,编写多线程程序与单线程程序相比,肯定是复杂且容易出错的,那么为什么说建模更简单呢?这需要从管理线程中执行的任务角度去分析。在单线程中执行多个任务,与多个线程每个线程仅执行一个任务相比,哪个更容易管理呢?很明显是后者。虽然多线程中的任务彼此可能存在通信,但是从模型上看,一个线程只管理一个任务,职责更单一。 异步事件简化处理。这一点可以理解为编程模型的简化。单线程和多线程程序,在IO模型的选择上存在很大的不同。单线程程序,要提高并发请求数,底层需要使用异步IO、非阻塞IO或者IO多路复用模型 [2],但他们都是由操作系统底层支持,编程难度大;而多线程程序,由于应用层面本身采用了多线程模型,底层的IO则可以选择同步IO(异步的事情多线程做了),这就降低了开发难度。比如单线程模型中,Nginx采用多进程单线程和IO多路复用模型的架构,具备极高的并发处理能力;Redis采用单线程和IO多路复用模型,同样具备很高的处理效率。 响应更灵敏的用户界面。Java的GUI框架AWT和Swing采用事件分发线程来替代传统的主事件循环,并可将将耗时的处理任务放到单独的线程中异步执行,从而提高用户界面的响应速度。 3. 多线程的挑战 多线程是一把双刃剑,带来诸多优势时也让开发者面临更大的挑战。 1、开发难度问题 由于多线程程序运行的不确定性,给开发难度带来很大的挑战。相对于单线程程序,多线程编程更难以掌握,不仅需要充分理解编程语言级的多线程支持,还需要掌握操作系统层级的多线程模型。尽管如此,多线程程序仍然难以调试和跟踪,尤其是产生 "活跃性" 问题时往往无法重现。 2、安全性问题 什么是安全性?安全性指的是程序永远不会返回错误的结果。看下边的示例: public class UnsafeSequence { private int count; public int increment() { return count++; } public int getCount() { return count; } } 多个线程调用 increment 方法,由于 count++ 包含了 读取、修改、写入三步内存操作,这三步不是一个原子操作,存在多个线程重复写同一值的情况,如下图示意: Figure 1. 线程竞争导致错误的结果 由于线程在同时操作相同的count域,一个线程可能会覆盖另一个线程写入的值(线程竞争),最终count得到错误的值6,而不是预期的7. 线程安全本身是一个非常复杂的问题,由于线程之间存在资源共享,如果多个线程交替执行又没有有效的同步机制,执行结果将难以预料。尽管Java提供了足够的同步机制来保证线程安全性,但是这就要求开发者必须完全理解多线程程序的执行原理和Java的同步机制,这给程序开发带来很大的难度。 3、活跃性问题: ...

2022-01-22 · 1 min · 125 words · 老墨

多线程活跃性——哲学家就餐问题及死锁

死锁是多线程编程中最常见的一种"活跃性问题",除了死锁还包括"饥饿"和"活锁",这些活跃性问题给并发编程带来极大的挑战。比如出现死锁时,定位和分析问题相对困难,一旦出现死锁,通常只能重启应用程序。本文通过死锁最经典的"哲学家就餐问题"来介绍死锁的产生原因和解决办法。 1. 死锁 死锁指的是多个线程相互等待彼此而进入永久暂停状态。比如,线程 T1 持有锁 L1 去申请锁 L2,但是线程 T2 持有锁 L2 申请锁 L1,此时它们都在等待对象释放锁,从而进入永久阻塞状态。这就好比两个小朋友,他们各有一个玩具,但都不愿意分享给对方,却希望获得对方的玩具,最终互不相让,只能彼此干瞪眼了。 写一个死锁的程序很简单,比如下边的代码: public class DeadLockTest { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { new Thread(new MyThread1(lock1, lock2)).start(); new Thread(new MyThread2(lock1, lock2)).start(); } } class MyThread1 implements Runnable { private final Object lock1; private final Object lock2; public MyThread1(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { while (true) { synchronized (lock1) { (1) System.out.println("using lock1"); synchronized (lock2) { System.out.println("using lock2"); } } } } } class MyThread2 implements Runnable { private final Object lock1; private final Object lock2; public MyThread2(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { while (true) { synchronized (lock2) { (2) System.out.println("using lock2"); synchronized (lock1) { System.out.println("using lock1"); } } } } } ...

2022-01-20 · 6 min · 1208 words · 老墨

读《Java并发》— 简介

这是个人的《Java并发编程实战》阅读笔记整理系列的第一篇文章,这个系列包括对书中并发编程内容的整理、总结以及个人的一些感悟和实践心得。 1. 简介 《Java并发编程实战》是 Brian Goetz 等 6 位 Java 大师合著的介绍 Java 并发编程的经典著作,这部名著由浅入深的介绍了 Java 并发编程的诸多知识,是一本完美的Java并发参考手册, 豆瓣评分 9.0,可见其受欢迎程度。 1.1. 内容概要 《Java并发编程实战》从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容,最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类。 这本书介绍的是 Java 中 "并发编程" 这样的高级主题,不太适合初学者阅读。书中诸多的内容都较难理解,比如线程的 "竞态条件"、"同步机制"、"活跃性" 等等,如果对并发知之甚少,不熟悉操作系统层面的相关内容,阅读起来比较吃力。 1.2. 作者简介 本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。Tim Peierls是“现代多处理器”的典范,他在BoxPop.biz、唱片艺术和戏剧表演方面也颇有研究。Joseph Bowbeer是一个Java ME专家,他对并发编程的兴趣始于Apollo计算机时代。David Holmes是《The Java Programming Language》一书的合著者,任职于Sun公司。Joshua Bloch是Google公司的首席Java架构师,《Effective Java》一书的作者,并参与著作了《Java Puzzlers》。Doug Lea是《Concurrent Programming》一书的作者,纽约州立大学 Oswego分校的计算机科学教授。 也许你对这几位大师不怎么熟悉,但是你一定读过它们的著作,尤其是 Joshua Bloch 的《Effective Java》,这本书也是 Java 开发者必读的书籍,目前已经出到了第三版。 如果经常编写并发程序,那么对于 Doug Lea 一定不会陌生,因为 Java 5 开始的很多并发工具(它们都在 java.util.concurrent 包中,简称 juc 包)都出自他的手笔,如 AbstractQueuedSynchronizer、AtomicXxx 原子类、BlockingQueue、ConcurrentMap、ExecutorService 等等,几乎整个并发工具包的类都是他实现的。 1.3. 阅读建议 前边说过,尽管《Java并发编程实战》是经典著作,但是由于其内容的特殊性,存在一定的阅读门槛。如果英文过关,建议直接阅读英文版,对于大多数人只能阅读翻译的中文版本了,但中文版存在一些问题,诸多概念介绍的很官方,内容不够充实、通俗易懂,理解起来费力,也存在一些翻译不通顺的问题。 ...

2022-01-20 · 1 min · 125 words · 老墨

Java设计模式(24)-职责链模式

1. 引言 您有没有遇到各种需要走流程的事情?比如,请假申请,假设公司规定,3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。类似的场景还有很多,尤其在工作流中,比如物资审批、报账审批、资金审批等等…… 这里场景中,请求要发送给多个处理者,处理者要能够向后继续转发请求。存在着两种情况: 一方面,每一个处理者有自身能够处理的权限范围,超过权限范围的请求自身不处理,而是将请求转交给上级来处理,如果处理了则终止; 另一方面,每一个处理者可以部分处理请求,处理后可以再将请求发送给上级,也可以决定是否终止。 这样,可能多个处理者处理同一个请求,也有可能只有一个处理者能够处理请求,如下图所示: 多个处理者连成了一条链,请求沿着这条链可以向后传递,这里就会用到今天要讲的职责链模式。我么以请假流程为例,看看使用职责链模式前后的变化,感受职责链模式带来的好处。 2. 请假流程初步 根据前边的场景描述:3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。具备面向对象思维的我们不难写出如下的代码: 1、定义请假请求 请假请求类 class LeaveRequest { private final int days; (1) private final String name; (2) public LeaveRequest(int days, String name) { this.days = days; this.name = name; } // …… 省略getter和toString } 1 请假的天数,它来决定谁可以审批这个请假请求 2 请假人的名称 2、既然请假要求领导审批,我们来抽象一个领导的接口 抽象的领导接口,默认实现了请假审批逻辑 interface Leader { default void handle(LeaveRequest request) { (1) Random random = new Random(47); if (canHandle(request)) { (2) if (random.nextBoolean()) { System.out.println(this.getClass() + " [通过] 了请假请求: " + request); } else { System.out.println(this.getClass() + " [拒绝] 了请假请求: " + request); } return; } throw new RuntimeException(this.getClass() + " 不能处理请假请求: " + request); } boolean canHandle(LeaveRequest request); (3) } ...

2022-01-17 · 3 min · 613 words · 老墨

Java设计模式(23)-备忘录模式

中国有句古话:"千金难买后悔药"。生活中很多时候,我们做过了的事情,虽然后悔,却无济于事。但是,在软件世界,我们却可以自己制作"后悔药"。比如,以前玩"仙剑",每每遇到Boss战,那必须要存档的,就算没打过也可以恢复存档再来一次,否则,玩过的人都知道,哭去吧……这里的游戏存档,就会用到今天说的——备忘录模式。 其实还有很多这种例子,比如Word等办公软件的撤销操作,撤销后需要恢复到前一状态;又比如,虚拟机或者今天的云服务器都支持创建快照,如果系统出问题了,可以直接恢复到快照版本…… 1. 概念 备忘录,顾名思义用来做备份的,后续可能按照备份数据进行恢复。DP 对备忘录模式的定义如下: 定义 备忘录模式(Memento Pattern):在 不破坏封装性 的前提下,捕获一个对象的内部状态,将在 该对象之外 保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 这个定义比较好理解,其基本思想就是要将对象的内部状态保存下来,便于之后恢复,但是保存的逻辑不由对象本身负责,而是单独抽出取来,减轻对象的职责,同时也便于扩展。这个思想与前边的 迭代器模式] 非常类似。 需要保存内部状态的这个对象,我们称为 原发器 (Originator),抽取出来的保存原发器状态的对象我们称为 备忘录 (Memento),我们来看看它们之间的关系。 2. 结构 备忘录模式的结构如下: 可以看到,备忘录模式中有三种角色: Originator: 即原发器,持有内部状态,提供创建备忘录的方法,以保存某个时刻内部的全部或部分状态,同时还提供还原的方法,从而支持后续可以还原到保存的状态 Memento: 备忘录对象,它存储 Originator 的状态,但要求它能够防止除了 Originator 之外的对象访问备忘录 Caretaker: 管理者,它负责管理备忘录,包括存储、删除操作,但是要求它本身不能访问和修改备忘录 备忘录的宽窄接口 备忘录对象它要求能够防止除了 Originator 之外的对象访问备忘录,而管理者又不能访问和修改备忘录,因此,要求备忘录能够具有 宽窄两种接口,Originator 能够通过 宽接口 创建、访问、修改备忘录对象,而管理者只能使用 窄接口 存储、删除和允许他人查询备忘录,本身不能修改和访问备忘录。 备忘录模式具有如下的优缺点: 抽取了备忘录对象,用来保存状态,减轻了原发器的职责 备忘录和原发器都可以再次抽象出单独的接口,便于扩展 备忘录会创建多个状态的副本,可能造成很大的开销 管理者管理多个备忘录,但它并不知道备忘录的内部状态,一个小的管理者可能存储和删除很大的备忘录,带来大的开销 备忘录模式适用于以下场景: 对象需要保存自身某一时刻的状态,以便后续进行恢复 对象保存自身状态时不暴露其实现细节,需要保持其封装性不被破坏 3. 实现 备忘录模式的实现大概有三种方式,每一种都有其适用场景。 3.1. 标准实现 标准实现方式,它抽取备忘录为单独的对象,由管理者来存储。我们以仙剑游戏为例,看看示例代码实现,如下: 1、定义备忘录对象 public class Memento { private State state; public Memento(State state) { (1) this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } } ...

2022-01-12 · 3 min · 614 words · 老墨

Java设计模式(22)-迭代器模式

生活中,存在这样的场景:需要逐个遍历一堆对象,然后判断对象是否符合要求,做出相应的处理。例如,乘火车时,检票员站在门口挨个检票,没有买票的人不能乘车。类似的场景还有很多,比如乘地铁、乘公交、从书架上找书、食堂排队打饭等等…… 我们把需要逐个遍历的这一堆对象称为 对象集合,把挨个遍历的过程称为 迭代。迭代时,如果能将迭代过程从对象集合中抽取出来单独实现,让对象集合只负责管理自身状态,而不用负担迭代的任务,这就减轻了对象集合的职责,这就是今天要说的——迭代器模式。 也许你会想,对象集合不就是 List 吗,直接使用 "增强for循环" 遍历不就完成迭代过程了吗?说的没错!迭代器模式在很多面向对象的语言中都已经内置了,比如 Iterator,所以我们大多数情况下不会再自己实现它,但是学习模式本身的原理还是非常有必要。 1. 概念 DP对迭代器模式的定义如下:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。 这个定义还是比较抽象,简单而言:迭代器模式,提倡将聚合对象(就是前边提的 "对象集合")的迭代逻辑抽取成一个 迭代器,聚合对象本身只需要提供一个返回迭代器的方法即可,而不需要关注迭代逻辑。客户端拿到对象集合的迭代器,就可以遍历了。 这种由客户端来决定如何遍历的方式,被称为 外部(主动)迭代器;还有一种迭代方式,客户端调用一个方法告诉聚合对象它的每一个元素该如何处理,然后由聚合对象内部负责迭代,这被称为 内部(被动)迭代器。Java 两种都支持,比如外部用增强for循环遍历 List,就是外部迭代器,而调用 List 的 forEach(Consumer<? super T> action) 方法,或者调用 Iterator 的 forEachRemaining(Consumer<? super E> action) 方法就属于内部迭代器。 聚合对象中的元素并不一定是有序存储的,比如 Set。此外,迭代器除了从头开始遍历聚合对象,还可以实现从尾部开始遍历,这取决于具体实现。比如,Java 提供了 ListIterator 支持双向迭代,它继承自 Iterator。 2. 结构 迭代器模式的结构如下图所示: 这里我使用的Java的泛型方式来定义的类,标准的迭代器模式显然没有泛型。从图可知,迭代器模式一共分为四个角色: Aggregate<T>: 抽象聚合对象,定义了通用的接口方法,通常是一个对象集合,支持存储多个对象,并支持创建一个迭代器。如 Java的 List、Set、Coleection 等接口 Iterator<T>: 抽象迭代器,定义了迭代的接口方法,如 next 迭代下一个对象,hasNext() 返回是否还有下一个对象等等 ConcreteIterator<T>: 具体迭代器实现,一个 Aggregate 可能存在多个迭代器实现 ConcreteAggregate<T>: 具体聚合对象,如 Java中的 ArrayList,HashSet 等等 ...

2022-01-11 · 3 min · 571 words · 老墨

Java设计模式(21)-中介者模式

如果您有租房的经历,那么您对中介并不陌生。租房时,我们先去房屋租赁中介登记,告诉它您的租房需求,然后中介会按照您的要求为您筛选适合的房子。房子确定后,您会与中介签订租赁合同,缴纳费用,然后拿到钥匙……整个过程中,所有事项都由中介一手包办,入住以后房屋有任何问题,您都直接去找中介,您甚至可能并不知道房东是谁。这就是我们今天要说的模式——中介者模式。 中介者模式来源于生活中的各个中介机构,不如前边提到的房产中介、贷款中介、票务中介等等。 1. 概念 DP 对中介者模式的定义是这样的:用一个 中介对象 来封装一系列的对象交互,各个对象 不显式地相互引用,从而使其 松散耦合,而且可以独立的改变它们之间的交互。这完全符合 迪米特法则。 简而言之,中介者模式将多个相互引用的对象解耦,使得它们不直接交互,而是"有事找中介"。 除了生活中的中介机构外,中介者模式在软件中应用也很多,比如,服务网关,各个服务不直接通信,而是都通过网关来互相调用;又比如,MVC 模式中,service 层就是使用典型的中介者模式,因为 service 层依赖了多个 dao 层的类,而 dao 层的类之间并不直接交互…… 2. 结构 中介者模式的类图如下: 存在4个角色: Mediator: 抽象中介者,定义了通用接口让同事类之间进行通信 ConcreteMediator: 具体中介者,实现 Mediator 的具体业务来支持同事类之间的通信 Colleague: 抽象同事类,定义同事类通用接口,内部聚合 Mediator ,通过它与其他同事类交互 ConcreteColleague: 具体同事类,调用 Mediator 实现与其他具体同事类的通信 在中介者模式中,同事类之间互不认识,但是他们都认识中介者,就好比租房时您不认识房东,但是房东和您都认识中介,通过中介来完成通信,因此,中介者是作为一个中心化的角色存在。 优点: 同事类之间完全结构,符合 迪米特法则] 同事类可以扩展和复用,并且减少了它们的子类数量 简化了对象模型,由多对多关系变成了一对多关系 中介者决定同事类之间如何交互,而抽象中介者对这种交互进行了抽象,便于扩展 缺点: 同事类之间的交互完全由中介者来控制,中介者复杂度增加 中心化的中介者出现问题,则同事类之间无法交互,即存在单点问题 3. 适用场景 中介者模式适用于以下场景: 多个对象间存在复杂的依赖关系,结构混乱且难以理解 一个对象引用其他多个对象,并直接与它们通信,导致难以复用该对象 一个对象被多个对象引用,难以扩展,又不想为其生成子类 4. 与其他模式的区别 与观察者模式 上一篇 讲了观察者模式,与中介者模式非常类似。 结构类似,都定义了类似的4个角色,两个抽象角色和两个具体角色 两者实现的功能不同,观察者模式适用于一个对象改变而其他对象也需要做出改变的场景,而中介者模式重点在于解耦对象间多对多的复杂通信关系 中介者模式可以与观察者模式组合使用,如:中介者也作为通知发送者 与外观模式 中介者模式与 外观模式 也类似: ...

2022-01-08 · 2 min · 303 words · 老墨

Java设计模式(20)-观察者模式

很快又到年底了,一年一度的春节除了可以享受愉快地享受假期之外,每年的"春节联欢晚会"也是让人倍感期待。然而,大多数人都没能坐在电视机跟前等着它的开始,有的人在厨房忙着准备丰富的食物,有的在一起打麻将、玩牌,还有的可能在玩电脑游戏……大家都想看晚会,于是派出一个小朋友,告诉他:"如果晚会开始了,你要立刻来通知我们哦!"虽然,大家就可以继续做自己的事情,小朋友就坐在电视跟前,当晚会开始的时候,他就立即跑去挨个通知大家…… 观察者模式示意(图片来源网络) 模式源于生活,上边的这个场景,就是一个典型的观察者模式的例子。 1. 概念 先来看看观察者模式的定义。 DP 对观察者模式的定义 观察者模式(Observer),又叫发布-订阅(publish-subscribe)模式,定义了对象间的一种一对多的依赖关系,当一个对象发生改变时,所有依赖它的对象都得到通知并被自动更新。 生活中,有很多这样的一个对象依赖其他多个对象(一对多关系)的例子,该对象状态发生变化了,其他关联的对象都需要知道并且做出行动。如上边的小孩儿看到晚会开始了,要立即通知其他人,其他人可以选择去看春晚,也可以选择继续做自己的事情。又比如,路口红绿灯变绿时,意思是告诉大家可以通行了;微信群有人发消息了,群成员都可以看到这条消息,等等…… 在软件领域,观察者模式的运用非常多,如:消息队列(MQ) 可以视为观察者模式实现;又比如,响应式编程 的核心就是观察者模式。 2. 结构 观察者模式类图如下: 可以看到,观察者模式有4个角色: Subject: 抽象主题对象,又叫做被观察者(Observable),或者目标对象,定义了添加、删除和通知观察者的方法,知道注册的观察者列表 ConcreteSubject: 具体主题对象,实现 Subject,有自身的状态,状态变化后向各个 Observer 发出通知 Observer: 抽象观察者,定义观察者的通用接口,包含一个用于更新的 update 方法 ConcreteObserver: 具体观察者,实现 Observer,依赖 ConcreteSubject ,当 ConcreteSubject 变化时得到通知,自身也可以更改 ConcreteSubject 的状态,此时可以通知其他观察者更新 上边的几个角色,将抽象和实现相分离,主题对象和观察者都可以各自进行扩展。另外,并不总是主题对象调用 notify 方法去通知观察者们,观察者也可以调用它从而通知其他观察者。 优点: 观察者模式降低了对象间的耦合性,提高了复用性,符合依赖倒置原则 主题对象广播通知给所有观察者,并不关心具体的观察者是谁,易于添加和删除观察者,而具体的更新与否以及如何更新由观察者自身实现 缺点: 观察者自身并不知道其他观察者的存在,它对更改主题状态的代价一无所知,因此需要定义和维护依赖准则,否则可能引起错误更新 主题和观察者之间仍然存在耦合性(没有完全解耦),存在相互依赖关系甚至可能造成循环依赖 3. 适用场景 观察者模式的使用场景: 对象间存在一对多的依赖关系,双方都需要独立的扩展和复用 一个对象的改变会同时改变其他依赖对象 一个对象必须通知其他对象,但是并不知道其他对象具体是谁 4. 示例 接下来看看观察者模式的示例代码(注意,下边的代码没有考虑并发安全性)。 1、定义观察者 public interface Observer { void update(); (1) } ...

2022-01-06 · 3 min · 489 words · 老墨