容器接口

BeanFactory

什么是BeanFactory

1.它是ApplicationContext的父接口

2.它才是Spring的核心容器,主要的ApplicationContext实现都【组合】了它的功能,BeanFactory是ApplicationContext的成员变量

BeanFactory能干啥

1.表面上只有getBean方法

2.实际上,控制反转、基本的依赖注入,制止Bean的生命周期的各种功能,都由它的实现类提供例如DefaultListableBeanFactory,继承关系如下

DefaultListableBeanFactory继承关系图

DefaultListableBeanFactory

DefaultSingletonBeanRegistry

该类有一个如下成员变量
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
是私有的,我们可以通过反射的方式拿到它的成员变量

ApplicationContext有哪些扩展功能

org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String…)方法返回org.springframework.context.ConfigurableApplicationContext,我们来看一下他的接口继承关系

ConfigurableApplicationContext继承关系图

可以看出ApplicationContext间接继承了BeanFactory

ApplicationContext对BeanFactory进行了一些扩展,其中主要有(MessageSource、ResourcePatternResolver、ApplicationEventPublisher、EnviromentCapable)

MessageSource

MessageSource提供了处理国际化的能力,国际化字典我们一半存储在resource目录下的messages开头的properties文件

resource示例

1
2
3
ConfigurableApplicationContext context = SpringApplication.run(MarketServerApplication.class);
String hi = context.getMessage("hi", null, Locale.CHINA);
System.out.println(hi);

ResourcePatternResolver

ResourcePatternResolver资源文件的处理能力,如application.properties

1
2
3
4
5
ConfigurableApplicationContext context = SpringApplication.run(MarketServerApplication.class);
Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
System.out.println(resource);
}
1
2
3
URL [jar:file:/Users/anakin/develop/mvn-repository/org/springframework/boot/spring-boot/2.3.12.RELEASE/spring-boot-2.3.12.RELEASE.jar!/META-INF/spring.factories]
URL [jar:file:/Users/anakin/develop/mvn-repository/org/springframework/boot/spring-boot-autoconfigure/2.3.12.RELEASE/spring-boot-autoconfigure-2.3.12.RELEASE.jar!/META-INF/spring.factories]
URL [jar:file:/Users/anakin/develop/mvn-repository/org/springframework/spring-beans/5.2.15.RELEASE/spring-beans-5.2.15.RELEASE.jar!/META-INF/spring.factories]

EnviromentCapable

EnviromentCapable提供配置信息查看能力,可以是配置文件,也可以是环境变量(不区分大小写)等。

1
2
System.out.println(context.getEnviroment().getProperty("java_home"));
System.out.println(context.getEnviroment().getProperty("server.port"));

ApplicationEventPublisher

ApplicationEventPublisher提供事件发布的能力

1
2
Event event = new Event();
applicationContext.publishEvent(event);
1
2
3
@EventListener
public void listenEvent(Event event){
}

容器实现

BeanFactory实现的特点

BeanFactory不会主动做的事情

不会主动调用BeanFactory后处理器

不会主动添加Bean后处理器

不会主动初始化单例对象

不会解析BeanFactory,还不会解析${}与#{}

Bean后处理器有排序的逻辑

DefaultListableBeanFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args){
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// bean 的定义(class,scope,初始化,销毁)
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton).getBeanDefinition();
// 给BeanFactory添加一些常用的后置处理器(如@Configuration、@Bean等等扩展功能)
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
// 执行Bean工厂后置处理器
Map<String, BeanFactoryPostProcessor> beansOfType = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
beansOfType.values().stream().forEach(beanFactoryPostProcessor -> beanFactoryPostProcessor.postProcessBeanFactory(context.getBeanFactory()));
// 添加Bean后置处理器与bean工厂的联系(针对Bean生命周期的各个阶段提供扩展,例如@Autowired、@Resource等)
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
// beanFactory默认延迟创建对象,我们可以执行下面的方法提前创建对象
beanFactory.preInstantiateSingletons();

}

以上添加的后置处理器如下(Bean工厂后置处理器及Bean后置处理器都有)

1
2
3
4
5
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

常见的Bean工厂后置处理器

1
2
3
4
5
6
7
org.springframework.context.annotation.internalConfigurationAnnotationProcessor------>org.springframework.context.annotation.ConfigurationClassPostProcessor@2b27d5d3
org.springframework.context.event.internalEventListenerProcessor------>org.springframework.context.event.EventListenerMethodProcessor@12115c28
propertySourcesPlaceholderConfigurer------>org.springframework.context.support.PropertySourcesPlaceholderConfigurer@cc3fc5c
preserveErrorControllerTargetClassPostProcessor------>org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor@2aac60b
cn.hutool.extra.spring.SpringUtil------>cn.hutool.extra.spring.SpringUtil@173602c0
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration$RefreshScopeBeanDefinitionEnhancer------>org.springframework.cloud.autoconfigure.RefreshAutoConfiguration$RefreshScopeBeanDefinitionEnhancer@fdeec12
refreshScope------>org.springframework.cloud.context.scope.refresh.RefreshScope@de579ff

常见的Bean后置处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
org.springframework.context.annotation.internalAutowiredAnnotationProcessor------>org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@6fe55fcf
org.springframework.context.annotation.internalCommonAnnotationProcessor------>org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@4d1f6e1c
org.springframework.aop.config.internalAutoProxyCreator------>proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor------>org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor@3431cb1f
webServerFactoryCustomizerBeanPostProcessor------>org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor@42c9f2cd
errorPageRegistrarBeanPostProcessor------>org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor@797c67c
methodValidationPostProcessor------>proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false
dataSourceInitializerPostProcessor------>org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor@31ce271c
org.springframework.amqp.rabbit.config.internalRabbitListenerAnnotationProcessor------>org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor@b1d7b09
healthEndpointGroupsBeanPostProcessor------>org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration$HealthEndpointGroupsBeanPostProcessor@64faf3d
meterRegistryPostProcessor------>org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryPostProcessor@3f523dae
rabbitConnectionFactoryMetricsPostProcessor------>org.springframework.boot.actuate.autoconfigure.metrics.amqp.RabbitConnectionFactoryMetricsPostProcessor@7fe40b9f
persistenceExceptionTranslationPostProcessor------>proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=false; frozen=false
projectingArgumentResolverBeanPostProcessor------>org.springframework.data.web.config.ProjectingArgumentResolverRegistrar$ProjectingArgumentResolverBeanPostProcessor@1f41f259
configurationPropertiesBeans------>org.springframework.cloud.context.properties.ConfigurationPropertiesBeans@648c80cb
org.springframework.context.annotation.internalAsyncAnnotationProcessor------>proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false

一些Bean工厂后置处理器的实现类图

一些Bean后置处理器的实现类图

ApplicationContext的常见实现与特点

ApplicationContext实现关系图

内嵌容器注册DispatcherServlet

废话少说直接看代码

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
 package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
)

/***********************服务器抽象***********************/
// Server 抽象服务器
type Server interface {
// Route 设定一个路由,命中该路由的会执行handleFunc的代码
Route(pattern string, handleFunc http.HandlerFunc)

// Start 启动我们的服务器
Start(address string) error
}

/***********************服务器实现***********************/
type sdkHttpServer struct {
Name string // Name 服务器应用名称查看方法 NewSdkHttpServer
}

// Route 设定一个路由,命中该路由的会执行handleFunc的代码 实现了Server接口
func (server *sdkHttpServer) Route(pattern string, handleFunc http.HandlerFunc) {
http.HandleFunc(pattern, handleFunc)
}

// Start 启动我们的服务器
func (server *sdkHttpServer) Start(address string) error {
return http.ListenAndServe(":8080", nil)
}

// NewSdkHttpServer 新建名为name的sdkHttpServer
func NewSdkHttpServer(name string) Server {
return &sdkHttpServer{Name: name}
}

/********************HttpContext抽象********************/
type Context struct {
writer http.ResponseWriter
request *http.Request
}

// NewContext 构造方法
func NewContext(writer http.ResponseWriter, request *http.Request) *Context {
return &Context{writer: writer, request: request}
}

// ReadJson 读取request请求发过来的json数据
func (context *Context) ReadJson(data interface{}) error {
body, err := io.ReadAll(context.request.Body)
//读流错误
if err != nil {
return err
}
return json.Unmarshal(body, data)
}

// WriteJson 响应Json数据
func (context *Context) WriteJson(status int, data interface{}) error {
bytes, err := json.Marshal(data)
if err != nil {
return err
}
_, err = context.writer.Write(bytes)
if err != nil {
return err
}
context.writer.WriteHeader(status)
return nil
}

// OkJson 请求成功
func (context Context) OkJson(data interface{}) error {
return context.WriteJson(http.StatusOK, data)
}

// SystemErrJson 系统内部异常
func (context Context) SystemErrJson(data interface{}) error {
return context.WriteJson(http.StatusInternalServerError, data)
}

// BadRequestJson 错误的请求数据
func (context Context) BadRequestJson(data interface{}) error {
return context.WriteJson(http.StatusBadRequest, data)
}

/**********************响应实体封装**********************/
// commonResponse 公共的响应结果包装
type commonResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}

