问题1:字符串序列化后,&符号 转成 “\u0026“ #
问题描述: #
从CGO拿到的字符串,序列化后存入数据库后,& < > 符号变成了类似 “\u0026"的形式,但编译器、界面等其他地方看到的确实原始的& < > 符号。
解决方案: #
数据结构中的值 带有 & > < 等符号,当我们要将 struct map 转成json时,使用
json.Marshal() 函数,此函数会将 值中的 & < > 符号转义 为 类似 “\u0026”
parm := make(map[string]string)
parm["path"] = "http://baidu.com?a=djflks&b=1231131"
//转成json 不转义特殊字符 这段替换原来的json.marshal
bf := bytes.NewBuffer([]byte{})
jsonEncoder := json.NewEncoder(bf)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.Encode(parm)
fmt.Println(bf.String())
问题2:IDEA大面积报红,但编译能通过 #
问题描述: #
在使用IDEA编译代码的伙伴,偶合会发现个别函数报红,鼠标悬停显示函数未声明或者找不到等等,但是go build 却可以通过。
解决方案: #
IEDA缓存混乱的问题,点击 文件->修复IDE->
根据右下角提示,一路点到底,重新建立索引就好了。
问题4:CGO不支持在函数参数列表中使用默认参数 #
问题描述: #
使用CGO调用别人的动态库时,经常出现这种问题:
cgo:
gcc errors for preamble:
In file included from .\hikvision.go:6:0:
C:/Users/c/go/src/VideoForensic/GoldenEyes/videogoplugin/networkscan/include/HCNetSDK.h:51623:68: error: expected ';', ',' or ')' before '=' token
NET_DVR_API BOOL __stdcall NET_DVR_SetConnectTime(DWORD dwWaitTime = 3000, DWORD dwTryTimes = 3);
^
C:/Users/c/go/src/VideoForensic/GoldenEyes/videogoplugin/networkscan/include/HCNetSDK.h:51624:66: error: expected ';', ',' or ')' before '=' token
NET_DVR_API BOOL __stdcall NET_DVR_SetReconnect(DWORD dwInterval = 30000, BOOL bEnableRecon = TRUE);
解决方案: #
找到引入的.h文件,找到相应函数定义,修改默认参数值。
NET_DVR_API BOOL __stdcall NET_DVR_SetReconnect(DWORD dwInterval = 30000, BOOL bEnableRecon = TRUE);
改成这样
NET_DVR_API BOOL __stdcall NET_DVR_SetReconnect(DWORD dwInterval, BOOL bEnableRecon);
问题5:CGO不识别自定义枚举类型 #
问题描述: #
使用CGO调用别人动态库时,类似如下情况:
error: unknown type name 'ADDITIONAL_LIB'; did you mean 'PARTITION_LDM'?
NET_DVR_API BOOL __stdcall NET_DVR_LoadAdditionalLib(ADDITIONAL_LIB libType, char const *sDllName);
^~~~~~~~~~~~~~
PARTITION_LDM
解决方案: #
CGO不认下面这个
enum ADDITIONAL_LIB
{
PLAYCTRL = 0,
DSSDK,
STREAMCONVERT,
STREAMTRANS,
QOSSDK,
DLL_PATH_AUDIO,
EZVIZ_SSL_SDK,
ANALYZE_DATA_LIB,
DLL_LIBICONV,
SSLEAY32_SDK,
LIBEAY32_SDK,
HCNETUTILS_SDK,
NPQ_LIB,
LOAD_DLL_COUNT,
};
改成这样
typedef enum
{
PLAYCTRL = 0,
DSSDK,
STREAMCONVERT,
STREAMTRANS,
QOSSDK,
DLL_PATH_AUDIO,
EZVIZ_SSL_SDK,
ANALYZE_DATA_LIB,
DLL_LIBICONV,
SSLEAY32_SDK,
LIBEAY32_SDK,
HCNETUTILS_SDK,
NPQ_LIB,
LOAD_DLL_COUNT,
} ADDITIONAL_LIB;
问题6:CGO中不能使用引用(&
)语法,这是 C++ 语言的特性
#
问题描述: #
使用CGO调用别人动态库时,编译出现类似以下错误:
C:/Users/c/go/src/VideoForensic/GoldenEyes/videogoplugin/networkscan/include/dhnetsdk.h:73691:148: error: expected ';', ',' or ')' before '&' token
typedef void (CALLBACK *fSubLogDataCallBack)(LLONG lLogHandle, NET_EM_LOG_QUERY_TYPE emLogType, const DH_DEVICE_LOG_ITEM_EX *pstuLogData, const int& nCount, LDWORD dwUser, void *reserved);
^
C:/Users/c/go/src/VideoForensic/GoldenEyes/videogoplugin/networkscan/include/dhnetsdk.h:76134:64: error: unknown type name 'fSubLogDataCallBack'; did you mean 'fLogDataCallBack'?
CLIENT_NET_API void CALL_METHOD CLIENT_SetSubscribeLogCallBack(fSubLogDataCallBack pLogDataCB, LDWORD dwUser);
^~~~~~~~~~~~~~~~~~~
fLogDataCallBack
解决方案: #
CGO中不能使用引用(&
)语法,这是 C++ 语言的特性。
将 const int& nCount 改为 const int* nCount,并确保正确的类型名称定义:
typedef void (CALLBACK *fSubLogDataCallBack)(LLONG lLogHandle, NET_EM_LOG_QUERY_TYPE emLogType, const DH_DEVICE_LOG_ITEM_EX *pstuLogData, const int* nCount, LDWORD dwUser, void *reserved);
问题7:GORM中,Limit函数入参为0,查不到数据 #
问题描述: #
当我们使用gorm数据库框架查询数据时,若使用limit函数,函数入参没有赋值,则会默认为0,查不到任何数据。
eng := engine.Table(tableName).Where("pid=?", pid)
err = eng.Limit(limit).Offset(skip).Count(&nCount).Find(&resultData).Error
解决方案: #
若要查全部内容,limit设为-1
若有其他需求,limit自行赋值。
问题8:GORM中,使用更新仅适用于非零值 #
问题描述: #
当我们使用gorm数据库框架时,当使用struct更新时,FORM将仅更新具有非空值的字段
对于下面的更新,什么都不会更新为”",0,false是其类型的空白值
db.Model(&user).Updates(User{
Name: "", Age: 0, Actived: false})
若想要更新空值,需要用到AllCols()函数
engine.Id(id).AllCols().Update(bean, condiBeans...)
问题9:GORM中,Count方法不适合放在raw方法后面,否则将会出错 #
问题描述: #
当我们使用gorm数据库框架时,Count方法放在raw方法后面,会出错
count:=0
db.Raw(sql).Count(&count)
问题10: crontab 定时脚本无法执行 #
问题描述: #
如果我们使用 crontab 来定时执行脚本,无法执行,但是如果直接通过命令(如:./test.sh)又可以正常执行,这主要是因为无法读取环境变量的原因。
解决方法: #
-
1、所有命令需要写成绝对路径形式,如: /usr/local/bin/docker。
-
2、在 shell 脚本开头使用以下代码:
#!/bin/sh . /etc/profile . ~/.bash_profile
-
3、在 /etc/crontab 中添加环境变量,在可执行命令之前添加命令 . /etc/profile;/bin/sh,使得环境变量生效,例如:
20 03 * * * . /etc/profile;/bin/sh /var/www/runoob/test.sh
使用crontab启动脚本,但找不到环境变量 pnpm go node
解决方法:
sudo ln -s /.... /usr/bin
问题11:Windows文件夹名,文件名不区分大小写 #
问题描述: #
使用Windows系统时,系统默认不区分文件夹和文件名的大小写。
func Test_path(t *testing.T) {
path1 := "E:\\视频取证测试\\live.mp4\\"
path2 := "E:\\视频取证测试\\live.MP4\\"
if !IsDirExist(path1) {
err := os.MkdirAll(path1, os.ModePerm)
if err != nil {
return
}
}
if !IsDirExist(path2) {
err := os.MkdirAll(path1, os.ModePerm)
if err != nil {
return
}
}
}
func IsDirExist(path string) bool {
fi, err := os.Stat(path)
if err != nil {
// fmt.Println("check dir failed:", err.Error())
return false
}
if fi.IsDir() {
return true
}
return false
}
解决方法: #
统一将字符串转小写,进行过滤改名。
并发必坑总结 #
清理数据时,不要频繁创建新切片 #
每次提交数据后,代码都会重新创建一个新切片。可以预先分配一个较大的切片并维护其大小,从而减少内存分配和垃圾回收的开销。
case <-tick: //五秒提交一次数据
if len(fileData) > 0 {
err := task.CommitData(file2.FileType, fileData)
if err != nil {
common.Log.Error(err.Error())
}
fileData = fileData[:0] //重置就行
}
if len(recoveredData) > 0 {
err := task.CommitData(file2.RecoveredFileType, recoveredData)
if err != nil {
common.Log.Error(err.Error())
}
recoveredData = recoveredData[:0]
}
if len(relateFiles) > 0 {
err := task.CommitData(file2.RelateBlockType, relateFiles)
if err != nil {
common.Log.Error(err.Error())
}
relateFiles = relateFiles[:0]
}
if len(pictureData) > 0 {
g.addPictureDataCount(int64(len(pictureData)))
pictureTree.ChildCount = g.PictureToTalCount - int64(len(pictureData))
pictureTree.ChildDeleteCount = g.DelPictureCount
pictureTree.WriteData(pictureData)
pictureData = make([]file2.PictureInfo, 0)
}
if len(videoData) > 0 {
g.addVideoDataCount(int64(len(videoData)))
tree.ChildCount = g.VideoTotalCount - int64(len(videoData))
tree.ChildDeleteCount = g.DelVideoCount
tree.WriteData(videoData)
videoData = make([]file2.VideoTapeInfo, 0)
}
使用 time.NewTicker
代替 time.Tick
#
time.Tick
会创建一个无法停止的定时器,可能会导致资源泄露。推荐使用 time.NewTicker
,这样可以在完成任务时停止定时器,避免内存泄漏。ticker的通道是单向通道,有利于代码接口的严谨性。
tick := time.Tick(time.Second * 3)
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop() // 确保定时器在任务结束时停止
for循环中带协程,要传参 #
var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
func main() {
wg.Add(len(nums))
for _, num := range nums {
go func() {
defer wg.Done()
fmt.Println(num)
}()
}
wg.Wait()
}
for循环中的goroutine在实际运行的时候,循环已经执行完毕了,num的值为循环后的最后一个值
var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
func main() {
wg.Add(len(nums))
for _, num := range nums {
go func(num *int) {
defer wg.Done()
fmt.Println(*num)
}(&num)
}
wg.Wait()
}
当你改成这样时,又掉入了下一个坑,go func()里保存了同一个内存地址
,即&num
在for循环
中指向的是同一个内存地址,但该地址上存储的值在for中不断发生变化。
假如我们想将处理后的结果保存起来
var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var results []int
func main() {
wg.Add(len(nums))
for _, num := range nums {
go func(num int) {
defer wg.Done()
results = append(results, num)
}(num)
}
wg.Wait()
fmt.Println(results)
}
如果你的结论是results中包含顺序不定的4 1 2 3 7 10 5 8 9 6
,那么恭喜你又入坑了!正常情况下,len(results)
的值应该为10,但上面代码多运行几次的结果表明,len(results)
的值几乎都是小于10的,如果你拿到了正确值,建议多跑几次。因为在go中,切片slice
类型是非并发安全的,也就是说results
中的某一个位置在同一时刻插入了多个值,最终造成了数据丢失。
用 for range 来遍历数组或者 map 的时候,被遍历的指针是不变的,每次遍历仅执行 struct 值的拷贝 #
func main(){
var stus []student
stus = []student{
{Name:"one", Age: 18},
{Name:"two", Age: 19},
}
data := make(map[int]*student)
for i, v := range stus{
data[i] = &v //应该改为:data[i] = &stus[i]
}
for i, v := range data{
fmt.Printf("key=%d, value=%v \n", i,v)
}
}
key=0, value=&{two 19}
key=1, value=&{two 19}
Go 中没有继承!Go 中是叫组合! #
import "fmt"
type student struct{
Name string
Age int
}
func (p *student) love(){
fmt.Println("love")
}
func (p *student) like(){
fmt.Println("like first")
p.love()
}
type boy struct {
student
}
func (b * boy) love(){
fmt.Println("hate")
}
func main(){
b := boy{}
b.like()
}
like first
love
并不是使用 new 就一定会在堆上分配内存 #
编译器会自动选择在栈上还是在堆上分配存储空间,但可能令人惊讶的是,这个选择并不是由用 var 还是 new 声明变量的方式决定的。
var global *int
func f() {
var x int x=1
global = &x
}
func g() {
y := new(int)
*y = 1
}
f()函数中的 x 就是在堆上分配内存,而 g()函数中的 y 就是分配在栈上。
init 函数在同一个文件中可以包含多个 #
在同一个包文件中,可以包含有多个 init 函数,多个 init 函数的执行顺序和定义顺序一致。
Golang 中没有“对象” #
type test struct {
name string
}
func (t *test) getName(){
fmt.Println("hello world")
}
func main() {
var t *test
t = nil
t.getName()
}
能正常输出吗?会报错吗?
输出为:
hello world
可以正常输出。Go 本质上不是面向对象的语言,Go 中是不存在 object 的含义的,Go 语言书籍中的对象也和 Java、PHP 中的对象有区别,不是真正的”对象”,是 Go 中 struct 的实体。
调用 getName 方法,在 Go 中还可以转换,转换为:Type.method(t Type, arguments)
所以,以上代码 main 函数中还可以写成:
func main() {
(*test).getName(nil)
}
map 引用不存在的 key,不报错 #
请问下面的例子输出什么,会报错吗?
func main(){
newMap := make(map[string]int)
fmt.Println(newMap["a"])
}
答案是:
不报错。不同于 PHP,Golang 的 map 和 Java 的 HashMap 类似,Java 引用不存在的会返回 null,而 Golang 会返回初始值