8 channel、反射、网络编程【Go语言教程】

news2024/11/17 13:27:42

8 channel、反射、网络编程【Go语言教程】

1 channel

1.1 概念及快速入门

channel:管道,主要用于不同goroutine之间的通讯

需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到 map 中。最后显示出来。要求使用 goroutine 完成

分析思路:

  1. 使用 goroutine 来完成,效率高,但是会出现并发/并行安全问题.
  2. 这里就提出了不同 goroutine 如何通信的问题

在这里插入图片描述

package main
import(
	"fmt"
	"time"
)

var (
	myMap = make(map[int]int, 10)
)

//计算n!,将计算结果放入myMap
func test(n int){
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	myMap[n] = res //concurrent map writes?
}

func main(){
	//开启200个协程
	for i := 1; i <= 200; i++ {
		go test(i)
	}
	//休眠10s[防止主线程直接跑完,而协程中的任务未完成]
	time.Sleep(time.Second * 10)
	for i, v := range myMap {
		fmt.Printf("map[%d]=%d\n", i, v)
	}
}

运行代码会出现并发问题:
在这里插入图片描述

不同goroutine之间如何通讯:

  1. 全局变量的互斥锁
  2. 使用管道channel来解决

计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到 map 中。最后显示出来。用下面方法改进:

①使用全局变量的互斥锁[并发、并行问题]

  • 没有对全局变量m加索,会出现资源争夺问题,提示:concurrent map writes

  • 加入互斥锁

  • 我们的数的阶乘很大,结果会越界,可以将求阶乘改成 sum += i

package main
import(
	"fmt"
	"time"
	"sync"
)

var (
	myMap = make(map[int]int, 10)
	//声明一个全局的互斥锁
	//lock 是一个全局的互斥锁
	//sync 是包, synchronized 同步
	//Mutex:是互斥
	lock sync.Mutex
)

//计算n!,将计算结果放入myMap
func test(n int){
	res := 1
	for i := 1; i <= n; i++ {
		res += int(i)
	}
	//加锁
	lock.Lock()
	myMap[n] = res //concurrent map writes?
	//解锁
	lock.Unlock()
}

func main(){
	//开启200个协程
	for i := 1; i <= 200; i++ {
		go test(i)
	}
	//休眠10s[防止主线程直接跑完,而协程中的任务未完成]
	time.Sleep(time.Second * 10)
	for i, v := range myMap {
		fmt.Printf("map[%d]=%d\n", i, v)
	}
}

在这里插入图片描述

②使用channel

1. 为什么需要channel
  • 为什么需要channel?
  1. 前面使用全局变量加锁同步来解决 goroutine 的通讯,但不完美
  2. 主线程在等待所有 goroutine 全部完成的时间很难确定,我们这里设置 10 秒,仅仅是估算。
  3. 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有 goroutine 处于工作状态,这时也会随主线程的退出而销毁
  4. 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
  5. 上面种种分析都在呼唤一个新的通讯机制-channel
2. 基本介绍
  1. channle 本质就是一个数据结构-队列【示意图】
  2. 数据是先进先出【FIFO : first in first out】
  3. 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
  4. channel 有类型的,一个 string 的 channel 只能存放 string 类型数据。
    在这里插入图片描述
3. 声明、定义channel
  • var 变量名 chan 数据类型
    举例:
    var intChan chan int (intChan 用于存放 int 数据)
    var mapChan chan map[int]string (mapChan 用于存放 map[int]string 类型) var perChan chan Person
    var perChan2 chan *Person
  • 说明:
    channel 是引用类型
    channel 必须初始化才能写入数据, 即 make 后才能使用管道是有类型的,intChan 只能写入 整数 int
4. 快速入门及注意事项

(1)快速入门:

管道的初始化,写入数据到管道,从管道读取数据及基本的注意事项

package main
import (
	"fmt"
)

