0%

按值传送

在java中,若将对象实例作为变量传送给方法时,自变量的值是对对象的引用。

重载方法名

java依靠参数列表来区分不同的重载的方法

java构造方法

java的默认构造方法会为各个域的值赋空(null)或赋0。

java构造方法重载时常常借助this关键字

finalize()方法

相当于C++中的析构方法,用于释放对象所占用的系统资源。

this引用

在java中,常常不需要像python那样,总是用类似self.的this.来指明类中的对象.

java继承与extends关键字

格式:

1
2
3
public class A extends B{
...
}

表面A派生于B

子类拥有父类所有变量和方法

单重继承

若一个类有父类,则其父类只能有一个

子类不能从父类继承构造方法

只有两种方法能让一个类得到一个构造方法:一,自己编写一个;二,不编写,系统默认给予

子类不能访问父类的私有元素

转换对象

假设Employee是Manager和Contractor的父类

那么:

1
2
3
4
5
6
// 合法
// 此时e可访问Employee的变量和方法,却不能访问Manager独有的
Employee e = new Manager();

// 不合法
Manager m = new Employee();

Employee类型的引用指向一个对象,但分不清该对象是Employee类还是Manager类还是Contractor类,则:

1
2
3
4
5
6
7
8
9
10
11
12
public void method(Employee e) {
if (e instanceof Manager) {
// 此时e只能访问Employee的变量和方法,若想要访问Manager的,需要转换引用
Mnager m = (Manager)e; // m能够访问Manager的变量和方法
}
else if (e instanceof Contractor){
...
}
else {
...
}
}

转换引用的类之间必须要有继承关系

异类集合

可以创建有公共祖先的任何元素的集合

1
2
3
Employee [] staff = new Employee[1024];
staff[0] = new Manager();
staff[1] = new Employee();

super关键字

若子类已重写父类的方法,但子类还想访问,则可用super关键字

super.method()访问的不一定是父类自由的,也可能是父类从祖先继承来的

重写规则

  • 重写方法允许访问的权限不能比原方法小
  • 抛出的异常不能比原方法多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这样写是错的
class SuperClass{
public void method1(){
...
}
public void method2(){
}
}

class SonClass extends SuperClass{
private void method1(){
...
}
public void method2() throws IOException{
}
}

父类构造方法的调用

Java要求父类的对象要在子类运行前完全初始化

借助super()初始化

java包

基本概念

编译单元:一个java源代码文件。规定一个编译单元中只能有一个public类,且该类名名称与文件相同

支撑类:编译单元中其它的类

包机制:用于类名空间的管理

包:类的容器,利用包来划分名字空间,分隔类名空间

设文件声明:

1
package java.awt.image

则此文件需放在java\awt\image目录下

import语句

设已定义包:

1
2
3
4
package mypackage;
public class MyClass{
...
}

则在使用该类的办法:

1
2
3
4
mypackage.MyClass m = new mypackage.MyClass();

import mypackage.*
MyClass m = new MyClass();

类成员

包括类变量和类方法

4zY9yT.png

类变量

有时被称为静态变量,使用static关键字定义,在支撑类中定义的类变量能被包中所有成员访问,若其在public类中调用,还可被其它包访问

访问方式:类名.类变量

类方法

同样使用static定义

访问方式:类名.类方法

调用类方法的限制:

  • 静态方法只能使用其内部定义的参数或静态变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 错误引用
public class Wrong{
int x;
public static void main(String args[]){
x = 9;
}
}
// 正确
public class wrong {
int x;

public static void main(String args[]) {
hello_world h = new hello_world();
h.x = 9;
}
}
  • 静态方法不能被重写
1
2
3
4
5
6
7
8
// 错误写法
class Super{
static void noOverload(){}
}

class Sub{
void noOverload() {}
}

关键字final

一个变量被标记为final,则会成为一个常量;一个方法被final修饰,则不能被重写;一个类被定义为final,则不能有子类。

终极类

错误写法:

1
2
3
4
5
6
7
8
final public class FinalClass{
int memberar;
void memberMethod() {};
}
class SubFinalClass extends FinalClass{
int submembervar;
void subMemberMethod;
}

终极方法

1
2
3
4
5
6
7
8
9
10
class FinalMethodClass{
final void finalMethod (){
...
}
}
class OverloadClass extends FinalMethodClass{
void finalMethod(){ // 将会报错
...
}
}

终极变量

1
2
3
4
5
6
7
8
9
class Const{
final float PI = 3.14f;
}
public class UseConst{
public static void main(String args[]){
Const myconst = new Const();
myconst.PI = 3.14159f; // 报错
}
}

若将一个引用类型变量标记为final,那么这个变量将不能再指向其他对象,但它所指对象的值可改变

1
2
3
4
5
6
7
8
9
10
class Car{
int number = 1234;
}
class FinalVariable{
public static void main(String args[]){
final Car mycar = new Car();
mycar.number = 8888; // 可以
mycar = new Car(); // 错误
}
}

抽象类

借助abstract关键字创建抽象类

示例:

1
2
3
4
5
6
public abstract class Shape{
// 定义体
}// 创建子类以使此类有用

// 抽象方法
public abstract <retrunType> <methodName>(参数列表);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
abstract class ObjectStorage{
int objectnum=0;
Object storage[] = new Object[100];

abstract void put(Object o);
abstract Object get();
}

class Stack extends ObjectStorage{
private int point=0;

public void put(Object o){
storage[point++]=o;
objectnum++;
}
public Object get(){
objectnum--;
return storage[--point];
}
}

class Queue extends ObjectStorage{
private int top=0;
private int bottom=0;

public void put(Object o){
storage[top++]=o;
objectnum++;
}
public Object get(){
objectnum--;
return storage[bottom++]
}
}

接口

接口特点:

  • 本身具有数据成员和方法
  • 数据成员一定要赋初值,且该值不能再被修改
  • 方法必须是抽象方法

使用接口的原因:

  • 用于实现抽象
  • 通过接口,实现多重继承
  • 实现松耦合

形式:

1
2
3
[接口修饰符] interface 接口名称 [extends 父类名]{
... //方法原型或静态常量
}

接口的实现:

  • 实现接口的类不从该接口的定义中继承任何行为

  • 该类的任何对象可以调用接口中定义的方法

  • 一个类可实现多个接口

  • 用implements表示该类实现的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	interface Shape2D{
doube pi=3.14;
double area;
}

class Circle implements Shape2D{
double radius;
public Circle(double r){
radius=r;
}
public double area(){
return (pi * radius * radius);
}
}

class Rectangle implements Shape2D{
int width,height;
public Rectangle(int w,int h){
width=w;
height=h;
}
public double area(){
return (width * height);
}
}

同抽象类一样,使用接口名称作为一个引用变量也是允许的,既可以声明接口类型的变量(或数组)来访问对象

1
2
3
4
5
6
7
8
9
public class VariableTester {
public static void main(String []args){
Shape2D var1,var2;
var1 = new Rectangle(5,6);
System.out.println(var1.area());
var2 = new Rectangle(2.0);
System.out.println(var2.area());
}
}

内部类

基本

也称嵌套类

特性:

  • 类名只能在定义范围内使用
  • 内部类可以使用外部类的类变量和实例变量,也可使用局部变量
  • 内部类可为abstract类型
  • 内部类可以是一个接口,需要另一个内部类来实现
  • 内部类可以被定义为private或protected
  • 内部类可以无视外部类的访问保护访问外部类成员
  • 被定义为static的内部类应视为顶层类,不能再使用局部范围或其他内部类中的数据和变量
  • 内部类不能定义static型成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Test10 {
public static void main(String[] args) {
/*方式1创建成员内部类对象*/
C c = new C();
C.D d = c.new D();
/*方式2创建成员内部类对象*/
C.D d1 = c.getClassD();
}
}
class C{
private String name = "外部类";
public void run(){
System.out.println("外部类奔跑");
}
/*创建一个返回D对象的方法*/
public D getClassD(){
return new D();
}
/*使用内部类的属性和方法*/
public void eat(){
D d = new D();
System.out.println(d.value);
d.say();
}
class D{
private String value = "DDD";
private String name = "内部类";
public void say(){
System.out.println(C.this.name);
System.out.println(name);
run();
}
}
}

匿名类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Polygon {
public void display() {
System.out.println("在 Polygon 类内部");
}
}

class AnonymousDemo {
public void createClass() {

// 创建的匿名类继承了 Polygon 类
Polygon p1 = new Polygon() {
public void display() {
System.out.println("在匿名类内部。");
}
};
p1.display();
}
}

