相关信息

本章节主要学习网络知识

短连接

  • 模式:建立连接 -> 发送数据 -> 关闭连接。
  • 背景:早期的 HTTP/1.0。每请求一个网页上的图片或 CSS,都要经历一次 TCP 三次握手和四次挥手。
  • 缺点:开销极大。频繁地创建和销毁连接非常耗费 CPU 和内存资源。

长连接

  • 模式:建立连接 -> 发送数据 -> 保持连接 -> 发送数据 -> ... -> 关闭连接。
  • 背景:HTTP/1.1 默认开启。在 HTTP Header 中加入 Connection: keep-alive。
  • 特点:
    • 复用:同一个 TCP 连接可以发送多个 HTTP 请求。
    • 半双工:虽然连接没断,但依然是“请求-响应”模式。必须客户端先问,服务端才能答。
    • 阻塞:前面的请求没处理完,后面的请求就得排队(Head-of-line blocking)

WebSocket (全双工长连接)

  • 模式:HTTP 握手升级 -> 建立持久 TCP 连接 -> 双向实时传输。
  • 背景:为了解决“服务端主动推送到客户端”的问题(如聊天、实时股票)。
  • 特点:
    • 全双工:客户端和服务器可以同时给对方发消息,不需要等对方回应。
    • 协议升级:它开始时借用 HTTP 的 80/443 端口进行握手,握手成功后就“脱离” HTTP,转为 WebSocket 协议。
    • 轻量:数据头非常小,适合频繁发送短消息。
2026-01-150

相关信息

堆栈的区别

内存分配的区别

  • 栈内存
    • 分配方式:由编译器自动分配和释放
    • 存储内容: 函数的局部变量、参数值、返回地址
    • 生长方向: 向低地址生长
    • 连续的内存区域
    • 异常类型: 栈溢出
    • 访问速度: 快
  • 堆内存
    • 分配方式: 由程序员手动申请和释放(go中的new),go中由垃圾回收期处理
    • 存储内容: 动态生成的对象,大型数组
    • 访问速度:慢(随机访问较多)
  • go中堆栈的情况

相关信息

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

js
package main import ( "fmt" ) // 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 // 最近公共祖先的定义:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x, // 满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” // 例如,如下二叉树: Integer[] levelOrder = {3,5,1,6,2,0,8,null,null,7,4}; // 3 // 5 1 // 6 2 0 8 // 7 4 type TreeNode struct { Val int Left *TreeNode Right *TreeNode } func main() { // 构建二叉树 root := &TreeNode{Val: 3} root.Left = &TreeNode{Val: 5} root.Right = &TreeNode{Val: 1} root.Left.Left = &TreeNode{Val: 6} root.Left.Right = &TreeNode{Val: 2} root.Right.Left = &TreeNode{Val: 0} root.Right.Right = &TreeNode{Val: 8} root.Left.Right.Left = &TreeNode{Val: 7} root.Left.Right.Right = &TreeNode{Val: 4} // 测试最近公共祖先 p := root.Left.Right.Left q := root.Left.Right.Right lca := lowestCommonAncestor(root, p, q) fmt.Println("最近公共祖先:", lca.Val) // fmt.Println("Hello world!") } func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { if root == nil || root == p || root == q { return root } left := lowestCommonAncestor(root.Left, p, q) right := lowestCommonAncestor(root.Right, p, q) if left != nil && right != nil { return root } if left != nil { return left } return right }
2025-12-150

相关信息

接雨水,这道题最大的问题在于理解怎么才能接到雨水

接雨水其实就是看当前位置的左右是否存在格挡,存在格挡可以存雨水,能存放的最大容积为左右出现的格挡的最大高度

第一步 声明变量初始值 left right maxLeft Maxleft res

第二步 从左右两边开始对比: for left < right {}

第三步 if height[left] < height[right] 则处理左边,否则处理右边

第四步 处理左边 如果当前位置(left)大于等于 maxLeft 则更新maxLeft 否则说明左边的高度是超出当前位置的,可以进行容积增加: maxLeft - height[left]

右边的逻辑和左边一样,发现右边的格挡更高,则增加高度差,否则更新高度最大值(当前值比maxRight更大)

js
func trap(height []int) int { left, right := 0, len(height)-1 leftMax, rightMax := 0, 0 res := 0 for left < right { if height[left] < height[right] { if height[left] >= leftMax { leftMax = height[left] } else { res += leftMax - height[left] } left++ } else { if height[right] >= rightMax { rightMax = height[right] } else { res += rightMax - height[right] } right-- } } return res }

