Java转Go快速入门教程_java转golang需要多久-程序员宅基地

技术标签: java  golang  编程语言  go  项目经验  

1 背景和综述

  1. 作者作为校招新人入职,公司用Golang, 自己有Java、PHP的 Web 开经验,熟悉前端的Vue技术栈,在此之前没写过Golang项目,只是几个月前按照菜鸟教程看了一遍Golang的语法 ,了解其天生高并发的特性,知道其有协程、管道这两个重要的特性。

  2. 入职当天就要求自学做一个Golang的新手村任务,做一个分布式的在2000并发访问下能够做到1000QPS的登录注册,期限一周,要求细节不便透露,这里就不详细展开了,本文所分享的干货主要来自于这个新手村任务的积累。

  3. Golang 语法入门很简单,但是根据我的入门经验,最容易对初学者造成困扰的是代码的组织形式,稍有语法基础的读者建议直接看 2.2 代码组织部分。


2 Golang 的常用语法以及代码组织

2.1 语法

  1. 如果你曾经系统地看过Golang的语法,那么这里我向你推荐 y 分钟学会 x语言系列教程,以及其搬运的国内版本,通过这个可以快速地把语法复习一遍(语法没必要背,都是用的多了就记住了)
  2. 如果你不曾系统看过Golang语法,那么没关系,认真看完下面这个代码,就足够了
// 单行注释
/* 多行
    注释 */
// go的IDE我推荐Vscode,Goland也不错,前者我自用,后者同同事都在用
// 用 go 开发时注释中也会经常见到文档注解,如 @Version 1.0.0

// 导入包的子句在每个源文件的开头。
// Main比较特殊,它用来声明可执行文件,而不是一个库。
package main

// Import语句声明了当前文件引用的包。
import (
    "fmt"       // Go语言标准库中的包
    "net/http"  // 一个web服务器包
    "strconv"   // 字符串转换
    "strings"   // 字符串标准库
)
// 等你写习惯了go不带分好,写Java会很变扭

// 函数声明:Main是程序执行的入口。
// 不管你喜欢还是不喜欢,反正Go就用了花括号来包住函数体,花括号配合严格的缩进,代码非常易读。
func main() {
    
    // 往标准输出打印一行。
    // 用包名fmt限制打印函数。
    fmt.Println("Hello world!")
	// 写项目时用 log.Println("Hello world!") 会更多
	
    // 调用当前包的另一个函数。
    beyondHello()
}

// 函数可以在括号里加参数。
// 如果没有参数的话,也需要一个空括号。
func beyondHello() {
    
    var x int   // 变量声明,变量必须在使用之前声明。
    x = 3       // 变量赋值。
    // 可以用:=来偷懒,它自动把变量类型、声明和赋值都搞定了,这个在项目中最常用。
    y := 4
    sum, prod := learnMultiple(x, y)        // 返回多个变量的函数
    fmt.Println("sum:", sum, "prod:", prod) // 简单输出
    learnTypes()                            // 少于y分钟,学的更多!
}

// 多变量和多返回值的函数
func learnMultiple(x, y int) (sum, prod int) {
    
    return x + y, x * y // 返回两个值
    /*
	// 因为这里返回值不仅有类型,还写了名称,因此函数体其实还可以这么写
	sum = x + y // 注意这里不是 := 
	prod = x * y
	return
	*/
}

