Wstone的主页

码农备忘录


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

Golang面试参考

发表于 2019-07-08 更新于 2019-10-13 分类于 Golang 阅读次数: Disqus:

前言

整理了 Golang 面试用的笔记,仅供参考


扩展阅读


##

本文结构:

1
2
3
4
5
6
7
8
└──计算机基础
├── 计算机网络
├── 数据结构
├── 算法
├── 操作系统
├── 数据库
└── OOP 与设计模式
└── Golang 面试题

参考资料:笔试面试知识整理、Golang 面试题解析、Go面试题答案与解析

这篇文章的内容力大多一笔带过,细节上我参考的书籍有:

  • 计算机网络:《计算计网络(谢仁希)》、《图解TCP/IP》、《TCP/IP 卷1: 协议》、《自顶向下方法》
  • 数据结构:《数据结构与算法分析:C语言描述》
  • 算法:《算法:C语言实现》
  • 操作系统:《现代操作系统》

计算机网络

img

  • TCP / UDP 传输层:端到端的服务
  • IP 网络层:点到点的服务

HTTP 协议

请求报文

1
2
3
<method> <request-URL> <version>	# 状态行		# 状态行
<headers> # 请求头 # 响应头
<entity-body> # 请求实体 # 响应体

HTTP 协议不限制 GET URL 长度,但浏览器限制字符数(Chrome 8K)& 也不限制 POST 资源大小

  • GET:查
    • 安全的:获取信息,非修改信息。一般不会产生副作用
    • 幂等的:同一 URL 多个请求返回同一结果
  • POST:改,服务端通过请求 header 的 Content-Type 字段解析实体数据。提交数据的方式:
    • application/x-www-form-urlencoded:浏览器原生的 form 表单,提交的数据按 url 编码,Ajax大多默认使用
    • form_data:表单文件上传
    • application/json:API 使用较多

响应报文

状态行:协议版本、状态码、状态描述

1: Informational

  • 100 continue:POST 提交数据大于 100KB 时候发的第一个请求,允许上传则返回 100

2:Success

  • 200 OK:请求被成功处理,GET 返回资源、POST 返回对请求处理的结果
  • 204 No Content:请求处理完毕,但不返回响应体,客户端页面不刷新

3:Redirection

  • 301 Moved Permanently:永久重定向,资源分配了新的 URI,处理:

    • HEAD:响应头的 Location 指明新的 URI
    • GET:Location 指明新 URI & 在响应体中附上 URI
  • 302 Moved Temporarily:临时性重定向,希望用户本次能访问新的 URI。重定向后的请求方法不变

  • 303 See Other:请求资源有另一个 URI,重定向后的方法变为 GET 获取新 URI

    注意: 很多浏览器将 303 理解为 302,直接使用 GET 请求 Location 中的 URI

  • 304 Not Modified:请求头中带有 If-Modified、If-Match,自从上次请求后资源并未更新,则不发送响应体。

4:Client Error

  • 400 Bad Request:请求报文有语法错误

  • 401 Unauthorized:请求需要有 HTTP 认证(nginx 的 auth_basic),返回 401 时头部中

    www-authenticate 指明认证方式,再次请求时也需带上 authorization 认证信息

  • 403 Forbidden:访问被拒绝,响应实体中可说明原因

  • 404 Not Found:请求资源不存在、403 不想说明原因

5:Server Error

  • 500 Internel Error:服务器处理请求出错
  • 502 Bad Gateway: 服务器作为代理,从 upstream 收到无效响应
  • 503 Server Unavailable:服务器暂时无法处理请求,恢复时间在 Retry-After

Conditional Get (条件 GET)

用户访问过该网页,再次访问。

GET 头部带有 If-Modified-Since:,若响应 304,则直接使用浏览器的缓存。否则返回正常实体

持久连接

HTTP 1.0 中:客户端请求头添加 Connection: Keep-alive,服务端同样在响应头中添加,保持连接

HTTP 1.1 中:默认所有连接都是长连接,添加 Connection: Close 才关闭,设置

  • Keep-Alive: timeout=5, max=100:长连接保持 5s,最多接收 100 次请求后断开
  • 注意:Keep-Alive 连接也是无状态的
  • 传输结束的条件:传输的数据达到 Content-Length

HTTP Pipelining 管线化