func main(){
	//1. 创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int, 3)
	//2. 看看intChan是什么
	fmt.Printf("intChan的值=%v intChan本身地址=%p\n", intChan, &intChan)
	//3. 向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- -50
	//intChan <- 100 //注意:我们在给管道写入数据时,不能超过其容量
	//4. 看看管道的长度和cap(容量)
	fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan))
	//5. 从管道中读取数据
	var num2 int
	num2 = <- intChan
	fmt.Println("num2=", num2)
	fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan))

	//6. 在没有使用协程的情况下,如果管道中的数据已经全部取出,再取就会报告deadlock
	num3 := <- intChan
	num4 := <- intChan
	fmt.Printf("num3=%v, num4=%v", num3, num4)
	// num5 := <- intChan
	// fmt.Println("num5=", num5) //fatal error: all goroutines are asleep - deadlock!   
}

在这里插入图片描述
(2)注意事项:

  1. channel 中只能存放指定的数据类型
  2. channle 的数据放满后,就不能再放入了
  3. 如果从 channel 取出数据后,可以继续放入
  4. 在没有使用协程的情况下,如果 channel 数据取完了,再取,就会报 dead lock
5. channel的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
练习题:

在这里插入图片描述

package main
import (
	"fmt"
	"math/rand"
	"time"
	"strconv"
)

type Person struct {
	Name string
	Age int
	Address string
}

func main(){
	var personChan chan Person
	//make给chan开辟空间
	personChan = make(chan Person, 10)
	//取纳秒时间戳作为种子,保证每次的随机种子都不同
	//给rand种种子
	rand.Seed(time.Now().UnixNano())
	for i := 1; i <= 10; i++ {
		index := rand.Int()
		fmt.Println("index===", index)
		person := Person{
			Name: "zhangsan" + strconv.Itoa(index),
			Age: i,
			Address: "beijing" + strconv.Itoa(index),
		}
		personChan <- person
	}
	len := len(personChan)
	for i := 0; i < len; i++ {
		p := <- personChan
		fmt.Println(p)
	}
}
6. channel的关闭和遍历

(1)channel的关闭

使用内置函数close 可以关闭 channel, 当 channel 关闭后,就不能再向 channel 写数据了,但是仍然可以从该 channel 读取数据

package main
import (
	"fmt"
)

func main(){
	intChan := make(chan int, 5)
	intChan <- 10
	intChan <- 20
	close(intChan) //close
	//关闭之后不能再向chan写入数据,但是可以读取
	// intChan <- 30 //panic: send on closed channel
	n1 := <- intChan
	fmt.Println("n1=", n1) //n1= 10
}

(2)channel的遍历

channel 支持 for–range 的方式进行遍历,请注意两个细节

  1. 在遍历时,如果 channel 没有关闭,则回出现 deadlock 的错误
  2. 在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
package main
import (
	"fmt"
)

func main(){
	intChan := make(chan int, 5)
	intChan <- 10
	intChan <- 20
	intChan <- 50
	close(intChan) //close
	//关闭之后不能再向chan写入数据,但是可以读取
	// intChan <- 30 //panic: send on closed channel
	n1 := <- intChan
	fmt.Println("n1=", n1) //n1= 10
	for v := range intChan {
		fmt.Printf("value=%v\n", v)
	}
}
7. 综合实例
  • 需求:
    要求统计 1-8000的数字中,哪些是素数?使用 goroutine
    和 channel 的知识完成
  • 分析思路:
    传统的方法,就是使用一个循环,循环的判断各个数是不是素数【ok】。
    使用并发/并行的方式,将统计素数的任务分配给多个(4 个)goroutine 去完成,完成任务时间短。
    在这里插入图片描述
package main
import (
	"fmt"
	"time"
)

//向intChan放入1-8000个数
func putNum(intChan chan int){
	for i := 1; i <= 8000; i++ {
		intChan <- i
	}
	//关闭chan
	close(intChan)
}