class Main {
public static void main(String[] args) {
AnonymousDemo an = new AnonymousDemo();
an.createClass();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Polygon {
public void display();
}

class AnonymousDemo {
public void createClass() {

// 匿名类实现一个接口
Polygon p1 = new Polygon() {
public void display() {
System.out.println("在匿名类内部。");
}
};
p1.display();
}
}

class Main {
public static void main(String[] args) {
AnonymousDemo an = new AnonymousDemo();
an.createClass();
}
}

protobuf基本语法

https://developers.google.com/protocol-buffers/docs/proto3#enum

python沟通protobuf时会遇到的问题

若想传一个列表类型,在poroto文件中应使用repeated关键字

1
2
3
4
message HelloRequest {
string name = 1;
repeated int32 id = 2;
}

此时在python代码中不能对其直接实例化后直接赋值,而是应使用列表的方法(如:extend,append)等去改变它的值

此外,嵌套定义message也不能实例化后直接赋值

go_package详解

设置:

1
option go_package = "common/stream/proto/v1";

执行:

cd 到目标文件夹

1
protoc --go_out=plugins=grpc:./ ./hello.proto

即使目标文件夹下不存在common/stream/proto/v1也会生成common/stream/proto/v1/hello.pb.go脚本

或者:

设置:

1
 option go_package = "../common/stream/proto/v1";

执行命令后会在父路径下生成一系列文件夹及文件

protobuf的一点原理

protobuf不是根据名称来对应的,而是根据编号:

1
2
3
4
5
message HelloRequest {
// 根据编号1来传值而不是name
string name = 1;
repeated int32 id = 2;
}

假设对方proto文件为:

1
2
3
4
5
message HelloRequest {
// 根据编号1来传值而不是name
string name = 2;
repeated int32 id = 1;
}

此时会将name映射到id,id映射到name

一个proto文件中引入其它proto文件

应用场景:有一些常用的service与message可能很多proto文件都要用到,这时不妨将这些service与message放到一个公用的proto文件中,在需要调用时import。

1
2
3
4
5
6
7
8
9
10
// base.proto
syntax = "proto3";

message Empty{
// 置空
}

message Pong{
string id = 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 文件二
syntax = "proto3";
import "python_grpc_helloworld/proto/base.proto";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
// 一个服务必须有参数,此时我们可以传空参数
rpc Ping (Empty) returns (Pong) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

message对象嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;

message Result {
string name = 1;
string age = 2;
}
repeated Result persons = 2;
}

python下使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import grpc
from concurrent import futures
from proto import hello_pb2,hello_pb2_grpc

class Greeter(hello_pb2_grpc.GreeterServicer):

def SayHello(self, request, context):
# 使用嵌套对象时的生成
names = ["name1", "name2", "name3"]
results = []
for name in names:
result = hello_pb2.HelloReply.Result(name=name,age="17")
results.append(result)
return hello_pb2.HelloReply(message=f"hello,{request.name}",persons=results)

if __name__ == "__main__":
server = grpc.server(futures.ThreadPoolExecutor(5))
hello_pb2_grpc.add_GreeterServicer_to_server(servicer=Greeter(),server=server)
server.add_insecure_port('127.0.0.1:50000')
server.start()
server.wait_for_termination()
1
2
3
4
5
6
7
8
import grpc
from python_grpc_helloworld.proto import hello_pb2_grpc,hello_pb2

with grpc.insecure_channel('127.0.0.1:50000') as channel:
stub = hello_pb2_grpc.GreeterStub(channel)
rsp: hello_pb2.HelloReply = stub.SayHello(hello_pb2.HelloRequest(name="lily"))

print(rsp.persons) # l

go下使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"context"
"net"

"google.golang.org/grpc"

"awesomeProject/grpc_test/proto"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,error){
var results []*proto.HelloReply_Result
for i:=0;i<10;i++{
result := proto.HelloReply_Result{
Name: "name",
Age: string(i),
}
results = append(results,&result)
}
return &proto.HelloReply{
Message: "Hello" + request.Name,
Persons: results,
},nil
}

func main(){
server := grpc.NewServer()
proto.RegisterGreeterServer(server,&Server{})
listen,err := net.Listen("tcp","127.0.0.1:50000")
if err != nil{
panic("fail to listen:" + err.Error())
}
err = server.Serve(listen)
if err != nil{
panic("fail to start:" + err.Error())
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"awesomeProject/grpc_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
)

func main(){
conn, err := grpc.Dial("127.0.0.1:50000", grpc.WithInsecure())
if err != nil{
panic(err)
}
defer conn.Close()

c := proto.NewGreeterClient(conn)
resp, err := c.SayHello(context.Background(), &proto.HelloRequest{Name:"Lily"})
if err != nil{
panic(err)
}
fmt.Println(resp.Persons)
}

几种类型

枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
syntax = "proto3";

option go_package = "./;proto";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}


message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;

message Result {
string name = 1;
string age = 2;
}
repeated Result persons = 2;

enum Gender{
MALE = 0;
FEMALE = 1;
}

Gender g = 3;
}

生成的相关代码

1
2
3
4
5
6
type HelloReply_Gender int32

const (
HelloReply_MALE HelloReply_Gender = 0
HelloReply_FEMALE HelloReply_Gender = 1
)

map

不建议使用,message本身很像map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
syntax = "proto3";

option go_package = "./;proto";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}


message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;

message Result {
string name = 1;
string age = 2;
}
repeated Result persons = 2;

enum Gender{
MALE = 0;
FEMALE = 1;
}
Gender g = 3;

map<string ,string >mp = 4;
}
1
2
3
4
5
6
7
// 客户端用法
return &proto.HelloReply{
Message: "Hello" + request.Name,
Persons: results,
G: proto.HelloReply_FEMALE,
Mp: map[string]string{"hhh":"2333","QAQ":"QWQ"},
},nil

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
list_ = [x for x in range(10)]

# 注意运算符优先级
if (list_length := len(list_)) > 7:
print(f"列表太长,长度:{list_length}")

list_ = [list_len:=len(list_),list_len**2,list_len**3]
print(list_)

# 若为true执行,false不执行
import re
desc = "hwc:19"
if m := re.match("hwc:(.*)",desc):
print(m.group(1))

类型注解

动态语言不需要声明变量类型,这种做法不太好

惯例:

1
2
3
# 隐式的说明变量类型
course_dict = {}
course_list = []
1
2
3
4
# 显式说明,只起到一个提示作用,实际不会影响使用
age: int = 18
name: str = "hwc"
sex: bool = True

protobuf

Protocol Buffer:轻量高效结构化存储数据

使用protobuf之前必须按格式写一个文件,根据这个文件生成各种脚本

以python为例:

1
2
3
4
5
6
7
8
9
// file:proto.hello
//必须标明版本
syntax = "proto3";

//只能发送message类型
message HelloRequest {
string name = 1; //name表示名称, name编号为1
string date = 2;
}

命令行cd进文件夹后执行:

1
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. hello.proto

生成:

1
2
3
4
├─protobuf3_demo
│ │ hello.proto
│ │ hello_pb2.py
│ │ hello_pb2_grpc.py

记得改hello_pb2_grpc.py中import路径,否则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用示例
from protobuf3_demo import hello_pb2

request = hello_pb2.HelloRequest()

request.name = "hhhh"
request.date = "2021.10.2"
req_str = request.SerializeToString()
print(req_str)

request2 = hello_pb2.HelloRequest()

request2.ParseFromString(req_str)
print(request2)

python grpc helloworld

首先写.proto文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

package proto;//不能不一样