批量提交 HTTP 请求,不排队等待响应才发送下一个请求:请求1 -> 响应1 -> 请求2 -> 响应2 -> 请求3 -> 响应3 变为 请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3

  • 管线化机制仅 HTTP1.1 支持
  • 只支持 GET、POST

HTTP1.0 中发下一个请求前,必须等待响应。

会话追踪

HTTP 请求是无状态的协议,不会保存客户端信息,实现:

cookie

  • 服务端发送给客户端的一小段信息,客户端每次请求都会带上,在有效期内识别身份。
  • 分为保存在浏览器的临时 cookie 和保存在内存的永久 cookie
  • 可被禁用

session

  • 服务端创建 session 对象并用 sessionID 标识,将 sessionID 放到 cookie 中,发送给客户端
  • cookie 被禁用,session 也会生效

token(重写 URL)

  • 在 URL 中添加标识每个用户的 token(cookie 被禁用时,将 sessionID 重写到 URL 中)

HTTP 安全

CSRF 跨站请求伪造:攻击者知道所有参数、构造合法请求

伪造成其他用户,发起请求:如拼接恶意链接给其他用户点击。防范:

  • 关键操作限制 POST
  • 合适的使用验证码
  • 添加 token:发起请求时添加附加的 token 参数,值随机。服务端做验证
  • header refer:外部来源可拒绝请求
XSS 跨站脚本攻击:在客户端不知情的情况下运行 js 脚本。防范:
  • 过滤和转义用户的输入内容:htmlspecialchars()
  • 限制参数类型
  • 文件上传做大小、类型限制

TCP 协议

特点

  • 面向连接、可靠、字节流服务:有 TCP 缓冲,可切割较长数据块、累积较短数据块。与 UDP 每次数据报不同
  • 校验和、确认和重传机制来保证可靠传输
  • 动态改变滑动窗口来控制流量
  • 一对一的通信,不能用于多播个广播

应用:HTTP、FTP、SMTP、SSH(数据准确性要求高)

优点:稳定可靠

缺点:慢、效率低、占资源多、易攻击(DDOS)

三次握手 Three-way Handshake

客户端执行 connect() 主动连接:

1
2
3
4
5
6
A:听得到吗?
B:听得到,你能听到我吗?
A:可以,我们可以交流了233

前两次:保证 B 能接收到 A 的信息,并作出正确响应
第三次:为了防止 A 的延迟的连接请求,B 一直在等待 A 的数据而浪费资源

理解:传输的信道不是绝对可靠的。为了不可靠的信道上可靠的传输信息,最少要进行三次通信。

TCP Flags 标志位:

1
2
3
4
5
6
SYN:synchronous 建立连接
ACK:acknowledgement 确认连接
PSH:push 推送
FIN:finish 释放连接(请求方数据已发送完毕)
RST: reset 重置(复位请求)
URG: urgent 紧急
  • SYN = 1 & ACK = 0:请求连接报文
  • SYN = 1 & ACK = 1:同意建立连接的响应报文
过程:

1

  • 第一次握手:客户端请求建立连接

    SYN = 1,Seq = x,进入 SYN_SEND 状态,等待服务端应答

  • 第二次握手:服务器允许建立连接

    SYN = 1,ACK = x+1,Seq = y,进入 SYNC_RCVD 状态,等待客户端确认

  • 第三次握手:客户端确认建立连接

    ACK = y + 1,连接建立。双方进入 ESTABLIASHED 状态

四次挥手 Four-way handshake

服务端和客户端均可主动断开连接:服务端、客户端均需确认对方无数据再发送2

  • 第一次握手:无数据再发送,主动关闭连接

    发送 FIN 报文,等待对法发送 ACK 报文,进入 FIN_WAIT_1 状态

  • 第二次握手:同意关闭连接

    发送 ACK 报文确认可关闭,并将未发送完毕的数据推送给对方

  • 第三次握手:请求对方关闭连接

    发送 FIN 报文,等待 ACK 报文

  • 第四次握手:关闭

    发送 ACK,进入 TIME_WAIT 状态,过 2 MSL(最大分段生存时间)未收到重传信息,直接关闭。

注意: 中间直接发送 ACK + FIN,则主动方会直接跳过 FIN_WAIT_2 状态

TCP Keep-Alive 机制(心跳包)

数据交互完毕后,一方主动释放连接。但出现意外时,TCP 连接不能及时释放,导致要维护很多半打开的连接。