//从intChan取出数据,并判断是否是素数,如果是,就放入到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool){
	var flag bool
	for {
		time.Sleep(time.Millisecond * 10)
		num, ok := <- intChan
		if !ok { //取不到数据了,就退出
			break
		}
		flag = true //假设是素数
		for i := 2; i < num; i++ {
			if num % i == 0 {
				flag = false
				break
			}
		}
		if flag {
			primeChan <- num
		}
	}
	fmt.Println("有一个primeNum协程因为取不到数据,退出")
	//这里我们还不能关闭primeChan
	//向exitChan写入true
	exitChan <- true
}

func main(){
	intChan := make(chan int, 1000)
	primeChan := make(chan int, 2000)
	//标识退出的管道
	exitChan := make(chan bool, 4)
	//开启一个协程,向intChan放入1-8000个数
	go putNum(intChan)
	//开启4个协程,从intChan取出数据,并判断是否是素数
	for i := 0; i < 4; i++ {
		go primeNum(intChan, primeChan, exitChan)
	}
	//主线程进行处理
	go func(){
		for i:= 0; i < 4; i++{
			<-exitChan
		}
		//当我们从exitChan取出了4个结果,就可以放心的关闭primeChan
		close(primeChan)
	}()

	//遍历primeChan,把结果取出
	for {
		res, ok := <- primeChan
		if !ok {
			break
		}
		//将结果取出
		fmt.Printf("素数=%d\n", res)
	}
}

结果:
在这里插入图片描述

8. 只读、只写管道及注意事项
  • 只读、只写管道:
    在这里插入图片描述
    在这里插入图片描述
  • 注意事项
  1. channel 可以声明为只读,或者只写性质
  2. 只读只写案例
    在这里插入图片描述
    3)使用 select 可以解决从管道取数据的阻塞问题

select用法:

package main
import (
	"fmt"
	"time"
)

func main(){
	//使用select可以解决管道取数据的阻塞问题
	//1. 定义一个管道 10 int
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
		intChan <- i
	}
	//2. 定义一个管道 5 string
	stringChan := make(chan string, 5)
	for i := 0; i < 5; i++ {
		stringChan <- "hello" + fmt.Sprintf("%d", i)
	}
	//传统的方法在遍历管道时,如果不关闭,则会因阻塞导致deadlock
	//可是我们在实际开发中,我们可能不好确定什么时候关闭管道
	//办法:我们可以使用select方式解决
	//label:
	for {
		select {
			//注意:这里,如果intChan一直没有关闭,不会一直阻塞而deadlock,会自动到下一个case匹配
			case v := <- intChan:
				fmt.Printf("从intChan读取的数据=%d\n", v)
				time.Sleep(time.Second)
			case v := <- stringChan:
				fmt.Printf("从stringChan读取的数据=%s\n", v)
				time.Sleep(time.Second)
			default:
				fmt.Printf("不玩了,都取不到了【程序员可以在这里加入自己的逻辑】\n")
				time.Sleep(time.Second)
				return
				//break label
		}
	}
}

在这里插入图片描述

2 反射

2.1 概念

  1. 反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
  3. 通过反射,可以修改变量的值,可以调用关联的方法。
  4. 使用反射,需要 import (“reflect”)
    在这里插入图片描述
    5)反射常见的应用场景
    在这里插入图片描述

2.2 反射中重要的函数

在这里插入图片描述

  1. 变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。
    在这里插入图片描述

2.3 快速入门

请编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作

package main
import (
	"reflect"
	"fmt"
)

type Student struct {
	Name string
	Age int
}

func reflectTest01(b interface{}){
	//通过反射获取到传入变量的type、kind值
	//1. 先获取到reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)
	//2. 获取到reflect.Value
	rVal := reflect.ValueOf(b)
	n2 := 2 + rVal.Int()
	fmt.Println("n2=", n2)
	fmt.Printf("rVal=%v rType=%T\n", rVal, rType)
	//下面我们将rVal转成interface{}
	iV := rVal.Interface()
	//将interface{}通过断言转成需要的类型
	num2 := iV.(int)
	fmt.Println("num2=", num2)
}

