Golang基础知识(笔记迁移)

news2024/11/14 19:22:22

golang

变量作用域

  • 局部作用域:代码块、函数内的
  • 全局作用域:顶层作用域,代码块外的就是全局,如果变量名大写,则改变量整个程序都可以使用。

类型断言

golang的类型断言在变量后加上.(type),如果类型断言与实际类型不一致会返回第二个参数bool代表是否成功

func main(){
    var a inteface{}
    b := 1
    b = a
    c,ok := b.(boolean)
    if ok {
		... succcess
    }
    // 简写
    if c,ok := b.(boolean); ok {
		... succcess
    }
}

错误处理

默认情况发生错误panic程序会退出。golang中捕获错误采用如下方法,defer+recover

func tes(){
    defer func(){
        err := recover()
        if err != nil{
            // 
        }
    }()
    fmt.Println(a+b)
}

自定义错误

golang中自定义错误采用errors.Newpanic函数

func main(){
    
}

流程控制的细节

switch

  • 穿透:golang的switch默认不会穿透,不需要像js一样在case内加上break,如果需要穿透加上fallthrough
  • case后面可有多个值
func main(){
    a := 5
    switch a{
    	case 1,2,3,4:
        	println("ok1,2,3")
    	case 5:
        	println("ok5")
        default:
        	println("default")
    }
}

可以将switch当作if else分支

func main(){
    a := 5
    switch {
        case a == 5:
        	println("a = 5 true")
    }
}

Type Switch

switch可以用来判断某个变量的实际类型

var mapData map[string]interface{}
func main(){
    for k,v := range mapData{
        switch v.(type){
            case nil:
            //
        	case int:
            //
            case float64:
            //
            case bool,string:
            //
        	default:
            //
        }
    }
}

函数

函数传递两种方式

  • 值传递
  • 引用传递。

引用传递效率高,因为只拷贝地址。对于引用类型,指针、slice切片、map、管道、chan、interface都属于引用类型。 值类型 int、float、bool、string、数组和结构体

init函数

每一个源文件都可以包含一个init函数,该函数将会在main函数之前被调用

defer关键字

defer使用场景主要是函数执行完毕后释放函数执行期间创建的资源

当go执行到defer时,会将defer后的代码压入栈中,当函数执行完毕后会从栈中取出语句执行(语句中引用的值会做一个拷贝)。

func sum(a int,b int)int{
    // 将println(a)   println(b)语句入栈,此时a,b值10,20被拷贝,
    defer println(a)  //10
    defer println(b)  //20
    a++  //11
    b++  //21
    res := a + b
    return res
}
sum(10,20)

打印操作

golang打印需要导入fmt包,如果需要详细打印信息需要符号%#,使用%v打印任何值,%.2f可以打印小数并展示到小数后两位

s := "hello"
f := 3.1415926
fmt.Printf("s=%v",s)
fmt.Printf("%.2f",f)

new和make的区别

new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。举个例子:

func main() {
    a := new(int)
    b := new(bool)
    fmt.Printf("%T\n", a) // *int
    fmt.Printf("%T\n", b) // *bool
    fmt.Println(*a)       // 0
    fmt.Println(*b)       // false
}

var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。

总结
  • 二者都是用来做内存分配的。
  • make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
  • 而new用于类型的内存分配,并且内存对应的值为类型零值(初始值),返回的是指向类型的指针。

结构体

结构体相当于java、js的class

type userInfo struct{
    name string
    age int
    hobby []
}

定义结构体方法

func (u *userInfo) resetPassword(password string){
    u.password = password
}
func (u userInfo) checkPassword(password string) bool {
    return u.password == password
}

结构体的实例化

我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:

    var p2 = new(person)
    fmt.Printf("%T\n", p2)     //*main.person
    fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。

type person struct {
    name string
    city string
    age  int8
}

func main() {
    var p1 person
    p1.name = "pprof.cn"
    p1.city = "北京"
    p1.age = 18
    fmt.Printf("p1=%v\n", p1)  //p1={pprof.cn 北京 18}
    fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"pprof.cn", city:"北京", age:18}
}

使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。

p3 := &person{}
fmt.Printf("%T\n", p3)     //*main.person
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
p3.name = "博客"
p3.age = 30
p3.city = "成都"
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"博客", city:"成都", age:30}

