|
<!DOCTYPE html> |
|
<html lang="zh-CN"> |
|
<head> |
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" /> |
|
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" /> |
|
<meta name="renderer" content="webkit" /> |
|
<title>Golang题目总结</title> |
|
<meta name="keywords" content="Golang题目总结" /> |
|
<meta name="description" content="1. slice底层数据结构和扩容原理 |
|
数据结构 Go 的 slice 底层数据结构是由一个 array 指针指向底层数组&#xff0c;len 表示切片长度&#xff0c;cap 表示切片容量。扩容原理 &#xff08;1&#xff09;扩容思路&#xff1a;对于 append 向 slice 添加元素时&#xff0c;若 sl…" /> |
|
<link rel="stylesheet" media="screen" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" /> |
|
<link rel="stylesheet" media="screen" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" /> |
|
<link rel="stylesheet" media="screen" href="/templates/wed.xjx100.cn/static/css/common.css" /> |
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3426945312236276" |
|
crossorigin="anonymous"></script> |
|
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script> |
|
<script>LA.init({id:"3GoIvY2mdpaTwZF3",ck:"3GoIvY2mdpaTwZF3"})</script> |
|
</head> |
|
<body> |
|
<div class="cl-header container-fluid"> |
|
<div class="header-content container px-0"> |
|
<nav class="navbar navbar-expand-lg navbar-light"> |
|
<a class="navbar-brand" href="/"> |
|
<img |
|
src="/templates/wed.xjx100.cn/picture/logo.png" |
|
height="46" |
|
/> |
|
</a> |
|
<button |
|
class="navbar-toggler" |
|
type="button" |
|
data-toggle="collapse" |
|
data-target="#navbarNav" |
|
aria-controls="navbarNav" |
|
aria-expanded="false" |
|
aria-label="Toggle navigation" |
|
> |
|
<span class="navbar-toggler-icon"></span> |
|
</button> |
|
<div |
|
class="collapse navbar-collapse justify-content-end" |
|
id="navbarNav" |
|
> |
|
<ul class="navbar-nav"> |
|
<li class="nav-item"> |
|
<a class="nav-link" href="/">首页</a> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" href="/news.html">编程日记</a> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" href="http://lihuaxi.xjx100.cn">梨花溪</a> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" href="http://mw.xjx100.cn/">美文频道</a> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" href="https://www.xjx100.cn/">金喜网</a> |
|
</li> |
|
<li class="nav-item"> |
|
<a class="nav-link" href="http://bk.xjx100.cn/">金喜百科</a> |
|
</li> |
|
</ul> |
|
</div> |
|
</nav> |
|
</div> |
|
</div> |
|
<div class="container"> |
|
<div class="row"> |
|
<div class="col-md-9 cl-left"> |
|
<div class="cl-artical-content"> |
|
<h2 class="cl-artical-title"> |
|
Golang题目总结</h2> |
|
<div class="cl-card-tag"> |
|
<div> |
|
<a href="/news.html" class="text-muted">news</a>/<span>2024/1/23 20:06:39</span></span> |
|
</div> |
|
</div> |
|
<div class="cl-artical"> |
|
<article class="baidu_pl"><div id="article_content" class="article_content clearfix"><link rel="stylesheet" href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/kdoc_html_views-1a98987dfd.css"><link rel="stylesheet" href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/ck_htmledit_views-25cebea3f9.css"><div id="content_views" class="markdown_views prism-atom-one-dark"><svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path></svg><h1><a id="1_slice_0"></a>1. slice底层数据结构和扩容原理</h1> |
|
<ol><li><strong>数据结构</strong><br /> Go 的 slice 底层数据结构是由一个 array 指针指向底层数组,len 表示切片长度,cap 表示切片容量。</li><li><strong>扩容原理</strong><br /> (1)<strong>扩容思路</strong>:对于 append 向 slice 添加元素时,若 slice 容量够用,则追加新元素进去,slice.len++,返回原来的 slice。当原容量不够,则 slice 先扩容,扩容之后 slice 得到新的 slice,将元素追加进新的 slice,slice.len++,返回新的 slice。<br /> (2)<strong>扩容规则</strong>:<strong>当切片比较小时</strong>(容量小于 1024),<strong>采用较大的扩容倍速进行扩容</strong>(新的扩容会是<strong>原来的 2 倍</strong>),避免频繁扩容,从而减少内存分配的次数和数据拷贝的代价。<strong>当切片较大的时</strong>(原来的 slice 的容量大于或者等于 1024),采用较小的扩容倍速(新的扩容将扩大<strong>大于或者等于原来 1.25 倍</strong>),主要避免空间浪费(网上其实很多总结的是 1.25 倍,那是在不考虑内存对齐的情况下,实际上还要考虑内存对齐,扩容是大于或者等于 1.25 倍)。</li></ol> |
|
<h1><a id="2__6"></a>2. 数组和切片的区别</h1> |
|
<p>在此之前,先理解下 <strong>引用</strong>和 <strong>指针</strong> 区别:</p> |
|
<p><strong>引用</strong>可以是一个变量或者一个结构体,<strong>若是结构体,它的属性可以为指针类型</strong>,那么此后,每个<strong>引用的副本中都有该指针类型的属性</strong>,而且它们<strong>都是指向同一个内存块</strong>。</p> |
|
<p><strong>指针</strong>是 <strong>一个存储内存块的地址</strong> 的变量。</p> |
|
<ol><li><strong>相同点</strong><br /> (1)只能存储一组相同类型的数据结构<br /> (2)都是通过下标来访问</li><li><strong>不同点</strong><br /> (1)数组定长,而切片可扩容<br /> (2)<strong>数组作为参数传递时传递数组副本</strong>,而<strong>切片作为参数传递时传递的是指向底层数组中的指针</strong><br /> (3)切片只能通过make函数进行初始化</li></ol> |
|
<h1><a id="3__rune__19"></a>3. 能介绍下 rune 类型吗?</h1> |
|
<p><strong>前言</strong>:<strong>golang中string底层是通过byte数组实现</strong>的(所以获取字符串长度是按照字节来的)。中文字符在unicode下占2个字节,在utf-8编码下占3个字节。(<strong>golang默认编码是utf-8</strong>)。</p> |
|
<p>介绍:<strong>相当于 int32</strong>。rune 是<strong>用来处理unicode或utf-8字符</strong>的(byte用来处理ascii字符)。举例(使用<br /> [ ] rune来接受字符串时,它能够正确获取字符串长度。)</p> |
|
<h1><a id="4__24"></a>4. 调用函数传入结构体时,应该传值还是指针?</h1> |
|
<p><strong>传值会拷贝整个对象,而传指针只会拷贝指针地址</strong>,指向的对象是同一个。<strong>传指针可以减少值的拷贝</strong>,但是会<strong>导致内存分配逃逸到堆中</strong>,<strong>增加</strong>垃圾回收(<strong>GC</strong>)的<strong>负担</strong>。</p> |
|
<p><strong>一般情况下,对于需要修改原对象值,或占用内存比较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值能够获得更好的性能。</strong></p> |
|
<h1><a id="5_slicemapchan_28"></a>5. 调用函数传参(slice、map、chan)时候,传的是什么?</h1> |
|
<p><strong>golang中所有函数参数传递都是传值,slice、map和chan看上去像引用只是因为他们内部有指针或本身就是指针而已</strong>。(slice其实是一个含有指针的结构体,而map和slice本身就是一个指针)</p> |
|
<h1><a id="6__Go__select__30"></a>6. 讲讲 Go 的 select 底层数据结构和一些特性?</h1> |
|
<p><strong>概念</strong>:go 的 select语句会依次检查每个case分支,如果其中有一个通道已经准备好,就会执行相应的操作。如果有多个通道都已经准备好,select语句会随机选择一个通道执行相应的操作。<br /> <strong>底层数据结构</strong>:select语句的底层数据结构是一个select结构体,它包含了多个case分支和一个默认分支。<br /> <strong>特性</strong>:<br /> (1)case语句必须是一个channel操作。<br /> (2)select中的default子句总是可运行的<br /> (3)select语句可以阻塞等待通道操作。<br /> (4)如果有多个case都可以运行,select会随机公平地选出一个执行。<br /> (5)所有channel表达式都会被求值</p> |
|
<h1><a id="7__Go__defer__39"></a>7. 讲讲 Go 的 defer 底层数据结构和一些特性?</h1> |
|
<p><strong>每个defer语句都会创建一个defer结构体</strong>,并<strong>将其添加到当前函数的defer链表中</strong>。当函数返回时,<strong>Go运行时会依次执行defer链表中的函数</strong>,<strong>直到链表为空为止</strong>。这个过程是在函数返回之前执行的,因此可以保证被延迟执行的函数在函数返回之前被执行。<br /> <strong>defer 的规则总结</strong>:<br /> (1)延迟函数执行按照<strong>后进先出</strong>的顺序执行,即先出现的 defer 最后执行。<br /> (2)延迟函数<strong>可能操作主函数的返回值</strong>。<br /> (3)<strong>申请资源后立即使用 defer 关闭资源</strong>是个好习惯</p> |
|
<h1><a id="8_map__45"></a>8. map 的数据结构是什么?是怎么实现扩容?</h1> |
|
<p><strong>数据结构</strong>:map的底层数据结构是hmap,hmap有多个bmap桶,每个bmap桶包含一个哈希链表,哈希链表中的每个元素都包含一个键值对。<br /> <strong>解决哈希冲突</strong>:当向map中插入一个元素时,Go运行时会先计算元素的哈希值,然后根据哈希值找到对应的桶。如果桶中已经存在一个元素,那么新元素会被插入到链表的头部;<br /> <strong>怎么扩容</strong>:Go 会<strong>创建一个新的 buckets 数组</strong>,<strong>新的 buckets 数组的容量是旧buckets数组的两倍(或者和旧桶容量相同)</strong>,将<strong>原始桶数组中的所有元素重新散列到新的桶数组中</strong>。这样做的目的是为了使每个桶中的元素数量尽可能平均分布,以提高查询效率。<strong>旧的buckets数组不会被直接删除</strong>,而是会把原来对旧数组的引用去掉,让GC来清除内存。在<strong>map进行扩容迁移的期间</strong>,<strong>不会触发第二次扩容</strong>。只有在前一个扩容迁移工作完成后,map才能进行下一次扩容操作。(注意:<strong>以上的搬迁过程为渐进式搬迁的策略</strong>)<br /> <strong>扩容时机</strong>:<br /> (1)当装载因子超过6.5时,扩容一倍,属于增量扩容;<br /> (2)当使用的溢出桶(bmap中有溢出桶这个属性)过多时,重新分配一样大的内存空间,属于等量扩容;</p> |
|
<h1><a id="9_slicesmapkey_52"></a>9. slices能作为map类型的key吗?</h1> |
|
<p>在golang规范中,<strong>可比较的类型都可以作为map key</strong>。<br /> 故<strong>不能作为map key 的类型包括</strong>:<br /> (1)<strong>slices</strong><br /> (2)<strong>maps</strong><br /> (3)<strong>functions</strong></p> |
|
<h1><a id="10_map__58"></a>10. map 使用注意的点,是否并发安全?</h1> |
|
<ol><li><strong>注意的点</strong>:<br /> (1)map的键必须是可比较的类型,否则会在编译时报错。<br /> (2)map是无序的,不能保证遍历的顺序和插入的顺序一致。<br /> (3)map的值可以为任意类型,但键必须是可比较的类型。<br /> (4)在并发环境下,<strong>map不是并发安全的</strong>,需要使用互斥锁等机制进行保护。</li><li><strong>解决并发安全</strong><br /> (1)sync.Map —— 可以安全地在多个goroutine之间并发访问(在使用sync.Map时,需要注意它的一些限制,例如不能使用range遍历、不能在Load和Store方法中传递指向map的指针等)。</li></ol> |
|
<h1><a id="11__map__key_66"></a>11. map 中删除一个 key,它的内存会释放么?</h1> |
|
<p><strong>golang的map在key被删除之后,并不会立即释放内存。将map设置为nil后,内存被释放。</strong></p> |
|
<h1><a id="12__map__68"></a>12. 解决对 map 进行并发访问?</h1> |
|
<ol><li><strong>sync.Map</strong> —— 可以安全地在多个goroutine之间并发访问</li><li><strong>对map进行并发访问时,需要使用锁来保证并发安全</strong>。常用的锁包括<strong>互斥锁</strong>(sync.Mutex)和读<strong>写锁</strong>(sync.RWMutex)</li></ol> |
|
<h1><a id="13__nil_map__map__71"></a>13. nil map 和空 map 有何不同?</h1> |
|
<p><strong>nil map 未初始化,空map是长度为空</strong></p> |
|
<h1><a id="14_context_context__73"></a>14. context 结构是什么样的?context 使用场景和用途?</h1> |
|
<p><strong>浅入</strong>:context通知一个或多个goroutine停止。( context.WithCancel 函数)</p> |
|
<pre><code class="prism language-go"><span class="token keyword">func</span> <span class="token function">TestContext</span><span class="token punctuation">(</span>t <span class="token operator">*</span>testing<span class="token punctuation">.</span>T<span class="token punctuation">)</span> <span class="token punctuation">{ |