// 内置变量类型和关键词
func learnTypes() {
    
    // 短声明给你所想。
    s := "Learn Go!" // String类型

    s2 := `A "raw" string literal
can include line breaks.` // 同样是String类型

    // 非ascii字符。Go使用UTF-8编码。
    g := 'Σ' // rune类型,int32的别名,使用UTF-8编码

    f := 3.14195 // float64类型,IEEE-754 64位浮点数
    c := 3 + 4i  // complex128类型,内部使用两个float64表示

    // Var变量可以直接初始化。
    var u uint = 7  // unsigned 无符号变量,但是实现依赖int型变量的长度
    var pi float32 = 22. / 7

    // 字符转换
    n := byte('\n') // byte是uint8的别名

    // 数组类型编译的时候大小固定。
    var a4 [4] int              // 有4个int变量的数组,初始为0
    a3 := [...]int{
    3, 1, 5}     // 有3个int变量的数组,同时进行了初始化

    // Slice 可以动态的增删。Array和Slice各有千秋,但是使用slice的地方更多些。
    s3 := []int{
    4, 5, 9}        // 和a3相比,这里没有省略号
    s4 := make([]int, 4)        // 分配一个有4个int型变量的slice,全部被初始化为0

	// 常见操作
	str := "带加豪,偶稀渣渣辉"
	bs := []byte(str) // 字符串转化为byte切片
	str2 := string(bs) // byte切片转字符串
	println(str, len(str), bs, len(bs), str2, strings.Count(str, "")-1) // println函数更像 Java的 sysout
	fmt.Println(str, len(str), bs, len(bs), str2, strings.Count(str2, "渣")) // 这个效果更好
	
    var d2 [][]float64          // 声明而已,什么都没有分配
    bs := []byte("a slice")     // 类型转换的语法

    p, q := learnMemory()       // 声明p,q为int型变量的指针
    fmt.Println(*p, *q)         // * 取值

    // Map是动态可增长关联数组,和其他语言中的hash或者字典相似。
    m := map[string]int{
    "three": 3, "four": 4}
    m["one"] = 1

    // 在Go语言中未使用的变量在编译的时候会报错,而不是warning。
    // 下划线 _ 可以使你“使用”一个变量,但是丢弃它的值。
    _,_,_,_,_,_,_,_,_ = s2, g, f, u, pi, n, a3, s4, bs
    // 输出变量, 从结果可知上面这种操作并不会清除变量
    fmt.Println(s, c, a4, s3, d2, m)

    learnFlowControl() // 回到流程控制 
}

// Go全面支持垃圾回收。Go有指针,但是不支持指针运算。
// 你会因为空指针而犯错,但是不会因为增加指针而犯错。
func learnMemory() (p, q *int) {
    
    // 返回int型变量指针p和q
    p = new(int)    // 内置函数new分配内存
    // 自动将分配的int赋值0,p不再是空的了。
    s := make([]int, 20)    // 给20个int变量分配一块内存
    s[3] = 7                // 赋值
    r := -2                 // 声明另一个局部变量
    return &s[3], &r        // & 取地址
}

func expensiveComputation() int {
    
    return 1e6 // 1 * 10^6
}

func learnFlowControl() {
    
    // If需要花括号,括号就免了
    if true {
    
        fmt.Println("told ya")
    }
    // 用go fmt 命令可以帮你格式化代码,所以不用怕被人吐槽代码风格了,
    // 也不用容忍被人的代码风格。
    if false {
    
        // pout
    } else {
    
        // gloat
    }
    // 如果太多嵌套的if语句,推荐使用switch
    x := 1
    switch x {
    
    case 0:
    case 1:
        // 隐式调用break语句,匹配上一个即停止
    case 2:
        // 不会运行
    case 3:
    	// 如果非要匹配上一个不停止,也不是不可
    	fallthrough // 但是不建议使用
    default:
    	// 都没匹配上执行
    }
    
    // 和if一样,for也不用括号
    for x := 0; x < 3; x++ {
     // ++ 自增
        fmt.Println("iteration", x)
    }
    // x在这里还是1。为什么?

    // for 是go里唯一的循环关键字,不过它有很多变种
    for {
     // 死循环
        break    // 骗你的 
        continue // 不会运行的
    }
    // 和for一样,if中的:=先给y赋值,然后再和x作比较。
    if y := expensiveComputation(); y > x {
    
        x = y
    }
    
    // 闭包函数
    xBig := func() bool {
    
        return x > 100 // x是上面声明的变量引用
    }
    fmt.Println("xBig:", xBig()) // true (上面把y赋给x了) 
    x /= 1e5                     // x变成10
    fmt.Println("xBig:", xBig()) // 现在是false

    // 当你需要goto的时候,你会爱死它的!
    goto love
love:

    learnInterfaces() // 好东西来了!
}

