当前位置: 首页 > news >正文

Go中的匿名函数与闭包

关键词:

函数式编程 闭包 匿名函数 匿名函数特别适合作为函数或方法的回调

在Go中函数是一等公民,和string,int等一样。 而在C、C++ 等不支持匿名函数的语言中,函数不能在运行期创建

go 学习笔记之仅仅需要一个示例就能讲清楚什么闭包




闭包 与 普通函数的区别


在(普通)函数里面定义一个内部函数(匿名函数),并且这个内部函数(匿名函数)用到了外面(普通)函数的变量,那么将这个内部函数和用到的一些变量统称为闭包

  • 在闭包中,既有函数,又有数据,而且(其内部定义的)数据是闭包里面独有的数据,与外界无影响;

  • (普通)函数中,需要使用的全局变量,在一定程度上是受到限制的,因为全局变量不仅仅是一个函数使用,其他的函数也可能会使用到,一旦修改会影响到其他函数使用全局变量,所以全局变量不能随便修改从而在函数的使用中受到一定局限性




匿名函数和闭包的关系


简单来说匿名函数是指不需要定义函数名的一种函数实现方式。匿名函数是由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必声明(一个子方法)所以(在某些场景下)被广泛使用

关于闭包的定义存在以下广泛流传的公式:闭包=函数+引用环境。函数指的是匿名函数,引用环境指的是编译器发现闭包,直接将闭包引用的外部变量在堆上分配空间;当闭包引用了函数的内部变量(即局部变量)时,每次调用的外部变量数据都会跟随闭包的变化而变化,闭包函数和外部变量是共享的。
显然,闭包只能通过匿名函数实现,可以把闭包看作是有状态的匿名函数,反过来,如果匿名函数引用了外部变量,就形成了一个闭包

Go 函数式编程篇(三):匿名函数和闭包

一般来说,一个函数返回另外一个函数,这个被返回的函数可以引用外层函数的局部变量,这形成了一个闭包。在Go中,「闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的上下文环境(相当于一个符号查找表)」

type closure struct {
   F uintptr   // 函数指针,代表着内部匿名函数
   x *int      // 自由变量x,代表着对外部环境的引用
}

Go函数闭包底层实现

在Go,PHP中,匿名函数可以认为就是闭包(Go 规范和 FAQ 都这么说了 ),哪怕这个匿名函数没有入参,没有引用外部的变量,也没有任何返回值,如
func(){
    print(123)
    }()

严格来说,这其实只是个匿名函数, 不算闭包。

但Go里称其为闭包也ok,即模糊了匿名函数和闭包的界限(有引用外部变量的匿名函数为闭包)


一道 Go 闭包题,面试官说原来自己答错了:面别人也涨知识




一些例子


无参数也无返回值的匿名函数


package main

import (
 "fmt"
)

func main() {

 f := func() {
  fmt.Println("不加括号就只是定义,赋值给f,可通过f()来调用")
 }

 f()
 fmt.Printf("变量f的类型为: %T\n", f) // func()

 
 
 
 // 下面这种方式定义,只在此调用一次,不如上面的方式,可以随时复用
 fmt.Println("--------------")
 func() {
  fmt.Println("而加上最后加上()就是直接调用(这种方式只能在此调用一次,没法复用了)")
 }()

}

输出:

不加括号就只是定义,赋值给f,可通过f()来调用
变量f的类型为: func()
--------------
而加上最后加上()就是直接调用(这种方式只能在此调用一次,没法复用了)

带参数的匿名函数


package main

import (
 "fmt"
)

func main() {

 i := 0
 // 后面有(),一次执行
 func(i int) {
  fmt.Println(i + 1)
 }(i)

 i = -100000

 
 
 // 赋值给add,可通过add()方式多次调用
 add := func(k int) {
  fmt.Println(k + 6)
 }
 add(200)

}

输出:

1
206