//对结构体的反射
func reflectTest02(b interface{}){
	//通过反射获取到传入的变量的type、kind,值
	//1. 先获取到reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)
	//2. 获取到reflect.Value
	rVal := reflect.ValueOf(b)
	//下面我们将rVal转成interface{}
	iV := rVal.Interface()
	fmt.Printf("iv=%v iv type=%T\b", iV, iV)
	//将interface{}通过断言转成需要的类型
	//这里,我们使用类型断言【同学们可以使用switch的断言形式来更加灵活的判断】
	stu, ok := iV.(Student)
	if ok {
		fmt.Printf("stu.Name=%v\n", stu.Name)
	}

}

func main(){
	//1. 基本数据类型 反射
	var num int = 100
	reflectTest01(num)
	//2. 定义一个Student实例
	stu := Student{
		Name: "tom",
		Age: 20,
	}
	reflectTest02(stu)
}

在这里插入图片描述

2.4 反射的细节和注意事项

  1. reflect.Value.Kind,获取变量的类别,返回的是一个常量
    在这里插入图片描述
  2. Type 和 Kind 的区别【type更加具体】
    Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的. 比如: var num int = 10 num 的 Type 是 int , Kind 也是 int
    比如: var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct
    在这里插入图片描述
  1. 通过反射的来修改变量, 注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到reflect.Value.Elem()方法
    在这里插入图片描述
  1. reflect.Value.Elem() 应该如何理解?
    在这里插入图片描述

2.5 综合案例

使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值

package main
import (
	"reflect"
	"fmt"
)

type Monster struct {
	Name string `json:"name"`
	Age	int `json:"monster_age"` 
	Score float32 `json:"成绩"`
	Sex	string
}
//方法,返回两数的和
func (s Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}

//方法,接收四个值,给结构体赋值
func (s Monster) Set(name string, age int, score float32, sex string){
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}

//方法,显示结构体的值
func (s Monster) Print(){
	fmt.Println("----start----")
	fmt.Println(s)
	fmt.Println("----end----")
}

func TestStruct(a interface{}){
	//获取reflect.Type类型
	typ := reflect.TypeOf(a)
	//获取reflect.Value类型
	val := reflect.ValueOf(a)
	//获取到a对应的类别
	kd := val.Kind()
	//如果传入的不是struct,就退出
	if kd != reflect.Struct {
		fmt.Println("expect struct")
		return 
	}
	//是结构体,获取该结构体有几个字段
	num := val.NumField()
	fmt.Printf("struct has %d fields\n", num)
	for i := 0; i < num; i++ {
		fmt.Printf("Field %d值为=%v\n", i, val.Field(i))
		//获取到struct标签,注意需要通过reflect.Type来获取tag标签的值
		tagVal := typ.Field(i).Tag.Get("json") //因为前面定义结构体用到了'json标签'
		//如果该字段有tag标签就显示,否则就不显示
		if tagVal != "" {
			fmt.Printf("Field %d tag 为=%v\n", i, tagVal)
		}
	}

	//获取到该结构体有多少个方法
	numOfMethod := val.NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)
	//var params []reflect.Value
	//方法的排序默认是按照函数名的排序(ASCII码)
	val.Method(1).Call(nil) //获取到第二个【下标为1】方法,调用它 【传参为空】

	//调用结构体的第1个方法 Method(0)
	var params []reflect.Value //声明了 []reflect.Value()
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := val.Method(0).Call(params) //传入的参数是[]reflect.Value,返回[]reflect.Value
	fmt.Println("res=", res[0].Int()) //返回结果,返回的结果是[]reflect.Value
}

func main(){
	var a Monster = Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.9,
	}
	TestStruct(a)
}

在这里插入图片描述

3 网络编程