实现:定时(半秒等)给对方发一个探测包,若收到 ACK 则认为连接存活,若 重试一定次数 都没收到回应则直接丢弃该 TCP 连接。

在我的 B 站直播间数据爬虫 抓取时,就需要每隔半分钟给 B 站的弹幕服务器发一个心跳包,否则连接会在一分钟后断开。

UDP 协议

特点

  • 不可靠:没有确认、超时重传、序列号机制:UDP 数据报不保证能送达、不保证数据报的顺序
  • 无需建立连接:创建 UDP 连接前无需握手创建连接
  • 一次发送一个报文,给定报文长度:过长 IP 层会分片
  • 支持多播和广播

应用:DNS、流媒体(速度要求 > 质量要求)

优点:快、比 TCP 稍安全

缺点:不可靠、不稳定

TCP UDP
报文 面向字节流 面向报文
双工性 全双工 一对一、一对多、多对一、多对多
流量控制 滑动窗口 无
拥塞控制 快重传、快恢复 无
传输速度 慢 快

IP 协议

地址分类

IPv4 用点分十进制表示,IP 地址 = 网络地址 + 主机地址(层次)

全零 0.0.0.0 :本主机、全一:255.255.255.255 当前子网的广播地址

img

A 类:8 - 1(0) = 7 位网络号

  • 主机A的地址为:58.1.2.3/8,前面58位于1~127之间,所以这是一个A类地址.
  • 跟主机A位于同一个网络中的IP地址有 : 58.0.0.1 ~ 58.255.255.254,一共有 2^24-2个
  • 不包括:58.0.0.0(网络地址)跟58.255.255.255(广播地址)

B 类:16 - 2(10) = 14 位网络号

C 类:24 - 3(110) = 21 位网络号

子网掩码

用子网掩码划分一个 IP 的网络地址和主机地址。

1
IP & 子网掩码 = 网络地址

192.168.1.1/24 是 192.168.1.1/255.255.255.0 的简写,前 24 位为网络号

子网划分:将大的整体网络划为小的子网络

Socket 编程

socket:三元组(IP 地址、协议、端口号)标识网络中唯一的进程,在 unix 中是文件

socket 使用了门面 Facade 模式:外部与内部的通信必须经过 facade

socket 隐藏了 TCP/IP 细节,开放交互接口。有:

1
2
3
4
5
6
7
socket()	// 创建套接字
bind() // 将套接字绑定到服务器地址上
listen() // 等待连接请求
accept() // 允许连接
read() // 读数据
write() // 写数据
close() // 关闭连接

搭建简单的 Server:

  • 等待连接
  • 建立连接
  • 接收请求:读取 HTTP 请求报文
  • 处理请求:访问资源(文件)
  • 构建响应:创建 HTTP 响应报文
  • 发送响应

数据结构

1
2
3
4
5
6
7
8
9
└── 数据结构
├── 数组
├── 链表
├── 栈
├── 队列
├── 哈希表
├── 二叉树
├── 堆
└── 字典树 trie

参考资料:常见数据结构及其多种实现的可视化

数组 Array

元素在内存中连续存放。每个元素占用内存相同,可通过下标算出元素位置快速访问

优点:访问快 O(1)

缺点:增加、删除元素需要移动大量元素,慢 O(N)

场景:快速访问元素,很少插入和删除元素

数组 链表
内存分配、元素存储位置 静态分配内存(栈,系统自动分配) 动态分配内存(堆,申请和管理麻烦)
栈 堆
分配方式 系统自动分配、速度快 自己申请和管理、new 慢
大小 编译时确定,具体值 是不连续的内存区域

链表 Linked List

元素在内存中不是连续存放。元素间通过指向指针联系在一起,访问元素必须从第一个元素开始遍历查找

优点:插入、删除元素只需改变指针,快 O(1)

缺点:访问慢 O(N)

场景:经常插入、删除元素

分类

  • 单向链表:节点仅指向下一节点,最后一个节点指向 nil
  • 双向链表:每个节点有 2 个指针 pre 和 next,最后一个节点的 next 指向 nil
  • 循环链表:单链表 + 最后一个节点指向第一个节点

时间复杂度

  • 查找:O(N)
  • 插入、移除:O(1)

栈 Stack

元素遵循后进先出(LIFO)原则。元素仅在表尾(栈顶)进行插入(入栈 push)、删除(出栈 pop)