配合defer,可以使问题非常复杂。也是高阶面试常问的~

变形1:


package main

import (
 "fmt"
)

func main() {

 i := 0
 // 后面有(),一次执行
 defer func(i int) {
  fmt.Println(i + 1)
 }(i)

 i = -100000

 // 赋值给add,可通过add()方式多次调用
 add := func(k int) {
  fmt.Println(k + 6)
 }
 add(200)

}

输出:

206
1

目前还好理解,defer在return时执行(确切地说,是在return和计算return值的中间执行)


变形2:


package main

import (
 "fmt"
)

func main() {

 i := 0
 // 后面有(),一次执行
 defer func(k int) {
  fmt.Println(i + 1)
 }(i)

 i = -100000

 // 赋值给add,可通过add()方式多次调用
 add := func(k int) {
  fmt.Println(k + 6)
 }
 add(200)

}

输出:

206
-99999

如果有人说Go简单,可以请其解释一下这个输出..


有返回值的匿名函数


package main

import "fmt"

func main() {

 name := "张三"

 say := func(name string) string {
  return "hello " + name
 }

 res := say(name)

 fmt.Println(res) //hello 张三
}

当返回值是匿名函数


package main

import "fmt"

func main() {

 a := Fun()

 b := a("hello ")
 c := a("hello ")

 d := Fun()
 e := d("hello ")
 f := d("hello ")
 fmt.Println(b) //world+hello
 fmt.Println(c) //world+hello hello
 fmt.Println(e) //world+hello
 fmt.Println(f) //world+hello hello
}
func Fun() func(string) string {
 rs := "world+"
 return func(args string) string {
  rs += args
  return rs
 }
}

等同于

package main

import "fmt"

func main() {

 cui := func() func(string) string {
  rs := "world+"
  return func(args string) string {
   rs += args
   return rs
  }
 }

 a := cui()
 b := a("hello ")
 c := a("hello ")

 d := cui()
 e := d("hello ")
 f := d("hello ")
 fmt.Println(b) //world+hello
 fmt.Println(c) //world+hello hello
 fmt.Println(e) //world+hello
 fmt.Println(f) //world+hello hello
}

参考自 GO 匿名函数和闭包[1]


当参数是匿名函数


参考下方[回调函数:闭包可以用作回调函数(例如在异步编程中,可以捕获外部函数的上下文) && 高阶函数:闭包可以用作高阶函数的参数,并在调用时返回新的函数?(将匿名函数作为函数参数;可以让该函数执行多种不同逻辑)]( "回调函数:闭包可以用作回调函数(例如在异步编程中,可以捕获外部函数的上下文) && 高阶函数:闭包可以用作高阶函数的参数,并在调用时返回新的函数?(将匿名函数作为函数参数;可以让该函数执行多种不同逻辑)")

多个匿名函数


package main

import "fmt"

func main() {
 f1, f2 := F(12)
 fmt.Println(f1(4)) //6
 fmt.Println(f2())  //6
}
func F(x, y int) (func(int) intfunc() int) {

 f1 := func(z int) int {
  return (x + y) * z / 2
 }

 f2 := func() int {
  return 2 * (x + y)
 }
 return f1, f2
}



常见使用场景



私有数据:闭包可以捕获函数内部的数据,并且对外部不可见。这是一种创建私有数据的方法(保证局部变量的安全性)


package main

import "fmt"

func main() {

 var j int = 1

 f := func() {
  var i int = 1 // i 在闭包内部定义,其值被隔离,不能从外部修改
  fmt.Printf("i, j: %d, %d\n", i, j)
 }

 f()
 j += 2
 f() // 对比下面的输出,可见并不是调用时刻的值,而只是记录变量的引用

 defer f()
 j += 10000
}

输出:

i, j: 11
i, j: 13
i, j: 110003

package main

import (
 "fmt"
)