/***********************请求的实体***********************/
type signUpReq struct {
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"confirmed_password"`
}

/**********************路由处理方法**********************/
// handler
func handler(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintf(writer, "Hi there, I love %s!", request.URL.Path)
}

// parseForm
func parseForm(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintf(writer, "before parse form %v\n", request.Form)
err := request.ParseForm()
if err != nil {
fmt.Fprintf(writer, "parse form error %v\n", err)
}
fmt.Fprintf(writer, "before parse form %v", request.Form)
}

// signUp 用户注册
func signUp(writer http.ResponseWriter, request *http.Request) {
context := NewContext(writer, request)
req := &signUpReq{}
err := context.ReadJson(req)
// 没有读到json请求错误
if err != nil {
context.BadRequestJson(&commonResponse{
Code: 400,
Msg: fmt.Sprintf("intalid request %v", err),
})
return
}
_ = context.OkJson(&commonResponse{Code: 0, Msg: "success", Data: req})
}

// main 程序入口
func main() {
httpServer := NewSdkHttpServer("test")
httpServer.Route("/", handler)
httpServer.Route("/parseForm", parseForm)
httpServer.Route("/signUp", signUp)
httpServer.Start(":8080")
}

认识JVM

认识JVM规范:重点是理解JVM规范的作用,了解JVM规范的主要内容

认识JVM:是什么、有什么、能干什么
JVM:Java virtual machine,也就是java虚拟机

所谓的虚拟机是指:通过软件模拟的具有完整硬件系统功能、运行在一个完全隔离环境中的计算机系统

JVM是通过软件来模拟Java字节玛的指令集,是Java程序运行的环境

java如何实现平台无关

JVM概述图

JVM主要功能
1、通过ClassLoader寻找和装在class文件
2、解释字节码成为指令并执行,提供class文件运行环境
3、运行期间内存非配
4、提供硬件交互的平台

虚拟机是平台无关的保障

虚拟机是平台无关的保障

JVM规范作用
1.Java虚拟机规范为不同的硬件平台提供了一种编译Java技术代码的规范。
2.该规范使用Java软件独立于平台,因为编译是针对作为虚拟机的“一般机器”而做。
3.这个“一般机器”可用软件模拟并运行于各种现存的计算机系统,也可用硬件来实现。

JVM规范定义的主要内容
字节码指令集(相当于中央处理器CPU),详看JVM虚拟机规范
class文件格式
数据类型和值,详看JVM虚拟机规范
运行时数据区
栈帧
特殊方法
<init>:实例初始化方法,通过invokespecial指令来调用
<clinit>:类或接口的初始化方法,不包含参数,返回void
类库
Java虚拟机必须要对一些Java类库提供支持,否则这些类库根本无法实现,比如(反射、加载和创建类或接口,如ClassLoader、连接或初始化类和接口的类、安全,如security、多线程、弱引用)
异常处理
虚拟机的启动、加载、链接和初始化

Java虚拟机规范下载地址
https://docs.oracle.com/javase/specs/index.html

class文件格式

  • class文件是JVM的输入,Java虚拟机规范中定义了Class文件结构。Class文件Jvm实现平台无关、技术无关的基础

    • 1.class文件是一组以8字节为单位的字节流,各个数据项目按顺序紧凑排列

    • 2.对于占用空间大于8字节的数据项,按照高位在前的方式分割成多个8字节进行存储

    • 3.class文件格式里面只有两种类型:无符号数、表,详看JVM虚拟机规范

      • (1)无符号数:基本数据类型,以u1、u2、u4、u8来代表几个字节的无符号数

      • (2)表:由多个无符号数和其他表构成的符合数据类型,通常以“_info”结尾

  • javap工具生成非正式的“虚拟机汇编语言”,格式如下

1
<index> <opcode> [<operand1> [<operand2> ...]][<comment>]
  • <index>是指令操作码在数组种的下标,该数组以字节的形式存储当前方法的Java虚拟机代码;也可以是相对方法起始处的字节偏移量

  • <opcode>是指令的助记码、<operand>是操作数、<comment>是注释

class文件格式说明

  • constant_pool_count:是从1开始的

  • 不同的常亮类型,用tag来区分的,它后面对应的info结构是不一样的

  • L表示对象,[表示数组,V表示void

  • 了解预定义attribute的含义

  • stack:方法执行时,操作栈的深度

  • Locals:局部变量所需的存储空间,单位是slot

  • slot:是虚拟机为局部变量分配内存所使用的最小单位

  • arg_size:参数个数,为1的话,因实例方法会默认传入this,locals也会预留一个slot来存放

TestJVM.java原始文件

1
2
3
4
5
6
7
8
9
10
package world.ismyfree.demo;

public class TestJvm {

public static final String HELLO_JAVA = "hello java";

public static void main(String[] args) {
System.out.println(String.format("get message = %s", HELLO_JAVA));
}
}

TestJVM.class原始文件

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
cafe babe 0000 0034 002d 0a00 0400 1909
001a 001b 0800 1c07 001d 0700 1e08 001f
0a00 2000 210a 0022 0023 0100 0a48 454c
4c4f 5f4a 4156 4101 0012 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 0100 0d43
6f6e 7374 616e 7456 616c 7565 0100 063c
696e 6974 3e01 0003 2829 5601 0004 436f
6465 0100 0f4c 696e 654e 756d 6265 7254
6162 6c65 0100 124c 6f63 616c 5661 7269
6162 6c65 5461 626c 6501 0004 7468 6973
0100 1d4c 776f 726c 642f 6973 6d79 6672
6565 2f64 656d 6f2f 5465 7374 4a76 6d3b
0100 046d 6169 6e01 0016 285b 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 2956
0100 0461 7267 7301 0013 5b4c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b01 000a
536f 7572 6365 4669 6c65 0100 0c54 6573
744a 766d 2e6a 6176 610c 000c 000d 0700
240c 0025 0026 0100 1067 6574 206d 6573
7361 6765 203d 2025 7301 0010 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 0100 1b77
6f72 6c64 2f69 736d 7966 7265 652f 6465
6d6f 2f54 6573 744a 766d 0100 0a68 656c
6c6f 206a 6176 6107 0027 0c00 2800 2907
002a 0c00 2b00 2c01 0010 6a61 7661 2f6c
616e 672f 5379 7374 656d 0100 036f 7574
0100 154c 6a61 7661 2f69 6f2f 5072 696e
7453 7472 6561 6d3b 0100 106a 6176 612f
6c61 6e67 2f53 7472 696e 6701 0006 666f
726d 6174 0100 3928 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 5b4c 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 3b29 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
0100 136a 6176 612f 696f 2f50 7269 6e74
5374 7265 616d 0100 0770 7269 6e74 6c6e
0100 1528 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 2956 0021 0005 0004 0000
0001 0019 0009 000a 0001 000b 0000 0002
0006 0002 0001 000c 000d 0001 000e 0000
002f 0001 0001 0000 0005 2ab7 0001 b100
0000 0200 0f00 0000 0600 0100 0000 0300
1000 0000 0c00 0100 0000 0500 1100 1200
0000 0900 1300 1400 0100 0e00 0000 4300
0600 0100 0000 15b2 0002 1203 04bd 0004
5903 1206 53b8 0007 b600 08b1 0000 0002
000f 0000 000a 0002 0000 0008 0014 0009
0010 0000 000c 0001 0000 0015 0015 0016
0000 0001 0017 0000 0002 0018

javap -verbose TestJVM.class(助记符)

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
88
89
90
91
92
93
94
95
96
97
Classfile /home/anakin/IdeaProjects/untitled/target/classes/world/ismyfree/demo/TestJvm.class
Last modified 2021-8-8; size 764 bytes
MD5 checksum b5c5da06a2ed9a50c599a61bf2f71066
Compiled from "TestJvm.java"
public class world.ismyfree.demo.TestJvm
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#25 // java/lang/Object."<init>":()V
#2 = Fieldref #26.#27 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #28 // get message = %s
#4 = Class #29 // java/lang/Object
#5 = Class #30 // world/ismyfree/demo/TestJvm
#6 = String #31 // hello java
#7 = Methodref #32.#33 // java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
#8 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
#9 = Utf8 HELLO_JAVA
#10 = Utf8 Ljava/lang/String;
#11 = Utf8 ConstantValue
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lworld/ismyfree/demo/TestJvm;
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 args
#22 = Utf8 [Ljava/lang/String;
#23 = Utf8 SourceFile
#24 = Utf8 TestJvm.java
#25 = NameAndType #12:#13 // "<init>":()V
#26 = Class #36 // java/lang/System
#27 = NameAndType #37:#38 // out:Ljava/io/PrintStream;
#28 = Utf8 get message = %s
#29 = Utf8 java/lang/Object
#30 = Utf8 world/ismyfree/demo/TestJvm
#31 = Utf8 hello java
#32 = Class #39 // java/lang/String
#33 = NameAndType #40:#41 // format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
#34 = Class #42 // java/io/PrintStream
#35 = NameAndType #43:#44 // println:(Ljava/lang/String;)V
#36 = Utf8 java/lang/System
#37 = Utf8 out
#38 = Utf8 Ljava/io/PrintStream;
#39 = Utf8 java/lang/String
#40 = Utf8 format
#41 = Utf8 (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
#42 = Utf8 java/io/PrintStream
#43 = Utf8 println
#44 = Utf8 (Ljava/lang/String;)V
{
public static final java.lang.String HELLO_JAVA;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String hello java

public world.ismyfree.demo.TestJvm();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lworld/ismyfree/demo/TestJvm;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=6, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String get message = %s
5: iconst_1
6: anewarray #4 // class java/lang/Object
9: dup
10: iconst_0
11: ldc #6 // String hello java
13: aastore
14: invokestatic #7 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
17: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
LineNumberTable:
line 8: 0
line 9: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 args [Ljava/lang/String;
}
SourceFile: "TestJvm.java"

ASM开发

认识ASM

  • ASM是一个Java字节码操纵框架,它能用来动态生成类或者增强既有类的功能

  • ASM能直接产生二进制class文件,也可以在类被加入虚拟机之前动态改变类行为,ASM类文件读入信息后,能够改变类行为,分析类信息,甚至能根据要求生成新类。

  • 目前许多框架如cglib、Hibernate、Spring都直接或间接地使用ASM操作字节码

ASM编程模型

  • Core API:提供了基于事件形式的编程模型。该模型不需要一次性将整个类的结构读取到内存中,因此这种方式更快,需要更少的内存,但这种编程方式难度较大。

  • Tree API:提供了基于树形的编程模型。该模型需要一次性将一个类的完整结构全部读取到内存当中,所以这种方法需要更多的内存,这种编程方式较简单。

ASM核心API

ASM的Core API

  • ASM Core API种操纵字节码的功能基于ClassVisitor接口。这个接口种的每个方法对应了class中的每一项。

  • ASM提供了三个基于ClassVisitor接口的类实现class文件的生成和转换

    • 1.ClassReader:ClassReader解析一个类的class字节码

    • 2.ClassAdapter:ClassAdapter是ClassVisitor的实现类,实现要变化的功能

    • 3.ClassWriter:ClassWriter也是ClassVisitor的实现类,可以用来输出要变化的字节码

ASMifier

  • 1.ASM给我们提供了ASMifier工具帮助开发,可以使用ASMifier工具生成ASM结构来对比

  • 2.工具Maven依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>9.2</version>
</dependency>
  • 3.idea使用maven命令将jar包copy到当前项目的dependency目录方便调试
1
mvn dependency:copy-dependencies
  • 4.执行命令使用工具解析字节码
1
java -cp .:../dependency/asm-9.2.jar:../dependency/asm-util-9.2.jar org.objectweb.asm.util.ASMifier world.ismyfree.jvm.asm.CC

转换示例如下

1
2
3
4
5
6
7
8
9
10
SELECT
order_id orderId,
REPLACE(REPLACE(FROM_UNIXTIME(order_time/1000,'%Y%m%D%H:%i'),'th',''),':','') AS orderTime,
"202102" AS orderMonth
FROM
mall_order_sku_202102 as t1
WHERE
sku_id = 10023919984358
AND order_time >= 1614355200000
AND order_time < 1614441600000

配置ssl域名证书

参考阿里云帮助文档在Nginx(或Tengine)服务器上安装证书

下载证书到本地

去域名服务提供商申请域名证书(腾讯云与阿里云都可以申请免费的域名证书)

下载域名证书

上传证书文件到服务器

例如使用CRT附带的文件上传工具将证书文件上传到/usr/local/nginx/conf/cert目录下面

在nginx配置文件中添加配置(如下配置是根据nginx注释掉的默认配置直接修改而来,或参考上面提到的阿里云的文档)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 443 ssl;
server_name www.ismyfree.world;

ssl_certificate /usr/local/nginx/conf/cert/4810494_www.ismyfree.world.pem;
ssl_certificate_key /usr/local/nginx/conf/cert/4810494_www.ismyfree.world.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

location / {
root html;
index index.html index.htm;
}
}

本次演示基于本地服务器(无意义),正常情况下我们是通过现有的与服务器进行配置。

为什么要配置https域名证书

符合苹果app审核的必要条件

同理安卓或微信小程序审核也是要符合这个条件的

在/etc/profile.d/路径下新建一个文件,名为java.sh

1
vim /etc/profile.d/java.sh

在文件中配置如下环境配置内容

1
2
3
4
export JAVA_HOME=/usr/local/java/jdk1.8.0_251
export JRE_HOME=${JAVA_HOME}/jre
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH

保存并关闭文件,执行以下命令使之可运行

1
chmod +x /etc/profile.d/java.sh

最后,执行以下命令来永久设置环境变量

1
source /etc/profile.d/java.sh

错误描述信息

1
Error running 'SaleAdminApplication': Command line is too long. Shorten command line for SaleAdminApplication or also for Spring Boot default configuration.

这个错误信息在run运行方式不出现,在debug的运行方式才会出现

解决办法

使用编辑器打开工作目录下的文件 —> .idea\workspace.xml

找到标签

1
<component name="PropertiesComponent">

标签中添加内容

1
<property name="dynamic.classpath" value="true" />

复制表结构导入旧表数据

新建新表与旧表表结构相同

1
create table new_table like old_table;

将旧表数据导入新表

1
insert into new_table select * from old_table;

什么是tag

tag是git版本库的一个标记,指向某个commit的指针。

tag主要用于发布版本的管理,一个版本发布之后,我们可以为git打上 v.1.0.1 v.1.0.2 …这样的标签。

tag感觉跟branch有点相似,但是本质上和分工上是不同的:

tag 对应某次commit, 是一个点,是不可移动的。

branch 对应一系列commit,是很多点连成的一根线,有一个HEAD 指针,是可以依靠 HEAD 指针移动的。

创建tag

创建本地tag

1
git tag <tagName>

创建tag指定message

1
git tag -a <tagname> -m "message"

指定某个commit id创建tag

先查看历史的comitId

1
git log --pretty=oneline

指定某个commitId创建tag

1
git tag -a <tagName> <commitId>

将tag推送到远程仓库

指定tag推送到远端

1
git push origin <tagName>

推送所有tag到远端

1
git push origin --tags

查看tag

查看本地某个 tag 的详细信息

1
git show <tagName>

查看本地所有 tag

1
git tag 或者 git tag -l

查看远程所有 tag

1
git ls-remote --tags origin

删除标签

本地标签删除

1
git tag -d <tagName>

远程 tag 的删除

1
git push origin :<tagName>

检出标签

1
git checkout -b <branchName> <tagName>

基础语法

变量定义

使用var关键字

var a,b,c bool
var s1,s2 string = “hello”,”world”
可以放在函数内,或直接放在包内
使用var()集中定义变量

使用:=定义变量

a,b,i,s1,s2 := true,false,3,”hello”,”world”,让编译器自动决定编译类型
只能在函数内使用

变量定义注意事项

定义过得变量一定要使用,否则编译器会报错
字符串类型的变量初始值是空字符串(不像java为null)
变量定义的时候赋值可以不用定义数据类型,编译器会进行类型推断,甚至在同一行代码定义不同类型的变量都是可以的
在函数外面定义变量不能使用:=来定义变量,只能使用var关键字进行变量定义
函数外面定义变量作用域在包内部,没有全局变量的说法

内建变量类型

数据类型

bool:布尔

string字符串

int整形

(u)int 根据操作系统位数决定长度(32位系统长度(u)int32/64位系统(u)int64)
(u)int8
(u)int16
(u)int32
(u)int64

uinttpr指针

byte字节

rune字符(32bit)

float(浮点)

float32
float64

complex(复数)

complex64 实部和虚部各32位浮点数
complex128 实部和虚部各64位浮点数

复数回顾

什么是复数:复数是形如 a + b i的数.式中a,b 为 实数,i是一个满足i^2 =-1的数,因为任何实数的平方不等于-1,所以i不是实数,而是实数以外的新的数.

|3+4i| = 5 ?到底是怎么算的?有知道的可以告诉我一下,我不会算😢开始还以为这个公式有问题,结果程序跑出来的结果就是这么多😫

复数

i^0 = 1

i^1 = i

i^2 = -1

i^3 = -i

i^4 = 1

i^5 = i

i^6 = -1

我们发现每增加一个次方就逆时针旋转90度

欧拉公式的由来

最美公式-欧拉公式

常量与枚举

常量定义使用const关键字定义

常量可以规定数据类型,也可以让编译器自主推断类型

常量也可以定义在const()里面表示定义一组常量

go语言的常量名称定义一般不大写,大写F代表public

枚举类型使用const()定义

go语言中没有专门的枚举类型,所以我们以常量组作为枚举类型

1
2
3
4
5
const (
zero = 0
one = 1
two = 2
)

go语言提供了iota关键字是枚举值的自增表达式,使用_下划线跳过

1
2
3
4
5
6
7
const (
cpp = iota
_
python
golang
javascript
)

以上枚举常量的值为cpp:0/_:1/python:2/golang:3/javascript:4

还能以iota关键之定义磁盘容量表达式如下

1
2
3
4
5
6
7
8
const (
b 1 << (10 * iota)
kb
mb
gb
tb
pb
)

条件语句

if条件语句

示例:读取文件

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

import (
"fmt"
"io/ioutil"
)

func main() {
const filename = "abc.txt"
//go语言的函数可以返回多个值
file, err := ioutil.ReadFile(filename)
//错误不为空打印错误
if err != nil {
fmt.Println(err)
} else {
//没有错误打印文本文件内容
fmt.Printf("%s\n\n", file)
}
}

注意:go语言的if语句可以像其它语言的for语句的写法,先初始化赋值(定义变量),再进行条件判断,使用分号分隔,作用域在if以内,写法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"fmt"
"io/ioutil"
)
func main() {
const filename = "abc.txt"
if file, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n\n", file)
}
}

switch条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func eval(a int, b int, op string) int {
var result int
switch op {
case "+":
result = a+b
case "-":
result = a-b
case "*":
result = a*b
case "/":
result = a/b
default:
panic("unsupported operator" + op)
}
return result
}

看看这段

switch代码块中的case会自动break,如果不适用break使用fallthrough,也就是其它预压的case穿透。

panic是go语言中的报错,报错会让程序停下来。

switch里面如果没有表达式也可以将条件放到case语句里面,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func grade(score int) string {
g := ""
switch {
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
case score <= 100:
g = "A"
default:
panic(fmt.Sprintf("Wrong score:%d",score))
}
return g
}

循环

for

一个简单的for循环

1
2
3
4
5
6
func loopdemo() {
sum := 1
for i := 0; i < 100; i++ {
sum+=1
}
}

for语句的条件里不需要括号

for的条件里面可以省略初始条件,结束条件,递增表达式

没有初始化条件的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//将数字转换为2进制字符
func convertToBin(number int) string {
result := ""
for ; number > 0; number /= 2 {
lsb := number % 2
result = strconv.Itoa(lsb) + result
}
return result
}
//go语言对换行有所讲究,需要加上逗号
func main() {
fmt.Println(
convertToBin(2),
convertToBin(3),
convertToBin(4),
)
//打印结果为10 11 100
}

初始化条件及递增表达式同时省略(相当于while,但是go语言中没有while)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}

}

func main() {
printFile("abc.txt")
}

死循环写法(并发编程相互通信会用到死循环)

1
2
3
4
5
6
7
8
9
func forever() {
for true {
fmt.Println("死循环")
}
}

func main() {
forever()
}

函数

函数可以返回多个值

函数返回多个值时可以起名字(仅限于非常简单的函数,但对于调用者可以随便起名)

go语言函数是函数式编程语言,函数是一等公民,函数里面的参数、返回值、函数体都可以有函数

go语言没有花哨的lambda表达式,是需要将匿名函数写出即可

go语言没有默认参数,也没有方法重载

go语言函数有可变参数列表

使用函数式编程实现3的4次方

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//重新定义pow函数接收int类型
func pow(a, b int) int {
return int(math.Pow(float64(a), float64(b)))
}
//op定义了调用方传递的函数
func apply(op func(int, int) int, a int, b int) (result int) {
//反射获取到传递过来的函数的指针
pointer := reflect.ValueOf(op).Pointer()
//通过指针获取函数名称
name := runtime.FuncForPC(pointer).Name()
//打印传递的函数名称及参数
fmt.Printf("calling function is %s with args (%d,%d) \n", name, a, b)
//返回值由传入的函数处理后的结果决定
return op(a, b)
}
func main() {
//计算3的4次方等于81,传的的函数式操作为pow,a的b次方
fmt.Println(apply(pow, 3, 4))
//也可以使用匿名函数的方式,计算2的3次方
fmt.Println(apply(
func(a int, b int) int {
return int(math.Pow(float64(a), float64(b)))
}, 2, 3))
}

执行结果

1
2
3
4
calling function is main.pow with args (3,4) 
81
calling function is main.main.func1 with args (2,3)
8

函数的可变参示例

1
2
3
4
5
6
7
func sum(numbers ...int) int {
sum := 0
for i := range numbers {
sum += numbers[i]
}
return sum
}

指针

先看一段代码

1
2
3
4
5
6
7
8
9
10
func pointer() {
//定义变量a赋值为2
var a int = 2
//定义一个指针pa,类型为*int(指针型int),指向变量a的地址值
var pa *int = &a
//将指针*pa指向的地址值赋值为3,也就是将变量a赋值为3
*pa = 3
fmt.Println(a)
//打印结果为3
}

go语言的指针不能进行运算

go语言只有值传递一种方式(值传递会copy一份数据进行操作/引用传递会操作传递的值)

go语言通过指针传递能打到引用传递的效果

自定义类型需要考虑该类型是需要作为指针使用还是作为值来使用

使用指针对变量进行操作

1
2
3
4
5
6
7
8
9
10
func swap(a *int, b *int) {
*a, *b = *b, *a
}
func main() {
a := 3
b := 4
//使用指针对变量进行操作
swap(&a, &b)
fmt.Println(a,b)
}

不使用指针将操作后的结果返回或许更好

1
2
3
4
5
6
7
8
9
10
11
func swap(a int, b int) (resa int, resb int) {
return b, a
}

func main() {
a := 3
b := 4
//使用指针对变量进行操作
a, b = swap(a, b)
fmt.Println(a,b)
}

内建容器

数组

数组是值类型,在函数中执行的时候进行拷贝一份再处理

go语言中认为[3]int类型与[5]int认为是不同的类型,在调用的时候数组长度不一致go会认为是不同类型

go语言中我们一般情况下不使用数组,也不使用数组的指针,我们使用切片Slice

数组的定义方法

1
2
3
4
5
6
7
8
//定义容量为5元素类型为int的数组(元素初始值为0)
var array1 [5]int
//定义容量为3元素类型为int的数组(指定元素初始值)
array2 := [3]int{1,2,3}
//容量为编译器来确定类型为int的数组
array3 := [...]int{2,4,6,8,10}
//定义4行5列的int类型二维数组(元素初始值为0)
var grid [4][5]int

普通for循环遍历数组

1
2
3
4
5
6
func foreachArr() {
arr := [...]int{2,4,6,8,10}
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
}

for i := range遍历数组

1
2
3
4
5
6
7
func foreachArr() {
arr := [...]int{2,4,6,8,10}
//这里的i取到的是arr的索引
for i := range arr{
fmt.Println(arr[i])
}
}

for i := range遍历数组同时取索引和值

1
2
3
4
5
6
7
func foreachArr() {
arr := [...]int{2,4,6,8,10}
//这里的i取到的是arr的索引,v取到的是值
for i,v := range arr{
fmt.Println(i,v)
}
}

for i := range遍历数组只获取值

1
2
3
4
5
6
7
func foreachArr() {
arr := [...]int{2,4,6,8,10}
//这里的索引以下标_替换代表不取索引,v取到的是值(任何地方都可以使用下标_省略变量)
for _,v := range arr{
fmt.Println(v)
}
}

切片(Slice)

我们先看一段代码,看看什么是切片

1
2
3
arr := [...]int{0,1,2,3,4,5,6,7}
s := arr[2:6]
//这里的s就是切片,一个左闭右开的区(数学里面的闭区间是取等的)

Slice其实是数组的一个视图

1
2
3
4
arr[2:6] = [2 3 4 5]
arr[:6] = [0 1 2 3 4 5]
arr[2:] = [2 3 4 5 6 7]
arr[:] = [0 1 2 3 4 5 6 7]

Slice本身没有数据,是对底层array的一个view

Reslice

1
2
3
4
5
6
7
arr := [...]int{0,1,2,3,4,5,6,7}
s2 := arr[:]
//[0 1 2 3 4 5 6 7]
s2 = s2[:5]
//[0 1 2 3 4]
s2 = s2[2:]
//[2 3 4]

slice的扩展

1
2
3
4
5
6
7
8
arr := [...]int{0,1,2,3,4,5,6,7}
//上面是元素数据
s1 := arr[2:6]
//[2 3 4 5]
//fmt.Pringln(s1[4])
//上面这行注释的代码会报索引越界异常,用下面的slice(s2)可以结局这个问题,golang的slice有cap的概念
s2 := s1[3:5]
//[5 6]

上面这一块的代码图解如下

slice扩展

slice的实现

slice的实现

slice底层有一个array

ptr指向了slice开头的元素

len指向了slice的长度,使用slice[n]只能取len长度里面的值,超过长度值会报索引越界

cap代表了从ptr开始整个数组的长度,扩展的时候只要不超过cap的长度就可以

slice可以向后扩展,但是不可以往前扩展,向后扩展不可以超过cap的长度

len(slice)可以取到这个slice的长度,cap(slice)可以取到这个slice的cap长度

切片(slice)的操作

slice的append操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
arr := [...]int{0,1,2,3,4,5,6,7}
//上面是元素数据
s1 := arr[2:6]
//s1--->[2 3 4 5]
//fmt.Pringln(s1[4])
//上面这行注释的代码会报索引越界异常,用下面的slice(s2)可以结局这个问题,golang的slice有cap的概念
s2 := s1[3:5]
//s2--->[5 6]
s3 := appent(s2,10)
//s3--->[5 6 10]
s4 := appent(s3,11)
//s4--->[5 6 10 11]
s5 := appent(s4,12)
//s5--->[5 6 10 11 12]

//arr最终的值为[0 1 2 3 4 5 6 10],arr的长度并没有发生变化,而s4及s5是对被扩展的数组的一个view,我们拿不到扩展的数组,s3 appent过后但是他并没有超过arr长度,所以对底层arr相应索引位置的数据进行了修改

添加元素时如果超过cap的长度,系统会重新分配更大的底层数组作为新slice的view

如果原来的数组有用系统会保留,如果没用会被go的垃圾回收机制给回收掉

由于值传递的关系必须接受appent的返回值 s = append(s,val)

slice的其它操作

slice的创建

定义一个空的slice长度为0

1
var s []int

定义指定元素内容的slice

1
s1 := []int{2,4,6,8}

内建函数定义len为16的slice

1
s2 := make([]int,16)

内建函数定义len为10 cap为32 的slice

1
s3 := make([]int,10,32)

slice的copy及元素remove操作

1
2
3
4
5
6
s1 := []int{2,4,6,8}
//s1--->[2 4 6 8]
s2 := make([]int,16)
//s2--->[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
copy(s2,s1)
//s2--->[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0]

如果我们想要把s2中的元素8删掉怎么操作呢

1
2
3
//s2--->[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0]
s2 = append(s2[:3],s2[4:]...)//s2所在的参数列表是一个可变参,如果需要将s2索引4右面的所有元素都送个它需要加上...的语法
//s2--->[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0]

去掉s2的首尾操作

1
2
3
4
5
6
7
8
9
//s2--->[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0]
//获取首元素的值,可以用于打印
front := s2[0]
//获取尾元素,可以用于打印
tail := s2[len(s2)-1]
//剔除首位操作
s2 := s2[1:]
s2 := s2[:len(s2)-1]
//s2--->[4 6 0 0 0 0 0 0 0 0 0 0 0]

Map

Map的创建

map[Key]Value 创建单层Key/Value类型的Map

map[Key1]map[Key2]Value 创建复合多层级Map

1
2
3
4
5
6
7
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
fmt.Println(mapDemo)

空map的创建

1
2
3
4
5
6
7
8
//使用make
m1 := make(map[string]int)
//m1--->map[] (m1 == empty)
//或者使用
var m2 map[string]int
//m2--->map[] (m2 == nil)

//go语言中的nil及empty是可以参与运算的,跟其它语言有所区别,值是nil也可以很安全的使用

Map的迭代

使用range对map进行迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
func mapIterate() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
fmt.Println(mapDemo)
for k,v := range mapDemo{
fmt.Println(k,v)
}
}
//map打印出来的值我们发现map中的元素是无序的

map获取指定元素

通过key获取map中value的值

1
2
3
4
5
6
7
8
9
10
func mapDemo() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
courseName := mapDemo["course"]
fmt.Println(courseName)
}

如果map中没有这个key的话获取map的值不会报错,他是一个空串,Println他会打印一个空行

判断map中的某个元素是否存在

判断元素在map中是否存在,存在则打印,不存在不打印

1
2
3
4
5
6
7
8
9
10
11
12
13
func mapDemo() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
if courseName, ok := mapDemo["course"]; ok {
fmt.Println(courseName)
} else {
fmt.Println("key does not exist")
}
}

map删除指定元素

删除map中的指定key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func mapDemo() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
delete(mapDemo,"name")
//删除key为name的元素,后面的key does not exist提示将会被打印
if courseName, ok := mapDemo["name"]; ok {
fmt.Println(courseName)
} else {
fmt.Println("key does not exist")
}
}

什么样的数据类型才能作为map的key呢

map使用哈希表,必须可以比较相等

除了slice,map,function的内建类型都可以作为key

Struct(自建)类型不包含上述字段也可以作为key

go在编译器会对上述条件进行检查

map实现leetCode算法3.无重复字符的最长子串

英文版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func lengthOfLongestSubstring(s string) int {
//记录每个字母最后出现的位置
lastOccurred := make(map[byte]int)
start := 0
maxLenght := 0
for i,ch := range []byte(s) {
//有可能不存在这个map获取出来的值是0,而0又是一个合法的下标,所以要判断这个key是否存在
if lastI,ok := lastOccurred[ch] ; ok && lastI >= start {
start = lastOccurred[ch] +1
}
if i - start +1 > maxLenght {
maxLenght = i - start + 1
}
lastOccurred[ch] = i
}
return maxLenght
}

国际版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func lengthOfLongestSubstring(s string) int {
//记录每个字母最后出现的位置
   lastOccurred := make(map[rune]int)
start := 0
maxLenght := 0
   for i,ch := range []rune(s) {
//有可能不存在这个map获取出来的值是0,而0又是一个合法的下标,所以要判断这个key是否存在
if lastI,ok := lastOccurred[ch] ; ok && lastI >= start {
start = lastOccurred[ch] +1
}
if i - start +1 > maxLenght {
maxLenght = i - start + 1
}
lastOccurred[ch] = i
}
return maxLenght
}

面向对象

go语言仅支持封装,不支持继承和多态

go语言没有class,只有struct

结构体

结构体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义结构体
type TreeNode struct {
value int
left,right *TreeNode
}

//对结构体数据进行处理
func main() {
node1 := TreeNode{value: 3}
left := TreeNode{value: 1}
right := TreeNode{value: 2}
node1.left = &left
node1.right = &right
fmt.Println(node1)
}
//打印结果
{3 0xc000004480 0xc0000044a0}

go语言没有构造函数的说法,但是可以使用func实现自定义工厂构造函数

1
2
3
4
5
func createNode(value int) *TreeNode {
//c**函数返回局部变量不可用,不过在go语言中是可以的
//注意返回的是局部变量的地址
return &TreeNode{value: value}
}

为结构体定义方法

结构体的方法有一个特点,就是它在函数名前面多了一个接收者

1
2
3
4
5
6
7
8
9
//定义结构体方法
func (node TreeNode) print() {
fmt.Print(node.value)
}
//使用结构体方法
func main() {
node := TreeNode{value: 3}
node.print()
}

go语言中所有方法参数的传递都是值传递,参数会被copy执行,执行过后传递的参数并没有被修改,如需修改需要使用指针

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
type TreeNode struct {
value int
left,right *TreeNode
}
func (node TreeNode) print() {
fmt.Println(node.value)
}
func (node TreeNode) setVal(value int) {
node.value = value
}
//解决值传递问题需要加*号以指针的方式即可修改传递过来的值
func (node *TreeNode) setValue(value int) {
node.value = value
}
func main() {
node := TreeNode{}
node.print()
//打印值为0
node.setVal(1)
node.print()
//打印值为0
node.setValue(2)
node.print()
//打印值为2
}

方法的值接收者与指针接收者原则

要改变内容必须使用指针接收者

结构过大也要考虑使用指针接收者

一致性:如果有指针接收者,最好都是用指针接收者

值接收者是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
type TreeNode struct {
value int
left,right *TreeNode
}
func (node TreeNode) print() {
fmt.Print(node.value)
}

func (node *TreeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}

func main() {
root := TreeNode{value:3}
root.left = &TreeNode{}
root.right = &TreeNode{value: 5}
root.left.right = &TreeNode{value: 2}
root.right.left = &TreeNode{value: 4}
root.traverse()
}

执行结果 0 2 3 4 5

遍历树图解

遍历树

traverse()函数先遍历左边再遍历自己再遍历右边往复递归

封装和包

封装

定义完方法我们一般要进行封装

go语言名字一般使用CamelCase

方法结构常量等定义首字母大写代表:public(针对包),

方法结构常量等定义首字母小写代表:private(针对包),

每个目录只能有一个包,包名不一定和目录名一样

main包包含和执行入口

一个目录下面main函数只能有一个main包,否则我们需要给目录起一个其它的名字

为结构定义方法必须放在同一个包内,但是可以是不同的文件

扩展已有类型

扩展已有类型可以定义别名或者使用组合

包装TreeNode为MyTreeNode实现新的遍历方式这里使用的方式是组合

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"
"world.ismyfree/go-learning/tree"
)

type MyTreeNode struct {
node *tree.Node
}

func (myNode *MyTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := MyTreeNode{myNode.node.Left}
left.postOrder()
right := MyTreeNode{myNode.node.Right}
right.postOrder()
myNode.node.Print()
}

func main() {
root := tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{Value: 5}
root.Left.Right = &tree.Node{Value: 2}
root.Right.Left = &tree.Node{Value: 4}
root.Traverse()
fmt.Println()
node := MyTreeNode{&root}
node.postOrder()
}

树还是之前的树,就不图解了,遍历结果为 2 0 4 5 3

定义slice的别名实现一个先进先出的队列

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
Queue.go
------------------------------------------------------
package queue

//定义一个队列,xian,它是一个int类型的slice
type Queue []int

//推进队列
func (q *Queue) Push(v int) {
*q = append(*q,v)
}

//弹出队列
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}

//队列元素是否为空
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
------------------------------------------------------


main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/queue"
)

func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
}
------------------------------------------------------

打印结果

1
2
3
4
5
1
2
false
3
true

安装第三方包goimports

移动到一下目录%GOPATH%/src/golang.org/x从github上download以下包

1
git clone git@github.com:golang/tools.git
1
git clone git@github.com:golang/mod.git
1
git clone git@github.com:golang/xerrors.git

执行命令安装goimports

1
2
//请将%GOPATH%替换为绝对路径后执行命令
go install %GOPATH%/src/golang.org/x/tools/cmd/goimports

尝试使用第三方库

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

import (
"fmt"
"golang.org/x/tools/container/intsets"
)

func testSparse() {
sparse := intsets.Sparse{}
sparse.Insert(1)
sparse.Insert(2)
sparse.Insert(3)
sparse.Insert(4)
fmt.Println(sparse.Has(5))
fmt.Println(sparse.Has(4))
}

func main() {
testSparse()
}

GOPATH目录结构

go build进行编译

go install产生pkg文件和可执行文件

go run直接编译运行

src

git repository1
git repository2

pkg

git repository1
git repository2

bin

可执行文件1,2,3

面向接口

go语言的接口是使用者定义的

接口实现是隐式的,无需注明实现了哪个接口,只需拥有接口同样的方法

接口变量自带指针

接口变量同样采用值传递,几乎不需要使用接口的指针

指针接收者实现只能以指针的方式使用;值接收者两者都可以

接口变量强制转换用变量接收后后面加’,ok’,可以使用ok判断是否该类型,学名叫Type assertion

除了使用Type assertion方式判断类型,也可以使用switch case语句的方式来判断类型并执行相关逻辑

interface{} 可以表示任何类型

接口定义

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
mockretriever.go
------------------------------------------------------
package mock

type Retriever struct {
Contents string
}
//定义接口实现,无需指明实现了哪个接口,方法只需要实现接口中的方法
func (r Retriever) Get(url string) string {
return r.Contents
}
------------------------------------------------------


realretriever.go
------------------------------------------------------
package rel

import (
"net/http"
"net/http/httputil"
"time"
)

type Retriever struct {
UserAgent string
TimeOut time.Duration
}
//定义接口实现,无需指明实现了哪个接口,方法只需要实现接口中的方法
func (r Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
------------------------------------------------------


main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/retriever/mock"
"world.ismyfree/go-learning/retriever/rel"
)
//定义接口
type Retriever interface {
Get(url string) string
}

func download(r Retriever) string {
return r.Get("http://www.baidu.com")
}

func main() {
//接口变量(里面有是闲着类型/实现者的值或实现者的指针(它指向了一个实现者))
//不要使用接口变量的地址,因为接口变量本身可以包含指针,内部指向实现者即可
var r Retriever
//假的retriever
r = mock.Retriever{Contents: "this is a fack imooc.com"}
fmt.Println(download(r))
//真实的retriever
r = rel.Retriever{}
fmt.Println(download(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
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
mockretriever.go
------------------------------------------------------
package mock

type (
Retriever struct {
Contents string
}
)

func (r *Retriever) Post(url string, form map[string]string) string {
r.Contents = form["contents"]
return "ok"
}

func (r *Retriever) Get(url string) string {
return r.Contents
}
------------------------------------------------------

main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/retriever/mock"
)

type Retriever interface {
Get(url string) string
}

type Poster interface {
Post(url string,form map[string]string) string
}

func download(r Retriever) string {
return r.Get("http://www.baidu.com")
}

func post(poster Poster) {
poster.Post("http:www.baidu.com", map[string]string{"name":"zhangsan","course":"java"})
}

type RetrieverPoster interface {
Retriever
Poster
}

const url = "http://www.baidu.com"
func session(s RetrieverPoster) string{
s.Post(url, map[string]string{"contents":"anather faked imooc.com"})
return s.Get(url)

}

func main() {
var r RetrieverPoster
r = &mock.Retriever{Contents: "this is a fack imooc.com"}
//type assertion
if mockRetriever,ok := r.(*mock.Retriever); ok{
fmt.Println(mockRetriever.Contents)
} else {
fmt.Println("not a mock retriever")
}
fmt.Println(session(r))
}

常用标准接口

该接口相当于其它语言中的toString

io.go —> io.Reader#Read(p []byte) (n int, err error)

文件、网络、byte、slice、string等等都会用Reader#Read

io.go —> io.Writer#Write(p []byte) (n int, err error)

文件、网络、byte、slice、string等等都会用Reader#Write

使用io.go —> io.Reader#Read(p []byte)代替file示例

这里不仅可以使用文件还可以使用其它字符串等数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
printFileContents(file)
}

func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}

func main() {
printFile("basic/loop/abc.txt")
s := `abc"d"
kkkk
123
p`
printFileContents(strings.NewReader(s))

}

函数式编程

函数是一等公民:参数、变量、返回值等都可以是函数,可以给函数实现接口

高阶函数:函数的参数还是一个函数

函数—>闭包

正统函数式编程

不可变性:不能有状态,只有常量和函数
正统函数只能有一个参数
go语言式通用语言,不会再正统函数式做文章

使用函数式编程实现一个累加器(闭包)

代码

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 adder() func(int) int {
sum := 0
//这里的v既是一个参数又是函数体的一个局部变量
return func(v int) int {
sum += v
//这里的sum是这个函数体外面定义的,他是一个自有变量
return sum
}
}

func main() {
//这里返回的是一个函数实例,每调用一次函数实例将对实例的返回值进行累加
funcAdder := adder()
for i := 0; i < 10; i++ {
fmt.Printf("0+...+%d = %d\n",i,funcAdder(i))
}
}

执行结果

1
2
3
4
5
6
7
8
9
10
0+...+0 = 0
0+...+1 = 1
0+...+2 = 3
0+...+3 = 6
0+...+4 = 10
0+...+5 = 15
0+...+6 = 21
0+...+7 = 28
0+...+8 = 36
0+...+9 = 45

闭包

自由变量不断地找与局部变量的联系然后整体返回一个闭包

以正统函数方式实现累加器

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

import "fmt"

type innerAdder func(int) (int, innerAdder)

func adder(base int) innerAdder {
return func(v int) (int, innerAdder) {
return base + v, adder(base + v)
}
}

func main() {
innerAdder := adder(0)
for i := 0; i < 10; i++ {
var s int
s, innerAdder = innerAdder(i)
fmt.Printf("0+...+%d = %d\n",i,s)
}
}

java中的闭包实现累加器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
private static Function<Integer, Integer> adder() {
final Holder<Integer> sum = new Holder<>(0);
return (Integer value) -> {
sum.value += value;
return sum.value;
};
}
public static void main(String[] args) {
Function<Integer, Integer> adder = adder();
for (int i = 0; i < 10; i++) {
System.out.println(adder.apply(i));
}
}
}

java8后:使用Function接口和lambda表达式来创建函数对象

匿名类或者lambda表达式均支持闭包

斐波那契生成器

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

import "fmt"

//斐波那契生成器
func fibonacci() func() int {
a,b := 0,1
return func() int {
a,b = b,a+b
return a
}
}

func main() {
generate := fibonacci()
fmt.Println(generate())
fmt.Println(generate())
fmt.Println(generate())
fmt.Println(generate())
fmt.Println(generate())
}

实现io.Reader遍历斐波那契生成器中的数据

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
package main

import (
"bufio"
"fmt"
"io"
"strings"
)
//定义函数类型
type intGen func() int
//为函数类型实现接口
func (g intGen) Read(p []byte) (n int, err error) {
next := g()
//斐波那契生成器永远读不完做一个限制
if next > 10000 {
return 0,io.EOF
}
s := fmt.Sprintf("%d\n", next)
//TODO: 如果p太小则不正确
return strings.NewReader(s).Read(p)
}
//斐波那契生成器(替换定义类型,它实现了io.Reader接口)
func fibonacci() intGen {
a,b := 0,1
return func() int {
a,b = b,a+b
return a
}
}
//传递reader实现
func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)
//遍历生成器中的数据
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
//主函数
func main() {
//实现reader的斐波那契生成器
generate := fibonacci()
//读取斐波那契生成器的数据
printFileContents(generate)
}

遍历二叉树传递遍历过程中的操作

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
node.go
------------------------------------------------------
package tree

import "fmt"

func (node Node) setVal(value int) {
node.Value = value
}
func (node *Node) setValue(value int) {
node.Value = value
}
//定义类型
type Node struct {
Value int
Left,Right *Node
}
//定义print
func (node Node) Print() {
fmt.Print(node.Value," ")
}
//遍历树
func (node *Node) Traverse() {
node.TraverseFunc(func(n *Node) {
if node == nil {
return
}
n.Print()
})
fmt.Println()
}
//遍历树同时做什么操作由传递的函数决定
func (node *Node) TraverseFunc(f func(node *Node)) {
if node == nil {
return
}
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}
------------------------------------------------------

main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/tree"
)

func main() {
root := tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{Value: 5}
root.Left.Right = &tree.Node{Value: 2}
root.Right.Left = &tree.Node{Value: 4}
root.Traverse()
count := 0
root.TraverseFunc(func(node *tree.Node) {
count++
})
fmt.Println("Noce count",count)
}

错误处理与资源管理

defer调用

defer确保在函数结束时发生调用

参数在defer语句时计算(for loop 会栈执行)

defer调用里面时栈存储,先进后出

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
fib.go
------------------------------------------------------
package fib

import (
"fmt"
"io"
"strings"
)

type fibIntGen func() int

//为函数类型实现接口
func (g fibIntGen) Read(p []byte) (n int, err error) {
next := g()
//斐波那契生成器永远读不完做一个限制
if next > 10000 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)
return strings.NewReader(s).Read(p)
}

func Fibonacci() fibIntGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
------------------------------------------------------


main.go
------------------------------------------------------
package main

import (
"bufio"
"fmt"
"os"
"world.ismyfree/go-learning/functional/fibonacci/fib"
)

func writeFile(filename string) {
file, err := os.Create(filename)
if err != nil {
panic(err)
}
//关闭文件资源
defer file.Close()
writer := bufio.NewWriter(file)
//将数据写入文件
defer writer.Flush()
fibonacci := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer,fibonacci())
}
}

func main() {
writeFile("fib.txt")
}

由于defer调用是栈类型,writer.Flush()会比file.Close()先执行

defer在写代码的时候就想到创建file就要关闭,创建writer就要想到flush

何时使用defer调用

Open/Close

Lock/Unlock

PrintHeader/PrintFooter

错误处理

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 (
"bufio"
"fmt"
"os"
"world.ismyfree/go-learning/functional/fibonacci/fib"
)

func writeFile(filename string) {
// If there is an error, it will be of type *PathError.
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
//如果是*os.PathError,打印指定信息
if pathError,ok := err.(*os.PathError); ok {
fmt.Println(pathError.Op,pathError.Path,pathError.Err)
} else {
//否则打印正常错误信息
panic(err)
}
}
defer file.Close()

writer := bufio.NewWriter(file)
defer writer.Flush()

fibonacci := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer,fibonacci())
}
}

func main() {
writeFile("fib.txt")
}

统一错误处理

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
88
89
90
91
92
93
94
95
handler.go
------------------------------------------------------
package filelisting

import (
"io/ioutil"
"net/http"
"os"
)

//包装为http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))的第二个参数
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
path := request.URL.Path[len("/list/"):]
file, err := os.Open(path)
//错误文件路径会控制台会报错,我们需要对错误进行统一处理
//panic serving [::1]:55751: open fib.txt1: The system cannot find the file specified.
/*if err != nil {
panic(err)
}*/
//内部错误展示给用户不太好,需要进行包装
/*if err != nil {
http.Error(writer,
err.Error(),
http.StatusInternalServerError)
return
}*/
//错误直接返回使用通用处理方法
if err != nil {
return err
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(content)
return nil
}
------------------------------------------------------


web.go
------------------------------------------------------
package main

import (
"net/http"
"os"
"world.ismyfree/go-learning/errhanding/filelistingserver/filelisting"
)

//定义函数类型对函数进行包装
type appHandler func(writer http.ResponseWriter, request *http.Request) error

/*
统一错误处理方法
1.http.HandleFun函数的第二个参数是一个func(ResponseWriter, *Request)
2.使用errWrapper方法对其进行包装返回一个func(ResponseWriter, *Request)
3.handler(writer,request)执行过程中如果报错就会返回err,然后对err进行处理
*/
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
//包装一个函数,返回一个函数,在返回一个无返回值的函数中进行处理
return func(writer http.ResponseWriter, request *http.Request) {
//这个handler实质就是filelisting.HandleFileList
err := handler(writer, request)
if err != nil {
//控制台打印错误信息
log.Warn("Error handling request: %s",err.Error())
//赋值默认值
code := http.StatusOK
switch {
//文件不存在code
case os.IsNotExist(err):
code = http.StatusNotFound
//权限不够禁止访问
case os.IsPermission(err):
code = http.StatusForbidden
//默认系统内部异常code
default:
code = http.StatusInternalServerError
}
//将错误消息响应给客户端
http.Error(writer, http.StatusText(code), code)
}
}
}

func main() {
http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

panic与recover

panic(恐慌)

看到panic想到这幅图(重)

panic是一个很重的词,尽量少用

停止当前函数执行(跟其它语言的throw Exception有点像)

一致向上返回,执行每一层的defer调用

如果没有遇见recover,程序退出

recover(恢复)

仅在defer调用中使用

在defer调用recover里面可以获取panic的值进行处理

如果在defer调用中无法处理panic的值,可以重新panic

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"
)

func main() {
tryRecover()
fmt.Println("test")
}

func tryRecover() {
//匿名函数
defer func() {
//defer调用遇见recover
r := recover()
//如果是一个错误对错误进行处理
if err, ok := r.(error); ok {
fmt.Println("Error occurred:", err)
} else {
//否则重新panic
//panic(r)
panic(fmt.Sprintf("I don't know what to do: %v",err))
}
}() //大括号是匿名函数的函数体,我们需要匿名函数被调用在函数体后面需要加上小括号
//recover这个错误我们在defer中调用
//panic(errors.New("this is an error"))
//b := 0
//a := 5 / b
//fmt.Println(a)
panic(123)

}

对统一错误处理进行改进 使用dever调用recover

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
//http.HandleFunc调用到server.go文件中的func (c *conn) serve(ctx context.Context)方法走了defer调用recover
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed)
}
}()
------------------------------------------------------


web.go
------------------------------------------------------
//我们手动处理更友好,处理未知错误,友好相应给客户端
package main

import (
"log"
"net/http"
"os"
"world.ismyfree/go-learning/errhanding/filelistingserver/filelisting"
)

//定义函数类型对函数进行包装
type appHandler func(writer http.ResponseWriter, request *http.Request) error

/*
统一错误处理方法
1.http.HandleFun函数的第二个参数是一个func(ResponseWriter, *Request)
2.使用errWrapper方法对其进行包装返回一个func(ResponseWriter, *Request)
3.handler(writer,request)执行过程中如果报错就会返回err,然后对err进行处理
*/
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
//包装一个函数,返回一个函数,在返回一个无返回值的函数中进行处理
return func(writer http.ResponseWriter, request *http.Request) {
//defer对未知错误进行处理!!!!!!!!!!!!!!
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()

//这个handler实质就是filelisting.HandleFileList
err := handler(writer, request)
if err != nil {
//控制台打印错误信息
log.Printf("Error occurred handling request: %s", err.Error())
//赋值默认值
code := http.StatusOK
switch {
//文件不存在code
case os.IsNotExist(err):
code = http.StatusNotFound
//权限不够禁止访问
case os.IsPermission(err):
code = http.StatusForbidden
//默认系统内部异常code
default:
code = http.StatusInternalServerError
}
//将错误消息响应给客户端
http.Error(writer, http.StatusText(code), code)
}
}
}

func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

自定义用户错误处理未知错误给前端友好提示(终极版)

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
handler.go
------------------------------------------------------
package filelisting

import (
"io/ioutil"
"net/http"
"os"
"strings"
)

const prefix = "/list/"

type userError string
//error接口有一个Error方法相当于实现了error接口
func (e userError) Error() string {
return e.Message()
}
//userError包装了一个Message方法,这两个方法相当于实现了userError接口及error接口
func (e userError) Message() string {
return string(e)
}

//包装为http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))的第二个参数
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
if strings.Index(request.URL.Path, prefix) != 0 {
//return errors.New("path mast start with :" + prefix)
return userError("path must start with: "+prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
//错误文件路径会控制台会报错,我们需要对错误进行统一处理
//panic serving [::1]:55751: open fib.txt1: The system cannot find the file specified.
/*if err != nil {
panic(err)
}*/
//内部错误展示给用户不太好,需要进行包装
/*if err != nil {
http.Error(writer,
err.Error(),
http.StatusInternalServerError)
return
}*/
//错误直接返回使用通用处理方法
if err != nil {
return err
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(content)
return nil
}
------------------------------------------------------


web.go
------------------------------------------------------
package main

import (
"log"
"net/http"
"os"
"world.ismyfree/go-learning/errhanding/filelistingserver/filelisting"
)

//定义函数类型对函数进行包装
type appHandler func(writer http.ResponseWriter, request *http.Request) error

/*
统一错误处理方法
1.http.HandleFun函数的第二个参数是一个func(ResponseWriter, *Request)
2.使用errWrapper方法对其进行包装返回一个func(ResponseWriter, *Request)
3.handler(writer,request)执行过程中如果报错就会返回err,然后对err进行处理
*/
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
//包装一个函数,返回一个函数,在返回一个无返回值的函数中进行处理
return func(writer http.ResponseWriter, request *http.Request) {
//defer对未知错误进行处理!!!!!!!!!!!!!!
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()

//这个handler实质就是filelisting.HandleFileList
err := handler(writer, request)
if err != nil {
//控制台打印错误信息
log.Printf("Error occurred handling request: %s", err.Error())
//如果错误是希望展示给用户的错误
if userError, ok := err.(userError); ok {
http.Error(writer,userError.Message(),http.StatusBadRequest)
return
}

//赋值默认值
code := http.StatusOK
switch {
//文件不存在code
case os.IsNotExist(err):
code = http.StatusNotFound
//权限不够禁止访问
case os.IsPermission(err):
code = http.StatusForbidden
//默认系统内部异常code
default:
code = http.StatusInternalServerError
}
//将错误消息响应给客户端
http.Error(writer, http.StatusText(code), code)
}
}
}

//定义能给用户看的错误
type userError interface {
error
Message() string
}

func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

测试与性能调优

表格驱动测试

方法名称以func TestXxx(t *testing.T)格式

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
package main

import (
"math"
"testing"
)

//测试方法参数需要添加*testing.T作为方法参数
func TestTriangle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 6},
{5, 12, 13},
{8, 15, 17},
{30000, 40000, 50000},
}
for _, element := range tests {
if actual := calcTriangle(element.a, element.b); actual != element.c {
t.Errorf("calcTriangle(%d,%d) got %d ; expected %d",element.a,element.b,actual,element.c)
}
}
}

//计算勾股定理
func calcTriangle(a int, b int) int {
var c int
c = int(math.Sqrt(float64(a*a + b*b)))
return c
}

打印结果
------------------------------------------------------
API server listening at: [::]:50325
=== RUN TestTriangle
add_test.go:17: calcTriangle(3,4) got 5 ; expected 6
--- FAIL: TestTriangle (0.00s)
FAIL

Debugger finished with exit code 0
------------------------------------------------------

查看代码覆盖率

先生成代码覆盖率文件

1
go test -coverprofile=c.out

使用可视化工具查看文件

1
go tool cover -html=c.out

代码覆盖率可视化查看

红色是没有覆盖的代码

绿色是覆盖的代码

代码性能测试

方法名称以func Benchmark(b *testing.B)格式

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
package main

import (
"math"
"testing"
)
//性能测试代码
func BenchmarkSubStr(b *testing.B) {
s := "黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花"
ans := 8
//b.N让系统决定代码性能执行的次数
for i := 0; i < b.N; i++ {
actual := lengthOfLongestSubstring(s)
if actual != ans {
b.Errorf("got %d for input %s; expected %d", actual,s,ans)
}
}
}
//需要进行性能测试的方法快
func lengthOfLongestSubstring(s string) int {
//记录每个字母最后出现的位置
lastOccurred := make(map[rune]int)
start := 0
maxLenght := 0
for i,ch := range []rune(s) {
//有可能不存在这个map获取出来的值是0,而0又是一个合法的下标,所以要判断这个key是否存在
if lastI,ok := lastOccurred[ch] ; ok && lastI >= start {
start = lastOccurred[ch] +1
}
if i - start +1 > maxLenght {
maxLenght = i - start + 1
}
lastOccurred[ch] = i
}
return maxLenght
}

打印结果
------------------------------------------------------
API server listening at: [::]:62673
goos: windows
goarch: amd64
pkg: world.ismyfree/go-learning/basic/loop
BenchmarkSubStr
BenchmarkSubStr-8 523216 2315 ns/op
PASS

Debugger finished with exit code 0
------------------------------------------------------

也可以使用命令行对代码进行测试

1
go test -bench .

生成性能测试生成cpu报告

1
go test -bench . -cpuprofile cpu.out

使用pprof工具查看cpu.out报告文件

输入命令进入工具命令行

1
go tool pprof cpu.out

进入命令行工具后输入web命令即可以图形化网页形式展示性能问题,针对问题进行调优

输入help可查看帮助命令

各种报错没能成功,有时间再研究

http测试

仅对函数进行测试(更像单元测试)

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
88
89
90
91
92
93
errorwrapper_test.go
------------------------------------------------------
package main

import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)

type testingUserError string

//error接口有一个Error方法相当于实现了error接口
func (e testingUserError) Error() string {
return e.Message()
}

//testingUserError包装了一个Message方法,这两个方法相当于实现了testingUserError接口及error接口
func (e testingUserError) Message() string {
return string(e)
}

func errPanic(writer http.ResponseWriter, request *http.Request) error {
panic(123)
}

func errUserError(writer http.ResponseWriter, request *http.Request) error {
return testingUserError("user error")
}

func errNotFound(writer http.ResponseWriter, request *http.Request) error {
return os.ErrNotExist
}

func errNoPermission(writer http.ResponseWriter, request *http.Request) error {
return os.ErrPermission
}

func errUnknown(writer http.ResponseWriter, request *http.Request) error {
return errors.New("unknown error")
}

func noError(writer http.ResponseWriter, request *http.Request) error {
fmt.Fprintln(writer, "no error")
return nil
}

func TestErrWrapper(t *testing.T) {
tests := []struct {
h appHandler
code int
message string
}{
{errPanic, 500, "Internal Server Error"},
{errUserError, 400, "user error"},
{errNotFound, 404, "Not Found"},
{errNoPermission, 403, "Forbidden"},
{errUnknown, 500, "Internal Server Error"},
{noError,200,"no error"},
}
for _, tt := range tests {
f := errWrapper(tt.h)
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://localhost:8888/list/fib.txt", nil)
f(response, request)
b, _ := ioutil.ReadAll(response.Body)
body := strings.Trim(string(b), "\n")
if response.Code != tt.code || body != tt.message {
t.Errorf("expect (%d,%s); got (%d, %s)", tt.code, tt.message, response.Code, body)
}

}
}
------------------------------------------------------
响应结果
------------------------------------------------------
API server listening at: [::]:50904
=== RUN TestErrWrapper
2020/12/28 17:26:00 Panic: 123
2020/12/28 17:26:00 Error occurred handling request: user error
2020/12/28 17:26:00 Error occurred handling request: file does not exist
2020/12/28 17:26:00 Error occurred handling request: permission denied
2020/12/28 17:26:00 Error occurred handling request: unknown error
--- PASS: TestErrWrapper (0.01s)
PASS

Debugger finished with exit code 0
------------------------------------------------------

开一个服务器对http进行测试(集成度更高)

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
errorwrapper_test.go
------------------------------------------------------
package main

import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)

type testingUserError string

//error接口有一个Error方法相当于实现了error接口
func (e testingUserError) Error() string {
return e.Message()
}

//testingUserError包装了一个Message方法,这两个方法相当于实现了testingUserError接口及error接口
func (e testingUserError) Message() string {
return string(e)
}

func errPanic(writer http.ResponseWriter, request *http.Request) error {
panic(123)
}

func errUserError(writer http.ResponseWriter, request *http.Request) error {
return testingUserError("user error")
}

func errNotFound(writer http.ResponseWriter, request *http.Request) error {
return os.ErrNotExist
}

func errNoPermission(writer http.ResponseWriter, request *http.Request) error {
return os.ErrPermission
}

func errUnknown(writer http.ResponseWriter, request *http.Request) error {
return errors.New("unknown error")
}

func noError(writer http.ResponseWriter, request *http.Request) error {
fmt.Fprintln(writer, "no error")
return nil
}

func TestErrWrapperInServer(t *testing.T) {
tests := []struct {
h appHandler
code int
message string
}{
{errPanic, 500, "Internal Server Error"},
{errUserError, 400, "user error"},
{errNotFound, 404, "Not Found"},
{errNoPermission, 403, "Forbidden"},
{errUnknown, 500, "Internal Server Error"},
{noError, 200, "no error"},
}
for _, tt := range tests {
f := errWrapper(tt.h)
server := httptest.NewServer(http.HandlerFunc(f))
resp, _ := http.Get(server.URL)
verifyResponse(resp,tt.code,tt.message,t)
}
}

func verifyResponse(resp *http.Response,expectCode int,expectMessage string,t *testing.T) {
b, _ := ioutil.ReadAll(resp.Body)
body := strings.Trim(string(b), "\n")
if resp.StatusCode != expectCode || body != expectMessage {
t.Errorf("expect (%d,%s); got (%d, %s)", expectCode, expectMessage, resp.StatusCode, body)
}
}

查看接口文档

需要安装godoc工具

1
go install golang.org/x/tools/cmd/godoc

执行命令查看接口文档

1
godoc -http=:6060

打开本地端口网页6060查看(所有接口文档,包含本地及官方)

web页面接口文档

并发编程

协程Coroutine

轻量级”线程”

非抢占式多任务处理,由协程主动交出控制权

编译器/解释器/虚拟机层的多任务

多个协程可以在一个或者多个线程上运行

子程序是协程的一个特例(协程是比子程序更宽泛的一个概念)

协程与普通函数的对比

协程简单演示go func

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
package main

import (
"fmt"
"runtime"
"time"
)

func main() {
var a [10]int
for i := 0; i < 10; i++ {
//协程
go func(i int) {
for true {
//id操作协程之间会切换
//fmt.Printf("hello from goroutine %d",i)
a[i]++
//手动交出协程控制权
runtime.Gosched()
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}

此段代码有数据访问冲突,可以使用以下命令进行检测

1
go run -race xxx.go

命令执行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
==================
WARNING: DATA RACE
//读
Read at 0x00c0001300f0 by main goroutine:
main.main()
     D:/goProject/src/world.ismyfree/go-learning/goroutine/goroutine.go:24 +0x104
//写
Previous write at 0x00c0001300f0 by goroutine 7:
main.main.func1()
D:/goProject/src/world.ismyfree/go-learning/goroutine/goroutine.go:17 +0x6f

Goroutine 7 (running) created at:
main.main()
D:/goProject/src/world.ismyfree/go-learning/goroutine/goroutine.go:13 +0xca
==================
[4400 3695 3707 3321 3159 2643 2964 2641 2969 2490]
Found 1 data race(s)
exit status 66

goroutine

goroutine抽象图

任何函数只要加上go就能送给调度器运行

不需要在定义是区分是否是异步函数

调度器在合适的点进行切换

使用-race可以检测数据访问冲突

调度器会将各个协程映射到物理机的各个线程

goroutine可能切换的参考点

I/O,select (如print)
d
channel

等待锁

函数调用(有时)

runtime.Gosched()(手动交出协程控制权)

channel

channel

goroutine与goroutine之间双向的通道就是channel

channel是goroutine与goroutine之间的交互,一个goroutine发送数据就要有一个goroutine接收数据,没有goroutine接收数据会发生dadelock

channel

channel
buffered channel
range

理论基础:Communication Sequential Process (CSP模型)

不要通过共享内存来通信;通过通信来共享内存

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
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
package main

import (
"fmt"
"time"
)

//worker是一个goroutine
func worker(id int, c chan int) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
fmt.Printf("worker %d received %c\n", id, n)
}
}

//创建worker
func createWorker(id int) chan<- int {
c := make(chan int)
//这部分操作需要交给一个goroutine
go worker(id, c)
//返回一个channel
return c
}

//channel示例代码
func chanDemo() {
var channels [10]chan<- int
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
channels[i] = createWorker(i)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}

//bufferedChannel
func bufferedChannel() {
c := make(chan int, 3)
go worker(0, c)
c <- 'A'
c <- 'B'
c <- 'C'
c <- 'C'
c <- 'C'
time.Sleep(time.Millisecond)
}

//bufferedChannel
func channelClose() {
c := make(chan int, 3)
go worker(0, c)
c <- 'A'
c <- 'B'
c <- 'C'
c <- 'C'
c <- 'C'
close(c)
time.Sleep(time.Millisecond)
}

//main
func main() {
fmt.Println("Channel as first-class citizen")
chanDemo()
fmt.Println("Buffered channel")
bufferedChannel()
fmt.Println("Channel close and range")
channelClose()
}

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
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
package main

import (
"fmt"
)

//worker是一个goroutine
func doWorker(id int, c chan int, done chan bool) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
fmt.Printf("worker %d received %c\n", id, n)
//通过通信来共享内存通知外面打印完成
//done <- true
//channel发送数据需要在另外的goroutine中做,否则会发生阻塞
//go func() {done<-true}()
done <- true
}
}

//创建worker
func createWorker(id int) worker {
w := worker{
in: make(chan int),
done: make(chan bool),
}
//c := make(chan int)
//这部分操作需要交给一个goroutine
go doWorker(id, w.in, w.done)
//返回一个channel
return w
}

type worker struct {
in chan int
done chan bool
}

//channel示例代码
func chanDemo() {
var workers [10]worker
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
workers[i] = createWorker(i)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
}
for _, worker := range workers {
<-worker.done
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
}
for _, worker := range workers {
<-worker.done
}

/*for _,worker := range workers{
<-worker.done
<-worker.done
}*/
//sleep的方式很不好,我们需要通知到外面已经打印完成
//time.Sleep(time.Millisecond)
}

//main
func main() {
chanDemo()
}

使用go提供的waitGroup改进示例

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
package main

import (
"fmt"
"sync"
)

//worker是一个goroutine
func doWorker(id int, c chan int, wg *sync.WaitGroup) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
fmt.Printf("worker %d received %c\n", id, n)
//通过通信来共享内存通知外面打印完成
//done <- true
//channel发送数据需要在另外的goroutine中做,否则会发生阻塞
//go func() {done<-true}()
//done <- true
wg.Done()
}
}

//创建worker
func createWorker(id int,wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
wg: wg,
}
//c := make(chan int)
//这部分操作需要交给一个goroutine
go doWorker(id, w.in, wg)
//返回一个channel
return w
}

type worker struct {
in chan int
wg *sync.WaitGroup
}

//channel示例代码
func chanDemo() {
//go提供的等待多任务完成的方法
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
workers[i] = createWorker(i,&wg)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
wg.Wait()
}

//main
func main() {
chanDemo()
}

使用函数式编程对示例进行抽象重构

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
package main

import (
"fmt"
"sync"
)

//worker是一个goroutine
func doWorker(id int, w worker) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range w.in {
fmt.Printf("worker %d received %c\n", id, n)
//通过通信来共享内存通知外面打印完成
//done <- true
//channel发送数据需要在另外的goroutine中做,否则会发生阻塞
//go func() {done<-true}()
//done <- true
w.done()
}
}

//创建worker
func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
done: func() {
wg.Done()
},
}
//c := make(chan int)
//这部分操作需要交给一个goroutine
go doWorker(id, w)
//返回一个channel
return w
}

type worker struct {
in chan int
done func()
}

//channel示例代码
func chanDemo() {
//go提供的等待多任务完成的方法
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
workers[i] = createWorker(i, &wg)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
wg.Wait()
}

//main
func main() {
chanDemo()
}

使用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
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
88
89
90
91
92
Node.go
------------------------------------------------------
package tree

import "fmt"

func (node Node) setVal(value int) {
node.Value = value
}
func (node *Node) setValue(value int) {
node.Value = value
}

//定义类型
type Node struct {
Value int
Left, Right *Node
}

//定义print
func (node Node) Print() {
fmt.Print(node.Value, " ")
}

//遍历树
func (node *Node) Traverse() {
node.TraverseFunc(func(n *Node) {
if node == nil {
return
}
n.Print()
})
fmt.Println()
}

//遍历树同时做什么操作由传递的函数决定
func (node *Node) TraverseFunc(f func(node *Node)) {
if node == nil {
return
}
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}

//使用channel对树进行遍历
func (node *Node) TraverseWithChannel() chan *Node {
out := make(chan *Node)
go func() {
node.TraverseFunc(
func(node *Node) {
out <- node
},
)
close(out)
}()
return out
}
------------------------------------------------------

main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/tree"
)

func main() {
root := tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{Value: 5}
root.Left.Right = &tree.Node{Value: 2}
root.Right.Left = &tree.Node{Value: 4}
root.Traverse()
count := 0
root.TraverseFunc(func(node *tree.Node) {
count++
})
fmt.Println("Node count: ", count)

//使用channel遍历树获取最大值
c := root.TraverseWithChannel()
maxNode := 0
for node := range c {
if node.Value > maxNode {
maxNode = node.Value
}
}
fmt.Println("Max node value:", maxNode)
}

使用select方式对chnnel进行调度

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
package main

import (
"fmt"
"math/rand"
"time"
)

//worker是一个goroutine
func worker(id int, c chan int) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
time.Sleep(time.Second)
fmt.Printf("worker %d received %d\n", id, n)
}
}

//创建worker
func createWorker(id int) chan<- int {
c := make(chan int)
//这部分操作需要交给一个goroutine
go worker(id, c)
//返回一个channel
return c
}

func generator() chan int {
out := make(chan int)
go func() {
i := 0
for true {
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}

func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)
var values []int
//10s后往这个channel发送时间
afterTenSecond := time.After(10 * time.Second)
//每秒往这个
tick := time.Tick(time.Second)
for true {
var activeWorker chan<- int
var activeValue int
if len(values) > 0 {
activeWorker = worker
activeValue = values[0]
}
select {
case n := <-c1:
values = append(values, n)
case n := <-c2:
values = append(values, n)
case activeWorker <- activeValue:
values = values[1:]
case <-time.After(500 * time.Millisecond):
//每次进寻魂生成数据超过500毫秒打印time out
fmt.Println("time out")
case <-tick:
fmt.Println("queue len = ", len(values))
case <-afterTenSecond:
//10s后冲这个channel获取到数据结束程序
fmt.Println("Bye")
return
}
}
}

传统同步机制

WaitGroup

Mutex(互斥量)

cond(条件变量)

Mutex示例(真实操作请使用系统库提供的原子操作,这里仅作为示例)

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"
"time"
)

type atomicInt struct {
value int
lock sync.Mutex
}

func (a *atomicInt) increment() {
fmt.Println("safe increment")
func(){
a.lock.Lock()
defer a.lock.Unlock()
a.value++
}()
}

func (a *atomicInt) get() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.value
}

func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.get())
}

http及其它标准库

httpClient示例

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"
"net/http"
"net/http/httputil"
)

func main() {
request, err := http.NewRequest(http.MethodGet, "https://www.imooc.com", nil)
//控制请求的头部信息
request.Header.Add("User-Agent","Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")
//自己构建客户端打印重定向时候的操作
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
fmt.Println("Redirect", req)
return nil
},
}
//我们使用自己构建的client
resp, err := client.Do(request)
//resp, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
//使用httputil简化工作
s, err := httputil.DumpResponse(resp, true)
if err != nil {
panic(err)
}
fmt.Printf("%s\n",s)
}

httpServer性能分析

在程序中import _ “net/http/pprof”

在浏览器中打开接口路径为/debug/pprof/的请求

http pprof工具

使用命令行pprof工具获得程序30秒的性能访问权限(30秒内随意访问服务的接口)

1
go tool pprof http://localhost:8888/debug/pprof/profile

以上命令操作完后输入web以图形化界面进行分析程序(需要安装Graphviz)

也可以使用如下命令查看内存使用情况

1
go tool pprof http://localhost:8888/debug/pprof/profile

其它标准库

常用标准库

bufio

log

encoding/json

regexp

time

strings/math/rand

起一个服务查看标准库文档

1
godoc -http=:6060

中文标准库网址

点此进行跳转go中文api

迷宫的广度优先搜索

迷宫示意图

0代表可以走的地方

1代表墙

人为规定入口和出口为左上角和右下角

思路

想想我们在一个未知的世界

第0步

顺序探索并放入未探索队列当中(上左下右)

第1步

每探索一个移除未探索队列并将新探索到未探索的加入队列中

第2步上

第2步左

第2步下

第2步右

代码实现

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
maze.in
------------------------------------------------------
6 5
0 1 0 0 0
0 0 0 1 0
0 1 0 1 0
1 1 1 0 0
0 1 0 0 1
0 1 0 0 0
------------------------------------------------------
maze.go
------------------------------------------------------
package main

import (
"fmt"
"os"
)

//读取迷宫文件
func readMaze(filename string) [][]int {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
//定义行列临时变量
var row, col int
//扫描第一行获取文件的行列数(\n读换行)
fmt.Fscanf(file, "%d %d\n", &row, &col)
//创建第一维slice
maze := make([][]int, row)
for i := range maze {
//创建第二维slice
maze[i] = make([]int, col)
//将数据放到二维slice中的每一个元素中
for j := range maze[i] {
a := &maze[i][j]
if j == len(maze[i])-1 {
//(\n读换行)
fmt.Fscanf(file, "%d\n", a)
} else {
fmt.Fscanf(file, "%d", a)
}
}
}
return maze
}

type point struct {
i, j int
}

//移动(上左下右)
var dirs = [4]point{
{-1, 0},
{0, -1},
{1, 0},
{0, 1},
}

//移动add
func (p point) add(r point) point {
return point{p.i + r.i, p.j + r.j}
}

//at校验当前位置
func (p point) at(grid [][]int) (int, bool) {
//往上走或者往下走越界的时候
if p.i < 0 || p.i >= len(grid) {
return 0, false
}
//往左走或者往右走越界的时候
if p.j < 0 || p.j >= len(grid[p.i]) {
return 0, false
}
return grid[p.i][p.j],true
}

//走迷宫
func walk(maze [][]int, start, end point) [][]int{
//要走的每一步
steps := make([][]int, len(maze))
for i := range steps {
steps[i] = make([]int, len(maze[i]))
}
//将起点放入队列当中
Q := []point{start}

for len(Q) > 0 {
//取出队列中当前的位置作为要探索的点
cur := Q[0]
Q = Q[1:]
//判断当前探索点是否是终点
if cur == end {
break
}
for _, dir := range dirs {
next := cur.add(dir)
//如果越界或者撞墙,继续探索
val, ok := next.at(maze)
if !ok || val == 1 {
continue
}
//如果当前有值说明已经有值了,继续探索
val, ok = next.at(steps)
if !ok || val != 0 {
continue
}
//如果当前点在起点继续探索
if next == start {
continue
}
//探索(需要做的事:1.探索到的下一步填上是第几步2.将探索到的下一步加入到队列)
curSteps, _ := cur.at(steps)
steps[next.i][next.j] = curSteps + 1
Q = append(Q, next)
}
}
return steps
}

func main() {
maze := readMaze("maze/maze.in")
fmt.Println("print maze")
fmt.Println("-----------------------")
//打印读取出来的数据
for _, row := range maze {
for _, val := range row {
fmt.Printf("%d ", val)
}
fmt.Println()
}
fmt.Println("-----------------------")
fmt.Println("print steps")
fmt.Println("-----------------------")
//走迷宫
steps := walk(maze, point{0, 0}, point{len(maze) - 1, len(maze[0]) - 1})
for _,row := range steps {
for _, val := range row {
fmt.Printf("%3d ",val)
}
fmt.Println()
}
fmt.Println("-----------------------")
}

打印结果

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

print maze
-----------------------
0 1 0 0 0
0 0 0 1 0
0 1 0 1 0
1 1 1 0 0
0 1 0 0 1
0 1 0 0 0
-----------------------
print steps
-----------------------
0 0 4 5 6
1 2 3 0 7
2 0 4 0 8
0 0 0 10 9
0 0 12 11 0
0 0 13 12 13
-----------------------