实现

  • 单链表实现:保存头节点的指针,在头节点前入栈、头节点上出栈
  • 数组实现:直接操作数组最后一个元素,可能出现数组溢出:
    • 空栈(空数组)上 pop()
    • 满栈(满数组)上 push(),加倍数组大小

时间复杂度

  • 查找:O(N)
  • 插入、删除:O(1)

队列

元素遵循先进先出(FIFO)原则。元素在一端插入(进队列 enqueue)、另一端删除(出队列 dequeue)

出队列元素是在队列中存在时间最长的元素。

实现

  • 单链表实现:保存指向首尾节点的指针,从链表尾进队列,链表头出队列
  • 数组实现:修改 arr[0] 进队列,修改 arr[len - 1] 出队列

时间复杂度

  • 查找:O(N)
  • 插入、删除:O(1)

哈希表

散列表:根据 key 键值直接访问数据的内存地址

hash(data) = key:hash() 是一类算法,处理任意长度的 data,得到定长的 key 值,过程不可逆。

若 data 是数据集,则 key 也是数据集,将 keys 与原始数据一一映射就得到哈希表,即 M[key] = data

二叉树

img

  • 满二叉树:深度为 k 且有 2^k -1 个节点的二叉树
  • 完全二叉树:最后一层只缺右边节点,其它层节点数已为最大值

遍历方式(栈实现较好)

img

  • 前序遍历:根 -> 左 -> 右:abdefgc
  • 中序遍历:左 -> 根 -> 右:debgfac
  • 后序遍历:左 -> 右 ->根:edgfbac

二叉搜索树

性质:左子节点均小于根节点、右子节点均大于根节点

复杂度

  • 搜索:O(log(n))
  • 插入和删除:O(log(n))

算法

1
2
3
4
└── 算法
├── 排序
├── 查找
└── 字符串算法

参考资料:常见算法的过程可视化、常见算法的 Go 实现

排序

img

数据大小 n,好的复杂度 O(logN),坏的 O(N2)

应用场景

  • n 较小:直接插入排序
  • 基本有序:冒泡排序、直接插入
  • n 较大:快速排序、归并排序、堆排序等 O(NlogN)

交换排序一:冒泡排序

img

过程

  • 比较相邻元素,前大于后就交换。
  • 遍历操作第 1~N 个元素,此时 第 N 个元素最大。
  • 遍历操作 1~N-1,重复遍历并比较、交换。…
  • 嵌套遍历结束即完成排序。

优化

  • 设置 flag,发生交换设为 true,某趟提前排序完毕则为 false,直接退出。
  • 记录每轮最后发生排序的位置,下轮遍历到此处即可。

分析

情况 复杂度
最佳情况:已有序,全遍历 1 次 O(N)
最坏情况:反序,全遍历 N 次 O(N^2)
平均情况 O(N^2)

交换排序二:快速排序

img

过程

  • 选取基准:从数组中选一个数(第一个)作为基准
  • 分区:遍历数组,比基准更小的值放在基准前边、更大的在后边
  • 递归:递归的在分区后的数组中,再选基准、分区

分析

情况 复杂度
最佳 O(logN)
最坏(数组反序、数组元素全部相同) O(N^2)
平均 O(logN)

选择排序一:简单选择排序

img

过程

  • 遍历第 1 次:找出最小的元素置于第 1 位。…
  • 遍历第 N-1 次:排序完毕

分析

情况 复杂度
最佳、最差、平均 O(N^2)

选择排序二:堆排序

img

过程

  • 将无序的数组构建成二叉树
  • 二叉树整理为堆(完全二叉树)
  • 输出根结点,再次整理堆输出根结点…

分析

情况 复杂度
最佳、最差、平均 O(logN)

插入排序一:直接插入排序

img

过程

  • 第一个元素(认为已排序好)
  • 从第二个元素开始,往后选择元素向前遍历,找到合适的位置插入
  • 遍历到最后一个位置,排序完毕

分析

情况 复杂度
最佳(数组升序) O(N)
最坏(数组反序) O(N^2)
平均 O(N^2)

插入排序二:希尔排序

img

过程:选择一个固定(动态)的步长,对步长内的元素进行直接插入排序

分析

情况 复杂度
最佳、最坏、平均(都要步长分割、遍历) O(NlogN)

归并排序

img

过程

  • 将长度为 N 的序数组分为 N / 2 的子数组
  • 递归划分子数组,子数组内部排序

分析