func main() {
 accumulator := SomeFunc() //使用accumulator变量接收一个闭包

 // 累加计数并打印
 fmt.Println("The first call CallNum is ", accumulator()) //运行结果为:The first call CallNum is 1
 // 累加计数并打印
 fmt.Println("The second call CallNum is ", accumulator()) //运行结果为:The second call CallNum is 2
}

func SomeFunc() func() int { // 创建一个函数,返回一个闭包,闭包每次调用函数会对函数内部变量进行累加
 var CallNum = 0 //函数调用次数,系函数内部变量,外部无法访问,仅当函数被调用时进行累加

 return func() int { // 返回一个闭包
  CallNum++ //对value进行累加
  //实现函数具体逻辑
  return CallNum // 返回内部变量value的值
 }
}

输出:

The first call CallNum is  1
The second call CallNum is  2

通过闭包既没有暴露CallNum这个变量,又实现了为函数计数的目的


回调函数:闭包可以用作回调函数(例如在异步编程中,可以捕获外部函数的上下文) && 高阶函数:闭包可以用作高阶函数的参数,并在调用时返回新的函数?(将匿名函数作为函数参数;可以让该函数执行多种不同逻辑)


Go基础系列:函数(2)——回调函数和闭包 [2]

参考自 【Go基础】搞懂函数回调和闭包[3]

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。
日常开发中,可以将函数B作为另一个函数A的参数,可以使得函数A的通用性更强(可随意定义函数B,只要满足规则,函数A都可以去处理),这比较适合于回调函数。

下面看几个简单的例子来理解回调:

package main

import "fmt"

type Callback func(x, y int) int

// 提供一个接口,让外部去实现
func test1(x, y int, callback Callback) int {
 return callback(x, y)
}

// 回调函数的具体实现
func calculationXOR(x, y int) int {
 return x ^ y
}
func calculationAND(x, y int) int {
 return x & y
}

// 回调函数的具体实现
func main() {
 fmt.Println(test1(23, calculationXOR)) //这样调用test1就能实现异或 以及 与的运算
 fmt.Println(test1(23, calculationAND))
}
1
2

再看个简单例子:将字符串转为Int,转换失败时执行回调函数,输出错误信息

package main

import (
 "fmt"
 "strconv"
)

type Callback func(msg string)

// 将字符串转换为int64,如果转换失败调用Callback
func stringToInt(s string, callback Callback) int64 {
 if value, err := strconv.ParseInt(s, 00); err != nil {
  callback(err.Error())
  return 0
 } else {
  return value
 }
}

// 记录日志消息的具体实现
func errLog(msg string) {
 fmt.Println("Convert error(转换发生了错误!): ", msg)
}

func main() {
 fmt.Println(stringToInt("18", errLog))
 fmt.Println(stringToInt("hh", errLog))
}

输出:

18
Convert error(转换发生了错误!):  strconv.ParseInt: parsing "hh": invalid syntax

下面这个例子和第一个类似:


package main

import "fmt"

func main() {

 // 普通的加法操作
 add1 := func(a, b int) int {
  return a + b
 }

 // 定义另一种加法规则(即  加数*10+第二个加数)
 base := 10
 add2 := func(a, b int) int {
  return a*base + b
 }

 handleAdd(12, add1)
 handleAdd(12, add2)
}

// 将匿名函数作为参数
func handleAdd(a, b int, call func(intint) int) {
 fmt.Println(call(a, b))
}

输出:

3
12

这样就可以通过一个函数执行多种不同加法实现算法,提升代码的复用性


可以基于这个功能特性实现一些更复杂的业务逻辑,如 Go 官方 net/http 包底层的路由处理器[4]也是这么实现的:

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 DefaultServeMux.HandleFunc(pattern, handler)
}

Go源码中还有非常多的将func作为参数的高阶函数,参数的func即回调函数,更多可参考