3.1 概念及前置知识

  1. TCP socket 编程,是网络编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协议的. 比如: QQ 聊天
  2. b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协议,而 http 底层依旧是用 tcp socket 实现的。 比如: 京东商城 【这属于 go web 开发范畴 】
  3. 计算机间要相互通讯,必须要求网线,网卡,或者是无线网卡.

在这里插入图片描述
4. 协议
在这里插入图片描述

在这里插入图片描述
5. 端口

  • 0 号是保留端口.
  • 1-1024 是固定端口(程序员不要使用),又叫有名端口,即被某些程序固定使用,一般程序员不使用.
  • 常见端口:22: SSH 远程登录协议 23: telnet 使用 21: ftp 使用
    25: smtp 服务使用 80: iis 使用 7: echo 服务
  • 1025-65535 是动态端口
    这些端口,程序员可以使用.

注意:

  1. 在计算机(尤其是做服务器)要尽可能的少开端口

  2. 一个端口只能被一个程序监听

  3. 如果使用 netstat –an 可以查看本机有哪些端口在监听

  4. 可以使用 netstat –anb 来查看监听端口的 pid,在结合任务管理器关闭不安全的端口

3.2 快速入门

  • 服务端的处理流程
  1. 监听端口 8888
  2. 接收客户端的 tcp 链接,建立客户端和服务器端的链接.
  3. 创建 goroutine,处理该链接的请求(通常客户端会通过链接发送请求包)
  • 客户端的处理流程
  1. 建立与服务端的链接
  2. 发送请求数据[终端],接收服务器端返回的结果数据
  3. 关闭链接

①服务端功能及代码

功能:

  • 编写一个服务器端程序,在 8888 端口监听可以和多个客户端创建链接
    链接成功后,客户端可以发送数据,服务器端接受数据,并显示在终端上. 先使用 telnet 来测试,然后编写客户端程序来测试

代码:

package main

import (
	"fmt"
	"net"
)

func process(conn net.Conn) {
	//循环接收客户端发送的数据
	defer conn.Close()
	for {
		//创建一个新的切片
		buf := make([]byte, 1024)
		//1. 等待客户端通过conn发送消息
		//2. 如果客户端没有write(发送消息),那么协程就阻塞在这里
		fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
		n, err := conn.Read(buf) //从conn中读取
		if err != nil {
			fmt.Printf("客户端退出 err=%v", err)
			return
		}
		//3. 显示客户端给服务端发送的数据(打印在控制台上)
		fmt.Print(string(buf[:n]))
	}
}
func main() {
	fmt.Println("服务器开始监听...")
	//1. tcp表示使用的网络协议是tcp
	//2. 0.0.0.0:8888表示在本地监听8888端口
	listen, err := net.Listen("tcp", "0.0.0.0:8888")
	if err != nil {
		fmt.Println("listen err=", err)
		return
	}
	defer listen.Close() //延时关闭listen
	//循环等待客户端来连接服务端
	for {
		//等待客户端连接
		fmt.Println("等待客户端来连接...")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept() err=", err)
		} else {
			fmt.Printf("Accept() success con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
		}
		//这里准备起一个协程,为客户端服务
		go process(conn)
	}
}

运行之后效果:

服务器开始监听...
等待客户端来连接...

②客户端功能及代码

  1. 编写一个客户端端程序,能链接到 服务器端的 8888 端口
  2. 客户端可以发送单行数据,然后就退出
  3. 能通过终端输入数据(输入一行发送一行), 并发送给服务器端
  4. 在终端输入 exit,表示退出程序.
package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

func main() {
	conn, err := net.Dial("tcp", "192.168.1.100:8888")
	if err != nil {
		fmt.Println("client dial err=", err)
		return
	}
	//功能一:客户端可以发送单行数据,然后就退出
	reader := bufio.NewReader(os.Stdin) //os.Stdin 表示标准输入:【终端】
	for {
		//从终端读取一行用户输入,并发送给服务端
		line, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("readString err=", err)
		}
		//功能二:当用户输入exit就退出
		line = strings.Trim(line, "\r\n")
		if line == "exit" {
			fmt.Println("客户端退出...")
			break
		}
		n, err := conn.Write([]byte(line))
		if err != nil {
			fmt.Println("conn Write err=", err)
		}
		fmt.Printf("客户端发送了 %d字节的数据\n", n)
	}
}