情况 复杂度
最佳 O(N)
最坏 O(NlogN)
平均 O(NlogN)

基数排序(非比较)

img

过程

  • 获取数组中最大值的位数 b
  • 从 1 ~ b 遍历组合位数相同的元素

分析

情况 复杂度
最佳、最坏、平均 O(N * b)

查找

无序查找:顺序查找

  • 顺序扫描序列,依次比较值
  • 复杂度:O(N)

有序查找(无序序列需要提前排序)

二分查找(折半查找)

  • 找到中间节点比较值,相等则查找到,更小则继续对左边折半查找,更大则向右边
  • 查找比对点:mid = (low + high) / 2 或 mid = low + (high - low) / 2
  • 复杂度:比较次数,O(log2 N)

插值查找

  • 动态的改进二分查找的查找点,使其更接近区间
  • 查找对比点:mid = low + (key - arr[low]) / (arr[high] - arr[low]) * (high - low)
  • 复杂度:O(log2(log2N))

二叉查找树

  • 中序遍历获得排序好的数组
  • 复杂度:一般 O(log2N),最坏情况 单支树 O(N)

平衡二叉查找树(AVL 树)

  • 任何节点的 2 棵子树,高度差最多为 1
  • 平衡:根结点到任意一个叶子节点,距离都相等

操作系统

体系结构

  • 机器码:最高位 0 为正、1 为 负

  • 原码:符号位 + 真值绝对值的二进制

  • 反码:正数反码就是本身、负数除符号位外取反

  • 补码:正数补码就是本身、负数除符号位外取反 + 1

    1
    2
    [+1] = [00000001]原 = [00000001]反 = [00000001]补
    [-1] = [10000001]原 = [11111110]反 = [11111111]补
  • 1 byte = 8 bits,1 word = 2 byte(16位)= 4 byte(32位机)& 字是计算机数据处理和运算的单位

  • 字节序:占用内存超过 1 byte 的数据在内存中的存放顺序

    • 小端字节序:低字节数据放在内存低地址

    • 大端字节序:低字节数据放在内存高地址(符合阅读习惯,网络中数据传输的协议,即网络字节序)
      B 站直播数据抓取 中,请求数据的打包格式就是大端字节序,否则连接到弹幕服务器。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      0x12345678 的存储:

      Big Endian
      低地址 高地址
      ---------------------------------------------------->
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      | 12 | 34 | 56 | 78 |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

      Little Endian
      低地址 高地址
      ---------------------------------------------------->
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      | 78 | 56 | 34 | 12 |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

基础

操作系统功能:文件管理、存储管理、输入输出管理、作业管理、进程管理

中断

  • CPU 暂停执行当前程序,去执行中断程序
  • 中断的优先级指明处理的紧急程度,如:机器错误 > 时钟 > 磁盘 > 网络设备 > 终端 > 软件中断

系统调用

  • 2 个级别:核心态、用户态
  • 程序执行一般在 user model,需要使用操作系统服务(创建、读写文件),请求切换到 kernel model 执行
  • 核心态能存取用户、内核的指令和数据,用户态只能存取用户自己的指令和数据

中断和系统调用的关系:程序申请核心态时,将产生一个软件中断,系统将处理

并发多任务

多道程序:程序运行, CPU 空闲时(等待 IO),此时 CPU 去运行其他程序

分时系统:改进后的多道程序,程序运行一段时间后会主动让出 CPU

多任务系统:操作系统从底层接管所有硬件资源,程序以进程运行,程序运行超时会被强制暂停

进程

运行中的程序

4 个地址空间

  • 文本域:要执行的代码
  • 数据域:存放变量、程序执行期间动态分配的内存
  • 堆栈:存放本地变量和指令

3 种状态

  • 等待态:IO
  • 就绪态:等待系统分配 CPU 运行
  • 运行态:占用 CPU 正在处理

4 种进程间通信

  • 消息传递:pipe管道
  • 同步:信号量、读写锁
  • 内存共享
  • 远程过程调用:RPC

死锁:多个进程因循环等待资源而都无法执行

线程

轻量级进程

  • 解决问题:很多不同的进程需要共享同样的资源(文件),切换进程的成本很高
  • 是可以独立运行的单位,切换快速、开销小 & 可并发执行
  • 共享进程的资源:在同一进程中的线程因为有相应的地址空间,可以共享进程已打开的文件、定时器等