可通过关键字func(检索


延迟计算:闭包可以延迟计算,直到闭包被调用时才执行计算(将匿名函数作为函数返回值)


package main

import "fmt"

// 将函数作为返回值
func deferAdd(a, b int) func() int {
 return func() int {
  return a + b
 }
}

func main() {

 // 此时返回的是匿名函数
 addFunc := deferAdd(12)

 // 这里才会真正执行加法操作
 fmt.Println(addFunc()) // 3

}

Go函数闭包底层实现


易错问题


循环里打印出的都是最后一个值


case1


package main

import "fmt"

func main() {

 // 此时a是F()的返回值,即一个[]func()
 //a := F()
 a := func() []func() {
  b := make([]func(), 3, 3)
  for i := 0; i < 3; i++ {
   b[i] = func() {
    fmt.Println(&i, i)
   }
  }
  return b
 }()

 a[0]( "0"//0x140000200c8 3
 a[1]( "1"//0x140000200c8 3
 a[2]( "2"//0x140000200c8 3
}

//func F() []func() {
// b := make([]func(), 3, 3)
// for i := 0; i < 3; i++ {
//  b[i] = func() {
//   fmt.Println(&i, i)
//  }
// }
// return b
//}

解决办法:

每次复制变量 i 然后传到匿名函数中,让闭包的环境变量不相同。

package main

import "fmt"

func main() {
 a := F()
 a[0]( "0"//0x14000128008 0
 a[1]( "1"//0x14000128010 1
 a[2]( "2"//0x14000128018 2
}
func F() []func() {
 b := make([]func(), 3, 3)
 for i := 0; i < 3; i++ {
  b[i] = (func(j int) func() {
   return func() {
    fmt.Println(&j, j)
   }
  })(i)
 }
 return b
}

//或者
//package main
//
//import "fmt"
//
//func main() {
// a := F()
// a[0]( "0") //0xc00004c080 0
// a[1]( "1") //0xc00004c088 1
// a[2]( "2") //0xc00004c090 2
//}
//func F() []func() {
// b := make([]func(), 3, 3)
// for i := 0; i < 3; i++ {
//  j := i
//  b[i] = func() {
//   fmt.Println(&j, j)
//  }
// }
// return b
//}


参考自 GO 匿名函数和闭包[5]


package main

import "fmt"

func main() {
 // 保存函数闭包
 var s []func()

 for _, v := range []string{"a""b""c""d""e"} {
  s = append(s, func() {
   // 捕获v, 保存在闭包中
   fmt.Printf("value: %v\n", v)
  })
 }

 for _, f := range s {
  f()
 }
}

输出:

value: e
value: e
value: e
value: e
value: e

闭包中捕获的v不是"值", 而是"有地址的变量"(如GoLang闭包,注意!这里有蹊跷 中图1所示),且创建闭包时,循环变量的值已经被确定,并与闭包关联。当闭包被调用时,它使用捕获的值,而不是当前值,解决的关键就在于重新声明变量,这样每个闭包都有自己的变量,能够正确地访问其所需的值


case2(for range+Goroutine 使用闭包不当)


package main

import (
 "fmt"
 "time"
)

func main() {
 tests1ice := []int{12345}
 for _, v := range tests1ice {

  go func() {
   fmt.Println(v)
  }()
 }
 time.Sleep(2 * time.Second)
}

5
5
5
5
5

由于没有在Goroutine中对切片执行写操作,所以首先排除了内存屏障的问题,最终还是通过反编译查看汇编代码,发现Goroutine打印的变量v,其实是地址引用,Goroutine执行的时候变量v所在地址所对应的值已经发生了变化,汇编代码如下:

for _, v := range tests1ice {
  499224:       48 805 f5 af 00 00    lea    0xaff5(%rip),%rax        # 4a4220 <type.*+0xa220>
  49922b:       48 89 04 24             mov    %rax,(%rsp)
  49922f:       e8 83a f7 ff          callq  40ccc0 <runtime.newobject>
  499234:       48 844 24 08          mov    0x8(%rsp),%rax
  499239:       48 89 44 24 48          mov    %rax,0x48(%rsp)
  49923e:       31 c9                   xor    %ecx,%ecx
  499240:       eb 3e                   jmp    499280 <main.main+0xc0>
  499242:       48 89 424 18          mov    %rcx,0x18(%rsp)
  499247:       48 854 cc 20          mov    0x20(%rsp,%rcx,8),%rdx
  49924c:       48 89 10                mov    %rdx,(%rax)
                go func() {
  49924f:       c7 04 24 08 00 00 00    movl   $0x8,(%rsp)
  499256:       48 815 f3 b7 02 00    lea    0x2b7f3(%rip),%rdx        # 4c4a50 <go.func.*+0x6c>
  49925d:       48 89 54 24 08          mov    %rdx,0x8(%rsp)
  499262:       48 89 44 24 10          mov    %rax,0x10(%rsp)
  499267:       e8 54 3a fa ff          callq  43ccc0 <runtime.newproc>

解决方案一:在参数方式向匿名函数传递值引用

package main

import (
  "fmt"
  "time"
)

func main() {
  tests1ice := []int{12345}
  for _, v := range tests1ice {
   w := v
   go func(w int) {
    fmt.Println(w)
   }(w)
  }
  time.Sleep(time.Second)
}
2
4
5
1
3


解决方案二:在调用gorouinte前将变量进行值拷贝

package main

import (
 "fmt"
 "time"
)

func main() {
 tests1ice := []int{12345}
 for _, v := range tests1ice {
  w := v
  go func() {
   fmt.Println(w)
  }()
 }
 time.Sleep(time.Second)
}

1
3
2
5
4

Go的闭包看你犯错,Rust却默默帮你排坑


另外的例子:

package main

import (
 "fmt"
 "time"
)

func main() {
 s := []int{123}
 for _, v := range s {
  go func() {
   fmt.Println(v) // 输出结果3 3 3
  }()
 }

 time.Sleep(1e9)
}

无法得到预期结果1,2,3的原因是在没有将变量 v 的拷贝值传进匿名函数之前,只能获取最后一次循环的值,是新手最容易遇到的坑之一。有效规避方式为每次将变量v的拷贝传进函数:

package main

import (
 "fmt"
 "time"
)

func main() {

 s := []int{123}
 for _, v := range s {
  go func(v int) {
   fmt.Println(v) // 输出结果1,2,3或 1,3,2 或其他顺序
  }(v)
 }

 time.Sleep(1e9)
}



搭配defer使用:往defer里传入一个闭包,虽然是值传递,但是拷贝的是函数指针,可以解决一些使用defer会立刻拷贝函数中引用的外部参数引起的时机问题。

package main

import "fmt"

func main() {
 x, y := 12

 defer func(a int) {
  fmt.Printf("x:%d,y:%d\n", a, y) // y 为闭包引用,最终结果为x:1,y:102
 }(x) // 复制 x 的值

 x += 100
 y += 100
}

无法得到期待的结果x:1,y:2的原因是:defer 调用会在当前函数执行结束前才被执行,这些调用被称为延迟调用,而defer 中使用匿名函数是一个闭包,y为闭包引用的外部变量会跟着闭包环境变化,当延迟调用时y已经变成102,所以最终输出的y也不再是2了。

有效规避方式只需要去掉defer即可

参考资料

[1]

GO 匿名函数和闭包: https://segmentfault.com/a/1190000018689134

[2]

Go基础系列:函数(2)——回调函数和闭包 : https://www.cnblogs.com/f-ck-need-u/p/9878898.html

[3]

【Go基础】搞懂函数回调和闭包: https://blog.csdn.net/dl962454/article/details/123460053

[4]

路由处理器: https://github.com/golang/go/blob/master/src/net/http/server.go#L2573

[5]

GO 匿名函数和闭包: https://segmentfault.com/a/1190000018689134

本文由 mdnice 多平台发布

相关文章:

Go中的匿名函数与闭包

关键词: 函数式编程 闭包 匿名函数 匿名函数特别适合作为函数或方法的回调 在Go中函数是一等公民&#xff0c;和string&#xff0c;int等一样。 而在C、C 等不支持匿名函数的语言中&#xff0c;函数不能在运行期创建 go 学习笔记之仅仅需要一个示例就能讲清楚什么闭包 闭包 与…...

中文分词工具jieba的使用

1.jieba简介 在自然语言处理任务时中文文本需要通过分词获得单个的词语,这个时候就需要用到中文分词工具jieba jieba分词是一个开源项目,地址为github.com/fxsjy/jieba 它在分词准确度和速度方面均表现不错。 2.jieba的安装 全自动安装pip install jieba / pip3 install …...

CTF Stegano练习之隐写初探

今天要介绍的是CTF练习中的Stegano隐写题型 。做隐写题的时候&#xff0c;工具是很重要的&#xff0c;接下来介绍一些工具。 1、TrID TrID是一款根据文件二进制数据特征进行判断的文件类型识别工具。虽然也有类似的文件类型识别工具&#xff0c;但是大多数都是使用硬编码的识…...

大数据课程H2——TELECOM的电信流量项目实现

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解TELECOM项目的数据收集; ⚪ 了解TELECOM项目的数据清洗; ⚪ 了解TELECOM项目的数据导出; ⚪ 了解TELECOM项目的数据可视化; ⚪ 了解TELECOM项目的其他; 一、数据收集 1. 在实…...

Langchain module ‘hnswlib‘ has no attribute ‘Index‘ 错误解决

Langchain module hnswlib has no attribute Index 错误解决 使用 Langchain 操作 Chroma 向量数据库时&#xff0c;报一下错误信息&#xff0c; module hnswlib has no attribute Index试着重装了不同 hnswlib 版本没有解决&#xff0c;最后解决方法是&#xff0c;不要使用 h…...

HIVE学习

1.什么是HIVE 1.HIVE是什么? Hive是由Facebook开源&#xff0c;基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。 大白话: HIVE就是一个类似于Navicat的可视化客户端, 2.HIVE本质 Hive是一个Hadoop客户端&a…...

逆了个天了,阿里开源自然语言写SQL的神器级别工具快用起来

Chat2DB 是一款有开源免费的多数据库客户端工具&#xff0c;支持windows、mac本地安装&#xff0c;也支持服务器端部署&#xff0c;web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力&#xff0c;能够将自然语言转换为SQL&#xff0c;也可以…...

85. 最大矩形

题目描述 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵&#xff0c;找出只包含 1 的最大矩形&#xff0c;并返回其面积。 示例 1&#xff1a; 输入&#xff1a;matrix [["1","0","1","0","0"],["1…...

Vue [Day5]

自定义指令 全局注册 和 局部注册 inserted在指令所在的元素 被插入到页面中时&#xff0c;触发 main.js import Vue from vue import App from ./App.vueVue.config.productionTip false// 1.全局注册指令 Vue.directive(focus, {// inserted在指令所在的元素 被插入到页…...

备战大型攻防演练,“3+1”一套搞定云上安全

在重大活动保障期间&#xff0c;企业不仅要面对愈发灵活隐蔽的新型攻击挑战&#xff0c;还要在人员、精力有限的情况下应对不分昼夜的高强度安全运维任务。如何在这种多重压力下&#xff0c;从“疲于应付”迈向“胸有成竹”呢&#xff1f; 知己知彼&#xff0c;百战不殆&#…...

网络_每日一学——网络的整体概述

今天我们将继续探讨网络相关的知识。网络是由许多设备互相连接而成的&#xff0c;可以传输数据的系统。通过网络&#xff0c;我们可以远程访问他人的计算机、浏览网页、发送电子邮件等。网络是信息时代中不可或缺的一部分。 在网络中&#xff0c;每个设备都有一个唯一的标识符…...

【ChatGPT 指令大全】怎么使用ChatGPT来帮我们写作

在数字化时代&#xff0c;人工智能为我们的生活带来了无数便利和创新。在写作领域&#xff0c;ChatGPT作为一种智能助手&#xff0c;为我们提供了强大的帮助。不论是作文、文章&#xff0c;还是日常函电&#xff0c;ChatGPT都能成为我们的得力助手&#xff0c;快速提供准确的文…...

Redis 如何解决缓存雪崩、缓存击穿、缓存穿透难题

前言 Redis 作为一门热门的缓存技术&#xff0c;引入了缓存层&#xff0c;就会有缓存异常的三个问题&#xff0c;分别是缓存击穿、缓存穿透、缓存雪崩。我们用本篇文章来讲解下如何解决&#xff01; 缓存击穿 缓存击穿: 指的是缓存中的某个热点数据过期了&#xff0c;但是此…...

SSRF(服务器端请求伪造)漏洞

CSRF漏洞与SSRF漏洞的主要区别在于伪造目标的不同。 一、SSRF是什么 SSRF漏洞&#xff1a;&#xff08;Server-Side Request Forgery&#xff0c;服务器端请求伪造&#xff09;是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下&#xff0c;SSRF攻击的目标是从…...

【Axure动态面板】利用动态面板实现树形菜单的制作

利用动态面板&#xff0c;简单制作高保真的树形菜单。 一、先看效果 https://1poppu.axshare.com 二、实现思路 1、菜单无非就是收缩和展开&#xff0c;动态面板有个非常好的属性&#xff1a;fit to content&#xff0c;这个属性的含义是&#xff1a;面板的大小可以根据内容多少…...

Android 实现 RecyclerView下拉刷新,SwipeRefreshLayout上拉加载

上拉、下拉的效果图如下&#xff1a; 使用步骤 1、在清单文件中添加依赖 implementation ‘com.android.support:recyclerview-v7:27.1.1’ implementation “androidx.swiperefreshlayout:swiperefreshlayout:1.0.0” 2、main布局 <LinearLayout xmlns:android"http…...

使用MethodInterceptor和ResponseBodyAdvice做分页处理

目录 一、需求 二、代码实现 父pom文件 pom文件 配置文件 手动注册SqlSessionFactory&#xff08;MyBatisConfig &#xff09; 对象 实体类Users 抽象类AbstractQuery 查询参数类UsersQuery 三层架构 UsersController UsersServiceImpl UsersMapper UsersMapper.…...

WEB集群——LVS-DR 群集、nginx负载均衡

1、基于 CentOS 7 构建 LVS-DR 群集。 2、配置nginx负载均衡。 一、 LVS-DR 群集 1、LVS-DR工作原理 LVS-DR&#xff08;Linux Virtual Server Director Server&#xff09; 名称缩写说明 虚拟IP地址(Virtual IP Address) VIPDirector用于向客户端计算机提供服务的IP地址真实…...

倒计时87天!软考初级信息处理技术员2023下半年报名考试攻略

软考初级信息处理技术员2023下半年报名条件&#xff1a; 1、凡遵守中华人民共和国宪法和各项法律&#xff0c;恪守职业道德&#xff0c;具有一定计算机技术应用能力的人员&#xff0c;均可根据情况报名参加相应专业类别、级别的考试。 2、获准在中华人民共和国境内就业的外籍…...

【腾讯云 Cloud Studio 实战训练营】使用Cloud Studio构建SpringSecurity权限框架

1.Cloud Studio&#xff08;云端 IDE&#xff09;简介 Cloud Studio 是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供了一个永不间断的云端工作站。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器就能在线编程。 Clou…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...