欢迎来到我的代码宇宙!
这里记录着一个计算机学生的成长轨迹。
白天调试 BUG,晚上调试星光。
在星辰与字节之间,写下一行属于宇宙的代码。
欢迎来到我的代码宇宙!
这里记录着一个计算机学生的成长轨迹。
白天调试 BUG,晚上调试星光。
在星辰与字节之间,写下一行属于宇宙的代码。
模版 注:尖括号的是必须出现的元素,方括号是可选的。所有的内容必须是英文。 1 2 3 <类型>[范围]: <描述> [正文] [脚注] 常见类型 fix:修复了一个 bug feat:新增了一个功能 build: 用于修改项目构建系统,例如修改依赖库、外部接口或者升级 Node 版本等; chore: 用于对非业务性代码进行修改,例如修改构建流程或者工具配置等; ci: 用于修改持续集成流程,例如修改 Travis、Jenkins 等工作流配置; docs: 用于修改文档,例如修改 README 文件、API 文档等; style: 用于修改代码的样式,例如调整缩进、空格、空行等; refactor: 用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑; perf: 用于优化性能,例如提升代码的性能、减少内存占用等; test: 用于修改测试用例,例如添加、删除、修改代码的测试用例等。 fix 和 feat 是最常用的两个类型,必须记住。 示例 1 feat: allow provided config object to extend other configs 1 feat: allow provided config object to extend other configs 1 2 3 4 5 6 7 8 9 10 fix: prevent racing of requests Introduce a request id and a reference to latest request. Dismiss incoming responses other than from latest request. Remove timeouts which were used to mitigate the racing issue but are obsolete now. Reviewed-by: Z Refs: #123 参考文献 约定式提交 ...
什么是 Git Git 是一个分布式版本控制软件。 你为什么需要 Git 版本管理 假设你有一天打算给项目添加新的代码,你噼里啪啦敲了一大堆代码,然后发现代码有问题,项目运行不起来了,被玩坏啦。 这时候你就需要返回到之前那个能稳定运行的版本。Git 通过一个命令就可以直接返回到之前那个版本,这就是版本管理的好处。 分支管理 假设项目需要两个新功能:小张负责功能 A,小王负责功能 B。 如果没有 Git,那可能需要考虑:小张先把 A 的功能写完后,把代码交给小王,再开发功能 B。这样存在最大的弊端:一是效率低下,两个功能没有“并行”开发,而是“串行”的。 分支管理是这样解决的:项目的源代码叫做 main 分支,小张新建一个 feat/A 分支,这个分支就像 main 分支的"双胞胎",但两人之后会长得不一样。小王则新建一个 feat/B 分支。从此以后,小张只修改 feat/A 分支的代码,小王修改 feat/B 分支的代码。现在就存在三个分支: 没有新功能的 main 分支 存在 A 功能的 feat/A 分支 存在 B 功能的 feat/B 分支。 假如 A 功能开发完了,就把 feat/A 分支合并(merge)到 main 分支里面,使得 main 分支具有 A 功能。B 功能也是一样的原理。 合并(merge)的原理是:找出 feat/A 分支和 main 分支的“共同祖先”,再把不同的代码应用到这个“共同祖先”上。 通过分支管理,就可以做到两个功能可以同时开发,大大提高了开发效率(当然优点不止这个)。 什么是 GitHub GitHub 是一个在线软件源代码托管服务平台,用于公开程序或软件的代码,使用 Git 作为版本控制软件。 为什么需要 Github 托管 Git 中的代码 刚刚 Git 解决了版本管理和分支管理的问题,但是如果一个公司要多人协作,就需要一台服务器存储 main 分支的代码,然后大家访问这个服务器,从 main 分支上新增自己的分支。 ...
静态方法和非静态方法 概念解释: 猴子 猴子 20 岁了 猴子偷玉米 对于 OOP 属性 方法 对于 Java 实例变量 函数 建立以下 两个文件: 1 2 3 4 5 6 // static方法 public class Dog { public static void makeNoise() { System.out.println("Bark!"); } } 1 2 3 4 5 public class DogLauncher { public static void main(String[] args) { Dog.makeNoise(); } } 在这里,static 是静态的意思。被它修饰的方法,应该直接用类名调用;而没有 static 的方法,应该先实例化一个类(生成一个对象),用对象名来调用方法: 1 2 3 4 5 6 // 非静态方法,也被称为实例方法 public class Dog { public void makeNoise() { System.out.println("Bark!"); } } 1 2 3 4 5 6 public class DogLauncher { public static void main(String[] args) { Dog xibei = new Dog; // 这里实例化出一个xibei对象,类型为Dog。 xibei.makeNoise(); // 这里通过对象名来调用方法。 } } 总结:实例方法和实例变量需要实例化之后,通过对象名来使用;静态方法和静态变量可以直接使用类名来使用。 ...
Hello world 下面是 Java 的第一个 Hello world。 1 2 3 4 5 public class HelloWorld { public static void main(String[] args) { System.out.println("Hello world!"); } } 面向对象的特性是:所有的代码都放在类(class)里面。 public static void main (String[] args) 是 main函数的定义。 Java 编译运行 安装好 JDK 之后,想要运行 Java 程序,需要以下两个步骤: 编译 Java 源文件。 运行字节码文件。 1 2 3 $ javac HelloWorld.java # 1. javac指令编译 $ java HelloWorld # 2. java指令运行 Hello World! 变量和循环 1 2 3 4 5 6 7 8 9 public class HelloNumbers { public static void main(String[] args) { int x = 0; while (x < 10) { System.out.print(x + " "); x = x + 1; } } } 编译运行之后这样显示: ...
题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 你可以按任意顺序返回答案。 示例 1: 输入: nums = [2,7,11,15], target = 9 输出:[0,1] 解释: 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 示例 2: 输入: nums = [3,2,4], target = 6 输出:[1,2] 示例 3: 输入: nums = [3,3], target = 6 输出:[0,1] 提示: 2 <= nums.length <= 104 -109 <= nums[i] <= 109 -109 <= target <= 109 只会存在一个有效答案 进阶: 你可以想出一个时间复杂度小于 O(n2) 的算法吗? ...
引言 与我们平时枯燥乏味的教育风格不同,美国的计算机教育似乎更为风趣。计算机四大名校(Stanford, CMU, MIT, Berkeley)还公开了一系列基础的计算机课程,这些课程以极高的教育质量著称,并通过爆火的 CSDIY 等计算机自学网站广为人知。 因此西贝打算驻足一下,去体验一下这些国外计算机课程。 最首要的问题是,我应该刷哪些课程?这个问题已经在下面这个 course guide 得到解答:Eta Kappa Nu (HKN), Mu Chapter 这个 course guide 是 Berkeley 推出的,因此里面都是 Berkeley 的课程,可能具有局限性。但是其中的核心课程:CS 61A、CS 61B 和 CS 61C,非常值得学习。尤其是 CS 61A,作为计算机经典书籍 SICP 的改编,可以说是 CS 课程系列的精华之作,非常值得一学。 CS 61B is all you need. CS 61B 是一门用 Java 学习的算法与数据结构课程(重点在数据结构和算法,而不是 Java!)。 CS 61B 已经开展多年,每年都有大量的课件和资料流传下来。我以资源的完整性,选择 sp21 版本的 CS 61B 进行学习。 学习方法 我计划的学习方式:Lecture -> Reading -> Discussion -> Lab -> HW -> Project 整个流程是围绕 CS 61B 的官方网站进行的(文章最后会附有官网链接)。 ...
一、互联网导论 互联网是什么? 互联网不是我们平时打开浏览器看到的花花绿绿的网页,那只是互联网应用的一部分——万维网。电子邮件、我的世界游戏服务器,甚至物联网设备都算互联网的一份子。 互联网是联盟式的、可扩展的 互联网由很多运营商(ISP)在维护,每个运营商独立运作,但每个运营商都必须合作以连接整个世界。因此运营商们必须达成一致的协议,才能让互联网遍布世界的每一个角落。 互联网不是“金字塔层级”,也就是不是只有一个运营商,因此我们互联网需要做的就是把各个运营商链接在一起,每个局域网连接在一起,每个设备连接在一起… 这就是互联网的可扩展性。 庞大体积也给互联网本身带来很多挑战,比如消息可能在传输中途就失效了,再比如中途有一个或者很多个路由器坏了等等,因此互联网需要面对随机和众多的故障。 协议 互联网的重点在于协议。协议就是一种标准,规定了互联网中的数据以什么方式、格式传输,从哪传送到哪,这样才能保证一致性。协议一般是以请求评论(RFC)的方式发布的,由 IETF 负责。 二、互联网层次 第一层:物理层 物理层的作用就是把位信息通过某种方式传输出去。这种方式通常有电线上的电压、无线无线电波、光纤电缆中的光脉冲等,这是电气领域的事情,是计算机网络的最低层次,我们不作深究。 第二层:数据链路层 在互联网中,一条链路连接两台机器,多条链路连接多台机器构成了局域网(local area network,LAN)。 链路层主要是把物理层的位信息分组成数据包(packets)(有时候也叫帧frames),以其作为基本单位。 第三层:网络层 不同局域网的设备想要沟通,就必须搭建局域网之间的桥梁,不同的局域网连接起来就构成了互联网。这个桥梁的两个端点就是交换器(switch) 或者 路由器(router)。 网络层解决了以下问题: 交换机或者路由器收到数据包,该往哪里转发?怎么发路径最短?(路由单元的重点) 如何保证链路上有足够的容量来传输我们的数据?(拥塞控制单元的重点) 网络层的两个特性是: 尽力而为传输:只管传输,不管有没有传输到目的地,尽力就好。 数据包抽象:如果传输大量数据,就把它切分成多个小数据包传输。 第四层:传输层 传输层以第三层网络层为基础,实现了一个额外的协议,用于重传丢失的数据包、将数据分割成数据包,以及重新排序乱序到达的数据包(以及其他功能)。 传输层协议使我们能够停止以数据包为单位思考,转而以流的形式思考,即两个端点之间交换的数据包流。 第七层:应用层 应用层使得互联网可以支持不同的应用,例如收邮件,下载视频等等。如果没有第七层,恐怕互联网就只能有一种用途,换句话说应用层使得互联网有更多的应用。 会话层(第五层)原本应该将不同的数据流组装成一个会话(例如,加载各种图像和广告来形成一个网页),而表示层(第六层)原本应该帮助用户可视化数据。如今,这些层的功能主要在第七层中实现,因此直接被跳过了。 三、报头 为什么需要报头 在第三层网络层我们提出了一个问题:数据包应该经过什么路径发送? 这件事关乎交换机或者路由器,我们应该提前告诉他们这个数据包往哪发送。实现方法就是在数据前面加上报头(headers)。而数据在这里我们就叫它载荷(payload)。 每一个网络设备都需要遵循一个报头标准,就像世界通用一个语言——英语,这样数据包的传输才不会乱套。 报头里有什么信息 目标地址:告诉路由器和交换器,数据包要往何处发送。 源地址:允许数据接收方可以回信。 校验和:确保数据包在传输中途没有损坏。 其他元数据:例如数据包的长度。 报头疯狂嵌套 信息顺着互联网层次越低,修饰的报头就越多;层次越高,报头就越少。 每一个设备都会处理前三层:物理层、数据链路层和网络层,但只有发信和收信的主机才可以处理后两层:传输层和应用层。经过这样不停的拆包和封装过程,数据最终被传输到目的地址。 第一层、第二层的协议可以采用无线或者有线方式传输,但是必须和上一跳或者下一跳的协议一样;第四层和第七层中,发送主机和目标主机的协议必须完全一样,以保证功能相同。 三、网络架构 设计范式 上一节我们自底向上了解了互联网层次,现在我们自顶向下来认识互联网设计采用的范式。 互联网的范式有很多也很有争议,比如联盟式(独立运营商合作),但在近年来,软件定义网络(SDN)作为一种更集中的网络管理方式出现。 “楚王好细腰” 互联网层次中的一层可以只有一个协议,也可以有很多很多协议。 现代互联网的具体协议如上图所示,形成了一个上下宽中间窄的图像。可以发现在第三层只有 IP 协议,互联网的每一个人都必须使用 IP 协议才能传输数据。 解复用 上面说到,多个协议可能采用同一个路径传递,因此在第四层解包的时候我们要在报头中声明,下一个协议具体是什么协议(TCP/UDP);而在第七层我们需要声明下一个端口是什么。端口用来区分数据包属于哪个应用程序。 端口分为逻辑端口和物理端口。逻辑端口就是报头里面的 port 字段;物理端口就是交换机上插的网线物理位置。 注意:套接字(socket)指的是操作系统用于将应用程序连接到操作系统中的网络栈的机制。当应用程序打开一个套接字时,该套接字将与一个逻辑端口号相关联。当操作系统接收到一个数据包时,它使用端口号将该数据包定向到相关联的套接字。 ...
瞎折腾是我一直以来的主旋律,光是网站我就折腾了不少框架。今天给大伙们分享一下自己曾经用过的一些框架。 WordPress:小白建站不二选择 计算机领域有个词:低代码平台,意思大概就是几乎不用写代码,就可以实现自己想要的功能。WordPress 就是这样的,你不用写一行代码就可以快速搭建起一个像模像样的网站。 WordPress 有插件市场,还自带了海量企业级、博客级的模板,真的就像模像样… 我接触的第一个博客框架就是 WordPress。当时是想自己建立一个天文文章汇总网站,任何天文爱好者都可以在网站上发布文章(现在想起来有点幼稚,但是也很有意思)。当时还没有大模型,我在必应上面搜了一整天博客框架,几乎百分之 80 的回答都是 WordPress。于是我自己买了一个阿里云服务器,跌跌撞撞花了一整个月备案,选了一个自以为好看的主题 Bravada,搭建了人生中第一个网站——银河派对。 当时实在是乐在其中,而且那个时候很多天文大佬发文都很精华,属于是赶上浪潮之巅了。更可贵的是凭着厚脸皮联系上了不少大佬,后面打造自己的社群。高中的这番经历让我对建站有了大概的了解,这段经历也很可能潜移默化地影响了我未来走计算机的道路。 最后总结一下:如果你不太熟悉写代码,WordPress 是最佳选择。 VuePress:高度自定义友好 银河派对最后是因为没有能力续费阿里云 ECS 和域名,被迫关停了。不过到了大学我有了建立自己博客网站的需求。我对于博客网站的要求只有两个:一个是优雅,简洁而不简陋;另一个是可以用 markdown 格式写文章。WordPress 的模板大多花里胡哨、加载慢,很难达到第一个需求。通过大量调研,我发现自己看过的不少网站都是一种风格,似乎是同一套框架写出来的,最后我发现了VuePress。 VuePress 做出来的网站差不多都这个样子,网上称呼这种风格为“文档风”(看上去就像代码文档一样,不过确实很多文档也就用的 VuePress)。同类型的博客框架还有 VitePress,也是这种风格。 于是我把高中那一套用了起来:买 ECS、备案… 后面的就几乎不会了:什么 git 克隆仓库,什么 vue 组件,后面我连文章写在哪,怎么运行网站都完全不知道!因为这类框架需要一定的计算机基础,适合计算机从业人群使用。我又花了一个月研究 github、简单入门 vue 和 vite,边看 VuePress 的官方文档边修改网站,这才上线了第二个网站——西贝的天文茶馆。 VuePress 是我目前用过的最久的框架,因为它满足了我对网站风格的基本需求,markdown 写文章也相当方便,可以迁移到微信公众号、知乎等平台,而且 VuePress 是静态网站,加载速度也相对 WordPress 花里胡哨的动画快不少。 总的来说,如果你懂一些计算机知识,喜欢简洁优雅的主题,VuePress/VitePress 是可以考虑的框架。 Hexo:生态圈完整的程序员友好框架 最近我实在有点不想忍受 VuePress 的一大槽点:文章和配置文件不分离。写文章需要在项目深处精确地创建 markdown 文件,并且旁边一大堆配置文件看着很乱。于是我又增添了一个需求:文章与配置文件分离,项目短小精悍。 这次我交给大模型来调研,大模型给我推荐了两个框架:Hugo 和 Hexo。Hexo 是基于 nodejs 构建的,Hugo 是基于 Go 语言构建的。Hexo 很多程序员都在用作个人博客,有许多插件和主题,这跟 WordPress 很像;Hugo 构建速度快,适合高度自定义。我不愿意再花太多心思折腾网站布局,于是我选择了 Hexo 来快速部署一个新的博客网站。 ...
Sleep Sleep 就是完成一个工具:sleep [time],输入后休眠 time 秒。 完成这个工具需要掌握两个关键点: 如何在 shell 中给程序传递参数 Sleep 的原理 Main 函数传参 C 语言中,Main 函数要么传递参数,要么传递参数。下面的示例写的很清楚。第一种不传递参数,直接在参数列表写一个 void ,不写也是可以的;第二种传递参数看起来就比较迷惑了。 1 2 3 4 5 6 7 8 int main(void) { // 无参数形式 return 0; } int main(int argc, char *argv[]) { // 带参数形式 return 0; } int argc, char *argv[],这到底传递的是什么?Argc 其实是参数的个数,agrv 则是参数的指针。也就是说我们 main 函数需要一个大小为 agrc 的列表,名字叫 argv,范围是 0-agrc-1 。 比如说我们执行 cp demo.c demo.cpp,agrc 为 3,argv 为 ['cp', 'demo.c', 'demo.cpp']。 那么 argc 和 argv 是如何得出的?这是 shell 的解析器在负责,不需要我们一个一个数参数到底有多少个。 总结一下:我们输入的指令,会被 shell 解析成 argc 和 argv ,然后交给 main 函数来使用。这里面还有更具体的流程,后面再说。 ...