协程

  • 微线程,coroutine:用户级的线程
  • 线程是抢占式调度、协程是协同式调度,避免无意义的抢占,但是调度由用户决定

IO 多路复用

内核发现某个进程指定的 IO 准备读取,就会通知该进程,如客户端处理多个描述符时,大大减小创建和维护的开销。

并发与并行

  • 并发:多个操作可在重叠的时间运行
  • 并行:同一时刻有多条指令在执行,多核是并行的前提。

数据库

事务:一系列 SQL 集合

ACID 特性

  • 原子性 automatic:事务是原子工作单位,要么全部执行、要么全部不执行
  • 一致性 consistency:数据库通过事务完成状态转变
  • 隔离性 isolation:事务提交前,对数据的影响是不可见的
  • 持久性 duration:事务完成后,对数据的影响是持久的

4 种隔离级别

  • 脏读:一个事务读取了另一个事务尚未提交的修改
  • 非重复读:一个事务对同一行数据读取两次,得到不同结果
  • 幻读:事务在操作过程中进行了两次查询,第二次的结果包含了第一次未出现的新数据
  • 丢失修改:当两个事务更新相同的数据源,第一个事务提交,第二个撤销。那么第一个也要撤销

3 种实现隔离的锁

  • 共享锁 S 锁:只读 SELECT 操作,锁定共享资源,阻止其他用户写数据
  • 更新锁 U 锁:阻止其他用户更新数据
  • 独占锁 X 锁:一次只能有一个独占锁占用一个资源,阻止添加其他所有锁。有效防止脏读

索引

优点

  • 大大加快检索速度
  • 加快表之间的关联
  • 唯一性索引 UNIQUE:保证行数据的唯一性

缺点

  • 创建和维护消耗时间和物理空间

场景

  • 用在经常需要连接的字段:如外键,加快连接速度
  • 用在经常需要排序的字段:索引已排序,直接利用,加快排序时间

OOP

三个基本特征:封装、继承、多态

封装

  • 提取对象的特征,抽象成类。
  • 外部只能访问 public 的属性和方法。private 的属性和方法内部调用,设置 getter、setter 开放接口允许外部访问和修改私有数据,protected 的数据和方法子类继承和调用

继承:使用现有类的所有功能。

多态:子类覆盖父类的同名方法

Golang 面试题

问答类

1. 在 Go 中如何使用多行字符串?

使用反引号 来包含多行字串,或使用 + 来连接多行字符串(注意换行会包含\n,缩进会包含 \t,空格没有转义符):

1
2
3
4
5
6
7
8
9
func main() {
str1 := `
line1
line2
`
str2 := "\n line1\n\t" +
"line2\n"
fmt.Println(str1 == str2) // true
}

2. 如何获取命令行的参数?

有两种方法:

使用 os 库,如:

1
2
3
4
5
6
7
8
func main() {
args := os.Args
if args == nil { // 校验参数并输出提示信息
return
}
fmt.Printf("%T\n", args)
fmt.Printf("%v\n", args)
}

可以看出 os.Args 接收到的参数是 string slice,元素分别是运行的程序名、多个参数值:

img

使用 flag 库,步骤:

  • 定义各个参数的类型、名字、默认值与提示信息
  • 解析
  • 获取参数值
1
2
3
4
5
6
7
8
9
10
func main() {
name := flag.String("name", "", "Your name")
var age int
flag.IntVar(&age, "age", -1, "Your age")

flag.Parse()

println("name", *name)
println("age", age)
}

注意上边获取参数值的两种方式,使用时也有所不同:

1
2
func Int(name string, value string, usage string) *string // 返回地址
func IntVar(p *int, name string, value int, usage string) // 修改第一个参数值

img

3. 如何在不输出的情况下格式化字符串?

使用 func Sprintf(format string, a ...interface{}) string 即可,常用在手动组合 SQL 语句上:

1
2
3
4
5
6
7
func main() {
fmt.Println(formatSQL(20))
}

func formatSQL(id int) string {
return fmt.Sprintf("SELECT * FROM users WHERE id=%d", id)
}

4. 如何交换两个变量的值?

直接使用元组(tuple)赋值即可:

1
a, b = b, a

注意元组赋值是对应有序赋值的:

1
2
3
4
5
a, b, c = b, c, a // 交换三个变量的值

a := 1
b := 2
a, b, a = b, a, b // a = 2, b = 1

5. 如何复制 slice、map 和 interface 的值?