// 服务名称
service Greeter {
// 依次为:rpc关键字,函数名,函数参数,return关键字,返回值,函数内容体
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

执行命令并改import路径

完成操作后,开始写server和client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import grpc
from concurrent import futures
from proto import hello_pb2,hello_pb2_grpc

class Greeter(hello_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return hello_pb2.HelloReply(message=f"hello,{request.name}")

if __name__ == "__main__":
# 1,实例化server
server = grpc.server(futures.ThreadPoolExecutor(5))
# 2,注册逻辑到server中
hello_pb2_grpc.add_GreeterServicer_to_server(servicer=Greeter(),server=server)
# 3,启动server
server.add_insecure_port('127.0.0.1:50000')
server.start()
# 4,让server保持服务
server.wait_for_termination()
1
2
3
4
5
6
7
8
import grpc
from python_grpc_helloworld.proto import hello_pb2_grpc,hello_pb2

with grpc.insecure_channel('127.0.0.1:50000') as channel:
stub = hello_pb2_grpc.GreeterStub(channel)
rsp: hello_pb2.HelloReply = stub.SayHello(hello_pb2.HelloRequest(name="lily"))

print(rsp.message)

python concurrent.futures

  • 基本组成:

    concurrent.futures.Executor类:虚拟基类,ThreadPoolExecutor是其子类

    submit(function, argument):调度函数执行,argument为参数

    map(function,argument):同上,但是以异步的方式调度函数

    shutdown(Wait=True):发出让执行者释放所有资源的信号,wait为True时等待任务完成,False时立即返回

    concurrent.futures.Future:封装了异步执行,在executor.submit()时创建

  • concurrent.futures.ThreadPoolExecutor类

1
concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())

max_workers:最大线程数;若为None或没有指定,默认为机器处理器数量

这是一个线程池对象

go grpc helloworld

在go中写.proto文件,基本和python一样(除了option那一行,一定要完全一样)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
syntax = "proto3";

package proto;//不能不一样
option go_package="./;proto";

// 服务名称
service Greeter {
// 依次为:rpc关键字,函数名,函数参数,return关键字,返回值,函数内容体
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

cd到目标文件下:

执行

1
protoc --go_out=plugins=grpc:./ ./hello.proto

生成一个脚本文件

之后编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//服务端
package main

import (
"context"
"net"

"google.golang.org/grpc"

"awesomeProject/grpc_test/proto"
)

type Server struct{}

//简单的通信需要一个上下文对象
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,error){
return &proto.HelloReply{
Message: "Hello" + request.Name,
},nil
}

func main(){
server := grpc.NewServer()
proto.RegisterGreeterServer(server,&Server{})
//指定端口
listen,err := net.Listen("tcp","127.0.0.1:50000")
if err != nil{
panic("fail to listen:" + err.Error())
}
//开始监听
err = server.Serve(listen)
if err != nil{
panic("fail to start:" + err.Error())
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//客户端
package main

import (
"awesomeProject/grpc_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
)

func main(){
conn, err := grpc.Dial("127.0.0.1:50000", grpc.WithInsecure())
if err != nil{
panic(err)
}
defer conn.Close()

c := proto.NewGreeterClient(conn)
resp, err := c.SayHello(context.Background(), &proto.HelloRequest{Name:"Lily"})
if err != nil{
panic(err)
}
fmt.Println(resp.Message)
}

grpc stream

  • 简单模式(simple RPC)

    客户端发起一个请求,服务端返回一个请求

  • 服务端数据流模式(Server-side streaming RPC)

    客户端发起一个请求,服务端返回一段连续的数据流(典型如客户端向服务端发送一个请求,服务端源源不断的返回数据)

  • 客户端数据流模式(CLient-side streaming RPC)

    客户端源源不断的发送数据,发送结束后,客户端返回一个响应(如物联网终端向服务器报送数据)

  • 双向数据流模式(Bidirectional streaming RPC)

    实现实时交互(如聊天)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
syntax = "proto3";

option go_package = "./;proto";

service Greeter{
//stream关键字表明是数据流
rpc GetStream(StreamReqData) returns (stream StreamResData); //服务端流模式
rpc PutStream(stream StreamReqData) returns (StreamResData); //客户端流模式
rpc AllStream(stream StreamReqData) returns (stream StreamResData); //双向流模式
}

message StreamReqData {
string data = 1;
}

message StreamResData {
string data = 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// server
package main

import (
"awesomeProject/stream_grpc_test/proto"
"errors"
"fmt"
"google.golang.org/grpc"
"log"
"net"
"sync"
"time"
)

const PORT = ":50000"
type Server struct {}

// 服务端 单向流
func (s *Server)GetStream(req *proto.StreamReqData,serveStream proto.Greeter_GetStreamServer) error{
for i:=0;i<10;i++ {
err := serveStream.Send(&proto.StreamResData{
Data: time.Now().String(),
})
if err != nil{
panic(err.Error())
}
time.Sleep(time.Second)
}
return nil
}

// 客户端 单向流
func (s *Server)PutStream(clientStream proto.Greeter_PutStreamServer) error{
for {
a,err := clientStream.Recv()
if err != nil{
return errors.New("fail to receive data:"+err.Error())
}
log.Println(a.Data)
}
}

// 双向流
func (s *Server)AllStream(allStream proto.Greeter_AllStreamServer) error{
wg := sync.WaitGroup{}
wg.Add(2)
go func(){
defer wg.Done()
for i:=0;i<10;i++{
err := allStream.Send(&proto.StreamResData{Data: time.Now().String()})
if err != nil{
fmt.Println("allStream sending goes wrong:"+err.Error())
}
time.Sleep(time.Second)
}
}()

go func(){
defer wg.Done()
for i:=0;i<10;i++{
data,err := allStream.Recv()
if err != nil{
fmt.Println("allStream sending goes wrong:"+err.Error())
break
}
fmt.Println(data)
}
}()
wg.Wait()
return nil
}

func main(){
server := grpc.NewServer()
proto.RegisterGreeterServer(server,&Server{})
listen,err := net.Listen("tcp",PORT)
if err != nil{
panic(err.Error())
}

err = server.Serve(listen)
if err != nil{
panic(err.Error())
}


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//client
package main

import (
"awesomeProject/stream_grpc_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
"log"
"sync"
"time"
)

const ADDR = "127.0.0.1:50000"

func main(){
conn, err := grpc.Dial(ADDR,grpc.WithInsecure())
if err != nil{
panic(err.Error())
}
defer conn.Close()

c := proto.NewGreeterClient(conn)
// 服务端 流模式
res,err := c.GetStream(context.Background(),&proto.StreamReqData{Data: "Hello,Lily!"})
if err != nil{
log.Fatalln("fail to send message:"+err.Error())
}

for {
a,err := res.Recv()
if err != nil{
log.Println("fail to receive data:"+err.Error())
break
}
log.Println(a)
}

// 客户端 流模式
putStream,err := c.PutStream(context.Background())
for i:=0;i<10;i++{
err = putStream.Send(&proto.StreamReqData{Data: time.Now().String()})
if err != nil{
log.Println("fail to send stream:"+err.Error())
}
time.Sleep(time.Second)
}

// 双向流模式
allStream,err := c.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func(){
defer wg.Done()
for i:=0;i<10;i++{
err := allStream.Send(&proto.StreamReqData{Data: time.Now().String()})
if err != nil{
fmt.Println("allStream sending goes wrong:"+err.Error())
}
time.Sleep(time.Second)
}
}()

go func(){
defer wg.Done()
for i:=0;i<10;i++{
data,err := allStream.Recv()
if err != nil{
fmt.Println("allStream sending goes wrong:"+err.Error())
break
}
fmt.Println(data)
}
}()
wg.Wait()

}

迭代器

【进阶Python】第五讲:迭代器与生成器

实现:

  • 可迭代对象只需要实现iter方法
  • 迭代器还需要实现next方法,使用next访问非迭代器会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Iterator:
def __init__(self,array):
self.array = array
self.index = 0

def __iter__(self):
return self

def __next__(self):
if(self.index < len(self.array)):
result = self.array[self.index]
self.index += 1
return result
raise StopIteration

it = Iterator([1,2,3,5,7,9])

for i in it:
print(i)

# 啥也没输出
for t in it:
print(t)

# 此时再迭代会报错
# print(next(it))
# print(next(it))
# print(next(it))
# print(next(it))
# print(next(it))
# print(next(it))

生成器

定义:一边循环一边计算的机制,叫做生成器:generator

生成器的用途:列表所有数据存储在内存中,若数据量过多非常消耗内存。若列表中元素按照某种算法推演出来,那么可以一边循环一边计算后面的元素,而不必生成完整列表,从而节省大量空间。

方法一:

1
2
3
4
list_ = [x+x for x in range(10)]
print(type(list_)) # list
list__ = (x+x for x in range(10))
print(type(list__)) # generator

方法二:

函数中加yield:yield相当于return一个值,并记住位置,下次调用函数从该yield语句之后执行

send()与next()一样,都可以让generator走一步,但send()可以传一个值,修改上一个yield表达式

如:a = yield 5,第一次运行到这里时返回5,a未被赋值;这时使用.send(10),就会赋10给a

1
2
3
4
5
6
7
8
9
10
11
12
#encoding:UTF-8
def yield_test(n):
for i in range(n):
yield call(i)
print("i=",i)
print("Done.")

def call(i):
return i*2

for i in yield_test(5):
print(i,",")

线程对象:threading.Thread

threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

  • traget:该线程要调用的函数
  • args:该线程要传入的参数,元组形式,默认为空元组,可以和kwargs一起使用
  • kwargs:该线程要传入的参数,字典形式

方法:

  • sart():开启一个线程,使run()方法在一个线程中被调用

  • run():主要用去创建子类时重写

  • join():确保调用该方法的线程执行完之后再执行下一个线程

  • is_alive():判断该线程是否存活

  • daemon():判断该线程是否是守护线程。所有在主线程中创建的线程默认都是非守护线程。

补充:

  • 守护线程:只有当所有守护线程都结束,python程序才会退出。若python程序退出,那么所有的守护线程都会被终止,从而结束程序
  • 非守护线程:当python程序结束时,若还有非守护线程在运行,那么会等到所有非守护线程结束才会退出

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"""
通过继承threading.Thread的子类创建线程
"""
import time
import threading


class TestThread(threading.Thread):
def __init__(self, para='hi', sleep=3):
# 重写threading.Thread的__init__方法时,确保在所有操作之前先调用threading.Thread.__init__方法
super().__init__()
self.para = para
self.sleep = sleep

def run(self):
"""线程内容"""
time.sleep(self.sleep)
print(self.para)


def main():
# 创建线程
thread_hi = TestThread()
thread_hello = TestThread('hello', 1)
# 启动线程
thread_hi.start()
thread_hello.start()
print('Main thread has ended!')


if __name__ == '__main__':
main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"""
使用join方法阻塞主线程
"""
import time
import threading


def test_thread(para='hi', sleep=5):
"""线程运行函数"""
time.sleep(sleep)
print(para)


def main():
# 创建线程
thread_hi = threading.Thread(target=test_thread)
thread_hello = threading.Thread(target=test_thread, args=('hello', 1))
# 启动线程
thread_hi.start()
thread_hello.start()
time.sleep(2)
print('马上执行join方法了')
# 执行join方法会阻塞调用线程(主线程),直到调用join方法的线程(thread_hi)结束
thread_hi.join()
print('线程thread_hi已结束')
# 这里不会阻塞主线程,因为运行到这里的时候,线程thread_hello已经运行结束了
thread_hello.join()
print('Main thread has ended!')

# 以上代码只是为了展示join方法的效果
# 如果想要等所有线程都运行完成后再做其他操作,可以使用for循环
# for thd in (thread_hi, thread_hello):
# thd.join()
#
# print('所有线程执行结束后的其他操作')


if __name__ == '__main__':
main()

  • 锁只有锁定与非锁定两种状态
  • 当一个锁被锁定时,它并不属于某一个线程
  • 锁被创建时,处于非锁定状态,使用acquire()方法来锁定锁;一个锁定的锁无法再次获得
  • 使用release()来释放一个锁,不只是获得锁的线程可以释放锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"""
使用锁实现线程同步
"""
import time
import threading

# 创建锁
lock = threading.Lock()

# 全局变量
global_resource = [None] * 5


def change_resource(para, sleep):
# 请求锁
lock.acquire()

# 这段代码如果不加锁,第一个线程运行结束后global_resource中是乱的,输出为:修改全局变量为: ['hello', 'hi', 'hi', 'hello', 'hello']
# 第二个线程运行结束后,global_resource中还是乱的,输出为:修改全局变量为: ['hello', 'hi', 'hi', 'hi', 'hi']
# 若想在函数内部对函数外的变量进行操作,就需要在函数内部声明其为global
global global_resource
for i in range(len(global_resource)):
global_resource[i] = para
time.sleep(sleep)
print("修改全局变量为:", global_resource)

# 释放锁
lock.release()


def main():
thread_hi = threading.Thread(target=change_resource, args=('hi', 2))
thread_hello = threading.Thread(target=change_resource, args=('hello', 1))
thread_hi.start()
thread_hello.start()


if __name__ == '__main__':
main()

递归锁

  • 释放递归锁的操作必须由获得该锁的线程来进行(同一递归等级)
  • 同一个线程在释放锁之前再次获得锁不会阻塞该线程,而是将递归等级加一(初始递归等级为1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"""
在普通锁中可能造成死锁的情况,可以考虑使用递归锁解决
"""
import time
import threading


# 如果是使用的两个普通锁,那么就会造成死锁的情况,程序一直阻塞而不会退出
# rlock_hi = threading.Lock()
# rlock_hello = threading.Lock()

# 使用成一个递归锁就可以解决当前这种死锁情况
rlock_hi = rlock_hello = threading.RLock()


def test_thread_hi():
# 初始时锁内部的递归等级为1
rlock_hi.acquire()
print('线程test_thread_hi获得了锁rlock_hi')
time.sleep(2)
# 如果再次获取同样一把锁,则不会阻塞,只是内部的递归等级加1
rlock_hello.acquire()
print('线程test_thread_hi获得了锁rlock_hello')
# 释放一次锁,内部递归等级减1
rlock_hello.release()
# 这里再次减,当递归等级为0时,其他线程才可获取到此锁
rlock_hi.release()


def test_thread_hello():
rlock_hello.acquire()
print('线程test_thread_hello获得了锁rlock_hello')
time.sleep(2)
rlock_hi.acquire()
print('线程test_thread_hello获得了锁rlock_hi')
rlock_hi.release()
rlock_hello.release()


def main():
thread_hi = threading.Thread(target=test_thread_hi)
thread_hello = threading.Thread(target=test_thread_hello)
thread_hi.start()
thread_hello.start()


if __name__ == '__main__':
main()

条件变量对象

补充:

1
2
3
4
5
6
7
8
>>> print(True == 1)
>>> print(True == 2)
>>> print(False == 0)
>>> print(False == 2)
True
False
True
False

threading.Condition(lock=None):一个条件变量对象允许一个或多个线程等待,直到被另一个线程通知

方法:

  • wait(timeout=None):释放锁,等待直到被通知或超时,等待的时间内阻塞线程。
  • wait_for(predicate,timeout=None):与wait方法类似,等待。这个方法首先调用predicate函数,判断其返回值,若为true,释放锁,并阻塞,待被通知或超时后,调用predicate函数,返回true,则获得锁,不再阻塞;若为false,继续阻塞;若为false,则不会释放锁,程序继续执行。
  • notify(n=1):唤醒n个正在等待这个条件变量的线程
  • notify_all():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
"""
让一个线程等待,直到另一个线程通知
"""
import time
import threading


# 创建条件变量对象
condition_lock = threading.Condition()

PRE = 0


# predicate可调用函数
def pre():
print(PRE)
return PRE


def test_thread_hi():
# 在使用wait/wait_for之前必须先获得锁
condition_lock.acquire()

print('等待线程test_thread_hello的通知')
# 先执行一次pre,返回False后释放掉锁,等另一个线程释放掉锁后再次执行pre,返回True后再次获取锁
# wait_for的返回值不是True和False,而是predicate参数的返回值
condition_lock.wait_for(pre)
# condition_lock.wait()
print('继续执行')

# 不要忘记使用wait/wait_for之后要释放锁
condition_lock.release()


def test_thread_hello():
time.sleep(1)
condition_lock.acquire()

global PRE
PRE = 1
print('修改PRE值为1')

print('通知线程test_thread_hi可以准备获取锁了')
condition_lock.notify()

# 先notify/notify_all之后在释放锁
condition_lock.release()
print('你获取锁吧')


def main():
thread_hi = threading.Thread(target=test_thread_hi)
thread_hello = threading.Thread(target=test_thread_hello)
thread_hi.start()
thread_hello.start()


if __name__ == '__main__':
main()

惊鸿一瞥

Hello,World

1
2
3
4
5
6
7
8
9
package main	

import {
"fmt"
}

func main() {
fmt.Println("Hello, World!")
}

package关键字声明代码所属的包,本例中包名为main。
import关键字导入需要的包
func关键字声明函数
当运行一个Go程序,总是从main包的main函数开始

fmt包:format之缩写,提供格式化输入输出函数

此外,大括号有其唯一的制定格式

基本知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
//声明常量
const ( days = 365
name = "days"
)
//声明变量
var (
dream = "I have a dream"
new_dream = "I had a dream"
)
//格式化输出
fmt.Println("One year has",days,name)
fmt.Print("One year has ")
fmt.Print(days)
fmt.Print(" days\n")
fmt.Printf("One year has %d days\n",days)
fmt.Println(dream,"\n",new_dream)
}

Println会自动补换行符

Go中运算符和C中大抵相同,但没有++count这种前置增量操作

基本控制结构

在go中,true是唯一真值,false是唯一假值

比较运算符:==,!=,<=,<,>=,>

Go不允许文本和数值直接比较

1
2
3
4
5
6
7
8
9
10
func demo(){
if 判断语句{
//do sth.
}
else if 判断语句{
//do sth.
}
else 判断语句{
//do sth.
}

逻辑与:&&
逻辑或:||
逻辑非:!

Switch实现分支判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
const days = 365

switch days {
case 100:
fmt.Println(100,"days")
case 200,365://使用逗号分隔多个可选值
fmt.Println("hhhh")
fallthrough
default:
fmt.Println("23333333")
}
}

for实现循环结构
关于range关键字:

1
2
3
4
5
//假设array_cut是一个数组或切片
//index和value分别对应了下标和值
for index,value := range array_cut{
//do sth.
}

作用域

go作用域:以大括号分界
虽然没有用到大括号,但default和case都i引入了新的作用域

简短声明:

1
2
3
//两方法等价
var name = 'ubuntu'
name := 'ubuntu'

简短声明作用:
以下三种情况,变量作用域都在大括号内
这种使用方法无法用var声明替代

1
2
3
4
5
6
7
8
9
10
11
for count := 10; count > 0;count--{
//do sth.
}

if count := rand.Intn(3); count == 0{
//do sth.
}

switch num := rand(10); num{
//do sth.
}

类型

浮点数

1
2
3
4
5
6
7
8
9
10
//双精度浮点数
age := 19.2
var age =19.2
var age float64 = 19.2

//单精度浮点数
var age float32 = 19.2

//默认零值
var age

整数

1
2
3
4
5
6
year := 2021
var year = 2021
var year int 2021

//e12表示10的12次方
var distance int64 = 2.33e12

big包

big包提供3种类型:

  • 存储大整数big.Int
  • 存储任意精度浮点数big.Float
  • 存储分数big.Rat
1
2
3
4
5
6
7
8
9
10
//创建int64类型
hhhhh := big.NewInt(2333333)

//创建很大很大的数
hhhhhhh := new(big.Int)
hhhhhhh.SetString("23333333333333333",10)

//Go语言不会为常量推断类型,而是默认无类型(untyped)
//由big包提供底层支持
const distance = 23333333333333333333333333

多语言文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//go语言推断双引号内字面值为string类型
peace := "peace"
var peace = "peace"
var peace string = "peace"

//声明零值
var peace string

//反引号包围的文本不考虑转义字符`
var peace 'hhhhhhh\n'//不会输出换行符,而是输出/n

//反引号可跨越多行,打印时将包含这些行
var peace 'hhhhh
hhhhhhhh
hhhhh'
1
2
3
4
5
6
//可以独立访问字符串中的单个字符,但无法单独修改
hhhh = 'laugh'
h = laugh[4]

//非法操作:
laugh[4] = 'd'

类型转换

类型转换:
转换一个变量,只需用与目标类型同名的函数来包裹该变量

1
2
3
4
5
6
7
8
//字符串拼接
hhhh = 'laugh ' + 'is the power of birth.'

//非法:
hhh ='laugh ' + 'is the power of birth.' + 10

//合法
hhh = 'laugh ' + 'is the power of birth.' + '10'

这部分与c语言大致相同
浮点数转换为整数,没有任何舍入,而是直接截断

关于字符串转换

1
2
3
4
5
6
7
8
9
10
11
12
//根据代码点转换
fmt.Println(string(960),string(940),string(969),string(33))
//输出π ά ω !

//转换为字符串方法:
//使用strconv包的Itoa函数
hhh ='laugh ' + 'is the power of birth.' + strconv.Itoa(10)

//字符串转换为整数
//使用strconv包的Atoi函数
const hhh string = '23333'
number := strconv.Atoi(hhh)

代码点:
一个数字对应一个特殊字符
如:π ά ω !分别对应960 940 969 33

构建块

函数

基本格式:

func function_name( [parameter list] ) [return_types] { 函数体 }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func Add(number1 int,number2 int)(sum int){
sum = number1 + number2
return sum
}

func Add(number1 int,number2 int)(sum int){
sum = number1 + number2
return //省略了返回值的名字
}

func Calculate(number1 ,number2 int)(sum,multitude int){//减少了int的使用次数
sum = number1 + number2
multitude = number2*number1
return sum,multitude
}

func Calculate(number1 ,number2 int)(sum,multitude int){
sum = number1 + number2
multitude = number2*number1
return

方法

声明新类型:
格式:

1
type 新类型名称 底层类型

注意:

1
2
3
4
5
6
7
8
9
10
type type1 float64
type type2 float64

var number1 type1 := 10
var number2 type2 := 15
var number3 float64 = 20

//以下操作非法.尽管底层相同,他们仍为不同的类型
numebr1 = number2
number2 = number3

在go语言中,没有类和对象
格式:

1
2
3
func (接受者) 方法名(形参列表) (返回值列表){
//do sth.
}

接受者是某种非内置类型的变量
用于建立类型和方法之间的关联

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

type myInt int

func (number1 myInt) calculate(number2 int) (sum int,multitude int){
return int(number1)+number2,int(number1)*number2
}

func main() {
var number1 myInt = 10
var number2 =20
var sum int
var multitude int
sum,multitude = number1.calculate(number2)
fmt.Println(sum,multitude)
}

一等函数

在go中,函数是 一等 值,可以把函数赋值给变量,可以把函数传递给函数,也可以编写创建并返回函数的函数。

将函数赋值给变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import "fmt"

type myInt int64

func Add(number1,number2 int) myInt {
return myInt(number1+number2)
}

func Multitude(number1,number2 int) myInt{
return myInt(number1*number2)
}

func main() {
//将函数赋值给变量方法一:
func1 := Add
//将函数赋值给变量方法二
var func2 func(int,int) myInt = Multitude

var number1 int = 10
var number2 int = 20
var result myInt

result = func1(number1,number2)
fmt.Println(result)
result = func2(number1,number2)
fmt.Println(result)

func1 = func2
result = func1(number1,number2)
fmt.Println(result)
func2 = Add
result = func2(number1,number2)
fmt.Println(result)
}

将函数传递给其他函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type myInt int64
//声明函数类型
type myFunc func(int,int) myInt

func Add(number1,number2 int) myInt {
return myInt(number1+number2)
}

func Multitude(number1,number2 int) myInt{
return myInt(number1*number2)
}

func Add_Multitude(number1,number2 int, func1,func2 myFunc) (sum,multitude myInt){
sum = func1(number1,number2)
multitude = func2(number1,number2)
return sum,multitude
}

闭包和匿名函数

匿名函数在Go中也称为函数字面量

闭包函数:声明在一个函数内部的函数
闭包:内部函数总是可以访问其所在外部函数中的变量,即使它所在的外部函数已返回(寿命终结)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "fmt"

type myFunc func() int

func func1(func2 myFunc,offset int) myFunc{
return func() int {
return func2() + offset
}
}

func func2() int{
return 10
}

func main() {
//匿名函数赋值给变量
func4 := func(message string){
fmt.Println(message)
}
func4("hhhhhhhhhhhhhhhhhhhhhhh")

//同时声明和调用匿名函数
func(){
fmt.Println("hhhhhhhhh")
} ()

//从函数里返回另一个函数
func3 := func1(func2,20)
fmt.Println(func3())
}

可变参数函数

在这里插入图片描述
声明放在函数形参的最后一位
可以传入多个参数或不传参数

1
2
3
4
5
6
7
func f(name...string) {
//do sth.
}

cut_test := []string{"sss","hhh","2333"}
//将cut_test的多个元素传递给函数
cut = f(cut_test..)

收集器

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var planests [8]string

planest[0] = "Mercury"
mercury := planets[0]
fmt.Println(len(planest))//输出8

//未赋值的元素将默认为零值
var numbers = [5]int{1,2,3}

//也可以不用指定数组长度
var numbers = [...]int{
1,
2,
3,
}//结尾逗号是必需的

数组用于赋值或传递给参数时会产生一个副本
故函数内部对数组的修改不会影响原数组

数组嵌套定义即形成所谓的多维数组

切片

  • 对于数组

切片数组不会导致数组被修改,只是创建了指向数组的视图,把该视图称为切片类型

格外注意的是,切片或切片的切片都是数组的视图,对他们进行修改会导致原数组的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"sort"
)

func main() {
numbers := [5]int{1,2,3,4,5}
cut1 := numbers[0:3]
cut2 := numbers[0:2]
fmt.Println(numbers,cut1,cut2)
cut2[1] = 100
fmt.Println(numbers)

//可以切割string类型,将创建另一个字符串,两个字符串相互独立
name := "yangzi"
name1 := name[:4]
fmt.Println(name,name1)
// name 和 name1 都不会改变
name = "hhhhh"
fmt.Println(name,name1)
name1 = "233333"
fmt.Println(name,name1)

//创建完全切片方法一:
//numbers2 := numebrs[:]

//创建完全切片方法二:
numbers3 := []string{"11","40","ann","ccc","bbbb"}

//切片可以带有方法,纯数组不行
sort.StringSlice(numbers3).Sort()
fmt.Println(numbers3)
}
  • 关于sort:
    标准库sort包声明了一种StringSlice类型:
1
type StringSlice []string

该类型还有关联的Sort方法:

1
func (p StringSlice) Sort()

Go语言通过切片和append函数来实现动态数组的功能

切片长度:切片中可见元素的数量
切片容量:切片的底层数组决定了切片的容量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

func main(){
//切片的底层数组长度为5
//切片长度为5,容量为5
vocabulary := []string{"terminally","visible","lethal","legally","prescribe"}

//此时,append函数将切片元素复制到新创建的数组里面并将新创建的数组作为切片的底层数组,该数组长度为原底层数组的两倍
//切片长度为8,容量为10
vocabulary = append(vocabulary,"partnership","compassion","publication")

//三索引切片操作:
//三索引操作旨在避免对底层数组的修改
arry_vocabulary := [...]string{"candidness","dose","medication","constitutionally","uphold","legislation",}
//切片长度为4,容量为4
cut_vocabulary := arry_vocabulary[0:4:4]
fmt.Println(cut_vocabulary)
cut_vocabulary = append(cut_vocabulary,"spark")
fmt.Println(cut_vocabulary)
fmt.Println(arry_vocabulary)

//两索引
//长度为2,容量为6(与底层数组相同),可以看到底层数组仍未被修改
cut_vocabulary2 := arry_vocabulary[0:4]
cut_vocabulary2 = append(arry_vocabulary[:],"notable")
fmt.Println(cut_vocabulary2)
fmt.Println(arry_vocabulary)

//三索引但容量大于长度
cut_vocabulary3 := arry_vocabulary[0:4:5]
cut_vocabulary3 = append(cut_vocabulary,"spark")
fmt.Println(cut_vocabulary3)
fmt.Println(arry_vocabulary)

//使用make函数对切片容量实行预分配
//切片长度为0,容量为10
test_cut1 := make([]string,0,10)
test_cut1 = append(test_cut1,"hhhhhhhhhhhhhh")


//切片长度为10,容量为10,元素默认为零值
test_cut2 := make([]string,10)
//此时将向切片中追加第11个元素
test_cut2 = append(test_cut2,"hhhhhhhhh")
}

映射

映射(map):可将键映射到值
与数组和切片使用序列整数作为索引不同的是,映射的键几乎可以是任何类型

可以理解为Python中的字典

Go语言必须为映射的键和值指定类型
格式:

1
map[键的类型]值的类型

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import "fmt"

func main(){
//声明map方法一:
map1 := map[string]int{
"key1":1,
"key2":2,
}

//声明map方法二
map2 := make(map[string]int)
map2["New_York"] = 1
map2["San_Franscisco"] = 2
map2["Tokyo"] = 3

//数组或一些基本类型在赋值给新变量或传递至函数/方法时都会创建相应的副本,但映射不会
map3 := map1
map3["key1"]=2
fmt.Println(map3)
fmt.Println(map1)

//遍历映射
//遍历键
for key := range map2{
fmt.Print(key)
}
fmt.Print("\n")

//遍历值
for _,value := range map2{
fmt.Print(value)
}
fmt.Print("\n")

//遍历键值
for key,value := range map2{
fmt.Println(key,value)
}

//删除键值对
//delete(map_name,key_name)
delete(map2,"New_York")

//判断某一个键值对是否存在
//当键值对不存在,输出对应数据类型的零值
fmt.Println(map2["New_York"])

//使用value, ok := map[key]的写法判断
map2["Shenzheng"] = 0
if value,ok := map2["Shenzheng"]; ok{
fmt.Println(value," is exited.")
}else {
fmt.Println("not exited")
}
}

状态与行为

状态:可以是值或结构package main
行为:函数和方法

结构

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import "fmt"

//定义结构
type city struct{
country,name string
rank int
}

//定义结构
type city struct{
country,name string
rank int
}

func main(){
//声明方法一:
var newYork city
newYork.rank = 1
newYork.name = "New York"
newYork.country = "American"

//声明方法二:
var tokyo = city{country:"Japan",rank:2}
//未给定字段则自动声明为对应的零值
fmt.Println(tokyo.name)
tokyo.name = "Tokyo"

//结构复制
//两个结构变化不会对彼此有任何影响
anotherTokyo := tokyo
anotherTokyo.rank = 3
fmt.Println(anotherTokyo,"\n",tokyo)

//由结构组成的切片
cities := []city{newYork,tokyo}
fmt.Println(cities)

//临时使用的匿名结构
hhhh := struct {
ccc int
ddd string
}{2333333,"2333333333"}
fmt.Println(hhhh)
}

Go面向对象

Go没有类,而是通过结构与方法的结合实现面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

type literallyPopulation struct {
literalRate float64
totalPopulation int
}

type country struct {
countryName string
//运用组合
theLiterallyPopulation literallyPopulation
}

//实现构造函数
func newCountry(countryName string, literalRate float64,totalPopulation int) (c country){
l := literallyPopulation{literalRate:literalRate,totalPopulation:totalPopulation}
c = country{countryName:countryName,theLiterallyPopulation: l}
return c
}

func (theLiteralPopulation literallyPopulation) countLiteralPopulation() (float64){
return theLiteralPopulation.literalRate*float64(theLiteralPopulation.totalPopulation)
}

//转发
//手动转发
func (c country) countLiteralPopulation() float64{
return c.theLiterallyPopulation.literalRate*float64(c.theLiterallyPopulation.totalPopulation)
}

func main(){
c := newCountry("hhhh",0.34,10000)
lp := c.countLiteralPopulation()
fmt.Println(lp)
}

实现自动转发:

1
//我代码呢?

接口

用于实现多态

接口是一种类型,是一组method的集合
接口命名一般以-er结尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package main

import "fmt"

type shower interface {
showName() string
showRank() int
}

type sayer interface {
sayCharacteristic() string
}

//接口嵌套
type sayAndShower interface {
sayer
shower
}

type city struct {
name string
rank int
characteristic string
}

type country struct {
name string
rank int
characteristic string
}

func (city city) showName() string {
return city.name
}

func (city city) showRank() int {
return city.rank
}

func (city city) sayCharacteristic() string {
return city.characteristic
}

func (country country) showName() string {
return country.name
}

func (country country) showRank() int {
return country.rank
}

func (country country) sayCharacteristic() string {
return country.characteristic
}

func main() {
structCity := city{characteristic: "very big", rank: 23333, name: "city of night"}
structCountry := country{characteristic: "nb", rank: 100, name: "nbcountry"}

//指针类型
var show shower
//var say sayer

show = structCity
fmt.Println(show.showRank())
fmt.Println(show.showName())

show = structCountry
fmt.Println(show.showRank())
fmt.Println(show.showName())

say := structCity
fmt.Println(say.sayCharacteristic())

say = structCountry
fmt.Println(say.sayCharacteristic())

var showAndSay sayAndShower
showAndSay = structCountry
fmt.Println(showAndSay.showRank(), showAndSay.showName(), showAndSay.sayCharacteristic())
}

指针

基本使用

几点需要注意的:

  • 对于指向结构的指针,Go中用.即可完成对方法和字段的访问,且解引用不是必须的
  • Go中数组与指针是两种独立的类型
  • 对于指向数组的指针,Go语言提供了自动解引用的特性,即解引用不是必须的
  • Go语言没有为切片和映射提供了自动解引用的特性
  • 映射是一种隐式指针,所以是指针指向映射的举动往往是多此一举
  • 切片实际上是指向数组的指针
  • 指向切片的指针的作用是修改切片本身,包括切片的长度,容量和起始偏移量
  • 方法的接收者和函数形参在处理指针方面十分类似,都可以实现对原有字段的改变
  • 关于指针和接口:指针接收者能仅能接收指针变量,而值接收者可接受指针或非指针变量

nil

nil是指针,切片,映射和接口的零值
解引用nil指针将引发恐慌(panic)

  • nil与方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

type person struct {
score int
}

func (p *person) getScore() {
p.score++ //此处发生了解引用
}

func main() {
var p *person
p.getScore() //解引用nil指针,引发恐慌
}
  • nil与函数:
1
2
var fn func(a,b int) int
fn(1,2) //解引用nil指针引发惊恐
  • nil与切片:
    不包含任何元素的空切片和nil切片并不相等,但通常可以替换使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func main() {
var slice []int //值为nil的切片

//关键字range,len()和append()等可以正常处理nil切片
for _, ingredient := range slice {
fmt.Println(ingredient) //不会包含任何输出
}

fmt.Println(len(slice))

slice = append(slice, 1, 2, 3)
fmt.Println(slice)
//关键字range,len()和append()等可以正常处理nil切片
for _, ingredient := range slice {
fmt.Println(ingredient) //不会包含任何输出
}

fmt.Println(len(slice))

//不包含任何元素的空切片和nil切片替换使用
slice = append(slice, 1, 2, 3)
fmt.Println(slice)
}

nil与映射
映射在声明之后若没有使用复合字面量或内置make函数初始化,则其值为默认的nil。
可以对值为nil的map进行读取操作,但不可进行写入操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
var map1 map[string]int

//进行读取操作
value, ok := map1["hhhh"]
if ok {
fmt.Println(value)
}

for key, value := range map1 {
fmt.Println(key, value)
}
}

nil与接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
)

//空接口妙用之一:以空接口作为函数行参,可以向函数传递任何类型的值
func show(x interface{}){
fmt.Printf("Type:%T Value:%v \n",x,x)
}

func main(){
//空接口可以储存任意类型的值
//定义一个空接口的值
var x interface{}

i:= 100
j:= "hhhhhh"
k:= true

x = i
fmt.Printf("Type:%T Value:%v \n",x,x)

x=j
fmt.Printf("Type:%T Value:%v \n",x,x)

x=k
fmt.Printf("Type:%T Value:%v \n",x,x)

show(i)
show(j)
show(k)

//空接口妙用之二:使用空接口实现保存任何值的特点
var stuInfo = make(map[string]interface{})

stuInfo["name"] = "bai"
stuInfo["age"] = 12
stuInfo["sex"] = "nan"
}

并发

扫一下盲

Go中,独立运行的任务谓之goroutine
goroutine之间的通信依赖通道
通道(channel)是一种类型

A.进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。
B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
C.一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行。

并发:多线程程序在一个核的cpu上运行
并行:多线程程序在多个核的cpu上运行

协程:比线程更轻量级的存在,一个线程可拥有多个协程。协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。(线程是由操作系统内核管理的)

协程可以理解为一个特殊的函数

一个线程的多个协程的运行是串行的。当一个协程运行时,其它协程必须挂起。

上下文切换

  • 进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。
  • 线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。
  • 协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。

python两种并发:1,多线程和多进程来进行并发;2,使用协程进程并发

python大部分库基于前一种并发方式,而go则完全基于后一种来开发

sync.WaitGroup

方法名 功能
(wg * WaitGroup) Add(delta int) 计数器+delta
(wg *WaitGroup) Done() 计数器-1
(wg *WaitGroup) Wait() 阻塞直到计数器变为0

例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。

goroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup

func sleepyGopher(i int) {
defer wg.Done()
fmt.Println(i, "awake")
}

func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
//使用go关键字启动一个线程
go sleepyGopher(i)
}
//因为main函数本身也是一个协程,而主协程一旦结束,则其它的协程也会结束
//所以需要sync.WaitGroup来实现并发任务的同步
wg.Wait()
}

使用互斥锁同步协程

  • 并发要面临资源竞争问题

最后得到的结果与预期不相符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var x int64
var wg sync.WaitGroup

func add() {
for i := 0; i < 5000; i++ {
x = x + 1
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
  • 此时引入互斥锁解决数据同步的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"fmt"
"sync"
)

var x int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
for i := 0; i < 5000; i++ {
lock.Lock()
x = x + 1
lock.Unlock()
}
wg.Done()
}

func sub() {
for i := 0; i < 5000; i++ {
//互相竞争资源的步骤必须使用同一把锁
lock.Lock()
x = x - 1
lock.Unlock()
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(x)
}

能不用锁就不用锁,因为开锁解锁消耗时间,影响性能

读写锁

对于绝大多数web系统,读多写少

我们必须要在读和写上加锁,但当读的操作很多时,一个读的操作上锁对其它读的操作影响不容小觑。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup
var lock sync.Mutex

func read() {
defer wg.Done()
lock.Lock()
fmt.Println("读")
time.Sleep(time.Second * 1)
fmt.Println("读毕")
lock.UnLock()
}

func main() {
for i := 0; i < 1000 ; i++{
wg.Add(1)
go read()
}
wg.Wait()
}

此时,我们需要一把读之间不会相互竞争,而读写之间会相互竞争的锁。

引入读写锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup
var lock sync.RWMutex

func read() {
defer wg.Done()
lock.RLock()
fmt.Println("读")
time.Sleep(time.Second * 1)
fmt.Println("读毕")
lock.RUnlock()
}

func write() {
defer wg.Done()
lock.Lock()
fmt.Println("写")
time.Sleep(time.Second * 10)
fmt.Println("写毕")
lock.Unlock()
}

func main() {
for i := 0; i < 5; i++ {
wg.Add(1)
go read()
}

wg.Add(1)
go write()

wg.Wait()
}

协程间的通信机制

channel提供了一种定向通信机制(python和java常用消息队列实现)

分类:有无缓冲;双向还是单向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup
var lock sync.Mutex

func consumer(data chan int) {
defer wg.Done()
lock.Lock()
//第一种取法
/* msg,ok := <- data
if(ok){
fmt.Println(msg)
}*/
//使用 for range 来取:
for msg := range data {
fmt.Println(msg)
}

lock.Unlock()
}

func main() {
//定义时必须指定类型
//channel为引用类型,默认为nil
var msg chan int
//使用make初始化的有三种:chan map slice
//若缓冲空间满了还往其中放值会引发错误
//msg = make(chan int) //这种存储方式无缓冲
msg = make(chan int, 1) //第二种存储方式:有缓冲;第二个参数指定缓冲空间
msg <- 1
//利用一个协程来腾空缓冲空间
wg.Add(1)
go consumer(msg)
msg <- 2
//fmt.Println(data)

//关闭通道 1,关闭的channel不能再向其中发送数据 2,消费者可继续消费数据,直到数据被消费完
close(msg)
wg.Wait()
}

  • 无缓冲通道的使用

对于一个无缓冲的channel,在启动一个消费者之前放数据就会报错

在放一个数据到channel中时,会阻塞数据,阻塞之前会放一把锁;只有当取值时,这把锁才会解开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup
var lock sync.Mutex

func consumer(data chan int) {
defer wg.Done()
lock.Lock()
//当通道未关闭时,ok返回true,否则,返回false
for {
msg, ok := <-data
if !ok {
break
}
fmt.Println(msg)
}
lock.Unlock()
}

func main() {

var msg chan int

msg = make(chan int)

//使用无缓冲通道时,在向管道发送信息之前,另一端必须要有消费者
go consumer(msg)
msg <- 1
msg <- 3
close(msg)
wg.Wait()
}

双向和单向的channel

能使用单向channel就尽量使用单向channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup
var lock sync.Mutex

//只能从中取值,不能从中放值
func consumer(data <-chan int) {
defer wg.Done()
lock.Lock()
for {
msg, ok := <-data
if !ok {
break
}
fmt.Println(msg)
}
lock.Unlock()
}

//定义一个只能往其中放值,不能从中取值的channel
func producer(data chan<- int) {
defer wg.Done()
for i := 0; i < 1000; i++ {
data <- i
}
}

func main() {
var msg chan int

msg = make(chan int)

//使用无缓冲通道时,在向管道发送信息之前,另一端必须要有消费者
//普通的channel可直接转换为单向的channel,反之则不行
go consumer(msg)
go producer(msg)

close(msg)
wg.Wait()
}

select

select作用域通讯场景,伪随机选择一组可能的send和receive操作去执行。

考虑有多个case等待处理的情况,比如同时有多个channnel可以接收数据,那么select会伪随机选择一个case去处理;若无case需要处理,那么select会选择default取处理;如果没有default,那么select会阻塞,直到某个case需要处理。

select应用场景:

  • timeout超时机制
  • 判断channel是否被阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//timeout超时机制
package main

import (
"fmt"
"time"
)

func main() {
var msg chan int

msg = make(chan int, 1)

go func() {
time.Sleep(time.Second * 2)
msg <- 1
}()
select {
case res := <-msg:
fmt.Println(res)
case <-time.After(time.Second):
fmt.Println("timeout 1")
}
close(msg)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//判断channel是否被阻塞
//若所有的channel都被阻塞了,那么就会执行default
package main

import (
"fmt"
"time"
)

func main() {
msg1 := make(chan int, 1)
msg2 := make(chan int, 1)

go func() {
time.Sleep(time.Second * 3)
msg1 <- 1
}()
go func() {
time.Sleep(time.Second * 2)
msg2 <- 1
}()

select {
case res := <-msg1:
fmt.Println(res)
case res := <-msg2:
fmt.Println(res)
default:
fmt.Println("timeout")
}
close(msg1)
close(msg2)
}

场景:

假设在有一个goroutine,希望它在一定时间内监控cpu信息,之后退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"sync"
"time"
)

var stop chan bool = make(chan bool)
var wg sync.WaitGroup

func cpuInfo() {
defer wg.Done()
for {
select {
case <-stop:
fmt.Println("monitor closes")
return
default:
time.Sleep(time.Second)
fmt.Println("monitor is running")
}
}
}

func main() {
wg.Add(1)
go cpuInfo()
time.Sleep(6 * time.Second)
stop <- true
wg.Wait()
fmt.Println("goroutine finishes")
}

context

考虑更复杂的场景,若有多个监控协程在运行,还想用上面的select加channel的方式来指定时间退出的操作,我们就需要向管道中传多个值。

这时引入context:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package main

import (
"context"
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup

func cpuInfo(ctx context.Context) {
defer wg.Done()
for {
select {
//检验context是否cancel
case <-ctx.Done():
fmt.Println("cpu monitor finishes")
return
default:
time.Sleep(time.Second)
fmt.Println("cpu monitor is running")
}
}
}

func memoryInfo(ctx context.Context) {
defer wg.Done()
//若父context cancel,则子context也会cancel
ctx2, _ := context.WithCancel(ctx)
for {
select {
case <-ctx2.Done():
fmt.Println("memory monitor finishes")
return
default:
time.Sleep(time.Second)
fmt.Println("memory monitor is running")
}
}
}

func indicatorInfo(ctx context.Context) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("indicator monitor finishes")
return
default:
time.Sleep(time.Second)
fmt.Println("indicator monitor is running")
}
}
}