结构体的初始化

type person struct {
    name string
    city string
    age  int8
}

func main() {
    var p4 person
    fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}

使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。

p5 := person{
    name: "pprof.cn",
    city: "北京",
    age:  18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"pprof.cn", city:"北京", age:18}

也可以对结构体指针进行键值对初始化,例如:

p6 := &person{
    name: "pprof.cn",
    city: "北京",
    age:  18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"pprof.cn", city:"北京", age:18}

当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。

p7 := &person{
    city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}

使用值的列表初始化

初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:

p8 := &person{
    "pprof.cn",
    "北京",
    18,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"pprof.cn", city:"北京", age:18}

使用这种格式初始化时,需要注意:

    1.必须初始化结构体的所有字段。
    2.初始值的填充顺序必须与字段在结构体中的声明顺序一致。
    3.该方式不能和键值初始化方式混用。

结构体标签(Tag)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

    `key1:"value1" key2:"value2"`

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

例如我们为Student结构体的每个字段定义json序列化时使用的Tag:

//Student 学生
type Student struct {
    ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的key
    Gender string //json序列化是默认使用字段名作为key
    name   string //私有不能被json包访问
}

func main() {
    s1 := Student{
        ID:     1,
        Gender: "女",
        name:   "pprof",
    }
    data, err := json.Marshal(s1)
    if err != nil {
        fmt.Println("json marshal failed!")
        return
    }
    fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"女"}
}

JSON操作

JSON的键需要大写,如果想小写,则需要加上tag,例如下面的json:age

type userInfo struct{
    Name string
    Age int `json:age`
    Hobby []string
}

序列化和反序列化对象

import (
	"encoding/json"
    "fmt"
)

a:= userinfo{Name:"Yu",Age:25,Hobby:[]string{"Golang","TypeScript"} }
buf,err := json.Marshal(a)// 序列化后编程buf数组(16进制编码) 可以通过string(buf)转为字符串
string(buf)
var b userInfo 
err = json.Unmarshal(buf,&b)
if err!= nil{
    panic(err)
}

日期操作

  • 两个时间相减 调用Sub方法
  • 格式化时间不同于其他语言传入YYMM,需要传入固定时间2006-01-02 15:04:05进行格式化
package main
import (
	"fmt"
    "time"
)
func main(){
    now := time.Now()// 获取现在时间
    t := time.Date(2023,3,27,1,25,36,0,time.UTC) // 2023-03-27 01:25:36 +0000 UTC
    t2 := time.Date(2023.3.27,2,30,36,0,time.UTC)
    fmt.Println(t.Year(),t.Month(),t.Day(),t.Hour(),t.Minute())//2023, March 27 1 25 
    dif := t2.Sub(t)// 时间相减
    t.Format("2006-01-02 15:04:05") // 格式化时间
    now.Unix() // 当前时间戳
}

切片

创建/初始化切片

func main() {
   //1.声明切片
   var s1 []int
   if s1 == nil {
      fmt.Println("是空")
   } else {
      fmt.Println("不是空")
   }
   // 2.:=
   s2 := []int{}
   // 3.make()
   var s3 []int = make([]int, 0)
   fmt.Println(s1, s2, s3)
   // 4.初始化赋值
   var s4 []int = make([]int, 0, 0)
   fmt.Println(s4)
   s5 := []int{1, 2, 3}
   fmt.Println(s5)
   // 5.从数组切片
   arr := [5]int{1, 2, 3, 4, 5}
   var s6 []int
   // 前包后不包
   s6 = arr[1:4]
   fmt.Println(s6)
}
// 通过make创建切片  创建了一个长度为2容量为5的切片
var slice []int = make([]int,2,5)

// 初始化切片
全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end] 
var slice1 []int = arr[:end]        
var slice2 []int = arr[start:]        
var slice3 []int = arr[:] 
var slice4 = arr[:len(arr)-1]      //去掉切片的最后一个元素
局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]        
slice7 := arr[start:]     
slice8 := arr[:]  
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
操作含义
s[n]切片s中索引位置为n的项
s[:]从切片s的索引0到len(s)-1
s[start:]从start到len(s)-1
s[:end]从0到end处
s[start:end]startend
s[start:end:max]startend cap=max-low len=hight-low
len(s)切片长度
cap(s)切片