③运行效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634404.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[LeetCode周赛复盘] 第 349 场周赛20230611

[LeetCode周赛复盘] 第 349 场周赛20230611 一、本周周赛总结6470. 既不是最小值也不是最大值1. 题目描述2. 思路分析3. 代码实现 6465. 执行子串操作后的字典序最小字符串1. 题目描述2. 思路分析3. 代码实现 6449. 收集巧克力1. 题目描述2. 思路分析3. 代码实现 6473. 最大和…

测试老鸟总结,接口自动化测试用例设计编写,高级测试之路...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口信息来源 与…

百度图像识别 API

首先预览下效果 feaa250077a543a39f037ae8e78a3e80~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp (640594) (byteimg.com) 从以上预览图中可看出&#xff0c;每张图片识别出5条数据&#xff0c;每条数据根据识别度从高往下排&#xff0c;每条数据包含物品名称、识别度…

VisualStdio中scanf报错问题

VisualStdio中scanf报错问题 目录 一&#xff0e; 概述二&#xff0e; 解决方法 一&#xff0e; 概述 报错代码及说明 报错代码为C4996 会在哪种编译器中报错&#xff1f; VisualStdio系列编译器 为什么会报错&#xff1f; 因为VisualStdio比较严谨&#xff0c;认为scanf不…

内网安全:横向传递攻击( RDP || Cobalt Strike )

内网安全&#xff1a;横向传递攻击&#xff08; RDP || Cobalt Strike &#xff09; 横向移动就是在拿下对方一台主机后&#xff0c;以拿下的那台主机作为跳板&#xff0c;对内网的其他主机再进行后面渗透&#xff0c;利用既有的资源尝试获取更多的凭据、更高的权限&#xff0…

【基础知识整理】时间复杂度 空间复杂度

概览 时间复杂度与空间复杂度的作用是在衡量一个算法的优劣性&#xff0c;以及在二者之间进行权衡&#xff0c;寻找二者的平衡点。 时间复杂度是指执行算法所需时间的增长率&#xff0c;而空间复杂度则是指执行算法所需存储空间的增长率。 高时间复杂度的算法可能需要在短时间…

合金氢化物动力学与瞬时流量

在经典的合金氢化物动力学描述中&#xff0c;有一种是用JMAK方程来描述和拟合合金的吸放氢过程&#xff0c;方程很简洁&#xff1a;&#xff0c;其中是反应程度或者百分比&#xff0c;表示合金氢化物吸氢或者放氢的程度&#xff0c;是该合金吸氢或放氢的一种特征常数&#xff0…

使用wab2app将网页打包成APK

前言 通过开源项目ChatGPT-Next-Web部署完了私人网页ChatGPT&#xff0c;为了使访问更加便捷&#xff0c;便想着使用wab2app打包为APK&#xff0c;虽然最终达成了效果&#xff0c;可一路走来遇到了很多坑儿&#xff0c;记录下 打包流程 有要打包的网站和软件HBuilder X&…

【Kubernetes存储篇】持久化存储PV、PVC详解

文章目录 一、PV、PVC持久化存储理论1、PV、PVC是什么&#xff1f;2、PV的供应方式3、PV、PVC的回收策略 二、案例&#xff1a;PV、PVC持久化存储案例演示1、搭建NFS服务端2、创建PV&#xff0c;并使用NFS共享存储3、创建PVC&#xff0c;并和PV绑定4、创建Pod&#xff0c;并挂载…

docker中运行PostgreSQL容器

我们如何在docker中运行postgresql容器&#xff0c;要进过如下几个步骤就可以了。 拉取postgresql容器 docker pull postgres:latest使用上述命令将从 Docker Hub 存储库中提取最新可用版本的 PostgreSQL。 从 PostgreSQL 服务器 Docker 镜像运行容器 在部署之前&#xff0c;…