// 定义Stringer为一个接口类型,有一个方法String
type Stringer interface {
    
    String() string // 这里可以有多个方法声明
}

// 定义pair为一个结构体,有x和y两个int型变量。
// 这个pair相当于Java中的一个类
type pair struct {
    
    x, y int
}

// 定义pair类型的成员方法,实现Stringer接口,这里不需要显示指定 Stringer
func (p pair) String() string {
     // p被叫做“接收器”
    // Sprintf是fmt包中的另一个公有函数。
    // 用 . 调用p中的元素。
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func learnInterfaces() {
    
    // 花括号用来定义结构体变量,:=在这里将一个结构体变量赋值给p。
    p := pair{
    3, 4}
    fmt.Println(p.String()) // 调用pair类型p的String方法 
    var i Stringer          // 声明i为Stringer接口类型 
    i = p                   // 有效!因为p实现了Stringer接口(类似java中的塑型) 
    // 调用i的String方法,输出和上面一样
    fmt.Println(i.String())

    // fmt包中的Println函数向对象要它们的string输出,实现了String方法就可以这样使用了。
    // (类似java中的序列化)
    fmt.Println(p) // 输出和上面一样,自动调用String函数。
    fmt.Println(i) // 输出和上面一样。

    learnErrorHandling()
}

// 你可以定义别的结构体作为接收器来实现 String() string 方法,实现多态,
// 在实例化时以Stringer作为其类型 (var obj Stringer), 赋值赋不同的结构体
// 可详细参考 https://www.jianshu.com/p/1227c5145cd8
// 我认为 Golang勉强可以面向对象,但是没必要对Java那套太挂念
func learnErrorHandling() {
    
	// 一开始不适应没 try catch,习惯了你会爱上她
    // ", ok"用来判断有没有正常工作 
    m := map[int]string{
    3: "three", 4: "four"}
    if x, ok := m[1]; !ok {
     // ok 为false,因为m中没有1
        fmt.Println("no one there")
    } else {
    
        fmt.Print(x) // 如果x在map中的话,x就是那个值喽。
    }
    // 错误可不只是ok,它还可以给出关于问题的更多细节。
    if _, err := strconv.Atoi("non-int"); err != nil {
     // _ discards value
        // 输出"strconv.ParseInt: parsing "non-int": invalid syntax"
        fmt.Println(err)
    }
    // 待会再说接口吧。同时,
    learnConcurrency()
}

// c是channel类型,一个并发安全的通信对象。
func inc(i int, c chan int) {
    
    c <- i + 1 // <-把右边的发送到左边的channel。
}

// 我们将用inc函数来并发地增加一些数字。
func learnConcurrency() {
    
    // 用make来声明一个slice,make会分配和初始化slice,map和channel。
    c := make(chan int)
    // 用go关键字开始三个并发的goroutine,如果机器支持的话,还可能是并行执行。
    // 三个都被发送到同一个channel。
    go inc(0, c) // go is a statement that starts a new goroutine.
    go inc(10, c)
    go inc(-805, c)
    // 从channel中独处结果并打印。
    // 打印出什么东西是不可预知的。
    fmt.Println(<-c, <-c, <-c) // channel在右边的时候,<-是读操作。

    cs := make(chan string)       // 操作string的channel
    cc := make(chan chan string)  // 操作channel的channel
    go func() {
     c <- 84 }()       // 开始一个goroutine来异步发送一个新的数字 
    go func() {
     cs <- "wordy" }() // 发送给cs
    
    // Select类似于switch,但是每个case包括一个channel操作。
    // 它随机选择一个准备好通讯的case。
    select {
    
    case i := <-c: // 从channel接收的值可以赋给其他变量
        fmt.Println("it's a", i)
    case <-cs: // 或者直接丢弃
        fmt.Println("it's a string")
    case <-cc: // 空的,还没作好通讯的准备 
        fmt.Println("didn't happen.")
    }
    // 上面c或者cs的值被取到,其中一个goroutine结束,另外一个一直阻塞。

    learnWebProgramming() // Go很适合web编程,我知道你也想学!
}
// 上述函数可是涉及到了 多线程,多线程通信,但是却简单明了

// http包中的一个简单的函数就可以开启web服务器。
func learnWebProgramming() {
    
    // ListenAndServe第一个参数指定了监听端口,第二个参数是一个接口,特定是http.Handler。
    err := http.ListenAndServe(":8080", pair{
    })

    fmt.Println(err) // 不要无视错误。
    // 如何优雅的关闭 http server: https://www.cnblogs.com/oxspirt/p/7058812.html
}

// 使pair实现http.Handler接口的ServeHTTP方法。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    
    // 使用http.ResponseWriter返回数据
    w.Write([]byte("You learned Go in Y minutes!"))
    defer fmt.Println("最后一个执行")
	// defer 语句常用于处理 io的关闭,在函数内代码执行完之后依次执行
	// 执行顺序遵循压栈出栈顺序
	defer func(){
    
		fmt.Println("倒数第二个执行")
	}()
}