切片内存布局

读写操作实际目标是底层数组,只需注意索引号的差别

在这里插入图片描述

关于cap

:::tip

超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。

:::

package main

import (
    "fmt"
)

func main() {

    data := [...]int{0, 1, 2, 3, 4, 10: 0}
    s := data[:2:3]

    s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。

    fmt.Println(s, data)         // 重新分配底层数组,与原数组无关。
    fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。
}
// [0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
// 0xc4200160f0 0xc420070060

Go并发编程

go并发编程中有三个核心

  • Goroutine 协程 调用函数的时候在函数前加上go关键字即可
  • Channel
  • Sync

1.1协程之间通信Channel

提倡通过**通信共享内存(通道)**而不是共享内存实现通信(存在多个协程同时操作一个数据的情况)

在这里插入图片描述

1.2通道-通信共享内存

通道分为两类

  • 有缓冲通道make(chan int,通道大小example:2)类似于快递格子,如果满了就没法放快递等待格子腾出来才行,典型的生产消费模型。
  • 无缓冲通道make(chan int),也被称为同步通道
func CalSquare(){
	src := make(chan int)
	dest := make(chan int,3)
	go func(){
		defer close(src)
		for i:=0;i<10;i++ {
			src <- i
		}
	}()
	go func(){
		defer close(dest)
		for i:=range src {
			dest <- i * i
		}
	}()
	for i:= range dest {
		println(i)
	}
}
//0 1 4 9 16 25 36 49 64 81

1.3通信-共享内存

var (
	x int64
	lock sync.Mutex
)
func addWithLock(){
	for i := 0; i < 2000; i++ {
		lock.Lock()// 临界区资源上锁
		x += 1
		lock.Unlock()//释放临界区资源
	}
}
func addWithoutLock(){
	for i := 0; i < 2000; i++ {
		x += 1
	}
}
func Add(){
	x = 0
	for i := 0; i <5; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second)
	println("addWithLock",x)
	x = 0
	for i := 0; i <5; i++ {
		go addWithoutLock()
	}
	time.Sleep(time.Second)
	println("addWithoutLock",x)
}
addWithLock 10000
addWithoutLock 6550
WaitGroup

之前防止主进程结束导致协程还没运行完就接触,我们采用time.Sleep,正确方式来了
在这里插入图片描述

func helloRoutine(i int) {
	println("hello" + fmt.Sprint(i))
}
func ManyGoWait(){
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++{
		go func(j int){
			defer wg.Done()
			helloRoutine(j)
		}(i)
	}
	wg.Wait()
}
func main(){
    ManyGoWait()
}

依赖管理

Go依赖管理的演进GOPATH->GO Vendor -》Go Module

GOPATH和GO Vendor 都是采用源码副本方式管理依赖没有版本规则

依赖管理三要素:

  • 配置文件,描述依赖 go.mod
  • 中心仓库管理依赖库 Proxy
  • 本地工具 go get/go mod

GOPATH

$GOPATH工作区

  1. bin 项目编译的二进制文件
  2. pkg 项目编译的中间产物,加速编译
  3. src 项目源码

这种管理方式,项目代码直接依赖src目录下的代码,然后通过go get下载最新包到src目录下

这种管理方式的缺点

本地两个项目A,B ;A和B依赖某一个PKG的不同版本,没法构建成功,无法实现多版本控制

Go Vendor

为了解决GOPATH的问题,该方式在项目目录下增加vendor文件,所有依赖包副本形式放在项目根目录下/vendor(如果没有找到就去GOPATH),每个项目都引入一份依赖的副本就解决了依赖同包不同版本的问题

Go Vendor问题

如果A项目依赖pkg B和pkg C,而B和C同时依赖了pkg D的不同版本,我们无法控制其版本选择问题
在这里插入图片描述

Go Module

通过go.mod文件管理依赖包版本,通过go get/go mod指令管理依赖包

go.mod的配置 类似于前端package.json

在这里插入图片描述

go mod工具

go mod init #初始化 创建go.mod文件,项目开始前的必备操作
go mod download #下载模块到本地
go mod tidy #增加需要的依赖,删除不需要的  每次提交代码前可以执行

