Dawn's Blogs

分享技术 记录成长

0%

数组

特点

  • 数组长度固定,所以在声明阶段必须显示的声明数组长度
1
2
3
4
5
6
// 声明一个长度为10的int数组var 
a [10]int
// 数组的长度是根据初始化值的个数来计算
b := [...]int{1, 2, 3}
// 定义了一个含有100个元素的数组r,最后一个元素被初始化为-1,其它元素都是用0初始化
c := [...]int{99: -1}
  • 数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定

  • 同类型数组之间可以相互比较。如果一个数组的元素类型可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的

1
2
3
4
5
6
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int
阅读全文 »

字符串

GO语言中的字符串是由UTF-8编码的,所以GO语言字符串是变宽字符序列,每一个字符都用1到4个字节表示。

字符串不可变

字符串的值是不可变的,意味着:

  • 如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。

  • 同样,一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。

在这两种情况下都没有必要分配新的内存

字符串切片共享内存

注意:Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串

bytes.Buffer

bytes.Buffer是一个实现了读写方法的可变大小的字节缓冲

使用bytes.Buffer进行字符串的累加比起+=要高效的多,尤其是在面对大数量的字符串时

1
2
3
4
var b bytes.Buffer // A Buffer needs no initialization.
b.Write([]byte("Hello "))
fmt.Fprintf(&b, "world!")
b.WriteTo(os.Stdout)

垃圾回收GC

Mutator 即用户线程,Collector 为 GC 线程。

Serial GC:只有一个 Collector

Parallel GC:支持多个 Collector 同时回收

Concurrent GC:Mutator 和 Collector 可用同时执行

1655261106302

经典垃圾回收策略

追踪垃圾回收

  • 被回收的条件:不可达的对象

  • 过程:

    • 标记根对象 (GC roots): 静态变量、全局变量、常量、线程栈等标记为存活

    • 标记:从根对象出发,找到所有可达对象

    • 清理:回收所有不可达对象占据的内存空间。清理策略如下:

      • Copying GC: 将存活对象从一块内存空间复制到另外一块内存空间,原先的空间可以直接进行对象分配

      img

      • Mark-sweep GC: 将死亡对象所在内存块标记为可分配,使用 free list 管理可分配的空间。缺点在于:会产生内存碎片

      img

      • Mark-compact GC: 将存活对象复制到同一块内存区域(原地)的开头缺点在于:压缩计算的成本(需要计算位置,同时需要改变引用对象的指针)、实现复杂。

      img

阅读全文 »

二叉树的完全性检验

二叉树的完全性检验

解题思路

对一个完全二叉树进行层序遍历时,其特点就是:若遍历到一个节点左/右子树为空,那么这个节点的所有右边的节点和下层节点的左右子树都为空。可以根据这个特性来进行二叉树的完全性检验

解题代码

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
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func isCompleteTree(root *TreeNode) bool {
q := []*TreeNode{root} // 队列
var flag bool // 标记是否已经碰到过左/右子树为空的节点

// 开始层序遍历
for i := 0; len(q) > 0; i++ {
p := []*TreeNode{} // p记录下一层节点
for j := 0; j < len(q); j++ {
cur := q[j] // 当前遍历节点

if cur.Left != nil && !flag{
// 左子树入队
p = append(p, cur.Left)
} else if cur.Left != nil && flag{
// 已经遇到了左/右子树为空的节点,且当前节点的左子树非空,不是完全二叉树
return false
} else {
flag = true
}

if cur.Right != nil && !flag{
// 右子树入队
p = append(p, cur.Right)
} else if cur.Right != nil && flag{
// 已经遇到了左/右子树为空的节点,且当前节点的右子树非空,不是完全二叉树
return false
} else {
flag = true
}
}
q = p // 访问下一层节点
}

// 遍历完成,说明是完全二叉树
return true
}

最长连续序列

最长连续序列

解题思路

  1. 哈希表记录数组中的元素。对于数组中的元素x,首先查看数组中是否有x-1,若有则跳过x;若没有x-1,则进行枚举x+1, x+2, ...,同时记录连续序列的长度,从而得到最长连续序列的长度

解题代码

  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
func longestConsecutive(nums []int) int {
hashMap := map[int]bool{} // 哈希表记录已有数字
for _, v := range nums { // 初始化哈希表
hashMap[v] = true
}
var MaxLength int // 数字连续的最长序列的长度

for _, v := range nums {
if !hashMap[v-1] { // 没有等于v-1的元素,无需跳过
curLength := 1 // 当前数字连续的序列长度
i := 1
for hashMap[v+i] {
curLength++
i++
}
if curLength > MaxLength {
// 更新最大长度
MaxLength = curLength
}
}
}

return MaxLength
}

寻找旋转排序数组中的最小值

寻找旋转排序数组中的最小值

解题思路

因为整个升序数组在旋转后,可以分为两部分,并且这两部分都是升序排列的,同时第一部分的所有元素比第二部分的所有元素大。基于二分查找,通过判断mid指向第一部分还是第二部分来更新lowhigh指针:

  • nums[mid] > nums[high],则mid指向第一部分,最小元素在mid左侧,low更新为mid+1
  • 否则mid指向第二部分,最小元素为mid或者在mid右侧,high更新为mid

解题代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func findMin(nums []int) int {
low, high := 0, len(nums)-1
var mid int

for low < high {
mid = (low+high)/2
if nums[mid] > nums[high] {
// 最小元素在mid左侧
low = mid + 1
} else {
// 最小元素为mid或者在mid右侧
high = mid
}
}

return nums[low]
}