相关信息

算法实现-golang实现交替打印数字字母

该题的关键在意通过channel进行控制

  • 声明一个数字ch,一个字母ch
  • 分别启动一个go 协程, 一直读取channel,读取到则进行消费,消费后,写入对方的信号到通道中

相关信息

golang基础学习

为什么选择go

  • 优势:
    • GC 自动管理内存
      • 不用开发人员手动管理、开发效率更高
    • goroutine 和 channel 的优势以及go的并发模型更加简单
      • 让并发编程相对简单,运行时调度成本低
      • 非常适合微服务、RPC 和网络服务
    • 占用内存低
    • 编译快、运行启动快
    • 标准库完善,云原生生态也非常成熟
  • 缺点
    • 错误处理比较繁琐
    • gc 有一点的开销

值拷贝

  • string 和[]byte的转化是否存在值拷贝
    • 会,原因是string是只读类型,[]byte是非切片类型,互相转化时,想要不影响原有的类型,那么一定需要进行值的拷贝复制
  • golang中默认所有的变量的赋值、转化都会发生拷贝,只是引用类型拷贝的是指针,而指针指向同一个对象

nil 是否可以比较

  • nil只能和类型零值为nil的变量或者表达式进行比较
    • map、pointer、slice、channel、function、interface(error)
  • nil 和 nil不能直接比较

结构体是否可以比较

  • 当结构体的字段都是可比较字段时,才可以进行==比较
  • 可以通过reflect.DeepEqual(性能比较低)或者自定义equal比较

context的使用

  • 职责: 控制请求链路的生命周期
    • 取消信号
    • 超时时间/deadline
    • 少量请求级元数据:request_id、trace_id、日志字段等
  • 不放业务数据的原因
    • 类型不安全
      • context.Value的类型是any,取值需要类型断言
    • 依赖关系不清晰
      • 函数需要什么业务参数,应该体现在函数签名里,放进context后,调用双方的依赖会变为“隐式”
    • 滥用会让context变成全局map
      • context变成隐形参数包,排查问题困难
    • 生命周期不匹配
  • 总结

context 的核心用途是控制请求链路的生命周期,比如取消、超时和传递少量请求级元数据。业务数据应该通过明确的函数参数传递,而不是放到 context.Value 里。否则会带来类型不安全、依赖隐式、可读性差和滥用成全局 Map 的问题。Context 里可以放 trace id、request id 这类跨函数、跨服务的元信息,但不应该放订单对象、用户实体这类核心业务数据

context的使用场景

  • context可以协调多个 groutine 中的代码执行“取消”操作,并且可以存储键值对。
    • 最重要的是它是并发安全的。
  • 可以存储键值对,供上下文(协程间)读取【建议不要使用】
  • 优雅的主动取消协程(Cancel)。主动取消子协程运行,用不到子协程了,回收资源。比如一个http请求,客户端突然断开了,就直接cancel,停止后续的操作;
  • 超时退出协程(Timeout),比如如果三秒之内没有执行结束,直接退出该协程;
  • 截止时间退出协程(Deadline),如果一个业务,2点到4点为业务活动期,4点截止结束任务(协程)

Go 程序启动顺序

js
1. 编译期(build) 2. 生成可执行文件 3. 运行时启动(runtime start) 4. 初始化 package 级别变量 5. 执行 init() 6. 执行 main.main()
  • 备注: 局部变量是在函数执行调用时,才会被触发声明

编译时做了那些事情

js
1. 语法检查 2. 类型检查 3. 依赖分析 4. 变量初始化代码生成(决定怎么初始化、按什么顺序初始化) 5. init 调用链生成 6. main 入口标记

gc 垃圾回收

GC 的过程,就是从 Root 对象(栈、全局变量)出发,把它们标记成灰色,然后不断把灰色引用的对象变成灰色,自己变成黑色。直到灰色全部消失,剩下的白色就是垃圾。”

  • Go 的垃圾回收器采用的是并发三色标记清除 + 混合写屏障
    • GC 开始时会短暂 STW,开启写屏障,
      • 目的:获得一致的 Root 快照,保证标记起点正确
    • 然后进入并发标记阶段,
      • 从 GC Roots 出发,利用三色标记遍历所有可达对象。
      • 标记过程中,由于用户 Goroutine 仍在运行,可能修改对象引用关系,
      • 因此 Go 使用混合写屏障维护三色不变性,防止存活对象漏标。
    • 标记结束后再次进行一次很短的 STW,
      • 目的: 保证标记完整,没有漏标对象,才能安全开始回收
    • 随后并发清扫所有未标记的对象并释放内存
  • 混合写屏障解决了什么问题
    • 在并发标记期间维护三色不变性,防止黑色对象直接引用未标记的白色对象,从而避免存活对象被错误回收
  • 混合写屏障具体做了什么
      1. 被删除的旧引用对象,如果还没标记,要标灰
      1. 新写入的引用对象,如果还没标记,也要标灰