依赖分发Proxy

由于依赖一般放在github等代码托管平台,就会存在一些问题

  • 作者可能增删改软件的版本,无法保证构建稳定性
  • 无法保证依赖可用性,作者可以删除
  • 代码托管平台负载问题

proxy会缓存原站的内容,版本也不会变。

当存在proxy项目依赖会先从proxy上查找,当proxy代理的地址没有时,就去direct源站

go项目初始化

go mod init 名字
go mod tidy

安装gorm

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

创建models文件夹,该文件夹用于gorm创建表

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

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

相关文章

【Android】系统启动流程分析 —— init 进程启动过程

本文基于 Android 14.0.0_r2 的系统启动流程分析。 一、概述 init 进程属于一个守护进程&#xff0c;准确的说&#xff0c;它是 Linux 系统中用户控制的第一个进程&#xff0c;它的进程号为 1&#xff0c;它的生命周期贯穿整个 Linux 内核运行的始终。Android 中所有其它的进程…

python5:基于多进程的并发编程、基于协程的并发编程的学习笔记

进程 为什么要使用多进程&#xff1f;——GIL的存在&#xff0c;多线程实际不是并发执行 将任务分为两类&#xff1a;IO密集型&#xff08;多线程&#xff09;CPU密集型&#xff08;多进程&#xff09; 多进程的基本用法 concurrent.futures.process.ProcessPoolExecutor#进…

移除和替换任何内容:AI 驱动的图像修复工具 | 开源日报 No.204

Sanster/IOPaint Stars: 15.1k License: Apache-2.0 IOPaint 是一款由 SOTA AI 模型驱动的图像修复工具。 该项目解决了从图片中移除任何不需要的对象、瑕疵或人物&#xff0c;以及擦除和替换图片上任何内容&#xff08;由稳定扩散技术支持&#xff09;的问题。 完全免费且开…

ETH Denver 2024 精彩回顾|波卡,远不止一个区块链

ETH Denver 2024 于 2 月 23 日至 3 月 3 日在美国丹佛举行&#xff0c;该活动由 SporkDAO 主办&#xff08;SporkDAO 是一个来自美国科罗拉多州的 Web3 公益开发者组织&#xff0c;其前身或者说起源就是 ETHDenver&#xff0c;后随着组织发展逐渐转变为 DAO 开展了研究、投资等…

YOLO-v8-seg实例分割使用

最近需要实例分割完成一些任务&#xff0c;一直用的SAM(segment anything&#xff09;速度慢&#xff0c;找一个轻量分割模型。 1. YOLO-v8-seg使用 git clone https://github.com/ultralytics/ultralytics.git cd ultralytics vim run.py from ultralytics import YOLO# L…

JS加密解密之字符编码知识

在前端开发中&#xff0c;字符编码是一个至关重要的概念&#xff0c;特别是在数据传输、加密和解密等方面。JavaScript作为一种常用的脚本语言&#xff0c;在处理字符编码时也有其独特之处。本文将详细介绍JavaScript中的字符编码知识&#xff0c;包括字符编码的分类和相关案例…

【C++】1600. 请假时间计算

问题&#xff1a;1600. 请假时间计算 类型&#xff1a;基本运算、整数运算 题目描述&#xff1a; 假设小明的妈妈向公司请了 n 天的假&#xff0c;那么请问小明的妈妈总共请了多少小时的假&#xff0c;多少分钟的假&#xff1f;&#xff08;提示&#xff1a; 1 天有 24 小时&…

2_27. 移除元素

2_27. 移除元素 难度: 简单 提示: 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变…

知识管理入门:轻松选择合适的知识管理软件

你是不是经常觉得自己的大脑像个杂乱的仓库&#xff0c;各种信息、知识和想法在里面乱窜&#xff0c;找不到头绪&#xff1f;别担心&#xff0c;知识管理软件来帮你解决这个问题啦&#xff01;今天&#xff0c;我们就来聊聊知识管理软件这个神奇的工具&#xff0c;新手也能轻松…

python基础——语句

一、条件语句 就是 if else 语句 &#xff01; 代表不等于 代表等于if 关键字&#xff0c;判断语句&#xff0c;有“如果”的意思&#xff0c;后面跟上判断语句else 常和“if” 连用&#xff0c;有“否则”的意思&#xff0c;后面直接跟上冒号 …