2023年6月DAMA-CDGA/CDGP数据治理工程师认证报名特惠

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

WinDbg安装入坑3(C#)

由于作者水平有限&#xff0c;如有写得不对的地方&#xff0c;请指正。 使用WinDbg的过程中&#xff0c;坑特别的多&#xff0c;对版本要求比较严格&#xff0c;如&#xff1a; 1 32位应用程序导出的Dump文件要用32位的WinDbg打开&#xff0c;想要没有那么多的问题&#xf…

Xcode 15 beta (15A5160n) - Apple 平台 IDE

Xcode 15 beta (15A5160n) - Apple 平台 IDE IDE for iOS/iPadOS/macOS/watchOS/tvOS/visonOS 请访问原文链接&#xff1a;https://sysin.org/blog/apple-xcode-14/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Xcode 15 使…

windows系统cmd命令设置别名,并添加到环境变量

众所周知&#xff0c;Linux 命令很强大&#xff0c;使用起来也很方便&#xff0c;但是想在 windows 系统上使用 Linux 命令有些困难&#xff0c;要么下载第三方终端工具&#xff0c;要么就是安装一系列命令环境。 作为一个前端开发&#xff0c;其实可以全局安装一下 npm 命令行…

分库分表-ShardingSphere

分库分表拆常见分方法与特点 分片策略 数据分布 以后扩展 基于Hash&#xff1a;hash(分片键)%分片数 数据分布均匀 不易扩容&#xff0c;扩容需要数据迁移 范围分片&#xff1a;例如按年分&#xff0c;按月&#xff0c;按日 数据分表可能不均匀 易扩展&#xff0c;扩展…

API自动化测试利器Postman,帮助你更好地进行 API 自动化测试

目录 前言&#xff1a; 一、基本功能 二、测试工具 三、示例 前言&#xff1a; Postman 是一个易于使用的 API 开发和测试工具&#xff0c;可以在其中快速构建、测试和文档化 Web API。Postman 提供了一个直观的用户界面&#xff0c;可以轻松地创建 HTTP 请求、测试响应、…

深入解析XMLHttpRequest:实现异步通信的利器

文章目录 介绍什么是XMLHttpRequest&#xff1f;XMLHttpRequest的基本用法1.创建XMLHttpRequest对象2.配置请求3.发送请求 XMLHttpRequest 属性sendreadyStatestatusstatusTextresponseresponseTextresponseTyperesponseURLtimeoutwithCredentials 异步请求与同步请求请求类型和…

淡季,老手买入的季节。

刚在刷抖音&#xff0c;刷到马云的视频。心里在想&#xff1a;这么大的企业家大富豪怎么会哽咽呢&#xff1f;他缺钱吗&#xff1f;他需要为生存奋斗吗&#xff1f; 他当然不缺钱&#xff0c;也不需要为生存去奋斗。如果以家庭生活支出来算&#xff0c;他的钱够几代儿孙挥霍了。…

漂亮国因一颗气球而疯狂给质量团队带来的启示

最近漂亮国因为我国的一颗漂洋过海的淘气的民用气球而疯狂。这颗气球成功躲过了号称全球最先进的防空系统&#xff0c;跨越大半个漂亮国&#xff0c;直到被一居民拍照无意间发现&#xff0c;漂亮国才反应过来。多次派战斗机拦截无果&#xff0c;在气球降到15km后&#xff0c;F2…

数据链路层之以太网协议基本知识总结分享

目录 1. 以太网协议报文格式 2. MTU是什么?它对哪些协议有影响? 1. 以太网协议报文格式 这里的目的地址不是ip地址,是Mac地址,mac地址是6个字节,比ipv4地址大了2个字节,当前的mac地址是可以让每个设备都是唯一的,且不需要动态分配,一般是网卡出厂就分配好了的.这就代表我们…