slice:

1
2
3
4
5
6
7
8
9
10
func main() {
names := []string{"Tom", "Jerry"}
nums := []string{"one", "two", "three"}
pNames := names // 确认 names 被更新

// names = nums // 直接赋值

// fmt.Println(copy(names, nums)) // 使用 copy
fmt.Println(names, nums, pNames)
}
  • 直接赋值, 底层数组将不会更新:

img

  • 使用 copy()
    返回值是 min(len(names), len(src)),只会拷贝前两个元素,pNames 的值显示 names 的底层数组已被覆盖更新:

    img

map:

最简单的方法,遍历所有 key:

1
2
3
4
5
6
7
8
9
func main() {
src := map[string]bool{"key1": false, "key2": true}
dst := make(map[string]bool)

for key, value := range src { // 遍历所有 key
dst[key] = value
}
fmt.Println(dst)
}

interface:

Go 中没有内建的函数来直接拷贝 interface 的值,也不能直接赋值。如 2 个 struct 的字段完全一致,可以使用强制类型转换或反射来赋值。

参考:关于结构体复制问题、Copying Interface Values In Go

6. 下边两种 slice 的声明有何不同?哪种更好?

1
2
var nums []int
nums := []int{}

第一种如果不使用 nums,就不会为其分配内存,更好(不使用编译也不会通过)。

写出程序运行输出的内容

1. 考察多个 defer 与 panic 的执行顺序

1
2
3
4
5
6
7
8
9
10
11
func main() {
deferCall()
}

func deferCall() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()

panic("触发异常")
}

defer 可以类比为析构函数,多个 defer 本身的执行是栈 LIFO 先进后出的顺序,代码抛出的 panic 如果在所有 defer 中都不使用 recover 恢复,则直接退出程序。

如果手动使用 os.Exit() 退出,则 defer 不执行。

img

2. 考察 defer 与 return 的执行顺序

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
func main() {
fmt.Println(double1(5))
fmt.Println(double1(6))
fmt.Println()
fmt.Println(double2(5))
fmt.Println(double2(6))
}

// 匿名返回
// 加倍参数,若结果超过 10 则还原
func double1(v1 int) int {
var v2 int
defer func() {
if v2 > 10 {
v2 = v1 // v2 不会被修改
}
}()

v2 = v1 * 2
return v2
}

// 有名返回
func double2(v1 int)(v2 int) {
// v2 与函数一起被声明,在 defer 中能被修改
defer func() {
if v2 > 10 {
v2 = v1 // v2 被修改
}
}()

v2 = v1 * 2
return
}

注意 return var 会分为三步执行:

return 语句为 var 赋值

  • 匿名返回值函数:先声明,再赋值
  • 有名返回值函数:直接赋值

检查是否存在 defer 语句:逆序执行多条 defer,有名返回函数可能会再次修改 var

真正返回 var 到调用处

img

3. 考察 goroutine 的传值方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
runtime.GOMAXPROCS(1) // 强制使多个 goroutine 串行执行
wg := sync.WaitGroup{}
wg.Add(10)

for i := 0; i < 5; i++ {
go func() {
fmt.Println("i: ", i)
wg.Done()
}()
// time.Sleep(1 * time.Second) // 此时将顺序输出 1 2 3 4 5
}

for i := 0; i < 5; i++ {
go func(i int) {
fmt.Println("i: ", i)
wg.Done()
}(i)
}
wg.Wait()
}

第一个 for 循环:以极快的速度分配完 5 个 goroutine,此时 i 的值为 5,gouroutine 得到的 i 都是 5

第二个 for 循环:每次都会将 i 的值拷贝一份传给 goroutine,得到的 i 不同,输出不同

img

4. 考察 defer 参数的计算时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
a := 1
b := 2
defer add("A", a, add("B", a, b))
a = 0
defer add("C", a, add("D", a, b))
b = 1
}


func add(desc string, a, b int) int {
sum := a + b
fmt.Println(desc, a, b, sum)
return sum
}

defer 语句会计算好 func 的参数,再放入执行栈中。

注意第 7 行:四个 defer func 的参数此时已是确定值,不再对 defer 中的 b 造成影响。

img

5. 考察 Go 的组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type People struct{}

func (p *People) ShowA() {
fmt.Println("people showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("people showB")
}


type Teacher struct {
People
}

func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}

func main() {
t := Teacher{}
t.ShowB()
t.ShowA()
}