华为配置WLAN 802.1X认证实验

配置WLAN 802.1X认证示例 组网图形 图1 配置802.1X认证组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤 业务需求 用户接入WLAN网络&#xff0c;使用802.1X客户端进行认证&#xff0c;输入正确的用户名和密码后可以无线上网。且在覆盖区域内移动发生漫游时&…

鸿蒙一次开发,多端部署(三)应用UX设计原则

设计原则 当为多种不同的设备开发应用时&#xff0c;有如下设计原则&#xff1a; 差异性 充分了解所要支持的设备&#xff0c;包括屏幕尺寸、交互方式、使用场景、用户人群等&#xff0c;对设备的特性进行针对性的设计。 一致性 除了要考虑每个设备的特性外&#xff0c;还…

vue3 reactive丢失响应式

问题 使用 reactive 构造响应式对象时&#xff0c;当对其进行重新赋值后&#xff0c;会导致原有变量失去响应式&#xff0c;页面不会发生联动更新 例如&#xff1a; 1、使用 reactive 定义一个响应式的对象变量 let data1 reactive({name: 小李,date: 2024-03-18,address: xx…

基于Python的HTTP接口自动化测试框架实现

对服务后台一系列的http接口功能测试。 输入&#xff1a;根据接口描述构造不同的参数输入值 输出&#xff1a;XML文件 二、实现方法 1、选用Python脚本来驱动测试 2、采用Excel表格管理测试数据&#xff0c;包括用例的管理、测试数据录入、测试结果显示等等&#xff0c;这…

美团2023年财报:全年营收2767亿元,即时配送订单219亿笔

3月22日&#xff0c;美团(股票代码:3690.HK)发布2023年第四季度及全年业绩。公司各项业务继续取得稳健增长&#xff0c;全年营收2767亿元(人民币&#xff0c;下同)&#xff0c;同比增长26%&#xff0c;经营利润134亿元。 本年度&#xff0c;美团继续围绕“零售科技”战略&…

C# winform修改背景图 控件双向绑定 拖拽打开图片

修改背景图 说明 这里我准备基于百度飞桨PaddleSeg项目的人像分割模块做一个人像抠图&#xff0c;这里顺便用上了双向绑定和图片拖拽打开。 下面就是示例&#xff1a; 用颜色替换 用背景图替换 保存成功后的图片 一、使用百度飞桨PaddleSeg //初始化 引擎engine new Padd…

用易货模式做成小程序商城的可行性分析

在当前的商业环境中&#xff0c;易货模式已经逐渐被人们所熟知和接受。这种以物易物的交易方式&#xff0c;不仅能帮助企业降低库存压力&#xff0c;还能有效促进资源的优化配置。那么&#xff0c;如果我们用易货模式来打造一个小程序商城&#xff0c;这样的想法是否可行呢&…

【探索Linux】—— 强大的命令行工具 P.29(网络编程套接字 —— 简单的TCP网络程序模拟实现)

阅读导航 引言一、TCP协议二、TCP网络程序模拟实现1. 预备代码⭕ThreadPool.hpp&#xff08;线程池&#xff09;⭕makefile文件⭕打印日志文件⭕将当前进程转变为守护进程 2. TCP 服务器端实现&#xff08;TcpServer.hpp&#xff09;3. TCP 客户端实现&#xff08;main函数&…

Doris实战——天眼查Doris实时数仓构建

目录 前言 一、业务背景 二、原有架构及痛点 三、理想架构 四、技术选型 五、新数仓架构 六、应用场景优化 6.1 人群圈选 6.2 C端分析数据及精准营销线索场景 七、优化经验 八、规模和收益 九、未来规划 原文大佬的这篇实时数仓构建有借鉴意义的&#xff0c;这些摘…

JavaEE 初阶篇-深入了解进程与线程(常见的面试题:进程与线程的区别)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 进程概述 2.0 线程概述 2.1 多线程概述 3.0 常见的面试题&#xff1a;谈谈进程与线程的区别 4.0 Java 实现多线程的常见方法 4.1 实现多线程方法 - 继承 Thread 类…