func main() {
ctx, cancel := context.WithCancel(context.Background())

wg.Add(3)
go cpuInfo(ctx)
go memoryInfo(ctx)
go indicatorInfo(ctx)
time.Sleep(6 * time.Second)
cancel()
wg.Wait()
fmt.Println("all monitors finish")
}

几种context:

1
2
3
context.WithCancel() //可取消,手动cancel
context.WithDeadline() //可取消,在某一个时间点之前取消,也可手动取消
context.WithTimeout() //可取消,设置超时间,也可在timeout之前手动取消

异常处理

随便说说

  • go异常处理思想:
    若一个函数可能出现异常,那么就应把异常作为返回值,没有异常就返回nil

关于defer

  • defer之后只能是函数调用,不能是表达式

  • 若有多个defer,则会创建一个defer栈,后注册的defer先执行

  • 向defer中传递的函数是值,不是引用;压入的不仅是函数,还有参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //输出hhhh
    package main

    import (
    "fmt"
    )

    func main() {
    f := func() {
    fmt.Println("hhhh")
    }
    defer f()
    f = func() {
    fmt.Println("23333")
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //输出1
    package main

    import (
    "fmt"
    )

    func main() {
    x := 1
    f := func(a int) {
    fmt.Println(a)
    }
    defer f(x)
    x++
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //输出2
    package main

    import (
    "fmt"
    )

    func main() {
    x := 1
    f := func(a *int) {
    fmt.Println(a)
    }
    defer f(&x)
    x++
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //输出2,这是由于闭包的特性
    package main

    import (
    "fmt"
    )

    func main() {
    x := 1
    //使用的是全局的值,上面几个例子优先使用局部变量
    f := func() {
    fmt.Println(x)
    }
    defer f()
    x++
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //输出1,优先使用局部变量
    package main

    import (
    "fmt"
    )

    func main() {
    x := 1
    f := func(x int) {
    fmt.Println(x)
    }
    defer f(x)
    x++
    }

panic和recover

错误和异常概念区分:

  • 错误:错误你可以预判

  • 异常:不可预判,往往是由于代码不严谨

错误:

错误的场景:

  • 文件IO
  • 数据库连接
  • 空指针引用
  • 下标越界
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//对于可能出现错误的地方要提取预知
package main

import (
"errors"
"fmt"
)

func f(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("hhhhh")
}
return a / b, nil
}

func main() {
a := 1
b := 0
r, err := f(a, b)
if err != nil {
fmt.Println(err)
}
fmt.Println(r)
}

抛出异常和捕捉异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//对于可能出现错误的地方要提取预知
package main

import (
"fmt"
)

func f(a, b int) int {
if b == 0{
panic("hhhhh")
}
return a / b
}

func main() {
a := 1
b := 0
//recover必须执行
defer func() {
err := recover()
if err != nil {
fmt.Println(err)
}
}()
//这样无效
//defer recover()
fmt.Println(f(a, b))
}
  • 在任一子协程中使用panic抛出错误会导致主协程挂掉,同时导致其它的协程挂掉

  • 在一个协程中再开一个协程,父协程捕获不到子协程抛出的异常,所以会出现上述问题

  • 一种处理方法:单独写一个函数用于捕获异常,每开一个协程就defer这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pymongo import MongoClient

class TestMongo:
def __enter__(self):
self.__client = MongoClient(host="127.0.0.1",port=27017)
self.collection = self.__client['test']["t1"] #使用方括号选择数据库和集合

def __exit__(self, exc_type, exc_val, exc_tb):
self.__client.close()

def test_insert(self):
#insert接收字典,返回ObjectID
ret = self.collection.insert({"name":"lily","age":17})
return ret

def test_insert_many(self):
item_list = [{"name":f"test{i}" for i in range(10)}]
#isnert_many接收列表,列表中为需要插入的字典
ret_list = self.collection.insert_many(item_list)
#ret_list.inserted_ids为一个列表,包括所有插入数据的id
return ret_list.inserted_ids

def try_find_one(self):
#find_one返回一个结果,接收一个字典形式的字典
t = self.collection.find_one({"name":"test5"})
return t

def try_find_many(self):
#返回所有结果,若条件为空,返回所有数据
t = self.collection.find()
#返回一个Cursor游标
for i in t:
print(i)
for j in t:
print(j) #由于上面游标已经移到最后一个,不会再有输出
print(list(t)) #可以强转

def try_update_one(self):
self.collection.update_one({"name":"test5"},{"$set":{"name":"new_test5"}})

def try_update_many(self):
self.collection.update_many({"name": "test5"}, {"$set": {"name": "new_test5"}})

def try_delete_one(self):
self.collection.delete_one({"name":"test5"})

def try_delete_many(self):
self.collection.delete_many({"name":"test5"})

NoSQL

“non-relational”或”Not Only SQL”

非关系数据库。数据与数据之间不再有一对一或一对多之类的对应的关系,数据从一个个清晰的条目(如MySQL)变成了一个独立的个体。

MySQL扩展性差,大数据下IO困难,表结构更改困难。MongoDB易扩展,大数据高性能,灵活的数据模型,高可用。

  • 易扩展:数据之间无关系,所以易扩展

  • 大数据量,高性能:读写性能高,得益于其数据库结构简单

  • 数据模型灵活:无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系型数据库中,增删字段十分麻烦。

  • 缺点:存储空间开销大

基本操作

1
2
3
4
db //查看当前数据库
show dbs //查看所有数据库
use db_name //切换数据库
db.dropDatabase() //删除当前数据库
1
2
3
4
5
6
7
8
9
10
11
12
创建数据库:
use db_name
插入数据操作

不手动创建集合:
想不存在的集合第一次插入数据时,集合会被创建出来
手动创建集合:
db.createConnection(name,options)
db.createCollection("sub",{capped:true,size:10})

show collections
db.col_name.drop()
1
2
创建日期:
new Data('2021-09-25')
1
2
3
4
db.col_name.insert({key1:value1,key2:value2})
db.col_name.find() //展示col中所有数据
db.col_name.save(document) //insert插入不会将原有相同_ID相同的数据修改,而save会
db.update(<query>,<update>,{multi:<boolean>}) //三个参数:更新条件,更新操作,默认false,表示只更新第一条,true则更新所有
1
2
3
4
5
//已有数据{"_id":1,"name":"joker","age":19}
db.col_name.update({"_id",1},{"name":"joke"}) //此时数据变为{"_id":1,"name":"joke"}
db.col_name.update({"_id":1},{$set:{name:"joke"}}) //此时数据变为{"_id":1,"name":"joke","age":19}

//multi update only works with $ operator
1
db.col_name.remove(<query>,{justOne:<boolean>}) //<query>为删除文档的条件(必选),justOne设为1或true,则只删除一条,默认false,表示删除多条
1
2
3
db.col_name.find({条件文档})
db.col_name.findOne({条件文档})
db.col_name.find().pretty() //将结果格式化

比较运算符:

  • 等于:默认等于
  • 小于 $lt (less than)
  • 小于等于 $lte (less than equal)
  • 大于 $gt (greater than)
  • 大于等于 $gte
  • 不等于 $ne
1
db.col_name.find({age:{$gte:18}})

范围运算符:

  • $in $nin
1
db.stu.find({age:{$in:[18,28,38]}})

逻辑运算符:

  • and:,
  • or:$or
1
db.stu.find({$or:[{age:{$gte:16}},{hometown:"hhh"}]})
1
2
3
//正则表达式:使用//或$regex
db.products.find({sku:/^abc/})
db.products.find({sku:{$regex:'789$'}})
1
db.col_name.find().limit(NUMBER).skip(NUMBER) //用于读取或跳过指定数量文档,可同时使用
1
2
3
4
5
6
//自定义查询,使用&where后面写一个函数,返回满足条件的数据
db.stu.find({
$where:function(){
retrun this.age>30//this就是当前集合
}
})
1
2
3
//投影:在查询返回的结果中,只显示必要的字段
db.col_name.find({},{字段名称:1,...}) //1表示显示,0表示不显示,不能同时写1和0(_id除外)

1
2
//排序
db.col_name.find().sort({gender:-1}) //参数1为升序,参数-1为降序
1
2
3
//统计个数
db.col_name.find({条件}).count()
db.col_name.count({条件})
1
2
//消除重复并返回不重复的结果
db.col_name.distinct('去重字段',{条件})

数据类型

1
2
3
4
5
6
7
8
9
10
Object ID:文档ID
String:字符串,有效的utf-8
Boolean:true or false
Integer:32位或64位
Double:浮点值
Arrays:数组/列表,多个值存储到一个键
Object:一个值为一个文档,啥意思呢,一个字典中嵌套的另一个字典的意思吧
Null:存储Null值
Timestamp:unix时间戳
Data:当前时间或日期的unix格式
  • 每个文档都有一个属性,为_id,保证每个文档的唯一性
  • 可以自行设计_id插入文档,若没有提供,那么mongodb为每个文档提供l一个独特的_id,类型为object ID
  • objectID是一个12字节十六进制数:前四个字节为当前时间戳,接下来三个字节为机器ID,接下来两个字节为Mongodb的服务进程ID,最后三个字节是增量值

聚合

聚合:基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。

1
db.col_name.aggregate({管道:{表达式}})

常用管道:

1
2
3
4
5
6
7
$group:将集合中的文档分组
$match:过滤数据,只输出符合条件的文档
$project:修改输入文档的结构,如重命名,增加,删除字段,创建计算结果
$sort:将输入文档排序后输出
$limit:限制聚合管道返回的文档数
$skip:跳过指定数量文档,返回余下的文档
$unwind:将数组类型的字段进行拆分

常用表达式:

用于处理文档并输出,操作一般发生在管道之前

1
2
3
4
5
6
7
8
9
语法:表达式:'$列名'
$sum:计算总和
$sum:1 表示以一倍计数
$avg:计算平均值
$min:获取最小值
$max:获取最大值
$push:在结果文档中插入值到一个数组中
$first:根据资源文档的排序获取第一个文档的数据
$last:根据资源文档的排序获取最后一个文档的数据

$group

_id表示分组的依据,使用某个字段的格式为”$字段”

$group对应的字典中有几个键,结果就有几个

  • 统计男生、女生的总人数,并计算平均值
1
2
3
4
5
6
7
8
9
db.stu.aggregate(
{$group:
{
_id:'$gender',
counter:{$sum:1},
avg_age:{$avg:"$age"}
}
}
)
  • 求总人数,计算平均年龄
1
2
3
4
5
6
7
8
9
db.stu.aggregate(
{$group:
{
_id:null,
counter:{$sum:1},
avg_age:{$avg:"$age"}
}
}
)
  • 按多个条件进行分组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
db.stu.aggregate(
{
$group:
{
_id:{
country:'$country',
city:'$city'
}
}
},
{
$project:
{
country:{$_id.country},
city:{$_id.city},
_id:0
}
}
)

$project

  • 统计男生、女生的总人数,并计算平均值,并隐去_id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
db.stu.aggregate(
{$group:
{
_id:'$gender',
counter:{$sum:1},
avg_age:{$avg:"$age"}
}
},
{
{
gender:"$_id",
_id:0
}
}
)

$match

match是管道命令,能将结果交给后一个管道,但是find不可以

  • 查询年龄大于20的学生
1
2
3
4
5
db.stu.aggregate(
{
$macth:{age:{$gt:20}}
}
)

$sort

  • 查询学生信息,按年龄进行排序
1
b.stu.aggregate({$sort:{age:1}})

$limit

  • 查询两名学生的信息
1
db.stu.aggregate({$limit:2})

$skip

  • 跳过两名学生的信息
1
db.stu.aggregate({$skip:2})

$unwind

1
2
3
4
5
6
7
8
9
现有如下数据:
{_id:1,item:"t-shirt",size:['S','M','L']}

db.col_name.aggregate({$unwind:'size'})

结果:
{_id:1,item:"t-shirt",size:"S"}
{_id:1,item:"t-shirt",size:"M"}
{_id:1,item:"t-shirt",size:"L"}

索引

提高查询速度

1
2
3
4
5
6
7
8
for(i=0;i<100000;i++){db.col_name.insert({name:'test'+i,age;i})}

db.col_name.find({name:'test10000'}).explain('executionStats') //explain('executionStats')会输出操作用时
db.col_name.ensureIndex({属性:1}) //1升序 -1降序
db.col_name.ensureIndex({属性:1},{"unique":true}) //建立唯一索引,被索引标记的值不可重复
db.col_name.ensureIndex({属性1:1,属性2:1}) //建立联合索引
db.col_name.getIndexes() //查看索引
db.col_name.dropIndex(索引名称) //删除索引

数据备份和恢复

  • 备份
1
2
3
4
5
6
mongodump -h dbhost -d dbname -o dbdirectory
-h:数据所在服务器地址,可指定端口号
-d:数据库名称
-o:备份的数据存放的地址,此目录中放着备份出来的数据

mongodump -h localhost -d test1 -o D:\mongodb_5.1
  • 恢复
1
2
3
4
5
6
mongorestore -h dbhost -d dbname --dir dbdirectory
-h:同上
-d:需要恢复的数据库实例
--dir:备份数据所在位置

mongorestore -h localhost -d test1 -o D:\mongodb_5.1

栈的表示和实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include "stdio.h"
#include "malloc.h"
#include "stdlib.h"

#define OK 1
#define ERROR 0

#define LIST_INIT_SIZE 5
#define LISTINCREMENT 5

typedef int Status;

//typedef struct{
// char name[8];
// int age;
//}ElemType;

typedef int ElemType;

typedef struct {
ElemType *firstElem;
ElemType *lastElem;
int stackSize;
}Stack;

Status initStack(Stack &s ){
s.firstElem = (ElemType*) malloc(sizeof (ElemType)*LIST_INIT_SIZE);
if(!s.firstElem) return ERROR;
s.lastElem = s.firstElem;
s.stackSize = LIST_INIT_SIZE;
return OK;
}

Status push(Stack &s, ElemType e){
if(s.stackSize+1>LIST_INIT_SIZE){
s.firstElem = (ElemType*) realloc(s.firstElem,sizeof (ElemType)*(s.stackSize+LISTINCREMENT));
if(!s.firstElem) return ERROR;
s.stackSize += LISTINCREMENT;
}
*(s.lastElem) = e;
s.lastElem++;
return OK;
}

Status pop(Stack &s, ElemType &e){
if(s.lastElem == s.firstElem)
return ERROR;
e = *(s.lastElem-1);
s.lastElem--;
return OK;
}

int main(){
Stack s;

if(initStack(s)!=OK){
printf("init wrong");
return -1;
}

for(int i=0;i<7;i++){
if(push(s,i)==OK) printf("push Ok:%d",i);
else{
printf("push wrong:%d",i);
}
}

ElemType e;
for(int i=0;i<7;i++){
if(pop(s,e)==OK) printf("pop Ok:%d",e);
else{
printf("pop error:%d",i);
}
}

return 0;
}

栈的应用

  • 10进制数N转换成8进制的算法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void converse(int N){
    Stack S;
    initStack(S);
    while (N){
    push(S,N%8);
    N /= 8;
    }
    while(!isEmpty(S)){
    ElemType e;
    push(S,e);
    printf("%d",e);
    }
    }
  • 括号匹配检验

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    Status isMatch(char *string){
    Stack s;
    if(!initStack(s)) return ERROR;

    ElemType e;
    for(char *p=string;*p;p++){
    if(*p == '(' || *p == '{' || *p == '[')
    push(s,*p);

    if(*p == ')' ){
    pop(s,e);
    if(e != '(')
    return ERROR;
    }

    if(*p == ']' ){
    pop(s,e);
    if(e != '[')
    return ERROR;
    }

    if(*p == '{' ){
    pop(s,e);
    if(e != '}')
    return ERROR;
    }
    }
    if(isEmpty(s))
    return OK;
    return ERROR;
    }

链队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "stdio.h"
#include "malloc.h"
#include "stdlib.h"

using namespace std;

#define OK 1
#define ERROR 0

#define LIST_INIT_SIZE 5
#define LISTINCREMENT 5

typedef int Status;

//typedef struct{
// char name[8];
// int age;
//}ElemType;

typedef int ElemType;

//结点类型
typedef struct QNode{
ElemType data;
struct QNode * next;
}QNode,*Queue;

//链队列类型
typedef struct{
Queue frontPtr;
Queue rearPtr;
}LinkQueue;

Status initQueue(LinkQueue &Q){
Q.frontPtr = (QNode*) malloc(sizeof (QNode));
Q.rearPtr = Q.frontPtr;
if(!Q.frontPtr) return ERROR;
Q.frontPtr->next == NULL;
return OK;
}

Status EnQueue(LinkQueue &Q,ElemType e){
QNode *m = (QNode*)malloc(sizeof (QNode));
if(!m) return ERROR;
m->data = e;
m->next = NULL;
Q.rearPtr->next = m;
Q.rearPtr = m;
return OK;
}

Status isEmpty(LinkQueue Q){
if(Q.frontPtr == Q.rearPtr) return ERROR;
else return OK;
}

Status DeQueue(LinkQueue &Q,ElemType &e){
if(isEmpty(Q)) return ERROR;
QNode *m = Q.frontPtr;
Q.frontPtr = Q.frontPtr->next;
e = m->data;
free(m);
return OK;
}

顺序队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "stdio.h"
#include "malloc.h"
#include "stdlib.h"

using namespace std;

#define OK 1
#define ERROR 0

#define LIST_INIT_SIZE 5
#define LISTINCREMENT 5

typedef int Status;

//typedef struct{
// char name[8];
// int age;
//}ElemType;

typedef int ElemType;

#define MAXQSIZE 10

typedef struct {
ElemType *base;
int front;
int rear;
}Queue;

Status isFull(Queue &Q){
if((Q.rear - Q.front + MAXQSIZE)%MAXQSIZE < MAXQSIZE - 1)
return ERROR;
else return OK;
}

Status initQueue(Queue &Q){
Q.base = (ElemType*) malloc(sizeof (ElemType)*MAXQSIZE);
if(!Q.base) return ERROR;
Q.front = Q.rear = 0;
return OK;
}

Status enQueue(Queue &Q,ElemType e){
if(isFull(Q)) return ERROR;
Q.base[Q.rear] = e;
Q.rear = (++Q.rear)%MAXQSIZE;
return OK;
}

Status deQueue(Queue &Q,ElemType &e){
if(Q.rear == Q.front) return ERROR;
e = Q.base[Q.front];
Q.front = (++Q.front)%MAXQSIZE;
return OK;
}

双端队列

双端队列:双端队列是一种特殊的线性表,特殊在限定插入 和删除操作只能在表的两端进行。