内存逃逸

Go 编译器的原则是:能放在栈上的尽量放在栈上,实在放不下了或者生命周期超出了,才让它逃逸到堆上

  • 什么是内存逃逸
    • 编译器的一种内存分配策略,对象从栈分配变成了堆分配。
  • 逃逸一定就不好么
    • 逃逸意味着
      • 多一次堆分配;
      • 增加 GC 压力
    • 如果对象生命周期确实超过了函数调用
      • 逃逸就是正确且必要的
  • 场景
    • 给一个引用类对象中的引用类成员进行赋值
      • 可以理解为访问一个引用对象实际上底层就是通过一个指针来间接的访问了
      • 但如果再访问里面的引用成员就会有第二次间接访问
      • 这样操作这部分对象的话,极大可能会出现逃逸的现象
  • case
    • 返回局部变量的地址
    • 闭包捕获局部变量
    • 引用类型的返回
    • 被接口(interface)引用
    • goroutine 使用局部变量
    • 返回包含底层数据的 slice、map 等,使底层对象需要继续存活
    • 对象过大,不适合放在栈上

内存泄漏

  • 核心原因
    • 长期持有对象引用,导致gc无法回收
  • case:
    • 从一个大的slice中截取一小段
      • 只要这部分小的slice一直在使用,那么这个大的slice就一直无法被gc
      • 通过copy(newSlice, large[0:1])
    • channel阻塞卡死
    • channel没有关闭
    • 全局变量、map、slice一直持有引用,导致对象一直可达
    • map的无限增大
    • slice 持有大数组
    • goroutine 因阻塞在 channel、锁或网络 I/O 上无法退出,造成泄漏
      • goroutine 中的消费一直未写入ch导致额永久阻塞
      • goroutine 中写入的ch,一直无法消费导致的阻塞
    • context 未 cancel
    • 不会因为循环引用导致泄漏
      • 标记-清除(Mark & Sweep)GC
    • time.Ticker 或 time.Timer 使用后没有及时 Stop
    • 缓存(如 map、sync.Map)没有淘汰策略,数据持续增长

如何避免channel被重复关闭

  • 重复关闭会发生什么
    • panic
  • 怎么避免
    • 通过sync.once
    • 遵循“由发送者关闭”原则

grpc是什么

  • 概念
    • gRPC 是一个基于 HTTP/2 和 Protobuf 协议的高性能 RPC 框架,
    • 支持多语言和流式通信,
    • 主要用于微服务之间的高性能调用。
    • 相比 REST,它序列化效率更高、连接复用能力更强,
    • 但可读性较差,
    • 更适合内部服务通信。
  • 优势
    • RPC 底层使用 HTTP/2 ,相比 HTTP/1.1 有几个优势:
      • 多路复用(Multiplexing):
        • 一个 TCP 连接可以同时传输多个请求,
        • 不需要像 HTTP/1.1 那样为并发请求建立多个连接或排队等待。
      • 二进制分帧:
        • 传输效率更高,协议解析开销更小。
      • Header 压缩(HPACK):
        • 减少重复请求头带来的网络开销。
    • 使用 Protocol Buffers,相比 JSON,序列化效率高
      • 数据更小
      • 编解码速度更快
      • 类型明确
    • 通过 .proto 定义接口
      • 可以自动生成多语言客户端和服务端代码
      • 具有强类型约束,开发效率高
    • 原生支持 Unary、服务端流、客户端流和双向流四种 RPC 模式
      • 非常适合微服务和实时通信场景
    • 官方生态完善
2025-11-270

相关信息

内存四驱

代码区:存放代码 全局区:常量+全局变量。最终在进程退出时,由操作系统回收。 堆区:空间充裕,数据存放时间较久。一般由开发者分配,启动Golang的GC由GC清除机制自动回收。 栈区:空间较小,要求数据读写性能高,数据存放时间较短暂。由编译器自动分配和释放,存放函数的参数值、局部变量、返回值等、局部变量等(局部变量如果产生逃逸现象,可能会挂在在堆区)

相关信息

相关信息

  • 首先需要用ctrl + z将任务暂停
  • 然后执行下面的操作
js
bg 1&&disown %1