至此你已经掌握了多数 Go语言教程中的重点内容,但是下面的Golang代码组织篇却更为重要,是笔者早期驯服Go语言时的珍贵经验。

2.2 代码组织

  1. 对于Java或者别的很多编程语言,都有 global、var、public、private之类的作用域限定关键词,但是Golang不是这样的,Go 语言中的 大写字母开头的方法是Public的小写字母开头的是private的,这就是为什么fmt.Println()的方法名是大写字母开头的原因

明白了这一点,作为 Javaer 的你再去看Go 的代码,是不是觉得眉目清秀起来了?

  1. 这一点非常重要,作者早期在用json.Marshal序列化一个struct时,序列化得到的字符串是没有成员变量的空对象{},直到我给成员变量名改为大写字母开头,问题才解决!
  2. Java 中的类和方法会有static 关键字修饰,在Golang中包变量和包函数就是static的,也就是说在多处import的同一个包,其中直接定义的变量是全局的
  3. 关于 packageimport, 跟Java中的有着较大区别,Java 中的package是一个完整的路径字符串,和目录结构是一致的,Go中路径是路径,包名是包名,import导入时导入的不是包名,而是包的路径,包名是在导入包的代码中使用时使用的,如fmt.Println()
  4. 这一点也非常重要,作者早期试图像导入Java的类一样导入Golang 的package下的一个文件,怎么都不能成功,反复试错,直到发现 Golang 的一个package下的若干文件的作用域是互通的,也就是一个目录下(不包含子目录)的若干文件是同一个包,引入这个包所在的目录路径即可。同一个包下的不同文件中小写字母开头的函数和变量作用域是互通的,就像在同一个文件中一般。
  5. 关于GOPATHgo modGOPATH 是 Go语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录,当我们的第三方包不是官方库时都要放在GOPATH/src下才可以使用。该方案导致第三方库和自己的项目都在GOPATH/src目录下,鱼龙混杂,如果GOPATH/src下面不同的包依赖的同一第三方库的不同版本,那么就会出现版本冲突,总之就是对多项目支持不友好。于是在go1.11版本之后出现了go mod,其于Java 的 Maven 比较类似,项目根目录的go.mod依赖配置文件类比于Maven项目根目录的pom.xml

解决了代码组织问题,你已经掌握了使用 使用Golang 生存的基本技能


3 Go 语言常用组件的使用

众所周知,go语言早期使用C和汇编实现,在1.5版本,其实现自举( 使用Go实现Go编译器 ),这意味着弄懂了Golang语法,然后要看懂Golang源码是不困难的,看看源码,总会有一些意料之外的收货。

