raft算法理论前言这几天看了一下raft算法，在这里着重讲解一下raft算法的理论介绍raft 是一种分布式的共识算法，其目的是要实现多个节点集群的容错性,一致性从而能够构建大规模的软件系统。在raft之前，比较有名的是Paxos。但是paxos难于理解。raft的诞生是为了让共识算法更容易理解,在工程上更容易实现。特点leader：raft中会有一个领导者具有超级权限，可以把自己的log 复制到其他节点中。leader election： raft每隔一段随机的时间就会进行leader的选举raft允许集群配置变化时正常运行。状态机状态机是分布式系统中的一个重要概念，任何一个系统的最终状态都可以看成是每一个操作的集合。因此，算法会维护一份replicated log，将每一份操作都存储起来。每一个节点只要按顺序执行log中的命令，就会到达相同的最终状态。这样，即便是系统奔溃也可以快速的恢复。共识算法需要保证relicated log的一致性,服务器收到客户端发出来的执行命令Command后，会将其加入到log中。服务器之间会相互的交流，保证最后的log的一致性（即便服务器奔溃），即Command 会复制到其他服务器的log中，所有服务器的log是相同的，有序的。其他服务器会执行此log，即会执行此命令。最后，所有的服务器都会到达同一个状态。基础raft集群一般保持奇数个数量（5个节点比较普遍). 从而只要大部分节点存活，即可用。raft中的节点有3种状态。 leader, Candidates, follower。一般的状态只会存在一个leader，其余的节点都是follower。leader会处理所有的客户端请求， 如果是客户端请求follower，也会被转发到leader处理。Candidates 是一种选举时候的过渡状态，用于自身拉票选举leader。在raft中会有一个叫做term的时间周期。term是以选举leader开始的，如果Candidates选举成为了leader，那么其会成为这个term剩下时间的leader。有时候，在整个term周期都没有选举出leader。这时一个新的选举会在不久后开始。Terms 在raft中类似于一种时间戳，后一个一定比前一个后发生，这一点和比特币中的区块链很类似。每一个服务器会存储一个当前的term，其会随着时间的增加而增长。如果某一个节点的当前term小于其他节点，那么节点会更新自己的term为最大的term。如果一个candidate 发现自己当前的term 过时了，那么其会立即变为follower。raft节点之间通过RPC（远程过程调用）来进行通信。 RequestVote 方法用于candidate在选举时候使用，AppendEntries用于leader在通知其他节点复制log时使用。同时也用于心跳检测。RPC 是并发的，并支持失败重试。选举在raft中会有一套心跳检测，只要follower收到来自leader或者Candidates的数据，那么其会保持follower的状态。如果follower一段时间内没有收到RPC请求，这意味着选举超时（ election timeout ）。这时follower会将current term 加1，过渡到Candidates状态。其会给自己投票，并发送RequestVote RPC请求给其他的节点，拉票！Candidates状态会持续，直到下面的3种情况发生:当其获得了大部分节点的支持后，其赢得了选举，变为了leader。一旦其变为了leader，其会向其他节点发送 AppendEntries RPC， 确认其leader的地位，阻止选举。其他节点成为了leader。如果其收到了其他节点的AppendEntries RPC. 并发现其他节点的current term比自己的大,则其变为follower状态。一段时间过去任然没有参与者。如果有许多的节点同时变为了candidate,则可能会出现一段时间内都没有节点能够选举成功的情况。在raft中，为了快速解决并修复这个问题，规定了每一个candidate在选举前会重置一个随机的选举超时（ election timeout ）时间，此随机时间会在一个区间内（eg.150-300ms）随机时间保证了在大部分的情况下，有一个唯一的节点首先选举超时，其在大部分节点选举超时前发送心跳检测，赢得了选举。当一个leader在心跳检测中发现另一个节点有更高的term时，会转变为follower。否则其将一直保持leader状态。
golang实现发送邮箱随机六位验证码有时我们需要给用户发送验证码，但是手机短信验证会有成本，所以今天我在这里分享一下邮箱发送验证码的方法依赖包go get gopkg.in/gomail.v2详细代码下面的代码附有详细注释package main import ( "fmt" "log" "math/rand" "time" "gopkg.in/gomail.v2" ) // MailboxConf 邮箱配置 type MailboxConf struct { // 邮件标题 Title string // 邮件内容 Body string // 收件人列表 RecipientList []string // 发件人账号 Sender string // 发件人密码，QQ邮箱这里配置授权码 SPassword string // SMTP 服务器地址， QQ邮箱是smtp.qq.com SMTPAddr string // SMTP端口 QQ邮箱是25 SMTPPort int } func main() { var mailConf MailboxConf mailConf.Title = "验证" //这里就是我们发送的邮箱内容，但是也可以通过下面的html代码作为邮件内容 // mailConf.Body = "坚持才是胜利，奥里给" //这里支持群发，只需填写多个人的邮箱即可，我这里发送人使用的是QQ邮箱，所以接收人也必须都要是 //QQ邮箱 mailConf.RecipientList = []string{"邮箱账号1","邮箱账号2"} mailConf.Sender = `邮箱账号` //这里QQ邮箱要填写授权码，网易邮箱则直接填写自己的邮箱密码，授权码获得方法在下面 mailConf.SPassword = "这里填写自己QQ邮箱授权码" //下面是官方邮箱提供的SMTP服务地址和端口 // QQ邮箱：SMTP服务器地址：smtp.qq.com（端口：587） // 雅虎邮箱: SMTP服务器地址：smtp.yahoo.com（端口：587） // 163邮箱：SMTP服务器地址：smtp.163.com（端口：25） // 126邮箱: SMTP服务器地址：smtp.126.com（端口：25） // 新浪邮箱: SMTP服务器地址：smtp.sina.com（端口：25） mailConf.SMTPAddr = `smtp.qq.com` mailConf.SMTPPort = 25 //产生六位数验证码 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) vcode := fmt.Sprintf("%06v", rnd.Int31n(1000000)) //发送的内容 html := fmt.Sprintf(` 尊敬的用户，您好！ 你本次的验证码为%s,为了保证账号安全，验证码有效期为5分钟。请确认为本人操作，切勿向他人泄露，感谢您的理解与使用。 此邮箱为系统邮箱，请勿回复。 `, vcode) m := gomail.NewMessage() // 第三个参数是我们发送者的名称，但是如果对方有发送者的好友，优先显示对方好友备注名 m.SetHeader(`From`, mailConf.Sender, "小铃铛官方") m.SetHeader(`To`, mailConf.RecipientList...) m.SetHeader(`Subject`, mailConf.Title) m.SetBody(`text/html`, html) // m.Attach("./Dockerfile") //添加附件 err := gomail.NewDialer(mailConf.SMTPAddr, mailConf.SMTPPort, mailConf.Sender, mailConf.SPassword).DialAndSend(m) if err != nil { log.Fatalf("Send Email Fail, %s", err.Error()) return } log.Printf("Send Email Success") } QQ邮箱获取授权码的方法打开我们的邮箱，左上角点击设置然后点击账户在下面找到SMTP并开启，在这里我已经开启过了，开启后他会给你一个授权码，如果授权码忘了，可以关闭再打开他会给你一个 新的授权码
今天在这里就简单的说一下golang中操作redis数据库在这里需要用到golang中优秀的第三方包开源库redigo的使用github地址：https://github.com/garyburd/redigo获取的redigo包的命令（一般通过git来操作）go get github.com/garyburd/redigo链接redispackage main import( "github.com/garyburd/redigo/redis" ) func main(){ //链接redis redisConn,err :=redis.Dial("tcp","127.0.0.1:6379") if err != nil{ fmt.Println("连接失败:",err) return } defer redisConn.Close() }在这里tcp可以是"tcp4"、"tcp6"、"tcp"中的任意一个，分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个)。127.0.0.1:6379是端口号，redis默认为6379.redis.Dial()返回的是一个redis.conn的指针.读写package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { redisConn, err := redis.Dial("tcp", "127.0.0.1:6379") if err != nil { fmt.Println("连接失败:", err) return } defer redisConn.Close() _, err = redisConn.Do("SET", "mykey", "hello,world") if err != nil { fmt.Println("redis set failed:", err) } data, err := redis.String(c.Do("GET", "mykey")) if err != nil { fmt.Println("redis get failed:", err) } else { fmt.Println("Get mykey:", data) } }输出:hello,world在c.Do()函数里写入对应的redis操作指令就可以执行该指令。第21行加了一个redis.String()函数是因为执行get命令时返回的是一个[]uint8类型的数据，可以把他转化为string类型.如果想设置过期时间，可以使用SET的附加参数：_, err = redisConn.Do("SET", "mykey", "hello,world", "EX", "5") if err != nil { fmt.Println("redis set failed:", err) }第一行是让他存活5秒，然后会delete掉检测值是否存在EXISTS keyjudge, err := redis.Bool(c.Do("EXISTS", "mykey1")) if err != nil { fmt.Println("error:", err) } else { fmt.Println("exists or not:", judge) }输出:exists or not: false删除 _, err = redisConn.Do("DEL", "mykey") if err != nil { fmt.Println("redis delelte failed:", err) }会找到键名为key的删除掉，如果没有，返回一个错误读写json到redispackage main import ( "encoding/json" "fmt" "github.com/garyburd/redigo/redis" ) func main() { redisConn, err := redis.Dial("tcp", "127.0.0.1:6379") if err != nil { fmt.Println("Connect to redis error", err) return } defer redisConn.Close() key := "user" //创建集合 mymap := map[string]string{"userName": "hello", "passWord": "world"} //通过json包把json数据封装 value, _ := json.Marshal(mymap) n, err := redisConn.Do("SETNX", key, value) if err != nil { fmt.Println(err) } if n == int64(1) { fmt.Println("success") } var mymapGet map[string]string valueGet, err := redis.Bytes(c.Do("GET", key)) if err != nil { fmt.Println(err) } //解封装 errShal := json.Unmarshal(valueGet, &mymapGet) if errShal != nil { fmt.Println(err) } fmt.Println(mymapGet["userName"]) fmt.Println(mymapGet["passWord"]) }开源库go-redis/redis的使用**github地址：https://github.com/go-redis/redis获取的go-redis/redis包的命令（一般通过git来操作）go get github.com/go-redis/redis链接redisclient := redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", // no password set DB: 0, // use default DB })第一行产生的是一个redis.client指针第四行默认使用的是redis的第一个数据库，redis默认是有16个数据库下标0~15读写err = client.Set("name", "孟家恒", 0).Err() if err != nil { panic(err) } val, err := client.Get("name").Result() if err != nil { panic(err) } fmt.Println("name", val) val2, err := client.Get("name").Result() if err == redis.Nil { fmt.Println("name does not exists") } else if err != nil { panic(err) } else { fmt.Println("key2", val2) } err = client.HSet("tmap", "1", "f1").Err() if err != nil { fmt.Printf("try hset map[tmap] field[1] error[%s]\n",err.Error()) } kvMap := make(map[string]interface{}) kvMap["3"] = "f3" kvMap["4"] = "f4" err = client.HMSet("tmap", kvMap).Err() if err != nil { fmt.Printf("try mset map[tmap] field[3] field[4] error[%s]\n",err.Error()) }go-redis包是把函数名定义成了redis操作指令的开头例如：第一行client.Set("name","孟家恒"，0).Err(),就是设置key属性，第20行就是设置哈希值第一行的第三个参数是这个键存活的时间，默认0的话就是永久，如果想设置过期时间，如下第19行package main import ( "fmt" "time" "github.com/go-redis/redis" ) func main() { client := redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", // no password set DB: 0, // use default DB }) pong, err := client.Ping().Result() fmt.Println(pong, err) err = client.Set("name", "孟家恒", time.Second*10).Err() if err != nil { panic(err) } }注：如果想设置10秒不能直接写10，而应写time.Second*10更多指令操作请参考菜鸟教程https://www.runoob.com/redis/redis-tutorial.html
列表项目简介基本的NATS服务器专为高性能和简洁而设计，它不会为通过NATS发布的消息提供持久性存储。缺乏对消息的持久存储对于许多分布式系统来说都是一个问题。例如，假设你的一个用户系统在你发布消息时发生故障，则该用户系统不会收到该消息，因此你必须提供处理此类情况的架构方法。NATS Streaming附带一个持久性存储，用于为通过 NATS 服务器发布的消息提供日志。如果你需要持久性消息传输和交付保证，则可以使用 NATS Streaming 而不是核心 NATS 平台。NATS Streaming是一个非常高性能，轻量级和可靠的流式平台，它构建在核心NATS平台的顶层，提供持久性日志。NATS Streaming 是用 Go 语言编写的。它可以用来添加事件流，交付保证和将历史数据重放到NATS。然而NATS Streaming 不是一个独立的服务器，但它使用 NATS 服务器（gnatsd）。简而言之，NATS Streaming 内嵌 NATS 服务器作为消息传输服务器，并提供了一个额外的功能，可以为事件流系统提供持久性日志使用。安装首先我们先使用go get获得NATS流go get github.com/nats-io/nats-streaming-server当我们下载好之后，打开我们的github.com/nats-io/nats-streaming-server包，在里面有个nats-streaming-server.go文件，把这个文件复制一份到我们go工作目录下的bin目录里为了用 Go 语言创建 NATS 客户端应用程序，请使用_go get_命令下载并安装 Go 语言包：go get github.com/nats-io/go-nats-streaming要运行NATS流，请运行我们放在bin目录下的 _nats-streaming-server_二进制文件：nats-streaming-server默认情况下，NATS Streaming使用内存存储来存储消息，因此如果NATS服务器关闭，你将丢失消息。下面是我运行的结果：发布事件首先我们先在这里创建一个事件的发布者，nats streaming一般都是用在集群的发布订阅。package main import ( "encoding/json" "log" //stan "github.com/nats-io/go-nats-streaming" // 如果这个包导入不成功就,导入下面的包 "github.com/nats-io/stan.go" ) const ( clusterID = "test-cluster" //集群ID：自定义 clientID = "event-store1" //客户端ID：自己这个发布者的ID，自定义 channel = "order-notification" //发布的频道：把自己 产生的事件要发布的频道ID ) //创建一个学生结构体 type student struct { Name string `json:"name,omitempty"` Age int `json:"age,omitempty"` Gender string `json:"gender,omitempty"` } func main() { //建立nats streaing连接 sc, err := stan.Connect( clusterID, clientID, stan.NatsURL(stan.DefaultNatsURL), //这里为默认， ) if err != nil { log.Print(err) } defer sc.Close() //创建实例 var stu student stu.Name = "小明" stu.Age = 19 stu.Gender = "男" //把结构体实例转化为json数据 data, err := json.Marshal(stu) if err != nil { log.Println(err, " nats-streaming 解析json数据失败") } //通过推送函数，把[]byte类型的json数据推送到该集群下的"order-notification"频道 sc.Publish(channel, data) } 订阅事件基本的NATS服务器没有提供持久性日志，订阅消息的能力非常有限。当你发布消息时，如果订阅者客户端关闭，它将无法接收来自服务器的消息。由于NATS Streaming 服务器有持久化日志功能，它提供了很多从 NATS 服务器订阅消息的功能。在这里我们讲两种订阅方式：队列组订阅订阅者客户端可以通过指定一个_队列组_来创建。具有相同队列名称的同一频道的多个订户客户端形成队列组。队列订阅者可让你分发给多个订阅者消息处理。当你在频道上发布消息时，该消息将被发送到同一队列组中的一个用户。当你在短时间内发布数百万条消息时，如果消息处理的顺序并不重要，则排队订阅者可以高效地并行分发消息处理，并且提供高性能。package main import ( "encoding/json" "fmt" "log" "runtime" "github.com/nats-io/stan.go" ) const ( clusterID = "test-cluster" clientID = "order-query-store2" //订阅者ID channel = "order-notification" //订阅的频道 durableID = "store-durable" //队列成员持久化ID queueGroup = "order-query-store-group" //队列组ID ) //创建一个学生结构体 type student struct { Name string `json:"name,omitempty"` Age int `json:"age,omitempty"` Gender string `json:"gender,omitempty"` } func main() { //订阅者建立连接 sc, err := stan.Connect( clusterID, clientID, //这里只需改订阅者ID就可以作为一个新的订阅者 stan.NatsURL(stan.DefaultNatsURL), ) if err != nil { log.Println("OrderQuery :", err) } //该函数为队列组从频道里订阅接收消息，传入参数为订阅频道和自己要加入的队列组，如果队列组不存在 //则创建队列组 sc.QueueSubscribe(channel, queueGroup, func(msg *stan.Msg) { var stu student //订阅到的消息通过msg.Data获得，为[]byte数组类型 err := json.Unmarshal(msg.Data, &stu) if err != nil { log.Println(err, "解析json数据失败") } fmt.Println(stu) }, stan.DurableName(durableID), ) runtime.Goexit() } 在这里我创建了两个订阅者组成一个队列组同时订阅同一个频道。这里我 发布了四次事件，两个订阅者分别受到两条信息订阅者client1：订阅者client2：持久化订阅持久化订阅的意思是该订阅者会收到所有发布者所发布的事件，而上面的队列组则是队列组会收到所有的事件再把事件随机分给队列组内的成员package main import ( "encoding/json" "fmt" "log" "runtime" "time" "github.com/nats-io/stan.go" ) const ( clusterID = "test-cluster" clientID = "order-query-store3" channel = "order-notification" durableID = "store-durable" ) //创建一个学生结构体 type student struct { Name string `json:"name,omitempty"` Age int `json:"age,omitempty"` Gender string `json:"gender,omitempty"` } func main() { sc, err := stan.Connect( clusterID, clientID, stan.NatsURL(stan.DefaultNatsURL), ) if err != nil { log.Println("PersistentSubscription :", err) } // 以手动ack模式订阅, 且设置 AckWait 时间至60s aw, _ := time.ParseDuration("60s") sc.Subscribe(channel, func(msg *stan.Msg) { msg.Ack() // Manual ACK var stu student err := json.Unmarshal(msg.Data, &stu) if err != nil { log.Println(err, "解析json数据失败") } fmt.Println(stu) }, stan.DurableName(durableID), stan.MaxInflight(25), stan.SetManualAckMode(), stan.AckWait(aw), ) runtime.Goexit() }
深广度优先遍历一般情况下我们都是用在复杂的图结构，以方便我们进行必要的检索操作。之前我用C++，Python写过图的深广度优先遍历，今天在这里想用go语言来实现这一操作，并希望对各位有帮助邻接表图的存贮结构很多，在这里我们选择中等难度的邻接表作为我们图的储存结构，下面的图是我模拟的一个图结构，是一个有向非连通图（F点未相连）来作为这次深广度遍历的实验图。下面附上邻接表的代码（含注释）package main import "fmt" //邻接表我们需要把顶点和边都看成是一个结构节点 //所以我们在下面创建这两种结构 //VertexNode 顶点节点结构体 type VertexNode struct { data string //存放顶点数据 firstarc *ArcNode //顶点的第一条边 } //ArcNode 边节点结构体 type ArcNode struct { adjvex int //存放顶点的下标 next *ArcNode //存放下一条边节点 } //GraphAdjacencyList 图的邻接表结构体 type GraphAdjacencyList struct { vertexNode []VertexNode //存放顶点的数组 visted []bool //是否遍历过的bool类型数组 } // 添加顶点 func (g *GraphAdjacencyList) addVertexNode(data string) bool { //定义一个顶点节点实例，并储存顶点的数据 temp := VertexNode{} temp.data = data //判断这个顶点是否已经存在 for _, v := range g.vertexNode { if v.data == data { return false } } //把顶点节点存入切片中，方便我们后面遍历使用 g.vertexNode = append(g.vertexNode, temp) //初始化访问切片为false，代表这个顶点没有被访问过 g.visted = append(g.visted, false) return true } //添加边节点 func (g *GraphAdjacencyList) addArcNode(verNode1, verNode2 string) bool { //获得该边节点两头顶点在切片中的下标 index1 := g.verNodeIndex(verNode1) index2 := g.verNodeIndex(verNode2) //判断顶点是否存在或重复 if index1 == -1 || index2 == -1 || index1 == index2 { return false } //进行顶点和边节点的逻辑连接 temp := new(ArcNode) //创建边节点实例 temp.adjvex = index2 //对边节点和顶点进行绑定 temp.next = g.vertexNode[index1].firstarc //设置该边指向的下一条边 g.vertexNode[index1].firstarc = temp //同理设置另一个顶点 temp = new(ArcNode) temp.adjvex = index1 temp.next = g.vertexNode[index2].firstarc g.vertexNode[index2].firstarc = temp return true } // 获得顶点的下标 func (g *GraphAdjacencyList) verNodeIndex(data string) int { for i, v := range g.vertexNode { if v.data == data { return i } } return -1 } //初始化访问切片visted func (g *GraphAdjacencyList) initVisted() { for i, _ := range g.visted { g.visted[i] = false } } func main() { g := GraphAdjacencyList{} //定义一个邻接表实例 //添加四个顶点 topData := [5]string{"A", "B", "C", "D", "E"} for _, v := range topData { if g.addVertexNode(v) == false { fmt.Println("顶点数据重复") } } //添加五条边 g.addArcNode("A", "B") g.addArcNode("A", "C") g.addArcNode("A", "D") g.addArcNode("B", "D") g.addArcNode("C", "D") g.addArcNode("D", "E") }深度优先遍历深度优先遍历（Depth-First-Search），是搜索算法的一种，它沿着树的深度遍历树的节点，尽可能深地搜索树的分支。当节点v的所有边都已被探寻过，将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已探寻源节点到其他所有节点为止，如果还有未被发现的节点，则选择其中一个未被发现的节点为源节点并重复以上操作，直到所有节点都被探寻完成。简单的说，DFS就是从图中的一个节点开始追溯，直到最后一个节点，然后回溯，继续追溯下一条路径，直到到达所有的节点，如此往复，直到没有路径为止。例如上图，如果从A开始则会按照我标的数字顺序进行遍历：A->C(因为C点还有连通点，所以会去D而不返回A去B)->D(同上理)->E->B->F深度优先遍历的代码//开始DFS func (g *GraphAdjacencyList) travelDFS(data string) bool { index := g.verNodeIndex(data) if index == -1 { return false } g.DFS(index) // 通过循环来达到遍历所有顶点的目的，防止非连同图无法充分遍历的情况 for i, _ := range g.vertexNode { if g.visted[i] == false { g.DFS(i) } } g.initVisted() //初始化访问数组 return true } //DFS 深度优先遍历 func (g *GraphAdjacencyList) DFS(index int) { fmt.Println(g.vertexNode[index].data) g.visted[index] = true //获得该顶点的第一条边节点，通过这个边节点找到另一头的顶点 p := g.vertexNode[index].firstarc for p != nil { u := p.adjvex //如果顶点没有被访问，递归遍历 if g.visted[u] == false { g.DFS(u) } //边节点后移 p = p.next } }广度优先遍历广度优先遍历（Breadth-First-Search）是从根节点开始，沿着图的宽度遍历节点，如果所有节点均被访问过，则算法终止，BFS 同样属于盲目搜索，一般用队列数据结构来辅助实现BFS。例如上图，如果从A开始则会按照我标的数字顺序进行遍历：A->C(因为A点还有连通点，所以会返回A去B而不通过C去D)->B(A的所有连通点都被访问过同所以通过B顶点继续访问)->D->E->F广度优先遍历的代码//因为我们要使用队列这个结构来辅助我们进行广度优先遍历 //所以在这里模拟一个队列出来 //QNode 队列节点结构体 type QNode struct { data int //用来存放顶点的下标 next *QNode //指向下一个节点 } //Queue 队列结构体 type Queue struct { front *QNode //指向队列头的指针 rear *QNode //指向队列尾的指针 length int //队列的长度 } //初始化队列 func (q *Queue) initQueue() { q.front = new(QNode) q.rear = new(QNode) q.length = 0 q.front = q.rear q.front.next = nil } //入队 func (q *Queue) enQueue(data int) { p := new(QNode) p.data = data p.next = nil q.rear.next = p q.rear = p q.length += 1 } //出队 func (q *Queue) deQueue() int { q.length -= 1 p := q.front.next e := p.data q.front.next = p.next if q.rear == p { q.rear = q.front } return e } //判断队列是否为空 func (q *Queue) judgeEmpty() bool { if q.length == 0 { return true } else { return false } } //开始BFS func (g *GraphAdjacencyList) travelBFS(data string) bool { //获得开始访问顶点的下标 index := g.verNodeIndex(data) if index == -1 { return false } //开始BFS g.BFS(index) // 通过循环来达到遍历所有顶点的目的，防止非连同图无法充分遍历的情况 for i, _ := range g.vertexNode { if g.visted[i] == false { g.BFS(i) } } g.initVisted() //初始化访问数组 return true } //BFS func (g *GraphAdjacencyList) BFS(index int) { //创建队列的实例 q := new(Queue) //初始化队列 q.initQueue() fmt.Println(g.vertexNode[index].data) g.visted[index] = true //访问过的顶点入队 q.enQueue(index) //队列不为空 for q.judgeEmpty() == false { //出队找未遍历的顶点 v := q.deQueue() //创建边实例，找顶点 p := g.vertexNode[v].firstarc for p != nil { //如果未访问输出顶点值，入队 if g.visted[p.adjvex] == false { fmt.Println(g.vertexNode[p.adjvex].data) g.visted[p.adjvex] = true q.enQueue(p.adjvex) } //边节点后移 p = p.next } } }完整代码详细注释在上面，这里只是一提供一个完整的深广度遍历代码package main import "fmt" // //QNode 邻接表节点结构体 // type QNode struct { // Data string //存放节点的数据 // Next *QNode //存放下一个节点的指针 // } //QNode 队列节点结构体 type QNode struct { data int //用来存放顶点的下标 next *QNode //指向下一个节点 } //Queue 队列结构体 type Queue struct { front *QNode //指向队列头的指针 rear *QNode //指向队列尾的指针 length int //队列的长度 } //VertexNode 顶点结点结构体 type VertexNode struct { data string //存放顶点数据 firstarc *ArcNode //顶点的第一条边 } //ArcNode 边节点结构体 type ArcNode struct { adjvex int //存放顶点的下标 next *ArcNode //存放下一条边节点 } //GraphAdjacencyList 图的邻接表结构体 type GraphAdjacencyList struct { vertexNode []VertexNode //存放顶点的数组 visted []bool //是否遍历过的bool类型数组 } //初始化队列 func (q *Queue) initQueue() { q.front = new(QNode) q.rear = new(QNode) q.length = 0 q.front = q.rear q.front.next = nil } //入队 func (q *Queue) enQueue(data int) { p := new(QNode) p.data = data p.next = nil q.rear.next = p q.rear = p q.length += 1 } //出队 func (q *Queue) deQueue() int { q.length -= 1 p := q.front.next e := p.data q.front.next = p.next if q.rear == p { q.rear = q.front } return e } //判断队列是否为空 func (q *Queue) judgeEmpty() bool { if q.length == 0 { return true } else { return false } } // 添加顶点 func (g *GraphAdjacencyList) addVertexNode(data string) bool { temp := VertexNode{} temp.data = data for _, v := range g.vertexNode { if v.data == data { return false } } g.vertexNode = append(g.vertexNode, temp) g.visted = append(g.visted, false) //初始化为false代表该节点没有被访问 return true } //添加边节点 func (g *GraphAdjacencyList) addArcNode(verNode1, verNode2 string) bool { index1 := g.verNodeIndex(verNode1) index2 := g.verNodeIndex(verNode2) if index1 == -1 || index2 == -1 || index1 == index2 { return false } //进行顶点和边节点的逻辑连接 temp := new(ArcNode) temp.adjvex = index2 temp.next = g.vertexNode[index1].firstarc g.vertexNode[index1].firstarc = temp temp = new(ArcNode) temp.adjvex = index1 temp.next = g.vertexNode[index2].firstarc g.vertexNode[index2].firstarc = temp return true } // 获得顶点的下标 func (g *GraphAdjacencyList) verNodeIndex(data string) int { for i, v := range g.vertexNode { if v.data == data { return i } } return -1 } //初始化访问切片visted func (g *GraphAdjacencyList) initVisted() { for i, _ := range g.visted { g.visted[i] = false } } //开始DFS func (g *GraphAdjacencyList) travelDFS(data string) bool { index := g.verNodeIndex(data) if index == -1 { return false } g.DFS(index) // 通过循环来达到遍历所有顶点的目的 for i, _ := range g.vertexNode { if g.visted[i] == false { g.DFS(i) } } g.initVisted() //初始化访问数组 return true } //DFS 深度优先遍历 func (g *GraphAdjacencyList) DFS(index int) { fmt.Println(g.vertexNode[index].data) g.visted[index] = true p := g.vertexNode[index].firstarc for p != nil { u := p.adjvex if g.visted[u] == false { g.DFS(u) } p = p.next } } //开始BFS func (g *GraphAdjacencyList) travelBFS(data string) bool { index := g.verNodeIndex(data) if index == -1 { return false } g.BFS(index) for i, _ := range g.vertexNode { if g.visted[i] == false { g.BFS(i) } } g.initVisted() //初始化访问数组 return true } //BFS func (g *GraphAdjacencyList) BFS(index int) { q := new(Queue) q.initQueue() fmt.Println(g.vertexNode[index].data) g.visted[index] = true q.enQueue(index) for q.judgeEmpty() == false { v := q.deQueue() p := g.vertexNode[v].firstarc for p != nil { if g.visted[p.adjvex] == false { fmt.Println(g.vertexNode[p.adjvex].data) g.visted[p.adjvex] = true q.enQueue(p.adjvex) } p = p.next } } } func main() { g := GraphAdjacencyList{} //定义一个邻接表 //添加四个顶点 topData := [6]string{"A", "B", "C", "D", "E", "F"} for _, v := range topData { if g.addVertexNode(v) == false { fmt.Println("顶点数据重复") } } //添加五条边 g.addArcNode("A", "B") g.addArcNode("A", "C") g.addArcNode("B", "D") g.addArcNode("C", "D") g.addArcNode("D", "E") //深度优先遍历 fmt.Println("深度优先遍历") g.travelDFS("A") //广度优先遍历 fmt.Println("广度优先遍历") g.travelBFS("A") }
目的在我们后端写项目时，数据一般都是保存在MySQL数据库里。但是有时我们不能只是简单的把数据保存在MySQL数据库里（删库跑路）。在这里我们就要经常备份一下我们的数据库或者是数据库里面的某一个表。Navicat在这里我们使用的是MySQL可视化工具里最棒的navicat，所以先安装：Navicat下载连接先点击左上角的连接，然后连接名是自己随便取的，主机名填想要连接数据库的IP，自己用一般默认为localhost，用户名和密码填自己MySQL的用户名和密码。Go代码实现导入包import ( "io/ioutil" "log" "os/exec" "time" )在这里我们要使用一个平常很少使用的包os/exex, exec包执行外部命令，详情可以去参考Go语言标准文档。全代码package main import ( "io/ioutil" "log" "os/exec" "time" ) func main() { BackupMySqlDb("localhost", "3306", "root", "123456", "school", "", "E:/") } /** * * 备份MySql数据库 * @param host: 数据库地址: localhost * @param port: 端口: 3306 * @param user: 用户名: root * @param password: 密码: root * @param databaseName: 需要被分的数据库名: test * @param tableName: 需要备份的表名: user * @param sqlPath: 备份SQL存储路径: D:/backup/test/ * @return backupPath * */ func BackupMySqlDb(host, port, user, password, databaseName, tableName, sqlPath string) (error,string) { //定义Cmd结构体对象指针 var cmd *exec.Cmd //在这里如果没有传输表名，那么将会备份整个数据库，否则将只备份自己传入的表 if tableName == "" { cmd = exec.Command("mysqldump", "--opt", "-h"+host, "-P"+port, "-u"+user, "-p"+password, databaseName) } else { cmd = exec.Command("mysqldump", "--opt", "-h"+host, "-P"+port, "-u"+user, "-p"+password, databaseName, tableName) } //StdinPipe方法返回一个在命令Start后与命令标准输入关联的管道。 stdout, err := cmd.StdoutPipe() if err != nil { log.Println(err) return err,"" } if err := cmd.Start(); err != nil { log.Println(err) return err,"" } bytes, err := ioutil.ReadAll(stdout) if err != nil { log.Println(err) return err,"" } //获得一个当前的时间戳 now := time.Now().Format("20060102150405") var backupPath string //设置我们备份文件的名字 if tableName == "" { backupPath = sqlPath+databaseName+"_"+now+".sql" } else { backupPath = sqlPath+databaseName+"_"+tableName+"_"+now+".sql" } //写入文件并设置文件权限 err = ioutil.WriteFile(backupPath, bytes, 0644) if err != nil { log.Println(err) return err,"" } return nil,backupPath } 示例上述代码以school数据库为例，由于表名为空所以在这里是备份一整个数据库，运行程序即可然后会在我们设置的路径下找到我们代码生成的sql文件运行sql文件右键点击我们的数据库或者表里面有个运行SQL文件，然后选中自己生成的SQL文件即可，但是导入SQL文件会把相同表名的表数据替换，所以导入时一般选择一个额外的数据库显示。
简介RabbitMQ是一个开源的，使用最广的消息转发器。提供匿名消息类型，可以使用多种消息协议，消息队列等，支持集群部署。它的轻量级能够让你很轻易地在私有云或公有云环境中部署，同时提供丰富的插件工具，能够让你轻易的进行扩展，且可使用HTTP-API、命令行工具和UI界面以便于管理和监控。专业术语生产者：产生并发送消息的程序队列：类似于一个存在RabbitMQ中的邮筒，虽然消息是在应用程序和RabbitMQ中进行传递，但队列才是唯一能够存储消息的的地方。队列的大小取决于寄主机器的内存和磁盘容量，他本质上是一个巨大的消息缓存池。多个生产者可以发送消息给同一个队列，多个消费者也可以从同一个队列中读取消息消费者：等待接收消息的程序Hello World最开始接触消息队列，我们先来一个简单传输 Hello World的消息队列。其中包含两个小程序，一个是用于发送消息send.go, 一个用来接收消息的reveive.go在使用Go语言操作RabbitMQ之前我们需要先从github上拉取amqp包：go get github.com/streadway/amqpSending首先我们要把需要用到的包导入我们的程序package main import ( "fmt" "log" "github.com/streadway/amqp" )创建请求连接在这里我们使用的消息队列将会基于TCP协议进行数据传输//guest为消息队列网站注册的（可以修改），端口号默认为5672 conn, err := amqp.Dial("amqp://guest:guest@127.0.0.1:5672") if err!=nil{ log.Println("conn err:",err) } defer conn.Close()上面代码会建立一个socket连接，处理一些协议转换及版本对接和登录授权的问题。建立连接之后，我们需要创建一个通道channel，之后我们的大多数API操作都是围绕通道来实现的创建通道ch, err := conn.Channel() if err!=nil{ log.Println("create Channel err:",err) } defer ch.Close()创建队列通道创建完毕后，我们要在通道的基础上创建一个队列q, err := ch.QueueDeclare( "hello", //通道名称 false, //持久的 false, //使用后是否删除 false, // 唯一性 false, //不允许等待 nil, //命令参数 )在这里q就为我们创建的队列，这个队列的名字叫做hello生产当我们创建好一个消息队列后就可以向消息队列中推送消息了body:="hello worldd" err = ch.Publish( //发布 "", //exchange 交换器名，这里为空 q.Name, //routing key 路由 key，这里写消息队列的名字 false, //mandatory 托管的 false, //immediate 直接的，立即的 amqp.Publishing{ //发布中 ContentType: "text/plain", //内用类型 Body: []byte(body), //消息队列传输时传的是byte字节型的切片 }, )Receiving首先，我们还是要导入同样的包package main import ( "fmt" "log" "github.com/streadway/amqp" )创建请求连接接着与生产者一样创建连接，注意这里的参数必须与send中的queue name相一致，这样才能实现发送/接受的配对。//guest为消息队列网站注册的（可以修改），端口号默认为5672 conn, err := amqp.Dial("amqp://guest:guest@127.0.0.1:5672") if err!=nil{ log.Println("conn err:",err) } defer conn.Close()创建通道ch, err := conn.Channel() if err!=nil{ log.Println("create Channel err:",err) } defer ch.Close()创建队列通道创建完毕后，创建的队列里的属性必须要与生产者队列属性一致，不然会不匹配q, err := ch.QueueDeclare( "hello", //通道名称 false, //持久的 false, //使用后是否删除 false, // 唯一性 false, //不允许等待 nil, //命令参数 )消费紧接着我们就可以从队列里消费信息了msgs, err := ch.Consume( //消费 q.Name, // queue //队列的名字 "", // consumer true, // auto-ack false, // exclusive false, // no-local false, // no-wait nil, // arguments ) forever := make(chan bool)//在这里创建一个通道，防止main函数执行结束 go func() { for d := range msgs { log.Println("Received a message : ",string(d.Body))//tong } }() <-foreverRuning首先找到我们的消费者，在蓝色路径处输入cmd打开终端然后运行我们的receive.go同理再去运行生产者，就可以看到我们的消费者已经接收到到了信息
目的之前学过一段时间的python，感觉受益匪浅，个人觉得python语言能火起来也有它一定的道理，所以在这里想总结一下之前所学习的知识，做一篇博客，希望其他人能从中学到知识。简介Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。Python 的设计具有很强的可读性，相比其他语言经常使用英文关键字，其他语言的一些标点符号，它具有比其他语言更有特色语法结构，废话不多说，直接开始。基础知识序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置，或索引，第一个索引是0，第二个索引是1，依此类推。（类似数组）Python有6个序列的内置类型，但最常见的是列表和元组。序列都可以进行的操作包括索引，切片，加，乘，检查成员。此外，Python已经内置确定序列的长度以及确定最大和最小的元素的方法。列表是最常用的Python数据类型，它可以作为一个方括号内的逗号分隔值出现。列表的数据项不需要具有相同的类型。1.列表Name_list["王梅"，“大傻”，“二傻”]定义一个列表，名字是Name_list,初始化赋值三个属性Name_list.index("大傻")获取数据在列表中的索引值Name_list.append("三傻")向列表的末尾追加数据Name_list.insert(1,"四傻")向指定位置插入数据Name_list.extend(temp_list)把其他列表内容追加到末尾.remove("大傻")从列表中删除指定数据.pop().pop(3)默认条件下删除最后一个元素（第一个）,也可根据索引删除（第二个）.clear()清空列表del name_list[1]清空列表len(Name_list)获取列表的长度Name_list.count("二傻")统计数据出现的次数.sort()对列表进行升序排序.sort(reverse=True)对列表进行降序排序.reverse()把列表中的数据翻转（逆序）下面为定义三个列表L1，L2，L3，然后把L1和L2放到L3里，并且去重L1 = [] L2 = [] L3 = [] print("请输入两个数分别代表要录入列表L1和列表L2的元素个数") num1 = int(input()) num2 = int(input()) print("请输入列表L1的数据") while num1 != 0: L1.append(int(input())) num1 -= 1 print("请输入列表L2的数据") while num2 != 0: L2.append(int(input())) num2 -= 1 print("L1:",L1) print("L1:",L2) for i in L1: if not i in L3: L3.append(i) for i in L2: if not i in L3: L3.append(i) print("L3:",L3) 2.元组元组操作与列表相似，但元组定义好之后无法修改元组定义好之后无法修改空元组Info_tuple(5,)单个数据元组3.字典一个字典变量最好记录一个物品的信息zidian={"name":小明, "age":18, "gender":True} 4.字符与ASCII码之间的转化Python提供了ord和chr两个内置的函数，用于字符与ASCII码之间的转换。print ord('a') #97 print chr(97) #a面向对象Python是一门面向对象的语言，所以它有类这个结构，接下来将会介绍一下类的定义格式（对比其他面向对象语言）1.构造函数def __init__(self): print("这是构造函数")2.析构函数def __del__(self): print("这是个析构函数")3.自定义函数def __del__(self): return"这是个自定义函数"4.对象赋值为空new_data=None5.身份运算符用于判断两个对象的内存地址是否相同id(x) is id(y) 类似 id(x) == id(y) id(x) is not id(y) 类似 id(x) != id(y)is用来判断 两个变量 引用对象是 否为同一个==用来判断 引用变量的值 是否为同一个6.定义私有属性定义私有属性或私有函数时在变量或函数名前面加 两个下划线 __name（但是Python是没有绝对的私有的）class TreeNode:#树节点 def __init__(self,data): self.__data = data self.__lchild = None self.__rchild = None7.类的继承狗类继承动物类class Animal: pass class Dog(Animal): pass #pass为占位符如果子类中重写了父类的方法，在使用子类对象调用方法时调用的是子类重写的方法8.定义类方法（通过类名调用）@classmethod def 类方法名(cls): pass9.定义静态方法（通过类名调用）@staticmethod def run(): pass数据结构示例1.约瑟夫环约瑟夫问题是个有名的问题：N个人围成一圈，从第一个开始报数，第M个将被杀掉，最后剩下一个，其余人都将被杀掉。例如N=6，M=5，被杀掉的顺序是：5，4，6，2，3，1。import random class Node:#定义一个链表节点类 # __slots__=['__num','__pwd','__next']#在类中定义私有变量 def __init__(self,num,pwd): self.__num = num self.__pwd = pwd self.__info = True #用来判断人是否出队 self.__next = None def setInfo(self,info): self.__info = info def setNext(self,next): self.__next = next def setNum(self,num): self.__num = num def setPwd(self,pwd): self.__pwd = pwd def getInfo(self): return self.__info def getNext(self): return self.__next def getNum(self): return self.__num def getPwd(self): return self.__pwd class LinkList:#循环链表 def __init__(self): self.__head = None self.__size = 0 def __judgeEmpty(self):#判断链表是否存在 return self.__head == None def addNode(self,num,pwd):#向链表中添加数据 self.__size = self.__size + 1 temp = Node(num,pwd) if self.__judgeEmpty(): self.__head=temp #若为空表，将添加的元素设为第一个元素 self.__head.setNext(self.__head)#循环链表 else: p=self.__head while p.getNext()!=self.__head: p=p.getNext() #遍历链表 temp.setNext(self.__head) p.setNext(temp) def yueSeFuHuan(self,m ):#约瑟夫环游戏 p = self.__head n = 1 while True: if n == m and p.getInfo()== True: print(p.getNum()) m = p.getPwd() n = 1 p.setInfo(None) self.__size = self.__size - 1 if self.__size == 0: break if p.getInfo()== True: n = n + 1 p = p.getNext() #遍历链表 num_max = int(input("请输入一个正整数代表初始报数上限数值：")) # m = random.randint(1,num_max) while True: m = int(input("m=")) if m <= 0 or m > num_max: print("输入错误请重新输入：") else: break # print( m ) people_num = int(input("请输入人数：")) L = LinkList() num = 1 while num <= people_num: password = int(input("请输入第 %d 个人的密码:"% num)) L.addNode(num ,password) num = num + 1 print("出队次序为") L.yueSeFuHuan(m) # L.show()2.数学公式运算在这里只写了最基本的四则运算，使用栈来实现中缀表达式转后缀表达式class SNode: #栈的结点类 def __init__(self): self.__data = None self.__next = None def set_data(self,data): self.__data = data def set_next(self,next): self.__next = next def get_data(self): return self.__data def get_next(self): return self.__next class Stack: #栈类 def __init__(self): self.top = SNode() self.base = SNode() self.count = 0 def push(self,data):#入栈 p = SNode() if self.count == 0: self.base = p.get_next() p.set_data(data) p.set_next(self.top) self.top = p self.count += 1 def pop(self): if not self.Empty(): print("栈已空") return temp = SNode() temp = self.top self.top = self.top.get_next() self.count -= 1 return temp.get_data() def Empty(self):#判断栈是否为空 # if self.top == self.base: if self.count == 0: return False else: return True def Top(self): #获得栈顶元素 p = SNode() p = self.top return p.get_data() def print(self): if not self.Empty(): print("栈已空") return temp = SNode() temp = self.top n = 0 while n < self.count: print(self.top.get_data()) self.top = self.top.get_next() n += 1 self.top = temp def compare(n,m ): return n in ["*", "/"] and m in ["+", "-"] def getValue(temp1, temp2, operator): if operator == "+": return temp1 + temp2 elif operator == "-": return temp1 - temp2 elif operator == "*": return temp1 * temp2 else: return temp1 / temp2 def transfersReckon(St1, St2): operator = St2.pop() temp2 = St1.pop() temp1 = St1.pop() St1.push(getValue(temp1, temp2, operator))#运算结果存入栈中 def Reckon(arr): St1 = Stack() St2 = Stack() i = 0 # 表达式的索引值 while i < len(arr): if arr[i].isdigit(): # 判断是否是数字字符 start = i # 数字字符开始位置 while i + 1 < len(arr) and arr[i + 1].isdigit(): i += 1 St1.push(int(arr[start: i + 1]))# i为最后一个数字字符的位置 elif arr[i] == ')': # 右括号，St2出栈同时St1出栈并计算，计算结果入栈St1，直到St2出栈一个左括号 while St2.Top() != '(': transfersReckon(St1, St2) St2.pop() # 出栈"(" elif not St2.Empty() or St2.Top() == "(": # 操作符栈为空，或者操作符栈顶为左括号，入栈 St2.push(arr[i]) elif arr[i] == "(" or compare(arr[i], St2.Top()): # 当前操作符为左括号或者比栈顶操作符优先级高，入栈St2 St2.push(arr[i]) else: # 优先级不比栈顶操作符高时，St2出栈同时St1出栈并计算，计算结果如栈St1 while St2.Empty() and not compare(arr[i], St2.Top()): if St2.Top() == "(": # 若遇到左括号，停止计算 break transfersReckon(St1, St2) St2.push(arr[i]) i += 1 # 遍历索引后移 while St2.Empty(): transfersReckon(St1, St2) print(St1.pop()) print("请输入表达式：") formula = input() Reckon(formula)3.二叉树排序树做了一个简单的二叉树排序树，附带前中后三种遍历方式class TreeNode:#树节点 def __init__(self,data): self.__data = data self.__lchild = None self.__rchild = None def set_data(self,data): self.__data = data def set_lchild(self,lchild): self.__lchild = lchild def set_rchild(self,rchild): self.__rchild = rchild def get_data(self): return self.__data def get_lchild(self): return self.__lchild def get_rchild(self): return self.__rchild class BinaryTree:#二叉树 def __init__(self): #初始化定义树根 self.__root = None def __judge_tree_root(self): #判断树根是否存在数据 return self.__root == None def add_tree_node(self,data): #添加树的结点0 temp = TreeNode(data) if self.__judge_tree_root(): self.__root = temp else: p = self.__root while True: if data <= p.get_data(): if p.get_lchild() == None: p.set_lchild(temp) break else: p = p.get_lchild() if data > p.get_data(): if p.get_rchild() == None: p.set_rchild(temp) break else: p = p.get_rchild() def three_trversal(self): print("前序遍历") self.preorder_traversal(self.__root) print("中序遍历") self.inorder_traversal(self.__root) print("后续遍历") self.subsequent_traversal(self.__root) def preorder_traversal(self,tree_node): if tree_node: print(tree_node.get_data()) self.preorder_traversal(tree_node.get_lchild()) self.preorder_traversal(tree_node.get_rchild()) def inorder_traversal(self,tree_node): if tree_node: self.inorder_traversal(tree_node.get_lchild()) print(tree_node.get_data()) self.inorder_traversal(tree_node.get_rchild()) def subsequent_traversal(self,tree_node): if tree_node: self.subsequent_traversal(tree_node.get_lchild()) self.subsequent_traversal(tree_node.get_rchild()) print(tree_node.get_data()) tree = BinaryTree() num = int(input("请输入你要录入的数据个数:")) while num > 0: data = input("请输入数据：") tree.add_tree_node(data) num -= 1 tree.three_trversal()4.图的邻接表和深广度遍历class QNode: def __init__(self): self.__next = None self.__data = None def set_next(self,next): self.__next = next def set_data(self,data): self.__data = data def get_next(self): return self.__next def get_data(self): return self.__data class Queue: def __init__(self): self.__front = None self.__rear = None self.length = 0 def InitQueue(self): self.__front = QNode() self.__rear = QNode() self.__front = self.__rear self.__front.set_next(None) def EnQueue(self,elem):#入队 p = QNode() if p == None: exit(0) p.set_data(elem) p.set_next(None) self.__rear.set_next(p) self.__rear = p self.length += 1 def DeQueue(self):#出队 self.length -= 1 if self.__front == self.__rear: return False #p = QNode() p = self.__front.get_next() e = p.get_data() self.__front.set_next(p.get_next()) if self.__rear == p: self.__rear = self.__front return e def JudgeEmpty(self):#判断队列是否为空 if self.length == 0: return True else: return False class VertexNode: #顶点结点类 def __init__(self): self.__data = None self.__firstarc = None def set_data(self,data): self.__data = data def set_firstarc(self,firstarc): self.__firstarc = firstarc def get_data(self): return self.__data def get_firstarc(self): return self.__firstarc class ArcNode: #边结点类 def __init__(self): self.__adjvex = None self.__weight = None self.__next = None def set_adjvex(self,node_data): self.__adjvex = node_data def set_weight(self,arc_weight): self.__weight = arc_weight def set_next(self,next): self.__next = next def get_adjvex(self): return self.__adjvex def get_weight(self): return self.__weight def get_next(self): return self.__next class GraphAdjacencyList: #图的邻接表类 def __init__(self): self.__vertex_node = [] self.visted = [] def addVertexNode(self,ver_node_data): #添加顶点 temp = VertexNode() temp.set_data(ver_node_data) for i in self.__vertex_node: if i.get_data() == ver_node_data: return False self.__vertex_node.append(temp) self.visted.append(False) return True def addArcNode(self,ver_node1,ver_node2,arc_weight): #添加边 index1 = self.verNodeIndex(ver_node1) index2 = self.verNodeIndex(ver_node2) if index1 == -1 or index2 == -1 or index1 == index2: return False temp = ArcNode() temp.set_adjvex(index2) temp.set_next(self.__vertex_node[index1].get_firstarc()) self.__vertex_node[index1].set_firstarc(temp) temp = ArcNode() temp.set_adjvex(index1) temp.set_next(self.__vertex_node[index2].get_firstarc()) self.__vertex_node[index2].set_firstarc(temp) return True def verNodeIndex(self,data): #获得顶点在列表中的索引值 num = 0 for i in self.__vertex_node: if i.get_data() == data: return num num += 1 return -1 def travelBFS(self,data): length = len(self.__vertex_node) while length != 0: self.visted[length-1] = False length -= 1 self.BFS(self.verNodeIndex(star_data)) for i in self.__vertex_node: if self.visted[self.verNodeIndex(i.get_data())] == False: self.BFS(self.verNodeIndex(i.get_data())) def BFS(self,v): queue = Queue() queue.InitQueue() print(self.__vertex_node[v].get_data()) self.visted[v] = True queue.EnQueue(v) while not queue.JudgeEmpty() : v = queue.DeQueue() p = self.__vertex_node[v].get_firstarc() while p: if not self.visted[p.get_adjvex()]: print(self.__vertex_node[p.get_adjvex()].get_data()) self.visted[p.get_adjvex()] = True queue.EnQueue(p.get_adjvex()) p = p.get_next() def travelDFS(self,data): self.DFS(self.verNodeIndex(star_data)) for i in self.__vertex_node: if self.visted[self.verNodeIndex(i.get_data())] == False: self.DFS(self.verNodeIndex(i.get_data())) def DFS(self,v): print(self.__vertex_node[v].get_data()) self.visted[v] = True p = self.__vertex_node[v].get_firstarc() while p: u = p.get_adjvex() if not self.visted[u]: self.DFS(u) p = p.get_next() # def show_ver_node(self): # for i in self.__vertex_node: # print(i.get_data()) g_a_l = GraphAdjacencyList() ver_node_num = int(input("请输入你要创建的顶点数：")) while ver_node_num > 0: while True: node_data = input("请输入顶点数据：") if g_a_l.addVertexNode(node_data) == True: break else: print("该顶点已存在请重新输入") ver_node_num -= 1 arc_node_num = int(input("请输入你要创建边的个数：")) while arc_node_num > 0: while True: node_data1 = input("请输入第一个结点的数据：") node_data2 = input("请输入第二个结点的数据：") arc_weight = int(input("请输入该边的权值：")) if g_a_l.addArcNode(node_data1,node_data2,arc_weight) == True: break else: print("输入错误请重新输入") arc_node_num -= 1 print("深度优先遍历") star_data = input("请输入一个顶点数据：") g_a_l.travelDFS(star_data) print("广度优先遍历") star_data = input("请输入一个顶点数据：") g_a_l.travelBFS(star_data) # g_a_l.DFS(g_a_l.verNodeIndex(star_data)) # g_a_l.show_ver_node() 5.哈夫曼树此代码使用哈夫曼树实现了简单的压缩解压#实验五哈夫曼树 class QNode: def __init__(self): self.__next = None self.__trNode = None self.__weight = 0 def set_weight(self,weight): self.__weight = weight def set_next(self,next): self.__next = next def set_trNode(self,trNode): self.__trNode = trNode def get_weight(self): return self.__weight def get_next(self): return self.__next def get_trNode(self): return self.__trNode class Queue: def __init__(self): self.__first = QNode() self.__size = 0 def set_first(self,first): self.__first=first def set_size(self,size): self.__size=size def get_first(self): return self.__first def get_size(self): return self.__size def createQueue(self,list): for i in list: aux = htNode() #创建一个树结点对象 aux.set_lchild(None) aux.set_rchild(None) aux.set_data(i.get_data())#赋值 self.EnQueue(aux,i.get_weight())#插入队列 #aux待插入的结点，i.get_weight()代表字符的权值 def EnQueue(self,aux,weight):#入队 temp = QNode() temp.set_weight(weight) temp.set_trNode(aux) if self.__size==0: temp.set_next(None) self.__first = temp self.__size += 1 return else: if weight <= self.__first.get_weight(): temp.set_next(self.__first) self.__first = temp self.__size += 1 return else: iterator = QNode() iterator = self.__first while iterator.get_next()!=None: if weight<=iterator.get_next().get_weight(): temp.set_next(iterator.get_next()) iterator.set_next(temp) self.__size += 1 iterator = iterator.get_next() if iterator.get_next() == None: temp.set_next(None) iterator.set_next(temp) self.__size += 1 return def getQueue(self): returnValue = htNode() if self.__size>0: returnValue = self.__first.get_trNode() self.__first = self.__first.get_next() self.__size -= 1 else: print("空队列") return returnValue class htNode: #哈夫曼树节点类 def __init__(self): self.__data = None self.__lchild = None self.__rchild = None def set_data(self,data): self.__data = data def set_lchild(self,lchild): self.__lchild = lchild def set_rchild(self,rchild): self.__rchild = rchild def get_data(self): return self.__data def get_lchild(self): return self.__lchild def get_rchild(self): return self.__rchild class htTree: #哈夫曼树 def __init__(self): self.__root = htNode() def get_root(self): return self.__root def three_trversal(self): print("中序遍历") self.inorder_traversal(self.__root) def inorder_traversal(self,tree_node): if tree_node: self.inorder_traversal(tree_node.get_lchild()) print(tree_node.get_data()) self.inorder_traversal(tree_node.get_rchild()) def BuildTree(self,list): huffmanQueue = Queue()#创建初始化队列 huffmanQueue.createQueue(list) while huffmanQueue.get_size()!=1: weigth = huffmanQueue.get_first().get_weight()#新节点的权值 weigth += huffmanQueue.get_first().get_next().get_weight() lchild = htNode() rchild = htNode() lchild = huffmanQueue.getQueue() rchild = huffmanQueue.getQueue() newNode = htNode() newNode.set_lchild(lchild) newNode.set_rchild(rchild) huffmanQueue.EnQueue(newNode,weigth) self.__root = huffmanQueue.getQueue() class hlNode: #编码链表的的节点类 def __init__(self): self.__data = None self.__code = [] self.__next = None def set_data(self,data): self.__data = data def set_code(self,code,k): n = 0 while n<k: self.__code.append(code[n]) n += 1 def set_next(self,next): self.__next = next def get_data(self): return self.__data def get_next(self): return self.__next def get_code(self): return self.__code class hlEncode: #编码类 def __init__(self): self.__first = None self.__last = None def BuildList(self,huffmanTree): code = [] k = 0 self.traverseTree(huffmanTree.get_root(),k,code) def traverseTree(self,treeNode,k,code): if treeNode.get_lchild() == None and treeNode.get_rchild() == None: aux = hlNode() aux.set_code(code,k) aux.set_data(treeNode.get_data()) aux.set_next(None) # print(code) # print(treeNode.get_data()) if self.__first == None: self.__first = aux self.__last = aux else: self.__last.set_next(aux) self.__last = aux #左0 右1 if treeNode.get_lchild() != None: code.append('0') if k <len(code): code[k] = '0' self.traverseTree(treeNode.get_lchild(),k+1,code) if treeNode.get_rchild() !=None: code.append('1') if k <len(code): code[k] = '1' self.traverseTree(treeNode.get_rchild(),k+1,code) def encode(self,list): if self.__first == None: return False traversal = hlNode() traversal = self.__first for i in list: traversal = self.__first while traversal.get_data()!=i.get_data(): traversal = traversal.get_next() print(traversal.get_data(),":",traversal.get_code()) return True def decode(self,tree,list): if tree.get_root()==None: print("1") return False traversal = tree.get_root()#临时根 for i in list: if traversal.get_lchild()==None and traversal.get_rchild()==None: print(traversal.get_data()) traversal = tree.get_root() if i == '0': traversal = traversal.get_lchild() if i == '1': traversal = traversal.get_rchild() if i!='0'and i!='1': print("2") return False #防止编码最后一次不输出 if traversal.get_lchild() == None and traversal.get_rchild() == None: print(traversal.get_data()) traversal = tree.get_root() print() return True class Elem: def __init__(self,data,weight): self.__data = data self.__weight = weight def set_data(self,data): self.__data = data def set_weight(self,weight): self.__weight = weight def get_data(self): return self.__data def get_weight(self): return self.__weight tree = htTree()#哈夫曼树 encoding = hlEncode()#编码 while True: print() print("创建编码表输入1") print("显示编码表输入2") print("显示哈夫曼树输入3") print("解码输入4") n = int(input("请输入你的选择：")) if n==1: num = int(input("请输入字符集大小：")) list = [] while num>0: elem = Elem(input("请输入字符："),int(input("请输入字符代表的权值："))) list.append(elem) num -= 1 tree.BuildTree(list) encoding.BuildList(tree) elif n==2: if encoding.encode(list): print("显示成功") else: print("操作失败") elif n==3: tree.three_trversal() elif n==4: coding = str(input("请输入编码：")) if encoding.decode(tree,coding): print("解码成功") else: print("解码失败") else: break
我们在后端服务中有时要进行数据之间的交流，所以在这里我说一下用后端模拟http请求去访问其他后端服务的方法。创建请求连接 //生成要访问的url url := "http://192.168.235.128:8080" //http请求 request, err := http.NewRequest("POST", url, "nil") // request, err := http.NewRequest("POST", url, body)第二行：我们定义一个url用来储存要请求服务的地址第四行：我们用http包的NewRequest()函数来创建连接，第一个参数是请求方式，第二个参数是访问地址，第三个放http请求的body（如第五行）,在这里我们用不到，所以我们设置为空访问连接//生成clint 参数为默认 client := &http.Client{} //处理返回结果 response, err := client.Do(request)第二行：我们模拟一个用户对象client。第四行：用client对象去访问要访问的服务，然后返回一个*http.Request对象解析数据// 将结果定位到标准输出 也可以直接打印出来 或者定位到其他地方进行相应的处理 jsonData, err = ioutil.ReadAll(response.Body) //获取post的数据 fmt.Println(string(jsonData)) if err != nil { log.Println("response.Body err:", err) } err = json.Unmarshal(jsonData, &redisStatus) if err != nil { log.Println("fixedRefresh Unmarshal json err 2:", err) pb.ResponseServe(w, &returnArg, 404, "error") }当我们接收到其他服务返回来的响应对象时，我们就可以把他body里面的json数据解析出来。完整代码这个例子是从前端接收到请求然后再拿前端请求里的数据去访问其他服务，最终获得想要的数据然后再返回给前端。package ssf import ( "bytes" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "studentStatus/pb" ) func fixedRefresh(w http.ResponseWriter, r *http.Request) { returnArg := pb.ReturnArg{} //响应对象 jsonData, err := ioutil.ReadAll(r.Body) //获取post的数据 if err != nil { log.Println("fixedRefresh readAll json err:", err) pb.ResponseServe(w, &returnArg, 404, "error") return } defer r.Body.Close() var redisStatus pb.RedisStatus //定义初始化化redis数据的 err = json.Unmarshal(jsonData, &redisStatus) if err != nil { log.Println("fixedRefresh Unmarshal json er:", err) pb.ResponseServe(w, &returnArg, 404, "error") return } redisStatus.Fid = redisStatus.BuildingNo data, err := json.Marshal(redisStatus) if err != nil { log.Println("fixedRefresh Marshal json err:", err) } body := bytes.NewBuffer(data) //转化[]byte //生成clint 参数为默认 client := &http.Client{} //生成要访问的url url := "http://192.168.235.128:9090/returnSno" //http请求 // request, err := http.NewRequest("POST", url, body) request, err := http.NewRequest("POST", url, body) if err != nil { log.Println("request err:", err) return } //处理返回结果 response, err := client.Do(request) if err != nil { log.Println("response err:", err) return } // 将结果定位到标准输出 也可以直接打印出来 或者定位到其他地方进行相应的处理 jsonData, err = ioutil.ReadAll(response.Body) //获取post的数据 fmt.Println(string(jsonData)) if err != nil { log.Println("response.Body err:", err) return } err = json.Unmarshal(jsonData, &redisStatus) if err != nil { log.Println("fixedRefresh Unmarshal json err 2:", err) pb.ResponseServe(w, &returnArg, 404, "error") return } } func main() { http.HandleFunc("/fixedRefresh", fixedRefresh) //特定时间刷新学生临时记录 if err := http.ListenAndServe(":8090", nil); err != nil { log.Fatal("ListenAndServe:", err) } } 从上例第十一行与第五十九行我们可知无论在http.Request中还是在http.Reponse中都可以读取body中的json数据，这样我们就可以把其他服务的数据返回给前端了。
Xshell和Xftp的初步使用XshellXshell是一个强大的安全终端模拟软件，它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议。Xshell 通过互联网到远程主机的安全连接以及它创新性的设计和特色帮助用户在复杂的网络环境中享受他们的工作。Xshell可以在Windows界面下用来访问远端不同系统下的服务器，从而比较好的达到远程控制终端的目的。除此之外，其还有丰富的外观配色方案以及样式选择。我在这里将会介绍Xshell和linux虚拟机建立连接。建立连接1.点击左上角的文件然后点击新键会出现一个弹窗。2.名称自定义，一般写连接虚拟机的名字，协议选择SSH协议，端口号选22，主机填写你要建立连接的虚拟机的IP地址。获得虚拟机的IP（1）打开要建立连接的虚拟机，在这里我要与CentOS7建立连接。（2）输入ip addr命令，会出现该虚拟机的ip地址，在这里我们取192.168.10.63。获得ip地址后我们把ip地址填入Xshell弹窗的主机栏里，并点击用户身份验证，把你虚拟机的用户名和该用户名的密码填上（在这里推荐填root用户），点击确定即可。3.找到自己建立的连接，点击连接即可。!连接成功使用Xshell的一个好用的地方就是他能把windows上的文字粘贴复制到虚拟机上1.我们先创建一个文件 a.2.打开文件a,并去其他地方复制一段文字，右键点击会出现粘贴。3.反正功能很多，随便试。Xftp建立连接1.点击左上角的文件然后点击新键会出现一个弹窗。名称自己定义，主机还是建立连接的linuxIP，协议选择SFTP，端口号选择22，下面登录栏的方法选择Password，用户名和密码选择虚拟机的root用户和该用户的密码。点击选项可以设置默认连接的文件夹2.点击确定后，左上角的文件有个打开点击一下，然后选择你要建立的连接。连接成功后可以直接拖拽文件到彼此的系统上