寻找峰值

寻找峰值

解题思路

  1. 因为nums[-1] = nums[n] = -∞,索引找到数组中的最大值即可,这个最大值就是峰值

  2. 爬坡的思想,随机一个下标i,沿着上升的方向走,一定可以到峰值

    • nums[i] < nums[i+1],则向右侧走一定可以到达峰值
    • 否则,向左侧走一定可以到达峰值
  3. 基于二分查找,查找时比较nums[mid]nums[mid+1]的值:

    • 如果nums[mid] < nums[mid+1],则右侧存在峰值,low = mid+1
    • 否则,左侧存在峰值,high = mid

解题代码

  1. 找到最大值
1
2
3
4
5
6
7
8
9
10
11
12
13
func findPeakElement(nums []int) int {
max := nums[0] // 最大值初始化为nums[0]
maxIndex := 0 // 最大值元素的下标

for i := 1; i < len(nums); i++ { // 寻找最大值,最大值必为峰值
if nums[i] > max {
max = nums[i]
maxIndex = i
}
}

return maxIndex
}
  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
func findPeakElement(nums []int) int {
n := len(nums) // 数组长度
i := rand.Intn(n) // 随机下标i
right := (i+1 < n) && (nums[i+1] > nums[i]) // 向右走的标记,true为向右走,false为向左走

for {
if right { // 向右走
if i+1 < n && nums[i+1] > nums[i] {
i++ // 继续向右
} else {
// 到达峰值
break
}
}

if !right { // 向左走
if i-1 >= 0 && nums[i-1] >nums[i] {
i-- // 继续向左
} else {
// 到达峰值
break
}
}
}

return i
}
  1. 二分查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func findPeakElement(nums []int) int {
low, high := 0, len(nums)-1

for low < high {
mid := (low+high)/2

if nums[mid] < nums[mid+1] { // 右侧存在峰值
low = mid + 1
} else { // 左侧存在峰值
high = mid
}
}

return low
}

索引的使用

查看索引

1
SHOW INDEX FROM 表名;

创建索引

创建表的时候添加索引

  1. 隐式的方式添加索引:在声明有主键约束、唯一性约束、外键约束的字段上,会自动的添加索引
1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE dept(
dept_id INT PRIMARY KEY AUTO_INCREMENT, # 主键约束,自动添加索引
dept_name VARCHAR(20)
);

CREATE TABLE emp(
emp_id INT PRIMARY KEY AUTO_INCREMENT, # 主键约束
emp_name VARCHAR(20) UNIQUE, # 唯一性约束,自动添加索引
dept_id INT,
# 外键约束,自动添加是索引
CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);
阅读全文 »

页概述

InnoDB将数据划分为若干个页,InnoDB中数据页的默认大小为16KB。数据库管理存储空间的基本单位是页,数据库IO操作的最小单位是页

页之间不在物理上相邻,通过双向链表相关联。数据页中的每条记录通过单向链表按照顺序连接

页的上层结构

数据库中,还存在着区(Extent)、段(Segment)和表空间(Tablespace)的概念。它们的关系如下:

结构关系

区(Extent):比页大一级的存储结构,在InnoDB中,一个区会分配64个连续的页。一个区的大小是64*16KB=1MB

段(Segment):由一个或者多个区组成,区在文件系统中是一个连续的空间,在段中区之间不要求相邻。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。在创建数据表、索引的时候会创建相应的段

表空间(Tablespace):是一个逻辑容器,一个表空间中有一个或者多个段。数据库由一个或者多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。

页的内部结构

页的内部结构可以分为7个部分,分别是文件头、页头、最大最小记录、用户记录、空闲空间、页目录、文件尾:

页结构示意图

阅读全文 »

多数元素

多数元素

解题思路

  1. 以空间换时间,用哈希表来记录元素出现的次数
  2. 摩尔投票法,因为多数元素是指出现次数大于一半的元素,因此多数元素的个数 - 其他元素的个数 >= 1,相当于每个多数元素和其他元素两两抵消,最后至少剩余一个多数元素
阅读全文 »

索引概述

索引是用于MySQL高效获取数据的数据结构,用于快速查找

优点

  • 提高数据检索效率,降低磁盘IO
  • 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性
  • 在实现数据的参考完整性方面,可以加速表和表之间的连接
  • 可以减少查询中分组和排序的时间

缺点:

  • 创建和维护索引需要耗费时间,且随着数据量的增加,所耗费的时间也会增加
  • 索引需要占用磁盘空间
  • 虽然索引提高了查询速度,但会同时降低更新表的速度。当对表进行增删改数据的时候,索引也需要动态的维护(保持数据的有序性)

典型的索引结构


如果需要频繁的更新表,最好的方法就是,先删除索引,更新完数据后,最后再创建索引。

阅读全文 »

逻辑架构

概述

MySQL逻辑架构分为3层

  • 连接层
    • Connection Pool:提供多个与客户端进行连接的线程
  • 服务层
    • SQL Interface:接收SQL命令,返回查询结果
    • Parser:对SQL语句进行语法解析、语义解析,生成语法树
    • Optimizer:对解析结果进行优化,生成一个执行计划
    • Caches & Buffers:查询缓存,以key-value的方式缓存查询语句和结构
  • 引擎层
    • Storage Engines:负责MySQL中数据的存储和提取,与底层文件系统进行交互

1644303206142

阅读全文 »