3.1 rpc

  • Golang的源码中可以看到两个关于rpc的包,如下图,一个是 net/rpc,另一个是 net/rpc/jsonrpc
    在这里插入图片描述
  • 对应的包有测试代码,如 server.go 的测试代码为server_test.go, 可以看做非常高质量的官方Demo

3.2 ginweb

package main

import "github.com/gin-gonic/gin"

func main() {
    
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
    
		c.JSON(200, gin.H{
    
			"message": "pong",
		})
	})
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
  • 这些年开发流行前后端分离,前端纯静态文件服务:
func main() {
    
	router := gin.Default()
	router.Static("/assets", "./assets")
	router.StaticFS("/more_static", http.Dir("my_file_system"))
	router.StaticFile("/favicon.ico", "./resources/favicon.ico")

	// 监听并在 0.0.0.0:8080 上启动服务
	router.Run(":8080")
}

3.3 redis

  • go-redis 还可以
  • redis 单点失效在我的开发经验中的确发生过,因此redis高可用是必要的,面试中也是常问的,关于 redis 集群方案,推荐阅读此文章,文章最后总结 官方Redis-Cluster 是更靠谱的选择,但是我觉得 Codis 对开发者更为友好,解决了一些关键痛点,更为推荐。

3.4 mysql


4 Go 语言压测工具 wrk的基本使用

  • Golang 性能较高,单点使用 Jmeter 不一定能压到性能上限,因此我使用 wrk
  • wrk 的 lua 脚本的执行过程和生命周期比较坑多, 在这里进行一些总结
    1. 生命周期:
    
    首先总体上分为三个阶段 :setup, ruuning, done
    然后每个阶段可细分:
    setup(thread) 每个线程执行一次
    runing:
        init(args) 每个线程执行一次
            delay() 每个请求执行一次
            request() 每个请求执行一次
            response(status, headers, body) 每个请求执行一次
    done(summary, latency, requests)

    这里值得注意的是,周期函数外的语句会被执行 thread + 1 次,因此想要初始化一次的东西,应该使用计数器,判断为第一次时执行

    2. 变量作用域
        a. lua 脚本内, 生命周期函数外变量
        b. setup 阶段函数内
        c. runing 阶段的函数内
        d. done 阶段的函数内
        e. 其他自定义函数内
    这里面 
        - abd作用域互通
        - c的所有函数中作用域互通,但是每个线程单独持有
        - d可以通过 thread 获取到每个线程的局部变量
        
    issue: 如何在每个thread中获取全局变量,如测试数据集?
    - 方法一: 在生命周期函数setup中给传入的thread传入数据,缺点,不适合大量数据
    - 方法二: 引用传递(其可行性还需调查wrk内存调用模型以及lua如何跨进程引用传递)从报错情况来看,猜测init阶段使用堆内存,可以加载较多数据
  • 一个复杂的 example:
samples = {
    } -- lua 中函数传递table时引用传递
local counter = 1 -- 全局的变量
local threads = {
    }


