[{"content":"Java历史 wiki 之所以把Java历史摆在最前面,是因为我们需要知道Java的生态为何如此混乱.\n1991年06月：项目启动 James Gosling、Mike Sheridan 与 Patrick Naughton 发起 Java 语言项目，最初命名为 Oak，随后曾更名为 Green，最终定名为 Java。 1996年：正式发布 Sun Microsystems 发布 Java 1.0 实现。该版本确立了“一次编写，到处运行”（WORA）的核心理念，并因其安全特性和浏览器对 Java Applet 的支持迅速普及。 1997年：标准化博弈 Sun 曾尝试通过 ISO/IEC JTC 1 和 Ecma International 推动 Java 标准化，但随后撤出，转而通过 Java Community Process (JCP) 维持事实上的标准控制。 1998年12月：架构分化 Java 2 (J2SE 1.2) 发布。Java 体系被正式划分为三个方向：面向企业级的 J2EE、面向桌面的 J2SE 以及面向移动设备的 J2ME。 2006年：更名与开源启动 出于市场营销考虑，Sun 将版本重新命名为 Java EE、Java SE 和 Java ME。同年 11 月，Sun 开始基于 GPL-2.0 协议发布 JVM 开源软件。 2007年：完成开源 除了极少数不持有版权的代码外，Sun 完成了 JVM 核心代码的开源分发。 2009年-2010年：所有权更迭 Oracle 收购 Sun Microsystems。随后不久，Oracle 就 Android SDK 中使用 Java 的问题对 Google 发起诉讼。 2010年04月：核心人物离职 Java 之父 James Gosling 从 Oracle 辞职。 2016年01月：淘汰过时技术 Oracle 宣布从 JDK 9 开始，Java 运行时环境将停止支持浏览器插件。 环境配置 编译器下载 与Cpp运行需要MSVC等编译器,python运行需要python虚拟环境一样,Java运行需要的是JDK(Java Development Kit). 如前面所说,尽管Java版权归Oracle公司所有,但也有开源的社区版本和基于开源版本制作的第三方JDK,性能上的差别非常小. 为了省事,我们直接上微软官网下载JDK21即可,这样可以跳过烦人的环境变量配置环节.\n至于为什么不选最新的25版本是因为体积更大也没必要. 配置环境变量(可跳过) 与cpp类似,如果不配置编译器的环境变量的话,系统是无法识别你的命令的:\n1 2 3 4 5 6 7 8 java --version java : 无法将“java”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写，如果包括路径，请确保路径正确 ，然后再试一次。 所在位置 行:1 字符: 1 + java --version + ~~~~ + CategoryInfo : ObjectNotFound: (java:String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException 但是,如果你用的是微软的OpenJDK,就可以在安装的时候勾选配置环境变量直接跳过这一步:\n1 2 3 4 java --version openjdk 21.0.10 2026-01-20 LTS OpenJDK Runtime Environment Microsoft-13106404 (build 21.0.10+7-LTS) OpenJDK 64-Bit Server VM Microsoft-13106404 (build 21.0.10+7-LTS, mixed mode, sharing) 但如果你铁着头选择了Oracle官方版本的话,那么还是需要自己配置的\u0026hellip;预先警告这很麻烦\nVScode配置Java环境 当你第一次创建.java文件时,VScode会自动为你推荐所需的扩展并配置好环境,所以无需额外操作.\nJava基础学习 W3schools 不要看廖雪峰教程\u0026hellip;写的并不是很好,而且很枯燥,搞不懂为什么流量这么大. 建议先学好cpp后再学Java,本部分经常会拿cpp来跟Java做比较 基础语法 学习前的要点 我们首先需要知道JAVA的面向对象特性:\n所有函数和变量都写在类里面,因此函数都变成了方法,变量都变成了属性 如果源文件中包含public类,则文件名必须与该类名完全一致 所有的类名都需要大写,仅是一种规范,但最好大写. 例如:\n1 2 3 4 5 public class Main { public static void main(String[] args) { System.out.println(\u0026#34;Hello World\u0026#34;); } } 包含了这个代码的文件必须叫做Main.java,区分大小写.\n主函数 上述代码中的main函数是Java程序的入口,其作用与c/cpp中的main函数别无二致:\n1 2 3 public static void main(String[] args) { System.out.println(\u0026#34;Hello World\u0026#34;); } 如果你不觉得main函数里的String[] args参数很奇怪的话,那我就觉得你很奇怪了.\n这个参数作为命令行交互的接收器,尽管你未必会显式用到这个参数,但是必须要写上. 尽管从Java21开始允许不写这个参数了,但是很多企业内部的数据库交互用的依然是Java8\u0026hellip;所以还是写上比较好\n很难想象这么一个简单的修改需要经过将近三十年的考量\u0026hellip;Java生态的陈旧性可见一斑 变量类型 1 2 3 4 5 6 7 // 常用变量类型 int myNum = 5; float myFloatNum = 5.99f; double myDoubleNum = 3.14159; char myLetter = \u0026#39;D\u0026#39;; boolean myBool = true; String myText = \u0026#34;Hello\u0026#34;; 值得注意的是唯独String这个变量类型是大写的,因为它是一个类,如前面所说,Java中的类名都需要大写,所以这里就大写了.\nJava 10(2018年发布)引入了类似于cpp中auto的变量类型var:\n1 2 var x = 5; // x is an int System.out.println(x); 与auto一样,var不允许只声明不赋值:\n1 2 var x; // Error var x = 5; // OK Java数组 由于cpp反直觉的类似int a[10]这样的语法,所以Java将声明时的[]优化到了变量名前面,变成了这样:\n1 2 3 String[] cars = {\u0026#34;Volvo\u0026#34;, \u0026#34;BMW\u0026#34;, \u0026#34;Ford\u0026#34;, \u0026#34;Mazda\u0026#34;}; System.out.println(cars[0]); // Outputs Volvo 尽管其他特性没什么改变,但看上去顺眼多了,毕竟我们都说int 数组a,而不会说int a数组 二维数组 自然,如果是二维数组,就要写两个[]了:\n1 2 3 4 5 6 7 int[][] myNumbers = { {1, 4, 2}, {3, 6, 8, 5, 2} }; for (int row = 0; row \u0026lt; myNumbers.length; row++) { for (int col = 0; col \u0026lt; myNumbers[row].length; col++) { System.out.println(\u0026#34;myNumbers[\u0026#34; + row + \u0026#34;][\u0026#34; + col + \u0026#34;] = \u0026#34; + myNumbers[row][col]); } } Java的for-each循环 由于Java的大多数逻辑语句和cpp别无二致,因此全都略过,但值得一提的是下面这个语法:\n1 2 3 4 5 String[] cars = {\u0026#34;Volvo\u0026#34;, \u0026#34;BMW\u0026#34;, \u0026#34;Ford\u0026#34;, \u0026#34;Mazda\u0026#34;}; for (String car : cars) { System.out.println(car); } 这被称为for-each循环,于04年的Java5.0引入,而cpp直到c++11才正式引入:\n1 2 3 4 5 std::vector\u0026lt;std::string\u0026gt; cars = {\u0026#34;Volvo\u0026#34;, \u0026#34;BMW\u0026#34;, \u0026#34;Ford\u0026#34;, \u0026#34;Mazda\u0026#34;}; for (const std::string\u0026amp; car : cars) { std::cout \u0026lt;\u0026lt; car \u0026lt;\u0026lt; std::endl; } OOP 修饰符 先概览一下Java中的常用修饰符,之后会逐渐根据代码理解的\n类修饰符 (Class Modifiers) 在 Java 中，类修饰符决定了类的访问权限、继承特性以及实例化规则。\n修饰符 描述 适用范围 public 最宽泛的访问级别。该类对所有类可见。 顶级类、内部类 protected 对同一包内的类及所有子类可见。 仅内部类 default (不写关键字) 仅对同一包内的类可见。 顶级类、内部类 private 仅对定义它的外部类可见。 仅内部类 abstract 抽象类。不能被实例化，必须由子类继承并实现其抽象方法。 顶级类、内部类 final 最终类。不能被继承（例如 java.lang.String）。 顶级类、内部类 static 静态内部类。不需要依赖外部类实例即可创建。 仅内部类 sealed 密封类（Java 17+）。限制哪些类可以继承它。 顶级类 方法修饰符 (Method Modifiers) 方法修饰符控制方法的访问权限、执行逻辑、以及子类覆盖规则。\n访问控制修饰符 public: 方法对所有类可见。 protected: 方法对同一包内的类及所有子类可见。 default: (不写关键字) 仅对同一包内的类可见。 private: 仅在当前类内部可见。 非访问控制修饰符 static: 静态方法。属于类而非实例，通过类名直接调用，不能访问非静态成员。 final: 最终方法。子类可以继承但不能覆盖（Override）此方法。 abstract: 抽象方法。没有方法体，必须由非抽象子类实现。 static修饰符详解 首先我们需要知道一件事:尽管Java强制要求所有方法都写在类中,但是有一些方法我们并不想让它与某个类的实例有任何关系,也就是说,我们想要像cpp定义全局函数那样,直接在类内方法中调用该函数,而不需要带上类访问符.,那么就可以用static关键字来修饰某个方法:\n1 2 3 4 5 6 7 8 9 10 11 public class Main { static void myMethod() { System.out.println(\u0026#34;Hello World!\u0026#34;); } public static void main(String[] args) { myMethod(); } } // Outputs \u0026#34;Hello World!\u0026#34; 这里我们直接调用了myMethod方法而不需要通过this指针或者类访问符来操作它 总结一下就是说,Java的static方法属于这个类,类外访问时通过类名.方法调用,类内访问可以直呼其名,而非static方法属于类实例,需要先实例化一个类后再通过实例调用.\n创建类实例 Java创建类实例的方法有很多,但最常用的还是通过new操作符:\n1 2 3 4 5 6 7 8 public class Main { int x = 5; public static void main(String[] args) { Main myObj = new Main(); System.out.println(myObj.x); } } 由于Java的垃圾回收机制(很后面会提到),我们不用像在cpp中一样,new一个对象后就要使用delete操作符删除它,还是很便利的.\n构造函数 与Java有new没有delete同理,Java有构造函数但没有析构函数,构造函数的写法与cpp的写法相同:与类同名\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Main { int x; public Main(int y) { x = y; } public static void main(String[] args) { Main myObj = new Main(5); System.out.println(myObj.x); } } // Outputs 5 this引用符 由于Java不再显式使用指针,但又需要像cpp一样用某个符号代指类实例,否则无法通过类内方法访问私有属性.\n因此,Java引入了this引用符,用来指向当前的类实例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Main { int x; // Class variable x // Constructor with one parameter x public Main(int x) { this.x = x; // refers to the class variable x } public static void main(String[] args) { // Create an object of Main and pass the value 5 to the constructor Main myObj = new Main(5); System.out.println(\u0026#34;Value of x = \u0026#34; + myObj.x); } } 这与Python中的self有异曲同工之处,但不同的是self需要显示声明,而Java的this与cpp的this一样都是隐式存在的 构造函数的重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class Main { int modelYear; String modelName; // Constructor with one parameter public Main(String modelName) { // Call the two-parameter constructor to reuse code and set a default year this(2020, modelName); } // Constructor with two parameters public Main(int modelYear, String modelName) { // Use \u0026#39;this\u0026#39; to assign values to the class variables this.modelYear = modelYear; this.modelName = modelName; } // Method to print car information public void printInfo() { System.out.println(modelYear + \u0026#34; \u0026#34; + modelName); } public static void main(String[] args) { // Create a car with only model name (uses default year) Main car1 = new Main(\u0026#34;Corvette\u0026#34;); // Create a car with both model year and name Main car2 = new Main(1969, \u0026#34;Mustang\u0026#34;); car1.printInfo(); car2.printInfo(); } } 通过构造函数的重载可以实现类的不同初始化数值\n非常值得注意\n上述代码的this(2020, modelName)并非是简单的语法糖,它指向的是完整版本的构造函数,从而实现代码的复用,换句话说,如果只写这一句而不写完整构造函数就会报错.\n这么来看的话,它与cpp中的初始化列表完全不同 1 2 3 4 5 6 7 8 9 class Main { public: int modelYear; string modelName; // 使用初始化列表：变量(值) Main(string name) : modelYear(2020), modelName(name) { } } 继承与多态 由于cpp中的继承符号:过于简单和抽象,因此Java将继承符号改为了继承关键字extends:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Vehicle { protected String brand = \u0026#34;Ford\u0026#34;; // Vehicle attribute public void honk() { // Vehicle method System.out.println(\u0026#34;Tuut, tuut!\u0026#34;); } } class Car extends Vehicle { private String modelName = \u0026#34;Mustang\u0026#34;; // Car attribute public static void main(String[] args) { // Create a myCar object Car myCar = new Car(); // Call the honk() method (from the Vehicle class) on the myCar object myCar.honk(); // Display the value of the brand attribute (from the Vehicle class) and the value of the modelName from the Car class System.out.println(myCar.brand + \u0026#34; \u0026#34; + myCar.modelName); } } final修饰符详解 Java将cpp中的常量修饰符const改成了final,这可不是脱裤子放屁,而是因为Java中的final还可以限制某些类不可被继承:\n1 2 3 4 5 6 7 8 9 10 11 final class Vehicle { ... } class Car extends Vehicle { ... } // Main.java:9: error: cannot inherit from final Vehicle // class Main extends Vehicle { // ^ // 1 error) 将类也看做常量的想法确实挺奇妙的,但一般来说根本用不到吧.\n多态 Java中的方法默认是可以被重载的,这比起cpp要便利很多,同样,被final修饰的方法只能被继承但不能被重载:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Animal { public void animalSound() { System.out.println(\u0026#34;The animal makes a sound\u0026#34;); } } class Pig extends Animal { public void animalSound() { System.out.println(\u0026#34;The pig says: wee wee\u0026#34;); } } class Dog extends Animal { public void animalSound() { System.out.println(\u0026#34;The dog says: bow wow\u0026#34;); } } super引用符 Java设计了super引用符来指向父类:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Animal { public void animalSound() { System.out.println(\u0026#34;The animal makes a sound\u0026#34;); } } class Dog extends Animal { public void animalSound() { super.animalSound(); // Call the parent method System.out.println(\u0026#34;The dog says: bow wow\u0026#34;); } } public class Main { public static void main(String[] args) { Dog myDog = new Dog(); myDog.animalSound(); } } 当然,super更强大的地方在于能够调用父类的构造函数:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Animal { Animal() { System.out.println(\u0026#34;Animal is created\u0026#34;); } } class Dog extends Animal { Dog() { super(); // Call parent constructor System.out.println(\u0026#34;Dog is created\u0026#34;); } } public class Main { public static void main(String[] args) { Dog myDog = new Dog(); } } // Animal is created // Dog is created 这其实很好理解,把super换成父类的类名即可看懂了.\n抽象与接口 Java将Cpp中的虚函数与重载机制进一步发扬光大,发明了抽象方法和接口,简单来说的话,抽象类内的抽象方法没有函数内容,而且必须被子类重载实现;而接口就是抽象类的简写版,里面的所有函数和属性默认都要被重载实现. 抽象类和抽象方法\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Abstract class abstract class Animal { // Abstract method (does not have a body) public abstract void animalSound(); // Regular method public void sleep() { System.out.println(\u0026#34;Zzz\u0026#34;); } } // Subclass (inherit from Animal) class Pig extends Animal { public void animalSound() { // The body of animalSound() is provided here System.out.println(\u0026#34;The pig says: wee wee\u0026#34;); } } class Main { public static void main(String[] args) { Pig myPig = new Pig(); // Create a Pig object myPig.animalSound(); myPig.sleep(); } } 为了区分抽象类和接口,Java特定设置了接口的继承关键字implements,从而与extends区分开来:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Interface interface Animal { public void animalSound(); // interface method (does not have a body) public void sleep(); // interface method (does not have a body) } // Pig \u0026#34;implements\u0026#34; the Animal interface class Pig implements Animal { public void animalSound() { // The body of animalSound() is provided here System.out.println(\u0026#34;The pig says: wee wee\u0026#34;); } public void sleep() { // The body of sleep() is provided here System.out.println(\u0026#34;Zzz\u0026#34;); } } class Main { public static void main(String[] args) { Pig myPig = new Pig(); // Create a Pig object myPig.animalSound(); myPig.sleep(); } } 至于为什么Java要单独设置implements关键字,是因为它确实与extends有些许不同,implements后可以跟多个接口,而extends后只可以跟一个父类.\n为什么这么设计?那是设计者的问题了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 interface FirstInterface { public void myMethod(); // interface method } interface SecondInterface { public void myOtherMethod(); // interface method } class DemoClass implements FirstInterface, SecondInterface { public void myMethod() { System.out.println(\u0026#34;Some text..\u0026#34;); } public void myOtherMethod() { System.out.println(\u0026#34;Some other text...\u0026#34;); } } class Main { public static void main(String[] args) { DemoClass myObj = new DemoClass(); myObj.myMethod(); myObj.myOtherMethod(); } } Java高级特性 说是高级,其实都很简单,都怪营销号和垃圾博客把Java渲染的多么复杂高深,比起Cpp来说,Java简直不能再简单了.\nWrapper Classes(封装类) Primitive Data Type Wrapper Class byte Byte short Short int Integer long Long float Float double Double boolean Boolean char Character 由于Java内置的数据结构如ArrayList只能存储对象,所以我们需要将数据类型包装成一个数据类来处理:\n1 2 3 4 5 6 7 8 9 10 11 12 import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList\u0026lt;String\u0026gt; cars = new ArrayList\u0026lt;String\u0026gt;(); cars.add(\u0026#34;Volvo\u0026#34;); cars.add(\u0026#34;BMW\u0026#34;); cars.add(\u0026#34;Ford\u0026#34;); cars.add(\u0026#34;Mazda\u0026#34;); System.out.println(cars); } } 自然,这种脱裤子放屁的事情在Java中还有很多\u0026hellip; Java 泛型(generic) 将不同类型的数据统一处理\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Box\u0026lt;T\u0026gt; { T value; // T is a placeholder for any data type void set(T value) { this.value = value; } T get() { return value; } } public class Main { public static void main(String[] args) { // Create a Box to hold a String Box\u0026lt;String\u0026gt; stringBox = new Box\u0026lt;\u0026gt;(); stringBox.set(\u0026#34;Hello\u0026#34;); System.out.println(\u0026#34;Value: \u0026#34; + stringBox.get()); // Create a Box to hold an Integer Box\u0026lt;Integer\u0026gt; intBox = new Box\u0026lt;\u0026gt;(); intBox.set(50); System.out.println(\u0026#34;Value: \u0026#34; + intBox.get()); } } 处理不同的数据输入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { // Generic method: works with any type T public static \u0026lt;T\u0026gt; void printArray(T[] array) { for (T item : array) { System.out.println(item); } } public static void main(String[] args) { // Array of Strings String[] names = {\u0026#34;Jenny\u0026#34;, \u0026#34;Liam\u0026#34;}; // Array of Integers Integer[] numbers = {1, 2, 3}; // Call the generic method with both arrays printArray(names); printArray(numbers); } } Java Annotations(Java注解) Annotations are special notes you add to your Java code. They start with the @ symbol.\n注解并不会改变程序的运行方式,但是会为编译器和构建工具提供额外的信息,这与Python中的语法糖完全不同.\n最常用的注解有三个:\n@Override: Indicates that a method overrides a method in a superclass @Deprecated: Marks a method or class as outdated or discouraged from use @SuppressWarnings: Tells the compiler to ignore certain warnings 尽管有点用吧,但依然是累赘设计\u0026hellip; Java多线程 Java中有两种方法可以创建进程:\n1 2 3 4 5 6 7 8 9 10 11 12 // 1.继承Thread系统类 public class Main extends Thread { public void run() { System.out.println(\u0026#34;This code is running in a thread\u0026#34;); } } // 2.实现Runnable接口,更推荐 public class Main implements Runnable { public void run() { System.out.println(\u0026#34;This code is running in a thread\u0026#34;); } } 真正要详细了解多线程需要在后面的Java线程池中学习\nJava Lambda函数 基本格式\n1 (parameters) -\u0026gt; { body } (parameters)：类似于方法的参数列表。如果没有参数，直接写 ()；如果只有一个参数且类型可推导，可以省略圆括号。 -\u0026gt;：Lambda 运算符，固定写法，代表“传递”或“应用”。 { body }：函数体。如果逻辑只有一行代码，可以省略花括号和 return 关键字。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.util.ArrayList; import java.util.function.Consumer; public class Main { public static void main(String[] args) { ArrayList\u0026lt;Integer\u0026gt; numbers = new ArrayList\u0026lt;Integer\u0026gt;(); numbers.add(5); numbers.add(9); numbers.add(8); numbers.add(1); Consumer\u0026lt;Integer\u0026gt; method = (n) -\u0026gt; { System.out.println(n); }; numbers.forEach(method); } } 如果你详细看上述代码的话,你会看到一个神奇的地方, method类竟然和一个匿名函数用等号连接起来了:\n1 Consumer\u0026lt;Integer\u0026gt; method = (n) -\u0026gt; { ... }; 这个特性就引出了Java的函数式接口特性,该特性由Java 8引入,属于比较高级的特性,故放在后面讲\nJava系统库 随便看看即可,用上的时候再去详细了解\nJava文件读写 The File class from the java.io package, allows us to work with files:\n1 2 3 import java.io.File; // Import the File class File myObj = new File(\u0026#34;filename.txt\u0026#34;); // Specify the filename 创建文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.io.File; // Import the File class import java.io.IOException; // Import IOException to handle errors public class CreateFile { public static void main(String[] args) { try { File myObj = new File(\u0026#34;filename.txt\u0026#34;); // Create File object if (myObj.createNewFile()) { // Try to create the file System.out.println(\u0026#34;File created: \u0026#34; + myObj.getName()); } else { System.out.println(\u0026#34;File already exists.\u0026#34;); } } catch (IOException e) { System.out.println(\u0026#34;An error occurred.\u0026#34;); e.printStackTrace(); // Print error details } } } 写入文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.io.FileWriter; // Import the FileWriter class import java.io.IOException; // Import the IOException class public class WriteToFile { public static void main(String[] args) { try { FileWriter myWriter = new FileWriter(\u0026#34;filename.txt\u0026#34;); myWriter.write(\u0026#34;Files in Java might be tricky, but it is fun enough!\u0026#34;); myWriter.close(); // must close manually System.out.println(\u0026#34;Successfully wrote to the file.\u0026#34;); } catch (IOException e) { System.out.println(\u0026#34;An error occurred.\u0026#34;); e.printStackTrace(); } } } 读取文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.io.File; // Import the File class import java.io.FileNotFoundException; // Import this class to handle errors import java.util.Scanner; // Import the Scanner class to read text files public class ReadFile { public static void main(String[] args) { File myObj = new File(\u0026#34;filename.txt\u0026#34;); // try-with-resources: Scanner will be closed automatically try (Scanner myReader = new Scanner(myObj)) { while (myReader.hasNextLine()) { String data = myReader.nextLine(); System.out.println(data); } } catch (FileNotFoundException e) { System.out.println(\u0026#34;An error occurred.\u0026#34;); e.printStackTrace(); } } } Java I/O 读缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Main { public static void main(String[] args) { try (BufferedReader br = new BufferedReader(new FileReader(\u0026#34;filename.txt\u0026#34;))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { System.out.println(\u0026#34;Error reading file.\u0026#34;); } } } 写缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class Main { public static void main(String[] args) { try (BufferedWriter bw = new BufferedWriter(new FileWriter(\u0026#34;filename.txt\u0026#34;))) { bw.write(\u0026#34;First line\u0026#34;); bw.newLine(); // add line break bw.write(\u0026#34;Second line\u0026#34;); System.out.println(\u0026#34;Successfully wrote to the file.\u0026#34;); } catch (IOException e) { System.out.println(\u0026#34;Error writing file.\u0026#34;); } } } Java数据结构 最常用的数据结构都被包装到java.util包中了:\nArrayList HashSet HashMap 这三个数据结构同样也是Java岗面试的必考点,尽管不太看得出来考的必要\n快速对照表 Java 数据结构 C++ (STL) 对照 底层实现 排序性 ArrayList std::vector 动态数组 插入顺序 HashSet std::unordered_set 哈希表 无序 HashMap std::unordered_map 哈希表 无序 TreeSet std::set 红黑树 自动排序 TreeMap std::map 红黑树 自动排序 Java进阶学习 Java编译与构建 学习完OOP后,我们很自然的会将不同功能的类拆分到不同java文件中,那么接下来就来看看Java是如何编译和构建不同文件的\nJVM Java8新特性 函数式接口 Java构建工具 与Cpp有Make,ninja,CMake类似,Java也有自己的构建工具,早期的构建工具为Ant,目前由Maven和Gradle两款工具统治,它俩也同时承担了包管理器的责任.\nMaven Maven历史 1. 概念起源与孵化 (2002 - 2003) 2002年：由 Jason van Zyl 创建，最初作为 Apache Turbine 的子项目，旨在解决该项目极其复杂的构建过程。 2003年：正式被接纳为 Apache 软件基金会的顶级项目。 2. 标准确立与快速普及 (2004 - 2005) 2004年7月 (Version 1.0)：第一个里程碑版本发布。确立了“约定优于配置”的核心理念。 2005年10月 (Version 2.0)：重大架构升级版本。 核心贡献：确立了 Maven 中央仓库 (Central Repository) 机制，支持动态下载依赖。 插件架构：采用基于插件的架构，使其具备了跨语言构建（如 C/C++）的潜力。 3. 架构优化与并行化 (2010) 2010年10月 (Version 3.0)： 解耦与兼容：在保持与 2.x 项目兼容的同时，重构了核心项目构建器。 性能提升：引入并行构建特性，能够利用多核 CPU 处理大型多模块项目。 非 XML 尝试：开始支持非 XML 的项目定义文件（如 Ruby、YAML、Groovy），解耦了内存表示与文件格式。 使用方法 Gradle Gradle历史 1. 概念孵化与 Groovy 基因 (2007 - 2008) 2007年：Hans Dockter 开始构思一种结合 Ant 的灵活性与 Maven 的依赖管理能力的新工具。 2008年4月 (Version 0.1)：首个版本发布。创始人最初想命名为“Cradle”，但为了使其更具独特性，取 Groovy 语言的首字母 G，更名为 Gradle。其核心特征是使用基于 Groovy 的 DSL 取代繁琐的 XML 配置。 2. 核心架构确立 (2012 - 2014) 2012年6月 (Version 1.0)：历经四年迭代，首个正式稳定版发布。确立了有向无环图 (DAG) 任务模型，解决了复杂构建流程的编排问题。 2013年：Google 宣布 Android Studio 采用 Gradle 作为官方构建系统。这一决策直接将 Gradle 推向了顶级构建工具的地位。 2014年7月 (Version 2.0)：提升了构建性能，并正式引入了对原生语言（C/C++）构建的初步支持，展示了其跨语言构建的野心。 3. 性能突破与 Kotlin 引入 (2016 - 2018) 2016年8月 (Version 3.0)：引入 Gradle Daemon（守护进程）并默认开启，极大地缩短了短周期任务的启动时间。 2017年6月 (Version 4.0)：引入 Build Cache（构建缓存）技术，允许跨机器共享编译产物，大幅减少了大型项目的重复编译时间。 2018年11月 (Version 5.0)：正式支持 Kotlin DSL。开发者可以使用类型安全的 Kotlin 编写构建脚本，解决了 Groovy 脚本缺乏 IDE 智能补全和编译时检查的痛点。 4. 现代化与大规模构建优化 (2019 - 2025) 2021年4月 (Version 7.0)：引入 Version Catalogs（版本目录），实现了多项目之间依赖版本的集中管理，正式统一了大型项目的依赖规范。 2023年2月 (Version 8.0)：针对配置阶段进行了深度优化，支持 Configuration Cache，使得复杂项目的构建配置速度提升数倍。 2025年7月 (Version 9.0)：最新里程碑版本。进一步强化了云端构建能力和对现代 JDK 特性的深度适配，确立了其在高复杂度、高性能要求构建场景下的统治地位。 使用Docker开发Java ","date":"2026-04-26T08:00:00Z","image":"/p/java%E7%AC%94%E8%AE%B0/55474959_p0-%E5%BC%8F%E3%81%95%E3%82%93.webp","permalink":"/p/java%E7%AC%94%E8%AE%B0/","title":"java笔记"},{"content":"尽管我最一开始的计划是一季度做一次总结,并做一些阶段学习安排,但计划总是赶不上变化的.所以先在这里做一次总结来重新设定一下学习目标.\n先看看我4月开头的季度计划:\n参加人工智能大赛,实现一个完整的智能体(尽量多用ai) 精通/掌握爬虫和机器学习 编写一个开源的量化平台(尽量少用ai),虽然GitHub上已经有不少大佬项目了,但我还是想在学习之后做出一个自己的项目 根据jmcomic api实现一个Android app 深入学习nextjs和nestjs,nextjs已经是二战了\u0026hellip; 学习阅读财报并买入第二支股票 读完\u0026laquo;设计数据密集型应用\u0026raquo;,\u0026laquo;程序员自我修养\u0026raquo;,\u0026laquo; C++程序设计语言 \u0026raquo;,\u0026laquo; RESTful Web APIs \u0026raquo;,一本设计模式书 深入学习docker,可以的话再学kubernetes 我可以很自信的打下几个不是很完整的勾:\n参加人工智能大赛,实现一个完整的智能体(尽量多用ai) 不再追求保研后找不到一点打比赛的欲望了,点开比赛文件后看到那密密麻麻的计划书要求后我就不想再打了 另外一个原因是看到去年的国际大学生创新大赛里面,冠军项目\u0026quot;万格智能\u0026quot;被打假了,但没有任何处罚就不了了之了,如此高规格的赛事,项目负责人均为清华高材生,竟然用一个破败不堪的AI垃圾项目就拿到了冠军.这样的比赛还有什么意义呢.. 精通/掌握爬虫和机器学习 爬虫库基本都学会了,但真要我写还是一窍不通的,还必须得去钻研各种各样的真实项目才可以学会诸如中间件,负载均衡这样的技术 机器学习刚刚打头,但我发现即使底层的原理不清楚,只用transformer封装的库也可以做出一些很不错的智能体应用,所以对算法岗有了一些兴趣 编写一个开源的量化平台(尽量少用ai),虽然GitHub上已经有不少大佬项目了,但我还是想在学习之后做出一个自己的项目 自从发现A股的市场过于劣质后,现在兴致缺缺了,而且要做量化的前提是去弄懂各种各样的财报数据,折腾各种各样的参数,很难提得起动力去干这件事 根据jmcomic api实现一个Android app 还没开始\u0026hellip; 深入学习nextjs和nestjs,nextjs已经是二战了\u0026hellip; 还没开始\u0026hellip; 学习阅读财报并买入第二支股票 如前所说,不打算继续钻研A股了,在港美股和期货尚不了解也没能力投资的情况下,我宁愿去买债券和普通基金 读完\u0026laquo;设计数据密集型应用\u0026raquo;,\u0026laquo;程序员自我修养\u0026raquo;,\u0026laquo; C++程序设计语言 \u0026raquo;,\u0026laquo; RESTful Web APIs \u0026raquo;,一本设计模式书 \u0026laquo;设计数据密集型应用\u0026raquo;更适合有了大量实际生产经验后再去读,\u0026laquo;程序员自我修养\u0026raquo;还在读,我发现\u0026laquo; C++程序设计语言 \u0026raquo;写的不是很好,过于宽泛和跳跃了,得换一本书,另外两本书还没开始 深入学习docker,可以的话再学kubernetes docker的特性基本掌握了,kubernetes还没开始学,但也没发现一定要学的必要 重新制定一下5月份的计划吧:\n深入学习ML/Agent领域,最少要能够看懂前沿论文/技术文章在讲什么 深入学习Java,掌握spring boot,消息队列和搜索引擎 学习Redis和MongoDB 从头开始重新接触前端知识,直到深入学习nextjs和nestjs 读完\u0026laquo;程序员自我修养\u0026raquo;,\u0026laquo; RESTful Web APIs \u0026raquo;,一本设计模式书 完整弄懂前后端通信的全过程和加密传输 如果只在一个月内完成的话日程还是有点紧张的\u0026hellip;\n","date":"2026-04-25T21:40:47+08:00","image":"/p/4%E6%9C%88%E4%BB%BD%E6%80%BB%E7%BB%93/54943053_p0-a%20fantastical%20journey.webp","permalink":"/p/4%E6%9C%88%E4%BB%BD%E6%80%BB%E7%BB%93/","title":"4月份总结"},{"content":" python最大的魅力在于各种各样的第三方库让它能够适配几乎所有的应用场景\n官方文档 学习python的各种疑难问题都是因为没有去阅读第一手资料 python基础 这部分我觉得没什么必要去写,因为python的基本语法确实太简单了,可以说是最好入门的后端语言了,所以简单谈谈OOP部分即可.\nOOP 如果用 C++ 术语来描述的话，类成员（包括数据成员）通常为 public,所有成员函数都为 virtual\n创建类实例 构造函数 继承与多态 self详解 由于python没有指针,自然也没有this指针,但又需要像cpp一样,提供一个直接访问类实例的入口,所以python引入了关键字self.\n事实上,上述的说法是不严谨的:\n方法的第一个参数常常被命名为 self。 这也不过就是一个约定: self 这一名称在 Python 中绝对没有特殊含义。 但是要注意，不遵循此约定会使得你的代码对其他 Python 程序员来说缺乏可读性，而且也可以想像一个 类浏览器 程序的编写可能会依赖于这样的约定。 也就是说,我们可以起名叫this,apple,但别人不一定看得懂就是了.\n我们可以看到,大多数类中的函数都需要至少给出一个参数,也就是self,即使函数中并没有用到self,原因如下:\nPython 的类实例方法在调用时，解释器会自动将实例对象作为第一个位置参数传入,如果你没有写self参数,那么由于该函数没有参数,但却传入了一个参数,就会报错 至于为什么会这样,那就是设计上的问题了,只能被动接受. 这也解释了为什么我们从来没有手动处理self参数过 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Dog: tricks = [] # mistaken use of a class variable def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) \u0026gt;\u0026gt;\u0026gt; d = Dog(\u0026#39;Fido\u0026#39;) \u0026gt;\u0026gt;\u0026gt; e = Dog(\u0026#39;Buddy\u0026#39;) \u0026gt;\u0026gt;\u0026gt; d.add_trick(\u0026#39;roll over\u0026#39;) \u0026gt;\u0026gt;\u0026gt; e.add_trick(\u0026#39;play dead\u0026#39;) \u0026gt;\u0026gt;\u0026gt; d.tricks # unexpectedly shared by all dogs [\u0026#39;roll over\u0026#39;, \u0026#39;play dead\u0026#39;] super()详解 同样,python需要用一个\n包管理器: uv 管理python版本 如果系统上已经安装了 Python，uv 将无需配置即可检测并使用它。但是，uv 也可以安装和管理 Python 版本。uv 会根据需要自动安装缺失的 Python 版本——你无需为了开始使用而预先安装 Python。\nuv python install：安装最新 Python 版本。 eg: uv python install 3.12 uv python list：查看可用的 Python 版本。 但是:\n当 uv 安装 Python 后，它不会在全局范围内可用（即通过 python 命令）。 此功能的支持尚处于_预览_阶段 你仍然可以使用uv run 或 创建并激活虚拟环境来直接使用 python。\n运行python脚本 如果脚本没有依赖模块或者依赖标准库中的模块,可以直接使用 uv run 来执行它,例如:uv run example.py.\nuv可以使用特定的python版本运行脚本\n1 2 3 $ # 使用特定的 Python 版本 $ uv run --python 3.10 example.py 3.10.15 包的使用 uvx 命令可以在不安装工具的情况下调用它:\n1 uvx ruff 使用 uvx 时，工具会安装到临时的、隔离的环境中.\n如果一个工具经常使用，最好将其安装到持久环境中并添加到 PATH，而不是重复调用 uvx.\n1 uv tool install ruff 安装工具后，其可执行文件会放在 PATH 中的 bin 目录中，这样就可以在没有 uv 的情况下运行该工具.\n项目中的包管理 uv通过pyproject.toml文件来定义依赖项.\n使用 uv init 命令创建一个新的 Python 项目:\n1 2 uv init hello-world cd hello-world 或者在工作目录中初始化uv\n1 2 3 $ mkdir hello-world $ cd hello-world $ uv init 项目结构 一个项目由几个协同工作的重要部分组成，这些部分允许 uv 管理你的项目。除了 uv init 创建的文件外，当你第一次运行项目命令（即 uv run、uv sync 或 uv lock）时，uv 将在你的项目根目录中创建一个虚拟环境和 uv.lock 文件。\n1 2 3 4 5 6 7 8 9 10 . ├── .venv │ ├── bin │ ├── lib │ └── pyvenv.cfg ├── .python-version ├── README.md ├── main.py ├── pyproject.toml └── uv.lock 直接震惊了好吧,终于不用输入python -m venv venv这种东西了 pyproject.toml\n1 2 3 4 5 6 7 [project] name = \u0026#34;uv\u0026#34; version = \u0026#34;0.1.0\u0026#34; description = \u0026#34;Add your description here\u0026#34; readme = \u0026#34;README.md\u0026#34; requires-python = \u0026#34;\u0026gt;=3.13\u0026#34; dependencies = [] .python-version .python-version 文件包含项目的Python 版本。此文件告诉 uv 在创建项目的虚拟环境时使用哪个 Python 版本。\n我相信应该不只有我一个人好奇:就这一条信息为什么不合并到toml文件中呢? 经过浏览我推测: requires-python配置项要求了在特定python版本下才能运行项目,如果把当前使用的不合规python版本写入toml,那么uv编译的时候要听谁的呢? 因此,单独分出这一个文件既是为了保证python版本管理的方便,也为了防止错误的python版本被用来执行.\nuv.lock uv.lock 是一个人类可读的 TOML 文件，但由 uv 管理，不应手动编辑,包含有关你的项目依赖项的精确信息.\n包管理和从pip迁移 你可以使用 uv add 命令将依赖项添加到你的 pyproject.toml 中。这也将更新锁文件和项目环境：\n1 2 3 4 5 $ # 指定版本约束 $ uv add \u0026#39;requests==2.31.0\u0026#39; $ # 添加一个 git 依赖 $ uv add git+https://github.com/psf/requests 1 2 $ # 从 `requirements.txt` 添加所有依赖项。 $ uv add -r requirements.txt -c constraints.txt 1 2 # --upgrade-package 标志将尝试将指定的包更新到最新的兼容版本，同时保持锁文件的其余部分不变 $ uv lock --upgrade-package requests 运行和同步 在每次调用 uv run 之前，uv 将验证锁文件是否与 pyproject.toml 同步，以及环境是否与锁文件同步，从而使你的项目保持同步，无需手动干预。uv run 保证你的命令在一致、锁定的环境中运行。\n当接手一个使用了uv的项目时,建议先运行uv sync命令以创建虚拟环境并下载库进行同步,尽管使用uv run 随便一个python文件 会默认使用uv sync,但就不够优雅了.\n构建分发包 1 2 3 4 $ uv build $ ls dist/ hello-world-0.1.0-py3-none-any.whl hello-world-0.1.0.tar.gz TL;DR 如果是自己新建python项目,则运行:\n1 2 3 4 5 6 7 8 9 10 uv init hello-world # 这会在创建子文件夹并填入初始内容 uv init # 在当前文件夹填入初始内容 uv add ... # 本地加入自己需要的依赖 # 或者自己在toml里填入包,如果这样的话需要使用uv sync uv run main.py # 使用uv运行某个脚本 如果是接手某个项目:\n1 2 3 uv sync uv run main.py # 使用uv运行某个脚本 实战:Using uv with PyTorch 参考 1 2 3 4 5 6 7 8 [project] name = \u0026#34;project\u0026#34; version = \u0026#34;0.1.0\u0026#34; requires-python = \u0026#34;\u0026gt;=3.14\u0026#34; dependencies = [ \u0026#34;torch\u0026gt;=2.9.1\u0026#34;, \u0026#34;torchvision\u0026gt;=0.24.1\u0026#34;, ] This is a valid configuration for projects that want to use CPU builds on Windows and macOS, and CUDA-enabled builds on Linux. However, if you need to support different platforms or accelerators, you\u0026rsquo;ll need to configure the project accordingly.\n使用CUDA13.0\n1 2 3 4 [[tool.uv.index]] name = \u0026#34;pytorch-cu130\u0026#34; url = \u0026#34;https://download.pytorch.org/whl/cu130\u0026#34; explicit = true TL;DR: 先在nvidia官网下载cuda13.0,然后根据这个toml运行uv sync即可.\n提示:要下载差不多2个G. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [project] name = \u0026#34;ml\u0026#34; version = \u0026#34;0.1.0\u0026#34; description = \u0026#34;Add your description here\u0026#34; readme = \u0026#34;README.md\u0026#34; requires-python = \u0026#34;\u0026gt;=3.13\u0026#34; dependencies = [ \u0026#34;torch\u0026#34;, \u0026#34;torchvision\u0026#34;, \u0026#34;torchaudio\u0026#34;, ] [tool.uv] # 1. 物理定义 PyTorch 的专用硬件加速索引库 [tool.uv.index] name = \u0026#34;pytorch-cu130\u0026#34; url = \u0026#34;https://download.pytorch.org/whl/cu130\u0026#34; explicit = true # 强制：只有在 sources 中明确指定的包才去这里找，防止污染其他依赖 [tool.uv.sources] # 2. 将核心组件物理绑定到上述索引 torch = { index = \u0026#34;pytorch-cu130\u0026#34; } torchvision = { index = \u0026#34;pytorch-cu130\u0026#34; } torchaudio = { index = \u0026#34;pytorch-cu130\u0026#34; } 解释一下:\ntool.uv: 告诉uv编译器,下面是我要你遵循的规则 tool.uv.index: 提供自定义组件源,而不是到官方库下载 tool.uv.sources: 将组件与index绑定,只有与index绑定的组件才会去自定义组件源下载 运行一下代码,很成功:\n1 2 3 import torch print(f\u0026#34;CUDA status: {torch.cuda.is_available()}\u0026#34;) print(f\u0026#34;CUDA version: {torch.version.cuda}\u0026#34;) 1 2 3 uv run ch1.py CUDA status: True CUDA version: 13.0 python进阶 python关键字与内置函数 with is和is not yield try finally catch throw async与await 于2015年的python3.5引入 assert 官方文档 菜鸟教程 基础用法 1 2 3 4 5 assert expression # 等价于下述代码: if __debug__: if not expression: raise AssertionError 报错后输出提示\n1 2 import sys assert (\u0026#39;linux\u0026#39; in sys.platform), \u0026#34;该代码只能在 Linux 下执行\u0026#34; python常用语法糖 官方文档 @classmethod Transform a method into a class method.\n字面意思,将某个方法实例化到这个类中,从而可以直接调用,不需要使用self来指向实例,也不需要进行类的实例化. 但仍然需要填入参数cls(自然可以叫别的名字,cls只是一个习惯上的写法),用来指向这个类\n因为这个类还没完成,就不能用类名.var/method来调用类内变量和函数,故需要通过cls来指向该类. 1 2 3 4 5 6 7 8 9 10 11 class A(object): bar = 1 def func1(self): print (\u0026#39;foo\u0026#39;) @classmethod def func2(cls): print (\u0026#39;func2\u0026#39;) print (cls.bar) cls().func1() # 调用 foo 方法 A.func2() # 不需要实例化 @property Return a property attribute.\nA property object has getter, setter, and deleter methods usable as decorators that create a copy of the property with the corresponding accessor function set to the decorated function. This is best explained with an example:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class C: def __init__(self): self._x = None @property def x(self): \u0026#34;\u0026#34;\u0026#34;I\u0026#39;m the \u0026#39;x\u0026#39; property.\u0026#34;\u0026#34;\u0026#34; return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x 1 2 3 4 5 6 7 8 class Parrot: def __init__(self): self._voltage = 100000 @property def voltage(self): \u0026#34;\u0026#34;\u0026#34;Get the current voltage.\u0026#34;\u0026#34;\u0026#34; return self._voltage The @property decorator turns the voltage() method into a “getter” for a read-only attribute with the same name, and it sets the docstring for voltage to “Get the current voltage.” 如果还是看不懂的话,就把property看成是一个将方法转换成类内只读属性的语法糖(可以少写一对括号,并且不可修改),但可以通过setter和deleter来修改.\n@dataclass 官方文档 参考教程 是什么,怎么用 一般来说,我们定义类时需要这么写来初始化:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class CoinTrans: def __init__( self, id: str, symbol: str, price: float, is_success: bool, addrs: list, ) -\u0026gt; None: self.id = id self.symbol = symbol self.price = price self.addrs = addrs self.is_success = is_success if __name__ == \u0026#34;__main__\u0026#34;: coin_trans = CoinTrans(\u0026#34;id01\u0026#34;, \u0026#34;BTC/USDT\u0026#34;, \u0026#34;71000\u0026#34;, True, [\u0026#34;0x1111\u0026#34;, \u0026#34;0x2222\u0026#34;]) print(coin_trans) # \u0026lt;__main__.CoinTrans object at 0x0000022A891FADD0\u0026gt; 自然,python打印类的时候默认是打印类的内存地址的,这需要我们去单独实现一个打印函数返回类中的各种信息.\n但如果使用dataclass装饰器的话,可以这样写:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 from dataclasses import dataclass @dataclass class CoinTrans: id: str symbol: str price: float is_success: bool addrs: list if __name__ == \u0026#34;__main__\u0026#34;: coin_trans = CoinTrans(\u0026#34;id01\u0026#34;, \u0026#34;BTC/USDT\u0026#34;, \u0026#34;71000\u0026#34;, True, [\u0026#34;0x1111\u0026#34;, \u0026#34;0x2222\u0026#34;]) print(coin_trans) # CoinTrans(id=\u0026#39;id01\u0026#39;, symbol=\u0026#39;BTC/USDT\u0026#39;, price=\u0026#39;71000\u0026#39;, is_success=True, addrs=[\u0026#39;0x1111\u0026#39;, \u0026#39;0x2222\u0026#39;]) 不需要写__init__,也不需要写打印函数,就可以直接实现上述的效果.\npython源码剖析 参考书籍:(python源码剖析:深度探索动态语言核心技术),本书围绕的是06年的python2.5.0\n概览(include/object.h) 我觉得python最明显的优点是自动内存管理,不需要进行垃圾收集,减少了写代码的各种烦恼\npyobject python中所有对象和类型都用pyobject指针类型来表示,可以通过将指针指向不同的内存区域来实现内存区域的扩张与收缩,这也是python之所以能够称为动态语言的本钱,其中pyobject定义如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define _PyObject_HEAD_EXTRA\t\\ struct _object *_ob_next;\t\\ struct _object *_ob_prev; #define PyObject_HEAD\t\\ _PyObject_HEAD_EXTRA\t\\ Py_ssize_t ob_refcnt;\t\\ struct _typeobject *ob_type; /* ... */ typedef struct _object { PyObject_HEAD } PyObject; 这里的Py_ssize_t实际上就是 long long,我就说世界是一个巨大的草台班子 1 2 3 typedef ssize_t\tPy_ssize_t; //... typedef __int64 ssize_t; 而每次pyobject对象A被引用时,引用计数就增加1,当引用对象被删除时,引用计数就减小1,减小到零时就会从堆上被删除\n1 2 3 4 5 6 7 8 9 #define _Py_INC_REFTOTAL\t_Py_RefTotal++ #define _Py_DEC_REFTOTAL\t_Py_RefTotal-- /* ... */ #define Py_INCREF(op) (\t\\ _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA\t\\ (op)-\u0026gt;ob_refcnt++) 1 2 3 4 5 6 7 #define PyObject_VAR_HEAD\t\\ PyObject_HEAD\t\\ Py_ssize_t ob_size; /* Number of items in variable part */ typedef struct { PyObject_VAR_HEAD } PyVarObject; 可以看到python里还有一类PyVarObject,用来管理可变长度的容器对象\nPyIntObject intobject.h\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 /* Integer object interface */ /* PyIntObject represents a (long) integer. This is an immutable object; an integer cannot change its value after creation. There are functions to create new integer objects, to test an object for integer-ness, and to get the integer value. The latter functions returns -1 and sets errno to EBADF if the object is not an PyIntObject. None of the functions should be applied to nil objects. The type PyIntObject is (unfortunately) exposed here so we can declare _Py_TrueStruct and _Py_ZeroStruct in boolobject.h; don\u0026#39;t use this. */ #ifndef Py_INTOBJECT_H #define Py_INTOBJECT_H #ifdef __cplusplus extern \u0026#34;C\u0026#34; { #endif typedef struct { PyObject_HEAD long ob_ival; } PyIntObject; PyAPI_DATA(PyTypeObject) PyInt_Type; #define PyInt_Check(op) PyObject_TypeCheck(op, \u0026amp;PyInt_Type) #define PyInt_CheckExact(op) ((op)-\u0026gt;ob_type == \u0026amp;PyInt_Type) PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int); #ifdef Py_USING_UNICODE PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int); #endif PyAPI_FUNC(PyObject *) PyInt_FromLong(long); PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t); PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t); PyAPI_FUNC(long) PyInt_AsLong(PyObject *); PyAPI_FUNC(Py_ssize_t) PyInt_AsSsize_t(PyObject *); PyAPI_FUNC(unsigned long) PyInt_AsUnsignedLongMask(PyObject *); #ifdef HAVE_LONG_LONG PyAPI_FUNC(unsigned PY_LONG_LONG) PyInt_AsUnsignedLongLongMask(PyObject *); #endif PyAPI_FUNC(long) PyInt_GetMax(void); /* Macro, trading safety for speed */ #define PyInt_AS_LONG(op) (((PyIntObject *)(op))-\u0026gt;ob_ival) /* These aren\u0026#39;t really part of the Int object, but they\u0026#39;re handy; the protos * are necessary for systems that need the magic of PyAPI_FUNC and that want * to have stropmodule as a dynamically loaded module instead of building it * into the main Python shared library/DLL. Guido thinks I\u0026#39;m weird for * building it this way. :-) [cjh] */ PyAPI_FUNC(unsigned long) PyOS_strtoul(char *, char **, int); PyAPI_FUNC(long) PyOS_strtol(char *, char **, int); #ifdef __cplusplus } #endif #endif /* !Py_INTOBJECT_H */ 涉及的代码并不长,因此整个贴出来了 python类型注释 类型注释在PEP484中引入,也就是2015年的python3.5,从而在很大程度上解决了python动态类型带来的混乱.\n简单的类型注释 如下方代码所示,类型注释有两种格式:\n变量名: 类型: 用于提示参数的类型 函数末尾 -\u0026gt; 类型:: 用于提示函数的返回值类型 1 2 def surface_area_of_cube(edge_length: float) -\u0026gt; str: return f\u0026#34;The surface area of the cube is {6 * edge_length ** 2}.\u0026#34; 大多数类型注释都不需要导入任何库即可使用,下面是一个常用的系统直接支持的类型注释表格:\n分类 语法示例 说明 适用版本 基础标量 var: int, var: float, var: bool, var: str, var: bytes 整数、浮点、布尔、字符串、字节流 全版本 空值/无返回 def fn() -\u0026gt; None: 表示函数没有返回值 全版本 列表 var: list[int] 元素全为整数的列表 3.9+ 字典 var: dict[str, int] 键为字符串、值为整数的字典 3.9+ 元组 (定长) var: tuple[int, str] 包含一个整数和一个字符串的二元组 3.9+ 元组 (变长) var: tuple[int, ...] 包含任意数量整数的元组 3.9+ 集合 var: set[str] 元素全为字符串的集合 3.9+ 联合类型 var: int | str 变量可以是整数或字符串 (Union) 3.10+ 可选类型 var: str | None 变量可以是字符串或为空 (Optional) 3.10+ 类对象 var: type[MyClass] 变量是类本身，而不是类的实例 3.9+ 自定义类 var: MyClass 变量是该类的实例 全版本 双向队列 var: collections.deque[int] 需 Python 3.9+，虽在 collections 但无需 import typing 3.9+ 切片 var: slice 内存索引切片对象 全版本 范围 var: range 迭代范围对象 全版本 枚举迭代 var: enumerate 枚举对象 全版本 这些简单类型已经可以涵盖大多数应用场景了,如果需要使用更高级的类型注释功能,就需要导入typing库来使用.\ntyping系统库 官方文档 类型别名: type 类型别名是使用 type 简写 = 复杂类型 语句来定义的，它将创建一个 TypeAliasType 的实例.\n1 2 3 4 5 6 7 type Vector = list[float] def scale(scalar: float, vector: Vector) -\u0026gt; Vector: return [scalar * num for num in vector] # 通过类型检查；浮点数列表是合格的 Vector。 new_vector = scale(2.0, [1.0, -4.2, 5.4]) Any: 支持任何类型 不使用类型注释时,所有的变量和返回值都被视为Any,用Any类型注解的变量不会在类型检查时报错.\n在现代python项目中,有三种情况会用到它:\n某一个变量支持不同类型的值 你不知道它应该是什么值 无论它是什么值都无所谓 显然,如果你全用Any的话也可以通过类型检查,但这样就没有意义了.\npython测试 如何写测试 pytest文档 写的很好,所以全文摘录 In the simplest terms, a test is meant to look at the result of a particular behavior, and make sure that result aligns with what you would expect. Behavior is not something that can be empirically measured, which is why writing tests can be challenging.\n“Behavior” is the way in which some system acts in response to a particular situation and/or stimuli. But exactly how or why something is done is not quite as important as what was done.\nYou can think of a test as being broken down into four steps:\nArrange Act Assert Cleanup Arrange is where we prepare everything for our test. This means pretty much everything except for the “act”. It’s lining up the dominoes so that the act can do its thing in one, state-changing step. This can mean preparing objects, starting/killing services, entering records into a database, or even things like defining a URL to query, generating some credentials for a user that doesn’t exist yet, or just waiting for some process to finish.\nAct is the singular, state-changing action that kicks off the behavior we want to test. This behavior is what carries out the changing of the state of the system under test (SUT), and it’s the resulting changed state that we can look at to make a judgement about the behavior. This typically takes the form of a function/method call.\nAssert is where we look at that resulting state and check if it looks how we’d expect after the dust has settled. It’s where we gather evidence to say the behavior does or does not align with what we expect. The assert in our test is where we take that measurement/observation and apply our judgement to it. If something should be green, we’d say assert thing == \u0026quot;green\u0026quot;.\nCleanup is where the test picks up after itself, so other tests aren’t being accidentally influenced by it.\nAt its core, the test is ultimately the act and assert steps, with the arrange step only providing the context. Behavior exists between act and assert.\n也就是说,在写测试之前,我们需要设计一些能够体现代码功能或者bug的操作,放入测试函数中,然后执行函数并检测输出是否与预期的一致,并保证测试结果彼此之间互不影响.\npytest Intro pytest在python测试库中占据了统治地位,而python系统库自带的unittest就显得逊色很多了,故测试库里我只介绍pytest.\n我们先创建一个test_parts.py文件,填入以下代码:\n1 2 3 4 5 6 def func(x): return x + 1 def test_answer(): assert func(3) == 5 如果是全局安装过,或者在虚拟环境安装了的话,只要在终端输入pytest即可 如果使用uv管理的话,只需输入以下命令: 1 uv run pytest 该命令将运行当前目录并递归运行子目录中所有形式为 test_*.py 或 *_test.py 的文件.\n如果文件中的代码块不是全局的而是位于函数中,则需要函数名带有类似的test_*()格式 如果把函数放在类里面,则需要在类名前面加上Test,否则该类被整个跳过 来看看输出结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ================================= test session starts ================================== platform win32 -- Python 3.13.7, pytest-9.0.2, pluggy-1.6.0 rootdir: xxx configfile: pyproject.toml plugins: anyio-4.12.1 collected 1 item test_parts.py F [100%] ======================================= FAILURES ======================================= _____________________________________ test_answer ______________________________________ def test_answer(): \u0026gt; assert func(3) == 5 E assert 4 == 5 E + where 4 = func(3) test_parts.py:7: AssertionError =============================== short test summary info ================================ FAILED test_parts.py::test_answer - assert 4 == 5 ================================== 1 failed in 0.10s =================================== [100%] 指的是运行所有测试用例的总体进度 使用pytest语法糖 官方教程 参考教程 用@pytest.fixture()修饰的函数在文件内部可以直接被其他函数调用名字并获取返回值,具体如下所示:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import pytest @pytest.fixture() def login(): print(\u0026#34;登录\u0026#34;) return 8 class Test_Demo(): def test_case1(self): print(\u0026#34;\\n开始执行测试用例1\u0026#34;) assert 1 + 1 == 2 def test_case2(self, login): print(\u0026#34;\\n开始执行测试用例2\u0026#34;) print(login) assert 2 + login == 10 def test_case3(self): print(\u0026#34;\\n开始执行测试用例3\u0026#34;) assert 99 + 1 == 100 if __name__ == \u0026#39;__main__\u0026#39;: pytest.main() login()在这里相当于一个测试工具函数 语法糖参数: autouse 默认情况下,被@pytest.fixture()修饰的工具函数只在被请求时才被加载,如果没有任何一个测试用例用到这个函数,它就永远不会运行,也就是懒加载(lazy loading).\n乍一看挺好的,但是如果测试中有大量的测试用例更改了数据库,我们不希望一个个去撤销数据库更改后还原,不仅让代码变得臃肿,而且很累.\n所以,我们使用autouse=True来让被修饰的函数强制生效,而不管测试用例有没有调用这个函数.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import pytest @pytest.fixture(autouse=True) def login(): print(\u0026#34;登录...\u0026#34;) class Test_Demo(): def test_case1(self): print(\u0026#34;\\n开始执行测试用例1\u0026#34;) assert 1 + 1 == 2 def test_case2(self): print(\u0026#34;\\n开始执行测试用例2\u0026#34;) assert 2 + 8 == 10 def test_case3(self): print(\u0026#34;\\n开始执行测试用例3\u0026#34;) assert 99 + 1 == 100 if __name__ == \u0026#39;__main__\u0026#39;: pytest.main() 终端输出\n1 2 3 4 5 6 7 8 9 登录... PASSED [ 33%] 开始执行测试用例1 登录... PASSED [ 66%] 开始执行测试用例2 登录... PASSED [100%] 开始执行测试用例3 语法糖参数: scope fixture作用范围可以为module、class、session和function，默认作用域为function。\n其核心逻辑是：在指定作用域内，只执行一次初始化，然后所有人共享这个缓存的对象。\nfunction（函数级） 频率：最高。 含义：每个测试函数执行前，都会重新运行一遍 Fixture。 场景：你需要每个测试用例都拥有一个全新的、干净的数据副本，防止 A 用例的操作影响到 B 用例。 class（类级） 频率：中。 含义：如果一个测试类（class TestXXX）里有 10 个测试方法，这个 Fixture 只会在进入该类时运行一次，10 个方法共用同一个对象。 场景：测试类中的所有方法都需要同一个昂贵的对象（如一个已经打开的浏览器窗口）。 module（模块级） 频率：低。 含义：在一个 .py 文件中，无论有多少个类或函数，Fixture 只在该文件开始时运行一次。 场景：同一个文件内的测试都依赖于同一个外部配置函数。 session（会话级） 频率：最低。 含义：当你运行 pytest 命令开始，到所有测试结束，Fixture 只运行一次。 场景：启动整个项目的测试数据库、初始化大型算法模型或全局 API 客户端。 yield关键字在pytest中的使用 在yield关键字之前的代码在测试函数开始运行之前执行，yield之后的代码在函数运行结束后执行\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import pytest @pytest.fixture() def login(): print(\u0026#34;登录\u0026#34;) yield print(\u0026#34;退出登录\u0026#34;) class Test_Demo(): def test_case1(self): print(\u0026#34;\\n开始执行测试用例1\u0026#34;) assert 1 + 1 == 2 def test_case2(self, login): print(\u0026#34;\\n开始执行测试用例2\u0026#34;) assert 2 + 8 == 10 def test_case3(self): print(\u0026#34;\\n开始执行测试用例3\u0026#34;) assert 99 + 1 == 100 if __name__ == \u0026#39;__main__\u0026#39;: pytest.main() 终端输出\n1 2 3 4 5 6 7 8 PASSED [ 33%] 开始执行测试用例1 登录 PASSED [ 66%] 开始执行测试用例2 退出登录 PASSED [100%] 开始执行测试用例3 conftest.py文件 自动识别机制 只要文件名为 conftest.py，pytest 会在启动时自动扫描并加载它。你不需要在测试文件中显式 import 它。\n作用范围（层级继承） conftest.py 的作用范围遵循目录树结构：\n根目录：如果放在项目根目录，其定义的配置对整个项目生效。 子目录：如果放在某个子目录（如 tests/unit/conftest.py），则仅对该目录及其子目录下的测试文件生效。 优先级：子目录中的 conftest.py 会重写或扩展父目录中的同名配置。 核心用途 它本质上是一个本地插件库，主要处理以下三类任务：\nFixtures（固件）共享： 在 conftest.py 中定义的 @pytest.fixture 可以被该目录下的所有测试用例直接通过参数名调用,不用再进行导入 Hook函数自定义： 可以修改 pytest 的内部行为。例如 pytest_runtest_setup（在测试开始前执行）或 pytest_addoption（添加自定义命令行参数）。 外部插件加载： 通过 pytest_plugins = [\u0026quot;plugin1\u0026quot;, \u0026quot;plugin2\u0026quot;] 在特定目录下引入额外的插件。 关键限制 不可跨目录手动导入：永远不要尝试 from conftest import ...。如果这样做，会破坏 pytest 的加载机制，可能导致配置冲突或重复初始化。 文件命名固定：必须严格命名为 conftest.py，否则 pytest 会将其视为普通的 Python 模块 实战 事实上,上述的内容基本涵盖了我们所需的pytest知识了,我们现在拿fastapi模板项目中的test部分来做例子,深入探讨一下pytest的实际应用\n看来我这个博客可以靠着fastapi啃很久了 先翻到后端的Readme:\nIf your stack is already up and you just want to run the tests, you can use:\n1 docker compose exec backend bash scripts/tests-start.sh 看来这就是测试脚本了,让我们看看tests-start.sh的内容:\n1 2 3 4 5 6 7 8 9 10 11 12 #! /usr/bin/env bash set -e # 立即退出模式,脚本中任何一条命令执行失败将停止脚本继续执行 set -x # 调试模式: 执行每条命令前先将命令打印到终端 python app/tests_pre_start.py # 执行预启动脚本 bash scripts/test.sh \u0026#34;$@\u0026#34; # 执行test.sh脚本 # \u0026#34;$@\u0026#34;: 将当前脚本用到的参数传给test.sh脚本 # 如果我执行./tests-start.sh --verbose --fail-fast # 那么执行test.sh时也会带有--verbose --fail-fast参数 那我们再看看test.sh脚本\n1 2 3 4 5 6 7 8 9 10 11 #!/usr/bin/env bash set -e set -x coverage run -m pytest tests/ # 执行tests文件夹下的测试 coverage report # 在终端输出测试信息 coverage html --title \u0026#34;${@-coverage}\u0026#34; # 生成可视化html报告,通常位于 htmlcov/ 目录 也就是说,到头来还是用pytest执行了tests文件夹里的测试,只不过多了一些其他的包装而已.\n这就是全部的测试文件了,还是很多的,这说明测试并非是无关轻重的代码部分 先来看看最外层的conftest.py文件: conftest.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from collections.abc import Generator import pytest from fastapi.testclient import TestClient from sqlmodel import Session, delete from app.core.config import settings from app.core.db import engine, init_db from app.main import app from app.models import Item, User from tests.utils.user import authentication_token_from_email from tests.utils.utils import get_superuser_token_headers @pytest.fixture(scope=\u0026#34;session\u0026#34;, autouse=True) def db() -\u0026gt; Generator[Session, None, None]: with Session(engine) as session: init_db(session) yield session statement = delete(Item) session.execute(statement) statement = delete(User) session.execute(statement) session.commit() # @pytest.fixture(scope=\u0026#34;module\u0026#34;) # def client() -\u0026gt; Generator[TestClient, None, None]: # with TestClient(app) as c: # yield c # @pytest.fixture(scope=\u0026#34;module\u0026#34;) # def superuser_token_headers(client: TestClient) -\u0026gt; dict[str, str]: # return get_superuser_token_headers(client) # @pytest.fixture(scope=\u0026#34;module\u0026#34;) # def normal_user_token_headers(client: TestClient, db: Session) -\u0026gt; dict[str, str]: # return authentication_token_from_email( # client=client, email=settings.EMAIL_TEST_USER, db=db # ) 第一个函数db在整个测试开始时启动一次,将数据库初始化,并删除Item和User关系表,从而清空所有数据;至于其他被注释掉的函数都是给其他测试模块用的工具函数\n换句话说,这个测试只能在开发环境做,一旦部署好了就不要再搞测试了 除了utils文件夹下的文件都是工具函数外,其余的文件基本都是以test_前缀打头的pytest文件了.我们只挑一个最精华的文件来看: test_items.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import uuid from fastapi.testclient import TestClient from sqlmodel import Session from app.core.config import settings from tests.utils.item import create_random_item def test_create_item( client: TestClient, superuser_token_headers: dict[str, str] ) -\u0026gt; None: data = {\u0026#34;title\u0026#34;: \u0026#34;Foo\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Fighters\u0026#34;} response = client.post( f\u0026#34;{settings.API_V1_STR}/items/\u0026#34;, headers=superuser_token_headers, json=data, ) assert response.status_code == 200 content = response.json() assert content[\u0026#34;title\u0026#34;] == data[\u0026#34;title\u0026#34;] assert content[\u0026#34;description\u0026#34;] == data[\u0026#34;description\u0026#34;] assert \u0026#34;id\u0026#34; in content assert \u0026#34;owner_id\u0026#34; in content def test_read_item( client: TestClient, superuser_token_headers: dict[str, str], db: Session ) -\u0026gt; None: item = create_random_item(db) response = client.get( f\u0026#34;{settings.API_V1_STR}/items/{item.id}\u0026#34;, headers=superuser_token_headers, ) assert response.status_code == 200 content = response.json() assert content[\u0026#34;title\u0026#34;] == item.title assert content[\u0026#34;description\u0026#34;] == item.description assert content[\u0026#34;id\u0026#34;] == str(item.id) assert content[\u0026#34;owner_id\u0026#34;] == str(item.owner_id) def test_read_item_not_found( client: TestClient, superuser_token_headers: dict[str, str] ) -\u0026gt; None: response = client.get( f\u0026#34;{settings.API_V1_STR}/items/{uuid.uuid4()}\u0026#34;, headers=superuser_token_headers, ) assert response.status_code == 404 content = response.json() assert content[\u0026#34;detail\u0026#34;] == \u0026#34;Item not found\u0026#34; 第一个函数test_create_item模拟管理员创建一个测试数据,并判断收到的响应报文中的数据是否相同. 第二个函数test_read_item模拟管理员在创建一个随机物品后,判断使用get请求是否正常. 第三个函数test_read_item_not_found模拟管理员直接访问一个不存在的物品,需要注意的是,这里的uuid4方法有可能产生恰好与之前测试生成相同的物品id,而我们的数据库清空是只在开始运行时执行,而不是每次执行测试函数都执行,因此有极低的概率会返回200状态码导致测试失败 后面的函数都大差不差了,基本就是构造测试数据,使用client模拟前端进行访问,并判断响应是否正常,但是有一个问题:既然要模拟前端访问,自然需要后端能够响应,才能执行测试,但根据前面的脚本分析,我们仅仅是用了pytest启动test文件夹中的测试而已,并没有真正的启动后端,那么测试是如何执行的呢?\n答案在最开始的conftest.py中,我们的测试函数中都引入了client: TestClient这个工具函数,而这个函数在conftest.py中早就定义好了:\n1 2 3 4 5 6 from app.main import app from fastapi.testclient import TestClient @pytest.fixture(scope=\u0026#34;module\u0026#34;) def client() -\u0026gt; Generator[TestClient, None, None]: with TestClient(app) as c: yield c 这里的TestClient方法的作用域为模块级,即只在该文件的测试开始执行时调用一次,使用了main.py中的app对象:\n1 2 3 4 5 6 7 # 真实后端里的main.py app = FastAPI( title=settings.PROJECT_NAME, openapi_url=f\u0026#34;{settings.API_V1_STR}/openapi.json\u0026#34;, generate_unique_id_function=custom_generate_unique_id, ) 也就是说,我们启动了后端中的关键部分,从而实现对后端的整体调用,测试整个应用的运行是否正常.\npython格式检查 ruff 是什么,怎么用 ruff是用rust编写的python格式检查库,可以迅速将py文件规范化,速度比一版的格式检查库都要快很多.\n要使用ruff,我们需要先将它加入到当前项目中:\n1 uv add --dev ruff 之后再运行以下命令就可以检查该项目是否规范\n1 uv run ruff check 基本用法 ruff check\n1 2 3 4 ruff check # Lint files in the current directory. ruff check --fix # Lint files in the current directory and fix any fixable errors. ruff check --watch # Lint files in the current directory and re-lint on change. ruff check path/to/code/ # Lint files in `path/to/code`. python读取文件 本部分所用的weekly_hiring_comments.json示例的结构如下:\n1 2 3 4 5 6 7 8 9 10 [ { \u0026#34;issue\u0026#34;: 692, \u0026#34;author\u0026#34;: \u0026#34;ruanyf\u0026#34;, \u0026#34;created_at\u0026#34;: \u0026#34;2019-07-18T07:00:46Z\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;### **高级 Web 前端工程师**\\r\\n \\r\\n[深圳追一科技](https://zhuiyi.ai/)，人工智能创业公司。工作地点：深圳市南山区。\\r\\n\\r\\n公司主打 NLP 方向的 B 端 AI 产品落地，诚求英才。要求4年以上实际前端项目的开发经验，熟练掌握 Vue 或 React 生态，查看[详细信息](https://www.zhipin.com/job_detail/79ca9be7fb736e4d03Nz3924FVA~.html)。\\r\\n\\r\\nEmail 联系：[winchang@wezhuiyi.com](mailto:winchang@wezhuiyi.com)\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://github.com/ruanyf/weekly/issues/692#issuecomment-512691467\u0026#34; }, // ... ] open方法 读写文件一般都通过open方法来进行操作,基本用法看下面的代码就很容易理解了:\n1 2 3 4 5 with open(\u0026#34;weekly_hiring_comments.json\u0026#34;, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: posts = json.load(f) with open(\u0026#34;本科及以上.json\u0026#34;, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: json.dump(bachelor_posts, f, ensure_ascii=False, indent=2) 三个参数分别为:\nfile(文件路径) mode(操作方式) encoding(解码方式) mode 的值包括以下几种:\n\u0026lsquo;r\u0026rsquo; ，表示读取文件 \u0026lsquo;w\u0026rsquo; 表示写入文件（现有同名文件会被覆盖） \u0026lsquo;a\u0026rsquo; 表示打开文件并追加内容，任何写入的数据会自动添加到文件末尾 \u0026lsquo;r+\u0026rsquo; 表示打开文件进行读写 mode 实参是可选的，省略时的默认值为 \u0026lsquo;r\u0026rsquo; 当然,如果看源码的话还能看到一堆参数,但我们一般只用得到上述的三个参数:\n1 2 3 4 5 6 7 8 9 10 def open( file: FileDescriptorOrPath, mode: OpenTextMode = \u0026#34;r\u0026#34;, buffering: int = -1, encoding: str | None = None, errors: str | None = None, newline: str | None = None, closefd: bool = True, opener: _Opener | None = None, ) -\u0026gt; TextIOWrapper: ... 现在的问题是这个读写的文件会有很多种格式(.pdf,.txt,.json,.html,.js, \u0026hellip;),我们来看看open是怎么处理的:\ntext mode - 默认格式: 通常情况下,文件以该模式打开,一般使用utf-8进行编码,该模式主要用于处理文本文件 binary mode - 以二进制模式读取文件,需要在mode词尾加上一个\u0026rsquo;b\u0026rsquo;,如wb,ab等,在二进制模式下无法指定encoding(也没有必要指定),该模式主要用于读取.png,.mp3,.pdf这样的二进制文件 换句话说,open函数根本不会对每种文件进行特殊处理,只是有两种读取方式而已了,对于一些特殊的文件格式,我们都需要额外用其他库去处理.\n但是对于一般的文件格式,open函数读取文件名后会返回一个TextIOWrapper对象,它有两种常用的方法:\n.read()方法: 将全文读入一个字符串变量 例子: content = f.read() .write()方法: 写入字符串 例子: f.write(f\u0026quot;## 招聘 \\n\\n\u0026quot;) json系统库:处理json文件 既然是系统库,那自然要先导入后使用,事实上只有两个常用函数: json.load()和json.dump().\n示例\n1 2 3 4 5 6 7 with open(\u0026#34;weekly_hiring_comments.json\u0026#34;, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: posts = json.load(f) bachelor_posts = [] with open(out_dir / \u0026#34;本科及以上.json\u0026#34;, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: json.dump(bachelor_posts, f, ensure_ascii=False, indent=2) 看看源码和参数:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # load() (function) def load( fp: SupportsRead[str | bytes], *, cls: type[JSONDecoder] | None = None, object_hook: ((dict[Any, Any]) -\u0026gt; Any) | None = None, parse_float: ((str) -\u0026gt; Any) | None = None, parse_int: ((str) -\u0026gt; Any) | None = None, parse_constant: ((str) -\u0026gt; Any) | None = None, object_pairs_hook: ((list[tuple[Any, Any]]) -\u0026gt; Any) | None = None, **kwds: Any ) -\u0026gt; Any # dump() (function) def dump( obj: Any, fp: SupportsWrite[str], *, skipkeys: bool = False, ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, cls: type[JSONEncoder] | None = None, indent: int | str | None = None, separators: tuple[str, str] | None = None, default: ((Any) -\u0026gt; Any) | None = None, sort_keys: bool = False, **kwds: Any ) -\u0026gt; None 速览一下就知道用法了,读json文件时指定文件名,写json文件时指定写入内容和写入文件名就可以了\n处理md文件 md文件没有专门的库,直接读写就可以了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 with open(\u0026#34;weekly_hiring_comments.json\u0026#34;, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: posts = json.load(f) out = Path(\u0026#34;weekly_hiring_comments.md\u0026#34;) with out.open(\u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: for i, p in enumerate(posts, 1): f.write(f\u0026#34;## 招聘 {i}\\n\\n\u0026#34;) f.write(f\u0026#34;- Issue: #{p[\u0026#39;issue\u0026#39;]}\\n\u0026#34;) f.write(f\u0026#34;- 作者: {p[\u0026#39;author\u0026#39;]}\\n\u0026#34;) f.write(f\u0026#34;- 时间: {p[\u0026#39;created_at\u0026#39;]}\\n\u0026#34;) f.write(f\u0026#34;- 来源: {p[\u0026#39;url\u0026#39;]}\\n\\n\u0026#34;) f.write(p[\u0026#34;text\u0026#34;]) f.write(\u0026#34;\\n\\n---\\n\\n\u0026#34;) pathlib库 该库在不同平台下都能轻松读取文件路径,而不需要操心系统问题或者字符串问题.\n官方文档 如果以前从未用过此模块，或不确定哪个类适合完成任务，那要用的可能就是 Path。它在运行代码的平台上实例化为具体路径.\n接下来我们来详细介绍Path库\nPath对象的创建 1 2 3 4 5 6 7 8 9 10 11 12 13 from pathlib import Path # 基础用法 out_file: Path = Path(\u0026#34;a.md\u0026#34;) # 拼接路径的两种写法 # 简写 out_file: Path = Path(\u0026#34;modules\u0026#34;) / \u0026#34;a.py\u0026#34; # 分开写 out_dir: Path = Path(\u0026#34;modules\u0026#34;) out_file: Path = out_dir / \u0026#34;issues.md\u0026#34; 上述的代码由于没有指定绝对路径,故都是相对于python运行目录的路径,但我们也可以指定绝对路径,如下文所示:\n1 2 3 4 5 6 7 from pathlib import Path # Windows 风格 abs_path_win = Path(\u0026#34;C:/Users/Admin/Desktop/a.md\u0026#34;) # Linux/macOS 风格 abs_path_unix = Path(\u0026#34;/home/user/project/a.md\u0026#34;) 也就是说,我们不需要再去折腾不同操作系统的路径问题了,统一用/就可以确定相对的路径.\n使用Path来创建文件夹 只需要调用mkdir方法即可:\n1 2 out_dir: Path = Path(\u0026#34;issues_md\u0026#34;) out_dir.mkdir(exist_ok=True) exist_ok参数的作用: 默认为False,设置为True时,即便当前路径下有这个文件夹,也不会报错 Path对象的open方法 事实上,这个open方法与python内置的open方法基本没有区别,只是把文件路径提到外面来了而已:\n1 2 3 4 5 6 7 out_dir: Path = Path(\u0026#34;issues_md\u0026#34;) out_dir.mkdir(exist_ok=True) for issue, items in by_issue.items(): path = out_dir / f\u0026#34;issue_{issue}.md\u0026#34; with path.open(\u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: f.write(f\u0026#34;# Issue #{issue} 招聘汇总\\n\\n\u0026#34;) Path对象的glob方法(待补充) 实战 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import json from pathlib import Path from collections import defaultdict with open(\u0026#34;weekly_hiring_comments.json\u0026#34;, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: posts = json.load(f) # 读取json列表 by_issue = defaultdict(list) for p in posts: by_issue[p[\u0026#34;issue\u0026#34;]].append(p) # 处理为一个有序字典,这在json列表本身是无序的时候比较好用 out_dir: Path = Path(\u0026#34;issues_md\u0026#34;) out_dir.mkdir(exist_ok=True) for issue, items in by_issue.items(): path = out_dir / f\u0026#34;issue_{issue}.md\u0026#34; with path.open(\u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: f.write(f\u0026#34;# Issue #{issue} 招聘汇总\\n\\n\u0026#34;) for i, p in enumerate(items, 1): f.write(f\u0026#34;## 招聘 {i}\\n\\n\u0026#34;) f.write(f\u0026#34;- 作者: {p[\u0026#39;author\u0026#39;]}\\n\u0026#34;) f.write(f\u0026#34;- 时间: {p[\u0026#39;created_at\u0026#39;]}\\n\u0026#34;) f.write(f\u0026#34;- 来源: {p[\u0026#39;url\u0026#39;]}\\n\\n\u0026#34;) f.write(p[\u0026#34;text\u0026#34;]) f.write(\u0026#34;\\n\\n---\\n\\n\u0026#34;) re系统库 官方文档 该库是对正则表达式(regular expression)的封装,所以叫re.\ncompile方法 compile是一个实例化pattern对象的方法,pattern一词在re中指的是正则表达式字符串\n1 2 3 4 5 prog = re.compile(pattern) result = prog.match(string) # 上述代码等价于下面的这个 result = re.match(pattern, string) # 为了规范化和复用,我们还是多用compile方法来指明pattern对象 事实上,re库中的大多数常用方法都有两种写法,一种是模式.方法(参数),另一种是方法.(模式,参数).为了规范起见,我们后面都采用模式.方法(参数)写法,就不再次说明了\nsearch方法与match方法 菜鸟教程 re.match只匹配字符串的开始，如果字符串开始不符合正则表达式，则匹配失败，函数返回None；而re.search匹配整个字符串，直到找到一个匹配。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/usr/bin/python import re line = \u0026#34;Cats are smarter than dogs\u0026#34;; matchObj = re.match( r\u0026#39;dogs\u0026#39;, line, re.M|re.I) if matchObj: print \u0026#34;match --\u0026gt; matchObj.group() : \u0026#34;, matchObj.group() else: print \u0026#34;No match!!\u0026#34; matchObj = re.search( r\u0026#39;dogs\u0026#39;, line, re.M|re.I) if matchObj: print \u0026#34;search --\u0026gt; searchObj.group() : \u0026#34;, matchObj.group() else: print \u0026#34;No match!!\u0026#34; 运行结果\n1 2 No match!! search --\u0026gt; searchObj.group() : dogs 实战 下面的整个代码流程为:\n载入json文件为列表posts 使用compile方法组织匹配模式 将posts里对应学历要求的帖子中的text字段里的值插入列表中 导出json文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 with open(\u0026#34;weekly_hiring_comments.json\u0026#34;, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: posts = json.load(f) bachelor_patterns = [ r\u0026#34;本科及以上\u0026#34;, r\u0026#34;本科以上\u0026#34;, ] master_patterns = [ r\u0026#34;硕士及以上\u0026#34;, r\u0026#34;硕士以上\u0026#34;, ] # 这里的r是为了禁用`\\`转义符,但这里都是中文,不写也可以,为了规范所以加上了 bachelor_re = re.compile(\u0026#34;|\u0026#34;.join(bachelor_patterns)) master_re = re.compile(\u0026#34;|\u0026#34;.join(master_patterns)) # 拼接了两个匹配字符串 bachelor_posts = [] master_posts = [] for p in posts: # 这个p是列表的子元素,在这里为字典 text = p.get(\u0026#34;text\u0026#34;, \u0026#34;\u0026#34;) # get方法的第一个参数是,查找该字典中的对应字段并返回值,第二个参数是,若查找不到返回的默认值 if master_re.search(text): master_posts.append(p) elif bachelor_re.search(text): bachelor_posts.append(p) # === 输出目录 === out_dir = Path(\u0026#34;degree_split\u0026#34;) out_dir.mkdir(exist_ok=True) # === 写文件 === with open(out_dir / \u0026#34;本科及以上.json\u0026#34;, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: json.dump(bachelor_posts, f, ensure_ascii=False, indent=2) with open(out_dir / \u0026#34;硕士及以上.json\u0026#34;, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: json.dump(master_posts, f, ensure_ascii=False, indent=2) dotenv库: 读取.env文件 对于密码,API密钥这些文件,用json文件存取不够方便也不够安全,因此我们有了.env文件,样式如下:\n1 2 # github token token=\u0026#34;ghp_xxxxxxxxxxxx\u0026#34; 当我们想要读取这个.env文件中的token字段时,我们可以导入dotenv库和os库来进行简单的读取:\n1 2 3 4 5 from dotenv import load_dotenv import os load_dotenv() TOKEN = os.getenv(\u0026#34;token\u0026#34;) load_dotenv()函数会递归寻找.env文件并返回内容供os库读取,从而避免了写路径的麻烦.\npython爬虫 和机器学习一样,我第一次学习python爬虫是没有任何成果的,一开始是听说有这么个东西,就去zlib上随便下了本参考书,由于参考书是十年前的,因此使用了很多老掉牙的库和奇奇怪怪的语法,再加上当时水平有限,根本无法复现,于是就浅尝辄止了.\n但现在,我想要试着用爬虫找到合适的招聘数据用来为以后的暑期实习和秋招服务,所以又把这门技术捡起来从零开始学了.\n参考文章: 菜鸟教程以及官方文档 爬虫概念 wiki Web crawler, sometimes called a spider or spiderbot and often shortened to crawler, is an Internet bot that systematically browses the World Wide Web and that is typically operated by search engines for the purpose of Web indexing (web spidering) 实际上,只要一个自动化程序做了下列的某一件事情,就可以认定为爬虫:\n获取web资源 模拟浏览器/用户行为 批量获取数据 这几个操作基本涵盖了抢票脚本,pdf下载,训练数据爬取等一系列常见的爬虫情景.\n最常见的爬虫无疑就是搜索引擎了,这些巨无霸爬虫不间断的访问数以千万计的网站,并给数据做好归类和索引.\n爬虫历史 翻遍全网,我确实找不到一个能够好好讲讲从爬虫概念的诞生到最新爬虫框架应用的博客文章(难道谈这个是犯法吗!)\n遗憾的是,我目前没有做这方面梳理的打算,等我真正闲下来再写吧,毕竟随便一想就知道这需要大量的检索和查证.\npython爬虫工具时间线 04年: BeautifulSoup,绝对的老资历,爬虫入门书十本有十本会谈到它\n08年: Scrapy,工业级别的异步爬虫框架,也是推荐的爬虫入门库\n08年: Selenium,浏览器自动化鼻祖, 物理驱动 WebDriver 模拟真实用户操作\n12年: Requests,物理简化 HTTP 请求逻辑\n20年: Playwright,微软出品,物理支持多驱动与自动等待,现代爬虫框架\n但我不打算按照时间顺序来,而是按照难易程度来讲解😊\nrequests库学习 官方文档 质量比较高,深入浅出 是什么,怎么用 Requests is an elegant and simple HTTP library for Python, built for human beings.\n官方的宣言非常简单明了 1 2 3 4 5 6 7 8 import requests r = requests.get(\u0026#39;https://api.github.com/events\u0026#39;) r = requests.post(\u0026#39;https://httpbin.org/post\u0026#39;, data={\u0026#39;key\u0026#39;: \u0026#39;value\u0026#39;}) r = requests.put(\u0026#39;https://httpbin.org/put\u0026#39;, data={\u0026#39;key\u0026#39;: \u0026#39;value\u0026#39;}) r = requests.delete(\u0026#39;https://httpbin.org/delete\u0026#39;) r = requests.head(\u0026#39;https://httpbin.org/get\u0026#39;) r = requests.options(\u0026#39;https://httpbin.org/get\u0026#39;) 自然,当我们使用爬虫时,只需要使用get请求就足够了\n给get请求带上参数 If you were constructing the URL by hand, this data would be given as key/value pairs in the URL after a question mark, e.g. httpbin.org/get?key=val. Requests allows you to provide these arguments as a dictionary of strings, using the params keyword argument.\n1 2 3 4 5 6 7 8 9 10 11 payload = {\u0026#39;key1\u0026#39;: \u0026#39;value1\u0026#39;, \u0026#39;key2\u0026#39;: \u0026#39;value2\u0026#39;} r = requests.get(\u0026#39;https://httpbin.org/get\u0026#39;, params=payload) print(r.url) # https://httpbin.org/get?key2=value2\u0026amp;key1=value1 # another file payload = {\u0026#39;key1\u0026#39;: \u0026#39;value1\u0026#39;, \u0026#39;key2\u0026#39;: [\u0026#39;value2\u0026#39;, \u0026#39;value3\u0026#39;]} r = requests.get(\u0026#39;https://httpbin.org/get\u0026#39;, params=payload) print(r.url) # https://httpbin.org/get?key1=value1\u0026amp;key2=value2\u0026amp;key2=value3 通过在对应地址后面加参数可以实现按页数/分类爬取 给get请求设置超时时限 1 requests.get(\u0026#39;https://github.com/\u0026#39;, timeout=0.001) 当get请求用时超过timeout值时自动报错\n定制头部(Headers) If you’d like to add HTTP headers to a request, simply pass in a dict to the headers parameter.\n1 2 3 4 url = \u0026#39;https://api.github.com/some/endpoint\u0026#39; headers = {\u0026#39;user-agent\u0026#39;: \u0026#39;my-app/0.0.1\u0026#39;} r = requests.get(url, headers=headers) 处理get请求获取的内容 1 2 3 4 5 6 7 8 9 10 import requests r = requests.get(\u0026#39;https://api.github.com/events\u0026#39;) # 我们可以以多种文本形式来处理这个r对象,requests库会自动帮我们返回所需的文本形式 r.text # 纯文本形式 \u0026#39;[{\u0026#34;repository\u0026#34;:{\u0026#34;open_issues\u0026#34;:0,\u0026#34;url\u0026#34;:\u0026#34;https://github.com/... r.content # 二进制形式 b\u0026#39;[{\u0026#34;repository\u0026#34;:{\u0026#34;open_issues\u0026#34;:0,\u0026#34;url\u0026#34;:\u0026#34;https://github.com/... r.json() # json形式 [{\u0026#39;repository\u0026#39;: {\u0026#39;open_issues\u0026#39;: 0, \u0026#39;url\u0026#39;: \u0026#39;https://github.com/... 处理返回的状态码 1 2 3 r = requests.get(\u0026#39;https://httpbin.org/get\u0026#39;) r.status_code # 200 状态码在判断是否正常爬取内容的时候非常重要 处理返回的头部 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 r.headers # { # \u0026#39;content-encoding\u0026#39;: \u0026#39;gzip\u0026#39;, # \u0026#39;transfer-encoding\u0026#39;: \u0026#39;chunked\u0026#39;, # \u0026#39;connection\u0026#39;: \u0026#39;close\u0026#39;, # \u0026#39;server\u0026#39;: \u0026#39;nginx/1.0.4\u0026#39;, # \u0026#39;x-runtime\u0026#39;: \u0026#39;148ms\u0026#39;, # \u0026#39;etag\u0026#39;: \u0026#39;\u0026#34;e1ca502697e5c9317743dc078f67693f\u0026#34;\u0026#39;, # \u0026#39;content-type\u0026#39;: \u0026#39;application/json\u0026#39; # } # we can access the headers using any capitalization we want r.headers[\u0026#39;Content-Type\u0026#39;] \u0026#39;application/json\u0026#39; r.headers.get(\u0026#39;content-type\u0026#39;) \u0026#39;application/json\u0026#39; 实战 爬取仓库的issues\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 SEARCH_URL=\u0026#34;https://api.github.com/search/issues\u0026#34; # 爬虫专用的GitHub api网址 HEADERS: dict = { \u0026#34;Authorization\u0026#34;: f\u0026#34;token {TOKEN}\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;application/vnd.github+json\u0026#34;, } # 带有TOKEN认证的请求头部可以扩大爬虫的权限 # Accept字段表明希望获取json格式的数据 def fetch_issues(start, end): page = 1 issues = [] while True: r = requests.get( SEARCH_URL, headers=HEADERS, params={ \u0026#34;q\u0026#34;: f\u0026#34;repo:ruanyf/weekly 谁在招人 created:{start}..{end}\u0026#34;, # start..end 为时间范围筛选 \u0026#34;per_page\u0026#34;: 100, # 单次请求返回的数据条数,100为最大值 \u0026#34;page\u0026#34;: page, # 当前页码 \u0026#34;sort\u0026#34;: \u0026#34;created\u0026#34;, # 按照创建时间排序 \u0026#34;order\u0026#34;: \u0026#34;asc\u0026#34;, # asc-ascend(升序),另外有desc-descend(降序) }, ) data = r.json() # 用json格式读取数据 items = data.get(\u0026#34;items\u0026#34;, []) # 用get方法获取data的items键对应的列表,若不存在该键则返回空列表 if not items: break # 若为空则说明全部爬取完了 issues.extend(items) # 将新列表连接到issues列表的末尾 page += 1 # 爬取下一页 time.sleep(1) # 等一秒再请求,避免被封 return issues # 返回issues列表 items中元素的结构示例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 { \u0026#34;url\u0026#34;: \u0026#34;https://api.github.com/repos/ruanyf/weekly/issues/737\u0026#34;, \u0026#34;repository_url\u0026#34;: \u0026#34;https://api.github.com/repos/ruanyf/weekly\u0026#34;, \u0026#34;labels_url\u0026#34;: \u0026#34;https://api.github.com/repos/ruanyf/weekly/issues/737/labels{/name}\u0026#34;, \u0026#34;comments_url\u0026#34;: \u0026#34;https://api.github.com/repos/ruanyf/weekly/issues/737/comments\u0026#34;, \u0026#34;events_url\u0026#34;: \u0026#34;https://api.github.com/repos/ruanyf/weekly/issues/737/events\u0026#34;, \u0026#34;html_url\u0026#34;: \u0026#34;https://github.com/ruanyf/weekly/issues/737\u0026#34;, \u0026#34;id\u0026#34;: 474420491, \u0026#34;node_id\u0026#34;: \u0026#34;MDU6SXNzdWU0NzQ0MjA0OTE=\u0026#34;, \u0026#34;number\u0026#34;: 737, \u0026#34;title\u0026#34;: \u0026#34;谁在招人？\u0026#34;, \u0026#34;user\u0026#34;: { \u0026#34;login\u0026#34;: \u0026#34;hobo-tt\u0026#34;, \u0026#34;id\u0026#34;: 53465562, \u0026#34;node_id\u0026#34;: \u0026#34;MDQ6VXNlcjUzNDY1NTYy\u0026#34;, \u0026#34;avatar_url\u0026#34;: \u0026#34;https://avatars.githubusercontent.com/u/53465562?v=4\u0026#34;, \u0026#34;gravatar_id\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt\u0026#34;, \u0026#34;html_url\u0026#34;: \u0026#34;https://github.com/hobo-tt\u0026#34;, \u0026#34;followers_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/followers\u0026#34;, \u0026#34;following_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/following{/other_user}\u0026#34;, \u0026#34;gists_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/gists{/gist_id}\u0026#34;, \u0026#34;starred_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/starred{/owner}{/repo}\u0026#34;, \u0026#34;subscriptions_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/subscriptions\u0026#34;, \u0026#34;organizations_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/orgs\u0026#34;, \u0026#34;repos_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/repos\u0026#34;, \u0026#34;events_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/events{/privacy}\u0026#34;, \u0026#34;received_events_url\u0026#34;: \u0026#34;https://api.github.com/users/hobo-tt/received_events\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;User\u0026#34;, \u0026#34;user_view_type\u0026#34;: \u0026#34;public\u0026#34;, \u0026#34;site_admin\u0026#34;: false }, \u0026#34;labels\u0026#34;: [], \u0026#34;state\u0026#34;: \u0026#34;closed\u0026#34;, \u0026#34;locked\u0026#34;: false, \u0026#34;assignees\u0026#34;: [], \u0026#34;milestone\u0026#34;: null, \u0026#34;comments\u0026#34;: 0, \u0026#34;created_at\u0026#34;: \u0026#34;2019-07-30T07:28:48Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2019-07-30T07:29:41Z\u0026#34;, \u0026#34;closed_at\u0026#34;: \u0026#34;2019-07-30T07:29:41Z\u0026#34;, \u0026#34;assignee\u0026#34;: null, \u0026#34;author_association\u0026#34;: \u0026#34;NONE\u0026#34;, \u0026#34;active_lock_reason\u0026#34;: null, \u0026#34;sub_issues_summary\u0026#34;: { \u0026#34;total\u0026#34;: 0, \u0026#34;completed\u0026#34;: 0, \u0026#34;percent_completed\u0026#34;: 0 }, \u0026#34;issue_dependencies_summary\u0026#34;: { \u0026#34;blocked_by\u0026#34;: 0, \u0026#34;total_blocked_by\u0026#34;: 0, \u0026#34;blocking\u0026#34;: 0, \u0026#34;total_blocking\u0026#34;: 0 }, \u0026#34;body\u0026#34;: \u0026#34;北京国际音乐节文化传播有限公司\\r\\n地点：北京市朝阳区三间房南里4号院第96栋综合办公楼\\r\\n简历投递Email：[](url)yuanweitong@bmfbj.com\\r\\n\\r\\n### 前端工程师\\r\\n**岗位职责：**\\r\\n1、负责项目前端架构设计及研发工作；\\r\\n3、参与复杂业务系统技术选型，架构设计实现，新兴技术研究职责要求；\\r\\n4、负责前端界面的开发工作；\\r\\n5、根据产品和需求，依照当前技术架构进行前端开发；\\r\\n6、负责页面布局优化和调整。\\r\\n7、对接API数据、并可协调整体数据对接\\r\\n**任职条件：**\\r\\n1、本科及以上学历，三年以上web前端开发工作经验；\\r\\n2、精通HTML，CSS，了解W3C标准，能够熟练配合美工完成兼容主流浏览器的前端页面精通JavaScript，Ajax，DOM等前端技术，精通Vue前端框架；\\r\\n3、熟悉HTML5/CSS3de.js/Less/Scss等技术能持续优化前端页面的兼 容性和执行效率了解前端页面组件化；\\r\\n4、对单页WEB应用开发有极强的学习能力，对新技术有浓厚的研究兴趣；\\r\\n5、熟悉一门非JavaScript语言，如Java、Python、Ruby等\\r\\n6、熟悉小程序、VUE\\r\\n\\r\\n职位详情见[招聘网](https://www.lagou.com/jobs/6077252.html?source=pl\u0026amp;i=pl-1\u0026amp;show=b0079c35e8a042c49b24a6018cec2bac)\u0026#34;, \u0026#34;reactions\u0026#34;: { \u0026#34;url\u0026#34;: \u0026#34;https://api.github.com/repos/ruanyf/weekly/issues/737/reactions\u0026#34;, \u0026#34;total_count\u0026#34;: 0, \u0026#34;+1\u0026#34;: 0, \u0026#34;-1\u0026#34;: 0, \u0026#34;laugh\u0026#34;: 0, \u0026#34;hooray\u0026#34;: 0, \u0026#34;confused\u0026#34;: 0, \u0026#34;heart\u0026#34;: 0, \u0026#34;rocket\u0026#34;: 0, \u0026#34;eyes\u0026#34;: 0 }, \u0026#34;timeline_url\u0026#34;: \u0026#34;https://api.github.com/repos/ruanyf/weekly/issues/737/timeline\u0026#34;, \u0026#34;performed_via_github_app\u0026#34;: null, \u0026#34;state_reason\u0026#34;: \u0026#34;completed\u0026#34;, \u0026#34;pinned_comment\u0026#34;: null, \u0026#34;score\u0026#34;: 1.0 } bs4学习 官方文档 一般只用得上其中的一小部分功能,所以翻翻就好了 Beautiful Soup现在的版本为4.13.3,且第四版从12年就已经发布,故一般称为bs4.\n名字来源\nIt takes its name from the poem Beautiful Soup from Alice\u0026rsquo;s Adventures in Wonderland and is a reference to the term \u0026ldquo;tag soup\u0026rdquo; meaning poorly-structured HTML code.\n是什么,怎么用 bs4主要用于解析HTML和XML文档,但是它本身不负责解析,而是需要你配合解析库如\u0026quot;lxml\u0026quot;或者python内置的html.parser来进行解析,但是你没有单独在文件里导入,只要虚拟环境中有这个lxml库就可以了\n1 2 3 4 5 6 7 8 9 10 from bs4 import BeautifulSoup import requests # 使用 requests 获取网页内容 url = \u0026#39;https://cn.bing.com/\u0026#39; # 抓取bing搜索引擎的网页内容 response = requests.get(url) # 使用 BeautifulSoup 解析网页 soup = BeautifulSoup(response.text, \u0026#39;lxml\u0026#39;) # 使用 lxml 解析器 # 解析网页内容 html.parser 解析器 # soup = BeautifulSoup(response.text, \u0026#39;html.parser\u0026#39;) find与find_all 基础用法\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from bs4 import BeautifulSoup import requests # 指定你想要获取标题的网站 url = \u0026#39;https://www.baidu.com/\u0026#39; # 抓取bing搜索引擎的网页内容 # 发送HTTP请求获取网页内容 response = requests.get(url) soup = BeautifulSoup(response.text, \u0026#39;lxml\u0026#39;) # 查找第一个 \u0026lt;a\u0026gt; 标签 first_link = soup.find(\u0026#39;a\u0026#39;) print(first_link) print(\u0026#34;----------------------------\u0026#34;) # 获取第一个 \u0026lt;a\u0026gt; 标签的 href 属性 first_link_url = first_link.get(\u0026#39;href\u0026#39;) print(first_link_url) print(\u0026#34;----------------------------\u0026#34;) # 查找所有 \u0026lt;a\u0026gt; 标签 all_links = soup.find_all(\u0026#39;a\u0026#39;) print(all_links) # 获取第一个 \u0026lt;p\u0026gt; 标签中的文本内容 paragraph_text = soup.find(\u0026#34;p\u0026#34;).get_text() # 获取页面中所有文本内容 all_text = soup.get_text() print(all_text) 加入标签或属性\n1 2 3 4 5 # 查找所有 class=\u0026#34;example-class\u0026#34; 的 \u0026lt;div\u0026gt; 标签 divs_with_class = soup.find_all(\u0026#39;div\u0026#39;, class_=\u0026#39;example-class\u0026#39;) # 查找具有 id=\u0026#34;unique-id\u0026#34; 的 \u0026lt;p\u0026gt; 标签 unique_paragraph = soup.find(\u0026#39;p\u0026#39;, id=\u0026#39;unique-id\u0026#39;) 非常显然的是,bs4仅支持获取静态网页内容,在现在很多网页都是用js渲染的情况下不太实用了,但作为新手入门库还是很不错的,可以一下子感受到爬虫的威力,没有任何学习难度.\nbs4源码概览 如果你闲的蛋疼,可以像我一样下载bs4的源码:\n1 git clone https://git.launchpad.net/beautifulsoup 事实上,当我们翻阅源码时,会惊讶的发现这个有着20年悠久历史的python库竟然只有这么一点文件!\n而且还能看到dammit.py这么一个神奇的名字 小结 结合requests(,bs4)和支持读写文件的库,我们现在基本可以爬取所有的静态网页资源,并在处理后进行存储了.\nSelenium学习 官方教程 非常遗憾的是,官方的文档写的很烂 geeksforgeeks 翻来翻去能找到的唯一质量比较好的教程,反过来说明selenium本身的用户生态太差了 是什么,怎么用 Selenium 通过使用 WebDriver 支持市场上所有主流浏览器的自动化。 WebDriver 是一个 API 和协议，它定义了一个语言中立的接口，用于控制 web 浏览器的行为。 每个浏览器都有一个特定的 WebDriver 实现，称为驱动程序。 驱动程序是负责委派给浏览器的组件，并处理与 Selenium 和浏览器之间的通信。\n换句话说,没有WebDriver就用不了selenium,所以我们需要下载自己浏览器版本对应的驱动器.\n当然,去官方慢慢翻驱动器版本还是太琐碎了,现在的selenium支持自动下载对应的驱动器,就没必要如过时的教程所说去配置驱动器的环境变量了.\n1 2 3 4 5 from selenium import webdriver driver = webdriver.Chrome() driver.get(\u0026#34;https://www.google.com\u0026#34;) # 只写这三行代码也会自动下载对应的驱动器到本地缓存目录中 运行上方代码,我们成功用chrome打开了google网站\n至于为什么会自动退出,是因为我们后面没有其他代码了,webdriver会自动关闭 如果你还是没明白selenium的用法的话,你可以想一下,如果有一个实验要你去找一百个人填问卷,你可以将问卷设定为允许多次填写,安排一些合理的选择题,就可以很轻松的用selenium模拟真实用户登录问卷星网站,用预先设定的随机值去逐个填写问卷,这样一下来,就算要填一千份你一个小时也能搞定了.\n注意!这是严重的学术不端行为!请大家千万不要模仿! 可能的vpn问题 我用的clash设定了系统代理,在终端配置了代理端口,还在bypass列表里设定了排除系统端口,但selenium还是会被拦截\u0026hellip;\n我AI的解决方案\n1 2 3 4 5 import os os.environ[\u0026#34;HTTP_PROXY\u0026#34;] = \u0026#34;\u0026#34; os.environ[\u0026#34;HTTPS_PROXY\u0026#34;] = \u0026#34;\u0026#34; os.environ[\u0026#34;no_proxy\u0026#34;] = \u0026#34;localhost,127.0.0.1\u0026#34; # 在代码上方加上这个,但更推荐单独放入一个文件后再导入 基础用法一览 进入网页并输入\n注意用chrome的话容易被拦截,反正我被拦截了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # Launch browser and open Google drv = webdriver.Chrome() drv.get(\u0026#34;https://www.google.com//\u0026#34;) # Search \u0026#34;GeeksforGeeks\u0026#34; box = drv.find_element(By.NAME, \u0026#34;q\u0026#34;) # 寻找第一个name=\u0026#34;q\u0026#34;的元素,也就是搜索框 box.send_keys(\u0026#34;GeeksforGeeks\u0026#34;, Keys.RETURN) # Keys.RETURN模拟回车键 # Wait and close browser time.sleep(5) drv.quit() find_element与find_elements方法 该方法的第一个参数为要查找的属性名,第二个参数为属性值\n1 2 3 4 5 6 element = driver.find_element(By.ID, \u0026#34;passwd-id\u0026#34;) element = driver.find_element(By.NAME, \u0026#34;passwd\u0026#34;) element = driver.find_element(By.XPATH, \u0026#34;//input[@id=\u0026#39;passwd-id\u0026#39;]\u0026#34;) # If you need to find multiple elements, use: elements = driver.find_elements(By.NAME, \u0026#34;passwd\u0026#34;) 模拟键盘交互 1 2 3 4 5 6 7 8 9 # If you want to input text into a field, you can use: element.send_keys(\u0026#34;some text\u0026#34;) # You can also simulate pressing arrow keys or other keys using the Keys class: element.send_keys(\u0026#34; and some\u0026#34;, Keys.ARROW_DOWN) # To clear the contents of a text field or textarea, use the clear method: element.clear() 实战 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 # Import the necessary modules from Selenium from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys # Added import for Keys from selenium.webdriver.support.ui import WebDriverWait # To wait for elements from selenium.webdriver.support import ( expected_conditions as EC, ) # For expected conditions import time def test(): # you can choose other browsers like Chrome, Firefox, etc. driver = webdriver.Edge() # Navigate to the GeeksforGeeks website driver.get(\u0026#34;https://www.geeksforgeeks.org/\u0026#34;) # Maximize the browser window driver.maximize_window() # Wait for 3 seconds to ensure the page is loaded time.sleep(3) # Handle iframe if one exists (e.g., an overlay) iframe_element = driver.find_element( By.XPATH, \u0026#34;//iframe[contains(@src,\u0026#39;accounts.google.com\u0026#39;)]\u0026#34; ) driver.switch_to.frame(iframe_element) # Close the overlay (e.g., Google sign-in iframe) closeele = driver.find_element(By.XPATH, \u0026#34;//*[@id=\u0026#39;close\u0026#39;]\u0026#34;) closeele.click() # Wait for the iframe action to complete time.sleep(3) # Switch back to the main content driver.switch_to.default_content() # Locate the search icon element using XPath searchIcon = driver.find_element(By.XPATH, \u0026#34;//span[@class=\u0026#39;flexR gs-toggle-icon\u0026#39;]\u0026#34;) # Wait for 3 seconds before interacting with the search input time.sleep(3) # Locate the input field for search text using XPath enterText = driver.find_element(By.XPATH, \u0026#34;//input[@class=\u0026#39;gs-input\u0026#39;]\u0026#34;) # Enter the search query \u0026#34;Data Structure\u0026#34; into the input field enterText.send_keys(\u0026#34;Data Structure\u0026#34;) # Send the RETURN key to submit the search query enterText.send_keys(Keys.RETURN) 真\u0026raquo;实战 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Options import time import random wjx_url = \u0026#34;\u0026#34; # wjx问卷网址 def generate_one_response(): return { \u0026#34;div1\u0026#34;: random.choice([\u0026#34;男\u0026#34;, \u0026#34;女\u0026#34;]), \u0026#34;div2\u0026#34;: random.choice( [\u0026#34;理工类\u0026#34;, \u0026#34;文史哲类\u0026#34;, \u0026#34;社会科学类（法学、管理学等）\u0026#34;, \u0026#34;艺术类\u0026#34;, \u0026#34;其他\u0026#34;] ), \u0026#34;div3\u0026#34;: random.choice([\u0026#34;非常重要\u0026#34;, \u0026#34;重要\u0026#34;, \u0026#34;一般\u0026#34;, \u0026#34;不重要\u0026#34;]), \u0026#34;div4\u0026#34;: random.choice( [\u0026#34;深入学习过\u0026#34;, \u0026#34;了解过部分内容\u0026#34;, \u0026#34;听说过但不了解\u0026#34;, \u0026#34;完全不了解\u0026#34;] ), \u0026#34;div5\u0026#34;: random.choice([\u0026#34;是，应该加强\u0026#34;, \u0026#34;不需要，当前已足够\u0026#34;, \u0026#34;无所谓\u0026#34;]), \u0026#34;div6\u0026#34;: random.choice([\u0026#34;总是\u0026#34;, \u0026#34;经常\u0026#34;, \u0026#34;偶尔\u0026#34;, \u0026#34;很少\u0026#34;]), \u0026#34;div7\u0026#34;: random.choice( [ \u0026#34;先转发再说\u0026#34;, \u0026#34;只分享来自官方渠道的信息\u0026#34;, \u0026#34;自己查证后再决定是否转发\u0026#34;, \u0026#34;看到也不管，不会转发\u0026#34;, ] ), \u0026#34;div8\u0026#34;: random.choice([\u0026#34;从不\u0026#34;, \u0026#34;偶尔\u0026#34;, \u0026#34;经常\u0026#34;]), \u0026#34;div9\u0026#34;: random.choice([\u0026#34;从不\u0026#34;, \u0026#34;偶尔\u0026#34;, \u0026#34;经常\u0026#34;]), \u0026#34;div10\u0026#34;: random.choice([\u0026#34;经常\u0026#34;, \u0026#34;偶尔\u0026#34;, \u0026#34;很少\u0026#34;, \u0026#34;从不\u0026#34;]), \u0026#34;div11\u0026#34;: random.choice([\u0026#34;是，经常\u0026#34;, \u0026#34;偶尔\u0026#34;, \u0026#34;有过一次\u0026#34;, \u0026#34;没有遇到过\u0026#34;]), \u0026#34;div12\u0026#34;: random.sample( [ \u0026#34;网络暴力（如恶意攻击、辱骂）\u0026#34;, \u0026#34;网络诈骗（如假兼职、中奖信息）\u0026#34;, \u0026#34;虚假信息或网络谣言\u0026#34;, \u0026#34;侵犯隐私（如曝光个人信息）\u0026#34;, \u0026#34;不良言论或低俗内容\u0026#34;, \u0026#34;网络沉迷（如过度使用短视频/游戏）\u0026#34;, ], random.randint(2, 4), ), } def fill_and_submit(): # options.add_argument(\u0026#34;--headless\u0026#34;) # 可取消注释用于无头运行 driver = webdriver.Chrome() driver.get(wjx_url) time.sleep(2) # 点击“开始作答”按钮 try: start_button = driver.find_element(By.CLASS_NAME, \u0026#34;startbtn\u0026#34;) start_button.click() print(\u0026#34;已点击开始作答按钮\u0026#34;) except Exception as e: print(\u0026#34;未找到开始按钮，可能已跳转页面\u0026#34;) time.sleep(3) # 等待问卷加载 answers = generate_one_response() for div_id, value in answers.items(): try: div = driver.find_element(By.ID, div_id) if isinstance(value, list): for val in value: labels = div.find_elements(By.CLASS_NAME, \u0026#34;label\u0026#34;) for label in labels: if val in label.text: label.click() break else: labels = div.find_elements(By.CLASS_NAME, \u0026#34;label\u0026#34;) for label in labels: if value in label.text: label.click() break except Exception as e: print(f\u0026#34;{div_id} 填写失败: {e}\u0026#34;) time.sleep(0.3) # 点击提交按钮 try: submit_btn = driver.find_element(By.ID, \u0026#34;ctlNext\u0026#34;) submit_btn.click() print(\u0026#34;问卷提交成功！\u0026#34;) except Exception as e: print(f\u0026#34;提交失败: {e}\u0026#34;) time.sleep(2) driver.quit() fill_and_submit() # 我们可以加上一个while循环... 整个代码并没有任何难懂的地方,只需要我们亲自去查html元素对应的名字就可以实现自动化答题了 PlayWright学习 官方文档 看似内容很多,其实有用的东西很少\u0026hellip; 是什么,怎么用 Playwright was created specifically to accommodate the needs of end-to-end testing. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox. Test on Windows, Linux, and macOS, locally or on CI, headless or headed with native mobile emulation.\n这段文字把playwright介绍为一个测试工具,乍一看与爬虫没有任何关系,但黑体部分不正是selenium支持的功能吗,那它自然也可以实现selenium的爬虫功能了.\nplaywright的历史并不很长,最开始是以js版本推出的,不知为何又引入到了python里,并制作了两个python库,一个是我们常用的playwright库,还有一个是pytest-playwright,试图取代常规的pytest库,并在官方文档里反复提及\u0026hellip;,在我看来完全没有必要.\n以下代码是一个playwright功能展示的简单示例,光是学习的话,我们可以使用自己电脑里的浏览器内核,不必像官网或者其他博客所说先运行playwright install命令,那会默认在全局安装多种浏览器内核,占用体积还不小😇.\n由于playwright与selenium不同,不能在代码中默认安装,而是需要提前下载好驱动.因此在生产环境下还是得老老实实装的,当然也只要装自己所需的那一款内核就行了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch( executable_path=\u0026#34;C:/Program Files/Google/Chrome/Application/chrome.exe\u0026#34;, headless=True, ) # 如果chrome.exe路径不对你就改成自己的路径 page = browser.new_page() page.goto(\u0026#34;https://playwright.dev\u0026#34;) print(page.title()) page.goto(\u0026#34;https://google.com\u0026#34;) print(page.title()) browser.close() 基本语法 同步/异步API 参考 看上图就知道playwright中有两个主要的模块:sync_api和async_api,分别对应着同步和异步的请求,我们先来看同步请求的用法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from playwright.sync_api import sync_playwright def testcase1(): print(\u0026#34;testcase1 start\u0026#34;) with sync_playwright() as p: browser = p.chromium.launch( executable_path=\u0026#34;C:/Program Files/Google/Chrome/Application/chrome.exe\u0026#34;, headless=False, ) page = browser.new_page() page.goto(\u0026#34;https://www.baidu.com/\u0026#34;) print(page.title()) page.fill(\u0026#34;#chat-textarea\u0026#34;, \u0026#34;test\u0026#34;) # 文本框输入test page.click(\u0026#34;#chat-submit-button\u0026#34;) # 提交 browser.close() print(\u0026#34;testcase1 done\u0026#34;) def testcase2(): print(\u0026#34;testcase2 start\u0026#34;) with sync_playwright() as p: browser2 = p.chromium.launch( executable_path=\u0026#34;C:/Program Files/Google/Chrome/Application/chrome.exe\u0026#34;, headless=False, ) page2 = browser2.new_page() page2.goto(\u0026#34;https://www.sogou.com/\u0026#34;) print(page2.title()) page2.fill(\u0026#39;input[name=\u0026#34;query\u0026#34;]\u0026#39;, \u0026#34;test\u0026#34;) page2.click(\u0026#34;text=搜索\u0026#34;) browser2.close() print(\u0026#34;testcase2 done\u0026#34;) 显然,同步的用法和selenium几乎没有差别,那我们再来看看异步:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import asyncio from playwright.async_api import async_playwright async def testcase1(): print(\u0026#34;testcase1 start\u0026#34;) async with async_playwright() as p: browser = await p.chromium.launch( executable_path=\u0026#34;C:/Program Files/Google/Chrome/Application/chrome.exe\u0026#34;, headless=False, ) page = await browser.new_page() await page.goto(\u0026#34;https://www.baidu.com/\u0026#34;) print(await page.title()) await page.fill(\u0026#34;#chat-textarea\u0026#34;, \u0026#34;test\u0026#34;) # 文本框输入test await page.click(\u0026#34;#chat-submit-button\u0026#34;) # 提交 await browser.close() print(\u0026#34;testcase1 done\u0026#34;) async def testcase2(): print(\u0026#34;testcase2 start\u0026#34;) async with async_playwright() as p: browser2 = await p.chromium.launch( executable_path=\u0026#34;C:/Program Files/Google/Chrome/Application/chrome.exe\u0026#34;, headless=False, ) page2 = await browser2.new_page() await page2.goto(\u0026#34;https://www.sogou.com/\u0026#34;) print(await page2.title()) await page2.fill(\u0026#39;input[name=\u0026#34;query\u0026#34;]\u0026#39;, \u0026#34;test\u0026#34;) await page2.click(\u0026#34;text=搜索\u0026#34;) await browser2.close() print(\u0026#34;testcase2 done\u0026#34;) async def main(): await testcase2() await testcase1() if __name__ == \u0026#34;__main__\u0026#34;: asyncio.run(main()) 其实异步版本只是给关键的函数调用加上了异步的修饰而已,但这样就可以显著提升爬取速度了;而selenium并不支持异步爬取,因此逐渐被playwright取代.\nScrapy学习 官方文档 比较详尽 是什么,怎么用 scrapy是一个一体化爬虫框架,request的上位替代,支持异步处理和终端交互\ntest.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import scrapy class QuotesSpider(scrapy.Spider): name = \u0026#34;quotes\u0026#34; start_urls = [ \u0026#34;https://quotes.toscrape.com/tag/humor/\u0026#34;, ] def parse(self, response): for quote in response.css(\u0026#34;div.quote\u0026#34;): yield { \u0026#34;author\u0026#34;: quote.xpath(\u0026#34;span/small/text()\u0026#34;).get(), \u0026#34;text\u0026#34;: quote.css(\u0026#34;span.text::text\u0026#34;).get(), } next_page = response.css(\u0026#39;li.next a::attr(\u0026#34;href\u0026#34;)\u0026#39;).get() if next_page is not None: yield response.follow(next_page, self.parse) 运行方式: scrapy runspider test.py -o quotes.jsonl 运行之后将得到这样这样的内容:\n1 2 3 4 {\u0026#34;author\u0026#34;: \u0026#34;Jane Austen\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;\\u201cThe person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.\\u201d\u0026#34;} {\u0026#34;author\u0026#34;: \u0026#34;Steve Martin\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;\\u201cA day without sunshine is like, you know, night.\\u201d\u0026#34;} {\u0026#34;author\u0026#34;: \u0026#34;Garrison Keillor\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;\\u201cAnyone who thinks sitting in church can make you a Christian must also think that sitting in a garage can make you a car.\\u201d\u0026#34;} // ... 可以看出来,scrapy通过类来封装爬虫(这类似于pytest中对测试的封装),并且不需要我们再进行额外的库导入,而是在后台包办一切.\nscrapy命令行 虽然可以修改，但默认情况下所有 Scrapy 项目都具有相同的文件结构，类似于：\n1 2 3 4 5 6 7 8 9 10 11 12 scrapy.cfg tutorial/ __init__.py items.py middlewares.py pipelines.py settings.py spiders/ __init__.py spider1.py spider2.py ... scrapy.cfg 文件所在的目录被称为 项目根目录。该文件包含定义项目设置的 Python 模块名称。示例如下：\n1 2 3 4 5 [settings] default = tutorial.settings [deploy] project = tutorial 创建和运行项目 1 scrapy startproject myproject [project_dir] 这将在当前目录下创建一个project_dir文件夹,里面有一个Scrapy 项目,名字为myproject.\n如果未指定 project_dir，project_dir的名字将与 myproject 相同 命令行参数一览 全局命令\nstartproject: 创建项目 genspider: 在当前项目的spiders文件夹中创建一个新爬虫文件 示例: uv run scrapy genspider hello_world www.bing.com将在文件夹中创建hello_world.py文件. settings: 过 runspider: 不创建项目直接运行python文件中的独立爬虫 shell: 过 fetch: 使用scrapy下载器访问给定的网页 view: 过 version: 过 仅限项目的命令\ncrawl: 运行某一个爬虫文件 check: 自动化测试 list: 列出该项目中可用的爬虫文件 edit: 编辑某个爬虫文件 parse: 过 bench: 过 显然,大多数命令都没什么用,还是需要自己去写爬虫文件和测试.\nSpider类 基本调用方法\n1 2 3 4 5 6 7 8 import scrapy class HelloWorldSpider(scrapy.Spider): name = \u0026#34;hello_world\u0026#34; allowed_domains = [\u0026#34;www.bing.com\u0026#34;] start_urls = [\u0026#34;https://www.bing.com\u0026#34;] def parse(self, response): pass Spider源码剖析 1 2 3 4 5 6 \u0026#34;\u0026#34;\u0026#34;Base class that any spider must subclass. It provides a default :meth:`start` implementation that sends requests based on the :attr:`start_urls` class attribute and calls the :meth:`parse` method for each response. \u0026#34;\u0026#34;\u0026#34; 上述代码为Spider类的注释,也就是说这个类会自动调用start()方法并爬取目标网址后使用parse()方法解析. 让我们看看具体的实现:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class Spider(object_ref): # 三个默认参数 name: str custom_settings: dict[_SettingsKey, Any] | None = None #: Start URLs. See :meth:`start`. start_urls: list[str] # 实例化三个参数 def __init__(self, name: str | None = None, **kwargs: Any): if name is not None: self.name: str = name elif not getattr(self, \u0026#34;name\u0026#34;, None): raise ValueError(f\u0026#34;{type(self).__name__} must have a name\u0026#34;) self.__dict__.update(kwargs) if not hasattr(self, \u0026#34;start_urls\u0026#34;): self.start_urls: list[str] = [] # 启用调试函数log @property def logger(self) -\u0026gt; SpiderLoggerAdapter: # circular import from scrapy.utils.log import SpiderLoggerAdapter # noqa: PLC0415 logger = logging.getLogger(self.name) return SpiderLoggerAdapter(logger, {\u0026#34;spider\u0026#34;: self}) def log(self, message: Any, level: int = logging.DEBUG, **kw: Any) -\u0026gt; None: self.logger.log(level, message, **kw) # 初始化爬虫 @classmethod def from_crawler(cls, crawler: Crawler, *args: Any, **kwargs: Any) -\u0026gt; Self: spider = cls(*args, **kwargs) spider._set_crawler(crawler) return spider def _set_crawler(self, crawler: Crawler) -\u0026gt; None: self.crawler: Crawler = crawler self.settings: BaseSettings = crawler.settings crawler.signals.connect(self.close, signals.spider_closed) # 核心函数start,使用start_requests()函数 async def start(self) -\u0026gt; AsyncIterator[Any]: with warnings.catch_warnings(): warnings.filterwarnings( \u0026#34;ignore\u0026#34;, category=ScrapyDeprecationWarning, module=r\u0026#34;^scrapy\\.spiders$\u0026#34; ) for item_or_request in self.start_requests(): yield item_or_request # 默认会使用start_urls变量进行爬取 def start_requests(self) -\u0026gt; Iterable[Any]: warnings.warn( ( \u0026#34;The Spider.start_requests() method is deprecated, use \u0026#34; \u0026#34;Spider.start() instead. If you are calling \u0026#34; \u0026#34;super().start_requests() from a Spider.start() override, \u0026#34; \u0026#34;iterate super().start() instead.\u0026#34; ), ScrapyDeprecationWarning, stacklevel=2, ) if not self.start_urls and hasattr(self, \u0026#34;start_url\u0026#34;): raise AttributeError( \u0026#34;Crawling could not start: \u0026#39;start_urls\u0026#39; not found \u0026#34; \u0026#34;or empty (but found \u0026#39;start_url\u0026#39; attribute instead, \u0026#34; \u0026#34;did you miss an \u0026#39;s\u0026#39;?)\u0026#34; ) # 这里的Request并非是request库 for url in self.start_urls: yield Request(url, dont_filter=True) def _parse(self, response: Response, **kwargs: Any) -\u0026gt; Any: return self.parse(response, **kwargs) # 省略其他部分代码 看了源码就可以知道,start,parse两个方法并不会自动调用,换句话说,使用scrapy命令行的时候,其内部是通过调用了这两个方法来进行爬取的.\nPython网络通信 尽管大家都说python无法很好的处理高并发,但在绝大多数网络通信环境下,它都比其他语言好用的多\n前置概念: WSGI与ASGI ASGI doc wiki WSGI详解 WSGI的全称为Web Server Gateway Interface,是python官方提出的python后端通信接口约定,换句话说,它与Restful Web API一样,是一个社区普遍遵循的规范,而不是一个具体到代码怎么写的条例.\nPEP 333: Python 目前拥有种类繁多的 Web 应用框架，例如 Zope、Quixote、Webware、SkunkWeb、PSO 和 Twisted Web 等等。如此之多的选择对 Python 新手来说可能是一个问题，因为一般来说，他们选择的 Web 框架会限制他们可用的 Web 服务器的选择，反之亦然……相比之下，尽管 Java 也拥有同样多的 Web 应用框架，但 Java 的“Servlet”API 使得使用任何 Java Web 应用框架编写的应用程序都可以在任何支持 Servlet API 的 Web 服务器上运行。\nWSGI does not specify how the Python interpreter should be started, nor how the application object should be loaded or configured, and different frameworks and webservers achieve this in different ways.\nWSGI结构 WSGI由两部分组成:\n服务器/网关: 例如nignx/Apache等反向代理服务器 python应用程序: 如fastapi/flask等python框架 在服务器和应用程序之间,还可以加入WSGI中间件(middleware),它同样也是一个python应用程序,具有以下功能:\n将不同的请求导向对应的URL路径 均衡负载 加工处理网络请求的内容 简单示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 import time # ========================================== # 1. WSGI 应用程序 (Application) # 核心逻辑：接收 environ 字典，返回迭代器 # ========================================== def simple_app(environ, start_response): \u0026#34;\u0026#34;\u0026#34;一个符合 PEP 3333 规范的原始 WSGI 应用\u0026#34;\u0026#34;\u0026#34; status = \u0026#39;200 OK\u0026#39; # 响应头必须是包含元组的列表 headers = [(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/plain; charset=utf-8\u0026#39;)] start_response(status, headers) # 路径分发逻辑 path = environ.get(\u0026#39;PATH_INFO\u0026#39;, \u0026#39;/\u0026#39;) response_body = f\u0026#34;Hello WSGI! You requested: {path}\\n\u0026#34; # 必须返回字节流的可迭代对象 return [response_body.encode(\u0026#39;utf-8\u0026#39;)] # ========================================== # 2. WSGI 中间件 (Middleware) # 核心逻辑：同时扮演 Server 和 App 的角色 # 它拦截请求，处理后传递给下游 App # ========================================== class ExecutionTimeMiddleware: \u0026#34;\u0026#34;\u0026#34;计算响应耗时的中间件\u0026#34;\u0026#34;\u0026#34; def __init__(self, app): self.app = app def __call__(self, environ, start_response): start_tick = time.time() # 包装原有的 start_response 来获取执行时机 def wrapped_start_response(status, headers, exc_info=None): duration = time.time() - start_tick headers.append((\u0026#39;X-Execution-Time\u0026#39;, f\u0026#34;{duration:.4f}s\u0026#34;)) return start_response(status, headers, exc_info) # 调用下游应用 return self.app(environ, wrapped_start_response) # ========================================== # 3. 运行环境配置 (Infrastructure) # 结构：Gunicorn(WSGI Server) -\u0026gt; Middleware -\u0026gt; App # ========================================== # 组装中间件链 app = ExecutionTimeMiddleware(simple_app) \u0026#34;\u0026#34;\u0026#34; 4. 反向代理 (Reverse Proxy) 配置示例 (Nginx) 物理机制：Nginx 并不直接运行 Python 代码，它通过 TCP/Unix Socket 将请求转发给 WSGI Server。 nginx.conf 片段: server { listen 80; server_name example.com; location / { # 转发请求给 Gunicorn 绑定的端口 proxy_pass http://127.0.0.1:8000; # 传递原始请求信息，WSGI 服务器会将这些填入 environ 字典 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } \u0026#34;\u0026#34;\u0026#34; # 5. 如何启动 (以 Gunicorn 为例) # 在终端执行: gunicorn -w 4 module_name:app ASGI详解 ASGI的全称为Asynchronous Server Gateway Interface,在WSGI的基础上加入了对异步的支持.ASGI协议的最新版本为3.0,于2019年修订.\n为什么要引入ASGI The WSGI specification has worked well since it was introduced, and allowed for great flexibility in Python framework and web server choice. However, its design is irrevocably tied to the HTTP-style request/response cycle, and more and more protocols that do not follow this pattern are becoming a standard part of web programming (most notably, WebSocket).\nPython数据处理 Numpy库 Matplotlib库 ","date":"2026-04-25T09:56:06+08:00","image":"/p/python%E7%AC%94%E8%AE%B0/12904418_p0-%E5%A4%8F%E3%81%AE%E6%97%A5%E3%81%AE%E6%98%BC%E4%B8%8B%E3%81%8C%E3%82%8A.webp","permalink":"/p/python%E7%AC%94%E8%AE%B0/","title":"python笔记"},{"content":"数据结构 栈 队列 哈希表 基础算法 复杂度分析 二分大法 二分法本身的思想不难,最难的是写\u0026lt;=还是\u0026lt;的时候. 事实上,二分法总共只有两种写法,一个是左闭右闭,一个是左闭右开,但如果两种方法混着用,很容易就记混了,现场推导或许也可以,但总归是要想一下子的. 故我认为只使用下面一种逻辑算法就行了,因为两端均为闭区间最符合正常人直觉.\n1 2 3 4 5 6 7 8 while (l \u0026lt;= r) { int mid = (l + r) / 2; res = findsum(mid); if (res \u0026gt;= k) l = mid + 1; else r = mid - 1; } 排序 冒泡排序 搜索 事实上,翻遍全网,找不到一个真正详尽的入门教程,基本都是丢给你几道算法题的解答就结束了,却从来没有真正的讲明白为什么要这样写\n到底是用dfs还是bfs?(3/14) 参考文章 问ai或者上网查,很容易就知道bfs用来求最短路径,而dfs用来求路径条数,那么,为什么是这样呢? 先概览一下代码: dfs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; const int MAX = 105; int n,m; char g[MAX][MAX]; int vis[MAX][MAX]; int dx[4]={-1,1,0,0}; int dy[4]={0,0,-1,1}; void dfs(int x,int y) { vis[x][y]=1; for(int i=0;i\u0026lt;4;i++) { int nx=x+dx[i]; int ny=y+dy[i]; if(nx\u0026lt;1||nx\u0026gt;n||ny\u0026lt;1||ny\u0026gt;m) continue; if(vis[nx][ny]) continue; if(g[nx][ny]==\u0026#39;#\u0026#39;) continue; dfs(nx,ny); } } int main() { int sx,sy; cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;m; for(int i=1;i\u0026lt;=n;i++) for(int j=1;j\u0026lt;=m;j++) { cin\u0026gt;\u0026gt;g[i][j]; if(g[i][j]==\u0026#39;S\u0026#39;) sx=i,sy=j; } dfs(sx,sy); } 可以看到dfs使用的是层层递归的方式,会一条路走到底,直到没有路可走或者找到答案才返回上一级,处理完后再返回上一级. 换句话说也就是先进后出,后来新出现的路径优先处理,这也是栈的工作原理. 所以,dfs的数据结构注定了它不能处理太大深度的复杂搜索,否则栈就很容易溢出. 比如下面这题:\n1 2 3 4 5 6 7 乔治有一些同样长的小木棍，他把这些木棍随意砍成几段，直到每段的长都不超过 50。 现在，他想把小木棍拼接成原来的样子，但是却忘记了自己开始时有多少根木棍和它们的长度。 给出每段小木棍的长度，编程帮他找出原始木棍的最小可能长度。 对于全部测试点，1≤n≤65，1≤a[i]≤50 bfs\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; const int MAX = 105; int n,m; char g[MAX][MAX]; int vis[MAX][MAX]; int dx[4]={-1,1,0,0}; int dy[4]={0,0,-1,1}; struct Node{ int x; int y; int step; }; int bfs(int sx,int sy) { queue\u0026lt;Node\u0026gt; q; q.push({sx,sy,0}); vis[sx][sy]=1; while(!q.empty()) { Node cur=q.front(); q.pop(); int x=cur.x; int y=cur.y; int step=cur.step; if(g[x][y]==\u0026#39;E\u0026#39;) //终点 return step; for(int i=0;i\u0026lt;4;i++) { int nx=x+dx[i]; int ny=y+dy[i]; if(nx\u0026lt;1||nx\u0026gt;n||ny\u0026lt;1||ny\u0026gt;m) continue; if(vis[nx][ny]) continue; if(g[nx][ny]==\u0026#39;#\u0026#39;) //墙 continue; vis[nx][ny]=1; q.push({nx,ny,step+1}); } } return -1; } int main() { int sx,sy; cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;m; for(int i=1;i\u0026lt;=n;i++) for(int j=1;j\u0026lt;=m;j++) { cin\u0026gt;\u0026gt;g[i][j]; if(g[i][j]==\u0026#39;S\u0026#39;) sx=i,sy=j; } cout\u0026lt;\u0026lt;bfs(sx,sy); } 最值得关注的便是bfs使用的是队列来存储要处理的节点,而队列的特点便是先进先出,如果遍历四个方向,那么bfs会依次处理这四个方向之后,再处理下一层的节点,这样依次扩展,直到找到终点. 那么bfs之所以能够找到最短路的原因就很明显了,如果当前层找不到终点,说明终点在下一层,如果找到了终点,说明当前路径就是最好的结果,不用继续找了.\n事实上,下面才是更为常见的写法,由于OI选手一般能不写函数就不写,所以刚入门时看到这样的代码是比较难理解bfs的.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int main() { const int MAX=105; int n,m; char g[MAX][MAX]; int vis[MAX][MAX]={0}; int dx[4]={-1,1,0,0}; int dy[4]={0,0,-1,1}; cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;m; int sx,sy; for(int i=1;i\u0026lt;=n;i++) for(int j=1;j\u0026lt;=m;j++) { cin\u0026gt;\u0026gt;g[i][j]; if(g[i][j]==\u0026#39;S\u0026#39;) sx=i,sy=j; } struct Node{ int x,y,step; }; queue\u0026lt;Node\u0026gt; q; q.push({sx,sy,0}); vis[sx][sy]=1; while(!q.empty()) { Node cur=q.front(); q.pop(); int x=cur.x; int y=cur.y; int step=cur.step; if(g[x][y]==\u0026#39;E\u0026#39;) { cout\u0026lt;\u0026lt;step; break; } for(int i=0;i\u0026lt;4;i++) { int nx=x+dx[i]; int ny=y+dy[i]; if(nx\u0026lt;1||nx\u0026gt;n||ny\u0026lt;1||ny\u0026gt;m) continue; if(vis[nx][ny]) continue; if(g[nx][ny]==\u0026#39;#\u0026#39;) continue; vis[nx][ny]=1; q.push({nx,ny,step+1}); } } } 为什么要写vis数组来标记访问路径? {% raw %}\n\u0026lt;style\u0026gt;\r/* 针对 Butterfly 的局部隔离样式 */\r#dfs-root button { cursor: pointer; padding: 6px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; margin: 4px; border: 1px solid #e2e8f0; transition: all 0.2s; background: #fff; color: #475569;\r}\r#dfs-root button:hover { border-color: #3b82f6; color: #3b82f6; }\r#dfs-root .active-btn { background: #3b82f6 !important; color: #fff !important; border-color: #2563eb !important; }\r#dfs-root .grid-container { display: grid; grid-template-columns: repeat(3, minmax(50px, 70px)); gap: 8px; background-color: #f1f5f9; padding: 10px; border-radius: 8px; width: fit-content; margin: 20px auto;\r}\r#dfs-root .cell { aspect-ratio: 1/1; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: bold; border-radius: 4px; border: 1px solid #e2e8f0;\rbackground: #fff; transition: background 0.1s; }\r#dfs-root .v { background-color: #cbd5e1; color: #64748b; } /* visited */\r#dfs-root .c { background-color: #3b82f6; color: #fff; transform: scale(0.95); } /* current */\r#dfs-root .s { outline: 3px solid #22c55e; outline-offset: -2px; } /* start */\r#dfs-root .e { outline: 3px solid #ef4444; outline-offset: -2px; } /* end */\r#dfs-root .console { background: #f8fafc; padding: 12px; border-radius: 6px; font-family: 'Fira Code', monospace;\rfont-size: 13px; border-left: 4px solid #3b82f6; min-height: 50px;\r}\r\u0026lt;/style\u0026gt;\r\u0026lt;div style=\u0026quot;display: flex; flex-wrap: wrap; justify-content: center; margin-bottom: 10px;\u0026quot;\u0026gt;\r\u0026lt;button onclick=\u0026quot;dfsApp.run('no_mark', this)\u0026quot;\u0026gt;1. 无标记 (死循环)\u0026lt;/button\u0026gt;\r\u0026lt;button onclick=\u0026quot;dfsApp.run('no_unmark', this)\u0026quot;\u0026gt;2. 无释放 (遗漏)\u0026lt;/button\u0026gt;\r\u0026lt;button onclick=\u0026quot;dfsApp.run('correct', this)\u0026quot;\u0026gt;3. 标准回溯 (全空间)\u0026lt;/button\u0026gt;\r\u0026lt;/div\u0026gt;\r\u0026lt;div class=\u0026quot;grid-container\u0026quot; id=\u0026quot;dfs-grid\u0026quot;\u0026gt;\u0026lt;/div\u0026gt;\r\u0026lt;div style=\u0026quot;display: flex; justify-content: center; gap: 10px; margin-bottom: 15px;\u0026quot;\u0026gt;\r\u0026lt;button id=\u0026quot;dfs-pause\u0026quot; onclick=\u0026quot;dfsApp.togglePause()\u0026quot;\u0026gt;暂停\u0026lt;/button\u0026gt;\r\u0026lt;button id=\u0026quot;dfs-speed\u0026quot; onclick=\u0026quot;dfsApp.toggleSpeed()\u0026quot;\u0026gt;速度: 常速\u0026lt;/button\u0026gt;\r\u0026lt;/div\u0026gt;\r\u0026lt;div class=\u0026quot;console\u0026quot;\u0026gt;\r\u0026lt;div id=\u0026quot;dfs-log\u0026quot;\u0026gt;系统状态：准备就绪\u0026lt;/div\u0026gt;\r\u0026lt;/div\u0026gt;\r{% endraw %}\n1 2 3 4 5 6 if (nx \u0026gt;= 1 \u0026amp;\u0026amp; nx \u0026lt;= n \u0026amp;\u0026amp; ny \u0026gt;= 1 \u0026amp;\u0026amp; ny \u0026lt;= m \u0026amp;\u0026amp; a[nx][ny] != -1 \u0026amp;\u0026amp; !vis[nx][ny]) { vis[nx][ny] = 1; // 【标记】占领这个点 dfs(nx, ny); // 【递归】继续深入 vis[nx][ny] = 0; // 【回溯】释放这个点，让别的路径也能经过它 } 以此代码为例,如果不写vis[nx][ny] = 1;,则会导致搜索时反复回到已经走过的路径,造成死循环;如果不在递归后写vis[nx][ny] = 0;,会导致一个已经遍历过的路径中的方格无法被其他其他路径使用.\n再解释一下为什么不写vis就会死循环: 从方格1到方格2后,方格2会遍历上下左右四个方向,因为方格1对于2来说是可达的,因此会回到方格1,以此往复. 因此,如果题目要求找到所有路径,或者所写算法中有往回走的可能,就需要使用vis数组,无论是dfs还是bfs. 图论 ","date":"2026-04-25T09:55:45+08:00","image":"/p/%E7%AE%97%E6%B3%95%E7%AC%94%E8%AE%B0/28876767_p0-%EF%BC%BC%20%E3%83%8F%E3%83%83%E3%83%94%E3%83%BC%E3%83%90%E3%83%BC%E3%82%B9%E3%83%87%E3%82%A4%20%EF%BC%8F.webp","permalink":"/p/%E7%AE%97%E6%B3%95%E7%AC%94%E8%AE%B0/","title":"算法笔记"},{"content":"工作就是为了钱,这句话并没有什么可羞耻的,在我们有了相应的技术,并且有了一些意向公司,还做出了充足的准备后,我们只剩下一件事了:谈薪资.\n但是,这类信息你是很少能够直接在官网上看到的,而在第三方招聘网站,猎头也只是给你一个大致的范围让你自己去\u0026quot;争取\u0026quot;.\n因此,我专门搜集了一些信息整理出一个公司野榜,并且调查了主要互联网公司的评级体系.\n野榜 评级体系 ","date":"2026-04-23T11:00:00Z","image":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E5%9B%9B-%E9%87%8E%E6%A6%9C%E5%92%8C%E8%96%AA%E8%B5%84%E8%B0%83%E6%9F%A5/82924488_p0-Happy%208th%20Anniversary.webp","permalink":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E5%9B%9B-%E9%87%8E%E6%A6%9C%E5%92%8C%E8%96%AA%E8%B5%84%E8%B0%83%E6%9F%A5/","title":"面向面试学习(四)-野榜和薪资调查"},{"content":"在了解了前两部分的内容后,我们就可以准备投递实习和面试了,显然,我们需要先有一份简历.\n简历编写 简历的历史 内推码 在有了简历之后,我们就可以去对应公司的官网投递了,由于内地公司盛行内推码(真不知道最早是哪个小可爱想出来的),所以你需要先去小红书/微信上搜索对应公司的内推码,一般直接搜就会有最新的内推码了,直接用就可以.\n当然有认识的学长学姐更好,但正常来说不会这么凑巧吧\u0026hellip; 准备面试 不管怎样,投递了简历之后就要开始准备面试了,面试中的问题大致有三种:\n算法题 技术题(通常会拷打你简历上的项目) 生活相关的提问 所以我们针对每一类题型都要做好相应的准备,接下来我重点谈谈两个环节: 算法题和反问环节,技术题和生活题真全靠个人修养了.\n算法题 刷题是面试的必备环节,尽管这些算法你以后再也不会在项目中用到,但还是需要你背得滚瓜烂熟.\n至于为什么要考算法题,我之前看到有个人说的很好: 企业不敢招连算法题都不会的人.\n这本质上是一场服从性测试,如果你面试前连算法都不愿意去刷的话,你又怎么愿意为了这个公司付出更大的努力呢.\n而由于面试的算法题跟竞赛题比起来简直是小儿科,因此基本只要一个月每天刷个10道题就可以秒杀所有的面试题了.如果你不满足于刷题的话,可以去看洛谷的深入浅出系列,并跟着题单来刷,保证可以快速入门算法.至于其他的算法书,由于不成体系或者体量过大,基本全都是狗屁\u0026hellip;\n反问环节 可参考GitHub仓库: reverse-interview-zh \u0026ldquo;你有什么想问的吗?\u0026rdquo;,这个问题基本是面试的收尾必备环节,到了这一步你的表现其实已经不大重要了,毕竟该暴露的都已经暴露了\u0026hellip;所以情商不要太低就行,随便问点问题水过去.\n尽管如此,工程师一般都是心地淳朴(呆头呆脑)的人,所以有人特定总结了一点反问用的语句(根据上面的仓库总结):\n职责 我的日常工作是什么？ 有给我设定的特定目标吗？ 团队里面初级和高级工程师的比例是多少？（有计划改变吗） 入职培训 (onboarding) 会是什么样的？ 技术 公司常用的技术栈是什么？ 你们怎么使用源码控制系统？ 你们怎么测试代码？ 团队 工作是怎么组织的？ 团队内 / 团队间的交流通常是怎样的？ 你们使用什么工具来做项目组织？你的实际体会是什么？ 如果遇到不同的意见怎样处理？ 不同的意见如何处理？ 如果被退回了会怎样？（“这个在预计的时间内做不完”） 当团队有压力并且在超负荷工作的时候怎么处理？ 如果有人注意到了在流程或者技术等其他方面又改进的地方，怎么办？ ","date":"2026-04-23T10:00:00Z","image":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E4%B8%89-%E5%AE%9E%E4%B9%A0%E4%B8%8E%E9%9D%A2%E8%AF%95%E5%87%86%E5%A4%87/60263222_p0-%E3%83%81%E3%83%A3%E3%82%A4%E3%83%8A%E3%82%A2%E3%83%AA%E3%82%B9.webp","permalink":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E4%B8%89-%E5%AE%9E%E4%B9%A0%E4%B8%8E%E9%9D%A2%E8%AF%95%E5%87%86%E5%A4%87/","title":"面向面试学习(三)-实习与面试准备"},{"content":"在了解了可以面试哪些岗位之后,就可以根据自己想去的岗位来投递自己的意向公司了,但是参加面试之前总得对自己要面试的公司有个了解吧,因此我在这里介绍了一些常见的大型互联网公司.\n该文对于一个公司的调研主要分为以下四点:\n公司概况: 快速了解这家公司 公司历史: 公司的发展历史 主要产品: 该公司的优秀产品 招聘需求: 内地应聘的条件和要求 国际企业 该类企业有以下优缺点:\n工资较高,福利好,无996 应试要求低,但一般都要求英语好 晋升难 难外调(relocate) 招聘岗位少,竞争激烈,大牛多 由于招聘岗位少,招聘需求部分就不贴具体岗位分析了,只写一点吐槽.\n值得一提的是,外企都没有内推码这种让我深恶痛绝的东西,凭什么我投个简历还要去到处找关系托人情啊! Microsoft (微软) 多少人梦寐以求的头部外企啊 公司概况 微软发迹于为早期的 Altair 8800 提供 BASIC 解释器，凭借 MS-DOS 和 Windows 确立了在个人电脑操作系统市场的垄断地位；其核心业务由 Windows 操作系统、Office 生产力套件、Azure 云平台以及 Xbox 游戏生态组成，通过收购 LinkedIn、Skype 和动视暴雪等企业实现多元化，目前是全球收入最高的软件公司。\n操作系统市场的垄断地位是微软最可怕的地方 公司历史 Wiki 1972–1985：初创与系统业务奠基 早期尝试：盖茨与艾伦通过 Traf-O-Data 开启创业，1975 年受 Altair 8800 启发开发出 Altair BASIC，并正式成立 Microsoft。 MS-DOS 的崛起：1980 年通过收购 86-DOS 并授权给 IBM PC（品牌化为 MS-DOS），确立了在操作系统市场的统治地位，且保留了对非 IBM 硬件的授权权益。 1985–1994：Windows 与 Office 时代的开启 图形化转型：1985 年发布 Windows 1.0，1986 年公司上市（IPO）。随后推出的 Windows 3.0 取得巨大市场成功。 生产力套件：1990 年引入 Microsoft Office，将 Word、Excel 等应用打包，主导了办公软件市场。 内核演进：发布基于 32 位模块化内核的 Windows NT，奠定了现代 Windows 系统的架构基础。 1995–2007：互联网转型、XP 与 Xbox 扩张 拥抱 Web：在“互联网潮汐”备忘录指引下，发布集成浏览器的 Windows 95，随后陷入与网景（Netscape）及司法部的反垄断纠纷。 硬件与娱乐：2001 年发布 Windows XP 统一系统内核，并推出首款 Xbox 游戏机进入主机市场。 管理层更迭：2000 年史蒂夫·鲍尔默接任 CEO，盖茨转任首席软件架构师。 2007–2014：移动端挫败与云端转型初探 Azure 诞生：2008 年发布 Azure 平台，正式进军云计算领域。 移动端困局：Windows Vista 市场反响平平后推出 Windows 7。为应对移动端冲击，推出 Windows Phone 并收购诺基亚手机业务，但市场份额始终未能突破。 自研硬件：2012 年推出 Surface 系列，标志着微软开始直接制造个人电脑硬件。 2014–2020：纳德拉时代的“云优先” 战略重心转移：萨提亚·纳德拉接任 CEO，将公司重心全面转向云计算。 开源与融合：加入 Linux 基金会，收购 GitHub，推出 Windows 10。 业务多元化：通过收购 Minecraft（Mojang）和 LinkedIn，强化内容与社交版图。 2020 至今：AI 浪潮与大规模收购 游戏帝国：通过收购 ZeniMax Media（Bethesda）及动视暴雪，确立了在全球游戏行业的领先地位。 生成式 AI：深度注资 OpenAI，将 Copilot 融入 Azure、Office 和 Windows 全线产品。 基础设施投入：2026 年大规模投资 AI 算力与能源项目，并针对数据中心用电签署白宫能源协议。 控股公司及主要产品 控股公司及主要子公司 Microsoft Gaming：整合了 Xbox Game Studios、ZeniMax Media（Bethesda）及 Activision Blizzard（动视暴雪），是全球最大的游戏出版商之一。 LinkedIn Corporation：2016 年收购的职业社交平台，独立运营并深度集成于微软的企业服务生态。 GitHub：2018 年收购的全球最大开源代码托管平台，作为微软开发者工具链的核心组成部分。 Microsoft Mobile Oy：原诺基亚设备与服务部门，曾负责 Lumia 系列手机（现已停止手机硬件业务）。 Nuance Communications：2022 年完成收购，专注于医疗保健领域的对话式 AI 和环境智能技术。 主要产品线 操作系统（Windows）：核心产品包括 Windows 11 及针对企业级市场的 Windows Server，是全球 PC 市场的主导系统。 生产力与业务流程：包含 Microsoft 365（原 Office 365）订阅服务，核心组件为 Word、Excel、PowerPoint、Teams 及 Outlook。 智能云（Azure）：提供计算、存储、数据库及 Azure OpenAI 等 AI 基础设施服务，是微软当前的增长引擎。 硬件设备（Surface \u0026amp; Xbox）：包括 Surface 系列二合一笔记本、Xbox Series X/S 游戏主机及其相关的 Xbox Game Pass 订阅服务。 人工智能（Copilot）：基于 GPT-4 等技术的生成式 AI 助手，贯穿于搜索引擎 Bing、浏览器 Edge 及全线办公软件中。 开发工具：包括 Visual Studio、VS Code 以及针对企业数据的 SQL Server 数据库系统。 硬件软件两开花,可以看得出来微软互联网巨头的地位是不可撼动的 招聘需求 招聘官网 公众号: 微软招聘 官网的岗位很多对吧,但如果你把地点更改一下的话: 行了,都知道你不想在中国招人了.\n面经1 链接 尽管是22年的实习面经,但问题确实很简单,看得出来微软是不喜欢刁难实习生的\u0026hellip;\n面经2 链接 19年的实习面经,面试题也都不难\nGoogle (谷歌) 公司概况 Google 由拉里·佩奇和谢尔盖·布林于 1998 年创立，凭借核心产品 Google 搜索确立了全球信息索引的统治地位，并于 2015 年重组为 Alphabet Inc. 的最大子公司。其业务版图深度覆盖在线广告、云计算（Google Cloud）、操作系统（Android、ChromeOS）、视频共享（YouTube）及消费电子（Pixel、Nest），并在量子计算、自动驾驶（Waymo）和人工智能（Gemini）等前沿领域处于领先。作为全球访问量最大的网站拥有者，Google 在搜索引擎、移动生态和生产力工具市场占据绝对优势，同时也面临着关于垄断、隐私及税务问题的持续监管与争议。\nGoogle与Alphabet Alphabet 是 Google 的母公司，两者于 2015 年通过企业架构重组确立了控股关系：Alphabet 作为顶层控股实体，将盈利核心 Google（含搜索、YouTube、Android）与风险高、投入大的“登月计划”子公司（如 Waymo 自动驾驶、Calico 生物技术）剥离。这一变革的历史缘由在于，当时的 Google 已从单一搜索工具扩张至机器人、生命科学等极度分散的领域，创始人拉里·佩奇希望通过这种“瘦身”结构提高财务透明度，使各业务在拥有独立 CEO 和预算的同时，能更灵活地追求长远创新，而不受核心搜索业务财务波动的束缚。\n公司历史 Wiki 1996–2004：车库创业与技术突破 起源与算法：1996 年，拉里·佩奇和谢尔盖·布林在斯坦福大学发起 BackRub 项目，开发出 PageRank 算法，通过分析网页链接关系而非关键词频率来提升搜索质量。 正式成立：1998 年获得安迪·贝托谢姆 10 万美元投资后正式注资成立公司，办公地点位于苏珊·沃西基的家用车库。 早期增长：1999 年获得 Kleiner Perkins 和 Sequoia Capital 2500 万美元注资。2000 年成为 Yahoo 的默认搜索引擎，并开启基于文本的关键字广告业务。 2004–2015：IPO 与生态版图扩张 公开上市：2004 年以每股 85 美元的价格进行 IPO，市值超过 230 亿美元。 关键收购：2006 年以 16.5 亿美元收购 YouTube；2008 年收购 DoubleClick 强化广告业务；2012 年以 125 亿美元收购摩托罗拉移动（侧重专利保护）。 技术演进：2011 年月度独立访客破 10 亿。2014 年收购 DeepMind，同年 AlphaGo 击败围棋职业选手，标志着 AI 战略的加速。 2015–2022：Alphabet 重组与管理层交替 架构重组：2015 年成立母公司 Alphabet，桑达尔·皮查伊接任 Google CEO。2019 年，佩奇和布林退居幕后，皮查伊兼任 Alphabet CEO。 内部动荡与法律挑战：2018 年发生全球范围内的员工罢工，抗议公司处理性骚扰及军事项目（Project Maven）的方式。2020 年起面临美国司法部和欧盟的多项反垄断起诉。 2023 至今：AI 红码与重塑核心 AI 转型：面对 ChatGPT 的竞争，公司内部发布“红色代码”警报。2023 年发布 Bard（后更名为 Gemini）和高性能 AI 芯片 TPU。 监管定论：2024 年，美国法院裁定 Google 在搜索市场存在非法垄断。2025 年法院裁定其不得签署独家预装合同，且必须向竞争对手共享搜索数据。 巨额投入：2025 年以 320 亿美元收购网络安全公司 Wiz。2026 年签署白宫能源承诺，承担数据中心扩张带来的额外发电成本。 控股公司与组织架构 Alphabet Inc.：顶层母公司，将核心互联网业务与高风险前瞻项目（Other Bets）分离。 Google LLC：Alphabet 旗下最大的子公司，涵盖搜索、广告、YouTube、Android、Chrome 和云服务。 Google DeepMind：核心 AI 研究部门，由原 DeepMind 与 Google Brain 合并而成。 前瞻业务 (Other Bets)：包括 Waymo（自动驾驶）、Verily（生命科学）、Calico（抗衰老研究）及 X Development（秘密实验室）。 主要子公司：包含 YouTube、Waze、Fitbit 以及新近收购的 Wiz（云安全）。 主要产品线 搜索与信息：核心 Google Search、Google Maps、Google Translate 以及 Google Lens。 广告平台：Google Ads、AdSense 及 AdMob，构成了公司主要的营收来源。 内容与平台：YouTube（视频及音乐流媒体）、Google Play 商店。 操作系统与硬件：Android 移动系统、ChromeOS、Pixel 系列手机、Nest 智能家居及 Fitbit 穿戴设备。 企业与云服务：Google Cloud Platform (GCP) 基础设施及 Google Workspace（Gmail、文档、驱动器等）。 人工智能 (Gemini)：原生多模态大模型 Gemini，以及集成在各产品中的 Copilot 式助手。 底层技术与芯片：TensorFlow 机器学习框架、定制化 AI 芯片 TPU。 招聘需求 招聘官网 公众号: 谷歌招聘包打听 之所以内地很少看见有人应聘谷歌是因为它在内地的岗位确实少,而且产品人员招的比技术人员多\u0026hellip; 面经1 大牛的上岸分享\nApple (苹果) 安静的做好自己的产品,其他公司别来搭理我 公司概况 Apple Inc. 由史蒂夫·乔布斯、斯蒂夫·沃兹尼亚克和罗纳德·韦恩于 1976 年创立，总部位于加州库比蒂诺。公司凭借 Apple II 和带图形界面的 Macintosh 推动了个人电脑的普及，在经历 90 年代的濒临破产后，通过收购 NeXT 迎回乔布斯，并凭借 iMac、iPod、iPhone 和 iPad 实现业务重组与盈利。核心产品涵盖 iPhone、Mac、iPad 等硬件及其配套的 iOS、macOS 系统和 Apple Music、iCloud 等服务，于 2025 年 10 月市值突破 4 万亿美元。\n公司历史 Wiki 1976–1984：初创与图形界面革命 公司成立：1976 年 4 月 1 日，史蒂夫·乔布斯、斯蒂夫·沃兹尼亚克与罗纳德·韦恩创立 Apple，首款产品为 Apple I 主板。 Apple II 的成功：1977 年发布，成为首款取得大规模商业成功的个人电脑，确立了苹果在微型计算机行业的地位。 GUI 创新：受施乐帕罗奥多研究中心（Xerox PARC）启发，先后研发出 Lisa（1983）和 Macintosh（1984），首次将图形用户界面（GUI）和鼠标推向大众市场。 1985–1997：权力斗争与财务危机 创始人离职：在与 CEO 约翰·斯卡利的权力斗争后，乔布斯于 1985 年辞职并创立 NeXT，沃兹尼亚克随后也离开公司。 市场份额侵蚀：面对“Wintel”联盟（Windows 与 Intel）的低价竞争，苹果市场份额大幅缩减，Newton PDA 等实验性产品均告失败。 濒临破产：1990 年代中期公司深陷亏损。1996 年，苹果以 4.29 亿美元收购 NeXT 获取其操作系统技术（NeXTSTEP），乔布斯重返公司担任顾问。 1997–2011：乔布斯回归与数字化巅峰 业务重组：乔布斯于 1997 年出任临时 CEO，精简产品线并推出 iMac G3（1998）。 移动化转型：通过 iPod（2001）和 iTunes Store 重新定义了音乐产业。 核心产品跨越：2007 年发布 iPhone，彻底改变了智能手机市场，公司由“苹果电脑”更名为“苹果”。2010 年发布 iPad，开创了现代平板电脑市场。 领导层更迭：2011 年乔布斯病逝前，蒂姆·库克接任 CEO。 2011 至今：生态扩张与服务转型 穿戴设备与服务：在库克领导下，推出 Apple Watch（2015）和 AirPods（2016）。重心转向以 Services（Apple Music, iCloud, Apple Pay）为主的持续性收入。 Apple Silicon：2020 年开始将 Mac 全线转用自研的 M 系列芯片，显著提升了能效比和生态整合度。 空间计算：2023 年发布 Apple Vision Pro，标志着公司正式进军虚拟现实/增强现实（空间计算）领域。 市值里程碑：2018 年成为首家市值破 1 万亿美元的美国公司，并于 2025 年 10 月突破 4 万亿美元。 控股公司及产品 控股子公司与核心实体 Beats Electronics：2014 年收购的音频技术公司，负责 Beats 品牌耳机、扬声器研发及早期的流媒体技术整合。 Claris (原 FileMaker)：专注于企业级低代码开发平台及数据库软件的开发。 Braeburn Capital：苹果设立的资产管理公司，总部位于内华达州，负责管理公司巨额现金储备及投资组合。 Apple Energy, LLC：电力子公司，负责管理苹果持有的太阳能、风能资产，并向批发市场销售多余电量。 Beddit：专注于睡眠监测技术的子公司，为 Apple Watch 的健康追踪功能提供底层算法支持。 核心硬件产品线 iPhone：公司支柱产品，运行 iOS 系统，包括数字系列、Pro 系列及 SE 系列。 Mac：搭载 Apple Silicon (M系列) 芯片的个人电脑，涵盖 MacBook Air/Pro、iMac、Mac mini、Mac Studio 及 Mac Pro。 iPad：平板电脑产品线，包括 iPad、iPad Air、iPad Pro 及 iPad mini，运行 iPadOS。 穿戴与配件：包括 Apple Watch、AirPods（含 Pro/Max）、HomePod 智能音箱以及 AirTag。 Apple Vision Pro：公司首款空间计算设备，运行 visionOS 系统，定位增强现实（AR）与虚拟现实（VR）市场。 软件与操作系统 六大系统：iOS、macOS、iPadOS、watchOS、tvOS 及 visionOS，构建了封闭且协同的生态闭环。 专业与创意软件：包括 Xcode（开发环境）、Final Cut Pro（视频剪辑）、Logic Pro（音频制作）以及 iWork 办公套件。 在线服务与金融科技 媒体订阅：Apple Music、Apple TV+、Apple Arcade（游戏）及 Apple News+。 基础设施：提供云存储与同步服务的 iCloud+。 金融服务：Apple Pay、Apple Card 以及关联的支付与钱包生态。 软件分发：全球核心分发渠道 App Store。 招聘需求 招聘官网 公众号: Apple招聘 招聘岗位比起谷歌和微软都多上不少,但产品人员和硬件开发招的比较多,软件开发的岗位实际上也很少: Meta 不在内地招人,可直接跳过\n公司概况 Meta Platforms, Inc.（前身为 Facebook, Inc.）是一家总部位于加州门洛帕克的美国跨国科技巨头，与 Alphabet、亚马逊、苹果、微软及英伟达并称为美国六大科技巨头（Big Tech）。该公司由马克·扎克伯格于 2004 年创立，旗下拥有 Facebook、Instagram、WhatsApp、Messenger 和 Threads 等全球主流社交与通信平台，广告收入占其总营收的 97.8% 以上。2021 年，公司正式更名为 Meta，反映其转向构建以虚拟现实（VR）和增强现实（AR）技术为核心的“元宇宙”生态系统的战略布局。作为全球研发投入最高的公司之一，Meta 在 2023 年福布斯全球 2000 强中排名第 31 位。\n公司历史 Wiki 2004–2005：哈佛宿舍的诞生与起步 TheFacebook 启动：2004 年 2 月 4 日，马克·扎克伯格在哈佛大学宿舍内上线了 TheFacebook.com。最初仅限于哈佛学生，随后迅速扩张至常春藤盟校及北美各大高校。 联合创始人：参与开发的还包括埃德华多·萨维林（资金支持）、达斯汀·莫斯科维茨（编程）、安德鲁·麦科勒姆（设计）和克里斯·休斯。 核心股权结构：创始人通过发行具有超强投票权的 B 类股，使扎克伯格在 IPO 后仍能保留约 57% 的投票权，确立了对公司的绝对控制。 更名与扩张：2005 年，公司支付 20 万美元收购了 Facebook.com 域名，正式更名为 Facebook，并将注册范围扩大至高中生及全球用户。 2012–2014：移动转型与关键并购 IPO 的曲折：2012 年 5 月，Facebook 以 1040 亿美元的估值上市。尽管开盘遭遇技术故障且股价在首周下跌 16.5%，但其融资额（160 亿美元）成为当时美股历史上规模第三大的 IPO。 “移动优先”战略：面对用户向手机端转移的趋势，扎克伯格下令全公司转型。2014 年，他将内部座右铭从“快速行动，打破常规”改为“在稳定的基础设施下快速行动”，以应对大规模业务运营的稳定性需求。 生态版图成型： Instagram (2012)：以 10 亿美元收购，击败了 Twitter 的竞争。 WhatsApp (2014)：以惊人的 190 亿美元收购，锁定了全球即时通讯市场的统治地位。 Oculus VR (2014)：以 20 亿美元收购，埋下了后续转型元宇宙的伏笔。 2018–2021：从社交巨头到元宇宙 (Meta) 元宇宙愿景文档：2018 年，Oculus 负责人 Jason Rubin 提交了长达 50 页的《元宇宙》愿景报告，极力主张通过重金投资来阻断苹果、谷歌在 VR/AR 领域的竞争。 加密货币尝试 (Libra)：2019 年发起 Libra 稳定币计划，试图建立全球数字支付标准，但因全球监管机构的强烈抵制，该项目在更名为 Diem 后于 2022 年彻底关停并清算。 全面重塑：2021 年 10 月 28 日，扎克伯格在 Connect 大会上正式宣布将母公司更名为 Meta。此举旨在将品牌重心从单一的社交媒体应用转移到结合 VR/AR 的虚拟数字生态系统。 2022–2024：效率之年与 AI 追赶 财务阵痛：2022 年 2 月，因 Apple 隐私政策调整（ATT）导致年度广告损失预估达 100 亿美元，Meta 股价单日暴跌 26%，市值蒸发超 2300 亿美元。 大规模裁员：2022 年底至 2023 年，Meta 开启“效率之年”，裁减超过 2.1 万名员工，并精简中间管理层。 AI 爆发：2023 年发布开源大模型 Llama，通过“开源换生态”的策略在生成式 AI 领域与 OpenAI、Google 形成鼎足之势。 2025–2026：基础设施爆发与自研生态 万亿级投资：2026 年，Meta 联合英伟达并投入约 6500 亿美元建设 AI 基础设施。同年，公司签署白宫能源承诺，承担数据中心扩张带来的巨大电力成本。 硬件落地：2024 年展示了首款全功能 AR 眼镜 Orion 原型；2026 年发布了四款自研 AI 芯片（MTIA 项目）并大幅扩展了与雷朋（Ray-Ban）合作的智能眼镜产品线。 控股公司及产品 控股实体与业务单元 Meta Platforms, Inc.：顶层控股公司。 Reality Labs：元宇宙核心研发部门，负责 Quest 系列及 AR 眼镜研发。 Fundamental AI Research (FAIR)：Meta 的顶尖 AI 实验室。 WhatsApp Inc. / Instagram, LLC：独立运营的社交/通讯子公司。 Moltbook：2026 年新收购的 AI 代理自主社交网络。 核心产品线 Family of Apps：Facebook, Instagram, WhatsApp, Messenger, Threads。 硬件设备：Meta Quest (VR), Ray-Ban Meta (智能眼镜), Portal (视频通话设备, 已精简)。 AI 与开发者工具：Llama 模型系列, PyTorch (由 Meta 创建的深度学习框架), Movie Gen (视频生成模型)。 Amazon (亚马逊) 对于亚马逊退出中国市场一事我一直很遗憾 公司概况 Amazon.com, Inc.（简称亚马逊）是一家总部位于华盛顿州西雅图的美国跨国科技巨头，业务涵盖电子商务、云计算、在线广告、数字流媒体及人工智能。公司由杰夫·贝佐斯于 1994 年创立，最初是一家在线书店，后演变为提供全品类商品的“万能商店”（The Everything Store）。作为“五大科技巨擘”（Big Tech）之一，亚马逊在多个领域占据全球领先地位：它是全球最大的在线零售商、智能音箱供应商和云计算服务商（通过 AWS）。\n公司历史 Wiki 1994–2009：从车库书店到全品类扩张 创立与更名：1994 年 7 月 5 日，杰夫·贝佐斯在华盛顿州贝尔维尤自家的车库中创立公司，原名 Cadabra。由于发音易被误听为“尸体”（Cadaver），数月后更名为 Amazon。选择西雅图是因其拥有微软和华盛顿大学的技术人才库，且靠近俄勒冈州的图书分销中心。 初创期：1995 年 7 月 16 日正式上线运营，最初仅售卖图书。贝佐斯至今保留着 relentless.com 域名并将其重定向至官网。 品类扩张与上市：1997 年 5 月公司公开上市。1998 年开始销售音乐和视频，并启动国际化扩张。1999 年产品线进一步扩展至消费电子、家居改进、软件及玩具等。 云服务的起源：2002 年推出 AWS，最初仅为 API 接口。2006 年发布 S3 存储，2008 年发布 EC2 计算服务，标志着亚马逊向企业服务转型的关键一步。 FBA 模式：2006 年启动 Fulfillment by Amazon，允许第三方卖家利用亚马逊的仓储和物流设施。 2010 至今：生态深化与 AI 转型 并购与增长：2017 年收购 Whole Foods Market。疫情期间业务激增，仅在美加地区就增聘了 10 万名员工。 领导层交接：2021 年 7 月 5 日，贝佐斯卸任 CEO 转任执行主席，AWS 负责人 安迪·贾西（Andy Jassy） 接任。 大规模裁员与效率优化：受宏观环境及 AI 转型影响，亚马逊经历了多轮剧烈裁员。2023 年裁员 1.8 万人；2025 年 10 月宣布减员 1.4 万人；2026 年 1 月再次裁减 1.6 万个企业岗位，旨在减少官僚机构并加速适配 AI 技术应用。 AI 军备竞赛：2026 年 1 月，亚马逊与 OpenAI 洽谈高达 500 亿美元的投资，打破了此前仅重仓 Anthropic 的格局。同年 2 月，宣布在路易斯安那州投资 120 亿美元建设 AI 数据中心。 能源与基础建设：2026 年 3 月签署白宫能源承诺，承担数据中心扩张带来的新增发电成本。根据分析，亚马逊在 2026 年将与 Meta 等巨头共同投入 6500 亿美元用于 AI 基础设施建设。 进军中国市场始末 2004–2011：入场与本土化初期 收购卓越网：2004年8月，亚马逊以7500万美元收购中国最大图书音像电商卓越网（Joyo.com），避开牌照限制正式入场。 品牌整合：2007年更名为“卓越亚马逊”，2011年统一品牌为**“亚马逊中国”**。初期凭借全球供应链和自建物流（15个运营中心）在图书领域占据领先地位。 Kindle 入华：2013年Kindle电子书店正式在中国上线，随后硬件入华，一度占据中国电子阅读器市场65%以上的份额。 2012–2018：竞争红海与份额滑坡 本土巨头崛起：阿里巴巴（天猫/淘宝）与京东凭借更灵活的促销策略（如双11、618）和极致的本土物流速度，迅速稀释亚马逊的市场空间。 系统“水土不服”：亚马逊中国长期采用全球统一的后台架构，导致界面陈旧、促销配置缓慢，无法适应中国电商高频、复杂的营销节奏。 份额触底：至2018年，其在中国电商市场的占有率已从巅峰期的15%萎缩至不足1%。 2019–2024：战略收缩与境内业务退出 裁撤境内电商：2019年7月，亚马逊正式停止为中国境内第三方卖家提供服务，关闭境内纸质书销售业务，仅保留海外购、全球开店及 AWS（亚马逊云科技）。 Kindle 彻底撤离：2023年6月关闭电子书店；2024年6月30日停止云端下载服务，标志着Kindle在中国长达11年的运营正式终结。 App 停运转型：2023年7月停运原亚马逊中国App，业务整合至微信小程序。 控股公司及产品 核心子公司 Amazon Web Services (AWS)：全球领先的云平台，提供 S3、EC2 及 Bedrock (AI 平台)。 Zoox：全自动驾驶技术研发公司。 Amazon MGM Studios：影视内容生产与分发中心，包含米高梅影业。 Project Kuiper：低轨卫星互联网计划。 Lab126：位于硅谷的核心硬件研发部门。 核心硬件 Kindle：电子书阅读器系列。 Echo：搭载 Alexa 语音助手的智能音箱系列。 Fire 系列：平板电脑、流媒体电视棒。 Ring \u0026amp; Blink：家庭安全视频监控系统。 服务与软件 Amazon Prime：拥有超 2 亿用户的订阅体系，包含物流、视频、音乐特权。 Amazon Publishing \u0026amp; Audible：图书出版与全球最大的有声读物平台。 Twitch：顶级游戏直播平台。 Rufus：基于生成式 AI 的新型购物助手。 招聘需求 招聘官网 公众号: 亚马逊招聘 与Apple一样,管理岗位和产品岗位招的特别多,真正的软件开发岗位很少\nIntel (英特尔) 母校不是上交的可以直接跳过了\n公司概况 英特尔（Intel）是总部位于加州圣克拉拉的跨国科技巨头，由戈登·摩尔和罗伯特·诺伊斯于1968年创立，是硅谷崛起的核心基石。作为x86架构的奠基者，英特尔曾通过“Wintel”联盟长期统治个人电脑市场，目前仍是全球领先的半导体制造商，核心业务涵盖酷睿（Core）系列处理器、Arc显卡、芯片组及数据中心基础设施。尽管近年来面临AMD的激烈竞争及移动互联网转型的挑战，英特尔依然凭借其集设计与制造于一体的IDM模式，在PC和服务器芯片领域保持着显著的市场领先地位。\n公司历史 Wiki 1968–1981：硅谷基石与存储器时代 起源与命名：1968年7月18日，化学家戈登·摩尔、物理学家（集成电路共同发明人）罗伯特·诺伊斯离开仙童半导体，创立了“NM Electronics”，不久更名为 Intel（集成电子）。第三名员工安迪·格鲁夫随后加入。 早期突破：英特尔最初专注于半导体存储器，试图取代磁芯存储。1970年发布全球首款商用DRAM芯片 1103，至1972年成为全球最畅销的内存芯片。 微处理器的诞生：1971年，英特尔受日本公司委托开发出全球首款商用微处理器 4004，开启了计算设备小型化的革命。 1982–2000：Wintel 霸权的黄金时代 核心转型：由于日本半导体厂商在DRAM市场的激烈竞争，CEO戈登·摩尔于80年代中期决定裁撤内存业务，全力押注微处理器。 Wintel 联盟：英特尔处理器与微软Windows操作系统的结合，使其成为PC产业的绝对主导者。通过 \u0026ldquo;Intel Inside\u0026rdquo; 营销计划（1991年），英特尔成功将品牌深度植入消费者认知。 快速扩张：在安迪·格鲁夫（1987-1998任CEO）的带领下，英特尔经历了前所未有的高速增长。尽管因反垄断指控和与AMD的法律诉讼频发，但仍稳坐市场头把交椅。 2001–2020：架构迭代与工艺瓶颈 酷睿（Core）的辉煌：在经历了NetBurst架构的失利后，2006年英特尔推出 Core 酷睿微架构，重新确立了性能领先地位。同年，苹果Mac转投英特尔阵营。 制程挑战：2016年后，英特尔在 10nm 工艺节点上遭遇严重技术瓶颈，多次推迟量产，导致产品迭代陷入停滞，并被迫放弃著名的“Tick-Tock”模型。 安全危机：2018年，Meltdown (熔断) 和 Spectre (幽灵) 漏洞被曝光，影响了几乎所有现代英特尔处理器，对品牌信任造成巨大冲击。 2021–2026：动荡、重组与政府入股 IDM 2.0 战略：2021年帕特·基辛格接任CEO，提出转型计划，包括设立独立的代工服务（IFS）和巨额基础设施投资。 管理层巨变：由于转型进度未达预期及巨额亏损（2024年Q2亏损16亿美元），基辛格于2024年底被罢免。2025年3月，陈立武（Lip-Bu Tan） 接任CEO。 国家战略持股：2025年8月，美国政府斥资约89亿美元购入英特尔9.9%的股份，将其视为保障国家半导体安全的“被动所有权”资产。 战略合纵连横： 英伟达入场：2025年9月，英伟达注资50亿美元与英特尔合作开发数据中心CPU。 AMD代工意向：2025年10月，英特尔开始接洽竞争对手AMD，试图将其纳入代工客户。 架构收复：2026年4月，英特尔斥资142亿美元回购爱尔兰工厂49%的股权，重新掌握核心制造设施的控制权。 控股公司及产品 核心事业部 Intel Foundry (英特尔代工)：独立的代工业务实体，利用 Intel 18A 等先进制程对外提供制造服务。 Client Computing Group (CCG)：负责酷睿（Core）系列 PC 处理器的核心部门。 Data Center and AI (DCAI)：负责至强（Xeon）服务器处理器及 Gaudi 系列 AI 加速器。 Intel Labs：负责前沿研究，包括量子计算和硅光子技术。 关键子公司与分拆实体 Altera：专注于 FPGA（现场可编程逻辑门阵列）的业务单元。 Mobileye：自动驾驶技术公司（曾被收购后再次分拆上市）。 RealSense：2025年分拆为独立实体的 AI 机器人与生物识别视觉公司。 核心产品线 CPU：Intel Core（消费级）、Intel Xeon（服务器）、Intel Lunar Lake（AI PC 专用）。 GPU：Intel Arc 系列独立显卡。 AI 硬件：Gaudi 3 加速器，直接竞争英伟达 H100 等产品。 制程技术：Intel 7, Intel 4 (7nm), Intel 3, 以及即将量产的 Intel 18A。 招聘需求 招聘官网 服务号: 英特尔招聘在线 只在上海闵行区招聘AI岗位,目标院校是什么不用多说\u0026hellip; 招聘岗位特别少,看的出来是不愿意在中国招人的那种,可以直接跳过 Nvidia(英伟达) 入职能送我5090吗😃\n公司概况 NVIDIA 是一家全球领先的科技巨头，总部位于美国加利福尼亚州圣克拉拉。公司由黄仁勋、Chris Malachowsky 和 Curtis Priem 于 1993 年创立。其核心竞争力在于设计与研发图形处理器 (GPU)、系统级芯片 (SoC) 以及支撑高性能计算的 CUDA 软件架构。\n业务架构与产品线 数据中心 (核心增长极)：提供 Blackwell、Ampere 架构的 AI 加速器（如 H100、GB200）。截至 2026 财年，该业务营收占比已接近 90%，是全球 AI 算力基础设施的垄断级供应商。 游戏与创作：主力产品为 GeForce 系列显卡。虽然在公司总收入中占比下降至约 11%，但在独立 GPU 市场仍维持超过 90% 的份额。 专业可视化：提供 RTX 系列专业级显卡，用于科学研究、工业设计及 Omniverse 数字孪生平台。 汽车与机器人：研发 DRIVE 自动驾驶平台及 Jetson/Thor 机器人 SoC，聚焦于物理 AI 与智能化交通解决方案。 行业地位 软硬件生态：通过 CUDA API 建立了极高的开发者粘性，在 AI 模型训练与部署市场占有率超过 80%。 资本市场表现：2025 年，NVIDIA 成为全球首个市值突破 5 万亿美元 的公司。 战略转型：公司已从传统的游戏显卡厂商转型为一家全栈加速计算公司，其技术支撑了全球超过 75% 的最强超级计算机及绝大部分主流大语言模型 (LLM) 的运行。 公司历史 Wiki 早期初创与架构试错 (1993–1996) 1993年：黄仁勋、Chris Malachowsky 及 Curtis Priem 创立 NVIDIA。 1995年：推出首款芯片 NV1。该产品尝试集成图形、音频及游戏控制，但因采用非主流的四边形纹理映射技术，在微软发布以三角形映射为核心的 DirectX 标准后失去竞争力。 1996年：因 NV1 失败导致财务崩溃，世嘉 (Sega) 的 500 万美元投资成为关键救命钱。 图形标准确立与 GPU 诞生 (1997–2005) 1997年：发布 RIVA 128。这是公司首款支持三角形渲染的 128 位图形处理器，市场反响剧烈，解决了生存危机。 1998年：发布 RIVA TNT，确立了在多纹理处理领域的领先地位。 1999年：发布 GeForce 256。NVIDIA 正式定义了 GPU (图形处理器)，通过硬件集成 T\u0026amp;L（几何转换与光照）引擎，将图形处理从 CPU 中解放出来。 2000–2002年：收购竞争对手 3dfx；发布 GeForce 3，引入可编程着色器技术。 2004年：推出基于 SLI 技术的 GeForce 6 系列，允许双显卡并联。 CUDA 生态与移动端探索 (2006–2015) 2006年：发布 Tesla 架构及 CUDA 计算平台。这是一次战略性赌注，使 GPU 能够处理通用并行计算任务，为后续 AI 爆发奠定基础。 2008–2010年：发布 Fermi 架构，强化高性能计算性能。同期推出 Tegra 系列移动处理器，尝试进入智能手机与平板市场。 2012年：发布 Kepler 架构。同年 AlexNet 神经网络利用 NVIDIA GPU 在 ImageNet 竞赛中获胜，开启了深度学习时代。 2014年：发布 Maxwell 架构，大幅提升能效比；业务重心向游戏、数据中心、汽车电子及可视化四大方向多元化转型。 AI 算力爆发与光追时代 (2016–2023) 2016年：发布 Pascal 架构 (GTX 10系列)，采用 16nm 工艺，性能飞跃。发布专门针对 AI 训练的 DGX-1 超级计算机。 2017年：发布 Volta 架构 (V100)，首次引入 Tensor Core (张量核心)，专为深度学习加速设计。 2018年：发布 Turing 架构 (RTX 20系列)，引入 RT Core 实现硬件级实时光线追踪及 DLSS 技术。 2019–2020年：完成对 Mellanox 的收购，整合 InfiniBand 高速网络技术。发布 Ampere 架构 (RTX 30系列及 A100)，A100 成为大模型训练的标准配置。 2022年：发布 Hopper 架构 (H100)，专门针对 Transformer 模型优化；同年发布 Ada Lovelace 架构 (RTX 40系列)。 万亿市值与全栈算力帝国 (2024–2026) 2024年：发布 Blackwell 架构 (B200/GB200)，单芯片支持万亿参数模型推理。市值突破 3 万亿美元。 2025年1月：面对 DeepSeek 等算法优化带来的算力需求波动，市场出现剧烈震荡，但随后通过技术迭代稳固地位。 2025年7–10月：市值接连突破 4 万亿与 5 万亿美元大关，成为全球市值第一。 2025年下半年：发布 Alpamayo-R1 自动驾驶模型及 Nemotron-3 混合专家 (MoE) 模型。 2026年：通过注资英特尔 (Intel) 强化 X86 架构兼容性，并与 OpenAI 达成协议，转型为提供“算力+网络+模型”的全栈 AI 服务商。 主要产品 数据中心与加速计算 (Data Center) Blackwell 架构 GPU 代表作：B200 / GB200 地位：当前 AI 算力的巅峰。GB200 超级芯片由一颗 Grace CPU 和两颗 Blackwell GPU 组成，专为万亿参数规模的大模型 (LLM) 训练与推理设计。 Hopper 架构 GPU 代表作：H100 / H200 地位：AI 工业革命的“功勋机型”。H200 凭借 141GB HBM3e 内存，成为大模型部署的主力军。 Vera Rubin 架构 (2026 新品) 代表作：Vera CPU / BlueField-4 地位：最新发布的下一代平台，聚焦“智能体 AI (Agentic AI)”，通过 BlueField-4 STX 架构极大提升了存储访问与上下文缓存处理能力。 游戏与桌面计算 (Gaming) GeForce RTX 系列 代表作：RTX 5090 / RTX 5080 (Blackwell 架构) 地位：2026 年最新旗舰，搭载 DLSS 4.5 技术。其核心特点是引入了动态多帧生成技术，仅支持 50 系列显卡，为 4K/8K 游戏提供极限帧率。 汽车与自动驾驶 (Automotive) NVIDIA DRIVE Thor 代表作：极越 (Jiyue) 2026 款量产车型 地位：集中式车载计算平台，单颗芯片算力达 2000 TFLOPS，首批搭载 Blackwell GPU 架构，支持端到端智驾及车内生成式 AI。 网络与基础设施 (Networking) BlueField DPU 代表作：BlueField-3 / BlueField-4 地位：数据中心基础设施的加速器，负责卸载 CPU 的网络、存储和安全任务。BlueField-4 是 2026 年推出的最新款，针对智能体 AI 的数据瓶颈进行了专项优化。 Spectrum-X / Quantum-X800 代表作：Spectrum-X800 以太网平台 地位：为超大规模 AI 云设计的网络架构，支持 800Gb/s 高速互联。 企业级软件与平台 NVIDIA Omniverse 代表作：Omniverse Cloud 地位：工业数字孪生标准平台，用于模拟工厂、气候及物理精确的虚拟环境。 NVIDIA Nemotron (模型家族) 代表作：Nemotron-3 500B (Ultra) 地位：英伟达自研的大语言模型系列，深度适配其硬件架构，提供从 Nano 到 Ultra 的全尺寸选择。 招聘需求 招聘官网 公众号: NVIDIA英伟达 招聘岗位在外企里算多的了,建议优先考虑,但同样比较偏硬件 Tesla (特斯拉) 尽管是汽车公司,但是还是有不少软件需求的\u0026hellip;\n公司概况 特斯拉（Tesla, Inc.）总部位于德克萨斯州奥斯汀，是一家集汽车与清洁能源为一体的美国跨国公司。公司由马丁·艾伯哈德和马克·塔彭宁于 2003 年创立，随后由埃隆·马斯克领投并出任 CEO。特斯拉不仅通过 Model S/3/X/Y 等车型引领了全球纯电动汽车（BEV）市场，还涉足家用及电网级储能设备、太阳能产品。尽管在 2025 年底失去了全球最大电动汽车制造商的地位，但其市值多次突破 1 万亿美元，稳居全球市值最高车企。\n马斯克控股公司名列 这些企业并没有像其他巨头一样整合成一家公司,所以有必要在这里说一下:\nTesla (特斯拉)：马斯克担任 CEO 且为第一大股东。虽然是上市公司，但他通过持股和极高的个人影响力行使控制权。 SpaceX (太空探索技术公司)：马斯克担任 CEO 兼首席技术官，拥有绝对控股权。这是目前全球估值最高的私有航天企业，旗下包含 Starlink (星链) 卫星互联网业务。 X (原 Twitter)：马斯克于 2022 年全资收购并将其私有化。目前他拥有 100% 的决策权。 xAI：马斯克于 2023 年创立的人工智能公司，旨在开发 Grok 等大模型，与 OpenAI 竞争。其计算资源常与 X 平台深度整合。 Neuralink：脑机接口技术公司，马斯克为联合创始人及主要控股人，致力于实现人脑与计算机的直接连接。 The Boring Company (隧道挖掘公司)：马斯克创立的地下交通基础设施公司，旨在通过超高速隧道解决城市拥堵。 公司历史 Wiki 2003–2009：创立与 Roadster 的探索 公司创立：2003 年 7 月 1 日，马丁·艾伯哈德和马克·塔彭宁创立 Tesla Motors。 马斯克入场：2004 年 2 月，埃隆·马斯克领投 650 万美元 A 轮融资并出任董事长。2009 年的法律和解协议认定艾伯哈德、塔彭宁、莱特、马斯克和斯特劳贝尔五人为共同创始人。 首款车型：2008 年开始生产 Roadster。同年，马斯克接任 CEO。尽管面临财务危机，马斯克投入个人资金并于 2009 年获得美国能源部 4.65 亿美元贷款，帮助公司度过难关。 2010–2018：IPO、Model S 与“生产地狱” 上市与扩张：2010 年 6 月特斯拉在纳斯达克 IPO，是自 1956 年福特以来首家上市的美国车企。 产品矩阵成型：2012 年发布 Model S（全球首款高端电动轿车），2015 年交付 Model X（豪华 SUV）。2014 年上线 Autopilot 辅助驾驶系统。 能源与量产挑战：2016 年收购 SolarCity 并更名为 Tesla, Inc.。同年发布廉价车型 Model 3，随后经历了两年的“生产地狱”，最终在 2018 年克服产能瓶颈，使其成为全球最畅销电动车。 2019–2024：全球爆发与万亿市值 全球工厂建设：2019 年上海超级工厂开工并于当年投产。随后柏林和德克萨斯工厂相继于 2022 年投产。 财务巅峰：2020 年起实现连续盈利，市值于 2021 年首次突破 1 万亿美元。 技术创新：2023 年开始交付 Cybertruck 纯电动皮卡。期间，特斯拉的 NACS 充电标准 成为北美行业事实标准。 2025–2026：AI 转型、竞争格局与 Terafab 计划 市场易主：2026 年 1 月，官方数据显示特斯拉在 2025 年的交付量被中国竞争对手 BYD 超越，失去全球最大纯电动车制造商头衔。 AI 与机器人重心：2025 年 7 月在车内集成 Grok AI。2026 年 1 月，马斯克宣布将于 2026 年 Q2 停产 Model S 和 Model X，以全力转向 Optimus 人形机器人 的制造。 超级工程 Terafab：2026 年 3 月，特斯拉联合 SpaceX 和 xAI 启动 Terafab 项目，旨在建造一座每年可产生 1 万亿瓦（1 Terawatt）AI 算力的垂直整合半导体超级工厂，涵盖逻辑芯片与先进封装。 控股公司及产品 核心子公司与部门 Tesla Energy：负责 Powerwall、Megapack 及太阳能业务。 Tesla Foundry (Terafab)：2026 年启动的半导体制造单元，负责自研 AI 芯片生产。 Reality Automation：负责 Optimus 机器人的核心算法与机械架构。 核心产品线 乘用车：Model 3、Model Y、Cybercab（自动驾驶出租车原型）。 商用与特种车：Cybertruck、Tesla Semi（重卡）。 智能设备：Optimus（通用人形机器人）。 软件：FSD (Full Self-Driving)、Grok 语音助手（车内集成）。 关键技术平台 Dojo：自研 AI 训练超级计算机。 NACS (North American Charging Standard)：北美通用充电网络标准。 4680 电池：自研高能量密度圆柱电池技术。 招聘需求 公众号: 特斯拉招聘 社会招聘在官网,但校园招聘用的是第三方平台,看不懂了. 服务岗位很多,软件类的岗位还是很少的\nAMD 岗位均偏硬件方向\n公司概况 AMD（Advanced Micro Devices,超威半导体）总部位于加州圣克拉拉，由杰里·桑德斯于 1969 年创立，是硅谷历史最悠久的半导体巨头之一。公司经历了从存储器生产到 x86 处理器授权制造，再到自研架构的战略演变，凭借 Zen 架构及 Ryzen（锐龙）、EPYC（霄龙） 系列处理器，AMD 在 2020 年代实现了史诗级逆袭：不仅在 2022 年市值首次超越宿敌英特尔，更在 2026 年初将其桌面 CPU 市场份额推至 36.4% 的历史新高，并在高利润的服务器营收份额中突破 41%。通过 2022 年对 Xilinx（赛灵思） 的世纪收购，AMD 完成了从 PC 芯片商向覆盖数据中心、AI、嵌入式及游戏主机的全能型高性能计算领导者的转型。\n公司历史 Wiki 1969–1981：初创与“第二供应商”模式 起源与创立：1969年5月1日，杰里·桑德斯（Jerry Sanders）与七位仙童半导体同事共同创立 AMD。由于起步比英特尔晚一年，AMD 最初作为“第二供应商”，生产由仙童和国家半导体设计的逻辑芯片，并以美军标准（Mil-Spec）的严苛品控赢得了初期市场。 进入处理器市场：1975年，AMD 通过逆向工程推出了英特尔 8080 的克隆版 Am9080。1976年，两家公司签署了微代码交叉授权协议，确立了长期竞合关系的法律基础。 西门子注资：1977年，德国西门子购入 AMD 20% 股份，资金注入助力其产品线扩张及海外工厂建设。 1982–2005：x86 授权与自主研发的转型 IBM 强制授权：1982年，因 IBM 要求其 PC 必须拥有两个处理器来源，英特尔被迫与 AMD 签署技术交换协议，AMD 成为 8086、80186、80286 芯片的法定授权制造商。 走向独立竞争：80年代中后期，由于日本半导体的低价倾销，AMD 退出 DRAM 市场。1991年推出兼容英特尔架构但完全自研的 Am386，标志着两家公司正式进入直接竞争。 战略收购：1996年收购 NexGen 奠定了 AMD K6 的成功。2003年，为专注处理器业务，将闪存业务分拆成立 Spansion（后与富士通合资）。 2006–2019：架构动荡与苏姿丰时代的复兴 ATI 世纪收购：2006年以54亿美元收购显卡巨头 ATI，AMD 成为全球唯一同时拥有高性能 CPU 和 GPU 技术的公司。 轻资产化转型：2008-2009年，AMD 将其制造部门分拆为独立的代工厂 GlobalFoundries，转型为专注于芯片设计的“无厂”（Fabless）半导体公司。 濒临破产与主机救命：2010年代初期，受 Bulldozer 架构失利及 Intel 挤压，AMD 裁员超 25%。最终依靠为 PS4 和 Xbox One 提供半定制芯片带来的稳定现金流度过危机。 Lisa Su 掌舵：2014年苏姿丰（Lisa Su）出任 CEO，将战略重心转向高性能计算和数据中心。2017年 Zen 架构及 Ryzen（锐龙） 系列发布，AMD 开始在性能和能效比上反超英特尔。 2020–2026：AI 爆发与市场格局重塑 赛灵思并购：2022年完成对 Xilinx（赛灵思） 的500亿美元收购，极大强化了其在 FPGA 和自适应计算领域的地位。 服务器与 AI 逆袭： 份额跃升：至2025年7月，AMD 在服务器 CPU 市场的份额已攀升至 36.5%。 算力竞赛：2024-2025年连续收购 Silo AI 和 ZT Systems 以构建 AI 生态。2025年6月发布 MI400 系列 AI 芯片（Helios 服务器核心）。 OpenAI 深度绑定：2025年10月，AMD 与 OpenAI 达成协议，未来五年内供应 6GW 的 AI 处理器。作为交易，OpenAI 获得增持 AMD 10% 股份的期权。 市值巅峰：2024年市值首次突破 3000 亿美元。2026年初，随着 AI 算力需求爆发，AMD 已成为 Nvidia 在数据中心推理市场的主要竞争对手。 控股公司及产品 核心事业部 Computing and Graphics：负责锐龙（Ryzen）消费级 CPU 及 Radeon 显卡。 Data Center Solutions：负责霄龙（EPYC）服务器芯片及 Instinct MI 系列 AI 加速器。 Embedded Group：整合赛灵思业务，专注 FPGA、自适应 SoC。 Semi-Custom Business：负责游戏机（PS5/Xbox）及手持设备芯片。 核心产品线 桌面/笔记本 CPU：Ryzen 5/7/9, Ryzen AI (集成 NPU)。 服务器 CPU：EPYC (Gen 4/5/6)。 AI/高性能计算：AMD Instinct MI300/MI400 系列。 可编程器件：Versal Adaptive SoC, Virtex FPGA。 图形技术：Radeon RX 系列显卡, FSR (FidelityFX Super Resolution) 图像技术。 招聘需求 招聘官网 招的岗位也很多,但不少是面向AI模型和硬件架构的,难度很高.\nCisco (思科) 岗位很少,可直接跳过\n公司概况 思科（Cisco Systems）总部位于加州圣何塞，由斯坦福大学两位计算机科学家于 1984 年创立，是全球联网技术与网络安全领域的绝对领导者。作为 LAN（局域网）多协议路由系统的先驱，思科在 2000 年互联网泡沫巅峰期曾一度超越微软成为全球市值最高的公司，目前市值约 3170 亿美元。公司业务深度覆盖核心网络硬件、网络安全、云计算、物联网（IoT）及人工智能，拥有 Webex、OpenDNS 等知名品牌.\n公司历史 Wiki 1984–1995：多协议路由器的先驱与上市 起源与争议：1984 年 12 月，斯坦福大学计算机科学家列昂纳德·波萨克和桑迪·勒纳夫妇创立思科。初期产品源于斯坦福校内的“蓝箱”（Blue Box）多协议路由器。由于涉及知识产权争议，思科于 1987 年向斯坦福支付了授权费。 命名与品牌：公司名称取自旧金山（San Francisco），标志则是由金门大桥的双塔抽象而成的电波信号。 走向公众：1990 年思科在纳斯达克上市。同年，创始人勒纳因与资方冲突被解雇，波萨克随之辞职。 早期战略：思科凭借 Cisco 2500 等经典型号统治市场，并开始通过收购（如 Crescendo）切入以太网交换领域，形成了著名的 Catalyst 业务线。 1996–2012：互联网泡沫与业务转型 巅峰时刻：在约翰·钱伯斯的领导下，思科抓住互联网协议（IP）普及的浪潮。2000 年互联网泡沫顶峰，其市值突破 5000 亿美元，一度成为全球市值最高的公司。 技术重塑：面对 Juniper 等对手在硬件转发上的挑战，思科自研了高性能 ASIC 芯片，并于 2004 年推出 CRS-1 核心路由器。 消费市场尝试与撤退：曾通过收购 Linksys 尝试进入家庭消费市场，并提出“人类网络”口号。但 2011 年后因利润不及预期，思科大幅裁员，并在 2013 年出售了 Linksys，重新聚焦企业级服务。 2013–2023：云转型与安全转型 领导层交替：2015 年，钱伯斯卸任，查克·罗宾斯接任 CEO。思科开始从单纯的硬件厂商向软件订阅与服务模式转型。 核心收购：2013 年以 27 亿美元收购 Sourcefire，强化网络安全能力。2017 年推出 Cisco Umbrella 云安全网关。 自研芯片突破：2019 年发布 Silicon One ASIC 芯片，打破了 Broadcom 在高速网络芯片市场的垄断。 退出俄罗斯：2022 年俄乌冲突后，思科停止在俄销售并于 2023 年销毁了无法复运出口的价值 2300 万美元的库存设备。 2024–2026：AI 驱动与 Splunk 时代的开启 史上最大收购：2024 年 3 月，思科以 280 亿美元 完成对网络安全与数据分析巨头 Splunk 的收购，标志着思科全面向 AI 驱动的监控与安全领域转型。 AI 伦理与技术协作：2024 年 4 月，CEO 罗宾斯与教皇方济各签署《罗马 AI 伦理倡议》。2026 年，思科继续优化其 Silicon One 芯片至 G200 规格，支持 51.2 Tbit/s 的超高速 AI 算力集群网络。 结构调整：2024 至 2025 年间，思科多次精简团队以整合网络、安全与协作部门。尽管如此，至 2025 年底其财报显示营收仍稳步增长，市值维持在 3170 亿美元 左右。 控股公司及产品 核心事业部 Networking (网络)：交换机（Catalyst、Nexus）、路由器（ASR、ISR）。 Security (安全)：防火墙、云安全服务（Umbrella、Duo）、Splunk 数据分析平台。 Collaboration (协作)：Webex 会议系统、IP 电话及智能协同终端。 Observability (可观测性)：集成 AppDynamics 与 Splunk，提供全栈监控服务。 关键子品牌与收购实体 Splunk：2024 年并入，负责网络安全与机器数据分析的核心。 Meraki：云管理网络设备的领导者。 Webex：全球领先的企业级音视频协作平台。 AppDynamics：应用性能管理（APM）平台。 核心产品线 硬件架构：Catalyst 系列交换机（企业网核心）、Nexus（数据中心）、Silicon One 芯片。 软件系统：Cisco IOS / IOS-XE / IOS-XR（网络操作系统）、Cisco DNA Center。 人工智能：Webex AI Assistant（实时翻译与摘要）、AI-Native Security Cloud。 招聘需求 招人,但都是无关紧要的岗位.\nAirbnb (爱彼迎) 岗位很少,可直接跳过\n公司概况 Airbnb（爱彼迎）总部位于旧金山，由布莱恩·切斯基、乔·杰比亚和内森·布莱查奇克于 2008 年创立，是全球共享经济模式的开创者。作为一家轻资产的在线旅行租赁平台，它通过连接房东与房客，提供从气垫床到城堡的多样化住宿及当地体验服务，并从中抽取佣金。尽管经历了 2020 年疫情的冲击及 2022 年撤出中国本土房源市场的战略调整，Airbnb 目前仍是全球市值最高的旅游科技公司之一，市值超 1000 亿美元。\n公司历史 Wiki 2007–2009：从“气垫床”到“孵化器” 起源：2007 年，乔·杰比亚和布莱恩·切斯基为了支付旧金山的房租，在公寓里放置了三张气垫床并提供早餐（Airbed and Breakfast）。2008 年，内森·布莱查奇克加入并担任 CTO，公司正式成立。 筹资奇招：早期公司极度缺钱，创始人曾在 2008 年大选期间设计并销售定制款麦凯恩和奥巴马主题早餐麦片（Obama O\u0026rsquo;s），赚取了 3 万美元的启动资金。 Y Combinator：2009 年公司进入顶级孵化器 YC。在保罗·格雷厄姆的建议下，创始人飞往纽约亲自为房源拍摄高质量照片，这一举动成为公司业绩的转折点。 2010–2019：共享经济的全球化 指数级增长：2011 年，Airbnb 获得红杉资本注资，估值突破 10 亿美元，正式开启全球扩张。 体验业务：2016 年推出 Airbnb Experiences（体验），将业务从单一的住宿扩展到由当地人领队的旅游活动。 中国市场：2015 年正式进入中国，起名“爱彼迎”。 2020–2026：疫情冲击、上市与业务重塑 裁员与转型：2020 年初受疫情重创，营收暴跌 80%。布莱恩·切斯基随后宣布裁员 25% 并大幅削减非核心项目（如酒店和交通）。 IPO：2020 年 12 月，Airbnb 在纳斯达克上市。尽管处于疫情中，其市值在首日即突破 1000 亿美元。 后疫情策略：随着远程办公普及，Airbnb 转向推广“长租”和“非城市中心”房源。 退出中国本土市场：2022 年 5 月，Airbnb 宣布停止中国本土房源的经营，保留跨境旅游业务（Outbound），将重心转回服务出境游的中国游客。 AI 整合 (2025-2026)：2026 年，Airbnb 深度整合了 Joule-like 生成式 AI 系统，用户可以通过自然语言描述复杂的旅行方案（如“适合带狗、有恒温泳池且附近有酒庄的意式庄园”），AI 可实现端到端的筛选与预订。 控股公司及产品 核心管理主体 Airbnb, Inc.：位于旧金山的母公司，负责全球平台运营。 Airbnb China (爱彼迎中国)：现阶段主要负责中国游客的海外房源预订及跨境服务支持。 核心产品线 Airbnb Stays：核心住宿业务，涵盖单间、整套公寓、独特房源（如树屋、城堡）。 Airbnb Experiences：由当地达人提供的徒步、烹饪、工作坊等在地化服务。 Airbnb for Work：针对企业出差团队的商务住宿管理工具。 Airbnb Luxe：提供经过严苛筛选的高端豪华住宅及管家服务。 招聘需求 招聘官网 内地岗位很少: SAP 公司概况 SAP SE（思爱普）总部位于德国沃尔多夫，由五位前 IBM 工程师于 1972 年创立，是全球最大的企业管理软件（ERP）供应商。作为欧洲市值最高的科技公司及全球最大的非美软件企业，SAP 专注于开发能够处理企业核心业务流程（如财务、后勤、人力资源）的集成系统。从早期的 R/2、R/3 架构到如今基于内存计算技术的 S/4HANA 平台，SAP 的产品支撑着全球绝大多数大型企业的运作。其业务遍布 180 个国家，是德国 DAX 指数及欧洲蓝筹股的核心成分股，代表了企业级应用软件的工业标准。\n公司历史 Wiki 1972–1981：初创与“实时”系统的诞生 起源：1972 年 6 月，五名前 IBM 工程师因不满项目被终止而离职，在德国曼海姆创立了 SAPD 公司。 技术革命：当时 IBM 仍在使用穿孔卡片进行批处理，SAP 率先实现了在大型机上通过逻辑数据库进行**实时（Real-time）**数据存储与处理。其首个客户是英国帝国化学工业（ICI），为其开发的财务与账务系统奠定了 SAP R/1 的基础（“R”代表实时）。 总部迁移：1977 年总部搬迁至沃尔多夫。1981 年，私人合伙制转变为 SAP GmbH。 1982–1999：R/2、R/3 与全球扩张 大型机时代 (R/2)：1979 年发布 SAP R/2，将功能扩展至物料管理和生产计划，能够处理多语种和多币种，助力其进入国际市场。 客户端-服务器架构 (R/3)：1992 年发布的 SAP R/3 是公司历史上的重大转折点。它顺应了从大型机向客户端-服务器（Client-Server）架构转型的趋势，使 SAP 迅速成为全球 ERP 市场的霸主。 资本市场：1988 年 SAP 挂牌上市，并于 1995 年被纳入德国 DAX 指数。 2000–2014：ERP 演进与云转型起步 架构升级：2004 年，R/3 被 SAP ERP Central Component (ECC) 5.0 取代，随后 2006 年发布了长期服役的 ERP 6.0 版本。公司架构向面向服务（SOA）转型。 大规模收购：为应对甲骨文（Oracle）的挑战，SAP 开始疯狂并购云服务商。2014 年以 83 亿美元收购 Concur（差旅管理），创下当时最高收购纪录。 法律形态变更：2014 年 7 月，公司正式更名为 SAP SE（欧洲公司），反映其跨国治理结构的成熟。 2015–2026：HANA 时代、AI 与云优先战略 核心平台：2015 年后，基于内存计算技术的 S/4HANA 成为核心。它结合了机器学习与 IoT 技术，将实时处理能力提升到了新高度。 战略精简：2019 年 SAP 宣布裁员 4000 人，旨在将资源从传统业务向区块链、量子计算和人工智能倾斜。 AI 驱动的招聘与协作：2025 年收购 SmartRecruiters，将其 AI 助手 Winston 整合进 SuccessFactors 模块，并与 SAP 的生成式 AI 产品 Joule 协同工作。 云端霸权与挑战：截至 2025 年 6 月，SAP 市值达到 3200 亿欧元，成为欧洲市值最高的公司。其 80% 的客户业务已迁移至云端。同年 9 月，欧盟委员会对其发起反垄断调查，关注其竞争实践。 控股公司及产品 核心事业部与合资实体 SAP Deutschland SE \u0026amp; Co. KG：负责德国本土业务。 SAP Fioneer：2021 年成立，专注于金融服务行业（FSI）的独立部门。 SAP SuccessFactors：全球领先的人力资本管理（HCM）云平台。 核心产品线 ERP 旗舰：SAP S/4HANA（下一代智能 ERP）、SAP ERP 6.0 (ECC)（传统核心）。 云应用套件：SAP Concur（差旅与费用）、SAP Ariba（采购与供应链）、SAP Fieldglass（外部劳动力管理）。 技术平台：SAP BTP（业务技术平台）、SAP HANA（内存数据库）。 AI 与智能化：Joule（生成式 AI 副驾驶）、Joule + Winston 智能招聘解决方案。 招聘需求 官网 公众号: SAP招聘,会跳转到一个移动端网站 看着多,但软件岗位依然很少.\nOracle (甲骨文) 直接跳过,不在中国招人\n公司概况 Oracle（甲骨文）总部位于德克萨斯州奥斯汀，由拉里·埃里森等人在 1977 年创立，是全球最大的数据库软件供应商及核心企业级软件巨头。作为全球市值排名前 20 的顶级科技公司，Oracle 以其核心的**关系型数据库（Oracle Database）**统治了金融、电信等关键行业数十年，并以此为基础构建了涵盖 ERP、HCM 和供应链管理（SCM）的完整企业应用生态。\n公司历史 Wiki 1977–1989：从 CIA 项目到数据库霸主 初创与灵感：1977 年，拉里·埃里森（Larry Ellison）与鲍勃·迈纳、爱德·欧茨在加州创立了 SDL 公司。其灵感源于埃德加·科德（Edgar F. Codd）关于**关系型数据库（RDBMS）**的论文。 命名渊源：公司首个客户是美国中央情报局（CIA），其项目代号为“Oracle”。1983 年，公司正式更名为 Oracle Systems Corporation，与其核心产品保持一致。 商业奇才：三位创始人认为埃里森编程最差，于是让他负责销售。他通过演示 SQL 语言的强大威力成功打开市场。1986 年，Oracle 在纳斯达克上市。 人才策略：早期大规模从名校招聘，由于职位供不应求，部分名校毕业生甚至先担任接待员或分发咖啡，直到研发岗位空缺。 1990–2009：应用软件扩张与“红木城”时代 全栈转型：80 年代末，Oracle 开始基于数据库销售财务和制造软件。虽然初期应用软件业务亏损多年，但埃里森坚持通过收购（如 2005 年对 PeopleSoft 的恶意收购）切入 ERP 领域，最终使 Oracle 能够与 SAP 竞争。 战略 footprint：通过提供完整的软件栈，Oracle 从单一的技术供应商转型为客户的战略合作伙伴，成功进入大企业的董事会决策层。 2010–2020：云端追赶与总部迁移 融合应用：2010 年发布 Fusion Applications，整合了此前收购的 PeopleSoft、Siebel 和 JD Edwards 的技术优势。 迁移奥斯汀：2018 年在德克萨斯州奥斯汀设立办公室，并于 2020 年宣布将全球总部从加州红木岸正式迁至奥斯汀。 云联盟：为了挑战 AWS，Oracle 曾与宿敌微软结盟，实现 Oracle Cloud 与 Microsoft Azure 的直接互联。 2021–2026：AI 基础设施与全球版图重塑 医疗与 AI 收购：2022 年以 283 亿美元完成对医疗 IT 巨头 Cerner 的收购。 AI 军备竞赛： Stargate 计划：2025 年初，Oracle 与 OpenAI、软银等参与了 5000 亿美元的“Stargate”AI 基础设施投资计划。 超级云联盟：2025 年，Oracle 数据库已登陆四大超算平台（AWS、Azure、Google Cloud、OCI），实现了真正的多云部署。 权力交接与 TikTok 交易：2025 年 9 月，萨夫拉·卡茨卸任 CEO，由 Clay Magouyrk 和 Mike Sicilia 接任联合 CEO。2026 年 1 月，Oracle 正式获得 TikTok 美国业务 15% 的股权。 总部再迁：2024 年宣布计划将总部从奥斯汀迁往田纳西州的纳什维尔（Nashville）。 最新业绩 (2026)：截至 2025 年底，其云基础设施（IaaS）营收同比增长 68%。由于 AI 算力需求旺盛，其剩余履约义务（RPO）高达 1300 亿美元。 控股公司及产品 核心事业部 Oracle Cloud Infrastructure (OCI)：提供高性能计算、存储及 AI 算力集群。 Cloud Applications：包含 ERP、HCM、CX 及 SCM 云套件。 Financial \u0026amp; Healthcare：包含 Cerner 业务，专注行业深度方案。 核心产品线 数据库：Oracle Database 23c/25c（支持向量检索、AI 驱动）、MySQL HeatWave。 云平台：OCI (Oracle Cloud Infrastructure)，因其能效比高、网络延迟低，成为许多 AI 模型训练的首选。 应用软件：Oracle Fusion Cloud、NetSuite（针对中小企业）。 招聘需求 不在中国招人,印度人倒是招的挺多,就别看了.\n内地企业 目录的编排大致有一个排名顺序\n腾讯 公司概况 腾讯是一家总部位于深圳的全球顶尖科技与投资控股公司，成立于1998年。它是全球最大的游戏发行商及领先的社交媒体巨头，运营着微信（WeChat）和QQ等国民级应用。业务横跨娱乐、人工智能、金融科技及云服务，通过对全球600多家企业的股权投资，构建了庞大的产业生态。\n横跨多个领域的巨头 公司历史 Wiki 1998–2010：初创与快速成长 1998年11月： 马化腾、张志东、许晨晔、陈一丹、曾李青在开曼群岛创办腾讯。 1999年2月： 发布即时通讯产品 OICQ（后更名为 QQ）。 2001年： 南非媒体巨头 Naspers 购入腾讯 46.5% 的股份。 2004年6月16日： 腾讯控股在香港联交所正式挂牌上市。 2005年： 收入模式多元化，涵盖移动 QQ、电信增值服务及周边授权。 2007年： 成立腾讯公益慈善基金会。 2008年： 腾讯被纳入恒生指数成份股；虚拟物品销售成为利润增长点；开始大规模代理游戏（如《穿越火线》、《地下城与勇士》）。 2011–2014：移动转型与投资扩张 2011年1月21日： 推出 微信 (Weixin/WeChat)，开启移动社交新时代。 2011年2月： 以约 2.3 亿美元收购 Riot Games（英雄联盟开发商）92.78% 的股权。 2012年6月： 收购 Epic Games（虚幻引擎、堡垒之夜开发商）少数股权。 2013年： 投资搜狗（4.48 亿美元）及金山网络；成为动视暴雪的被动投资者。 2014年： 1月： 投资华南城，进军物流电商。 2月： 4 亿美元购入大众点评 20% 股份。 3月： 购入 JD.com (京东) 15% 股份，并将旗下电商业务并入京东。 11月： 与 HBO 达成独家分销协议。 12月： 领投滴滴打车；上线 微众银行 (WeBank)。 2015–2020：全球布局与市值巅峰 2015年： 与 NBA 签署 7 亿美元独家流媒体协议；完成对 Riot Games 的全资收购。 2016年： 以 86 亿美元收购 Supercell（部落冲突开发商）84.3% 股权。 入股 特斯拉 (Tesla) 5% 股权（2017年披露，价值 17.8 亿美元）。 2017年： 5月：市值超越富国银行，进入全球前 10。 6月：入榜 BrandZ 全球最有价值品牌前 8。 11月：市值突破 5000 亿美元，超越 Facebook 成为亚洲首家跨过此门槛的公司。 2018年： 投资万达商业、乐高、家乐福；设立 10 亿元“科学探索奖”。 2020年： 收购 iflix；在新加坡设立亚洲中心；购买《系统震荡 3》及其续作版权。 2021–至今：监管合规与 AI 转型 2021年： 7月： 虎牙斗鱼合并案因反垄断监管被正式禁止；搜狗私有化获批。 12月： 收购 Turtle Rock Studios。 2022年： 1月： 因未按规定申报并购交易多次受罚。 11月： 以实物分红方式减持 美团 绝大部分股份。 2023年： 减持特斯拉股份；收购育碧母公司 49.9% 股份；12 月受网络游戏监管新规草案影响，市值一度单日大幅波动。 2024年： 12月： 苹果公司洽谈在华销售的 iPhone 中集成腾讯 AI 模型。 2025年： 1月： 发布 3D 模型生成器 Hunyuan3D。 3月： 发布基于 Transformer-Mamba 架构的推理语言模型 Hunyuan T1。 截至年底： 持有环球音乐集团 (UMG) 11.45% 的股份。 旗下公司及产品 主要产品 通信与社交： QQ： 经典即时通讯平台，目前侧重年轻用户生态及频道化运营。 微信 (WeChat)： 全球用户超13亿的超级App，包含朋友圈、视频号、小程序、微信支付及企业微信。 数字内容： 腾讯视频： 头部长视频流媒体平台。 腾讯音乐 (TME)： 旗下拥有 QQ音乐、酷狗音乐、酷我音乐及全民 K 歌。 腾讯新闻： 资讯服务平台。 腾讯阅文集团： 掌管起点中文网等头部网络文学平台（控股）。 金融科技与企业服务： 微信支付 (财付通)： 移动支付解决方案。 微众银行： 中国首家互联网银行（第一大股东）。 腾讯云： 基础设施及产业互联网核心支撑。 腾讯混元 (Hunyuan)： 旗下全链路自研大语言模型，包含 Hunyuan-DiT、Hunyuan-T1（推理模型）。 腾讯游戏 (Tencent Games) 核心工作室群： 天美工作室群 (TiMi)： 代表作《王者荣耀》、《使命召唤手游》。 光子工作室群 (Lightspeed)： 代表作《和平精英》、《PUBG Mobile》。 魔方工作室群 (Morefun)： 代表作《火影忍者》手游。 北极光工作室群： 代表作《天涯明月刀》。 控股及核心关联公司 直播领域： 虎牙 (Huya)： 控股子公司，财务已并表。 斗鱼 (DouYu)： 第一大股东。 全球游戏巨头 (控股/全资)： Riot Games (拳头游戏)： 100% 控股，代表作《英雄联盟》、《瓦罗兰特》。 Supercell： 控股股东，代表作《部落冲突》、《荒野乱斗》。 Turtle Rock Studios： 全资收购，代表作《求生之路》开发团队。 Sumo Group： 全资收购的英国游戏开发巨头。 全球游戏巨头 (重要持股)： Epic Games： 持有约 40% 股份。 Ubisoft (育碧)： 通过持有其母公司股权拥有约 9.9% 股份。 蓝洞 (Krafton)： 持有约 13.5% 股份，代表作《绝地求生》。 环球音乐集团 (UMG)： 持有约 20% 股份。 本地生活与电商 (策略持股)： 美团： 虽然 2022 年进行了实物分红减持，但仍保持战略合作。 京东： 重要股东及战略合作伙伴。 拼多多： 重要股东。 招聘需求 官网 腾讯的业务很多,岗位也非常多,官网上的要求都比较简单,但实际应聘的时候都是会狠狠拷打你的: 翻了翻都是远程面试. 面试流程一览 腾讯面试流程\n面经1 来源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 ## 项目深挖与常规问答 * **自我介绍** * **核心项目介绍：** 挑一个花费时间最多、最重点的项目介绍，并罗列一两个难点。 * **后续追问：** 目前项目的访问量多大？（如实回答目前仅作个人和朋友测试使用）。 --- ## 计算机基础与后端八股 ### 操作系统与网络 * Python多进程解决OOM问题，为什么不用多线程？ * 进程和线程在操作系统层面的核心区别是什么？ * FastAPI 服务端延迟极低，客户端发起请求时，TCP 建立连接的过程是怎样的？ * 项目中实现在线推送为什么使用 WebSocket 而不用 HTTP 轮询？ ### JVM 基础 * Java 程序运行时，JVM 内存分为哪几块？ * 堆里的对象是一定会被回收的吗？ * 引用类型会被回收吗？ ### Redis * 项目中的布隆过滤器、互斥锁、逻辑过期分别是解决什么问题的？ * 逻辑过期和物理过期的区别是什么？ * HyperLogLog、ZSet、Bitmap 的底层原理和适用场景是什么？ * 场景题：如何统计最近七天内每天都活跃的日活用户交集？ ### 消息队列 (RabbitMQ) * 如何保证消息百分之百入库？描述消息从生产到消费的完整可靠链路。 * 死信队列里面是怎么处理的？ * 怎么保证消息的幂等性？ ### 数据库 (MySQL) * 索引场景题：有用户表、签到表（自增ID，user_id，签到时间，状态），要查某个用户某个月的签到记录，怎么加索引？ * 如果不用 Redis，直接在 MySQL 层面避免高并发下的重复点赞，怎么设计？ * 如果并发量很大，使用乐观锁和悲观锁的区别？使用悲观锁有什么问题？ --- ## 算法与代码手撕 * 实现 `O(1)` 时间复杂度的 LRU 缓存 * 合并 K 个升序链表 --- ## AI 与大模型工程 * RAG（检索增强生成）的工作流分哪几步？ * RAG 知识库生成的步骤是什么？ * 向量检索时，怎么判断相似度？ * 你项目里的 Agent 架构是怎么设计的？ --- ## 反问环节 * 如果有幸入职，主要会做哪些工作？难点在哪里？ * 腾讯内部对使用 AI 辅助编程的态度是什么？ * 对我今天的面试表现有什么评价或建议？ 阿里 公司概况 阿里巴巴是一家成立于1999年、总部位于杭州的全球性科技巨头，也是全球最大的电子商务与零售平台之一。其业务体系以淘宝、天猫为核心，覆盖了云计算、数字媒体、物流及金融科技（蚂蚁集团）等多元领域。2014年阿里巴巴在纽交所完成了当时全球规模最大的IPO。进入2026年，阿里通过“千问”大模型全面向AI驱动转型，旗下云智能与国际商业板块表现强劲，目前仍是全球最具价值的互联网公司之一。\n目标是电商和云计算等上游产业,从而摆脱腾讯的限制 公司历史 Wiki 1. 1999–2004：初创与淘宝崛起 1999年6月28日： 马云带领 18 人在杭州公寓创立 Alibaba.com（B2B 平台）。 1999年10月： 获得高盛、软银等 2500 万美元投资。 2002年： 阿里巴巴 B2B 业务开始盈利。 2003年： 为了应对 eBay 进入中国市场，秘密成立 淘宝网 (Taobao)。 2004年： 淘宝凭借免费模式和第三方信用担保（支付宝前身）迅速获得市场信任。 2. 2005–2014：全球最大 IPO 与扩张 2005年： 雅虎 (Yahoo!) 以 10 亿美元和雅虎中国资产交换阿里巴巴 40% 的股份。 2007年： 淘宝市场份额超越 eBay；后者随后退出中国市场。 2012年： 中投公司领衔回购雅虎所持有的 40% 股份。 2014年： 3月： 投资银泰商业，开启线下零售布局。 6月： 12 亿元收购广州恒大足球俱乐部 50% 股权。 9月19日： 在纽约证券交易所挂牌上市，融资 250 亿美元，创下当时全球最大 IPO 纪录。 3. 2015–2022：领导层更迭与监管风暴 2015年： 成立阿里文学；开始投资印度支付平台 Paytm。 2018年： 成为奥运会全球赞助商；市值突破 5000 亿美元。 2019年9月： 马云正式卸任董事局主席，由 张勇 (Daniel Zhang) 接任。 2019年11月： 在香港二次上市，成为当年全球最大融资案。 2020年11月： 蚂蚁集团 (Ant Group) IPO 被监管部门叫停，阿里股价受挫。 2021年4月： 因违反反垄断法被处以 28 亿美元 (182.28亿元) 罚款。 2022年： 被美国证监会列入预摘牌名单；国资背景基金入股优酷、UC 业务子公司（“金股”）。 4. 2023–至今：拆分重组与 AI 时代 2023年3月： 启动 “1+6+N” 组织变革，将业务拆分为云智能、淘天、菜鸟、本地生活、阿里国际数字商业、大文娱六大业务集团，计划独立上市。 2023年9月： 菜鸟向港交所提交上市申请（后撤回）；领导层再次更迭（蔡崇信接任董事局主席，吴泳铭接任 CEO）。 2024年12月： 将银泰百货出售给雅戈尔集团，退出非核心零售业务。 2025年11月： 股价及估值逐步回升；面临关于提供技术支持的指控（阿里否认）。 2026年1月： 由于地缘政治及数据安全考量，美国德克萨斯州禁止在政府设备上使用阿里产品和服务。 旗下公司及产品 主要产品 核心电商： 淘宝 (Taobao)： 全球最大的 C2C 零售平台。 天猫 (Tmall)： 品牌 B2C 平台，包含天猫超市、天猫国际。 闲鱼： 闲置物品交易社区。 全球与批发： Alibaba.com： 核心 B2B 贸易平台。 速卖通 (AliExpress)： 面向全球消费者的跨境零售平台。 Lazada： 东南亚领先的电商平台。 基础设施与服务： 阿里云 (Alibaba Cloud)： 全球领先的云计算服务商，包含通义千问大模型体系。 菜鸟 (Cainiao)： 智慧物流网络，提供端到端供应链服务。 钉钉 (DingTalk)： 智能协同办公平台。 本地生活： 饿了么： 本地即时配送平台。 高德地图： 领先的移动出行与位置服务平台。 飞猪 (Fliggy)： 在线旅游服务平台。 控股及核心关联公司 金融科技 (核心关联)： 蚂蚁集团 (Ant Group)： 腾讯持有约 33% 股份；运营 支付宝 (Alipay)。 数字媒体与娱乐： 优酷 (Youku)： 头部长视频平台。 阿里影业： 影视内容投资与发行平台。 大麦网： 现场娱乐票务平台。 UC 浏览器： 移动互联网入口及内容资讯。 新零售与制造： 盒马 (Freshippo)： 数据驱动的新零售连锁。 银泰商业： 曾高度控股，2024 年底已宣布将其转让给雅戈尔集团。 犀牛智造： 数字化服装制造工厂。 海外市场： Trendyol： 土耳其最大电商平台（控股）。 Daraz： 南亚领先电商平台（全资）。 招聘需求 公众号: 阿里巴巴集团招聘 校园招聘 社会招聘 不懂为什么要特意分开\u0026hellip; 全面拥抱AI,很多岗位都是算法和Agent. 需要注意的是,阿里很多岗位都是实习转正的,如果目标确定是阿里的话建议先投递暑期实习. 阿里的招聘要求说的都比腾讯清楚很多,能够让应聘者快速知道自己是不是这块料:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 职位要求 1.基础条件 ● 计算机、软件工程、人工智能等相关专业优先。 2.专业能力 ● AI编程工具重度玩家：Cursor、Claude code等AI编程工具重度或顶级玩家，具备极强的Prompt编写与调优能力，有过完整的项目级开发经验，理解如何让AI写出生产级代码。 ● 大模型能力理解与掌握：理解主流LLM的能力与局限，能够清晰拆解任务并通过LLM或确定性逻辑兜底实现；熟悉主流大模型的应用范式(Context Engineering、Prompt Engineering、Agent、工具/函数调用等等)及主流Agent框架(如LangChain等)，具备大模型幻觉、Prompt注入等风险的工程化应对思路。 ● 扎实的代码和工程能力：具备扎实的计算机基础知识，深入理解数据结构、算法、网络和操作系统等相关知识，能至少在一种主流编程语言（如 Java / Python / JS 等）上有深度的实践经验，掌握常见工程实践并具备优秀的Coding 能力，能够根据场景灵活选型并快速上手。 3.能力特质 ● 学习能力： 具备快速啃透前沿论文（Paper）的能力，并能将理论知识转化为工程代码。 ● 开放性与动手能力： 拒绝纸上谈兵，有自己独立完成的小项目（开源项目或个人 Demo），展现极客精神。 ● 好奇心与想象力： 面对未知事物有独特的想法，具备敏锐的问题定义能力和坚韧的解决问题能力。 ● 审美追求： 技术亦有审美，鼓励你提出能提出打动人心、简洁优雅的好想法。 ● 高能动性： 具备强烈的自驱力，能够主动探索边界，而不是等待被分配任务。 加分项 ● 有AI应用或Agent实际落地经验：包括不限于RAG系统、多智能体编排、结合MCP、Skill等的Agent项目，有可展示的项目/实习成果者优先。 ● 开源贡献或技术影响力：在Github上有高质量AI项目、技术博客或社区影响力。 ● 对AI Infra有基本理解：了解vLLM、Ollama等推理框架原理，理解延迟优化、KV cache优化、流式输出等工程全局视角。 ● 在 CV（计算机视觉）或 NLP（自然语言处理）方向有扎实的理论基础，有实际业务场景模型训练(SFT、RL)等经验的优先。 面经1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ### Java线程池的使用及工作原理 ### 并发情况下线程安全的问题介绍一下 ### HashMap是线程安全的吗 ### ConcurrentHashMap原理，是如何实现线程安全的？ ### ConcurrentHashMap的段是怎么划分的？ConcurrentHashMap的存储结构是怎样的？ ### ConcurrentHashMap和HashMap存储数据的区别是什么？ ### NIO和AIO分别是什么原理？ ### 零拷贝介绍一下 ### HTTPS加密通信的过程？ ### MySQL四种隔离级别是什么？分别怎么实现的？ ### 你的MySQL设置的隔离级别是什么？可重复读如何实现的？ ### 两个字符串的最长连续相等字符串长度 字节 公司概况 字节跳动（ByteDance）是一家成立于2012年、总部位于北京的全球领先互联网技术巨头，由张一鸣、梁汝波等人创立。公司凭借TikTok和抖音（Douyin）重塑了全球短视频社交格局，同时拥有资讯平台今日头条（Toutiao）、视频剪辑工具剪映（CapCut）以及Lemon8等多元化产品矩阵，并在生成式人工智能领域积极布局。\n公司历史 Wiki 2012–2015：初创与算法起步 2012年3月： 张一鸣与梁汝波在中关村成立字节跳动；发布首款应用内涵段子（2018年被关停）。 2012年8月： 旗舰产品今日头条（Toutiao）首个版本上线，利用大数据算法实现个性化内容推荐。 2013年1月： 制定国际化愿景，计划建立英文版头条以进军海外。 2016–2018：短视频爆发与全球扩张 2016年3月： 成立 ByteDance AI Lab，发力人工智能底层研究。 2016–2017年： 开启全球收购潮，投资印尼平台 BABE，收购 Flipagram 及 News Republic。 2017年11月： 以约 10 亿美元收购 musical.ly，为 TikTok 的全球扩张奠定基础。 2018年8月： 将 musical.ly 与 TikTok 合并，统一品牌开启全球化运营。 2018年起： 与腾讯陷入长期法律诉讼，涉及不正当竞争及数据抓取争议。 2019–2022：业务多元化与监管挑战 2021年4月： 成立 BytePlus 部门，向外部企业输出 TikTok 核心底层算法技术。 2021年8月： 收购虚拟现实（VR）初创公司 Pico。 2022年6月： 伦敦办公室因文化冲突引发员工离职潮，引发对其内部“赛马机制”文化的关注。 2023–至今：生成式 AI 与算力布局 2023年： 成立 AI 团队“Seed”；8 月发布首款 AI 聊天机器人豆包（Doubao）。 2023年12月： 因在训练过程中使用 OpenAI API 数据引发争议，随后进行合规整改。 2024年： 进行全球业务优化，裁减约 1000 名用户运营及市场人员；6 月推出图片社交应用 Whee。 2025年2月： 展示 AI 视频生成系统 OmniHuman-1，可由单图及动作信号生成逼真视频。 2026年3月： 披露与 Aolani Cloud 合作，在马来西亚部署包含 36,000 片英伟达 B200 芯片的 Blackwell 计算系统。 。 旗下公司及产品 主要产品 短视频与社交： 抖音 (Douyin)： 中国领先的短视频社交平台，包含电商、生活服务等生态。 TikTok： 抖音海外版，全球下载量最高的社交应用之一。 Whee： 2024 年推出的海外图片分享社交应用。 信息流与工具： 今日头条 (Toutiao)： 基于算法推荐的通用信息平台。 剪映 (CapCut)： 全球领先的视频编辑工具，海外版名为 CapCut。 番茄小说： 免费网络文学平台。 人工智能 (AI)： 豆包 (Doubao)： 核心 AI 聊天机器人，基于其自研大模型。 OmniHuman-1： 2025 年发布的 AI 视频生成系统，支持单图转视频。 办公与企业服务： 飞书 (Lark)： 企业协作与管理平台，海外版名为 Lark。 BytePlus： 向企业输出推荐算法、数据分析等底层技术的服务部门。 其他领域： Lemon8： 种草类兴趣社区，定位类似小红书。 8th Note Press： 2023 年设立的图书出版品牌。 核心事业部 (BU) 架构 字节跳动目前采取以下六大业务板块架构：\n抖音： 负责抖音、西瓜视频、今日头条、搜索等国内信息流业务。 大力教育： 涵盖智慧学习等教育技术业务。 飞书： 负责领跑协同办公领域。 火山引擎： 企业级技术服务平台（云服务）。 朝夕光年： 游戏研发与发行（近年来进行了战略收缩与调整）。 TikTok： 负责 TikTok 全球业务及其海外延伸产品（如 Lemon8）。 控股及核心关联公司 Pico (小鸟看看)： 2021 年全资收购的 VR 硬件及内容平台。 沐瞳科技 (Moonton)： 全球知名移动游戏开发商（《无尽对决》）。 沐九歌 (Aolani Cloud)： 2026 年披露的算力合作伙伴，助力其在东南亚部署英伟达 Blackwell 计算集群。 BABE (印尼)： 控股的印尼新闻推荐平台。 News Republic： 收购自猎豹移动的全球新闻平台。 招聘需求 招聘官网 公众号: 字节跳动招聘,实质上还是跳转到招聘官网 由于字节的业务增长迅速,人才缺口是头部企业中最旺盛的,光是上海的实习岗就有1800个: 美团 公司概况 美团（Meituan）是一家成立于2010年、总部位于北京的领先生活服务电子商务平台，由王兴创立。公司通过美团、大众点评及境外品牌 KeeTa，构建了涵盖外卖配送、到店餐饮、酒店旅游及即时零售的全方位业务矩阵。截至2024年底，美团年度交易用户已突破7.7亿，年活跃商家达1450万，是全球最大的本地生活服务平台之一，并于2018年在香港联交所成功上市。\n专注于生活领域 公司历史 Wiki 2010–2014：团购大战与市场突围 2010年： 王兴在北京创立美团网，最初定位于团购（deal-of-the-day）模式。 2011–2014年： 经历“千团大战”，美团从2000多家团购公司中脱颖而出，迅速向二三线城市扩张；获得红杉资本、泛大西洋资本等机构多轮融资。 2014年： 美团在中国团购市场的份额达到 60%，确立行业领先地位。 千团大战AI总结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 “千团大战”是中国互联网史上最残酷的存量淘汰赛，也是美团确立其“本地生活”霸主地位的关键战役。 #### 1. 背景：疯狂的“C2C”模式 (2010 - 2011) 2010年初，受美国团购鼻祖 Groupon 启发，中国市场在半年内涌现出超过 **5000 家** 团购网站。 * **资本狂热：** 当时VC（风险投资）疯狂注资，拉手网、窝窝团、满座网等头部玩家动辄融资数千万美元。 * **野蛮生长：** 行业门槛极低，基本模式就是“扫街签商户+线上卖券+线下消费”。 #### 2. 混乱割据：烧钱与广告战 在2010年到2011年间，多数公司采取了**“高空轰炸”**战略： * **疯狂烧钱：** 头部玩家砸数亿元聘请代言人、购买地铁和电视广告。 * **恶性竞争：** 为了抢占商户，有些平台甚至提供“负毛利”结算，即商家卖100元的东西，平台补贴后只收80元，甚至直接给商家现金提成。 * **美团的选择：** 王兴在此时表现出极度的**财务克制**。美团几乎不打电视广告，而是将资金投入到后台 IT 系统建设和地推团队的管理工具上。 #### 3. 转折点：资本寒冬与“死人堆里爬出来” (2011下半年) 2011年中期，由于中概股诚信危机和资本市场遇冷，融资渠道瞬间关闭。 * **断粮潮：** 那些依赖高获客成本、高运营杠杆的网站（如拉手网、24券）因为烧光了钱且无法持续融资，迅速崩盘。 * **美团的“剩者为王”：** * **账上有钱：** 阿里注资的 5000 万美元被王兴死死握住，不仅没烧光，还趁对手倒闭时低价接收人才。 * **效率机器：** 依靠自研的 ERP 系统，美团商户审核和结款速度极快，赢得了在动荡中极其焦虑的商户的信任。 #### 4. 决胜时刻：从“团购”向“本地生活”升维 当其他对手还在纠结如何卖券时，美团完成了两次关键跳跃： * **T型战略：** 以团购为“一横”，迅速切入酒店、电影票（猫眼）等垂直细分领域作为“一竖”。 * **移动端转型：** 在移动互联网爆发前夕，果断倾斜资源到 App 开发，抓住流量红利。 #### 5. 结果：定局 到2014年，中国团购市场从“千团”缩减至“三强”（美团、大众点评、糯米），美团以 **60% 以上** 的份额占据绝对优势。 --- #### 核心逻辑总结 美团能赢，本质上不是因为比别人更有钱，而是因为王兴践行了**“极高效率、极低成本”**的工程学思维： * **反逻辑：** 在同行都在扩张时，美团在算账；在同行都在裁员时，美团在抄底。 * **第一性原理：** 王兴认为团购的核心是**服务效率**而非**广告声量**。这让美团在“死人堆”里熬到了最后，并最终通过与大众点评的合并彻底终结了战场。 2015–2019：战略合并与香港上市 2015年10月8日： 美团与大众点评宣布合并，成立“美团点评”，整合了到店餐饮、评价与团购资源。 2016年1月： 完成超 33 亿美元融资，估值大幅提升。 2018年9月20日： 美团点评在香港联交所正式挂牌上市，发行价为每股 69 港元。 2020–2022：品牌更名与监管挑战 2020年9月30日： 公司名称由“美团点评”简化为“美团”，旨在建立更统一的品牌形象。 2021年4月： 通过配售股票及发行可转债融资近 100 亿美元，用于投入社区团购（零售）及无人机、自动配送车等前沿技术。 2021年4–10月： 国家市场监管总局因涉嫌垄断行为对其立案调查。美团随后接受罚款并进行全面合规整改。 2023–至今：出海布局与技术落地 2023年5月： 在香港推出外卖品牌 KeeTa，开启迈向国际市场（内地以外）的第一步。 2024–2025年： KeeTa 在香港市场份额显著提升，公司重点发力“即时零售”及自动驾驶技术在城市配送场景的大规模应用。 2025年： 年度交易用户突破 7.7 亿，继续巩固其作为本地生活服务领域第一大平台的地位。 旗下公司及产品 基本上来说,美团旗下最常用的就是两个App: 美团和大众点评,而不是像其他公司一样做精细化拆分,这种把功能绑定在一个软件上的做法有利有弊,但技术难度上显然比较高.\n主要产品 本地生活服务： 美团 App： 核心入口，整合外卖、到店、打车、共享单车、电影票（猫眼）等多项服务。 大众点评 (Dazhong Dianping)： 国内领先的城市生活信息与消费者评论平台，侧重于“发现”与“评价”。 即时零售与配送： 美团外卖： 全球最大的即时配送服务平台。 美团闪购： 30分钟送达的即时零售业务，涵盖超市、药品、鲜花等。 美团配送： 开放式的即时配送网络。 零售与民生： 美团优选： 社区电商平台，采取“预付款+自提”模式。 小象超市 (原美团买菜)： 前置仓模式的自营即时零售。 旅游与交通： 美团酒店/美团门票： 酒店预订及旅游景点票务。 美团单车/助力车： 共享出行服务。 出海品牌： KeeTa： 专门面向内地以外市场（如香港、利雅得等）的外卖与即时配送品牌。 核心业务事业部 (BU) 到家事业群： 负责外卖、配送及即时零售（闪购）。 到店事业群： 负责餐饮到店、婚庆、休闲娱乐及丽人等本地生活服务。 核心本地商业： 2024 年组织架构调整后，将到家与到店整合，由王莆中负责。 科技/自动配送： 负责无人机、自动配送车（无人车）的研发与运营。 控股及关联公司 摩拜单车 (Mobike)： 2018 年全资收购，现已全面更名为“美团单车”。 光年之外： 2023 年全资收购王慧文创立的 AI 初创公司，加强大模型技术储备。 猫眼娱乐 (Maoyan)： 由美团电影业务拆分独立，美团仍是其核心股东及战略合作伙伴。 美团金融： 运营支付（美团支付）、小额贷款等金融服务相关实体。 招聘需求 招聘官网 招聘需求也很多,但是AI/算法岗位占比很高,超过一半了.\n京东 公司概况 京东（JD.com）是一家成立于1998年、总部位于北京的中国最大零售商，由刘强东创立。公司自2004年转型线上零售以来，凭借自建物流体系和B2C自营模式，发展成为与天猫比肩的电商巨头，2024年营收超过1588亿美元，位列财富世界500强前列。其业务版图涵盖零售、物流、科技、健康、工业及国际商业等多个领域，以正品保障和极速配送为核心竞争力。\n主打电商平台 公司历史 Wiki 1998–2009：从线下柜台到自建物流 1998年： 刘强东在北京中关村创立“京东多媒体”，名称取自其女友龚晓京与自己名字的组合。 2004年： 转型线上零售，建立 B2C 网站 jdlaser.com；随后启用 360buy.com 域名。 2007年： 这是一个决定性的转折点，京东开始自建物流部门，旨在提供更可靠、及时的配送服务。同年更名为“京东商城”。 2009年： 在宿迁设立行业规模最大的自营客服中心。 2010–2014：图书大战与纳斯达克上市 2010年： 刘强东发起图书价格战，宣布比竞争对手便宜 20%，与当当网展开激烈博弈。 2013年： 正式切换域名为 JD.com，并发布新的 Logo 和金属狗吉祥物 Joy。 2014年： 在美国**纳斯达克（Nasdaq）**上市，融资 18 亿美元，当时估值约 260 亿美元，是当时仅在美国上市的最大中国公司。 2015–2021：多板块拆分与回港上市 2017–2018年： 京东金融（现京东科技）完成拆分并融资 21 亿美元；销售额持续刷新纪录。 2020年6月： 在香港联交所完成二次上市。 2020–2021年： 资本化加速。京东健康于 2020 年完成 35 亿美元 IPO；京东物流于 2021 年完成 32 亿美元 IPO。 2023–至今：全球化布局与欧洲扩张 2023–2024年： 在香港开启“4小时极速达”服务；上线美国、日本及东南亚的全球直邮业务。 2025年7–11月： 开启大规模海外并购。以 25 亿美元收购德国零售巨头 Ceconomy（MediaMarkt 及 Saturn 的母公司）超 70% 的股权，确立欧洲市场据点。 2025年9月： 曾尝试收购英国零售商 Argos，但最终因条款未能达成一致而终止。 2026年1–3月： 在欧洲扩张遭遇不同监管反馈。德国准许其收购案，但法国政府于 2026 年 1 月正式封锁了其对 Fnac Darty 的收购意向。此外，在奥地利面临国家投资控制法的严格审查，京东需在数据保护和本地就业方面做出额外承诺。 旗下公司及产品 主要产品 京东零售： 核心 B2C 自营平台，主打 3C 家电、快消等全品类。 京东物流 (JD Logistics)： 以仓配一体为核心，提供极速达、211限时达服务。 京东健康： 在线诊疗与医药电商平台。 京东科技 (原京东金融)： 提供数字支付、财富管理及金融科技服务。 海外零售： 收购后的德国 Ceconomy 连锁体系（MediaMarkt / Saturn）。 控股及关联公司 京东物流： 港交所上市公司，京东控股。 京东健康： 港交所上市公司，京东控股。 达达集团 (Dada Group)： 旗下即时零售平台（京东到家），京东控股。 德邦快递： 2022 年由京东物流完成控股。 Ceconomy (德国)： 控股超 70%，京东欧洲扩张的核心支点。 招聘需求 招聘官网 需求比起前面的公司骤减,并且全面拥抱AI.\n网易 公司概况 网易是一家成立于1997年、总部位于杭州的领先互联网技术公司，由丁磊创立。作为全球顶尖的游戏开发商，其核心支柱为《梦幻西游》等自研游戏及暴雪游戏的中国代理，同时深度布局网易云音乐、网易有道（在线教育）和网易邮箱等多元化服务。凭借2023年达146亿美元的营收规模，网易在深耕国内内容社区的同时，正通过在全球建立工作室加速向国际化游戏市场转型。\n核心业务是游戏 公司历史 Wiki 1997–2003：从邮箱服务到游戏转型 1997年6月： 丁磊在广州创立网易，初期仅有3名员工，主要销售邮件服务器软件。 1998–1999年： 推出 163 邮箱，成为中国首个提供免费邮箱、在线社区和个性化信息的门户网站。 2000年7月1日： 在美国纳斯达克（Nasdaq）上市，发行价 15.5 美元。 2001年： 成立网易游戏（NetEase Games）；推出首款自主研发的 MMORPG《大话西游 Online》，标志着公司战略重心的转移。 2003年： 获得高盛、软银等机构投资；邮箱注册用户达到 170 万。 2004–2014：多元化探索与暴雪合作 2005–2007年： 推出博客服务；正式上线自主搜索引擎“有道（Youdao）”，取代与谷歌的合作。 2008年： 开始与暴雪娱乐（Blizzard Entertainment）合作，获得《魔兽世界》等作品在中国内地的代理权。 2011年： 推出网易轻博客 LOFTER，成为中国最活跃的同人文化平台之一。 2013年： 与 Coursera 合作推出中文公开课平台；上线网易云音乐，进入音乐流媒体市场。 2014年： 与腾讯产生版权纠纷，最终达成音乐版权转授权协议，成为行业标准模型。 2015–2022：电商高潮、全球收购与监管挑战 2015–2016年： 推出跨境电商考拉海购及自营品牌网易严选；在旧金山设立首个美国办公室。 2017年： 与漫威（Marvel）达成合作，出版中国超级英雄漫画；邮箱用户突破 9.4 亿。 2019年： 将考拉海购以 20 亿美元出售给阿里巴巴。 2020年： 在香港联交所完成二次上市。 2021–2022年： * 收购与扩张： 全资收购 Quantic Dream、草蜢工作室（Grasshopper Manufacture）；入股 Devolver Digital。 合作伙伴关系： 推出《永劫无间》及《暗黑破坏神：不朽》。 2023年1月： 由于授权协议到期，暴雪旗下游戏（除《暗黑破坏神：不朽》）在中国内地暂时停服。 2023–至今：AI 驱动、暴雪回归与海外收缩 2023年3月： 推出动画品牌 Anici。 2024年4月： 宣布与暴雪娱乐更新协议，暴雪游戏重返中国市场；同时与微软达成战略合作。 2024年12月： 自研游戏《漫威争锋》（Marvel Rivals）上线，前三天注册用户即突破 1000 万。 2024–2025年： 战略调整期。 为应对行业变化并与腾讯、米哈游竞争，网易开始收缩海外投资，关停包括 Ouka Studios、Jar of Sparks、Fantastic Pixel Castle 在内的多家海外工作室，寻求更小而精的全球布局。 2025–2026年： 发布《命运：崛起》等重磅作品；部分海外工作室（如名越工作室、GPTRACK50）转向自筹资金或自出版模式。 旗下公司及产品 主要产品 核心游戏： 经典自研： 《梦幻西游》、《大话西游》、《倩女幽魂》、《天下》、《逆水寒》（端手游）。 爆款竞技/生存： 《永劫无间》、《第五人格》、《蛋仔派对》、《荒野行动》、《明日之后》。 国际合作： 《漫威争锋》（Marvel Rivals）、《命运：崛起》（Destiny: Rising）、《暗黑破坏神：不朽》、《光·遇》（国内代理）。 通信与工具： 网易邮箱： 包含 163、126、Yeah.net，中国领先的电子邮件服务商。 网易有道： 包含有道词典、有道翻译、有道精品课等智能学习工具。 数字内容： 网易云音乐： 领先的音乐社区，主打独立音乐人扶持与歌单社交。 网易新闻： 门户网站 163.com 及移动端新闻客户端。 LOFTER： 泛兴趣创作社区（国内领先的同人/绘画社区）。 生活与电商： 网易严选： 自营生活方式品牌。 网易味央： 现代农业品牌（网易黑猪）。 核心工作室群 (NetEase Games) 国内事业群： 梦幻事业部： 负责核心西游 IP 运营。 雷火游戏/雷火事业群： 驻地杭州，代表作《倩女幽魂》、《逆水寒》、《永劫无间》。 互动娱乐事业群 (在线游戏)： 包含广州和上海等地的多个工作室，负责《阴阳师》、《第五人格》等。 国际工作室 (控股/全资)： Quantic Dream (法国)： 代表作《底特律：变人》，全资收购。 Grasshopper Manufacture (日本)： 由须田刚一领导，全资收购。 SkyBox Labs (加拿大)： 曾支持《光环》、《我的世界》开发，全资收购。 控股及关联公司 网易有道 (Youdao, Inc.)： 纽交所上市公司，网易控股。 云音乐 (Cloud Village Inc.)： 港交所上市公司，网易控股。 24 Entertainment： 《永劫无间》开发商，隶属于雷火事业群。 Mattel163： 与美泰 (Mattel) 合资成立，开发《UNO》等手游。 策略投资： 持有 Bungie（少数股权）、Devolver Digital（约 8% 股份）等全球游戏公司的股权。 招聘需求 招聘官网 岗位很少,均为游戏相关,想去的话注意校招的时候抓紧投递 华为 公司概况 华为（Huawei）是一家成立于1987年、总部位于深圳的全球通信技术巨头，由任正非创立。公司业务涵盖电信网络基础设施、智能终端、云服务、智能汽车解决方案及光伏产品，是全球最大的电信设备制造商。尽管近年来面临严峻的美国制裁与国际准入限制，华为通过剥离荣耀品牌、自主研发鸿蒙系统（HarmonyOS）及麒麟芯片实现战略突围，2025年起重回中国智能手机市场前列。作为技术领先的“国家冠军”企业，华为正全面发力 5.5G 通信、全栈 AI 算力及汽车智能化转型。\n真正的国家队,基岩后台,横跨手机,汽车,通信,芯片等硬件领域 公司历史 wiki 华为发展历史时间线 (1987–2026) 1987–1995：初创与农村包围城市 1987年： 前解放军团级干部任正非在深圳创立华为，注册资本 2.1 万人民币。初期业务为代销香港交换机（PBX）。 1992–1993年： 确立“农村包围城市”战略，避开国际巨头（阿尔卡特、朗讯）直面竞争。1993 年发布 C\u0026amp;C08 数字程控交换机，打破国外垄断。 1994年： 任正非提出“交换机技术关乎国家安全”观点，获得国家高层认可。 1996–2009：全球扩张与技术积淀 1996年： 被政府和军队确立为“国家冠军”企业，获得政策支持并限制海外竞争对手进入。 1997–1998年： 开启全球扩张，从非洲和中东起步；1999 年在印度班加罗尔建立 R\u0026amp;D 中心。 2003–2007年： 先后与 3Com（成立 H3C）、赛门铁克成立合资公司；2005 年海外合同首次超过国内销售额。 2009年： 在挪威奥斯陆交付全球首个 LTE/EPC 商业网络。 2010–2020：登顶全球与制裁冲击 2010年： 首次入选《财富》世界 500 强。 2012年： 超越爱立信成为全球最大电信设备商。 2018–2019年： 美国启动制裁；CFO 孟晚舟在加拿大被拘。华为发布 鸿蒙系统 (HarmonyOS) 应对安卓禁令；将荣耀（Honor）品牌出售以求生存。 2020年Q2： 华为手机出货量首次超越三星，登顶全球第一。 2021–2024：战略突围与重回巅峰 2021–2022年： 业绩因制裁大幅下滑；孟晚舟获释回国。华为加码 R\u0026amp;D 投入，位列全球第二。 2023–2024年： 实现供应链去美化，国产零件替代成功。2024 年 Q1 利润同比增长近 6 倍；6 月宣布 HarmonyOS 装机量突破 9 亿台。 2024年： 萝卜快跑及其他无人驾驶技术落地，华为智能汽车解决方案（华为系车型）市场爆发。 2025–至今：全球合规争议与 AI 算力布局 2025年3–4月： 遭遇欧洲合规危机。因涉嫌贿赂、伪造和洗钱，华为说客被禁止进入欧洲议会，欧盟委员会宣布不再会见华为相关利益代表。 2026年： 保持全球 PCT 专利申请量第一（连续 8 年）。华为全栈 AI 算力集群（昇腾系列）成为中国大模型训练的核心基础设施。 旗下公司及产品 1. 终端与生态 智能手机： Mate 系列（商务旗舰）、Pura 系列（原 P 系列，影像旗舰）、Nova 系列。 操作系统： HarmonyOS（手机/全场景）、OpenHarmony（开源底座）、EulerOS（服务器）。 硬件： 平板电脑（MatePad）、笔记本（MateBook）、智能穿戴及全屋智能。 2. 华为智能汽车解决方案 (Huawei Inside / 鸿蒙智行) 问界 (AITO)： 与赛力斯合作。 智界 (Luxeed)： 与奇瑞合作。 享界 (Stelato)： 与北汽合作。 自研技术： 乾崑 (Qiankun) 智驾、ADS 3.0 高阶辅助驾驶、鸿蒙座舱。 3. 运营商与企业业务 5.5G (5G-A)： 推动 5G 向万兆速率演进。 华为云 (Huawei Cloud)： 提供基础设施及盘古大模型（Pangu Models）。 昇腾 (Ascend) \u0026amp; 鲲鹏 (Kunpeng)： 自研 AI 算力芯片及通用计算芯片。 4. 其它领域 华为数字能源： 智能光伏、车载充电系统。 海思 (HiSilicon)： 负责核心半导体芯片设计。 招聘需求 招聘官网 社会招聘的技术岗位基本为零\u0026hellip;看得出来是很不缺人了,待遇真的好到大家都不愿意跳槽吗😃 快手 公司概况 快手科技（Kuaishou Technology）是一家成立于2011年、总部位于北京海淀区的中国上市科技公司（由宿华和程一笑创立）。公司主要通过同名短视频 App、社交网络及视频特效编辑工具提供服务，是中国领先的短视频与直播平台之一。在海外市场，该应用以 Kwai 之名运营，而在印度、巴基斯坦及印度尼西亚等地区则被称为 Snack Video。目前，快手已在香港联交所上市，且包含部分国有股份。\n公司历史 Wiki 2011–2013：从 GIF 工具到短视频转型 2011年3月： 曾在谷歌和百度工作的工程师宿华与程一笑在北京创立“GIF 快手”，最初是一款制作和分享 GIF 图片的工具。 2013年： 转型为短视频社交平台，用户数迅速突破 1 亿。 2014–2020：流量爆发与国际化受挫 2017–2018年： 获得腾讯领投的 3.5 亿美元融资，估值达 180 亿美元。2018 年因涉及未成年妈妈等低俗内容被央视曝光并短暂下架整改。 2019年： 日活用户突破 2 亿；与《人民日报》达成 AI 技术合作。 2020年： 受中印边境冲突影响，海外版 Kwai 在印度被禁。 2021–2023：港股上市与裁员重组 2021年2月： 在香港联交所上市，首日股价飙升 194%，市值一度突破 1590 亿美元。 2021年12月： 受监管环境及业绩压力影响，股价从巅峰下跌近 80%。公司启动大规模重组，裁员 30%，主要针对高薪中层。 2022年10月： 北京广播电视台入股快手，快手正式成为部分国有的控股公司。 2024–2025：AI 突破与安全危机 2024年4-6月： * 媒体曝光其内部代号为“石灰石”的裁员计划，被指清退 35 岁以上高龄员工。 进军巴西市场，在圣保罗建立分支中心。 技术飞跃： 发布视频生成大模型 Kling（可灵），支持生成 2 分钟 1080p 视频，性能对标 OpenAI 的 Sora。 2025年12月： 遭受网络攻击，导致平台短时间内涌入大量违禁暴力与色情内容。 核心争议与标签 年龄歧视： 内部“石灰石”计划被指针对 30 岁中后期员工进行结构性优化。 内容监管： 历史上多次因低俗内容引发监管关注，并曾遭受严重的黑客攻击导致内容失控。 资本背景： 拥有腾讯等头部互联网资本支持，并包含地方官方媒体的国有股份。 旗下产品 快手/快手极速版： 国内核心短视频与直播电商平台，以“老铁文化”和高互动率为特征。 Kwai / Snack Video： 负责拉美（巴西为主）及东南亚市场的国际化拓展。 快影 (KwaiCut)： 视频剪辑工具，目前是可灵（Kling）大模型的主要入口。 可灵 (Kling)： 领先的 Diffusion Transformer 架构视频生成模型。 招聘需求 米哈游 公司概况 米哈游（miHoYo）是一家长安成立于 2012 年、总部位于上海的全球领先电子游戏开发商。公司以开发《原神》、《崩坏》系列、《未定事件簿》及《绝区零》等高产值、全球化运作的抽卡类（Gacha）游戏闻名，业务范围涵盖动画、小说、漫画、音乐及周边产品。凭借《原神》获得的巨大全球成功，公司于 2022 年在新加坡设立全球发行品牌 HoYoverse（其运营实体为 Cognosphere），负责中国大陆以外市场的全球化内容制作与发行，目前在蒙特利尔、洛杉矶、东京及首尔设有办事处。\nThe letters \u0026ldquo;H\u0026rdquo; and \u0026ldquo;Y\u0026rdquo; in miHoYo\u0026rsquo;s name are derived from the names of two of the three founders, Cai Haoyu and Luo Yuhao. The letter \u0026ldquo;O\u0026rdquo; was then added because famous companies like Facebook, Google and Microsoft contain that letter. Since the combination \u0026ldquo;HoYo\u0026rdquo; was already registered, the prefix \u0026ldquo;mi\u0026rdquo; was prepended to the name. The prefix \u0026ldquo;mi\u0026rdquo; was inspired by the VOCALOID software Hatsune Miku, who was chosen due to her widespread popularity among the otaku community.\n这名字到底是怎么想出来的\u0026hellip; 公司历史 wiki 2011–2012：技术宅男的宿舍创业 2011年： 上海交通大学学生蔡浩宇、刘伟和罗宇皓在宿舍创立 miHoYo 工作室，核心理念为“技术宅拯救世界”。同年发布首款移动游戏 《FlyMe2theMoon》，主角为琪亚娜·卡斯兰娜（Kiana）。 2012年2月： 上海米哈游网络科技股份有限公司正式成立。随后发布横版射击游戏《崩坏学园》（Zombiegal Kawaii）。 2013–2016：崩坏 IP 的确立 2014年： 《崩坏学园2》（Guns Girl Z）上线并走红，为公司积累了早期核心粉丝。同年启动《崩坏3》项目。 2016年10月： 《崩坏3》（Honkai Impact 3rd）发布，凭借 3D 渲染技术和极致的打击感，米哈游实现了从 2D 到 3D 的技术跨越，并开始走向亚洲市场。 2017–2020：破圈与《原神》奇迹 2017年： 启动开放世界项目《原神》。同年申请 A 股 IPO，因过度依赖单一 IP 等原因面临质疑。 2018年： 推出官方社区 米游社（Miyoushe）。 2020年： * 撤回 IPO 申请，保持私人控股。 7月发布乙女向律政手游《未定事件簿》。 9月28日： 《原神》（Genshin Impact）全球全平台同步上线，迅速成为全球收入最高的移动游戏之一，标志着米哈游跻身世界顶级开发商行列。 2021–2024：全球化布局与多赛道并发 2021年： 员工人数突破 1000 人。虚拟偶像 鹿鸣 (Lumi) 走红。 2022年2月： 推出全球发行品牌 HoYoverse（新加坡运营），正式确立全球化战略。 2023年： * 4月发布银河冒险回合制游戏 《崩坏：星穹铁道》，市场表现极佳。 9月蔡浩宇卸任董事长，由刘伟接任。蔡浩宇转向北美专注于 AI 与前沿技术研究。 2024年7月： 都市动作冒险游戏 《绝区零》（Zenless Zone Zero）全球公测。 2025–2026：法律维权与 AI 深耕 2025年： 米哈游通过打击内鬼（泄密者）诉讼获得约 538 万美元赔偿金。 2026年2月： 在美国佐治亚州对著名内鬼 HomDGCat 提起诉讼，指控其非法获取并泄露未发布的测试版本内容（侵犯版权与窃取商业秘密）。 2026年： 鹿鸣 (Lumi) 停止活跃，公司资源进一步向 AI 模型研究及新项目整合。 核心标签与特质 技术驱动： 坚持自主研发引擎技术，将 3D 卡通渲染（Cel-shading）推向行业顶尖。 IP 宇宙： 围绕“崩坏”和“提瓦特”构建多媒体矩阵（动画、漫画、音乐、周边）。 独立性： 极少接受外部投资（仅有一笔 100 万人民币的天使投资），由创始团队绝对控股。 反泄密铁腕： 长期与“内鬼”进行法律博弈，通过 DMCA 警告和高额索赔维护商业秘密。 主要产品 崩坏系列： 《FlyMe2theMoon》、《崩坏学园2》、《崩坏3》、《崩坏：星穹铁道》。 开放世界： 《原神》（Genshin Impact）。 都市/动作： 《绝区零》（Zenless Zone Zero）。 女性向： 《未定事件簿》（Tears of Themis）。 虚拟技术： N0va Desktop（鹿鸣）、HoYoLAB（玩家社区）。 招聘需求 百度 公司概况 百度（Baidu）是一家成立于2000年、总部位于北京的全球领先人工智能及互联网服务巨头，由李彦宏和徐勇创立。作为中国搜索引擎市场的统治者，百度不仅拥有百度搜索、百度百科、爱奇艺及百度贴吧等核心互联网产品，更转型为拥有全栈AI技术实力的科技公司，业务涵盖自动驾驶（Apollo）、智能硬件（小度）及云计算基础设施。作为首家入选纳斯达克100指数的中国企业.\n曾经的头部企业,由于战略走向问题逐渐衰落,如今抱牢着人工智能和智能驾驶慢慢恢复生机,但是既然有这么多战略失误,而且方向都向智能驾驶靠拢了,所以建议放到第二梯队.\nAI总结的战略失误 百度在互联网时代的战略失误，常被外界总结为“错过了一个时代”或“在关键路口反复横跳”。其核心症结在于过早建立的搜索霸权带来的路径依赖，导致在多次行业重塑中落后。\n以下是百度公认的四大战略失误：\n1. 错过移动互联网的“入口门票” 失误点： 百度在移动化转型上极其迟钝。当阿里巴巴布局手机淘宝、腾讯孵化微信时，百度仍寄希望于通过“手机百度”浏览器承载一切。 后果： 移动时代形成了“App孤岛”，搜索不再是唯一入口。百度直到 2013 年才斥资 19 亿美元收购 91 无线，试图通过应用商店补票。但这被公认为一次昂贵的失败，因为随后 App Store 和各手机厂商自带商店迅速封死了第三方商店的空间。 2. O2O 战略的摇摆与收缩 失误点： 2015 年，李彦宏曾高调宣布“拿 200 亿砸向 O2O（百度外卖、糯米）”，试图将搜索与生活服务闭环。 后果： 在与美团、饿了么的烧钱大战中，百度由于内部资源整合不力及战略耐性不足，最终在 2017 年前后全面退缩，卖掉外卖业务。这导致百度失去了继搜索之后最真实的用户消费场景和支付数据。 3. 短视频与内容生态的缺位 失误点： 百度拥有强大的技术基因，却缺乏产品经理基因。在短视频爆发前夕，百度并未利用其搜索带来的巨大流量孵化出类似抖音、快手的短视频应用。 后果： 当用户的信息获取习惯从“搜文字”转向“看短视频”时，百度流量遭到字节跳动等对手的结构性打击。虽然百度后来补齐了好看视频和百家号，但已无法撼动已成型的市场格局。 4. “医疗广告”对品牌护城河的侵蚀 失误点： 过度依赖医疗竞价排名带来的高利润。2016 年的“魏则西事件”成为百度的品牌分水岭。 后果： 这次危机不仅导致了严格的监管处罚，更重创了公众对百度的信任。这种品牌负资产使得百度在后来的产品推广中（如 AI、自动驾驶）背负了极高的心理信任成本。 总结 百度的技术基因使其在 AI 领域领先（如 2017 年起全力押注 Apollo 和大模型），但产品化能力的缺失和对核心营收（广告）的过度依赖，使其在过去十年中不断面临“赢了技术、输了场景”的尴尬局面。\n直到 2024-2025 年，随着萝卜快跑（Robotaxi）和文心大模型（ERNIE）在 B 端与 C 端的落地，百度才算真正找到了摆脱“搜索路径依赖”的第二增长曲线。\n公司历史 Wiki 1994–2004：RankDex 算法与搜索引擎诞生 1996年： 李彦宏在 IDD 期间开发了 RankDex 站点评分算法，这是全球首个利用超链接评估网站质量的引擎，比谷歌的 PageRank 早两年。 2000年1月18日： 李彦宏与徐勇在北京创立百度。 2001年： 推出竞价排名（竞标广告位），早于谷歌采用类似的广告模式。 2003年： 上线新闻搜索和图片搜索，利用识别技术对文章进行自动聚类。 2005–2016：纳斯达克上市与移动转型 2005年8月5日： 百度在纳斯达克挂牌上市，成为当时美股市场的焦点。 2007–2008年： 成为首个获得新闻发布牌照的搜索平台；启动日本搜索业务（后于2015年关闭）。 2010年1月： 遭遇著名的 DNS 劫持攻击，导致服务中断四小时。 2013年： 以 18.5 亿美元收购 91 无线，创下当时中国互联网史上最大并购案；收购纵横中文网成立百度文学。 2014年： 吴恩达（Andrew Ng）加盟担任首席科学家，百度研究院开始发力人工智能（AI）。 2017–2022：自动驾驶与 AI 转型 2017年4月： 推出 Apollo（阿波罗） 自动驾驶开放平台，正式进军智能驾驶领域。 2017年9月： 联合阿里、腾讯等注资 120 亿美元入股中国联通。 2018年： 剥离全球移动应用业务（如 ES 文件浏览器），更名为 DO Global 独立运营；被中国政府指定为“人工智能国家队”。 2021年3月： 在香港联交所完成二次上市。 2022年6月： 旗下集度汽车（Jidu Auto）发布首款概念车 ROBO-01。 2023–至今：大模型爆发与全球布局 2023年8月： 正式向公众发布大语言模型 文心一言（Ernie Bot）。 2024年4月： 自动驾驶出行平台“萝卜快跑（Apollo Go）”累计订单突破 600 万单，在武汉部署超 400 辆无人车。 2025年3月： 发布 文心大模型 4.5 及推理模型 ERNIE X1，宣称在性能对标 DeepSeek R1 的同时成本仅为一半。 2025年7月： 与 Uber 达成合作，计划将萝卜快跑（Apollo Go）部署至美国和中国内地以外的市场。 2026年1月： 百度正式向港交所提交申请，拆分其半导体业务子公司 昆仑芯（Kunlunxin） 独立上市。 旗下公司及产品 1. 移动生态与人工智能应用 百度 App： 旗舰产品，集成搜索、信息流、百家号、小程序等功能，目前深度嵌入 文心一言 (ERNIE Bot) 实现 AI 原生搜索。 文心大模型 (ERNIE)： 文心一言 5.0： 2026 年 1 月发布的下一代原生多模态大模型，支持文字、视觉、音频的统一理解与生成。 ERNIE X1： 高性能推理模型，主打逻辑推理与深度思考（Deep Thinking），对标全球顶尖推理模型。 百度百科 \u0026amp; 百度贴吧： 核心内容社区，国内最大的百科全书和基于兴趣的中文论坛。 爱奇艺 (iQIYI)： 控股的长视频流媒体平台。 2. 智能云与算力基础设施 百度智能云 (Baidu AI Cloud)： 国内市场份额领先的 AI 云供应商，主打 MaaS (Model-as-a-Service) 模型即服务，为企业提供大模型训练与部署环境。 昆仑芯 (Kunlunxin)： 百度旗下的半导体子公司，专注 AI 芯片（如 M100/M300 系列），已于 2026 年 1 月正式启动在港上市流程。 3. 智能驾驶与机器人 萝卜快跑 (Apollo Go)： 全球领先的无人驾驶出行服务平台。 规模： 截至 2026 年初，累计订单突破 2000 万单，在武汉、北京等地实现商业化盈利（Unit Economics Breakeven）。 全球布局： 与 Uber 达成全球战略合作，2026 年上半年开始在英国伦敦及中东迪拜测试及部署无人车车队。 Apollo RT6： 自研 Level 4 级量产无人车，采用无方向盘设计，单车成本降至 3 万美元（约 20 万人民币）以内。 4. 智能硬件与消费电子 小度 (Xiaodu)： 独立运营的智能硬件品牌。 智能屏与音箱： 搭载 DuerOS 系统的智能家居核心。 小度添添家庭智能机器人： 2025 年推出的全球首款大模型驱动的家庭机器人，具备情绪互动与护理功能。 AI 酒店解决方案 4.0： 2026 年 4 月发布，覆盖客房数突破 260 万间，并拓展至新加坡、泰国市场。 核心事业部架构 MEG (移动生态事业群)： 负责搜索、信息流、贴吧、百家号等。 ACG (智能云事业群)： 负责云计算、企业级 AI 服务、昆仑芯业务。 IDG (智能驾驶事业群)： 负责 Apollo 自动驾驶、萝卜快跑、智能交通。 SLG (智能生活事业群)： 即小度科技，负责智能硬件与语音助手。 招聘需求 面经1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ## 题目 \u0026gt; **题目来源**：https://www.nowcoder.com/feed/main/detail/d39aabc0debd4dba810b4b9671d54348 **1.基础题** - 有几种网络 Io 模型？ - 异步网络模型在什么场景下你了解有应用过？（回答了线程相关的场景） - 除了用线程完成，还有什么操作可以完成异步操作？ - 同步阻塞和同步非阻塞在Java层面怎么实现？（说前面网络io模型答得挺顺畅，具体实现细节还需要提升一下） - 描述一下一次完整的 Http 请求 - 知道的长连接有几种实现方式？ - 一个 Http 请求包含哪几部分内容？ **2.代码题** - 设计一个 HashSet（完全不会） **3.场景题** - 1T 的数据怎么加载到 200M 的内存中，并且找到两行一样的数据？ - Java 打开 1T 文件，第一部操作做什么？ - 用代码打开一个文件和用鼠标打开一个文件有什么区别？ 滴滴 公司概况 滴滴出行（DiDi）是一家成立于2012年、总部位于北京的全球领先移动出行科技公司，由程维创立。公司通过整合原滴滴打车与快的打车，并于2016年收购优步中国（Uber China），确立了在中国共享出行市场的统治地位，业务涵盖网约车、出租车、单车、汽车服务及自动驾驶等。尽管在2021年纽交所上市后经历了严峻的网络安全审查、退市及巨额罚款，滴滴于2023年初恢复新用户注册并重回正常运营，目前正持续深耕国内市场并积极拓展拉美等海外业务。\n负责打车业务,中国uber 公司历史 Wiki 2012–2014：初创与补贴大战 2012年6月： 程维在北京创立“滴滴打车”（小桔科技），最初仅提供出租车呼叫服务。随后获得腾讯 1500 万美元投资。 2014年： 与阿里巴巴支持的“快的打车”展开疯狂的补贴大战，争夺刚兴起的移动出行市场入口。 2015–2016：巨头合并与击退 Uber 2015年2月： 滴滴打车与快的打车宣布战略合并，成立“滴滴快的”，占据国内出租车打车市场 99% 的份额。随后更名为“滴滴出行”。 2016年8月： 滴滴宣布收购 优步中国 (Uber China)。交易完成后，Uber 获得滴滴 17.7% 的经济权益。此举标志着滴滴彻底终结了中国网约车市场的混战，确立了绝对统治地位。 2017–2020：安全危机与整改 2017年： 完成多轮巨额融资（包括软银、苹果投资），公司估值达到 500 亿美元。 2018年： 发生两起“顺风车”乘客遇害严重安全事件。滴滴随后无限期下线顺风车业务（后逐步恢复），并投入 2000 万美元用于安全系统升级，设立 8000 人客服团队。 2018年： 启动“红旗方向盘”计划，招募 1000 名党员司机以增强合规与安全性。 2021–2022：监管风暴与纽交所退市 2021年6月30日： 滴滴在纽约证券交易所 (NYSE) 挂牌上市。 2021年7月： 上市仅数日，因违反《网络安全法》等法规，滴滴被国家网信办（CAC）实施网络安全审查，旗下 25 款应用被要求下架。 2022年6月： 滴滴正式从纽交所退市。同年 7 月，网信办对其处以 80.26 亿元人民币巨额罚款。 2023–至今：恢复增长与造车业务整合 2023年1月： 经报网络安全审查办公室同意，滴滴恢复“新用户注册”。 2023年8月： 滴滴策略性收缩造车业务。将旗下智能电动汽车（Mona 项目）及自动驾驶资产以约 7.44 亿美元对价出售给 小鹏汽车 (XPeng)，并成为小鹏的战略股东。 2024年5月： 联合创始人柳青 (Jean Liu) 辞去总裁及董事职务，转任“永久合伙人”及首席人才官，公司不再设总裁职位。 2025–2026年： 滴滴持续深耕海外市场（如拉美、非洲），并加速与小鹏、比亚迪在定制网约车及自动驾驶技术上的协同落地。 旗下业务与产品板块 核心出行： 滴滴快车、滴滴优享、滴滴专车、滴滴豪华车。 公共出行： 滴滴出租车、滴滴公交（路线查询及定制巴士）。 两轮车： 青桔单车、青桔电单车。 汽车服务： 小桔能源（充电、加油）、养车、租赁及二手车。 自动驾驶： DiDi Autonomous Driving（已与小鹏、广汽等深入合作落地 L4 级技术）。 海外业务： 覆盖巴西（收购 99 平台）、墨西哥、智利、南非、日本等多个国际市场。 招聘需求 小米 公司概况 小米（Xiaomi）是一家成立于2010年、总部位于北京的全球顶尖科技巨头，由雷军及其合伙人创立。公司以智能手机、互联网物联网（IoT）平台及电动汽车为核心业务，凭借高性价比策略和独特的粉丝文化迅速崛起，2025年已稳居全球第三大智能手机厂商，并成为《财富》世界500强中最年轻的企业。通过自研的澎湃 OS（HyperOS）及庞大的生态链系统，小米构建了涵盖家电、穿戴设备及智能出行的全方位科技生态。\n手机和汽车厂商,计算机的只招ML和硬件领域的,同样放在第二梯队 公司历史 Wiki 2010–2013：初创与“互联网手机”风暴 2010年4月6日： 雷军联合林斌（原谷歌）、周光平（原摩托罗拉）等七人于北京创立小米。 2010年8月： 发布基于 Android 的自研系统 MIUI（现已演进为 HyperOS）。 2011年8月： 首款手机 小米1 发布，以 1999 元的颠覆性价格和“为发烧而生”的定位开启互联网手机时代。 2013年： 引入谷歌副总裁 Hugo Barra 负责国际化；同年发布小米3及首款小米智能电视。 2014–2020：生态链扩张与港股上市 2014年： 以 360 万美元购买 mi.com 域名；小米成为中国市场份额第一的手机品牌，估值达 450 亿美元。 2015–2017年： 经历巴西扩张受挫及国内市场调整；2017 年在印度市场超越三星夺冠；确立“小米生态链”模式，投资石头科技等初创公司。 2018年7月： 在香港联交所正式挂牌上市。随后推出游戏手机品牌黑鲨（Black Shark）及高性价比品牌 POCO。 2020年： 小米手机出货量升至全球第三；发布首款折叠屏手机 Mi Mix Fold。 2021–2024：造车战略与高端化转型 2021年3月： 雷军宣布“人生最后一次重大创业”——投入 100 亿美元进军电动汽车领域。同时更换由原研哉设计的“超椭圆”新 Logo。 2022–2023年： 成立半导体公司芯片研发团队；与徕卡（Leica）达成影像战略合作；与华为达成 5G 等通信技术全球专利交叉许可协议。 2024年： 首款电动轿车 小米 SU7 正式上市并交付，凭借强大的生态连接能力迅速引爆汽车市场。 2025–至今：自研芯片、AI 大模型与 SUV 落地 2025年4-5月： 进军大模型领域，推出 Xiaomi MiMo；5月正式发布自研 3nm 工艺手机处理器芯片 XRING O1，首搭于小米 15S Pro。 2025年6月： 首款纯电 SUV 车型 小米 YU7 正式上市。 2025年10月： 推出短剧应用“微观短剧”，探索内容分发新赛道。 2026年4月： 发布万亿参数大语言模型 MiMo-V2-Pro；雷军宣布未来三年将投入至少 87 亿美元 深耕人工智能领域。 旗下公司及产品 1. 智能终端 手机： 小米系列（高端/影像）、红米（Redmi，高性价比）、POCO（海外）。 可穿戴设备： 小米手环、小米手表、耳机。 自研芯片： 澎湃 (Surge) 系列（电源管理、影像处理）、XRING 系列（核心 SoC）。 2. 小米汽车 (Xiaomi EV) 小米 SU7： 纯电 C 级高性能轿车。 小米 YU7： 2025 年发布的纯电 SUV。 核心技术： 超级电机 (HyperEngine)、自研 CTB 电池技术、全栈自研自动驾驶系统（Deepmotion 收购支持）。 3. AI 与软件生态 HyperOS (澎湃 OS)： 贯穿手机、人、家、车的全生态操作系统。 MiMo 系列大模型： 涵盖轻量化端侧模型及万亿参数专业模型。 4. 生态链控股/参股公司 智米 (Smartmi)： 空气净化器、空调等。 石头科技 (Roborock)： 扫地机器人。 华米 (Zepp Health)： 智能穿戴。 九号公司 (Segway-Ninebot)： 平衡车、电动滑板车。 半导体布局： 珠海芯试半导体、湖北小米长江产业基金投资的多家国产芯片厂商。 招聘需求 得物 公司概况 得物（Poizon）是一家成立于2015年、总部位于上海的全球领先潮流网购社区，由杨冰创立（原虎扑联合创始人）。公司最初作为潮流资讯社区起步，后通过首创“先鉴别，后发货”的购物流程，解决了球鞋及奢侈品行业的真伪痛点，确立了其在中国年轻人潮流消费市场的领导地位。业务涵盖球鞋、服装、美妆、数码以及二手交易等。\n公司历史 Wiki 2015–2017：社区起步与资讯积累 2015年7月： 杨冰在虎扑内部孵化出“毒”App，最初定位为纯粹的潮流资讯与球鞋鉴别社区。 2017年： 正式引入电商交易模式，首创“先鉴别，后发货”的商业逻辑，从资讯平台转型为闭环交易平台。 2018–2019：爆发式增长与融资潮 2018年： 获得红杉中国、普思资本等多轮投资，平台交易额（GMV）进入高速增长期，成为独角兽企业。 2019年： 完成由 DST Global 领投的 A轮融资，估值达到 10 亿美元。平台通过“球鞋指数”及透明的交易机制，深刻影响了国内球鞋二级市场的定价体系。 2020–2022：品牌升级与品类扩张 2020年1月： “毒”App正式更名为“得物 (Poizon)”，旨在淡化单一的球鞋标签，向全品类潮流生活方式平台迈进。 2021年： 大力拓展美妆、数码、潮玩、艺术品等非鞋类业务，并吸引超过数千家国内外知名品牌入驻开设官方旗舰店。 2022年： 建立全球规模领先的查验鉴别中心，通过计算机视觉、AI 辅助与人工复核相结合，进一步提升查验效率与准确性。 2023–至今：全球化布局与技术深耕 2023年： 加速海外版 Poizon 的运营，通过跨境电商模式将中国潮流设计推向全球市场。 2024年： 进一步强化社区内容生态，投入数亿元扶持潮流博主，强化“社交+电商”的双轮驱动模式。 2025–2026年： 持续优化“鉴别实验室”技术体系，探索 AR 试穿、VR 购物等前沿交互体验，并与头部品牌深度联动开发独家限量单品。 旗下业务与产品板块 核心电商： 得物 App（涵盖球鞋、服装、箱包、配饰等全品类）。 技术创新： Poizon Lab（专注于商品鉴别算法、3D 建模及数字水印技术）。 海外业务： Poizon Global（覆盖北美、欧洲及东南亚市场，提供全球化的潮流消费体验）。 招聘需求 面经1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ## 题目 \u0026gt; 题目来源：https://www.nowcoder.com/discuss/525371909735792640 **（1）八股：** - 生产场景下什么时候用 ArrayList ，什么时候用 LinkedList - 创建线程的方式 - 为什么 volatile 能保证多线程可见 - 在并发量特别高的情况下是使用 synchronized 还是 ReentrantLock - 说一下 ConcurrentHashMap 中并发安全的实现 - 你说高并发下 ReentrantLock 性能比 synchronized 高，那为什么 ConcurrentHashMap 在 JDK 1.7 中要用 ReentrantLock，而 ConcurrentHashMap 在 JDK 1.8 要用 Synchronized - 有哪些并发安全的实现方式（简单讲了下 JUC） **（2）场景题：** - 不用 ThreadLocal 你会想用什么方式存用户信息 - 有千万级数据，如何判断一个整数是否存在（布隆过滤器） - 如何理解：布隆过滤器说某个元素存在，则大概率在。布隆过滤器说某个元素不在，则一定不在 - 千万级数据用布隆过滤器初始化的时候 redis 太慢了，有没有什么好方法（RDB 的 bgsave） - 多线程间如何传值（volatile修饰共享变量、阻塞队列、ThreadLocal） - 如何设计登陆黑名单 **（3）手撕：面试官说看我笔试AK了，就不出手撕了** 拼多多 公司概况 拼多多（Pinduoduo）是拼多多控股（PDD Holdings）旗下的核心电商平台，由黄峥于2015年在上海创立。通过开创性的“拼购”社交电商模式和对下沉市场及农产品供应链的深耕，拼多多在阿里与京东的垄断中迅速崛起，并在2020年成为中国用户规模最大的电商平台。2023年公司更名为拼多多控股，并将法律总部迁至爱尔兰都柏林，同时推出全球化平台 Temu。截至2026年初，凭借“C2M”模式和全球化的半托管策略，PDD Holdings 已成为全球利润率最高的零售实体之一，2025年营收突破617亿美元，其现金储备甚至一度超越阿里巴巴。\n一家初创公司能够在非常成熟的电商领域里,在阿里和京东两大巨头的垄断下,杀出一条血路,用的手段一定非常肮脏 公司历史 Wiki 2015–2017：差异化崛起 2015年： 软件工程师黄峥创立拼多多，初期深耕农业领域，在阿里与京东垄断的电商市场中通过“社交拼团”模式寻找生存空间。 2017年： 凭借微信生态的病毒式传播迅速扩张，触达传统电商忽视的下沉市场和低收入群体。 2018–2020：纳斯达克上市与用户增长神话 2018年7月： 拼多多在纳斯达克 (NASDAQ) 上市。随后因假货和“山寨”产品问题遭遇中国监管部门（SAMR）调查及卖家抗议。 2019年： 推出“百亿补贴”战略，试图扭转品牌形象并吸引一二线城市用户。 2020年： * 疫情期间推出“多多买菜”，进军社区团购。 上线“多多批发” B2B 平台。 里程碑： 年度活跃买家数达 7.88 亿，首次超越阿里巴巴成为中国用户规模最大的购物 App。 2021–2023：全球化与总部迁移 2021年： GMV（成交总额）达到 4.17 万亿人民币。黄峥辞任董事长。 2022年9月： 姊妹公司 Temu 在美国上线，开启全球化扩张。 2023年： PDD Holdings 将法律总部（注册地）从上海迁至爱尔兰都柏林。同年，主站 App 因存在利用 Android 漏洞的恶意代码风险被谷歌下架。 2024–至今：合规压力与东南亚版图 2024年： * 因强迫劳动质疑和知识产权盗窃指控，Temu 在美国遭遇严厉审查，多个州对其提起消费欺诈诉讼。 内部“竞业协议”争议引发媒体关注，被指通过视频监控证据起诉跳槽员工。 2025年12月： 启动马来西亚战役，在当地推行免运费政策，加速深耕东南亚。 2026年： PDD Holdings 成为全球利润率最高的零售企业之一。 核心争议与挑战 假货与山寨： 长期被美国贸易代表办公室 (USTR) 列入“恶名市场”名单。尽管公司采取封店和 10 倍赔偿措施，但供应链监管压力持续存在。 网络安全与隐私： 2023 年被多家国际安全机构（如卡巴斯基、CNN 访谈团队）贴上“恶意软件”标签，指控其非法获取用户文件及通知权限。 劳工与竞业： 极高强度的工作节奏及针对基层员工的严苛竞业条款在行业内引发广泛争议。 旗下产品 拼多多 (Pinduoduo)： 国内 C2M 社交电商平台，核心竞争力在于农产品直采和低价策略。 Temu： 全球化跨境电商平台，通过“全托管/半托管”模式在北美、欧洲、东南亚极速扩张。 多多买菜： 高频生活服务入口，负责生鲜配送及社区自提业务。 招聘需求 自己搜吧\n总结 巨头分析 一步领先,步步领先,上述企业中的巨头很早就在市场的对应领域上建立了统治地位,并且屹立十年/二十年/三十年不倒,可想而知,不发生什么特别离谱的事情的话,这些巨头将会持续存在下去,并将业务越做越大,不断蚕食和合并初创公司和小型企业,就像韩国的三星那样垄断了多个特定区域/领域的市场.\n而这些巨头也有自己的本钱:\n高投入的科研机构 顶尖人才的聚集 庞大的产业帝国 不必打广告就人尽皆知的名气 一家初创公司如果不是瞄准某个特别刁钻的痛点,如果没有大量资金的投入,如果没有完美的运营方式,是不太可能与这些巨头抗衡的,只能在角落瑟瑟发抖.\n内地公司分析 内地的互联网公司起步普遍比外企晚了二十年以上,比得过外企反而是不正常的.\n组织架构上也都比较稚嫩,历史底蕴也不深,很多公司的领导人至今都没变过,技术积累也不多,产业链也不够庞大,不少还保留着暴发户的气质,有点强盗/小偷风范.\n由于政府的支持和内地的特殊环境,内地企业能够轻松抵御巨头的外来影响,统治本土市场,但想要向外推广通常都是举步维艰的.\n","date":"2026-04-23T09:00:00Z","image":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E4%BA%8C-%E5%A4%A7%E5%9E%8B%E4%BA%92%E8%81%94%E7%BD%91%E5%85%AC%E5%8F%B8%E8%B0%83%E7%A0%94/83088427_p0-%E6%B0%B4%E6%97%8F%E9%A4%A8.webp","permalink":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E4%BA%8C-%E5%A4%A7%E5%9E%8B%E4%BA%92%E8%81%94%E7%BD%91%E5%85%AC%E5%8F%B8%E8%B0%83%E7%A0%94/","title":"面向面试学习(二)-大型互联网公司调研"},{"content":"cpp学习 学习cpp的难点有二:\n特性多 系统化资料少,垃圾资料多 正是因为特性多,所以系统化讲述cpp特性的资料特别少,就算有也都讲的不深.需要到处搜集二手/三手信息,从而导致大多数人对于cpp的理解都是破碎不堪的.\n接下来我会尽量通过cpp官网的文档来学习.\ncpp历史 学习一门语言之前,我们必须要去了解它的历史,如果你连它的重要性都不知道的话,那又为什么要去学呢\nwiki 1. 诞生背景与“带类的 C”（1979 - 1982） Bjarne Stroustrup 在贝尔实验室工作期间，因分析 UNIX 内核需要，试图结合 Simula 的抽象能力与 C 的高效性能。\n1979年：开始研发 “C with Classes”（C++ 的前身）。 核心特性：引入类（Classes）、继承（Derived Classes）、强类型检查、内联函数（Inlining）和默认参数。 2. C++ 奠基时代（1983 - 1991） 1983年：正式更名为 C++（利用 C 语言的自增运算符 ++，寓意 C 的进化）。 1984年：实现首个 流输入/输出库（Stream I/O）。 1985年： 发布经典著作 《The C++ Programming Language》 第一版。 首个商业化 C++ 编译器正式面世。 1989年 (C++ 2.0)：引入多重继承、抽象类、静态成员函数、const 成员函数以及 protected 访问权限。 1990年：发布《The Annotated C++ Reference Manual》，为后续标准化工作奠定逻辑框架。 3. 标准化与工业成熟期（1998 - 2003） 1998年 (C++98)：第一个国际标准 ISO/IEC 14882:1998 发布。 里程碑特性：模板（Templates）、异常处理、命名空间（Namespaces）、布尔类型、STL（标准模板库）正式入标。 2003年 (C++03)：一个主要针对 C++98 的技术修正版本，修复了大量编译器实现层面的细节问题，稳定性提升。 4. 现代 C++ 复兴（2011 - 2020） 在经历了长达 8 年的停滞后，C++ 进入了高频迭代周期：\n2011年 (C++11)：具有划时代意义的“新 C++”。 核心特性：自动类型推导（auto）、Lambda 表达式、右值引用（Rvalue references）与移动语义、智能指针、基于范围的 for 循环。 2014年 (C++14)：对 C++11 的微调与完善，增强了泛型 Lambda 和 constexpr 的能力。 2017年 (C++17)：引入结构化绑定、std::optional、std::variant、文件系统库（Filesystem API）以及对并行的原生支持。 2020年 (C++20)：被称为继 C++11 后最大的变革。 四大支柱：概念（Concepts）、范围（Ranges）、协程（Coroutines）、模块（Modules）。 环境配置 市面上有三大主流编译器,分别是微软的MSVC(Microsoft Visual C++),被集成在VS中;开源的GCC(GNU Compiler Collection),主要在Linux中使用;基于GCC和LLVM(通用编译链)的Clang,由苹果公司赞助,是macOS唯一官方支持的编译器,集成在Xcode中.\n另外一个在Windows系统常用的MinGW编译器则是GCC的物理移植版 当然现在的电脑性能这么好,用哪个编译器都可以,下载后将对应的bin目录添加到环境变量即可. 基础语法 变量类型 官方文档 main函数 archive 关键字 const与volatile 参考 使用const修饰int x的时候,与单纯的写int x在内存区域上的表现没有什么不同,也就是说,const是一个编译时关键字,保证在运行时没有任何指令可以修改这块内存区域. Any attempt to modify a const object during its lifetime results in undefined behavior.\n而volatile强制编译器取消对这个变量的运行期优化,要求每次对这个变量的操作都实际发生在内存,但现在基本很难看见这个关键字了.\nstatic 来源 我们知道在函数内部定义的变量，当程序执行到它的定义处时，编译器为它在栈上分配空间，函数在栈上分配的空间在此函数执行结束时会释放掉，这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时，如何实现？ 最容易想到的方法是定义为全局的变量，但定义一个全局变量有许多缺点，最明显的缺点是破坏了此变量的访问范围（使得在此函数中定义的变量，不仅仅只受此函数控制）。static 关键字则可以很好的解决这个问题。\n另外，在 C++ 中，需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部，对外不可见时，可将其定义为静态数据。\nTL;DR: （1）在修饰变量的时候，static 修饰的静态局部变量只执行初始化一次，而且延长了局部变量的生命周期，直到程序运行结束以后才释放。 （2）static 修饰全局变量的时候，这个全局变量只能在本文件中访问，不能在其它文件中访问，即便是 extern 外部声明也不可以。 （3）static 修饰一个函数，则这个函数的只能在本文件中调用，不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区，包括全局静态变量和局部静态变量，都在全局数据区分配内存。初始化的时候自动初始化为 0。\nstatic_cast与其他的类型转换 参考 StackOverflow上的解答 当我们需要强制更改变量类型的时候,应该考虑到一点:\n如果直接在内存对应地址增加或者减小它的占用空间大小,显然会导致各种各样的内存问题. 因此,类型转换一般是不更改内存的,而是在编译时告诉编译器这个变量的类型改变了,请你按照改变后的类型来处理这个变量. 隐式类型转换是安全的，显式类型转换是有风险的，C语言之所以增加强制类型转换的语法，就是为了强调风险，让程序员意识到自己在做什么。\n但是，这种强调风险的方式还是比较粗放，粒度比较大，它并没有表明存在什么风险，风险程度如何。\n为了使潜在风险更加细化，使问题追溯更加方便，使书写格式更加规范，C++ 对类型转换进行了分类，并新增了四个关键字来予以支持，它们分别是：\n关键字 说明 static_cast 用于良性转换，一般不会导致意外发生，风险很低。 const_cast 用于 const 与非 const、volatile 与非 volatile 之间的转换。 reinterpret_cast 高度危险的转换，这种转换仅仅是对二进制位的重新解释，不会借助已有的转换规则对数据进行调整，但是可以实现最灵活的 C++ 类型转换。 dynamic_cast 用于多态和向下转型 constexpr decltype 指针与引用 指针实质 事实上,接触了差不多一年cpp,我还是没有彻底搞懂指针,教材上,网上讲的基本都是怎么用指针,简单的告诉你指针就是取地址,调用的时候就是解引用取得引用对象,但并没有告诉我,为什么这么写就能行. 试着阅读\u0026laquo; C++ Programming Language \u0026raquo;的第七章,里面是这么讲的:\n对于类型T来说,T是指向T的指针的类型,换句话说,T类型的变量能够存放T类型对象的地址.\n对指针的一个基本操作是解引用(dereferencing),即引用指针所指的对象,也被称为间接取值(indirection),解引用运算符为*. 比如:\n1 2 3 char c = \u0026#39;a\u0026#39;; char* p = \u0026amp;c; char c2 = *p; 但书上到这里就戛然而止了,相当于啥都没讲,并没有触及底层的设计理念.不过话说回来,指针是从c传下来的,不关cpp设计的事.🙂\n那么,为什么要这样写呢,也就是说,为什么不能直接写char p = \u0026amp;c;来存储地址,然后再把这个解引用运算符用来根据地址p来找到原来的c变量呢?\n思考一下,char p = \u0026amp;c只是定义了一个char变量而已,也就是规定死了为1字节长,这里由于没用unsigned char,故只能取到0-127的地址值,因为地址的绝对值显然不会为负值.\n显然,与其用char,我们不如用long long int类型来存储高达8字节的地址值,从而保证能存取足够多的地址,也就是这样写:\n1 2 char c = \u0026#39;a\u0026#39;; long long p =\u0026amp;c; 那么,取到地址之后我们又要怎么根据这个地址取到应该取的值呢?\n要知道,变量值是分布在一块连续内存上的,你并不知道这个地址的前后是什么,可能是程序的核心部分,也可能是上次运行后尚未清除的缓存垃圾.当然,我们可以根据p所引用的变量c的类型来判断要读取的连续字节数,比如在我们的例子中p对应的是char c,也就是说我们只需要在这个地址往后取一个字节,就可以找回这个变量c存储的值了.\n可是,上述的论述中有一个问题,那就是p只是存储了c的地址而已,它并不知道c的类型!那么,显然我们需要一种标识来取代单纯的long long p声明,这个标识还需要能够表明我所引用地址对应变量的类型. 比如,为了满足上述的要求,它可以写成类似long long char **mark**的形式,但显然这太长了,也太丑了\u0026hellip;\n但我们又可以想到,既然指针需要使用long long类型来保证取到尽可能多的地址,那么long long这个部分就可以省略了,上述例子从而简化成了char **mark**,让编译器根据这个mark来了解这是一个需要用long long来存储的指针变量.\n那么,问题就简化到了这个mark用什么符号来表示比较好.\n非常可惜的是,c语言的设计者们并没有想过将解引用符号*和取指针符号mark分开来表示,而是直接把mark定为了*,从而导致了学习c语言的无穷痛苦\u0026hellip;\n所以,回到这一句:\n1 char* p = \u0026amp;c; 我们现在可以很清楚的知道, 这个p是一个long long大小的变量,存储了char类型变量c的地址,尽管美中不足的是,这个标记*偏偏和解引用的*是同一个符号!\nvoid*,NULL与nullptr void*是一个指向void类型的指针,由于不存在有一个void类型的变量,故这个指针自然没有任何指向对象,也就无法进行解引用去取对象,无法进行算术运算.在使用时必须显式地转换成某一特定类型的指针.\n在实际生产中很少被用在上层设计中,多用于底层的资源调度 现在我们根据**__stddef_null.h**来看一看NULL\n1 2 3 4 5 6 7 8 9 #ifdef __cplusplus #if !defined(__MINGW32__) \u0026amp;\u0026amp; !defined(_MSC_VER) #define NULL __null #else #define NULL 0 #endif #else #define NULL ((void*)0) #endif 可以清楚的发现NULL事实上是一个宏,有时候是0,有时候是一个\u0026hellip;,(void*)0,这是什么东西? 还是从一个简单代码起步好了,我们知道,有时候需要将一个高位类型比如int,塞入一个低位类型比如char中,由于直接塞进去的话编译器会警告可能会丢失值,所以我们可以这样写:\n注意: static_cast我会在后面涉及,而且在C语言中我们只能这样强制转换. 1 2 int s = 999999999; char c = (char)s; 我们可以更进一步,加入指针试试:\n1 2 3 int* p = (int*)100; // 把数字 100 强制转换成 int* 类型 // 现在 p 指向的“地址”是 100 // 这是一个非常危险的操作，实际程序几乎永远不应该这么写 你可能会很好奇,这怎么就取到地址100了呢,真正取地址100,应该写成以下形式:\n1 int* p = \u0026amp;100; 但事实上这段代码会报错,因为\u0026amp;无法作用于100这样一个纯右值(可以理解为临时值). 当然你会说: 就算这样,我也不能接受(int*)100怎么就直接简单的变成地址100了! 我们可以这样理解,(int*)100必须得指向一个东西,因为如果不指向某个东西的话,说明它是一个类似于int* p这样没有赋值的野指针,但是(int*)100并不是这样,它是一个指向int类型的\u0026quot;右值100\u0026quot;,那么我不能随便让它指向某块区域,否则就会导致内存混乱,最好指向一个与它的内容\u0026quot;100\u0026quot;有关系的区域,那么在设计者的角度来看,自然是指向地址100比较好了,实际应用中的编译器也是这么处理的.(当然我的这段分析可能是错误的,甚至整个都错了,但是我们需要牢牢记住: (int*)100就是地址100!)\n经过上面的一大段分析,那么(void*)0就比较好理解了:将int类型的0强制转换成指向void类型的地址0,而地址0由于受到保护,故不能被解引用和运算,从而将NULL变成一个受保护的空指针.\n很显然,void*和NULL并不够直观和好用,所以c++11引入了nullptr这个关键字用来表示空指针,这里我没有给源码,是因为这个nullptr与int,double这些类型一样,是一个编译器硬编码的运行期对象,不存在用一个库来定义nullptr. 既然nullptr叫做空指针,那么自然无法给int,double这些普通类型变量赋值,而是只能给指针变量赋值:\n1 2 3 4 5 6 7 8 9 10 11 struct Node { int data; Node* left; Node* right; Node(int val) : data(val) , left(nullptr) , right(nullptr) {} }; 如果不赋与nullptr这个初始值,就会产生野指针,从而导致各种各样的内存问题.\n数组中的指针 当我们需要处理一个分组的数据比如{1,2,3,4,5}时,我们可以这样写:\n1 2 3 4 5 6 7 //{1,2,3,4,5} int s =12345; //将s作为存储变量 int s1=s/10000; int s2=s/1000%10; int s3=s/100%10; int s4=s/10%10; int s5=s%10; 自然,当数据量过大时这么写就有点过分了,显然不应该用某一个普通的int型或者long long型变量来存数据,而是应该转换思路,用一块连续内存来存数据,从而保证能够容纳足够大的数据量.\n因此可以引入一个新的符号,姑且称为x,我们希望这个x可以实现以下几个要求:\n初始值为这个存储变量s的首地址,对应的是存储对象列表的第一个元素 根据存储对象的类型(这里是int)得到相邻元素之间的内存距离 可以经过简单的数学运算获取任意一个元素的位置,这被称为随机存取 你有可能会想,怎么能有这么好的事可以一下子解决三个问题呢?\n但根据前面的讨论,我们知道:指针可以用来映射到一块特定的内存上,也就是说我们可以将指针对准这个存储变量的首地址,并且可以简单的对指针进行加减操作,从而取到下一个元素的地址甚至是任意一个元素的地址!所以,问题解决了.\n于是,我们应该可以这么写:\n1 int *s =mark(1,2,3,4,5); 很显然,这个mark应该能够做到以下事情:\n告诉编译器这是一个数据列表,而不是别的什么 为*s提供这个数据列表的首地址 尽管我们可以设计成类似$(1,2,3,4,5)或者#(1,2,3,4,5)这种比较正常的标记方式,但遗憾的是设计者想起来还有{}这个大括号没用过,于是将mark()变成了{}.(另一个好处是,在早期一个字符的开销也很重要的时候,可以减少表达式所用的字符).\n于是,我们或许可以这样写:\n1 2 3 4 5 6 7 int *s={1,2,3,4,5}; int s1 = *s; int s2 = *(s+1); int s3 = *(s+2); int s4 = *(s+3); int s5 = *(s+4); 但如果你试着去运行的话,这串代码一定会报错!因为设计者并没有让这个mark成功做到上述的两个事情,上述的一系列设想都是我们的一厢情愿.\n相反,{}只是一个构造器,没有任何返回值,必须需要等号左边部分的配合才可以填入值,而不能单独存在.\n如果你好奇为什么的话,我们可以这样想,当你写 {1,2,3,4,5} 时，这五个整数总得找个地方落脚:\n如果存放在栈（Stack）上，那么当函数执行完毕，这块内存就会被回收。此时你的指针 s 将变成一个恐怖的野指针。 如果存放在静态区（Data Segment），那么这块内存就是只读的，你无法在运行时修改它。 如果存放在**堆（Heap）**上，谁来负责 delete 它？C++ 的设计哲学是“不为不使用的东西付费”，这种隐式的内存分配违背了确定性。 因此,在将指针指向这个数据列表之前,我们需要先为这个数据列表分配一个合适的地址. 换句话说,我们还需要引入一个新类型变量来存储这个数据列表的地址,因为现有的变量类型是无法存入列表的.\n同时,我们应该让这个新类型能够规范数据列表内部的类型为单一的一种,因为如果这个列表内又有int,又有long long,在用指针访问元素时必然出现混乱.\n我们不妨写成这样:\n1 2 int mark(t) = {1,2,3,4,5}; int *s = t; 这个mark()出色的完成了以下两个任务:\n通知编译器划出该数据列表的空间 为*s提供这个数据列表的首地址 更加遗憾的是,设计者又想起来还有[]这个中括号没用过\u0026hellip;于是整个代码变成了我们熟悉的样子:\n1 2 3 4 5 6 7 8 int t[] = {1,2,3,4,5}; int *s = t; int s1 = *s; int s2 = *(s+1); int s3 = *(s+2); int s4 = *(s+3); int s5 = *(s+4); 当然,本着物尽其用的原则,[]空在那里显然不太好看,于是设计者动了点巧思,让它在初次定义的时候可以规定划定的空间大小,并且在定义之后,能作为解引用符号来访问特定元素:\n1 2 3 4 5 6 7 int t[5] = {1,2,3,4,5}; int s1 = t[0]; //t[0] 等价于 *(t+0) int s2 = t[1]; int s3 = t[2]; int s4 = t[3]; int s5 = t[4]; 尽管这个设计很精妙,但我的意见是,与其让一个符号承担多个责任,不如清楚的用不同符号区分责任,(我的意见自然是不重要的).\n值得一提的是数组退化问题:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 虽然形参写成 int arr[]，但在编译器眼里它就是 int* arr void process(int arr[]) { // 这里的 sizeof(arr) 返回的是指针的大小（通常是 8 字节），而不是数组的总大小 std::cout \u0026lt;\u0026lt; \u0026#34;函数内部 sizeof(arr): \u0026#34; \u0026lt;\u0026lt; sizeof(arr) \u0026lt;\u0026lt; \u0026#34; bytes\u0026#34; \u0026lt;\u0026lt; std::endl; // 通过指针偏移修改内存，会直接影响原数组 arr[0] = 99; } int main() { int my_array[5] = {1, 2, 3, 4, 5}; std::cout \u0026lt;\u0026lt; \u0026#34;函数外部 sizeof(my_array): \u0026#34; \u0026lt;\u0026lt; sizeof(my_array) \u0026lt;\u0026lt; \u0026#34; bytes\u0026#34; \u0026lt;\u0026lt; std::endl; // 传递数组名，触发退化：int[5] -\u0026gt; int* process(my_array); std::cout \u0026lt;\u0026lt; \u0026#34;修改后的首元素: \u0026#34; \u0026lt;\u0026lt; my_array[0] \u0026lt;\u0026lt; std::endl; return 0; } 当经过上述一系列讨论后,这个问题的答案就显然易见了,process()函数传入的是数组的首地址,那么就应该用指针int *arr来处理了,至于为什么形参可以写成int arr[]或者int arr[5],那可以理解为设计者还想让这个[]继续发光发热,既可以在形参中表示这是一个数组的首地址,还可以填入这个数组的预想空间大小-尽管实际运行时是不起作用的.\n综上所述,本来我们可以通过各种各样的标识来区分[]一个符号干的不同活儿,但遗憾的是cpp已经被设计成了这个样子了,那么只好随它去了.\nconst指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void f(char* p){ char s[]=\u0026#34;Gorm\u0026#34;; const char* pc = s; //指向常量的指针 pc[3] = \u0026#39;g\u0026#39;; //错误,pc指向常量 pc = p; //OK char* const cp = s; //指向char的常量指针 cp[3] = \u0026#39;a\u0026#39;; //OK cp = p; //错误,cp是一个常量 const char* const cpc = s; //指向常量的常量指针 cpc[3] = \u0026#39;a\u0026#39; //错误,cpc指向常量 cpc = p; //错误,cpc是一个常量 } 我们可以根据这段代码得出以下结论:\n当指针指向的变量使用const修饰时,无论指向的变量是否是常量,通过指针访问这个变量时都不允许做任何修改;但是,指针可以更改它指向的变量,也就是更改存储的地址内容 当指针被设定为常量时,其存储的地址内容不允许再被修改,也就是固定与初始化的变量绑定,但是可以通过指针修改绑定变量的内容 当常量指针指向常量时,既不能修改绑定变量,又不可以修改被绑定的变量的内容,怎么用都很安全 引用实质 由于指针过于复杂和难懂,我们希望找到另外一种简单的方式,解决跨越函数和文件更改变量值的问题,因此,我们引入了引用(\u0026amp;)这个概念. 当我们写出以下代码时:\n1 2 int a = 123; int \u0026amp;b = a; 应该有一个疑问: 变量b是什么?\n由于这里没有取地址符号,故b不是指针;它也不是一个新变量,因为当我们改变b的值的时候,a的值也会同步改变.\n那么,我们可以这样想:既然这个变量既不是一个指针,也不是一个变量,那么他就只能是一个临时值,换句话说,b是一个只存在于编译器的变量,作为绑定变量的别名,不会被存入内存中.\n尽管从底层来看的话,引用还是一个指针,因为你终归是要将这个别名指向原变量的 struct,union,enum struct struct是一个可以存放不同类型变量,甚至可以存放函数的数组,在内存中按照变量的声明顺序依次存储,按字节对齐 当结构体尚未完成声明时,我们可以直接使用这个结构体的指针,因为指针的内存空间是已知的,固定为8字节;但是你不能声明结构体本身,因为结构体的内存还是未知的.\n1 2 3 4 5 6 7 8 struct Link{ Link* previous; Link* successor; }; struct Failed_Link{ Failed_Link s; }; class 类的特性 类中的关键字与运算符 继承与多态 this指针 菜鸟教程 官方文档 this指针是类具有的隐藏指针,指向当前对象的实例,可以被直接调用:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include \u0026lt;iostream\u0026gt; class MyClass { private: int value; public: void setValue(int value) { this-\u0026gt;value = value; } void printValue() { std::cout \u0026lt;\u0026lt; \u0026#34;Value: \u0026#34; \u0026lt;\u0026lt; this-\u0026gt;value \u0026lt;\u0026lt; std::endl; } }; int main() { MyClass obj; obj.setValue(42); obj.printValue(); return 0; } 换句话说 对于搞不懂指针的小白来说,this-\u0026gt;value的出现有点莫名其妙了,让我们换成正常的形式:*this.value,*this相当于取得了当前这个对象后,再通过成员运算符.访问私有变量value.\n一个完整的类的示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 // class.cpp // compile with: /EHsc // Example of the class keyword // Exhibits polymorphism/virtual functions. #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; using namespace std; class dog { public: dog() { _legs = 4; _bark = true; } void setDogSize(string dogSize) { _dogSize = dogSize; } virtual void setEars(string type) // virtual function { _earType = type; } private: string _dogSize, _earType; int _legs; bool _bark; }; class breed : public dog { public: breed( string color, string size) { _color = color; setDogSize(size); } string getColor() { return _color; } // virtual function redefined void setEars(string length, string type) { _earLength = length; _earType = type; } protected: string _color, _earLength, _earType; }; int main() { dog mongrel; breed labrador(\u0026#34;yellow\u0026#34;, \u0026#34;large\u0026#34;); mongrel.setEars(\u0026#34;pointy\u0026#34;); labrador.setEars(\u0026#34;long\u0026#34;, \u0026#34;floppy\u0026#34;); cout \u0026lt;\u0026lt; \u0026#34;Cody is a \u0026#34; \u0026lt;\u0026lt; labrador.getColor() \u0026lt;\u0026lt; \u0026#34; labrador\u0026#34; \u0026lt;\u0026lt; endl; } 宏替换与别名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 //别名,只用于类型替换 using NewType = OldType; //示例 using ll =long long; using vec = std::vector\u0026lt;int\u0026gt;; //文本替换 #define NAME replacement //示例 #define PI 3.1415926 #define MAX 100 /* 但也可以带入参数 */ #define SQR(x) ((x)*(x)) #define LOOP(i,n) for(int i=0;i\u0026lt;n;i++) //typedef -using的下位替代,基本没用 typedef OldType NewType; //示例 typedef long long ll; io 读入多行 cin\u0026gt;\u0026gt;遇到空格或换行符会停止输入,想要读取一整行需要使用`getline(cin,s1) 这样的格式\nscanf和printf 1 2 3 4 5 6 7 8 9 10 11 12 13 int age; char name[20]; // 1. 缓冲区残留坑：读取字符/字符串前，若上方有残余换行符，需手动处理 printf(\u0026#34;Enter age: \u0026#34;); if (scanf(\u0026#34;%d\u0026#34;, \u0026amp;age) != 1) return 1; // 2. 返回值坑：必须检查返回值以确认物理输入成功 printf(\u0026#34;Enter name: \u0026#34;); // 3. 溢出与空格坑：使用 %s 无法读取空格且易越界。限制长度并注意数组名本身是地址 scanf(\u0026#34;%19s\u0026#34;, name); // 4. 格式化输出：printf 严格对应类型，%d 对应整型，%s 对应字符串 printf(\u0026#34;Data: Name=%s, Age=%d\\n\u0026#34;, name, age); 进阶特性 cpp的内存分配与执行过程 误区剖析 来源 通常一个由 C/C++ 编译的程序占用的内存分为以下 5 个部分: 栈区（stack）: 由编译器自动分配释放，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。 堆区（heap）: 一般由程序员分配释放，若程序员不释放，程序结束时可能由OS回收。注意它与数据结构中的堆是两回事，分配方式倒是类似于链表。 全局区（静态区）（static）: 全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域，未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。 全局区又可分为DATA段(全局初始化区):用来存放初始化的全局和静态变量,和BSS段(全局未初始化区):用来存放未初始化的全局和静态变量.在程序运行结束时这两类内存均会自动释放 文字常量区: 常量字符串等只读数据放在这里的。程序结束后由系统释放。 程序代码区: 存放函数体的二进制代码。 真实内存分配 事实上,上面这类常见的说法把目标文件和可执行文件弄混了,要想说清楚的话,我们可以这么说: cpp程序经过预处理后被编译器和汇编器处理得到二进制化的目标文件,目标文件里有三个常用区:\n.text段: 存放函数体 .data段: 存放初始化了的静态变量和全局变量 .bss段: 存放未初始化的静态变量和全局变量,存储时不占用内存空间,执行时占用空间 目标文件再被链接器处理后得到可执行文件,可执行文件装载时再有栈区和堆区的函数调用.\n说真的很多面试官都喜欢问上面那个错误的版本,可见他们的水平也不怎么样 左值引用和右值引用 参考1 参考2 上述的两个链接写的都非常好,我这里稍微总结一下:\n智能指针 为什么类和结构体定义时结尾要加一个分号 问题讨论 一种看法是将结构体的定义看作是类似与int a = 1;这样的变量声明,自然要加分号,而class源自struct,自然也保留了分号.\n不过java成功的去掉了这个烦人的分号,加一分. STL(Standard Template Library ) 在vscode里右键对应的头文件或方法,选择查找定义,则可以找到对应的stl源代码.\n通用 .size()方法是STL里通用的求容器长度的方法 memset(a, 0, sizeof(a));ormemset(a, -1, sizeof(a));是好用的重置大法 iterator(迭代器)详解 memset()详解 1 2 3 4 5 void* __cdecl memset( _Out_writes_bytes_all_(_Size) void* _Dst, _In_ int _Val, _In_ size_t _Size ); __cdecl,Out_writes_bytes_all(_Size), In: 这三个都是Microsoft专用的修饰用宏,由于太过底层所以不用去关注 void* _Dst: 空指针,指向对象内存区 int _Val: 填充内容,实际上函数底层会将int强制转换为unsigned char,故只有低8位有效.而且,由于memset的内部机制是逐字节将这低8位填入目标内存区域,如果传入1,则会导致填入内容为(0x01010101),无法做到将填入对象置为1的效果,故只能传入(-1和0),从而一致归0或者一致归1. size_t _Size: 传入单位为字节,而非直觉上以为的元素个数,这也是为什么不能直接写memset(a,-1,n)的原因. 正确的写法为memset(a,-1,sizeof(a)),sizeof是一个编译时运算符,用于获取对象占用的字节空间,并非是一个库函数. sort()详解 MSVC-algorithm部分源码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 template \u0026lt;class _RanIt, class _Pr\u0026gt; _CONSTEXPR20 void _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t\u0026lt;_RanIt\u0026gt; _Ideal, _Pr _Pred) { // order [_First, _Last) for (;;) { if (_Last - _First \u0026lt;= _ISORT_MAX) { // small _STD _Insertion_sort_unchecked(_First, _Last, _Pred); return; } if (_Ideal \u0026lt;= 0) { // heap sort if too many divisions _STD _Make_heap_unchecked(_First, _Last, _Pred); _STD _Sort_heap_unchecked(_First, _Last, _Pred); return; } // divide and conquer by quicksort auto _Mid = _STD _Partition_by_median_guess_unchecked(_First, _Last, _Pred); _Ideal = (_Ideal \u0026gt;\u0026gt; 1) + (_Ideal \u0026gt;\u0026gt; 2); // allow 1.5 log2(N) divisions if (_Mid.first - _First \u0026lt; _Last - _Mid.second) { // loop on second half _STD _Sort_unchecked(_First, _Mid.first, _Ideal, _Pred); _First = _Mid.second; } else { // loop on first half _STD _Sort_unchecked(_Mid.second, _Last, _Ideal, _Pred); _Last = _Mid.first; } } } _EXPORT_STD template \u0026lt;class _RanIt, class _Pr\u0026gt; _CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) { // order [_First, _Last) _STD _Adl_verify_range(_First, _Last); const auto _UFirst = _STD _Get_unwrapped(_First); const auto _ULast = _STD _Get_unwrapped(_Last); _STD _Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _STD _Pass_fn(_Pred)); } _EXPORT_STD template \u0026lt;class _RanIt\u0026gt; _CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last) { // order [_First, _Last) _STD sort(_First, _Last, less\u0026lt;\u0026gt;{}); } 先解释一下难看懂的地方:\n_RanIt: Random Access Iterator,可用[]进行定向访问的迭代器 _Iter_diff_t\u0026lt;_RanIt\u0026gt;:迭代器之间的差,可以用来体现容器长度 constexpr int _ISORT_MAX = 32 _Pred: cmp函数,排序规则,根据最下方函数可知默认使用less\u0026lt;\u0026gt;{},即从小到大排 for (;;) : 编译速度与while(1)没有任何区别,只是个人习惯或者历史遗留问题而已 可以看到sort函数内部对于不同的容器有三种处理方式:\n当容器大小不大于32时,使用插入排序; 当递归深度太大时,转而使用堆排序 默认使用快速排序 现在,仔细看一下快速排序的代码:\n1 2 3 4 5 6 7 8 9 10 11 12 // divide and conquer by quicksort auto _Mid = _STD _Partition_by_median_guess_unchecked(_First, _Last, _Pred); _Ideal = (_Ideal \u0026gt;\u0026gt; 1) + (_Ideal \u0026gt;\u0026gt; 2); // allow 1.5 log2(N) divisions if (_Mid.first - _First \u0026lt; _Last - _Mid.second) { // loop on second half _STD _Sort_unchecked(_First, _Mid.first, _Ideal, _Pred); _First = _Mid.second; } else { // loop on first half _STD _Sort_unchecked(_Mid.second, _Last, _Ideal, _Pred); _Last = _Mid.first; } 大致结构与平常io写的快排没有任何区别,只是专业化了一点而已\ndeque 为什么先讲deque再讲queue呢,是因为queue是用deque写的,这确实出乎我的意料. 由于deque部分洋洋洒洒有1800多行,故我先讲大致结构,再深入每个文件来讲\nqueue ","date":"2026-04-23T08:00:00Z","image":"/p/cpp%E7%AC%94%E8%AE%B0/45243652_p0-%E6%A5%BD%E5%9C%92%E3%81%AE%E7%B4%A0%E6%95%B5%E3%81%AA%E5%B7%AB%E5%A5%B3.webp","permalink":"/p/cpp%E7%AC%94%E8%AE%B0/","title":"cpp笔记"},{"content":"技术岗位概览 首先我们需要知道有哪些技术岗位\n岗位术语 每家公司都有自己独特的术语,相同岗位的名字可能大不相同,我们列举几个一堆常见的术语,从而能够在看到某一个岗位名字的时候能够快速定位:\nSoftware Engineer(SWE): 最通用的称呼,无论什么技术岗位都可以叫做是软件工程师,因此必须要看招聘的具体要求才好判断到底是什么岗位. Software Dev Engineer(SDE): 软件开发工程师,如名字所说是负责软件开发的,至于是负责哪一部分还是要看招聘需求的 Operations(Ops): 简称运维,维护系统,负责产品部署和监控 Infrastructure(Infra): 负责搭建底层工具链,需要精通底层语言和硬件原理 Architect: 这就是我们俗称的架构师,一般来说架构师都是在其他岗位历练后转调过来的,不太可能让一个实习生去架构吧. Site Reliability Engineering(SRE): 最早由Google提出,主要责任是管理大规模集群网络,解决系统故障,相当于高级运维 DevOps: 这鬼名字谁第一次看了不迷糊😅,不管怎样,该岗位的职责如名字所说是开发 (Dev) + 运维 (Ops),需要能够一边开发软件一边负责部署上线,一看就是事最多的岗位. CI/CD: Continuous Integration,持续集成;Continuous Delivery \u0026amp; Deployment,持续部署.换句话说就是精细化的版本控制. 面试题分析 自然,招聘网站上的信息只是一个初步的筛查而已,大厂还需要通过面试来真正的筛查所需的雇员. 事实上,从公司面试里出的技术题能够精确的反映面试者需要学习的知识点,因此,我在此处调研了多个领域的面试题,总结出各个领域所需的知识点,如果这些知识点你想都不想就能回答出来的话,那该你拿offer.\n计算机网络 主要考点: 老生常谈的TCP/UDP,HTTP/IP协议理解\n\u0026ldquo;谈一谈点击一个URL链接后页面响应的全过程,越详细越好\u0026rdquo; 解答: 先在网络层之上回答,第一轮是与某个DNS服务器的DNS查询,第二轮再与真正的服务器进行TCP握手(想炫技的话可以说TLS握手,把TLS的加密过程也掺进去),经过三次握手后传回网页内容; 再在网络层和链路层回答,如果使用的是以太网则讲一讲分组交换机和ARP广播,如果使用的是WiFi和5G则讲一讲无线链路.再宽泛的讲一讲路由转发. 还想装逼的话就可以讲一讲如果是在局域网里,就需要通过DHCP来获取自己的动态IP地址. HTTPS有什么优点和缺点？ 常用的HTTP请求方法,哪几个是幂等的? 一个 Http 请求包含哪几部分内容？ 说真的,一本\u0026quot;自顶向下方法\u0026quot;就能搞定的事情真的没必要单独去破碎的吸收二手博客和总结. 操作系统 通用 进程和线程之间有什么区别？ 进程间有哪些通信方式？ 简述操作系统进行内存管理的方法 前端(待补充) 由于我对前端暂时没有考量,所以就先搁置了 后端 C/C++ 说真的,cpp特性太多了,就算都学过了,一下子被问到的时候我还是不能说的很明白.\n基础知识 谈一谈指针和引用 解答: 常量指针和指针常量 谈一谈C中的malloc和Cpp中的new,delete struct和class的区别 #include\u0026lt;filename.h\u0026gt;和#include“filename.h”有什么区别？ C语言是强类型的语言，这是什么意思？ 进阶知识 动态库与静态库的区别 深拷贝和浅拷贝 inline,const,volatile,extern关键字的用法 左值和右值 谈一谈智能指针的用法 谈一谈常用的STL容器 C++友元的具体原理 C和C++的区别 为什么要有虚析构函数 C++中一个空类的大小为什么是1 define和typedef的区别 构造函数和析构函数的执行顺序？ 内存管理 谈一谈cpp中的内存分配,栈,堆,静态存储区 内存泄漏,野指针,指针越界你分别是怎么处理的 谈一谈结构体中的内存对齐 一个结构体中有一个int，一个char，一个static int，问这个结构体占多少内存？ Python 基础语法: yield的用法 wsgi是什么 Session,Cookie,JWT的理解 python的垃圾回收机制 ***args 和 kwargs Python中单下划线和双下划线 range和xrange的区别 简单讲讲lambda函数的应用 python闭包的特性 你怎么使用python多线程 为什么python没有重载机制 爬虫 你用过的爬虫框架或者模块有哪些？优缺点？ 怎么样让 scrapy 框架发送一个 post 请求（具体写出来） 你所知道的分布式爬虫方案有哪些？ 常见的反爬虫和应对方法？ Java Java基础语法 垃圾回收机制 Java中的内存泄露例子 解答: Java高级特性 Java的反射是什么 谈一谈Java的线程池机制 Go 基础语法 为什么说 Go 语言字符串是不可变的？ Go 语言 map 是并发安全的吗？ Go 语言 new 和 make 关键字的区别 Goroutine调度策略 简单聊聊内存逃逸？ Android 数据库 事实上很多后端岗位都需要涉及跟数据库的交互,因此面试题里总会问到数据库的具体数据结构等这些知识点.\nRedis 使用Redis有哪些好处 Redis性能问题都有哪些 Redis的同步机制 Redis中的底层数据结构 Redis 和 Memcached 有什么区别？Redis 的线程模型是什么？为什么单线程的 Redis 比多线程的 Memcached 效率要高得多？ Redis 集群模式的工作原理能说一下么？在集群模式下，Redis 的 key 是如何寻址的？分布式寻址都有哪些算法？了解一致性 hash 算法吗？如何动态增加和删除一个节点？ 生产环境中的 Redis 是怎么部署的？ 了解什么是 Redis 的雪崩、穿透和击穿？Redis 崩溃之后会怎么样？系统该如何应对这种情况？如何处理 Redis 的穿透？ 使用 Redis 如何设计分布式锁？使用 Zookeeper 来设计分布式锁可以吗？以上两种分布式锁的实现方式哪种效率比较高？ MongoDB MongoDB的架构和优势 MySQL 为什么MySQL使用B+树做索引？ 如何实现 MySQL 的读写分离？MySQL 主从复制原理是啥？如何解决 MySQL 主从同步的延时问题？ 通用 CRUD是哪四个词 数据库的几大范式 谈一谈B+,B,B-树,红黑树,跳表 游戏开发 计算机图形学 Unity 架构 微服务 什么是微服务？微服务之间是如何独立通讯的？ 你所知道的微服务技术栈都有哪些？ 数据处理 如何从大量的 URL 中找出相同的 URL？ 如何从 5 亿个数中找出中位数？ 解答: 使用分治法不断处理二进制最高位分组,直到凑齐2.5亿个数 1T 的数据怎么加载到 200M 的内存中，并且找到两行一样的数据？ IO 多路复用是什么？多路是什么？复用了什么？ 设计模式 谈谈常见的设计模式? kubernetes 简述Kubernetes的优势、适应场景及其特点？ 简述Kubernetes和Docker的关系？ 简述Kubernetes中什么是Minikube、Kubectl、Kubelet？ 简述Kubernetes自动扩容机制？ 简述Kubernetes的负载均衡器？ 简述Kubernetes数据持久化的方式有哪些？ 消息队列 为什么使用消息队列？消息队列有什么优点和缺点？Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点？ 如何解决消息队列的延时以及过期失效问题？消息队列满了以后该怎么处理？有几百万消息持续积压几小时，说说怎么解决？ 如果让你写一个消息队列，该如何进行架构设计啊？说一下你的思路。 搜索引擎 ES 的分布式架构原理能说一下么（ES 是如何实现分布式的啊）？ ES 写入数据的工作原理是什么啊？ES 查询数据的工作原理是什么啊？底层的 Lucene 介绍一下呗？倒排索引了解吗？ ES 生产集群的部署架构是什么？每个索引的数据量大概有多少？每个索引大概有多少个分片？ NLP/ML/CV 这三个领域的很多知识都是掺杂在一起的,所以就放到一起了.\nCPU和GPU的区别？ 深度学习框架有哪些？各有什么特点？ 求解马尔科夫决策过程都有哪些方法？ 更多的面试题 合集 osjobs 个人总结面经\n架构 GitHub仓库: k8s-books\n真实要求分析 工作经验 我们很容易看到某个岗位要求\u0026quot;X年工作经验\u0026quot;,但是就算真这么说了,如果你有相应实力的话,还是去试试水呗,你要是能胜任这个岗位的话工作经验就没那么重要了. Reddit讨论\n英语能力 面外企的话,可以看看自己的英语能力在哪个档:\n会读和写: 基础条件,一个合格的技术人员的最低标准. 会听和说: 有些岗位标明需要流畅地与native speaker交谈 但实际上来说只要你的面试口语不是太差,总会让你过的.\n技术能力 你面的是哪门语言,或者说你简历上写的精通xx语言,那么这门语言你至少要做到以下几点:\n独立设计一个架构良好的常见类 手撕一道洛谷绿题/力扣难题 常见的库/包/框架能够信口拈来 基本特性和容易踩坑的点要十分熟悉 不然的话,面试的时候有的你苦吃的,毕竟面试官都是有备而来\n到底招什么 大多数人对于就业的恐惧在于不知道企业要招什么样的人才,从而夸大了找工作的难度,要我说,既然不知道就赶紧去搜集信息看看要学什么啊!\n甚至问AI都可以:\n针对内地头部科技公司，根据 2026 年最新的技术栈演进与业务分布，后端架构已从早期的单体或简单微服务演变为高性能异构系统。\n以下是各家大厂主要软件产品的后端语言架构整合：\n1. 字节跳动 (ByteDance) 代表软件：抖音、今日头条、飞书 (Lark) 主导语言：Go (Golang) —— 字节是国内最早、最彻底全面 Go 化的公司。 架构特征： 微服务框架：自研高性能框架 Kiteex (RPC)、Hertz (HTTP)。 中间件：重度依赖 Service Mesh (如其自研的控制面)，实现海量微服务的治理。 存储：自研分布式 KV 存储（如 ByteKV），并结合分库分表的 MySQL。 2. 蚂蚁集团 / 阿里巴巴 (Ant / Alibaba) 代表软件：支付宝、淘宝、闲鱼 主导语言：Java —— 阿里系是国内 Java 规范的制定者，深度定制 JVM。 架构特征： 微服务框架：Spring Cloud Alibaba 体系，核心为 Dubbo 和 Nacos。 底层引擎：蚂蚁金服重度使用 OceanBase (分布式数据库) 和 SOFAStack (金融级中间件)。 异构补充：部分高性能网关和底层节点开始引入 Go 和 Rust。 3. 腾讯 (Tencent) 代表软件：微信、QQ、王者荣耀 主导语言：C++ / Go —— 微信由于历史积累和极致性能追求，底层大量使用 C++；新业务及 QQ 架构演进则转向 Go。 架构特征： 微服务框架：自研开源框架 tRPC (支持 C++, Go, Java, Python 多语言互通)。 存储：重度使用 TDSQL (分布式数据库) 和自研的数仓架构。 并发模型：微信著名的协程库 libco 支撑了数亿级的并发。 4. 华为 (Huawei) 代表软件：华为云 (Huawei Cloud)、鸿蒙 (HarmonyOS) 后端云服务 主导语言：Java / C++ / Rust 架构特征： 底层开发：内核与关键驱动层使用 C++/C，安全敏感模块开始向 Rust 迁移。 云架构：Java 支撑复杂的业务逻辑，配合自研的微服务引擎 CSE。 数据库：核心业务均跑在自研的 GaussDB 上。 5. 美团 (Meituan) 代表软件：美团 App、大众点评 主导语言：Java 架构特征： 工程化极强：拥有极其完善的 Java 开发规范和监控系统 (CAT)。 配送调度：后端不仅涉及业务流，还包含复杂的 LBS (地理位置服务) 逻辑。 中间件：使用 MT Thrift 作为内部通信协议。 后端 cpp/Java/Go你总得精通一个吧,数据库总得精通一个吧,网络通信总得熟悉吧,操作系统总得懂吧,微服务总得学习吧.\n算法岗 Pytorch总得会吧,常用的数据处理库得会吧,Agent优化,RAG处理总得熟练吧,传统神经网络和数学模型总得了解吧.\n什么岗位需求大 我们用两个案例来分析,一个是需求庞大的字节招聘官网,一个是阮一峰的weekly谁在招人issue集.\n字节招聘 搜索python: 搜索Java: 搜索cpp: 搜索Go: 搜索rust: 搜索前端: 搜索Redis: 搜索MySQL: 搜索PostgreSQL: 搜素kuber: 谁在招人issue 我爬取了从2023-01-01至今天的issues 搜索python: 搜索Java: 搜索go: 搜索cpp: 搜索rust: 搜索前端: 搜索kuber: 总结 前端的缺口依然很大,需求很多. 后端里cpp/Java/Go/python必须要精通一门 实战 本章通过剖析一些常见的招聘信息来说明找工作真没有那么难,难的只是找到自己心仪的工作而已.\n亚马逊 以这个招聘信息为例:\n描述 职位：SDE上海 · 毕业时间：2026年10月 - 2027年7月之间毕业的应届毕业生 · 入职日期：2026年5月及之前 · 实习时间：保证一周实习4-5天全职实习，至少持续6个月 · 工作地点：上海\n基本任职资格\nCurrently enrolled in Bachelor\u0026rsquo;s or Master\u0026rsquo;s degree in Computer Science, Software Development, Machine Learning, Mathematics, or related majors Graduation date: November 2026 - July 2027 Available for minimum 6-month internship Proficient in Java/Python/C++/JavaScript/TypeScript Solid foundation in algorithms and data structures Strong English reading and writing skills 优先任职资格\nPrevious technical internship or project experience Experience with agent frameworks Experience with distributed systems, algorithms, and relational databases Experience in optimization mathematics (linear programming, nonlinear optimization) Verbal proficiency in English 因为是个实习岗,所以要求很少,但其实细致考量一下的话,会发现这个岗位其实没有任何要求,任何一个普通的计算机专业学生都可以应聘\u0026hellip;\n特斯拉 职位描述\nFull Stack Engineer (Golang/Python+React), AI\nWe\u0026rsquo;re looking for a highly motivated full stack engineer specialized in backend development with desktop application experience. You will build scalable, high-performance desktop and web applications that enable agents to interact with users and automate complex workflows. The platform you build will power innovative features used by thousands of internal and external users.\nResponsibilities\nDesign and build scalable desktop and web applications with modern frontend frameworks Design and implement production-level AI agents and tool integrations Create end-to-end agentic workflows that enable AI agents to interact with internal system Work with backend Frameworks (Go, Python) to build high performance agent system Leverage AI coding tools (Cursor, Claude Code, GitHub Copilot) to accelerate development Work closely with product, other application team, and AI/ML teams Experience of AI coding assistants Experience with Next.JS Proficient in English, able to communicate with global team regarding solution and plan Requirements\n5+ years of strong development experience in building highly-reliable, mission-critical software Degree in Computer Science, Information Systems, or the equivalent in experience Experience in building AI agents or LLM-powered applications Experience with agent frameworks, tool calling, and agent orchestration Experience with backend development (Go, Python) and understanding of microservices architecture Frontend experience is a plus Deep knowledge of best practices, information security, and API design principles Experience with gathering and building requirements from multiple stakeholders Go, Kubernetes, and distributed systems experience a plus 看着东西多,其实一点都不吓人,只需要看我着重的部分就可以了,可以简单总结一下要求:\n5年以上的Go/Python后端开发经验 要会NextJs,但不用实际干过前端 要会用AI工具来搭建一个好用的智能体 很显然,这个岗位不用自己训练模型,只需要调用API就行了,还能用AI开发,那不是有手就行了.\n事实上,如果他能够在招人的时候网开一面,不死守五年工作经验的话,我都可以去了\u0026hellip; 英伟达 描述 SAI Verification Engineer\nWhat you’ll be doing: • Be part of NVIDIA SAI multi-national R\u0026amp;D team, contribute code to SAI community and Nvidia SAI implementation. • Design and implement robust, maintainable, and efficient automation test suite. • Work with continuous integration systems, regression tools, automate builds, run test suites, generate test reports, isolate and classify failures and review new degradation. • Work with experienced teams which are well known in the SAI community. • Develop high quality code, most of the code is open source and published and reviewed in industry leading open source environments.\nWhat we need to see: • B.Sc. degree or equivalent experience in Engineering/Computer Science/related field. • 5+ years of experience as a Software Engineer. • Intrinsically motivated with a desire for automation programming. • Strong programming skills in Python. • Strong technical abilities, problem solving skills, coding and design skills. • Ability to lead feature development, take full ownership and deliver independently. • Linux knowledge: have a general understanding of Linux operation system concepts. • C experience and extensive knowledge. • Ability to understand, debug and improve 3rd party complex code. • Excellent communication in English and leading skills.\nWays to stand out from the crowd: • Knowledge in one or more of the following Networking areas: Ethernet, VLANs, TCP/UDP/IP, QoS, L2-L3 protocols. • Prior software testing experience, with an understanding of Software Testing Tools and Methodologies. • Experience in development in Linux (user and/or kernel modes). • Python specialist.\n要求也很简单,五年工作经验,会python,懂C语言和底层通信技术,吃苦耐劳,能够团队协作.\n不厚道的说,我还是可以去应聘\u0026hellip; ","date":"2026-04-23T08:00:00Z","image":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E4%B8%80-%E9%9D%A2%E8%AF%95%E9%A1%BB%E7%9F%A5/72471572_p0-%E7%A7%A6%E3%81%93%E3%81%93%E3%82%8D.webp","permalink":"/p/%E9%9D%A2%E5%90%91%E9%9D%A2%E8%AF%95%E5%AD%A6%E4%B9%A0%E4%B8%80-%E9%9D%A2%E8%AF%95%E9%A1%BB%E7%9F%A5/","title":"面向面试学习(一)-面试须知"},{"content":" 我编程学习的动力很大程度上是为了做游戏,但这只是作为一个爱好而已,真要去大厂做游戏的话还是很累的. 不管怎样,我先在这里介绍一下一些常用的游戏引擎,方便游戏开发的新手来选择自己喜欢的开发语言和开发环境\nUnity 垄断级别的游戏引擎,市面上你能看到的游戏大部分都是用它开发的.\n著名游戏 原神,炉石传说,纪念碑谷,空洞骑士\n引擎历史 wiki Unity 1.0 (2005) Unity 游戏引擎于 2005 年发布，旨在通过降低开发门槛来实现游戏开发的“民主化”。\n发布背景：由 Scott Forstall 在 2005 年苹果全球开发者大会（WWDC）上首次展示，运行于 Mac OS X。 荣誉：次年获得苹果设计奖“最佳 Mac OS X 图形使用”亚军。 平台：最初仅支持 Mac OS X，随后增加了对 Windows 和网页浏览器的支持。 Unity 2.0 (2007) Unity 2.0 带来了约 50 项新功能，标志着引擎进入快速成长期。\n核心更新：新增了 DirectX 支持、优化的地形引擎、实时动态阴影、视频播放等。 协作与网络：引入版本控制系统以支持团队协作；增加了基于 UDP 的网络层，提供 NAT 穿透、状态同步和远程过程调用（RPC）。 移动端开端：2008 年苹果推出 App Store 后，Unity 迅速增加了对 iPhone 的支持。 编辑器扩展：2009 年的 2.5 版本首次增加了 Windows 版编辑器。 Unity 3.0 (2010) Unity 3.0 极大扩展了引擎在桌面和主机端的图形性能。\n图形技术：集成了 Beast 光照贴图工具，支持延迟渲染、内置树木编辑器、原生字体渲染及音频滤镜。 市场地位：2012 年其开发者数量达到 130 万。调研显示 Unity 成为当时移动平台的首选引擎，助力了独立游戏的爆发。 Android 支持：正式加入对 Android 系统的支持。 Unity 4.0 (2012) 该版本致力于强化动画系统与生态整合。\n技术突破：支持 DirectX 11 和 Adobe Flash，推出了全新的动画工具系统 Mecanim，并发布了 Linux 预览版。 社交整合：2013 年 Facebook 集成了 Unity SDK，支持广告追踪、深度链接及跨平台游戏发布。 Unity 5 (2015) Unity 5 被视为实现“通用开发”目标的重要一步。\n图形与物理：引入实时全局光照 (GI)、PhysX 3.3 物理引擎以及电影级图像效果，改善了“Unity 游戏”廉价的视觉印象。 无插件 Web：通过 WebGL 技术，使游戏无需插件即可在浏览器运行。 多平台扩展：5.6 版本增加了对 Nintendo Switch、Vulkan API、Google Daydream 及 4K/360 度虚拟现实视频的支持。 年度版本时期 (2017–2023) 从 2017 年起，Unity 将版本号改为年份标识，以匹配更频繁的更新节奏。\nUnity 2017：推出了 Timeline（时间轴动画）和 Cinemachine（智能摄像机系统），并加强了与 Autodesk 工具的集成。 Unity 2018： 可编程渲染管线 (SRP)：推出了面向高性能的 HDRP 和面向移动端的 LWRP（后更名为 URP）。 技术演进：引入机器学习工具、Magic Leap 支持以及 Unity Hub 管理工具。 Unity 2020 - 2022： 硬件适配：原生支持 Apple Silicon。 工具增强：引入可视化编程系统 Bolt、MARS（增强现实开发）、大幅优化了 Play Mode 的进入速度和 2D 物理系统。 Unity 6 (2024) 2023 年底，Unity 宣布回归数字版本号规则。\nAI 集成：正式推出 Unity Muse 和 Unity Sentis 等生成式 AI 工具。 性能飞跃：优化了在线多人内容开发流、Web 项目性能以及图形渲染质量。 政策变动：曾计划推行“运行时费用（Runtime Fee）”，但在 2024 年 9 月因社区强烈抵制而宣布取消该费用。 环境问题 如今的内地开发者学习unity的第一步是下载真正的unity,而非tuanjie引擎,尽管在国内开发游戏的话现在貌似只能用tuanjie引擎了\u0026hellip;\n这么丑陋的名字是谁想出来的 官方介绍 团结引擎是 Unity 中国的引擎研发团队基于 Unity 2022 LTS 版本为中国开发者定制的实时 3D 引擎；基于 Unity 的核心能力，团结引擎团队倾听中国开发者的声音和需求，为团结引擎加入了一些中国开发者需要的定制化功能，并会在未来持续不断的为中国开发者量身定制需要的功能。 团结引擎以 Unity 2022 LTS 为研发基础，融入了团结引擎独有功能和优化，未来会加入更多为中国开发者量身定制的功能和优化。\nunity is losing users in china It’s also worth noting that in certain industries, foreign companies in China are required to form a joint venture with a Chinese partner and are not allowed to hold more than 50%. That’s why Unity created Unity China, and it’s likely the reason why the regular global version of Unity can no longer be distributed directly in China by Unity itself.\nThe global Asset Store probably operated in a gray area for a while and was tolerated, but that seems to have changed.\nAs far as I know, there is no technical exchange between Unity and Unity China, Unity China operates completely independently. And from what it looks like, Unity even plans to sell its shares in Unity China and fully withdraw from the Chinese market.\nIt’s also possible that Epic may eventually have to change how it operates in China as well.\n因此,要想下载真正的unity的话需要挂日本,美国等国家的梯子,用香港梯子也会把你重定位到tuanjie的官网\u0026hellip;\n开发环境 需要注册Unity账户后下载Unity Hub,在Unity Hub中选择所需的Unity引擎版本. 由于Unity引擎的功能极其繁多,而且大部分操作都被图形化了,所以极其臃肿,什么扩展都不添加直接下载时也要占用6,7个G.\n而且由于功能多,有时候会出现一些莫名其妙的bug. 开发语言 脚本语言只能使用C#,C#的参考语言是cpp和java,所以如果你先学了这两门语言再来学C#的话基本能直接看懂大部分代码了. 微软官方的C#教程写的很烂(或者说没有教程),建议找第三方网站如w3schools.当然,直接根据游戏开发教程来上手也可以.\nUnreal Engine 著名游戏 黑神话悟空,绝地求生,无畏契约,幻兽帕鲁\n引擎历史 第一代：Unreal Engine 1 (1995–1998) 虚幻引擎的起点，由 Epic Games 创始人 Tim Sweeney 为游戏《虚幻》（Unreal）独立开发。\n技术核心：支持软件渲染与硬件加速并行，通过 3dfx Glide API 适配早期的 3D 加速卡（如 Voodoo Graphics）。 跨平台性：支持 Windows、Linux、Mac 及 Unix 系统。 商业模式：Epic 开始尝试将引擎授权给其他游戏工作室。 第二代：Unreal Engine 2 (2002–2005) 标志着引擎全面进入硬件渲染时代，并开启了主机市场的扩张。\n硬件转型：彻底从软件渲染转向硬件加速。 主机适配：增加了对 PlayStation 2、Xbox 和 GameCube 等主流家用机的支持。 发布节奏：首款基于 UE2 的游戏于 2002 年问世，版本更新持续至 2005 年。 第三代：Unreal Engine 3 (2006–2012) UE 历史上统治力最强的版本，确立了其在 3A 游戏市场的地位。\n并行计算：首批支持多线程处理的游戏引擎之一。 渲染简化：以 DirectX 9 为基准图形 API，简化了渲染代码逻辑。 工业化标准：首款游戏于 2006 年底发布，随后成为 PS3/Xbox 360 时代大型项目的通用标准。 第四代：Unreal Engine 4 (2014–2021) 虚幻引擎大众化与现代化的里程碑。\n视觉革命：引入了基于物理的材质 (PBR)，大幅提升了写实感。 蓝图系统：推出了 \u0026ldquo;Blueprints\u0026rdquo; 可视化脚本系统，让非程序员也能构建复杂的游戏逻辑。 分成模式：首个提供免费下载的版本，仅在游戏收入达标后收取版税。 第五代：Unreal Engine 5 (2022–2025) 打破资产复杂度瓶颈的颠覆性版本。\nNanite：虚拟化几何体系统，允许开发者直接使用数亿面数的高质量模型，引擎自动生成细节等级（LOD）。 Lumen：全动态全局光照与反射系统，结合了软件与硬件光线追踪技术。 发布历程：2020 年 5 月首次揭晓，2022 年 4 月正式发布。 开发环境 下载Epic Games后直接在面板上下载就行了: 虚幻引擎比起unity引擎更为臃肿,达到了可怕的十个G,启动速度也更慢,性能优化上也比较差.\n开发语言 蓝图和cpp\nGodot 使用了MIT协议的开源游戏引擎,历史比一般人想象的要悠久的多\n著名游戏 杀戮尖塔2,战地6,背包乱斗,土豆兄弟,恶魔轮盘\n开发出来的游戏风格如此多样,反过来说明Godot的可操作性极高 引擎历史 1999年－2014年 Juan Linietsky 与 Ariel Manzur 成立 Codenix 公司并开始研发代号为“Larvotor”的引擎。在此期间，引擎经历了 Legacy、NG3D、Larvita 等多次更名，最终定名为 Godot。该引擎当时主要用于为 Square Enix 等公司提供商业技术咨询。\n2014年－2018年 2014年，Godot 源代码正式以 MIT 协议在 GitHub 开源。2018年，3.0 版本发布，完成了闭源时期无法实现的重大重构，并在微软支持下引入了 C# 脚本语言，随后 3.1 版本增加了针对移动端的 OpenGL ES 2.0 渲染支持。\n2019年－2022年 引擎开发分为两个分支：3.x 分支维持更新，4.0 分支则着手重构核心架构以适配多核处理器与 Vulkan 渲染。2022年，开发团队成立 W4 Games 公司，旨在处理开源代码库无法包含的主机移植等商业咨询服务；同年，Godot 宣布成立独立的 Godot 基金会。\n2023年－2025年 2023年，支持 Vulkan 的 4.0 正式版发布，并上线 Epic 游戏商城。由于unity的runtime fee政策，不少开发者从 Unity 迁移至 Godot\n开发环境 官网下载引擎即可,主引擎极其轻量: 这反过来说明Godot本身有的功能极其有限,需要你自己去造很多轮子,但搞计算机的最喜欢造轮子了\u0026hellip; 开发语言 支持C#和GDScript,后者是Godot自己研发的语言.\nlibgdx 开源的Java游戏引擎,弥补了当年unity没有采用Java作为脚本语言的遗憾,历史比较短\n著名游戏 Mindustry,杀戮尖塔1\n引擎历史 2009 年中：Mario Zechner 开发 Android Effects (AFX) 框架。为解决移动端调试低效问题，引入桌面端兼容逻辑，奠定跨平台基础。 2010 年 3 月：AFX 在 Google Code 开源，采用 LGPL 协议。 2010 年 7 月：应社区要求切换至 Apache License 2.0，允许商业闭源使用。同年发布 phpBB 社区论坛。 2010 年 10 月：主要贡献者 Nathan Sweet 加入，后续共同持有版权并主导开发。 2011 年初： 音频后端由 Java Sound 迁移至 OpenAL。 集成基于 STB 库的图像处理库 Gdx2D。 引入 UI 库组件及初步 3D API。 2012 年初：发布 gdx-jnigen 辅助工具，简化 JNI 绑定开发，催生了 gdx-audio 和 gdx-freetype 扩展。 2012 年中： 受 PlayN 启发，利用 GWT 实现 HTML5/WebGL 后端。 版本控制由 Subversion 迁移至 Git (GitHub)。 构建系统由自定义结构迁移至 Maven。 2013 年： 3 月：引入 RoboVM 以解决 iOS 后端兼容性问题。 5 月：重构并集成全新 3D API。 9 月：项目完全搬迁至 GitHub（含 Issue 与 Wiki）；构建系统由 Maven 切换至 Gradle。 2014 年 4 月 20 日：libGDX 1.0 正式版发布。 2016 年：因 RoboVM 停止维护，项目组分叉（Fork）了开源版 RoboVM 以维持现状，并引入 Intel Multi-OS Engine (MOE) 作为 iOS 备选方案。 近年现状：核心后端持续迭代。RoboVM 分支演进为 MobiVM 以支持现代 iOS 版本；GWT 后端由于技术老化，社区开始探索基于 TeaVM 的新一代 Web 解决方案；构建环境全面适配 Java 8 及以上版本。 RPG Maker 专门用来开发RPG游戏的引擎,有很多版本,但又不采用标准的年份或者版本号命名,所以很容易搞蒙.\n最为人称道的是开发者只需要付买引擎的钱,后续的盈利均归开发者所有 著名游戏 blacksouls1\u0026amp;\u0026amp;2,恐惧与饥饿1\u0026amp;\u0026amp;2,Lisa两部曲,魔女之家\n引擎历史 1988 - 1992：起源与 Dante 98 早期探索：ASCII 公司自 1988 年起发布了一系列基于用户代码扩展的制作工具。 RPG Tsukūru Dante 98 (1992)：官方认定的系列首作，运行于 NEC PC-9801。其后续版本 Dante 98 II 亦在该平台发布。 1995 - 2003：Windows 时代的开启与普及 RPG Maker 95：首个 Windows 版本。提供高分辨率位图，但角色行走图固定为两帧（始终处于走动状态），且无法直接删除已创建的事件。 RPG Maker 2000 (RM2k)：系列最普及的版本之一。降低了素材分辨率以提升运行效率，引入了可无限调用的素材包（素材库概念），解决了 95 版的删除逻辑问题。 RPG Maker 2003 (RM2k3)：Enterbrain 接手后的首作。核心改进为引入了类似《最终幻想》的侧视角战斗系统（Side-view），并支持从 RM2k 升级工程。 2005 - 2011：脚本化与高分辨率化 RPG Maker XP (RMXP)：引入基于 Ruby 语言的 RGSS 脚本系统，允许开发者直接修改底层游戏逻辑。支持更高的 640x480 运行分辨率和更灵活的素材尺寸。 RPG Maker VX：将帧率提升至 60fps，并改用 RGSS2 脚本。简化了编辑器界面，但由于去除了多层图层支持（仅支持单一图块集），导致场景丰富度受限。 RPG Maker VX Ace：VX 的加强版。解决了图块集限制问题，引入了自定义伤害公式功能和素材 DLC 系统。 2015 - 2020：JavaScript 与跨平台 RPG Maker MV：底层全面转向 JavaScript，弃用 Ruby。支持多平台发布（PC、移动端、浏览器），回归了自动化图层系统，并原生支持侧视角战斗切换。 RPG Maker MZ：MV 的迭代版本。重新引入了类 XP 时代的图层手动管理功能，整合了 Effekseer 粒子特效系统，并增加了自动存档等现代游戏功能。 2023 至今：引擎融合 RPG Maker Unite：首次脱离独立运行环境，作为 Unity 引擎的一项插件资产发布。旨在利用 Unity 的渲染管线和跨平台能力，同时保留 RPG Maker 的工作流。 拓展阅读 开发环境 由于是一次性收费,故怎么想都不会便宜的,而且不同版本的引擎素材不可混用,只能针对特定引擎购买特定的官方素材或者自己画素材 以2020年发布,使用js的RPG Maker MZ为例,我们先看看它的steam商品页面,只够买引擎本体的话需要四百多人民币,如果想使用官方素材的话还得额外购买,不过本地带的素材也已经很多了 学习资料 新手入门 插件与素材网站合集\ncocos2d Cocos2d is an open-source game development framework for creating 2D games and other graphical software for iOS, Android, Windows, macOS, Linux, HarmonyOS, OpenHarmony and web platforms. It is written in C++ and provides bindings for various programming languages, including C++, C#, Lua, and JavaScript. The framework offers a wide range of features, including physics, particle systems, skeletal animations, tile maps, and others.\nCocos2d was first released in 2008, and was originally written in Python. It contains many branches with the best known being Cocos2d-ObjC (formerly known as Cocos2d-iPhone), Cocos2d-x, Cocos2d-JS and Cocos2d-XNA. There are also many third-party tools, editors and libraries made by the Cocos2d community, such as particle editors, spritesheet editors, font editors, and level editors, like SpriteBuilder and CocoStudio.\n引擎历史 2008年：起源与早期迭代 2月（Los Cocos）：Ricardo Quesada 与 Lucio Torre 等人在阿根廷 Los Cocos 村开发了基于 Python 的 2D 游戏引擎。 3月（Cocos2d 诞生）：发布 0.1 版本，正式更名为 Cocos2d。 6月（Cocos2d-iPhone）：紧随苹果 App Store 的潜力，Quesada 用 Objective-C 重写引擎，发布 Cocos2d-iPhone v0.1。此版本确立了 Cocos2d 家族的架构基础。 2010 - 2013：全球化与多平台扩张 2010年11月（Cocos2d-x 诞生）：中国开发者王哲基于 Cocos2d 分叉出 Cocos2d-x 项目。该版本采用 C++ 开发，支持一套代码多平台运行，并遵循 MIT 开源协议。 2013年：Ricardo Quesada 离开 Cocos2d-iPhone 团队，正式加入 Cocos2d-x 团队。 2015 - 2017：商业化与结构调整 2015年：Cocos2d 家族保持四个分支活跃。触控科技（Chukong Technologies）成为 Cocos2d-x 与 Cocos2d-html5 的主要赞助商和维护者，并开发了可视化编辑器 CocoStudio。 2017年3月：Ricardo Quesada 从触控科技离职。 现状与分支 分支架构：目前 Cocos2D-ObjC（由原 iPhone 版本演变）由 Lars Birkemose 维护；Cocos2d-x 及其 HTML5 版本由中国团队主导开发。 品牌标识：由 Michael Heald 设计了现行的 Cocos2d Logo，取代了最初的“奔跑的椰子”形象。 与Cocos Creator的关系 Cocos Creator是Cocos2d-x的内地商业化版本,在后端为主的游戏开发生态中拥抱前端开发,并在早期错误的投入大量精力在lua脚本上,因此发展的不是很好\u0026hellip; 以下是AI总结的Cocos Creator特性:\n技术架构的更迭 Cocos2d-x (API 驱动): 传统的编程框架。开发者通过 C++、Lua 或 JavaScript 编写代码来构建场景、添加节点。这种方式高度依赖代码逻辑，缺乏可视化支撑，开发过程类似“盲写”布局。 Cocos Creator (编辑器驱动): 以 数据驱动 为核心。它借鉴了 Unity 的组件化（Component-Based）设计思路，提供了一个可视化编辑器。开发者在界面中拖拽资源、配置属性，代码仅作为挂载在节点上的脚本组件。 核心语言的转变 Cocos2d-x: 主打 C++，同时支持 Lua 和 JS 绑定。追求底层控制力和执行效率。 Cocos Creator: 早期支持 JavaScript，现已全面转向 TypeScript。这使得开发更符合现代前端工程化标准，且更易于维护大型项目。 渲染引擎的重构 Cocos2d-x: 主要是为 2D 游戏设计，3D 功能是后续强行修补的插件化功能，架构较为沉重。 Cocos Creator (3.x 系列): 底层合并了原有的 2D 引擎与 3D 引擎分支，实现了真正的 2D/3D 一体化 渲染架构。它支持基于物理的渲染（PBR）和 GPU 粒子系统，而这些在旧版 Cocos2d-x 中难以实现。 跨平台能力的差异 Cocos2d-x: 侧重于原生平台（iOS, Android, Windows）。虽然有 Cocos2d-js 对应 Web，但双端表现往往不一致。 Cocos Creator: 针对 小游戏平台（微信、TikTok、华为等）进行了底层定制优化，拥有极高的 Web 运行时性能，同时兼顾原生跨平台打包。 总结 事实上,做游戏最难的地方其实是找素材和坚持开发,用什么引擎真的关系不大,如果你能够像神之天平的超人作者一样能够十几年如一日的开发,就算是在Windows xp上用RPG Maker 2000开发,我想都能够做出一款超神的作品. 不过,为了避免重复造轮子,还是需要根据自己想要开发的游戏选择引擎的:\nVR/XR类型: Unity,最为成熟 RPG类型(黄油类): RPG Maker,最能减少心智负担 3D: 虚幻(更推荐)和unity,参考黑神话悟空和原神 2D且逻辑简单: Godot,参考杀戮尖塔2和土豆兄弟 2D且逻辑复杂: Unity,参考奥日和空洞骑士 微信小游戏: cocos creator,参考大部分微信唐氏小游戏 ","date":"2026-04-18T08:00:00Z","image":"/p/2026-04-18-%E4%B8%BB%E6%B5%81%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E%E8%B0%83%E7%A0%94/67313183_p0-%E7%84%A1%E9%A1%8C.webp","permalink":"/p/2026-04-18-%E4%B8%BB%E6%B5%81%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E%E8%B0%83%E7%A0%94/","title":"2026-04-18 主流游戏引擎调研"},{"content":"html和js中广泛应用的dom到底是什么,我向来没有一个明确的认识,因此写了这篇文章来学习一下.\ndom的概念 wiki dom(Document Object Model) is a cross-platform and language-independent API that treats an HTML or XML document as a tree structure wherein each node is an object representing a part of the document. Nodes can have event handlers (also known as event listeners) attached to them. Once an event is triggered, the event handlers get executed.\n也就是说,dom将网页文档看作是由一个个节点组成的树,每个节点都可以被各种各样的事件触发,并执行触发函数. 这样看来,dom并没有什么神秘的地方,但却没有多少人把这个讲清楚. In HTML DOM (Document Object Model), every element is a node: A document is a document node. All HTML elements are element nodes. All HTML attributes are attribute nodes. Text inserted into HTML elements are text nodes. Comments are comment nodes. dom的历史 1995年：Netscape 发布 JavaScript (Navigator 2.0)，诞生 DOM Level 0（Legacy DOM）。支持简单的表单/链接访问（如 document.forms[0]），主要用于基础交互和表单验证。 1996年：微软发布 Internet Explorer 3.0，推出兼容 JavaScript 的 JScript。 1997年：Netscape 和微软分别发布 4.0 版本浏览器。引入 DHTML（动态 HTML），形成 Intermediate DOM。由于两家厂商各自开发扩展，导致该阶段的 DOM 实现严重不兼容。 1998年：在 ECMAScript 标准化后，W3C 发布 DOM Level 1 推荐标准。这是首个尝试统一各平台操作接口的独立标准。 2005年：随着 IE6、Firefox (Gecko)、Safari 等浏览器的普及，W3C DOM 标准获得主流引擎的广泛支持，浏览器战争引发的碎片化问题趋于解决。 dom的原理 浏览器内核(chrome,firefox等)将网页中的dom节点转换成cpp对象,例如,当你在 JS 中操作 document.getElementById 时,引擎会通过映射来操作内存里的 C++ 对象,这也是你为什么能够在网页中进行键盘和鼠标交互的原因.\ndom的应用 查找元素 (Select) 操作 DOM 的起点，将 HTML 标签映射为 JS 对象。\n按 ID 查找： 返回唯一的单个元素。 1 const header = document.getElementById(\u0026#39;main-header\u0026#39;); 按 CSS 选择器查找： 最灵活的方式。 1 2 const navItem = document.querySelector(\u0026#39;.nav-item\u0026#39;); // 找第一个匹配的 const allButtons = document.querySelectorAll(\u0026#39;button\u0026#39;); // 找所有匹配的（返回 NodeList） 修改内容与属性 (Modify) 一旦拿到对象，就可以直接通过操作 C++ 对象的映射属性来改变网页。\n修改文本内容： 1 2 el.textContent = \u0026#39;新内容\u0026#39;; // 纯文本，更安全 el.innerHTML = \u0026#39;\u0026lt;span\u0026gt;加粗内容\u0026lt;/span\u0026gt;\u0026#39;; // 会解析 HTML 标签 修改 CSS 样式： 1 2 el.style.color = \u0026#39;red\u0026#39;; el.classList.add(\u0026#39;active\u0026#39;); // 推荐做法：通过切换类名控制样式 修改属性： 1 2 el.setAttribute(\u0026#39;src\u0026#39;, \u0026#39;logo.png\u0026#39;); console.log(el.id); // 直接访问属性 创建与插入元素 (Create \u0026amp; Append) 动态生成网页内容。\n1 2 3 4 5 6 7 8 9 10 // 1. 创建元素（此时仅在内存中，未挂载到树上） const newDiv = document.createElement(\u0026#39;div\u0026#39;); newDiv.textContent = \u0026#39;我是新来的\u0026#39;; // 2. 挂载到 DOM 树中（挂载后用户才可见） const container = document.body; container.appendChild(newDiv); // 3. 删除元素 // newDiv.remove(); 事件监听 (Events) 让静态页面具有交互能力。\n1 2 3 4 5 6 7 const btn = document.querySelector(\u0026#39;#submit-btn\u0026#39;); // 监听点击事件 btn.addEventListener(\u0026#39;click\u0026#39;, (event) =\u0026gt; { alert(\u0026#39;按钮被点击了！\u0026#39;); console.log(event.target); // 触发事件的元素 }); ","date":"2026-04-18T08:00:00Z","image":"/p/dom%E8%AF%A6%E8%A7%A3/22566017_p0-%E3%83%A9%E3%83%B3%E3%82%AF%E3%82%A8%E3%82%AF%E3%83%AA%E3%82%A2%E8%A8%98%E5%BF%B5.webp","permalink":"/p/dom%E8%AF%A6%E8%A7%A3/","title":"dom详解"},{"content":"终端工具 本文主要聚焦于Windows系统,尽管有不少命令是和Linux通用的\n网络连接 ping 系统自带组件,基于ICMP实现的网络状态查询工具,换句话说,ping借助TCP/IP协议实现对网络状态的简单解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 用法: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS] [-r count] [-s count] [[-j host-list] | [-k host-list]] [-w timeout] [-R] [-S srcaddr] [-c compartment] [-p] [-4] [-6] target_name 选项: -t Ping 指定的主机，直到停止。 若要查看统计信息并继续操作，请键入 Ctrl+Break； 若要停止，请键入 Ctrl+C。 -a 将地址解析为主机名。 -n count 要发送的回显请求数。 -l size 发送缓冲区大小。 -f 在数据包中设置“不分段”标记(仅适用于 IPv4)。 -i TTL 生存时间。 -v TOS 服务类型(仅适用于 IPv4。该设置已被弃用， 对 IP 标头中的服务类型字段没有任何 影响)。 -r count 记录计数跃点的路由(仅适用于 IPv4)。 -s count 计数跃点的时间戳(仅适用于 IPv4)。 -j host-list 与主机列表一起使用的松散源路由(仅适用于 IPv4)。 -k host-list 与主机列表一起使用的严格源路由(仅适用于 IPv4)。 -w timeout 等待每次回复的超时时间(毫秒)。 -R 同样使用路由标头测试反向路由(仅适用于 IPv6)。 根据 RFC 5095，已弃用此路由标头。 如果使用此标头，某些系统可能丢弃 回显请求。 -S srcaddr 要使用的源地址。 -c compartment 路由隔离舱标识符。 -p Ping Hyper-V 网络虚拟化提供程序地址。 -4 强制使用 IPv4。 -6 强制使用 IPv6。 用例\n1 2 ping google.com # 判断VPN是否有效的最快方法 curl wiki curl,意为\u0026quot;Client for URLs\u0026quot;,是ping的上位替代,可以对指定网页采用多种方式进行查询,支持几乎所有的主流通信协议.由于是开源项目,几乎所有的操作系统都会预先安装. 常用参数一览\ncurl 常用参数速查表 参数 全称 功能描述 典型示例 -I --head 仅获取响应头。常用于检查服务器状态、文件大小或链接有效性。 curl -I https://google.com -v --verbose 调试模式。显示详细的通信过程，包括请求头、响应头及 TLS 握手。 curl -v https://google.com -X --request 指定请求方法。默认为 GET，可手动指定 POST, PUT, DELETE 等。 curl -X POST https://api.com -d --data 发送数据。用于 POST 请求向服务器提交表单数据或 JSON。 curl -d \u0026quot;id=1\u0026quot; https://api.com -H --header 设置请求头。常用于定义 Content-Type 或传入 API Token。 curl -H \u0026quot;Auth: 123\u0026quot; https://api.com -L --location 跟随重定向。当页面发生 301/302 跳转时，自动追踪到目标页。 curl -L http://google.com -o --output 保存为文件。将下载内容写入指定路径，而非直接打印在终端。 curl -o test.html https://abc.com -O --remote-name 远程同名保存。使用 URL 中的文件名作为本地文件名保存。 curl -O https://abc.com/v1.zip -u --user 身份认证。用于输入服务器的用户名和密码（Basic Auth）。 curl -u user:pass https://api.com -k --insecure 忽略 SSL 校验。访问证书过期或自签名的 HTTPS 站点时使用。 curl -k https://expired-site.com -s --silent 静默模式。不显示进度条或错误信息，常用于脚本自动化。 curl -s https://api.com --json --json 发送 JSON（新版本）。自动设置 Content-Type 并以 POST 方式发送。 curl --json '{\u0026quot;id\u0026quot;:1}' https://api.com 用例\n1 2 3 4 5 6 7 curl google.com # 不带任何参数的curl也能返回足够多的信息 StatusCode : 200 StatusDescription : OK Content : (网页内容) ParsedHtml : mshtml.HTMLDocumentClass RawContentLength : 80569 route wiki 用于查看和修改本电脑的IP路由表, 一般用不上 ipconfig wiki 微软官网介绍 Displays all current TCP/IP network configuration values and refreshes Dynamic Host Configuration Protocol (DHCP) and Domain Name System (DNS) settings. Used without parameters, ipconfig displays Internet Protocol version 4 (IPv4) and IPv6 addresses, subnet mask, and default gateway for all adapters.\n即ipconfig用于查看自己电脑的TCP/IP网络配置 nslookup wiki nslookup(Name System Lookup)用于查询DNS记录,检查DNS服务器是否正常 ssh wiki SSH(Secure Shell)协议是一种加密网络协议，用于在不安全的网络上安全地运行网络服务.通常用于登录远程计算机的shell或命令行界面(CLI)，并在远程服务器上执行命令.\n基础操作 1 2 3 ssh root@100.80.251.1 # 输入密码 # 在远程服务器中进行操作 进阶操作(待补充) wget wiki GNU Wget (or just Wget, formerly Geturl, also written as its package name, wget) is a computer program that retrieves content from web servers. It is part of the GNU Project. Its name derives from \u0026ldquo;World Wide Web\u0026rdquo; and \u0026ldquo;get\u0026rdquo;, a HTTP request method. It supports downloading via HTTP, HTTPS, and FTP.\n如上文所说,wget是一个用来下载网络资源的工具,尽管最初在Linux中使用,但Windows Powershell会通过管道运算符复现对应的功能.\n大多数场景下curl可以直接代替wget,因此wget的出现频率正不断下降 使用示例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 PS C:\\Users\\user\u0026gt; wget www.baidu.com StatusCode : 200 StatusDescription : OK Content : \u0026lt;!DOCTYPE html\u0026gt;\u0026lt;!--STATUS OK--\u0026gt;\u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;meta http-equiv=\u0026#34;Content-Type\u0026#34; content=\u0026#34;text/html;chars et=utf-8\u0026#34;\u0026gt;\u0026lt;meta http-equiv=\u0026#34;X-UA-Compatible\u0026#34; content=\u0026#34;IE=edge,chrome=1\u0026#34;\u0026gt;\u0026lt;meta content=\u0026#34;origin-when- cr... RawContent : HTTP/1.1 200 OK Bdpagetype: 1 Bdqid: 0x926a898e00009a46 Connection: keep-alive Content-Length: 632441 Content-Type: text/html; charset=utf-8 Date: Wed, 08 Apr 2026 10:24:26 GMT P3P: CP=\u0026#34; OTI DS... Forms : {form} Headers : {[Bdpagetype, 1], [Bdqid, 0x926a898e00009a46], [Connection, keep-alive], [Content-Length, 632441].. .} Images : {@{innerHTML=; innerText=; outerHTML=\u0026lt;img src=\u0026#34;https://pss.bdstatic.com/static/superman/img/topnav/ newfanyi-da0cea8f7e.png\u0026#34;\u0026gt;; outerText=; tagName=IMG; src=https://pss.bdstatic.com/static/superman/im g/topnav/newfanyi-da0cea8f7e.png}, @{innerHTML=; innerText=; outerHTML=\u0026lt;img src=\u0026#34;https://pss.bdstat ic.com/static/superman/img/topnav/newxueshuicon-a5314d5c83.png\u0026#34;\u0026gt;; outerText=; tagName=IMG; src=http s://pss.bdstatic.com/static/superman/img/topnav/newxueshuicon-a5314d5c83.png}, @{innerHTML=; innerT ext=; outerHTML=\u0026lt;img src=\u0026#34;https://pss.bdstatic.com/static/superman/img/topnav/newbaike-889054f349.p ng\u0026#34;\u0026gt;; outerText=; tagName=IMG; src=https://pss.bdstatic.com/static/superman/img/topnav/newbaike-889 054f349.png}, @{innerHTML=; innerText=; outerHTML=\u0026lt;img src=\u0026#34;https://pss.bdstatic.com/static/superma n/img/topnav/newzhidao-da1cf444b0.png\u0026#34;\u0026gt;; outerText=; tagName=IMG; src=https://pss.bdstatic.com/stat ic/superman/img/topnav/newzhidao-da1cf444b0.png}...} InputFields : {@{innerHTML=; innerText=; outerHTML=\u0026lt;input name=\u0026#34;ie\u0026#34; type=\u0026#34;hidden\u0026#34; value=\u0026#34;utf-8\u0026#34;\u0026gt;; outerText=; tag Name=INPUT; name=ie; type=hidden; value=utf-8}, @{innerHTML=; innerText=; outerHTML=\u0026lt;input name=\u0026#34;f\u0026#34; type=\u0026#34;hidden\u0026#34; value=\u0026#34;8\u0026#34;\u0026gt;; outerText=; tagName=INPUT; name=f; type=hidden; value=8}, @{innerHTML=; innerText=; outerHTML=\u0026lt;input name=\u0026#34;rsv_bp\u0026#34; type=\u0026#34;hidden\u0026#34; value=\u0026#34;1\u0026#34;\u0026gt;; outerText=; tagName=INPUT; nam e=rsv_bp; type=hidden; value=1}, @{innerHTML=; innerText=; outerHTML=\u0026lt;input name=\u0026#34;rsv_idx\u0026#34; type=\u0026#34;hi dden\u0026#34; value=\u0026#34;1\u0026#34;\u0026gt;; outerText=; tagName=INPUT; name=rsv_idx; type=hidden; value=1}...} Links : {@{innerHTML=百度首页; innerText=百度首页; outerHTML=\u0026lt;a class=\u0026#34;toindex\u0026#34; href=\u0026#34;/\u0026#34;\u0026gt;百度首页\u0026lt;/a\u0026gt;; oute rText=百度首页; tagName=A; class=toindex; href=/}, @{innerHTML=设置\u0026lt;i class=\u0026#34;c-icon c-icon-triangle -down\u0026#34;\u0026gt;\u0026lt;/i\u0026gt;; innerText=设置; outerHTML=\u0026lt;a name=\u0026#34;tj_settingicon\u0026#34; class=\u0026#34;pf\u0026#34; href=\u0026#34;javascript:;\u0026#34;\u0026gt;设置 \u0026lt;i class=\u0026#34;c-icon c-icon-triangle-down\u0026#34;\u0026gt;\u0026lt;/i\u0026gt;\u0026lt;/a\u0026gt;; outerText=设置; tagName=A; name=tj_settingicon; cl ass=pf; href=javascript:;}, @{innerHTML=登录; innerText=登录; outerHTML=\u0026lt;a name=\u0026#34;tj_login\u0026#34; class=\u0026#34;l b\u0026#34; onclick=\u0026#34;return false;\u0026#34; href=\u0026#34;https://passport.baidu.com/v2/?login\u0026amp;amp;tpl=mn\u0026amp;amp;u=http%3A%2F%2 Fwww.baidu.com%2F\u0026amp;amp;sms=5\u0026#34;\u0026gt;登录\u0026lt;/a\u0026gt;; outerText=登录; tagName=A; name=tj_login; class=lb; onclick= return false;; href=https://passport.baidu.com/v2/?login\u0026amp;amp;tpl=mn\u0026amp;amp;u=http%3A%2F%2Fwww.baidu.co m%2F\u0026amp;amp;sms=5}, @{innerHTML= 新闻 ; innerText= 新闻 ; outerHTML=\u0026lt;a class=\u0026#34;mnav c-font-normal c-color-t\u0026#34; href=\u0026#34;htt p://news.baidu.com\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt; 新闻 \u0026lt;/a\u0026gt;; outerText= 新闻 ; tagName=A; class=mnav c-font-normal c-color-t; href=htt p://news.baidu.com; target=_blank}...} ParsedHtml : mshtml.HTMLDocumentClass RawContentLength : 632441 从返回内容也能看出来基础功能与curl没什么区别 包管理器 Scoop(推荐) 官网 官网的介绍很简单,只有一行:\nA command-line installer for Windows\n由于微软自己开发了Winget包管理器,故不会预先安装Scoop,需要我们用脚本激活:\n1 2 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression 在终端输入scoop即可查询使用方式:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 PS C:\\Users\\user\u0026gt; scoop Usage: scoop \u0026lt;command\u0026gt; [\u0026lt;args\u0026gt;] Available commands are listed below. Type \u0026#39;scoop help \u0026lt;command\u0026gt;\u0026#39; to get more help for a specific command. Command Summary ------- ------- alias Manage scoop aliases bucket Manage Scoop buckets cache Show or clear the download cache cat Show content of specified manifest. checkup Check for potential problems cleanup Cleanup apps by removing old versions config Get or set configuration values create Create a custom app manifest depends List dependencies for an app, in the order they\u0026#39;ll be installed download Download apps in the cache folder and verify hashes export Exports installed apps, buckets (and optionally configs) in JSON format help Show help for a command hold Hold an app to disable updates home Opens the app homepage import Imports apps, buckets and configs from a Scoopfile in JSON format info Display information about an app install Install apps list List installed apps prefix Returns the path to the specified app reset Reset an app to resolve conflicts search Search available apps shim Manipulate Scoop shims status Show status and check for new app versions unhold Unhold an app to enable updates uninstall Uninstall an app update Update apps, or Scoop itself virustotal Look for app\u0026#39;s hash or url on virustotal.com which Locate a shim/executable (similar to \u0026#39;which\u0026#39; on Linux) scoop解决了应用安装路径不统一的问题,将安装的应用一律放到scoop文件夹中,而且用户可以自己指定默认安装位置.\n尽管大多数应用都可以很方便的使用scoop找到,但对于已经习惯了普通安装方式的我来说还是用的不太顺手.\nWinget(不推荐) 官方说明 WinGet 是一种命令行工具，使用户能够在 Windows 10、Windows 11 和 Windows Server 2025 计算机上发现、安装、升级、删除和配置应用程序\n具体用法只需要在终端输入winget即可了解:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PS C:\\Users\\user\u0026gt; winget WinGet 命令行实用工具可从命令行安装应用程序和其他程序包。 使用情况: winget [\u0026lt;命令\u0026gt;] [\u0026lt;选项\u0026gt;] 下列命令有效: install 安装给定的程序包 show 显示包的相关信息 source 管理程序包的来源 search 查找并显示程序包的基本信息 list 显示已安装的程序包 upgrade 显示并执行可用升级 uninstall 卸载给定的程序包 hash 哈希安装程序的帮助程序 validate 验证清单文件 settings 打开设置或设置管理员设置 features 显示实验性功能的状态 export 导出已安装程序包的列表 import 安装文件中的所有程序包 pin 管理包钉 configure 将系统配置为所需状态 download 从给定的程序包下载安装程序 repair 修复所选包 dscv3 DSC v3 资源命令 mcp MCP 信息 要搜索某个工具，请键入 winget search 确认你需要的工具可用后，可以通过键入 来winget install 该工具。 WinGet 工具会启动安装程序，将应用程序安装在你的电脑上 使用举例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 PS C:\\Users\\uesr\u0026gt; winget search npm 名称 ID 版本 匹配 源 --------------------------------------------------------------------- Node.js OpenJS.NodeJS 25.9.0 Command: npm winget Node.js 10 OpenJS.NodeJS.… 10.24.1 Command: npm winget Node.js 12 OpenJS.NodeJS.… 12.22.12 Command: npm winget Node.js 14 OpenJS.NodeJS.… 14.21.3 Command: npm winget # 一大堆类似的包 PS C:\\Users\\user\u0026gt; winget install bun 找到多个与输入条件匹配的程序包。请修改输入。 名称 ID 源 ------------------------------------- Bundle Generator 9NBLGGH43PMQ msstore Bun Oven-sh.Bun winget # 由于我这里已经安装了,所以就会报错 winget默认全局安装,如果不操心应用安装位置的话,使用winget比上官网找资源是要快一点的;但捣鼓计算机的一般都很在意应用的安装位置,所以winget基本就没什么用了\u0026hellip;\n\u0026ndash;version 尽管很多工具都支持工具名 \u0026ndash;version的方式查询版本,但遗憾的是有一些工具不愿意沿用这个惯例,所以只好单独在这里分类讨论了\n事实上,尽管大多数工具都统一使用\u0026ndash;参数的形式,但也有不少工具使用-参数的形式,真的就没人想过统一一下吗\u0026hellip; 构建工具 gcc/g++ make tar git cmake git docker curl 编程 python node npm ruby java: 使用java -version也可以 不支持的 go: go version,何必少写那两横呢 ","date":"2026-04-08T08:00:00Z","image":"/p/%E5%B7%A5%E5%85%B7%E7%AC%94%E8%AE%B0/95539505_p0-%E7%84%A1%E9%A1%8C.webp","permalink":"/p/%E5%B7%A5%E5%85%B7%E7%AC%94%E8%AE%B0/","title":"工具笔记"},{"content":"接触前后端一段时间了,我对异步这个常用概念仍然没有一个非常好的认识,因此写了这篇文章来进行学习.\n异步概念总览 异步这一概念是相对于同步来说的,我们先来看看同步的定义:\nIn computer science , synchronization is the coordination of simultaneous threads or processes to complete a task with correct runtime order and no unexpected race conditions; see synchronization (computer science) for details.\n也就是说,同步就是顺序执行了,那么异步就是不按顺序执行,那到底是怎么执行的呢?\n非常奇怪的是,你很难找到一个文档去真正的讲清楚异步是什么,我翻阅了各大问答网站,wiki,博客,都很难找到一个真正详尽的解答,这反过来说明这个概念并非如想象中的那般简单.\n让我们看看AI怎么说(尽管我用的是免费模型):\nGemini说 从物理机制和执行模型来看，异步（Asynchronous） 的本质是：允许调用方在发起一个耗时操作后，不等待结果返回，直接继续执行后续任务。\n为了透彻理解，我们需要从“阻塞”与“让渡”的核心逻辑出发：\n1. 核心模型：同步 vs. 异步 同步 (Synchronous)：线性执行。CPU 发起 I/O 请求（如读文件、连数据库）后，必须原地“挂起”等待硬件返回数据。此时 CPU 虽然空闲，但无法处理其他任务，这就是阻塞。 异步 (Asynchronous)：非线性执行。CPU 发起 I/O 请求后，立即标记该任务为“等待中”，然后转身去处理其他代码。当硬件数据准备好时，通过事件通知（回调、信号或 Promise）告知 CPU 回来处理结果。 2. 生活化类比：餐厅模式 同步模式（排队取餐）：你在柜台点完餐，必须站在收银台前等着厨师做。厨师没做好，你不能走，后面的人也点不了餐。你的时间被彻底阻塞。 异步模式（取餐铃）：你点完餐，服务员给你一个“取餐铃”（对应编程中的 Future 或 Promise）。你可以回座位玩手机或看书。当铃声响了（对应回调通知），你才过去取餐。你没有被“阻塞”在柜台。 3. 底层物理基础：为什么需要异步？ 计算机硬件的速度存在极大的数量级差异，这是异步存在的根本原因：\n操作 消耗 CPU 指令周期（近似） 等效人类时间 CPU 寄存器操作 1 周期 1 秒 内存访问 200 周期 3 分钟 SSD 硬盘读取 2,000,000 周期 23 天 网络请求 (跨海) 200,000,000 周期 6 年 结论：如果采用同步模式，CPU 在等待一次网络请求时，相当于浪费了人类时间里的“6 年”。异步通过让渡控制权，让 CPU 在这“6 年”里去处理成千上万个其他任务。\n4. 常见的实现机制 异步在不同领域有不同的物理表现：\n多线程 (Multi-threading)：开一个新的员工（线程）去等，主员工继续干活。缺点是切换员工（上下文切换）很累，且费工资（内存占用高）。 事件循环 (Event Loop)：如 JavaScript 或 Python 的 asyncio。只有一个员工，但他手里有个待办列表。遇到耗时任务就往列表一扔，做完一个就看一眼列表有没有回信。 回调函数 (Callback)：“做完了叫我”。 Promise / Await：“给我一个承诺，好了我再继续”。 5. 关键误区纠偏 异步 ≠ 并行： 并行是“多个人同时搬砖”。 异步是“一个人在等砖拉过来的同时，去拌水泥”。 异步不一定更快： 对于 计算密集型（纯算数学题），异步反而慢，因为多了调度开销。 对于 I/O 密集型（等网络、等文件），异步能极大地提高系统的并发能力（吞吐量）。 概念辨析 AI对异步的概念定义如下: 允许调用方在发起一个耗时操作后，不等待结果返回，直接继续执行后续任务。\n从这个角度来看,我们平常所说的多线程并不是异步,多线程是将同一个任务拆分成了很多小任务去执行,但异步并不存在将任务分开执行这个概念,而是存在一个顺序上的先后划分.\n因此,我们可以这样解释异步:\n将耗时的,或者需要其他操作来返回结果的任务挂起,优先执行其他任务,等到需要执行这个任务时再执行.\n以python爬虫为例:\n通过 asyncio 库和 await 关键字，程序在发起网络请求后不再原地阻塞，而是去执行其他请求，直到数据返回再回调处理。这种模式在单线程下实现了高并发，极大地提升了 I/O 密集型任务的吞吐量\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import asyncio import aiohttp async def fetch(session, url): # 发送请求并挂起，等待期间 CPU 可以处理其他任务 async with session.get(url) as response: return await response.text() async def main(): urls = [\u0026#34;https://example.com\u0026#34; for _ in range(10)] # 使用 aiohttp 管理异步连接池 async with aiohttp.ClientSession() as session: # 并发创建所有爬取任务 tasks = [fetch(session, url) for url in urls] # 统一等待所有任务完成 pages = await asyncio.gather(*tasks) print(f\u0026#34;成功爬取 {len(pages)} 个页面\u0026#34;) # 启动事件循环 if __name__ == \u0026#34;__main__\u0026#34;: asyncio.run(main()) ","date":"2026-04-07T08:00:00Z","image":"/p/%E5%BC%82%E6%AD%A5%E6%A6%82%E5%BF%B5%E5%89%96%E6%9E%90/62492065_p0-%E3%83%AF%E3%83%B3%E3%83%94%E3%83%BC%E3%82%B9%E3%81%AE%E3%81%8A%E3%82%93%E3%81%AA%E3%81%AE%E3%81%93.webp","permalink":"/p/%E5%BC%82%E6%AD%A5%E6%A6%82%E5%BF%B5%E5%89%96%E6%9E%90/","title":"异步概念剖析"},{"content":"前置知识: cpp编译原理(4/8) 编译器种类 市面上有三大主流编译器\n微软开发的MSVC(Microsoft Visual C++),集成在VS中,另外一个在Windows系统常用的MinGW编译器则是GCC的物理移植版 开源的GCC(GNU Compiler Collection),主要在Linux中使用 基于GCC和LLVM(开源编译器后端)的Clang,由苹果公司赞助,是macOS唯一官方支持的编译器,集成在Xcode中. 当然现在的电脑性能这么好,用哪个编译器都可以.\n编译的全过程 当我们使用GCC编译Hello World程序时,只需要这样写:\n1 2 gcc hello.c -o ./a.out # \u0026#39;./a.out\u0026#39;是文件名和路径,后缀名可以随便起,写成tho没有后缀或者a.xyz也可以 上述过程可以分解为4个步骤:\n预处理(Preprocessing) 编译(Compilation) 汇编(Assembly) 链接(Linking) 流水线解释\n预处理: 转换宏定义,删除注释 编译(狭义): 将cpp源码翻译成汇编代码(人类可读) 汇编: 将汇编代码翻译成机器指令(二进制码) 根据机器指令,地址位置等信息构造目标文件 链接: 将目标文件与系统库,用户库关联起来,得到可执行文件 编译(广义): 由于大多数人对cpp的装载过程没有一个清晰的认识,故通常使用编译代指从.cpp到.exe的全过程,也就是说我们一般都用广义的编译概念,很少特指\u0026quot;真正的编译\u0026quot; 但是,我们所用的编译器如gcc,clang等都是广义上的编译器,也就是说不仅仅做的是编译,而是包揽了从.cpp到.exe的全构建过程. 如果用前端和后端的概念来划分的话,是这样的:\n前端（Frontend） 范畴： 仅包含“编译”这一步的前半部分。\n输入： 预处理后的源码。 任务： 词法分析（Lexical Analysis）、语法分析（Syntax Analysis）、语义分析（Semantic Analysis）、生成中间表示（IR, Intermediate Representation）。 特性： 与具体的硬件架构（如 x86、ARM）无关，只与语言本身的规则有关。 后端（Backend） 范畴： 包含“编译”这一步的后半部分，以及“汇编”的全部。\n任务： * 中端优化（Optimizer）：对 IR 进行架构无关的优化。 代码生成（Code Generator）：将 IR 转换为特定硬件的汇编代码。 汇编器（Assembler）：将汇编代码转换为机器指令，产出目标文件。 特性： 强依赖于硬件架构。 其他项 预处理（Preprocessing）：通常被视为编译前的“文本清洁工作”，不属于狭义编译器（Compiler Core）的前后端逻辑。 链接（Linking）：属于编译链条的下游，是一个独立的二进制处理过程，不属于编译器（Compiler）的范畴。 为什么需要cpp工程构建 参考1 参考2 多文件管理 在一个文件里导入其他文件中的变量有两种方法: 1.使用extern关键字\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 文件 A (data.cpp) struct Counter { int value; void display() const { std::cout \u0026lt;\u0026lt; value \u0026lt;\u0026lt; std::endl; } }; Counter g_tracker = {100}; int global_count = 100; // 文件 B (main.cpp) extern int global_count; extern Counter g_tracker; void print_count() { g_tracker.display(); std::cout \u0026lt;\u0026lt; global_count \u0026lt;\u0026lt; std::endl; } int main() { print_count(); return 0; } 也就是说我们需要在用到这个变量的时候使用extern关键字来声明,才能让编译器明白这个变量是要到其他文件中去找的.\n2.使用头文件 当需要共享的变量或函数过多时,再一个个写extern不太现实,所以cpp使用者设计了单独的文件类型用来存放共享变量(包括常量,外部变量)的声明,也就是.h文件.\n1 2 3 4 5 6 7 8 9 10 // vars.h #ifndef VARS_H #define VARS_H // include guard,防止同一个头文件在同一个cpp文件中被多次导入 extern int shared_val; // 声明 #endif // vars.cpp #include \u0026#34;vars.h\u0026#34; int shared_val = 42; // 定义 当然,更为常见的是用class来封装变量和函数再放入.h文件,并在对应的cpp文件里实现: cocos示例项目\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 //AppDelegate.h #ifndef _APP_DELEGATE_H_ #define _APP_DELEGATE_H_ #include \u0026#34;cocos2d.h\u0026#34; class AppDelegate : private cocos2d::Application { public: AppDelegate(); virtual ~AppDelegate(); virtual void initGLContextAttrs(); }; #endif // _APP_DELEGATE_H_ //同名cpp文件,其实起什么名字都行,但为了标准化还是同名为好 #include \u0026#34;AppDelegate.h\u0026#34; // 导入后进行实现 AppDelegate::AppDelegate() { } AppDelegate::~AppDelegate() { #if USE_AUDIO_ENGINE AudioEngine::end(); #elif USE_SIMPLE_AUDIO_ENGINE SimpleAudioEngine::end(); #endif } void AppDelegate::initGLContextAttrs() { // set OpenGL context attributes: red,green,blue,alpha,depth,stencil,multisamplesCount GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8, 0}; GLView::setGLContextAttrs(glContextAttrs); } // ...诸如此类的实现 一个标准.h文件的例子 #pragma once: \u0026lsquo;pragma\u0026rsquo; 源自希腊语 \u0026lsquo;pragma\u0026rsquo;（意为“行动”或“事项”）,代指编译指令,整体的意思是只编译一次,也就是第二次在同一个cpp文件里遇到这个头文件时跳过编译 是msvc最早开始启用的预处理指令,之后GCC,Clang也开始支持这个指令,但至今都未纳入cpp标准中 这个奇怪的名字显然是某个自以为很有修养的工程师提出来的,正常人是不会这么起名的 以下示例显示了头文件中允许的各种声明和定义：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 // sample.h #pragma once #include \u0026lt;vector\u0026gt; // #include directive #include \u0026lt;string\u0026gt; namespace N // namespace declaration { inline namespace P { //... } enum class colors : short { red, blue, purple, azure }; const double PI = 3.14; // const and constexpr definitions constexpr int MeaningOfLife{ 42 }; constexpr int get_meaning() { static_assert(MeaningOfLife == 42, \u0026#34;unexpected!\u0026#34;); // static_assert return MeaningOfLife; } using vstr = std::vector\u0026lt;int\u0026gt;; // type alias extern double d; // extern variable #define LOG // macro definition #ifdef LOG // conditional compilation directive void print_to_log(); #endif class my_class // regular class definition, { // but no non-inline function definitions friend class other_class; public: void do_something(); // definition in my_class.cpp inline void put_value(int i) { vals.push_back(i); } // inline OK private: vstr vals; int i; }; struct RGB { short r{ 0 }; // member initialization short g{ 0 }; short b{ 0 }; }; template \u0026lt;typename T\u0026gt; // template definition class value_store { public: value_store\u0026lt;T\u0026gt;() = default; void write_value(T val) { //... function definition OK in template } private: std::vector\u0026lt;T\u0026gt; vals; }; template \u0026lt;typename T\u0026gt; // template declaration class value_widget; } 你很有可能会问为什么头文件中函数,class,struct等高级数据类型的声明不需要用extern修饰,而单独的变量声明却必须要用extern修饰? extern的原理是: 告诉编译器这个变量在别处已经定义;如果单纯写int x;,那么编译器就认为这是一个未进行初始化的新定义,会为x再次申请4字节的内存,从而引发重定义报错 函数的声明默认是为extern的 而类和结构体的声明不需要extern,因为它们本身不产生任何内存分配,只有实例化的对象才需要内存分配 头文件的由来 我们需要明确一个事实:cpp标准从没有规定cpp文件和头文件名字的扩展名要求! 事实上如果你将main函数放入x.txt文件中,照样可以正常编译:\n1 2 # g++根据扩展名推断语言,所以这里需要用-x c++强制指令语言为cpp,而且我们产生的exe文件使用了txt后缀也没报错 g++ -x c++ x.txt -o result.txt 换句话说,.cpp,.h这些后缀只不过是人为约定的而已,你在里面写的内容与文件名可以毫无关系,也就是说,就算你在头文件里实现了函数的定义也没关系,只要你没有导入进两个或更多文件里,就不会导致函数的重定义进而引发编译器的报错.\n复杂项目的处理 当然,如果只有一两个文件的话,我们只用g++进行编译也够了,比如有一个main.cpp和一个tools.cpp文件,那么我们只要写:\n1 g++ main.cpp tools.cpp -o result.exe 但事实上,我们需要根据各种各样的需求加上各种各样的参数:\n参数 分类 作用说明 示例 -o \u0026lt;file\u0026gt; 基础 指定输出文件名。如果不使用，Linux 默认生成 a.out，Windows 默认 a.exe。 g++ main.cpp -o app -c 基础 只编译，不链接。将 .cpp 转化为二进制目标文件 .o，用于大型项目的增量编译。 g++ -c tools.cpp -I \u0026lt;dir\u0026gt; 基础 指定头文件搜索路径。当 #include 的头文件不在当前目录或标准库时使用。 g++ main.cpp -I./include -L \u0026lt;dir\u0026gt; 基础 指定库文件搜索路径。告诉编译器去哪里找 .so 或 .a 静态/动态库文件。 g++ main.cpp -L./libs -l\u0026lt;name\u0026gt; 基础 链接指定的库。紧跟库名（自动去掉 lib 前缀和 .so/.a 后缀）。 g++ main.cpp -lpthread -g 调试 生成调试信息。在使用 gdb 调试器时，必须加此参数才能看到源码行号。 g++ -g main.cpp -o debug_app -Wall 警告 开启常规警告。检测潜在的逻辑错误（如变量未初始化、类型不匹配）。 g++ -Wall main.cpp -Wextra 警告 开启额外警告。比 -Wall 更严格，能发现更多隐蔽的代码规范问题。 g++ -Wall -Wextra main.cpp -Werror 警告 将警告视为错误。只要有任何警告出现，编译就会立即终止。 g++ -Werror main.cpp -O0 优化 不进行优化（默认）。编译最快，生成的代码与源码一一对应，适合开发阶段。 g++ -O0 main.cpp -O2 优化 标准优化。平衡编译时间和运行速度，是大多数生产环境项目的首选。 g++ -O2 main.cpp -O3 优化 激进优化。开启更多高级手段（如循环展开），提升性能但可能增大体积。 g++ -O3 main.cpp -Os 优化 体积优化。优先缩小生成的二进制文件大小，适合嵌入式或空间受限场景。 g++ -Os main.cpp -std=c++11/17/20 标准 指定 C++ 语言版本标准。开启对应年份的新特性支持（如 auto, concepts）。 g++ -std=c++17 main.cpp -D\u0026lt;macro\u0026gt; 预处理 定义宏。相当于在代码顶端写 #define，常用于区分测试和生产逻辑。 g++ -DDEBUG main.cpp -E 预处理 只执行预处理。查看宏展开、头文件包含后的最终代码文本，输出到屏幕。 g++ -E main.cpp -fPIC 进阶 生成位置无关代码。在编译动态链接库（Shared Library）时必须使用。 g++ -fPIC -c lib.cpp 显然当文件一多,要链接的库一多,要进行的预编译一多,用g++来写是不可接受的.\n让我们先以下面这个cmakelist.txt为例来看看编译时要考虑多少东西: cocos示例项目的CMakelists.txt\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 cmake_minimum_required(VERSION 3.6) set(APP_NAME HelloCpp) project(${APP_NAME}) set(COCOS2DX_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cocos2d) set(CMAKE_MODULE_PATH ${COCOS2DX_ROOT_PATH}/cmake/Modules/) include(CocosBuildSet) add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos ${ENGINE_BINARY_PATH}/cocos/core) # record sources, headers, resources... set(GAME_SOURCE) set(GAME_HEADER) set(GAME_RES_FOLDER \u0026#34;${CMAKE_CURRENT_SOURCE_DIR}/Resources\u0026#34; ) if(APPLE OR WINDOWS) cocos_mark_multi_resources(common_res_files RES_TO \u0026#34;Resources\u0026#34; FOLDERS ${GAME_RES_FOLDER}) endif() # add cross-platforms source files and header files list(APPEND GAME_SOURCE Classes/AppDelegate.cpp Classes/HelloWorldScene.cpp ) list(APPEND GAME_HEADER Classes/AppDelegate.h Classes/HelloWorldScene.h ) if(ANDROID) # change APP_NAME to the share library name for Android, it\u0026#39;s value depend on AndroidManifest.xml set(APP_NAME MyGame) list(APPEND GAME_SOURCE proj.android/app/jni/hellocpp/main.cpp ) elseif(LINUX) list(APPEND GAME_SOURCE proj.linux/main.cpp ) elseif(WINDOWS) list(APPEND GAME_HEADER proj.win32/main.h proj.win32/resource.h ) list(APPEND GAME_SOURCE proj.win32/main.cpp proj.win32/game.rc ${common_res_files} ) elseif(APPLE) if(IOS) list(APPEND GAME_HEADER proj.ios_mac/ios/AppController.h proj.ios_mac/ios/RootViewController.h ) set(APP_UI_RES proj.ios_mac/ios/LaunchScreen.storyboard proj.ios_mac/ios/LaunchScreenBackground.png proj.ios_mac/ios/Images.xcassets ) list(APPEND GAME_SOURCE proj.ios_mac/ios/main.m proj.ios_mac/ios/AppController.mm proj.ios_mac/ios/RootViewController.mm proj.ios_mac/ios/Prefix.pch ${APP_UI_RES} ) elseif(MACOSX) set(APP_UI_RES proj.ios_mac/mac/Icon.icns proj.ios_mac/mac/Info.plist ) list(APPEND GAME_SOURCE proj.ios_mac/mac/main.cpp proj.ios_mac/mac/Prefix.pch ${APP_UI_RES} ) endif() list(APPEND GAME_SOURCE ${common_res_files}) endif() # 省略一大堆编译项 这显然超出了g++的能力了\u0026hellip; 构建工具的出现 GNU make ninja作者自述 ninja官网 为了解决上述的问题,先后诞生了两种主流的cpp构建工具: make和ninja,它们可以指挥gcc或者其他编译器进行所需的构建. make 是什么,怎么用 The make utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them.\n为了执行make命令,我们需要将它写入makefile文档来执行.\nmakefile的大致格式如下:\n1 2 3 4 target … : prerequisites … recipe … … target: 生成的目标文件名,比如中间文件(.o)和可执行文件(.exe),当然我们不指明后缀名也行,还可以是要执行的指令名如\u0026rsquo;clean\u0026rsquo;等 prerequisites: 需要用到的源文件 recipe: make构建时的规则 以下是一个简单的makefile示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 edit : main.o kbd.o command.o display.o \\ insert.o search.o files.o utils.o cc -o edit main.o kbd.o command.o display.o \\ insert.o search.o files.o utils.o # 将所有中间文件编译成可执行文件 # 使用\\将一行长句分为两行,也就是说不带\\的话默认为单独一行,而makefile中一般一行写源文件名,一行写编译规则 main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o \\ insert.o search.o files.o utils.o 很明显,这个makefile是面向Linux系统的,毕竟有rm和cc这样的终端命令. 在当前目录输入make即可生成edit可执行文件,输入make clean即可清除中间文件 make处理makefile的原理 默认情况下,make命令会从makefile里的第一个target开始执行.\nmake reads the makefile in the current directory and begins by processing the first rule. In the example, this rule is for relinking edit; but before make can fully process this rule, it must process the rules for the files that edit depends on, which in this case are the object files. Each of these files is processed according to its own rule. These rules say to update each ‘.o’ file by compiling its source file. The recompilation must be done if the source file, or any of the header files named as prerequisites, is more recent than the object file, or if the object file does not exist.\nThe other rules are processed because their targets appear as prerequisites of the goal. If some other rule is not depended on by the goal (or anything it depends on, etc.), that rule is not processed, unless you tell make to do so (with a command such as make clean).\n也就是说,make命令只执行第一个target并解决所有的对应依赖项,不会执行第二个命令,除非显示指明 After recompiling whichever object files need it, make decides whether to relink edit. This must be done if the file edit does not exist, or if any of the object files are newer than it. If an object file was just recompiled, it is now newer than edit, so edit is relinked.\nThus, if we change the file insert.c and run make, make will compile that file to update insert.o, and then link edit. If we change the file command.h and run make, make will recompile the object files kbd.o, command.o and files.o and then link the file edit.\nmake会在target对应的源文件更新时自动重新编译target,而不触及其他未改动的部分 事实上了解到这里就差不多了,毕竟现在真的没必要手写makefile了,电脑系统再怎么古老CMake应该还是能用的吧\u0026hellip;\nninja 是什么,怎么用 Ninja is yet another build system. It takes as input the interdependencies of files (typically source code and output executables) and orchestrates building them, quickly.\nninja能够代替古老的make的原因就在于它很快,比make快了十倍以上 Ninja contains the barest functionality necessary to describe arbitrary dependency graphs. Its lack of syntax makes it impossible to express complex decisions.\nNinja has almost no features; just those necessary to get builds correct while punting most complexity to generation of the ninja input files. Ninja by itself is unlikely to be useful for most projects.\n事实上,ninja的设计初衷就是追求快速,摒弃一切不必要的功能,从而大大提高了构建速度. ninja官网的说明文档在加入了一大堆参数说明后仍然远远短于make官网的说明文档 一个简短的示例\n1 2 3 4 5 6 7 8 cflags = -Wall rule cc command = gcc $cflags -c $in -o $out # rule类似于make中的target,但用法上灵活的多 # $为变量插入声明,也就是说,这里的$cflags相当于-Wall # 当然,我们也可以写成${cflags},看个人喜好 build foo.o: cc foo.c 我们需要将这段代码放入build.ninja文件中,再在终端执行ninja命令即可进行构建,用法与make的命令也基本类似.\n实际上我们了解到这个程度也就足够了,只需要知道ninja的原理与make类似,但写法上灵活的多,构建速度也快的多.\n高级构建工具: CMake 现在来到了我们的重头戏:CMake,先来看一下wiki介绍\nCMake是个一个开源的跨平台自动化建构系统，用来管理软件建置的程序，并不依赖于某特定编译器，并可支持多层目录、多个应用程序与多个函数库. CMake的配置文件取名为CMakeLists.txt,它并不直接建构出最终的软件，而是产生标准的构建文件（如Unix的Makefile）\nCMake”这个名字是Cross platform Make的缩写。虽然名字中含有“make”，但是CMake和Unix上常见的make系统是分开的，而且更为高阶\n既然CMake是用来指挥ninja,make等构建文件的,自然它就是高级构建工具了. 怎么用 参考教程 除非另行说明，你始终应该建立一个专用于构建的目录并在那里构建项目。从技术上来讲，你可以进行内部构建（即在源代码目录下执行 CMake 构建命令），但是必须注意不要覆盖文件或者把它们添加到 git，所以别这么做就好。\n一个经典的CMake构建流程如下:\n1 2 3 4 5 6 7 8 9 10 11 mkdir build # 在当前目录创建build文件夹 # 尽管这是Linux命令,但Windows的Powershell现在也支持了 cd build cmake .. # 根据上级目录里的CMakelists进行CMake构建 # 并将构建出来的makefile放入build文件夹中 make # 自然,我们未必会使用make进行构建,所以还可以写成以下形式: cmake --build . # 在build目录中使用cmake的默认构建工具进行构建 但是四行命令明显太多了,我们可以这样写:\n1 2 3 4 5 cmake -S . -B build # -S: source,指定CMakelists所在目录 # -B: build,指定CMake的输出(如makefile,ninja.build)目录 cmake --build build # 在build目录中使用cmake的默认构建工具进行构建 也就是说,我们可以在根目录执行cmake命令,或者进入build文件夹后再执行cmake命令 自然,当我们的电脑上安装了多种构建工具时,我们希望在初次构建时选定自己所需的那种,只需要这么写:\n1 2 3 cmake -S . -B build -G \u0026#34;Ninja\u0026#34; # 指定ninja # -G: generator,构建工具 运行cmake --help可以查看基础的cmake命令和该操作系统上可用的构建工具:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 cmake --help Usage cmake [options] \u0026lt;path-to-source\u0026gt; cmake [options] \u0026lt;path-to-existing-build\u0026gt; cmake [options] -S \u0026lt;path-to-source\u0026gt; -B \u0026lt;path-to-build\u0026gt; Specify a source directory to (re-)generate a build system for it in the current working directory. Specify an existing build directory to re-generate its build system. Options -S \u0026lt;path-to-source\u0026gt; = Explicitly specify a source directory. -B \u0026lt;path-to-build\u0026gt; = Explicitly specify a build directory. -C \u0026lt;initial-cache\u0026gt; = Pre-load a script to populate the cache. -G \u0026lt;generator-name\u0026gt; = Specify a build system generator. -T \u0026lt;toolset-name\u0026gt; = Specify toolset name if supported by generator. -A \u0026lt;platform-name\u0026gt; = Specify platform name if supported by generator. # ...省略一大堆参数 Generators The following generators are available on this platform (* marks default): Visual Studio 18 2026 = Generates Visual Studio 2026 project files. Use -A option to specify architecture. * Visual Studio 17 2022 = Generates Visual Studio 2022 project files. Use -A option to specify architecture. Visual Studio 16 2019 = Generates Visual Studio 2019 project files. Use -A option to specify architecture. Visual Studio 15 2017 = Generates Visual Studio 2017 project files. Use -A option to specify architecture. Visual Studio 14 2015 = Deprecated. Generates Visual Studio 2015 project files. Use -A option to specify architecture. Borland Makefiles = Generates Borland makefiles. NMake Makefiles = Generates NMake makefiles. NMake Makefiles JOM = Generates JOM makefiles. MSYS Makefiles = Generates MSYS makefiles. MinGW Makefiles = Generates a make file for use with mingw32-make. Green Hills MULTI = Generates Green Hills MULTI files (experimental, work-in-progress). Unix Makefiles = Generates standard UNIX makefiles. Ninja = Generates build.ninja files. Ninja Multi-Config = Generates build-\u0026lt;Config\u0026gt;.ninja files. # ...省略一大堆 基础语法 当我们运行的是别人的项目时,知道如何用CMake构建就足够了,但很多时候我们都要自己写CMake来构建项目,这就需要我们去深入了解CMakelists的写法了.\n官方教程 CMakelists.txt的前置内容 最低版本要求 这是每个CMakeLists.txt都必须包含的第一行:\n1 2 3 4 cmake_minimum_required(VERSION 4.3) # 该命令不区分大小写,但习惯上小写 # CMake3.12以后的版本支持版本的范围要求: cmake_minimum_required(VERSION 3.12...3.21) 如果你用的cmake版本比声明上所写的更高,由于cmake向后兼容,因此会按照声明的版本来运行. 项目设置 声明该项目的名字,版本号,描述,使用的语言 1 2 3 4 project(MyProject VERSION 1.0 DESCRIPTION \u0026#34;Very nice project\u0026#34; LANGUAGES CXX) # 项目名字之外的参数没有顺序要求 When CMake sees the project() command it performs various checks to ensure the environment is suitable for building software; such as checking for compilers and other build tooling, and discovering properties like the endianness of the host and target machines.\n这两个部分最好放在顶部或者接近顶部,如:\n1 2 3 4 5 # ...版权声明 cmake_minimum_required(VERSION 3.23) project(MyProjectName) # ...剩余部分 CMake的一点基础特性 The only fundamental types in CMakeLang are strings and lists. Every object in CMake is a string, and lists are themselves strings which contain semicolons as separators.\n由于CMake的这个特性,故CMakelist看起来非常累,没有:,没有单引号,也没有--,只通过空格,空行和缩进来体现层次关系,注释则使用#. 设置变量并插入字符串\n1 2 set(var \u0026#34;World!\u0026#34;) message(\u0026#34;Hello ${var}\u0026#34;) set用于设置变量 message用于打印调试信息 ${var}用于插入变量 常用命令汇总(AI总结) 🔧 基础命令与项目配置 命令 / 语法 说明 示例 cmake_minimum_required(VERSION ...) 指定所需 CMake 最低版本 cmake_minimum_required(VERSION 3.15) project(Name VERSION x.x LANGUAGES CXX) 定义项目名称、版本与语言 project(MyApp VERSION 1.0 LANGUAGES CXX) add_executable(target source...) 创建可执行文件目标 add_executable(app main.cpp helper.cpp) add_library(target [STATIC|SHARED] source...) 创建库目标（静态/动态） add_library(utils STATIC utils.cpp) add_subdirectory(dir) 添加子目录构建 add_subdirectory(libs/foo) include_directories(dir) 添加全局头文件搜索路径（不推荐） include_directories(${CMAKE_SOURCE_DIR}/include) target_include_directories(target [PRIVATE|PUBLIC] dir) 为目标添加头文件路径（现代用法） target_include_directories(app PRIVATE include/) target_link_libraries(target [PRIVATE|PUBLIC] lib...) 为目标链接库 target_link_libraries(app PRIVATE utils) target_compile_options(target [PRIVATE|PUBLIC] flags) 为目标添加编译选项 target_compile_options(app PRIVATE -Wall -Wextra) set_target_properties(target PROPERTIES prop value) 设置目标属性（如输出名称、C++标准） set_target_properties(app PROPERTIES CXX_STANDARD 17) 📦 变量与常用预设变量 语法 / 变量名 说明 示例 / 默认值 set(VAR value) 设置普通变量 set(SRC_FILES main.cpp utils.cpp) set(VAR value CACHE TYPE \u0026quot;doc\u0026quot;) 设置缓存变量（可被命令行覆盖） set(ENABLE_TESTS ON CACHE BOOL \u0026quot;Build tests\u0026quot;) list(APPEND VAR value) 向列表变量追加值 list(APPEND SRC_FILES extra.cpp) option(VAR \u0026quot;description\u0026quot; default) 定义布尔选项（缓存） option(BUILD_SHARED_LIBS \u0026quot;Build shared libs\u0026quot; OFF) ${VAR} 引用变量 message(\u0026quot;Source files: ${SRC_FILES}\u0026quot;) CMAKE_SOURCE_DIR 顶层 CMakeLists.txt 所在目录 自动由 CMake 设置 CMAKE_BINARY_DIR 顶层构建目录 执行 cmake 的目录 CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在目录 - CMAKE_CXX_STANDARD 设置 C++ 标准（全局） set(CMAKE_CXX_STANDARD 17) PROJECT_NAME 当前项目名称 message(${PROJECT_NAME}) PROJECT_SOURCE_DIR 当前项目源码根目录 同 CMAKE_SOURCE_DIR（若仅一个项目） PROJECT_BINARY_DIR 当前项目构建目录 通常为 ${CMAKE_BINARY_DIR} 子目录 🔍 查找依赖与包管理 命令 说明 示例 find_package(Package [REQUIRED] [COMPONENTS ...]) 查找并加载外部依赖包 find_package(OpenCV REQUIRED COMPONENTS core imgproc) find_library(VAR name PATHS ...) 查找库文件路径 find_library(MATH_LIB m) find_path(VAR name PATHS ...) 查找头文件所在目录 find_path(CURL_INCLUDE_DIR curl/curl.h) 🔀 条件判断与循环 语法 说明 示例 if(condition) ... elseif() ... else() ... endif() 条件分支 if(WIN32) ... elseif(UNIX) ... endif() foreach(var IN LISTS list) ... endforeach() 列表循环 foreach(src ${SRC_FILES}) message(${src}) endforeach() while(condition) ... endwhile() while 循环 while(${counter} LESS 10) ... endwhile() break() / continue() 跳出循环 / 进入下一次迭代 在循环内使用 AND / OR / NOT 逻辑运算符 if(NOT DEFINED VAR) EXISTS path 判断文件/目录是否存在 if(EXISTS ${CMAKE_SOURCE_DIR}/config.h) TARGET target 判断目标是否已定义 if(TARGET mylib) ⚙️ 函数与宏 语法 说明 示例 function(name arg1 arg2) ... endfunction() 定义函数（变量作用域独立） function(print_msg msg) message(${msg}) endfunction() macro(name arg1 arg2) ... endmacro() 定义宏（变量作用域与调用者相同） macro(add_sources) list(APPEND SRC ${ARGN}) endmacro() return() 从函数或文件中提前返回 if(NOT VALID) return() endif() include(file) 载入并执行其他 CMake 脚本 include(cmake/Utilities.cmake) 📁 文件操作与安装 命令 说明 示例 file(GLOB VAR pattern) 使用通配符获取文件列表（不推荐用于源码收集） file(GLOB HEADERS \u0026quot;include/*.h\u0026quot;) file(COPY src DESTINATION dst) 复制文件/目录 file(COPY config.yaml DESTINATION ${CMAKE_BINARY_DIR}) configure_file(input output @ONLY) 替换模板变量生成配置文件 configure_file(config.h.in config.h @ONLY) install(TARGETS target DESTINATION dir) 安装目标（可执行文件、库） install(TARGETS app DESTINATION bin) install(FILES file DESTINATION dir) 安装普通文件 install(FILES README.md DESTINATION share/doc) install(DIRECTORY dir DESTINATION dir) 安装目录 install(DIRECTORY assets/ DESTINATION share/assets) \\\n🖨️ 消息输出与调试 命令 说明 示例 message([STATUS|WARNING|FATAL_ERROR] \u0026quot;text\u0026quot;) 打印消息 message(STATUS \u0026quot;Configuring ${PROJECT_NAME}\u0026quot;) message(STATUS \u0026quot;var = ${var}\u0026quot;) 打印变量值 常用于调试 message(FATAL_ERROR \u0026quot;missing dependency\u0026quot;) 致命错误，停止配置 - 实战 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 cmake_minimum_required(VERSION 3.6) set(APP_NAME cpp-empty-test) project(${APP_NAME}) if(NOT DEFINED BUILD_ENGINE_DONE) # DEFINED: 关键字,检查该变量是否用set关键字定义过 set(COCOS2DX_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../..) # CMAKE_CURRENT_SOURCE_DIR: 内置变量,表示当前的txt文件所在目录 # /..表示往上级查找,相当于找到上两级的根目录 set(CMAKE_MODULE_PATH ${COCOS2DX_ROOT_PATH}/cmake/Modules/) # CMAKE_MODULE_PATH: 内置变量,存放查找自定义脚本文件的搜索路径 include(CocosBuildSet) # include: 关键字,执行对应的自定义CMake脚本文件 add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos ${ENGINE_BINARY_PATH}/cocos/core) # add_subdirectory: 将该目录加入构建系统 endif() # record sources, headers, resources... set(GAME_SOURCE) set(GAME_HEADER) # 我们之前的set都会在名字后面加对应的变量名,如果不加默认为空字符串,如果先前已经定义则会重置该变量为空. set(GAME_RES_FOLDER \u0026#34;${CMAKE_CURRENT_SOURCE_DIR}/Resources\u0026#34; ) if(APPLE OR VS) cocos_mark_multi_resources(cc_common_res RES_TO \u0026#34;Resources\u0026#34; FOLDERS ${GAME_RES_FOLDER}) endif() # APPLE: cmake内置变量,根据当前平台判断是否为真 # VS: cocos自定义变量,判断是否使用VS进行编译 list(APPEND GAME_HEADER Classes/AppMacros.h Classes/HelloWorldScene.h Classes/AppDelegate.h ) list(APPEND GAME_SOURCE Classes/AppDelegate.cpp Classes/HelloWorldScene.cpp ) # 将对应的文件添加到先前定义的两个列表变量中 if(ANDROID) # change APP_NAME to the share library name for Android, it\u0026#39;s value depend on AndroidManifest.xml set(APP_NAME cpp_empty_test) list(APPEND GAME_SOURCE proj.android/app/jni/main.cpp ) elseif(LINUX) list(APPEND GAME_SOURCE proj.linux/main.cpp ) elseif(WINDOWS) list(APPEND GAME_HEADER proj.win32/main.h ) list(APPEND GAME_SOURCE proj.win32/main.cpp ${cc_common_res} ) elseif(APPLE) if(IOS) list(APPEND GAME_HEADER proj.ios/AppController.h proj.ios/RootViewController.h ) set(APP_UI_RES proj.ios/LaunchScreen.storyboard proj.ios/LaunchScreenBackground.png proj.ios/Images.xcassets ) list(APPEND GAME_SOURCE proj.ios/main.m proj.ios/AppController.mm proj.ios/RootViewController.mm ${APP_UI_RES} ) elseif(MACOSX) set(APP_UI_RES proj.mac/Icon.icns proj.mac/Info.plist proj.mac/en.lproj/MainMenu.xib proj.mac/en.lproj/InfoPlist.strings ) list(APPEND GAME_SOURCE proj.mac/main.cpp ${APP_UI_RES} ) endif() list(APPEND GAME_SOURCE ${cc_common_res}) endif() set(all_code_files ${GAME_HEADER} ${GAME_SOURCE} ) # 将两个列表变量打开后送入变量all_code_files中 # mark app complie info if(NOT ANDROID) add_executable(${APP_NAME} ${all_code_files}) # 使用所有文件编译成对应名字的可执行程序 else() add_library(${APP_NAME} SHARED ${all_code_files}) add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos/platform/android ${ENGINE_BINARY_PATH}/cocos/platform) target_link_libraries(${APP_NAME} -Wl,--whole-archive cpp_android_spec -Wl,--no-whole-archive) endif() target_link_libraries(${APP_NAME} cocos2d) # target_link_libraries: 关键字,指定链接器将cocos2d库和游戏可执行文件绑定 target_include_directories(${APP_NAME} PRIVATE Classes) # 当你在 .cpp 文件中写 #include \u0026#34;HelloWorldScene.h\u0026#34; 时，编译器需要知道去哪里找这个文件。默认情况下，编译器只在当前源文件所在目录和系统目录里找。 # 这行命令相当于告诉编译器： # “编译 ${APP_NAME} 的源文件时，记得额外去 Classes 目录下搜索头文件。” 根据这个cmakelist,我们大致可以明白cmake的构建过程:\n配置编译时要用到的环境变量 设置要进行编译的文件(如这里的GAME_HEADER和GAME_SOURCE) 针对不同的平台设置不同的编译链 加入静态和动态库链接 拓展阅读:\n比官网写得更好的cmake教程 ","date":"2026-04-03T08:00:00Z","image":"/p/cpp%E5%B7%A5%E7%A8%8B%E6%9E%84%E5%BB%BA-%E8%87%AA%E5%BA%95%E5%90%91%E4%B8%8A%E6%96%B9%E6%B3%95/76422957_p0-%E7%84%A1%E9%A1%8C.webp","permalink":"/p/cpp%E5%B7%A5%E7%A8%8B%E6%9E%84%E5%BB%BA-%E8%87%AA%E5%BA%95%E5%90%91%E4%B8%8A%E6%96%B9%E6%B3%95/","title":"cpp工程构建-自底向上方法"},{"content":"每日阅读 2025-11-15 1.\n很多人意识不到运气的重要性，而错把成功归功于自己的才能和努力， 却没有意识到好运在其中的重要性。忽视了这一点就难以保持谦虚，难以不断学习。 明白了运气的重要性，就知道不是人人生而能得到平等的机会的， 在遇到处境不如自己的人，不能假设这种差别是聪明或努力程度的不同造成的，应该知道善待弱者。 2. 此后不久，王先生得了脑血栓。我很有些感慨：敢于直面自己一丁点儿小错的性情中人，活在一个是非不分，且明知错了却死不认账的世道里，能不得脑血 栓？ 3. 我从 D 道开始，接受了大量的指导。周围的人都对我很好，我慢慢进步，终于进入了 C 道。那组的人也热情欢迎我。 但是，我注意到，旁边 B 道的人并不像 C 道那样友善。A 道选手都非常友善，慷慨给予鼓励、表扬和提示。 我怀疑这是普遍现象：A 道、C 道和 D 道的人都很友善，大家几乎都乐于助人；B 道的人则是对 A 道和其他 B 道选手友善，但对 C 道和 D 道则不然。 因为我后来发现，其他运动领域也是如此。那些仅次于顶级选手的运动员，往往对不如自己的选手很苛刻，害怕别人超过自己。 学术界也有这种现象。真正伟大的研究者慷慨而热于助人，许多普通水平的研究者也是这样。然而，那些有一定知名度、但又没有做出顶尖成果的研究者，对不如自己的人就不友善了。 当你是最好的 A 组时，很容易表现得宽宏大量，你确信自己会有成果，这让你安心无忧。 当你处于平均水平或低于平均水平（C 组或 D 组）时，表现得友善也很容易。远离顶尖水平，意味着竞争压力不大，所需要付出的努力可能也不大，你会有一种“放轻松”的心态（反正我到不了顶峰，就当作玩呗）。 那些仅次于优秀水平的人，感受到最大的竞争压力。你离顶峰如此之近，追赶却又艰难无比，放弃又不甘心。最令人沮丧的是，没有人记得第二名。同时，后面的人还可能超过你。所有这些因素，都可能导致一种不友善的态度。\n4.\n我从来不想辩论，但如果必须辩论，我希望自己会输。 我宁愿对方的观点是正确的，他来说服我，因为这样会比我的观点是正确的，我来说服他，对我更有趣。\n有些是周刊上的,有些是杂志里的,很后悔我以前不会用博客把这些记录下来,想要找到来源都是难上加难.\n2025-11-17 最近上wiki看了杨靖宇的词条杨靖宇wiki,缘由是看了周海婴写的回忆录,里面提到了民主元老马叙伦,马叙伦界面里提到了白朗,白朗界面里提到了杨靖宇,才发现自己的杨靖宇的了解确实贫乏.(或许我可以不断的跳转下去看一整天吧,wiki确实是一个知识宝库,五湖四海的人们四处搜集信息再将他们合并,方便后人来看,这也是开源精神触动我的核心)\n纵览杨将军的生平,确实是一个典型的共产主义战士,为了人民的解放先后与地主,军阀,日寇斗争,抱持着自己的信念和理想战斗到了最后一刻,即便只剩自己一人.这与古巴的切.格瓦拉又是何其相似.他们用自己的生命彰显了信念的力量,让其他或多或少抱有私心的灵魂都黯然退下,战栗不已.\n要抱有一个纯粹的理想,做一个纯粹的人多难啊,在当下更是如此.\n2025-11-19 现在的孩子们比以前更容易接触到成年人的世界，因此他们更早成人化。 从很小的年龄起，他们就在视频网站观看暴力和战争，在社交网络上看到性感和暴露的照片和视频。 然而，当孩子们成年以后，他们往往无法实现经济独立，也没有机会承担足够的责任。 结果，整个社会的文化就变得很幼稚，成年人感到无法做出承诺，即使承诺了也缺乏信心，对以后的生活感到难以把握。 他们的行事方式和处事态度，就像还在青少年时期。 周刊263期\n最近在看周海婴写的回忆录\u0026laquo;我与鲁迅七十年\u0026raquo;,最令人惊奇的地方在于他能记得许许多多零碎的小事情,以当时他七十岁的高龄来看,记忆力已经算很不错了,但另一方面,他的回忆基本是由这些琐事构成的,因此整篇传记的时间线有点散乱,主线不明晰,没什么重要的叙述点,经常在叙述一个人的时候联想到当下的事情,看得很累.\n正当我想就此放下这本书不再看的时候,突然想到,这个传记不就是我们一般人的生平吗,除了顶着鲁迅儿子的光环之外,他的日常生活与我们并没有什么不同, 都是由一件件琐事构成的,并没什么值得写入历史大书特书的事情,只是在随着时代的潮流艰难的生存着,没有大起大落,但确实有悲欢离合,这才是我们的生活.想到这里,我把这本书又拿起来了.\n2025-11-28 1.\n生命太短暂，不能花在那些不值得阅读的内容上面。 就算你是一个很爱读书的人，活到70岁最多大概能阅读15000本书，这只占世界最大图书馆美国国会图书馆3800万册藏书的0.04%。 我们一生中能够阅读的书籍其实很少。因此，关键技能不是多读，而是跳过那些不值得读的内容。 \u0026ndash; Hacker News 读者\n2.\n有些领域变化非常快，在有人写书之前，博客有时是唯一的信息来源。Stable diffusion 模型出现后的第二天，人们就已经在写博客了，书籍永远不会那么快。 而且，博客往往是免费的，而书籍和论文则被锁定在付费墙之后。因此，你可以这么认为，博客获取灵感，书籍获取知识 \u0026ndash; Hacker News 读者\n2025-12-09 每当有人给我的开源项目，提出这样或那样的要求，我就给他三个 F，让他自己选一个。 Fix it, Fork it, F**k off. 来源\n每当我看到一个比较小众的实用开源项目,通常都是只有一个contributer或者一个branch,而issue却有好几个或者几十个,这些issue很少是提修改建议的,更多的是要求增加各种各样的自己想要的需求.\n如果每个人都这样做,从不想到自己加入到这个项目中帮助创建者完善功能,那么开发者的热情将会急速冷却,开源社区也将不复存在,幸好现在还不是这样.\n人生有没有意义？人类又有什么意义？ 我说，人生是有意义的，而人类则是没有意义的。 询问人类的存在有没有意义，就等于询问地球或宇宙的存在有没有意义一样，是得不到答案的。 人生的意义是什么呢？它的意义就在于为没有意义的人类工作、服务等等，其目的不外乎是使人类生活得更好并得以延续。 反正人类是现实的存在，你又是其中一员，你有义务使它发展延续。你只要这样做了，你的人生就具有了意义，或者说价值，并不一定要去理会人类存在的意义。 来源\n要我说这段话只是消极中的乐观而已,人类没有意义,人生也没有意义.\n人类只是感觉的动物,我们当前的体验决定了我们的感受,也正是一次次体验塑造了我们.所以尽管人生没有意义,我还是想去多体验目前所没能看到和听到的一切.\n因此我不愿意沿着别人的老路走,因为这终究是别人的体验,重走一次未免太乏味;但我也不愿意去走一条全新的,险峻的道路,因为我不想让自己遭罪.\n2025-12-14 克伦斯基wiki\n今天翻wiki时通过多次跳转转到了克伦斯基的界面,之前对他的印象只有布尔什维克对他主政政府时期的控诉,以为他只是一个常见的反对派角色.\n看完wiki之后才发现他与陈独秀,托洛茨基并无区别,同样是内心向往着一个更加美好,自由的社会,同样是怀有或多或少的高士情怀,但是每况愈下的社会形势迫使他们站出来,担任自己本不想担任的领袖角色,在时代的浪潮下起起落落,最终被推上风口浪尖,只是因为不愿意参与尔虞我诈的政治斗争,狠不下心去为自己谋求应有的利益,怀着革命为人民的期望,最后却成为被革命的对象,从先驱者变成背叛者,不为敌我双方所认同,无路可走,跌下本就不属于自己的神坛,摔得粉碎. 被尘封在历史的角落,所有的功绩都被后来者掠夺殆尽.\n他们宛若一颗流星,照亮了整个世界,而后轰然坠落,再无人记得他们.\n这也是我厌恶英雄光环和领袖情结的原因,很多人之所以能够从渺渺众生中脱颖而出,原因便是对自己狠得下心,愿意去做那些旁人坚持不下去或者难以接受的事情,可是如果一个人对自己都能如此狠心,那对待别人又怎么可能会不狠心,不使用一些肮脏的手段,又怎么能让不甘居于人下的自己摘取桂冠,不通过残酷的斗争,又怎么可能胜过同样是英雄俊杰的对手甚至是同志.\n可惜我对自己也狠不下心,又何从说对别人狠心,只好默默地给英雄们让道了.\n之后会出一系列文章分析俄国革命的全流程,之所以写在这,是提醒自己别忘了.\n2025-12-18 我对 Quora 上瘾，情不自禁使用这个网站。那里有一些很棒的问题和讨论，激发了我的灵感和想法。\n但是当我重新阅读自己写的答案，一方面欣赏我的修辞和洞察力，另一方面也看到了很多想法可以成长为更大的成果。它们本可能进一步发展为软件、文章、论文、创业公司、书籍或社会运动，但任何事都没有发生。\n不仅如此，还有许多篇我写的长篇大论已经无关紧要，沦为了废文。还有很多我花了好几个小时写的评论，试图说服对于这些问题永远不可能改变观点的那些读者。\n我花了数千（也许是数万）小时在 Quora 上写作。我写的远不止11000个答案，还有5000多个草稿答案，其中很多已经写得很长了，只是因为来不及最终润色而没​​有发表。\n这篇文章说出了我想说的,我见过很多极其优秀的故事或者技术讲解,可作者发布的网站恰恰是贴吧,天涯或者知乎这样的问答网站或者论坛,作者的观点被一篇篇孤立的文章或者一条条回复分割开来,很难形成一个完整的体系.\n而天涯的倒闭也说明了在这些自己不能掌控的平台上并不能保证自己的思想可以永久保留下来,那些优秀的文章随着服务器的关闭直接成为了无法触及的历史,即便是作者本人也无法找回.\n因此,我拒绝在论坛里写长篇大论,而是作为读者去发掘优秀的文章,可惜的是现在优秀的文章也越来越少了,而AI生成的无意义内容充斥着论坛的每个角落,很难找到真正有意义的东西,这也是我悲哀的一点.\n一方面,论坛不能让作者的文字体系化,另一方面,博客不能保证作者的文章能广泛传播,比如我这个博客就不是所有国人都能访问到的,很难找到一个折衷点.\n如果我有那个能力,我可能会去开发一个跨平台的博客论坛吧,侧重点在于分享生活和技术,每个人都能像写博客一样把文章从自己本地上传,改进一下SEO算法,保证最好的文章优先显示,集成了评论系统和follow功能,最大幅度减少作者需要折腾的东西,只写md就行了,呈现的页面也由作者自己在前端设置.重点是这个网站能长久保存,不会让作者的心血白费.\n2025-12-21 1. 周刊210期 诺拉·劳森还说了一个观点。大家通常认为，复杂系统往往会在经济繁荣的时候崩溃，因为业务太多，支撑不过来，但他认为不是这样的，系统崩溃往往发生在经济收缩期。 经济繁荣时期，软件公司会大量雇佣新员工，投入更多的财力和人力，支撑复杂系统。等到经济收缩期，公司开始减少投入、冻结招聘或裁员，复杂系统可能就会在这个时候出问题，变得难以维护。 现在就是经济收缩期，那么接下来，会不会就是软件故障的高发期，我们将看到很多复杂系统的崩溃？\n2. IT 行业与传统制造业有一个重要区别，就是 IT 行业有着严重的垄断。 全世界的智能手机有70亿部，比汽车多出5倍（14亿辆）。但是，智能手机制造商比汽车制造商少了好几个数量级。搜索引擎、社交网络、操作系统都是这样，几个巨头就垄断了整个市场。\n3.来源 各个互联网公司都试图把自己的网站做成一个完全封闭的APP，你没法在搜索引擎上搜索到微信公众号的文章、小红书的内容、淘宝的商品描述……这就导致bing只能从非常有限的地方获得中文语料，最后就导致他的回答特别池沼……而且比别的AI都池沼十倍甚至九倍……\n2025-12-24 来源 我是一个习惯于早做规划与反复思索的人。 而坚持写博客最大的好处就是，可以时不时翻看之前写的文章，宛若跨越时间的荆棘，与曾经的自己促膝长谈。\n现在想来，十九岁时，我的迷惘归结下来，即是对将来自己的出路无所适从：出国留学，国内升学，抑或是早日进入职场。这几条路上，都有前人留下的无数足迹与丰碑，但也无可避免地悬着前人用泪水濯洗的种种失败警示牌。我曾尝试从中选出最优解，然而反复的纠结过后，我所意识到的，是这种比较的注定无结果：每当我觉得某个选择优于其他选择时，总会有某些信息刷新我的认知，让我匆忙撤回自己的决定。一如盲人同时摸象与鲸鱼，用片面的认知去比较复杂的事物，注定失败。\n先是出国。关于这个选项，我曾仔细考虑，然而最终出于经济上的原因（赴美读硕开销实在过于昂贵）和人生规划的原因（我对学术无甚兴趣，不愿耗费多年去追求博士学位），出国成为了三者中第一个被排除的选项。而站在当下（2020 年）来看，全球疫情的扩散与民族主义情绪的对立，出国虽然依旧有着不可抗拒的诱惑力（比如优渥的学术环境与工作环境），但是更显得充满了极度未知的不确定性。\n「选择比努力更重要。」这句话近年来已经广为人知，不少人以此自我调侃，感叹自己当初选择的失误（比如选错学校、选错专业），但当下一次选择到来时，却又不假思索地下意识地站到了主流的人群中，甚至拼尽全力挤出一条道路以加入主流人群。\n诚然，作为一个才识普通的人，我或许不具备选择最合适的道路的能力，但我所希望的，是不盲从、不追随，依靠自身的观察与思考，尽量选出一条相对合适的道路。 人类的悲欢并不相通，但是相似。\n关于读研与工作，面临同样困扰的也并非只有我一个人。\n在做出决定的过程中，我阅读了不少人的博客与帖子，从中获得了一些宝贵的信息与经验，或多或少地影响了我最终的决定。\n由于之前并未刻意保存浏览过的网页，在此，仅仅列出其中的一小部分：\n所以，到底要不要读研？ - laike9m’s blog\r研究生复盘 | 高策\riPotato | 在读研 \u0026amp; 工作中选择后者\r已有名校 CS 本科学历，读研对于计算机行业的职业发展有多大的意义？ - V2EX\r这恰恰是我当下迷惘而又感到无路可走的心境,专业课程的枯燥与无用让我感到厌烦,愈加激烈的保研争夺战让我望而却步,而那些学术论文里面的水分都可以让海平面再升高一米了.\n可日愈恶化的工作环境阻断了我得过且过的想法,作为雏鸟在第一家公司所学习的架构未必对我的技术提升有任何的助益,而我的学历劣势也将在很长一段时间内保持下去.\n这种左右为难的状况让我一边痛苦一边踟蹰,只好到处翻阅博客,搜寻资源,希望能够对当前的我有些微的救赎.\n这篇博客可惜的地方在于没有考虑到提前工作的坏处,但看了看他最近的文章,过得还算不错,可我现在还没有那个胆量去直接放弃保研,因为我讨厌唯一的选择,希望能多几条路可以走,而现在我仅仅是在本专业保研排名的边缘上(笑).这解释为是对我自己负责,但更可能只是我胆小罢了.\n题外话: 博客是我最喜欢的信息传播方式,我永远都可以在某个博客里找到一些用一般手段怎么搜都搜不到的棘手问题的解决方案,永远都可以找到一个处境和你相似的人,永远都可以找到能够为你指引道路的人.\n这半年来我搜集了差不多一百多个博客,我打算之后整理一下把里面的精华部分传到GitHub上,\n2025-12-26 来源 说实话大三上就开始找实习其实是一个有些冒险，但相应收益会比较高的做法。风险点在于学校的课程安排趋近于收尾，一些比较难的专业课也会集中在这个学期，如何平衡好学习和工作是一个首先要考虑的因素，这一点上我的做法比较粗暴：直接翘课。一是因为成绩也够不着保研的尾巴，无需那么在意绩点，二是因为除了体育课这种比较难逃的课，其他一律统统全翘，只是为了挤出了一周 4 天的实习时长，至于课业，只能安排到工作日下班，周末以及考试临近时的请假进行学习。但即便是翘课，对于一些专业课程还是要用心，比如 OS 以及编译原理等课程，可以说是专业的重中之重，不光学习会接触，在面试以及工作中也是非常核心的内容。好在一个学期下来课虽然都翘了，但最后的结果也不坏，没有顾此失彼而整出来个挂科。\n其实如果没打算读研的话就可以这么做,既然是本科毕业就工作,那绩点的意义就消失了. 这几天用rawweb不知道看了多少博客,足够大的体量保证了我搜任何一个关键词基本都能找到答案,每一个博客对我来说都是一个全新的领域,仿佛博主的生活就是我的生活,他们的迷惘和彷徨就是我的迷惘和彷徨.就比如我搜索关键词保研,一下就得到了一千多条结果,里面的很多文章对我来说都是对读研和工作这一抉择的重新认识.\nRamírez(2026-02-01) 阅读fastapi和sqlmodel的教程就觉得作者是一个风趣,谦虚,对于疑问要穷追不舍,对于新手非常友好的hacker\nRamírez主页 汉堡店比喻\n潦草搜了搜,互联网上关于他的信息还是比较少的,也没有什么比较亮眼的镜头表现,他的英语口音我也比较难接受.只大概知道他没有读大学,从哥伦比亚到了德国工作.\n看得出来,他将几乎所有的心血都投入到了开源项目中,所以才不会有时间去包装,宣传自己.\n这是一位真正令人尊重的hacker.\n迷茫时就多看看博客(02-04) 大学也已经过了一年半了,但我还是没能看清之后要怎么做,保研已经非常够呛了,除非之后一年半我能保持几乎满绩,但厌恶填鸭教育的我看来是很难做到了.所以,工作or保研?\n来源 本科前两年半时，我拿定主意直接工作，却在最后时刻，抱着想看看有没有新的机会的想法，极限转弯踏入了研究生的大门。而研究生的前两年的体验让我认识到高校做工程不靠谱，回去看业界的机会时，发现实验室做的工作不能让我踏入一个所谓更“高阶”的工作，我能投向的业界工作和读本科时基本没有区别。后来，当我意识到，当前国内大环境下体制内的技术工作也并非一无是处，想把握北大应届毕业生这个机会了解一下体制内的机会，但却错过了这个时间窗口，最后仍然投向了微软，和三年前的唯一的区别也就是大组（C+AI vs STCA）和工作地点（苏州 vs 上海）的区别了。\n来源 这次在各种因素的推动下终于做出改变，总共就简单面试了 3 家公司，最后拿到的机会是深圳的国企和武汉的互联网公司。之前加了一个武汉的交流群，了解到武汉更为恶劣的就业环境，如果回武汉工作几年被裁或者一定要跳槽换工作，那将是很困难的事情，也打消了最近几年有机会就考虑回武汉的念头。 最近几年经历了互联网的动荡，在互联网跟大家一起卷已经卷不动了，最终选择了深圳相对稳定的工作。参加工作后，自己对互联网的热情完全消磨殆尽了，我虽然不够上进，执行力也很弱，但是在学校比较闲就会捣鼓东西，写博客分享很开心。\n当前的经济形势下，今年的情况明显比去年前年更加严峻，各家互联网公司都十分默契地通过裁员来降本。几个月前，上周还在一起吃饭朋友某天午休过后，被叫去谈话沟通裁员。几乎没有交接流程，当天就收拾东西滚蛋，十分残酷。\n来源 整体来说今年就业环境在部分岗位还是有所回暖。最卷的赛道应该是后端，据我了解三月初各大厂开始面人，四月初就基本没有 hc 了，完全是地狱难度。前端和客户端今年有所扩招，有时候你投的是后端岗 HR 也会打电话来问你有没有面前端的意向。\n来源 此文我是想写给应届生的，1-3 年的工作经验没那么恐怖，大多数情况下，你的能力够了，公司不会跟你较真，用年限压你，所以看到自己技术水平能够达到，资历却不符合的岗位，也可以尝试着投一投。 这类公司其实已经算是对技术有了要求了，而且技术细节都明确了出来，但是，看到只对 jsp，servlet 这些技术有所要求，明眼人都知道，这是在招初级开发，了解一点框架，懂计算机基础，这样的新手，公司还是可以接受的，上海这边针对可以独立开发的应届生，或者培训班出来可以直接上手的非科班生：软件公司，实习开价大概在 4-5k，转正开价大概在 7-8k；互联网公司实习大概在 5-6k，转正开价 9-10k 起步。985/211 或者能力不错能够入职的高校生，在互联网名企的开价，就以阿里为例，我了解到的情况大概是 12k14 or 1216。这里都是说一个上海地区价格，不适用与全国。北京的情况是 IT 非常发达，很多互联网公司都在北京，而上海，深圳，广州其次，注意，上海是金融之都，并非 IT 之都。 \u0026hellip; 很多学生没有实习计划，或者出于对工作的恐惧，或者出于对自身能力的不自信，又或者是对实习工资的不屑…也有一大部分同学，觉得春招太早了，希望留着机会等到秋招。我这里有一份据阿里巴巴某部门的公开数据：2020 年转正入职的校招生中 80% 来自于春招，20% 来自于秋招。「好书一本，明日在读」，我不推荐大家错过现在这个最好的时机。\n来源 这些幻想要批判起来说一年也说不完。不过相信大家也发现了一些共性，就是这种盲目推崇读研的人，往往对很多问题都缺乏基本了解，看问题也非常片面。我暗自揣测，他们中大部分可能并没有真正读过研究生，却又把自己目前的不如意归咎于没有读研，并幻想出了所谓读研之后的美好生活。我在计算所的时候，周围很多同学都觉得读研浪费时间，也包括我在内。有个哥们实在受不了实验室安排的无聊工作直接退学然后面试进了头条，人家也没嫌他学历不够。总之呢，读研这件事好不好，各人有各人的情况。对想进互联网行业的同学，可能确实是浪费时间，但若是想去考公务员或者拿户口，可能又很必要。关键还是要清楚自己的目标，分析自身情况，再来判断读研到底是不是一个好的选择。\n来源 整个找实习过程确实挺不容易，毕竟直接和研二学生对线，但最后结果还算不错，去了自己最想去的，对于AI整个行业来说，个人认为在现阶段显然是能实际落地的工程大于算法本身，数据质量大于算法。\n博客阅读(4/2) 很久没看博客了,今天来看一下:\n链接 详细到可怕的CS硕士申请指南,尽管博客没有再更新过,但祝愿博主在美国能过得很好吧.\n链接 同一位博主身为过来人给初学者的建议. 博主认为年轻的时候就要选定一个专精的方向,进入工作岗位后再去随兴趣学习.\n这个观念我不敢苟同,因为如果不是运气特别好,能够进入一家允许你去尝试不同技术栈的公司的话,你是很容易在一个技术栈上把牢底坐穿的,枯燥的CRUD也会逐渐的消磨你的学习兴趣,对于其他的技术栈也会逐渐生疏,想要换方向就会非常非常的困难.\n然而,应届招聘的时候又确实要求你的技术深度过关,这就真没办法了,我们只能在短短的三年内(但一般人意识到紧迫性的时候就已经是大二,甚至大三了)去尽可能的探索多的技术栈,找到自己最感兴趣并且真的有市场需求的技术后,做到一定程度的精通. 大厂面试看的终归是你的成长潜力和基本功,而对于记忆力过关的年轻人来说,熟悉一个技术的基础特性和常用框架只需要三个月就够了,并且可以同时学习多个技术而不至于感到吃力,但还是建议越早开始学越好.\n开曼群岛(4/23) 之前看到某些头部公司的注册地在开曼群岛,就试着搜了一下,结果发现这个地方确实不得了.\nwiki 开曼群岛位于加勒比海地区,是英国的自治领地,以宽松的税收政策闻名,公司除了年度牌照费外不需申报和缴纳任何税项,所以无论是对个人、公司还是信托行业，开曼群岛都不征收任何直接税.吸引了无数公司在此注册以逃避本国的税收政策.\n到1997年6月，世界50家大银行中有47家在岛上设有分支机构。到1999年6月，在岛上注册的公司有4.1万多家，银行和信托机构590家，保险公司475家。 内地的主要互联网公司都在此处注册 思考 这里放一些不值得单独写一篇文章的吐槽\nai思考(2025-11-24) 随着我在计算机学习方面的逐渐深入,ai对我的作用越来越有限,顶多是在配置环境里帮我减少一点阅读文档的时间了,大多数时候它都不能给我一个正确的解答,而是说些通用的,却不符合我当前情况的解答.\n因此,现阶段的ai还是更多的用在文档办公,图像生成等这些日常场景或者娱乐场景中比较好,一旦想要深入的学习一些技术,ai的劣势就一览无遗,就连洛谷的黄题它也很多时候做不出来(虽然我有时候也做不出来~).\n因此,我现在更偏爱于技术博客和技术文档,书籍,再也不会去想用ai火速掌握一门技术的可能,而按照当前ai发展的状态来看,所谓的ai编辑器既然底层都是调用的热门大模型的api,那么自然也就不能处理太高端的技术,最多是辅助学生完成基础的代码作业而已.\n总结一下,如果ai不能在字面意义上实现自主学习,它永远只会是一个文档翻译器,当成省时间的阅读工具可以,想用来提升自我,学习技术,我看还是算了.一句话,你懂的越多,ai就越傻.\n道路与选择(2025-12-16) 这几天有点焦躁和郁闷,总有种无所适从的感觉,浑浑噩噩的,对很多事都不上心了,于是现在静下来认真思考了一下当前的处境.\n人生是由一大堆选择构成的,每一个分岔路口都决定了我以后的人生道路,有时候选择多种多样,似乎哪里都是通路,有时候仿佛是山穷水尽,让我别无选择.\n人自然是希望选择越多越好,这就是我以前为什么倾向于做事提早做准备的原因,为的就是不让自己在ddl的时候显得无路可走.\n但过多的选择,太多的可能,悲观的现实又让我痛苦万分,人生的道路总是要自己走的,但如果有一个引路人就更好了.看不清未来的道路只会使我无所适从,一边厌恶着填鸭式的教育,一边追求绩点的完美,只怕到头来也是一场空.\n考研人数的下降(2026-01-03) 可以看出来,尽管本科毕业人数越来越多,研究生招生人数越来越多,报考研究生的人数却越来越少,接着看下图 疫情的影响太大了,大家都开始捂住钱包,不会再去做大胆的投资了,因此收入较稳定的餐饮业扶摇直上,而旅游业直到现在也未必恢复了元气. 越来越多的人选择去找一些稳定的工作如公务员,医护人员,不愿意去创业了.\n顺带一提还有战败cg,尽管没有今年的数据,但我相信降幅不会相差太多 这也是普通人的无奈吧,尽管身处这个时代,却连发生了什么都难以把握,只能隐约感觉到大环境不好,却找不到合理的数据来帮自己掌握一点点情况,连知情权也被剥夺,不知道下一步要怎么走,迷茫的在原地打转,只好依着惯例提心吊胆的生活,提不上享受生活,只看得见压抑和悲哀,失去了相信他人的胆量,失去了期盼明天的幸福,只是靠本分承担着责任,不是为了自己,而是为了亲人\n很早就想要拿日本跟本国做一个对比了,尽管人口基数差的很离谱,但以前的日本或许就是现在的我们,现在这里预告提醒我自己一下\n再战算法(2026-01-11) 昨天考了程序设计范式,一开始觉得三个小时三道题不是有手就行,后来发现我太天真了.\n第一道题是leetcode2002,一道关于不相交回文子串的中级题,我硬是没想到用dfs做,可能是一个月没碰算法生疏了吧, 第二道题是设计几个角色的类,考察多态和继承,还真有点难,报错信息我都看不懂,第三道题是求满足给定和的最短子串,尽管我一开始就把双指针写出来了,但却在一些测试数据中一直死循环,还好机房上装了vscode,不然我都不知道要怎么调试,前前后后最少调了半个小时才解决这个问题,只能说是在第一题没做出来的情况下太紧张脑子短路了.\n这一顿折腾下来,我才发现我连最基本的dfs都没能很好地掌握,连普通的课程考试都过不了关,啥也不说了,速速去刷题, 之前看了百度之星的题目,彻底断了打acm的念想,但刷点水题娱乐一下也是挺好的,不仅能应付考试,还能应付面试.\n渺小的自我(2026-01-25) 今天看到了这篇博客,匆匆看了一遍博主的往年博客,仿佛就看到了一年之后的自己.\n我跟博主的经历极其相似,都是在一个不发达地区的小镇经历了懵懵懂懂的少年时光,觉得自己未来有无限可能,进入高中后才发现自己并不是天之骄子,最终考入一个中流985,并发现自己只是一个普通的不能再普通的人,并没有用读书改变命运的可能,也不存在任何奇幻的邂逅,只有一个灰暗的,却又捉摸不定的未来,为了最后一丝甜蜜的幻想而苦痛挣扎.\n或许,一年后的今天,我就要像他一样懵懵懂懂的参加实习,之后通过秋招走入职场,陷入都市的牢笼,住在一个窄小的出租屋,每天来回通勤两小时,熬夜加班赶文档,为了晋升和加薪的机会苦苦挣扎,因为被解雇的可能而夜不能寐,一个人蜷缩在屋内度过新年.\n可不知为什么,当想到这些的时候,我的内心却无比平静,仿佛认定了这就是我该走的道路一样.也罢,渺小的自我,又怎么能容纳什么宏大的理想呢.\n永远不要走极端(1/28) 很少有人能真正意识到互联网对普通人的深远影响,以极其傲慢的无知俯视着那些璀璨的工业成果.\n比如不少人用ai写出了一个勉强能看的网站,就在嚷嚷着前端可以被ai取代,却没想到真实工程情景下的前端处理极其复杂,需要面对各种各样的请求并与后端接口交互,并实现高效美观的反馈界面.现在的ai再怎么吹的天花乱坠也做不到这一点.\n事实上,绝大多数人在谈及某个人物或是某个事件时,都没有对自己所涉及的话题有着深入的,透彻的了解,而是肆无忌惮地宣扬自己的无知,仿佛自己道听途说的皮毛知识就已经足够应付那些我们本应该怀着敬畏看待的未知.\n很少有真正纯粹的恶与真正纯粹的善,既然都是从猿猴进化而来的动物,一个人不可能不具有与生俱来的生存本能,那么自己做出的大多数事都一定是为了自己好,从自己的利益出发,而一旦自己的领域被侵犯,就立马进入戒备状态,朝着入侵者厉声吠叫.这并非是一件羞于启齿的事情,反而应该视为人类的本性,\u0026ldquo;世上没有圣人\u0026quot;的事实反而愈加衬托着那些真情和壮举的可贵,这些不同寻常的举动是人性的光辉,而非人性的通常表现.\n最能凸显这一点的是互联网,历史在以前是被贵族阶级所垄断的,只有高贵的人才能随意涂抹,篡改历史,普通人能作为充实统计数字的材料便已经是万幸,怎么可能在哪个地方留下自己的声音呢.然而互联网做到了这一点,把真正的历史还给了普通人,让普通人也可以留下自己的印记,可以和素不相识的人分享自己的平淡日常,也可以看到千千万万的地球村民的与自己相异的日常.这才是真正的历史,或许未曾打磨,或许太过鸡毛蒜皮,或许太过丑陋不堪,但确比历史书显得真实可靠的多.\n然而,正是因为未曾打磨,未经天人过目,副作用便显得格外可怕.请永远牢记一点:或许网上冲浪的人很多,但在网上发言的人永远只占上网冲浪人群的一小部分.不信问问自己这几个问题:你多久才会发一条评论?你是不是大多数时候只是匆匆一览界面便离开?创作者的基数是不是比观众要小的多?\n一旦想清楚了这些问题,你才能真正意识到道听途说的可怖与可恨,如果大多数信息都是少数人通过过滤之后告诉你的,如果你从来不能安下心来去亲自了解那些信息,那么便可以很明确的知道,这些消息都只是废料罢了,是被他人咀嚼之后勉为其难的以高姿态施舍给你的,而你却视若珍宝,满怀着发现了事情真相的喜悦,恨不得让所有人都知道自己的无知,顺便还当了一回传教士,向其他人散播这些废料.\n这也就回到了我的论题:永远不要走极端,若是互联网上的声音都是少数人发出的,那么走极端那不是必然的吗,你怎么能期待他们会整齐划一的走中间车道而不是来个急转弯撞出护栏呢.所谓的对立,所谓的矛盾,能被摆上互联网就足够说明是个例了,普通人的普通日常也不会有人去大肆声扬.即便有万千的个例,也只是几十亿人口中的少之又少的部分而已,那么,又与真实的社会环境有何关系,这些个例能代表的只有极端而已,绝非是正常情况.\n也正是因为有着这些极端作为烟雾弹,真正的历史得以悄无声息的不断进行,直到历史的车轮碾到了身上,才吵嚷着个人的渺小和时代的沧桑,却没想到历史本就是由个人的普通日常组成的,正是因为每个人都有着自己的悲欢离合,都有着自己的大起大落,没有整齐划一的路线选择,才有了无法被预测的未来.\nai幻想(2026-01-31) ai幻想:认为ai可以帮自己包揽技术上的难题 我大学这两年经历了不少ai幻想的例子,但也看见过有些同辈依然脚踏实地钻研技术.不得不承认,后者的比例要小的多,而他们的知识水平和成绩都让我不得不低头,承认他们与众不同的技术造诣.\n很难想象ai浪潮下的普通大学毕业生在真实的工作场景下会暴露出怎样的一种丑态,而这样一批靠ai吃ai,从来没能真正掌握技术的人攻读研究生,博士生的时候又要去怎样的水出一篇sci.\n如果ai真的有他们想象的那么强大,那倒还可以糊弄过去,可惜的是目前ai的水平还远远不到家,近两年业界也并没有突破性的进展,依旧是在倒腾同一个模型,同一种方法,更多的是吃老本而不是真正的开拓创新.\n可惜我早就听说在技术好的人未必晋升的快,说不定这些用ai糊弄架构,用心美化ppt的人反而能混的风生水起呢,哈哈,其实现在就是这样了:smile:\n反思(2026-03-03) 家里人都希望我读研,有的是觉得程序员不稳定,有的是想让我进体制内当官,有的是觉得我可以扩大圈子.\n而我一开始以为自己之所以不想读研而是想直接工作,是因为想要干一些有创造力的活,想要挑战自己,但现在我发现,如果在保证有稳定收入的情况下,最理想的条件是事少空闲多,可以有大把的时间干自己喜欢的事情,但事实上并没有这样的工作.\n如果我选择进大厂,虽然有可能进一个很push很压抑的组,甚至还需要花力气去打点人际关系,走点向上社交,但这终归是可能,我可以通过各种方式改变自己的工作环境,即便可能在以后被裁员,我也总可以找到一份合适的工作,就算舆论再怎么渲染ai的可怕,我想自动化在取代高级程序员之前一定会先取代公务员和蓝领工人,如果当局不愿意动这个刀子(很大概率不愿意),那就一损俱损,如果经济危机来了,到头来还是要下手.(你要问为什么下手?因为当局总是优先保证高位者的权利,再通过裙带关系来让下位者吃残羹冷炙,如果你威胁到了他的切身利益,那肯定一脚踢开啊,无论在哪都是这样,民族国家之所以维持高军费警费,一方面是逗人的对外防御,更多的是对内防御,怕你不听话呢)\n而到了那个时候,若参考1930年代,由于当局的弱势,无法进行像样的改革,又无法向外转移矛盾,再加上绝大部分人口都在农村,可以实现自给自足,城市里的所谓知识分子和公务员基本都受了老罪;而现在由于新兴人口都在城市,当局强势,加上过度依赖当代技术,向内转移矛盾将会导致全面崩坏,故只能向外转移矛盾,而且是非常猛烈的那种,可参考当前美国的外交政策.\n所以,我坚决不选择考公,第一是因为流动性小,过于乏味,过多迎合,工资只能保证不饿死,加班也多(假期的值班,重要岗位的加班,什么,你说你的岗位很轻松基本没工作?那肯定是边缘岗位,未来反而更容易被裁),第二是因为太多人来考公,机遇过小难逢贵人,就算限制录取比例,但单位里(尤其是基层单位)的所谓元老都只是80年代生人,离退休还远着呢,导致机关臃肿无比,一定要疯狂的内卷才能吸引领导的注意,当然不卷也行,如果一辈子当个科员(当然博士有正科级待遇,说说而已啦,放首都里你算个屁,处级都没有实权,努力十年不如天降关系,若是像大多数博士公务员一样远离政治中心,晋升机会就小的多),我看你退休金未必会比爸妈高呢.\n事实上,现在我也不太愿意去高强度岗位累死累活挣大钱,除非工作内容合我的胃口,挣钱的目的是什么,如果单纯是为了存钱而挣钱,那就有点好笑了;如果是为了脸上有光的虚荣心,那就更好笑了.\n我的娱乐活动基本不是很花钱,也没什么旅游的欲望,目前也没有家庭的负担,但如果是为了未来,为了一点飘渺的机缘,为了所谓的经济自由,也可以去打拼打拼吧.\n一代人有一代人的路要走,我不想走老一辈的路了.\nChatGPT大失败(26/3/10) 现在GPT开始搞内部wiki,并且大幅度给免费版降智了,就连luogu的黄题都不太能搞得定,还总是输出垃圾文字和不存在的文献,可能是更改战略打算像Claude一样面向高端付费用户吧\n即便是最近降智的Gemini也更好用一点,而Grok更是非常擅长查找资料和总结文献,所以,我决定彻底抛弃gpt\n(3/24) Gemini终于把复制选项单独列在下面了\u0026hellip; 外文文献并非就比中文好 如果你不是很熟悉英文阅读,那么在读英文文献的时候你一定会逐行逐句的仔细阅读,不然你就看不懂了,而读中文文献的时候由于大多数句子一扫而过就能看懂,正常人不太会对某一个知识点看上太长时间.\n所谓的英文教材精炼详细,只是因为你花了更多时间在阅读和思考而已了,未必是教材本身写的有多好,我见过太多看似详细其实是废话太多的外文教材和文档了.\n事实上,如果去看中文翻译版的话,即使翻译质量再怎么差,花上与阅读外文同样的时间去扩展阅读,收获一定会大得多.\n至于讲座视频更是如此,之所以说看国外讲座学的更好只是因为你花精力去理解英文了而已,同样的时间你花在看教材上我想会有用的多.总不可能米国人人都是至圣先师,随便一句都是金口玉言吧,低质量的讲授才是常态,随便一句话或者一页ppt就让你醍醐灌顶那只能说明你本来就啥也不会,不如好好看文献.\n补充(3/22): 我错了,遇到垃圾翻译时中英文版本夹杂着看的话,可以实现既能一目十行,也能深刻理解 建议学生只用AI来打杂 我身边有不少人用AI来应付比赛和项目,费尽千辛万苦调试出一个勉强能过关的APP或者网站.但事实上这样啥也没学会.与其让AI去修复一个AI生成的大杂烩,不如靠自己的所学知识去排查异常,只有这样,才能真正的学到东西.\n当你想学习一门新技术的时候,最可怕的就是问AI来它来手把手教你,事实上,AI所作的只不过是去查文档,然后把它破碎的理解喂给你而已,得到的知识远不如自己去看文档来的有体系.\n英文翻译(3/17) 我认为中文的优点在于句式变换多样,能够以最少的字符表达出最多的意思,通过各种同义词和同音词实现独特的诙谐语感,需要读者从深层次理解句法的内涵,在文学领域是别领风骚的高端语言; 而英文则相反,以尽可能多的字符来表达足够精确的意思,这注定了英文句子大多是枯燥无味的,但是由于英文的表意精确,用语地道,从而在学术研究中占据了统治地位.\n那么这样看来,广传的翻译原则\u0026quot;信,达,雅\u0026quot;其实是有道理的,\u0026ldquo;信\u0026quot;意味着能够真实反映原文的意思,\u0026ldquo;达\u0026quot;意味着好理解,可以让普通人也能看懂,\u0026ldquo;雅\u0026quot;意味着足够优美,能够体现出汉语的独特美感.\n很可惜的是,在我看过的用中文翻译英文的学术文献中,很少有真正能实践这一点的,大多数译者都是硬着头皮把原文中的分词或者定义对照着词典一个个敲出来.如果自身不是技术专业的话就别来翻译技术类书籍啊(我不了解翻译界,或许有硬性指标之类的吗),但如果自身是技术专业还翻译成这个样子,就有点过分了.\n批判用例 这些CPU执行时间已大量测试过。虽然它们随进程和计算机的不同而变化很大，但是它们的频率曲线类似于图5-2所示。该曲线通常为指数或超指数的形式，具有大量短CPU执行和少量长CPU执行。I/O密集型程序通常具有大量短CPU执行。CPU 密集型程序可能只有少量长CPU执行。对于选择合适的CPU调度算法，这种分布是很重要的。\n尽管勉强能看懂,但这个名词翻译确实很离谱,看看原文:\nThe durations of CPU bursts have been measured extensively. Although they vary greatly from process to process and from computer to computer, they tend to have a frequency curve similar to that shown in Figure 6.2. The curve is generally characterized as exponential or hyperexponential, with a large number of short CPU bursts and a small number of long CPU bursts. An I/O -bound program typically has many short CPU bursts. A CPU-bound program might have a few long CPU bursts. This distribution can be important in the selection of an appropriate CPU-scheduling algorithm.\n首先这个The durations of CPU bursts翻译成CPU执行时间,是动了一点脑子的,但是完全没有体现出周期的意思,翻译为CPU周期长度应该会好一点.\n这个\u0026quot;不同而变化很大\u0026quot;够吐槽了,直接翻译成\u0026quot;随着\u0026hellip;不同而不同\u0026rdquo;,我想才像一个正常的中国人的思维,没必要非要代入副词.\nexponential or hyperexponential能翻译成\u0026quot;指数或超指数\u0026quot;也是无敌了好吧,翻译成\u0026quot;指数级或者超过指数级别的增长速度\u0026quot;我想才对,如果原文很难翻译,你倒是加一点副词啊.\nshort CPU bursts翻译成\u0026quot;短CPU执行\u0026quot;你自己不会笑吗? 我不得不怀疑译者真的系统学过英语不,直接翻译成\u0026quot;短区间\u0026quot;就可以了,在有上下文的环境,读者很容易理解这指的是cpu执行区间.\n总结 翻译英文的时候你就应该用中国人的思维来看原文,用中国人的想法去理解原文,在保持大致意思的情况下尽可能的还原中文的语法结构才是最重要的.\nAI取代程序员(26/4/1) 尽管很多人(包括程序员)都在说工作会被AI取代,但鲜有人真正懂得LLM的基本原理,只是看着AI输出一大段代码或者信息后就开始附和媒体的喧嚷,也给了大厂借着AI的名义大幅度裁员的机会.\n我的意见是: What the hell?\n到底是哪来的自信让你能够轻易的对一个自己一无所知的领域下判断的?如果你自己连LLM的原理都不清楚,那么你又怎么能知道它具有成长为包办一切的神器的潜力?是谁告诉你的?最会见风使舵的媒体吗?\n至少,我是没看到有真正在这一领域精通的大牛站出来信誓旦旦的说: 我保证它可以解决所有的代码难题!\n我想,在大学实验室里看到的那些大模型炼丹师,未必与闭源大模型的开发者有多大的区别.如果生物领域连大脑的运作机制都没搞明白,那又怎么可能通过普通的数学运算就精确的模拟人脑的认知呢?\nLLM目前就只是一个概率模型而已,直到泡沫破裂了,才会有优秀的人才去探索更为实际的研究方向,才会有真正的进步和突破.\n某code源码泄露(26/4/1) 谁看到这个图能不笑🤣,可惜不是在今天泄露的,不然更有乐子了.\n不许你们再说小龙虾的star是沽名钓誉了🤢 第一次见fork数量比肩star数量的,也是唯一一个fork数量逼近百万的仓库 文档学习法(26/4/3) 最好的学习方法就是去阅读一手的文档,而不是去看二手甚至三手的博客教程.\n毕竟官方文档的撰写者一般都是该领域的大牛甚至是作者本人,他对这个领域的了解程度会远远超过我们这些初学者.\n而二手的博客教程一般都是作者在经过了自己的处理之后再片面的告诉你,很难做到十全十美的程度.\n官方文档写的好不好,有没有及时更新或者维护,更是判断这个技术的生态是否足够好的硬性指标之一.\n数据库小测(4/8) (4/7): 明天就要小测了,但还是没有一点底\u0026hellip;不过心态还是要放平,我有预感这次小测会带给我一点启发或者冲击. 这次小测确实有点过于逆天了,我以为不会考的procedure和trigger各考了一个11分的大题,关系代数也考了一个10分的大题,而这三个部分我都基本上没怎么学\u0026hellip; 看来下次小测和期末要认真一点了,不然真要挂科重修了.\n以人文的方式去学习技术(4/14) 现在,我在学习一门新技术之前,总是倾向于先去了解这门技术的演变历史,创作的背景和社区的现状,只有这样我才可以判断这门技术值不值得学,好不好学,需不需要学.\n因此,我不再是像以往那样看到一个新名词,直奔AI了解大概意思,看一点二手博客就企图直接上手了\n相反,我会通过wiki和官方文档去深入了解这门技术的历史,接着阅读官方的教程说明和社区的最佳实践(Github仓库/教程案例),从而逐渐掌握这门技术的基本操作,最后再去实战和根据AI边问边学.\n这恰好对应了人文学科的学习方法:先是了解一门学科的历史,再接着了解这门学科的主要代表人物,然后理解该学科的主要研究内容,最后学习该学科的现代演进和实际案例.\n我想,这种学习方法才是最适合技术人员的学习方法.\n博客是为了自己写的(4/23) 博客是为了自己写的\n大多数博客的大多数文章都只是自我发泄而已,我们装作自己很优秀很清醒,发布各种各样的技术笔记和人生阅历分享.但实际上这些内容没多少经得起推敲的.\n你的人生阅历分享终归是个人的问题,很有可能掺杂了大量的私人感情和不可重复的性格印记,是无法复刻的,也不能从中学到什么,只能用垃圾情绪去感染读者而已.\n你的技术笔记大多是破碎的,凌乱不堪的,草草了结的,远不如官方文档和书籍有体系,反而是在搜索引擎里增加了垃圾资料的权重.\n抛开这些来说,博客仅仅是一个技术人灵魂的呐喊而已,他想要被人发现,被人称赞,被人尊敬;他想要宣泄悲哀,宣泄愤怒,宣泄无奈;他想要升职加薪,事业顺利,生活美满.\n真正优质的博客是与你平起平坐,促膝长谈的;是能够让你幡然醒悟的;是能够让你真正学到优质知识的.可惜的是,这样的博客很少很少.\n博客是为了自己写的,读懂了这句话,你就很少会去看博客了.\n当天晚上的思考 我觉得为了贯彻上述的说法,还是把之前我自己写的劣质文章通通删掉来个整合吧. 本来是想留着警醒我自己技术不够好的,但确实一点都不够看,质量都很低,也没必要留着了.\n重构之前: 重构之后 ","date":"2026-04-02T17:32:10Z","image":"/p/%E9%98%85%E8%AF%BB%E4%B8%8E%E6%80%9D%E8%80%83/84268234_p0-%E7%84%A1%E9%A1%8C.webp","permalink":"/p/%E9%98%85%E8%AF%BB%E4%B8%8E%E6%80%9D%E8%80%83/","title":"阅读与思考"},{"content":" GNU官网 我一直在使用GNU提供的各种软件,却从来没有好好的去深入理解这个组织,所以写了这篇文章来介绍一下.\n历史 wiki GNU is a recursive acronym for \u0026ldquo;GNU\u0026rsquo;s Not Unix!\u0026rdquo;, chosen because GNU\u0026rsquo;s design is Unix-like, but differs from Unix by being free software and containing no Unix code.\n软件列表 GNU组织名下的几百个软件本身就足够用来组成一个Linux操作系统了,尽管GNU名下的操作系统都不是很出名就是了\u0026hellip; 但是,GNU组织名下还有几个著名的软件: Bash,gcc,gdb,make,Emacs.每一个都有着举足轻重的地位 Bash 官方 wiki Bash是Bourne shell的后继兼容版本与开放源代码版本，它的名称来自Bourne shell（sh）的一个双关语（Bourne again / born again）：Bourne-Again SHell。\nBash is an interactive command interpreter and command language developed for Unix-like operating systems.\n大多数Linux系统都默认使用Bash作为脚本执行工具 gcc 官网 gcc全名为GNU Compiler Collection The GNU Compiler Collection includes front ends for C, C++, Objective-C, Objective-C++, Fortran, Ada, Go, D, Modula-2, COBOL, Rust, and Algol 68 as well as libraries for these languages (libstdc++,\u0026hellip;). GCC was originally written as the compiler for the GNU operating system. The GNU system was developed to be 100% free software, free in the sense that it respects the user\u0026rsquo;s freedom.\n我们需要知道的是,gcc通过不同的前端部分处理不同的编程语言,在经过中间处理后,使用相同的后端部分翻译成可执行文件,这种构造使得语言开发者不用太多的考虑中间文件到机器语言的转换.\ngdb 官网 GDB, the GNU Project debugger, allows you to see what is going on `inside\u0026rsquo; another program while it executes \u0026ndash; or what another program was doing at the moment it crashed.\nGDB can run on most popular UNIX and Microsoft Windows variants, as well as on macOS. 在vscode里对cpp程序进行断点调试靠的就是gdb\nmake 官网 cpp的底层构建工具,不必多谈 Emacs 官网 Emacs是一个\u0026quot;重量级\u0026quot;文本编辑器(当然比vscode还是要轻量的多),支持的基础功能就有很多,如下方表格所示: 功能模块 基础版内置支持说明 文本编辑 缓冲区（Buffer）管理、无限撤销（Undo）、矩形编辑、宏录制 文件系统 Dired（目录浏览器）、远程文件编辑（TRAMP）、多文件全局搜索 编程辅助 语法高亮（Font Lock）、自动缩进、S-expression 导航、基础补全 多任务管理 窗口（Window）任意切分、框架（Frame/多端显示）管理 自省系统 实时查看函数定义（Describe Function）、变量文档及快捷键绑定 内置终端 Eshell（纯 Elisp 实现的 Shell）、Term、Ansi-term 版本控制 VC 模式（支持 Git, SVN, Mercurial 的基础操作） 文本处理 正则表达式搜索替换、文本对齐、各种字符编码转换 组织与日程 Org-mode（基础笔记、任务追踪、导出 HTML/LaTeX） 网络工具 EWW 浏览器、邮件客户端（Gnus/Rmail）、FTP 支持 可以看的出来它比vim要复杂繁重的多,集成了很多不是非常有必要的功能,这也就导致Emacs的快捷键比vim要多,甚至有不少的多级快捷键操作,所以使用vi/vim的人比Emacs要多很多. Emacs的拥护者与vi(著名的vim为vi的改版)的拥护者曾经闹得不可开交,但在vscode成为了主流以后就没有争吵的必要了😆. ","date":"2026-04-02T08:00:00Z","image":"/p/gnu%E4%BB%8B%E7%BB%8D/85603687_p0-%E5%A4%A9%E4%BE%9D.webp","permalink":"/p/gnu%E4%BB%8B%E7%BB%8D/","title":"GNU介绍"},{"content":"我之所以把自己玩过的游戏,听过的歌,看过的电影都等信息一一记录在这里,那是因为我的记忆力真的很差,过不了一年半载就记不大清楚具体内容了;至于为什么要写成博客,自然是想分享出来让大家也试试.\ngame 轻型游戏 无光之海(25)-9.5 基本没有任何引导,通过文字和地图设计来渲染一种恐怖的氛围,非常优秀的一款2d航海游戏\nPyrene(25/7)-10.0 非常容易上头的卡牌地牢类游戏,玩法非常多样,甚至还有一些剧情设计上的小巧思\n将军对决(26/3)-9.5 玩法非常新颖的战斗游戏,也很容易上头\n极简塔防(25)-9.5 独一档的塔防游戏,难度很高,需要操作在线.\nRealm of Ink(25)-8.5 Hades的模仿作,机制简单不少,难度也要低一点,玩起来也挺爽的.\nThe King is Watching(25/12)-9.5 难度很高,需要操作在线,随机性高,需要规划好养成路径\n谁玩破防了我不说 杀戮尖塔(25)-10.0 \u0026amp;\u0026amp; 杀戮尖塔2(26)-10.0 一己之力撑起了卡牌类游戏的一片天\nMonster Train1\u0026amp;\u0026amp;2(25)-9.5 已经很好了,可惜不是杀戮尖塔\n渔帆暗涌-10.0 克苏鲁风格的2.5d航海游戏,相信所有玩过的人都会被惊艳到\n神之天平-10.0 不愧是年度游戏,后劲很足,作者能够十几年如一日的独立开发一款游戏,这本身就很燃了\n大型游戏 深海迷航(25)-10.0 \u0026amp;\u0026amp; 深海迷航: 零度之下(25)-9.5 最优秀的深海类游戏,没有之一.\nHades1(25)-8.0 \u0026amp;\u0026amp;Hades2(25)-9.5 不得不说二代是对一代的整体升华,无论是从关卡设计,战斗系统,剧情安排上,都做到了几乎是改头换面的效果,让人能够沉浸的不断战斗下去.\n闪之轨迹1st重制版(25/10)-9.5 不得不说重制版的完成度很高,主要的扣分点就是那些水分满满的剧情前置小任务.\n之前本来打算入坑轨迹系列的,但是现在打算一部部重制版玩下去算了 fate/samurai remnant(25/7)-9.5 是我正常玩下来的第一部3d的jrpg,这一部的完成度很高,探索上比较自由,支线任务也很多,正常难度的话也没什么卡关的地方,剧情整体也很王道很热血,尽管部分地方很无聊,但基本满足了我对jrpg的幻想,拉高了我对jrpg的整体印象\n八方旅人1-8.0 \u0026amp;\u0026amp; 八方旅人2-9.0 独特的2.5d非常不错,战斗系统有一定的挑战性,二代做的更加精致,打法也更多,世界观也很好. 唯一的缺点是: 脚本太烂了!坏人坏到了没脑子的地步,好人犟到了弱智的程度,总是有些莫名其妙的转场. 二代的角色开场都写的不错,我以为会有一个华丽的转变,可惜每个角色的后续剧情都越写越烂.\n影视 是,大臣\u0026amp;是,首相(24)-10.0 极度优秀的政治讽刺喜剧,遗憾的是再也没有能够比肩的优秀作品了\u0026hellip;\n宋飞正传(25)-10.0 一般的喜剧到后期灵感有些枯竭时,便想方设法加入一些主角们的情感戏来充实一下贫乏的剧集内容,无论是生活大爆炸,老友记还是老爸老妈恋爱史,到了后期总显得没有一开始那么好笑了,原因便是真正的包袱少了,以主人公本身特点来制作的包袱多了,如果前面几季都没看过,会觉得有点莫名其妙.\n然而宋飞正传不是这样,尽管后期有那么三四集有部分的包袱不够响,也有部分小片段是基于前几季的剧情来扩展的,但基本上来说从头到尾都是好笑的,包袱源源不断,每一集主人公们都会遇到各种各样有趣的事情,而这些事情彼此之间互不关联,从来没有一条分明的主线,主角们的关系整整九季都没变过,讲述的仅仅是关于他们普通而又特别的日常生活,无论什么时候,随便切入哪一集,都充斥着滑稽的片段,这对观众来说就足够了,而这恰恰是其他喜剧所做不到的.\n宋飞正传也是除了\u0026laquo;是,大臣\u0026raquo;以外我唯一计划再重温一遍的优秀喜剧.人生如戏,娱乐而已,平淡就好. 费城永远阳光灿烂(26/2)-9.5 拍到现在拍了十七季,甚至还要继续拍,可想而知这个系列有多么经典. 尽管有几集不够好笑,但大部分的剧情也都是非常好笑的,剧本的脑洞也非常之大,同样也是没有一条分明的主线,主角团永远是无所事事坏事做尽\n旅游 记录一下我去过的旅游景点,由于很少拍照,就不配图了.\n上海 海洋水族馆-9.5 除了价格稍微有点高外无可挑剔,海底隧道真是绝了,各种动物的喂食表演也很不错.\n海昌水族馆-9.0 动物很可爱,品种也很多,最值得一提的是虎鲸表演和海狮表演,甚至还有北极熊和北极狼. 非常可惜的是,海昌的管理太差了:\n景区规划不合理,馆区之间距离有点远,而且有很多没必要的娱乐设施,真要坐过山车的话我会去迪士尼和欢乐谷的\u0026hellip; 海底世界馆死气沉沉的,尽管亮眼的水生动物确实没有吧,但整个气氛就很阴森 北极馆的动物都趴着不动弹,看着就没有生机,真不知道是因为动物的天性,还是因为活动空间狭小无法动弹导致的 景区里面的饭店和摊点价格有点逆天,好像是不希望游客再来第二次了 省流版: 入园后看完虎鲸表演,海狮表演,其他表演就别看了,逛完企鹅馆,北极馆,水母馆,白鲸馆后就赶紧撤.\n外滩+南京东路-6.0 建议半夜的时候骑车逛一逛就可以了,其他时段不要去.\n豫园/城隍庙-6.0 不建议去,搞不懂为什么这么火\n佘山/欢乐谷-9.0 佘山很小(上海最高峰\u0026hellip;),基本两个小时就可以上下了,重点是旁边有欢乐谷!\n上海旅游节的时候去欢乐谷是最值的,门票半价,但人也会更多. 绕一圈下来只有过山车,漂流,4d影院比较有意思,其他的真没什么.\n古木游龙是真的吓人,热度也最高,但我不敢坐也不想排120min的队\u0026hellip; 上海中心大厦-8.0 电梯很爽,顶层的风光很好看,门票也是真的贵.\ngal age Muv-Luv三部曲-10.0 刚看完剧情的那一两天很难缓过来\nAlicesoft rance series(23/24)-10.0 兰斯系列我的游玩顺序相当乱,本来一部部按顺序推下来的话第十部就能有更好的体验了 我的大致顺序是第一部玩了一半-\u0026gt;第二部玩了一半-\u0026gt;直奔第九部-\u0026gt;再玩第十部-\u0026gt;发现确实是神作得玩前作-\u0026gt;第八部-\u0026gt;第七部-\u0026gt;第三部-\u0026gt;第六部-\u0026gt;云第四,五部-\u0026gt;重玩第一部\n质量最高的显然是第十部,然后是第七部\u0026gt;第六部\u0026gt;第九部\u0026gt;第八部\u0026gt;第三部重制版\u0026hellip; 可以看出来rance系列探索了各种各样的游戏玩法,而且与剧情结合的相当成功,有战棋,卡牌,地域压制,3d迷宫,2d迷宫,与其说是galgame,不如说是带cg的jrpg.\nAQUAPLUS/Leaf 传颂之物三部曲(25)-9.0 剧情\n第一部的男主其实并没有必要在之后出现\u0026hellip;换个世界观其实也挺好,硬要贴边就显得狗尾续貂了 后两部为同一男主,尽管有一些剧情上的衔接瑕疵,但主角团还算是有血有肉的,每个人都有自己的个性,也都很可爱,渐渐揭发真相的过程算也是为数不多的剧情亮点了. 世界观设定的很好,可惜的就是不能很好的通过文字表达出来,但玩游戏的话看的就是氛围,脚本差一点的话就一路ctrl吧 演出\nCG都很帅,愈发衬托出脚本的平淡和不痛不痒的力度 战斗过程枯燥,难度很低 White Album(24)-9.5 不得不品的甩巴掌环节 男主过于憋屈,玩的时候真看不下去,只过了两条女主线,其他的支线都没过,不得不说,如果是女生主动来追求你,她又能保持自己的独立人格,不会卑微的当个舔狗,那很难不心动. White Album 2(24)-9.5 明明是我先来的啊\u0026hellip; 三角恋写到极致之后看的真是有滋有味,尤其在男主是个狠心的\u0026quot;老好人\u0026quot;的情况下.说实话,这种第一人称的叙述很容易把自己代入进去,但是你跳出来细细品味就会发现男主就是个出生而已,既要又要,以自己的所谓悲情去裹挟两位好女孩(这次是真的好女孩\u0026hellip;),扣0.5分也就在这里,因为男主太不正常了(女主也未必正常\u0026hellip;).\nAugustsoft 大图书馆的牧羊人(22)-8.5 这个我也算是推了两遍,第一遍并没有推完,再捡起来已经忘了大致剧情了(虽然本来也没有什么剧情)\n尽管是废萌,但有些对话就是莫名的会戳中我笑点,而且人设都设定的非常好,也不单纯是那种围着男主转的剧情设定,可以说很少有废萌能够与之比肩了\n秽翼的尤斯蒂娅(22)-9.5 这个我应该推了两遍,第一遍还是在高中的时候,当时偶然在一个gal资源网站看到别人分享就去玩了,完全不知道它的名气,这样反而让我更好的沉浸进剧情里面,成功见证了一个底层人士自底端向顶峰的攻略故事.\n前两章让人只能体会到压抑,而第三章进入圣堂之后画风一转,仿佛误闯天家,可是光鲜亮丽的背后却藏有无尽的污秽,比前两章反而更为压抑,进入第四章后阴暗基本被揭露出来,却又在第五章来了一个转折,主人公的人设也就是这个时候开始崩塌的,前期的勇猛果决仿佛都消失了,唯唯诺诺地,不敢做出任何决定,仿佛一政变成功就当上圣母了,最后直到即将失去心爱的人时方才重新做回男人.尽管可以说有亲情因素和自卑因素在里面导致男主下不了手,但确实很憋屈.\n结尾我想剧本家也不知道该怎么写了,只好写一个圣母结局,但我自己也不知道应该怎么写才能圆回来,毕竟不能直接让所有人都死光吧,只能说前面写的太好反而让第五章看起来不太对头,没能做到一个完美的收尾\nblacksoul blacksoul1(26/1/20)-9.0 攻略1 攻略2 攻略3 这三个攻略交叉着看基本就能走到真结局了\n剧情比较简单,流程也比较短,如果不算上刷魂的话我觉得5个小时就能体验完所有结局,但是不看攻略自己走的话估计要花上50个小时,有太多隐藏剧情了,很多事件都是彼此关联的,一旦你漏掉了一部分提前推进了,就不可能进入真结局\n简要分析\nA/B/个人结局:10位女主全都没有好结局,而利耶芙结局基本点明了部分真相\nC结局(真结局):收集所有童话书需要把八个女主都刀了,最终把玛丽苏刀了之后所有成员再次团聚,但这也只是玛丽苏计划里的一环罢了\nD结局:显然像是个DLC样的结局,黑之裁判所的所长原来是玛丽苏的母亲,而玛丽苏把她干掉之后打算和格林继续过二人世界,后面突然出现了爱丽丝,游戏就戛然而止了\nblacksoul2(26/1/28)-9.5 第一次玩的时候没几个打得过的boss,到处乱转也找不到什么游戏提示,跟人物对话也全是乱码.一开始以为游戏设定是这样的,直到查攻略才发现是我黑魂吃太多,sen值太低了.\n受不了了,这真的只能靠自己一点一点摸索出来,而且新人非常容易跑到孢子之地见爱丽丝导致错过小红帽,都不敢想象没有攻略正常人怎么会知道有里世界这么一个东西,一代好歹有路牌和利耶芙引导,刚开始的骑士也贴心的告诉你怎么去魔女之家,二代就剩一只喵喵怪叫的猫和地上的红色文字了,地图也变得更加错综复杂,很容易误入后期地图然后遇到小怪立马暴毙\n大致看了看攻略,成功在三周目进入了里世界并进入DLC3,由于混沌迷宫的爆率感人,因此黑之斩击一些强力的技能并没有刷出来,即便属性基本都点满了,lv也999了,还是打不动小怪,当用ce买魂改完属性后很久才发现可以打灰烬刷魂.\n后来又发现是被暴杀天使偷偷改了难易度\u0026hellip; 总之,bs2体量确实大,地图确实绕,隐藏点确实多,体验与一般的rpg游戏截然不同\nce修改数值: rpgmaker中对应地址ce显示数据=原数据×2+1 祖传攻略 CLOVER GAME(26/1)-7.0 最近的作品是\u0026laquo;すれ違う兄妹の壊れる倫理観\u0026raquo;,其实这个会社的几部作品都中规中矩吧,当小品作看看也成,反正一路ctrl下来也没发现有什么特别亮眼的表现\ncotton soft 这家会社我非常喜欢,可惜在15年之后就消息全无,原班人马也不知道去哪里了\n琥珀结晶(25/12)-9.0 剧情写得非常好,通过章节来一步步揭露真相,一直吊着我的胃口,想要知道主人公以前到底经历了什么,同时,各女主的支线并非是单独的部分,而是作为最终主线的剧情构成部分,这点设计非常的巧妙,至少我玩了这么多gal里很少有能做到这点的\n最重要的是有两对苦命鸳鸯 双子座的paradox(26/1/5)-9.0 同样是剧情神作,他不是一下子抛出一大堆谜团,而是逐步地通过回忆和揭露来慢慢展开剧情,前文里的许多伏笔到了后期慢慢都被发掘出来,不得不说这个剧本家有点牛批,尽管h部分还是与剧情上有点割裂,没能做到推动剧情的作用,单纯是为了h而h,不过这样就对了\n同时剧情时间线的来回跳跃和要求先推进另一部分的剧情锁也是我从未有过的体验,而剧本家竟然能够通过形形色色的剧情提示做到不产生分离感,真的有点牛皮\n最神的我反而觉得是ED,尽管整部游戏的各个结局都是同一个ED,但这个ED是真nm的好听,不知道为什么没什么人听就是了,可能是作品太冷门了\n世界末日与生日快乐(2026/1/13)-9.0 推荐临近生日的时候去玩,最好是生日当天玩epilogue(哭) 最神的依然是音乐,graduation这首伴奏贯穿了整个剧本,确实好听,其他几首歌也不错\n重复的末日,重复的轮回,不变的感情,令人怅惘. 依然是一环套一环的剧情,越来越喜欢海富一了,可惜现在没有他的消息了\nEscude 废村少女-7.5 系列作画风都很顶级,尽管没有什么剧情,但大部分的cg表现力都很不错,如果能整个动态cg就更好了.\nTOKYOTOON 丸子与银河龙(25)-9.0 世界观和演出都很顶级,唯一遗憾的是脚本功力不够,总是差点火候,但游戏整体还是很好玩的,融合了多种演出方式,还有很多neta环节.\nHulotte-7.0 喜欢搞怪的废萌,每部作品的设定都很不错,人设也还行,就是剧情写得太无聊,跟yuzusoft可以坐一桌\nI\u0026rsquo;m moralist Surah(25)-7.0 很短,但是很涩\nKey Summer Pockets\u0026amp;RB(25)-8.0 部分剧情比较有趣,但大部分都是很无聊的培养感情过程,各女主的人设也都没有比较戳我的地方,唯一的亮点是乒乓球大作战().\nClannad-9.5 整体氛围还是很不错的,很温馨很日常,笑点也很多.\nAngel Beats-9.5 可惜的是烂尾了(没写完),但前面的剧情挺不错的,笑点也很多.\nLatte 妹选拔总选举(26/1/20)-6.0 这标题一开始是让我浮想联翩的,可是一进入画面立绘就暴雷了,尽管立绘甚至还会动,但就是长的太偏差了,很难相信是06年的作品,同期的作品立绘不至于这么离谱,只好一路ctrl了,很好奇汉化组是怎么坚持下来的\n速览下来,显然365个妹妹只是个幌子,基本只涉及了四个妹妹,只是一部单纯的倒贴废萌作品,说真的这么好的设定就这么浪费也太可惜了,不要求365个,只要10个我觉得也不错了,不走炎孕路线就行\nNANACAN 流星-7.5 小品作,剧情很短,尽管有剧情锁之类的,但也没什么出彩的地方,一路ctrl过去的.\nNitro+ 罪恶王冠-失落的圣诞节(25)-8.0 之前没接触过罪恶王冠的动画,玩完之后才发现是沿用的动画的世界观重新写了一个短篇,因此玩的时候觉得有些没头没尾的,但是拔剑确实帅,还有神曲作为配乐\nchaos:head(24)-9.0 这个是云过去的,因为游戏界面太古早了,不过脚本写的确实不错,废人男主前期一直在各种逃避,不敢面对现实,到了真结局时终于觉醒,拯救世界.\nchaos:child(24)-9.5 一脉相承的故事体系,但是ui现代多了,立绘也耐看,剧情上也更加波澜起伏,一旦开始推就停不下来,\n命运石之门(24)-10.0 失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了失败了\n剧情,人设,UI全都做到顶级的老资历旮旯\nSister Position 非常好的专写妹系的会社,本来叫Twinkle Position,不知为何改名,但原班人马还在\n妹抱1/妹抱2(22)-8.5 尽管没什么剧情,但确实是独一档的妹系后宫废萌了\n童真兄妹(21)-8.5 这貌似是我推的第一部gal,成功带我进入了旮旯game的世界,不得不说写得确实又日常又够色,入门作是这个而不是重生萝莉岛真是太好了\n病弱妹妹(25)-8.5 改名为\u0026laquo;病弱哥哥\u0026raquo;可能更合适,这妹妹也太猛了\n自宅すたじお-7.5 基本都是倒贴类型的废萌,没有一点剧情,但要当拔作玩的话剧情又显得稍微多了一点,不过每部作品基本都有几个动态CG,那还是原谅你吧.\nもんむす・くえすと！(25)-9.5 优秀的王道剧情,让人掉san的立绘,不过适应了画风之后看见怎样的魔物娘都能波澜不惊了.\n尽管战斗系统和引导系统都做的差点意思,但基本不用卡关,一路畅通无阻的感觉也不错,玩完之后还有一种怅然若失的感觉.\nもんむす・くえすと！RPG(26/3)-10.0 设计方面 体量大的吓人,几十张(或许快一百张)地图看的出来都是精心设计的,几百种武器装备道具到了后期翻都翻不过来,几百种技能,还有各种各样的连锁技,角色的专属技能,配上角色本身的特性,简直是眼花缭乱,算都算不过来.\n美中不足的是,由于角色太多太多,每到一个新区域就会获得新的强力角色,数值上基本能够碾压先前培养好的角色,同时敌人的数值也会有惊人的倍数提升,导致没有角色可以从头用到尾,主角团到了后期也显得弱鸡无比,打一个路边小怪也要耗上很久.\n然而这么大体量的游戏还有多周目,可以回到重大选择之前,继承所有的道具和伙伴(尽管有剧情锁强制你去做一些支线任务,故不能一键速通),保证可以把喜欢的角色刷到离谱的强度,而更别提游戏内部自带了作弊道具:平衡之钥,可以调整经验倍率和敌人强度,保证玩家不会因为后期的数值爆炸而破防\n剧情方面 基本是从前一部作品扩展开来的,但剧情的深度和广度都不在一个量级上,虽然整体来看剧情还是很简单的,就是拯救快要灭亡的世界,但是奋斗在灭亡边缘的每一个人物都刻画的有血有肉,人设这一块确实设计的很好了.\n我在后期打不过就开作弊道具,所以基本没怎么卡关,但就是这样也玩了差不多100个小时,去贴吧一看正常流程都是玩了两三百个小时的\u0026hellip; 可想而知整个游戏的内涵有多么丰富,我想这真的是前无古人后无来者的黄油了,所以我不谈及具体剧情,避免有任何形式的剧透.\nmilky factory(2026/1)-7.5 看着体量大,实际上每一部作品的实用性都不太够,尽管有动态cg,但夸张的体型和过于直白的推进还是让我觉得少了点什么. 前期的画风就正常一点,但还是不耐看.\nSAMOYED SMILE 夜晚,徘徊在我们的辅导教室-7.5 貌似是我玩的第二部gal,剧本写的还行,人设也安排的不错,没有什么太崩的地方,重点是有动态CG!\nSofthouse-seal GRANDEE 爱迪生牛顿大发明(23)-7.0 剧情比较逆天,玩法也多,比yuzusoft强就行\nWest Vision 精爆双姬(23)-9.0 终于看到正常的兄妹关系和恋人关系了\u0026hellip; 尽管画面很古老,但是你就去玩吧,保证大开眼界.\nyuzusoft-6.0 画风显然是越来越好的,脚本则是一成不变的糟糕,要不把固定女配的cg砍掉再把经费投入剧情和演出算了.\n散 Class of 09三部曲-9.5 使用renpy开发,尽管界面很简陋,但是架不住剧情和人设都太有趣了,想练英语可以看这个,一大堆生词可以直接打击你对英语的自信心.\n终末之际(25)-9.0 极其优秀的超短篇末日题材小说,没有立绘和配音,单纯靠场景,音乐和文字进行渲染就能够让读者领略到一股浓浓的悲哀与不舍\nNao in Heat(24)-8.0 很小很短,但是很涩\ngal脚本家 大部分文字来源 原帖 一个非常细致的脚本家介绍,事实上原帖作了一个排名,越往后排名越高,但是我把排名过滤掉了,毕竟能入榜的脚本家都很优秀了;另一个改动就是原帖对每部作品都配了图,而我这里都删掉了,毕竟好作品自己会说话\n元长柾木 元长很擅长对于具体情境氛围的刻画，对于世界观和人物心理活动的描写都比较精细，尤其擅长营造心理交杂的世界观。当然缺点也很明显，过于偏执的连续描写很多时候会发展成毒电波属性的自说自话，给人一种耍花活的感觉，当然对于电波对不上的人可能连感觉都很难有……\n代表作 《猫抚ディストーション》 元长全力全开的代表作品，话题作，只要你能合上电波就是当之无愧的杰作，当然大多数人是合不上电波的…… 《sense off ～a sacred story in the wind～》 元长早期的作品，世界观上亲切了许多，老实说其实我觉得这个才是元长最好的作品。 补充介绍一下，折戸伸治参与了senseoff的音乐制作，元长是神戸大学工学部的毕业生，小说家和作词家，和hain一样是位特殊剧本爱好者。13cm活跃过几部作品，推荐一下娇烙之馆。（资源hiyo菊苣在bangumi有传）\n御影 少数派的两大栋梁之一，当然实力公认比不上好机油镜游。 在死生观上有着卓越的笔力，名作EF中在这方面这位就展示了不俗的实力，不过还是比不上好机油镜游。 尽管语言有时会带着浓浓的中二气质，但同样也的确饱含深意，不过还是比不上好机油镜游。 由于总是同时兼任监督一职，所以未免在本社文字创作量有所下降，并且在担任某最炫GAL游戏OP的时候险些把少数派玩破产，不过还是比不上好机油镜游。\n代表作 《ef - the first tale.》,《ef - the latter tale》 前后两部EF，少数派的巅峰之作，经典地位不用咱多说了吧……虽然御影在这里的表现还是比不上好机油镜游。 《水夏～SUIKA～》 当然，御影同学还是有逃离机油阴影的幸福时光的，而且在名作水夏中发挥的也确实不错。 下仓バイオ N+的后起之秀，老实说毛病的确不少，不过也确实有着自己独到的地方。《スマガ》中显示的格外明显，与N+脚本群传统的战斗系燃点不太一样，下仓在世界观以及世界观引导下的剧情中表现的更好，实话说在老虚不务正业的情况下还是比较受社里重视的，创作风格真心在钢屋****奈良原这些家伙里别树一帜。 不过毛病也不少，尤其是对玩家满怀恶意……= =\n代表作 《月光のカルネヴァーレ》 算得上下仓的成名作了，表现的确实不错，不过缺点也很明显，某些地方玩得咱实在累无味…… 《スマガ》 个人认为下仓至今为止最优秀的作品，当然他本来的作品就不多……自带LOOP系统真是叫人又代入又咪疼…… 菅宗光 想当年LEAF东京部的一员虎将，另一位自然是三宅真介了，这两位都是LEAF自产的脚本家，唉现在多少孩子提起LEAF就是WA2就是丸户…… 菅宗光非常善于妄想……啊不虚拟世界观的设计和剧情的构筑，以一人之力构建出传颂之物这样的幻想系，名作，其实力可见一斑。可惜，LEAF多年的吃老本行径让这位实力派脚本家始终没有下一步杰作面世……\n代表作 《うたわれるもの》 个人认为LEAF的最经典作品，菅宗光呕心沥血的杰作，无论多久都难以磨灭。 《ToHeart2 AnotherDays》 回归日常的菅宗光表现明显下降了许多……可是作为他唯二的作品之一，还是放上来吧。 秋津环 虽说在大多时候总是隐藏在海富一、片冈等人后，但是环姐本人的实力也是毋庸置疑的，也许是因为性别原因（误）？环姐对于学园物表现的相当不错，而且对于女性的心理把握的相当不错。 环姐的文风更加软一些，非常的擅长渲染气氛，文字与画面的结合能力比较高，并且能够在简短的日常中轻易地展示人物性格所在，有着不错的心理描摹功底。\n代表作 《ラムネ》 主要搭档片冈，意外的产生了不错的反应，环姐负责的部分格外清新动人。 《レコンキスタ》 主要搭档海富一，表现无可挑剔，其实环姐果然是贤内助啊（泥垢）。 新岛夕 SAGA PLANETS的主力脚本，当然现在是前主力脚本了…… 文字温暖人心，嗯暖人心。（误） 新岛夕的文字功底在出道初期就表现的不错，尤其擅长对二人关系的细致描写，在校园物上有着明显的掌控能力。并且自从暖人心的某作后，还被发掘出了埋包袱和暗地里坑人的天赋…… 对于游戏节奏掌握的也很到位，不过在个别支线上还是比较突兀了些，但是还是能够妥帖的处理主线剧情的。\n代表作 《はつゆきさくら》 纯爱大作，充满了温情的冬季恋爱物语——你就当真的听。 《ナツユメナギサ》 初雪樱之前新岛夕最好的作品了，当然现在看起来也算是良作。 健速 都合大先生，不过真要说起来健速虽说总是都合，但是稍微不太都合的作品的确都是杰作。 大先生严谨的工作态度还是要赞一个的，虽说的确有很多作品都合严重，但是必须承认基本也没有什么雷作，甚至可以称之为业界良心了。 不就是日常各种催眠，剧情各种奇葩展开么……至少还是人干的事不是？ 当然大先生也有自己的超杰作**《そして明日的世界より――》**，只是大先生大多数的作品还是略都合了一些……其实就是这作也都合严重。\n代表作 《そして明日的世界より――》 大先生超神啦~！只要顶过前面的各种催眠，后面绝对震撼人心！ 《遥かに仰ぎ、丽しの》 大先生严谨的工作态度取得的成就！本校超级好男人！尽管超级好男人已经要称霸宇宙了……至于丸谷秀人写的那淫行教师…… 睦月たたら 行业杀手wwww 其实也是资格比较老的脚本家之一了，但是由于单独担当实在不多，而且骗子社始终不缺少令人眼前一亮的脚本家，所以似乎之前的名气在天朝并不是很响亮（当然也可能是笔者孤陋寡闻），不过自百合灵这一话题作诞生之后，睦月似乎一夜爆红…… 睦月是典型的实力派脚本家，他的文字表现力相当强，别的不说，在书写回忆以及渲染感伤氛围上相当得心应手，并且十分长于扔闪光弹wwww各种叙述手法都比较擅长，而且也很能够表达出人物复杂的心境。 在骗子社的一堆不说人话的家伙里算是比较亲切的了……\n代表作 《屋上の百合霊さん》 话题作，第三人称视角相当有新意，游戏的氛围也很棒wwwww。 《ぼ～ん・ふりーくす！》 睦月比较早的作品了，各种手段已经比较娴熟，不错的良作，可见睦月长期以来平稳的剧情表现实力。 铃鹿美弥 杉菜水姬的真爱，IG社的台柱。 文风相当的具有个人特色，虽然有七凭那种渣作，但是除此之外的剧本发挥的都不错。而且非常长于诡异气氛的刻画和对于郁结心情的渲染，营造处的气氛相当具有吸引力。 在谜题的设计上略显不友好，不过还是能够给人带来比较强的诡计震撼力，文字和画面的契合程度很高。 不过在题材的选取和剧情的发展上流露出明显的局限，经常产生莫名其妙的既视感，剧情上始终难以突破固有的模式，并且为了达到诡计目的对玩家相当不友好。 可是也许这正是IG的风格wwww。\n代表作 《壳ノ少女》 美弥阿姨的缺点在这表现的还不太明显，相应的，剧情的节奏和文字的表达做的很不错。 《虚ノ少女》 相应的，震撼力更强也更不友好的这一作就暴露出了其局限，对玩家的恶意也更加满载了wwww。 《和み匣》 至于这一作呢……实话说咱个人觉得绝对算是良作，但是对于玩家的特性要求也比较高啦。 镜游 御影的好机油，资历比御影要浅很多，不过个人觉得实力要比御影强。 镜游的文字底力明显更深厚得多，并且对于剧情的处理也更加自然，对于各种题材都有一定的把握，适应性更强。 文字较为具有亲和力，能够做到吸引玩家的同时减少对于玩家的催眠程度，擅长在日常的描写中穿插情感的表达。 能够熟练地进行描写对象的切换，并且在描摹环境与人物的映衬上做得很不错。\n代表作 《eden》* EDEN，尽管由于某些原因恶评如潮但是剧本的优秀还是必须承认的。 《ef - the latter tale》 不多说，两位机油的表现孰优孰劣有目共睹。 《はるのあしおと》 少数派早期的第三作，镜游对各题材的把握能力有着不错的展现。 钢屋ジン 图书馆三人众之一，钢屋JIN，咱一直认为老虚不务正业之后**N+**的领袖和代表。 钢屋的文字是典型的中二燃系的风格，并且十分擅长描写战斗。**N+**的剧本家描写战斗的能力在业界本就是响当当的，钢屋也是其中的佼佼者。借助立绘演出真心能够满足大部分战斗场面的要求。除此之外钢屋更是玩得一手好设定，在设定的辅助下对游戏剧情的把握相当不错。 整体作品感觉在主题表达，虽说这不代表他写不出内涵作，只是钢屋果然还是更加适合战个痛啊=A=\n代表作 《斩魔大圣デモンベイン》 钢屋的成名作，一扫之前老虚的治愈色彩，配合相性大变得CG战个痛！ 《ギルティクラウン ロストクリスマス》 钢屋最近难得的作品，虽说是短篇，但是看到钢屋自己写的东西还是可以说吉野你可以洗洗睡了…… 味盐ロケッツ 味盐ロケッツ也算得上实力派脚本家的代表之一了，虽说感觉这家伙的写的东西还是老物比较好，不过这也与领域的不同有关吧，感觉他不是很适合日常故事的写作，相反的在犯罪和解谜领域实力强劲，他主要的杰作也诞生于这一领域。 文字底力不错，不过不太擅长描写日常故事，更加能够适应非日常的展开，对人物的心境刻画比较精致，而且在解谜领域中文字的逻辑感很强，剧情的进展也比较流畅。 感觉塑造气氛的能力稍差，不过对人物自身面对环境时的深入描写可以比较不错的弥补这一点。\n代表作 《STEEL－スティール－》 味盐ロケッツ在自己得心应手的领域中的杰作，非日常一侧的剧情展开舒爽到爆。 《つくとり》 解谜作的代表，层层入扣的剧情发展和逻辑演进真心炒级赞！ 麻枝准 大魔王wwww 实力没人质疑，不过咱感觉也算不上多神，毕竟他的本行还是音乐。 文字功底一般，但是在剧情的设置上的确有着独到之处，大魔王的剧情水平是要远高于他的文字水平的，“先令你爱上一个人物，然后在折磨你”这样的模式也的确有着催泪的神奇之处。日常写的不错，但是对于麻枝总是卖老梗的行为还是不太感冒，谁知道笑点在哪里啊wwww 真心觉得麻枝准的音乐本行干得更好，说实话这么多年他除了LB附加线之外啥也没写吧……\n代表作 《AIR》 个人认为麻枝准最优秀的作品，无论是剧本还是音乐。 《CLANNAD》 传说中的超神作，家族神作,不过作为田中厨老实说感觉震撼程度没想象中那么大……当然CL的优秀还是必须承认的。 《KANON》 不多说了，麻枝准写的虽说也不错但明显比不上久弥。 なかひろ 实力派脚本家，对于人物的塑造和世界观下情节的发展都是可圈可点的优秀作者。 なかひろ的文风非常的具有灵气，在推动剧情发展的同时能够熟练地酝酿故事高潮所需要的情感，而且刻画起人物来也非常娴熟。描摹日常时得益于世界观的把握并不会导致催眠，而剧情突入到非日常一侧时又会爆发出奇妙的色彩。 描摹日常和展开剧情都得心应手的一位灵气逼人的脚本。\n代表作 《heaven\u0026rsquo;s cage》 なかひろ早期的作品，文字相当清新灵动，表现主题也恰到好处。 《星空のメモリア》 星空，其实是他作品里我最喜欢的一作，剧情展开和人物塑造进行的相当不错。 《こいとれ～REN-AI TRAINING～》 即使写这种日常系的文本，なかひろ的表现很出色呢。 久弥直树 神，魔王的天敌wwww 久弥是相当正统意义上的脚本家，和恐怕更多依靠天分创作剧情感动玩家的魔王不同，久弥本身的文字功底就很不错，久弥的文字表达能力相当出色，相当细腻的描写和情节的错铺垫使久弥的故事往往更加严谨。 比起魔王狂气的自我的表达更加注重对妹子的刻画，对于恋爱的微妙描写十分到位。 超喜欢约定的家伙wwww\n代表作 《ONE～辉之季节～》 久弥表现的相当不错，尤其是对于环境和人物的细腻刻画。 《KANON》 久弥在这里的表现确实相当优秀，对世界观 host 的把握能力和对女性角色的精细刻画都很优秀。 补充 额外说一句，虽说不是游戏，久弥在SOLA里的表现也可以说炒鸡棒啊。 海富一 个人最喜欢的猫猫社剧本团中的作者，实力派海富一。 虽然很多时候都在片冈老大的光环下创作，但老实说个人觉得海富一写的东西一点也不比片冈差，偶尔写得还更好…… 单独担当或者担任主役时往往能创作出令人眼前一亮的作品,并且在日常和狂气上都有不错的表现，主题色彩比较强，人物的性格十分鲜活，文字在优秀的同时意外的适合初心者。\n代表作 《サナララ》 小清新的海富一好评，个人觉得海富一写出了最美的部分。 《レコンキスタ》 狂气的海富一好评，意想不到的高质量。 《终わる世界とバースデイ》 偶尔这样子的海富一，其实也不错。 林直孝 打越的得意弟子，实力也确实很强，全年龄领域绝对的一流脚本家。 林直孝继承了打越不少的风格和特点，同时自身也具有相当优秀的文字表达能力，能够在轻松的氛围中组织运用复杂世界观和艰涩的设定。同时，林直孝的文字能力和工作态度都相当严谨，经手的作品大都不错。 此外，林直孝还非常擅长营造故事氛围，其对故事发展始终把握的十分出色，剧情的转折和伏笔都很自然，尽管其中还是偶尔带有都合的色彩，但是真实感往往更强。林直孝的作品往往带着的真实与幻想夹杂的气息非常独特。\n代表作 《CHAOS；HEAD》 出师之后的第一部话题作，林直孝对于真实和虚幻的把握十分从容，耍设定和卖中二也恰到好处。 《STEINS；GATE》 这个就不用多说了，的确是近年来位居前列的优秀剧本。 井上启二 老一辈的脚本家，ELF妖精馆黄金脚本群的杰出人物！ 井上启二的文字表现力就不必多说了，对人物的刻画和情节的推演绝对都是炉火纯青的水平，无论是轻快的世界观还是燃到爆的情节发展都驾驭的得心应手，讲故事的水平更是一流，感情的层层递进和情节的优秀把握绝对叫人简直停不下来=A= 井上对于情节的创造力更是屌炸天，无论是学园、SF还是燃系都能够创作出优秀的故事，老实说11年再次看到井上的名字时真心激动啊！井上这样的脚本家才是业界良心所在！\n代表作 《胜 あしたの雪之丞2》 名作雪之丞2，井上负责的续作完全超越了前作。 《アルテミスブルー》 开飞机啊开飞机！轻快的氛围和天空系的独有感触真心带感！ 《创世奇谭アエリアル》 井上的新作，虽说基到爆wwwww，但是还是带感啊=A= 早狩武志 早狩武志，业界文艺派脚本家之一www，货真价实的文学青年。 早狩武志的文字相当的具有灵性，并且他惊人的知识储量也是毋庸置疑的，这使他在SF领域表现的相当游刃有余，并且这种知识性的构筑还不至于将玩家完全封死在门外，相反，早狩武志凭借自己的文字构筑的世界观因此相当具有真实性。 此外，早狩在塑造情节上也相当的具有灵气，在他越早的作品中越明显，可惜感觉这位实力派脚本家实在是被业界的潮流打击得不轻，居然在恋ではなく里玩起了青春日常……显得剧情拖沓……越早的作品越好=A=\n代表作 《群青の空を越えて》 群青，早狩最优秀的作品，绝对的心血之作。早狩对军事、经济和政治等方面的杂学和剧情达成了相当完美的统一。 《潮风の消える海に》 潮风，虽然比不上群青但是文本的素质也是很赞的。 打越钢太郎 大名鼎鼎的打越，KID曾经的台柱，号称只要有这个名字的游戏就能首发销量收回成本的男人……虽说由于KID某种意义上自毁长城还是倒闭了（误）。 打越的文字表现力只能说一般，但是论起剧情设计的精巧，情节构思的严谨以及结局把握的震撼性，打越无疑都是业界一流的。对以诡计的设计与解答、谜题的复杂性与逻辑性的兼顾更是在这一领域独树一帜，并且打越在追求剧情严谨性，把握玩家心理并引导玩家进行头脑风暴的同时，对与玩家还是抱有一定的亲和性。以至于当年R11出现后面对大片非核心向受众“不明觉厉”的反应干脆就披上马甲撇清关系…… 但是无论是从名气还是实力上，打越在KID脚本群里都是绝对的首屈一指。\n代表作 《Never7 -the end of infinity-》 打越初次展现了自己的实力，虽说在结局上略显仓促而且有点不能自圆其说，但是整体的优秀是有目共睹的。 《Ever17 -the out of infinity-》 打越无敌啦！即使是现在看来，这部作品构思之精巧、场景之宏大、结局之震撼都是少有比拟的杰作！ 《Memories Off 2nd》 公认的MO系列最高杰作，触及心灵的美丽故事，打越证实了自己在多领域的杰出能力。 奈良原一铁 图书馆三人组里咱最喜欢的一位。 各种意义上来说风格都很独特的脚本家，和钢屋、东出不同，对单纯的战斗描写并不擅长。相反地，利用“说教”式的描写来传达战斗的神韵也相当具有独特性。 **N+**脚本群里节操值最高的一位，虽说这两年也开始啥玩意不写…… 奈良原对十一区本土杂学尤其是日本刀的热爱使得他的作品始终带有浓浓的个人色彩，而且文字功底也不错，契合世界观的文本描写和时不时的灵光闪现都让人记忆深刻。 重视对于锻炼和对战时临场心理的再现的创作风格也很符合其作品的主题。 说实话，我觉得咱对一个”突然在邻座上开始挥舞起模造刀来““在公司里研磨起刀刃来”的脚本家真是喜闻乐见啊wwww。\n代表作 《刃鸣散らす》 这才不是基作呢wwww，奈良原玩日本刀玩的真high…… 《装甲悪鬼村正》 不必说了，虽说不是他一个人的功劳，不过**N+**纪念作还是实至名归。另外我说我其实更喜欢刃鸣散会不会挨揍？ 鬼畜人タムー AGE脚本群的最强脚本，反正咱是这么觉得啦=A= 实力是毋庸置疑的强劲，对于不同题材的驾驭能力超强，文笔不能说多好但是表现能力屌炸天，非常擅长通过文字的堆砌引导玩家的心理状态，层层展开之后玩家的心理几乎完全能够与剧情发展同步起来，某种程度上可以说写的东西感染力非常强。 不得不说，AGE的合作模式一定程度上导致了鬼畜人作品整体观感的下降，比如说各线水平明显参差不齐的君望……但是还是掩盖不了鬼畜人的优秀！如果单评某一作品的脚本水平的话，鬼畜人在**《マブラヴ オルタネイティヴ》**里的表现绝对能跻身前五！\n代表作 《君が望む永远》 大名鼎鼎的君望！鬼畜人的表现很不错不过的确不是他完全发挥实力的作品。 《螺旋回廊》 鬼畜人在这种作品里果然就全力全开了一回=A=。 《マブラヴ オルタネイティヴ》 燃爆了wwwww!一句话：人类を軽蔑しないで下さい！ 王雀孙 葵妈的好拍档，橘子社王牌剧本！ 文字能力逆天了，堪称数一数二的难懂，日语白学系列之一。但是如果能领略一点就会发现王老师的文字功底有多深厚。至于文字的表现力，这个简直就属于论外级别……表现到日语白学的地步了……www 剧情设计和人物刻画都相当严谨，深厚的文字功底将人物的情感刻画和环境氛围的营造做的十分到位,咱因为王老师的存在甚至连葵妈脸都可以忽视了……\n代表作 《那是舞动散落的樱花》 舞散樱，绝对是当年数一数二的神作无误。 《我们没有翅膀》 别被那见鬼的动画误导了，王老师在这作里的优秀表现相当碉堡……只要你能看懂。 《靠近太阳之月的处女作法》 新作，相对浅白易懂的一作……但是咱倒不是怎么喜欢，感觉王老师的特色淡化了。 正田崇 中二帝，中二之神www 每个少年都曾经或多或少的中二过，中二怎么可以不玩中二帝的作品呢？ 正田崇对于世界观 host 的把握能力和人物的刻画及其精细，并且文字之中所带着的种种狂气之感实在是令咱难以忘怀啊。除此之外，对于人物性格的刻画也给人一种浓厚的狂乱色彩，颠覆常人的狂乱始终是咱最喜欢中二帝的地方。 另外，不得不说正田中二的台词设计太带感了，一群死中二不明觉厉的嘴炮大战真叫一个酣畅淋漓啊。\n代表作 《Dies irae～Acta est Fabula～》 神怒之日=A=，剧本的长度不要太屌，反正推完之后整个人肯定就直接中二中二掉了…… 《神咒神威神楽》 KKK，和风狂气中二作www，不过中二帝发挥的确实好。 奈须きのこ 蘑菇，世界观屌炸天的设定党。 虽说在大堆设定的映衬下填坑速度实在拙计，但是蘑菇的实力的确没得说，无论是对于战斗场景的描写还是对于人物复杂性的刻画做的都相当不错。 蘑菇的文字功底算不上深厚，但是应用功底的确很扎实，在各种场景描写中做的都很出色，语句基本上流畅而自然，玩家解决了设定问题之后的确有着比较好的游戏感受。并且蘑菇的文风跃动感很强，描摹人物时也没有太强的违和感，的确不愧为TM社炒冷饭的有力保障。 解决不了设定问题嘛……\n代表作 《Fate/stay night》 TM商业化后的第一作，蘑菇确实写出了相当不错的故事，更设定出了相当优秀的游戏世界观。 《魔法使いの夜》 魔使，解决了设定问题后的确可以称之良作。顺便说一句武内你以后还是继续努力做社长吧，原画家武内可以瞑目了。 《月姬》 月姬，我会说我觉得这才是蘑菇写的最好的剧本？ 虚渊玄 爱的战士就不必多赘述了吧……文字功底和剧情把握都是业界公认的，思想性也的确有着一定深度，虽然已经六七年没写过GAL剧本了……你丫给我回来喂！ 心理上的大开大合、注重情绪调动的文字表现、塑造非日常气氛的吊诡文风、隐藏在故事内核的思想本质……老虚的确有着自己成名的本钱。作为公认的喜好女色却厌憎女性，鄙视女性的感性思维却也同样厌倦粗暴的男权世界的乖僻脚本家，硬派而硬朗的文风的确独树一帜。无论是“最美的的就是毁灭”还是“人性中存在非人性”的表达都是这位脚本家阴郁冷硬的世界观塑造下的产物。\n代表作 《沙耶の呗》 的确是相当厚重的作品，也的确是披着猎奇外衣的虚渊玄式纯爱作。 《続・杀戮のジャンゴ —地狱の赏金首—》 老虚当年“什么都敢写，什么都写得不错”的产物。 《鬼哭街》 老虚的出道作也是成名作，这作的出现一时就让老虚风生水气，的确是了；老虚目前为止最出色的剧本。 这个帖子是我把爬取的内容交给AI处理的,最后两位由于原帖找不到只剩一个名字了,所以AI自动帮我续写了???? 田中罗密欧 田中，老实说在咱心里这位才是真正的神。 文字功底已经到了返璞归真的地步，平淡的文字中蕴含的巨大的感染力。作为业界最有思想深度的脚本家之一，其对社会、人性的洞察力简直叫人叹为观止。无论是《家族计划》中对“家”的探讨，还是《CROSS+CHANNEL》中对“沟通”的绝望与希冀，都达到了极高的艺术水准。 此外，他在塑造角色方面的能力也是顶级的，能够赋予角色鲜活的生命力和复杂的灵魂，绝非简单的标签化。\n剑乃 剑乃，业界传说级的存在。 作为《YU-NO》的创作者，他不仅是一个脚本家，更是一个天才的设计者。他在剧本中融入了超前的平行世界理论，并以严密的逻辑和宏大的构架将其呈现。 他的文字充满了知性的魅力，对于悬疑和解谜的氛围营造无人能及。虽然作品不多，但仅凭一部《YU-NO》就足以奠定其在GALGAME历史上不朽的地位。\n某不知名帖子上的脚本家列表 虚渊玄: Phantom、沙耶之歌、吸血歼鬼的维德哥尼亚 王雀孙: 我们没有翅膀、それは舞い散る桜のように、近月少女的礼仪 鬼畜人タムー: Muv-Luv Alternative、你所期望的永远、螺旋回廊 打越钢太郎: Ever17、秋之回忆1、Remember11 林直孝: 命运石之门、Chaos head、机器人笔记 奈须蘑菇: Fate/stay night、月姬、魔法使之夜 Sca-自: 樱之诗、樱之刻、美好的每一天~不连续存在 新岛夕: 初雪樱、魔女恋爱日记、想要传达给你的爱恋 星空流星: 腐姬、Forest 濑户口廉也: Swan song、Carnival、KIRA☆KIRA 龙骑士07: 寒蝉鸣泣之时、海猫鸣泣之时、彼岸花盛开之夜 麻枝准: Clannad、Little Busters、Air 六花梨花: 鬼畜王兰斯、兰斯6、斗神都市2 蛭田昌人: 河原崎家的一族、同级生、Words Worth 正田崇: Dies irae、神咒神威神乐、相州战神馆学园 八命阵 田中罗密欧: CROSS†CHANNEL、家族计画、最后的现在 Looseboy: 车轮之国、G弦上的魔王、A Profile 下仓バイオ: 冻京Necro、你和她和她的恋爱、月光嘉年华 和泉万夜: EXTRAVAGANZA ～蟲愛でる少女、无限炼奸、MinDeaD BlooD 丸户史明: 白色相簿2、青空下的约定、女仆咖啡帕露菲 奈良原一铁: 装甲恶鬼村正、刃鸣散 健速: 遥仰凰华、明日的世界、从此方到彼方 卑影ムラサキ: Baldr sky、Baldr force、Baldr heart 东出佑一郎: 妖人幻妖异闻录、Bullet Butlers、Evolimit(エヴォリミット) 朱门优: 从晴朗的朝色泛起之际开始、不要践踏天使的羽毛、何时到达那片晴朗的天空(いつか、届く、あの空に) 铃鹿美弥: 天之少女、壳之少女、虚之少女 渡辺僚一: 苍之彼方的四重奏、春开意遥遥(はるまで、くるる)、夏日云悠悠(なつくもゆるる) 漆原雪人: 五光十色的世界、五彩斑斓的世界、樱花萌放 御影: ef - a fairy tale of the two、天津罪、青鸟 昏式龙也: Maggot baits、眠れぬ羊和孤独な狼、Dead Days タカヒロ: 认真和我恋爱、娇蛮之吻、你是主人我是仆 海富一: 终结的世界与生日、Reconquista(レコンキスタ)、琥珀结晶(Amber Quartz) 浅生咏: Euphoria、Erewhon 镜游: ef - a fairy tale of the two、eden*、为与明日君相逢 片岡とも: 水仙、Scarlett、120日元之春 衣笠彰梧: 晓之护卫、Reminiscene(レミニセンス)、流星世界演绎者(流星ワールドアクター) さかき傘: 金色Loveriche、辻堂さんの純愛ロード、娇蛮之吻3学期 土天冥海: 我的民工女友、媚肉之香、人间残渣 吴一郎: 樱之杜†净梦者、来自昏暗的时间尽头、夏色乡愁 樱井光: 赫炎的印加诺克、黄雷的伽克苏恩、紫影的索纳尼尔 鋼屋ジン: 斩魔大圣、机神飞翔、罪恶王冠-失落的圣诞节 中岛大河: 若能再次与你相见、生命的备件、働くオトナの恋愛事情 森崎亮人: 幸福噩梦、Fake Azure Arcology(フェイクアズール・アーコロジー)、Sugar+Spice 元长柾木: 猫抚歪曲、Sense Off、Gangsta Republica(ギャングスタ・リパブリカ) 桐月: 黄昏的禁忌之药、月影魅像-解放之羽、五色浮影绽放于花之海洋 かずきふ米: 9-nine、七彩的轮回转世、あけいろ怪奇譚 七乌未奏: 隙间樱花与谎言都市、在这片天空展开翅膀、StarTRain 嵩夜あや: 少女爱上姐姐、木洩れ陽のノスタルジーカ、機関幕末異聞ラストキャバリエ 樱庭丸男: Chronobox、駄作 希: 霞外籠逗留記、信天翁航海录、神树之馆 音乐 演唱会 绫11演唱会 人生中参加的第一场演唱会,也是阿绫的第一场个人演唱会,基本的感受是很好的:\n观众们都很热情,有一大堆穿痛衣,出cos,发无料的. 节目编排还算合理,条子的歌(只)上了三首,但有几首歌比如(\u0026laquo;放大\u0026raquo;,\u0026laquo;九月不停雨\u0026raquo;)我真没听过,看来是假粉了\u0026hellip; 不知所云的AI乐队绘比较扣分,洛天依的声线调的比较崩\u0026hellip; 我看完日场就走了,没参加聚会,买了一个168的礼盒当作留念. 国语 李宗盛 山丘-9.5 听的第一首李宗盛的歌,当时还是初中,天天听以为自己老成熟了老伤感了\u0026hellip; 散文诗这块我只认可李宗盛 当爱已成往事-10.0 男女二重唱真的美哭了 霸王别姬的配乐,好电影配好歌 梦醒时分-10.0 作词作曲都是他,但他自己唱的就差很多了\u0026hellip; 顶级配乐,顶级作词,经典的不能再经典,任何时候听到都会有一种淡淡的忧伤 林俊杰 江南-9.5 小时候总是跟着唱,就是不知道叫什么名字 周传雄 黄昏-9.5 现象级歌曲\n寂寞沙洲冷-9.5 周杰伦 红尘客栈-9.0 告白气球-8.5 小时候总喜欢唱,导致长大了都不好意思听 兰亭序-9.5 听的红楼梦鬼畜才知道原曲 旋律很好听 稻香-9.5 阿杰 牵丝戏-10.0 河图 第三十八年夏至-10.0 意境渲染这一块没有比这首歌还强的了\n王朝1982 我本将心向明月-10.0 一曲定乾坤!真有种\u0026quot;天子呼来不上船,自称臣是酒中仙\u0026quot;的感觉\n卦者那啥子靈風 一体机,甚至还是p主,时不时会发一点v曲出来\n范进中举-10.0 现象级歌曲\n日语 White Album系列 神曲大集合\n届かない恋-10.0 After All ～綴る想い～-9.5 White Album-10.0 英语 John Denver 小平同志听了也说好!\nTake me home,country roads-10.0 Rocky Mountain High-10.0 Oasis Don\u0026rsquo;t Look Back In Anger-9.5 旋律很不错.\nvocaloid 中V\n社团/合作系列\n很多歌都是以合作的形式完成的,作曲,编曲,作词,混音有可能都是由不同人负责的 寂火-9.5 幻月音乐团 旋律编排真的绝了,那种调出来的沙哑嗓音也很棒 不年轻人-10.0 花间有鹿来 \u0026amp; 星尘 \u0026amp; 海格P \u0026amp; Kevinz \u0026amp; 猿马行歌 \u0026amp; 磷元素P 激情是我的谎言,大家都早已疲惫不堪 繁华唱遍-10.0 繁华落尽\n岁岁朝朝-9.0 旋律很不错\n梦语-10.0 出道曲哈哈 元老系列\n特点是大多数名字都很短 ilem 李白再世,飘逸如仙,基本都是一体机,除了一些毒曲外,歌词和曲调都能打动人心,很少有不好听的歌曲,早期的摸鱼程度还是比cop低一些,但现在发的歌比cop还少了,从作品也听得出来心境有很大的变化了.\n我没有歌能给你听-9.5 上山岗-10.0 勾指起誓-9.5 梦良衣-10.0 意境的渲染上很少有比得上这首歌的,真正的哀啭动人.\n夜间出租车-8.5 大氿歌-8.5 白鸟过河滩-10.0 告死鸟-10.0 第一次听就被震撼到了 悠悠天边飞过那漆黑美丽的告死鸟啊 cop 石墨系列-10.0 基本都是神曲 从悲怆哀伤的\u0026laquo;礼物\u0026raquo;,到声嘶力竭的\u0026laquo;世末歌者\u0026raquo;;从淅淅沥沥的\u0026laquo;凉雨\u0026raquo;,到暗无天日的\u0026laquo;世末积雨云\u0026raquo;;从无人问津的\u0026laquo;回音\u0026raquo;,到相互靠近的\u0026laquo;同归世界线\u0026raquo;;从孤独寂寞的\u0026laquo; hello\u0026amp;bye,days \u0026raquo;,到烟火灿烂的\u0026laquo;夏夜空\u0026raquo;;还有古早的\u0026laquo;夏风\u0026raquo;和尚未完成的\u0026laquo;碎梦\u0026raquo;.十首曲子构成了一个不断轮回,无法逃离世界末日的忧郁世界观.\n尽管整个世界观很朴素很简单,但就是那么的打动人心,让听众为之动容.\n世末系列专辑曲大致从16年就开始着手,但十年了仍然没有做出来,参考萌娘百科 设定 碎梦(26/4/5)-10.0 晚上在临港的酒店点开B站时,突然看见条子发歌了,那一刻的惊喜真的是无法用言语表达的. 这首歌我最初听到是22年的时候,之后就一直在等完整版,而条子果然不负众望,成功交出了一份完美的答案. 飞向总有容身处的世界,却碎裂,却碎裂~\n生贺曲\n为了你唱下去-10.0 前奏非常抓耳,高潮部分也很容易让人引起共鸣\n不停歇的旅途-7.5 尽管是第二次生贺曲,记忆点太少了,很难让人记住有什么动人的地方\n纯蓝-9.5 光与影的对白-9.5 直到最后一天-10.0 条的厨力大爆发!转调很绝.\n我一直驻足等候-10.0 久违的抒情曲真的太美了\n人生来就形只影单-9.0 星葵 此间花火-9.0 末等残想-9.0 月巷-9.0 途闻-9.0 雨狸 妄想系列-9.0 部分曲子很好听,重点是世界观非常独特\n乌龟 八辈子-9.5 词和曲都很好\n纯白 唱给雅音宫羽-9.5 古早的神曲,心境平淡的时候去听是一种享受.\n系列曲-9.0 里面的\u0026laquo;暧昧philosopher\u0026raquo;和\u0026laquo;虚拟paradise\u0026raquo;很不错\nlitterzy 冠世一战-10.0 神乎其技!\n鸽稀拉 寻遍星空-10.0 尽管是13年的曲子,可即便在26年1月听到也觉得无比惊艳,完美的旋律编排,和清澈带点沙哑的稚嫩v3声源,再加上动人的歌词,无论什么时候听都能立刻放松下来.\nZ新豪 东京不太热-10.0 干物女-10.0 黑凤梨-9.5 万能诊听器-9.0 同心桥-8.5 空气凝Klean 时间旅行-9.5 阿良良木健 东风来-9.0 旋律很好听~\nJUSF周存 芹菜猪肉大馄饨 纳兰寻风 豆腐p 我将沐火而唱-9.5 将赤羽的声线发挥到了极致 次世代系列\n特点是大多数名字都很怪 闹闹 小熠 信弦 胧音 花水r 南京夜无电波讯号-9.0 清风疾行 小孩的歌-9.5 ilem10周年里最惊艳的歌了,也是因为这首歌才买了专辑.\n安逸笙笙 沉年旧诗 海格p 陌生人-9.5 意境上很美的\n星语夜枫 一碗热汤p 旅行的蜗牛 宝藏p主,没一首歌是不好听的.\n直到凌晨也无法安眠 papaw泡泡 繁华唱遍就是他编写的曲子~\nbiapia 仅此-9.0 我比较喜欢这种简约的风格\nWOVOP MOCKER44 TOPKINGCREAM 日V\nDeco*27 匹诺曹p doriko jin Harry P 宝藏p主,每首歌都很对我胃口\nkeeno ","date":"2026-04-02T08:00:00Z","image":"/p/%E5%A8%B1%E4%B9%90%E6%9D%82%E8%B0%88/51160511_p0-%E6%9D%B1%E6%96%B9%E8%89%B2%E6%8A%80%E5%B8%96.webp","permalink":"/p/%E5%A8%B1%E4%B9%90%E6%9D%82%E8%B0%88/","title":"娱乐杂谈"},{"content":"重构 改善既有代码的设计 ch1: Intro 如果你要给程序添加一个特性，但发现代码因缺乏良好的结构而不易于进行更改，那就先重构那个程序，使其比较容易添加该特性，然后再添加该特性。\n无论每次重构多么简单，养成重构后即运行测试的习惯非常重要。 犯错误是很容易的——至少我知道我是很容易犯错的。做完一次修改就运行测试，这样在我真的犯了错时，只需要考虑一个很小的改动范围，这使得查错与修复问题易如反掌。 这就是重构过程的精髓所在：小步修改，每次修改后就运行测试。如果我改动了太多东西，犯错时就可能陷入麻烦的调试，并为此耗费大把时间。小步修改，以及它带来的频繁反馈，正是防止混乱的关键。\n这章用了巨量的篇幅来修改一个几十行的js代码,从而说明了一个良好的早期架构是有多么的重要,一旦那些架构混乱的项目开始变得复杂,就算是神仙来了也未必能够轻易看懂并重构 关键点: 尽可能多的使用OOP,通过多态,继承,接口来实现代码复用和类型统一;通过将复杂表达式拆分为工具函数并择合适的名字来增强代码的可读性\nch2: 重构的原则 重构有两种词性,一种是动词,一种是名词:\n重构（名词）：对软件内部结构的一种调整，目的是在不改变软件可观察行为的前提下，提高其可理解性，降低其修改成本。 重构（动词）：使用一系列重构手法，在不改变软件可观察行为的前提下，调整其结构。 如果我看见一块凌乱的代码，但并不需要修改它，那么我就不需要重构它。如果丑陋的代码能被隐藏在一个 API 之下，我就可以容忍它继续保持丑陋。只有当我需要理解其工作原理时，对其进行重构才有价值。 另一种情况是，如果重写比重构还容易，就别重构了。这是个困难的决定。如果不花一点儿时间尝试，往往很难真实了解重构一块代码的难度。决定到底应该重构还是重写，需要良好的判断力与丰富的经验，我无法给出一条简单的建议。\n如果一支团队想要重构，那么每个团队成员都需要掌握重构技能，能在需要时开展重构，而不会干扰其他人的工作。这也是我鼓励持续集成的原因：有了 CI，每个成员的重构都能快速分享给其他同事，不会发生这边在调用一个接口那边却已把这个接口删掉的情况；如果一次重构会影响别人的工作，我们很快就会知道。自测试的代码也是持续集成的关键环节，所以这三大实践——自测试代码、持续集成、重构——彼此之间有着很强的协同效应。\nch3: 代码的坏味道 需要重构的特征有以下几个:\n难以捉摸的命名 重复的代码段 函数太长: 将值得用注释说明的部分拆分成函数 过长参数列表: 使用类来传入参数 全局数据: 用函数或者类来封装这个全局数据,尽量控制其作用域 可变数据: 如果一个数据有不同的用途,最好将它按照用途分成不同的类 模块边界不清晰 修改一次需要在多个地方更改 ch4: 构筑测试体系 确保所有测试都完全自动化，让它们检查自己的测试结果。\n频繁地运行测试。对于你正在处理的代码，与其对应的测试至少每隔几分钟就要运行一次，每天至少运行一次所有的测试。\n考虑可能出错的边界条件，把测试火力集中在那儿。\n每当你收到 bug 报告，请先写一个单元测试来暴露这个 bug。\nch6: 第一组 提炼函数(Extract Function) 用例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function printOwing(invoice) { printBanner(); let outstanding = calculateOutstanding(); //print details console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); } function printOwing(invoice) { printBanner(); let outstanding = calculateOutstanding(); printDetails(outstanding); function printDetails(outstanding) { console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); } } 对于“何时应该把代码放进独立的函数”这个问题，我曾经听过多种不同的意见。有的观点从代码的长度考虑，认为一个函数应该能在一屏中显示。有的观点从复用的角度考虑，认为只要被用过不止一次的代码，就应该单独放进一个函数；只用过一次的代码则保持内联（inline）的状态。但我认为最合理的观点是“将意图与实现分开”：如果你需要花时间浏览一段代码才能弄清它到底在干什么，那么就应该将其提炼到一个函数中，并根据它所做的事为其命名。以后再读到这段代码时，你一眼就能看到函数的用途，大多数时候根本不需要关心函数如何达成其用途（这是函数体内干的事）。\n如果想要提炼的代码非常简单，例如只是一个函数调用，只要新函数的名称能够以更好的方式昭示代码意图，我还是会提炼它；但如果想不出一个更有意义的名称，这就是一个信号，可能我不应该提炼这块代码。不过，我不一定非得马上想出最好的名字，有时在提炼的过程中好的名字才会出现。有时我会提炼一个函数，尝试使用它，然后发现不太合适，再把它内联回去，这完全没问题。只要在这个过程中学到了东西，我的时间就没有白费。\n“如果需要返回的变量不止一个，又该怎么办呢？” 有几种选择。最好的选择通常是：挑选另一块代码来提炼。我比较喜欢让每个函数都只返回一个值，所以我会安排多个函数，用以返回多个值。如果真的有必要提炼一个函数并返回多个值，可以构造并返回一个记录对象—不过通常更好的办法还是回过头来重新处理局部变量\n内联函数（Inline Function） 这里的内联指的是将不必要的中间层删除,从而让函数更加清晰\n1 2 3 4 5 6 7 8 9 10 11 12 function getRating(driver) { return moreThanFiveLateDeliveries(driver) ? 2 : 1; } function moreThanFiveLateDeliveries(driver) { return driver.numberOfLateDeliveries \u0026amp;gt; 5; } function getRating(driver) { return (driver.numberOfLateDeliveries \u0026amp;gt; 5) ? 2 : 1; } 这显然与前面说的提炼函数正好相反,从而说明重构并不是一个简单的活儿,你不好判断加入函数和删除函数这两种做法哪一种会让代码更清晰 break\u0026amp;总结 后面的部分都是一些具体用例了,大部分内容都需要真正去实践才能体会,所以就不建议去看了.\n提炼一下本书的精华:\n软件的初步架构需要是合理的,工程化的,否则后期的重构难度甚至超过推翻重写 重构一般是一次一小步进行的,如果你的重构会让项目暂时无法运行,说明你做的不是重构 重构的方法有以下几种: 提炼/删除 函数 用类来存放函数和变量 去除不必要的全局变量 改一个好的名字 重构与添加新功能可以是同时进行的 程序员自我修养 讲的很深,可惜的是逻辑比较混乱,如果能再版后重构一下就真的是神书了 GitHub下载链接 OUTLINE 简介 编译和链接 目标文件里有什么 静态链接 windows PE/COFF exe的装载与进程 动态链接 Linux的共享库 Windows中的动态链接 内存 运行库 系统调用与API 运行库的实现 可以很明显的看出来,这本书主要涉及的是C语言程序经过编译与链接后装载的过程.\n编译和链接 程序运行的过程 当我们使用GCC编译Hello World程序时,只需要这样写:\n1 2 gcc hello.c -o ./a.out # \u0026#39;./a.out\u0026#39;是文件名和路径,后缀名可以随便起,写成tho没有后缀或者a.xyz也可以 上述过程可以分解为4个步骤:\n预处理(Preprocessing) 编译(Compilation) 汇编(Assembly) 链接(Linking) 预处理 c文件和h文件会被预处理成.i文件,cpp文件和hpp文件会被预处理为.ii文件.\n对应的命令为gcc -E hello.c -o a.i 该阶段主要处理源代码中以\u0026quot;#\u0026ldquo;打头的预编译指令,如\u0026rsquo;#include\u0026rsquo;,\u0026rsquo;#define\u0026rsquo;等,主要运行过程如下: 将所有的\u0026rdquo;#define\u0026quot;删除,并展开所有的宏定义,比如,将含有\u0026quot;#define PI 3.14\u0026quot;的文件中的所有PI替换为3.14 处理所有的条件预编译指令,如\u0026quot;#if\u0026quot;,\u0026ldquo;endif\u0026quot;等 处理\u0026rdquo;#include\u0026quot;,将被包含的文件插入到文件中该预编译指令所在的行,该过程是递归执行的 删除所有的注释\u0026quot;//\u0026ldquo;和\u0026rdquo;/* */\u0026quot; 添加行号和文件名标识,如 \u0026quot; #2 \u0026ldquo;hello.c\u0026rdquo; 2 \u0026ldquo;,这就是我们在程序报错的时候看到的那些行号和文件名的来历,至于行尾的2,是一个给编译器看的标志位 保留所有的\u0026rdquo;#pragma\u0026quot;指令 因此,经过预处理后的.i文件不包含任何宏定义,包含的文件也被插入到.i文件中\n编译 对.i文件进行一系列词法分析,语法分析,语义分析和优化,生成相应的汇编代码文件.\n对应的命令为gcc -S hello.i -o hello.s 汇编 根据汇编代码构建目标文件\n对应的命令为gcc -c hello.s -o hello.o 或者一步完成: gcc -c hello.c -o hello.o 链接 1 ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -1gcc -1gcc_eh -1c -end-group crtend.o crtn.o 可以看到需要链接一堆文件才可以得到最终的可执行文件\n编译的详细原理 下面我们来以一段简单的c语言代码为例来分析编译的全过程:\n1 array[index] = (index+4)*(2+ 6) 词法分析 源代码被输入到扫描器(Scanner),产生一系列记号:关键字,标识符,数字,字符串和特殊符号(加号,等号)等.\n语法分析 语法分析器(Grammar Parser)对扫描器产生的记号进行语法分析,产生由表达式组成的语法树(Syntax Tree) 语义分析 语义分析器(Semantic Analyzer)对表达式进行静态的语义分析,标识各个表达式的类型;动态语义则只能在运行期确定. 中间代码的生成 现代编译器会对源代码进行优化,将整个语法树转换成中间代码,尽管非常接近目标代码,但它与运行的操作系统无关,不包含数据尺寸,变量地址和寄存器名字等信息. 根据中间代码可以把编译器分为前端和后端.前端负责产生于操作系统无关的中间代码,后端负责将中间代码转换成目标文件 Java 编译体系 (Bytecode)\n前端：javac。它将 .java 源码编译成与平台无关的 Java Bytecode (.class 文件)。这就是所谓的中间代码。\n后端：JVM (Java Virtual Machine) 中的 JIT 编译器 (如 C1、C2)。当程序运行时，JIT 将字节码转换为当前运行机器（Windows x64、Linux ARM 等）的具体指令集。\n目标代码的生成与优化 编译器后端主要包括代码生成器和目标代码优化器二者. 代码生成器将中间代码转换成目标机器代码,例如:\n1 2 3 4 5 6 7 8 t1 = y + z x = t1 -\u0026gt; mov rax, QWORD PTR [rbp-8] ; 将变量 y 加载到寄存器 rax add rax, QWORD PTR [rbp-16] ; 将变量 z 的值加到 rax 中 mov QWORD PTR [rbp-24], rax ; 将结果 rax 存回变量 x 的内存地址 然后由目标代码优化器对上述目标代码进行优化,比如选择合适的寻址方式,使用位移来代替乘法运算,删除冗余指令等\n总结 经过这么多步骤后,源代码被编译成了目标代码,但有一个问题,变量的存储地址还没有确定,而且如果这个变量是来自其他模块的话又该怎么办?这就是链接派上用场的地方了\n链接概览 链接有以下几个步骤:\n地址和空间分配(Address and Storage Allocation) 符号决议(Symbol Resolution) 重定位(Relocation) 补充: 编译全过程;编译器的前端和后端 由于书上对这些概念没有做一个很清晰的介绍,因此我再在这里做一点辨析方便后续的阅读: 流水线解释\n预处理: 转换宏定义,删除注释 编译(狭义): 将cpp源码翻译成汇编代码(人类可读) 汇编: 将汇编代码翻译成机器指令(二进制码) 根据机器指令,地址位置等信息构造目标文件 链接: 将目标文件与系统库,用户库关联起来,得到可执行文件 编译(广义): 由于大多数人对cpp的装载过程没有一个清晰的认识,故通常使用编译代指从.cpp到.exe的全过程,也就是说我们一般都用广义的编译概念,很少特指\u0026quot;真正的编译\u0026quot; 但是,我们所用的编译器如gcc,clang等都是广义上的编译器,也就是说不仅仅做的是编译,而是包揽了从.cpp到目标文件的全构建过程. 如果用前端和后端的概念来划分的话,是这样的:\n前端（Frontend） 范畴： 仅包含“编译”这一步的前半部分。\n输入： 预处理后的源码。 任务： 词法分析（Lexical Analysis）、语法分析（Syntax Analysis）、语义分析（Semantic Analysis）、生成中间表示（IR, Intermediate Representation）。 特性： 与具体的硬件架构（如 x86、ARM）无关，只与 C++ 语言本身的规则有关。 后端（Backend） 范畴： 包含“编译”这一步的后半部分，以及“汇编”的全部。\n任务： * 中端优化（Optimizer）：对 IR 进行架构无关的优化。 代码生成（Code Generator）：将 IR 转换为特定硬件的汇编代码。 汇编器（Assembler）：将汇编代码转换为机器指令，产出目标文件。 特性： 强依赖于硬件架构。 其他项 预处理（Preprocessing）：通常被视为编译前的“文本清洁工作”，不属于狭义编译器（Compiler Core）的前后端逻辑。 链接（Linking）：属于编译链的下游，是一个独立的二进制处理过程，不属于编译器（Compiler）的范畴。 目标文件: 汇编的产物 目标文件的格式 可执行文件的格式主要有Windows中的PE(Portable Executable,不是那个重装windows用的Preinstallation Environment)和Linux中的ELF(Executable Linkable Format),两者都是COFF(Common file format)的变种.从广义上看,可执行文件的格式与目标文件基本相同,故这里将它们看作一种类型的文件,在Windows中称为PE-COFF文件格式,在Linux中称为ELF文件格式.(也就是说我们这里把目标文件就看成是ELF文件)\n事实上,动态链接库(DLL,Dynamic Linking Library)(Windows.dll和Linux的.so)和静态链接库(Static Linking Library)(Windows的.lib和Linux的.a)的存储方式也是可执行文件. 更为标准的分类方法如下:\nELF 文件类型 说明 实例 可重定向文件\n(Relocatable File) 包含代码和数据，可被用来链接成可执行文件或共享目标文件，静态链接库也归为此类。 Linux 的 .o\nWindows 的 .obj 可执行文件\n(Executable File) 包含可以直接执行的程序。 Linux 的 /bin/bash\nWindows 的 .exe 共享目标文件\n(Shared Object File) 包含代码和数据。可由链接器与其他可重定向/共享目标文件链接产生新目标文件；或由动态链接器与可执行文件结合，作为进程映像的一部分运行。 Linux 的 .so\nWindows 的 DLL 核心转储文件\n(Core Dump File) 当进程意外终止时，系统将该进程的地址空间内容及终止时的其他信息转储到该文件。 Linux 下的 core dump ELF文件的内容 目标文件将不同类型的信息用段(section)的形式存储:\nFile Header: 描述了整个文件的文件属性,包括文件是否可执行,目标硬件,目标操作系统等信息;还包括一个段表(section table),描述目标文件中各段的属性 使用C语言的结构体来定义 .text section: 保存汇编得到的及其代码 .data section: 保存已经初始化的全局变量和局部静态变量 .bss section: 保存未初始化的全局变量和局部静态变量,由于它们都是0,故没有必要放入.data段中,同时,由于程序运行时需要记录这两类变量,因此需要用.bss段来额外预留位置.该段并没有内容,故在目标文件中也不占据空间. bss的来历 BSS(Block Started by Symbol)来源于1950年代IBM大型机的汇编器中的一个伪指令,后来被引入标准汇编器FAP中,用于定义符号并且为该符号预留给定数量的未初始化空间\n整体来说,源代码被编译后分成两段:程序指令(代码段),程序数据(数据段和.bss段).\n事实上,ELF文件的内部结构比上述所说的四段式结构要复杂的多: [ 0] (NULL)：索引为 0 的段物理上必须存在且全为空，用于标识无效引用。 [ 1] .text：代码段。存放程序经过编译后的物理机器指令。 [ 2] .rel.text：代码重定位段。记录 .text 段中哪些物理地址需要在链接时进行修正。 [ 3] .data：已初始化数据段。存放程序中已初始化的全局变量和局部静态变量。 [ 4] .bss：未初始化数据段。为未初始化的全局变量预留的物理占位符，在文件中不占实际磁盘空间。 [ 5] .rodata：只读数据段。存放常量（如字符串常量、const 修饰的变量）。 [ 6] .comment：注释段。物理记录编译器版本信息（如 \u0026ldquo;GCC: (GNU) \u0026hellip;\u0026quot;）。 [ 7] .note.GNU-stack：堆栈属性段。物理标识堆栈是否可执行，用于系统安全防御（NX 位）。 [ 8] .shstrtab：段表字符串表。存储所有段名（如 \u0026ldquo;.text\u0026rdquo;, \u0026ldquo;.data\u0026rdquo;）的物理字符串池。 [ 9] .symtab：符号表。记录程序中定义和引用的所有函数名、变量名及其物理偏移。 [10] .strtab：字符串表。存储符号表中所使用的所有名称字符串。 下面我们来一个个解析:\n文件头,段表,重定位表 文件头 拓展阅读 本书使用的示例c程序分析得到的文件头内容如下:\nMagic (魔数)：7f 45 4c 46 01 01 01 00 ... 物理意义：文件开头的 16 个字节，用于标识该文件是一个 ELF 格式的可执行或目标文件。 Class (类别)：ELF32 物理意义：该文件是为 32 位 架构设计的。 Data (数据存储方式)：2's complement, little endian 物理意义：采用二补码形式，且为 小端序（低位字节存储在低地址）。 Version (版本)：1 (current) 物理意义：当前 ELF 格式的版本号。 OS/ABI (操作系统/接口)：UNIX - System V 物理意义：该文件遵循的物理调用约定标准。 Type (文件类型)：REL (Relocatable file) 物理意义：这是一个可重定位文件（通常为 .o 文件），尚未经过链接。 Machine (硬件平台)：Intel 80386 物理意义：物理运行的目标指令集架构。 Entry point address (入口地址)：0x0 物理意义：由于是可重定位文件，尚未装载，因此物理入口地址为 0。 Start of program headers (程序头起点)：0 (bytes into file) 物理意义：目标文件中通常不包含程序头表（Program Header Table），该表仅在可执行文件中存在。 Start of section headers (段表起点)：280 (bytes into file) 物理意义：**段表（Section Header Table）**在文件内部的物理偏移地址。 Size of this header (ELF 头大小)：52 (bytes) 物理意义：ELF Header 本身物理占据的字节长度。 Size of section headers (单段描述符大小)：40 (bytes) 物理意义：段表中每个条目物理占据的空间。 Number of section headers (段的数量)：11 物理意义：该文件物理包含 11 个段（如 .text, .data, .bss 等）。 Section header string table index (段表字符串表索引)：8 物理意义：存储段名字符串的表在段表中的物理下标。 事实上,这些信息是以C语言的结构体存储的:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct { unsigned char e_ident[16]; // 物理魔数区（含架构、字节序信息） Elf32_Half e_type; // 物理文件类型 Elf32_Half e_machine; // 物理硬件平台 Elf32_Word e_version; // 物理版本 Elf32_Addr e_entry; // 物理程序入口地址 Elf32_Off e_phoff; // 程序头表物理偏移 Elf32_Off e_shoff; // 段表物理偏移 Elf32_Word e_flags; // 处理器标志位 Elf32_Half e_ehsize; // ELF 头物理大小 Elf32_Half e_phentsize; // 单个程序头描述符大小 Elf32_Half e_phnum; // 程序头描述符数量 Elf32_Half e_shentsize; // 单个段表描述符大小 Elf32_Half e_shnum; // 段表描述符数量 Elf32_Half e_shstrndx; // 段名字符串表所在段的索引 } Elf32_Ehdr; 在 ELF 文件格式定义中，为了屏蔽不同平台下 int 或 long 长度不一带来的物理对齐问题，官方定义了一套标准数据类型：\n自定义类型 描述 原始类型 长度（字节） Elf32_Addr 32 位版本程序地址 uint32_t 4 Elf32_Half 32 位版本无符号短整型 uint16_t 2 Elf32_Off 32 位版本偏移地址 uint32_t 4 Elf32_Sword 32 位版本有符号整型 int32_t 4 Elf32_Word 32 位版本无符号整型 uint32_t 4 Elf64_Addr 64 位版本程序地址 uint64_t 8 Elf64_Half 64 位版本无符号短整型 uint16_t 2 Elf64_Off 64 位版本偏移地址 uint64_t 8 Elf64_Sword 64 位版本有符号整型 int32_t 4 Elf64_Word 64 位版本无符号整型 uint32_t 4 带上示例来解释:\n成员 内容 物理/逻辑解释 e_ident Magic: 7f 45 4c 46 01 01 01 00\u0026hellip; ELF 魔数。包含文件机器字节长度、数据存储方式、版本、运行平台及 ABI 版本。 e_type Type: REL (Relocatable file) ELF 文件类型。标识是可重定位文件、可执行文件还是共享对象文件。 e_machine Machine: Intel 80386 CPU 平台属性。相关常量以 EM_ 开头（如 EM_386）。 e_version Version: 0x1 ELF 版本号。通常为常数 1。 e_entry Entry point address: 0x0 入口地址。规定程序开始执行的虚拟地址。可重定位文件（.o）通常为 0。 e_phoff Start of program headers: 0 程序头（Program Header）偏移。在链接视图中暂不关心，执行视图的核心。 e_shoff Start of section headers: 280 段表（Section Header）偏移。即段表在文件内的起始物理位置。 e_word Flags: 0x0 ELF 标志位。标识特定平台相关的属性，格式通常为 EF_machine_flag。 e_ehsize Size of this header: 52 (bytes) ELF 文件头本身的大小。在本例中物理占据 52 字节。 e_phentsize Size of program headers: 0 程序头描述符的大小。 e_phnum Number of program headers: 0 程序头描述符的数量。 e_shentsize Size of section headers: 40 (bytes) 段表描述符的大小。物理上等于 sizeof(Elf32_Shdr)。 e_shnum Number of section headers: 11 段的数量。物理记录了 ELF 文件中拥有的段表描述符总数。 e_shstrndx Section header string table index: 8 段表字符串表下标。存储段名字符串的表在段表中的物理索引位置。 魔数详解 e_ident成员的前四个字节7f 45 4c 46中,第一个字节对应的是ASCII中的DEL控制符,后三个字节刚好是ELF这三个字母的ASCII码,从而唯一标识了ELF文件,故被称为ELF文件的魔数.\n接下来的一个字节用来标识ELF的文件类,01表示是32位的,02表示是64位的;第六个字节是字节序,规定该文件是大端存储还是小端存储;第七个字节为该文件的主版本号,一般是1,因为ELF标准从1.2版后就没有更新过.后面的9个字节ELF标准没有定义,一般填0. 至于为什么要多出来这9个字节,主要是为了兼容老编译器的考量.\n自然,所有的可执行文件都有一个魔数用来标识自己,比如PE/COFF文件的最开始两个字节为4d,5a,即ASCII字符MZ.\n段表 段表用于保存ELF文件中各个section(段)的基本信息,比如段的名字,长度,存储位置,读写权限等属性.编译器,链接器和装载器都是依靠段表来定位和访问各个段的属性的,至于段表的位置则是由文件头中的e_shoff字段来定义的.\n尽管书上讲的很详细,但我想只需要大概知道段表的作用即可 重定位表 链接器在处理目标文件的时候,需要对目标文件中的某些部分进行重定位,即.text段和.data段中对绝对地址引用的位置.\n对于每个要重定位的.text段和.data段,都会有一个相应的重定位表.\n链接的接口: symbol 如果目标文件B用到了目标文件A中的函数foo,那么就称A定义(define)了foo,B引用(reference)了A中的函数foo.\n在链接中,函数和变量统称为符号(symbol),函数名和变量名则为符号名(symbol name).\n链接中很关键的一部分就是符号的管理,每一个目标文件都有一个相应的符号表,记录该目标文件中用到的所有符号,每个定义的符号有一个对应的符号值,对于变量和函数来说,符号值就是它们的地址.\n除了函数和变量之外，还存在其他几种不常用到的符号。我们将符号表中所有的符号进行分类，它们可能是下面这些类型中的一种：\n定义在本目标文件的全局符号，可以被其他目标文件引用。比如 SimpleSection.o 里面的 “func1”、“main” 和 “global_init_var”。 在本目标文件中引用的全局符号，却没有定义在本目标文件，这一般叫做外部符号（External Symbol），也就是我们前面所讲的符号引用。比如 SimpleSection.o 里面的 “printf”。 段名，这种符号往往由编译器产生，它的值就是该段的起始地址。比如 SimpleSection.o 里面的 “.text”、“.data” 等。 局部符号，这类符号只在编译单元内部可见。比如 SimpleSection.o 里面的 “static_var” 和 “static_var2”。调试器可以使用这些符号来分析程序或崩溃时的核心转储文件。这些局部符号对于链接过程没有作用，链接器往往也忽略它们。 行号信息，即目标文件指令与源代码中代码行的对应关系，它也是可选的。 符号修饰与函数签名 在cpp中我们可以通过命名空间(namespace)和函数重载定义多个同名函数,那么编译器和链接器就需要在链接过程中区分这两个函数.\n首先,我们可以使用函数签名(function signature)来区分不同的函数,它包含了函数的名字,参数类型,命名空间,所在的类及其他信息.这样可以保证每一个函数都有一个独特的函数签名.\n其次,我们可以根据函数签名在编译过程中修饰这些函数,例如:\n函数签名 修饰后名称（符号名） int func(int) _Z4funci float func(float) _Z4funcf int C::func(int) _ZN1C4funcEi int C::C2::func(int) _ZN1C2C24funcEi int N::func(int) _ZN1N4funcEi int N::C::func(int) _ZN1N1C4funcEi 自然,cpp中的全局变量和静态变量也有同样的签名和修饰机制,而C语言由于不存在重名机制,故不需要对涉及的符号进行任何修改.\n强弱符号 在C/CPP中,函数和初始化的全局变量为强符号(strong symbol),未初始化的全局变量为弱符号(weak symbol).\n编译器按照以下三个规则来处理强弱符号:\n不允许强符号被多次定义,否则链接器会报出符号重复定义的错误 如果一个符号在某个目标文件中是强符号,在其他目标文件中是弱符号,则将它看作为强符号 如果一个符号在所有目标文件中都是弱符号,那么选择占用空间最大的一个作为它的定义,如int和double中选择double. 强弱引用 当目标文件中引用外部符号时,如果在链接时,没有找到该符号的定义,那么就会报出未定义错误,这类引用被称为强引用(strong reference).\n但是还有一类特殊的引用即使在链接时没找到该符号的定义也不报错,被称为弱引用(weak reference).这允许了冗余代码和缺失功能模块的设计.\n调试信息 目标文件里还会保存调试信息,如果我们用GCC编译时加上 “-g” 参数，编译器就会在产生的目标文件里面加上调试信息，我们通过 readelf 等工具可以看到，目标文件里多了很多 “debug” 相关的段：\n[Nr] Name Type Addr Off Size ES Flg Lk Inf Al \u0026hellip; [ 4] .debug_abbrev PROGBITS 00000000 000040 000034 00 0 0 1 [ 5] .debug_info PROGBITS 00000000 000074 0000af 00 0 0 1 [ 6] .rel.debug_info REL 00000000 000738 000038 08 9 5 4 [ 7] .debug_line PROGBITS 00000000 000123 000037 00 0 0 1 [ 8] .rel.debug_line REL 00000000 000770 000008 08 19 7 4 [ 9] .debug_frame PROGBITS 00000000 00015c 000034 00 0 0 4 [10] .rel.debug_frame REL 00000000 000778 000010 08 19 9 4 [11] .debug_loc PROGBITS 00000000 000190 00002c 00 0 0 1 调试信息在目标文件和可执行文件中占用了很大的空间,往往比程序的代码和数据本身大好几倍,因此,发布程序时,我们需要去除这些对于用户没有用的调试信息,从而节省大量的空间 例如可以在VS中选用Release模式而非Debug模式,从而将大部分调试信息排除在打包的可执行文件以外. 静态链接(4/15) 当我们运行gcc -c a.c b.c后,得到两个目标文件a.o和b.o,如何将他们链接起来,形成一个可执行文件?这个过程中发生了什么?\n链接方法 对于多个目标文件,链接器有以下两种方法,将他们的各个段(section)合并到一个可执行文件中:\n按序叠加 相似段合并 按序叠加 这种方法将输入的目标文件按照次序叠加起来: 显然,这种做法非常浪费空间,会堆积一大堆冗余数据\n相似段合并 将相同性质的段合并到一起是一个更为实际的方法,也是主流的链接器空间分配策略: 这种链接方式分为两步:\n空间和地址分配: 扫描所有的目标文件,将符号表中的所有信息统一放到一个全局符号表 符号解析和重定位: 根据上一步的信息,进行符号解析和重定位,调整代码中的地址 空间与地址分配 通过 objdump -h 命令观察目标文件 a.o、b.o 以及链接后的可执行文件 ab 的段分配情况：\na.o (目标文件) Idx Name Size VMA LMA File off Algn Flags 0 .text 00000034 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 00000000 00000000 00000068 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 00000068 2**2 ALLOC b.o (目标文件) Idx Name Size VMA LMA File off Algn Flags 0 .text 0000003e 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000004 00000000 00000000 00000074 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 00000078 2**2 ALLOC ab (链接后的可执行文件) Idx Name Size VMA LMA File off Algn Flags 0 .text 00000072 08048094 08048094 00000094 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000004 08049108 08049108 00000108 2**2 CONTENTS, ALLOC, LOAD, DATA VMA (Virtual Memory Address)：链接前目标文件的 VMA 均为 0，因为尚未物理分配虚拟地址；链接后 ab 中的段被物理分配到了以 0804xxxx 开头的地址空间。 LMA(Load Memory Address): 装载地址,正常情况下该值与VMA相同,但在部分嵌入式系统中,两个值是不同的. Size 合并：ab 的 .text 段大小（0x72）约等于 a.o（0x34）与 b.o（0x3e）之和，体现了链接器的段合并策略。 Flags 变化：链接后的 ab 移除了 RELOC 标志，说明物理重定位已完成。 可执行文件里没有物理地址,在执行时才由操作系统将虚拟地址映射到物理地址\n符号解析与重定位(4/22) 重定位: 修正编译器产生的符号(函数与变量)地址 符号解析: 根据全局符号表查找所需符号的地址,用于重定位 C++相关的链接问题(待补充) Windows PE/COFF 之所以叫PE/COFF,是因为windows32位的可执行文件格式PE与ELF一样,都是从古老的COFF格式发展来的.换句话说,PE(Portable Executable)是COFF的一种扩展,结构上大致相同,与ELF格式也基本相同,都采用了段的格式\nwindows64位中,对原本的PE格式做了一点修改,叫做PE32+,它没有增加新的字段,只是把原来的32位字段变成了64位,因此我们也可以把它看作是一般的PE文件. 总的来说的话,在Windows中目标文件为COFF格式,为.obj后缀;可执行文件/动态链接库为PE格式,为.exe/dll后缀\nCOFF文件结构 COFF 目标文件格式 (COFF Object File Format) 的常见结构如下：\nImage Header (IMAGE_FILE_HEADER) Section Table (IMAGE_SECTION_HEADER[]) .text（代码节） .data（数据节） .drectve（指示节） .debug$S（调试符号节） other sections（其他节） Symbol Table（符号表） 与ELF文件格式确实很像 前两个部分是COFF文件的文件头,分别是描述文件总体结构和属性的映像头和描述各段属性的段表\n映像(image): PE文件在装载时会被直接映射到进程的虚拟空间中,是进程虚拟空间的映像. PE文件结构 PE 文件格式 (PE File Format) 的常见结构如下：\nImage DOS Header (IMAGE_DOS_HEADER) Image DOS Stub PE File Header (IMAGE_NT_HEADERS) Image Header (IMAGE_FILE_HEADER) Image Optional Header (IMAGE_OPTIONAL_HEADER32) Section Table (IMAGE_SECTION_HEADER[]) .text .data .drectve .debug$S other sections Symbol Table 第一段和第二段中DOS部分的来历:\n在Windows发展的早期,古老的DOS系统还十分盛行,而此时的Windows甚至不能脱离DOS环境独立运行,所以为Windows1编写的程序必须加入这两个DOS段来兼容DOS系统. 为了兼容古老的程序,直到现在的Windows可执行文件都包含了这两个段\nImage DOS Header详解 “IMAGE_DOS_HEADER”结构也被定义在 WinNT.h 里面，该结构的大多数成员我们都不关心，唯一值得关心的是“e_lfanew”成员，这个成员表明了 PE 文件头（IMAGE_NT_HEADERS）在 PE 文件中的偏移，我们需要使用这个值来定位 PE 文件头。\n这个成员在 DOS 的“MZ”文件格式中的值永远为 0，所以当 Windows 开始执行一个后缀名为“.exe”的文件时，它会判断“e_lfanew”成员是否为 0。如果为 0，则该“.exe”文件是一个 DOS“MZ”可执行文件，Windows 会启动 DOS 子系统来执行它；如果不为 0，那么它就是一个 Windows 的 PE 可执行文件，“e_lfanew”的值表示“IMAGE_NT_HEADERS”在文件中的偏移。\nImage DOS Stub详解 当 PE 可执行映像在 DOS 下被加载的时候，DOS 系统检测该文件，发现最开始两个字节是“MZ”，于是认为它是一个“MZ”可执行文件。然后 DOS 系统就将 PE 文件当作正常的“MZ”文件开始执行。\nDOS 系统会读取 “e_cs” 和 “e_ip” 这两个成员的值，以跳转到程序的入口地址。然而 PE 文件中，“e_cs”和“e_ip”这两个成员并不指向程序真正的入口地址，而是指向文件中的 “DOS Stub”。\n“DOS Stub” 是一段可以在 DOS 下运行的一小段代码，这段代码的唯一作用是向终端输出一行字：\n“This program cannot be run in DOS”\n然后退出程序，表示该程序不能在 DOS 下运行。所以我们如果在 DOS 系统下运行 Windows 的程序就可以看到上面这句话，这是因为 PE 文件结构兼容 DOS “MZ” 可执行文件结构的缘故。\n可执行文件的装载 可执行文件只有装载到内存以后才能被 CPU 执行。早期的程序装载十分简陋，装载的基本过程就是把程序从外部存储器中读取到内存中的某个位置。随着硬件 MMU 的诞生，多进程、多用户、虚拟存储的操作系统出现以后，可执行文件的装载过程变得非常复杂。\n进程虚拟地址空间 Transformers快速入门 标题很具有迷惑性,事实上,这篇文章的前面几章深入浅出的讲述了大语言模型(LLM)的前世今生,让人受益匪浅\n自然语言处理 这一章的介绍相当精彩,通俗易懂 要让计算机处理自然语言，首先需要为自然语言建立数学模型，这种模型被称为语言模型（Language Model，LM）,其核心思想是判断一个文字序列是否构成人类能理解并且有意义的句子，即建模文本序列的生成概率。\n统计语言模型 可以看到,这里的数学原理是非常简单的,并不怎么难懂 即使是使用三元、四元甚至是更高阶的语言模型，依然无法覆盖所有的语言现象。在自然语言中，上下文之间的关联性可能跨度非常大，例如从一个段落跨到另一个段落，这是马尔可夫假设解决不了的。此时就需要使用 LSTM、Transformer 等模型来捕获词语之间的远距离依赖（Long Distance Dependency）了。\n神经语言模型 NNLM模型 具体来说，NNLM 模型首先从词表C中查询得到前面N个词语对应的词向量,然后将这些词向量拼接后输入到带有激活函数的隐藏层中，通过Softmax函数预测当前词语的概率,它不仅能够能够根据上文预测当前词语，同时还能够给出所有词语的词向量\nWord2Vec模型 Word2Vec 的模型结构和 NNLM 基本一致，只是训练方法有所不同，分为 CBOW (Continuous Bag-of-Words) 和 Skip-gram 两种: 可以看到，与严格按照统计语言模型结构设计的 NNLM 模型不同，Word2Vec 模型在结构设计上更加自由，训练目标也更多是为获得词向量服务。特别是 CBOW 训练方法同时通过上文和下文来预测当前词语，打破了语言模型“只通过上文来预测当前词”的固定思维，为后续一系列神经网络语言模型的发展奠定了基础\n预训练语言模型 然而，有一片乌云始终笼罩在 Word2Vec 模型的上空——多义词问题。一词多义是语言灵活性和高效性的体现，但是 Word2Vec 模型却无法处理多义词，一个词语无论表达何种语义，Word2Vec 模型都只能提供相同的词向量，即将多义词编码到完全相同的参数空间。\n实际上在 20 世纪 90 年代初，雅让斯基（Yarowsky）就给出了一个简洁有效的解决方案——运用词语之间的互信息（Mutual Information） 具体来说，对于多义词，可以使用文本中与其同时出现的互信息最大的词语集合来表示不同的语义。例如对于“苹果”，当表示水果时，周围出现的一般就是“超市”、“香蕉”等词语；而表示“苹果公司”时，周围出现的一般就是“手机”、“平板”等词语\nELMo 模型 为了更好地解决多义词问题，2018 年研究者提出了 ELMo 模型（Embeddings from Language Models）。与 Word2Vec 模型只能提供静态词向量不同，ELMo 模型会根据上下文动态地调整词语的词向量。\n但是 ELMo 模型存在两个缺陷：首先它使用 LSTM 模型作为编码器，而不是当时已经提出的编码能力更强的 Transformer 模型；其次 ELMo 模型直接通过拼接来融合双向抽取特征的做法也略显粗糙\nGPT 模型 不久之后，OpenAI 将 ELMo 模型中的 LSTM 更换为 Transformer 提出了 GPT 模型（Generative Pre-trained Transformer）。并且 GPT 模型继续追随 NNLM 的脚步，采用仅有解码器的 Transformer 架构，只通过词语的上文进行预测。\n虽然解码器架构适合于完成自然语言生成任务（如文本摘要），但是在一定程度上也限制了模型的应用场景，例如对于文本分类、阅读理解等任务，如果不把词语的下文信息也嵌入到词向量中就会白白丢掉很多信息。\nBERT 模型 2018 年底，Google 基于 Transformer 模型进一步提出了 BERT 模型（Bidirectional Encoder Representations from Transformers），这一阶段神经网络语言模型的发展终于出现了一位集大成者，BERT 模型在发布时在 11 个任务上都取得了最好性能.\nBERT 模型采用和 GPT 模型类似的两阶段框架，首先对语言模型进行预训练，然后通过微调来完成下游任务。但是，BERT 不仅像 GPT 模型一样采用 Transformer 作为编码器，而且采用了类似 ELMo 模型的双向语言模型结构，如图 1-11 所示。因此 BERT 模型不仅编码能力强大，而且对各种下游任务，BERT 模型都可以通过简单地改造输出部分来完成。\n大语言模型 除了优化模型结构，研究者发现扩大模型规模也可以提高性能。在保持模型结构以及预训练任务基本不变的情况下，仅仅通过扩大模型规模就可以显著增强模型能力，尤其当规模达到一定程度时，模型甚至展现出了能够解决未见过复杂问题的涌现（Emergent Abilities）能力。例如 175B 规模的 GPT-3 模型只需要在输入中给出几个示例，就能通过上下文学习（In-context Learning）完成各种小样本（Few-Shot）任务，而这是 1.5B 规模的 GPT-2 模型无法做到的。为了区分这两代模型之间的差异，业界将大型预训练语言模型命名为“大语言模型”（Large Language Model，LLM）。\n在规模扩展定律（Scaling Laws）被证明对语言模型有效之后，研究者基于 Transformer 结构不断加深模型深度，构建出了许多大语言模型 可以说，大语言模型的出现改变了自然语言处理的范式，从为特定 NLP 任务构建专用模型转变为使用单一的大型模型，通过提示或微调来处理各种语言任务，这使得复杂的语言处理更容易实现。相比早期的语言模型主要面向自然语言的建模与生成，最新的语言模型则侧重于复杂任务的求解。从语言建模到任务求解，这是人工智能科学思维的一次重要跃升。\n(26/4/15): 尽管直到现在都没人彻底搞明白为什么扩大模型规模就能实现性能的跃升,但还是有很多人都信誓旦旦的认为AI将会取代一切. Reddit讨论 Transformer模型 正如第一章所述，自从 BERT 和 GPT 模型取得重大成功之后， Transformer 模型已经替代循环神经网络（RNN）、卷积神经网络（CNN）等传统神经网络结构，成为各种 NLP 模型的标配。\n换句话说,出于工程应用的角度,不太有必要去学传统神经网络架构了 Transformer的架构 标准 Transformer 模型主要由**编码器（Encoder）和解码器（Decoder）**两个模块组成 其中编码器负责接收输入并构建输入的语义表示（语义特征），从而理解输入内容，而解码器则利用编码器输出的语义表示（语义特征）以及前序输出来生成目标序列。 Transformer模型分类 纯编码器模型（Encoder-Only） 只包含编码器部分，采用双向语言建模，从两个方向理解上下文。适合需要深度理解文本的任务，例如文本分类、命名实体识别等，典型代表如 BERT。\n纯解码器模型（Decoder-Only） 只包含解码器部分，从左到右处理文本。尤其擅长文本生成任务，可以根据提示完成句子、撰写文章，甚至生成代码，典型代表如 GPT、Llama。\n大多数大语言模型（LLM）都采用纯解码器架构，这些模型在过去的几年中规模和功能都得到了显著提升，一些最大的模型包含数千亿个参数。 编码器-解码器模型（Encoder-Decoder） 结合了编码器和解码器，使用编码器理解输入，解码器生成输出。擅长序列到序列任务，例如翻译、摘要、问答等，典型代表如 T5、BART。\n大语言模型的工作原理 推理是指大语言模型利用训练中积累的知识，根据给定的输入提示逐字逐句地生成生成类似人类语言文本的过程。具体来说，大语言模型会按照顺序生成的方式，利用从数十亿个参数中学习到的概率来预测和生成序列中的下一个词元（Token），从而生成连贯且与上下文相关的文本。\n注意力的作用 注意力机制赋予大语言模型理解上下文并生成连贯响应的能力，在预测下一个词时，句子中的每个词并非都具有相同的权重。 例如，在句子“法国的首都是……”中，“法国”和“首都”这两个词对于确定下一个词是“巴黎”至关重要.\n这种识别最相关词以预测下一个词元的方法已被证明非常有效。简而言之，注意力机制是语言模型能够生成既连贯又具有上下文感知能力的文本的关键，它也是使现代语言模型区别于前几代语言模型的关键\n想要了解大语言模型实际能够处理多少上下文信息，就需要引出上下文长度，或者说模型的“注意力跨度”。上下文长度是指大语言模型一次可以处理的最大词元数量，这会受到模型的架构和尺寸、可用计算资源以及输入和期望输出的复杂性等多个因素的限制。\n当我们向大语言模型传递信息时，会以某种方式组织输入以引导模型生成所需的输出，这被称为提示词工程（Prompting）。由于模型的主要任务就是通过注意力机制分析每个输入词元的重要性来预测下一个词元，因此输入序列的措辞至关重要。相比口语化的简单任务描述，精心设计的提示（Prompt）可以更容易地引导大语言模型生成符合预期的输出。\n两阶段推理过程 大语言模型生成文本的过程主要分为两个阶段：预填充（Prefill）和解码.\n预填充阶段就像烹饪中的准备阶段，所有初始食材都在此阶段进行加工和准备，该阶段包含三个关键步骤：\n分词（Tokenization）：将输入文本转换为模型可以理解的基本语言单元——词元(token)。 嵌入转换（Embedding Conversion）：将词元转换为能够表示其语义的密集嵌入表示。 初始处理：将这些嵌入向量输入模型的神经网络，以深入了解上下文。 这个阶段计算量很大，因为模型需要一次性处理完所有输入的词元，就像人类在回复消息之前，先需要阅读并理解消息中的所有文字。\n预填充阶段处理完输入后，就进入实际生成文本的解码阶段。在这个阶段，模型会逐个生成词元以构建完整的输出，称之为自回归过程（每个新词元都依赖于所有先前的词元）。这一阶段包含了针对每个新词元执行的多个关键步骤：\n注意力计算：回顾所有先前的词元以理解上下文； 概率计算：确定下一个可能出现的词元的概率； 词元选择：根据这些概率选择下一个词元； 持续性检查：决定是否继续或停止生成。 此阶段会占用大量内存，因为模型需要跟踪所有先前已经生成的词元以及它们之间的关系。\n采样策略 在模型生成过程中，就像作家可以选择更具创意还是更精确一样，我们也可以调整模型选择词元的方式。当模型生成下一个词元时，它首先会得到词汇表中每个词的原始概率（称为logits），然后基于这些概率来选择下一个词元，这个过程包含以下几个步骤:\n原始概率：可以将其视为模型对每个可能的下一个词的初始直觉。 温度控制（Temperature Control）：就像控制创造力的旋钮，设置较高的值（\u0026gt;1.0）会使选择更随机、更具创造性，而较低的值（\u0026lt;1.0）则会使选择更集中、更具确定性。 Top-p 采样（核采样）：不考虑所有可能的词语，而是只关注那些概率总和达到选定阈值的最可能词语（例如前 90%）。 Top-k 过滤：一种替代方法，只考虑最有可能的 k 个下一个词。 此外，大语言模型面临的一个常见挑战是重复性问题，即生成重复的内容。为了解决这个问题，通常可以采用两种惩罚机制:\n出现惩罚（Presence Penalty）：对任何已出现过的词元，无论其出现频率如何都施加固定惩罚，从而防止模型重复使用相同的词； 频度惩罚（Frequency Penalty）：根据词元使用频率递增的惩罚机制，一个词出现得越多，再次被选中的可能性就越小。 这些惩罚项会在词元选择的早期阶段就被应用，从而在其他采样策略实施之前就调整原始概率。可以被视为一种温和的引导，鼓励模型探索新的词汇。\n最后，考虑到局部最优解未必是全局最优解，如果每次只是简单地选择当前最合适的词元，未必能获得全局质量最好的生成结果，因此还可以使用束搜索（Beam search），同时生成多个词元序列，最后选择总体概率最高的作为最终输出:\n在每个步骤中，维护多个候选序列（通常为 5-10 个）。 对于每个候选词，计算其成为下一个词元的概率； 只保留最可能的序列和后续词元组合； 重复此过程，直至达到所需长度或停止； 选择总体概率最高的序列作为输出。 束搜索通常能生成更连贯、语法更正确的文本，但需要更多的计算资源。\n但它其实也是局部最优解,要想输出更合理的答案就可以根据之前所说的调整温度和引入惩罚机制 实际挑战与优化 在实际部署大模型时，通常需要考虑以下几个关键指标：\n首次响应时间（Time to First Token，TTFT）：获得首次响应的时间，这主要受预填充阶段的影响，对于用户体验非常重要。 输出每词元所需时间（Time Per Output Token，TPOT）：用户衡量生成后续词元的速度，这决定了整体生成速度。 吞吐量（Throughput）：可以同时处理的请求数量。 显存使用情况：GPU 显存的消耗量，这通常会成为实际应用中的主要瓶颈。 此外，有效管理上下文长度是大语言模型推理中最具挑战性的问题之一。虽然更长的上下文可以提供更多信息，但也会带来巨大的成本：内存使用量通常随上下文长度呈二次方增长，而处理速度则通常随上下文长度呈线性下降。例如像 Qwen2.5-1M 这样的新模型支持数百万个 token 的上下文窗口，但这也导致推理速度显著降低，因此关键在于找到适合实际场景的最佳平衡点。\n为了应对这些挑战，最有效的优化方法之一是 KV 缓存（Key-Value Caching），通过存储和重用中间计算结果来提高推理速度。这项优化可以减少重复计算，从而提升生成速度，使长上下文生成成为可能。虽然代价是会占用更多内存，但性能提升通常远远超过这一成本。\nTransformer详解 都是latex公式,就不摘抄了 由于太过专业,因此我自己找AI通俗化了一下: 注意力是什么 注意力机制（Attention Mechanism）的本质是资源的最优分配。它让模型学会从大量信息中，筛选出对当前任务最关键的少数核心信息。\n1. 核心逻辑：图书馆借书 要理解注意力，最经典的模型是 Query（查询）、Key（键）、Value（值）。你可以将其想象成在图书馆找书：\nQuery (Q)：你想找的东西。比如你脑子里的搜索词：“人工智能的历史”。 Key (K)：书架上每本书的标签/书名。模型会计算你的 Query 和每一个 Key 的匹配程度（相关性）。 Value (V)：书里的具体内容。 操作流程：\n你拿着 Q 去跟所有的 K 比对，发现《AI简史》匹配度 0.9，《高等数学》匹配度 0.1。 这个匹配度就是权重（Attention Weight）。 最后你带走的知识，就是根据权重加权后的结果：0.9 x 《AI简史》的内容 + 0.1 x 《高数》的内容。 2. 为什么需要它？（对比传统方法） 在注意力机制出现之前，机器处理信息像吞枣：\n传统模型（如 RNN）：像一个记性不太好的翻译官。读完一个长句后，他试图把所有信息压缩成一个固定长度的向量。结果就是：读到句尾，句头的信息就模糊了。 注意力模型：像一个带着荧光笔的读者。在处理每个词时，它会瞬间扫描全句，把相关的重点划出来。 3. 不同的注意力类型 自注意力（Self-Attention）：自己跟自己找关系。 例子：句子“它在马路上跑，因为它累了”。当处理第二个“它”时，注意力机制会高亮“跑”和“马路”，从而让模型明白这个“它”是指那个运动的物体。 交叉注意力（Cross-Attention）：在两个不同序列间找关系。 例子：翻译时。当准备输出英文单词 \u0026ldquo;Apple\u0026rdquo; 时，解码器会去中文原句里寻找权重最高的词——“苹果”。 深入理解注意力机制 Transformer 能够“一眼读完全文”且不丢失信息，主要靠的是位置编码和全连接的并行架构。\n1. 为什么它能“一眼读完”？ 传统的 RNN 像排队进场，信息必须一个接一个传递，前面的信息在传递过程中会像“传声筒游戏”一样逐渐失真。\nTransformer 像航拍全景。它利用矩阵运算，在计算的第一步就让序列中所有的词同时进入模型。\n物理机制：在自注意力层，每个词都会和全场所有词建立连接。从第 1 个词到第 1000 个词的距离，在矩阵里永远是 $1$。 无损传输：因为不存在“中间商”传递，信息是直接从 A 点点对点触达到 B 点的。 2. 既然是一眼读完，怎么知道谁先谁后？ 如果只是把词丢进去，模型会觉得“我吃鱼”和“鱼吃我”是一样的。为了解决这个问题，它引入了位置编码（Positional Encoding）。\n硬性叠加：在词向量进入模型之前，会加上一个代表位置的“指纹”。这个指纹是用余弦和正弦函数生成的独特数值序列。 坐标系化：这就好比给每个进场的词发了一个带编号的座位号。 “我”带上了一个“我是第1位”的属性； “鱼”带上了一个“我是第3位”的属性。 特征融合：模型在处理时，不仅能看到“鱼”的含义，还能感知到它携带的“第3位”这个特征。 3. 为什么信息不会丢失？ 信息丢失通常发生在“压缩”阶段。Transformer 采用以下手段锁定信息：\n全连接注意力（All-to-All）：每一个词在每一层都有机会重新审视全句。即使在第 10 层，它依然可以直接调用第 1 层输入的原始位置信息和语义信息。 残差连接（Residual Connection）：这是最关键的**“保底机制”**。 每一层加工完后，都会把“加工前的原始数据”直接加到“加工后的数据”上。 这相当于给信息铺设了多条高速公路。如果某一层加工坏了，原始信息可以直接跳过加工层往后传。 Transformer概览 Transformer 的核心逻辑是放弃了“排队处理数据”，改用全连接矩阵并行运算。它将语言处理变成了一个空间几何问题：通过计算词与词之间的距离和权重，捕捉语义。\n1. 输入层：数据数字化 计算机不认识文字，第一步是向量化（Embedding）。\n词嵌入（Embedding）：将每个词映射到一个高维空间的坐标（向量）。意思相近的词，坐标距离更近。 位置编码（Positional Encoding）：由于 Transformer 是同时读入所有词，它无法区分语序。我们必须给每个词的向量叠加上一个“位置指纹”（通常使用正弦/余弦函数生成），让模型知道谁在谁前面。 2. 核心机制：自注意力（Self-Attention） 这是 Transformer 的“灵魂”。它解决了**“联系”**的问题。\n计算逻辑：每个词都生成三个身份：Q（查询）、K（键）、V（值）。 物理过程： 每个词拿自己的 Q 去跟全场所有词的 K 做点积，算出匹配度。 匹配度经过 Softmax 变成权重（比如 0.8、0.1\u0026hellip;）。 根据权重去提取对应的 V（值）。 本质：它让每个词在处理时，都能根据上下文自动聚焦到相关的词上。比如在“他过马路”中，“他”会通过注意力强力连接到语境中的具体人物。 3. 编码器（Encoder）：特征提取 编码器由 $N$ 个相同的块堆叠而成。\n多头机制（Multi-Head）：并行运行多组自注意力，一组看语法，一组看逻辑，类似于多个人从不同角度审题。 残差连接（Residual）与归一化（Norm）：每一层加工完都会把原始输入加回来，防止深层网络信息丢失或梯度消失。 前馈网络（FFN）：对注意力提取的信息进行非线性转换，进一步强化特征。 4. 解码器（Decoder）：序列生成 解码器负责预测下一个词，它比编码器多了两样东西：\n掩码注意力（Masked Attention）：训练时把后面的词遮住，强制模型只能根据已有的上文预测未来。 交叉注意力（Cross-Attention）：解码器会去“盯着”编码器输出的特征矩阵。就像写作文时，一边写（解码），一边看题目要求（编码器的输出）。 5. 输出层：概率映射 线性层：将解码器的输出映射回词表大小。 Softmax：将数值转换为概率。概率最高的那个词，就是模型认为的“下一个词”。 总结：Transformer 为什么强大？ 并行性：不再像 RNN 那样一个字一个字读，大大缩短了训练时间。 长程依赖：因为是全连接，句子开头和结尾的词距离永远是 $1$，不会遗忘。 可扩展性：支持模型做大（Scaling Law），参数越多，学到的世界知识就越深邃。 现在的 LLM（如 GPT 系列）多采用 Decoder-Only 架构，即去掉了显式的编码器，让解码器自己处理输入并直接续写。\n编码器是什么 将编码器（Encoder）想象成一个**“深度阅读理解器”**。它的任务是将一串单词，通过层层理解，翻译成机器能懂的“思想地图”。\n1. 零件拆解：它由什么组成？ 如果把编码器比作一个加工车间，它主要有三个工位：\n特种雷达（自注意力机制）：每个词都在扫描全场。比如读到“苹果”这个词，雷达会看周围有没有“好吃”或者“乔布斯”。如果有“乔布斯”，它就把“苹果”理解为科技公司；如果有“好吃”，它就理解为水果。 多角度摄像头（多头机制）：不只用一个雷达，而是用 8 个或更多。有的看语法，有的看语气，有的看逻辑，最后汇总。 深加工机床（前馈网络）：雷达看清关系后，机床会对每个词的特征进行非线性强化，把零散的信号固定成深刻的记忆。 2. 运作流程：数据是怎么走的？ 打标签（位置编码）：Transformer 是一眼看完一整行字的，为了不让它分不清词序，进门前先给每个词贴个“我是第1个”、“我是第2个”的编号。 找关系（注意力计算）：每个词伸出无数条线连接其他词，根据关联程度分配权重。 加总与校准（残差与归一化）：算完之后，把原始信息和加工后的信息加在一起（怕算丢了），然后把数值拉回到一个标准范围，防止网络“走火入魔”。 3. 本质区别：为什么它更强？ RNN（老方法）：像一个学生背课文，读了后面忘前面，且必须一个字一个字读。 Encoder（新方法）：像摄影师拍全景。一眼望去，所有的词同时入画，所有的联系瞬间建立。 4. 结论 编码器的最终输出不是词，而是一组有“灵魂”的向量。这些向量已经不再是孤立的符号，而是吸收了整句话上下文精华的特征集合。\n解码器是什么 1. 结构本质：一个“定向生成器” 如果说编码器是理解全文，解码器（Decoder）则是根据理解，逐字产出。它在编码器的基础上多了一个关键组件：交叉注意力（Cross-Attention）。\n2. 核心工作机制 带掩码的自注意力（Masked Self-Attention）： 物理约束：生成时，模型不能“偷看”未来的词。 实现：通过掩码（Mask）屏蔽掉当前时刻之后的词，确保预测第 $n$ 个词时，只参考前 $n-1$ 个词。 交叉注意力（Cross-Attention）： 桥梁作用：这是解码器最核心的工位。它一边看着已经生成的词，一边盯着编码器传过来的“思想地图”（特征向量）。 逻辑：它在问编码器：“根据你刚才理解的原文，我现在写到这一步了，下一步最该接哪个信息？” 线性层与 Softmax： 将高维向量映射到词表大小的维度，通过概率分布选出得分最高的词。 3. 数据流向：从“过去”和“原文”中找答案 输入：输入的是已经生成出来的词（起始符或前文）。 自我对齐：通过 Masked Attention 整理已生成内容的逻辑。 吸取原文：通过 Cross-Attention 强行去对齐原文的重点。 预测输出：产出一个概率，选出一个词，然后把这个词再丢回输入端，循环往复。 4. 通俗比喻：像是在写“命题作文” 编码器（出题人）：把复杂的背景资料读完，提炼出一张写满考点的纸。 解码器（考生）： 他右手拿着已经写好的半篇作文（已生成的词）； 左手按着出题人的考点纸（编码器输出）； 他一边看左手确认别跑题，一边看右手确认逻辑连贯，最后写出下一个字。 新型模型架构 混合专家架构（Mixture-of-Experts，MoE）: 将Transformer模块中的特定前馈层替换成MoE层,即换成具有不同权重参数的独立网络(称为专家),对于每次输入,选取概率最高的k个专家进行激活,然后加权输出 状态空间模型（State Space Model，SSM）: 试图取代Transformer模型,性能依然有差距,代表模型有RetNet,Mamba等. Transformer库使用 Transformers 库将目前的 NLP 任务归纳为几下几类：\n文本分类：例如情感分析、句子对关系判断等； 对文本中的词语进行分类：例如词性标注 (POS)、命名实体识别 (NER) 等； 文本生成：例如填充预设的模板 (prompt)、预测文本中被遮掩掉 (masked) 的词语； 从文本中抽取答案：例如根据给定的问题从一段文本中抽取出对应的答案； 根据输入文本生成新的句子：例如文本翻译、自动摘要等。 Transformers 库最基础的对象就是 pipeline() 函数，它封装了预训练模型和对应的前处理和后处理环节。我们只需输入文本，就能得到预期的答案。目前常用的 pipelines 有：\nfeature-extraction：获得文本的向量化表示 fill-mask：填充被遮盖的词、片段 ner：命名实体识别 question-answering：自动问答 sentiment-analysis：情感分析 summarization：自动摘要 text-generation：文本生成 translation：机器翻译 zero-shot-classification：零训练样本分类 情感分析 断更原因 后面的论述和代码过于专业了,不是我现在能看得懂的,显然这并不是真正的快速入门.\n设计数据密集型应用 数据系统 数据系统有以下几个作用:\n存储数据，以便自己或其他应用程序之后能再次找到 （数据库，即 databases） 记住开销昂贵操作的结果，加快读取速度（缓存，即 caches） 允许用户按关键字搜索数据，或以各种方式对数据进行过滤（搜索索引，即 search indexes） 向其他进程发送消息，进行异步处理（流处理，即 stream processing） 定期处理累积的大批量数据（批处理，即 batch processing） 因此,常规的数据库,消息队列等信息处理系统都可以被归类为数据系统. 我们可以从三个维度来评价一个数据系统写的怎么样:\n可靠性: 出了故障仍然可以正常运行 可伸缩性: 能够应付系统的扩大和其他变化 可维护性: 架构清晰,职责分明,方便维护 可靠性 处理硬件故障 当想到系统失效的原因时，硬件故障（hardware faults） 总会第一个进入脑海。硬盘崩溃、内存出错、机房断电、有人拔错网线…… 任何与大型数据中心打过交道的人都会告诉你：一旦你拥有很多机器，这些事情总会发生！\n我们可以通过硬件冗余(redundancy of hardware)来解决这个问题,即提供后备组件来及时接替故障硬件,防止系统崩溃.\n软件故障 软件故障有以下几个例子:\n接受特定的错误输入，便导致所有应用服务器实例崩溃的 BUG。例如 2012 年 6 月 30 日的闰秒，由于 Linux 内核中的一个错误，许多应用同时挂掉了。 级联故障，一个组件中的小故障触发另一个组件中的故障，进而触发更多的故障 管理员的失误导致的故障 一项关于大型互联网服务的研究发现，运维配置错误是导致服务中断的首要原因，而硬件故障（服务器或网络）仅导致了 10-25% 的服务中断\n可伸缩性 系统今天能可靠运行，并不意味未来也能可靠运行。服务 降级（degradation） 的一个常见原因是负载增加，例如：系统负载已经从一万个并发用户增长到十万个并发用户，或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多\n负载: 以推特为例 以推特在 2012 年 11 月发布的数据为例,推特的两个主要业务是：\n发布推文 用户可以向其粉丝发布新消息（平均 4.6k 请求 / 秒，峰值超过 12k 请求 / 秒）。 主页时间线 用户可以查阅他们关注的人发布的推文（300k 请求 / 秒）。 大体上讲，这一对操作有两种实现方式。\n发布推文时，只需将新推文插入全局推文集合即可。当一个用户请求自己的主页时间线时，首先查找他关注的所有人，查询这些被关注用户发布的推文并按时间顺序合并。在如 图 1-2 所示的关系型数据库中，可以编写这样的查询： 1 2 3 4 5 SELECT tweets.*, users.* FROM tweets JOIN users ON tweets.sender_id = users.id JOIN follows ON follows.followee_id = users.id WHERE follows.follower_id = current_user 为每个用户的主页时间线维护一个缓存，就像每个用户的推文收件箱。当一个用户发布推文时，查找所有关注该用户的人，并将新的推文插入到每个主页时间线缓存中。因此读取主页时间线的请求开销很小，因为结果已经提前计算好了。 推特的第一个版本使用了方法 1，但系统很难跟上主页时间线查询的负载。所以公司转向了方法 2，方法 2 的效果更好，因为发推频率比查询主页时间线的频率几乎低了两个数量级，所以在这种情况下，最好在写入时做更多的工作，而在读取时做更少的工作。\n然而方法 2 的缺点是，发推现在需要大量的额外工作。平均来说，一条推文会发往约 75 个关注者，所以每秒 4.6k 的发推写入，变成了对主页时间线缓存每秒 345k 的写入。但这个平均值隐藏了用户粉丝数差异巨大这一现实，一些用户有超过 3000 万的粉丝，这意味着一条推文就可能会导致主页时间线缓存的 3000 万次写入！及时完成这种操作是一个巨大的挑战 —— 推特尝试在 5 秒内向粉丝发送推文。\n推特轶事的最终转折：现在已经稳健地实现了方法 2，推特逐步转向了两种方法的混合。大多数用户发的推文会写入其粉丝主页时间线缓存中。但是少数拥有海量粉丝的用户（即名流）会被排除在外。当用户读取主页时间线时，分别地获取出该用户所关注的每位名流的推文，再与用户的主页时间线缓存合并\n如何处理负载 适应某个级别负载的架构不太可能应付 10 倍于此的负载。如果你正在开发一个快速增长的服务，那么每次负载发生数量级的增长时，你可能都需要重新考虑架构 —— 或者更频繁。\n大规模的系统架构通常是应用特定的 —— 没有一招鲜吃遍天的通用可伸缩架构（不正式的叫法：万金油（magic scaling sauce） ）。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。\n举个例子，用于处理每秒十万个请求（每个大小为 1 kB）的系统与用于处理每分钟 3 个请求（每个大小为 2GB）的系统看上去会非常不一样，尽管两个系统有同样的数据吞吐量。\n可维护性 众所周知，软件的大部分开销并不在最初的开发阶段，而是在持续的维护阶段，包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债和添加新的功能。\n数据模型 多数应用使用层层叠加的数据模型构建。对于每层数据模型的关键问题是：它是如何用低一层数据模型来 表示 的？例如：\n作为一名应用开发人员，你观察现实世界（里面有人员、组织、货物、行为、资金流向、传感器等），并采用对象或数据结构，以及操控那些数据结构的 API 来进行建模。那些结构通常是特定于应用程序的。 当要存储那些数据结构时，你可以利用通用数据模型来表示它们，如 JSON 或 XML 文档、关系数据库中的表或图模型。 数据库软件的工程师选定如何以内存、磁盘或网络上的字节来表示 JSON / XML/ 关系 / 图数据。这类表示形式使数据有可能以各种方式来查询，搜索，操纵和处理。 在更低的层次上，硬件工程师已经想出了使用电流、光脉冲、磁场或者其他东西来表示字节的方法。 握一个数据模型需要花费很多精力（想想关系数据建模有多少本书）。即便只使用一个数据模型，不用操心其内部工作机制，构建软件也是非常困难的。然而，因为数据模型对上层软件的功能（能做什么，不能做什么）有着至深的影响，所以选择一个适合的数据模型是非常重要的。\n关系模型VS文档模型 关系模型曾是一个理论性的提议，当时很多人都怀疑是否能够有效实现它。然而到了 20 世纪 80 年代中期，关系数据库管理系统（RDBMSes）和 SQL 已成为大多数人们存储和查询某些常规结构的数据的首选工具。关系数据库已经持续称霸了大约 25~30 年 —— 这对计算机史来说是极其漫长的时间。\n关系数据库起源于商业数据处理，在 20 世纪 60 年代和 70 年代用大型计算机来执行。从今天的角度来看，那些用例显得很平常：典型的 事务处理（将销售或银行交易，航空公司预订，库存管理信息记录在库）和 批处理（客户发票，工资单，报告）。\nNoSQL 采用 NoSQL 数据库的背后有几个驱动因素，其中包括：\n需要比关系数据库更好的可伸缩性，包括非常大的数据集或非常高的写入吞吐量 相比商业数据库产品，免费和开源软件更受偏爱 关系模型不能很好地支持一些特殊的查询操作 常见的NoSQL数据库有以下几个:\nRedis: 基于内存的存储,核心逻辑是哈希表 MongoDB: 存储格式为JSON 文档和关系数据库的融合 随着时间的推移，关系数据库和文档数据库似乎变得越来越相似，这是一件好事：数据模型相互补充，如果一个数据库能够处理类似文档的数据，并能够对其执行关系查询，那么应用程序就可以使用最符合其需求的功能组合。\n(26/4/7): 我发现这本书我现在看太早了,很难有切实的收获,还是等几年再来探索吧 Docker 从入门到实践 比官方文档要简洁清晰的多\n入门部分 Docker简介 无论你的应用是用 Python、Java、Node.js 还是其他语言写的，无论它需要什么样的依赖库和环境，一旦被打包成 Docker 镜像，就可以用同样的方式在任何支持 Docker 的机器上运行.\n也就是说,通过将依赖和软件打包在一起,我们成功实现了无缝的跨环境运行 Docker不是虚拟机 传统虚拟机技术是虚拟出一套完整的硬件，在其上运行一个完整的操作系统，再在该系统上运行应用\n而 Docker 容器内的应用直接运行于宿主的内核，容器内没有自己的内核，也没有进行硬件虚拟\nDocker历史 Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目，于 2013 年 3 月以 Apache 2.0 授权协议开源\n很难想象这么优秀的技术竟然只有十年多一点的历史 Docker的核心优势 环境一致性\nDocker 镜像包含了应用运行所需的 一切：代码、运行时、系统工具、库、配置。这意味着:\n开发环境和生产环境完全一致 不会再有 “在我机器上能跑” 的问题 快速启动\n传统虚拟机启动需要几分钟 (引导操作系统)，而 Docker 容器启动通常只需要 几秒甚至几百毫秒\n当然这得要你先构建好了镜像和容器 Docker 的核心价值可以用一句话概括：让应用的开发、测试、部署保持一致，同时极大提高资源利用效率。 笔者认为，对于现代软件开发者来说，Docker 已经不是 “要不要学” 的问题，而是 必备技能。无论你是前端、后端、运维还是全栈开发者，掌握 Docker 都能让你的工作更高效。\n基本概念 Docker里有三个基本概念:\n镜像(Image): Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数 (如匿名卷、环境变量、用户等)。镜像不包含任何动态数据，其内容在构建之后也不会被改变。 容器 (Container)：镜像 (Image) 和容器 (Container) 的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。 仓库 (Repository)：镜像构建完成后，可以很容易的在当前宿主机上运行，但是，如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry 就是这样的服务。 镜像 Docker 镜像是一个只读的模板，包含了运行应用所需的一切：代码、运行时、库、环境变量和配置文件。 如果用一个类比：镜像就像是一张光盘或 ISO 文件。你可以用同一张光盘在不同电脑上安装系统，而光盘本身不会被修改。同样，一个镜像可以创建多个容器，而镜像本身保持不变。\n镜像的组成部分 类别 示例 程序文件 应用二进制文件、Python/Node 解释器 库文件 libc、OpenSSL、各种依赖库 配置文件 nginx.conf、my.cnf 等 环境变量 PATH、LANG 等预设值 元数据 启动命令、暴露端口、数据卷定义 镜像是只读的 镜像不包含动态数据 镜像构建后内容不会改变 镜像的分层存储 1 2 3 4 5 6 7 8 FROM ubuntu:24.04 # 第 1 层：基础系统（约 78MB） RUN apt-get update # 第 2 层：更新包索引 RUN apt-get install nginx # 第 3 层：安装 nginx COPY app.conf /etc/nginx/ # 第 4 层：复制配置文件 换句话说,只要某一行命令对镜像做了修改,就被docker视为单独的一个构建层,不可以被其他构建层修改,但可以与其他镜像共享\n镜像标识 镜像名称和标签\n1 2 3 4 5 6 7 8 9 10 11 12 13 ## 完整格式 registry.example.com/myproject/myapp:v1.2.3 ## 简写（使用 Docker Hub） nginx:1.25 ubuntu:24.04 ## 省略标签（默认使用 latest） nginx # 等同于 nginx:latest 镜像ID\n1 2 3 4 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest a6bd71f48f68 2 weeks ago 187MB ubuntu 24.04 ca2b0f26964c 3 weeks ago 78.1MB 镜像摘要 镜像摘要是基于镜像内容生成的哈希码\n1 2 3 $ docker images --digests REPOSITORY TAG DIGEST IMAGE ID nginx latest sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68 容器 容器是镜像的运行实例。如果把镜像比作程序，那么容器就是进程。 用面向对象编程的术语来说：镜像是类 (Class)，容器是对象 (Instance)。\n容器的本质 笔者认为，理解这一点是理解 Docker 的关键：容器的本质是一个特殊的进程 这种隔离是通过 Linux 内核的 Namespace 技术实现的。具体表现为：\n进程空间：容器看不到宿主机上的其他进程。 网络：容器拥有独立的 IP、端口等网络资源 文件系统：容器拥有独立的 root 目录。 用户：容器内的 root 用户不等于宿主机的 root 用户。 容器的存储层机制 当容器运行时，Docker 会在镜像的只读层之上创建一个可写层(容器存储层);\n而当容器需要修改镜像层中的文件时:\nDocker将该文件复制到容器存储层 在容器存储层中进行修改 原始镜像层保持不变 1 2 3 4 5 6 7 8 9 10 11 ## 创建容器，写入数据 $ docker run -it ubuntu bash root@abc123:/# echo \u0026#34;important data\u0026#34; \u0026gt; /data.txt root@abc123:/# exit ## 删除容器 $ docker rm abc123 ## 数据丢了！没有任何办法恢复！ 既然当容器被删除后数据就全部丢失,那么容器存储层就不应该保留任何重要的信息,而是只保留运行时数据.\n如果我们想要存储数据,可以使用数据卷(Volume)来存储数据库和应用数据,或者使用绑定到宿主机的目录. 1 2 3 4 5 6 7 8 ## 使用数据卷（推荐） $ docker run -v mydata:/var/lib/mysql mysql ## 使用绑定挂载 $ docker run -v /host/path:/container/path nginx # 这些位置的读写会跳过容器存储层，直接写入宿主机，性能更好，也不会随容器删除而丢失 容器的生命周期 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ## 创建并启动容器（最常用） $ docker run nginx ## 分步操作 $ docker create nginx # 创建容器（不启动） $ docker start abc123 # 启动容器 ## 停止容器 $ docker stop abc123 # 优雅停止（发送 SIGTERM，等待后发送 SIGKILL） $ docker kill abc123 # 强制停止（直接发送 SIGKILL） ## 暂停/恢复（不常用，但有时有用） $ docker pause abc123 # 暂停容器内所有进程 $ docker unpause abc123 # 恢复 ## 删除容器 $ docker rm abc123 # 删除已停止的容器 $ docker rm -f abc123 # 强制删除运行中的容器 仓库 Docker Registry 是存储和分发 Docker 镜像的服务，类似于代码的 GitHub 或包管理的 npm。\nDocker Registry 中可以包含多个 Repository，每个 Repository 可以包含多个 Tag:\n概念 说明 示例 Registry 存储镜像的服务 Docker Hub、ghcr.io Repository (仓库) 同一软件的镜像集合 nginx、mysql、mycompany/myapp Tag (标签) 仓库内的版本标识 latest、1.25、alpine 一个完整的 Docker 镜像名称由 Registry 地址、用户名/组织名、仓库名和标签组成。了解其结构有助于我们更准确地定位镜像。基本格式如下： [registry 地址/][用户名/]仓库名[:标签] 完整示例如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 registry.example.com/mycompany/myapp:v1.2.3 │ │ │ │ │ │ │ └── 标签 │ │ └── 仓库名 │ └── 用户名/组织名 └── Registry 地址 ## Docker Hub 官方镜像（省略 registry 和用户名） nginx:1.25 ubuntu:24.04 ## Docker Hub 用户镜像 jwilder/nginx-proxy:latest ## 其他 Registry ghcr.io/username/myapp:v1.0 gcr.io/google-containers/pause:3.10 公共 Registry Docker Hub 是最大的公共 Registry，也是 Docker 的默认 Registry,有以下特点:\n拥有大量官方镜像(nginx、mysql、redis) 免费账户可以创建公开仓库 付费账户支持私有仓库 除了 Docker Hub，还有以下几个常见的公共 Registry：\nRegistry 地址 说明 GitHub Container Registry ghcr.io GitHub 提供，与 GitHub Actions 集成好 Google Container Registry gcr.io Google Cloud 提供，Kubernetes 镜像常用 Quay.io quay.io Red Hat 提供 阿里云容器镜像服务 registry.cn-*.aliyuncs.com 国内访问快 腾讯云容器镜像服务 ccr.ccs.tencentyun.com 国内访问快 安装docker 如果是Windows端,开启WSL2后下载官方软件即可运行 如果是Linux端,尽管文章里给了一堆命令,但现在有Dokploy了.如果是部署在国外服务器上或者自己学习使用的话,使用Dokploy就没必要操心那么多了.\nDokploy 支持Dokploy的目前有以下服务器厂商:\nHostinger AmericanCloud Teramont Hetzner DigitalOcean Vultr Linode Scaleway Google Cloud AWS 而Dokploy目前可以在以下系统里部署: Ubuntu 24.04 LTS Ubuntu 23.10 Ubuntu 22.04 LTS Ubuntu 20.04 LTS Ubuntu 18.04 LTS Debian 12 Debian 11 Debian 10 Fedora 40 Centos 9 Centos 8 换句话说,主流的Linux操作系统现在都支持Dokploy了\nDokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as your free self hostable alternative to platforms like Heroku, Vercel, and Netlify, leveraging the robustness of Docker and the flexibility of Traefik.\nDokploy本身就是为了简化在Linux服务器部署Docker而产生的 Dokploy utilizes Docker, so it is essential to have Docker installed on your server. If Docker is not already installed, Dokploy\u0026rsquo;s installation script will install it automatically.\n甚至都不用提前安装docker,易用性可见一斑 部署 前置要求\nTo ensure a smooth experience with Dokploy, your server should have at least 2GB of RAM and 30GB of disk space. This specification helps to handle the resources consumed by Docker during builds and prevents system freezes.\n官方推荐使用Hetzner的服务器来省钱 避免以下端口被占用:\nPort 80: HTTP traffic (used by Traefik) Port 443: HTTPS traffic (used by Traefik) Port 3000: Dokploy web interface 我们只需要运行以下命令便可以在服务器的3000端口访问dokploy界面:\n1 curl -sSL https://dokploy.com/install.sh | sh 第一次进入dokploy界面时需要我们注册管理员账户,然后就可以在面板里部署自己的docker项目了,要进一步了解的话还是去看官方文档吧.\n使用镜像 获取镜像 docker pull用于从镜像仓库获取镜像:\n1 docker pull [选项] [Registry地址/]仓库名[:标签] 镜像名称的标准格式如下:\n1 2 3 4 5 docker.io / library / ubuntu : 24.04 ────┬──── ───┬─── ──┬─── ──┬── │ │ │ │ Registry地址 用户名 仓库名 标签 (可省略) (可省略) 组成部分 说明 默认值 Registry 地址 镜像仓库服务的域名或 IP 地址 docker.io (Docker Hub) 用户名 镜像所属的用户、组织或命名空间 library (官方镜像默认路径) 仓库名 镜像的具体名称 必须指定 标签 (Tag) 镜像的版本标识或分类标签 latest 示例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ## 完整格式 $ docker pull docker.io/library/ubuntu:24.04 ## 省略 Registry（默认 Docker Hub） $ docker pull library/ubuntu:24.04 ## 省略 library（官方镜像） $ docker pull ubuntu:24.04 ## 省略标签（默认 latest） $ docker pull ubuntu ## 拉取第三方镜像 $ docker pull bitnami/redis:latest ## 从其他 Registry 拉取 $ docker pull ghcr.io/username/myapp:v1.0 镜像是分层下载的,如果本地已经有相同的层(这可以通过ID来识别),那么就会跳过该层继续下载 docker pull常用参数 选项 说明 示例 \u0026ndash;all-tags, -a 下载仓库中该镜像的所有版本标签 docker pull -a ubuntu \u0026ndash;platform 在多架构镜像中指定运行平台（如 arm64, amd64） docker pull --platform linux/arm64 nginx \u0026ndash;quiet, -q 静默模式，只输出镜像 ID，不显示拉取进度详情 docker pull -q nginx 管理镜像 docker image ls 基本用法\n1 2 3 4 5 6 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE redis latest 5f515359c7f8 5 days ago 183MB nginx latest 05a60462f8ba 5 days ago 181MB ubuntu 24.04 329ed837d508 3 days ago 78MB ubuntu noble 329ed837d508 3 days ago 78MB 字段 说明 REPOSITORY 镜像仓库名称 TAG 镜像的标签（通常代表版本号） IMAGE ID 镜像的唯一标识符（取 SHA-256 哈希值的前 12 位） CREATED 镜像在构建服务器上被创建的时间 SIZE 镜像解压后在本地磁盘占用的实际空间 上面的 ubuntu:24.04 和 ubuntu:noble 拥有相同的 IMAGE ID——它们是同一个镜像的不同标签，只占用一份存储空间。 查找镜像 可以根据名字来找镜像:\n1 2 3 4 5 6 7 ## 列出所有 ubuntu 镜像 $ docker images ubuntu REPOSITORY TAG IMAGE ID SIZE ubuntu 24.04 329ed837d508 78MB ubuntu noble 329ed837d508 78MB ubuntu 22.04 a1b2c3d4e5f6 72MB 镜像删除 docker rmi/docker image rm 这两个命令等价,用于删除单个镜像: 使用ID删除\n1 2 3 4 5 6 7 8 9 10 $ docker image ls REPOSITORY TAG IMAGE ID SIZE redis alpine 501ad78535f0 30MB nginx latest e43d811ce2f4 142MB ## 只需输入足够区分的前几位 $ docker rmi 501 Untagged: redis:alpine Deleted: sha256:501ad78535f0... 使用镜像名删除\n1 2 3 $ docker rmi redis:alpine Untagged: redis:alpine Deleted: sha256:501ad78535f0... Untagged:移除镜像标签 Deleted: 删除镜像的存储层 docker image prune 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ## 查看虚悬镜像 $ docker images -f dangling=true $ docker image prune # 不带参数,默认只删除悬空镜像 ## 不提示确认 $ docker image prune -f ## 删除所有没有被容器使用的镜像 $ docker image prune -a ## 保留最近 24 小时的 $ docker image prune -a --filter \u0026#34;until=24h\u0026#34; 虚悬镜像 (dangling)：没有标签且未被容器引用的镜像，通常是旧版本被新版本覆盖后产生的 操作容器 启动容器 由于 Docker 容器非常轻量，实际使用中常常是随时删除和新建容器，而不是反复重启同一个容器。\n基本语法\n1 docker run [选项] 镜像 [命令] [参数...] 基本例子\n1 2 $ docker run ubuntu:24.04 /bin/echo \u0026#39;Hello world\u0026#39; Hello world 基础选项\n选项 说明 示例 -d 后台运行容器（detach） docker run -d nginx -it 分配交互式终端 docker run -it ubuntu bash --name 为容器指定自定义名称 docker run --name myapp nginx --rm 容器退出后自动删除 docker run --rm ubuntu echo hi 端口映射\n1 2 3 4 5 6 7 8 9 10 11 ## 将容器的 80 端口映射到宿主机的 8080 端口 $ docker run -d -p 8080:80 nginx ## 随机映射端口 $ docker run -d -P nginx ## 只绑定到 localhost $ docker run -d -p 127.0.0.1:8080:80 nginx 运行容器 当你在终端运行一个程序时，有两种模式：\n前台运行：程序占用当前终端，输出直接显示，关闭终端程序就停止 后台运行：程序在后台执行，不占用终端，终端关闭也不影响程序 Docker 容器默认是 前台运行 的。使用 -d (detach) 参数可以让容器在后台运行\n前台运行\n1 2 3 4 5 $ docker run ubuntu:24.04 /bin/sh -c \u0026#34;while true; do echo hello world; sleep 1; done\u0026#34; hello world hello world hello world hello world 容器会把输出的结果 (STDOUT) 打印到宿主机上面。此时：\n终端被占用，无法执行其他命令 按 Ctrl+C 会终止容器 关闭终端窗口，容器也会停止 后台运行\n1 2 $ docker run -d ubuntu:24.04 /bin/sh -c \u0026#34;while true; do echo hello world; sleep 1; done\u0026#34; 77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a 使用 -d 参数后：\n容器在后台运行 返回容器的完整 ID 终端立即释放，可以继续执行其他命令 输出不会直接显示 (需要用 docker logs 查看) 终止容器 终止容器有三种方式：\n方式 命令 说明 优雅停止 docker stop 先发 SIGTERM，超时后发 SIGKILL 强制停止 docker kill 直接发送 SIGKILL 信号 自动终止 - 容器主进程退出时自动停止 我们还可以在镜像被修改后重启容器\n1 2 3 4 5 6 7 ## 先停止再启动 $ docker restart 容器名 ## 自定义停止超时 $ docker restart -t 30 容器名 删除容器 随着容器的创建和停止，系统中会积累大量的容器。\n使用 docker rm 删除已停止的容器：\n1 $ docker rm 容器名或ID 该命令与docker container rm等效 使用 docker container prune 批量删除:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 ## 方式一：使用 prune 命令（推荐） $ docker container prune WARNING! This will remove all stopped containers. Are you sure you want to continue? [y/N] y Deleted Containers: abc123... def456... Total reclaimed space: 150MB ## 方式二：不提示确认 $ docker container prune -f 补充部分: 镜像的文件结构 非常离谱的是,这么详细的文档偏偏没有提到这一点: 镜像内部是怎么存放文件的? 自然,镜像是分层构建存储的,但是这些构建层显然要有个地方放吧.\n镜像的默认工作目录是根目录,类似于Linux的根目录,当我们需要切换存储目录或者启动某个目录下的脚本时,,可以显式指明,比如说以下的几个命令:\n1 2 3 4 5 6 7 ## 复制文件到指定目录 COPY package.json /app/ ## 复制文件并重命名 COPY config.json /app/settings.json 如此一来,我们成功的将本地文件复制到了app文件夹中. 因此,镜像不仅仅是一个iso,我们可以把它抽象成一个文件系统,存储层堆叠在不同的目录中,可以来回切换访问.\n进阶部分 dockerfile编写 概览 Dockerfile 是一个文本文件，其内包含了一条条的 指令 (Instruction)，每一条指令构建一层，因此每一条指令的内容，就是描述该层应当如何构建。\nDockerfile不是脚本，而是镜像的\u0026quot;设计图\u0026rdquo;。这个区别决定了你如何思考每条指令的作用:\n合并命令：应将 RUN apt-get update \u0026amp;\u0026amp; apt-get install -y ... 写入同一个 RUN 指令中,因为它们是同一层的逻辑 优化镜像大小：最后才清理缓存、删除临时文件，让这些\u0026quot;瘦身\u0026quot;操作在同一层完成 (补充)FROM: 基础镜像 很多时候我们都需要在官方镜像的基础上进行构建,这个时候我们可以这么写:\n1 2 FROM node:20-alpine AS deps # 在这个基础上进行构建 这个AS与python中的as一样,都是为导入的镜像重新设一个名字.\n事实上,如果你不写任何FROM,根本无法运行任何Linux命令 而FROM命令最厉害的地方在于,每一个FROM指令都会重新开辟一个新的文件系统,取代之前的所有内容. 因此,我们可以这么写:\n1 2 3 4 5 6 7 8 9 10 11 12 # 第一阶段：编译环境（命名为 builder） FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o myapp main.go # 物理产生了几百 MB 的编译器和缓存 # 第二阶段：运行环境（最小化镜像） FROM alpine:latest WORKDIR /root/ # 物理核心：通过 --from=builder 只从 builder 阶段拷贝最终的可执行二进制文件 COPY --from=builder /app/myapp . CMD [\u0026#34;./myapp\u0026#34;] 这样既可以利用上一个阶段的构建内容,又不会将多余的内容打包进镜像\nRUN: 执行命令 RUN 是 Dockerfile 中最常用的指令，主要用于在镜像构建阶段执行命令来修改镜像,有以下几个应用场景:\n安装依赖：RUN apt-get install nginx 编译程序：RUN gcc -o app main.c 下载文件：RUN curl -O https://example.com/file.tar.gz 配置系统：RUN mkdir -p /app/data 理解 RUN 的核心是理解镜像分层：每一个 RUN 都会在当前层之上创建新的一层，这会影响镜像大小。因此，合理使用 RUN（特别是合并多个 RUN）是构建轻量级镜像的关键。\n基本语法 有两种格式:\n1 2 RUN \u0026lt;command\u0026gt; RUN [\u0026#34;executable\u0026#34;, \u0026#34;param1\u0026#34;, \u0026#34;param2\u0026#34;] shell格式\n1 RUN apt-get update 默认通过 /bin/sh -c 执行。 可以使用环境变量、管道、重定向等 Shell 特性。 exec格式\n1 RUN [\u0026#34;apt-get\u0026#34;, \u0026#34;update\u0026#34;] 直接调用可执行文件，不经过 Shell。 无法使用 $VAR 环境变量替换 (除非显式调用 shell) 实战 由于每一个 RUN 指令都会新建一层镜像。为了减少镜像体积和层数，应使用 \u0026amp;\u0026amp; 连接命令:\n1 2 3 4 5 6 7 8 # 糟糕的写法(3层) RUN apt-get update RUN apt-get install -y nginx RUN rm -rf /var/lib/apt/lists/* # 推荐写法 RUN apt-get update \u0026amp;\u0026amp; \\ apt-get install -y nginx \u0026amp;\u0026amp; \\ rm -rf /var/lib/apt/lists/* 可以看到dockerfile将\\用于换行,这与makefile的写法一致 COPY: 复制文件 COPY 是在构建镜像时，将构建上下文（Dockerfile 所在目录及其子目录）中的文件或目录复制到镜像内的指令。它是处理应用代码、配置文件最常用的方式,应用场景如下:\nCOPY . /app (应用源码) COPY nginx.conf /etc/nginx/nginx.conf (配置文件) COPY public /app/public(静态资源) 基本语法 1 2 COPY [选项] \u0026lt;源路径\u0026gt;... \u0026lt;目标路径\u0026gt; COPY [选项] [\u0026#34;\u0026lt;源路径1\u0026gt;\u0026#34;, \u0026#34;\u0026lt;源路径2\u0026gt;\u0026#34;, ... \u0026#34;\u0026lt;目标路径\u0026gt;\u0026#34;] 复制文件\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ## 复制文件到指定目录 COPY package.json /app/ ## 复制文件并重命名 COPY config.json /app/settings.json ## 复制多个指定文件 COPY package.json package-lock.json /app/ ## 使用通配符 COPY *.json /app/ COPY src/*.js /app/src/ 复制目录\n1 2 3 4 5 6 7 8 ## 复制整个目录的内容（不是目录本身） COPY src/ /app/src/ # 构建上下文： 镜像内： # src/ /app/src/ # ├── index.js → ├── index.js # └── utils.js └── utils.js 指定路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 绝对路径 COPY app.js /usr/src/app/ # 相对路径：基于 WORKDIR WORKDIR /app COPY package.json ./ # 复制到 /app/package.json COPY src/ ./src/ # 复制到 /app/src/ # 如果目标目录不存在，Docker 会自动创建： ## /app/config/ 不存在也会自动创建 COPY settings.json /app/config/ dockerignore 排除不需要复制的文件,精简镜像体积\n1 2 3 4 5 6 7 8 ## .dockerignore node_modules .git .env *.log Dockerfile .dockerignore 实战 1 2 3 4 5 6 7 8 9 10 ## ✅ 好：先复制依赖定义，再安装，最后复制代码 COPY package.json package-lock.json ./ RUN npm install COPY . . ## ❌ 差：一次性复制所有文件，代码变更会导致重新 npm install COPY . . RUN npm install 详细解释一下,docker构建镜像是线性操作的,只有COPY,RUN,ADD三种命令会创建新的存储层,而每次构建时docker都会在本地存储缓存,如果下一次构建镜像时对应的命令没有变化,则会直接复用原来的缓存,不会重新构建;当docker发现COPY的文件内容有改动时,该行之后的所有命令被视为与原缓存不同,需要重新构建.\n因此,如果直接写COPY . .的话,修改任何一个文件后构建镜像都要重新运行npm install;但如果把COPY . .放在后面,只会在package.json变化时重新构建.\nADD: 更高级的COPY 实践中的建议：除非你明确需要自动解压功能（比如官方基础镜像构建根文件系统），否则始终使用 COPY。原因很简单——显式优于隐式。你的 Dockerfile 在 6 个月后被接手维护时，清晰的意图会让团队少走很多弯路。\n基本用法 1 2 ADD [选项] \u0026lt;源路径\u0026gt;... \u0026lt;目标路径\u0026gt; ADD [选项] [\u0026#34;\u0026lt;源路径\u0026gt;\u0026#34;, ... \u0026#34;\u0026lt;目标路径\u0026gt;\u0026#34;] ADD 在 COPY 基础上增加了两个功能：\n自动解压 tar 压缩包 支持从 URL 下载文件 (不推荐) CMD: 容器启动命令(4/10) 在深入 CMD 的细节之前，我们需要理解一个关键问题：CMD 和 ENTRYPOINT 应该在什么时候使用？\n这是 Dockerfile 使用中最常见的困惑之一。简单的答案是：\nCMD：定义容器的”默认命令”。如果用户在 docker run 时提供命令，CMD 会被覆盖 ENTRYPOINT：定义容器的”入口脚本”。通常用于启动应用的某个特定部分 基本用法 CMD指令用于指定容器启动时默认执行的命令。它定义了容器的 “主进程”。\n格式类型 语法示例 推荐程度 核心机制 Exec 格式 CMD [\u0026quot;executable\u0026quot;, \u0026quot;param1\u0026quot;, \u0026quot;param2\u0026quot;] ✅ 推荐 直接由内核执行，PID 为 1，可接收 SIGTERM 信号。 Shell 格式 CMD command param1 param2 ⚠️ 简单场景 通过 /bin/sh -c 调用，无法直接接收信号，环境变量会被解析。 参数格式 CMD [\u0026quot;param1\u0026quot;, \u0026quot;param2\u0026quot;] ⚓ 配合使用 仅作为 ENTRYPOINT 的默认参数传递。 exec 格式\n1 2 3 CMD [\u0026#34;nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] CMD [\u0026#34;python\u0026#34;, \u0026#34;app.py\u0026#34;] CMD [\u0026#34;node\u0026#34;, \u0026#34;server.js\u0026#34;] shell格式\n1 2 CMD echo \u0026#34;Hello World\u0026#34; CMD nginx -g \u0026#34;daemon off;\u0026#34; 实际执行：会被包装为 sh -c\n1 2 3 4 5 6 7 ## 你写的 CMD echo $HOME ## 实际执行的 CMD [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo $HOME\u0026#34;] 换句话说shell写法实际上是使用了sh的exec简写格式. CMD命令只能写一个 多个CMD只有最后一个生效. 因为CMD的PID为1,意思是在Linux内核中它作为根进程,是独一无二的.因此CMD一旦停止,容器就关闭了.\nENTRYPOINT: 入口点(4/11) 如果说 CMD 是\u0026quot;容器中的默认程序\u0026quot;，那么 ENTRYPOINT 就是\u0026quot;把容器变成一个命令\u0026quot;。这个思维转变决定了你何时使用 ENTRYPOINT。\n是什么,怎么用 ENTRYPOINT 指定容器启动时运行的入口程序。与 CMD 不同，ENTRYPOINT 定义的命令不会被 docker run 的参数覆盖，而是 接收这些参数。\n基本语法\n1 2 3 4 5 6 7 ## exec 格式（推荐） ENTRYPOINT [\u0026#34;nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] ## shell 格式（不推荐） ENTRYPOINT nginx -g \u0026#34;daemon off;\u0026#34; ENV: 设置环境变量 很好理解,就是设置了一个dockerfile中的变量而已. 1 2 3 4 5 6 7 ## 格式一：单个变量 ENV \u0026lt;key\u0026gt; \u0026lt;value\u0026gt; ## 格式二：多个变量（推荐） ENV \u0026lt;key1\u0026gt;=\u0026lt;value1\u0026gt; \u0026lt;key2\u0026gt;=\u0026lt;value2\u0026gt; ... 例子\n1 2 3 4 5 6 7 ENV NODE_VERSION 20.10.0 ENV APP_ENV production ENV NODE_VERSION=20.10.0 \\ APP_ENV=production \\ APP_NAME=\u0026#34;My Application\u0026#34; # 包含空格的值用双引号括起来 用法 使用 -e 或 \u0026ndash;env 覆盖 Dockerfile 中定义的环境变量：\n1 2 3 4 5 6 7 8 9 10 11 ## 覆盖单个变量 $ docker run -e APP_ENV=development myimage ## 覆盖多个变量 $ docker run -e APP_ENV=development -e DEBUG=true myimage ## 从环境变量文件读取 $ docker run --env-file .env myimage 运行时传入密码:\n1 2 3 4 5 6 7 ## ❌ 错误：密码写入镜像 ENV DB_PASSWORD=secret123 ## ✅ 正确：运行时传入 ## docker run -e DB_PASSWORD=xxx myimage 使用docker compose的话就没必要考虑这么多了\nARG: 构建参数 ARG仅在构建时生效,用于传递版本号之类的信息,可以出现在FROM指令之前,也能在docker build阶段传入对应的参数.换句话说,我们可以更改构建初始镜像所用的版本号.\n而ENV则会被打包进入镜像,在容器运行期间永久生效,也不能出现在FROM指令之前. 基本语法\n1 ARG \u0026lt;参数名\u0026gt;[=\u0026lt;默认值\u0026gt;] 用法 1 2 3 4 5 6 7 8 ARG BASE_IMAGE=python:3.12-slim FROM ${BASE_IMAGE} ## 可以构建不同基础镜像的版本 ## docker build --build-arg BASE_IMAGE=python:3.14-alpine . ... VOLUME: 定义匿名卷 是什么,怎么用 容器存储层应该保持无状态，任何运行时数据都应该存储在volume中。\n1 2 3 4 5 6 7 # 定义单个volume FROM mysql:8.0 VOLUME /var/lib/mysql # 定义多个volume FROM myapp VOLUME [\u0026#34;/data\u0026#34;, \u0026#34;/logs\u0026#34;, \u0026#34;/config\u0026#34;] volume的行为 自动创建匿名卷 如果运行时未指定挂载，Docker 会自动创建匿名卷：\n1 2 3 4 $ docker run mysql:8.0 $ docker volume ls DRIVER VOLUME NAME local a1b2c3d4e5f6... # 自动创建的匿名卷 会被命名卷覆盖\n1 2 3 ## 使用命名卷替代匿名卷 $ docker run -v mysql_data:/var/lib/mysql mysql:8.0 VOLUME 之后对该目录的修改会被丢弃！\n1 2 3 4 5 6 FROM ubuntu VOLUME /data ## ❌ 这个文件不会出现在镜像中！ RUN echo \u0026#34;hello\u0026#34; \u0026gt; /data/test.txt 原因：在构建过程中，VOLUME 指令会为该目录创建一个临时的匿名卷。后续 RUN 指令对该目录的写入实际发生在这个临时卷中，而非镜像层。当该 RUN 指令结束后，临时卷被丢弃，因此写入的内容不会保存到最终镜像中。注意：这与容器运行时创建的匿名卷是不同的——运行时创建的卷会在容器生命周期内持续存在。\n正确做法\n1 2 3 4 5 6 7 8 9 FROM ubuntu ## ✅ 先写入文件 RUN mkdir -p /data \u0026amp;\u0026amp; echo \u0026#34;hello\u0026#34; \u0026gt; /data/test.txt ## 再声明 VOLUME VOLUME /data 在compose中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 services: db: image: postgres:16 volumes: # 命名卷（推荐） - postgres_data:/var/lib/postgresql/data # Bind Mount - ./init.sql:/docker-entrypoint-initdb.d/init.sql volumes: postgres_data: # 声明命名卷 EXPOSE: 暴露端口(4/12) 是什么,怎么用 EXPOSE 声明容器运行时提供服务的端口。这是一个文档性质的声明，告诉使用者容器会监听哪些端口。\n换句话说只起一个约定作用,不通过-p的话不会起作用 基本用法\n1 2 3 4 5 6 7 8 9 10 11 12 ## 声明单个端口 EXPOSE 80 ## 声明多个端口 EXPOSE 80 443 ## 声明 TCP 和 UDP 端口 EXPOSE 80/tcp EXPOSE 53/udp 使用 docker run -P 时，Docker 会自动映射 EXPOSE 的端口到宿主机随机端口：\n1 2 3 4 5 6 ## Dockerfile # EXPOSE 80 $ docker run -P nginx $ docker port $(docker ps -q) 80/tcp -\u0026gt; 0.0.0.0:32768 实战 1 2 3 4 ## Dockerfile FROM nginx EXPOSE 80 # 1. 声明：这个容器会在 80 端口提供服务 1 2 3 ## 运行：需要 -p 才能从外部访问 $ docker run -p 8080:80 nginx # 2. 映射：宿主机 8080 → 容器 80 compose中的编写 1 2 3 4 5 6 7 services: web: build: . ports: - \u0026#34;8080:80\u0026#34; # 映射端口（类似 -p） expose: - \u0026#34;80\u0026#34; # 仅声明（类似 EXPOSE） WORKDIR: 指定工作目录 WORKDIR 指定后续指令的工作目录。如果目录不存在，Docker 会自动创建。\n基本用法\n1 2 3 4 5 WORKDIR /app RUN pwd # 输出 /app RUN echo \u0026#34;hello\u0026#34; \u0026gt; world.txt # 创建 /app/world.txt COPY . . # 复制到 /app/ 1 2 3 4 5 6 7 # 相对路径 WORKDIR /a WORKDIR b WORKDIR c RUN pwd # 输出 /a/b/c 实战 使用绝对命令\n1 2 3 4 5 6 7 ## ✅ 推荐：绝对路径，意图明确 WORKDIR /app ## ⚠️ 避免：相对路径可能造成混淆 WORKDIR app USER: 指定当前用户 是什么,怎么用 USER 指令切换后续指令 (RUN、CMD、ENTRYPOINT) 的执行用户\n1 2 USER \u0026lt;用户名\u0026gt;[:\u0026lt;用户组\u0026gt;] USER \u0026lt;UID\u0026gt;[:\u0026lt;GID\u0026gt;] 一般是用不上这个的 HEALTHCHECK: 健康检查 是什么,怎么用 HEALTHCHECK 指令告诉 Docker 如何判断容器状态是否正常。这是保障服务高可用的重要机制。\n1 2 HEALTHCHECK [选项] CMD \u0026lt;命令\u0026gt; HEALTHCHECK NONE 基本用法 1 2 3 4 5 FROM nginx RUN apt-get update \u0026amp;\u0026amp; apt-get install -y curl \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=30s --timeout=3s --retries=3 \\ CMD curl -fs http://localhost/ || exit 1 选项 说明 默认值 --interval 两次检查的间隔 30s --timeout 检查命令的超时时间 30s --start-period 启动缓冲期 (期间失败不计入次数) 0s --retries 连续失败多少次标记为 unhealthy 3 应用启动可能需要时间 (如 Java 应用)。设置 \u0026ndash;start-period 可以防止在启动阶段因检查失败而误判\n1 2 3 ## 给应用 1 分钟启动时间 HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1 LABEL: 为镜像添加元数据 是什么,怎么用 LABEL 指令以键值对的形式给镜像添加元数据。这些数据不会影响镜像的功能，但可以帮助用户理解镜像，或被自动化工具使用。\n版本管理：记录版本号、构建时间、Git Commit ID 联系信息：维护者邮箱、文档地址、支持渠道 自动化工具：CI/CD 工具可以读取标签触发操作 许可证信息：声明开源协议 1 LABEL \u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; \u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; ... 1 2 3 4 5 6 7 8 9 # 定义单个标签 LABEL version=\u0026#34;1.0\u0026#34; LABEL description=\u0026#34;这是一个 Web 应用服务器\u0026#34; # 定义多个标签 LABEL maintainer=\u0026#34;user@example.com\u0026#34; \\ version=\u0026#34;1.2.0\u0026#34; \\ description=\u0026#34;My App Description\u0026#34; \\ org.opencontainers.image.authors=\u0026#34;Yeasy\u0026#34; 数据管理 这一章介绍如何在 Docker 内部以及容器之间管理数据，在容器中管理数据主要有以下几种方式：\n数据卷 挂载主机目录 tmpfs 挂载 数据卷 容器的存储层有一个关键问题：容器删除后，数据就没了。数据卷 (Volume) 解决了这个问题，它的生命周期独立于容器。\n创建和查看数据卷 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 创建数据卷 $ docker volume create my-vol # 列出所有数据卷 $ docker volume ls DRIVER VOLUME NAME local my-vol local postgres_data local redis_data # 查看数据卷详情 $ docker volume inspect my-vol [ { \u0026#34;CreatedAt\u0026#34;: \u0026#34;2026-01-15T10:00:00Z\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Labels\u0026#34;: {}, \u0026#34;Mountpoint\u0026#34;: \u0026#34;/var/lib/docker/volumes/my-vol/_data\u0026#34;, \u0026#34;Name\u0026#34;: \u0026#34;my-vol\u0026#34;, \u0026#34;Options\u0026#34;: {}, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34; } ] 关键字段：\nMountpoint：数据卷在宿主机上的实际存储位置 Driver：存储驱动 (默认 local，也可以用第三方驱动) 挂载数据卷 方式一：\u0026ndash;mount：推荐\n1 2 3 4 $ docker run -d \\ --name web \\ --mount source=my-vol,target=/usr/share/nginx/html \\ nginx 参数 说明 source 数据卷名称 (不存在会自动创建) target 容器内挂载路径 readonly 可选，只读挂载 方式二：-v：简写 1 2 3 4 $ docker run -d \\ --name web \\ -v my-vol:/usr/share/nginx/html \\ nginx 提示：官方更推荐使用 \u0026ndash;mount。除了语法格式可读性更好之外，最重要的行为差异发生在 绑定挂载 (Bind Mount) 时：如果挂载的宿主机源路径尚未存在，-v 会擅自将其自动创建为一个空目录；而 \u0026ndash;mount 则会严格检查并直接报错。这能有效避免因路径拼写错误而在宿主机上留下垃圾目录（以及导致的容器访问空目录问题）。而对于本节的 数据卷 (Volume) 挂载而言，两者在目标指定的卷不存在时皆会自动创建卷，产生的结果是 完全一致 的。\n实战 数据库持久化\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ## 创建数据卷 $ docker volume create postgres_data ## 启动 PostgreSQL，数据存储在数据卷中 $ docker run -d \\ --name postgres \\ -e POSTGRES_PASSWORD=secret \\ -v postgres_data:/var/lib/postgresql/data \\ postgres:16 ## 即使删除容器，数据仍然保留 $ docker rm -f postgres ## 重新启动，数据还在 $ docker run -d \\ --name postgres \\ -e POSTGRES_PASSWORD=secret \\ -v postgres_data:/var/lib/postgresql/data \\ postgres:16 多容器共享数据\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ## 创建共享数据卷 $ docker volume create shared-data ## 容器 A 写入数据 $ docker run -d --name writer \\ -v shared-data:/data \\ alpine sh -c \u0026#34;while true; do date \u0026gt;\u0026gt; /data/log.txt; sleep 5; done\u0026#34; ## 容器 B 读取数据 $ docker run --rm \\ -v shared-data:/data \\ alpine cat /data/log.txt 配置文件持久化\n1 2 3 4 5 6 7 ## 将 nginx 配置存储在数据卷中 $ docker run -d \\ -v nginx-config:/etc/nginx/conf.d \\ -v nginx-logs:/var/log/nginx \\ -p 80:80 \\ nginx 挂载主机目录 绑定挂载 Bind Mount (绑定挂载) 将 Docker daemon 所在主机 上的目录或文件直接挂载到容器中。容器可以读写这台主机上的文件系统。 Bind Mount vs Volume\n特性 Bind Mount (绑定挂载) Volume (数据卷) 数据位置 宿主机任意路径 Docker 管理的特定目录 (/var/lib/docker/volumes/) 路径指定 必须是绝对路径 (如 /opt/app/data) 卷名 (如 my-vol)，隐式管理物理路径 可移植性 低。依赖宿主机特定的文件目录结构 高。不依赖物理路径，易于在不同环境迁移 性能 依赖宿主机文件系统原生性能 绕过 Storage Driver 层，具备原生 I/O 性能 适用场景 开发环境同步代码、挂载宿主机配置文件 生产环境数据库持久化、日志存储、多容器共享 备份与管理 手动定位宿主机路径进行备份 使用 docker volume 命令管理，备份需挂载容器操作 隔离性 宿主机进程可轻易修改，安全性较低 由 Docker 隔离，减少了被宿主机其他进程误删的风险 基本语法 方案 A：使用 --mount（推荐方式）\n1 2 3 4 $ docker run -d \\ --name web-bind \\ --mount type=bind,source=/宿主机路径,target=/容器路径 \\ nginx 方案 B：使用 -v（简写方式）\n1 2 3 4 $ docker run -d \\ --name web-v \\ -v /宿主机路径:/容器路径 \\ nginx 可以看到,这里的语法与之前的volume挂载基本相同\n网络配置 配置DNS Docker 容器的 DNS 配置有两种情况：\n默认 Bridge 网络：继承宿主机的 DNS 配置 (/etc/resolv.conf)。 自定义网络(推荐)：使用 Docker 嵌入式 DNS 服务器 (Embedded DNS)，支持通过 容器名 进行服务发现。 使用自定义网络 1 2 3 4 5 6 7 8 9 10 11 12 13 ## 1. 创建自定义网络 $ docker network create mynet ## 2. 启动容器 web 并加入网络 $ docker run -d --name web --network mynet nginx ## 3. 启动容器 client 并尝试 ping web $ docker run -it --rm --network mynet alpine ping web PING web (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.074 ms 端口映射 容器的网络访问规则如下：\n容器之间：可以通过 IP 或容器名 (自定义网络) 互通。 宿主机访问容器：可以通过容器 IP 访问。 外部网络访问容器：❌ 默认无法直接访问。 为了让外部 (如你的浏览器、其他局域网机器) 访问容器内的服务，我们需要将容器的端口 映射 到宿主机的端口。\n基本用法 1 2 3 ## 将宿主机的 8080 端口映射到容器的 80 端口 $ docker run -d -p 8080:80 nginx:alpine 此时访问 http://localhost:8080 即可看到 Nginx 页面。 格式 含义 示例 ip:hostPort:containerPort 绑定指定 IP 的特定端口 -p 127.0.0.1:8080:80 (仅允许本机访问) ip::containerPort 绑定指定 IP 的随机端口 -p 127.0.0.1::80 hostPort:containerPort 绑定所有网卡 IP (0.0.0.0) 的特定端口 -p 8080:80 (最常用格式) containerPort 绑定所有网卡 IP 的随机端口 -p 80 随机映射 如果不关心宿主机使用哪个端口，可以使用随机映射。使用 -P (大写) 参数，Docker 会把 Dockerfile 中 EXPOSE 指令暴露的所有端口发布到宿主机的随机高位端口。具体落在哪个端口，取决于宿主机当前可用的临时端口范围。\n1 2 3 4 5 6 docker run -d -P nginx docker ps CONTAINER ID PORTS abc123456 0.0.0.0:49153-\u0026gt;80/tcp # 此时 Nginx 被映射到了宿主机的一个随机高位端口49153 实战 默认情况下，-p 8080:80 会监听 0.0.0.0:8080，这意味着任何人只要能连接你的宿主机 IP，就能访问该服务。如果不希望对外暴露 (例如数据库服务)，应绑定到 127.0.0.1：\n1 2 3 ## 仅允许本机访问 $ docker run -d -p 127.0.0.1:3306:3306 mysql 网络隔离 不同网络之间默认隔离，容器只能与同一网络中的容器直接通信：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ## 创建两个网络 $ docker network create frontend $ docker network create backend ## 容器 A 在 frontend $ docker run -d --name web --network frontend nginx ## 容器 B 在 backend $ docker run -d --name db --network backend postgres ## web 无法直接访问 db（不同网络） $ docker exec web ping db ping: db: Name or service not known Docker Compose 概览 在学习 Compose 之前，笔者想强调它的真正价值。假设你正在开发一个微服务应用——前端、后端、数据库三个服务。如果你用 Docker 容器分别运行它们，你会遇到这些问题：\n启动顺序：需要先启数据库，再启后端，最后启前端 网络连接：三个容器需要能彼此通信 卷挂载：本地代码需要映射到容器内 环境变量：每个服务的配置需要逐个设置 使用 docker run 逐个启动的话，需要记住 3 条复杂的命令。而 Docker Compose 的核心价值就是用一个 YAML 文件来定义整个应用，然后一条命令 docker compose up 启动所有服务。这是 Compose 被广泛采用的原因——它极大地简化了本地开发和测试的复杂性。\nCompose 项目早期由 Python 编写，称为 Docker Compose V1,现在的 Docker Compose V2 是一个 Go 语言编写的 Docker CLI 插件。Docker Desktop 默认包含它\n关键定义\n服务 (service)：一个应用容器，实际上可以运行多个相同镜像的实例。 项目 (project)：由一组关联的应用容器组成的一个完整业务单元。 可见，一个项目可以由多个服务 (容器) 关联而成，Compose 面向项目进行管理。\n补充: compose命令行 基本使用格式\n1 docker compose [-f=\u0026lt;arg\u0026gt;...] [options] [COMMAND] [ARGS...] 参数说明\n-f, --file FILE: 指定使用的 Compose 模板文件。默认会自动识别 compose.yaml (也兼容 docker-compose.yml 等)，并且可以多次指定。 -p, --project-name NAME: 指定项目名称，默认将使用所在目录名称作为项目名。 --verbose: 输出更多调试信息。 -v, --version: 打印版本并退出。 事实上,想要更好的理解docker compose命令,需要和没有compose的docker命令进行比较:\n构建项目 docker compose build: 根据当前目录的compose.yml文件进行构建,如果没有用-f指定文件名字的话,会在当前目录中按以下顺序检索，匹配到第一个即停止查找 compose.yaml（官方推荐的首选名称） compose.yml docker-compose.yaml docker-compose.yml（历史最常用的名称，现降级为备选） docker build: 根据当前目录的dockerfile构建镜像 更多的相同点 都会在目标文件变更时才重新构建镜像 自然,它完全可以被且已经被docker compose up取代\n启动项目 docker run: 根据镜像名字启动容器,若依赖的镜像尚未构建且可以从远端拉取时,则先拉取该镜像后再启动容器, docker compose up: 根据compose.yml启动项目,若依赖的services(服务)有一些或者全部没有对应的镜像则会先构建再启动项目. 更多的相同点\n都支持使用-d参数来后台运行 事实上,docker compose up的特性比上面所说的要复杂得多,我们可以compose.yml中预先在build关键字中指定了对应的构建目录和dockerfile,则每次运行docker compose up时可以加上--build参数,来实现在对应的构建目录有更改时自动构建镜像后运行.\n因此,如果容器不报错的话我们可以只使用docker compose up命令完成实时的构建和项目运行.\n停止和删除容器 docker compose stop: 停止所有服务,保留容器和网络,当然也保留数据卷 docker compose down: 停止所有服务后删除容器和网络,默认保留数据卷,除非加上-v参数,这会删除所有匿名卷和命名卷,但保留绑定挂载文件 docker stop: 正常停止容器,不会删除容器 docker kill: 强制停止容器,不会删除容器 docker rm 容器名: 删除某个容器 docker rmi 镜像名: docker remove image的缩写,删除某个镜像 docker container prune: 删除虚悬容器 docker image prune: 删除虚悬镜像 docker compose logs: 日志查看 基本格式\n1 docker compose logs [options] [SERVICE...] 参数\n--tail=50: 指定对应的最新日志行数 tail参数的默认值为all,输出所有服务或者在指定服务名字时输出该服务的所有日志. 如果构建报错,都需要使用该命令来查看具体的构建问题.\n进阶用法: 多compose文件编排 官方文档 前面所说的docker compose up还可以通过使用多个-f参数,实现多文件的覆盖,从而将生产环境和部署环境彻底隔离开来,具体用法如下: 执行命令\n1 docker compose -f compose.yml -f compose.prod.yml up -d compose.yml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 核心基础配置，定义通用架构 services: web: image: nginx:alpine # 列表类字段（如 ports）在多文件模式下会执行“取并集”操作 ports: - \u0026#34;8080:80\u0026#34; # 映射类字段（如 environment）若键名重复，后续文件将覆盖此处的值 environment: - NODE_ENV=development - DEBUG=true # 默认重启策略 restart: \u0026#34;no\u0026#34; db: image: postgres:15-alpine volumes: - db_data:/var/lib/postgresql/data volumes: db_data: compose.prod.yml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 生产环境专用覆盖，修改性能参数与安全性 services: web: # 覆盖：将基础镜像替换为稳定版标签 image: nginx:stable-alpine # 覆盖：修改同名环境变量，关闭调试模式 environment: - NODE_ENV=production - DEBUG=false # 合并：保留基础文件的 8080 端口，并额外增加 443 端口映射 ports: - \u0026#34;443:443\u0026#34; # 覆盖：生产环境要求容器崩溃后自动重启 restart: always # 新增：生产环境特有的部署约束 deploy: resources: limits: cpus: \u0026#39;0.5\u0026#39; memory: 512M db: # 扩展：仅在生产环境中对数据库启用加密连接强制要求 command: [\u0026#34;postgres\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;ssl=on\u0026#34;] # 注意：volumes 和 networks 的合并同样遵循并集原则 补充: compose编写 build: 指定dockerfile路径 指定 Dockerfile 所在文件夹的路径 (可以是绝对路径，或者相对 Compose 文件的路径)。Compose 将会利用它自动构建这个镜像，然后使用这个镜像.\nDockerfile 中设置的选项 (例如：CMD、EXPOSE、VOLUME、ENV 等) 将会自动被获取，无需在 Compose 文件中重复设置。 1 2 3 4 5 6 7 services: webapp: build: # 指定dockerfile路径 context: ./dir # 指定dockerfile名字 dockerfile: Dockerfile-alternate 进阶用法 使用arg参数指定构建镜像时的变量:\n1 2 3 4 5 6 7 frontend: build: context: . dockerfile: frontend/Dockerfile args: - VITE_API_URL=https://api.${DOMAIN?Variable not set} - NODE_ENV=production image: 指定镜像 指定该服务使用的镜像名称或ID,如果该镜像本地不存在,则会去远程仓库拉取该镜像\n进阶用法 image可以与build进行配合,指定build生成的镜像名字:\n1 2 3 4 5 6 7 8 9 services: frontend: image: \u0026#39;${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}\u0026#39; build: context: . dockerfile: frontend/Dockerfile args: - VITE_API_URL=https://api.${DOMAIN?Variable not set} - NODE_ENV=production ${DOCKER_IMAGE_FRONTEND?Variable not set}: compose语法${VAR?ErrorMessage},若VAR为定义或为空,则compose会报错并停止运行,在终端输出该调试信息 TAG-latest: compose语法${VAR-DefaultValue},VAR变量未定义时提供默认值 如果compose.yml所在目录的.env文件中有如下定义:\nDOCKER_IMAGE_FRONTEND = frontend TAG = 1.0 而该项目的名字为web,则这个镜像在docker中的最终名字为web-frontend:1.0.\nvolumes: 指定挂载的数据卷 数据卷所挂载路径设置。可以设置为宿主机路径 (HOST:CONTAINER)(即绑定挂载) 或者数据卷名称 (VOLUME:CONTAINER)，并且可以设置访问模式 (HOST:CONTAINER:ro)。\n用法\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro # 如果路径为数据卷名称，必须在文件中配置数据卷。 services: my_src: image: mysql:8.0 volumes: - mysql_data:/var/lib/mysql volumes: mysql_data: env_file与environment: 环境变量 env_file: 指定环境变量文件路径。 如果通过 docker compose -f FILE 方式来指定 Compose 模板文件，则 env_file 中变量的路径会基于模板文件路径。 如果有变量名称与 environment 指令冲突，则按照惯例，以后者为准。 该环境文件需要严格符合.env格式:\n1 2 3 4 ## common.env: Set development environment PROG_ENV=development # 等号两边无空格 即使没有使用env_file关键字,compose依然会自动读取对应目录的.env文件.\nenvironment: 设置服务的环境变量,如果只给定名称,会自动读取环境变量，可以用来防止泄露不必要的数据. 示例\n1 2 3 4 5 6 7 8 9 10 11 services: db: image: postgres:18 env_file: - .env environment: - PGDATA=/var/lib/postgresql/data/pgdata - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - POSTGRES_USER=${POSTGRES_USER?Variable not set} - POSTGRES_DB=${POSTGRES_DB?Variable not set} command: 覆盖容器启动后默认执行的命令 command: bash scripts/prestart.sh\nhealthcheck: 健康检查 通过命令检查容器是否健康运行。\n1 2 3 4 5 healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;curl\u0026#34;, \u0026#34;-f\u0026#34;, \u0026#34;http://localhost:8000/api/v1/utils/health-check/\u0026#34;] interval: 10s timeout: 5s retries: 5 test: 检查时执行的命令 interval: 执行间隔 timeout: 超过该时间限制仍未收到响应则视为这次检查失败 retries: 第一次检查失败后的总尝试次数,若5次都失败则将该服务标记未不健康(unhealthy). 该关键字通常与depends_on关键字搭配使用.\ndepends_on 先看这个例子:\n1 2 3 4 5 6 7 8 9 10 11 12 services: web: build: . depends_on: - db - redis redis: image: redis db: image: postgres web需要在redis和db两个服务都启动后才可以启动. 也就是说depends_on规定了容器启动的先后顺序,保证需要其他服务作为依赖的容器滞后启动.\n进阶用法: 搭配healthcheck 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 services: db: image: postgres:18 restart: always healthcheck: test: [\u0026#34;CMD-SHELL\u0026#34;, \u0026#34;pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}\u0026#34;] interval: 10s retries: 5 start_period: 30s timeout: 10s prestart: depends_on: db: condition: service_healthy restart: true backend: depends_on: db: condition: service_healthy restart: true prestart: condition: service_completed_successfully 关键字解析\ncondition: 要求相关依赖服务必须达到的具体状态 restart: 对应服务重启时backend也必须重启 service_healthy: 要求对应服务通过了健康检查,状态变为healthy service_completed_successfully: 用于一次性服务中,要求对应服务运行后正常退出 labels 设置服务标签,供第三方工具识别\n例如我们可以给后端加上traefik标签:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 labels: - traefik.enable=true - traefik.docker.network=traefik-public - traefik.constraint-label=traefik-public - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=8000 - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`api.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`api.${DOMAIN?Variable not set}`) - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le # Enable redirection for HTTP and HTTPS - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect networks 配置容器连接的网络,若不声明,所有服务都加入默认的桥接网络,彼此可见\n写法如下:\n1 2 3 4 5 6 7 8 9 10 services: some-service: networks: - some-network - other-network networks: some-network: other-network: expose: 暴露端口 暴露端口,但不映射到宿主机仅可以指定docker内部端口为参数:\n1 2 3 expose: - \u0026#34;3000\u0026#34; - \u0026#34;8000\u0026#34; docker中装载操作系统 RESTful Web APIs 补充: 什么是RESTful Web API 非常令人震惊的是,这本书并没有谈到这一名词的具体概念和历史背景\u0026hellip;因此需要在这里做一点补充\n非常优秀的中文博客解析 只看上面的文章就够了,我再加上一点历史背景解析\n历史背景 根据\u0026laquo; RESTful Web APIs Patterns and Practices Cookbook \u0026raquo;总结 在www(万维网)的概念于1993年左右开始盛行时,并没有一个合适的规范来约束用户端和服务器之间的通信.\n于是,web技术大牛Roy T. Fielding在1998年的微软演讲提出了Representational State Transfer(REST)的初步构想,并在两年后的论文(“Architectural Styles and the Design of Network-based Software Architectures”)中完整的介绍了REST,总结一下大致意思就是:\nREST provides a set of architectural constraints that, when applied as a whole, emphasizes scalability of component interactions, generality of interfaces, independent deployment of components, and intermediary components to reduce interaction latency, enforce security, and encapsulate legacy systems.\n看不懂没关系,我们只需要知道Rest的主要准则如下:\n约束名称 核心要求 违背后的后果 客户机-服务器 (Client-Server) 前后端分离，职责解耦。 无法独立演进，扩展性受阻。 无状态 (Stateless) 每个请求必须包含处理所需的全部信息，服务器不保存会话上下文。 导致服务器无法水平扩展，容错性降低。 可缓存 (Cacheable) 响应必须定义自身是否可缓存。 增加网络延迟，浪费带宽和服务器资源。 统一接口 (Uniform Interface) 包含资源标识、通过表述操纵资源、自描述消息、HATEOAS。 最常被忽视的一项。不满足此项的通常只是“带 HTTP 的 RPC”。 分层系统 (Layered System) 客户端无法感知直接连接的是服务器还是中间件（代理、缓存）。 破坏安全性与负载均衡的透明度。 按需代码 (Code on Demand) （可选） 允许服务器向客户端发送可执行代码（如 JS）。 增加客户端复杂度和安全风险。 如果你的Web架构设计和API请求符合这些原则,则可以被称为Restful Web和Restful Web API.(rest-ful,意思大致为rest风格)\nDeep Learning from Scratch pdf链接 如前所说,阅读\u0026laquo; Transformers快速入门 \u0026raquo;的前提是要搞懂神经网络是什么,于是我辗转找到了这本享有盛名的书,希望能够彻底理解神经网络的概念 On Java 8 中文翻译版链接 Understanding The Linux Kernel(深入理解Linux内核) ","date":"2026-03-31T08:00:00Z","image":"/p/%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0%E5%90%88%E9%9B%86/123579977_p0-%E3%80%8Estroll%E3%80%8F.webp","permalink":"/p/%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0%E5%90%88%E9%9B%86/","title":"技术文章阅读笔记合集"},{"content":"尽管一季度还没完全结束,但还是先来个总结给自己打个底吧.\nOVERVIEW 回顾一下我年初的目标:\ncpp工程学习 后端学习(数据库+Java) 网络安全学习 编写一个开源项目 参加十场以上的比赛 明确就业还是读研 我可以很自信的打下几个勾:\ncpp工程学习 真真正正的从零开始学cpp了 后端学习(数据库+Java) 尽管我现在还是用的python,我真的感觉java的生态太古老了\u0026hellip; 网络安全学习 事实上,我认为所谓的网络安全是对计算机网络这个概念的套壳,单独列出这个名词来唬人有点不太厚道了;事实上如果你能把整个计算机网络的概念捋清楚,并能够真正的写出一个规范安全的全栈项目,那才是真正的精通网络安全 编写一个开源项目 尽管都是小项目吧,但我打算之后做几个真东西 参加十场以上的比赛 一场都没参加,我之前想的是参加一些ctf和算法竞赛,但当我看到百度之星的决赛题时,我就知道我绝对不是打算法竞赛的料,至于ctf,我还在入门ing\u0026hellip; 明确就业还是读研 感觉我这个选择经过了相当长的思考期,而当我最终意识到自己已经做下了这个选择时,一切都豁然开朗,身心上都轻松了不少,但随即而来的是就业压力\u0026hellip; 这么看来,当时定的目标基本都实现的差不多了,自己定的目标还是太轻松了,那我再来给二季度定一点目标:\n参加人工智能大赛,实现一个完整的智能体(尽量多用ai) 精通/掌握爬虫和机器学习 编写一个开源的量化平台(尽量少用ai),虽然GitHub上已经有不少大佬项目了,但我还是想在学习之后做出一个自己的项目 根据jmcomic api实现一个Android app 深入学习nextjs和nestjs,nextjs已经是二战了\u0026hellip; 学习阅读财报并买入第二支股票 读完\u0026laquo;设计数据密集型应用\u0026raquo;,\u0026laquo;程序员自我修养\u0026raquo;,\u0026laquo; C++程序设计语言 \u0026raquo;,\u0026laquo; RESTful Web APIs \u0026raquo;,一本设计模式书 深入学习docker,可以的话再学kubernetes 会不会有点太多了😃\n具体行动 学校生活:\n成功的把该翘的课全都翘了,只在必须要签到的时候露个面,生活质量整个上来了 将一切与学业无关的活动都推掉了,包括不能说的活动 家人:\n感觉现在我才是真正开智了,不愿意再被家里人牵着头走了,但又会尽量照顾到他们的情绪 人都是有自尊的,希望自己的话能被人听进去,再怎么说也是在以自己的能力和见识去尽量为你考虑的 学习:\n由于多出了很多很多时间,这才第四周我就基本将核心课程的3/4学完了,也会去找各种各样的参考书来加深理解 从到处搜罗信息转为只关注自己想要的信息,重点放在钻研单方面的知识,而不再是广泛的涉猎了 发现真正的信息链: 官方文档\u0026gt;博客文章\u0026gt;教材\u0026raquo;AI\u0026gt;问答网站\u0026raquo;视频\u0026raquo;社交平台\u0026raquo;传销号文章 娱乐活动:\n有了固定的每周一聚,偶尔还会刷新其他的聚会,让乡村生活瞬间美好了不少 多出了很多时间去玩自己喜欢的游戏,但现在发现废萌型的作品我是真玩不下去,我玩游戏的动力只有两点: 玩法非常新奇或者有趣 剧情非常好,界面交互不能太糟糕 喜欢上了那种坏坏的喜剧,如\u0026laquo;恶搞之家\u0026raquo;,\u0026laquo;费城永远阳光灿烂\u0026raquo; 体育锻炼: 由于大一下的过度锻炼: 一周三次健身加四五次跑步,尽管跑出了还算看的过去的3km12min的成绩,但是落下了一身伤,所以现在仅仅是完成刷段任务就满足了.\n","date":"2026-03-25T08:00:00Z","image":"/p/2026-03-25-2026%E4%B8%80%E5%AD%A3%E5%BA%A6%E6%80%BB%E7%BB%93/56852309_p0-%E6%A1%9C%E3%82%BB%E3%82%A4%E3%83%90%E3%83%BC.webp","permalink":"/p/2026-03-25-2026%E4%B8%80%E5%AD%A3%E5%BA%A6%E6%80%BB%E7%BB%93/","title":"2026-03-25 2026一季度总结"},{"content":" 前后端到底是怎么通信的?\n这个问题在我接触计算机后就一直在想,但是一直都没有一个清晰的认识,因此我将在下文以两个基础项目入手来谈一谈这个问题\n极简版fastapi+react 深入浅出的教程 强烈安利上面的教程,可以让小白迅速了解到前后端通信的实质操作\n我们可以清楚的看到,只需要用到这么一点文件就可以写出一个非常基础的支持增删日程的表格网站.\n后端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 # api.py from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware # mock data todos = [ { \u0026#34;id\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;item\u0026#34;: \u0026#34;Read a book.\u0026#34; }, { \u0026#34;id\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;item\u0026#34;: \u0026#34;Cycle around town.\u0026#34; } ] app = FastAPI() origins = [ \u0026#34;http://localhost:5173\u0026#34;, \u0026#34;localhost:5173\u0026#34; ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=[\u0026#34;*\u0026#34;], allow_headers=[\u0026#34;*\u0026#34;] ) @app.get(\u0026#34;/\u0026#34;, tags=[\u0026#34;root\u0026#34;]) async def read_root() -\u0026gt; dict: return {\u0026#34;message\u0026#34;: \u0026#34;Welcome to your todo list.\u0026#34;} @app.get(\u0026#34;/todo\u0026#34;,tags=[\u0026#34;todos\u0026#34;]) async def get_todos()-\u0026gt;dict: return {\u0026#34;data\u0026#34;: todos} @app.post(\u0026#34;/todo\u0026#34;, tags=[\u0026#34;todos\u0026#34;]) async def add_todo(todo: dict) -\u0026gt; dict: todos.append(todo) return { \u0026#34;data\u0026#34;: { \u0026#34;Todo added.\u0026#34; } } @app.put(\u0026#34;/todo/{id}\u0026#34;, tags=[\u0026#34;todos\u0026#34;]) async def update_todo(id: int, body: dict) -\u0026gt; dict: for todo in todos: if int(todo[\u0026#34;id\u0026#34;]) == id: todo[\u0026#34;item\u0026#34;] = body[\u0026#34;item\u0026#34;] return { \u0026#34;data\u0026#34;: f\u0026#34;Todo with id {id} has been updated.\u0026#34; } return { \u0026#34;data\u0026#34;: f\u0026#34;Todo with id {id} not found.\u0026#34; } @app.delete(\u0026#34;/todo/{id}\u0026#34;, tags=[\u0026#34;todos\u0026#34;]) async def delete_todo(id: int) -\u0026gt; dict: for todo in todos: if int(todo[\u0026#34;id\u0026#34;]) == id: todos.remove(todo) return { \u0026#34;data\u0026#34;: f\u0026#34;Todo with id {id} has been removed.\u0026#34; } return { \u0026#34;data\u0026#34;: f\u0026#34;Todo with id {id} not found.\u0026#34; } #main.py import uvicorn if __name__ == \u0026#34;__main__\u0026#34;: uvicorn.run(\u0026#34;app.api:app\u0026#34;, host=\u0026#34;0.0.0.0\u0026#34;, port=8000, reload=True) 可以发现,后端实质上只有两个文件,一个是使用uvicorn的启动文件,一个是用来处理前端传来的请求的api文件.\n我们首先要知道以下几件事:\nUvicorn 是一个超轻量级的 ASGI (Asynchronous Server Gateway Interface) 服务器。它的任务是物理监听网络端口，并将接收到的 HTTP 请求“翻译”给 FastAPI 处理。 app是一个FastAPI实例,在你用语法糖指明某个函数的作用对象时(例如@app.get(\u0026quot;/\u0026quot;, tags=[\u0026quot;root\u0026quot;])),他会自动在前端传来对应请求时执行这个函数,并将合法的返回值交付给前端 middleware字面意思是中间件,可以理解为夹在fastapi和前端之间的保安,会帮助fastapi执行以下操作: allow_origins=origins:只接收位于origins列表里的前端端口或地址发来的请求,其他端口一律过滤掉 allow_credentials=True:允许前端携带Cookies或者认证信息 allow_methods=[\u0026quot;*\u0026quot;]:允许所有的HTTP method,当然我们可以把内容改为\u0026quot;GET\u0026quot;,意为只允许GET请求 allow_headers=[\u0026quot;*\u0026quot;]:允许前端在request headers里自定义任何字段(如Content-Type) 事实上,我的上述说法是不完整的,在middleware与前端之间还有一个通信框架协议,也就是著名的Restful API协议,这个API框架严格定义了前端和后端相互发送的报文格式,也就是你在上述fastapi语法糖里看到的那些关键字,没有这些的话前后端是无法相互理解的.\n我们现在来深入看一下api.py这个文件,它总共定义了5个app函数:\n@app.get(\u0026quot;/\u0026quot;, tags=[\u0026quot;root\u0026quot;]): 执行read_root函数,返回一个欢迎访问的message到根页面 @app.get(\u0026quot;/todo\u0026quot;,tags=[\u0026quot;todos\u0026quot;]): 执行get_todos函数,将mock data返回到todo子页面 @app.post(\u0026quot;/todo\u0026quot;, tags=[\u0026quot;todos\u0026quot;]): 执行add_todo函数,接收todo对象并加到mock data里,同时返回成功添加的信息 @app.put(\u0026quot;/todo/{id}\u0026quot;, tags=[\u0026quot;todos\u0026quot;]): 执行update_todo函数,接收要更新的一个todo元素,遍历todos列表,将对应id的item字段改为body包含的item字段,并在找不到该id时返回错误提示 @app.delete(\u0026quot;/todo/{id}\u0026quot;, tags=[\u0026quot;todos\u0026quot;]): 执行delete_todo函数,与put函数原理类似,但这次找到对应的id直接删除 \u0026ldquo;message\u0026rdquo;,\u0026ldquo;data\u0026rdquo;,\u0026ldquo;tags\u0026quot;是个什么东西? 后端返回的对象实际上会再套一层大括号变成json格式,前端需要拆分这个json文件来提取所需的字段. 那么,显然前后端需要提前沟通好这些字段对应的用处,因为前端和后端编写人员在实际生产中是不太可能去仔细看对方代码的. 而fastapi推荐使用的json规范是openapi,规定好了各个字段和各个method的具体用途. 而在我们这个项目里,message作为调试信息,data作为返回数据,tags是一个便于openapi标识的标签,并不会发给前端,但可以帮助swagger UI分类路由(swagger UI是一个将json渲染成网页的中间产物).\n到这里,整个后端就分析完了,怎么想都不是很复杂吧!\n前端 如果你不太会react但是懂前端三件套的话,其实看下述内容只需要知道一点:\nreact采用的jsx语法可以将函数作为页面组件导入和导出,使用语法如\u0026lt;Header /\u0026gt;这样 但如果你连js都不懂的话,那还是去补补课吧\u0026hellip;\n先让我们看一下main.tsx这个文件:\n1 2 3 4 5 6 7 8 9 10 import { StrictMode } from \u0026#39;react\u0026#39; import { createRoot } from \u0026#39;react-dom/client\u0026#39; import \u0026#39;./index.css\u0026#39; import App from \u0026#39;./App.tsx\u0026#39; createRoot(document.getElementById(\u0026#39;root\u0026#39;)!).render( \u0026lt;StrictMode\u0026gt; \u0026lt;App /\u0026gt; \u0026lt;/StrictMode\u0026gt;, ) 可以看到核心组件就是这个App,那我们再看一下App.tsx:\n1 2 3 4 5 6 7 8 9 10 11 12 13 import { ChakraProvider } from \u0026#34;@chakra-ui/react\u0026#34;; import { defaultSystem } from \u0026#34;@chakra-ui/react\u0026#34;; import Header from \u0026#34;./components/Header\u0026#34;; import Todos from \u0026#34;./components/Todos\u0026#34;; // new export default function App() { return ( \u0026lt;ChakraProvider value={defaultSystem}\u0026gt; \u0026lt;Header /\u0026gt; \u0026lt;Todos /\u0026gt; \u0026lt;/ChakraProvider\u0026gt; ); } 可以看到App组件使用了Header组件和Todos组件,先看Header组件:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import React from \u0026#34;react\u0026#34;; import { Heading, Flex, Separator } from \u0026#34;@chakra-ui/react\u0026#34;; export default function Header() { return ( \u0026lt;Flex as=\u0026#34;nav\u0026#34; align=\u0026#34;center\u0026#34; justify=\u0026#34;space-between\u0026#34; wrap=\u0026#34;wrap\u0026#34; padding=\u0026#34;1rem\u0026#34; bg=\u0026#34;gray.400\u0026#34; width=\u0026#34;100%\u0026#34; position=\u0026#34;fixed\u0026#34; top=\u0026#34;0\u0026#34; left=\u0026#34;0\u0026#34; right=\u0026#34;0\u0026#34; zIndex=\u0026#34;1000\u0026#34; \u0026gt; \u0026lt;Flex align=\u0026#34;center\u0026#34; as=\u0026#34;nav\u0026#34; mr={5}\u0026gt; \u0026lt;Heading as=\u0026#34;h1\u0026#34; size=\u0026#34;sm\u0026#34;\u0026gt; Todos \u0026lt;/Heading\u0026gt; \u0026lt;Separator /\u0026gt; \u0026lt;/Flex\u0026gt; \u0026lt;/Flex\u0026gt; ); } 可以看到仅仅是个标题而已,没有其他的内容,那么可想而知重头戏都在Todos.tsx里了:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 import React, { useEffect, useState, createContext, useContext } from \u0026#34;react\u0026#34;; import { Box, Button, Container, Flex, Input, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger, Stack, Text, DialogActionTrigger, } from \u0026#34;@chakra-ui/react\u0026#34;; interface Todo { id: string; item: string; } interface UpdateTodoProps { item: string; id: string; fetchTodos: () =\u0026gt; void; } interface TodoHelperProps { item: string; id: string; fetchTodos: () =\u0026gt; void; } interface DeleteTodoProps { id: string; fetchTodos: () =\u0026gt; void; } const TodosContext = createContext({ todos: [], fetchTodos: () =\u0026gt; {}, }); export default function Todos() { const [todos, setTodos] = useState([]); const fetchTodos = async () =\u0026gt; { const response = await fetch(\u0026#34;http://localhost:8000/todo\u0026#34;); const todos = await response.json(); setTodos(todos.data); }; useEffect(() =\u0026gt; { fetchTodos(); }, []); return ( \u0026lt;TodosContext.Provider value={{ todos, fetchTodos }}\u0026gt; \u0026lt;Container maxW=\u0026#34;container.xl\u0026#34; pt=\u0026#34;100px\u0026#34;\u0026gt; \u0026lt;AddTodo /\u0026gt; \u0026lt;Stack gap={5}\u0026gt; {todos.map((todo: Todo) =\u0026gt; ( \u0026lt;TodoHelper item={todo.item} id={todo.id} fetchTodos={fetchTodos} /\u0026gt; ))} \u0026lt;/Stack\u0026gt; \u0026lt;/Container\u0026gt; \u0026lt;/TodosContext.Provider\u0026gt; ); } function AddTodo() { const [item, setItem] = React.useState(\u0026#34;\u0026#34;); const { todos, fetchTodos } = React.useContext(TodosContext); const handleInput = (event: React.ChangeEvent\u0026lt;HTMLInputElement\u0026gt;) =\u0026gt; { setItem(event.target.value); }; const handleSubmit = (event: React.FormEvent\u0026lt;HTMLFormElement\u0026gt;) =\u0026gt; { event.preventDefault(); const newTodo = { id: todos.length + 1, item: item, }; fetch(\u0026#34;http://localhost:8000/todo\u0026#34;, { method: \u0026#34;POST\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify(newTodo), }).then(fetchTodos); }; return ( \u0026lt;form onSubmit={handleSubmit}\u0026gt; \u0026lt;Input pr=\u0026#34;4.5rem\u0026#34; type=\u0026#34;text\u0026#34; placeholder=\u0026#34;Add a todo item\u0026#34; aria-label=\u0026#34;Add a todo item\u0026#34; onChange={handleInput} /\u0026gt; \u0026lt;/form\u0026gt; ); } const UpdateTodo = ({ item, id, fetchTodos }: UpdateTodoProps) =\u0026gt; { const [todo, setTodo] = useState(item); const updateTodo = async () =\u0026gt; { await fetch(`http://localhost:8000/todo/${id}`, { method: \u0026#34;PUT\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify({ item: todo }), }); await fetchTodos(); }; return ( \u0026lt;DialogRoot\u0026gt; \u0026lt;DialogTrigger asChild\u0026gt; \u0026lt;Button h=\u0026#34;1.5rem\u0026#34; size=\u0026#34;sm\u0026#34;\u0026gt; Update Todo \u0026lt;/Button\u0026gt; \u0026lt;/DialogTrigger\u0026gt; \u0026lt;DialogContent position=\u0026#34;fixed\u0026#34; top=\u0026#34;50%\u0026#34; left=\u0026#34;50%\u0026#34; transform=\u0026#34;translate(-50%, -50%)\u0026#34; bg=\u0026#34;white\u0026#34; p={6} rounded=\u0026#34;md\u0026#34; shadow=\u0026#34;xl\u0026#34; maxW=\u0026#34;md\u0026#34; w=\u0026#34;90%\u0026#34; zIndex={1000} \u0026gt; \u0026lt;DialogHeader\u0026gt; \u0026lt;DialogTitle\u0026gt;Update Todo\u0026lt;/DialogTitle\u0026gt; \u0026lt;/DialogHeader\u0026gt; \u0026lt;DialogBody\u0026gt; \u0026lt;Input pr=\u0026#34;4.5rem\u0026#34; type=\u0026#34;text\u0026#34; placeholder=\u0026#34;Add a todo item\u0026#34; aria-label=\u0026#34;Add a todo item\u0026#34; value={todo} onChange={(event) =\u0026gt; setTodo(event.target.value)} /\u0026gt; \u0026lt;/DialogBody\u0026gt; \u0026lt;DialogFooter\u0026gt; \u0026lt;DialogActionTrigger asChild\u0026gt; \u0026lt;Button variant=\u0026#34;outline\u0026#34; size=\u0026#34;sm\u0026#34;\u0026gt; Cancel \u0026lt;/Button\u0026gt; \u0026lt;/DialogActionTrigger\u0026gt; \u0026lt;Button size=\u0026#34;sm\u0026#34; onClick={updateTodo}\u0026gt; Save \u0026lt;/Button\u0026gt; \u0026lt;/DialogFooter\u0026gt; \u0026lt;/DialogContent\u0026gt; \u0026lt;/DialogRoot\u0026gt; ); }; function TodoHelper({ item, id, fetchTodos }: TodoHelperProps) { return ( \u0026lt;Box p={1} shadow=\u0026#34;sm\u0026#34;\u0026gt; \u0026lt;Flex justify=\u0026#34;space-between\u0026#34;\u0026gt; \u0026lt;Text mt={4} as=\u0026#34;div\u0026#34;\u0026gt; {item} \u0026lt;Flex align=\u0026#34;end\u0026#34;\u0026gt; \u0026lt;UpdateTodo item={item} id={id} fetchTodos={fetchTodos} /\u0026gt; \u0026lt;DeleteTodo id={id} fetchTodos={fetchTodos} /\u0026gt; {/* new */} \u0026lt;/Flex\u0026gt; \u0026lt;/Text\u0026gt; \u0026lt;/Flex\u0026gt; \u0026lt;/Box\u0026gt; ); } const DeleteTodo = ({ id, fetchTodos }: DeleteTodoProps) =\u0026gt; { const deleteTodo = async () =\u0026gt; { await fetch(`http://localhost:8000/todo/${id}`, { method: \u0026#34;DELETE\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify({ id: id }), }); await fetchTodos(); }; return ( \u0026lt;Button h=\u0026#34;1.5rem\u0026#34; size=\u0026#34;sm\u0026#34; marginLeft={2} onClick={deleteTodo}\u0026gt; Delete Todo \u0026lt;/Button\u0026gt; ); }; 没接触过interface的可以理解为是一个要求js变量必须实现对应字段的接口,其实这与java里的interface没有什么区别 大概浏览一下便知道,该文件有五个函数,我们按照从上到下的方式看一遍.\nTodos 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 export default function Todos() { const [todos, setTodos] = useState([]); const fetchTodos = async () =\u0026gt; { const response = await fetch(\u0026#34;http://localhost:8000/todo\u0026#34;); const todos = await response.json(); setTodos(todos.data); }; useEffect(() =\u0026gt; { fetchTodos(); }, []); return ( \u0026lt;TodosContext.Provider value={{ todos, fetchTodos }}\u0026gt; \u0026lt;Container maxW=\u0026#34;container.xl\u0026#34; pt=\u0026#34;100px\u0026#34;\u0026gt; \u0026lt;AddTodo /\u0026gt; \u0026lt;Stack gap={5}\u0026gt; {todos.map((todo: Todo) =\u0026gt; ( \u0026lt;TodoHelper item={todo.item} id={todo.id} fetchTodos={fetchTodos} /\u0026gt; ))} \u0026lt;/Stack\u0026gt; \u0026lt;/Container\u0026gt; \u0026lt;/TodosContext.Provider\u0026gt; ); } 在这里需要先明确一个事实: 尽管项目是在本地运行的,但前后端之间并不是直接通信的,而是需要相互通过http通信获取报文,然后分别拆分报文得到具体内容.\n如果不这样的话,前后端就无法真正实现分开部署了. 因此,我们前面所说的fastapi发送json其实不完全正确,实际上我们在写fastapi的时候只是填写了json内部的字段,但fastapi会帮我们封装成json文件,再由编译器处理后装入HTTP响应报文,前端读取响应报文后在拆分处理;至于前端是怎么发送请求报文的我们后面会提到. 再讲一下这个函数内部运行的全过程:\nconst [todos, setTodos] = useState([]);将todos这个列表与setTodos函数绑定,这个setTodos会在react编译器实现,我们只需要知道当我们调用setTodos并在括号里填入值时就会相应更改todos列表并重新渲染相关组件 这个response接收\u0026quot;http://localhost:8000/todo\u0026quot;传来的信息,其中8000/todo是fastapi所在的8000端口对应的todo页面,而由于fetch函数在不指定具体method时默认为get请求,也就是说,这里向后端发送了get请求. todos变量接收response的json部分,然后将todos的data字段填入todos列表,也就是说todos变量只是一个中间变量而已(所以这里的命名有一点不规范,比较难理解) useEffect是一个Hook组件,用useEffect包裹的函数将会根据第二个参数的状态来执行:首次渲染时一定执行,如果之后参数变化则会再次执行.但由于我们这里第二个参数是[],故只会在第一次渲染时执行fetchTodos函数,也就是只起到了初始化的作用. 至于return部分需要在我们讲完其他函数后再来分析 AddTodo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function AddTodo() { const [item, setItem] = React.useState(\u0026#34;\u0026#34;); const { todos, fetchTodos } = React.useContext(TodosContext); const handleInput = (event: React.ChangeEvent\u0026lt;HTMLInputElement\u0026gt;) =\u0026gt; { setItem(event.target.value); }; const handleSubmit = (event: React.FormEvent\u0026lt;HTMLFormElement\u0026gt;) =\u0026gt; { event.preventDefault(); const newTodo = { id: todos.length + 1, item: item, }; fetch(\u0026#34;http://localhost:8000/todo\u0026#34;, { method: \u0026#34;POST\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify(newTodo), }).then(fetchTodos); }; return ( \u0026lt;form onSubmit={handleSubmit}\u0026gt; \u0026lt;Input pr=\u0026#34;4.5rem\u0026#34; type=\u0026#34;text\u0026#34; placeholder=\u0026#34;Add a todo item\u0026#34; aria-label=\u0026#34;Add a todo item\u0026#34; onChange={handleInput} /\u0026gt; \u0026lt;/form\u0026gt; ); } 这个片段最难理解的地方其实是const { todos, fetchTodos } = React.useContext(TodosContext);,让我们把调用层级涉及的代码单独列出来,好容易理解一点:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // Todos.tsx //createContext对象 const TodosContext = createContext({ todos: [], fetchTodos: () =\u0026gt; {}, }); //主函数Todos export default function Todos() { //... return ( \u0026lt;TodosContext.Provider value={{ todos, fetchTodos }}\u0026gt; \u0026lt;Container\u0026gt; \u0026lt;AddTodo /\u0026gt; {/* ... */} \u0026lt;/Container\u0026gt; \u0026lt;/TodosContext.Provider\u0026gt; ); } //AddTodo函数 function AddTodo() { // const [item, setItem] = React.useState(\u0026#34;\u0026#34;); const { todos, fetchTodos } = React.useContext(TodosContext); const handleSubmit = (event: React.FormEvent\u0026lt;HTMLFormElement\u0026gt;) =\u0026gt; { // event.preventDefault(); 可以理解为防止触发提交表单时会刷新整个页面的旧浏览器bug const newTodo = { id: todos.length + 1, item: item, }; }; } 我们可以将createContext理解为一个可以跨越组件进行通信的Hook函数,也就是说父组件可以跨越多层子组件直接将值传给目标子组件,而不需要层层传递所需的js变量或者函数.\n在这里,创建的TodosContext对象具有Provider子对象,它可以将TodosContext涉及的对象和变量传入被Provider组件包裹的所有子组件,但是子组件要用到这些对象和变量是需要声明的,也就是这里所用的const { todos, fetchTodos } = React.useContext(TodosContext);,这行代码将TodosContext解构后获取了todos列表和fetchTodos函数.\ntips React 19.0后的版本不用再写provider了,可以直接写成以下形式\n1 2 3 4 5 6 7 8 return ( \u0026lt;TodosContext value={{ todos, fetchTodos }}\u0026gt; \u0026lt;Container\u0026gt; \u0026lt;AddTodo /\u0026gt; {/* ... */} \u0026lt;/Container\u0026gt; \u0026lt;/TodosContext\u0026gt; ); 当然,并没有简化太多,还是需要提前在子组件中解构\nUpdateTodo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 const UpdateTodo = ({ item, id, fetchTodos }: UpdateTodoProps) =\u0026gt; { const [todo, setTodo] = useState(item); const updateTodo = async () =\u0026gt; { await fetch(`http://localhost:8000/todo/${id}`, { method: \u0026#34;PUT\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify({ item: todo }), }); await fetchTodos(); }; return ( \u0026lt;DialogRoot\u0026gt; \u0026lt;DialogTrigger asChild\u0026gt; \u0026lt;Button h=\u0026#34;1.5rem\u0026#34; size=\u0026#34;sm\u0026#34;\u0026gt; Update Todo \u0026lt;/Button\u0026gt; \u0026lt;/DialogTrigger\u0026gt; \u0026lt;DialogContent position=\u0026#34;fixed\u0026#34; top=\u0026#34;50%\u0026#34; left=\u0026#34;50%\u0026#34; transform=\u0026#34;translate(-50%, -50%)\u0026#34; bg=\u0026#34;white\u0026#34; p={6} rounded=\u0026#34;md\u0026#34; shadow=\u0026#34;xl\u0026#34; maxW=\u0026#34;md\u0026#34; w=\u0026#34;90%\u0026#34; zIndex={1000} \u0026gt; \u0026lt;DialogHeader\u0026gt; \u0026lt;DialogTitle\u0026gt;Update Todo\u0026lt;/DialogTitle\u0026gt; \u0026lt;/DialogHeader\u0026gt; \u0026lt;DialogBody\u0026gt; \u0026lt;Input pr=\u0026#34;4.5rem\u0026#34; type=\u0026#34;text\u0026#34; placeholder=\u0026#34;Add a todo item\u0026#34; aria-label=\u0026#34;Add a todo item\u0026#34; value={todo} onChange={(event) =\u0026gt; setTodo(event.target.value)} /\u0026gt; \u0026lt;/DialogBody\u0026gt; \u0026lt;DialogFooter\u0026gt; \u0026lt;DialogActionTrigger asChild\u0026gt; \u0026lt;Button variant=\u0026#34;outline\u0026#34; size=\u0026#34;sm\u0026#34;\u0026gt; Cancel \u0026lt;/Button\u0026gt; \u0026lt;/DialogActionTrigger\u0026gt; \u0026lt;Button size=\u0026#34;sm\u0026#34; onClick={updateTodo}\u0026gt; Save \u0026lt;/Button\u0026gt; \u0026lt;/DialogFooter\u0026gt; \u0026lt;/DialogContent\u0026gt; \u0026lt;/DialogRoot\u0026gt; ); }; 看着长,其实真正有效的内容只有以下部分:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const UpdateTodo = ({ item, id, fetchTodos }: UpdateTodoProps) =\u0026gt; { // const [todo, setTodo] = useState(item); const updateTodo = async () =\u0026gt; { await fetch(`http://localhost:8000/todo/${id}`, { method: \u0026#34;PUT\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify({ item: todo }), }); await fetchTodos(); }; return ( // \u0026lt;DialogRoot\u0026gt; // \u0026lt;DialogContent\u0026gt; \u0026lt;DialogBody\u0026gt; \u0026lt;Input value={todo} onChange={(event) =\u0026gt; setTodo(event.target.value)} /\u0026gt; \u0026lt;/DialogBody\u0026gt; \u0026lt;DialogFooter\u0026gt; \u0026lt;Button onClick={updateTodo}\u0026gt; Save \u0026lt;/Button\u0026gt; \u0026lt;/DialogFooter\u0026gt; // \u0026lt;/DialogContent\u0026gt; // \u0026lt;/DialogRoot\u0026gt; ); }; 可以看到在按下save时会执行updateTodo函数,向后端传入要更改对象的id和内容. 同时,在输入框内输入的值会传入对应的to变量\nDeleteTodo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const DeleteTodo = ({ id, fetchTodos }: DeleteTodoProps) =\u0026gt; { const deleteTodo = async () =\u0026gt; { await fetch(`http://localhost:8000/todo/${id}`, { method: \u0026#34;DELETE\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify({ id: id }), }); await fetchTodos(); }; return ( \u0026lt;Button h=\u0026#34;1.5rem\u0026#34; size=\u0026#34;sm\u0026#34; marginLeft={2} onClick={deleteTodo}\u0026gt; Delete Todo \u0026lt;/Button\u0026gt; ); }; 与前面的内容基本一样,不用讲了\nTodoHelper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function TodoHelper({ item, id, fetchTodos }: TodoHelperProps) { return ( \u0026lt;Box p={1} shadow=\u0026#34;sm\u0026#34;\u0026gt; \u0026lt;Flex justify=\u0026#34;space-between\u0026#34;\u0026gt; \u0026lt;Text mt={4} as=\u0026#34;div\u0026#34;\u0026gt; {item} \u0026lt;Flex align=\u0026#34;end\u0026#34;\u0026gt; \u0026lt;UpdateTodo item={item} id={id} fetchTodos={fetchTodos} /\u0026gt; \u0026lt;DeleteTodo id={id} fetchTodos={fetchTodos} /\u0026gt; \u0026lt;/Flex\u0026gt; \u0026lt;/Text\u0026gt; \u0026lt;/Flex\u0026gt; \u0026lt;/Box\u0026gt; ); } 仅仅是把UpdateTodo和DeleteTodo两个组件封装在一起了而已.\n回到Todos函数 现在让我们看一下Todos函数的返回值:\n1 2 3 4 5 6 7 8 9 10 11 12 return ( \u0026lt;TodosContext.Provider value={{ todos, fetchTodos }}\u0026gt; \u0026lt;Container maxW=\u0026#34;container.xl\u0026#34; pt=\u0026#34;100px\u0026#34;\u0026gt; \u0026lt;AddTodo /\u0026gt; \u0026lt;Stack gap={5}\u0026gt; {todos.map((todo: Todo) =\u0026gt; ( \u0026lt;TodoHelper item={todo.item} id={todo.id} fetchTodos={fetchTodos} /\u0026gt; ))} \u0026lt;/Stack\u0026gt; \u0026lt;/Container\u0026gt; \u0026lt;/TodosContext.Provider\u0026gt; ); 可以看到,返回值是一个AddTodo输入框,再加上一个纵向的Todo事项列表而已,每个Todo有一个item标题,有两个操作组件:更新和删除\n事实上,看到这里你会发现,虽然使用了createContext,但其实只在AddTodo组件里和Stack里使用了.我们当然也可以单独让fetchTodos成为一个createContext,避免多次传递,但这是个人喜好问题,你想怎么写就怎么写.\n前端是如何发送请求报文的 现代的前端框架之所以如此复杂和臃肿,不仅仅是因为为了美观和修饰,还为的是能够实现本地启动web服务器并生成网页的功能:\nnodejs(使用cpp编写)通过与操作系统内核交互,提供一个类似于浏览器进程的运行环境 vite在运行时扫描下载的库和本地文件,构建一个静态页面,并根据用户交互或者外部API来将请求或者响应传入本地代码进行处理,之后重新渲染对应组件并将请求报文传给后端 总结 看完这个项目我们基本了解了最基础的前后端通信问题,那么,接下来,我们要处理一个中级的工程项目,解决数据库,后端,前端三者之间的复杂通信问题.\nTO BE CONTINUED ","date":"2026-03-21T08:00:00Z","image":"/p/2026-03-21-%E5%89%8D%E5%90%8E%E7%AB%AF%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86/53692621_p0-%E9%97%87%E3%81%AE%E6%B8%93%E8%B0%B7.webp","permalink":"/p/2026-03-21-%E5%89%8D%E5%90%8E%E7%AB%AF%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86/","title":"2026-03-21 前后端通信原理"},{"content":"Intro 有一个项目要做成网站部署上线,但我从来没有部署过一个完整的网站,最多是用github.io部署一个静态的hexo博客再加上用vercel部署评论系统而已.所以折腾了差不多半天. 工具集\n查询自己的域名是否同步到主流dns服务器 生成随机的香港地址 vultr教程 首先我们需要明白一点计算机网络的知识: 如何让别人看到你的网页?\n首先你需要让自己的网站部署在一个云服务器或者本地服务器上,为什么需要服务器呢,是因为服务器具有以下几个功能: 提供一个合法的ip地址 一般使用Linux系统,从而保证能够在低功耗下24小时不间断运行 其次为了让自己的网站变得容易被搜索引擎爬取并让他人发现,我们需要在域名注册商内注册购买自己的域名,并通过DNS映射到服务器ip地址 其实部署到这里就结束了,如果有需要可以在服务器上配置防火墙,预防ddos攻击,预存快照等功能 所以,为了能够让自己的网页顺利上线,你需要保证自己的代码能够在通用的linux版本上运行,其次,需要选择一个适合自己架构的服务器,最次要的要素才是选择域名.\n由于我的项目使用了docker compose,故可以轻松在linux上运行,但很可惜的是不少服务器都不支持直接用compose部署(或者不支持Alipay\u0026hellip;).\n我辗转找到了vultr这样一个支持compose直接部署的服务器厂商,它的收费政策先充钱进账户,按使用量收费.\n但vultr的部分ip有时会被封,故你如果当你ping不通ip地址的时候,需要销毁后重新建一个虚拟机.\n服务器正常运行后,便可以使用Dokploy来在服务器上部署自己的项目(如果不知道如何操作的可以看文末的ai教程备份)\n部署成功后就可以申请域名并绑定了,我用的是dynadot域名注册商,也是因为能用支付宝而且不用备案\u0026hellip;\n域名记录栏填入ip地址,子域名记录栏填入域名即可,这样别人无论访问www.域名.com还是直接访问域名.com都可以定向到你的网站了.\n如何远程连接服务器? 在服务器后台看到自己的ip地址和对应密码后,在cmd输入以下命令:\n1 ssh 你的用户名@你的服务器IP 之后再输入密码,就可以连上服务器并进行各种各样的操作了.\n如果你和我一样打算用compose.yml来部署网站并遇到了一些问题,欢迎在评论区提出!\nAI教程备份 🚀 第一步：Vultr 服务器选型与开机 你的项目包含 4 个容器（PostgreSQL + pgvector、FastAPI 后端、Next.js 前端、Nginx），其中 pgvector 和 Next.js 构建时比较吃内存。\n1. 推荐配置（性价比最高） 类型：Cloud Compute (Shared CPU) -\u0026gt; Regular Performance 节点：Singapore（新加坡） 或 Tokyo（东京）（对中国大陆延迟最低，通常在 50-80ms） 系统：Ubuntu 22.04 LTS 或 Ubuntu 24.04 LTS 配置：1 vCPU / 2GB RAM / 55GB SSD（价格：$12/月） 注：虽然有 $6/月（1GB RAM）的选项，但 1GB 内存跑 4 个容器极易 OOM（内存溢出） 导致数据库崩溃，强烈建议 2GB 起步。 2. 开机步骤 注册并登录 Vultr，绑定支付方式（支持支付宝/微信/信用卡）。 点击右上角 Deploy + -\u0026gt; Deploy New Server。 按上述推荐选择节点、系统和配置。 Server Settings：取消勾选 \u0026ldquo;Auto Backups\u0026rdquo;（可省 $2.4/月，后续可用 Dokploy 备份）。 点击右下角 Deploy Now。 等待 1-2 分钟，状态变为 \u0026ldquo;Running\u0026rdquo; 后，点击服务器名称，记录下 IP Address 和 Password。 🛠️ 第二步：安装 Dokploy（可视化面板） Dokploy 是一个开源的轻量级 PaaS 面板，能让你像用 Railway 一样通过网页管理 Docker Compose 项目。\n在你的电脑上打开终端（Windows 用 PowerShell，Mac 用 Terminal）。 SSH 连接 到你的 Vultr 服务器： 1 ssh root@你的服务器IP 输入 yes，然后粘贴 Vultr 面板里的密码。 运行 Dokploy 一键安装脚本： 1 curl -sSL https://dokploy.com/install.sh | sh 安装大约需要 3-5 分钟。完成后，在浏览器中访问：http://你的服务器IP:3000 首次访问会要求你设置管理员账号和密码，设置完成后进入 Dokploy 控制台。 📦 第三步：部署青松项目 1. 准备项目文件 将你本地的 x 文件夹或者上传到 GitHub 私有仓库\n2. 在 Dokploy 中创建项目 在 Dokploy 左侧菜单点击 Projects -\u0026gt; Create Project，命名为 x。 进入项目，点击 Create Service -\u0026gt; 选择 Compose。 命名为 x-app。 3. 配置代码源 如果你用 GitHub：选择 Github，绑定你的账号，选择仓库和分支。 4. 配置环境变量 在 Dokploy 的 Environment 选项卡中，粘贴你的 .env 文件内容。\n5. 启动部署 点击右上角的 Deploy 按钮。Dokploy 会自动拉取镜像、构建前端和后端、启动数据库。你可以在 Logs 选项卡中实时查看构建进度。\n🌐 第四步：绑定域名与 HTTPS 1. 购买与解析域名 在 Cloudflare 或阿里云购买一个便宜的域名（如 .xyz 或 .top，首年通常不到 10 块钱）。 在域名 DNS 设置中，添加一条 A 记录： Name: @ 或 chat（取决于你想用主域名还是子域名） Content: 你的 Vultr 服务器 IP Proxy status: 仅限 DNS（关闭小黄云，让 Dokploy 自己处理 SSL） 2. 在 Dokploy 中配置域名 回到 Dokploy 面板，进入你的 x-app Compose 服务。 找到 Domains 选项卡。 添加你的域名（例如 chat.yourdomain.com）。 目标端口（Target Port） 填写 80（因为你的 compose.yml 中 Nginx 暴露的是 80 端口）。 勾选 Enable SSL（Dokploy 会自动向 Let\u0026rsquo;s Encrypt 申请免费的 HTTPS 证书）。 点击 Save。 如果使用了nginx,在domains选项只需要像下面这样填写就行了: 其中马赛克部分填写自己申请的域名 ","date":"2026-03-20T08:00:00Z","image":"/p/2026-03-20-compose%E7%B1%BB%E5%9E%8B%E7%BD%91%E7%AB%99%E4%B8%8A%E7%BA%BF%E5%B0%9D%E8%AF%95/53656198_p0-%E5%BF%85%E6%AE%BA%E3%80%82.webp","permalink":"/p/2026-03-20-compose%E7%B1%BB%E5%9E%8B%E7%BD%91%E7%AB%99%E4%B8%8A%E7%BA%BF%E5%B0%9D%E8%AF%95/","title":"2026-03-20 compose类型网站上线尝试"},{"content":"从来没用过manus,这次试试水,我只给出了以下命令,用的是免费的lite版本:\n帮我做一个量化A股网站,要求使用了fastapi作为后端,nextjs作为前端,数据库用postgresql,要求使用docker配置连接,用compose.yml部署,每次生成时如果有必要重构立刻重构,保证项目结构清晰分明,routes文件夹写明,提前针对不同路由拆分好routes为多个文件.\n然后生成了这个架构: 还是挺像模像样的.\n后记:实际上有不少bug 实战(3/20) 由于有朋友想让我帮他的大创弄个智能体出来,尽管我基本学习了前端和后端需要的知识,但真要我写还是无从下手的.\n于是我就打算用manus来试试水,先充了一个20刀的会员(竟然可以用支付宝).\n然后写了个项目需求md后贴给它,这是需求的大致结构,我没有全部贴是为了照顾别人的隐私.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ## 1. 核心技术栈 (Physical Stack) * **前端**: Next.js (App Router), Tailwind CSS (UI 框架)。 * **后端**: FastAPI (异步 Web 框架), SQLModel (ORM 映射),PostgreSql作为存储数据库 * **AI 编排**: LangChain (LLM 逻辑链), 允许使用 DeepSeek API (核心推理引擎)和一个本地部署的ollama模型接口 * **基础设施**: Docker, `compose.yml` (容器化与端口转发)。 * **环境文件**: 将要用到的基础配置变量写入环境文件env,并让数据库和python从这个环境库中读取配置,而非在代码中填写 **架构示例** Socrates_AI/ ├── docker-compose.yml ├── .env # 全局环境变量映射中心 ├── prompt.json # 系统人格与逻辑配置文件 ├── data/ # 本地 RAG 训练资料文件夹 ├── scripts/ │ └── deploy.sh # Linux 一键部署脚本 ├── frontend/ # Next.js 源码 │ └── Dockerfile └── backend/ # FastAPI 源码 ├── main.py ├── models.py # SQLModel 定义 └── Dockerfile 该分文件和文件夹的时候就需要分 ### 2. 物理布局与 UI 要求 (Gemini Style) * **深色极简**: 采用 `zinc-900` 主色调，模拟高质感的思辨氛围。 * **两栏架构**: * **左侧边栏**: 物理展示对话历史、新建对话按钮。 * **中央对话区**: 气泡式对话流，底部固定悬浮输入框。 * **输入组件**: * 支持多行文本输入。 * 具备**附件上传图标**，物理支持文件拖拽或点击上传。 * 增加一个旁栏图标,对应的是已经本地配置好的智能体 然后在我要求manus向我核实一些需要我确认的内容后,花了差不多10min就完成了主要的架构. 但非常遗憾的是,我前前后后调了11版才实现了基础的调api进行对话的功能,耗时总计为2个多小时. 不得不承认,现在的智能体真的比一年前要聪明了不少,至少看上去是这样,而且经过一系列排查之后,总是能够找到实际的解决方案. 期间我基本上是这么做的:\n使用docker运行当前版本并测试基本功能 查看终端报错或者按F12 将报错信息贴给他 manus自己修改生成下一版 最后,成功实现了本地对话: 部署智能体\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 针对你的情况（学生、1–2 个月、低流量、操作简单、要临时域名），最适合的方案是 Railway，理由如下： 推荐方案：Railway 网址：railway.app 为什么选它 有免费额度：注册后每月赠送 5 美元的使用额度，你这个项目（后端 + 前端 + 数据库）一个月的实际消耗大约在 3–5 美元之间，基本可以免费跑完，流量费用几乎为零。 自带临时域名：每个服务部署后 Railway 会自动分配一个 xxx.up.railway.app 的免费域名，并且自带 HTTPS，不需要买域名。 操作极简：把项目文件夹上传或推送到 GitHub，Railway 识别 compose.yml 后点几下鼠标就部署完成，全程不需要登录服务器、不需要敲命令。 用完即删：1–2 个月后直接在网页上删除项目，不产生任何后续费用。 操作步骤（简版） 第一步：将项目推送到 GitHub（免费私有仓库即可）。 第二步：用 GitHub 账号登录 railway.app，点击 New Project → Deploy from GitHub repo，选择你的仓库。 第三步：Railway 会自动识别 compose.yml，分别创建后端、前端、数据库、Nginx 四个服务。在每个服务的 Variables 页面填入 .env 中的环境变量（主要是 DEEPSEEK_API_KEY 和 SECRET_KEY）。 第四步：点击 Deploy，等待 3–5 分钟构建完成。Railway 会自动给 Nginx 服务分配一个 xxx.up.railway.app 的临时域名，直接访问即可。 唯一需要注意的事 Railway 的免费额度是按 CPU + 内存用量计时计算的，不是按流量。你的项目在无人访问时几乎不消耗资源，所以 5 美元的额度对于低流量的 1–2 个月使用完全够用。如果超出，Railway 会发邮件提醒，你可以选择充值（最低 5 美元）或直接关闭项目。 备选方案：Render（如果 Railway 注册有问题） render.com 操作逻辑类似，同样支持 Docker 部署和免费临时域名。免费套餐的服务在 15 分钟无流量后会自动休眠（首次访问需要等待约 30 秒唤醒），对于偶尔演示的场景可以接受，但不适合需要随时响应的场景。 结论：Railway 是你这个场景下操作最简单、费用最低、最省心的方案，临时域名开箱即用，1–2 个月后删掉项目即可，不会有任何遗留费用。 补充: Railway只支持dockerfile前后端分开部署,不支持compose.yml,真要用可以试试Vultr 在有一定docker,python,nextjs的前提下,我基本能够看懂错误出自什么地方,有哪些地方写错了,有哪些地方的架构不够好,哪些库是不必导入的. 但是如果你自己啥也不懂,就单纯是复制粘贴信息,甚至是截图的话,我想应该要花上两倍以上的时间,还不一定能够完全搞定.\n所以,我的体会是:\n即便AI能够包办整个代码的生成,但如果你连什么是好的代码,什么是应该使用的框架都不知道的话,我想AI对你的意义并没有那么大;相反,如果你能够精通所需的技术栈,再让AI帮你写代码,那么就真正做到了事半功倍,成功的实现了所谓的vibe coding.\n纸上得来终觉浅,绝知此事要躬行\n","date":"2026-03-15T08:00:00Z","image":"/p/2026-03-15-manus%E5%B0%9D%E8%AF%95/31778303_p0-%E9%9D%92%E9%9C%8A%E5%A4%A2.webp","permalink":"/p/2026-03-15-manus%E5%B0%9D%E8%AF%95/","title":"2026-03-15 manus尝试"},{"content":"基础React使用的是自创的jsx,在实际运行时需要经过编译器转译变成js. 实际上,jsx与原生js的最大区别在于可以将html元素作为组件,也就是说,React组件是一段可以使用标签进行扩展的JavaScript 函数.\nUI描绘 组件概念 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function Profile() { return ( \u0026lt;img src=\u0026#34;https://i.imgur.com/MK3eW3As.jpg\u0026#34; alt=\u0026#34;Katherine Johnson\u0026#34; /\u0026gt; ); } export default function Gallery() { return ( \u0026lt;section\u0026gt; \u0026lt;h1\u0026gt;Amazing scientists\u0026lt;/h1\u0026gt; \u0026lt;Profile /\u0026gt; \u0026lt;Profile /\u0026gt; \u0026lt;Profile /\u0026gt; \u0026lt;/section\u0026gt; ); } profile()函数返回的是img元素,并可以在Gallery()函数中使用的形式直接调用.\n组件的导入导出 1 2 3 4 5 6 7 import Gallery from \u0026#39;./Gallery.js\u0026#39;; //import Gallery from \u0026#39;./Gallery\u0026#39;; 这种写法在React中也可以,但不够符合原生ES规范 export default function App() { return ( \u0026lt;Gallery /\u0026gt; ); } 两种导出方式 类型 导出语句 导入语句 默认导出（default export） export default function Button() {} import Button from './Button.js'; 具名导出（named export） export function Button() {} import { Button } from './Button.js'; 默认导入时名称可以任意命名,而具名导入时名称需要完全一致并用{}标识\njsx基础语法 如何将网页元素作为返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export default function TodoList() { return ( // 这不起作用！ \u0026lt;h1\u0026gt;海蒂·拉玛的待办事项\u0026lt;/h1\u0026gt; \u0026lt;img src=\u0026#34;https://i.imgur.com/yXOvdOSs.jpg\u0026#34; alt=\u0026#34;Hedy Lamarr\u0026#34; class=\u0026#34;photo\u0026#34; \u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;发明一种新式交通信号灯 \u0026lt;li\u0026gt;排练一个电影场景 \u0026lt;li\u0026gt;改进频谱技术 \u0026lt;/ul\u0026gt; ); } 返回值实际上应该先写成以下形式:\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;\u0026gt; \u0026lt;h1\u0026gt;海蒂·拉玛的待办事项\u0026lt;/h1\u0026gt; \u0026lt;img src=\u0026#34;https://i.imgur.com/yXOvdOSs.jpg\u0026#34; alt=\u0026#34;Hedy Lamarr\u0026#34; class=\u0026#34;photo\u0026#34; \u0026gt; \u0026lt;ul\u0026gt; ... \u0026lt;/ul\u0026gt; \u0026lt;/\u0026gt; 这个空标签被称作 Fragment。React Fragment 允许你将子元素分组，而不会在 HTML 结构中添加额外节点。\n为什么多个 JSX 标签需要被一个父元素包裹? JSX 虽然看起来很像 HTML，但在底层其实被转化为了 JavaScript 对象，你不能在一个函数中返回多个对象，除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。\n上述写法其实还不够正确,因为JSX 要求标签必须正确闭合。像 这样的自闭合标签必须书写成 ,而像 oranges 这样只有开始标签的元素必须带有闭合标签，需要改为 oranges。\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;\u0026gt; \u0026lt;img src=\u0026#34;https://i.imgur.com/yXOvdOSs.jpg\u0026#34; alt=\u0026#34;Hedy Lamarr\u0026#34; class=\u0026#34;photo\u0026#34; /\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;发明一种新式交通信号灯\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;排练一个电影场景\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;改进频谱技术\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/\u0026gt; 由于class是js保留字,故这里还需要将class改为className,使用的是驼峰命名法.\n1 2 3 4 5 \u0026lt;img src=\u0026#34;https://i.imgur.com/yXOvdOSs.jpg\u0026#34; alt=\u0026#34;Hedy Lamarr\u0026#34; className=\u0026#34;photo\u0026#34; /\u0026gt; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export default function TodoList() { return ( \u0026lt;\u0026gt; \u0026lt;h1\u0026gt;海蒂·拉玛的待办事项\u0026lt;/h1\u0026gt; \u0026lt;img src=\u0026#34;https://i.imgur.com/yXOvdOSs.jpg\u0026#34; alt=\u0026#34;Hedy Lamarr\u0026#34; className=\u0026#34;photo\u0026#34; /\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;发明一种新式交通信号灯\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;排练一个电影场景\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;改进频谱技术\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/\u0026gt; ); } 这是最终成果.\n如何在网页元素或者组件中写入js变量 1 2 3 4 5 6 export default function TodoList() { const name = \u0026#39;Zara\u0026#39;; return ( \u0026lt;h1\u0026gt;{name}的待办事项列表\u0026lt;/h1\u0026gt; ); } 在 JSX 中，只能在以下两种场景中使用大括号：\n用作 JSX 标签内的文本：\u0026lt;h1\u0026gt;{name}'s To Do List\u0026lt;/h1\u0026gt; 是有效的，但是 \u0026lt;{tag}\u0026gt;Gregorio Y. Zara's To Do List\u0026lt;/{tag}\u0026gt; 无效 用作紧跟在 = 符号后的 属性：src={avatar} 会读取 avatar 变量，但是 src=\u0026quot;{avatar}\u0026quot; 只会传一个字符串 {avatar} 1 2 3 4 5 6 7 8 9 10 11 12 export default function TodoList() { return ( \u0026lt;ul style={{ backgroundColor: \u0026#39;black\u0026#39;, color: \u0026#39;pink\u0026#39; }}\u0026gt; \u0026lt;li\u0026gt;Improve the videophone\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;Prepare aeronautics lectures\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;Work on the alcohol-fuelled engine\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; ); } 上述代码通过双大括号传入了一个css对象.\n在组件间传递props 1 2 3 4 5 export default function Profile() { return ( \u0026lt;Avatar /\u0026gt; ); } 这个代码没有向子组件转递任何props.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 export default function Profile() { return ( \u0026lt;Avatar person={{ name: \u0026#39;Lin Lanying\u0026#39;, imageId: \u0026#39;1bX5QH6\u0026#39; }} size={100} /\u0026gt; ); function Avatar({ person, size }) { // 在这里 person 和 size 是可访问的 return ( \u0026lt;img className=\u0026#34;avatar\u0026#34; src={getImageUrl(person)} alt={person.name} width={size} height={size} /\u0026gt; ); } 你可以将 props 想象成可以调整的“旋钮”。它们的作用与函数的参数相同 —— 事实上，props 正是 组件的唯一参数！ React 组件函数接受一个参数，一个 props 对象：\n1 2 3 4 5 function Avatar(props) { let person = props.person; let size = props.size; // ... } 通常你不需要整个 props 对象，所以可以将它解构为单独的 props,也就是说,下面两段代码是等价的:\n1 2 3 function Avatar({ person, size }) { // ... } 1 2 3 4 5 function Avatar(props) { let person = props.person; let size = props.size; // ... } 同时,还可以写入默认值,如:\n1 2 3 function Avatar({ person, size = 100 }) { // ... } 还可以使用展开语法,将传入的props展开为对象后再传入:\n1 2 3 4 5 6 7 function Profile(props) { return ( \u0026lt;div className=\u0026#34;card\u0026#34;\u0026gt; \u0026lt;Avatar {...props} /\u0026gt; \u0026lt;/div\u0026gt; ); } 字符串变量与普通变量的传入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function AlertButton({ message, children }) { return ( \u0026lt;button onClick={() =\u0026gt; alert(message)}\u0026gt; {children} \u0026lt;/button\u0026gt; ); } export default function Toolbar() { return ( \u0026lt;div\u0026gt; \u0026lt;AlertButton message=\u0026#34;正在播放！\u0026#34;\u0026gt; 播放电影 \u0026lt;/AlertButton\u0026gt; \u0026lt;AlertButton message=\u0026#34;正在上传！\u0026#34;\u0026gt; 上传图片 \u0026lt;/AlertButton\u0026gt; \u0026lt;/div\u0026gt; ); } 注意到这里的AlertButton组件在调用时直接传入message字符串,而不是写message={\u0026quot;正在播放！\u0026quot;}这种普通的传入变量方式,是因为字符串变量默认是可以直接传入的.\n条件渲染 在一些情况下，你不想有任何东西进行渲染。比如，你不想显示已经打包好的物品。但一个组件必须返回一些东西。这种情况下，你可以直接返回 null。\n1 2 3 4 if (isPacked) { return null; } return \u0026lt;li className=\u0026#34;item\u0026#34;\u0026gt;{name}\u0026lt;/li\u0026gt;; 但事实上还可以这么写:\n1 2 3 4 5 return ( \u0026lt;li className=\u0026#34;item\u0026#34;\u0026gt; {name} {isPacked \u0026amp;\u0026amp; \u0026#39;✅\u0026#39;} \u0026lt;/li\u0026gt; ); 也就是说将js变量直接与字符串绑定,仅在isPacked为true时渲染勾选符号.\nJavaScript 会自动将左侧的值转换成布尔类型以判断条件成立与否。然而，如果左侧是 0，整个表达式将变成左侧的值（0），React 此时则会渲染 0 而不是不进行渲染。 例如，一个常见的错误是 messageCount \u0026amp;\u0026amp; New messages\n。其原本是想当 messageCount 为 0 的时候不进行渲染，但实际上却渲染了 0。 为了更正，可以将左侧的值改成布尔类型：messageCount \u0026gt; 0 \u0026amp;\u0026amp; New messages\n。\n渲染列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { people } from \u0026#39;./data.js\u0026#39;; import { getImageUrl } from \u0026#39;./utils.js\u0026#39;; export default function List() { const chemists = people.filter(person =\u0026gt; person.profession === \u0026#39;化学家\u0026#39; ); const listItems = chemists.map(person =\u0026gt; \u0026lt;li key={person.id}\u0026gt; \u0026lt;img src={getImageUrl(person)} alt={person.name} /\u0026gt; \u0026lt;p\u0026gt; \u0026lt;b\u0026gt;{person.name}:\u0026lt;/b\u0026gt; {\u0026#39; \u0026#39; + person.profession + \u0026#39; \u0026#39;} 因{person.accomplishment}而闻名世界 \u0026lt;/p\u0026gt; \u0026lt;/li\u0026gt; ); return \u0026lt;ul\u0026gt;{listItems}\u0026lt;/ul\u0026gt;; } 这个代码是这一小节的核心内容,filter()和map()经常被用在react元素中用来渲染数据库的数据\n添加交互 事件处理 1 2 3 4 5 6 7 export default function Button() { return ( \u0026lt;button\u0026gt; 未绑定任何事件 \u0026lt;/button\u0026gt; ); } =\u0026gt;\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export default function Button() { function handleClick() { alert(\u0026#39;你点击了我！\u0026#39;); } return ( \u0026lt;button onClick={handleClick}\u0026gt; 点我 \u0026lt;/button\u0026gt; ); } //在button组件中传入了handleClick函数 //或者可以直接写为内联箭头函数 \u0026lt;button onClick={() =\u0026gt; { alert(\u0026#39;你点击了我！\u0026#39;); }}\u0026gt; =\u0026gt;\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function AlertButton({ message, children }) { return ( \u0026lt;button onClick={() =\u0026gt; alert(message)}\u0026gt; {children} \u0026lt;/button\u0026gt; ); } export default function Toolbar() { return ( \u0026lt;div\u0026gt; \u0026lt;AlertButton message=\u0026#34;正在播放！\u0026#34;\u0026gt; 播放电影 \u0026lt;/AlertButton\u0026gt; \u0026lt;AlertButton message=\u0026#34;正在上传！\u0026#34;\u0026gt; 上传图片 \u0026lt;/AlertButton\u0026gt; \u0026lt;/div\u0026gt; ); } 非常值得注意的是这一串代码: \u0026lt;button onClick={() =\u0026gt; alert(message)}\u0026gt; 这里的message并没有用{}包裹,是因为外面已经有一层{}了,而react默认{}里就可以直接写入js变量了,如果再把message用{}包裹,就是把message当成对象来处理了.\n1 2 3 \u0026lt;AlertButton message=\u0026#34;正在播放！\u0026#34;\u0026gt; 播放电影 \u0026lt;/AlertButton\u0026gt; 同时,这里明面上只传入了message一个prop,但children作为react的约定变量,可以直接隐式传入组件内部包裹的字符串,故还是传入了两个变量.\nstate使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import { sculptureList } from \u0026#39;./data.js\u0026#39;; export default function Gallery() { let index = 0; function handleClick() { index = index + 1; } let sculpture = sculptureList[index]; return ( \u0026lt;\u0026gt; \u0026lt;button onClick={handleClick}\u0026gt; Next \u0026lt;/button\u0026gt; \u0026lt;h2\u0026gt; \u0026lt;i\u0026gt;{sculpture.name} \u0026lt;/i\u0026gt; by {sculpture.artist} \u0026lt;/h2\u0026gt; \u0026lt;h3\u0026gt; ({index + 1} of {sculptureList.length}) \u0026lt;/h3\u0026gt; \u0026lt;img src={sculpture.url} alt={sculpture.alt} /\u0026gt; \u0026lt;p\u0026gt; {sculpture.description} \u0026lt;/p\u0026gt; \u0026lt;/\u0026gt; ); } 由于index是局部变量,而react函数保证每次执行函数时都是从头开始渲染从而得到相同的结果,故每次执行时index都被初始化为0.\n为了保留渲染之前的数据,并触发react使用新数据重新渲染,需要引入useState组件.\nconst [state,setState]=useState(0) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { useState } from \u0026#39;react\u0026#39;; import { sculptureList } from \u0026#39;./data.js\u0026#39;; export default function Gallery() { const [index, setIndex] = useState(0); function handleClick() { setIndex(index + 1); } let sculpture = sculptureList[index]; return ( \u0026lt;\u0026gt; \u0026lt;button onClick={handleClick}\u0026gt; Next \u0026lt;/button\u0026gt; \u0026lt;h2\u0026gt; \u0026lt;i\u0026gt;{sculpture.name} \u0026lt;/i\u0026gt; by {sculpture.artist} \u0026lt;/h2\u0026gt; \u0026lt;h3\u0026gt; ({index + 1} of {sculptureList.length}) \u0026lt;/h3\u0026gt; \u0026lt;img src={sculpture.url} alt={sculpture.alt} /\u0026gt; \u0026lt;p\u0026gt; {sculpture.description} \u0026lt;/p\u0026gt; \u0026lt;/\u0026gt; ); } 在 React 中，useState 以及任何其他以“use”开头的函数都被称为 Hook,是一类特殊的函数,只在React渲染时有效. useState 的唯一参数是 state 变量的初始值,在本例中被设置为0. 同时,state是组件实例内部的状态,如果渲染一个组件两次,每个组件都有完全隔离的state状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { useState } from \u0026#39;react\u0026#39;; export default function Counter() { const [number, setNumber] = useState(0); return ( \u0026lt;\u0026gt; \u0026lt;h1\u0026gt;{number}\u0026lt;/h1\u0026gt; \u0026lt;button onClick={() =\u0026gt; { setNumber(number + 5); alert(number); }}\u0026gt;+5\u0026lt;/button\u0026gt; \u0026lt;/\u0026gt; ) } 第一次点击button时,alert的内容为0,因为useState只会为下一次渲染更改state的值,永远不会在一次渲染的过程中改变state变量.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { useState } from \u0026#39;react\u0026#39;; export default function Counter() { const [number, setNumber] = useState(0); return ( \u0026lt;\u0026gt; \u0026lt;h1\u0026gt;{number}\u0026lt;/h1\u0026gt; \u0026lt;button onClick={() =\u0026gt; { setNumber(n =\u0026gt; n + 1); setNumber(n =\u0026gt; n + 1); setNumber(n =\u0026gt; n + 1); }}\u0026gt;+3\u0026lt;/button\u0026gt; \u0026lt;/\u0026gt; ) } 但是,上述代码会真正执行+3,因为n =\u0026gt; n + 1在这里作为更新函数,会被加入更新队列,react只有在执行完更新队列后才会开始渲染,因此,下述代码的结果是每次加6.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { useState } from \u0026#39;react\u0026#39;; export default function Counter() { const [number, setNumber] = useState(0); return ( \u0026lt;\u0026gt; \u0026lt;h1\u0026gt;{number}\u0026lt;/h1\u0026gt; \u0026lt;button onClick={() =\u0026gt; { setNumber(number + 5); setNumber(n =\u0026gt; n + 1); }}\u0026gt;增加数字\u0026lt;/button\u0026gt; \u0026lt;/\u0026gt; ) } state进阶(待补充) 与外部系统交互 useRef 当你希望组件“记住”某些信息，但又不想让这些信息 触发新的渲染 时，你可以使用 ref\n1 2 3 4 5 6 7 import { useRef } from \u0026#39;react\u0026#39;; const ref = useRef(0); //useRef 返回一个这样的对象: // { // current: 0 // 你向 useRef 传入的值 // } 像 state 一样，你可以让它指向任何东西：字符串、对象，甚至是函数。与 state 不同的是，ref 是一个普通的 JavaScript 对象，具有可以被读取和修改的 current 属性。\nuseEffect 首先我们需要明白Events和Effect这两个概念的区别:\nEvents: 用户交互产生的效果 Effect: 组件本身产生的效果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import { useState, useRef, useEffect } from \u0026#39;react\u0026#39;; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() =\u0026gt; { if (isPlaying) { ref.current.play(); } else { ref.current.pause(); } }); return \u0026lt;video ref={ref} src={src} loop playsInline /\u0026gt;; } export default function App() { const [isPlaying, setIsPlaying] = useState(false); return ( \u0026lt;\u0026gt; \u0026lt;button onClick={() =\u0026gt; setIsPlaying(!isPlaying)}\u0026gt; {isPlaying ? \u0026#39;暂停\u0026#39; : \u0026#39;播放\u0026#39;} \u0026lt;/button\u0026gt; \u0026lt;VideoPlayer isPlaying={isPlaying} src=\u0026#34;https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4\u0026#34; /\u0026gt; \u0026lt;/\u0026gt; ); } useEffect会在每次渲染后运行 createContext 上下文使得组件能够无需通过显式传递参数的方式 将信息逐层传递,使用格式如下:\n1 const SomeContext = createContext(defaultValue); 用例\n1 2 3 4 5 6 7 8 9 10 11 import { createContext } from \u0026#39;react\u0026#39;; const ThemeContext = createContext(\u0026#39;light\u0026#39;); function App() { const [theme, setTheme] = useState(\u0026#39;light\u0026#39;); // …… return ( \u0026lt;ThemeContext value={theme}\u0026gt; \u0026lt;Page /\u0026gt; \u0026lt;/ThemeContext\u0026gt; ); } 老版本会出现类似Provider这样的东西,但从React 19开始可以直接这样写了.\ncreateRoot 1 2 3 4 5 6 7 8 import { createRoot } from \u0026#39;react-dom/client\u0026#39;; const domNode = document.getElementById(\u0026#39;root\u0026#39;); const root = createRoot(domNode); //createRoot返回一个带有两个方法的对象,这两个方法是：render 和 unmount,一个是渲染节点,一个是销毁节点 //root.render(\u0026lt;App /\u0026gt;); //root.unmount(); ","date":"2026-03-15T08:00:00Z","image":"/p/2026-03-15-react-learn/42340231_p0-%E9%87%91%E5%89%9B%E3%83%87%E3%83%BC%E3%82%B9.jpg","permalink":"/p/2026-03-15-react-learn/","title":"2026-03-15 react-learn"},{"content":"Outline ch1 概述 ch2 硬件环境 ch3 操作系统结构 ch4 进程基础 ch5 线程 ch6 CPU调度 ch7 进程同步 ch8 死锁 ch9 内存管理基础 ch10 虚拟内存管理 ch11 文件系统 ch12 设备管理 ch13 磁盘结构 按照课程大纲来看,只需要学到教材的第十三章就可以了\n参考教材:操作系统概念(第九版) 事实上,看原文会很累,废话太多,真东西太少,而且比较混乱,我只好移步中文翻译,节省点时间 archive1\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 # ch1\u0026amp;\u0026amp;ch2 操作系统:运行在计算机上的程序(称为内核(kernel)) ## 实时操作系统 (RTOS) 与 分时操作系统 (TSOS) * **分时系统 (Time-Sharing):** 追求**资源利用率**与**公平性**。通过将 CPU 时间划分为极小的“时间片”，轮流分配给多个用户或任务，营造“独占计算机”的错觉。其关键指标是**平均响应时间**。 * **实时系统 (Real-Time):** 追求**确定性 (Determinism)** 与**可预测性**。任务必须在严格定义的截止时间内完成。其关键指标是**最坏情况下的响应延迟**。 # ch3:进程 进程:操作相关资源执行某一命令的活动实体 ### 进程控制块(Process Control Block,PCB) 进程可以用PCB来表示,它包含许多与某个特定进程相关的信息: - 进程状态: new,running,waiting,terminated - 程序计数器(program counter,PC): 进程要执行的下个指令的地址 - 寄存器(CPU register) - CPU调度信息 ### 进程调度 #### 调度序列 进程在进入系统时,会被加入到一个作业队列(job queue)中. 最初,新进程会进入就绪队列(ready queue),直到被选中执行,之后可能发生以下事件: - 进程创建了一个新的子进程,并等待子进程终止 - 进程由于中断被强制返回就绪队列 - 发出使用某个设备的请求,进入设备队列 #### 调度类型 * **高级调度 (High-Level Scheduling)** * 也称为**作业调度**或**宏观调度**。 * **核心逻辑**：负责从后备队列中挑选作业进入内存，为其创建进程并分配必要的资源。 * **时间尺度**：通常是**分钟、小时或天**。 * **中级调度 (Intermediate-Level Scheduling)** * 涉及进程在**内、外存间的交换**（Swapping）。 * **资源管理维度**：从存储器资源管理的角度来看，把进程部分或全部**换出到外存**上，可为当前运行进程的执行提供所需内存空间；反之，将当前进程所需部分**换入到内存**。 * **物理约束**：指令和数据必须在内存里才能被处理机直接访问。 * **低级调度 (Low-Level Scheduling)** * 也称**微观调度**或**进程调度**。 * **资源分配维度**：从处理机资源分配角度来看，处理机需要经常选择**就绪进程或线程**进入运行状态。 * **时间尺度**：通常是**毫秒级**的。 * **实现要求**：由于低级调度算法使用频繁，要求在实现时做到**高效**。 ### 进程运行 #### 进程创建 当进程创建新进程时,有两种情况: 1. 父进程和子进程并发执行 2. 父进程等待子进程执行完毕 新进程的地址空间有两种可能: 1. 子进程与父进程有同样的程序和数据 2. 子进程加载的是另一个新程序 #### 进程终止 \u0026gt;Process executes last statement and asks the operating system to delete it (exit). Parent may terminate execution of children processes (abort). - Child has exceeded allocated resources. - Task assigned to child is no longer required. - Parent is exiting. - Operating system does not allow child to continue if its parent terminates,which is called as **Cascading termination**. - [chrome多线程架构](https://developer.chrome.com/blog/inside-browser-part1?hl=zh-cn) ### 进程间通信(InterProcess Communication,IPC) 与其他进程共享数据的进程为协作进程,协作进程需要有一种进程间通信的机制,允许进程之间交换数据,有两种基本模型: 1. 共享内存模型: 建立起一块供协作进程共享的内存区域. 2. 消息传递模型: 通过消息队列来处理. #### 共享内存模型 一种方法是通过一个缓冲区供producer存放信息和consumer提取信息,这里的producer和consumer是相对的,协作进程可以同时扮演这两个角色 #### 消息传递系统(待补充) # ch4:线程 ### 概述 线程:cpu使用的基本单元,包括线程ID,程序计数器,寄存器组和堆栈,与同一进程的其他线程共享代码段,数据段和其他操作系统资源. - 如果一个进程有多个线程,那么就能同时执行多个任务 ### 多核编程 对于单核系统,并发仅意味着线程随着时间推移交错执行,因为内核在某一时刻只能执行单个线程;但对于多核系统,并发可以让线程并行运行 #### 并行类型 **数据并行**: 将数据分布于多个核上,在每个核上执行相同操作 **任务并行**: 不同核上的每个线程都执行一个单独的操作,可以操作相同的,也可以操作不同的数据 ### 多线程模型 用户层的用户线程需要内核层的内核线程来支持和管理,有三种常见的关系模型 #### 多对一模型 映射多个用户级线程到一个内核线程,显然这无法利用多核操作系统 #### 一对一模型 一个用户线程对应一个内核线程,唯一缺点是创建一个用户线程就要创 建一个相应的内核线程,会影响应用程序的性能,故需要限制某一时刻下可供使用的线程数量,Linux和Windows是采用这一模型的代表 #### 多对多模型 该模型可以将多个用户线程映射到多个内核线程中 ### 线程库 线程库为程序员提供创建和管理线程的API,有两种方法实现线程库: 1. 在用户层实现一个无内核支持的库,手写寄存器调用之类的操作 2. 在内核层实现一个库,创建线程时会发生系统调用 # ch5:CPU如何调度进程 ### 调度算法 #### 先到先服务(First Come First Served ,FCFS) 当一个进程进入准备队列时,会被链接到队列尾部. **缺点**: 平均等待事件很长,一旦CPU分配给了一个进程,进程就会一直运行直到CPU释放,不适用于分时系统 #### 最短作业优先调度(Shortest Job First ,SJF) 当CPU空闲时,会分配给执行时间最短的进程. **缺点**: 很难判断新进程的具体执行时间,只能通过估计来处理 可分为抢占的和非抢占的,对于抢占SJF算法来说,如果当前执行的进程所需剩余执行时间比新进程长,就会被抢占. #### 优先级调度(priority scheduling) 通过对进程标识优先级,保证优先级最高的进程先执行,同样可以分为抢占的和非抢占的. **缺点**: 无穷阻塞,即低优先级进程可能永远无法执行,解决方法是老化(aging),即根据等待时间提高等待进程的优先级 #### 轮转调度(Round Robin,RR) 这个算法是专门为分时系统设计的,结合了FCFS调度和抢占的概念: 将一个较小时间单元(如10-100ms)定义为时间片,CPU调度程序为每个进程分配不超过一个时间片的占用时间,当一个进程占用时间超过一个时间片时,将会被抢占,并被加到队列尾部. 关键在于要根据进程切换的时间花费来设计时间片大小,从而保证进程切换不会过度拖延平均的进程运行时间. #### 多级队列调度(multilevel queue) 根据进程的类型,大小,将准备队列分成具有优先级别划分的多个单独队列,每个队列使用独特的调度算法. #### 多级反馈队列调度(multilevel feedback queue) 在多级队列调度中,进程进入系统时会被永久分配到某个队列. 但现在讨论的反馈队列调度算法允许进程在队列之间迁移,将等待过长的进程放入高优先级队列,将用时过长的进程放入低优先级队列. - 是最通用,但也是最复杂的算法 ### 线程调度(待补充) ### 多处理器调度 当操作系统有多个核即有多个CPU时,进程的调度会变得更加复杂,有两种方法,一种是让一个处理器负责处理所有的调度决定,I/O处理核其他系统活动,而其他的处理器只执行用户代码.这被称为**非对称多处理**.因为只有一个处理器能够访问系统数据,减少了数据共享的负担. 另一种方法是使用对称多处理(Symmetric MultiProcessing,SMP),每个处理器自我调度,可以使用一个共享的准备队列,也可以每个处理器单独使用一个准备队列. 几乎所有的现代操作系统都采用这一方案. #### 处理器亲和性(processor affinity) 由于将一个进程从一个处理器转移到另一个处理器需要在处理器之间迁移缓存,代价过高,因此需要让一个进程一直运行在同一个处理器上,这被称为**处理器亲和性**. #### 负载平衡(load balance) 对于共享队列的多处理器操作系统,由于处理器需要一直从队列中取得最新的进程,故没有处理器会保持空闲. 但对于具有单独准备队列的多处理器操作系统,需要决定每个处理器的负载分配,避免有处理器的负载太高或者太低. 有两种方法: 1. 推迁移(push migration): 超载处理器将进程push到空闲处理器 2. 拉迁移(pull migration): 空闲处理器从超载处理器pull进程. 这两种方法经常被同时使用,但因为发生了进程在处理器之间的迁移,会抵消**处理器亲和性**带来的好处. #### 多核处理器 在多核处理器中应用多线程有两种方法: 1. 粗粒度(coarse-grained): 线程一直在处理器上执行,直到突然遇到长时间的中断(这里的中断仅仅是指处理器停止运行,而非通常意义的主动性中断),这时处理器会切换执行另一个线程. 2. 细粒度(fine-grained): 通过精细化线程的运行逻辑,减小线程的切换成本 ### 实时操作系统的CPU调度(过) # ch6:同步 ### 临界区问题 假设某个系统有n个进程,每个进程有一段代码,称为**临界区**(critical section).当一个进程在临界区执行时,其他进程不允许在各自的临界区内执行,因为这个正在执行的进程有可能正在修改公共资源,不能被打扰. 临界区问题的具体情景如下: 在进入临界区前,每个进程需要进行访问申请,实现访问申请的代码段称为**进入区**;在临界区执行完后有**退出区**,其他代码为**剩余区**. 因此,我们需要实现以下三个要求: 1. 互斥: 保证某一时刻只有一个进程能够在临界区执行 2. 提升: 如果没有进程在临界区执行,那么只有不处于剩余区的进程可以选择是否进入临界区. 3. 有限等待: 如果有一个进程位于进入区,那么其他进程允许进入临界区的次数有一个上限值,从而保证该进程可以进入临界区 ### 基于软件的临界区问题解决方案: Peterson 不妨假设有两个交替运行的进程P0和P1,这两个进程共享以下数据项: ```java int turn; boolean flag[2]; //turn表示允许让哪个进程进入临界区,flag表示哪个进程位于进入区. archive2\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 那么,只有当turn为i且flag[i]=1的时候这个进程才能真正执行. 现在我们需要证明这个解决方案是有效的,即证明前文提及的三点. 1. 当flag[0]和flag[1]都为1时,由于turn只能有一个值,故只有一个进程可以在临界区执行 2. 当进程P0在临界区执行完毕后,它进入退出区,并切换turn为1.此时flag[0]==0,而如果P1在进入区的话,那么flag[1]==1,而turn为1,故P1进入临界区执行,执行完后再将turn置为0.从而实现循环运行. ### 基于硬件的解决方案(待补充) ### # ch7:死锁(deadlock)(3/22) - 从这里开始使用中英文版交杂的形式 当某个进程申请资源时,若该资源当前不可用,则进程进入等待队列,但如果这个资源一直被其他进程占有,这个进程将一直位于待定状态,这被称为**死锁**. ### System Model 进程使用资源的全过程: 1. 申请: 进程请求资源,如果资源暂时无法获取,那么进入等待队列 2. 使用: 进程使用资源 3. 释放: 进程释放资源 - 那么很显然的是,只有我们能够保证进程在使用后会释放资源,就不会出现死锁问题,除非新进程不停的大量出现 ### 死锁出现的必要条件 1. 互斥: 某个资源同时刻只能被一个进程使用 2. 占有和等待: 一个进程应占有至少一个资源,并等待另一个被其他进程占有的资源 3. 不允许抢占: 资源只能在进程完成任务后自动释放 4. 循环队列: A set {P 0 , P 1 , ..., P n } of waiting processes must exist such that P 0 is waiting for a resource held by P 1 , P 1 is waiting for a resource held by P 2 , ..., P n−1 is waiting for a resource held by P n , and P n is waiting for a resource held by P 0 . 只有着四种条件都满足时才会出现死锁,这显然是一个非常极端的情况 #### Resource-Allocation Graph(过) 光画图的话我想考试不会考的 ### Methods for Handling Deadlocks(处理死锁问题) Generally speaking, we can deal with the deadlock problem in one of three ways: • We can use a protocol to prevent or avoid deadlocks, ensuring that the system will never enter a deadlocked state. • We can allow the system to enter a deadlocked state, detect it, and recover. • We can ignore the problem altogether and pretend that deadlocks never occur in the system. 由于大多数操作系统采用第三种方式也就是忽视死锁问题,因此开发应用程序时需要开发人员自己来处理死锁问题,有三种常见的操作: 1. 死锁预防(deadlock prevention): 确保死锁发生的必要条件不全部成立 2. 死锁避免(deadlock avoidance): 为操作系统提供相关进程信息来合理调度资源 3. 死锁恢复: 检测是否发生了死锁和如何从死锁中恢复 ### Deadlock Prevention 我们通过讨论发生死锁的4个条件来谈谈如何预防死锁: 1. **互斥**: 使用可共享的资源,如只读文件等 2. **持有并等待**: 当进程申请一个资源时,它不能占有其他资源,我们有两种方法: 1. 进程需要在执行前申请所需的所有资源 2. 进程仅在没有资源时才可以申请资源,或者在申请更多资源之前需要释放占用的资源 3. 但这两种方法会导致资源利用率低,某个进程进入**饥饿**状态等问题 3. 无抢占: 如果一个进程申请一个被占用的资源时,他目前占用的资源都可以被抢占 4. 循环等待: 对所有的资源类型进行排序,确保每个进程按顺序申请资源 #### 循环等待的解决方法详解 ### Deadlock Avoidance #### Safe State \u0026gt;A state is safe if the system can allocate resources to each process (up to its maximum) in some order and still avoid a deadlock. More formally, a system is in a **safe state** only if there exists a **safe sequence**. #### Banker’s Algorithm ### Deadlock Detection ### Recovery from Deadlock 可以使用两种方法: 1. 进程终止: 可以终止所有死锁进程或者一次终止一个进程直到消除死锁 2. 资源抢占: 抢占死锁进程占用的资源直到消除死锁 # ch8: Main Memory 本章主要讨论内存管理的各种方法 ## Background ### Basic Hardware CPU可以直接访问的存储器是内存和寄存器,但由于内存的执行时间一般超过一个时钟周期,故需要在CPU和内存之间增加一个高速缓存(cache) 为了保证不同进程之间不会相互影响,我们可以用下面所述的一种简单方法来处理: 1. 每个进程都应该有一个单独的内存空间,因此我们需要确定单一进程可以访问的地址范围 2. 我们使用两个寄存器: 基地址寄存器(base register)-含有最小的合法物理内存地址,地址范围寄存器(limit register)-指定地址的范围大小. 1. 例如: 基地址为10000,地址范围为1200,则该进程可以合法访问从10000到11200的所有地址 3. 当然,如果进程本身可以修改这两个寄存器,那就没有意义了,因此,只有操作系统可以加载并修改这两个寄存器 ### Address Binding 用户程序执行时,操作系统需要将指令和数据绑定到存储器里的对应地址上(不然怎么执行),有三种常见的绑定时机: 1. compile time: 编译时就将进程绑定在一个固定地址上,非常省心,但缺点是当地址变化时,就需要重新编译代码. 2. load time: 加载时用相对地址来绑定进程,当地址变化时,只需要重新加载程序代码就可以了 3. runtime time: 如果进程在执行时会跳跃地址,那么我们就只能在进程执行的时候再绑定地址,这也是现代操作系统采用的方法 ### Logical Versus Physical Address Space 当我们采用runtime time方案来绑定进程时,将程序生成的地址称为**虚拟地址**(也称为逻辑地址),虚拟地址对应的实际地址称为**物理地址**. 将虚拟地址映射到物理地址的硬件设备是Memery-Management Unit(MMU,内存管理单元),在这里我们先介绍一种简单的映射方案: - 将虚拟地址加上重定位寄存器(relocation register)内存储的偏移值,得到物理地址. 这种简单的方法能够有效的防止进程得知自己的实际执行地址,不让它搞破坏. ## Swapping(3/28) 在第五章里我们提到了调度队列的问题,进程会经常在磁盘和内存之间交换,在这一节我们再来深入探讨交换的详细过程 ### Standard Swapping(过) 这种交换方式在内存和备份存储之间进行,由于交换速度过慢,因此现代操作系统不使用标准交换 - ...我不知道这一节想表达什么 ## 内存分配 \u0026gt;In the **variable-partition** scheme, the operating system keeps a table indicating which parts of memory are available and which are occupied. Initially, all memory is available for user processes and is considered one large block of available memory, a **hole**. another approach ","date":"2026-03-14T08:00:00Z","image":"/p/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%95%99%E6%9D%90%E7%AC%94%E8%AE%B0/53031871_p0-%E9%BB%84%E6%98%8F%E7%9A%84%E5%AE%B4%E4%BC%9A.webp","permalink":"/p/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%95%99%E6%9D%90%E7%AC%94%E8%AE%B0/","title":"操作系统教材笔记"},{"content":" 教材:\u0026laquo; Database System Concepts \u0026raquo; 这本书讲的还是比较全面的,但废话也很多,部分地方讲的不够深 Introduction to the Relational Model Structure of Relational Databases A relational database consists of a collection of tables, each of which is assigned a unique name.\nIn mathematical terminology, a tuple is simply a sequence(or list) of values. A relationship between n values is represented mathematically by an n-tuple of values, that is, a tuple with n values, which corresponds to a row in a table.\nThus, in the relational model:\nthe term relation is used to refer to a table; the term tuple is used to refer to a row; the term attribute refers to a column of a table. For each attribute of a relation, there is a set of permitted values, called the domain of that attribute. Thus, the domain of the salary attribute of the instructor relation is the set of all possible salary values, while the domain of the name attribute is the set of all possible instructor names.\nA domain is atomic if elements of the domain are considered to be indivisible units.For example, suppose the table instructor had an attribute phone number, which can store a set of phone numbers corresponding to the instructor. Then the domain of phone number would not be atomic, since an element of the domain is a set of phonenumbers, and it has subparts, namely, the individual phone numbers in the set.\n如果一个域的值在逻辑上/使用上还能再拆成更小的有意义的单元，那它就不是原子的 The null value is a special value that signiﬁes that the value is unknown or does not exist.\nDatabase Schema schema: the logical design of the database 即数据库中所有关系,属性的处理 这个概念其实很重要,会在之后多次出现. Keys superkey: a set of one or more attributes that identify uniquely a tuple in the relation. For example, the ID attribute of the relation instructor is suﬃcient to distinguish one instructor tuple from another. Thus, ID is a superkey. The name attribute of instructor, on the other hand, is not a superkey, because several instructors might have the same name.\ncandidate keys: superkeys for which no proper subset is a superkey. It is possible that several distinct sets of attributes could serve as a candidate key. Suppose that a combination of name and dept name is suﬃcient to distinguish among members of the instructor relation. Then, both {ID} and {name, dept name} are candidate keys. Although the attributes ID and name together can distinguish instructor tuples, their combination, {ID, name}, does not form a candidate key, since the attribute ID alone is a candidate key.\nprimary key: denote a candidate key that is chosen by the database designer as the principal means of identifying tuples within a relation 在这里,primary key可以是由多个attribute组成的tuple The primary key should be chosen such that its attribute values are never, or are very rarely, changed.\nSchema Diagrams A database schema, along with primary key and foreign-key constraints, can be depicted by schema diagrams\nPrimary-key attributes are shown underlined. Foreign-key constraints appear as arrows from the foreign-key attributes of the referencing relation to the primary key of the referenced relation. We use a two-headed arrow, instead of a single-headed arrow, to indicate a referential integrity constraint that is not a foreign-key constraints.\nRelational Query Languages Query Language: A language used by a user to request information from a database. It operates at a higher level of abstraction than standard programming languages.\nCategorization:\nImperative Query Language: The user instructs the system to perform a specific sequence of operations. These languages maintain a notion of state variables that are updated during computation. Functional Query Language: Computation is expressed as the evaluation of functions. These functions operate on database data or results of other functions. They are side-effect free and do not update program state. Declarative Query Language: The user describes the desired information using mathematical logic without providing specific steps or function calls. The database system is responsible for determining the physical retrieval method. Pure Query Languages:\nRelational Algebra: A functional query language that forms the theoretical basis of SQL. Tuple Relational Calculus and Domain Relational Calculus: Declarative languages. Characteristics: These formal languages lack the \u0026ldquo;syntactic sugar\u0026rdquo; found in commercial languages but illustrate fundamental techniques for data extraction.\nThe Relational Algebra(3/26) The Select Operation σ dept_name = “Physics” (instructor)\nWe use the lowercase Greek letter sigma (σ) to denote selection.\nWe can ﬁnd all instructors with salary greater than $90,000 by writing: σ salary\u0026gt;90000 (instructor)\nThe Project Operation Projection is denoted by the uppercase Greek letter pi (Π). Π ID, name, salary (instructor) The project operation is a unary operation that returns its argument relation, with certain attributes left out. 我们也可以在投影的时候进行计算: Π ID,salary∕12 (instructor)\nComposition of Relational Operations “Find the names of all instructors in the Physics department.”: Π name (σ dept_name = “Physics” (instructor))\nThe Cartesian-Product Operation 也就是说关系上的笛卡尔积会生成一个nxm大小的单元组表 The Join Operation 注意这里的join没有过滤掉重复的id列! Set Operations 求并集(union)的前提条件:\n输入的两个关系具有相同数量的属性 当属性相关联时,两个关系中对应属性的类型必须相同 求交集(intersection): 求集差(set-diﬀerence): The Assignment Operation It is convenient at times to write a relational-algebra expression by assigning parts of it to temporary relation variables. The assignment operation, denoted by ←, works like assignment in a programming language.\nThe Rename Operation The rename operator refers to the results of relational-algebra expressions,denoted by the lowercase Greek letter rho (ρ). Introduction to SQL Overview of the SQL Query Language The SQL language has several parts: Data-definition language (DDL). The SQL DDL provides commands for deﬁning relation schemas, deleting relations, and modifying relation schemas. Data-manipulation language (DML). The SQL DML provides the ability to query information from the database and to insert tuples into, delete tuples from, and modify tuples in the database. 从这可以看到Data-manipulation是操作实例,Data-definition是操作关系模型 Integrity. The SQL DDL includes commands for specifying integrity constraints that the data stored in the database must satisfy. Updates that violate integrity constraints are disallowed. 即规范性. and so on SQL Data Definition Basic Types The SQL standard supports a variety of built-in types, including:\nchar(n)\nFixed-length character string.\nExactly n characters long.\nPadded with spaces if shorter value inserted.\nFull form: character(n).\nvarchar(n)\nVariable-length character string.\nMaximum n characters.\nStores only actual characters (no padding).\nFull form: character varying(n).\nint\nInteger.\nMachine-dependent range (usually 32-bit).\nFull form: integer.\nsmallint\nSmall integer.\nSmaller machine-dependent range than int (usually 16-bit).\nnumeric(p, d)\nFixed-point (exact decimal) number.\nTotal digits: p (including sign).\nDecimal places: d.\nExample: numeric(3,1) stores -99.9 to 99.9 exactly.\nCannot store 444.5 (exceeds p) or 0.32 (needs d≥2).\nreal\nSingle-precision floating-point.\nMachine-dependent precision (usually IEEE 32-bit).\ndouble precision\nDouble-precision floating-point.\nMachine-dependent higher precision (usually IEEE 64-bit).\nfloat(n)\nFloating-point with minimum precision of n decimal digits.\nImplementation chooses actual precision ≥ n.\nEach type may include a special value called the null value. A null value indicates an absent value that may exist but be unknown or that may not exist at all.\nWhen comparing a char type with a varchar type, one may expect extra spaces to be added to the varchar type to make the lengths equal, before comparison; however, this may or may not be done, depending on the database system. As a result, even if the same value “Avi” is stored in the attributes A and B above, a comparison A=B may return false. We recommend you always use the varchar type instead of the char type to avoid these problems.\n也就是说,可变数组在和固定长度数组比较时未必会加上对应长度的空格后再比较 Basic Schema Definition We deﬁne an SQL relation by using the create table command. The following command creates a relation department in the database:\n1 2 3 4 5 6 7 create table department ( dept_name vacchar(20), building varchar(15), budget numeric(12,2), primary key(dept_name) ); 结尾的;在大多数sql版本中是可选的 SQL supports a number of diﬀerent integrity constraints. In this section, we discuss only a few of them:\nprimary key: required to be nonnull and unique\n也就是说不可重复,不可为null Although the primary-key speciﬁcation is optional, it is generally a good idea to specify a primary key for each relation. foreign key(A) references s: the value of A in this relation must correspond to value of the primary key attributes in relation s.\n也就是说不允许把s关系中不存在的值写在这个关系中 not null: the null value is not allowed for that attribute\ndrop table: remove a relation from an SQL database\ndrop table r;只要这样就可以删除关系r了 delete from: retains relation r, but deletes all tuples in r.\ndelete from r; alter table: add or drop attributes to an existing relation\nalter table r add A D;:where r is the name of an existing relation, A is the name of the attribute to be added,and D is the type of the added attribute. alter table r drop A;: drop attributes from a relation eg 1 2 3 4 5 6 7 8 9 10 11 12 13 create table teaches ( ID varchar (5), course id varchar (8), sec id varchar (8), semester varchar (6), year numeric (4,0), primary key (ID, course id, sec id, semester, year), foreign key (course id, sec id, semester, year) references section, foreign key (ID) references instructor ); alter table teaches drop ID; drop table teaches; Basic Structure of SQL Queries The basic structure of an SQL query consists of three clauses: select, from, and where. A query takes as its input the relations listed in the from clause, operates on them as speciﬁed in the where and select clauses, and then produces a relation as the result.\n事实上在部分语句或者某些数据库系统中where,from是可选的,但select是必须出现的 Queries on a Single Relation 1 2 select dept_name from instructor; Since more than one instructor can belong to a department, a department name could appear more than once in the instructor relation. We can rewrite the preceding query as:\n1 2 select distinct dept name from instructor; The result of the above query would contain each department name at most once.\nAlso,SQL allows us to use the keyword all to specify explicitly that duplicates are not removed:\n1 2 select all dept name from instructor; Since duplicate retention is the default, we shall not use all in our examples.\nThe select clause may also contain arithmetic expressions involving the operators +, −, ∗, and / operating on constants or attributes of tuples:\n1 2 select ID, name, dept name, salary * 1.1 from instructor; 这不会改动原关系里的salary数值,只是在输出上将salary乘以1.1了\nThe where clause allows us to select only those rows in the result relation of the from clause that satisfy a speciﬁed predicate(断言)\n1 2 3 select name from instructor where dept name = \u0026#39;Comp. Sci.\u0026#39; and salary \u0026gt; 70000; SQL allows the use of the logical connectives and, or, and not in the where clause. The operands of the logical connectives can be expressions involving the comparison operators \u0026lt;, \u0026lt;=, \u0026gt;, \u0026gt;=, =, and \u0026lt;\u0026gt;.\nQueries on Multiple Relations 1 2 3 select instructor.dept_name, building, name from instructor, department where instructor.dept_name= department.dept_name; 注意到在不同关系中同时出现的attribute需要用关系名作为前缀来指明,而独特的attribute则不用\nfrom解析 The from clause by itself deﬁnes a Cartesian product of the relations listed in the clause. It is deﬁned formally in terms of relational algebra, but it can also be understood as an iterative process that generates tuples for the result relation of the from clause.\n1 2 3 4 5 6 for each tuple t1 in relation r1 for each tuple t2 in relation r2 … for each tuple tm in relation rm Concatenate t1 , t2 , … , tm into a single tuple t Add t into the result relation 也就是说from实际上会将所有关系用笛卡尔积制成一个nxm的表,如果不用where来过滤掉多余组合的话,在大型数据库中select将很难处理这么多数据\n1 2 SELECT s.name, c.course_name FROM students s, courses c; → 结果行数 = students 行数 × courses 行数（所有学生 × 所有课程的组合）\nAdditional Basic Operations The Rename Operation 1 2 3 select name, course id from instructor, teaches where instructor.ID= teaches.ID; The result of this query is a relation with the following attributes: name, course id\nWe cannot, however, always derive names in this way, for several reasons:\nFirst, two relations in the from clause may have attributes with the same name, in which case an attribute name is duplicated in the result.\nSecond, if we use an arithmetic expression in the select clause, the resultant attribute does not have a name.\nThird, even if an attribute name can be derived from the base relations as in the preceding example, we may want to change the attribute name in the result.\nHence, SQL provides a way of renaming the attributes of a result relation. It uses the as clause, taking the form: old-name as new-name\nFor example, if we want the attribute name name to be replaced with the name instructor_name, we can rewrite the preceding query as:\n1 2 3 select name as instructor name, course id from instructor, teaches where instructor.ID= teaches.ID; The as clause is particularly useful in renaming relations.\nOne reason to rename a relation is to replace a long relation name with a shortened version that is more convenient to use elsewhere in the query.\nTo illustrate, we rewrite the query “For all instructors in the university who have taught some course, find their names and the course ID of all courses they taught.”\n1 2 3 select T.name, S.course_id from instructor as T, teaches as S where T.ID = S.ID; Another reason to rename a relation is a case where we wish to compare tuples in the same relation. We then need to take the Cartesian product of a relation with itself and, without renaming, it becomes impossible to distinguish one tuple from the other.\nSuppose that we want to write the query “Find the names of all instructors whose salary is greater than at least one instructor in the Biology department.”\nWe can write the SQL expression:\n1 2 3 select distinct T.name from instructor as T, instructor as S where T.salary \u0026gt; S.salary and S.dept_name = \u0026#39;Biology\u0026#39;; String Operations SQL speciﬁes strings by enclosing them in single quotes, for example, \u0026lsquo;Computer\u0026rsquo;. A single quote character that is part of a string can be speciﬁed by using two single quote characters.\nFor example, the string “It’s right” can be speciﬁed by \u0026lsquo;It\u0026rsquo;\u0026rsquo;s right\u0026rsquo;.\nThe SQL standard speciﬁes that the equality operation on strings is case sensitive;as a result, the expression “\u0026lsquo;comp. sci.\u0026rsquo; = \u0026lsquo;Comp. Sci.\u0026rsquo;” evaluates to false.\n但在MySQL和SQL Server中,在字符串比较时不区分大小写,故“\u0026lsquo;comp. sci.\u0026rsquo; = \u0026lsquo;Comp. Sci.\u0026rsquo;” 为真. Pattern matching can be performed on strings using the operator like. We describe patterns by using two special characters:\nPercent (%): The % character matches any substring. Underscore (_): The character matches any character. Patterns are case sensitive.To illustrate pattern matching, we consider the following examples:\n\u0026lsquo;Intro%\u0026rsquo; matches any string beginning with “Intro”. \u0026lsquo;%Comp%\u0026rsquo; matches any string containing “Comp” as a substring, for example, \u0026lsquo;Intro. to Computer Science\u0026rsquo;, and \u0026lsquo;Computational Biology\u0026rsquo;. \u0026lsquo;___\u0026rsquo; matches any string of exactly three characters. \u0026lsquo;___%\u0026rsquo; matches any string of at least three characters. For patterns to include the special pattern characters (that is, % and ), SQL allows the speciﬁcation of an escape character. The escape character is used immediately before a special pattern character to indicate that the special pattern character is to be treated like a normal character. We deﬁne the escape character for a like comparison using the escape keyword. To illustrate, consider the following patterns, which use a backslash(∖) as the escape character:\nlike \u0026lsquo;ab∖%cd%\u0026rsquo; escape \u0026lsquo;∖\u0026rsquo; matches all strings beginning with “ab%cd”. like \u0026lsquo;ab∖∖cd%\u0026rsquo; escape \u0026lsquo;∖\u0026rsquo; matches all strings beginning with “ab∖cd”. 也就是说,sql没有通用的转义符,而是需要用户定义并写入字符串中\nSQL allows us to search for mismatches instead of matches by using the not like com- parison operator. Some implementations provide variants of the like operation that do not distinguish lower- and uppercase.\nAttribute Specification in the Select Clause The asterisk symbol “ * ” can be used in the select clause to denote “all attributes.”\n1 2 3 select instructor.* from instructor, teaches where instructor.ID= teaches.ID; It indicates that all attributes of instructor are to be selected.\nOrdering the Display of Tuples The order by clause causes the tuples in the result of a query to appear in sorted order.\nBy default, the order by clause lists items in ascending order. To specify the sort order, we may specify desc for descending order or asc for ascending order. Furthermore, ordering can be performed on multiple attributes. Suppose that we wish to list the entire instructor relation in descending order of salary. If several instructors have the same salary, we order them in ascending order by name. We express this query in SQL as follows:\n1 2 3 select * from instructor order by salary desc, name asc; Where-Clause Predicates SQL includes a between comparison operator to simplify where clauses that specify that a value be less than or equal to some value and greater than or equal to some other value.\nSimilarly, we can use the not between comparison operator.\n1 2 3 select name from instructor where salary between 90000 and 100000; 尽管它等价于以下语句,但看上去就直白了一点\n1 2 3 select name from instructor where salary \u0026lt;= 100000 and salary \u0026gt;= 90000; SQL permits us to use the notation (v1 , v2 , … , vn ) to denote a tuple of arity n con- taining values v1 , v2 , … , vn ; the notation is called a row constructor. The comparison operators can be used on tuples, and the ordering is deﬁned lexicographically. For ex- ample, (a1 , a2 ) \u0026lt;= (b1 , b2 ) is true if a1 \u0026lt;= b1 and a2 \u0026lt;= b2 ; similarly, the two tuples are equal if all their attributes are equal. Thus, the SQL query:\n1 2 3 select name, course id from instructor, teaches where instructor.ID= teaches.ID and dept name = \u0026#39;Biology\u0026#39;; can be rewritten as follows:\n1 2 3 select name, course id from instructor, teaches where (instructor.ID, dept name) = (teaches.ID, \u0026#39;Biology\u0026#39;); 看似简写了,但其实看上去更懵了 Set Operations The SQL operations union, intersect, and except operate on relations and correspond to the mathematical set operations ∪, ∩, and −.\nThe Union Operation 1 2 3 4 5 6 7 (select course id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017) union (select course id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); 这里只是把两个结果合并了一下而已\nNote that the parentheses we include around each select- from-where statement below are optional but useful for ease of reading.\nThe union operation automatically eliminates duplicates, unlike the select clause. If we want to retain all duplicates, we must write union all in place of union.\nThe Intersect Operation 1 2 3 4 5 6 7 (select course id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017) intersect (select course id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); 找交集\n同样可以加一个all来保留相同字段 1 2 3 4 5 6 7 (select course id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017) intersect all (select course id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); The Except Operation To ﬁnd all courses taught in the Fall 2017 semester but not in the Spring 2018 semester,we write:\n1 2 3 4 5 6 7 (select course id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017) except (select course id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); The except operation outputs all tuples from its ﬁrst input that do not occur in the second input.\n同样可以加一个all来保留相同字段 1 2 3 4 5 6 7 (select course id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017) except all (select course id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); Null Values Null values present special problems in relational operations, including arithmetic operations, comparison operations, and set operations.\nThe result of an arithmetic expression (involving, for example, +, −, ∗, or ∕) is null.\nIf any of the input values is null. For example, if a query has an expression r.A + 5, and r.A is null for a particular tuple, then the expression result must also be null for that tuple.\n也就是说null参与的数学运算结果都是null Comparisons involving nulls are more of a problem.SQL therefore treats as unknown the result of any comparison involving a null value.This creates a third logical value in addition to true and false.\nSince the predicate in a where clause can involve Boolean operations such as and,or, and not on the results of comparisons, the deﬁnitions of the Boolean operations are extended to deal with the value unknown.\nand: The result of true and unknown is unknown, false and unknown is false, while unknown and unknown is unknown. or: The result of true or unknown is true, false or unknown is unknown, while unknown or unknown is unknown. not: The result of not unknown is unknown. 为什么这样设计?\nTRUE AND UNKNOWN 为什么是 UNKNOWN? 如果 UNKNOWN 实际上是 TRUE：true and true = true 如果 UNKNOWN 实际上是 FALSE: true and false =false 由于结果可能是 TRUE 也可能是 FALSE，数据库无法给出定论，只能保守地标注为 UNKNOWN。 false and unknown 为什么是 false? 含有false的and语句恒为false,结果是确定的,故可标记为false. true or unknown 为什么是 true? 同理,含有true的or语句恒为true. false or unknown 为什么是 unknown? 同理,结果可能是 TRUE 也可能是 FALSE,无法定论. SQL uses the special keyword null in a predicate to test for a null value. Thus, to ﬁnd all instructors who appear in the instructor relation with null values for salary, we write:\n补充: 5*20+3=null,null=null,返回值都是unknown. 1 2 3 select name from instructor where salary is null; 有is null当然就有is not null. SQL allows us to test whether the result of a comparison is unknown,by using the clauses is unknown and is not unknown.For example:\n1 2 3 select name from instructor where salary \u0026gt; 10000 is unknown; Aggregate Functions Aggregate functions are functions that take a collection (a set or multiset) of values as input and return a single value. SQL oﬀers ﬁve standard built-in aggregate functions:\nAverage: avg Minimum: min Maximum: max Total: sum Count: count Basic Aggregation 1 2 3 select avg (salary) as avg_salary from instructor where dept name = \u0026#39;Comp. Sci.\u0026#39;; 找到salary的平均值 1 2 3 select count (distinct ID) from teaches where semester = \u0026#39;Spring\u0026#39; and year = 2018; 使用count关键字统计()内的具有对应属性的行数 Aggregation with Grouping 1 2 3 select dept_name, avg (salary) as avg salary from instructor group by dept_name; 这里把instructor用dept_name分组,从而统计出每个部门的avg_salary.\n也就是说group by 需要在select语句中写明操作的对象,才能在结果中反映出来分组的结果 1 2 3 4 /* erroneous query */ select dept_name, ID, avg (salary) from instructor group by dept_name; 上述sql语句由于一个分组中的tuple可以有不同的id属性,故不能变成一个分组,会引发错误 总结一下: 当 SQL 查询包含 GROUP BY 子句时，SELECT 子句中的任何属性（列名）必须满足以下两个条件之一，否则该查询在逻辑上是错误的：\n该属性出现在 GROUP BY 子句中。 该属性被包裹在聚合函数（如 SUM, COUNT, AVG, MAX, MIN）之中。 进阶版\n1 2 3 4 5 select dept_name, count (distinct ID) as instr count from instructor, teaches where instructor.ID = teaches.ID and semester = \u0026#39;Spring\u0026#39; and year = 2018 group by dept_name; “Find the number of instructors in each department who teach a course in the Spring 2018 semester.” The Having Clause 为了处理分组,而不是处理分组后的tuples,引入了having clause.\n1 2 3 4 select dept name, avg (salary) as avg_salary from instructor group by dept name having avg (salary) \u0026gt; 42000; 上述语句会过滤掉ayg_salary小于等于42000的分组 The logical execution sequence of an SQL query containing aggregation, GROUP BY, or HAVING clauses is defined as follows:\nEvaluate the FROM clause The FROM clause is first evaluated to generate a relation (the initial set of data from specified tables or joins). Apply the WHERE clause (if present) The predicate in the WHERE clause is applied to the result relation of the FROM clause. Only tuples satisfying this predicate proceed. Apply the GROUP BY clause (if present) Tuples satisfying the WHERE predicate are placed into groups based on the GROUP BY clause. If the GROUP BY clause is absent, the entire set of filtered tuples is treated as a single group. Apply the HAVING clause (if present) The HAVING clause is applied to each group. Groups that do not satisfy the HAVING clause predicate are removed entirely. Evaluate the SELECT clause The SELECT clause uses the remaining groups to generate the final result tuples. Aggregate functions are applied here to produce a single result tuple for each group. 长难句分析\n1 2 3 4 5 select course_id, semester, year, sec_id, avg (tot_cred) from student, takes where student.ID= takes.ID and year = 2017 group by course_id, semester, year, sec_id having count (ID) \u0026gt;= 2; 这里把学生按照“班级”扔进不同的筐里。比如“数据库-2017-秋-01班”是一个筐，“算法-2017-秋-02班”是另一个筐 也就是说,group by后面的属性全部相同时才会被归为一组\nAggregation with Null and Boolean Values 1 2 select sum (salary) from instructor; 当salary中有null值的时候,并不会像普通的5+null=null一样返回null,而是会忽略null\nIn general, aggregate functions treat nulls according to the following rule: All aggre- gate functions except count (*) ignore null values in their input collection. As a result of null values being ignored, the collection of values may be empty. The count of an empty collection is deﬁned to be 0, and all other aggregate operations return a value of null when applied on an empty collection\n由于count是统计行数的,故当某一行有null时也会计入 Nested Subqueries A subquery is a select-from-where expression that is nested within another query. A common use of subqueries is to perform tests for set membership, make set comparisons, and determine set cardinality by nesting subqueries in the where clause.\nSet Membership 1 2 3 4 5 6 select distinct course_id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017 and course_id in (select course_id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); We use the not in construct in a way similar to the in construct. For example, to ﬁnd all the courses taught in the Fall 2017 semester but not in the Spring 2018 semester, which we expressed earlier using the except operation, we can write:\n1 2 3 4 5 6 select distinct course id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017 and course id not in (select course id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); 也就是说,之前提到的intersect和except都可以用subquery的形式来解决 Set Comparison The phrase “greater than at least one” is represented in SQL by \u0026gt; some.\n1 2 3 4 5 select name from instructor where salary \u0026gt; some (select salary from instructor where dept_name = \u0026#39;Biology\u0026#39;); SQL also allows \u0026lt; some, \u0026lt;= some, \u0026gt;= some, = some, and \u0026lt;\u0026gt; some comparisons. As an exercise, verify that = some is identical to in, whereas \u0026lt;\u0026gt; some is not the same as not in.\nThe construct \u0026gt; all corresponds to the phrase “greater than all.”\n1 2 3 4 5 select name from instructor where salary \u0026gt; all (select salary from instructor where dept name = \u0026#39;Biology\u0026#39;); As it does for some, SQL also allows \u0026lt; all, \u0026lt;= all, \u0026gt;= all, = all, and \u0026lt;\u0026gt; all comparisons. As an exercise, verify that \u0026lt;\u0026gt; all is identical to not in, whereas = all is not the same as in.\nTest for Empty Relations The exists construct returns the value true if the argument subquery is nonempty.\n1 2 3 4 5 6 7 8 select course_id from section as S where semester = \u0026#39;Fall\u0026#39; and year= 2017 and exists (select * from section as T where semester = \u0026#39;Spring\u0026#39; and year= 2018 and S.course_id= T .course_id); The above query also illustrates a feature of SQL where a correlation name from an outer query (S in the above query), can be used in a subquery in the where clause. A subquery that uses a correlation name from an outer query is called a correlated subquery.\n当然,有exists就有not exists:\n1 2 3 4 5 6 7 8 9 select S.ID, S.name from student as S where not exists ((select course_id from course where dept_name = \u0026#39;Biology\u0026#39;) except (select T .course_id from takes as T where S.ID = T .ID)); 长难句分析 首先计算“Biology 系开设了，但该学生没选”的课程,如果这类课程not exists,说明该学生选了所有Biology系的课程.\nTest for the Absence of Duplicate Tuples(3/18) Using the unique construct, we can write the query “Find all courses that were oﬀered at most once in 2017” as follows:\n如果你记忆力够好的话,可能会记得之前在create table的时候会用unique来保证对应的属性不能有重复值 1 2 3 4 5 6 select T .course id from course as T where unique (select R.course id from section as R where T .course id= R.course id and R.year = 2017); Note that if a course were not oﬀered in 2017, the subquery would return an empty result, and the unique predicate would evaluate to true on the empty set. 也就是说即使结果为空,但由于是unique,故也会返回true.\n当然,有了unique就有not unique,要求返回结果中的每个tuple至少出现两次\n1 2 3 4 5 6 select T .course id from course as T where not unique (select R.course id from section as R where T .course id= R.course id and R.year = 2017); 可以看到,where部分的subquery可以访问外层的关系 Subqueries in the From Clause 1 2 3 4 5 6 select dept name, avg salary from (select dept name, avg (salary) from instructor group by dept name) as dept avg (dept name, avg salary) where avg salary \u0026gt; 42000; 是的,from的关系也能用别名\u0026hellip; 但是值得注意的是,在from中的subquery不能访问外层的关系,因为from中的关系是相互并列的,不存在嵌套关系,只能通过lateral关键字来强制让后继关系能够获取前置关系\n1 2 3 4 select name, salary, avg_salary from instructor I1, lateral (select avg(salary) as avg_salary from instructor I2 where I2.dept name= I1.dept_name); 如果你跟我一样从头看到这里的话,你会清楚的记得前面都是写类似instructor as I1的方式来起别名的,但是as实际上是可选的!尽管这里并没有提到这一点-这也是我讨厌这本教材的原因之一.\nThe With Clause The with clause provides a way of deﬁning a temporary relation whose deﬁnition is available only to the query in which the with clause occurs.\n1 2 3 4 5 6 with max_budget (value) as (select max(budget) from department) select budget from department, max_budget where department.budget = max_budget.value; 注意到这里是先写别名再在as后写原关系,而我们之前都是先写原关系再写as的\nwith得到的是关系而非属性,因此我们每次都需要用关系(属性)的方式来使用with\n实际上,上述的with语句完全可以放到where中 1 2 3 select budget from department where budget = (select max(budget) from department); We could have written the preceding query by using a nested subquery in either the from clause or the where clause. However, using nested subqueries would have made the query harder to read and understand. The with clause makes the query logic clearer; it also permits this temporary relation to be used in multiple places within a query.\nScalar(标量) Subqueries SQL allows subqueries to occur wherever an expression returning a value is permitted,provided the subquery returns only one tuple containing a single attribute; such sub-queries are called scalar subqueries.\n也就是说只返回一行一列 长难句分析 1 2 3 4 5 6 select dept_name, (select count(*) from instructor where department.dept_name = instructor.dept_name) as num_instructors from department; select count(*)在这里是无论是否有空值都返回满足epartment.dept_name = instructor.dept_name的所有行的意思 Scalar Without a From Clause 1 2 3 4 (select count (*) from teaches) / (select count (*) from instructor); -- 在某些数据库中会由于没有from语句而报错 select (select count (*) from teaches) / (select count (*) from instructor) from dual; -- 提供了一个dummy relation来提供from语句且不产生其他副作用 Modification of the Database(3/19) Deletion A delete request is expressed in much the same way as a query. We can delete only whole tuples; we cannot delete values on only particular attributes. SQL expresses a deletion by:\n1 2 3 4 delete from r where P; -- where P represents a predicate and r represents a relation. -- The where clause can be omitted, in which case all tuples in r are deleted. 1 2 3 delete from instructor where salary between 13000 and 15000; -- Delete all instructors with a salary between $13,000 and $15,000. 事实上delete与select的用法没有什么区别,只不过一个是选择关系,一个是删除关系而已. Insertion The attribute values for inserted tuples must be members of the corresponding attribute’s domain. Similarly, tuples inserted must have the correct number of attributes.\n1 2 3 4 5 6 7 8 9 insert into course values (\u0026#39;CS-437\u0026#39;, \u0026#39;Database Systems\u0026#39;, \u0026#39;Comp. Sci.\u0026#39;, 4); -- 如果忘记了表的顺序,那么可以具体写出属性名来插入,这三种写法的结果完全相同 insert into course (course id, title, dept name, credits) values (\u0026#39;CS-437\u0026#39;, \u0026#39;Database Systems\u0026#39;, \u0026#39;Comp. Sci.\u0026#39;, 4); insert into course (title, course id, credits, dept name) values (\u0026#39;Database Systems\u0026#39;, \u0026#39;CS-437\u0026#39;, 4, \u0026#39;Comp. Sci.\u0026#39;); 1 2 3 4 insert into instructor select ID, name, dept name, 18000 from student where dept name = \u0026#39;Music\u0026#39; and tot cred \u0026gt; 144; 当然,我们可以从其他关系中获取tuple后再插入,而不是只用value来具体写.\nUpdates n certain situations, we may wish to change a value in a tuple without changing all values in the tuple. For this purpose, the update statement can be used.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 update instructor set salary= salary * 1.05; -- 我们可以对某一列属性整体生效 update instructor set salary = salary * 1.05 where salary \u0026lt; 70000; -- 也可以对该列属性的部分值生效 update instructor set salary = salary * 1.05 where salary \u0026lt; (select avg (salary) from instructor); -- 还可以对满足子查询的属性值生效 进阶写法\n1 2 3 4 5 6 7 8 9 10 11 12 13 update instructor set salary = case when salary \u0026lt;= 100000 then salary * 1.05 else salary * 1.03 end -- 具体模板如下 -- case -- when pred 1 then result1 -- when pred 2 then result2 -- … -- when pred n then resultn -- else result0 -- end Intermediate SQL(3/15) Join Expressions 事实上,单独的join语句不加任何前缀和后缀会报错,因为不存在只写一个join而不进行过滤操作的语法.\nThe Natural Join 1 2 3 select name, course_id from student, takes where student.ID = takes.ID; 上述语句在执行的中间结果中会保留两个ID列,只是在select时抛弃掉了而已.\n1 2 select name, course id from student natural join takes; 而natural join的中间结果只保留一个ID列 规则:\n找到同名列 过滤掉同名列中的值不相等的行 两个同名列只保留一列 1 2 select name, title from (student natural join takes) join course using (course_id); The operation join … using requires a list of attribute names to be speciﬁed. Both relations being joined must have attributes with the speciﬁed names.\n也就是说只用using()里面那个属性在join的时候来判断,而不像natural join一样在有多个同名属性的情况下全都要求相等. Join Conditions The on condition allows a general predicate over the relations being joined.\n1 2 select * from student join takes on student.ID = takes.ID; 完全等价于下面语句,也就是结果都保持了两个ID列\n1 2 3 select * from student, takes where student.ID = takes.ID; 所以基本用不上on Outer Joins 1 2 select * from student natural join takes; 由于natural join会丢弃takes.id!=student.id的行,对于只在一个表中存在的ID,由于在另一个表中找不到对等的值,也会被抛弃,但在实际需求中,即使没选课的学生,我们也希望展示出来,故这里需要使用outer join.\nThere are three forms of outer join:\nThe left outer join preserves tuples only in the relation named before the left outer join operation. The right outer join preserves tuples only in the relation named after the right outer join operation. The full outer join preserves tuples in both relations. In contrast, the join operations we studied earlier that do not preserve nonmatched tuples are called inner-join operations, to distinguish them from the outer-join operations.\nWe now explain exactly how each form of outer join operates. We can compute the left outer-join operation as follows: First, compute the result of the inner join as before. Then, for every tuple t in the left-hand-side relation that does not match any tuple in the right-hand-side relation in the inner join, add a tuple r to the result of the join constructed as follows:\nThe attributes of tuple r that are derived from the left-hand-side relation are ﬁlled in with the values from tuple t. The remaining attributes of r are ﬁlled with null values. 1 2 select * from student natural left outer join takes; 如果对于在student里出现但在takes里没有的id,会将student表中的值填入,对应takes部分的值均为null.\nThe full outer join is a combination of the left and right outer-join types. After the operation computes the result of the inner join, it extends with nulls those tuples from the left-hand-side relation that did not match with any from the right-hand-side relation and adds them to the result. Similarly, it extends with nulls those tuples from the right- hand-side relation that did not match with any tuples from the left-hand-side relation and adds them to the result. Said diﬀerently, full outer join is the union of a left outer join and the corresponding right outer join.\n也就是说保留两边的缺值 进阶例子\n1 2 3 4 5 6 7 8 select * from (select * from student where dept name = \u0026#39;Comp. Sci.\u0026#39;) natural full outer join (select * from takes where semester = \u0026#39;Spring\u0026#39; and year = 2017); 一开始明明都是用student natural full outer join takes的,这里为什么要单独把两个结果提出来呢?\n1 2 3 4 5 6 select * from student natural full outer join takes where dept_name = \u0026#39;Comp. Sci.\u0026#39; and semester = \u0026#39;Spring\u0026#39; and year = 2017; 因为如果这样写的话,尽管在from部分保留了null,但where部分又把null全部过滤掉了\nThe on condition is part of the outer join speciﬁcation, but a where clause is not.\n1 2 select * from student left outer join takes on student.ID = takes.ID; 也就是说上面这个语句不等价于下面的,因为on是在join阶段生效,即使不匹配也会置为null; 而where是在join之后生效,因此where会过滤掉null行,而上面语句中的on则会保留\n1 2 3 select * from student left outer join takes on true where student.ID = takes.ID; Join Types and Conditions The keyword inner is optional:\n1 2 select * from student join takes using (ID); is equivalent to:\n1 2 select * from student inner join takes using (ID); Similarly, natural join is equivalent to natural inner join.\nViews SQL allows a “virtual relation” to be deﬁned by a query, and the relation conceptually contains the result of the query. The virtual relation is not precomputed and stored but instead is computed by executing the query whenever the virtual relation is used.\n因此就有了view这个概念用来代表尚未执行的sql语句\nView Definition 1 create view v as \u0026lt;query expression\u0026gt;; where \u0026lt; query expression \u0026gt; is any legal query expression. The view name is represented v\n1 2 3 create view faculty as select ID, name, dept name from instructor; 通过这样写,可以给低权限用户传递view这个关系而不直接接触instructor这个关系.\nUsing Views in SQL Queries The attribute names of a view can be speciﬁed explicitly as follows:\n1 2 3 4 create view departments total salary(dept name, total salary) as select dept name, sum (salary) from instructor group by dept name; Since the expression sum(salary) does not have a name, the attribute name is speciﬁed explicitly in the view deﬁnition.\nUpdate of a View Not recommended!\nTransactions blablabla\u0026hellip;\nIntegrity Constraints Constraints on a Single Relation not null unique check() Not Null Constraint 1 2 name varchar(20) not null budget numeric(12,2) not null The not null constraint prohibits the insertion of a null value for the attribute, and is an example of a domain constraint.\nUnique Constraint The unique speciﬁcation says that no two tuples in the relation can be equal on all the listed attributes.\nThe Check Clause When applied to a relation declaration, the clause check(P) speciﬁes a predicate P that must be satisﬁed by every tuple in a relation.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 create table section (course id varchar (8), sec id varchar (8), semester varchar (6), year numeric (4,0), building varchar (15), room number varchar (7), time slot id varchar (4), primary key (course id, sec id, semester, year), check (semester in (\u0026#39;Fall\u0026#39;, \u0026#39;Winter\u0026#39;, \u0026#39;Spring\u0026#39;, \u0026#39;Summer\u0026#39;))); Here, we use the check clause to simulate an enumerated type by specifying that semester must be one of \u0026lsquo;Fall\u0026rsquo;, \u0026lsquo;Winter\u0026rsquo;, \u0026lsquo;Spring\u0026rsquo;, or \u0026lsquo;Summer\u0026rsquo;.\n还可以把check写在定义的属性之后\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 CREATE TABLE classroom ( building VARCHAR(15), room_number VARCHAR(7), capacity NUMERIC(4,0), PRIMARY KEY (building, room_number) ); CREATE TABLE department ( dept_name VARCHAR(20), building VARCHAR(15), budget NUMERIC(12,2) CHECK (budget \u0026gt; 0), PRIMARY KEY (dept_name) ); CREATE TABLE course ( course_id VARCHAR(8), title VARCHAR(50), dept_name VARCHAR(20), credits NUMERIC(2,0) CHECK (credits \u0026gt; 0), PRIMARY KEY (course_id), FOREIGN KEY (dept_name) REFERENCES department ); CREATE TABLE instructor ( ID VARCHAR(5), name VARCHAR(20) NOT NULL, dept_name VARCHAR(20), salary NUMERIC(8,2) CHECK (salary \u0026gt; 29000), PRIMARY KEY (ID), FOREIGN KEY (dept_name) REFERENCES department ); CREATE TABLE section ( course_id VARCHAR(8), sec_id VARCHAR(8), semester VARCHAR(6) CHECK (semester IN (\u0026#39;Fall\u0026#39;, \u0026#39;Winter\u0026#39;, \u0026#39;Spring\u0026#39;, \u0026#39;Summer\u0026#39;)), year NUMERIC(4,0) CHECK (year \u0026gt; 1759 AND year \u0026lt; 2100), building VARCHAR(15), room_number VARCHAR(7), time_slot_id VARCHAR(4), PRIMARY KEY (course_id, sec_id, semester, year), FOREIGN KEY (course_id) REFERENCES course , FOREIGN KEY (building, room_number) REFERENCES classroom ); Referential Integrity 默认引用 (Implicit Reference) foreign key (dept_name) references department\n底层行为：当你省略括号中的属性名时，SQL 标准规定该外键必须引用被参照表的 主键 (Primary Key) 显式引用 (Explicit Reference) foreign key (dept_name) references department(dept_name)\n底层行为：目标列（dept_name）不必是主键，但必须具有 唯一性约束 (UNIQUE) 或本身就是 主键。即它必须是一个 超键 (Superkey)。 Assigning Names to Constraints 1 2 3 4 -- 使用constraint关键字命名该限制 salary numeric(8,2), constraint minsalary check (salary \u0026gt; 29000), -- 删除该限制 alter table instructor drop constraint minsalary; Complex Check Conditions and Assertions An assertion is a predicate expressing a condition that we wish the database always to satisfy.\n格式\n1 create assertion \u0026lt;assertion-name\u0026gt; check \u0026lt;predicate\u0026gt;; 1 2 3 4 5 6 7 create assertion credits earned constraint check (not exists (select ID from student where tot_cred \u0026lt;\u0026gt; (select coalesce(sum(credits), 0) from takes natural join course where student.ID= takes.ID and grade is not null and grade\u0026lt;\u0026gt; ’F’ ))) 非常好的是这种东西不会考,因为主流数据库也不用 SQL Data Types and Schemas There are additional built-in data types supported by SQL, which we describe below.\nDate and Time Types in SQL date: A calendar date containing a (four-digit) year, month, and day of the month. time: The time of day, in hours, minutes, and seconds. timestamp: A combination of date and time. Date and time values can be speciﬁed like this:\n1 2 3 4 date \u0026#39;2018-04-25\u0026#39; -- Dates must be speciﬁed in the format year followed by month followed by day time \u0026#39;09:30:00\u0026#39; timestamp \u0026#39;2018-04-25 10:29:01.45\u0026#39; Type Conversion and Formatting Functions(过) 由于我找到了中文版教材,故从现在开始大部分使用中文版,少部分使用英文版(因为这本书真的是又臭又长) 1. 显式类型转换 (Type Casting) 语法： cast(expression as data_type)\n用途： 物理改变数据的解析方式，常用于修正字符串排序逻辑。\n1 2 3 4 5 6 7 /* 修正前：\u0026#39;11\u0026#39; 会排在 \u0026#39;2\u0026#39; 前面（字符串字典序） */ select ID from instructor order by ID; /* 修正后：将字符串物理转为数字，实现数值大小排序 */ select ID from instructor order by cast(ID as numeric); 2. 格式化函数 (Formatting Functions) 用途： 不改变物理类型，只给数据套上“显示滤镜”。各数据库系统实现不同。\n1 2 3 4 5 6 7 8 -- MySQL: 数字千分位格式化 select format(salary, 2) from instructor; -- PostgreSQL/Oracle: 日期转特定格式字符串 select to_char(now(), \u0026#39;YYYY-MM-DD\u0026#39;); -- SQL Server: 风格化转换 select convert(varchar, getdate(), 101); -- 返回 mm/dd/yyyy 3. 空值合并 (Coalesce) 语法： coalesce(arg1, arg2, ...)\n逻辑： 物理扫描参数列表，返回第一个非 null 的值。要求所有参数物理类型一致。\n1 2 3 4 5 /* 如果 salary 是 null，物理替换为 0 以便后续数学运算 */ select ID, coalesce(salary, 0) as actual_salary from instructor; /* 错误示例：coalesce(salary, \u0026#39;N/A\u0026#39;) 会报错，因为数字和字符串类型不匹配 */ 4. 条件解码 (Decode - Oracle 特有) 语法： decode(value, search, result, default)\n逻辑： 类似于 switch-case。它允许 null = null 的物理匹配，且不强制要求类型一致。\n1 2 3 /* 将物理空值直接翻译成业务描述字符串 \u0026#39;N/A\u0026#39; */ select ID, decode(salary, null, \u0026#39;N/A\u0026#39;, salary) as salary_text from instructor; 随便一看就知道不会考的\u0026hellip; Default Values SQL allows a default value to be speciﬁed for an attribute as illustrated by the following create table statement:\n1 2 3 4 5 6 create table student (ID varchar (5), name varchar (20) not null, dept_name varchar (20), tot_cred numeric (3,0) default 0, primary key (ID)); Large-Object Types SQL provides large-object data types for character data (clob) and binary data (blob). The letters “lob” in these data types stand for “Large OBject.”\n1 2 3 book_review clob(10KB) image blob(10MB) movie blob(2GB) User-Defined Types(过) SQL supports two forms of user-deﬁned data types. The ﬁrst form, which we cover here, is called distinct types.\nThe create type clause can be used to deﬁne new types:\n1 2 3 4 5 6 7 8 9 10 create type Dollars as numeric(12,2) ﬁnal; create type Pounds as numeric(12,2) ﬁnal; create table department (dept name varchar (20), building varchar (15), budget Dollars); 这一节也不重要,因为主流数据库也不支持 Generating Unique Key Values 1 2 3 4 5 6 ID number(5) generated always as identity -- When the always option is used, any insert statement must avoid specifying a value -- for the automatically generated key. insert into instructor (name, dept_name, salary) values (\u0026#39;Newprof\u0026#39;, \u0026#39;Comp. Sci.\u0026#39;, 100000); Create Table Extensions Applications often require the creation of tables that have the same schema as an existing table.\n1 2 -- SQL provides a create table like extension to support this task create table temp_instructor like instructor; Schemas, Catalogs, and Environments(过) Index Definition in SQL(过) 由于这部分基本什么都没讲,还是得到index章节去看\nAuthorization We may assign a user several forms of authorizations on parts of the database. Authorizations on data include:\n• Authorization to read data. • Authorization to insert new data. • Authorization to update data. • Authorization to delete data.\nEach of these types of authorizations is called a privilege. Granting and Revoking of Privileges 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -- grant的使用格式 grant \u0026lt;privilege list\u0026gt; on \u0026lt;relation name or view name\u0026gt; to \u0026lt;user/role list\u0026gt;; -- grants database users Amit and Satoshi select authorization on the department relation: grant select on department to Amit, Satoshi; -- If the list of attributes is omitted, the update privilege will be granted on all attributes of the relation. grant update (budget) on department to Amit, Satoshi; -- To revoke an authorization, we use the revoke statement. It takes a form almost identical to that of grant: revoke \u0026lt;privilege list\u0026gt; on \u0026lt;relation name or view name\u0026gt; from \u0026lt;user/role list\u0026gt;; revoke select on department from Amit, Satoshi; revoke update (budget) on department from Amit, Satoshi; Roles 我们使用Roles来解决需要多次指定相同权限的问题 1 2 3 4 5 6 7 8 9 10 create role instructor; -- Roles can then be granted privileges just as the users can, grant select on takes to instructor; -- Roles can be granted to users, as well as to other roles, create role dean; grant instructor to dean; grant dean to Satoshi; Authorization on Views 1 2 3 4 5 6 7 8 9 -- 为了让某个工作人员能够看到地质系的所有教师但不能看到其他系的教师表,我们可以这样写: create view geo_instructor as (select * from instructor where dept name = \u0026#39;Geology\u0026#39;); -- 但是,下面的操作实际上还是间接的访问了instructor表 select * from geo_instructor; Authorizations on Schema SQL includes a references privilege that permits a user to declare foreign keys when creating relations.\n1 grant references (dept_name) on department to Mariano; 为什么要限制引用外码呢?\nHowever, recall that foreign-key constraints restrict deletion and update operations on the referenced relation. Suppose Mariano creates a foreign key in a relation r referencing the dept name attribute of the department relation and then inserts a tuple into r pertaining to the Geology department. It is no longer possible to delete the Geology department from the department relation without also modifying relation r.\nTransfer of Privileges 当我们授予某用户一个权限时,默认他是无法将获得的权限传递给其他用户的,如果希望他能够传递权限,我们可以这样写:\n1 grant select on department to Amit with grant option; 当然,关系/视图/角色(relation/view/role) 的创建者拥有该对象的所有权限并且可以给其他用户授权(不然就没人能授权了) Revoking of Privileges(过) Advanced SQL 事实上,这部分我觉得期末不考,我猜老师自己也不会😅 (4/8): 第一次小测考了前三节,\u0026hellip; Accessing SQL from a Programming Language(过) JDBC 你猜怎么着,我们的课程安排是在学完这门课后再学java\u0026hellip; The JDBC standard deﬁnes an application program interface (API) that Java programs can use to connect to database servers. (The word JDBC was originally an abbreviation for Java Database Connectivity, but the full form is no longer used.)\nODBC The Open Database Connectivity (ODBC) standard deﬁnes an API that applications can use to open a connection with a database, send queries and updates, and get back results.\nFunctions and Procedures(待补充) Triggers(待补充) A trigger is a statement that the system executes automatically as a side eﬀect of a modiﬁcation to the database.\n也就是说,trigger是一个满足条件时自动执行的函数 第一次小测复习 ch1 使用文件处理系统而不是数据库的弊端 Data redundancy and inconsistency: 可能有多个文件记录了相同的信息,又或者不同文件的记录不相同,没有同步更新 Diﬃculty in accessing data: 普通的编程语言不能做到用高效又便利的方式取回数据 Data isolation: 数据被存储在不同类型的不同文件中,很难统一管理 Integrity problems: 很难通过普通的编程语言去给存储的数据加上各种限制 Atomicity problems: 转账时,如果在付款方付钱后系统故障,那么收款方账户未必会收到钱,但付款方账户已经扣了钱.也就是说,很难实现这样一种效果:要么两边操作全都发生,要么都不发生 Concurrent-access anomalies: 并发访问数据时可能导致异常 Security problems: 很难设置不同管理员的权限 数据库结构的基础: 数据模型 本书介绍了4种模型:\nRelational Model: uses a collection of tables to represent both data and the relationships among those data. Each table has multiple columns, and each column has a unique name. Tables are also known as relations. Entity-Relationship Model: The entity-relationship (E-R) data model uses a collection of basic objects, called entities, and relationships among these objects. Semi-structured Data Model: individual data items of the same type may have diﬀerent sets of attributes. Object-Based Data Model: 仅仅是第一种类型的扩展,适用于面向对象的编程语言 ch2: Introduction to the Relational Model schema到底是什么 Relation Schema（关系模式）：对应编程中的“类型定义”或“类声明”。 物理组成：由属性名（Attributes）及其对应的域（Domains/Data Types）组成。 示例分析：department (dept_name, building, budget) 物理规定了任何存入该表的记录必须且只能包含这三个维度。 Database Schema（数据库模式）：对应软件架构中的“逻辑设计”。 ch3 数据类型 char(n): 固定长度为n的字符串 varchar(n): 最大长度为n的可变长字符串 int: 整数 numeric(p,d): 有p位数字(加上一个符号位)和小数点右边的p位中的d位数字 float(n): 精度至少为n位数字的浮点数 操纵关系表 创建表时可以对变量使用以下六个限制:\nprimary key(d): d非空且唯一 foreign key(d) references s: d的值必须出现在s的对应主码上 foreign key(d) references s(t): d的值必须与t对应,要求t是唯一的,即具有unique约束 not null: 非空 unique: 唯一 check(d\u0026hellip;): 要求新加入的d满足对应条件,否则插入时报错 1 2 3 4 5 6 create table department( dept_name varchar(20) not null, building varchar(15) unique, budget numeric(12,2),check(buget\u0026gt;0), primary key(dept_name) ); 删除表或更改属性\n1 2 3 4 5 6 -- 删除表r drop table r; -- 增加(删除)属性A,类型为D alter table r add A D; -- alter table r drop A 很多数据库系统不支持 删除元组\n1 2 3 4 5 -- 删除表r中的所有元组,保留框架 delete from r; -- 删除满足条件P的元组 delete from r where P; 插入元组\n1 2 3 4 5 6 7 8 9 10 11 12 -- 需要按照属性名顺序来,否则可能会插入失败 insert into r values(a1,a2,a3,....) -- 指定插入值的对应属性 insert into course (course id, title, dept name, credits) values (\u0026#39;CS-437\u0026#39;, \u0026#39;Database Systems\u0026#39;, \u0026#39;Comp. Sci.\u0026#39;, 4); -- 插入查询结果 insert into instructor select ID, name, dept_name, 18000 from student where dept name = \u0026#39;Music\u0026#39; and tot_cred \u0026gt; 144; 更新元组\n1 2 3 4 -- 更改满足条件的属性名 update instructor set salary = salary * 1.05 where salary \u0026lt; 70000; select\u0026hellip;from\u0026hellip;where select基础部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -- !!!select会选中null行,不自动过滤!!! -- 默认启用all保留重名行 select (all) dept_name from instructor; -- 去掉重名行 select distinct dept_name from instructor; -- 简单计算 select ID,salary*1.1 from insructor; -- 选中所有关系的所有属性 select * from instructor -- 选中一个关系的所有属性 select instructor.* from instructor, teaches where instructor.ID= teaches.ID; select进阶部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 -- max,min,avg,sum:均为op(attribute)格式 select avg (salary) from instructor where dept_name = \u0026#39;Comp. Sci.\u0026#39;; -- 启用as select avg (salary) as avg_salary from instructor where dept_name = \u0026#39;Comp. Sci.\u0026#39;; -- count(attribute)和count(*) -- count(attribute): 过滤重名行,一个ID统计一行,但会忽略ID为null的行 select count (distinct ID) from teaches where semester = \u0026#39;Spring\u0026#39; and year = 2018; -- count(*): 统计所有行,包括存在空值的行 select count (*) from course; from基础部分 1 2 3 4 -- 启用as select T .name, S.course_id from instructor as T , teaches as S where T .ID= S.ID; where基础部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 -- =判断符 select name from instructor where dept_name = \u0026#39;Comp. Sci.\u0026#39; -- \u0026gt;,\u0026lt;,\u0026gt;=,\u0026lt;=判断符 select distinct T .name from instructor as T , instructor as S where T.salary \u0026gt; S.salary -- and,or联结词 select name from instructor where dept_name = \u0026#39;Comp. Sci.\u0026#39; and salary \u0026gt; 70000; -- (not) between...and...联结词 select name from instructor where salary between 90000 and 100000; -- 等价于 select name from instructor where salary \u0026lt;= 100000 and salary \u0026gt;= 90000; -- 字符串比较: like,not like -- 注意sql中只有单引号,没有双引号 -- %: 匹配任意子串 -- _: 匹配任意一个字符 -- ___%: 匹配至少含有任意三个字符的字符串 select dept_name from department where building like \u0026#39;%Watson%\u0026#39;; where进阶 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 -- 构造器写法: (a,b)=(c,d)-\u0026gt; a=c and b=d select name, course_id from instructor, teaches where (instructor.ID, dept_name) = (teaches.ID, \u0026#39;Biology\u0026#39;); -- unknown: 如果对于某一个元组来说,判断符(即使是=)的一边为null,那么结果就是unkown ,则该元组不能被加入到结果中 -- is (not) null,is (not) unknown select name from instructor where salary is null; select name from instructor where salary \u0026gt; 10000 is unknown; -- 解释: 只有salary为null的行才可以出现在结果中 where中的子查询 in,not in\n1 2 3 4 5 6 select distinct course_id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017 and course_id in (select course_id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); some\n1 2 3 4 5 6 select name from instructor where salary \u0026gt; some (select salary from instructor where dept_name = \u0026#39;Biology\u0026#39;); -- 至少比生物学部门的一个老师工资要高 SQL also allows \u0026lt; some, \u0026lt;= some, \u0026gt;= some, = some, and \u0026lt;\u0026gt; some comparisons. As an exercise, verify that = some is identical to in, whereas \u0026lt;\u0026gt; some is not the same as not in. all\n1 2 3 4 5 6 select name from instructor where salary \u0026gt; all (select salary from instructor where dept_name = \u0026#39;Biology\u0026#39;); -- 比生物学部门的所有老师工资都高 SQL also allows \u0026lt; all, \u0026lt;= all, \u0026gt;= all, = all, and \u0026lt;\u0026gt; all comparisons. As an exercise, verify that \u0026lt;\u0026gt; all is identical to not in, whereas = all is not the same as in. (not) exists\n1 2 3 4 5 6 7 8 select course_id from section as S where semester = \u0026#39;Fall\u0026#39; and year= 2017 and exists (select * from section as T where semester = \u0026#39;Spring\u0026#39; and year= 2018 and S.course_id= T .course_id); exists的原理: 当括号内的查询第一次被满足时即返回true,让sql引擎继续扫描下一行.\nunique\n1 2 3 4 5 6 select T .course id from course as T where unique (select R.course id from section as R where T .course id= R.course id and R.year = 2017); 如果无重复元组则返回true,无论元组是否为空\nfrom中的子查询 1 2 3 4 5 6 select dept_name, avg_salary from (select dept_name, avg (salary) from instructor group by dept_name) as dept_avg (dept_name, avg_salary) where avg_salary \u0026gt; 42000; with: 子查询的变种 1 2 3 4 5 6 with max_budget (value) as (select max(budget) from department) select budget from department, max_budget where department.budget = max_budget.value; 标量子查询 1 2 3 4 5 6 select dept_name, (select count(*) from instructor where department.dept_name = instructor.dept_name) as num_instructors from department; having和group by group by\n1 2 3 4 -- 在group子句中的所有属性取值相同的元组将被分在一个组内,显然,当涉及的属性名越多,分的组也会越多 select dept_name, avg (salary) from instructor group by dept_name; 注意,没有出现在group by中的属性只能以聚集函数的参数形式在select语句中出现,如下方的例子中ID就是不该出现的属性,因为一个组中的教师可以有不同的ID,那就无法选定保留哪一个ID:\n1 2 3 4 /* erroneous query */ select dept_name, ID, avg (salary) from instructor group by dept_name; having: 作用于group的条件判断 1 2 3 4 5 select dept_name, avg (salary) as avg_salary from instructor group by dept_name having avg (salary) \u0026gt; 42000; -- 平均薪资低于42000的部门分组将被过滤掉 关系的集合 三种运算均自动去重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -- union(并集): 找到至少在一个关系中出现的被select选中的元组,自动去重 -- union all: 保留重复项 (select course_id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017) union (select course_id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); -- intersect(交集): 找到在两个关系中都出现的被select选中的元组,自动去重 -- intersect all: 保留重复项 (select course_id from section where semester = \u0026#39;Fall\u0026#39; and year= 2017) intersect (select course_id from section where semester = \u0026#39;Spring\u0026#39; and year= 2018); -- except(差集): 找到在前一个关系中出现,但不在第二个关系中出现的,被select选中的元组,自动去重 -- except all: 保留重复项 排在最末尾的order by: 结果排序 1 2 3 4 5 6 7 8 9 -- 不写默认为升序asc select name from instructor where dept_name = \u0026#39;Physics\u0026#39; order by name; -- 排序按字典序,首字母大的在后面 -- 先排第一个,同名再按第二个排 select * from instructor order by salary desc, name asc; 习题3.1 a\n1 2 3 select title from course where credits = 3 and dept_name = \u0026#39;Comp.Sci.\u0026#39; b\n1 2 3 4 select distinct ID from instructor,teaches,student,takes where instructor.name=\u0026#39;Einstein\u0026#39; and instructor.ID = teaches.ID and student.ID =takes.ID and(takes.semester ,takes.sec_id,takes.year,teaches.course_id)=(teaches.semester ,teaches.sec_id,teaches.year,takes.course_id) -- 写疯了 c\n1 2 select max(salary) from instructor d\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 with max_salary(v) as (select max(salary) from instructor) select ID,name from instructor where salary =max_salary(v) -- 炫技写法1 select ID,name from instructor where salary \u0026gt;= all(select salary from instructor) -- 2 select ID,name from instructor where salary = (select max(salary)from instructor) e 目标: 2017年 秋季 每个课程段 选课人数 approach: section,takes\n1 2 3 4 select count(ID),course_id,sec_id; from section natural join takes where year=2017 and semester=\u0026#39;autumn\u0026#39; group by coures_id,sec_id; f target: max enrollment in Autumn 2009\n1 2 3 4 5 6 7 with enrollment(counts) as ( select count(ID) from section natural join takes where semester = \u0026#39;Autumn\u0026#39; and year = 2009 group by course_id, sec_id ) select max(counts) from enrollment; ch4 join natural join: 自动去重的发散join 自动去重,多列匹配\n1 2 3 4 5 6 7 select name, course id from student, takes where student.ID = takes.ID; -- 两者作用相同,尽管from的结果不同,但select时没有选择ID列,所以看不出来区别 select name, course id from student natural join takes; join\u0026hellip;using: 自动去重的定向join using 只能用于等值连接,不能配合其他谓词使用\n1 2 select name, title from (student natural join takes) join course using (course id); 相当于一个仅比对所需列的natural join,更加安全\njoin\u0026hellip;on: 不自动去重的定向join 1 2 3 4 5 6 7 select * from student join takes on student.ID = takes.ID; -- 等价 select * from student, takes where student.ID = takes.ID; 当然,on在大多数情况下都可以用where直接替换,但对于outer join来说,二者是有所不同的 outer join: 保留空值的发散join前缀 outer join本身不能直接使用,需要搭配on,natural或者using\n当我们希望即便一边的ID有值,但另一边不存在这个ID时,也保留该行时,可以使用outer join.\n有三种形式:\nleft outer join: 只保留左边关系中的元组 right outer join: 只保留右边关系中的元组 full outer join: 保留两边关系的元组 以left outer join为例子来说明:\nThe attributes of tuple r that are derived from the left-hand-side relation are ﬁlled in with the values from tuple t. The remaining attributes of r are ﬁlled with null values. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 select ID from student natural left outer join takes where course id is null; select * from (select * from student where dept name = \u0026#39;Comp. Sci.\u0026#39;) natural full outer join (select * from takes where semester = \u0026#39;Spring\u0026#39; and year = 2017); Database Design Using the E-R Model 数据库的设计并不简单,所以我们需要一些好用的模型来组织数据库,比如这章涉及的E-R模型.\n忽然发现前面的笔记中废话太多了,像我的小测复习这样精炼就已经可以了,能够轻松的获取所需的信息 The Entity-Relationship Model entity: a “thing” or “object” in the real world that is distinguishable from all other objects. 实体是一个独一无二的个体 attribute: 实体通过一组属性来表示,每个实体在它的每个属性上都有一个值(value) entity set: 共享部分属性的实体集合 relationship: 多个实体之间的关系 relationship set: 相同类型的联系集合 看这个图就很好理解了,单独的两个实体,如Crick和Tanaka之间的关系就是relationship,而实体集之间的映射关系就是relationship set E-R图 entity set: 使用矩形来表示,分为两个部分,一部分是实体集的名称,一部分是实体集所有属性的名称\nrelationship set: 使用菱形来标识,通过线条连接到多个实体集\nrelationship set的描述性属性用单矩形虚线连接 映射基数(mapping cardinality): 一个实体通过一个联系集关联的其他实体的数量,有以下四种:\n一对一: 从联系集到两个实体集各画一个有向线段 一对多: \u0026ldquo;多方\u0026quot;使用无向线段连接,\u0026ldquo;一方\u0026quot;使用有向线段 多对一: 与一对多刚好相反 多对多: 两边都是无向线段 The participation of an entity set E in a relationship set R is said to be total if every entity in E must participate in at least one relationship in R. If it is possible that some entities in E do not participate in relationships in R, the participation of entity set E in relationship R is said to be partial.\n使用双线来表示一个实体集在联系集中全部参与.\nstrong entity and weak entity 强实体 (Strong Entity) 强实体是自足的，它不依赖于数据库中任何其他实体的存在。\n物理特征：拥有一个独立的主键 (Primary Key)。这个主键不包含任何其他实体的属性。 物理隐喻：像一个拥有独立身份证的“自然人”。 ER 图标识：矩形框。 例子：Student（学生）。每个学生都有唯一的 student_id，无论这个学生是否选课，他作为“学生”这个实体的物理记录在数据库中都是独立存在的。 弱实体 (Weak Entity) 弱实体是寄生的，它必须依赖于另一个实体（称为“标识实体”或“父实体”）才能获得物理意义上的唯一性。\n物理特征：没有足够的属性来构成自己的主键。它的主键是物理借用来的，由“父实体的物理主键 + 自己的部分键（Discriminator/Partial Key）”共同组成。 物理隐喻：像“公寓里的房间”。没有公寓（父实体），“101室”这个编号物理上没有任何意义。 ER 图标识：双边矩形框。 例子：Section（开课班级）。单独看 sec_id（如 1 班）无法确定是哪门课。它必须物理依赖于 Course（课程），主键通常是 (course_id, sec_id, semester, year)。 概化与特化 特化 (Specialization) —— 自上而下 物理逻辑：将一个高层级的实体集（父类）根据特定特征，物理拆分为多个低层级的子实体集（子类）。 触发场景：当你发现某些属性只适用于部分成员，而不适用于全体时。 ER图表示: 由特化实体用空心箭头指向父类,如果一个实体可以属于多个特化实体集,则称为重叠特化(overlapping specialization),使用两个单独箭头;如果只能属于一个实体集,则称为不相交特化(disjoint specialization),使用一个箭头 物理示例：员工 (Employee) 实体。 只有“厨师”拥有 厨师等级 属性。 只有“司机”拥有 驾驶证号 属性。 物理动作：从 员工 集合中特化出 厨师 和 司机 子集。 概化 (Generalization) —— 自下而上 物理逻辑：提取多个实体集中的共同属性，物理归纳为一个更高层级的通用实体集（父类）。 触发场景：为了消除数据冗余，并在物理层面提供统一的访问接口。 物理示例：存在 老虎、狮子、大象 三个独立实体。 它们物理上共有 年龄、体重、所属区域 等属性。 物理动作：将它们概化为 动物 (Animal) 实体。 汇总 The Unified Modeling Language (UML)(待补充) Relational Database Design(较难,待补充) 根据上一章的E-R图我们可以导出一组关系模式: Complex Data Types In this chapter, we discuss several non-atomic data types that are widely used,including semi-structured data, object-based data, textual data, and spatial data.\nSemi-structured Data Overview of Semi-structured Data Models Flexible Schema Some database systems allow each tuple to potentially have a diﬀerent set of attributes; such a representation is referred to as a wide column data representation. The set of attributes is not ﬁxed in such a representation; each tuple may have a diﬀerent set of attributes, and new attributes may be added as needed.\nA more restricted form of this representation is to have a ﬁxed but very large number of attributes, with each tuple using only those attributes that it needs, leaving the rest with null values; such a representation is called a sparse column(稀疏表) representation.\nMultivalued Data Types Some representations allow attributes to store key-value maps, which store key-value pairs. A key-value map, often just called a map, is a set of (key, value) pairs, such that each key occurs in at most one element.\nNested Data Types All of these data types represent a hierarchy of data types, and that structure leads to the use of the term nested data types.\nJSON和XML是该数据库类型的最重要的两个代表 JSON(JavaScript Object Notation) The JavaScript Object Notation (JSON), is a textual representation of complex data types that is widely used to transmit data between applications and to store complex data. JSON supports the primitive data types integer, real and string, as well as arrays, and “objects,” which are a collection of (attribute name, value) pairs.\n1 2 3 4 5 6 7 8 9 10 11 12 { \u0026#34;ID\u0026#34;: \u0026#34;22222\u0026#34;, \u0026#34;name\u0026#34;: { \u0026#34;firstname: \u0026#34;Albert\u0026#34;, \u0026#34;lastname: \u0026#34;Einstein\u0026#34; }, \u0026#34;deptname\u0026#34;: \u0026#34;Physics\u0026#34;, \u0026#34;children\u0026#34;: [ {\u0026#34;firstname\u0026#34;: \u0026#34;Hans\u0026#34;, \u0026#34;lastname\u0026#34;: \u0026#34;Einstein\u0026#34; }, {\u0026#34;firstname\u0026#34;: \u0026#34;Eduard\u0026#34;, \u0026#34;lastname\u0026#34;: \u0026#34;Einstein\u0026#34; } ] } XML(Extensible Markup Language) The XML data representation adds tags enclosed in angle brackets, \u0026lt;\u0026gt;, to mark up information in a textual representation. Tags are used in pairs, with and delimiting the beginning and the end of the portion of the text to which the tag refers.\n1 2 3 4 5 6 \u0026lt;course\u0026gt; \u0026lt;course_id\u0026gt; CS-101 \u0026lt;/course_id\u0026gt; \u0026lt;title\u0026gt; Intro. to Computer Science \u0026lt;/title\u0026gt; \u0026lt;dept_name\u0026gt; Comp. Sci. \u0026lt;/dept_name\u0026gt; \u0026lt;credits\u0026gt; 4 \u0026lt;/credits\u0026gt; \u0026lt;/course\u0026gt; Object Orientation Three approaches are used in practice for integrating object orientation with database systems:\nBuild an object-relational database system, which adds object-oriented features to a relational database system. Automatically convert data from the native object-oriented type system of the programming language to a relational representation for storage, and vice versa for retrieval. Data conversion is speciﬁed using an object-relational mapping. Build an object-oriented database system, that is, a database system that natively supports an object-oriented type system and allows direct access to data from an object-oriented programming language using the native type system of the language. We provide a brief introduction to the ﬁrst two approaches in this section.\nObject-Relational Database Systems User-Defined Types\n1 2 3 4 5 6 7 8 9 10 11 12 create type Person (ID varchar(20) primary key, name varchar(20), address varchar(20)) ref from(ID); create table people of Person; -- We can create a new person as follows: insert into people (ID, name, address) values (\u0026#39;12345\u0026#39;, \u0026#39;Srinivasan\u0026#39;, \u0026#39;23 Coyote Run\u0026#39;); Type Inheritance\n1 2 3 4 create type Student under Person (degree varchar(20)) ; create type Teacher under Person (salary integer); Table Inheritance\n1 2 3 4 5 6 7 -- PostgreSQL create table students (degree varchar(20)) inherits people; create table teachers (salary integer) inherits people; Object-Relational Mapping(ORM) A fringe beneﬁt of using an ORM is that any of a number of databases can be used to store data, with exactly the same high-level code. ORMs hide minor SQL diﬀerences between databases from the higher levels. Migration from one database to another is thus relatively straightforward when using an ORM, whereas SQL diﬀerences can make such migration signiﬁcantly harder if an application uses SQL to communicate with the database.\nOn the negative side, object-relational mapping systems can suﬀer from signiﬁcant performance ineﬃciencies for bulk database updates, as well as for complex queries that are written directly in the imperative language. It is possible to update the database directly, bypassing the object-relational mapping system, and to write complex queries directly in SQL in cases where such ineﬃciencies are discovered.\nTextual Data Textual data consists of unstructured text. The term information retrieval generally refers to the querying of unstructured textual data.\nKeyword Queries A keyword query retrieves documents whose set of keywords contains all the keywords in the query.\n搜索引擎是information retrieval systems的典型例子,根据关键词返回网页内容.\nRelevance Ranking The set of all documents that contain the keywords in a query may be very large; in particular, there are billions of documents on the web, and most keyword queries on a web search engine ﬁnd hundreds of thousands of documents containing some or all of the keywords.\nInformation-retrieval systems therefore estimate relevance of documents to a query and return only highly ranked documents as answers.\nRanking Using TF-IDF term: refers to a keyword occurring in a document, or given as part of a query. TF: term frequency One way of measuring TF(d, t), the relevance of a term t to a document d, is: where n(d) denotes the number of term occurrences in the document and n(d, t) denotes the number of occurrences of term t in the document d.\nHowever, not all terms used as keywords are equal. Suppose a query uses two terms, one of which occurs frequently, such as “database”, and another that is less frequent, such as “Silberschatz”. A document containing “Silberschatz” but not “database” should be ranked higher than a document containing the term “database” but not “Silberschatz”.\nTo ﬁx this problem, weights are assigned to terms using the inverse document fre- quency (IDF), deﬁned as: where n(t) denotes the number of documents (among those indexed by the system) that contain the term t. The relevance of a document d to a set of terms Q is then deﬁned as: Almost all text documents (in English) contain words such as “and,” “or,” “a,” and so on, and hence these words are useless for querying purposes since their inverse doc- ument frequency is extremely low. Information-retrieval systems deﬁne a set of words, called stop words, containing 100 or so of the most common words, and ignore these words when indexing a document. Such words are not used as keywords, and they are discarded if present in the keywords supplied by the user.\n这就是为什么输入python和爬虫得到的结果与python 爬虫相差无几的原因 Ranking Using Hyperlinks Hyperlinks between documents can be used to decide on the overall importance of a document, independent of the keyword query; for example, documents linked from many other documents are considered more important.\nPagerank是该排序方法的著名代表之一\nSpatial Data Two types of spatial data are particularly important:\nGeographic data: such as road maps, land-usage maps, topographic elevation maps, political maps showing boundaries. Geometric data: include spatial information about how objects— such as buildings, cars, or aircraft— are constructed 游戏建模,谷歌地图都属于空间数据这一范畴\nApplication Development(过) Big Data 当数据的数量和种类多到一定程度时,传统的关系型数据库就无能为力了,需要采用更为复杂的数据结构来处理.\nThe MapReduce Paradigm(待补充) Data Analytics(过) The term data analytics refers broadly to the processing of data to infer patterns, correlations, or models for prediction.\nPhysical Storage Systems Overview of Physical Storage Media(物理存储介质概述) 高速缓存 (Cache)：速度最快且成本最高的存储形式。容量较小，由计算机系统硬件管理。数据库系统实现者在设计查询处理数据结构和算法时会关注高速缓存效应。 主存储器 (Main memory)：用于存放可被操作数据的存储介质，通用机器指令在主存上运行。主存容量在个人电脑上通常为几十 GB，大型服务器可达数百至数千 GB。主存具有易失性 (volatile)，电源故障或系统崩溃时内容会丢失。 闪存 (Flash memory)：与主存不同，闪存是非易失性 (non-volatile) 的。其单位字节成本低于主存，但高于磁带。 广泛用于照相机、手机和 USB 闪存盘。 固态硬盘 (Solid State Drive,SSD) 内部使用闪存存储数据，提供类似于磁碟的面向块的接口 (block-oriented interface)，典型块大小为 512 字节至 8 KB。 磁碟存储 (Magnetic-disk storage)：长期在线存储数据的主要介质，也称为硬盘驱动器 (Hard Disk Drive,HDD)。磁碟是非易失性的。访问磁碟数据时，系统必须先将数据从磁碟移至主存。虽然比 SSD 便宜，但在每秒支持的数据访问操作次数上性能较低。 俗称为机械硬盘 光存储 (Optical storage)：包括 DVD 和蓝光光盘，使用激光读写。虽然可用于存储备份，但不适合存储活动数据库数据，因为其访问时间远长于磁碟。存在只读、单次写入 (WORM) 和多次擦写版本。 磁带存储 (Tape storage)：主要用于备份和归档数据（如出于法律原因需长期安全存储的数据）。 磁带比磁盘便宜且可拆卸，但访问速度极慢，因为必须从头开始顺序访问 (sequential-access)。 磁碟和 SSD 被称为直接访问存储 (direct-access storage)。 常用于存储海量科学数据（可达 PB 级）或大型视频文件。 Storage Interfaces 接口标准 SATA (Serial ATA)：常用接口。SATA-3 版本理论带宽 6 Gbps，实际数据传输速率可达 600 MB/s。 SAS (Serial Attached SCSI)：通常仅用于服务器。SAS-3 版本支持 12 Gbps 的传输速率。 NVMe (Non-Volatile Memory Express)：专门为 SSD 优化的逻辑接口标准，通常运行在 PCIe 总线上。 存储网络架构 SAN (Storage Area Network)： 定义：通过高速网络将大量磁盘连接到多台服务器。 NAS (Network Attached Storage)： 定义：与 SAN 类似，但不对外呈现为裸磁盘，而是通过 NFS 或 CIFS 等网络文件系统协议提供文件系统接口。 云存储 (Cloud Storage) 特征：数据存储在云端，通过 API 访问。 限制：若数据未与数据库同地部署，延迟高达数十至数百毫秒，因此不适合作为数据库的基础存储。 应用：常用于存储对象（Object Storage）。 Data Storage Structures(过) 就算考了我也不会\u0026hellip;\nIndexing(索引) Basic Concepts(基本概念) 使用索引可以在不深入查询数据库的情况下快速找到数据\n有两种基本的索引类型:\n顺序索引(ordered index): 基于值的顺序排序 哈希索引(hash index): 通过哈希函数映射 被索引标记的,用来查找记录的属性或属性集被称为搜索码(search key).\nOrdered Indices(顺序索引) The records in the indexed ﬁle may themselves be stored in some sorted order. A ﬁle may have several indices, on diﬀerent search keys. If the ﬁle containing the records is sequentially ordered, a clustering index is an index whose search key also deﬁnes the sequential order of the ﬁle.\nClustering indices are also called primary indices Indices whose search key speciﬁes an order diﬀerent from the sequential order of the ﬁle are called nonclustering indices, or secondary indices.\n也就是说,与文件顺序相同就是聚集索引,不同就是非聚集索引.\n顺序索引的类别 顺序索引可以细分为两类:\n稠密索引(dense index): 对于文件中的每个搜索码值都有一个索引项 稀疏索引(sparse index): 只为某些搜索码值建立索引项. 自然,稠密索引更容易定位一条记录,但稀疏索引占用的空间和维护开销更小.\n索引更新 在以下两种情况下需要更新索引:插入和删除\n插入 如果是稠密索引: 如果插入项的搜索码值未出现在索引中,则插入一个新的索引项 如果该搜索码值出现在索引中,则根据不同的索引结构来插入: 如果是每个索引项对应一个指针列表或一个桶的结构,那么就将该值指向该索引项的指针队列末端即可. 如果是索引项仅存储一个指针的结构,那么只需要把新纪录插入到该索引项对应记录的末端,不需要改动索引项 如果是稀疏索引(将记录分成块): 如果新记录创建了新块,那么就需要插入一个新索引,选择该块中搜索键序号最小的键值作为索引键 如果新记录插入到现有块中,并且键值比该块的所有记录都要小,那么就将旧键值替换成新的键值;否则不做任何改动 删除 如果是稠密索引:\n如果被删记录是该搜索码值的唯一记录, 则直接从索引中删除对应的索引项。 如果该搜索码值在索引中仍有其他记录, 则根据索引结构处理: 如果是每个索引项对应一个指针列表或桶的结构, 则仅从该列表中删除指向被删记录的指针。 如果是索引项仅存储一个指针的结构（指向首条记录）: 如果被删的是该键值的首条记录, 则更新索引项, 使其指向下一条具有相同键值的记录。 如果被删的不是首条记录, 则无需改动索引项。 如果是稀疏索引 (以块为基准):\n如果索引中不存在该被删记录的搜索码值, 则无需对索引做任何改动。 如果索引中存在该搜索码值, 则根据记录余留情况处理: 如果被删记录是该搜索码值的唯一记录: 用数据文件中下一个出现的搜索码值替换当前的索引项。 如果下一个搜索码值已经拥有索引项（即它已经是某块的块首）, 则直接删除当前索引项即可。 如果该键值仍有其他记录存在（即被删者是块首但不是唯一记录）: 如果索引项正指向该被删记录, 则更新索引项, 使其指向该键值的下一条记录（新的块首）。 B+-Tree Index Files(待补充) B+-Tree Extensions Hash Indices(哈希索引)(过) Multiple-Key Access(过) Creation of Indices(创建索引) 1 2 3 4 5 -- 就某个关系的某个属性创建索引 create index \u0026lt;index-name\u0026gt; on \u0026lt;relation-name\u0026gt; (\u0026lt;attribute-list\u0026gt;); -- 删除索引 drop index \u0026lt;index-name\u0026gt;; 例子\n1 create index dept index on instructor (dept name); Bitmap Indices(位图索引) 书上讲的特别烂,所以让AI写了一份:\n为了透彻理解位图索引，我们直接通过一个教职员工数据表的完整流程来拆解。\n1. 原始数据表 (Relation) 假设有 5 名员工，我们要对“性别”和“收入等级”建立位图索引：\n记录编号 (ID) 姓名 性别 (Gender) 收入等级 (Income) 0 张三 男 (m) L1 1 李四 女 (f) L2 2 王五 女 (f) L1 3 赵六 男 (m) L4 4 孙七 女 (f) L3 2. 索引的物理构造 系统会为每个属性的每一个可能取值生成一个位图。\n性别属性 (Gender) 由于性别只有 m 和 f，产生两个位图：\n性别=m 的位图：1 0 0 1 0（第0、3位是男，填1） 性别=f 的位图：0 1 1 0 1（第1、2、4位是女，填1） 收入等级属性 (Income) 收入等级有 L1, L2, L3, L4，产生四个位图：\nL1 位图：1 0 1 0 0（记录0和2是L1） L2 位图：0 1 0 0 0（记录1是L2） L3 位图：0 0 0 0 1（记录4是L3） L4 位图：0 0 0 1 0（记录3是L4） 3. 为什么它在多条件查询时极快？ 假设我们要找：“收入等级为 L1 的男性”。 SQL 语句：SELECT * FROM table WHERE Gender='m' AND Income='L1';\n数据库的操作步骤：\n加载 Gender=m 的位图：1 0 0 1 0 加载 Income=L1 的位图：1 0 1 0 0 进行“与”运算 (AND)： $$\r\\begin{array}{r@{\\quad}l}\r\u0026 1 \\ 0 \\ 0 \\ 1 \\ 0 \\\\\r\\text{AND} \u0026 1 \\ 0 \\ 1 \\ 0 \\ 0 \\\\\r\\hline\r\u0026 1 \\ 0 \\ 0 \\ 0 \\ 0\r\\end{array}\r$$ 读取结果：结果位图只有第 0 位是 1，系统直接去磁盘取 record 0 的数据。 4. 关键点总结：到底好在哪里？ 对比 B+ 树： 如果你用 B+ 树找“男性”，它会给你一堆指针列表；如果你再找“L1”，它又给你一堆指针。要把这两堆指针取交集，CPU 需要进行复杂的列表比对。而位图索引将这个问题简化成了计算机最擅长的位运算。 对比全表扫描： 如果不建索引，数据库必须把每条记录的姓名、性别、收入等所有字段都读进内存再判断。而位图索引非常小，通常可以全部塞进内存，在不碰磁盘的情况下就已经把目标行号算出来了。 空间压缩： 在实际工程中，如果数据量巨大，这些 000001 会经过特殊的压缩算法（如 WAH 压缩），占用的空间微乎其微。 5. 什么时候不能用？ 如果一个属性的取值非常多（高基数），比如“身份证号”：\n如果有 1 亿条记录，身份证号就有 1 亿种。 你需要建立 1 亿个位图，每个位图 1 亿比特。 结果：索引的大小会远超数据表本身，查询效率崩塌。 复习要点 仔细想想,中文班和英文班的卷子除了语言不同外应该没有什么区别,由于中英文版本教材的编排不同,所以只需要把内容更少的中文版教材搞定就行了!\n全部的关系代数 全部的sql语法 索引 Query Processing(只要看1,2小节)(过) Transactions Transaction Concept transaction: a unit of program execution that accesses and possibly updates various data items. 事务需要满足以下特性,这被称为ACID特性:\natomicity: 要么不执行要么全部执行,不存在中间状态 consistency: 保证数据库的属性,接口,完整性约束不变 isolation: 事务能够正常执行,不被并发执行的事务影响 durability: 即使事务执行时系统崩溃,该操作也不可撤销. 存储器分类 volatile storage: 易失性存储器,包括cache和main memory,存储的信息在系统崩溃后即丢失 non-volatile storage: 非易失性存储器,包括SSD,HDD和磁带等存储设备,尽管不会在系统崩溃时丢失数据,但是容易受到故障的影响导致信息丢失. stable storage: 稳定存储器,将数据备份到多个非易失性存储器中 Transaction Atomicity and Durability 补充 Outline Of The Course Chapter 1: Introduction Chapter 2: Introduction to Relational Model Chapter 3: Introduction to SQL Chapter 4: Intermediate SQL Chapter 5: Advanced SQL (只要求前三节) Chapter 6: Entity-Relationship Model Chapter 7: Relational Database Design Chapter 8: Complex Data Types Chapter 9: Application Design Chapter 10: Big Data Chapter 11: Data Analytics Chapter 12: Physical Storage Systems (Sections 12.5 (RAID) omitted) Chapter 13: Storage and File Structure Chapter 14: Indexing and Hashing Chapter 15: Query Processing (Section 15.1, 15.2) Chapter 17: Transactions 斜体的为需要熟练的部分,黑体的为非常重要的部分 吐槽 教材详细过头了,而ppt基本是原封不动的搬运了整本书1300多页的内容,一个ppt最少五六十张,而总共有20多个ppt. 要说他敬业呢,ppt上的内容破碎无比,速览一遍发现不如看书完整,要说他不敬业呢,好歹有这么大的工作量.\n(3/26)这本书真的是又臭又长\u0026hellip; (3/28)我服了原来这个ppt是官网上的,而我们老师实际啥都没干\u0026hellip; 本教材核心: 大学数据库 keys used schema diagram 关系表解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 create table classroom (building\tvarchar(15), room_number\tvarchar(7), capacity\tnumeric(4,0), primary key (building, room_number) ); create table department (dept_name\tvarchar(20), building\tvarchar(15), budget\tnumeric(12,2) check (budget \u0026gt; 0), primary key (dept_name) ); create table course (course_id\tvarchar(8), title\tvarchar(50), dept_name\tvarchar(20), credits\tnumeric(2,0) check (credits \u0026gt; 0), primary key (course_id), foreign key (dept_name) references department (dept_name) on delete set null ); create table instructor (ID\tvarchar(5), name\tvarchar(20) not null, dept_name\tvarchar(20), salary\tnumeric(8,2) check (salary \u0026gt; 29000), primary key (ID), foreign key (dept_name) references department (dept_name) on delete set null ); create table section (course_id\tvarchar(8), sec_id\tvarchar(8), semester\tvarchar(6) check (semester in (\u0026#39;Fall\u0026#39;, \u0026#39;Winter\u0026#39;, \u0026#39;Spring\u0026#39;, \u0026#39;Summer\u0026#39;)), year\tnumeric(4,0) check (year \u0026gt; 1701 and year \u0026lt; 2100), building\tvarchar(15), room_number\tvarchar(7), time_slot_id\tvarchar(4), primary key (course_id, sec_id, semester, year), foreign key (course_id) references course (course_id) on delete cascade, foreign key (building, room_number) references classroom (building, room_number) on delete set null ); create table teaches (ID\tvarchar(5), course_id\tvarchar(8), sec_id\tvarchar(8), semester\tvarchar(6), year\tnumeric(4,0), primary key (ID, course_id, sec_id, semester, year), foreign key (course_id, sec_id, semester, year) references section (course_id, sec_id, semester, year) on delete cascade, foreign key (ID) references instructor (ID) on delete cascade ); create table student (ID\tvarchar(5), name\tvarchar(20) not null, dept_name\tvarchar(20), tot_cred\tnumeric(3,0) check (tot_cred \u0026gt;= 0), primary key (ID), foreign key (dept_name) references department (dept_name) on delete set null ); create table takes (ID\tvarchar(5), course_id\tvarchar(8), sec_id\tvarchar(8), semester\tvarchar(6), year\tnumeric(4,0), grade\tvarchar(2), primary key (ID, course_id, sec_id, semester, year), foreign key (course_id, sec_id, semester, year) references section (course_id, sec_id, semester, year) on delete cascade, foreign key (ID) references student (ID) on delete cascade ); create table advisor (s_ID\tvarchar(5), i_ID\tvarchar(5), primary key (s_ID), foreign key (i_ID) references instructor (ID) on delete set null, foreign key (s_ID) references student (ID) on delete cascade ); create table time_slot (time_slot_id\tvarchar(4), day\tvarchar(1), start_hr\tnumeric(2) check (start_hr \u0026gt;= 0 and start_hr \u0026lt; 24), start_min\tnumeric(2) check (start_min \u0026gt;= 0 and start_min \u0026lt; 60), end_hr\tnumeric(2) check (end_hr \u0026gt;= 0 and end_hr \u0026lt; 24), end_min\tnumeric(2) check (end_min \u0026gt;= 0 and end_min \u0026lt; 60), primary key (time_slot_id, day, start_hr, start_min) ); create table prereq (course_id\tvarchar(8), prereq_id\tvarchar(8), primary key (course_id, prereq_id), foreign key (course_id) references course (course_id) on delete cascade, foreign key (prereq_id) references course (course_id) ); departname: 部门的办公地点和预算 course: 培养计划中的课程 instructor: 教师的信息 section: 课表中的真实课程 section是course在某一学期的映射 teaches: 教师教授的课程信息 student: 学生的信息 takes: 学生所选的真实课表 ","date":"2026-03-14T08:00:00Z","image":"/p/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%95%99%E6%9D%90%E7%AC%94%E8%AE%B0/38431299_p0-%E5%BC%8F%E3%81%95%E3%82%93.webp","permalink":"/p/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%95%99%E6%9D%90%E7%AC%94%E8%AE%B0/","title":"数据库教材笔记"},{"content":"很多人在考入大学的时候都已成年,但事实上没有多少大学生是真正的成年人,我认为成年人需要有以下三个表现:\n能够自己处理日常杂务(衣食住行样样都会),并且有自己的时尚眼光,选择合适的穿搭 能够安排好工作,娱乐,家庭三者之间的关系 独立思考,对外自信,对己严格 最难的其实就是独立思考了,很多人都在抱怨重点大学里越来越卷的环境,却没有想过一件事,你自己为什么要去卷呢?\n自然,要说为了保持高绩点,奖学金倒是其次,重点是有保研机会.\n那么你为什么要保研呢? 是真的有自己的目标院校或者目标导师通过保研才能选上吗?是觉得自己能撑过高考却撑不过考研吗? 或者说连自己是不是真的想读研究生都不知道呢?\n如果你和我一样觉得本科的课程都已经崩溃了,大多数比赛也都是社交工程,面子工程,豆腐渣工程,那为什么还天天痛苦的去上早八,去花大精力修饰ppt,去思想必修课上卷发言,还要看着大师们用不成文法的语言讲授着过时已久的ppt.\n如果反过来想,不考虑保研,而是奔着工作抑或是考研去,是不是本科的前三年就轻松多了,所有课只需要保证最基本的出席率,大多数时候都不必勉强自己去上课,只需要本着糊弄的态度去完成那些没有技术含量的作业,只需要在签到之后从后门悄悄溜出教室,一周满打满算可以剩下六天的自由时间.\n如果有了这么多空闲时间,我想只要不是病入膏肓,是不会整天整夜去打游戏的,相反,你轻松摆脱了那些上课的垃圾时间,不再是什么碎片化学习了,而是有了一整块的完整学习时间,可以去以自己的节奏深入学习必修同时也是工作所需要的课程知识,这一定远比跟着老师走的效率高上好几倍,那么期末周的时候你只需要搞懂那些教材上多出来的陈旧的概念就可以了,而基本的大框架你早就学会了,这想必很难挂科吧.\n即便这样,你还是能剩下很多时间,深入学习自己感兴趣的知识,去看演唱会,去挑战自己没试过的娱乐,去参加那些真正有技术含量的比赛,去应聘工作日也要出席的日常实习,去体验你应该有的青春生活\n实际上,你相当于比同龄人多了好几倍的学习时间和娱乐时间,对于计算机专业来说这是非常可怕的,你可以精通多个前后端技术栈,可以真正从底层了解系统内核和网络通信,可以信手拈来网络安全原理和常见IO算法,可以自己去写几个有技术含量的开源项目或者参与到大型开源项目中.这到了春招暑期实习和秋招入职面试的时候,是远超出其他面试者一大截的,再加上名校的光环加持,很容易收获自己梦寐以求的offer,毕竟面试是不看你绩点的,如果你满绩倒是能坐实做题蛆的身份😅\n当然,如果你选择考研的话,那还是老老实实做一年题吧,但考研也是不看绩点的,即便你绩点爆炸又如何呢? 虽然我很难理解不做算法岗还要读研的同辈😃,如果有自己的考量倒是另说,倒是看见过不读研进入算法岗的博客帖子.\n这个时候又有人要说了,\u0026ldquo;大环境不好,你光读个本科以后怎么办\u0026rdquo;,那我只好把一般的招聘条件贴上来了:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [广州] 南方仕通 资深前端开发工程师 福利待遇: 薪资: 20~35K *14, 双休, 不加班 （一）岗位职责 1.负责智能体界面的前端开发工作，确保界面的美观、易用及良好的用户体验。 2.负责智能体界面加载速度优化，提升界面的响应速度和运行效率，减少用户等待时间。 3.完成智能体界面的跨浏览器适配，保证界不同主流浏览器能正常显示和稳定运行，解决各类浏览器兼容性问题。 5.与产品经理、UI 设计师及后端研发人员紧密协作，参与需求分析、界面设计讨论及技术方案制定，推动产品功能的实现和迭代。 6.关注前端技术发展趋势，将新技术、新方法合理应用到实际项目中，提升团队的技术水平和开发效率。 7.负责前端代码的维护和优化，确保代码的质量、可读性和可维护性，参与代码审查工作。 （二）任职要求 1.本科及以上学历，计算机相关专业，985/211院校优先，3 年以上前端研发toC经验。 2. 精通网页响应式布局与设计，能够应用media、rem等进行页面布局和优化 3.具备丰富的智能体界面加载速度优化经验，熟悉各类前端性能优化技术（如资源压缩与合并、缓存策略、懒加载、CDN 使用等），并能实际应用于项目中取得显著效果。 4.拥有智能体界面跨浏览器适配的成功案例，深入了解不同浏览器的渲染机制和差异，能够快速定位并解决兼容性问题。 5.精通 HTML5、CSS3、JavaScript 等前端基础技术，熟悉Astro、Vue、React、Angular 等至少一种主流前端框架，并能熟练运用进行项目开发。 6.熟悉前端工程化工具（如 Webpack、Vite 等），了解模块化开发思想和规范。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 拼多多集团-PDD社招火热进行中！（直推） 坐标：上海 1.Java开发工程师（风控方向） 坐标：上海 岗位职责 参与高可用实时和离线风控系统的架构设计，研究行业最新技术； 负责高可用业务模块、规则引擎、数据收集等具体功能的设计、实现和优化； 负责大流量线上问题的排查、定位和解决，持续优化和提高风控性能和效率。 任职要求 具有互联网项目开发和设计经验，熟悉Java技术栈； 有高并发、微服务架构、多线程开发、大数据、模型算法相关经验者加分； 在电商领域拥有风控和安全经验者加分； 具有团队精神、责任感强、具备服务意识。 2.C++研发工程师（后端引擎方向） 坐标：上海 岗位职责 1.负责搭建业务规则引擎，支撑百亿级DOC的检索查询； 2.优化在线、离线数据流程，保障服务的稳定运行； 3.维护和开发基础组件,提高系统的性能和扩展性； 4.深刻的理解业务，抽象和设计合理的技术架构,以适应不断变化的需求。 任职要求 1.计算机、通讯或相关专业本科以上学历, 3年以上的服务端开发经验； 2.有互联网开发经验，对算法和数据结构有深刻的理解； 3.C/C++基本功扎实，熟悉Linux开发环境和网络编程； 4.有较好的沟通和逻辑思维能力，善于分析和解决实际问题，对技术有强烈的兴趣； 5.有平台治理、风控等相关工作经验，有高并发、高可用服务开发及维护经验优先考虑。 3.C++研发工程师（算法工程引擎方向） 坐标：上海 岗位职责 1、负责图像、音频、视频、文本相关深度学习算法的工程化； 2、构建稳定的引擎服务\u0026amp;架构支撑百万QPS级别的平台治理业务。 任职要求 1.计算机、通讯或相关专业本科以上学历，2年以上互联网经验； 2.精通C/C++，了解java、python等常见开发语言，熟悉多线程技术； 3.有图像、NLP、音频相关项目经验，有调优模型推理性能经验，有cuda开发经验，有向量检索相关经验者更佳； 4.能完成独立模块的开发，也能够较好的联合其他人协同开发； 5.对开发工作拥有热情，能够主动积极的发现和解决问题； 6.有平台治理、风控等相关工作经验优先考虑。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ### Python开发工程师(爬虫方向) 薪资待遇：30~50K/月，具体面议 招聘人数：3人 紧急程度：紧急 岗位职责： 1、负责各大竞品网或者APP数据采集，主要是动态数据 2、负责网页爬虫架构设计(包括基础资源，如缓存池技术、反爬技术、浏览器采集技术等等)与核心技术研发 3、负责API或者爬虫获取数据，数据解析和分析等功能的研发 4、RESTFUL API签权、图片签权，视频播放签权等 任职要求： 1、专科及以上学历，5年以上开发经验 2、精通API和网页爬虫、分布式、多线程开发技术 3、有扎实的算法和数据结构能力，能够使用主流的爬虫框架构建爬虫 4、熟悉爬虫原理，熟悉常用的浏览器调试技术，常见的反爬虫技术，能够解决签权、签名、封IP、cookie识别等问题 5、掌握http协议，熟悉Javascript、HTML、CSS、正则表达式、xPath等信息抽取技术熟悉抓包分析请求并模拟 6、有大规模数据处理、数据挖掘、信息提取等经验者优先 事实上绝大部分工作是不卡硕士学历的,(虽然有些岗位本科要求211,985也比较卡学历了),而要求硕士学历的基本都是算法岗的,而且都是新兴企业. 总体来说,如果不是铁着头要做算法岗,非要冲所谓高薪的话,反而是本科就业更好,因为很多岗位反而有\u0026quot;三年以上工作经历\u0026quot;这样的经验限制而非学历限制.\n而更为实际的是,读了研还不能找到满意工作,或者被大厂看不上的人也不少呢,当然这些他们就不会特意去了解了.\n当然,又会有人说,\u0026ldquo;你这只是个例而已,再说程序员又不稳定,还是考研后进体制内好\u0026rdquo;,自然我举的例子都是个例,你了解的例子都是全面的了.话又说回来,体制内当然好啊,喜欢体制内的一般人自然会自己去不用你说的,但谁让我是二般人呢,不甘愿自己所学的知识就这样被埋没在连低级AI都能处理的CRUD里,就这样把鸡蛋装到一个所谓安全至极的篮子里.\n后悔的是我到了大二下才有了上述的这些体会,要是大一入学的时候就能有这些经验的话那我早就起飞了😇,不过走错的路并非是对自己的否定,更多的是教训和经验.\n","date":"2026-03-09T08:00:00Z","image":"/p/2026-03-09-%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BC%9A%E4%B8%8A%E5%AD%A6%E5%90%97/5290131_p0-%E7%A9%BA%E3%81%B8.webp","permalink":"/p/2026-03-09-%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BC%9A%E4%B8%8A%E5%AD%A6%E5%90%97/","title":"2026-03-09 你真的会上学吗"},{"content":"Windows端 把先前的推荐重新整理了一下,方便自己重装电脑时知道要装哪些软件 Magpie 强大的窗口缩放器,畅玩一切古早游戏.\nContextMenuManager 不多解释,改注册表还是太麻烦了,用这个快速除去流氓软件的菜单项.\nokular 不多解释,薄纱其他所有的windows pdf阅读器,真正的秒天秒地,我就是想看个pdf要那么多其他功能干什么.另外提一嘴安卓端的阅读神器是readera,同样遥遥领先.\ncalibre 支持的书籍格式还是很多的,可惜启动有点慢,太臃肿了,但我找到的SumatraPDF等阅读器由于支持格式少代替不了它.\nAnki 背单词神器,可以自己定制卡片这点就非常nice. 有史料为证\nAIMP 由于网易云启动太慢,加载音乐太卡,很多功能太臃肿了,因此我转而加入AIMP神教,不仅可以自定义界面,还可以下载各式各样的皮肤.运行速度飞快,而且有手机桌面双端.尽管这个软件是离线的,只能下好歌之后再导入,歌词也要自己下,但这一切都值得! qView Windows自己的图片浏览器真的是拉完了,加载慢的离谱,换成qview后就舒服多了,界面简洁的异常,毕竟看个图片而已,要什么名堂. 常见 Everything,PotPlayer,JiJiDown,Motrix,Quicker,Pixpin 浏览器插件 免费看CSDN付费文章 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // ==UserScript== // @name 100%解锁CSDN文库vip文章阅读限制 // @namespace http://tampermonkey.net/ // @version 2.2 // @description CSDN文库阅读全文，去除VIP文章遮罩 // @author Mrlimuyu // @match *://*.csdn.net/* // @grant none // @license yagiza // @downloadURL https://update.greasyfork.org/scripts/495150/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.user.js // @updateURL https://update.greasyfork.org/scripts/495150/100%25%E8%A7%A3%E9%94%81CSDN%E6%96%87%E5%BA%93vip%E6%96%87%E7%AB%A0%E9%98%85%E8%AF%BB%E9%99%90%E5%88%B6.meta.js // ==/UserScript== (function() { \u0026#39;use strict\u0026#39;; const adjustArticle = () =\u0026gt; { // 移除遮罩层和限制高度的内容 document.querySelectorAll(\u0026#39;.hide-article-box, .login-mark, .mask, .vip-caise\u0026#39;).forEach(el =\u0026gt; el.remove()); // 展开被限制高度的内容 const articleContainer = document.querySelector(\u0026#39;.article_content\u0026#39;); if (articleContainer) { articleContainer.style.maxHeight = \u0026#39;none\u0026#39;; articleContainer.style.height = \u0026#39;auto\u0026#39;; } }; // 启用复制功能 const enableCopy = () =\u0026gt; { document.body.oncopy = null; document.oncopy = null; document.querySelectorAll(\u0026#39;*\u0026#39;).forEach(el =\u0026gt; { el.style.userSelect = \u0026#39;auto\u0026#39;; el.style.webkitUserSelect = \u0026#39;auto\u0026#39;; el.style.msUserSelect = \u0026#39;auto\u0026#39;; el.style.mozUserSelect = \u0026#39;auto\u0026#39;; }); }; // 使用MutationObserver来监视文档的变化 const observer = new MutationObserver((mutations) =\u0026gt; { mutations.forEach((mutation) =\u0026gt; { if (mutation.addedNodes.length) { adjustArticle(); enableCopy(); } }); }); observer.observe(document.body, { childList: true, subtree: true }); // 页面加载时尝试执行一次 window.addEventListener(\u0026#39;load\u0026#39;, () =\u0026gt; { adjustArticle(); enableCopy(); }); })(); 很难想象这么简单的代码就可以破解成功 尽管CSDN上绝大部分内容都是垃圾,但架不住多年的历史沉淀,还是有些优质的中文文章的,其中还有一部分是付费的,有了这个插件就好办多了\nPowerful Pixiv Downloader 喜欢逛Pixiv的有福了\n将图片从内嵌文件夹中提取出来的脚本:\n1 2 3 4 5 6 7 8 9 10 11 12 import os import shutil src = r\u0026#34;F:\\\\archive\\\\pixiv\u0026#34; dst = r\u0026#34;F:\\\\output\u0026#34; for root, dirs, files in os.walk(src): for f in files: if f.lower().endswith((\u0026#34;.jpg\u0026#34;, \u0026#34;.jpeg\u0026#34;, \u0026#34;.png\u0026#34;, \u0026#34;.webp\u0026#34;, \u0026#34;.bmp\u0026#34;, \u0026#34;.gif\u0026#34;)): src_path = os.path.join(root, f) dst_path = os.path.join(dst, f) shutil.copy(src_path, dst_path) Bypass Paywalls Clean 没钱看外刊的有福了\nuBlacklist 讨厌csdn的有福了,不过一般用Google搜索的话比bing搜索出来的垃圾就是会少很多\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 # CSDN *://*.csdnimg.cn/* /csdn\\.(com|net)/ /gitcode\\.(com|net|host)/ # 营销号（标题党、洗稿等低质或错误内容） *://*.sohu.com/* *://*.sina.cn/* *://*.163.com/* *://*.douyin.com/* *://www.doubao.com/* *://*.tiktok.com/* *://*.toutiao.com/* *://new.qq.com/* *://news.qq.com/* *://cloud.tencent.com/developer/* *://cloud.tencent.cn/developer/* *://www.showapi.com/news/* # 电商平台产品 *://*.taobao.com/* *://*.1688.com/* *://www.jd.com/* *://m.jd.com/* *://so.m.jd.com/* *://*.jd.hk/* *://i-search.jd.com/* # 抄袭（复制/镜像/同步搬运原创者内容） *://*.iii80.com/* *://*.iii80.cc/* *://i80.free.fr/* *://iii80.free.fr/* *://kknews.cc/* *://read01.com/* *://shrimpskin.com/* # AI 生成 *://*.jimowang.com/* *://docs.pingcode.com/* *://*.worktile.com/kb/* *://*.anzhuoe.net/* *://*.blog.51cto.com/* *://developer.baidu.com/* *://www.oryoy.com/* *://cloud.tencent.com/* *://developer.aliyun.com/* *://zhidao.baidu.com/* *://*.zeeklog.com/* 这是我结合大佬数据后弄的光荣榜\nCompetitive Companion 喜欢打oi的有福了\n","date":"2026-03-03T14:22:34Z","image":"/p/%E5%A5%BD%E7%94%A8%E5%B7%A5%E5%85%B7/44873217_p0-%E9%B5%9C%E9%A3%BC%E3%81%84.webp","permalink":"/p/%E5%A5%BD%E7%94%A8%E5%B7%A5%E5%85%B7/","title":"好用工具"},{"content":" 教材:\u0026laquo;计算机网络-自顶向下方法\u0026raquo; (3/20): 不得不承认是一本神书,尽管部分地方所作的假设与计算过于枯燥和无用,但整本书还是很有体系足够全面的 (4/3): 主干概念都讲的很明白,但边缘概念很多都讲的不明不白,不如不讲 应用层 应用层协议 http协议(2026/1/28) 事实上我把之前博客里写的内容直接搬过来了,比书上还是要详细不少的\n概览 参考链接 HTTP 是一个客户端—服务器协议：请求由一个实体，即用户代理（user agent），或是一个可以代表它的代理方（proxy）发出。大多数情况下，这个用户代理都是一个 Web 浏览器，不过它也可能是任何东西，比如一个爬取网页来充实、维护搜索引擎索引的机器爬虫。\n每个请求都会被发送到一个服务器，它会处理这个请求并提供一个称作响应的回复。在客户端与服务器之间，还有许许多多的被称为代理的实体，履行不同的作用，例如充当网关或缓存。\n客户端发送的请求\n1 2 3 4 5 6 7 8 9 10 11 POST /api/v1/user/update?id=1024 HTTP/1.1 Host: www.example.com Content-Type: application/json User-Agent: Mozilla/5.0 Authorization: Bearer eyJhbGci... Content-Length: 45 { \u0026#34;nickname\u0026#34;: \u0026#34;Apollo\u0026#34;, \u0026#34;gender\u0026#34;: \u0026#34;male\u0026#34; } POST: 客户端发起的请求所用方法 /api/v1/user/update?id=1024:服务器对应资源的路径和Query参数 Host: 目标服务器域名 Content-Type: 传输数据格式声明 User-Agent: 使用的浏览器代理 Authorization: cookie或token等身份识别头 Content-Length: 请求体字节长度 {\u0026quot;nickname\u0026quot;: \u0026quot;Apollo\u0026quot;,\u0026quot;gender\u0026quot;: \u0026quot;male\u0026quot;}: 传输的数据 服务端返回的报文\n1 2 3 4 5 6 7 8 9 10 HTTP/1.1 200 OK Date: Sat, 09 Oct 2010 14:28:02 GMT Server: Apache Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT ETag: \u0026#34;51142bc1-7449-479b075b2891b\u0026#34; Accept-Ranges: bytes Content-Length: 29769 Content-Type: text/html \u0026lt;!DOCTYPE html\u0026gt;…（此处是所请求网页的内容） METHOD wiki HTTP/1.1 协议中共定义了八种方法来以不同方式操作指定的资源,下面我列举常用的几种\nGET The request is for a representation of a resource.The server should only retrieve data; not modify state.\nHEAD The request is like a GET except that the response should not include the representation data in the body. HEAD = GET − Response Body\nPOST The request is to process a resource in some way.\nPUT The request is to create or update a resource with the state in the request.\nDELETE The request is to delete a resource.\nstatus code In HTTP, you send a numeric status code of 3 digits as part of the response. These status codes have a name associated to recognize them, but the important part is the number.\nIn short: 100 - 199 are for \u0026ldquo;Information\u0026rdquo;. You rarely use them directly. Responses with these status codes cannot have a body. 200 - 299 are for \u0026ldquo;Successful\u0026rdquo; responses. These are the ones you would use the most. 200 is the default status code, which means everything was \u0026ldquo;OK\u0026rdquo;. Another example would be 201, \u0026ldquo;Created\u0026rdquo;. It is commonly used after creating a new record in the database. A special case is 204, \u0026ldquo;No Content\u0026rdquo;. This response is used when there is no content to return to the client, and so the response must not have a body. 300 - 399 are for \u0026ldquo;Redirection\u0026rdquo;. Responses with these status codes may or may not have a body, except for 304, \u0026ldquo;Not Modified\u0026rdquo;, which must not have one. 400 - 499 are for \u0026ldquo;Client error\u0026rdquo; responses. These are the second type you would probably use the most. An example is 404, for a \u0026ldquo;Not Found\u0026rdquo; response. For generic errors from the client, you can just use 400. 500 - 599 are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes.\nUser Agent wiki 用户代理（user agent）在计算机科学中指的是代表用户行为的程序（软件代理程序）。例如，网页浏览器就是一个“帮助用户获取、渲染网页内容并与之交互”的用户代理\n简单来说,user agent是http header里的客户端标识,告诉目标服务器自己是通过哪个浏览器进行访问的\n爬虫总是需要伪装自己是通过某个代理访问服务器的,否则容易被拦截 一般格式\n1 User-Agent: \u0026lt;product\u0026gt; / \u0026lt;product-version\u0026gt; \u0026lt;comment\u0026gt; web浏览器的通用格式\n1 User-Agent: Mozilla/5.0 (\u0026lt;system-information\u0026gt;) \u0026lt;platform\u0026gt; (\u0026lt;platform-details\u0026gt;) \u0026lt;extensions\u0026gt; 具体示例\n1 2 Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59 cookie MDN 服务器收到 HTTP 请求后，服务器可以在响应标头里面添加一个或多个 Set-Cookie 选项。浏览器收到响应后通常会保存下 Cookie，并将其放在 HTTP Cookie 标头内，向同一服务器发出请求时一起发送。你可以指定一个过期日期或者时间段之后，不能发送 cookie。你也可以对指定的域和路径设置额外的限制，以限制 cookie 发送的位置\nWeb cache Web缓存是用于临时存储（缓存）Web文档（如HTML页面和图像），以减少服务器延迟的一种信息技术。Web缓存系统会保存下通过这套系统的文档的副本；如果满足某些条件，则可以由缓存满足后续请求\n流程如下：\n浏览器建立 TCP 连接 到 Web Cache，发送 HTTP 请求获取某个对象。\nWeb Cache 查询本地缓存。\n若存在对象副本（cache hit），直接返回 HTTP 响应给浏览器。 若本地没有该对象（cache miss），Web Cache 建立 TCP 连接 到 Origin Server。\nWeb Cache 通过该 TCP 连接向 Origin Server 发送 HTTP 请求。\nOrigin Server 返回包含对象的 HTTP 响应。\nWeb Cache 接收响应后：\n在本地缓存该对象副本 通过已有的 浏览器 ↔ Web Cache TCP 连接 将 HTTP 响应返回给浏览器。 从HTTP/1到HTTP/3 HTTP1采用非持续连接,每个HTTP请求都使用独立的TCP连接,可想而知慢的吓人 因此,HTTP1.1采用了并行的TCP持续连接,共享带宽,但这会增加连接占用的带宽,容易引发网络阻塞 这也是HTTP2推出的原因,只使用一个TCP持续连接来处理多个并行请求,将每个HTTP报文划分为帧单位,在第一个请求发送第一帧后,发送第二个请求的第一帧,以此类推,不断循环 至于HTTP3,改进的程度比较有限,且基于UDP而非TCP连接,在22年已经标准化\nSMTP协议(3/4) SMTP,一种广泛使用,历史悠久的邮件传输协议,与HTTP一样使用的是持续的TCP连接 流程如下:\nAlice 在 用户代理（Mail User Agent） 中输入 Bob 的邮箱地址（如 bob@someschool.edu），编写邮件并发送。\n用户代理将邮件发送到 Alice 的邮件服务器（Mail Server），邮件进入服务器的 邮件队列（message queue）。\nAlice 邮件服务器上的 SMTP 客户端 从队列中发现该邮件，并建立 TCP 连接 到 Bob 的邮件服务器上的 SMTP 服务器。\n完成 SMTP 握手 后，SMTP 客户端通过该 TCP 连接发送邮件内容。\nBob 的邮件服务器上的 SMTP 服务器 接收邮件，并将邮件存入 Bob 的邮箱（mailbox）。\nBob 在需要时启动 用户代理，从邮箱中读取邮件。\nDNS 识别主机有两种方式———主机名(hostname)和 IP 地址。人们喜欢便于记忆的主机名标识方式,而路由器则喜欢定长的、有着层次结构的 IP 地址,因为hostname(如www.baidu.com)不能给路由器任何有关这个主机所在位置的信息\n因此,我们需要一种能够将主机名转换成IP地址的服务,也就是 域名系统（ Domain Name System ， DNS ）,由DNS服务器和DNS协议构成,运行在UDP之上,故也是应用层协议\n常用查询方法 A 记录查询 (Address Record)\n物理定义：将域名直接映射到一个具体的 IPv4 地址。 工作机制：当发起 A 记录查询时，DNS 服务器会返回一个由 4 组数字组成的物理地址（如 192.168.1.1）。 特性：它是最基础、速度最快的解析方式。浏览器拿到 A 记录后，可以直接根据该 IP 地址物理寻找目标服务器并建立 TCP 连接。 CNAME 记录查询 (Canonical Name Record)\n物理定义：将一个域名指向另一个域名，而不是具体的 IP 地址。 工作机制： 客户端查询 www.example.com。 DNS 返回一个 CNAME 记录（如 example.cdn.com）。 客户端必须针对这个新域名再次发起 DNS 查询，直到最终获得一个 A 记录（IP 地址）。 特性： 别名逻辑：常用于 CDN 加速或云服务。当后端物理服务器 IP 变动时，只需更改最终域名的 A 记录，所有指向它的 CNAME 无需修改。 性能成本：相比 A 记录，CNAME 至少会增加一轮 DNS 查询的物理往返时间。 阮一峰教程 为什么DNS用UDP 我们可以简单总结一下 DNS 的发展史，1987 年的 RFC1034 和 RFC1035 定义了最初版本的 DNS 协议，刚被设计出来的 DNS 就会同时使用 UDP 和 TCP 协议，对于绝大多数的 DNS 查询来说都会使用 UDP 数据报进行传输，TCP 协议只会在区域传输的场景中使用，其中 UDP 数据包只会传输最大 512 字节的数据，多余的会被截断；两年后发布的 RFC1123 预测了 DNS 记录中存储的数据会越来越多，同时也第一次显式的指出了发现 UDP 包被截断时应该通过 TCP 协议重试\n实际应用举例 来看这样一个图,这是我在服务器上部署好了网站后,并在dynadot购买域名进入后台时将服务器的IP地址与对应域名绑定的过程.\n使用A记录时,只要将服务器的ip地址填入就可以生效,但是别人访问这个服务器的时候只能在地址栏输入类似qin******.com的内容来访问. 但用户可能会输入www.qin******.com来访问这个服务器,但DNS并不会自动帮我将www前缀删除,所以我需要增加一条CNAME记录,将www.qin******.com指向qin******.com.\nwww前缀代表着这个网站使用的是http协议,是一个web网站,但由于现在基本都是web网站,少有类似ftp.qin*****.com这样的网站名了 P2P协议 BitTorrent是里面最为著名的代表,简单来说是一种多人之间互相传数据分包的协议,从而避免多人仅通过连接一台服务器导致的下载速度延缓. 磁力链接,torrent等资源链接都采用这一协议进行传输\nDASH和CDN DASH(Dynamic Adaptive Streaming over HTTP),将视频编码为不同清晰度的版本,根据用户的可用带宽来动态选择对应画质版本的视频\nCDN(Content Distribution Network),由名字可以看出来,CDN相当于一个存储视频,文档等数据的分布式服务器,避免只用一个服务器来接受http请求导致的各种问题\nPort(端口) 可以理解为通信接口,也就是说,应用层的进程通过特定的端口实现与运输层TCP/UDP的连接.\n一个端口号使用16位无符号整数（unsigned integer）来表示，其范围介于0与65535之间 在TCP协议中，端口号0是被保留的，不可使用。1\u0026ndash;1023 系统保留，只能由root用户使用。1024\u0026ndash;4999 由客户端程序自由分配。5000\u0026ndash;65535 由服务器端程序自由分配。在UDP协议中，来源端口号可选择是否填上，如果设为0，则代表无来源端口号。 运输层 Reliable Data Transfer Protocol (RDT) 显然书上这部分的引入太长了,而且并没有作者自以为的讲得很详细很清晰,还是让AI来总结一下吧 可靠数据传输协议（Reliable Data Transfer Protocol，RDT）是计算机网络中用来保证发送方的数据能够完整、正确、按顺序送达接收方的一类协议。即使底层信道可能存在丢包、乱序或传输错误，RDT 也能通过设计机制让数据安全传输 1. RDT 的核心问题 在现实网络中，直接发送比特流存在很多潜在问题：\n丢包：数据包在传输中可能完全丢失。 数据错误：信道噪声或硬件故障可能导致比特翻转，接收方收到的数据与发送方发送的数据不同。 重复数据：网络重传机制或路由问题可能导致同一个数据包被接收多次。 乱序：数据包可能通过不同路径传输，先发送的包晚到达。 可靠数据传输协议需要解决这些问题，保证端到端的可靠性。\n2. 核心机制概览 RDT 的核心机制可以概括为五个部分：\n分包（Segmentation）\n将大数据拆分成较小的数据包，每个包称为 segment。\n优点：小包更容易重传，减少重发的代价，同时便于序列号管理。\n每个包通常包含以下内容：\n数据内容（payload） 序列号（Sequence Number） 校验和（Checksum） 错误检测（Error Detection）\n利用 Checksum 或 CRC（Cyclic Redundancy Check） 检测数据在传输中是否被破坏。\n发送方：计算数据包的校验和并附加在包头。\n接收方：收到包后重新计算校验值，如果与包头的值相同，则认为数据正确，否则认为数据出错。\n示例：\n1 2 数据内容: 10110011 校验和: 0110 接收方校验，如果不匹配，就触发重传。\n序列号（Sequence Number）\n每个数据包被赋予唯一的序列号（Sequence Number），用于：\n防止重复包处理 保证接收顺序 序列号通常为 0、1 或更大的整数，循环使用。\n接收方通过序列号判断是否收到新的包还是重复包。\n确认机制（Acknowledgment, ACK / Negative Acknowledgment, NAK）\nACK（确认）：接收方收到正确的数据包后发送 ACK 给发送方。\nNAK（否定确认）：接收方收到错误数据包时发送 NAK，要求发送方重发。\n作用：\n让发送方知道哪些数据包成功到达。 在丢包或错误情况下触发重传。 示例流程：\n1 2 发送方 -\u0026gt; 数据包 1 -\u0026gt; 接收方 接收方 -\u0026gt; ACK 1 -\u0026gt; 发送方 重传机制（Timeout \u0026amp; Retransmission）\n发送方设置 超时时间（Timeout）。\n如果在超时时间内没有收到 ACK，发送方会自动 重发数据包。\n与 ACK/NAK 配合，可以保证即使网络丢包，最终数据仍然完整送达。\n关键点：\n超时必须合理，否则可能导致过早重发或等待过久。 与序列号结合，保证重发的数据仍能正确排序，不被重复处理。 3. 可靠数据传输模型示例 3.1 Stop-and-Wait RDT（最基础模型） 发送端：\n发送一个数据包，附带序列号。 等待接收方发送 ACK。 如果超时未收到 ACK，重发数据包。 收到 ACK 后发送下一个包。 接收端：\n收到数据包，检查校验和。 如果正确，处理数据并发送 ACK。 如果错误或重复，丢弃数据或发送 NAK。 优点：简单易实现，理解方便。\n缺点：效率低，因为发送方在等待 ACK 期间 无法发送下一个数据包。\n3.2 Sliding Window RDT（滑动窗口机制,流水线机制） 改进 Stop-and-Wait，允许 发送方在等待 ACK 的同时发送多个包。\n核心概念：\n发送窗口：发送方未确认的数据包集合。 接收窗口：接收方能够接收的序列号范围。 优点：提高链路利用率，尤其是高延迟链路。\n实现机制：\n发送方维护一个窗口，窗口内的包可以连续发送。 接收方按序号接收，并发送 ACK。 窗口滑动：收到 ACK 后，发送方窗口向前滑动，可以发送新的包。 常见变种：\nGo-Back-N：出错时从错误包开始重发之后所有包。 Selective Repeat：只重发出错的包，更高效。 4. RDT 的工作流程总结 以最简单的 Stop-and-Wait 为例，端到端流程如下：\n1 2 3 发送方: [数据包 seq=0] --------\u0026gt; 接收方: 检查校验 -\u0026gt; 正确 -\u0026gt; [ACK seq=0] --------\u0026gt; 发送方: 收到 ACK -\u0026gt; 发送下一个数据包 seq=1 异常情况：\n数据包丢失：\n发送方超时重发。 数据包出错：\n接收方丢弃包并发送 NAK 或不发送 ACK。 ACK 丢失：\n发送方超时重发数据包，接收方根据序列号判断重复，避免重复处理。 核心思想：发送方持续重发，接收方持续确认，直到每个包安全到达。\nUDP(User Datagram Protocol) UDP的原理非常简单,就是将src post传输的数据 打包进UDP报文,并传递给网络层,再分发给dst post d\n使用 UDP 时，在发送报文段之前,发送方和接收方的运输层实体之间没有握手.正因为如此，UDP 被称为无连接的. 为什么要用UDP 无连接状态。TCP 需要在端系统中维护连接状态。此连接状态包括接收和发送缓存、拥塞控制参数以及序号与确认号的参数。另一方面， UDP 不维护连接状态 ， 也不跟踪这些参数。 因此， 当应用程序运行在 UDP 之上而不是运行在 TCP 上时，某些专门用于某种特定应用的服务器一般都能支持更多的活跃客户。 分组首部开销小 。每个 TCP 报文段都有 20 字节的首部开销 ， 而 UDP 仅有 8 字节的开销。 TCP 的拥塞控制会导致如因特网电话、视频会议之类的实时应用性能变得很差 。由于这些原因，多媒体应用开发人员通常将这些应用运行在 UDP 之上而不是 TCP 之上 UDP包分析 一个完整的 UDP 包由 UDP 头部 + 数据负载（Payload）组成\nUDP头部(固定8字节) 字段 字节数 说明 Source Port 2 源端口 Destination Port 2 目标端口 Length 2 UDP 头 + 数据总长度 Checksum 2 校验和，验证头部和数据是否损坏 1 2 3 4 5 6 0 15 16 31 +---------+---------+ | src port| dst port| +---------+---------+ | length | checksum| +---------+---------+ UDP 数据负载（Payload） 长度 = UDP Length – 8（头部大小） 完全由应用程序生成 UDP检验和 检验和用于确定当 UDP 报文段从源到达目的地移动时，其中的比特是否发生了改变 （ 例如，由于链路中的噪声干扰或者存储在路由器中时引入问题 ）。发送方的 UDP 对报文段中的所有 16 比特字的和进行反码运算，求和时遇到的任何溢出都被回卷(把最高位进位送入最低位相加) 。 得到的结果被放在 UDP 报文段中的检验和字段. 显然接收方可以再进行一次求和与反码相加,如果出现了0就说明数据有地方出错了.\nTCP(Transmission Control Protocol) TCP 被称为是面向连接的 （ connection-oriented），这是因为在一个应用进程可以开始向另一个应用进程发送数据之前 ， 这两个进程必须先相互 “ 握手 ”，即它们必须相互发送某些预备报文段，以建立确保数据传输的参数\nTCP报文段分析 一个完整的 TCP 报文段由 **TCP头部（Header）+ 选项（Options，可选）+ 数据负载（Payload）**组成，其中最小 TCP 头部固定 20 字节。\nTCP头部（最小20字节） 字段 字节数 说明 Source Port 2 源端口，标识发送应用进程 Destination Port 2 目标端口，标识接收应用进程 Sequence Number 4 序列号，标识第一个数据字节编号 Acknowledgment Number 4 确认号，期望收到的下一个字节编号（ACK置1有效） Data Offset 4位 TCP头部长度（单位：32位字/4字节） Reserved 3位 保留，必须置0 Flags（控制位） 9位 URG, ACK, PSH, RST, SYN, FIN 等 Window Size 2 接收端可接收缓冲区大小（流量控制） Checksum 2 TCP头+数据+伪首部校验和 Urgent Pointer 2 紧急指针，URG置1时有效 TCP头部字段示意图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0 15 16 31 +-------------------+--------------------+ | Source Port | Destination Port | +-------------------+--------------------+ | Sequence Number | +---------------------------------------+ | Acknowledgment Number | +----+---+---+--------------------------+ |Data|Res|Flags| Window Size | |Offset| | | | +----+---+---+--------------------------+ | Checksum | Urgent Pointer | +-------------------+--------------------+ | Options (可选, 0~40 字节) | +---------------------------------------+ | TCP Payload (数据) | +---------------------------------------+ Flags（控制位）：\nURG：紧急指针有效 ACK：确认号有效 PSH：接收端应用立即读取数据 RST：重置连接 SYN：同步序列号，用于建立连接 FIN：关闭连接 数据偏移（Data Offset）：\n表示 TCP 头部长度，单位4字节 最小值 5（20字节），最大 15（60字节，包括选项） 窗口大小用于流量控制，控制发送端发送未确认的数据量。\n校验和覆盖 TCP 头部、数据以及伪首部（IP源/目的地址、协议号、TCP长度）。\n序号和确认号 报文段的序号 （ sequence number for a segment ） 是该报文段首字节的字节流编号\n举例来说 ，假设主机 A 上的一个进程想通过一条 TCP 连接向主机 B 上的一个进程发送一个数据流 。主机 A 中的 TCP 将隐式地对数据流中的每一个字节编号 。假定数据流由一个包含 500 000 字节的文件组成，其 MSS 为 1000 字节，数据流的首字节编号是 0。则该 TCP 将为该数据流构建 500 个报文段。给第一个报文段分配序号 0 ，第二个报文段分配序号 1000 ，第三个报文段分配序号 2000，以此类推。每一个序号被填入到相应TCP报文段首部的序号字段中。\n至于确认号,首先我们需要明确为什么需要确认号这样一个东西,由于TCP连接是双向的,故发送方也需要收到接收方传来的信息,故需要在确认号字段里填写缺失的来自接收方传来的包序号\n假设主机 A 已收到一个来自主机 B 的包含字节 0 ～ 535 的报文段，以及另一个包含字节 900 ～ 1000 的报文段。 由于某种原因， 主机 A 还没有收到字节 536 ～ 899 的报文段。 在这个例子中， 主机 A 为了重新构建主机 B 的数据流，仍在等待字节 536 （ 和其后的字节）。 因此 ， A 到 B 的下一个报文段将在确认号字段中包含 536\nTCP连接的建立(三次握手) 第一步：SYN 报文段 客户端向服务器发送一个首部 SYN 比特置为 1 的特殊报文段。该报文不含应用层数据，但包含客户端随机选择的初始序号 client_isn。该报文段被封装在 IP 数据报中发送。 第二步：SYNACK 报文段 服务器接收到 SYN 后，为连接分配 TCP 缓存和变量，并回送允许连接的报文段。此时 SYN 比特置为 1，确认号字段设为 client_isn + 1，同时服务器选择自己的初始序号 server_isn。此报文段表明服务器同意建立连接。 第三步：ACK 报文段 客户端收到 SYNACK 后，也为连接分配缓存和变量，并向服务器发送最后的确认报文。此时 SYN 比特置为 0，确认号字段设为 server_isn + 1。该阶段的报文段负载可以开始携带实际的客户数据。 注意,第三步的时候客户端可以主动选择终止连接 拓展:为什么要三次握手 想象一下这个场景，如果通信双方的通信次数只有两次，那么发送方一旦发出建立连接的请求之后它就没有办法撤回这一次请求，如果在网络状况复杂或者较差的网络中，发送方连续发送多次建立连接的请求，如果 TCP 建立连接只能通信两次，那么接收方只能选择接受或者拒绝发送方发起的请求，它并不清楚这一次请求是不是由于网络拥堵而早早过期的连接。\n使用三次握手和 RST 控制消息将是否建立连接的最终控制权交给了发送方，因为只有发送方有足够的上下文来判断当前连接是否是错误的或者过期的，这也是 TCP 使用三次握手建立连接的最主要原因。\nTCP连接的终止(四次挥手) TCP 协议是对称的全双工协议，连接的任何一方（无论最初是客户端还是服务器）都可以主动调用 close() 发送 FIN 报文段来开启连接终止流程。在RFC 793中，通常将发起关闭的一方称为 Active Closer（主动关闭方），将接收关闭请求的一方称为 Passive Closer（被动关闭方）。\n全双工:双方都可以同时发送和接收消息 主流程 主动方 (Active Closer) 发送 FIN 该方应用进程决定不再发送数据。TCP 实体构造首部 FIN 位为 1 的报文段。发送后，主动方进入 FIN-WAIT-1 状态。 被动方 (Passive Closer) 确认 ACK 被动方收到 FIN 后，由 TCP 协议栈自动回送 ACK。此时被动方进入 CLOSE-WAIT 状态，并通知上层应用进程对端已关闭。主动方收到 ACK 后进入 FIN-WAIT-2。 被动方 (Passive Closer) 发送 FIN 当被动方的应用进程处理完所有剩余数据后，显式关闭套接字。TCP 发送 FIN 位为 1 的报文段，被动方进入 LAST-ACK 状态。 主动方 (Active Closer) 发送最终 ACK 主动方收到对端的 FIN 后，发送最后一个 ACK，进入 TIME-WAIT 状态。被动方收到此 ACK 后直接进入 CLOSED 状态，释放所有资源。 主动方的资源释放延迟 主动方必须在 TIME-WAIT 状态维持 2MSL 时长，以确保最后一个 ACK 到达被动方，并清空网络中残存的旧报文段。倒计时结束后，主动方进入 CLOSED 并释放资源。 TCP中的可靠数据传输 为了实现可靠数据传输,TCP有以下三个机制: 超时间隔,冗余ACK和快速重传\n首先,TCP设置一个初始超时间隔,发出某个包时开始计时,如果接收方未能在超时间隔内将ACK确认包传来,则标记为超时. 每当超时事件发生时,TCP重传具有最小序号的还未被确认的报文段。只是每次 TCP 重传时都会将下一次的超时间隔设为先前值的两倍.\n接收方保存一个连续接收到的包末端序号,比如说,\u0026ldquo;已经收到了 100-200 和 300-400，但中间缺了 201-299\u0026rdquo;,那么末端序号就是201,尽管接收到了300-400的包,接收方仍会返回ACK=201,如果这次仍未收到201-299的包,则继续发送ACK=201,直到收到该部分的包为止,这被称为冗余ACK,之后,接收方立刻将序号调整为ACK=401.\n如果发送方接收到对相同数据的3个ACK时,这说明之后的报文段已经丢失,那么发送方就执行快速重传,即重新发送之后的报文段.\n换句话说,只收到两个ACK时不进行重传,原因如下: 如果包 A 比包 B 稍微晚了一点点到达（仅仅是走错了路或者在路由器队列里被插了队），接收方就会因为先收到 B 而发回一个重复 ACK。如果此时发送方立即重传，会导致网络中充斥着大量无谓的重传包，极大地浪费带宽。 因此,重传只在以下两种情况中发生:\n超出超时间隔未收到该包对应的ACK时重传,这被称为超时重传 收到该包的三个重复ACK则立刻重传,这被称为快速重传 TCP流量控制 TCP 为它的应用程序提供了流量控制服务 （ flow-control service ） 以消除发送方使接收方缓存溢出的可能性. 为了实现流量控制,发送方维护一个接收窗口(receive window,rwnd),接收方为该TCP连接分配了一个接收缓存,并维护以下三个变量:\nLastByteRead：应用进程从缓存读出的最后一个字节编号。 LastByteRcvd：放入接收缓存中的最后一个字节编号。 RcvBuffer：接收缓存的总大小。 为防止缓存溢出，必须满足： LastByteRcvd - LastByteRead ≤ RcvBuffer\n那么接收窗口就需要根据缓存可用的空间来设置： rwnd = RcvBuffer - [ LastByteRcvd - LastByteRead ]\n具体实现 通知机制：主机 B 将当前的 rwnd 值放入发给主机 A 的报文段“接收窗口”字段中。 发送方限制：主机 A 跟踪 LastByteSent（已发送编号）和 LastByteAcked（已确认编号）。 发送准则：主机 A 必须保证在连接生命周期内： LastByteSent - LastByteAcked ≤ rwnd 如果B的接收缓存已经存满,即 rwnd = 0 ,主机 B 随后清空缓存但没有数据回传，主机 A 将因无法获知新空间而被阻塞,因此当 rwnd = 0 时，主机 A 持续发送仅含一个字节数据的探测报文段,这些报文段会被确认，最终主机 B 的确认报文将包含非 0 的 rwnd 值，从而恢复传输。\n拥塞控制原理(3/11) 由于实际应用中网络层总是不能实现具有无限容量的,无数据丢失的信道,故当数据传输量很大时信道很容易发生拥塞,故需要运输层使用拥塞控制方法来尽可能减小拥塞的可能. 在最为宽泛的级别上,我们可根据网络层是否为运输层拥塞控制提供了显式帮助,来区分拥塞控制方法。\n端到端拥塞控制 在端到端拥塞控制方法中，网络层没有为运输层拥塞控制提供显式支持。即使网络中存在拥塞，端系统也必须通过对网络行为的观察 （ 如分组丢失与时延） 来推断之\n网络辅助的拥塞控制 路由器会向发送方提供关于网络中拥塞状态的显式反馈信息\nTCP拥塞控制 TCP 所采用的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向 连接发送流量的速率 。 如果一个 TCP 发送方感知从它到目的地之间的路径上没什么拥塞 ， 则 TCP 发送方增加其发送速率 ； 如果发送方感知沿着该路径有拥塞 ， 则发送方就会降低其发送速率 。\n但是,这种方法提出了三个问题:\nTCP 发送方如何限制它向其连接发送流量的速率呢 ？ TCP 发送方如何感知从它到目的地之间的路径上存在拥塞呢 ？ 当发送方感知到端到端的拥塞时 ， 采用何种算法来改变发送速率呢 ？ TCP发送方如何限制发送速率 如之前的TCP流量控制所说,TCP 连接的每一端都是由一个接收缓存,一个发送缓存和几个变量 （ LastByteRead 、rwnd 等） 组成。 运行在发送方的 TCP 拥塞控制机制跟踪一个额外的变量,即拥塞窗口(congestion window,cwnd),它对一个 TCP 发送方能向网络中发送流量的速率进行了限制,即在一个发送方中未被确认的数据量不会超过 cwnd 与 rwnd 中的最小值.\nLastByteSent -LastByteAcked≤ min{cwnd，rwnd}\n因此,在接收窗口比较大的时候,通过调节cwnd的值,发送方可以调整发送数据的速率.\nTCP发送方如何感知拥塞以及如何调整速率 至于如何感知发生了拥塞,TCP使用下列指导性原则：\n丢失报文段意味着拥塞：当丢失报文段时，应当降低 TCP 发送方的速率。一个超时事件或四个确认（一个初始 ACK 和其后的三个冗余 ACK）被解释为“丢包事件”的隐含指示。TCP 发送方应当减小它的拥塞窗口长度，以应对这种推测的丢包事件。 确认报文段指示网络交付正常：当对先前未确认报文段的确认到达时，能够增加发送方的速率。确认的到达被认为是一切顺利的隐含指示，即报文段正成功交付，网络不拥塞。拥塞窗口长度因此能够增加。 带宽探测：TCP 调节其传输速率的策略是增加其速率以响应到达的 ACK，除非出现丢包事件，此时才减小传输速率。为探测拥塞开始出现的速率，TCP 发送方增加它的传输速率，从该速率后退，进而再次开始探测。网络中没有明确的拥塞状态信令，ACK 和丢包事件充当了隐式信号，每个 TCP 发送方根据异步于其他发送方的本地信息而行动。 完整的论述: TCP拥塞控制算法 该算法由三个阶段组成:\n慢启动(Slow Start) 拥塞避免(Congestion Avoidance) 快速恢复(Fast Recovery) - 非必需 慢启动 cwnd的初始值通常都设置的很小,对于发送方而言,希望能够迅速找到可用带宽的大小.因此,cwnd的值以一个**MSS(最大报文长度)**开始,每当传输的报文段被确认就增加一个MSS,这样一来,每经过一个RTT(往返时间),发送速率就翻倍,以指数级别增长,如图所示: 如果丢包事件发生,发送方将会把cwnd值重新置为1,并设置一个新的变量ssthresh (slow start threshold)为cwnd/2-这里的cwnd值检测到丢包时的拥塞窗口大小,然后继续发送包. 当cwnd值增长到等于ssthresh时,发送方结束慢启动阶段,进入拥塞避免阶段\n拥塞避免 该阶段中,每次传输后(即经过一个RTT后)将cwnd的值加一.\n当再次触发丢包事件时,cwnd的值被再次置为1,ssthresh的值再次置为当前cwnd值的一半.\n然而丢包事件有两个可能的触发机制:超时和收到3个冗余ACK. 对于后一种情况,既然能够收到接收方传来的ACK,这说明信道不是那么拥塞,因此,发送方只会将cwnd的值减半,并进入快速恢复阶段.\n快速恢复 该阶段中,每当收到针对丢失报文冗余的ACK时,cwnd值加1 最终发送方收到对于丢失报文的ACK时,TCP降低cwnd后进入拥塞避免阶段;如果出现超时事件,则迁移到慢启动状态.\n总览 TCP连接的公平性 如果在某一条有限带宽的链路上,不同的TCP连接的传输速率基本相同,则认为该拥塞控制算法是公平的.\n在实践中,具有较小RTT的TCP连接能够在链路空闲时更快的占据带宽,从而享用更高的吞吐量;而由于UDP没有拥塞控制,所以需要专门抑制UDP的无限制增长来防止UDP占用所有带宽.\n不管怎样,这个问题至今都没有很好的得到解决.\nQUIC QUIC,Quick UDP Internet Connections,与HTTPS同为应用层协议,但它使用UDP作为运输层协议,主要特征如下:\n数据流: 允许不同的应用程序使用同一个QUIC连接 可靠传输: 尽管UDP是不可靠的,但我们可以让QUIC的应用层可靠,即加上数据检验和重传机制 网络层(3/12) 网络层用一句话来表示就是: 通过多个路由器将数据从服务端移动到客户端.为此,网络层需要具备以下两种功能:\n转发: 当数据输入时,路由器需要将数据移动到适当的输出端口 路由选择: 网络层需要解决数据从服务端传输到客户端所采用的路径,相当于规划好了具体要用到的转发路由器 注意这里的端口是指物理的输入输出接口,与虚拟的软件端口不是一个概念. 网络层不具有重传机制,而是由上层协议来决定是否重传 这样来看的话,网络层便可以分为负责转发功能的数据平面和负责路由选择功能的控制平面.\n数据平面 路由器工作原理 路由器有四个组件:\n输入端口 交换结构: 将路由器的输入端口连接到输出端口 输出端口 路由选择处理器: 主要作用于交换结构 输入端口处理和基于目的地转发 输入端口可以通过转发表和嵌入式的搜索算法来查找传入的ip地址对应的输出端口,故可以在本地实现转发决策,而无需调用路由选择处理器.\n交换方法 经内存交换 早期的路由器是传统的计算机,端口之间的交换.一个分组到达一个输入端口时，该端口会先通过中断方式向路由选择处理器发出信号。于是 ，该分组从输入端口处被复制到处理器内存中。路由选择处理器则从其首部提取目的地址，在转发表中查找适当的输出端口,并将该分组复制到输出端口的缓存中.\n经总线交换 输入端口经一根共享总线将分组直接传送到输出端口,不需要路由选择处理器的干预.由于总线单一时间只能通过一个数据分组,故交换速率受到总线速率的限制.\n经互联网络交换 克服单一、共享式总线带宽限制的一种方法是,使用一个更复杂的互联网络.如纵横式交换机使用由2N条总线构成的互联网络,能够并发转发多个分组,因此是非阻塞的\n输出端口处理 输出端口处理取出已经存放在输出端口内存中的分组并将其发送到输出链路上\n分组(packet):从传输层传入的打包好的数据帧 排队问题分析 在输入端口和输出端口处都可以形成分组队列，就像在环状交叉路的类比中我们讨论过的情况，即汽车可能等待在流量交叉点的入口和出口。排队的位置和程度（或者在输入端口排队，或者在输出端口排队）将取决于流量负载、交换结构的相对速率和线路速率。\n我们现在更为详细地考虑这些队列，因为随着这些队列的增长，路由器的缓存空间最终将会耗尽，并且当无内存可用于存储到达的分组时将会出现丢包（packet loss）。回想前面的讨论，我们说过分组“在网络中丢失”或“被路由器丢弃”。正是在一台路由器的这些队列中，分组被实际丢弃或丢失。\n为什么不加缓存空间? 我们很容易这样想：更多的缓存必定更好，因为更大的缓冲区能承受更大的分组到达率波动，从而降低路由器的丢包率。\n但更大的缓冲区也意味着潜在的更长的排队时延。\n对于游戏玩家和交互式电话会议用户，几十毫秒的时延就很关键。\n如果为了减少丢包而把每跳路由器的缓冲区大小增加10倍，端到端时延可能随之增加10倍。\n增加的RTT会使TCP发送方对拥塞或分组丢失的响应变慢：\nTCP依赖RTT来估计超时重传定时器（RTO）。 RTT增大 → RTO变大 → 检测到丢包后等待更久才重传。 拥塞控制窗口增长更慢（慢启动、拥塞避免阶段受RTT影响）。 整体吞吐响应性下降，尤其在突发拥塞或间歇性丢包场景。 如何决定分组转发的优先权? 有以下几种调度方法\n先进先出( First-In-First-Out，FIFO,也称为First-Come-First-Serve，FCFS) 调度规则按照分组到达输出链路队列的相同次序来选择分组在链路上传输\n优先权排队(priority queuing) 针对不同分组或者IP地址配置不同优先级别的权限,每次转发分组时均从当前最高优先级别的队列中选择分组来传输,同一级别则采用FIFO方式\n循环排队(round robin queuing discipline)和加权公平排队(Weighted Fair Queuing) 循环排队像优先权排队规则一样给不同分组分类,但是会轮流处理不同类,从而避免低优先级类难以被转发的情况. 加权公平排队会为不同类分配权重,保证在某一时刻下在排队队列中的某一类能够得到对应权重的转发量.\nIPv4,IPv6,寻址(3/13) IPv4数据报格式 网络层中的分组被称为数据报,IPv4数据报的格式如下:\n版本:使用的IP协议版本,帮助路由器决定如何解释数据报的剩余部分 首部长度:用来确定载荷(运输层报文)实际开始的字节 服务类型: 可以将不同服务类型的数据报区别开来 数据报长度: 首部加上数据的总字节长度,为16bit,故理论最大长度为65535字节,但实际上很少超过1500字节 标识,标志,片偏移: 将一个大数据报分片为几个小数据报所需的字段 寿命: 确保数据报不会一直在网络中循环 协议: 指示数据部分是由哪个协议处理的,例如值为17表示要交给UDP处理 首部检验和: 检验是否有数据错误或丢失 源IP地址和目的IP地址 可选字段: 很少用 数据(有效载荷): 来自运输层 IPv4编址 一台主机通常只有一条链路连接到网络 ，当主机中的 IP 想发送一个数据报时，它就在该链路上发送。主机与物理链路之间的边界叫作接口(interface)\n路由器与它的任意一条链路之间的边界也叫作接口,因此每台主机与路由器都能发送和接收IP数据报,为了明确源地址和目标地址,故每台主机和路由器接口都有自己的IP地址.\n在IPv4协议中,一个IP地址长度为32字节,书写方法为点分十进制记法(dotted-decimal notation),即每个字节都用它的十进制形式书写,因此会有3个点,4个部分,每个部分占8字节,如:\n193. 32. 216. 9 :11000001 00100000 11011000 00001001 互联这 3 个主机接口与 1 个路由器接口的网络形成一个子网(subnet),IP编址为这个子网分配一个地址223. 1. 1. 0 / 24,其中, /24被称为子网掩码,将地址的前24位设定为该子网的IP地址 表示为255.255.255.0.需要时可以通过将子网掩码与ip地址取和得到子网地址;换句话说,只有前24位等于223.1.1的地址才属于这个子网.\n可以在cmd输入ipconfig查看自己所属的子网. IP地址是如何分配的(3/15) IP 地址由因特网名字和编号分配机构(Internet Corporation for Assigned Names and Numbers,ICANN)管理,是非盈利的. 当某个组织获得了被分配的地址块后,可以为本组织内的主机与路由器接口逐个分配 IP 地址,这主要由动态主机配置协议 (Dynamic Host Configuration Protocol ， DHCP)完成.\n某给定主机每次与网络连接时能得到一个相同的 IP 地址,或者被分配一个临时的 IP 地址. 除了主机 IP 地址分配外，DHCP 还允许一台主机得知它的子网掩码、它的第一跳路由器地址（也就是直连路由器,常称为默认网关）与它的本地 DNS 服务器的地址等其他信息.\n网络地址转换(Network Address Translation,NAT) NAT（网络地址转换） 是工作在路由器等网关设备上的物理逻辑，它允许整个局域网（LAN）内的成百上千台设备，通过同一个公网 IP 地址（WAN 端）访问互联网。\nA. 出站请求 (SNAT - Source NAT) 当你的手机（192.168.1.5）访问网站时：\n原始数据包：源 IP 是 192.168.1.5，源端口是 10001。 物理改写：路由器将源 IP 修改为自己的 公网 IP（如 1.2.3.4），并将源端口修改为一个新分配的端口（如 5000）。 记录映射：路由器在 NAT 表中记下一笔：5000 端口 \u0026lt;-\u0026gt; 192.168.1.5:10001。 B. 入站响应 (DNAT - Destination NAT) 当网站服务器回传数据时：\n到达数据包：目标地址是 1.2.3.4:5000。 物理查询：路由器查找 NAT 表，发现 5000 端口对应的是内网的 192.168.1.5:10001。 物理还原：路由器将目标 IP 和端口还原，数据包准确送达手机。 IPv6 IPv6将地址长度从32bit增加到128bit,确保了IP地址不会被用完,具体结构如下:\n版本: 4bit长,字段值为6 流量类型: 与IPv4的服务类型类似 流标签: 对数据流中的某些数据报给出优先权 有效载荷长度: 载荷的字节数量 下一个首部: 与IPv4中的协议字段含义一样,表示要交付给哪个运输层协议 跳限制: 类似于IPv4中的寿命字段,当跳限制计数变为0时,包被丢弃 源地址和目的地址 数据: 载荷 事实上,IPv6不再使用子网掩码,而是直接用一个整数表示网络部分的位数，写在地址后的斜线后,如: fe80::49e0/128\n如何兼容IPv4 通过将IPv6数据报整个放入IPv4数据报的载荷部分中,并通过协议字段指示接收方这是一个装入了IPv6的IPv4数据报,从而实现了对网络层中使用IPv4路由器的兼容\n补充: 泛化转发(4/22) 之所以叫泛化转发(Generalized Forwarding),是因为这里的转发除了网络层之外,还可能会涉及链路层,这与之前路由器的转发并不相同,因此加上泛化两个字来表示这个转发是宽泛而言的.\n因此,泛化转发使用的设备也不是简单的使用链路层的交换机或者网络层的路由器,而是称为分组交换机 wiki 泛化转发主要基于OpenFlow标准来执行: 流表（Flow Table） 这是泛化转发的逻辑核心。每个分组交换机内部维护一个或多个流表。每个流表项（Flow Table Entry）由三个核心部分组成：\n首部字段匹配值（Match）：这是“泛化”的具体体现。匹配字段可以涵盖 入端口、源/目的 MAC 地址、以太网类型、VLAN 标签、源/目的 IP 地址、协议号（TCP/UDP）以及源/目的端口号。这种跨 1-4 层的匹配能力，使得分组交换机既可以充当路由器，也可以充当交换机、防火墙或 NAT 设备。 计数器（Counter）：用于实时统计匹配该规则的分组数量、字节数等。这是流量监控与计费的物理基础。 操作（Actions）：当分组匹配成功后执行的动作，包括： 转发：发送到指定端口。 丢弃：执行安全过滤逻辑。 修改字段：例如修改 IP 头部（NAT 功能）或重写 MAC 地址（路由功能）。 入栈/出栈：针对 MPLS 或 VLAN 标签的封装处理。 匹配-动作（Match-plus-Action）抽象 与传统路由器“查找最长前缀-转发”的单一模式不同，泛化转发将其抽象为通用的“匹配-动作”范式。\n机制本质：只要分组匹配首部字段（Match），就执行相应操作（Action）。这意味着网络管理员可以自定义非标准的处理流程。 流的概念：被相同规则处理的一系列分组被视为一个“流（Flow）”。网络管理的粒度从单纯的“路径选择”细化到了“流控制”。 控制与数据平面分离 OpenFlow 实现了物理上的解耦：\n数据平面（Data Plane）：分组交换机只负责执行流表中的匹配逻辑，逻辑简单且支持硬件加速（通常基于 TCAM 内存）。 控制平面（Control Plane）：由远程控制器（Controller）统一管理。当交换机遇到无法识别的未知流时，会将其通过 OpenFlow 协议发送给控制器。控制器计算出处理规则后，再下发回交换机的流表中。 换句话说,数据在网络层中转发的时候,不再是简单的通过路由器后再经过交换机,而是直接通过分组交换机转发,从而大幅度减小了传播过程中出差错的概率.因此,泛化转发才是真正的转发,取代了我们之前所说的路由器转发.\n中间盒(middlebox) 中间盒: 在源主机和目的主机之间的数据路径上,执行除了 IP 路由器的正常标准功能之外的其他功能的任何中间的盒子,可以提供以下三种功能服务:\nNAT转换 安全服务: 如防火墙和电子邮件过滤器等 性能增强: 提供内容缓存等功能 控制平面 路由选择算法 按照集中式的还是分散式的来给算法分类:\n集中式路由选择算法(centralized routing algorithm):也被称为链路状态(Link State,LS)算法,需要提前得知网络中每条链路的开销,从而计算出当前节点的最低开销路径 分散式路由选择算法(decentrlized routing algorithm): 每个节点仅有与其直接相连的节点链路开销,通过迭代和通信逐渐计算出最低开销路径,距离向量(Distance Vector)算法是一个代表. 链路状态(LS)算法 通过链路状态广播(link state broadcast)算法来获取整体的节点信息,并根据Dijkstra算法找到最佳路径. 但是,实际应用中会产生振荡(Oscillation)问题,也就是当路由恰巧都沿着最短路径转发时,这条路径的开销由于流量增大而不再是最优路径,因此路由都转向原本开销比较高但现在相对是开销最低的那条线路,又导致这条线路开销变高,路由又都回到原来的路由转发路径上,这样来回切换路径显然不利于网络减小时延.\n距离向量(DV)算法 使用Bellman-Ford算法\n因特网中自治系统内部的路由选择： OSPF 如果将网络看作一个大规模的路由器互联网络会遇到以下两个问题:\n规模瓶颈（Scalability）：数亿台设备若运行单一路由算法，将导致内存耗尽、广播风暴以及算法（如距离向量）无法收敛。 管理自治（Administrative Autonomy）：不同 ISP 需要独立控制内部协议、隐藏拓扑结构，并按自身策略进行管理。 因此,设计者将路由器规划成一个自治系统( Autonomous System,AS): 构成：处于相同管理控制下的路由器和链路集合。 标识：每个 AS 拥有全局唯一的 AS 号 (ASN)，由 ICANN 授权机构分配。 划分：ISP 可作为一个 AS，也可拆分为多个 AS。 光这样说还不够,看下面这个表格:\n运营商 网络名称 AS 号 (ASN) 角色 中国电信 ChinaNet (163 骨干网) AS4134 全球最大的 AS 之一，承载绝大多数宽带流量。 中国电信 CN2 (下一代承载网) AS4809 独立的 AS，专注于高质量、低延迟的精品业务。 中国联通 中国联通骨干网 AS4837 原中国网通与联通合并后的核心 AS。 中国移动 中国移动骨干网 AS9808 移动宽带及移动端流量的核心承载 AS。 这样就很好理解了,当我们接入网络的时候,实际上是进入了某一个运营商的AS,而不是直接接入整个互联网. 在一个AS内部运行的路由选择算法称为(intra-autonomous system routing protocol).\n开放最短路优先 （ OSPF ） 该算法使用Dijkstra和周期性的路由广播,也就是说:\n路由器向自治系统内所有其他路由器广播路由选择信息,而非只向相邻路由器广播. 每当一条链路的状态发生变化时(如开销的变化或连接 / 中断状态的变化)， 路由器就会广播链路状态信息 。即使链路状态未发生变化，它也要周期性地(至少每隔 30min一次)广播链路状态.从而能够运用Dijkstra得到正确的结果.\nISP之间的路由选择：BGP 前面讨论的是AS内部的路由选择,现在我们来考虑AS之间的路由选择,在internet中,所有AS运行相同的路由选择协议: 边界网关协议(Border Gateway Protocol,BGP).\nBGP的作用 作为一种 AS 间的路由选择协议，BGP 为每台路由器提供了一种完成以下任务的手段：\n从邻居 AS 获得前缀的可达性信息: 特别是，BGP 允许每个子网向因特网的其余部分通告它的存在。一个子网高声宣布 \u0026ldquo;我存在，我在这里\u0026rdquo;，而 BGP 确保在因特网中的所有 AS 知道该子网。如果没有 BGP 的话，每个子网将是隔离的孤岛，即它们孤独地存在，不为因特网其余部分所知和所达。\n确定到该前缀的 \u0026ldquo;最好的\u0026rdquo; 路由: 一台路由器可能知道两条或更多条到特定前缀的不同路由。为了确定最好的路由，该路由器将本地运行一个 BGP 路由选择过程 (使用它经过相邻的路由器获得的前缀可达性信息)。该最好的路由将基于策略以及可达性信息来确定。\n前缀指的是例如138.16.68/22这样的AS子网 通告BGP路由信息 一个AS中有两种类型的路由器:网关路由器(gateway router)和内部路由器(internal router),网关路由器直接与其他AS中的路由器相连接,内部路由器仅连接AS内部的主机和路由器\n与先前AS内部的路由广播类似,网关路由器也会将对应的转发信息传递给相邻的网关路由和内部路由,从而找到不同AS之间的转发路由路径\n找到最短路径(待补充) IP任播(anycast) 路由选择策略 SDN控制平面 wiki ICMP: 因特网控制报文协议 wiki 互联网控制消息协议（英语：Internet Control Message Protocol，缩写：ICMP）是互联网协议族的核心协议之一。它用于网际协议（IP）中发送控制消息，提供可能发生在通信环境中的各种问题反馈。通过这些信息，使管理者可以对所发生的问题作出诊断，然后采取适当的措施解决。\nICMP与传输协议（如TCP和UDP）显著不同：它一般不用于在两点间传输数据。它通常不由网络程序直接使用，除了 ping 和 traceroute 这两个特别的例子.\n事实上,ICMP位于IPv4和IPv6报文里面,用于提供网络发生问题时返回报错信息\n链路层 OVERVIEW 节点: 运行链路层协议的任何设备,包括主机,路由器,交换机,WiFi接入点 链路: 连接相邻节点的通信信道 链路层需要提供的服务 成帧(framing): 将网络层的数据报封装成链路层帧 协调多个节点之间的通信: MAC协议 可靠交付: 通过确认和重传确保数据不出错和丢失 差错检测 链路层是如何实现的 链路层控制器的大部分功能是在硬件中实现的,但也有部分链路层是在运行与主机CPU上的软件实现的.\n差错检测和纠正 奇偶校验 假设要发送的信息D有d比特,在偶校验方案中,发送方附加一个校验比特,使得者d+1个比特中1的总数是偶数;在奇校验方案中则保证是奇数. 接收方只需要检测接收的d+1个比特中1的个数即可以验证是否出现差错,比如在偶校验方案中发现了奇数个1比特,则说明至少出现了1个比特差错. 但如果出现了偶数个比特的差错,那就无法检测出差错了.因此,可以采用二维的奇偶校验方案,通过行和列来检验从而减小没有检测到差错的概率\n检验和方法 与TCP/UDP协议中采用的检验和类似.\n循环冗余检测(Cyclic Redundancy Check,CRC)(待补充) 双方协商取一个最高位为1的r+1位多项式G,发送方在d位数据段D的后端附加r个附加比特R,使得这个d+r位数据在模2算术下可以被G整除,接收方只需要用G去除这个收到的数据就可以知道是否出现差错.\n多路访问协议(multiple access protocol) 该协议的目标是实现多个发送和接收节点对同一个共享信道的访问.\n因为所有的节点都能够传输帧,所以多个节点可能会同时传输帧,那么其他节点就会同时接收到多个帧,发生碰撞(collide),这时没有一个节点能够有效获得传输的帧.\n在理想情况下，对于速率为 R bps 的广播信道，多路访问协议应该具有以下所希望的特性：\n当仅有一个节点发送数据时，该节点具有 R bps 的吞吐量; 当有 M 个节点发送数据时，每个节点吞吐量为 R / M bps. 这不必要求 M 个节点中的每一个节点总是有 R / M 的瞬间速率，而是每个节点在一些适当定义的时间间隔内应该有 R / M 的平均传输速率; 协议是去中心化的; 这就是说不会因某主节点故障而使整个系统崩溃; 协议是简单的，使实现不昂贵. 信道划分协议(过) 随机接入协议(待补充) 在随机接入协议中，一个传输节点总是以信道的全部速率（即 R bps）进行发送. 当有碰撞时，涉及碰撞的每个节点反复地重发它的帧（也就是分组），到该帧无碰撞地通过为止. 但是当一个节点经历一次碰撞时，它不必立刻重发该帧. 相反，它在重发该帧之前等待一个随机时延. 涉及碰撞的每个节点独立地选择随机时延. 因为该随机时延是独立地选择的，所以下述现象是有可能的：这些节点之一所选择的时延充分小于其他碰撞节点的时延，并因此能够无碰撞地将它的帧在信道中发出.\nSwitched Local Area Networks Link-Layer Addressing and Address Resolution Protocol(ARP) MAC地址 A link-layer address is variously called a LAN address, a physical address, or a MAC address.\n事实上,并不是主机或路由器具有链路层地址,而是它们的适配器(即网络接口)具有链路层地址\nThe MAC address is 6 bytes long, giving 2^48 possible MAC addresses. 尽管MAC地址是一个固定值并且是唯一的,但现在有可能用软件改变某个接口的MAC地址.\n需要注意的是,一般来说一个设备的MAC地址总是不变的,而对应的IP地址总是根据连接到的网络而改变 一般来说,当源适配器要向向一个目的适配器发送帧时,它会讲目的适配器的MAC地址插入帧中,并将该帧发送到局域网中;有时候源适配器或者中途经过的交换机会将帧广播到所有的适配器. 当目的适配器接收到帧时,如果帧中的目的MAC地址与自己的MAC地址匹配,则会提取出封装的数据报并沿着协议栈向上传输;如果不匹配,则直接丢弃这个帧.\n有时候源适配器需要让局域网的所有适配器接收并处理自己的帧,这个时候,可以在目的地址字段插入一个特殊的MAC广播地址,对于6字节MAC地址的局域网来说,广播地址是48个连续的1组成的字符串 Address Resolution Protocol,ARP(地址解析协议) 为了在发送链路层帧的时候指明接收该帧的适配器,需要提前知道适配器的MAC地址,从而实现包的正确发送,因此我们采用ARP协议来根据适配器对应路由器或主机的IP地址,找到其MAC地址\nARP: 将网络层地址与链路层地址相互转换的协议.它和DNS的作用基本类似,但一个重大区别是: DNS可以解析因特网上任意主机或者域名为IP地址,但ARP只为同一个子网的主机和路由器接口解析IP地址为MAC地址 ARP原理 每台主机或者路由器在内存中有一个ARP表(ARP table),包含了IP地址与MAC地址的映射关系,还有保存时限值,指示了从表中删除该映射的时间.\n主机要发送帧前,先要发送一个ARP packet给自己的适配器,这个ARP分组包括发送和接收方的IP地址以及自己的MAC地址,指示适配器使用MAC广播地址发送这个分组并发送该链路层帧,从而让子网的所有适配器接收这个分组,IP地址与该ARP packet中包含的目的IP地址匹配的适配器会向发送方传回一个响应ARP packet,让发送方可以更新它的ARP表.\n从这个角度来看,ARP与IP处于同一级别,都位于网络层 如何跨越子网传输数据 通过上面的论述,我们可以发现,ARP只适用于子网内部的转发,当我们要跨越子网转发数据时,应该先将数据转发给网关路由,再由网关路由来根据链路层帧包含的IP地址跳到下一个路由器,直到转发到目标主机的网关路由,再将数据交给目标主机\n以太网(Ethernet) 以太网(Ethernet)几乎完全占据了有线局域网市场 以太网的帧结构 Data field (46 to 1,500 bytes): This field carries the IP datagram. The minimum size of the data field is 46 bytes.This means that if the IP datagram is less than 46 bytes, the data field has to be “stuffed” to fill it out to 46 bytes. Destination address (6 bytes): This field contains the MAC address of the destination adapter. Source address (6 bytes): This field contains the MAC address of the adapter that transmits the frame onto the LAN Type field (2 bytes): 指示数据字段所用网络层协议的类型 Cyclic redundancy check (CRC) (4 bytes): 检测帧差错 Preamble (8 bytes): 用于唤醒适配器,告诉它现在有一个以太网帧来了 以太网的无连接传输 以太网技术向网络层提供不可靠服务. 具体来说, 当适配器 B 收到一个来自适配器 A 的帧, 它对该帧执行 CRC 校验, 但是当该帧通过 CRC 校验时它既不发送确认帧; 而当该帧没有通过 CRC 校验时它也不发送否定确认帧. 当某帧没有通过 CRC 校验, 适配器 B 只是丢弃该帧. 因此, 适配器 A 根本不知道它传输的帧是否到达了 B 并通过了 CRC 校验.\n在链路层缺乏可靠传输有助于使以太网变得简单且便宜. 但是这也意味着传递到网络层的数据报流可能存在间隙.\n如果由于丢弃了以太网帧而存在间隙, 主机 B 上的应用也会看见这个间隙吗? 正如我们在第 3 章中所学到的, 这完全取决于该应用是使用 UDP 还是 TCP.\n如果应用使用的是 UDP, 则主机 B 中的应用确实会看到数据中的间隙. 另一方面, 如果应用使用的是 TCP, 则主机 B 中的 TCP 将不会确认包含在丢弃帧中的数据, 从而引起主机 A 的 TCP 重传. 注意到当 TCP 重传数据时, 数据最终将回到曾经丢弃它的以太网适配器. 因此, 从这种意义上来说, 以太网确实重传了数据, 尽管以太网本身并不知道它正在传输的是一个包含全新数据的全新数据报, 还是一个包含已经被传输过至少一次的数据的数据报.\n链路层交换机(Link-Layer Switches)(待补充) 转发和过滤 Filtering is the switch function that determines whether a frame should be forwarded to some interface or should just be dropped. Forwarding is the switch function that determines the interfaces to which a frame should be directed, and then moves the frame to those interfaces.\n假定目的地址为 DD-DD-DD-DD-DD-DD 的帧从交换机接口 x 到达,那么会有以下三种情况:\n交换表中没有对应 DD-DD-DD-DD-DD-DD 的表项,则将这个帧广播到所有接口(除了接口x,因为帧是从接口x来的) 交换表中有对应 DD-DD-DD-DD-DD-DD 的表项,但是对应接口为x,那么显然你不可能把从接口x收到的帧再送回接口x,只能就地丢弃这个帧 交换表中有对应 DD-DD-DD-DD-DD-DD 的表项,对应接口不是x而是y,那么交换机就需要把这个帧送入接口y 疑问:第二种可能中,如果是局域网通信的话,那不就有可能用的是同一个接口吗? 在局域网（LAN）中，如果两个主机 A 和 B 都在“同一个接口”下，通常只有两种物理物理场景：\n场景 A：接了集线器（Hub） 你将 A 和 B 都接在一个外接 Hub 上，再把 Hub 连到交换机的接口 $x$。 场景 B：共享介质（旧式同轴电缆） 所有主机物理上连在同一根线上。 交换机（Switch）的核心作用是隔离冲突域并跨端口转发。\n当主机 A 发送一个目的 MAC 为 B 的帧进入接口 $x$ 时：\n查表：交换机查找 MAC 地址表，发现 B 的 MAC 对应接口也是 $x$。 判断：交换机意识到，“目的地”和“来源地”都在同一个物理方向上。 结论：如果 A 和 B 都在接口 $x$ 下方，那么当 A 发出信号时，信号在到达交换机之前，物理上已经流经了 B（如果是 Hub 或共享总线）。 底层逻辑：交换机认为，既然目标 MAC 就在接收端口所在的网段内，那么目标主机 B 应该已经通过物理介质直接收到了这个帧。如果交换机再把它从接口 $x$ “弹回”去，不仅是浪费带宽，更会导致 B 收到两份重复的数据帧。\n交换表的建立与交换机的自学习 交换表初始为空 对于在每个接口接收的帧,交换机会在表中记录以下信息: 该帧的源MAC地址 该帧是从哪个接口进来的 当前时间 如果一段时间后,交换机没有接收到过与记录的源MAC地址相同的帧,那么就会在表中删除这个地址 那么,让我们来详细分析一个新产生的帧被目的MAC地址对应的交换机接收的过程:\n当这个帧进入第一个交换机时,由于交换机不认识这个帧,故会在所有接口转发该帧并记录该帧信息 经过很多次转发,在传播路径上的交换机都记录了该帧的三种信息 当目的MAC地址对应的交换机接收到该帧时,由于目的MAC地址对应的服务器一定已经将自己的信息存在了该交换机中(思考一下是为什么),那么只需要将该帧送入服务器的适配器即可完成整个流程. 虚拟局域网(VLAN) wiki 由于书上讲的不明不白,所以去额外找资料了 VLAN的工作原理是在广播域内转发的网络帧上添加标签，从而使网络流量看起来如同被分割在不同的网络中。这样即使在同一个物理网络，VLAN也能将网络隔离开来，而无需部署多套电缆和网络设备。\n无线网络概览 前面谈的都是有线网络,现在来谈谈WiFi,4G等无线网络,它由以下三个要素组成:\n无线主机: 可以是手机,电脑,家用电器,可以是移动的,也可以是位置固定不动的 无线链路: 主机通过无线链路连接到一个基站或者另一个主机 基站: 负责收发无线链路数据 我们可以把无线网络分为四类:\n具有基站的单跳: 基站和主机之间可以直接通信,不需要经过中继节点,日常见到的无线网络如WiFi和4G都属于这一类型 无基站的单跳: 由一个主机进行收发无线数据,其他设备与该主机通信,而不通过基站,蓝牙协议便是典型的例子 具有基站的多跳: 主机与基站通信需要经过中继节点 无基站的多跳: 通过多个主机节点进行通信,这显然是最难设计的一类网络 无线链路的特征 与有线链路相比,无线链路有以下三个特点:\n信号强度递减: 电磁波穿过物体时强度将减弱,也就是路径损耗 来自其他通信源的干扰: 同一频段的电磁波会相互干扰 多路径传播: 电磁波会经过地面或者物体反射,导致出现多径传播的问题 显然,无线网络更容易出现差错,因此我们需要使用更为可靠的传输方法. 信噪比( Signal-to-Noise Ratio，SNR):单位为dB,是接收到的信号振幅与噪声幅度比值的以10为底的对数的20倍,显然,信噪比越大,信号就越干净. 比特差错率(BER): 接收方收到的一个比特为错误的概率 物理层的通信有以下特点:\n使用同一个调制方法,SNR越高,BER越低: 因此发送方可以通过增加传输功率提高SNR来降低BER,但是这样会消耗更多的能量,并且当功率超过阈值时不再有实际增益 对于给定的SNR,调制方法的传输速度越高BER越高,这很容易理解,速度越快越容易出差错 对于给定的信道我们可以采用不同的调制技术 CDMA(待补充) WiFi 尽管无线局域网(WLAN)的通信有很多技术可以选用,但是WiFi占据着统治地位. WiFi的正式名称为IEEE 802. 11 无线局域网\n基本服务集 (BSS) 是WiFi的基本构件，有两个部分：\n无线站点 (Station)：手机、笔记本等终端设备。 接入点 (Access Point,AP)：起中央基站作用的节点。 信道与关联 当管理员安装AP时,需要为AP分配一个服务集标识(Service Set Identifier,SSID)和一个信道号.\n802.11运行在 2. 4GHz ～ 2. 485GHz的频段中,由11个部分重叠的信道组成, 当且仅当两个信道由 4 个或更多信道隔开时它们才无重叠,因此管理员需要指示AP的信道号避免影响到其他AP.\n显然,当你进入咖啡馆时,你可以收到很多个AP传来的信号,那么我们是符合与一个特定AP连接的呢? 802.11协议要求每个AP周期性的发送信标帧,该帧包括该AP的SSID和MAC地址,供无线站点接收并处理;当然,主机也可以自己发送广播帧来主动扫描附近的AP,选定关联AP,具体过程如图所示: 选定与之关联的 AP 后，无线主机向该 AP 发送一个关联请求帧，并且该 AP 以一个关联响应帧进行响应。注意，对于主动扫描需要第二个请求 / 响应握手 ，因为一个对初始探测请求的帧进行响应的 AP 并不知道主机选择哪个 （ 可能多个 ） 响应的 AP 进行关联 ， 这与 DHCP 客户能够从多个 DHCP 服务器中进行选择有诸多相同之处\n与AP连接之后,主机便会发送DHCP报文获取自己的IP地址,从而与互联网连接.\n802. 11采用的传输协议: CSMA/CA 802.11采用的MAC协议类型为随机接入协议,称为带碰撞避免的CSMA(CSMA with collision avoidance), 简称为CSMA/CA. 之所以802.11采用碰撞避免而非和以太网相同的碰撞检测,有以下两个原因:\n检测碰撞需要主机同时具有发送信号和接收其他站点信号的能力,而802.11适配器接收到的信号强度往往远小于发送出去的信号强度 无线信号存在衰减,多路传播,隐藏终端等问题,无法检测到所有的碰撞 802.11的链路层确认方案 由于无线链路中的帧不能无损的到达目的地,因此802.11采用链路层确认方案来实现重传机制. 目的站点收到一个通过CRC校验的帧后,等待一小段时间-这被称为短帧间间隔(short inter-frame spacing),然后发回一个确认帧,如果发送站点在给定时间内没有收到确认帧,它就假定出现了错误并进行重传,如果若干次重传后仍未收到确认帧,则放弃发送并丢弃该帧.\n为什么现代以太网不重传而802.11重传 因为802.11的环境极易丢包,如果依靠上层协议如TCP的重传机制,由于跨越了多层硬件,传输速率将会大幅度下降;而以太网线路的环境非常稳定,发生丢包的概率几乎为零,将少量的丢包问题交给上层协议处理可以减少不必要的设计复杂度.\nCSMA/CA协议的详细原理 如果有一个帧要发送,发送站监听到信道空闲时,它等待一小段时间后发送该帧-这被称为分布式帧间间隔(distributed inter-frame space) 如果信道被占据,则站点选取一个随机回退值,在侦听到信道空闲时减小该值,信道繁忙时则保持该值不变 计数值减到零时,该站点发送帧并等待确认 如果收到确认,则帧被正确接收.如果要传输另一个帧,站点将从第二步重新开始;如果没收到确认,则站点进入第二步并在一个更大的范围内选取随机值 满格信号网速仍然很慢的原理 当大量主机使用同一个AP时,为了避免碰撞,单一主机的等待时间大量延长,从而导致了网速的降低;同时,AP的上行光纤容量有限,不允许所有设备以最大功率同时传输.\n处理隐藏终端问题 尽管每个无线站点对AP都不隐藏,但两者彼此是隐藏的 当H1和H2要发送帧时,由于它们彼此是看不见的,因此都以为信道是空闲的,就会同时发送并导致碰撞.\n为了避免碰撞,802.11通过让站点使用请求发送(Request to Send,RTS)控制帧和允许发送(Clear to Send,CTS)控制帧来预约何时访问信道. 发送方在发送帧之前,首先向AP发送一个RTS帧,指示自己传输帧和收到确认帧所需的总时间,AP收到该帧后广播一个CTS帧,指示发送方明确的发送许可并告知其他站点在这段时间内不要发送,从而避免碰撞.\n这两个控制帧都很短,所以在这个过程中发生碰撞的可能性很小 尽管 RTS / CTS 交换有助于减少碰撞 ， 但它同样引入了时延并消耗了信道资源。因此 ，RTS / CTS 交换仅仅用于为长数据帧预约信道 。 在实际中，每个无线站点可以设置一个 RTS门限值，仅当帧长超过门限值时，才使用 RTS / CTS 序列。对许多无线站点而言 ，默认的RTS 门限值大于最大帧长值 ， 因此对所有发送的 DATA 帧，RTS / CTS 序列都被跳过。\n802.11的帧结构 接下来对其中的重要组成部分做一点分析:\n有效载荷与CRC字段 地址字段: 地址4仅在AP相互转发时使用,前三个字段的定义如下: 地址1是要接收该帧的站点MAC地址 地址2是传输该帧的站点MAC地址 地址3是子网默认网关的MAC地址 序号,持续期,帧控制字段 无线站点在同一子网中的不同BSS之间移动 随着 H1 逐步远离 AP1 ，H1 检测到来自 AP1 的信号逐渐减弱并开始扫描一个更强的信号.H1 收到来自 AP2 的信标帧. H1然后与 AP1 解除关联,并与AP2 关联起来,同时保持其 IP 地址和维持正在进行的 TCP 会话.新AP2 会发送以太网广播,强制沿途交换机更新路径.\n蓝牙 蓝牙网络运行在 2.4 GHz ISM（工业、科学、医学） 免授权频段。由于该频段同时被微波炉、车库门遥控器和无绳电话等多种设备占用，蓝牙在设计之初就将抗噪与抗干扰作为核心目标。\n蓝牙的物理原理 时分复用（TDM）：蓝牙无线信道被划分为时长为 625 微秒 的时间隙。 频分跳频扩展频谱（FHSS）：在每个时间隙中，发送端在 79 个信道中的某一个进行传输。信道（频率）在每个时隙间按照已知但伪随机的规律切换。 抗干扰逻辑：通过跳频技术，即使 ISM 频段内存在其他设备的干扰，也只会影响到极少数特定时隙的蓝牙通信，从而保证了整体链路的稳健性。目前蓝牙数据速率最高可达 3 Mbps。 蓝牙网络的结构 在蓝牙网络（Piconet）中，设备角色分为以下三类：\n主节点 (Master Device)：核心控制单元，负责管理连接数量、调度时隙以及控制所有连接设备的传输功率。 客户设备 (Client/Slave Device)：受控单元，遵循主节点的跳频序列进行通信。 存放设备 (Parked Device)：低功耗睡眠模式设备。它们保持与主节点的同步，但不参与数据传输，仅在需要时由主节点唤醒进入活跃状态。 主节点如何与其他设备连接 连接过程本质上是一个从“频率盲区”到“时间与频率双重同步”的过程，分为 查询（Inquiry） 和 寻呼（Paging） 两个阶段：\n第一阶段：设备发现（查询） 广播探测：主节点在 32 个不同的信道上轮流发送询问消息，并将该序列重复传输多达 128 次，以确保覆盖所有可能的监听频率。 被动监听：潜在的客户设备在自己随机选择的频率上进行监听。 随机回退机制：一旦客户设备接收到查询，它会在 0 ~ 0.3 秒 之间选择一个随机时间量进行回退。这种“随机退避”设计是为了防止多个客户设备同时响应主节点而引发信号冲突。 身份响应：回退结束后，客户设备发送包含其唯一设备 ID 的报文响应主节点。 第二阶段：建立连接（寻呼） 定向寻呼：主节点在发现范围内所有潜在设备后，开始针对特定设备发送 32 条寻呼邀请报文。由于此时客户设备尚未获得跳频序列，主节点仍需在多个频率上重复发送。 确认握手：客户设备接收到寻呼报文后，返回 ACK（确认） 报文。 参数交付：主节点随后向客户设备发送关键配置信息，包括： 跳频序列模式（告知未来的频率路径）。 时钟同步信息（校准通信时间基准）。 活跃成员地址（分配逻辑地址）。 轮询激活：最后，主节点使用已同步的跳频模式对该客户设备进行轮询(polling)。一旦回复成功，双方正式进入连接状态，实现网络层面的握手。 更上层发生了什么 我们可以发现,蓝牙的链路层连接逻辑与其他的协议完全不同,这从而说明蓝牙的上层逻辑也与其他的协议不同,这里就不介绍了.\nCellular Networks: 4G and 5G 无线网络分为无线局域网和无线广域网,我们前面所说的WiFi和蓝牙就属于局域网,而这里的4G/5G就属于广域网了.\nThe term cellular(蜂窝) refers to the fact that the region covered by a cellular network is partitioned into a number of geographic coverage areas, known as cells. Each cell contains a base station that transmits signals to, and receives signals from, the mobile devices currently in its cell.\n4G LTE Cellular Networks: Architecture and Elements The 4G networks that are pervasive as of this writing in 2020 implement the 4G Long-Term Evolution standard, or more succinctly 4G LTE.\n4G网络由以下几个部件构成:\nMobile device: 连接到蜂窝运营商网络的智能手机 、 平板电脑、笔记本电脑或物联网设备 该移动设备还具有全球唯一的 64 位标识符，称为国际移动用户身份 （ IMSI），存储在其 SIM （ 用户身份模块） 卡上。IMSI 在全球蜂窝网络系统中识别用户，包括用户所属的国家和归属蜂窝网络 。 Base Station: The base station is responsible for managing the wireless radio resources and the mobile devices with its coverage area. 这类似于WiFi中的AP,但还有很多额外的功能 Home Subscriber Server (HSS): The HSS is a database, storing information about the mobile devices for which the HSS’s network is their home network. Serving Gateway (S-GW), Packet Data Network Gateway (P-GW), and other network routers: 用于实现NAT,路由转发等功能 Mobility Management Entity (MME): 控制平面部件,用于验证设备,设置路径,追踪设备位置 无线链路对高层协议的影响 鉴于无线链路传播的比特差错率远高于有线链路,因此需要对上层协议如TCP连接做出针对性的处理\n在所有情况下，TCP 的接收方到发送方的 ACK 都仅仅表明未能收到一个完整的报文段，发送方并不知道报文段是由于拥塞或切换 ， 还是由于检测到比特错误而被丢弃的。在所有情况下 ，发送方的反应都一样 ， 即重传该报文段 。 TCP 的拥塞控制响应在所有场合也是相同的 ，即 TCP 减小其拥塞窗口,这会导致带宽利用率骤降.\n链路层重传 (LL-ARQ) 这是解决无线丢包的最前线。LTE、5G 或 Wi-Fi 协议在数据链路层（MAC/RLC 层）实现了快速重传。\n机制： 基站与终端之间发现包序列不连续时，直接在二层进行重传，不向网络层汇报。 代价： 引入了抖动 (Jitter)，因为链路层重传会导致部分包延迟到达。 拥塞控制算法的进化 (BBR) Google 提出的 BBR (Bottleneck Bandwidth and RTT) 算法改变了判断逻辑。\n原理： BBR 不再将“丢包”作为减速信号。它通过周期性探测带宽极大值和延迟极小值来构建网络模型。 实战表现： 在一定比例（如 5% ~ 15%）的随机丢包环境下，BBR 能维持几乎满带宽的传输速率，而 CUBIC 则会因为不断减半窗口而彻底卡死。 选择性确认 (SACK) 原生 TCP 采用累积确认，丢一个包可能导致后续一连串包被重传。\n机制： 启用 TCP_SACK 选项。接收端在 ACK 中告知发送端具体哪些数据块（Ranges）已收到。 效果： 发送端可以精确补齐缺失的数据，而不是盲目重传所有未确认的包。 拆分连接协议 (Split-TCP / Proxy) 在移动通信核心网中，常使用 PEP (Performance Enhancing Proxy)。\n拓扑： 终端 \u0026lt;——无线链路——\u0026gt; 基站/边缘网关 \u0026lt;——有线主干——\u0026gt; 服务器。 机制： 将一个 TCP 连接拆分为两段。无线段使用针对高丢包优化的协议（如调整过初始窗口、禁用慢启动退避的私有协议），有线段保持标准 TCP。 逻辑： 避免了无线端的局部丢包反馈到远端服务器，缩短了重传的往返时间 (RTT)。 网络安全 网络通信中的安全需要满足以下要求:\n机密性(confidentiality): 仅有发送方和希望的接收方能够理解传输报文的内容,这需要报文能够被加密和解密 完整性(message integrity): 通信内容在传输过程中不应该被篡改,这需要我们实现数据检验和可靠的传输通道 通信认证(end-point authentication): 发送方和接收方都需要能够证明另一方的身份是真实的 防御恶意攻击(operational security): 通过防火墙和入侵检测系统来防御网络攻击 密码学 我们现在假设A要向B发送一个报文,最初的文本被称为明文(plaintext),A使用密钥(key)m来加密明文,从而得到密文(ciphertext),并将其发送出去. B为了解密这个密文,需要使用密钥n来处理这个密文,从而得到明文.\n显然,加密过程中最关键的就是密钥了,根据密钥是否对称(相同)可以将加密方式分为两种:\n对称密钥系统: A和B所用的密钥是相同而且保密的,否则就没有任何意义了 非对称密钥系统(也称为公开密钥系统): 有两个密钥,一个是公开的密钥,称为公钥,所有人都可以获取;另一个是只有A或者B才知道的密钥 攻击手段 根据攻击者掌握的信息,大致有三种攻击手段:\n密文攻击: 入侵者只截取到了密文,破解难度最高 已知明文攻击: 攻击者事先知道部分明文对应的密文 选择明文攻击: 攻击者能够选择任意一段明文并获取对应的密文,破解难度最低 对称密钥 Caesar Cipher(凯撒密码) 密钥为字母表偏移量,很容易被破解\nBlock Cipher(块密码) TLS采用的就是这个加密方式 将明文划分为固定长度k的块，通过一一对应的映射函数将其转换为相同长度的密文块.如果我们让k=3,就需要让000到111这8个输入转换到对应的三比特输出. 尽管当增大k值时转换表的破解难度大幅度上升,但交流的双方都需要维护一张2^k大小的转换表,这不太现实. 因此,块密码使用函数来模拟转换表,例如:当k=64时,我们可以把64比特块划成8个子块,每个8比特块按照一张独特的k=8的转换表处理,然后将转换后的64比特按照一定规则打乱后再分为8个子块进行处理,进行多次循环.这种算法的密钥是8张k-8的转换表.\n如果用 1 秒破解 56 比特 DES 的计算机 （ 就是说，每秒尝试所有 2^56个密钥 ） 来破解一个 128 比特的 AES 密钥 ，要用大约 149 万亿年的时间才有可能成功 Cipher-Block Chaining: 引入随机性 前面的块密码有一个缺陷: 如果有多个块中的明文内容相同,如\u0026quot;HTTP/1.1\u0026quot;这类常见的前缀词,那么将会得到相同的密文,从而被攻击者探测到并进行破解. 因此,我们可以在加密时引入随机数,如下图所示: 这样一来,即使明文相同,得到的密文也会不同,而密文相同时,得到的明文可能相同,破解难度大幅上升.\n同时,实际应用中不太可能一个个传输随机数,故块密码使用密码块链接(Cipher-Block Chaining,CBC)来高效传输接收方所需的随机数,基本方法如下:\n在发送数据之前,发送方生成一个随机的k比特串(对应k比特块加密),称为初始向量(Initialization Vector,IV).表示为c(0),并以明文方式发给接收方 对第一个块，发送方计算 c(1) = Ks(m(1) ⊕ c(0))后发送密文 对于第 i 个块，发送方根据 c(i) = Ks(m(i) ⊕ c(i-1)) 生成第 i 个密文块 非对称密钥 该类型的密钥有三个关键要素:\n一个公开的加密算法和一个公开的解密算法 一个公开的密钥,称为公钥 只有通信中的某一方才知道的密钥,称为私钥 使用非对称密钥的通信过程如下: 发送者用加密算法和公钥来加密报文,接收者使用解密算法和私钥来解密报文.\n但是,这种加密方式有不少问题:\n如何保证加密后的报文不会被破解? 由于任何人都可以通过公钥加密报文后发送给接收方,如何确定发送者是可靠的? 尽管有很多算法可以解决上述问题,但RSA算法已经成为了非对称密钥的代名词.\nRSA算法 为了生成RSA的公钥和私钥,接收方需要执行以下步骤:\n选择两个大素数p和q,值越大就越难破解. 计算n=pq和z=(p-1)(q-1) 选择小于n的一个数e,使得e和z互质. 选择一个数d,满足ed mod z = 1 那么接收方的公钥由(n,e)组成,私钥由(n,d)组成 通信过程如下:\n发送方发送的报文以二进制形式表示为一个整数m,由于报文的位数有限,故m一般不会很大,在RSA算法中要求m \u0026lt; n,然后计算c = m^e mod n,从而得到密文c,并交给接收方. 为了解密c,接收方计算m = c^d mod n,得到报文m. 举一个简单的例子:\n接收方选择p=5和q=7,得到n=35和z=24,在5,7,11等与24互质的数中选择一个数,比如说5,那么由于(5x29)-1可以被24整除(尽管5x5-1等取值也可以被24整除,但假设我们是随机取值取到了29),则d=29. 发送方使用(35,5)加密报文,接收方使用(35,29)报文. 会话密钥 由于大数据的指数运算非常耗时,所以实际应用中我们可以把对称密钥和RSA结合在一起,用RSA来加密对称密钥算法中所需的密钥.这个被加密的密钥称为会话密钥(session key)\nRSA原理 RSA运用了两个数论中的结论,这里不做说明,我们只需要知道通过(n,e)和(n,d)就足够进行加密和解密,攻击者如果想暴力破解，唯一的方法是:\n试图从公开的 n 中分解出两个原始质数 p 和 q。 只有得到 p 和 q，才能计算出z = (p-1)(q-1)。 通过ed ≡ 1 mod z 解出私钥 d。 RSA 的安全性依赖于这样的事实：目前没有已知的算法可以快速进行一个数的因数分解 ，这种情况下公开值 n 无法快速分解成素数 p 和 q。如果已知 p 和 q ，则给定公开值 e， 就很容易计算出秘密密钥 d 。 另一方面，不确定是否 存在 因数分解一个数的快速算法 ， 从这种意义上来说，RSA 的安全性也不是确保的 。\n报文完整性的鉴别和数字签名 接收方验证某条消息是否可靠,需要检测以下两个因素:\n该报文来自可靠的发送方 该报文在途中没有被篡改 加密哈希函数(cryptographic hash function) hash function: 任意输入都会得到相同长度的输出 cryptographic hash function: 在哈希函数的基础上,需要额外保证: 找到一个不同的输入得到相同的输出是基本不可能的. 从加密哈希函数的定义就可以看出来,攻击者一般来说是无法用其他报文伪造原报文的,从而保证该报文不会被篡改.\n常用的加密哈希函数有以下几种:\n算法 哈希长度 基本逻辑 安全性现状与建议 MD5 128 位 将输入信息按 512 位分组，通过复杂的压缩函数进行多轮迭代处理，生成 128 位哈希值。 ❌ 已破解，可人为制造碰撞。任何安全场景下都不应使用。 SHA-1 160 位 逻辑与 MD5 类似，哈希值更长（160 位），处理步骤更复杂，安全性有所提升。 ⚠️ 已不足够安全，存在理论碰撞攻击，Google 等已在 2017 年实现碰撞。应避免使用。 SHA-2 224 / 256 / 384 / 512 位 采用 Merkle-Damgård 结构，更多轮次和更复杂的逻辑（如更多常量）。其中 SHA-256 和 SHA-512 最常用。 ✅ 广泛认为安全，是替代 MD5 和 SHA-1 的首选标准。 SHA-3 224 / 256 / 384 / 512 位 采用全新的 Keccak 海绵函数结构，与 SHA-2 设计思路完全不同，提供更高的安全冗余。 ✅ 安全性高，可作为 SHA-2 的备选方案。 报文鉴别: 使用哈希函数和鉴别密钥 使用哈希函数,我们可以这样验证报文的完整性:\nAlice生成报文m并计算散列H(m). 然后Alice将H(m)附到报文m上,生成一个扩展报文(m, H(m)),并将该扩展报文发给Bob. Bob接到一个扩展报文(m, h)并计算H(m). 如果H(m) = h,Bob得出结论:一切正常. 但这种方法有一个问题: 攻击者可以声称他就是Alice,并将报文发送给Bob,这显然可以通过报文完整性的验证.\n因此,我们还需要加入鉴别密钥(authentication key),即一个被双方共享的秘密比特串,在下述过程中我们将其称为s.\nAlice生成报文m,加入s生成m+s,并计算H(m+s),这被称为报文鉴别码(Message Authentication Code,MAC). 然后Alice将MAC附加到报文m上,生成扩展报文(m, H(m+s)),并将该扩展报文发送给Bob. Bob接收到一个扩展报文(m,h),由于知道s,计算出报文鉴别码H(m+s).如果H(m+s)=h,Bob得出结论:一切正常. 数字签名 有时候,我们会需要用一个数字签名(digital signature)来标识某个文件,这个数字签名一定是独一无二,不可篡改的,这样才可以保证能够分辨出该文件的所有者.\n一种方法是发送方使用某一加密哈希函数取得报文的hash值,并用私钥加密该hash值;而在验证这个签名时,接收方只需要用公钥去解密这个hash值,如果计算结果与该文件的hash值一致,则证明报文的来源可靠\n公钥认证 要让公钥密码生效,必须要证明你使用的公钥来源可靠,比如A和B通信时,A需要证明报文中的公钥确实来自B.\n因此,我们需要通过CA(certification authority)来将公钥与特定实体(一个人,一台路由器,一个网站等)绑定.\nCA有以下两个作用:\n证实一个实体的身份.这需要该CA能够执行严格的身份验证,具有权威性,常见的CA有\u0026quot;Let\u0026rsquo;s Encrypt\u0026quot;,\u0026ldquo;DigiCert\u0026quot;等 将某个实体的身份与其公钥绑定为证书,由CA对这个证书进行数字签名 end-point authentication(端点鉴别) 前面的数字签名和公钥认证更像是被动被检测的,而这里提到的端点鉴别则是通信的某一方主动向另一方证明自己的身份.\n应用层的安全服务: 安全电子邮件(过) 运输层的安全服务: TLS TLS(Transport Layer Security)协议是由Netscape设计的安全协议-SSL(Secure Sockets Layer)协议-的升级版,所有的浏览器和服务器都支持该协议(看一下某个网站的URL,如果以https开始,就是启用了TLS),它有以下几个功能:\n机密性: 攻击者可能截获购买者的订单并得到银行卡信息 完整性: 攻击者可能会修改购买者的订单地址或者购买数量 服务器鉴别: 证明这个网站是官方的,安全的 可以看到,TLS只在应用层进行了加密而已,并没有深入运输层\nTLS Intro 先大致描述一下TLS的简化版本(称为类TLS),它具有三个阶段: 握手,密钥生成和传输数据\n现在以客户B与服务器A之间的通信来举例说明\n握手 在建立TCP连接后,B向服务器A发送一个hello报文,A在响应报文中返回该服务器的证书,里面包含了它的公钥.\n由于该证书被某个CA证实过,B知道了这个公钥真的来自A,然后B生成一个主密钥(MS)用于此次TLS会话,使用A的公钥加密MS生成加密的主密钥(EMS),再发给服务器A,A再用私钥解密该EMS得到MS.\n如此依赖,双方都知道了这次会话的主密钥MS.\n密钥生成 事实上,出于安全性的考量,通信双方都会使用MS生成4个密钥:\nE_B: 从B发送到A时所用的加密密钥 M_B: 从B发送到A时所用的HMAC密钥(用于验证报文完整性) E_A: 从A发送到B时所用的加密密钥 M_A: 从A发送到B时所用的HMAC密钥 传输数据 TLS breaks the data stream into records, appends an HMAC to each record for integrity checking, and then encrypts the record + HMAC. 比如说B要发送数据,它需要将数据流分割为一个个record,为每个record附上HMAC,然后使用M_B加密这个record+HMAC包后发送出去. 事实上,这个HMAC中海含有这次发送的record所属的序号,从而保证攻击者不会打乱record的顺序.\nrecord的结构 Type字段: 判断这是握手报文还是传输数据的报文,也用于关闭TLS连接 对于TLS更为精确的描述 握手阶段 TLS不强制通信双方使用特定的加密算法,而是允许双方在TLS会话开始时就统一决定加密算法,真实的步骤如下:\n客户发送它支持的密码算法的列表和它选取的不重数(在本次会话中不会第二次出现的数字) 服务器从列表中选择一个对称密钥算法,一种非对称密钥算法和一种HMAC算法,它把这些选择联通证书和另一个不重数发给服务器 客户验证证书后提取服务器的公钥,生成PMS(Pre-Master Secret),用公钥加密该PMS后发送给服务器. 客户和服务器根据PMS和各自的不重数得到上述的四种密钥. 双方发送一段用新生成的密钥加密过的握手信息摘要，用于验证双方计算出的密钥是否一致，以及握手过程是否被篡改 你可能想知道在步骤 1 和步骤 2 中存在**不重数（Nonce）**的原因。序号不足以防止报文段重放攻击吗？答案是肯定的，但它们并不只是防止“连接重放攻击”\n假设 Trudy 嗅探了 Alice 和 Bob 之间的所有报文。第二天，Trudy 冒充 Bob 并向 Alice 发送正好是前一天 Bob 向 Alice 发送的相同的报文序列。\n若未使用不重数：Alice 将以前一天发送的完全相同的序列报文进行响应。由于接收到的每个报文都能通过完整性检查，Alice 不会怀疑任何异常。如果 Alice 是一个电子商务服务器，她将认为 Bob 正在进行第二次相同的订购。 若使用不重数：Alice 将对每个 TCP 会话发送不同的不重数，使得两天的加密密钥完全不同。当 Alice 接收到 Trudy 重放的 TLS 记录时，由于密钥不匹配，该记录将无法通过完整性检查，假冒的事务便不会成功。 连接终止 如果仅通过TCP连接来终止TLS会话的话,攻击者可以通过截断攻击(truncation attack)来提前结束会话,也就是说他可以在会话中发送一个TCP FIN报文,从而让用户不能获取完整的信息.\n因此,我们可以用最后一个record指示终止会话.\n网络层的安全服务: IPsec(过) 补充: 有线网络的安全服务 有线网络不提供安全服务,因为链路通信是很难被攻击和篡改的,只需依靠上层的安全服务即可.\n无线网络的安全服务(待补充) 防火墙和入侵检测系统(待补充) 总结 显然,有这么多的防护措施,普通的cracker是很难通过截获IP数据报的形式来进行攻击的,相反,通过恶意软件(病毒,木马等)可以很轻易的跨越多层封锁,进入最薄弱的环节-主机-中,但这部分就不是网络通信所能负责的范畴了.\nppt概念补充 Intro OSI七层协议 事实上,OSI协议的前三层对应了本书中的应用层,后面的都是一一对应的\nModem v.s. Router Modem (调制解调器) 的本质是信号转换器。由于计算机内部处理的是二进制数字信号（Binary stream），而广域网传输介质（如电话线、光纤）传输的是模拟信号（Sinusoidal waves），Modem 负责将数字信号调制为模拟信号发出，并将接收到的模拟信号解调回数字信号。如果家中只有一个上网设备且不需要防火墙等功能，理论上仅需 Modem 即可拨号上网。\nCircuit Switching and Packet Switching Circuit Switching (电路交换) 核心流程：\n建立电路 (Establishment)：在发送数据前，必须先预留一条端到端的物理路径。 信息传输 (Transfer)：数据（模拟或数字）沿专用路径实时流动。 电路拆除 (Termination)：释放沿途占用的所有资源。 关键特性：\n优势：由于资源独占，数据传输速率恒定、带宽有保障，且数据严格按序到达，对用户而言网络是“透明”的。 劣势：存在明显的拨号/建立延迟。资源利用率低，即便不传输数据，信道依然被占用，无法分配给其他用户。 复用方式：通常通过多路复用 (Multiplexing) 将物理链路划分为多个子信道 Packet Switching (分组交换) 核心流程：\n分组化 (Packetization)：将长报文拆分为带有首部（源地址、目的地址、校验码）的小数据包。 存储转发 (Store-and-forward)：路由器必须接收完一个分组的所有比特后，才能开始向下一跳转发。 关键特性：\n优势： 带宽利用率高：单个分组可以使用链路的全带宽，资源按需分配，支持更多用户接入。 应对突发流量 (Bursty Data)：互联网流量通常是爆发式的，分组交换通过路由器内的缓冲区 (Buffer) 吸收临时流量峰值。 劣势：资源竞争可能导致拥塞和丢包；路由算法复杂；分组可能乱序到达目的地。 核心对比总结 特性 电路交换 (Circuit) 分组交换 (Packet) 资源分配 预先静态分配（独占） 动态按需分配（共享） 延迟来源 建立连接延迟 存储转发延迟、排队延迟 性能表现 稳定、无抖动、带宽保证 可能拥塞、有抖动、高并发 典型应用 传统电话网 (PSTN) 现代计算机网络 (Internet) 物理层 outline Bandwidth (带宽) and Data Rate Modulation (调制) of a Signal (ASK, PSK, QPSK, QAM) Medium and Transmission (传输) Multiplexing (复用) (FDMA, TDMA, CDMA) Bandwidth (带宽) and Data Rate Bandwidth: 单位时间内从一个节点传送到另一个节点的数据量 奈奎斯特采样定理 (Nyquist Sampling Theorem) 核心物理含义 在进行模数转换（将连续信号变为数字点）时，如果模拟信号包含的最高频率为 $H$ Hz，那么每秒钟至少需要进行 $2H$ 次采样（即采样频率 $f_s \\ge 2H$），才能通过这些采样点无失真地重建原始信号。这个最小值 $2H$ 被称为奈奎斯特速率。\n采样频率与波形还原 为了识别一个周期的波形，数学上至少需要记录其一个波峰和一个波谷。如果采样率低于 $2H$，采样点分布太稀疏，捕捉到的数据点在还原时会连成一个错误的低频波形，这种现象称为混叠 (Aliasing)。\n混叠 (Aliasing) 的直观理解 当采样频率不足时，高频信号会“伪装”成低频信号。\n视觉现象：在电影（每秒 24 帧采样）中看到快速转动的车轮时，车轮似乎在缓慢倒转，这就是典型的视觉混叠。 音频后果：若采样率过低，录入的高音会变成诡异的低频杂音，且这种失真是永久性的，无法通过软件修复。 工程应用实例\nCD 音质：人耳听觉上限约为 $20\\text{kHz}$。根据定理，采样率必须大于 $40\\text{kHz}$。CD 标准采用 $44.1\\text{kHz}$，正是为了确保完全覆盖人耳带宽并留出滤过缓冲带。 通信带宽：在带通信号传输中，该定理限制了在给定频率范围内可以传输的最大符号速率，是数字通信设计的底层物理约束。 信道容量与编码定理 (Channel Capacity \u0026amp; Coding Theorems) 奈奎斯特准则 (Nyquist Theorem) —— 无噪声信道 在理想的、没有噪声的信道中，由于信号在传输时存在码间串扰（带宽限制了信号的变化速率），最大数据传输速率由带宽和信号电平数（量化等级）决定。\n公式： $$R_{max} = 2 \\times BW \\times \\log_2 V \\text{ bps}$$ 参数含义： $BW$：信道带宽（Hz）。 $V$：信号的离散电平数（量化等级）。 物理本质：在无噪声情况下，理论上可以通过无限增加量化电平数 $V$ 来提升速率，但受限于实际硬件对电平分辨的精确度。 香农定理 (Shannon\u0026rsquo;s Theorem) —— 有噪声信道 在存在随机热噪声的实际信道中，由于噪声会掩盖信号的电平细节，最大可靠传输速率（信道容量）存在一个物理极限。\n公式： $$C = BW \\times \\log_2 (1 + \\frac{S}{N}) \\text{ bps}$$ 参数含义： $C$：信道容量，即该信道能实现的理论最大信息传输速率。 $S/N$：信噪比（SNR），信号功率与噪声功率的比值。 物理本质：噪声决定了我们能区分的最小信号电平差。即使无限增加发送电平 $V$，如果电平差小于噪声强度 $N$，接收方也无法识别。因此，带宽和信噪比共同限定了信息交换的上限。 信噪比与分贝 (SNR in Decibels) 在工程实践中，信噪比通常跨越多个数量级，因此常使用对数单位分贝 (dB) 来表示。\n分贝换算公式： $$SNR(dB) = 10 \\times \\log_{10} (\\frac{S}{N})$$ 典型值参考： 如果 $S/N = 10$，则 $SNR = 10\\text{ dB}$。 如果 $S/N = 100$，则 $SNR = 20\\text{ dB}$。 如果 $S/N = 2$（信号是噪声的两倍），则 $SNR \\approx 3\\text{ dB}$。 核心逻辑总结\n奈奎斯特告诉我们：由于带宽限制，采样率有上限（不能跑太快，否则波形会糊）。 香农告诉我们：由于噪声存在，信息的精细度有上限（不能分太细，否则分不清信号和噪声）。 实际应用：在设计网络系统（如 5G 或 Wi-Fi）时，通常会同时计算这两个值，取其中的较小者作为实际物理层的理论瓶颈。 Modulation 专业名词(即使前面提过也会放在这里) RTT: Round-Trip Time(往返时延) MSS: Maximum Segment Size,TCP 报文段中负载的最大长度限制 adapter: 适配器,主机与特定网络连接的硬件接口,俗称网卡. Internet: 是由无数个使用 TCP/IP 协议族 相互连接的计算机网络，物理上通过路由器和交换机在全球范围内实现数据交换与资源共享的网际网路。 packet(分组): 在特定层传输的数据包,从应用层,运输层,网络层,到链路层,每经过一层增加一个头部修饰 MAC: Medium Access Control,介质访问控制协议 ISP: Internet Service Provider NAT: Network Address Translation WAN: Wide Area Network - 广域网 LAN: Local Area Network - 局域网 SDN: Software Defined Networking,通过软件管理路由转发 CIDR: Classless Inter-Domain Routing,无类别域间路由,是一个用于给用户分配IP地址的方法 CSMA: Carrier Sense Multiple Access,发送信号前先监听,从而判断是否要在这个时候发送信号 全双工(full-duplex): 可以同时接收和发送数据 半双工(half-duplex): 同一时刻要么接收,要么发送,不能同时进行 实战 URL解析 如何给一个新房联网 网站证书 到了国外如何上网 备案流程 知乎介绍 wiki 个人网站备案需要准备：1份网站负责人的身份证件彩色扫描件或复印件；负责人的半身彩色照片（带接入商名称背景）；网站所使用的独立域名注册证书复印件（加盖公章）；主办单位所在地的详细联系方式；填写《信息安全管理协议》；填写《真实性核验单》。\n根据相关行政法规，所有在中国境内的互联网信息服务提供者都应完成备案登记手续方可开办，未按规定备案的不得开展服务。\n2005年2月8日，原中华人民共和国信息产业部部长王绪东签发《非经营性互联网信息服务备案管理办法》，并于3月20日正式实施。该办法要求从事非经营性互联网信息服务的网站进行备案登记，否则将予以关站、罚款等处理。\n根据《互联网信息服务管理办法》，提供非经营性互联网信息服务需办理备案，而办理备案的前提是使用中国境内服务商提供的内地机房IP\n事实上,在中国内地，合规运行网站需要两个核心要素：域名实名认证和ICP备案。\n后缀限制：并非所有域名后缀都能备案。只有在工信部正式批复的顶级域名列表中的后缀（如 .com, .cn, .net 等）才允许备案。如果你使用 .io, .ai 等部分未批复后缀，即便服务器在国内也无法通过备案。 注册商要求：办理 ICP 备案的域名，其注册商必须在中国内地经过许可。如果你的域名是在 Namecheap、GoDaddy 或 Google Domains 注册的，必须先将域名转入国内注册商（如万网、新网），才能提交备案申请。 (补充): 事实上,所有的备案都是通过第三方的,小程序上线需要通过微信开发者平台备案;网站上线需要通过阿里云,腾讯云备案\u0026hellip; 平台初审(一般需要1-2个工作日)通过后,再由平台提交给工信部审核(时间范围不确定,但一般不短),审核通过后会发短信提醒你. 这还没完,当你通过备案后,需要再进行公安联网备案,并提交审核,一般为2-3个工作日.\n瑟瑟发抖\u0026hellip;\n","date":"2026-02-28T08:00:00Z","image":"/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%AC%94%E8%AE%B0/18437721_p0-%E5%8D%83%E5%A7%AB%E3%81%95%E3%82%93.webp","permalink":"/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%AC%94%E8%AE%B0/","title":"计算机网络笔记"},{"content":"前提 既然工作就是为了挣钱,那么学会如何理财显然比找工作更为重要,在低利率的大环境下把钱存银行是不可能跑得过通胀的,只有找到合适的投资方法,才能在确保基本盘不大出血的前提下能够实现比较理想的盈利.\n可是,到底要怎么投资呢?\n概念理清 可惜的是,能够好好讲清楚什么是期货,股票的运作模式是怎样的,如何选基金的入门书太少了,充斥在市面上的只有诸如\u0026laquo;穷爸爸富爸爸\u0026raquo;这样的垃圾书,不过仔细想想也是,要是把如何致富的方法都告诉你了,那我还怎么赚钱. \u0026laquo;半小时漫画经济学4\u0026raquo;是我至今唯一能看的过去的入门书,别看内容少,但基本能够让小白大致了解到投资的各种途径以及对应的风险情况.\n债券 普通人接触最多的债券应该就是国债了,相当于借钱给国家去投资,非常稳定,也就说明利润很低,基本和在银行存款的利息没什么差别\n股票 股票原先是帮初创公司用来募集资金的(如果全国14亿人每人给我一元,那我不就成亿万富翁了),一般都是由大股东持有大部分股票,剩余的股票中再分出一小部分给散户来投资.\n但初创公司也需要选择在哪家交易所上市,如果全都挤到一个交易所里进行交易那显然不利于企业良性竞争,以下是交易所的类型:\n国内A股: 国内只有两家交易所,上交所(上海证券交易所)-金融能源板,大型国企板,深交所(深圳证券交易所)-创业板,科创板 港股: 香港联合交易所,内地投资者可以通过港股通代理商来投资,看了看好像要持仓50w以上😅 美股: 如NYSE等证券交易所,可以买入美国科技股,国内也需要走代理商 买入股票前需要先开户,然后选定自己看中的股票买入100或者其他份数的股票,一份股票的价格就是股票图上的价格. 注意事项\nT+1规则(A股):买入当天不能卖出,次日可卖 手续费:印花税,佣金等按比例收取的费用,会随着你持有股票(持仓)的日期逐渐下降,当你买卖股票的差额过小时可能连手续费都交不满 股票说来简单做起来很难,也就四个字:低买高卖,因此是高风险投资的首选,但可操作性和运气成分太高了,莽夫也可能误打误撞碰到连续涨停,运气差了即便你机关算尽也不能及时止损.\n期货 期货实际上就是一张合约,卖方要以约定的价格在给定期限来临时把合约上的相关产品卖给买方,刚发明出来的时候是为了方便买卖方进行现货交易的,但现在基本是用来炒的了,有两种方式来炒期货\n做多: 简单说就是赌这个产品会涨,低买高卖 做空: 赌这个产品会跌,在期限来临时以当日价格买入,但卖方要以当时的约定价格卖出,也是低买高卖 看上去就有风险对吧,谁说得清几个月后的事情是怎样的呢?同时,由于期货只需要冻结10%的保证金就可以持仓(买入后并持有期货),故有着相当大的杠杆,一旦价格波动过大导致保证金都不能弥补亏损,会被庄家强制平仓(以约定价格全款结算期货),想想都可怕,因此期货是勇敢者的游戏,不建议平民参与 基金 实际上就是找代理人帮你投资,分为两种情况\n稳健型基金: 大部分买入国债等稳定性资产,少部分用来进行高风险投资,保证基本盘 风险型基金: 大部分进行高风险投资,少部分买入,事实上ETF(exchange-traded funds,交易型开放式指数基金)就属于这一类型,但它会根据诸如纳斯达克指数这样的指标进行投资,相对来说风险会低一点点. 实物 房地产,黄金等可以用来炒作的(稀缺?)资源,均为高端局,不建议平民参与\n为什么普通人不适合投资实物黄金 财报阅读(4/8) 学会阅读财报是投资的首要技能,如果你连一个公司的好坏都判断不了,又怎么去投资这个公司呢.\n去哪看财报 债券详解 基金详解 股票详解 交易规则 交易时间 周一至周五 (法定休假日除外)\n上午9：30 \u0026ndash;11：30 下午1：00 \u0026ndash; 3：00 交易单位 股票的交易单位为“股”，100股＝1手，委托买入数量必须为100股或其整数倍\n交易方法 我开户用的是东方财富,只看股票就行,里面的资讯基本都是垃圾,当然用同花顺app或者其他的也可以,建议下载雪球app来学习别人的经验 切记创业板等高端局需要你在其他板块交易第一次之后两年才解封,所以建议先选一个价位稳定的股票交易一次,才可以在以后投资科创板.\n入股创业板的准确要求: 二十天内平均持仓20w以上并且有两年投资经验 使用港股通的准确要求: 二十天内平均持仓50w以上 术语理解(3/4) 委托: 事实上买卖股票是一个双方的过程,买股票时你自己的定价和买入份数便是一份买入委托,卖股票时你自己的定价和卖出份数便是一份卖出委托,只有当买入委托的价格高于或等于卖出委托的价格时,交易才会成立,不然就会一直留存在系统上,俗称\u0026rsquo;挂单'. 涨停:内地独有的机制,A股的日涨跌幅一般限制在10%,也有一些会限制在20%, 一旦达到限额,当日成交价高于涨停价或者低于跌停价的订单都要到次日进行结算,从而保证了股市不会大规模动荡,也就说明A股的短线交易利润率不会特别离谱 熔断:如果指数跌幅超过阈值,将会暂停整个交易所内的交易 杠杆: 当你借入资金来买入比如10倍份额的股票,那么亏损和收益就会被放大十遍,类似于物理学中的杠杆原理,实现作用力的放大.因此需要有自知之明,不要轻易加杠杆 短线/长线: 长短指的是持仓时间,如果你T+1日或者T+2日就卖出,那肯定是短线交易;如果你持仓几个月,或者好几年,那就是长线交易.这两种方法各有各的利弊. 阻力位: 目标价格上涨时可能遇到的压力，即交易者认为卖方力量开始反超买方，从而价格难以继续上涨或从此回调下跌的价位 支撑位: 交易者认为买方力量开始反超卖方，从而止跌或反弹上涨的价位。 股票种类 概念股(来源):概念股是与业绩股相对而言的。业绩股需要有良好的业绩支撑。概念股则是依靠某一种题材比如资产重组概念，三通概念等支撑价格。中国概念股就是外资因为看好中国经济成长而对所有在海外上市的中国股票的称呼。也有称中国概念股是“就是为了使人相信其谎言而编造的一切谎言”。 蓝筹股:股票市场上，投资者把那些在其所属行业内占有重要支配性地位、业绩优良，成交活跃、红利优厚的大公司股票称为蓝筹股。“蓝筹”一词源于西方赌场。在西方赌场中，有二种颜色的筹码、其中蓝色筹码最为值钱，红色筹码次之，白色筹码最差。投资者把这些行话套用到股票上就有了这一称谓。 绩优股: 就是业绩优良公司的股票，但对于绩优股的定义国内外却有所不同。在我国，投资者衡量绩优股的主要指标是每股税后利润和净资产收益率。一般而言，每股税后利润在全体上市公司中处于中上地位，公司上市后净资产收益率连续三年显著超过10％的股票当属绩优股之列。绩优股具有较高的投资回报和投资价值。 交易界面详解(根据知乎文章撰写) 上方部分\n贵州茅台：股票简称 600519：股票代码 1426.19: 股票现价 金额(65.65亿): 当日交易总金额 最高: 当日最高成交价 最低: 当日最低成交价 换手率: 当日的成交量/发行总股数×100%，是反映市场交投活跃程度最重要的技术指标之一 总手: 当日开盘到现在为止的总成交手数,一手等于一百股 总值: 以当前股价乘以总股本数计算出来的股票总价值 流值: 可以流通的股票市值(这里流值和总值相同说明所有股票都是流通的,全都抛出来赚钱了) 市盈率: 总市值/预估全年净利润,一般在10-20之间比较好,太低的要么是被过于低估,要么是流动性太低;太高的,比如说贵州茅台,都是热门的,饱受炒作的股票 看得出来持仓茅台的都是精英中的精英(毕竟一千多一股呢,动辄就几百万了),不会轻易出手,一次交易的手数一般都比较少,股价波动非常缓慢\n中间部分\n五档:包含卖盘、买盘等候栏,该栏中卖一、卖二、卖三依次等候卖出。按照价格优先，时间优先的原则，谁卖出的报价低谁就排在前面，而这一切由电脑自动计算。卖一后面的数字为价格，再后面的数字为股票手数;该栏中买一、买二、买三表示依次等候买进，与卖出相反，谁买进的价格高谁就排在前面 现手: 最近的一笔交易手数 量比: 开市后每分钟平均成交量与过去5个交易日每分钟平均交易量的比值,表示交易的活跃度和投机性 委买: 还没有成交的买单 委买手数:当前所有个股委托买入前三档的手数总和。委托买入的手数比委托卖出的手数多，表示买比卖强，指数向上的概率比较大；委卖手数同理 外盘: 以卖出的报价成交叫外盘，俗称主动性买盘。当外盘累计数量比内盘大很多，而股价上涨时，表示很多人在抢着买进股票。如：外盘32721 内盘 29452，说明当日主动性抛盘远小于主动性买盘，形势对多方有利。 内盘：以买入的报价成交叫内盘，俗称主动性抛盘。当内盘累计数量比外盘累计数量大很多，而股价下跌时，表示很多人在卖出股票 流通股：上市公司股份中，可以在交易所流通的股份数量，按市场属性的不同可分为A股、B股、法人股和境外上市股。与流通股对应的，还有非流通股，非流通股股票主要是指暂时不能上市流通的国家股和法人股 自由流通股: 去除大股东后所剩的流通股 好文记录 zlib上搜索雪球专刊精选,总共有四本,里面的每一篇文章都很优质,建议小白在了解基本概念后就去看\n但不要看太多,很多文章之间的观点都是相互矛盾的,并且到了最后你会发现讲的东西都差不多 投资体验 投资教学-爱吃鸡爪的阿辉 由于是雪球上的文章,不好转链接,文章又不是太长,故直接附录全文如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 今天来讲讲小白投资第一课，主要最近有一些大学生刚刚接触到这个市场，问我从哪里入手，那我们就来讲讲对于小白来说，普通人常见的失误，这些失误要不去犯，再接下来就是讲讲我觉得小白如何完成第一桶金投资，并建立正确的投资理念，当然这些仅是我的观点，不一定正确，仅供参考，不构成任何投资建议。 1、首先要做的就是先把自己的账户激活，我国开通创业板和科创板需要两年的投资经验，翻译过来就是说，你需要在中登公司有一笔交易，并且这个交易要达两年，这个交易在任何一个券商做的买卖都是可以的。 中登公司的全名叫着中国证券登记结算有限公司，是一个政府机构，不是一家公司，我们所开立的上海股东户，深圳股东户，每人只能开三个，就是中登公司的上海股东户和深圳股东户只能开三个的要求。 要激活这个第一笔交易，买入ETF可不算哦，需要激活的是股票交易，比如花个800元买一手工商银行，明天再卖回掉，就算激活了，工商银行波动小，一天不会产生什么大的亏损，当然如果本是想要边炒股边学习，就没有必要做这个动作了，只要是买入任何一个股票，都会激活账户。 2、常见错误。 2.1 对于小白来说，正常不会入很多资金，一般来说，好公司股价都偏贵，大几十块钱，上百元，所以买一手就要大几千元上万元，对于小白来说，天然的第一个失误，会去买十块钱以下的个股，10元以下的个股，如果还是好公司，要么公共基施设施，要么高负债的公司。 但总的来说10元以下的个股，大概率都是不好的股票为主，这个是小白第一坑，认为5元涨一元就能涨20%，50元涨1元才涨2%，股市涨跌不是这么算的，是按比率算的，5元涨1元，和50元涨10元，都是20%，难度是一样的。而选择不好的低单价的公司，输的概率就特别的高，比历史的统计数据中，中位数选择20至50元之间单价的个股，很多品质很好，并且活跃性特别好。 2.2 小白的选股渠道这是所有亏损原因的重大原因，小白不会使用软件，不知道市场有多少个股票，分别是什么，只能接触到一个股票了解一个，几年时间，手中知道的股票也没有超过20多支，之所了了解到，是因为亲友推荐，或者新闻中看到，或者证券公司软件弹出来的信息。 为何是重大的亏损原因，是因为以上几个渠道得来的股票，全是热门股，并且都天花板极别的个股，很多已经是3至5倍涨幅的个股，有的甚至10倍涨幅的个股，最关键的时，这些个股，因为很热门，买进去，一般情况下还是能先赚钱的，接下来就是，不卖，这么贵，大回撤是必然，很容易亏大钱，如果卖了，因为自己不会使用软件选股，所以换另一个热门股，而热门股总有结束的时候，所以赚小钱，亏大钱，几乎是必然的。 2.3 浮盈加仓，一把亏光。对于小白来说，如果很幸运遇到一个相对不是很贵的好公司，刚开始做投资谁也没有勇气买很多，我第一笔入市资金才5万元，对于不了解的东西，我们有着天然的恐惧心理，所以会买一点点，然后看着它上涨，然后把它卖了，不过它继续涨就会更大仓位买回来，具体操作可能如下。 第一次，小金额，10元买入，11元卖出，赚钱了信心加持，第二次，中等金额，15元再次买入，17元卖出，两次的正确，信心暴棚，第三次20元大仓位买入，并且还向好朋友推荐，因为自己已成功在这个个股赚到钱，信心暴棚是很正常的，以为会一直持续，但是实际该个股开始买就10元，到现在翻倍，底部上来可能就3至5倍了，然后重大回撤，用小仓位来赚钱，用大仓位来亏钱。 2.4 高位亏损补仓，重仓套牢。对于小白来说，基本上不太可能买冷门股，也接触不到，能接触到的全是热门股，都是在天上飘的个股，这些股一旦发生回撤，下跌50%很正常，就像我们刚刚举的10元变成50元的股票，回撤50%变成25元，也是很贵很贵的，所以高位补仓，基本没有好的。 这个是所有小白中亏损最为严重的原因之一，因为大家都怕高，所以也在等，等到个股回撤了百分之十几，觉得机会了，高位进行补仓，要平摊自己的成本，等反弹保本，也就是10元涨到50元，被小白买入，然后跌到45元就觉得差不多了，然后补，又跌到40元，觉得跌太多了，又补，等跌到35元，觉得这是底了，再补，结果就是用月线一看，10元的股票，现在35元觉得是底，这个判断严重有误。 于是就出现了，亏损越跌越补，第一次补在山顶上，然后补在半山腰上，总结经验得出结论为：你想抄它的底，它想抄你的家，以为补在地板了，发现还有地下室，以来地下室了，发现还有十八层地狱。 对于越跌越补，只能是好公司很便宜的时候，才能使用这个方法，这个方法有很强的局限性，因为如何判断它是不是好公司，并且便不便宜，新手基本不可能做到，所以新手千万不要使用越跌越补，就是跌了，要么让它跌等反弹，要么就止损，不可以补仓。 2.5 卖出盈利，补仓亏损，最后自动单仓单票套牢。这个我早年常干的大蠢事，比如我买入股票A、B、C、D四支股票，如果A 涨了，盈利了我担心回撤，会把A卖掉，去补正在亏损的BCD，然后B也开始赚钱了，又卖掉去补还是亏损的CD，等C也反弹赚钱了，再次卖出去补还在亏损的D，最后本来布局了AB­CD四个票，结果就是三个上涨全部卖飞，留下了处于亏损的D，然后抱怨自己的运气不太好，而不反思自己有意为之把钱集中到亏禹的D中进行套牢。 对于这个方法，我一般都是分AB­CD买入，买入后不论涨跌，都不动，涨的让它涨，跌的让它跌，那最终赚钱和亏钱，就看自己当时买入这四家公司的价格贵不贵了，如果不贵，四家都赚钱，如果买的是中性的价格，账户还是赚钱的，如果全买贵了，账户亏钱。 因为买得便宜，四家全能暴涨，买得中性，下跌最多跌50%非常极致了，而上涨的个股中会出现翻倍或翻好几倍的，最终赚钱还是赚钱不错的，那全买贵了，很多贵的公司是真的品质很好，会更贵，或者很长时间留在贵的区域用业绩来夯实股价，比如像阳光电源，当时很贵，但也不跌，因为业绩增长慢慢的内在价值上升，股价从很贵变成贵，变成中性，后来继续上涨，当然其它很贵的下跌，但只要不动，也不一定会亏很多钱。 2.6 单仓单票，新手对市场认知不足，不能像老股民可以拿个五年十年，那么单仓单票，没有涨的话，实在是熬不过去，最后追涨杀跌，结果就是一直在换票的路上，不断卖飞，不断套牢止损，结果就亏钱很严重了。 3、该如何规划自己的投资生涯呢。 3.1 以学习为主，投资为辅，但是需要边操作边学习，人对自己无关事天然无法引发关注，如果没有注入适当的资金，基本上年轻人是不太可能学得会投资的，因为不关心也不关注，也没有输赢，学习天然要有输赢加持才会努力，比如在学校为了分数，那些不在乎分数的学生，就不会去关注老师上课说了什么。 就像我现在说找工作，第一要求，不管收入多少，必须交医社保，否则再高工资都不考虑，这句话对于不生病，又很年轻的大学生，肯定不会关心，只是听到，不会听进，但是你让45岁的中年人，没有医社保的工作，基本上不考虑，我们这个年龄会生病，也要考虑养老金，这句话还是这句话，但年轻人基本用不上医保，一年难得感冒一次，也不知道人生之跌的艰难，养老太遥远了。 3.2 站在前人的肩膀上去成功，资本市场是一个很棒的市场，不需要高智商，也不需要高学历，也不需要高人脉关系，你只要复制其它世界上顶级的投资大师的投资体系，不要自己创新，直接硬搬，虽然过程你可以质疑，但是可以边质疑，边坚持，必竟对方成功过，而你未成功的人的质疑，份量几何，所以在选择的时候，要主动选择成功的人的战略，自己的仅为参考，还着质疑一路坚持别人已成功的方法，不要太相信自己，要相信已功成名就的人的方法。 比如巴菲特的，好公司+安全边际+重仓长期持有。邓普顿和费雪的：成长型公司+分散仓位+盈亏都不去动。耶鲁大学基金会：分多种类+仓位平衡，每个板块使用市值恒定法，该板块上涨很多了，卖一些使得回到原来建仓时的市值，该板块下跌了，就补一些，补完后，仓位市值为建仓时的市值。再比如我国的公募基金投资圣经【好赛道，好公司，好价格】，公司好不好决定买不买，价格贵不贵决定买多少，翻译过来就是好公司才会进入目标，好公司价格太贵不买，好公司价格中性，就买一些，好公司价格便宜就买成重仓。 3.3 小白90%的损失来源于二手信息，10%来自于操作失误，操作是经验，是能力，是水平，后期慢慢学，二手信息可以直接变成一手信息，学会使用炒股软件，用炒股软件找公司，找信息，不使用朋友的信息，新闻的只言片语和断章取义。 更不使用券商弹出的个股，这些都是热门，赚钱效应很好的公司，券商才会弹出这些介绍信息，券商不会弹出一个好公司已经跌得一踏糊涂的标的，除非它想被骂，他只会弹出明天大概率有可能会赚钱的信息，但结果是，券商自营盘也不买这些，他们的自营盘也是做价值投资，要明白他们的自营盘可是不用交易佣金的哦，所以这些信息只会亏钱，不会赚钱的，要是会赚钱，他们自营盘就会买这些了。 4、我的方法介绍：买入便宜的好公司，熬个三五年。 4.1 公司足够好，这个公司要满足两个条件：1、这个公司的生意是不是好到，我想把它整个公司买下来，如果是，那我就会选择买它的股票，成为它的股东。2、这个公司如果没有上市，我是不是也会因为它是一个很棒的生意，成为它的股东，如果是，我才会买入成为它的股东。 4.2 好公司很便宜的时候开始买，越跌越买，很少考虑止损，好公司越便宜，越想收集它的筹码，可以无限收集它的筹码，但是考虑到资金有限，也考虑资金的效率，所以单个公司设上限，并且同时设单个板块上限。 比如我喜欢A公司，我设的单个板块为1000万元，那么我在越跌越买时，如果这个公司加这个板块的其它公司合起来买了1000万元后，我就不再打算投入了，再跌就看着跌，也不会去补了，不去跨越这个设定上限，以防无意中把所有资金集中到这个板块或这个公司中。 4.3 资本市场最终是把没有耐心人的钱转移到有耐心的人身上，无论你等不等得起三五年，最后你的人生都会迎来三五年，只是有的人三五年在几百家公司每家公司熬几天，最终也是在资本市场熬三五年，就像有的人三五年换很多工作，但还是工作了三五年，而我会在选定的一家公司上班三五年，也只是工作了三五年，一秒没有多熬，在哪当牛马不是牛马，换来换去，就不是牛马了吗？ 4.4 反复做T，金顶战法，这个不做详细介绍，新手资金小，两个都不适用。 综上所述，核心就是，新手先学会炒股软件找公司找信息，不要到处听主播的，别人评论的二手信息，其次就是研究功成名就的人的方法，复制为主，复制是最容易成功的。最后少操作，多总结，多反思，三五年后，基本上就是别人十几二十年的功力了。 最后祝所有新手，小白，老股民2026年账户长虹。 以上仅为个人观点，不一定正确，仅供参考，不构成任何投资建议。 韭菜的自我修养 捕蛇者说博客 心得体会 炒股需要本金,工作优先投资(2026/3/13) 很多人都在吹港股和美股,但如果你跃跃欲试,想要注册港股通,点开链接一看,赫然写着:近20个交易日日均资产需不低于50万人民币,想要当韭菜也不给你机会.\n所以,当你能一下梭哈的资金量连50w都填不满的时候,在庄家看来你远远算不上是一个交易对等方.\n不妨认真想想,即便你买的股票侥幸遇到几个涨停,或者你通过所谓的量化交易成功押中了某支潜力股,如果你的持仓资金只有区区几百块钱,我想怎么看你赚不到多少钱.\n因此,还是努力找份好工作吧,等积攒了一定的资金实力后再真正下场试试股票,而不是坐等手头的几百块变成几万块,试试撞大运穿越回去还现实一点.\n股票不是投机 股票是拿100块赚10块的地方,不是拿100块赚1万的地方,如果真想发财,或许买彩票的概率还高一点.\n股票不是投机而是投资,不能别人买你也买,别人卖你也卖,这注定了你永远在步别人的后尘,就算侥幸赚了一点很快就会亏完.\n相反,应该在对一家公司经过细心的考察后,在自己认为股价合适的时候买入,在认为周期到头的时候卖出,才可以保证不会轻易被割韭菜.\n如果你连买日常用品的时候也要货比三家,看看好评差评,那为什么到了股市这样一个中高风险的投资行业里不能去好好的评判一下某只股票的价值呢?\n新手入门 事实上,我觉得新手最好的学习方法就是马上买入一支你觉得会涨的股票,不管是所谓的精确分析,量化处理,还是对上眼缘.\n如果像我一样是普通人,当然买入一手单价小于10的股票就比较好,毕竟谁的钱是大风刮来的呢.\n然后就开始了持仓环节,新手如果不是走狗屎运的话一般买入股票之后都会开始下跌,因为你了解的股票少,获得的资讯少,拥有的经验少,你不是韭菜谁是韭菜.\n日复一日看着账面上刺眼的绿色,或许你很有耐力,愿意慢慢去熬到上涨的那天,最终回本;或许你等到头了股票也也没有回涨的时候;或许在抛售之后股票突然或者仍然上涨.无论怎样,你都完整的体验了一次投资股票的全过程.\n我想,在这一段教训之后你一定会痛定思痛,决定在买入股票之前进行多方调研,在点击交易按钮之前反复斟酌.持仓之后在自己内心划定的价格区间内,即使看到绿字也能气定神闲,即使看到红字也能岿然不动\n五档买卖法(3/8) 当资金量大的时候在几档买卖相隔的利润也很可观了,如果你划不准合理的价位,就建议在现在的第五档买入或卖出,从而赚上一点差价\n长线or短线 短线交易意味着你需要在短短几日内持续不断的搜罗信息,找到次佳(散户很难找到最佳)的买入时机,并在恰当的时间卖出,基本这几天你别想做别的事情了.\n而长线交易需要你在交易之前细致的考察意向公司的财务情况,分析未来的股价趋势,考察公司有无负面消息,然后在股价下跌到你的心理预期时买入,坐等升值(最好能升值).\n一般来说,短线交易适合IPO(新股上市),舆论股,概念股,而长线交易适合慢牛股,蓝筹股,需要结合自己的考察选择合适的交易方式.\n当然,如果你是学生的话,还是别搞短线交易了,总不能天天盯盘吧,搞长线交易才是普通人在A股赚钱的王道.\n不正常的A股(4/15) 由于我一般不看港股和美股,故这里只分析A股. A股有两大特性:\n散户多,投机性强 劣质股票多,容易被套牢 由于A股的历史相对于美股来说短的可怜,因此极不成熟.同时,大多数股票投资者根本不会投资,只是跟风投资,因此一支股票只有三种走势,小幅度上下波动,涨停,跌停.很少有走向正常的股票.\n而且,A股里有很多业绩差的股票(很多国企的股票),如死猪一样躺在哪里,却从没有被强制退市.至于业绩良好的股票则被炒到几百上千一股的价位,一般人也根本消受不起.\n因此,投资A股只不过是散户们在绝境中的最后挣扎而已,如果房产和土地无法自由买卖,外汇也不能自由处理,个体经营也举步维艰,而国债和存款的利率却逐年下跌,那不就只能去买点股票来试图挽回一点资产吗.\n","date":"2026-02-25T08:00:00Z","image":"/p/%E7%90%86%E8%B4%A2%E7%AC%94%E8%AE%B0/96433029_p0-%E8%AA%B0%E3%82%82%E3%81%84%E3%81%AA%E3%81%84%E3%81%86%E3%81%A1%E3%81%AB.webp","permalink":"/p/%E7%90%86%E8%B4%A2%E7%AC%94%E8%AE%B0/","title":"理财笔记"},{"content":"英语 常见英文缩写 TL;DR: 是 \u0026ldquo;Too Long; Didn\u0026rsquo;t Read\u0026rdquo;（太长不看）的缩写,常作为文档总结 FYR: 是 \u0026ldquo;For Your Reference\u0026rdquo; 的缩写,表示供你参考 FYI: 是 \u0026ldquo;For Your Information\u0026rdquo; 的缩写，意为**“供你知晓”** PoC: \u0026ldquo;Proof of Concept\u0026rdquo;,为了证明某个技术方案在物理上是可行的，先写一个简陋的 Demo。 CI: Continuous integration,持续集成,也就是随时运行,随时与主分支合并 日语词汇 日语动词 参考文章 由于我能找到的中文文章都不太想教会我日语动词语法,就只好看看lingodeer了\n动词分类 Fear not, as Japanese verbs are divided into only three groups:\nThe U-verbs, also known as V1 verbs or Godan verbs The Ru-verbs, also known as the V2 verbs or Ichidan verbs Irregular verbs / V3. V3 only two verbs fall into this category: する (to do) and くる (to come). eg\n勉強べんきょうする (studies + to do = to study) 散歩さんぽする (walk+to do = to take a walk) V2 V2 verbs end in any kana in the い(i)/え(e) column + る(ru).\nBecause the base of the verb stays the same when it’s conjugated, these verbs are called 一段動詞いちだんどうし (“one-form verb”).\nV1 The U-verb group gathers all the verbs that end with a /u/ vowel sound, like 話す (to speak), 買う (to buy), 読む (to read), 飛ぶ (to fly) etc.\nWhen you conjugate a u-verb, the stem’s final /u/ vowel changes to another vowel in the hiragana chart: /a/, /e/, /i/, /o/.\n在V1动词变成/i/形之后可以接如masu(礼貌),nai(否定)等各种助动词,这也是/i/形被称为连用形的原因,而接下来就是讲这些连接的助动词\n助动词分类 Masu Form(the normal form) and Its Derivatives Without going too far ahead into advanced explanations, formality greatly influences Japanese language construction. The masu form, or 丁寧語ていねいご in Japanese, is the “normal” form native speakers use with people they’re not intimate with or with people that are socially higher.\nThe ます (masu) form can translate both the English present and future tense, and as such, is said to be a state of “nonpast”. For more clarity, you can consider that it expresses the polite present affirmative. Time-related words and context will tell you whether the present or the future is intended by the speaker.\nthe present and the future form:ます and ません -\u0026gt; the past form: ました and ませんでした 絵を描きます = I draw a picture 絵を描きません = I don’t draw a picture or I won’t draw a picture 絵を描きました = I drew a picture 絵を描きませんでした = I didn’t draw a picture 動詞グループ 変化規則 例（辞書形 → ます形） 例文 V1（五段動詞） 語尾の /u/ 段 → /i/ 段 + ます 話す → 話します 教師と話します V2（一段動詞） る を取る + ます 食べる → 食べます パンを食べます V3（不規則：する） する → します 勉強する → 勉強します 日本語を勉強します V3（不規則：くる） くる → きます くる → きます 学校にきます Plain Form(the dictionary form) The plain form is called dictionary form in Japanese (辞書じしょ形けい) because it is the form you find in dictionaries when you look up a verb. This form helps you find a verb’s group and stem. All Japanese verbs in plain form end with a hiragana from the /u/ row of the hiragana table.\nThe plain form is colloquial(口语的), which means you should use this form only when interacting with familiar people such as family members, friends or a very close colleague. Also, you usually write in plain form unless it is written to a specific reader, like an email. Newspaper articles, academic papers, documents that give information and are not directly addressing the reader, are written in plain Japanese.\n絵を描く = I draw a picture (I will draw a picture) Nai form – The Plain Negative form 動詞グループ 変化規則 例（辞書形 → ない形） 例文 V1（五段動詞） 語尾の /u/ 段 → /a/ 段 + ない 話す → 話さない 教師と話さない V1（語尾が「う」） う → わ + ない 吸う → 吸わない タバコを吸わない V2（一段動詞） る を取る + ない 食べる → 食べない パンを食べない V3（不規則：する） する → しない 勉強する → 勉強しない 日本語を勉強しない V3（不規則：くる） くる → こない くる → こない 学校にこない Ta form – The Plain Past Affirmative Form(2/23) The ta form, or plain past affirmative, expresses that an action was done in the past, like “I did my homework” (宿題をした) or “you ate bread” (パンを食べた).\nVerb Group Rules Examples U-verbs If the last hiragana is う, つ, る → add った 笑う → 笑った U-verbs If the last hiragana is む, ぶ, ぬ → add んだ 読む → 読んだ U-verbs If the last hiragana is く → add いた 働く → 働いた U-verbs 行く → 行った（irregular） 行く → 行った U-verbs If the last hiragana is ぐ → add いだ 泳ぐ → 泳いだ U-verbs If the last hiragana is す → add した 隠す → 隠した Ru-verbs Attach た to the verb stem 食べる → 食べた Irregular する → した 勉強する → 勉強した Irregular くる → 来た くる → 来た Nakatta Form – The Plain Past Negative Form A trick to remember how to conjugate the plain past negative form is start from the nai-form’s stem (/a/) and add “katta” to the verb.\nVerb Group Rules Examples U-verbs Change final /u/ vowel to /a/ + なかった 話す → 話さなかった U-verbs If ending is う → change う to わ + なかった 笑う → 笑わなかった Ru-verbs Remove る + add なかった 食べる → 食べなかった Irregular（する） する → しなかった 勉強する → 勉強しなかった Irregular（くる） くる → こなかった くる → こなかった Te Form Pillar of Japanese grammar, the te-form is the cement that helps connect clauses together to build more complex sentences.\neg\n手(て)を洗(あら)って、食事(しょくじ)を食(た)べた I washed my hands and ate my meal.\n朝(あさ)7時(じ)に起(お)きて、運動(うんどう)して、仕事(しごと)に行(い)く I wake up at 7, exercise, and go to work.\n時間(じかん)がなくて、レポートを書(か)かなかった I lacked the time and didn’t write my report.\nIn principle, a sentence with a te-form inflected verb is a subordinate clause(从句) that requires the main clause to be grammatically complete. However, when casually speaking, native speakers sometimes stop at a te-form verb clause, leaving the rest of the sentence implied.\nA: レポートは？ What about the report?\nB: 時間(じかん)がなくて…… I didn’t have time…\nFor u-verbs, the affirmative te-form conjugates like the ta-form form – you just need to switch the vowel /a/ to /e/.\nVerb Group Rules Examples U-verbs If last hiragana is う・つ・る → add って 吸う → 吸って U-verbs If last hiragana is む・ぶ・ぬ → add んで 読む → 読んで U-verbs If last hiragana is く → add いて（行く → 行って irregular） 書く → 書いて\n行く → 行って U-verbs If last hiragana is ぐ → add いで 泳ぐ → 泳いで U-verbs If last hiragana is す → add して 隠す → 隠して Ru-verbs Remove る + add て 食べる → 食べて Irregular（する） する → して 勉強する → 勉強して Irregular（くる） くる → きて くる → きて 高级一点的助动词 Conditional Form ba Japanese verb conjugation has two forms to express the conditional and make hypothetical statements: ba form and tara form.\nremember that the ba-form is a general conditional that cannot express any form of intention, such as a command, a request, an invitation or a wish.\nInstead,the ba-form is often used to ask for or to give advice, as well as to express regret for something in the past:\nどうすればいいですか What should I do?\n教師(きょうし)に聞(き)いてみれば Why don’t you ask the teacher?\nこうすればどうですか How about doing it this way?\n行(い)けばよかったのに I wish I had gone…\nLike for the ta-form and the te-form, the stem of u-verbs will vary depending on their ending hiragana. The vowel /u/ changes to the corresponding hiragana with the vowel /e/.\nJapanese Verb Conjugation Chart: Affirmative ば Form\nVerb Group Rules Examples U-verbs Change final /u/ vowel to /e/ + ば（う→え, つ→て, る→れ, む→め, ぶ→べ, ぬ→ね, く→け, ぐ→げ, す→せ） 吸う → 吸えば\n読む → 読めば\n行く → 行けば\n泳ぐ → 泳げば\n隠す → 隠せば Ru-verbs Remove る + れば 食べる → 食べれば Irregular（する） する → すれば こうする → こうすれば Irregular（くる） くる → くれば くる → くれば Japanese Verb Conjugation Chart: Negative ば Form\nVerb Group Rules Examples U-verbs Start from ない form, drop い + ければ 話す → 話さない → 話さなければ Ru-verbs Remove る + なければ 食べる → 食べなければ Irregular（する） する → しなければ 勉強する → 勉強しなければ Irregular（くる） くる → こなければ くる → こなければ Conditional Form tara 定义：基于普通过去形（た形 / なかった形）+ ら。 表达：\n条件成立后发生的结果 未来条件 非现实假设 用例 お金(かね)があったら、新(あたら)しいパソコンを買(か)う If I have the money, I’ll buy a new computer.\n冬休(ふゆやす)みがきたら、地元(じもと)に戻(もど)る When winter vacation comes, I will return to my hometown.\n王様(おうさま)だったら、毎日(まいにち)を日曜日(にちようび)にするのになぁ If I were king, I would make every day Sunday.\nJapanese Verb Conjugation Chart: Affirmative たら Form\nVerb Group Rules Examples U-verbs た形 + ら 笑う → 笑った → 笑ったら\n読む → 読んだ → 読んだら\n行く → 行った → 行ったら\n泳ぐ → 泳いだ → 泳いだら\n隠す → 隠した → 隠したら Ru-verbs Remove る + たら 食べる → 食べたら Irregular（する） する → したら する → したら Irregular（くる） くる → きたら くる → きたら Japanese Verb Conjugation Chart: Negative たら Form\nVerb Group Rules Examples U-verbs なかった + ら 話す → 話さなかったら Ru-verbs Remove る + なかったら 食べる → 食べなかったら Irregular（する） する → しなかったら する → しなかったら Irregular（くる） くる → こなかったら くる → こなかったら Imperative Form Set a rule for yourself to avoid as much as possible using the imperative form, whether casually or formally. Considered quite rude by Japanese people, the form is mostly used by authority figures, such as the police or parents with their children, and on public road signs 止まれ (Stop!).\nThe imperative should be limited to extreme contexts where you are required to give an order and there’s no time to lose with politeness and formality.\nVerb Group Rules Examples U-verbs Change final /u/ vowel to /e/ 話す → 話せ Ru-verbs Replace る with ろ 食べる → 食べろ Irregular（する） する → しろ 勉強する → 勉強しろ Irregular（くる） くる → こい くる → こい Volitional Form Shortly speaking, the volitional form turns verbs into suggestions. This form expresses our intention to do an action and can be translated by “let’s” or “shall we”.\n后面的部分还不是我现在能碰瓷的,以后学到了再来补充吧 Potential Form Passive Form Causative Form Causative Passive Form ","date":"2026-02-22T08:00:00Z","image":"/p/%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/92260989_p0-%E7%A7%98%E5%AF%86%E3%81%AE%E9%A1%98%E3%81%84%E4%BA%8B.webp","permalink":"/p/%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","title":"语言学习笔记"},{"content":"最近仍然是在挣扎,保研已是无望,那么到底是考研还是本科就业好呢.\n简单分析一下:\n考研之后无非也还是就业,这个学位更多的来自家里人的压力,我自己反而是没什么感觉的,说来也好笑,没有体验过研究生生活和所谓学历优势的人都在劝我读研,他们乐此不疲地阅读那些散播焦虑的,没有实质内容的公众号文章和抖音视频,成功地被同化和洗脑,坚信读研就一定能发达,就能实现阶层突破,读了研还不够,要再去读个博,之后就去参加选调,受尽恩宠后再回老家当个封疆大吏,再在过年时被家里人牵出来,给左邻右舍看看自家有多风光.\n不过家里人终归是抱着为了孩子的心劝说着我的,不愿意让我去吃苦,陷入网传的35岁即失业的漩涡.遗憾的是,我并不畏惧这样的未来,反而是不愿意活的太稳定,没有变化,日复一日,\u0026ldquo;work with unrelated person for unrelated issues\u0026rdquo;,如果连一点挑战都没有,我又怎么成长呢.\n至少,我是不愿意待在小城市的,尽管岁月静好,这也就意味着脱离了最新的潮流,丧失了基本的娱乐,就算能够通过网络去获取必要信息,还是没有自己亲身去体验来的实在.\n想明白了这一点,心里的压力就少很多了,正常学习就好,先把\u0026quot;uninteresting lessons taught by uninterested teachers\u0026quot;全翘掉,认真打磨项目,锻炼自己的全方面能力,期末周也认真复习,如果这样也无法保研,那我就无所谓了,谁愿意卷就让他去卷吧,我甘愿被筛选性教育淘汰掉,早早跑去工作.\n事实上,我认为迷茫的来源主要是信息差,而不是别的什么,正是因为我不知道自己本科毕业能找到怎样的工作才会迷茫,正是因为我不知道以后会不会真的失业跑去送外卖才会迷茫,正是因为我不知道自己接下来要做什么才会迷茫.\n那么,答案也很简单了,磨练技术,收集信息,备战三年难道还拿不下暑期实习和秋招吗.\n","date":"2026-02-08T08:00:00Z","image":"/p/2026-02-08-%E6%96%B9%E5%90%91%E9%80%89%E6%8B%A9%E4%B8%8E%E4%BF%A1%E6%81%AF%E5%B7%AE/91882132_p0-glow.webp","permalink":"/p/2026-02-08-%E6%96%B9%E5%90%91%E9%80%89%E6%8B%A9%E4%B8%8E%E4%BF%A1%E6%81%AF%E5%B7%AE/","title":"2026-02-08 方向选择与信息差"},{"content":"正则表达式 正则表达式是为了能够在不同情景下匹配到符合要求的字符串文本而产生的,比如你想一次性就能在一堆文档中找到标题含有\u0026quot;python\u0026quot;或者\u0026quot;cpp\u0026quot;,但不同时含有\u0026quot;机器学习\u0026quot;的文档,就只能用正则表达式,才能避免一般方法造成的不准确结果\n语法 正则字符 说明 例子 \\ 将下一个字符转义为原义 / 特殊 / 引用 / 转义字符 \\n 匹配换行，\\( 匹配 ( ^ 匹配字符串开始位置 ^abc 匹配 \u0026quot;abc123\u0026quot; $ 匹配字符串结束位置 abc$ 匹配 \u0026quot;123abc\u0026quot; * 前面的子表达式 0 次或多次 zo* 匹配 z、zo + 前面的子表达式 1 次或多次 zo+ 匹配 zo、zoo ? 前面的子表达式 0 或 1 次 colou?r 匹配 color {n} 精确匹配 n 次 o{2} 匹配 food {n,} 至少匹配 n 次 o{2,} 匹配 fooo {n,m} 匹配 n 到 m 次 o{1,3} 匹配 ooo *? +? ?? 非贪婪匹配 o+? 在 oooo 中只匹配一个 . 匹配除换行外的 任意字符 a.c 匹配 abc (pattern) 捕获分组，保存结果 (ab)+ 捕获 ab (?:pattern) 非捕获分组 (?:ab)+ 不保存 (?=pattern) 正向肯定预查 Windows(?=10) (?!pattern) 正向否定预查 Windows(?!XP) (?\u0026lt;=pattern) 反向肯定预查 (?\u0026lt;=\\$)\\d+ (?\u0026lt;!pattern) 反向否定预查 (?\u0026lt;!\\$)\\d+ x|y 匹配 x 或 y `cat [xyz] 字符集合，匹配任意一个 匹配 [\u0026hellip;] 中的所有字符，例如 [aeiou] 匹配字符串 \u0026ldquo;google runoob taobao\u0026rdquo; 中所有的 e o u a 字母。 [^xyz] 负字符集合 匹配除了 [\u0026hellip;] 中字符的所有字符，例如 [^aeiou] 匹配字符串 \u0026ldquo;google runoob taobao\u0026rdquo; 中除了 e o u a 字母的所有字符。 [a-z] 字符范围 [A-Z] 表示一个区间，匹配所有大写字母，[a-z] 表示所有小写字母。 [^a-z] 反向字符范围 [^a-z] \\b 单词边界 er\\b 匹配 never \\B 非单词边界 er\\B 匹配 verb \\d 数字字符 \\d+ \\D 非数字字符 \\D+ \\s 空白字符 \\s+ \\S 非空白字符 \\S+ \\w 单词字符 \\w+ \\W 非单词字符 \\W+ \\n 换行符 \\n \\t 制表符 \\t \\r 回车符 \\r \\unnnn Unicode 字符 \\u4E2D → 中 \\1 \\2 向后引用分组 (.)\\1 匹配 aa 修饰符 正则表达式修饰符（也称为模式修饰符或标记）是用于改变正则表达式匹配行为的特殊指令。 标记也称为修饰符，正则表达式的标记用于指定额外的匹配策略。 标记不写在正则表达式里，标记位于表达式之外，格式如下： /pattern/flags\n举两个例子:\ni (ignore case) - 忽略大小写\n使匹配不区分大小写 示例：/abc/i 可以匹配 \u0026ldquo;abc\u0026rdquo;, \u0026ldquo;Abc\u0026rdquo;, \u0026ldquo;ABC\u0026rdquo; 等\n支持语言：几乎所有正则表达式实现（JavaScript、PHP、Python等）\ng (global) - 全局匹配\n查找所有匹配项，而不是在第一个匹配后停止 示例：在字符串 \u0026ldquo;ababab\u0026rdquo; 中，/ab/g 会匹配所有三个 \u0026ldquo;ab\u0026rdquo;\n支持语言：JavaScript、PHP等\n可以看出来修饰符是由编程语言支持的,而不属于正则表达式的原生语法 ","date":"2026-02-06T08:00:00Z","image":"/p/2026-02-06-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/89796891_p0-%E6%89%8B%E8%A2%8B.webp","permalink":"/p/2026-02-06-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/","title":"2026-02-06 正则表达式"},{"content":"本来觉得hexo的图片管理太麻烦了,部署速度又比较慢,做不到动态同步,就想着换其他的博客框架试试,经过多方搜寻敲定了astro来尝鲜,下面是没有模板时的效果 看着还不错,之后再改改就是个还不错的博客框架了,但问题就是所有组件都要我一个个自己写,没有前端基础的话光靠AI做不出太好的效果,而当我一点点添加组件的时候部署速度就飞速下降了\n尽管相比起来的话astro还是快了不少,但我一想到之后再加入图片管理功能和访客统计,评论系统,加载动画这些东西之后那臃肿的package库,就觉得不太可能会有多快,所以决定及时止损,虽然hexo跑的不够快,但也是一辆历久弥新的自行车!\n而且不用我自己写组件 准备再试试hugo,如果真的有质的飞跃,再考虑换过去\n","date":"2026-01-18T08:00:00Z","image":"/p/2026-01-18-astro%E5%B0%9D%E8%AF%95/68751548_p0-%E5%A4%8F.webp","permalink":"/p/2026-01-18-astro%E5%B0%9D%E8%AF%95/","title":"2026-01-18 astro尝试"},{"content":"考试事项 考试分数占比60% 填空15分 一空一分 选择20分 20道题 综合题65分 七道大题,基本一个章节一道题\n重要的杂项 片选信号 CS(chip select): 片选信号,低电平有效,通过地址逻辑生成,故在原理图中一般都在上面加一横来表示\n译码器 译码器(decoder)是一种具有“翻译”功能的多输入多输出的组合逻辑电路器件。 译码器的功能：将每一组编码序列信号转换为一个特定的输出信号 译码器的输入：一组编码序列信号 译码器的输出：一条特定的译码信号（与每组输入信号对应） 译码器的工作原理：当某组编码进入输入端时，相应的译码线输出为低电平，与此同时，其他所有译码线输出保持为高电平。 通常，译码器的输出端与输入端的数量关系为（2^n）\n第一章 概论 计算机5个部件的示意图 由于运算器(ALU)与控制器都集成在cpu上,因此可以写成下图形式\n而早期的冯诺伊曼结构是这样的 各部件详解 运算器: 算术逻辑单元,用于完成算术运算和逻辑运算 控制器: 计算机的管理机构和指挥中心,协调计算机各部件自动工作 存储器: 计算机的存储部件,用于存储程序和数据,由内存和外存两部分构成 内存: 由大量存储单元组成,构成一个按地址访问的一维线性空间,也称为主存 输入设备: 将程序和数据以计算机能识别的形式输入到计算机内 输出设备: 将计算机处理的结果以人们能接受或其他系统所要求的形式输出到外部世界 操作系统: 最主要的系统软件,负责管理系统资源,为应用程序提供运行环境,为用户提供操作界面\n其中,控制器由以下6部分构成:\n指令寄存器(IR): 用于存放当前正在执行的指令 程序计数器(PC): 用于存放当前正在执行的指令的地址 指令译码器: 对指令进行译码,形成相应的控制信号 时钟脉冲(CP): 协调计算机各部件的同步主时钟,工作频率称为计算机的主频 时序信号发生器: 按时间顺序发出节拍信号 微操作控制部件: 根据时序信号发生器的节拍信号和指令译码器的译码结果产生微操作控制信号给各个计算机部件 而运算部件由运算器和通用寄存器组构成,寄存器组用于暂存运算数据和中间结果 内存则由存储体,地址寄存器AR,数据寄存器DR三部分构成\n计算机的性能指标 主频: 衡量计算机工作速度的主要指标之一 运算速度: 以每秒执行多少条指令或完成多少次浮点运算来表示 基本字长: 直接参与运算的数据的二进制位数,标志着运算精度,位数越多,精度越高 主存容量: 主存能储存信息的总量 主存存取周期: 对主存连续两次访问的最小时间间隔 外部设备的配置 其中运算速度由两种计量单位,分别是\nMIPS:百万条指令/秒(million instructions per second) MFLOPS:百万次浮点运算/秒(million flops per second) 第二章 数的表示（作为第十章的基础） 机器数：用二进制编码表示的数据 在计算机中，常用 原码、补码、反码 三种方法来表示带符号的机器数\n下面的.表示拼接 原码表示法 定义 原码由 符号位 + 数值位 组成\n符号位 s：0 表示正，1 表示负 数值位表示绝对值的二进制 表示公式\n正数\n原码 = 0 · |x|₂ 负数\n原码 = 1 · |x|₂ 简单说就是负数前面加个1 举例: [-0.1101]_原=1.1101 [-1101]_原=11101\n特点\n表示直观 存在 +0 与 −0 加减运算用根据符号来判断,但符号位不参与运算 反码表示法 定义 补码的中间过程 表示公式\n正数\n反码 = 原码 = 0 · |x|₂ 负数\n反码 = 符号位不变，数值位逐位取反 特点\n存在 +0 与 −0 运算仍不方便 补码表示法 定义 补码是计算机中实际采用的带符号数表示方法\n表示公式\n正数\n补码 = 原码 = 0 · |x|₂ 负数\n补码 = 原码数值位逐位取反 + 1 补充说明\n小数补码中，“加 1”是对最低有效位加 1 若产生进位，直接舍去最高进位 特点\n只有一个 0 加减法统一为加法 硬件实现简单 深入理解 一个数减去小于模的另一个数,可以用加上模与另一个数的绝对值之差来代替\n实际上负数的补码是用2的(n+1)次方加上该负数得到的,若负数x有n位,则补码相当于(11\u0026hellip;11)n +1 +x,这也就是负数补码之所以要对所有位取反再加一的真相\n当最高位即符号位有进位时实际上相当于加上了一个2的(n+1)次方,对结果取模时可以舍去 移码表示法 无论正负均用一个式子表示\n数的浮点表示 浮点是指小数点的位置不固定,随时浮动\n\\[\rX = M \\times 2^{E}\r\\] 其中： $E$：阶码，用定点整数表示,采用移码 $M$：尾数，用定点小数表示\n规格化浮点数 当浮点数的基数为 2 时，如果其尾数 $M$ 满足： \\[\r\\frac{1}{2} \\le |M| \u003c 1\r\\] 则称该浮点数为规格化浮点数\n很好理解,如果尾数小于1/2,说明有前导0,那么就可以将阶码减小1来消去0 例题 步骤如下:\n先将十进制数化成尾数乘以阶码的形式, 而在实际存储中,将阶码用移码表示,也就是加上2的4次方,从而保证都是正数 再把尾数用补码的形式表示出来,符号位放到最前面的数符位置,数值位放在最后面 第六章 计算机执行程序的过程 指令说明 Load 指令（装载寄存器指令） 功能：把一个数据送入指定的寄存器 数据来源： 立即数 存储器中的某个单元 Store 指令（存储指令） 功能：把指定寄存器中的数据存入存储器的某个单元 Add 指令（加法指令） 功能：将两个寄存器中的数据相加,把运算结果存入指定寄存器 Jump 指令（跳转指令） 功能：跳转到新的地址继续执行指令 记号说明 [Rx]：寄存器 Rx 中的内容 MEM[y]：存储器中地址为 y 的存储单元内容 →：传送（数据流向） 指令示例说明 第 1 条 Load R1，200(R0)\nMEM[[R0] + 200] → R1\n以 R0 为基址，加偏移量 200，将对应存储单元内容送入 R1\n第 2 条 Load R2，#4\n4 → R2\n4 直接包含在指令中，称为立即数\n第 3 条 Add R3，R1，R2\n[R1] + [R2] → R3\n将 R1 与 R2 中的数据相加，结果送入 R3\n第 4 条 Store R3，200(R2)\n[R3] → MEM[[R2] + 200]\n以 R2 为基址，加偏移量 200，将 R3 的内容存入存储器\n第 5 条 Store R2，@(208)\n[R2] → MEM[ MEM[208] ]\n@ 表示间接寻址，208 中的内容作为有效地址\n第 6 条 Jump 1000\n1000 → PC\n将程序计数器 PC 置为 1000，跳转执行\n指令后面跟着的第一个数就是指令操作的结果存储对象,再后面才是这个操作需要的数据 注意到这里的R1,R2,R3都是寄存器(register) 第一条指令**Load R1，200(R0)**详解 第一步\n[PC]-\u0026gt;AR,将pc存放的当前指令地址传入地址寄存器AR 从存储体读出对应地址的指令放入数据寄存器DR 将DR送入指令寄存器IR 第二步 指令译码器进行译码,结合CP和时序信号发生器产生的节拍信号,产生微操作控制信号\n第三步\n计算访问地址:[R0]+200-\u0026gt;AR [R0]-\u0026gt;ALU,把寄存器R0中的内容送入ALU IR中的操作数200送入ALU [ALU]-\u0026gt;AR,ALU进行加法运算,把结果传给AR 从存储器读出数据,送入寄存器R1 从存储器中地址200的存储单元读出数据,送入DR DR-\u0026gt;R1,将DR中的数据送入R1 第四步 PC+4,指向下一条指令\n显然所有指令的第1步(取指令),第2步(指令译码),第4步(PC+4)都是一样的,只是第三步的执行指令有区别 第七章 指令系统 指令格式 一条指令必须包含以下三条信息\n要执行的操作 操作的对象 操作的结果(操作结果要保存到哪里) 操作数:操作码操作的对象数据 指令的基本格式 指令的地址码 地址码中的地址可以为0,1个或多个,根据指令中地址码中的地址数量可以分为以下5种指令\n零地址指令 通常在两种情况下可能采用零地址指令\n指令本身不需要任何操作数 指令中所需的操作数是隐含指定的 一地址指令 在两种情况下可能采用一地址指令\n指令本身只需要一个操作数,如加1,求补等 A \u0026lt;- OP[A] 指令操作需要两个操作数，指令中指明一个操作数，而另外一个操作数在默认的某个地方 如 ：累加器AC中，操作结果存放到累加器AC中 AC \u0026lt;- [AC] OP [A] 二地址指令 A1 \u0026lt;- [A1] OP [A2] 根据存放操作数的位置不同，分为3种。\n寄存器-寄存器型（R-R型）指令 存储器-存储器型（M-M型）指令 寄存器-存储器型（R-M型）指令 显然其中一个是用来存放操作结果地址的 三地址指令和多地址指令 三地址指令被广泛采用,特别是在RISC计算机中\n多地址指令用于描述一批数据，指令中需要多个地址来指出数据存放的首地址、长度和下标等信息\n指令的操作码 指令系统中的每一条指令都有唯一确定的操作码，不同指令的操作码是不相同的。\n操作码的长度决定了指令系统的最大规模。\n若操作码的位数为 n 位，则该指令系统最多能有 $2^{n}$ 条指令\n固定长度操作码 所有指令操作码的长度都是固定的，且集中放在指令的一个字段内。\n有利于简化硬件设计，减少指令译码时间。\n很多现代计算机都采用了固定长度操作码。\n可变长度操作码 指令系统中操作码的长度有多种，不同指令的操作码长度不完全相同。\n使用频率高的指令使用短的操作码 使用频率低的指令使用较长的操作码 可以缩短操作码的平均长度，但会使硬件设计复杂化，增加指令译码的时间和难度。\n扩展操作码 将操作码设计为几种不同的固定长度，且相互之间按某种规则进行扩展。\n优点\n可以简化硬件设计； 当指令总长度一定时，可以使操作码的长度随地址数的增加而减少，\n不同地址数的指令的操作码长度也不同，从而有效缩短指令总长度。 扩展操作码的方法\n等长扩展 不等长扩展 扩展示例 指令长度 指令长度：指一条指令所包含的二进制代码的总位数。\n指令长度主要取决于以下因素：\n操作码的长度 操作数地址的长度 操作数地址的个数 指令长度通常与机器字长存在简单的倍数关系。\n按与机器字长的关系分类 单字长指令\n指令长度等于机器字长的指令\n半字长指令\n指令长度等于半个机器字长的指令\n双字长指令\n指令长度等于机器字长两倍的指令\n指令长度一般应是字节的整数倍 寻址方式 指令的地址码给出的地址不一定是操作数的真正地址,而是形式地址,确定操作数有效地址的方法就叫寻址方式\n直接寻址 在指令的地址码字段直接给出操作数所在主存单元的地址。\n简单、快速的寻址方式，但寻址范围受限于地址码字段的位数 间接寻址 指令的地址码字段给出的是操作数所在内存单元的地址的地址。\n指令中形式地址所指定的内存单元中存放的内容才是操作数的真正地址。 得到操作数需要访问两次内存，指令的执行速度比较慢 立即寻址 指令的地址码字段直接给出操作数本身，而不是操作数的地址。 指令执行速度最快，得到指令的同时就得到了操作数，不需要再访问内存。 寄存器直接寻址与寄存器间接寻址 寄存器直接寻址：指令的地址码字段给出一个寄存器编号，该寄存器中存放的内容就是操作数。 寄存器间接寻址：寄存器中存放的内容是操作数的地址，根据此地址访问内存取得操作数。\n隐含寻址 指令中不给出操作数的地址\n操作数约定在某个特定的寄存器或堆栈中 PC相对寻址 将程序计数器PC的内容与指令中给出的形式地址（偏移量）的值相加，形成操作数的有效地址\n形式地址（偏移量）的值可正可负。 对于短跳转和程序的再定位很有用。 变址寻址 将指令中给出的形式地址的值与变址寄存器的内容相加，形成操作数的有效地址。\n变址寄存器可以是专用寄存器，也可以是通用寄存器中的一个。 常用于字符串处理、数组运算等成批数据处理中，主要是面向用户，解决程序循环控制问题。 通常地址的变化体现在变址寄存器中，指令中的形式地址相对固定。 基址寻址 把指令中给出的形式地址的值与基址寄存器的内容相加，形成操作数的有效地址。\n基址寄存器的内容称为基地址。 主要用于将用户程序的逻辑地址转换成主存的实际地址。它面向系统，解决程序重定位和扩大寻址空间等问题。 通常地址变化体现形式地址上，基地址相对不变。 “基址+变址”寻址 有效地址=（RB）+（RX）+D 其中:\n（RB）：基址寄存器RB中的内容 （RX）：变址寄存器RX中的内容 D：指令字中给出的形式地址（偏移量） 指令按功能分类 数据传送类指令 将数据从一个地方传送到另一个地方\n主要实现主存和主存之间、主存和寄存器之间、寄存器和寄存器之间的数据传送。 数据传送指令一次可以传送一个数据，也可以一次传送一批数据。 数据运算类指令 用来实现数据的算术运算、逻辑运算和移位运算\n算术运算：加、减、乘、除运算；加1 、减1；比较指令等。 逻辑运算：包括与、或、非 、异或等运算。 移位指令：算术移位、逻辑移位和循环移位三类，它们又可分为左移和右移两种。 程序控制类指令 主要用来控制程序执行的顺序和方向。\n包含转移指令、子程序调用和返回指令、自陷指令等 输入输出指令\n简称I/O指令,主要用于实现主机与外部设备之间的信息交换 指令系统设计 指令系统的设计包括指令的功能设计,指令格式的设计两部分\n在确定哪些基本功能用硬件来实现时，主要考虑3个因素：速度、成本、灵活性。\n硬件实现的特点 速度快、成本高、灵活性差 软件实现的特点 速度慢、价格便宜、灵活性好 对指令系统的基本要求 完整性：在一个有限可用的存储空间内，对于任何可解的问题，编制计算程序时，指令集所提供的指令足够用。\n要求指令集功能齐全、使用方便 规整性：主要包括对称性和均匀性。 对称性：所有与指令集有关的存储单元的使用,操作码的设置等都是对称的。\n例如：在存储单元的使用上，所有通用寄存器都要同等对待。在操作码的设置上，如果设置了A-B的指令，就应该也设置B-A的指令。 均匀性：指对于各种不同的操作数类型、字长、操作种类和数据存储单元，指令的设置都要同等对待。\n例如：如果某机器有5种数据表示，4种字长，两种存储单元，则要设置5×4×2=40种同一操作的指令。 正交性：指在指令中各个不同含义的字段，如操作类型、数据类型、寻址方式字段等，在编码时应互不相关、相互独立。\n高效率：指指令的执行速度快、使用频度高。\n兼容性：主要是要实现向后兼容，指令系统可以增加新指令，但不能删除指令或更改指令的功能。\n指令格式的设计 3种常用的指令编码格式:可变长度编码格式、固定长度编码格式、混合型编码格式\n可变长度编码格式 当指令系统包含多种寻址方式和操作类型时，这种编码方式可以有效减少指令系统的平均指令长度，降低目标代码的大小。\n可能会使各条指令的字长和执行时间相差很大。 多数CISC指令系统均采用了这种编码格式。 固定长度编码格式 将操作类型和寻址方式一起编码到操作码中。\n当寻址方式和操作类型非常少时,这种编码格式非常好,可以有效地降低译码的复杂度，提高译码的速度。 大部分RISC的指令系统均采用这种编码格式。 混合型编码格式 提供若干种固定的指令字长,以期达到既能够减少目标代码长度又能降低译码复杂度的目标。 指令系统的两种设计策略 CISC（复杂指令系统计算机） Complex Instruction Set Computer\n增强指令功能 将越来越多的功能交由硬件实现 指令数量不断增加 单条指令可完成较复杂的操作 RISC（精简指令系统计算机） Reduced Instruction Set Computer\n尽可能简化指令系统 指令条数较少 指令功能相对简单 复杂功能由多条简单指令组合完成 为什么研发RISC CISC的优点:指令数量多、功能多样 问题:\n各种指令的使用频度相差悬殊，许多指令很少用\n据统计：只有20％的指令使用频度比较高，占运 行时间的80％。而其余80％的指令只在20％的运行时 间内才会用到。 指令系统庞大，指令条数很多，许多指令的功能又很复杂，使得控制器硬件非常复杂。\n占用了大量的芯片面积 增加了研制时间和成本，容易造成设计错误。 许多指令由于操作繁杂，其CPI值比较大，执行速度慢。采用这些复杂指令有可能使整个程序的执行时间反而增加。\n由于指令功能复杂，规整性不好，不利于采用流 水技术来提高性能。\n设计RISC机器遵循的原则\n指令条数少、指令功能简单。只选取使用频度很高的指令，在此基础上补充一些最有用的指令 采用简单而又统一的指令格式，并减少寻址方式；指令字长都为32位或64位 指令的执行在单个机器周期内完成 采用load-store结构:只有load和store指令才能访问存储器,其它指令的操作都是在寄存器之间进行 大多数指令都采用硬连逻辑来实现 强调优化编译器的作用,为高级语言程序生成优化的代码 充分利用流水技术来提高性能 典型RISC实例:MIPS 寄存器 32个64位通用寄存器,其中R0的值永远是0(GPRs) 32个64位浮点数寄存器(FPRs) 一些特殊寄存器 数据表示 整数:字节（8位）;半字（16位）;字（32位）; 双字（64位） 浮点数:单精度浮点数（32位） 双精度浮点数（64位） 字节、半字或者字在装入64位寄存器时，用零扩展或者用符号位扩展来填充该寄存器的剩余部分。装入以后，对它们将按照64位整数的方式进行运算。\n数据寻址方式 只有立即数寻址与偏移量寻址两种\n指令格式 只有三种指令格式,都是32位,其中操作码占6位\nI 类指令（Immediate） 包含类型\nLoad / Store 指令 立即数指令 分支指令 寄存器跳转指令 寄存器链接跳转指令 特点\n立即数字段长度：16 位 用途：提供立即数或地址偏移量 指令语义\nLoad 指令\n有效地址：Regs[rs] + immediate 从存储器读取数据，写入寄存器 rt Store 指令\n有效地址：Regs[rs] + immediate 将寄存器 rt 中的数据写入存储器 立即数指令\nRegs[rt] ← Regs[rs] op immediate 分支指令\n转移目标地址：Regs[rs] + immediate 寄存器 rt 不使用 寄存器跳转并链接\n转移目标地址：Regs[rs] R 类指令（Register） 包含类型\nALU 运算指令 专用寄存器读 / 写指令 move 指令等 ALU 指令语义\nRegs[rd] ← Regs[rs] funct Regs[rt] funct 字段指定具体运算类型 J 类指令（Jump） 包含类型\n跳转指令 跳转并链接指令 自陷指令 异常返回指令 特点\n指令字低 26 位为偏移量 与当前 PC 值组合形成跳转目标地址 MIPS的操作 MIPS指令可以分为四大类 load和store ALU操作 分支与跳转 浮点操作\n数据表示 数据表示 第八章 CPU CPU的功能 CPU具有以下4个方面的基本功能\n指令顺序控制 指控制程序中指令的执行顺序。 程序中各指令之间是有严格先后顺序的，必须严格按程序规定的顺序执行，才能保证计算机工作的正确性 操作控制 一条指令的功能往往是由计算机中的部件执行一序列的操作来实现的。 CPU要根据指令的功能，产生相应的操作控制信号，发送给相应的部件，从而控制这些部件按指令的要求进行动作 时间控制 对各种操作实施时间上的定时。 在一条指令的执行过程中，在什么时间做什么操作均应受到严格的控制 数据加工 即对数据进行算术运算和逻辑运算，或进行其他的信息处理 基本组成 现代CPU一般由运算器、控制器、数据通路和Cache组成 指令执行的基本步骤 一条指令的执行过程包括3个基本步骤：\n取指令：从存储器取出一条指令，该指令的地址由程序计数器PC给出。 译码：对该指令的操作码进行译码分析，确定是哪一种指令，并转到这种指令对应的执行阶段。 执行：按指令操作码的要求执行该指令。执行过程可能需要多步操作，控制器将为之形成完成该指令功能所需要的操作控制信号。执行完毕后,回到取指令阶段，去取下一条指令。如此反复，直到整个程序执行完。 模型机:MIPS结构的简单实现 所包含的指令\n算术逻辑运算指令（R类型指令格式） add，sub，and，or，slt 操作码字段Op=0 存储器访问指令（I类型指令格式） lw（load word，op=35） sw（store word，Op=43） 等于“0”分支（I类型指令格式） beqz，Op=63 说明：beqz在MIPS中实际上是条伪指令。 R Op —— 操作码字段 用 IR[Op] 或 IR[31:26] 表示\nrs —— 第一源操作数字段 用 IR[rs] 或 IR[25:21] 表示\nrt —— 第二源操作数字段 用 IR[rt] 或 IR[20:16] 表示\nrd —— 目标操作数字段(或结果字段) 用 IR[rd] 或 IR[15:11] 表示\nshamt —— 无用\nfunct —— ALU 指令的运算函数码字段 用 IR[funct] 或 IR[5:0] 表示\nI\nrs —— 基址寄存器字段 用 IR[rs] 或 IR[25:21] 表示 对于 beqz 指令来说, 是存放被检测的数据\nadr —— 偏移量字段 用 IR[adr] 或 IR[15:0] 表示 rs 和 adr 用于计算访存有效地址或分支目标地址\nrt —— 寄存器字段 对于 load 指令来说, rt 所指出的寄存器是存放所取的数据 对于 store 指令来说, 是存放要写入存储器的数据\n构建基本的数据通路 程序计数器(PC):指出当前正在执行的指令的地址,每执行一条指令,PC+4 指令存储器(IM):假设IM内已经加载好了所需的指令,在IA加载地址,就可以从Ins得到对应指令 数据存储器(DM):两个输入端,一个是DA,给出要写入或读出的存储单元的地址,另一个是WD,给出要写入DM的数据;有两个控制信号:DMRead和DMWrite,任何时候最多只有一个有效 通用寄存器组:输入端有4个,RR1和RR2给出两个读操作的地址,WR给出写操作的地址,WD给出要写入的数据;输出端有两个:RD1和RD2分别给出读出的寄存器单元的数据;有一个控制信号RegWrite,当对寄存器组进行写入时才有效 ALU:输入两个32位的数据,输出ALUo是运算结果,由ALUCtrl(4位)给出运算操作 加法器:将两个输入数据相加,结果放到输出端SUM 符号位扩展部件:把16位的数据按符号扩展为32位的数据 判0部件:输入一个32位的数据,输出是一位信号,若输入为0时输出为真 构建R类指令 构建访存指令(load和store) beqz指令 ALU控制器 ALU完成具体的运算有5个：加、减、或、与、比较\n多周期实现方案 为什么用多周期 单周期方案的不足\n效率低下 不同类型的指令所完成的工作量有很大的差别 所要用到的部件和所通过的数据通路不同 所用时间的长短也有很大的差别\n硬件利用率低 每个时钟周期中功能部件最多被使用一次 如果在执行一条指令的过程中多次使用某一部件 就需要重复设置该部件（增加实现成本）\n解决方法：采用多周期方案\n采用更短的时间作为时钟周期 允许一条指令的执行跨越多个时钟周期 该时钟周期通常等于一个基本部件的延迟时间 好处\n可以共享同一个功能部件 控制器设计 实现控制器的技术有两种：硬连逻辑和微程序设计\n硬连逻辑是建立在有限状态机的基础上，并且一般是以状态图的形式表示。 微程序设计则是采用微指令的方式来表示和实现控制。 控制器的组成 指令部件 程序计数器PC 指令寄存器IR 指令译码器ID 地址形成部件 时序控制部件 时钟脉冲CP 时序信号发生器 微操作控制信号形成部件 中断控制逻辑 程序状态寄存器 流水线技术 什么是流水线技术 定义： 流水线技术是把一条指令的执行过程划分为若干个顺序阶段，每个阶段由专门的部件完成，不同指令在不同阶段上并行执行的一种处理技术。\n为什么要采用流水线技术 提高处理器吞吐率：在同一时间并行处理多条指令的不同阶段，使单位时间内完成的指令数增加 提高硬件利用率：各功能部件（取指、译码、执行等）可以同时工作，减少空闲时间 提升整体性能：在不显著增加主频的情况下，提高平均指令执行速度\n第九章 微程序控制器 组合逻辑控制器存在的两个比较突出的缺点:\n设计复杂、繁琐，缺乏规律性，设计效率低 不易修改和扩充，缺乏灵活性 微程序控制器的缺点:速度比较慢\n因此RISC仍旧采用硬连逻辑设计 基本原理 用二进制编码字（称为微指令字）来代替组合逻辑控制器中的微操作控制信号的产生\n微操作和微命令 微命令:构成控制信号序列的最小单位 微操作:接受微命令后进行的最基本的操作\n分为两种： 相容的微操作：可以同时进行的微操作 互斥的微操作：不能同时进行的微操作 微指令和微程序 微指令 微指令：用来产生微控制信号的二进制编码字,用于控制完成一组微操作\n微程序 微程序：由一系列微指令构成的有序集合 每一条机器指令都对应一段微程序 通过解释执行该微程序，完成指令所规定的操作\n微指令周期 微指令周期 微指令周期：微程序控制器的工作周期 从控制存储器中读取一条微指令到执行完相应微操作所需时间的最大值\n微指令的编码方法 设计目标: 减少微指令的宽度, 减少微程序的长度, 提高执行速度, 保持微程序设计的灵活性\n共有四种编码方法\n直接控制编码法(不译码法) 微操作控制字段的每一位直接对应一个微操作 当某位为 1 时, 表示执行相应的微操作;为 0 时不执行该微操作 优点\n结构简单 并行性最好 操作速度快 缺点\n微指令字太长 最短字长编码法 将所有微命令进行统一的二进制编码 每条微指令只定义一个微操作 微操作控制字段长度关系\n微操作控制字段长度 L 与微命令总数 N 的关系\nL ≥ log2 N 优缺点\n微指令字长最短 需要经过译码才能得到所需的微命令, 执行速度受影响 一条微指令只能产生一个微命令 无法利用硬件的并行性 字段直接编码法 将微操作控制字段进一步划分为若干字段 每个字段单独编码 每个码点表示一个微命令 折中方案\n字段之间采用直接控制 字段内部采用最短字长编码 字段划分原则\n可按功能或部件划分\n对机器中的每一类功能或每一个部件分配一个字段 将互斥的微操作分在同一字段\n将相容的微操作分在不同字段\n字段划分应与数据通路相适应\n一般每个字段应保留一个码点,用于表示不发任何微命令 字段间接编码 字段编码的含义需要由另一个字段的编码来解释确定 一个解释字段可同时对多个字段进行控制 只有这样才能有效缩短微指令字长 解释字段应具有一定的分类特征 常数源字段的设置 作用\n提供常数 参与其他字段的间接编码 微指令的格式 分为两大类: 水平型微指令 和 垂直型微指令\n水平型微指令 一次能定义并执行多个微操作的微指令\n特点\n微指令字较长,一般为几十位到上百位\n描述并行微操作的能力强 在一个微周期中可并行执行多个微操作\n微指令译码简单 一般采用直接控制编码法和分段直接编码法\n优缺点\n优点\n并行操作能力强 执行速度快 代码长度短 缺点\n微指令字较长,明显增加控存宽度 微程序编制复杂,难度较大,不易实现设计自动化 垂直型微指令 一次只能定义一两个微操作,微指令字长较短\n特点\n微指令字短 一般为十几位到二十位左右\n并行微操作能力差 一条微指令只能控制数据通路的一两种信息传送\n通过微操作码字段定义微指令的基本功能和信息传送路径 执行时需要完全译码 译码过程较复杂\n微指令各二进制位与数据通路控制点之间不存在直接对应关系\n优缺点\n优点\n结构直观、规整,易于编制微程序 微指令字较短,控存横向较窄 缺点\n微程序较长,微指令需经译码产生微命令,执行速度较慢 描述并行微操作能力差,不适合并行性较强的数据通路机器 后续微地址的产生 两种方式: 增量方式, 断定方式\n增量方式 设置一个微程序计数器 μPC 顺序执行时 给 μPC 增加一个增量(通常为 1), 得到下一条微指令地址 遇到转移时 由微指令给出转移目标微地址 微地址字段 SCF 的组成\n转移控制字段 BCF 用于规定是顺序执行还是转移 若为转移, 由 BCF 指出转移地址的来源\n转移地址字段 BAF 转移地址的来源有 3 种\n由 BAF 给出的地址\n机器指令所对应微程序的入口地址\n微子程序入口地址和返回地址 返回地址存放在返回地址寄存器中\n优点\nSCF 字段较短 后继微地址生成逻辑较简单 微程序编制较容易 缺点\n不能直接实现多路转移 断定方式 后继微地址的确定方式\n由微程序设计者直接指定\n由微程序设计者指定的测试判别逻辑字段控制产生 后继微地址的组成\n非测试地址 由微程序设计者直接指定 地址不变 构成微地址的高位部分\n测试地址 在微程序执行过程中 通过测试某些状态位动态决定 构成微地址的低位部分\n分支能力与字段位数\n若测试地址位数为 m 分支路数为 2^m 测试字段个数为 m\n测试字段位数 n 取决于测试条件个数 N 一般有 n = [log2 N] + 1\n优点\n可实现快速多路转移 提高微程序执行速度 微程序在控存中的存放位置灵活、方便 缺点\n后继微地址生成逻辑较复杂 微程序执行顺序不直观 微指令的执行方式 串行执行 取微指令和执行微指令串行进行。在前一条的微指令执行完之后，才能取下一条微指令。 特点 设备效率低，执行速度慢。控制简单，易于实现。\n并行执行 当前微指令的执行和下一条微指令的取出重叠进行\n优点\n提高了执行速度和设备利用率 存在的问题\n当需要根据当前微指令的执行结果进行转移时 会产生控制上的困难 两种处理方法\n方法一: 推迟取指 推迟下一条微指令的取出 使其取出时间与串行执行方式相同\n方法二: 猜测法 采用猜测方式 在两条可能的分支中 猜测性地选择其中一条作为后继微指令\n第十章 运算方法和运算器 移位运算 逻辑移位 逻辑移位中,被移动的数据是逻辑数,没有符号和数值大小\n逻辑左移: 数据各位左移一位,最高位丢弃,最低位移入0 逻辑右移: 依次右移,最低位丢弃,最高位移入0 循环移位 将被移位的数据左右两端相连形成闭合回路 如10011左移变成00111\n算术移位 对带符号的数进行移位;会引发数值变化\n原码算术移位 符号位不参加移位,将相应数值进行逻辑移位\n补码算术移位 对补码进行算术移位时,符号位一起参与移位\n算术左移: 连同符号位一起左移一位,最高位丢弃,最低位移入0 算术右移:连同符号位一起右移一位,符号位不变,最低位移出丢弃 左移时可能发生溢出,取模时得到负数 定点数加减法运算 补码加减法 若符号位产生进位则舍弃进位\n判断溢出 采用两个操作数和结果的符号来判断 当两个同号数的补码相加，若得到的结果的符号与两个操作数的符号不同，则发生了溢出\n采用最高数值位产生的进位与符号位产生的进位是否相同来判断 ,当不相同时，则发生了溢出\n采用变形补码,将符号位扩展为2位,若运算结果的两个符号位不同,则发生了溢出\n定点数乘除法运算 原码一位乘法 浮点数加减法运算 设有两个规格化浮点数 X 和 Y, 分别为 X: XEXM Y: YEYM\n其中\nXE 和 YE: 阶码 XM 和 YM: 尾数 浮点加减法运算的步骤 判 0 操作 对阶 尾数加减 规格化与舍入 （在规格化过程中需要判断运算结果是否溢出） 1. 判 0 操作 若两个操作数 X 和 Y 中有一个为 0 则不需要进行运算 直接设置运算结果为 0 运算结束\n否则进入下一步\n2. 对阶 目的\n使小数点对齐 方法\n对其中一个操作数进行变换 使两个操作数的阶码相等 对齐大阶码 实现步骤 求阶差 △E = XE − YE\n若 △E \u0026gt; 0\n表示 X 的阶码大于 Y 的阶码\n调整操作数 Y\n将 Y 的尾数 YM 右移 每右移一位 阶码 YE 加 1 直到两数阶码相等 若 △E \u0026lt; 0\n表示 X 的阶码小于 Y 的阶码 调整操作数 X 调整方法与上述相同 3. 尾数加减 将两数的尾数 XM 和 YM 按照相应的定点加减运算规则 进行加法或减法运算 得到运算结果的尾数 4. 结果规格化并判溢出 右规情况\n若运算结果的绝对值大于 1\n将结果右移一位 相应的阶码加 1 右规最多只需一位 左规情况\n若运算结果的绝对值小于 1\n将结果左移 每左移一位 阶码减 1 直到结果的绝对值大于等于 1/2 为止 溢出判断\n每次阶码加 1 或减 1 后 都要判断阶码是否越界\n阶码上溢\n阶码大于可表示的最大正数 可置溢出标志 或将结果作为 +∞ 或 −∞ 处理 阶码下溢\n阶码小于可表示的最小负数 可置溢出标志 或将结果作为 0 处理 5. 舍入处理 常用的舍入方法有\n0 舍 1 入法:类似四舍五入 截断法:直接舍去右移的数字 朝 +∞ 舍入法:若为正数,只要移出的数位不全为0,则最低有效位加1;若为负数则采用截断法 朝 −∞ 舍入法:若为负数,只要移出的数位不全为0,则最低有效位加1;若为正数则采用截断法 第十一章 存储器 存储子系统概述 三级存储系统:\nCache（高速缓冲存储器） 主存储器 磁盘存储器（辅存） 最靠近CPU的Cache速度最快,容量最小;离CPU最远的硬盘速度最慢，但容量最大\nCPU所访问的指令和数据的绝大部分都能在Cache中找到,之所以可以做到,是根据程序访问的局部性原理,即程序在一小段时间间隔内访问的指令和数据在地址上是相对集中的\n两个存储层次\n“Cache—主存”层次：解决主存速度不足的问题 “主存—辅存”层次：解决主存容量不足的问题 存储器分类 按在计算机系统中的作用分类 主存储器,又简称为主存或内存。 它是整个存储系统的核心，用来存放计算机当前运行的程序以及所需的数据，CPU可直接随机地对它进行访问。 辅助存储器,简称为辅存或外存 弥补主存的容量不足,用来存放暂时不用的程序和数据。CPU不能直接访问它，当需要运行辅助存储器中的程序时，需将它们调入主存后供CPU使用。 高速缓冲存储器Cache 弥补主存的速度不足,位于CPU和主存储器之间 按照存取方式分类 存取方式：指访问存储单元的方法\n随机存储器 RAM Random Access Memory\n只读存储器 ROM Read Only Memory\n顺序存取存储器 SAM Sequential Access Memory\n随机存储器RAM 可随机地读取或写入存储器的任何一个单元，访问时间是固定的，与存储单元的物理位置无关\n在系统断电后大多数随机存储器RAM所保存的信息将丢失 常用来作主存和Cache 只读存储器ROM 只能随机读取存储器的任何一个单元，不能写入信息。\n系统断电后，所保存的信息不会丢失。 用来存放不需要改变的信息,比如存放系统程序 顺序存取存储器SAM 只能按顺序访问存储器中的信息，访问时间与信息在存储器中所处的物理位置有关。\n信息通常以文件或数据块的形式存放,如磁带 按照存储介质分类 存储介质一般具备3个特点:\n具有两种稳定的状态，分别代表二进制代码0和1\n能方便地检测出存储介质所处的状态\n两种状态容易相互转换\n半导体存储器\n磁表面存储器\n光存储器\n主存储器 主存的组成结构 存储体 存储二进制信息的主体，由许多存储单元构成，每一个存储单元存放1～8个字节 每一个存储单元都有一个统一的编号，称为地址,地址与存储单元之间是一一对应的。\n地址译码和驱动电路 读写电路 存储控制电路 主存的主要技术指标 存储容量 一个存储器中所能存储的二进制信息的总量.\n常用位 b 和字节 B 来表示 如 64Kb, 512KB, 4MB.\n存取速度 指访问存储器的速度.\n存取时间 TA\n又称访问时间或读写时间, 指从启动一次存储器访存操作到完成该操作所需要的时间.\n存储周期 TM\n指连续两次启动存储器访问所需的最小时间间隔.\n包括存储器的存取时间和自身恢复时间. 存储周期通常大于或等于存取时间. 主存带宽 BM\n存储器单位时间内所能存取的信息量.\n也称为数据传输率或主存的数据传输频率. 单位为 bit/s 或 byte/s. BM 的计算公式为:\nBM = 每个存储单元的位数 / TM (bit/s) BM = 每个存储单元的位数 / (TM × 8) (byte/s) 提高 BM 的方法有三种:\n增加存储单元的位数 减少 TM 采用多个存储体 可靠性 在规定的时间内, 存储器无故障读写的概率.\n通常用平均无故障间隔时间 MTBF (Mean Time Between Failures) 来衡量.\nMTBF 越长, 说明存储器的可靠性越高.\n功耗 指单位时间存储器所消耗的电能,功耗越小越好\n随机存储器 静态随机存储器SRAM 利用触发器来储存二进制信息 优点：SRAM 工作速度快，稳定可靠，不需要外加刷新电路，从而简化了外电路设计 缺点：所含晶体管较多，故集成度较低，功耗较大\n动态随机存储器DRAM 利用 MOS 晶体管的管极电容来存储二进制信息 优点：基本存储位元电路中所含晶体管数目少、集成度高、成本低、功耗小 缺点：它需外加刷新电路，工作速度比 SRAM 慢得多（破坏性读出且需要刷新）\nDRAM的刷新 什么叫刷新：为维持 DRAM 所存信息不变，需要定时地对 DRAM 中的电容充电，以补充泄漏掉的电荷。这个过程叫刷新\nDRAM 为什么需要刷新： DRAM 是利用电容上保存的电荷来存储信息的，由于存在漏电阻，即使电源不掉电，时间长了，电容上的电荷也会慢慢泄漏掉，DRAM 内存储的信息会自动消失。\n什么叫刷新周期：从上一次对整个存储器刷新结束到下一次对整个存储器刷新结束所需的时间\n集中式刷新：在一个刷新周期内，集中一段时间连续地对全部存储单元逐行刷新一遍。 在刷新操作期间，不允许 CPU 对存储器进行正常的访问 优点：读写操作时不受刷新工作的影响，系统的存取速度比较高 缺点：在集中刷新期间必须停止读写，这一段时间称为“死区”，而且存储容量越大，死区就越长。 2. 分散式刷新：把对每行存储单元的刷新分散到每个系统存取周期内完成。此时系统存取周期被分为两部分，周期前半段时间进行正常的存储器访问，后半段时间进行刷新操作。在一个系统存取周期内刷新存储矩阵中的一行,增加了系统的存取周期 优点：没有死区 缺点：刷新过于频繁。系统存取周期是存储芯片存取周期的两倍，降低了访问存储器的速度。 3. 异步式刷新：把刷新操作平均分配到整个最大刷新间隔内进行，相邻两行的刷新间隔为：刷新周期÷行数\n","date":"2026-01-15T08:00:00Z","image":"/p/2026-01-15-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86%E5%A4%8D%E4%B9%A0/67962380_p0-%E5%A4%A2%E4%B8%AD.webp","permalink":"/p/2026-01-15-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86%E5%A4%8D%E4%B9%A0/","title":"2026-01-15 计算机组成原理复习"},{"content":"最近一周都在忙cocos大作业,差点在30号晚上熬通宵.今天下午答辩,答辩前五分钟才把ppt写完,而项目到演示时都没能完整运行,还好老师们都不怎么在意真正的成品,反而是一直拷打代码中继承和多态的应用,侥幸过关,如果真的要当场演示,那估计就有大问题了.\n究其原因,是小组合作的环节出了问题,我们组有一个本来能自己单干的大佬,而剩下的两人包括我则是不一般的菜鸡,大多数代码都是由AI完成的,很多报错也只有问AI才能解决,因为我之前根本就没接触过CPP的工程构建,多文件的集成也都是靠AI解决的.\n尽管大佬给了很详细的接口文档,但由于文档全是英文,于是我们就直接贴给AI让它翻译,又没好意思让他一个人帮我们看代码,那样他就把活儿都干了.于是就悲剧了.尽管UI层的隔离测试可以跑通,Gameplay和Engine部分单独跑也可以,但由于我们这边的实现不够完全,接口没能全部写完整,于是在答辩前三天的合并时就出问题了,而且在大佬很早就提出要对齐接口的时候我们不够重视,导致接口一直有偏差,等发现是由于接口没对齐导致报错的时候离答辩只有不到一天了.而大佬以为我们都实现好了,只是路径包含或者平台差异导致报错,之前就没有来帮我们改代码,而这个时候再改就已经来不及了.\n复盘 小组项目成功的必要条件:\n组员之间关系平等,不存在不好意思说话的情况,只有好好的沟通交流才能保证项目协作正常 组员之间能力不能相差太大,如果能力都有限反而更好团队协作,光是抱大腿未免有些太要不得了 组员需要掌握基本的Debug能力,光是靠AI或者求爷爷告奶奶是不行的,而是要能够自己独立解决基本的技术问题 每次更新都需要及时通过文档告知其他组员,详细写明更改的地方,以及对合作者的接口要求,最好是中文(😀) 在开始项目之前就要搭出一个最基本的框架,即使再抽象也可以,把所有必须要实现的功能要求先列出来,才可以保证后续运行的时候不跑偏 分工不合理很有可能导致组员间暗藏怨气,需要找到一个合理的分工方式和架构保证工作量分配均匀 使用git进行版本控制,每次commit时不要偷懒,多打几个字把本次commit好好说清楚,才不会在日后受罪 需要尽早进行合并,才能确认基本功能有没有正常实现,否则后期的合并会极其痛苦 永远不要孤军奋战,给代码一通乱改,而是虚心请教他人,如果拉不下脸,想想项目失败的后果 想来这也是对我这一个学期课业的一个糟糕的总结吧,学的不够精,学的不够多,到最后啥都没剩下,还有三个小时就跨年了,祝新的一年我能更成熟一点,更好学一点,更活泼一点,更努力一点,不用太多,一点点就够了,能让我自己感觉到就够了.\n目标\ncpp工程学习 后端学习(数据库+Java) 网络安全学习 编写一个开源项目 参加十场以上的比赛 明确就业还是读研 ","date":"2025-12-31T08:00:00Z","image":"/p/2025-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/92320484_p0-%E3%80%8C%E8%81%B4%E3%81%84%E3%81%A6%E3%80%8D.webp","permalink":"/p/2025-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/","title":"2025 年终总结"},{"content":"命题逻辑 逻辑联结词 析取与合取 析取符号: ∨ 合取符号: ∧\n想象合取就是两个条件都要满足,析取符号就像漏斗一样析取东西,因此朝上 蕴含与等价 p-\u0026gt;q =¬p∨q\n不要读作如果p,那么q,而是读作p蕴含q,表示p是q的充分条件,q是p的必要条件 我的理解:当p为真自然可以推出q为真,故p真q假真值为F,当p为假时无法得知q的情况,故只能默认为T 真值表 p ↔ q=(¬p∨q)∧(¬q∨p)\n由于p,q互为充要条件,故p与q真值相同时等价式为真 命题 设A为一个命题公式\n若A在所有赋值下都为真,则为重言式 若A在所有赋值下都为假,则为矛盾式 若至少有一组成真赋值,则为可满足式 不能被分解,真值确定的简单陈述句称为简单命题(原子命题,命题常项)\n(真值未知但确定也是命题) 真值可以变化的简单陈述句称为命题变项(不是命题!!!!!!!!!!!!!!!!!!!!!!!!!!!) 使用联结词联结简单命题形成的命题称为复合命题 逻辑等值式汇总 双重否定律\n¬¬A ⇔ A 幂等律\nA ∧ A ⇔ A A ∨ A ⇔ A 交换律\nA ∨ B ⇔ B ∨ A A ∧ B ⇔ B ∧ A 结合律\n(A ∧ B) ∧ C ⇔ A ∧ (B ∧ C) (A ∨ B) ∨ C ⇔ A ∨ (B ∨ C) 分配律\nA ∨ (B ∧ C) ⇔ (A ∨ B) ∧ (A ∨ C) A ∧ (B ∨ C) ⇔ (A ∧ B) ∨ (A ∧ C) 德·摩根律\n¬(A ∨ B) ⇔ ¬A ∧ ¬B ¬(A ∧ B) ⇔ ¬A ∨ ¬B 吸收律\nA ∨ (A ∧ B) ⇔ A A ∧ (A ∨ B) ⇔ A 零律\nA ∨ 1 ⇔ 1 A ∧ 0 ⇔ 0 同一律\nA ∨ 0 ⇔ A A ∧ 1 ⇔ A 排中律\nA ∨ ¬A ⇔ 1 矛盾律\nA ∧ ¬A ⇔ 0 蕴涵等值式\nA → B ⇔ ¬A ∨ B 等价等值式\nA ↔ B ⇔ (A → B) ∧ (B → A) 假言易位（逆否命题）\nA → B ⇔ ¬B → ¬A 等价否定等值式\nA ↔ B ⇔ ¬A ↔ ¬B 归谬论\n(A → B) ∧ (A → ¬B) ⇔ ¬A 对称差等值式\nA ⊕ B ⇔ (A ∨ B) ∧ ¬(A ∧ B) A ⊕ B ⇔ (A ∧ ¬B) ∨ (¬A ∧ B) 联结词完备集 定义 设 S 是一个联结词集合。若任意真值函数都可以由仅含 S 中联结词构成的命题公式来表示，则称 S 为联结词完备集\n这里的真值函数可以看作是命题对应的函数,不同形式但等价的命题有相同的真值函数 与非和或非 与非:p ↑ q = ¬(p ∧ q)\n或非:p ↓ q = ¬(p ∨ q)\n可以把箭头想象成水流,自然要从开口进入,这样就好记忆了\n联结词完备集整理 编号 联结词集合 是否完备 证明思路 / 说明 S1 {¬, ∧, ∨, →, ↔} 完备 包含 ¬（非）、∧（与）、∨（或）三种基本联结词，可表示所有 n 元真值函数（经典教材定理）。→ 和 ↔ 可由 ¬、∧、∨ 表示。 S2 {¬, ∧, ∨, →} 完备 包含 ¬、∧、∨，能表示任意 n 元真值函数；→ 可由 ¬、∨ 表示（p → q ≡ ¬p ∨ q）。 S3 {¬, ∧, ∨} 完备 经典完备集，¬、∧、∨ 可表示任意 n 元真值函数。 S4 {¬, ∧} 完备 通过与非公式可表示 ∨：p ∨ q ≡ ¬(¬p ∧ ¬q)。因此可表示 ¬、∧、∨ → 完备。 S5 {¬, ∨} 完备 通过或非公式可表示 ∧：p ∧ q ≡ ¬(¬p ∨ ¬q)。因此可表示 ¬、∧、∨ → 完备。 S6 {¬, →} 完备 通过 ¬ 与 → 可以表示 ∧ 和 ∨： 观察可以知道,只要有¬和基础联结词中三个中的一个就是完备集\n同时↑和↓可以单独作为完备集\n与非（↑）单独作为完备集 ¬p ≡ p ↑ p p ∧ q ≡ (p ↑ q) ↑ (p ↑ q) 证明提示\n¬¬(p ∧ q)=¬(p↑q) 或非（↓）单独作为完备集 ¬p ≡ p ↓ p p ∨ q ≡ (p ↓ q) ↓ (p ↓ q) 范式 合取范式和析取范式 析取范式:由有限个简单合取式构成的析取式 合取范式:由有限个简单析取式构成的合取式\n极小项:简单合取式中每个命题变项及其否定有且只有一个出现过一次 极大项:简单析取式中每个命题变项及其否定有且只有一个出现过一次\n为什么说是极小项,因为所有命题的交集覆盖范围是最小的,效力最弱;而极大则是因为所有命题的并集覆盖范围是最大的,效力最强\n若命题A的析取范式中所有合取式都是极小项,则称为A的主析取范式 若命题A的合取范式中所有析取式都是极大项,则称为A的主合取范式 这时可以对每个极小项进行编码成m(001),每个极大项为M(001)这样的形式,用角标来表示主析取范式和主合取范式\n计算方法 求A的主析取范式的方法\n若合取式B中不含命题变项p及其否定,则将B展开为合取形式B ∧ (p ∨ ¬p),消去重复出现的命题和极小项,将极小项按照角标由小到大的形式排列\n至于为什么用合取形式而不是用析取形式B ∨(p ∧ ¬p),是因为要保证每个字式仍然是简单合取式 求出主析取范式后就可以求主合取范式了,主析取范式中没出现的极小项的角标作为极大项的角标,这些极大项构成的合取式为A的主合取范式.\n当然反过来也是可以的 一阶逻辑 个体与谓词 个体常项:表示具体或特定个体的词,用a,b,c\u0026hellip;表示 个体变项:表示抽象或泛指个体的词,用x,y,z\u0026hellip;表示 个体变项的取值范围称为个体域 当无特殊声明时,个体域可以表示所有事物,称为全总个体域 谓词常项与谓词变项和上述概念对应,用F,G,H\u0026hellip;表示 谓词中包含的个体数称为元数,n元谓词含有n个个体词,0元谓词就是简单命题 合式公式 合式公式:也叫谓词公式,简称公式,看做是命题A加上量词或者个体变项后的形态例如\u0026quot;∀x(A∧B)\u0026ldquo;就行了\n在\u0026rdquo;∀xA\u0026quot;,\u0026quot;∃xA\u0026quot;中的x称为指导变项,A为相应量词的辖域,在辖域x的所有出现称为约束出现,不受辖域约束的出现称为自由出现\n若公式A中无自由出现的个体变项,则称A为封闭的合式公式,简称闭式 换名规则: 将一个指导变项及其在辖域中所有的约束出现替换成没出现过的个体变项符号\n换句话说换名就是让公式中不存在既是自由出现又是约束出现的个体变项 解释: 对公式A中出现的每个个体常项和谓词变项进行赋值,由以下四部分构成\n非空个体域D 给个体常项指定一个D中的元素 给函数变项指定一个D上的函数 给谓词变项指定一个D上的谓词 这个时候∀可以看作是∧,∃就看作是∨,这个通过下面的例题就很好理解了\n例子 (1)中直接把x=2和x=3的情况用∧连接了 等值式和前束范式 等值式:若A ↔ B为永真式,则称A与B是等值的,记作A⇔B 前束范式:A=QB,其中Q为∀x或者∃x这样的形式,B为不含量词的谓词公式\n一、量词否定等值式 ¬∀xA(x) ⇔ ∃x¬A(x) ¬∃xA(x) ⇔ ∀x¬A(x) 二、量词辖域收缩与扩张等值式（B 中不含 x） ∀x(A(x) ∨ B) ⇔ ∀xA(x) ∨ B\n∀x(A(x) ∧ B) ⇔ ∀xA(x) ∧ B\n∀x(A(x) → B) ⇔ ∃xA(x) → B\n∀x(B → A(x)) ⇔ B → ∀xA(x)\n∃x(A(x) ∨ B) ⇔ ∃xA(x) ∨ B\n∃x(A(x) ∧ B) ⇔ ∃xA(x) ∧ B\n∃x(A(x) → B) ⇔ ∀xA(x) → B\n∃x(B → A(x)) ⇔ B → ∃xA(x)\n三、量词分配等值式 ∀x(A(x) ∧ B(x)) ⇔ ∀xA(x) ∧ ∀xB(x) ∃x(A(x) ∨ B(x)) ⇔ ∃xA(x) ∨ ∃xB(x) 注意到∀对∨是不可分配的,∃对∧是不可分配的,这也很好理解.\n因为含有B的公式中的约束变元x都本应该写成y(防止与A中的x相同),但在∀xA(x) ∧ ∀yB(y)中由于∧需要满足两边式子都成立,因此x=y时也要成立,所以可以写成∀x(A(x) ∧ B(x)),而若 是∀xA(x) ∨ ∀yB(y)只需要有一边式子成立,故当x=y时可能有一边不满足.\n同时,∃xA(x) ∨ ∃yB(y)由于只要找到一个数z满足一边条件就能让式子成立,无论是x=z还是y=z都可以,因此可以写成∃x(A(x) ∨ B(x)) ,而∃xA(x) ∧ ∃yB(y)中由于需要找到两个数z1,z2使得这个式子满足,而这两个数不一定相等,故不能合并.\n集合与关系 集合概念 真包含 A ⊂ B 当且仅当\n对任意 x，若 x ∈ A，则 x ∈ B 且 A ≠ B 幂集 P(A)：A 的所有子集组成的集合\n公式： P(A) = { B | B ⊆ A } |p(A)|=2^|A|\n集合运算 相对补 A - B = A ∩ ~B 对称差 A ⊕ B = (A − B) ∪ (B − A) A ⊕ B = (A ∪ B) − (A ∩ B) 对称差运算满足结合律,交换律,分配律,消去律 其中分配律最难理解\n证明：A ∩ (B ⊕ C) = (A ∩ B) ⊕ (A ∩ C)\n1 2 3 4 5 A ∩ (B ⊕ C) = A ∩ [(B − C) ∪ (C − B)] = [A ∩ (B − C)] ∪ [A ∩ (C − B)] = [(A ∩ B) − (A ∩ C)] ∪ [(A ∩ C) − (A ∩ B)] = (A ∩ B) ⊕ (A ∩ C) 例题 关系的定义 A到A的关系R称为A上的关系 全域关系与恒等关系 全域关系（EA）：集合 A 上的全体可能的有序对，即 EA = A × A。 恒等关系（IA）：集合 A 上所有元素与自身配对的关系，即 IA = {\u0026lt;x, x\u0026gt; | x ∈ A} 关系的几种性质 这里都是对于A上的关系R进行展开的\n性质名称 定义 关系矩阵特点 关系图特点 自反性 ∀x∈A，(x,x)∈R 主对角线元素全为 1 每个结点都有自环 反自反性 ∀x∈A，(x,x)∉R 主对角线元素全为 0 所有结点均无自环 对称性 ∀x,y∈A，(x,y)∈R ⇒ (y,x)∈R 矩阵关于主对角线对称 任一有向边必有反向边 反对称性 ∀x,y∈A，(x,y)∈R 且 (y,x)∈R ⇒ x=y 主对角线外不出现对称的 1 不同结点间不能出现双向边 传递性 ∀x,y,z∈A，(x,y)∈R 且 (y,z)∈R ⇒ (x,z)∈R 若 m_xy=1 且 m_yz=1，则 m_xz=1 存在长度为 2 的路径则必须有直接边 注意到反对称和对称可以是在单位矩阵中同时存在,因为两个对称位置都为0也可以认为是反对称的\n关系的运算 关系的逆 设关系 (R) 是集合 (A) 到集合 (B) 的一个二元关系，即 R ⊆ A × B，则关系 R 的逆关系记作 R⁻¹，定义为：\nR⁻¹ = {(b, a) | (a, b) ∈ R} 即将 R 中每一对元素的顺序交换。 性质：\n(R⁻¹)⁻¹ = R 如果 R 是 A 上的关系，则 R⁻¹ 仍是 A 上的关系 关系合成 设 R 是集合 A 到 B 的关系，S 是集合 B 到 C 的关系，则关系 S 与 R 的合成（或复合）记作 S ∘ R，定义为：\nS ∘ R = {(a, c) | 存在 b ∈ B，使得 (a, b) ∈ R 且 (b, c) ∈ S} 性质：\n(F ∘ G) ∘ H = F ∘ (G ∘ H) (F ∘ G)⁻¹ = G⁻¹ ∘ F⁻¹\n关系合成结合律的证明 要证：(F ∘ G) ∘ H = F ∘ (G ∘ H)\n证明思路：通过任意元素对 \u0026lt;x, y\u0026gt; 来判断两边是否相等。\n任取一对 \u0026lt;x, y\u0026gt;，假设 \u0026lt;x, y\u0026gt; ∈ (F ∘ G) ∘ H。 根据关系合成的定义，存在 t 使得：\n\u0026lt;x, t\u0026gt; ∈ F ∘ G \u0026lt;t, y\u0026gt; ∈ H 进一步展开 \u0026lt;x, t\u0026gt; ∈ F ∘ G 的定义，存在 s 使得：\n\u0026lt;x, s\u0026gt; ∈ F \u0026lt;s, t\u0026gt; ∈ G 现在我们有：\n\u0026lt;x, s\u0026gt; ∈ F \u0026lt;s, t\u0026gt; ∈ G \u0026lt;t, y\u0026gt; ∈ H 也就是存在 s 和 t，使得上述三条都成立。\n可以先固定 s，再看 t 是否存在：\n对于固定的 s，存在 t 使得 \u0026lt;s, t\u0026gt; ∈ G 且 \u0026lt;t, y\u0026gt; ∈ H 根据合成定义，这说明 \u0026lt;s, y\u0026gt; ∈ G ∘ H 因此我们得到：\n\u0026lt;x, s\u0026gt; ∈ F \u0026lt;s, y\u0026gt; ∈ G ∘ H 所以 \u0026lt;x, y\u0026gt; ∈ F ∘ (G ∘ H) 反向也类似（从 F ∘ (G ∘ H) → (F ∘ G) ∘ H），所以两边相等。\n关系的幂 设 R 是集合 A 上的关系，n 为自然数，则定义 R 的 n 次幂如下：\n零次幂 R⁰ = {\u0026lt;x, x\u0026gt; | x ∈ A} = IA（恒等关系）\n递推定义 Rⁿ⁺¹ = Rⁿ ∘ R（与矩阵乘法类似，用关系合成定义）\n集合 A 上关系的个数 设集合 A 有 n 个元素，即 |A| = n。\n关系的定义 A 上的一个关系 R 是 A × A 的任意子集。\n因为 A × A 有 n² 个有序对 每个有序对可以选择“在关系中”或“不在关系中” 关系数计算\n对每个有序对有 2 种选择 总共有 n² 个有序对 因此关系的总数为 2^(n²) 结论： 集合 A 上关系的总数 = 2^(n²)\n关系闭包 自反闭包(reverse) r(R) = R ∪ {(x,x) | x ∈ A} 相当于加上单位矩阵I 对称闭包(symmetry) s(R) = R ∪ R⁻¹ 补全另一半即可 传递闭包(transfer) t(R) = R ∪ R² ∪ R³ ∪ ··· 这个只好一个个推了,不出错就行' 等价类, 商集与划分 等价类 定义\n设 R 是集合 A 上的等价关系，对任意 a ∈ A， a 的等价类定义为 [a] = { x ∈ A | (a, x) ∈ R }\n四条性质及其证明 性质一：a ∈ [a]\n即每个元素属于自己的等价类\n证明\n因为 R 是等价关系，满足自反性， 所以 (a, a) ∈ R， 由等价类定义可知 a ∈ [a]。\n性质二：若 b ∈ [a]，则 [b] = [a]\n证明\nb ∈ [a] 等价于 (a, b) ∈ R。\n先证 [b] ⊆ [a]： 任取 x ∈ [b]，则 (b, x) ∈ R。 又因 (a, b) ∈ R，R 具有传递性， 得 (a, x) ∈ R，故 x ∈ [a]。\n再证 [a] ⊆ [b]： 由 (a, b) ∈ R 且 R 具有对称性， 得 (b, a) ∈ R， 同理可证任意 x ∈ [a] 有 x ∈ [b]。\n因此 [a] = [b]。\n性质三：若 [a] ∩ [b] ≠ ∅，则 [a] = [b] (即aRy)\n证明\n设存在 c ∈ [a] ∩ [b]， 则 (a, c) ∈ R 且 (b, c) ∈ R。\n由 (b, c) ∈ R 和对称性得 (c, b) ∈ R， 再由 (a, c) ∈ R 和传递性得 (a, b) ∈ R。\n于是 b ∈ [a]，由性质二可得 [a] = [b]。\n性质四：所有等价类的并集等于原集合 A\n证明\n任取 x ∈ A， 由于 R 是等价关系，满足自反性， 有 (x, x) ∈ R， 因而 x ∈ [x]。\n又因为 [x] 是 A 上的一个等价类， 所以 x 属于所有等价类的并集中。\n由 x 的任意性可得 ⋃{ [a] | a ∈ A } = A。\n商集 定义\n设 R 是集合 A 上的等价关系,\nA 关于 R 的商集定义为\nA / R = { [a] | a ∈ A }\n也就是说把A划分成多个等价类 划分 定义\n设 A 为非空集合, 若 A 的一个子集族 P 满足\n任意 B ∈ P, B ≠ ∅ 任意 B1, B2 ∈ P, 若 B1 ≠ B2, 则 B1 ∩ B2 = ∅ ⋃P = A 则称 P 是 A 的一个划分\n由集合 A 的一个划分得到 A 上的等价关系 设 A 为非空集合， π = { A₁, A₂, … } 是 A 的一个划分。\n定义关系 R：\nR = { \u0026lt;x, y\u0026gt; | x, y ∈ A，且 x 与 y 属于 π 中的同一划分块 }\n说明：\n对任意 x ∈ A，x 与自身属于同一划分块，故 \u0026lt;x, x\u0026gt; ∈ R 若 \u0026lt;x, y\u0026gt; ∈ R，则 x、y 在同一划分块中，对称性显然成立 若 \u0026lt;x, y\u0026gt; ∈ R 且 \u0026lt;y, z\u0026gt; ∈ R，则 x、y、z 属于同一划分块，传递性成立 因此，R 是集合 A 上的等价关系。\n例题 给出 A = {1, 2, 3} 上所有的等价关系\n求解思路 先写出 A 的所有划分，再由每个划分得到对应的等价关系。 第一步：列出 A 的所有划分 A = {1, 2, 3} 的所有划分共有 5 种：\nπ₁ = { {1}, {2}, {3} } π₂ = { {1, 2}, {3} } π₃ = { {1, 3}, {2} } π₄ = { {2, 3}, {1} } π₅ = { {1, 2, 3} } 第二步：由划分写出对应的等价关系 π₁ = { {1}, {2}, {3} }\nR₁ = { \u0026lt;1,1\u0026gt;, \u0026lt;2,2\u0026gt;, \u0026lt; 3,3\u0026gt; }\nπ₂ = { {1,2}, {3} }\nR₂ = { \u0026lt;1,1\u0026gt;, \u0026lt;2,2\u0026gt;, \u0026lt; 3,3\u0026gt;, \u0026lt;1,2\u0026gt;, \u0026lt;2,1\u0026gt; }\nπ₃ = { {1,3}, {2} }\nR₃ = { \u0026lt;1,1\u0026gt;, \u0026lt; 3,3\u0026gt;, \u0026lt;2,2\u0026gt;, \u0026lt;1,3\u0026gt;, \u0026lt; 3,1\u0026gt; }\nπ₄ = { {2,3}, {1} }\nR₄ = { \u0026lt;2,2\u0026gt;, \u0026lt; 3,3\u0026gt;, \u0026lt;1,1\u0026gt;, \u0026lt;2,3\u0026gt;, \u0026lt; 3,2\u0026gt; }\nπ₅ = { {1,2,3} }\nR₅ = { \u0026lt;1,1\u0026gt;, \u0026lt;2,2\u0026gt;, \u0026lt; 3,3\u0026gt;, \u0026lt;1,2\u0026gt;, \u0026lt;2,1\u0026gt;, \u0026lt;1,3\u0026gt;, \u0026lt; 3,1\u0026gt;, \u0026lt;2,3\u0026gt;, \u0026lt; 3,2\u0026gt; }\n结论\n集合 A = {1,2,3} 上一共有 5 个等价关系， 与 A 的 5 种划分一一对应。\n等价关系与偏序关系 等价关系:要求集合A上的R关系是自反,对称,传递的. 偏序关系:要求集合A上的R关系是自反,反对称,传递的.\n从定义可以知道偏序关系图中不存在双向箭头. 注意下面的小于等于只是表示偏序关系中的上下级关系 偏序集:A与A上的偏序关系R一起称做偏序集,记为\u0026lt;A,R\u0026gt;,对于任意的x,y∈A,若x\u0026lt;=y或者y\u0026lt;=x成立,则称x与y是可比的,若x\u0026lt;y,且x与y之间不存在z∈A使得x\u0026lt;z\u0026lt;y,则称y盖住x.\n简略一点说,y盖住x表示y是x的直属上司,y与x可比表示二者在同一条分支上,具有亲缘关系 全序集:若偏序集中∀x,y∈A,x与y都可比,则称(A,\u0026lt;=)是全序集\n设 (A, ≤) 为偏序集，B ⊆ A。\n最小元：若存在 m ∈ B，使得 ∀x ∈ B，都有 m ≤ x，则称 m 为 B 的最小元。 最大元：若存在 M ∈ B，使得 ∀x ∈ B，都有 x ≤ M，则称 M 为 B 的最大元。 极小元：若 m ∈ B，且不存在 x ∈ B 使得 x \u0026lt; m，则称 m 为 B 的极小元。 极大元：若 M ∈ B，且不存在 x ∈ B 使得 M \u0026lt; x，则称 M 为 B 的极大元。 上界：若 u ∈ A，使得 ∀x ∈ B，都有 x ≤ u，则称 u 为 B 的一个上界。 下界：若 l ∈ A，使得 ∀x ∈ B，都有 l ≤ x，则称 l 为 B 的一个下界。 最小上界：u是B的上界集合中的最小元，则称 u 为 B 的最小上界。 最大下界：l是B的下界集合中的最大元，则称 l 为 B 的最大下界。 简单一点说,最大元就是跟所有元素都可比,故需要位于哈斯图的上顶点,约束所有分支,故只能有一个,同理,最小元位于哈斯图的下顶点,只能有一个 极小元和极大元可以有多个,只要位于某一条分支的上顶点或者下顶点就可以了,因此最大元也是极小元,最小元也是极大元. 如果有多个极大元,就一定没有最大元,有多个极小元,就一定没有最小元,因为分支没有收束. 上界可以看成是偏序集中所有元素的上司,可以有多个,但最小上界只能有一个 我想到的问题:如果一个偏序子集B上方连接的两个直属上司构成了一个三角形时还会有最小上界吗\n经过思考后我发现偏序关系图中不存在三角形,因为反对称性决定了如果a\u0026lt;=b,b\u0026lt;=a则a=b,故不会有两个分支在同级连接的情况,否则两个元素就相互可比了. 函数 函数定义 设 f 是集合 A 与 B 之间的二元关系， 若满足： 对任意 x ∈ dom(f)，存在唯一的 y ∈ ran(f) 使得 x f y 成立， 则称 f 为 函数（从 A 到 B 的函数）。\n对于函数 f，如果 x f y，则通常记作 y = f(x)\n单射 / 满射 / 双射 设 f : A → B 为函数。\n单射\n若对任意 x₁, x₂ ∈ A， f(x₁) = f(x₂) ⟹ x₁ = x₂， 则称 f 为从 A 到 B 的单射。\n满射\n若对任意 y ∈ B， 存在 x ∈ A，使得 f(x) = y， 则称 f 为从 A 到 B 的满射。\n双射\n若 f 既是单射又是满射， 则称 f 为从 A 到 B 的双射。\n特征函数 设 A 为集合，B ⊆ A， 定义函数 χ_B : A → {0, 1} 为\nχ_B(x) = 1，当 x ∈ B χ_B(x) = 0，当 x ∉ B\nχ_B 称为集合 B 在 A 上的特征函数。\nB 上 A 定义\n所有从集合 A 到集合 B 的函数所组成的集合，记作 B 上 A， 读作“B 上 A”。\n符号化表示\nB^A = { f | f : A → B }\n计数性质\n设 |A| = m，|B| = n，且 m，n \u0026gt; 0， 则\n|B^A| = n^m\n特殊情况\n若 A = ∅，则\nB^∅ = { ∅ }\n若 A ≠ ∅ 且 B = ∅，则\n∅^A = ∅\n图的基本概念 有向图 \u0026lt;V,E\u0026gt;（v = vertex(节点)，e = edge(边)） 平凡图：只有一个 v，没有 e 的图 n 阶图：n 为节点数 邻接：有向边起点邻接到终点，故邻接矩阵中第 n 行为第 n 个节点作为起点，第 n 列为第 n 个节点作为终点，以此统计出度和入度 子图 真子图：与原图不一样就行 生成子图：节点数一样就行，边可以比原图连的少，但不能自己加边 导出子图：可以不用到所有节点，但母图中对应用到节点的边都要保留（或者不用到所有边，但对应的节点保留（废话，你都用到边了，那没节点哪来的边）） 补图 补图：设简单无向图 G = (V, E)，其补图记为 Ḡ = (V, Ē)，其中\nĒ = { {u, v} | u, v ∈ V，u ≠ v，且 {u, v} ∉ E }\n即在保持顶点集不变的情况下，Ḡ 中的边恰好是 G 中不存在的边。 同构图 同构图：设图 G₁ = (V₁, E₁)，G₂ = (V₂, E₂)，若存在一个双射 f : V₁ → V₂，使得\n对任意 u, v ∈ V₁，{u, v} ∈ E₁ 当且仅当 {f(u), f(v)} ∈ E₂， 则称 G₁ 与 G₂ 同构，记为 G₁ ≅ G₂。\n顶点数相同，边数相同，度数序列相同,但这只是必要条件，充分条件只能用瞪眼法了 连通性 回路与通路 当一条回路中的所有边互不相同时为简单回路，与此相反，有边相同时就是复杂回路，一个由 3 个节点形成的 8 是简单回路 若简单回路中除了初始节点和结束节点相同外，其他节点都不相同，则为初级回路，上文的 8 就不是初级回路\n初级回路和简单回路区分:初级想成是最基本的,结构最为合理,故不会出现重复节点;简单说明是回路就行,要求少一点 把上面的回路两个字换成通路就可以得到简单通路,复杂通路,初级通路的定义了 如果 u 到 v 存在通路，则称u和v是连通的,在有向图中称 u 可达 v，若图 G 任意两个顶点都连通，则称 G 是连通图，注意平凡图也是连通图 连通分支:图G相互之间不连通的导出子图 G的连通分支的数量记为p(G)\n有向图的连通性 可达与相互可达 设有向图 D = \u0026lt;V, E\u0026gt;，u, v ∈ V。\nu 可达 v：\n从 u 到 v 存在一条有向通路。\n规定：u 到自身总是可达。\nu 与 v 相互可达：\nu 可达 v 且 v 可达 u。\n弱连通 将 D 中所有有向边忽略方向，得到的无向图是连通图。\n单向连通 对任意 u, v ∈ V,u 可达 v 或 v 可达 u\n强连通 对任意 u, v ∈ V,u 与 v 相互可达\n割集 设 G = (V, E) 为连通图。\n边割集：设 C ⊆ E，若\n图 G − C 不连通； 对任意 e ∈ C，G − (C \\ {e}) 仍连通；\n则称 C 为 G 的边割集。 即C已经是能够让G不连通的最小边集合了,若C只有一条边e,称e为割边或桥 点割集：设 S ⊆ V，若\n图 G − S 不连通或退化为单点图； 对任意 v ∈ S，G − (S \\ {v}) 仍连通；\n则称 S 为 G 的点割集。 即S已经是能够让G不连通的最小点集合了,若S只有一个顶点v,称v为割点 分量与割 连通分量 无向图的连通分量\n无向图 G 的一个极大连通子图称为 G 的一个连通分量（或连通分支）。\n每一个顶点和每一条边都属于唯一的一个连通分量 连通图只有一个连通分量，即其自身 非连通无向图有多个连通分量 有向图的强连通分量\n有向图中的强连通分量是其极大的强连通子图。\n强连通图只有一个强连通分量，即其自身 非强连通的有向图有多个强连通分量 点割与点连通度 割点集\n在连通图 G 中，一个由顶点组成的集合，若从 G 中删除这些顶点后图变得不连通，则称该集合为割点集。\n点连通度\n点连通度 κ(G) 定义为割点集中顶点数的最小值。\nk-点连通图\n若图 G 可以在删除 k 个顶点后变得不连通，但不能在删除 k−1 个顶点后变得不连通，则称 G 为 k-点连通图。\n特别地，阶数为 n 的完全图是 (n−1)-点连通的。\n局部连通度 u, v 的割点集\n对一对顶点 u, v，若删除某个顶点集合后使 u 与 v 不连通，则该集合称为 u, v 的割点集。\n局部连通度\nκ(u, v) 表示使 u 与 v 不连通的最小割点集的大小。\n性质\n在无向图中，κ(u, v) = κ(v, u) 除完全图外，κ(G) 等于所有不相邻顶点对 u, v 的 κ(u, v) 的最小值 边割与边连通度 桥\n在图 G 中，删除某一条边后图变得不连通，则该边称为桥。\n割边集\n一个由边组成的集合，若删除这些边后图变得不连通，则称为割边集。\n边连通度\nλ(G) 表示最小割边集的大小。\n局部边连通度\nλ(u, v) 表示使 u 与 v 不连通的最小割边集的大小。\nk-边连通图\n若 λ(G) ≥ k，则称图 G 为 k-边连通图。\n连通度之间的关系 设 δ(G) 为图 G 的最小度，则有不等式： 1 2 κ(G) ≤ λ(G) ≤ δ(G) 极大连通图 若 κ(G) = δ(G)，称 G 为极大连通图 若 λ(G) = δ(G)，称 G 为极大边连通图 图的矩阵表示 无向图的关联矩阵 定义：设无向图 G = (V, E)，|V| = n，|E| = m。\n无向图的关联矩阵是一个 n × m 的 0-1 矩阵 M，其中\nM[i][j] = 1或2，当且仅当顶点 v_i 与边 e_j 关联； 否则 M[i][j] = 0。 关联即顶点作为该边的起点或者终点出现次数,当出现自环时关联次数为2 每一列都恰好有两个1或一个2 第i行元素之和为vi的度数,所有元素之和为2m 例： V = {v1, v2, v3, v4} E = {\ne1 = {v1, v2},\ne2 = {v1, v3},\ne3 = {v2, v3},\ne4 = {v4, v4}\n} e1 e2 e3 e4 v1 1 1 0 0 v2 1 0 1 0 v3 0 1 1 0 v4 0 0 0 2 有向图的关联矩阵 定义：设无环有向图 G = (V, E)，|V| = n，|E| = m。\n有向图的关联矩阵是一个 n × m 的矩阵 M，其中\nM[i][j] = -1，若边 e_j 从 v_i 出发； M[i][j] = 1，若边 e_j 指向 v_i； 否则 M[i][j] = 0。 每一列都有一个-1和1 例：\nV = {v1, v2, v3, v4} E = {\ne1: v1 → v2,\ne2: v1 → v3,\ne3: v2 → v4,\ne4: v3 → v4\n} e1 e2 e3 e4 v1 -1 -1 0 0 v2 1 0 -1 0 v3 0 1 0 -1 v4 0 0 1 1 有向图的邻接矩阵 定义：设有向图 G = (V, E)，|V| = n。\n有向图的邻接矩阵是一个 n × n 的矩阵 A，其中\nA[i][j] = 1，表示存在从 v_i 到 v_j 的有向边； 否则 A[i][j] = 0。 所有元素之和等于边数 例：\nv1 → v2，v1 → v3，v2 → v4，v3 → v4 v1 v2 v3 v4 v1 0 1 1 0 v2 0 0 0 1 v3 0 0 0 1 v4 0 0 0 0 有向图的可达矩阵 定义：设有向图 G = (V, E)，|V| = n。\n可达矩阵是一个 n × n 的矩阵 R，其中\nR[i][j] = 1，表示从 v_i 出发存在一条路径可到达 v_j； 否则 R[i][j] = 0。 由于顶点到自身都是可达的,故可达矩阵对角线上的元素恒为1 例：\n基于上述有向图 v1 v2 v3 v4 v1 1 1 1 1 v2 0 1 0 1 v3 0 0 1 1 v4 0 0 0 1 着色问题 着色问题：设 G = (V, E) 为无向无环图，给每个顶点分配一种颜色，使得\n若 {u, v} ∈ E，则 u 与 v 的颜色不同,即相邻顶点颜色不同 记使用的最少颜色数为k,称G为k-可着色的 Welsh–Powell 算法：\n是一种求图顶点着色的启发式算法，其基本思想是优先为度数大的顶点着色。\n算法步骤：\n将图中所有顶点按度数从大到小排序（若度数相同，可任意排列）； 取尚未着色的第一个顶点，赋予一种新颜色； 在剩余未着色顶点中，按排序顺序选择与已着该颜色顶点均不相邻的顶点，赋予同一颜色； 重复步骤 2–3，直到所有顶点均被着色。 例题\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 设 V = {v1, v2, v3, v4, v5} E = { {v1,v2}, {v1,v3}, {v1,v4}, {v2,v3}, {v3,v4}, {v4,v5} } 各顶点度数： deg(v1)=3，deg(v3)=3，deg(v4)=3，deg(v2)=2，deg(v5)=1 排序结果： v1, v3, v4, v2, v5 着色过程： - 颜色 1：v1，v5 - 颜色 2：v3 - 颜色 3：v4 - 颜色 4：v2 因此该算法得到 G 为 4-可着色（不一定是最小色数）。 练手 答案 特殊的图 二部图 把一个图的顶点划分为两个不相交子集，使得每一条边都分别连接两个集合中的顶点，如果存在这样的划分，则此图为一个二部图\n[深入理解](htt ps://blog.csdn.net/qq_26822029/article/details/90382581) 若 G 中无长度为奇数的回路，则无向图 G 是二部图 证明\n也就是说各自的子集中两个顶点之间都没有边 匹配 匹配（Matching）\n在无向图 G = (V, E) 中，若边集 M ⊆ E 满足：\n任意两条边不共享公共顶点(即不相连)，则称 M 为图 G 的一个匹配。\n极大匹配（Maximal Matching）\n若匹配 M 满足：\n在图中不能再加入任何一条边而仍保持是匹配，则称 M 为极大匹配。\n最大匹配（Maximum Matching）\n在图 G 的所有匹配中，边数最多的匹配称为最大匹配。\n匹配数\n最大匹配 M 中边的条数称为匹配数 完美匹配（Perfect Matching）\n若匹配 M 覆盖图中所有顶点，即每个顶点都恰好与一条匹配边相关联，\n则称 M 为完美匹配。\n完备匹配（Complete Matching）\n在二部图 G = (X, Y, E) 中，若存在一个匹配使得 X 中的每个顶点\n都与 Y 中某个顶点匹配，则称该匹配为完备匹配。(当|X|\u0026lt;|Y|时允许存在有顶点不匹配的情况)\nHall 定理及其引申 Hall 定理（婚姻定理）\n设 G = (X, Y, E) 为二部图。\n存在一个覆盖 X 的完备匹配，当且仅当对 X 的任意子集 S，\nS 的邻接顶点集合 N(S) 满足：\n|N(S)| ≥ |S| Hall 定理的等价表述\n二部图 G = (X, Y, E) 中存在完备匹配\n当且仅当 X 的任意子集都不“缺少”可匹配的邻接顶点。\n用人话说,当|X|\u0026lt;=|Y|时需要保证X中任意k个顶点至少邻接Y中k个顶点 Hall 定理的推论\n设 G = (X, Y, E) 为二部图，若存在t\u0026gt;0,使得:\nX中每个顶点至少关联t条边 Y中每个顶点至多关联t条边 则称G中存在X到Y的完备匹配 显然这是一个很强烈的充分条件而非必要条件 欧拉图 欧拉回路:通过图中所有边且每边仅通过一次的回路，具有欧拉回路的图称为欧拉图（Euler Graph）\n将回路改为通路就得到了欧拉通路的定义 无向图中：\n有欧拉回路：当且仅当 G 是连通图且无奇度顶点 有欧拉通路但没有欧拉回路：当且仅当 G 是连通图且恰好有两个奇度顶点 当然有欧拉回路就有欧拉通路,少连一条边就是了 有向图中：\n有欧拉回路：当且仅当 G 是连通图且每个顶点的入度等于出度 直观上很好理解，证明还是算了吧 哈密顿图 通过图 G 的每个结点一次且仅一次的通路（回路），就是哈密顿通路（回路），存在哈密顿回路的图就是哈密顿图\n判定方法 若n阶有向图G对应的无向图中含有生成子图Kn(即生成子图为完全图),则G中存在哈密顿通路\n这个挺显然的,如果每个顶点间都有连线,怎么都能把所有顶点连接起来 平面图 若 G 能画在平面上而不出现边交叉，则为平面图，画出的图称为 G 的平面嵌入\n面:设G是一个平面嵌入,G 的边将整个平面划分成若干区域,每个区域称为G的一个面,其中面积无限的区域称为无限面或外部面,面积有限的区域称为有限面或内部面\nnnd图论这一堆中文别名就不能统一一下,自己研究起来不麻烦吗 包围面R的所有边构成的回路称为R的边界,边界的长度称为R的次数,记为deg(R)\n注意若外部面包含割边,需要沿着割边的两侧绕行形成回路,故会计算两次 在简单平面图中，每个面的次数 ≥ 3 （因为不存在长度为 1 或 2 的回路）。 各面次数之和等于边数的 2 倍： 平面图各面的次数之和等于边数的2倍 证明 在平面图中，每一条边都有两侧：\n要么分别邻接两个不同的面； 要么作为桥或割边，在同一个面边界上出现两次 无论哪种情况， 每一条边都会被恰好计入两个面的次数中。\n极大平面图 若G是简单平面图, 且在任意两个不相邻的顶点 之间加一条新边所得图为非平面图, 则称G为极大平面图\n极大平面图是连通的 (若不连通则可以继续加边,矛盾) 设G为n(n3)阶简单图, G为极大平面图的充分必要条件是, G每个面的次数均为3. 欧拉公式 对于连通平面图有欧拉公式：n + r - 2 = m，其中 n 为顶点数，m 为边数，r 为面数（显然边数总是最多的一方）\n记住有个两个数相加减2等于另一个数,然后立方体中边数为12,顶点数为8,面数为6,就能记住欧拉公式 树 定义\n不含回路的连通无向图称为树，平凡图(即一个点)称为平凡树,度数为 1 的顶点称为树叶，度数大于 1 的称为分支点\n注意!!!!!!!! 关于二叉树本教材默认第一层高度为0,第二层高度为1,以此类推!!!!!!! 生成树 生成树\nG 是无向连通图，T 是 G 的生成子图且是树，则 T 为 G 的生成树（即连接了所有节点），G 在 T 中的边称为 T 的树枝，不在 T 中的边称为 T 的弦，T 中所有弦生成的导出子图称为 T 的余树(不一定是树,也未必连通)\n定理:任何无向连通图G都有生成树 证明:若G无回路,则本身就是生成树,若含有回路,则删去回路上任意一条边,直到图中不再有回路,这不影响G的连通性,从而得到生成树\n推论:n阶无向连通图G的边数大于等于n-1 证明:其生成树的边数为n-1\n基本回路和基本割集 基本回路（Fundamental Circuit）\n设 T 是连通图 G 的一棵生成树。\n对任意一条不属于 T 的边 e(弦)，将 e 加入 T 中，\n则在 T ∪ {e} 中产生且唯一的回路，称为关于生成树 T 的一个基本回路。\n基本回路系统\n由生成树 T 中所有弦各自对应的基本回路所组成的集合，\n称为图 G 的一个基本回路系统。\n基本割集（Fundamental Cutset）\n设 T 是连通图 G 的一棵生成树。\n对任意一条属于 T 的边 e，从 T 中删去 e，\n生成树被分成两个连通分量。\n在原图 G 中，所有连接这两个分量的边构成的集合，\n称为关于生成树 T 和边 e 的基本割集。\n基本割集系统\n由生成树 T 中每一条树边所对应的基本割集组成的集合，\n称为图 G 的一个基本割集系统。\n加一条非树边 → 一个基本回路 删一条树边 → 一个基本割集 基本回路数 = 非树边数 (m-n+1) 基本割集数 = 树边数 (n-1) 真整理完了发现确实是文科,基本上所有例题只要懂了概念就能做,唯一难的地方就是证明题,很多证明想破脑袋都想不到,只好继续背书了\n群论 由于学校规定的那本臭不可闻的ts离散教材已经沉浸在自己的优越感里了,找不到一点点系统性,只好我自己从头开始梳理,大多数定义都是直接引用的wiki\n运算律 结合律 a+(b+c)=(a+b)+c 结合律是群论中最为重要的部分,只有满足结合律的代数系统才可以进行两项以上的计算,例如计算a+b+c.如果(a+b)+c!=q+(b+c),那就无法得出唯一的结果了 交换律 a+b=b+a 很多代数系统不满足交换律,例如矩阵乘法 分配律 如果对+满足以下式子,则称对+满足分配律\na*(b+c)=a*b+a*c 幂等律 a+a=a 在幂等律成立的代数系统中，对同一元素重复运算不会产生新的结果，常见于集合的并运算与交运算中。 吸收律 需要同时满足下面两个式子\na+(a*b)=a a*(a+b)=a 消去律 逆元和结合律保证了消去律的存在\n例题 代数系统 概览 半群满足加法,群还满足减法,环还满足乘法,域还满足除法 子代数 子代数与原代数系统含有相同的代数常数,且对于所有运算都是封闭的\n封闭性 封闭性其实是非常重要的性质,保证了集合内的运算结果保留在集合内,而不会出现任何特殊情况,而由于作为整个群论基础的半群具有封闭性,故下文讨论到的所有代数系统都具有封闭性.\n同态和同构 如果同态满足单射,称为单同态,满足满射,称为满同态,满足双射,则称为同构\n例题 通过这个例题可以明确几点关于映射的知识\n没参与映射的元素不会计入原像集合,例如这里的0在原像集合中,故一定有像存在 双射时像和原像一定是一一对应的 半群 满足结合律的封闭代数系统称为半群\n例子: 自然数下的加法\n含有幺元的半群称为独异点(垃圾翻译)\n群 定义 存在一个数e,使得对于集合S内所有的元素a都满足以下公式,则称e为集合S的幺元\ne+a=a,a+e=a 若一个半群S存在幺元e,且对于集合S内所有的元素a都有逆元b满足以下公式,则称S为群\nb+a=a+b=e 由于零元不存在逆元,故群都不含零元 除单位元素e外群不存在等幂元素 群的阶数 群G中元素的个数称为阶数,记作|G|,若个数有限,称为有限群,否则为无限群,若G只有一个单位元素e,则称为平凡群\n交换群(阿贝尔群) 满足交换律的群称为交换群\nKlein四元群 Klein四元群G的运算具有以下特点:\ne为G中的幺元 .是可交换的 任何G中元素与自己运算的结果都为e 除幺元外任意两个元素的运算结果都等于另一个元素 可以看出Klein四元群既是交换群又是有限群\n子群 显然只要是群S的子集,并满足群的性质就可以称为子群,重点在于证明一个子集是一个群的子群 关键在于一点点补全群的定义,首先找到幺元,接着找到逆元存在,由于集合被划分,故原来的封闭性可能被破坏,还需要证明封闭性,而结合律不用证明,因为所有半群的子集都是满足结合律的\n循环群 这里的幂次不一定是乘方,只是表示对g进行多次运算 生成元g与群G的阶数是一样的,无限循环群的生成元是a和a^-1,而n阶循环群的生成元是与a所有与n互质的幂次(互质:最大公约数为1,因此1也包含在互质的幂次里) 从定义可以知道循环群一定是交换群 由于要满足封闭性,故n阶循环群子群的生成元的幂次一定都是n的因数,可以把g的n次方计为幺元e,这个刚看到时很难理解,但如果没有幺元,那么就可以一直运算下去,就不是有限群了,故一定存在一个边界保证阶数不超过n,那么把g的n次方看作e就非常合适了.\n置换群 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 置换通常写作轮换形式。例如，在轮换表示法中，给定集合 M = {1, 2, 3, 4}。 设 M 的一个置换 g 满足： - g(1) = 2 - g(2) = 4 - g(4) = 1 - g(3) = 3 则该置换可以写作： (1, 2, 4)(3) 或者更常见地写作： (1, 2, 4) 因为元素 3 在该置换下保持不变。 当对象用单个字母或数字表示时，逗号通常也可以省略，因此也可记作： (1 2 4) 涉及集合S中m个不同数的置换称为m阶轮换 环和域 显然wiki上定义的环是真环,乘法有幺元;教材里用的定义是伪环,乘法不含幺元\n1 2 3 4 5 6 一个环是一个集合 R，具有两个二元运算（+ 和 ·），分别称为“加法”和“乘法”。 其中，乘法对加法满足分配律,并且满足以下代数结构条件： - (R, +) 是一个阿贝尔群 - (R, ·) 是一个半群 零因子:\n1 2 对所有 (a, b) ∈ R × R， 若当a,b都不为0时存在a × b = 0 则a称为左零因子,b称为右零因子 证明零因子可以用消去律代替\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 假设 R 中没有零因子。 已知： a ≠ 0 a x b = a x c 两边相减，得到： a x (b − c) = 0 由于 a ≠ 0， 又因为没有零因子， 只能推出： b − c = 0 即： b = c 只有所有非零数相乘都不为0才可以说环不存在零因子,称为无零因子环 若环R中乘法满足交换律,含有幺元,无零因子,则R称为整环 若环R至少含有两个元素且含有幺元,无零因子,所有非零元素都存在乘法逆元,则称R为除环 简短说来,环中多了一个对有无零因子的考察,因为如果不满足消去律的话研究这个代数系统就很困难了,而整环中乘法是一个可交换独异点,除环除开零以外是一个群 若环R既是整环又是除环,则称R是域 格与布尔代数 1 2 3 4 5 6 7 8 考虑一个偏序集合 (L, ≤)。 如果对集合 L 中的任意两个元素 a、b， 它们在 L 中都存在最大下界和最小上界， 则称 (L, ≤) 是一个格。 (由于偏序符号不好打,这里的小于等于都是偏序符号,表示可比,可比要求两个元素在哈斯图里处于同一分支上) 从这个定义可以看出： 格并不要求像全序集合那样，任意两个元素都必须可比； 但仍要求任意两个元素都具有最大下界和最小上界。 1 2 3 4 5 在这里，取 a、b 的最大下界的运算记作 a ∧ b； 取 a、b 的最小上界的运算记作 a ∨ b。 格和全序集的区别: 简单画哈斯图便可以知道,全序集是一条线,而格可以有多个分支,只要分支最后能汇合到一点就可以 格满足交换律,结合律,幂等律和吸收律,这些从定义来看就很好理解,故无需证明 补充概念 最大元和最小元\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 设 (P, ≤) 为一个偏序集 ，S 为其子集。 若 S 中的元素 g 满足： 对 S 的任意元素 s，都有 s ≤ g， 则称 g 为 S 的最大元（greatest element）。 对偶地，若 S 中的元素 l 满足： 对 S 的任意元素 s，都有 l ≤ s， 则称 l 为 S 的最小元（least element）。 由定义可知，S 的最大元（最小元）必定是 S 的上界（下界）。 并且集合 S 至多只能有一个最大元： 若 g₁ 和 g₂ 都是 S 的最大元，则由定义有 g₁ ≤ g₂，且 g₂ ≤ g₁， 由反对称性可得 g₁ = g₂。 因此，若 S 存在最大元,则该最大元必定是唯一的,最小元同理 最小上界和最大上界\n1 2 3 4 5 6 7 8 9 10 11 设 (A, ≤) 为一个偏序集，B ⊆ A。 若存在 y ∈ A，使得对任意 x ∈ B 都有 x ≤ y， 则称 y 为集合 B 的上界。 对称地，若存在 z ∈ A，使得对任意 x ∈ B 都有 z ≤ x， 则称 z 为集合 B 的下界。 偏序集A的子集B在A中的上界集合的最小元称为最小上界,对偶概念即为最大下界 特殊的格 分配格\n1 2 3 4 5 6 7 设 (L, ∨, ∧) 是一个格。 若对任意 a、b、c ∈ L，都有： a ∧ (b ∨ c) = (a ∧ b) ∨ (a ∧ c) a ∨ (b ∧ c) = (a ∨ b) ∧ (a ∨ c) 则称 L 为一个分配格。 有界格\n1 2 3 4 设 (L, ∨, ∧) 是一个格。 若 L 有最小元0和最大元1,即对任意 a ∈ L 都满足 a ∨ 0 = a 和 a ∧ 1 = a， 则称 L 为一个有界格,这里的最小元称为全下界,最大元称为全上界 可以认为所有元素有限的格都是有界格,因为格的定义就要求了哈斯图的两端是封闭的,故一定有上下界 有补格\n1 2 3 4 5 设 (L, ∨, ∧) 是一个有界格 并且 L 中的每个元素 a 都存在一个b ∈ L 使得 a ∨ b = 1 且 a ∧ b = 0。 这样的 b 称为 a 的补元。 L则称为有补格 证明分配格中任意元素的补元唯一\n设 L 是一个分配格，有界格，且 a ∈ L 有两个补元 b 和 c。 根据补元的定义：\na ∨ b = 1 且 a ∧ b = 0 a ∨ c = 1 且 a ∧ c = 0 要证明 b = c。\n步骤 1：利用分配律\n考虑 b ∧ (a ∨ c)：\nb ∧ (a ∨ c) = (b ∧ a) ∨ (b ∧ c) （分配律）\n代入已知 a ∧ b = 0：\n0 ∨ (b ∧ c) = b ∧ c\n而 a ∨ c = 1，所以：\nb ∧ 1 = b\n所以 b = b ∧ c。\n步骤 2：对称地\n同理，c = a ∨ b ∧ c，经过类似推导可得：\nc = b ∧ c\n步骤 3：结合得到\nb = b ∧ c = c\n因此，补元是唯一的。\n布尔格(布尔代数)\n1 2 布尔格是一个有补分配格,换句话说，它是一个有最小元 0 和最大元 1 的格, 满足分配律，并且每个元素都有补元 由上述证明可以明确布尔代数每个元素的补元唯一,求补元可以看成是一元运算 仔细一看就算都理解透了,还是有一堆要背的,因为不可能到考试的时候现推啊,能把数学课教成文科也是挺nb的.\n","date":"2025-12-25T08:00:00Z","image":"/p/%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6%E7%AC%94%E8%AE%B0/60223956_p0-%E5%90%9B%E3%82%92%E5%BE%85%E3%81%A3%E3%81%A6%E3%82%8B.webp","permalink":"/p/%E7%A6%BB%E6%95%A3%E6%95%B0%E5%AD%A6%E7%AC%94%E8%AE%B0/","title":"离散数学笔记"},{"content":"下了一个破解版游戏,结果火绒直接弹出窗口提示我有三百多个exe感染了jadtre ax 病毒 到这个时候我才发现我对网络安全实际上是一窍不通, 只好先用火绒把所有感染的exe放到隔离区,然后来了一个全盘扫描,我以后一定要好好研究一下计算机的底层知识和病毒攻击原理.\n首先我找到了计算机病毒百科里关于这个病毒的详细信息\n它会修改系统文件，包括操作系统的关键文件，以隐藏自身并且避免被杀软检测到。 该病毒会通过劫持用户的浏览器、修改主页和搜索引擎设置等方式，进行钓鱼攻击，诱导用户点击恶意链接，从而进一步传播病毒。 它会窃取用户的个人敏感信息，如登录密码、银行账号等，并将这些信息发送到黑客的服务器上。 病毒会利用系统的漏洞，进行远程控制，从而盗取用户的隐私信息或者操控计算机进行其他恶意行为。 该病毒还会利用系统资源进行挖矿或者进行分布式拒绝服务（DDoS）攻击。 它会禁用或损坏杀软程序的功能，以保证自身不被杀软识别和清除。\n发现它主要是诱导用户点击恶意链接,还好火绒直接帮我拦截了网页跳转.\n顺藤摸瓜找到了这个网站,我才发现网络安全确实是一个壁垒高却极为重要的领域, 想着在网上找一点入门教程,结果用中文搜全是AI文章或者垃圾文字,再一次体会到中文互联网的封闭性,只好问gpt,找到了以下网站:\nBy Sunday evening, you could have taken your first real step into cybersecurity. Here are 15 websites to kick-start your cybersecurity journey (all beginner-friendly): 1.\tTryHackMe – Hands-on labs for all skill levels https://tryhackme.com 2.\tHack The Box – Gamified hacking challenges https://www.hackthebox.com 3.\tBlue Team Labs Online – Defensive security scenarios https://lnkd.in/dckAehnQ 4.\tOverTheWire – Fun war games to learn Linux \u0026amp; networking https://lnkd.in/d66wAqDb 5.\tPortSwigger Web Security Academy – Web app hacking from the pros https://lnkd.in/dyrBGjRh 6.\tVulnHub – Download vulnerable machines and hack away https://www.vulnhub.com 7.\tLetsDefend – SOC analyst simulation https://letsdefend.io 8.\tCyberDefenders – Threat hunting and forensics labs https://cyberdefenders.org 9.\tCTFtime – Find Capture the Flag competitions https://ctftime.org 10.\tSecurity Blue Team – Blue teaming certifications and practice https://lnkd.in/dmuUFfQX 11.\tPentesterLab – Web security training https://pentesterlab.com 12.\tMITRE ATT\u0026amp;CK – Learn real adversary tactics and techniques https://attack.mitre.org 13.\tOWASP – Secure coding \u0026amp; app security resources https://owasp.org 14.\tDFIR Diva – Free digital forensics resources https://dfirdiva.com 15.\tFlare-On Challenges – Reverse engineering practice https://flare-on.com\n找到了一个汇总链接 不多说了,尽管期末周快来了,兴趣学习的时间还是有的,开练.\n25号更新 一开始我觉得这个病毒没什么危害性,就又把火绒隔离区里的exe给恢复了,现在发现我确实是智障,今晚扫描全盘发现基本所有的exe文件都被感染了, 里面有几百款游戏exe和工具exe,受不了了,太蠢了这也.我决定不再点击恢复文件,只能把这些文件全部删掉了. 好的,这下真的激发我对网络安全的兴趣了,做病毒真有这么好玩吗(😡)\n这件事还让我看到火绒并不好用,虽然平时没什么弹窗打扰我,但是一但出事一点都救不了我. 病急乱投医,我甚至下载了360来试试,果不其然,连安装页面都没弹出来,就已经在修改我的注册表了,去你的吧!\n然后看了看别人的推荐,又综合了各种考虑,先拿卡巴斯基来试试全盘扫描,竟然要花9个小时多.\n26号更新 今天早上来看,发现问题并没有那么严重,大多数都是火绒的误报,我把那些隔离区里的exe上传到文件病毒检测网站里发现根本没有病毒,受不了了.而卡巴斯基也成功地把那些真正的病毒删除干净了,滚吧您呐,让我投入卡巴斯基的怀抱吧!\n参考链接 1 2\n","date":"2025-12-24T08:00:00Z","image":"/p/2025-12-24-%E7%97%85%E6%AF%92%E6%94%BB%E5%87%BB/49831005_p0-%E9%9B%AA.webp","permalink":"/p/2025-12-24-%E7%97%85%E6%AF%92%E6%94%BB%E5%87%BB/","title":"2025-12-24 病毒攻击"},{"content":"技术文章 cpp构造与析构 cpp书单 fastapi+react前后端基础教学 zip算法 react框架比较 职场文章-秋招与实习 校招流程 还是校招 微软实习 阅历文章 科大非全日制 研究生复盘 在职老程序员考非全211硕士 十年前的实习面试 读这篇文章会让人不由不想去奋发图强,开始学习\n疫情求职 已被删除,可惜的是我不记得大致内容了\n保研与工作 github-stars-wont-pay-your-rent git考古 速通日语 加拿大体验 日本游记 python的问题 My solopreneur story: zero to $45K/mo in 2 years 肮脏的交易 十年学会编程 契诃夫分析 软件开发经验 AI编程 有趣文章和网站 十年之约 gpt成长记录 谷歌街景随机图 ISBN全球书籍可视化 迈登斯的中国摄影集 スタジオジブリ作品一覧 各报纸(含南方周末,人民日报)新年献词 Astronomy Picture of the Day Archive games and stuff by Neal The San Francisco FogCam! 手绘极光 催眠曲 编程音乐网站 Funny scientific papers Review the contributions you have made on GitHub over the years Starlink Overview 朝天鸣枪的危险性 东京地铁3D 马屁股和航天飞机的关系 马屁股和航天飞机的关系\n美国铁路两条铁轨之间的标准距离是四点八五英尺。这是一个很奇怪的标准，究竟从何而来的？\n原来这是英国的铁路标准，因为美国的铁路最早是由英国人设计建造的。\n那么，为什么英国人用这个标准呢？\n原来英国的铁路是由建电车轨道的人设计的，而这个四点八五英尺正是电车所用的标准。电车轨标准又是从哪里来的呢？\n原来最先造电车的人以前是造马车的。而他们是用马车的轮宽做标准。好了，那么，马车为什么要用这个一定的轮距离标准呢？\n因为如果那时候的马车用任何其它轮距的话，马车的轮子很快会在英国的老路上撞坏的。为什么？\n因为这些路上的辙迹的宽度为四点八五英尺。这些辙迹又是从何而来呢？\n答案是古罗马人定的，四点八五英尺正是罗马战车的宽度。如果任何人用不同的轮宽在这些路上行车的话，他的轮子的寿命都不会长。我们再问：罗马人为什么用四点八五英尺为战车的轮距宽度呢？\n原因很简单，这是两匹拉战车的马的屁股的宽度。故事到此应该完结了，但事实上还没有完。\n下次你在电视上看到美国航天飞机立在发射台上的雄姿时，你留意看，在它的燃料箱的两旁有两个火箭推进器，这些推进器是由设在犹他州的工厂所提供的。如果可能的话，这家工厂的工程师希望把这些推进器造得再胖一些，这样容量就会大一些，但是他们不可以，为什么？\n因为这些推进器造好后要用火车从工厂运到发射点，路上要通过一些隧道，而这些隧道的宽度只比火车轨道的宽度宽了一点点\n故事是颇有趣的。从一定意义上说，今天世界上最先进的运输系统的设计，或许是由两千年前两匹战马的屁股宽度来决定的。历史惯性的力量是多么的强大，要冲破由惯性形成的规则又是多么的艰难。\n好习惯将不只影响您的一生，您的后代子子孙孙，皆会因而受益。\nRPGMaker的历史 ","date":"2025-12-20T10:58:05Z","image":"/p/%E9%87%8D%E8%A6%81%E6%96%87%E7%AB%A0%E5%AD%98%E6%A1%A3/36042993_p0-%E5%8D%83%E6%9C%AC%E6%A1%9C%20%E5%A4%9C%E3%83%8B%E7%B4%9B%E3%83%AC.webp","permalink":"/p/%E9%87%8D%E8%A6%81%E6%96%87%E7%AB%A0%E5%AD%98%E6%A1%A3/","title":"重要文章存档"},{"content":"折腾博客 hexo管理 hexo图片管理困难,帖子多了很难阅读 解决方法: config_yml里修改 new_post_name: :year-:month-:day-:title.md 同时,听从大佬建议,将所有图片改成webp格式,效果立竿见影 还是图片问题,每次复制images子文件夹路径太麻烦 根据教程在post文件夹里设置同名文件夹没解决\n1 2 3 4 5 post_asset_folder: true marked: prependRoot: true postAsset: true relative_link: false 最后写了一个脚本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 const fs = require(\u0026#39;fs\u0026#39;); const path = require(\u0026#39;path\u0026#39;); hexo.on(\u0026#39;new\u0026#39;, function(data) { // 用 data.path 生成文件夹名 // data.path 是相对于 _posts 的路径，带后缀 const filename = path.basename(data.path, path.extname(data.path)); // 去掉扩展名 const imagesDir = path.join(hexo.base_dir, \u0026#39;images\u0026#39;, filename); if (!fs.existsSync(imagesDir)) { fs.mkdirSync(imagesDir, { recursive: true }); console.log(`Created image folder: ${imagesDir}`); } }); 在同级目录下生成图像文件夹来管理,麻烦的是每次都要删去相对路径里的''\n改用butterfly主题 这个主题确实好用了不少\n增加评论系统 参考文章\n尝试新部署方式 增加了category条目,tag条目和背景图片 原来要用hexo new page tag才能在hexo里显示tag页和category页\n发现md只要在posts下都能被直接解析(25/12/21) 于是我将不会再修改的文章都移动到了archives文件夹,图片路径也做了对应的修改, 之前我还想文章多了要怎么处理呢.\n还是图片问题,hexo本地无法正确解析相对路径 例如images/archives/2025/2025-11-26/image.png 需要改为\u0026rsquo;images/archives/2025/2025-11-26/image.png\u0026rsquo;,每次改就很麻烦了 于是找ai弄了脚本\n1 2 3 4 5 6 7 8 9 10 11 hexo.extend.filter.register(\u0026#39;before_post_render\u0026#39;, function (data) { if (typeof data.cover === \u0026#39;string\u0026#39;) { data.cover = data.cover.replace( /^\\/?source\\//, \u0026#39;/\u0026#39; ) } return data }) 完美解决,以后只要复制相对路径就可以了,不用再删删减减了.\n增加rss订阅图标 参考文章 发现了RSS订阅方式,于是增加了这一功能\n更改了图像文件夹创建方式(25/12/26) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const fs = require(\u0026#39;fs\u0026#39;); const path = require(\u0026#39;path\u0026#39;); hexo.on(\u0026#39;new\u0026#39;, function (data) { // data.path: 2025-12-26-xxx.md const basename = path.basename(data.path, path.extname(data.path)); // 取前 10 个字符作为日期 const date = basename.substring(0, 10); // 图片目录：images/2025-12-26 const imagesDir = path.join( hexo.base_dir, \u0026#39;images\u0026#39;, date ); if (!fs.existsSync(imagesDir)) { fs.mkdirSync(imagesDir, { recursive: true }); console.log(`Created image folder: ${imagesDir}`); } }); 由于我的post创建格式在config里面改成了这样子 new_post_name: :year-:month-:day-:title.md 故可以根据日期直接找到对应的图片,这样图片管理起来更加方便了,但原来的几十个文件夹我是真不想再改了.\n突然发现没必要用file utils粘贴相对路径,直接复制图片就好了(12/27) 在settings.json里加上\n1 2 3 \u0026#34;markdown.copyFiles.destination\u0026#34;: { \u0026#34;**/*.md\u0026#34;: \u0026#34;images/${documentBaseName}/\u0026#34; }, 就可以在md里直接复制图片而不是自己写了这些东西了, 甚至会根据图片复制的位置智能选择是插入相对路径还是插入整个图片链接格式,又能偷一点懒了.\n每次hexo d的时候都要报warning(2026/1/1) 1 warning: in the working copy of \u0026#39;tags/离散数学/index.html\u0026#39;, LF will be replaced by CRLF the next time Git touches it 详细解释CR和CRLF LF和CRLF区别 LF: Line Feed换行 feed v.喂养,供给;将(信息)输入 line feed直译是”将行输入”,再意译”换行” CRLF: Carriage Return Line Feed 回车换行 Carriage n.马车,火车车厢;运输费用 在carriage return中,carriage译为“车”,return译为“回” 在过去的机械打字机上有个部件叫「字车」（Typewriter carriage），每打一个字符，字车前进一格，打完一行后，我们需要让字车回到起始位置，而“Carriage Return”键最早就是这个作用，因此被直接翻译为「回车」。尽管后来回车键的作用已经不止” 倒回字车”那么简单，但这个译名一直被保留下来。\n解决方法 这个警告的意思很直接，就是Git会把LF替换为CRLF，不过这是无关紧要的，完全可以禁用此功能，这样还可以避免这个警告信息刷屏。设置方法也很简单，在MinGW窗口中输入以下命令即可： git config --global core.autocrlf false\n很奇怪的是很少有人去想为什么回车叫做回车,这种粗暴的翻译在用电脑打字的时代显得非常奇怪,习惯的力量真有点可怕. 扔掉CRLF 嵌入数学公式(unsolved) 按照官方文档的步骤进行操作并没有解决,尝试了这个教程也没有解决\n图片路径又出问题了 待我期末周回来再搞,毕竟本地看图片还是正常的 (2026/1/18) 修好了,只要保证都是相对路径的格式就能正常渲染,尽管我也不知道为什么 ![alt text](../images/archives/2026/2026-01-18/PixPin_2026-01-18_14-18-42.webp)\n这大概是图片路径的最终解决方案了\n觉得\u0026rsquo;hexo g -d\u0026rsquo;还是太长了 写一个bat脚本,命名为d.bat,每次只要输一个d就可以了,完美解决懒癌,自然我还写了一个s脚本,作用是什么不言而喻\n将twikoo改为giscus(2026/1/24) 发现twikoo还是太麻烦了,于是改成不要动脑子操作的giscus\n将busuanzi换成Vercount(1/30) busuanzi现在天天转圈,于是换成别人推荐的Vercount,由于busuanzi写在了源代码pug里,因此不太好直接改,问了问ai,提前存档,改了之后发现立刻就不转圈了.\n只要在以下两个地方中改一下就行了\n这里插入脚本\u0026lt;script defer src=\u0026quot;https://events.vercount.one/js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n这里可以直接换成下面代码,因为busuanzi真没必要再放上去了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 if theme.aside.card_webinfo.enable .card-widget.card-webinfo .item-headline i.fas.fa-chart-line span= _p(\u0026#39;aside.card_webinfo.headline\u0026#39;) .webinfo if theme.aside.card_webinfo.post_count .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.article_name\u0026#39;)} :` .item-count= site.posts.length if theme.aside.card_webinfo.runtime_date .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.runtime.name\u0026#39;)} :` .item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date)) i.fa-solid.fa-spinner.fa-spin if theme.wordcount.enable \u0026amp;\u0026amp; theme.wordcount.total_wordcount .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.site_wordcount\u0026#39;)} :` .item-count= totalcount(site) if theme.umami_analytics.enable \u0026amp;\u0026amp; theme.umami_analytics.UV_PV.site_uv .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.site_uv_name\u0026#39;)} :` .item-count#umami-site-uv i.fa-solid.fa-spinner.fa-spin else .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.site_uv_name\u0026#39;)} :` .item-count#vercount_value_site_uv Loading if theme.umami_analytics.enable \u0026amp;\u0026amp; theme.umami_analytics.UV_PV.site_pv .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.site_pv_name\u0026#39;)} :` .item-count#umami-site-pv i.fa-solid.fa-spinner.fa-spin else .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.site_pv_name\u0026#39;)} :` .item-count#vercount_value_site_pv Loading if theme.aside.card_webinfo.last_push_date .webinfo-item .item-name= `${_p(\u0026#39;aside.card_webinfo.last_push_date.name\u0026#39;)} :` .item-count#last-push-date(data-lastPushDate=date_xml(Date.now())) i.fa-solid.fa-spinner.fa-spin 增加google-analytics 参考教程 vercount也出问题了,显示一堆乱码,这次换个大杀器. google_analytics 是免费的,给自己网站注册一下获得一串以G打头的神秘数字-tag,再填入butterfly的config.yml就可以了,当然网站上是看不到访客数据的,只能去后台看. (2/13)发现之前的vercount乱码只是因为我把同名压缩包放到脚本里面所以出错了\n加入live2d(2/22) 看大家都有那么帅的看板娘,我也试试整一个,在butterfly的_config.yml里对应的bottom注入js就可以了\n1 2 3 4 5 6 7 8 inject: head: - \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;/css/custom.css\u0026#34;\u0026gt; # - \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;/xxx.css\u0026#34;\u0026gt; bottom: #live2d看板娘 - \u0026lt;script src=\u0026#34;https://fastly.jsdelivr.net/npm/live2d-widgets@1.0.0/dist/autoload.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; # - \u0026lt;script src=\u0026#34;xxxx\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 确实可爱😄\n尝试加密文章(4/7) 参考文章 1 2 3 4 5 6 7 8 9 10 11 12 --- title: 这是一个私密文章 date: 2026-04-07 20:00:00 tags: [秘密] # 以下是加密核心配置 password: your_password_here abstract: 输入密码前，读者能看到的预览文字（可选） message: 这里是密码提示语（可选） --- 这里是加密后的正文内容，只有输入正确密码才能看到。 将博客从hexo迁移到hugo-stack(4/24) 由于hexo有很多地方是我不满意的,而且当时创建博客的时候还不太懂编程,用AI魔改了很多地方,修复起来很难. 所以就想着换一下博客框架,顺便整理以往的所有文章,把魔改的地方全部清空.\n整理原仓库 正常人的博客不会像我一样混乱的 因为我的图片路径弄得特别混乱,而hugo默认只支持文章md与图片在同一文件夹下时,才能识别图片,所以还得去整一些脚本来帮助我快速转换:\n将魔改的路径还原\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $contentDir = \u0026#34;content/post\u0026#34; $imagesSourceDir = \u0026#34;images\u0026#34; $utf8NoBom = New-Object System.Text.UTF8Encoding $false # 1. 预构建图片全局索引（解决路径偏移问题） Write-Host \u0026#34;Building image library index...\u0026#34; -ForegroundColor Cyan if (-not (Test-Path $imagesSourceDir)) { Write-Error \u0026#34;Source images directory not found!\u0026#34;; exit } $imageLibrary = Get-ChildItem -Path $imagesSourceDir -File -Recurse # 2. 获取所有待处理的 .md 文件 $mdFiles = Get-ChildItem -Path $contentDir -Filter \u0026#34;*.md\u0026#34; -Recurse | Where-Object { $_.Name -ne \u0026#34;index.md\u0026#34; -and $_.Name -ne \u0026#34;_index.md\u0026#34; } foreach ($file in $mdFiles) { Write-Host \u0026#34;`nProcessing: $($file.Name)\u0026#34; -ForegroundColor Cyan # 3. 创建 Page Bundle 文件夹 $folderName = $file.BaseName $targetDir = Join-Path $file.DirectoryName $folderName if (-not (Test-Path $targetDir)) { New-Item -ItemType Directory -Path $targetDir | Out-Null } # 4. 预读取内容并重命名/移动 MD 文件 $content = [System.IO.File]::ReadAllText($file.FullName, [System.Text.Encoding]::UTF8) $newMdPath = Join-Path $targetDir \u0026#34;index.md\u0026#34; # 5. 图片处理逻辑 $hasChanged = $false # 匹配各类 images/ 路径变体 $pattern = \u0026#39;(?:\u0026#34;|(?\u0026lt;=\\())(?:(?:\\.\\./)+|\\./)?images/([^\u0026#34;)\\r\\n\\s]+)(?:\u0026#34;|\\))\u0026#39; $matches = [regex]::Matches($content, $pattern) foreach ($match in $matches) { $extractedPath = $match.Groups[1].Value.Trim() $fileName = Split-Path $extractedPath -Leaf # 忽略变量占位符 if ($fileName -like \u0026#34;*$*\u0026#34;) { continue } # 搜索图片：先查直接路径，找不到再查全局索引 $sourceFile = Join-Path (Get-Location) (Join-Path $imagesSourceDir $extractedPath) if (-not (Test-Path -LiteralPath $sourceFile)) { $foundFile = $imageLibrary | Where-Object { $_.Name -eq $fileName } | Select-Object -First 1 if ($foundFile) { $sourceFile = $foundFile.FullName } } if (Test-Path -LiteralPath $sourceFile) { $destFile = Join-Path $targetDir $fileName # 移动图片到新文件夹 if (Test-Path -LiteralPath $sourceFile) { Move-Item -LiteralPath $sourceFile -Destination $destFile -Force } # 修正 Markdown 引用路径为纯文件名 $oldFullString = $match.Value $newFullString = $oldFullString.Substring(0,1) + $fileName + $oldFullString.Substring($oldFullString.Length - 1) $content = $content.Replace($oldFullString, $newFullString) $hasChanged = $true Write-Host \u0026#34; [Image] Moved \u0026amp; Relinked: $fileName\u0026#34; -ForegroundColor Green } else { Write-Warning \u0026#34; [Image] Missing: $fileName\u0026#34; } } # 6. 写入新 index.md 并物理删除旧 .md [System.IO.File]::WriteAllText($newMdPath, $content, $utf8NoBom) Remove-Item -Path $file.FullName -Force Write-Host \u0026#34; [Bundle] Created folder and index.md\u0026#34; -ForegroundColor Gray } Write-Host \u0026#34;`nAll operations completed.\u0026#34; -ForegroundColor Yellow 注册环境变量将hugo的复杂命令简化为hn \u0026lt;文章名\u0026gt;并给目录加上日期前缀\n在 PowerShell 中输入以下命令，如果文件不存在会自动创建： 1 2 if (!(Test-Path $PROFILE)) { New-Item -Type File -Path $PROFILE -Force } notepad $PROFILE 将以下代码粘贴进记事本并保存： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function hn { param($name) hugo new \u0026#34;post/drafts/$name/index.md\u0026#34; } function hugo { if ($args[0] -eq \u0026#34;new\u0026#34; -and $args.Count -ge 2) { $datePrefix = Get-Date -Format \u0026#34;yyyy-MM-dd-\u0026#34; $originalPath = $args[1] if ($originalPath -match \u0026#39;([^/]+)/index\\.md$\u0026#39;) { $pureName = $matches[1] } else { $pureName = Split-Path $originalPath -LeafBase } if ($originalPath -match \u0026#39;(.*/)([^/]+/[^/]+)$\u0026#39;) { $newPath = $matches[1] + $datePrefix + $matches[2] } else { $newPath = $datePrefix + $originalPath } $newArgs = @($args[0], $newPath) + $args[2..($args.Count-1)] \u0026amp; (Get-Command hugo.exe -CommandType Application) $newArgs $fullPath = Join-Path (Get-Location) \u0026#34;content/$newPath\u0026#34; if (Test-Path $fullPath) { $content = Get-Content $fullPath -Raw -Encoding UTF8 $content = $content -replace \u0026#39;(?m)^title\\s*:\\s*.*$\u0026#39;, \u0026#34;title: `\u0026#34;$pureName`\u0026#34;\u0026#34; [System.IO.File]::WriteAllText($fullPath, $content, (New-Object System.Text.UTF8Encoding($false))) } } else { \u0026amp; (Get-Command hugo.exe -CommandType Application) $args } } 回到 PowerShell，执行： 1 . $PROFILE 光是处理这些前前后后也花了差不多两个小时,可知处理糟糕的架构设计确实很累人.\n初始化hugo仓库 将原博客仓库名字改成随便的其他名字,预留username.github.io仓库名字 先forkstack仓库命名为username.github.io后,git clone到本地. 进入仓库的settings\u0026gt;Pages页面,给该仓库配置以下两个GitHub Actions:\nbuild and deploy hugo模板 之后在根目录的.github\\workflows文件夹下会出现两个新文件分别对应两个actions 然后将原仓库整理好的文档一键复制到hugo仓库的content/post文件夹中,除了cover字段需要改成image字段外,其他的frontmatter都是兼容的.\n至于其他的初始化操作由于官方文档都写的很明白我就不加上了 加入vercount访客统计 参考链接 加入gisgus评论系统 参考链接 配置tags界面 在pages目录里新增该tags文件夹即可,可以这么写从而启用内置的tag-cloud样式:\n1 2 3 4 5 6 7 8 9 10 11 --- title: \u0026#34;Tags\u0026#34; slug: \u0026#34;tags\u0026#34; layout: \u0026#34;tag-cloud\u0026#34; menu: main: weight: 4 params: icon: tag --- 但实际效果不是很好,能用就行 修改目录属性保证一级标题和五级标题可以被识别 在markup.toml里修改下列字段即可:\n1 2 3 4 [tableOfContents] endLevel = 5 ordered = true startLevel = 1 修改刺眼的白色背景 在项目根目录下编辑或创建 assets/scss/custom.scss,添加以下代码:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 :root { /* 1. 背景色：由纯白改为柔和的浅米色或灰白色 */ --body-background: #f5f5f3; /* 2. 卡片背景：略亮于底色，增加层次感 */ --card-background: #fafafa; /* 3. 文字颜色：避免纯黑，使用深灰色减轻对比压力 */ --body-text-color: #333333; /* 4. 标题颜色：稍深一点以保持清晰度 */ --heading-text-color: #222222; /* 5. 链接与强调色：如果觉得原生的蓝色太刺眼，可以调暗 */ --accent-color: #536471; /* 6. 代码块背景：调为更稳重的颜色 */ --code-background-color: #f0f0f0; } /* 针对侧边栏背景的微调（如果是 Stack 主题） */ .sidebar { background-color: var(--body-background); } /* 降低图片亮度（可选：防止亮色模式下图片太晃眼） */ .article-content img { filter: brightness(0.95); transition: filter 0.3s ease; } .article-content img:hover { filter: brightness(1); } 比默认配置柔和了不少 总结 不得不说,hugo的部署比起hexo快了好几倍,而且界面更加现代流畅.\nhugo增加专栏 例如,我想要将python相关的笔记放在一起: 可以将笔记放在python文件夹里,并设置_index.md和封面图片:\n1 2 3 4 5 6 7 8 9 10 11 --- title: \u0026#34;Python\u0026#34; description: \u0026#34;python相关的笔记\u0026#34; image: 58231596_p0-暑中見舞い.webp menu: main: name: \u0026#34;Python\u0026#34; weight: 100 # 数字越小，在侧边栏位置越靠上 --- 这就是效果了: 配置Google Analytics 1. 获取 Measurement ID 登录 Google Analytics 控制台。 进入 管理 (Admin) -\u0026gt; 数据流 (Data Streams)。 选择你的网站数据流，找到右侧以 G- 开头的 衡量 ID (Measurement ID)。 2. 修改配置文件 在 Hugo Stack 项目根目录下，根据你的配置文件格式进行修改：\n若使用 hugo.yaml 或 config.yaml： 1 2 3 services: googleAnalytics: ID: G-XXXXXXXXXX # 替换为你的 G- 开头的 ID 若使用 hugo.toml 或 config.toml： 1 2 3 [services] [services.googleAnalytics] ID = \u0026#34;G-XXXXXXXXXX\u0026#34; 验证生效 运行 hugo 命令生成静态文件。 检查生成的 index.html 的 \u0026lt;head\u0026gt; 部分，确认包含如下特征的代码： https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX 访问你的线上网站，在 Google Analytics 的 实时 (Real-time) 报告中查看是否有活跃用户。 折腾环境问题 vscode powershell终端打字缺字漏字 解决方法:\n搜索设置 Terminal \u0026gt; Integrated \u0026gt; Default Profile: Windows 换为cmd\n左斜线和右斜线问题 windows路径都是,转义符是,而网页链接,linux都是/ 解决方法:\n在搜索框输入：\u0026ldquo;Explorer: Copy Relative Path Separator\u0026rdquo; 将\\改为正斜杠/ 参考链接\ngpt废话太多 解决方法\n在设置里可以加入自定义提示词,这样就不用每次都提示让他精简输出了\ncmd输入python弹出微软应用商店 解决方法\n进入设置里的应用执行别名,去掉跟python有关的别名(很好奇为什么要把这玩意加进来)\npath环境变量变成一行 解决方法\n由于Windows的无敌bug,第一个如果是带有%开头的变量会把path变成一行,把带有盘符的变量放到第一行即可\n右键菜单中用vscode打开消失 参考链接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\\Directory\\Background\\shell\\VSCode] @=\u0026#34;使用 VSCode 打开\u0026#34; \u0026#34;Icon\u0026#34;=\u0026#34;your vscode path\u0026#34; [HKEY_CLASSES_ROOT\\Directory\\Background\\shell\\VSCode\\\\command] @=\u0026#34;\\\u0026#34;your vscode path\u0026#34; .\u0026#34; \\\u0026#34;%V\\\u0026#34;\u0026#34; [HKEY_CLASSES_ROOT\\Directory\\shell\\VSCode] \u0026#34;Icon\u0026#34;=\u0026#34;your vscode path\u0026#34; [HKEY_CLASSES_ROOT\\Directory\\shell\\VSCode\\\\command] @=\u0026#34;\\\u0026#34;your vscode path\u0026#34; .\u0026#34; \\\u0026#34;%V\\\u0026#34;\u0026#34; 这样加入了两个注册表项,分别是右键目录和目录背景,注意路径要用双斜线,然后将文件名字改为1.reg打开就行\n更改注册表后不必重启电脑,重启文件浏览器就能生效\n很久以后我发现其实重新安装一次vscode就行了,不用先卸载😅\n右键菜单去除系统自带压缩选项 参考链接 1 2 3 4 5 Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Blocked] \u0026#34;{b8cdcb65-b1bf-4b42-9428-1dfdb7ee92af}\u0026#34;=\u0026#34;Compressed (zipped) Folder Menu\u0026#34; \u0026#34;{EE07CEF5-3441-4CFB-870A-4002C724783A}\u0026#34;=\u0026#34;Compressed Archive Folder Context Menu\u0026#34; VMware装载32位win10虚拟机 参考链接 It turns out that VMWare Workstation Player 17 defaults to creating an NVMe disk and that is not compatible with the Windows 10 x86 installer. Changing the disk type to SATA or SCSI allows the build to run successfully.\nHere are the steps to build Windows 10 x86 on VMWare Workstation Player 17:\nCreate a New Virtual Machine Select your Windows 10 x86 ISO, Next Edit the name as desired, Next Accept default disk options, Next Uncheck \u0026ldquo;Power on this virtual machine after creation\u0026rdquo;, Finish Select Windows 10 x86 VM, Edit virtual machine settings Hard Disk (NVMe), Remove Add, Hard Disk, Next, SATA, Next Use an existing virtual disk, Next Select the existing VMDK, Finish, OK Play virtual machine and continue your build 设置vscode对所有语言自动格式化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 格式化部分 \u0026#34;editor.formatOnSave\u0026#34;: true, \u0026#34;editor.formatOnPaste\u0026#34;: true, \u0026#34;editor.formatOnType\u0026#34;: true, // 设置默认格式化器为 Prettier: 仅支持常用前端语言 \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;esbenp.prettier-vscode\u0026#34;, // clangd: cpp \u0026#34;[cpp]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;llvm-vs-code-extensions.vscode-clangd\u0026#34;, }, // black: python \u0026#34;[python]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;ms-python.black-formatter\u0026#34;, }, // rust-analyzer: rust \u0026#34;[rust]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;rust-lang.rust-analyzer\u0026#34;, \u0026#34;editor.formatOnSave\u0026#34;: true, }, 尽管全局设定了自动格式化,但是对于对应的语言,vscode是没办法自动识别的,需要单独安装对应的格式化插件\nWhat is the difference between Windows Terminal, Powershell and Cmd? 来源 CMD是Windows命令处理器——Windows的命令行界面。它存在了几乎永远的时间，并且可以说是DOS的残留物。 Windows PowerShell是一种基于.NET的脚本语言。你可以几乎把它当作CMD的替代品来用，因为许多命令都有别名，可以转换为Windows PowerShell cmdlet（例如，在PowerShell中输入\u0026rsquo;DIR\u0026rsquo;实际上会执行\u0026rsquo;Get-ChildItem\u0026rsquo;，这是PowerShell的语法）。然而，它主要被设计成一种脚本语言，如果你需要自动化一些事情，这就是它的用武之地——它也有很多模块可以连接到其他系统，例如Active Directory、Exchange、365等等，所以它更倾向于管理员而不是家用。 Windows Terminal本身什么也不是——它只是上面两者以及你系统上任何其他CLI或shell的前端应用程序。所以，你可以用一个标签页运行CMD，另一个标签页运行PowerShell。如果你有WSL（Windows Subsystem for Linux），那么你可以在另一个标签页运行BASH。如果你安装了GIT，那么你可以在另一个标签页运行GIT shell。对于可能需要快速切换不同shell的开发者/管理员来说，它非常有用。 ","date":"2025-12-16T08:00:00Z","image":"/p/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/86800864_p0-no%20title.webp","permalink":"/p/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/","title":"折腾记录"},{"content":"适应大学生活后日子过得飞快.\n就我来看,大学的课程是与提升自我无关的,绝大部分课程讲授的内容要么太古老,要么太庸俗,只有自己通过课余时间才能真正的学习,将那些课程稍微使劲学到足以应试的程度就足够了. 做ppt,小组汇报,复制粘贴ai代码,如果换成谁来做都一样,又怎么证明自己真的学到了东西呢?\n可另一方面,期末考试和绩点非倒逼着我去做这些无意义的举动,让我喘不过气来,可又不敢摆烂,只好不由衷的看着这些不感兴趣的内容\n在这样的大环境下,相比直接本科就业,我只能祈祷读研是一个好的选择吧.\n","date":"2025-11-15T08:00:00Z","image":"/p/2025-11-15-%E9%98%B6%E6%AE%B5%E6%80%BB%E7%BB%93/%E7%AA%97%E5%8F%B0%E7%9C%8B%E9%A3%8E.jpg","permalink":"/p/2025-11-15-%E9%98%B6%E6%AE%B5%E6%80%BB%E7%BB%93/","title":"2025-11-15 阶段总结"}]