第 13 行: Teacher 通过嵌入 People 来获取了 ShowA() 和 showB()

第 16 行:Teacher 实现并覆盖了 showB()

第 24 行:调用未覆盖的 showA(),因为它的 receiver 依旧是 People,相当于 People 调用

img

# Golang
Redis 主从同步配置实践
使用 ELK(Elasticsearch + Logstash + Kibana) 搭建日志集中分析平台实践
  • 文章目录
  • 站点概览

Wstone

36 日志
15 分类
24 标签
RSS
GitHub Twitter FB Page V2ex
Links
  • 山上淘金
  • terrancy
  • roger
  1. 1. 前言
  2. 2.
  3. 3. 计算机网络
    1. 3.1. HTTP 协议
      1. 3.1.1. 请求报文
      2. 3.1.2. 响应报文
      3. 3.1.3. Conditional Get (条件 GET)
      4. 3.1.4. 持久连接
      5. 3.1.5. HTTP Pipelining 管线化
      6. 3.1.6. 会话追踪
      7. 3.1.7. HTTP 安全
        1. 3.1.7.1. CSRF 跨站请求伪造:攻击者知道所有参数、构造合法请求
        2. 3.1.7.2. XSS 跨站脚本攻击:在客户端不知情的情况下运行 js 脚本。防范:
    2. 3.2. TCP 协议
      1. 3.2.1. 三次握手 Three-way Handshake
        1. 3.2.1.1. 过程:
      2. 3.2.2. 四次挥手 Four-way handshake
      3. 3.2.3. TCP Keep-Alive 机制(心跳包)
    3. 3.3. UDP 协议
    4. 3.4. IP 协议
      1. 3.4.1. 地址分类
      2. 3.4.2. 子网掩码
    5. 3.5. Socket 编程
  4. 4. 数据结构
    1. 4.1. 数组 Array
    2. 4.2. 链表 Linked List
    3. 4.3. 栈 Stack
    4. 4.4. 队列
    5. 4.5. 哈希表
    6. 4.6. 二叉树
      1. 4.6.1. 遍历方式(栈实现较好)
      2. 4.6.2. 二叉搜索树
  5. 5. 算法
    1. 5.1. 排序
      1. 5.1.1. 交换排序一:冒泡排序
      2. 5.1.2. 交换排序二:快速排序
      3. 5.1.3. 选择排序一:简单选择排序
      4. 5.1.4. 选择排序二:堆排序
      5. 5.1.5. 插入排序一:直接插入排序
      6. 5.1.6. 插入排序二:希尔排序
      7. 5.1.7. 归并排序
      8. 5.1.8. 基数排序(非比较)
    2. 5.2. 查找
      1. 5.2.1. 无序查找:顺序查找
      2. 5.2.2. 有序查找(无序序列需要提前排序)
  6. 6. 操作系统
    1. 6.1. 体系结构
    2. 6.2. 基础
      1. 6.2.1. 中断
      2. 6.2.2. 系统调用
      3. 6.2.3. 并发多任务
    3. 6.3. 进程
    4. 6.4. 线程
    5. 6.5. 协程
    6. 6.6. IO 多路复用
    7. 6.7. 并发与并行
  7. 7. 数据库
    1. 7.1. 事务:一系列 SQL 集合
    2. 7.2. 索引
  8. 8. OOP
  9. 9. Golang 面试题
    1. 9.1. 问答类
      1. 9.1.1. 1. 在 Go 中如何使用多行字符串?
      2. 9.1.2. 2. 如何获取命令行的参数?
      3. 9.1.3. 3. 如何在不输出的情况下格式化字符串?
      4. 9.1.4. 4. 如何交换两个变量的值?
      5. 9.1.5. 5. 如何复制 slice、map 和 interface 的值?
      6. 9.1.6. 6. 下边两种 slice 的声明有何不同?哪种更好?
    2. 9.2. 写出程序运行输出的内容
      1. 9.2.1. 1. 考察多个 defer 与 panic 的执行顺序
      2. 9.2.2. 2. 考察 defer 与 return 的执行顺序
      3. 9.2.3. 3. 考察 goroutine 的传值方式
      4. 9.2.4. 4. 考察 defer 参数的计算时机
      5. 9.2.5. 5. 考察 Go 的组合
闽ICP备15027528号-1 © 2020 Wstone
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Mist v7.3.0
|