function setup(thread)
    -- 执行 t 次
    if counter == 1 then 
        loadSamples()
    end
    thread:set("id", counter)
    math.randomseed(os.time())
    -- 线程初始化时就完成用例设定,因此是固定用户
    thread:set("sample", samples[math.random(0, #samples)]) 
    counter = counter + 1
end

function init(args)
    faild = 0
    responses = 0
end

-- 加载数据
function loadSamples()
    local j = 0
    for line in io.lines("../pressure/loginbody4lua.txt")
    do
        samples[j] = line
    end
    -- print("总共加载了"..j.."个用例")
end

-- 单元测试,验证正确性,正式压测时不注释的话脚本性能上不来
-- function response(status, headers, body)
--     -- 属于线程,每个请求完成执行一次, 统计失败
--     if status == 200 then
--         if(not string.find(body,'"code":0')) then
--             faild = faild + 1
--         end
--         wrk.thread:stop()
--     else 
--         faild = faild + 1
--     end
--     responses = responses + 1
-- end

wrk.method = "POST"
wrk.host = "127.0.0.1"
wrk.port = 8000
wrk.path = "/api/v1/login"
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Connection"] = "keep-alive"

function request(args)
    wrk.body = sample
    return wrk.format()
end


-- 200并发请求 
-- redis-cli flushall
-- redis-cli flushall & wrk -t200 -c200 -d60s -s login_fixuser.lua http://127.0.0.1:8000   --latency
-- wrk -t200 -c200 -d120s -s login_fixuser.lua http://127.0.0.1:8000   --latency

-- 2000并发请求 
-- redis-cli flushall
-- wrk -t2000 -c2000 -d30s -s login_fixuser.lua http://127.0.0.1:8000  --latency
-- wrk -t2000 -c2000 -d60s -s login_fixuser.lua http://127.0.0.1:8000  --latency
-- wrk -t2000 -c2000 -d120s -s login_fixuser.lua http://127.0.0.1:8000  --latency


5 Go 技术栈下的单元测试

  • 进行单元测试时,核心工作是把外部依赖mock掉,go技术栈较好地方案是:go-monkey
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_34820292/article/details/113782446

智能推荐

西门子HMI SMART 700 IE 设备概况以及WinCC flexible 2008常用配置小记-程序员宅基地

文章浏览阅读1.2w次,点赞3次,收藏32次。——参考自:SIMATIC HMI设备 Smart 700 IE、Smart 1000 IE 操作说明HMI 设备一旦探测到操作员控件被触摸就会立即返回一个反馈。该反馈是独立的,与 PLC 的通讯无关。 因此,其并不指示相关动作是否已真正执行。设计Smart Panel 700 IE是Smart Panel 700的升级版。连接HMI与PC连接组态PC与SMART PANEL有两种连接方式,一种是通过 RS485/422 接口连接,另一种是通过以太网接口连接,后者接线图如下图所示:HM_wincc flexible 2008

设备驱动模型(kobject、kset、ktype)_kobject kset ktype-程序员宅基地

文章浏览阅读570次。转自https://blog.csdn.net/guet_kite/article/details/78368928设备驱动模型概述Linux早期时候,一个驱动对应一个设备,也就对应一个硬件地址,那当有两个一样的设备的时候,就要写两个驱动,显然是不合理的。应该是从Linux2.5开始,就引入了device-bus-driver模型。其中设备驱动模型主要结构分为kset、kobject、kty..._kobject kset ktype

关于换行以及换行属性_nowarp也会换行-程序员宅基地

文章浏览阅读1.9k次。对于CSS的white-space属性,我想大部分人应该和我差不多,最常用的就是nowrap属性,最多用来做超长省略号显示的时候会用到【hiahiahia~】起因是这样的:产品doggie策划了一个元旦活动,活动主页最下边需要显示配置的活动规则,注意,是配置的活动规则,所以,免不了运营小妹要在后台配置一个活动规则,肯定不指望她们能配置html文本的呀,对吧,于是她们配置的是这样的: 1、封垫苏菲房间都是克拉夫; 2、对方萨芬的刷分放大; 3、粉打发打发打发这样色的,于..._nowarp也会换行

BRVAH万能适配器_andriod brvah适配器万能官网-程序员宅基地

文章浏览阅读353次。BRVAH一.简介二.使用一.简介BRVAH是一个强大的RecyclerAdapter框架(什么是RecyclerView?),它能节约开发者大量的开发时间,集成了大部分列表常用需求解决方案。二.使用在使用时,首先要项目的build.gradle导入allprojects { repositories { google() jcenter() ..._andriod brvah适配器万能官网

计算机文化基础作品ppt,计算机文化基础PPT课件-程序员宅基地

文章浏览阅读91次。计算机文化基础PPT课件2019-03-15计算机文化基础PPT课件 第1章http://wenku.baidu.com/view/e7ef8b6925c52cc58bd6be97.html计算机文化基础PPT课件 第2章 Windows 2000操作系统http://wenku.baidu.com/view/e85671f5f61fb7360b4c6594.html计算机文化基础PPT课件 ..._计算机文化基础ppt

获得迭代器最后一个元素_处理迭代器最后一个元素-程序员宅基地

文章浏览阅读3k次。来源python123获得迭代器最后一个元素问题尝试使用 * 迭代器展开运算,返回 range(0, 1000, 4) 的最后一个元素。print([x for x in range(0,1000,4)][-1])输出:996..._处理迭代器最后一个元素

随便推点

第七章 PX4-Pixhawk-Mavlink解析_px4 mavlink 波特率-程序员宅基地

文章浏览阅读5.2k次,点赞3次,收藏25次。第七章 PX4-Mavlink解析首先我们是还是来说一说mavlink吧。Mavlink协议是无人机的一种开源通信协议。可以理解就是按照一定的格式来发送数据。这一章节涉及到了消息的打包发送和接收解析。 首先我们还是找到入口函数然后回到脚本启动中找到mavlink的启动,这个找到应该不难吧,前面几章都有这个。这里有一个需要提一下,很多_px4 mavlink 波特率

Python 中RSA的用法 使用pyOpenssl 生成RSA密钥对, 使用rsa 加解密_import base64 import rsa from openssl.crypto impor-程序员宅基地

文章浏览阅读1.6k次,点赞4次,收藏8次。1. pyOpenSSL 生成RSA密钥对, 效率比较高2. rsa 加解密方法简单3. 代码import rsaimport base64from OpenSSL.crypto import PKeyfrom OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, FILETYPE_ASN1from OpenSSL.crypto import dum..._import base64 import rsa from openssl.crypto import pkey from openssl.crypto

Android 架构设计(四):组件化?_android 组件化 去除相关组件-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏3次。同系列传送门Android 架构设计(一):设计模式分析_赵星海的博客-程序员宅基地Android 架构设计(二):分包和文件结构_赵星海的博客-程序员宅基地_android 分包结构Android 架构设计(三):技术选型_赵星海的博客-程序员宅基地关于组件化,我这边分三步与大家分享:1定义,2需求,3优劣,4改造步骤(含框架推荐);1、组件化的定义:各个业务模块可单独运行,模块相互联系只可以使用唯一的入口。如图:2、当前项目是否需要采用组件化?首先看项目大小,.._android 组件化 去除相关组件

通过设置偏移 添加RecyclerView分隔线_rv_list.additemdecoration(new recyclerviewdivider距-程序员宅基地

文章浏览阅读701次。添加RecyclerView分隔线_rv_list.additemdecoration(new recyclerviewdivider距离左侧

深入理解计算机系统--计算机系统漫游_深入理解计算机系统 jeancheng-程序员宅基地

文章浏览阅读282次。第一章 计算机系统漫游 计算机系统是由硬件和系统软件组成的。所有计算机系统都是由相似的硬件和软件组成,它们又执行着相似的功能。 以hello程序为例。 1.1信息就是位+上下文 hello程序的生命是从源程序(源文件)开始的。源程序是程序员编写的,hello.c。源程序是 0和1 的比特位,8个一组。ASCII标准来表示文本字符。 这样的文件称为文本文件,所有其他_深入理解计算机系统 jeancheng

python读取json字符串_json数据处理:读取文件中的json字符串,转为python字典-程序员宅基地

文章浏览阅读1.4k次。方法1:读取文件中的json字符串,再用json.loads转为python字典import jsonstr_file = ‘./960x540/config.json‘with open(str_file, ‘r‘) as f:print("Load str file from {}".format(str_file))str1 = f.read()r = json.loads(str1)pri..._python 提取json元素 获取两个字段的值组成字典