-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 210 KB
/
content.json
1
[{"title":"【撸码系列】10x程序员高效工作方法之思考框架","date":"2020-06-01T16:00:00.000Z","path":"2020/06/02/2020/06/10x_work_method_0/","text":"缘起 毕业参加工作3年多了,一直没有去其他公司看看。回想这几年的工作经历,居然没啥说得出口的。没有什 么太多的成长。有时候忙得天昏地暗的,但是效果不佳。公司给了个账号,可以选10门课,无意选了一门《10x程序员工作方法》,这两天阅读GK上面看了一下《开篇词:程序员解决的问题,大多不是程序问题》和 《10x 程序员是如何思考的?》。 忙碌的原因到底是什么 在开篇词中,作者给出的观点:大部分程序员忙碌解决的问题,都不是程序问题,而是偶然复杂度导致的问题。 什么叫偶然复杂度呢,就是做事情选错工具或者方法引发不必要的问题。比如说,你需要开发一个 网页,你选择汇编语言去玩,那就是自己挖坑给自己跳:忙得死去活来。 怎么避免偶然复杂度 作者给出一个思考框架(2W1H) + 4个原则: 2W1H 现状:Where are we?(我们现在在哪?) 目标:Where are we going?(我们要到哪儿去?) 实现路径:How can we get there?(我们如何达到那里?) 4个原则 以终为始、任务分解、沟通反馈和自动化。 2W1H与4个原则的关系","comments":true,"tags":[{"name":"思考框架","slug":"思考框架","permalink":"https://cloudfeng.github.io/tags/%E6%80%9D%E8%80%83%E6%A1%86%E6%9E%B6/"}]},{"title":"【Golang】Gin 优雅停机","date":"2020-03-16T16:00:00.000Z","path":"2020/03/17/2020/03/20200317_gogin_graceful_shutdown/","text":"我们在web开发中,你的应用可能会在不同国家使用,所以需要将相关的错误提示根据本地语言进行相应的提示,也即是所谓的国际化。本文简单的介绍一下,在Gin中如何实现多语言的验证。我们借助validator.v10以及相应的语言包。 集成关键点 导入需要转换语言包 公共包: "github.com/go-playground/validator/v10" 需要转换的包,比如英文和中文 12345 en2 \"github.com/go-playground/locales/en\"zh2 \"github.com/go-playground/locales/zh\"ut \"github.com/go-playground/universal-translator\"en_translations \"github.com/go-playground/validator/v10/translations/en\"zh_translations \"github.com/go-playground/validator/v10/translations/zh\" 结构体定义不在是 binding,而是validate 12345type Person struct { Age int `form:\"age\" validate:\"required,gt=10\"` Name string `form:\"name\" validate:\"required\"` Address string `form:\"address\" validate:\"required\"`} 构建语言转换器以及注册 12345678910tans, _ := uni.GetTranslator(local)tans, _ := uni.GetTranslator(local) switch local { case \"en\": en_translations.RegisterDefaultTranslations(validate, tans) case \"zh\": zh_translations.RegisterDefaultTranslations(validate, tans) default: en_translations.RegisterDefaultTranslations(validate, tans) } 校验以及获取错误信息 校验,调用err := validate.Struct(xx);如果对返回的错误信息进行处理,validator返回的绑定错误信息格式:key = namespace & value = translated error;我们可以一次性获取所有的转换信息或者每次一次,具体如下: 12345678910err := validate.Struct(person)if err != nil { errs := err.(validator.ValidationErrors) // 一次性转换所有与的错误信息方式 fmt.Println(errs.Translate(trans)) // 每次转换一次 for _, e := range errs { fmt.Println(e.Translate(trans)) }} 具体示例 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879package mainimport ( \"github.com/gin-gonic/gin\" en2 \"github.com/go-playground/locales/en\" zh2 \"github.com/go-playground/locales/zh\" ut \"github.com/go-playground/universal-translator\" \"github.com/go-playground/validator/v10\" en_translations \"github.com/go-playground/validator/v10/translations/en\" zh_translations \"github.com/go-playground/validator/v10/translations/zh\" \"log\" \"net/http\")type Person struct { Age int `form:\"age\" validate:\"required,gt=10\"` Name string `form:\"name\" validate:\"required\"` Address string `form:\"address\" validate:\"required\"`}// use a single instance , it caches struct infovar ( uni *ut.UniversalTranslator validate *validator.Validate)func multiLangBindingHandler(c *gin.Context) { local := c.DefaultQuery(\"local\", \"en\") log.Println(\"local:\", local) tans, _ := uni.GetTranslator(local) switch local { case \"en\": en_translations.RegisterDefaultTranslations(validate, tans) case \"zh\": zh_translations.RegisterDefaultTranslations(validate, tans) default: en_translations.RegisterDefaultTranslations(validate, tans) } var person Person if err := c.ShouldBind(&person); err != nil { c.JSON(http.StatusBadRequest, gin.H{ \"message\": err.Error(), }) return } if err := validate.Struct(person); err != nil { errs := err.(validator.ValidationErrors) sliceErrs := []string{} for _, e := range errs { // can translate each error one at a time. sliceErrs = append(sliceErrs, e.Translate(tans)) } c.JSON(http.StatusBadRequest, gin.H{ // translate all error at once // returns a map with key = namespace & value = translated error // NOTICE: 2 errors are returned and you'll see something surprising // translations are i18n aware!!!! // eg. '10 characters' vs '1 character' \"message\": errs.Translate(tans), \"sliceErrs\": sliceErrs, }) return } c.JSON(http.StatusOK, gin.H{ \"personInfo\": person, })}func main() { zh := zh2.New() en := en2.New() uni = ut.New(en, zh) validate = validator.New() router := gin.Default() router.GET(\"/testMultiLangBinding\", multiLangBindingHandler) router.Run(\":9999\")} 测试 测试案例 测试结果 $ curl -X GET "localhost:9999/testMultiLangBinding" {"sliceErrs":["Age is a required field","Name is a required field","Address is a required field"]} $ curl -X GET "localhost:9999/testMultiLangBinding?local=zh&age=12&name=yunfeng&address=wuhan" {"personInfo":{"Age":12,"Name":"yunfeng","Address":"wuhan"}} $ curl -X GET "localhost:9999/testMultiLangBinding?local=zh&age=12&name=yunfeng&address=wuhan" {"personInfo":{"Age":12,"Name":"yunfeng","Address":"wuhan"}} $ curl -X GET "localhost:9999/testMultiLangBinding?local=zh" {"message":{"Person.Address":"Address为必填字段","Person.Age":"Age为必填字段","Person.Name":"Name为必填字段"},"sliceErrs":["Age为必填字段","Name为必填字段","Address为必填字段"]} 参考资料 validator.v10 Translations & Custom Errors","comments":true,"tags":[{"name":"go-gin","slug":"go-gin","permalink":"https://cloudfeng.github.io/tags/go-gin/"}]},{"title":"【Golang】Gin 框架之请求参数多语言验证","date":"2020-03-15T16:00:00.000Z","path":"2020/03/16/2020/03/20200316_gogin_mutil_lan/","text":"我们在web开发中,你的应用可能会在不同国家使用,所以需要将相关的错误提示根据本地语言进行相应的提示,也即是所谓的国际化。本文简单的介绍一下,在Gin中如何实现多语言的验证。我们借助validator.v10以及相应的语言包。 集成关键点 导入需要转换语言包 公共包: "github.com/go-playground/validator/v10" 需要转换的包,比如英文和中文 12345 en2 \"github.com/go-playground/locales/en\"zh2 \"github.com/go-playground/locales/zh\"ut \"github.com/go-playground/universal-translator\"en_translations \"github.com/go-playground/validator/v10/translations/en\"zh_translations \"github.com/go-playground/validator/v10/translations/zh\" 结构体定义不在是 binding,而是validate 12345type Person struct { Age int `form:\"age\" validate:\"required,gt=10\"` Name string `form:\"name\" validate:\"required\"` Address string `form:\"address\" validate:\"required\"`} 构建语言转换器以及注册 12345678910tans, _ := uni.GetTranslator(local)tans, _ := uni.GetTranslator(local) switch local { case \"en\": en_translations.RegisterDefaultTranslations(validate, tans) case \"zh\": zh_translations.RegisterDefaultTranslations(validate, tans) default: en_translations.RegisterDefaultTranslations(validate, tans) } 校验以及获取错误信息 校验,调用err := validate.Struct(xx);如果对返回的错误信息进行处理,validator返回的绑定错误信息格式:key = namespace & value = translated error;我们可以一次性获取所有的转换信息或者每次一次,具体如下: 12345678910err := validate.Struct(person)if err != nil { errs := err.(validator.ValidationErrors) // 一次性转换所有与的错误信息方式 fmt.Println(errs.Translate(trans)) // 每次转换一次 for _, e := range errs { fmt.Println(e.Translate(trans)) }} 具体示例 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879package mainimport ( \"github.com/gin-gonic/gin\" en2 \"github.com/go-playground/locales/en\" zh2 \"github.com/go-playground/locales/zh\" ut \"github.com/go-playground/universal-translator\" \"github.com/go-playground/validator/v10\" en_translations \"github.com/go-playground/validator/v10/translations/en\" zh_translations \"github.com/go-playground/validator/v10/translations/zh\" \"log\" \"net/http\")type Person struct { Age int `form:\"age\" validate:\"required,gt=10\"` Name string `form:\"name\" validate:\"required\"` Address string `form:\"address\" validate:\"required\"`}// use a single instance , it caches struct infovar ( uni *ut.UniversalTranslator validate *validator.Validate)func multiLangBindingHandler(c *gin.Context) { local := c.DefaultQuery(\"local\", \"en\") log.Println(\"local:\", local) tans, _ := uni.GetTranslator(local) switch local { case \"en\": en_translations.RegisterDefaultTranslations(validate, tans) case \"zh\": zh_translations.RegisterDefaultTranslations(validate, tans) default: en_translations.RegisterDefaultTranslations(validate, tans) } var person Person if err := c.ShouldBind(&person); err != nil { c.JSON(http.StatusBadRequest, gin.H{ \"message\": err.Error(), }) return } if err := validate.Struct(person); err != nil { errs := err.(validator.ValidationErrors) sliceErrs := []string{} for _, e := range errs { // can translate each error one at a time. sliceErrs = append(sliceErrs, e.Translate(tans)) } c.JSON(http.StatusBadRequest, gin.H{ // translate all error at once // returns a map with key = namespace & value = translated error // NOTICE: 2 errors are returned and you'll see something surprising // translations are i18n aware!!!! // eg. '10 characters' vs '1 character' \"message\": errs.Translate(tans), \"sliceErrs\": sliceErrs, }) return } c.JSON(http.StatusOK, gin.H{ \"personInfo\": person, })}func main() { zh := zh2.New() en := en2.New() uni = ut.New(en, zh) validate = validator.New() router := gin.Default() router.GET(\"/testMultiLangBinding\", multiLangBindingHandler) router.Run(\":9999\")} 测试 测试案例 测试结果 $ curl -X GET "localhost:9999/testMultiLangBinding" {"sliceErrs":["Age is a required field","Name is a required field","Address is a required field"]} $ curl -X GET "localhost:9999/testMultiLangBinding?local=zh&age=12&name=yunfeng&address=wuhan" {"personInfo":{"Age":12,"Name":"yunfeng","Address":"wuhan"}} $ curl -X GET "localhost:9999/testMultiLangBinding?local=zh&age=12&name=yunfeng&address=wuhan" {"personInfo":{"Age":12,"Name":"yunfeng","Address":"wuhan"}} $ curl -X GET "localhost:9999/testMultiLangBinding?local=zh" {"message":{"Person.Address":"Address为必填字段","Person.Age":"Age为必填字段","Person.Name":"Name为必填字段"},"sliceErrs":["Age为必填字段","Name为必填字段","Address为必填字段"]} 参考资料 validator.v10 Translations & Custom Errors","comments":true,"tags":[{"name":"go-gin","slug":"go-gin","permalink":"https://cloudfeng.github.io/tags/go-gin/"}]},{"title":"【Golang】Gin 框架之请求参数绑定","date":"2020-03-12T16:00:00.000Z","path":"2020/03/13/2020/03/20200311_go_gin_request_bind_type/","text":"最近在用Gin来做一个side project,用于练手以及学习前端。看了Gin的文档,此文只是将相关的文档作为一个归类,留存起来。首先我们看看,Gin中模型绑定和校验,是其他绑定类型请求的基础;后面再分别介绍Gin中相关的绑定类型(见下面的表)。 类型 重要程度 绑定Url 重要 请求参数与自定义结构绑定 重要 请求参数是前端上送的CheckBox 重要 仅仅绑定查询 一般,特殊化 绑定Header 一般 绑定查询类型或者POST数据 重要 模型绑定和校 模型绑定的作用是将请求体绑定到自定义类型,目前Gin支持:JSON、XML、YAML和标准form请求参数(比如:foo=bar&boo=baz)。Gin使用go-payground/validator/validtor/v10,做参数的校验。若参数是必输,可以说明的使用 binding:"required"修饰之。当在绑定的时候发现是空值就会返回错误。所以语法格式: `绑定标签类型:“fieldname” binding:“required”` Gin提供两种类型的方法来实现绑定功能,并且在调用绑定方法的时候,会根据请求中头部Content-Type内容来调用相关的方法。如果你确认绑定的参数类型,可以直接使用MustBindWith 或 ShouldBindWith,否则请使用ShouldBind作为万能钥匙。下面具体看一下此两种类型: 类型 功能 方法 注意点 Must bind 调用钩子函数:MustindWith;绑定出现错误程序中断:c.AbortWithError(400, err).SetType(ErrorTypeBind),效果就是返回400、Content-Type:text.plain;charset=utf-8 Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader 不够灵活 Should bind 调用钩子函数:ShouldBindWith ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader 灵活,绑定错误需要客户自行处理 示例代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879package mainimport ( \"github.com/gin-gonic/gin\" \"net/http\")// Binding from JSON、form、xmltype Login struct { User string `form:\"user\" json:\"user\" xml:\"user\" binding:\"required\"` Password string `form:\"password\" json:\"password\" xml:\"password\" binding:\"required\"`}func loginFormHandler(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. if err := c.ShouldBind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()}) return } if form.User != \"manu\" || form.Password != \"123\" { c.JSON(http.StatusUnauthorized, gin.H{\"status\": \"unauthorized\"}) return } c.JSON(http.StatusOK, gin.H{\"status\": \"you are logged in\"})}// Example for binding JSON ({\"user\": \"manu\", \"password\": \"123\"})func loginJSONHandler(c *gin.Context) { var json Login if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{ \"error\":err.Error(), }) return } if json.User != \"manu\" || json.Password != \"123\" { c.JSON(http.StatusUnauthorized, gin.H{ \"status\":\"unauthorized\", }) return } c.JSON(http.StatusOK, gin.H { \"status\":\"you are logged in\", })}// Example for binding XML (// <?xml version=\"1.0\" encoding=\"UTF-8\"?>// <root>// <user>user</user>// <password>123</password>// </root>)func loginXMLHandler(c *gin.Context) { var xml Login if err := c.ShouldBindXML(&xml); err != nil { c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()}) return } if xml.User != \"manu\" || xml.Password != \"123\" { c.JSON(http.StatusUnauthorized, gin.H{\"status\": \"unauthorized\"}) return } c.JSON(http.StatusOK, gin.H{\"status\": \"you are logged in\"})}func main() { router := gin.Default() router.POST(\"/loginJSON\", loginJSONHandler) // Example for binding a HTML form (user=manu&password=123) router.POST(\"/loginForm\", loginFormHandler) router.POST(\"/loginXML\", loginXMLHandler) router.Run()} 测试 类型 测试命令 JSON curl -X POST 'http://localhost:8080/loginJSON' -v -d '{"user":"manu", "password":"123"}' XML curl -X POST "http://localhost:8080/loginXML" -v -d '<?xml version="1.0" encoding="UTF-8"?><root><user>manu</user><password>123</password></root>' form curl -X POST "http://localhost:8080/loginForm" -v -d 'user=manu&password=123' 绑定Url 示例代码 此类型主要用在RESTful类型的接口,具体的示例代码如下: 1234567891011121314package main// 绑定Uriimport \"github.com/gin-gonic/gin\"func main() { route := gin.Default() route.GET(\"/:name/:id\", func (c *gin.Context) { c.JSON(200, gin.H{ \"name\": c.Param(\"name\"), \"uuid\": c.Param(\"id\"), }) }) route.Run()} 测试 12curl -X GET 'http://localhost:8080/yunfeng/12345'{\"name\":\"yunfeng\",\"uuid\":\"12345\"} 请求参数是前端上送的CheckBox 示例代码 我们这里需要用到Gin一个静态资源绑定功能或者加载HTML功能。 1234567891011121314151617181920212223242526272829303132333435363738394041package mainimport ( \"github.com/gin-gonic/gin\" \"net/http\")type myForm struct { Colors []string `form:\"colors[]\"`}func formHandler(c *gin.Context) { var fakeForm myForm // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`) err := c.ShouldBind(&fakeForm) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ \"error\":err, }) } c.JSON(http.StatusOK, gin.H{ \"color\": fakeForm.Colors, })}func indexHandler(c *gin.Context) { // render form.html c.HTML(http.StatusOK, \"form.html\", nil)}func main() { router := gin.Default() // load html router.LoadHTMLGlob(\"static/*\") router.Static(\"/static\", \"./static\") router.GET(\"/\", indexHandler) router.POST(\"/testBindHtmlCheckboxes\", formHandler) router.Run()} 测试 在命令行中 go build -o bindHtml & ./bindHtml 在浏览器中输入 http://localhost:8080/static/form.html 或者 http://localhost:8080 提交之后的结果: 浏览器上面的变为地址 http://localhost:8080/testBindHtmlCheckboxes 展示的信息 {"color":["red","green"]} 请求参数与自定义结构绑定 示例代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364package mainimport \"github.com/gin-gonic/gin\"// StructA 普通结构体type StructA struct { FieldA string `form:\"field_a\"`}// StructB 嵌套型结构体type StructB struct { NestedStruct StructA FieldB string `form:\"field_b\"`}// 嵌套结构指针type StructC struct { NestedStructPointer *StructA FieldC string `form:\"field_c\"`}// 嵌套匿名类型type StructD struct { NestedAnonyStruct struct { FieldX string `form:\"field_x\"` } FieldD string `form:\"field_d\"`}func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) c.JSON(200, gin.H { \"a\":b.NestedStruct, \"b\":b.FieldB, })}func GetDataC(c *gin.Context) { var b StructC c.Bind(fib) c.JSON(200, gin.H{ \"a\":b.NestedStructPointer, \"c\":b.FieldC, })}func GetDataD(c *gin.Context) { var b StructD c.Bind(&b) c.JSON(200, gin.H{ \"x\":b.NestedAnonyStruct, \"d\":b.FieldD, })}func main() { r := gin.Default() r.GET(\"/getb\", GetDataB) r.GET(\"/getc\", GetDataC) r.GET(\"/getd\", GetDataD) r.Run()} 12345678curl \"http://localhost:8080/getb?field_a=hello&field_b=world\"{\"a\":{\"FieldA\":\"hello\"},\"b\":\"world\"}curl \"http://localhost:8080/getc?field_a=hello&field_c=worldc&filed_x=helloX&filed_d=wordd\"{\"a\":{\"FieldA\":\"hello\"},\"c\":\"worldc\"}curl \"http://localhost:8080/getd?field_a=hello&field_c=worldc&field_x=helloX&field_d=wordd\"{\"d\":\"wordd\",\"x\":{\"FieldX\":\"helloX\"}} 仅仅绑定查询 示例代码 12345678910111213141516171819202122232425262728293031package mainimport ( \"github.com/gin-gonic/gin\" \"net/http\")type Person struct { Name string `form:\"name\"` Address string `form:\"address\"`}func onlyBindQueryStringHandler(c *gin.Context) { var person Person if err := c.BindQuery(&person); err != nil { c.JSON(http.StatusBadRequest, gin.H{ \"error\": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ \"Name\":person.Name, \"address\":person.Address, })}func main() { router := gin.Default() router.Any(\"/testOnlyBindQuery\", onlyBindQueryStringHandler) router.Run()} 测试 测试GET请求 curl -X GET "localhost:8080/testOnlyBindQuery?name=yunfen&address=xyz" {“Name”:“yunfen”,“address”:“xyz”} 测试POST请求,发现请求体的数据忽略了 curl -X POST "localhost:8080/testOnlyBindQuery?name=yunfen&address=xyz" -d "name=hhetest&address=ffff" {“Name”:“yunfen”,“address”:“xyz”} 绑定查询类型或者POST数据 示例代码 1234567891011121314151617181920212223242526272829303132333435363738package mainimport ( \"github.com/gin-gonic/gin\" \"net/http\" \"time\")type Person struct { Name string `form:\"name\"` Address string `form:\"address\"` Birthday time.Time `form:\"birthday\" time_format:\"2006-01-02\" time_utc:\"1\"` CreateTime time.Time `form:\"createTime\" time_format:\"unixNano\"` UnixTime time.Time `form:\"unixTime\" time_format:\"unix\"`}func bindQueryStringOrPostDataHandler(c *gin.Context) { var person Person if err := c.ShouldBind(&person); err != nil { c.JSON(http.StatusBadRequest, gin.H { \"error\":err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ \"Name\": person.Name, \"Address\": person.Address, \"Birthday\": person.Birthday, \"CreateTime\": person.CreateTime, \"UnixTime\": person.UnixTime, })}func main() { router := gin.Default() router.Any(\"/testQueryAndPost\", bindQueryStringOrPostDataHandler) router.Run()} 测试 测试GET请求 curl -X GET "localhost:8080/testQueryAndPost?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" {“Address”:“xyz”,“Birthday”:“1992-03-15T00:00:00Z”,“CreateTime”:“2019-07-06T16:00:33.000000123+08:00”,“Name”:“appleboy”,“UnixTime”:“2019-07-06T16:00:33+08:00”} 测试POST请求 curl -X POST "localhost:8080/testQueryAndPost" -d "name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" {“Address”:“xyz”,“Birthday”:“1992-03-15T00:00:00Z”,“CreateTime”:“2019-07-06T16:00:33.000000123+08:00”,“Name”:“appleboy”,“UnixTime”:“2019-07-06T16:00:33+08:00”} 绑定Header 示例代码 1234567891011121314151617181920212223242526272829303132package mainimport ( \"github.com/gin-gonic/gin\" \"net/http\")type DomainRate struct { Rate int `header:\"Rate\"` Domain string `header:\"Domain\"`}func bindHeaderHandler(c *gin.Context) { var header DomainRate if err := c.ShouldBindHeader(&header); err != nil { c.JSON(http.StatusBadRequest, gin.H{ \"status\":err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ \"Rate\":header.Rate, \"Domain\":header.Domain, })}// bind headerfunc main() { router := gin.Default() router.GET(\"/testBindHeader\", bindHeaderHandler) router.Run()} 测试 curl -X GET "localhost:8080/testBindHeader" -H "rate":100 -H "domain":localhost {“Domain”:“localhost”,“Rate”:100} 将请求body绑定到不同的结构 JSON、 XML、 MsgPack和ProtoBuf等格式请求体绑定,ShouldBind或者ShouldBindWith消费的 是c.Request.Body,会导致 c.Request.Body变成EOF。为此,ShouldBindBodyWith会在绑定之前 将请求体保存上下文中,但多少带来一定的性能损耗。若确定只绑定一次,就不要此方法。而其他格式的比如:Query、Form、FormPost和FormMultipart在多次使用ShouldBind绑定并不会消耗性能。 // ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request // body into the context, and reuse when it is called again. // // NOTE: This method reads the body before binding. So you should use // ShouldBindWith for better performance if you need to call only once. 例子 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package mainimport ( \"github.com/gin-gonic/gin\" \"github.com/gin-gonic/gin/binding\" \"net/http\")type formA struct { Foo string `json:\"foo\" binding:\"required\"`}type formB struct { Bar string `json:\"bar\" binding:\"required\"`}func usingShoudBindHandler(c *gin.Context) { objA := formA{} objB := formB{} // This c.ShouldBind consumes c.Request.Body and it cannot be reused. if errA := c.ShouldBindWith(&objA, binding.JSON); errA != nil { c.String(http.StatusBadRequest, \"bind A JSON err:%s\\n\", errA.Error()) } else if errB := c.ShouldBindWith(&objB, binding.JSON); errB != nil { // Always an error is occurred by this because c.Request.Body is EOF now. c.String(http.StatusBadRequest, \"bind B JSON err:%s\\n\", errB.Error()) } else { c.String(http.StatusOK, \"Foo:%s,Bar:%s\\n\", objA.Foo, objB.Bar) }}func usingShoudBindBodyWithHandler(c *gin.Context) { objA := formA{} objB := formB{} // This reads c.Request.Body and stores the result into the context. if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA != nil { c.String(http.StatusBadRequest, \"bind A JSON err:%s\\n\", errA.Error()) } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB != nil { // At this time, it reuses body stored in the context. c.String(http.StatusBadRequest, \"bind B JSON err:%s\\n\", errB.Error()) } else { c.String(http.StatusOK, \"Foo:%s,Bar:%s\\n\", objA.Foo, objB.Bar) }}func main() { router := gin.Default() router.POST(\"/testBodyDiffStr1\", usingShoudBindHandler) router.POST(\"/testBodyDiffStr2\", usingShoudBindBodyWithHandler) router.Run()} 测试 curl -X POST "localhost:8080/testBodyDiffStr1" -d '{"foo":"zdf", "bar":"test1"}' bind B JSON err:EOF curl -X POST "localhost:8080/testBodyDiffStr2" -d '{"foo":"zdf", "bar":"test1"}' Foo:zdf,Bar:test1 参考资料 go-gin doc go-gin readme","comments":true,"tags":[{"name":"go-gin","slug":"go-gin","permalink":"https://cloudfeng.github.io/tags/go-gin/"}]},{"title":"如何正确地写出单例模式","date":"2020-02-14T16:00:00.000Z","path":"2020/02/15/2020/02/20200215_design_pattern_singleton/","text":"什么是单例模式 一个类在JVM只有一个实例,并且提供一个全局访问入口。单例模式适用无状态的工具类,比如日志工具、字符串工具; 还有全局信息类,比如全局计数、环境变量;在Java中如下类库是适用单例模式: java.lang.Runtime#getRuntime(); java.awt.Desktop#getDesktop(); java.lang.System#getSecurityManager(); 单例模式的作用:节省内存;节省计算;结果的正确,比如全局计数器;方便管理。其实现方式很多,但不管何种实现方式,共同点: 私有的构造函数; 私有静态类对象; 公有静态方法,唯一一个访问私有静态对象实例的方法。 单例模式实现方式 饿汉式或静态代码模块式 123456789public class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return instance; }} 另外一种变种的写法是 123456789101112131415/** * 饿汉式的变种,静态代码形式 */public class StaticBlockSingleton { private static final StaticBlockSingleton singleton; private StaticBlockSingleton() {} static { singleton = new StaticBlockSingleton(); } public StaticBlockSingleton getInstance() { return singleton; }} 懒加载模式 线程不安全的懒加载模式 123456789101112131415/** * 懒汉式:只适合单线程模式 */public class LazySingleton { private static LazySingleton singleton; private LazySingleton() {} public LazySingleton getInstatnce(){ if (null == singleton) { singleton = new LazySingleton(); } return singleton; }} 线程安全的懒加载模式 1234567891011121314151617181920/** * @description: 线程安全的懒加载单例模式 * @author: agentzhu */public class ThreadSafeLazySingleton { private static ThreadSafeLazySingleton singleton; private ThreadSafeLazySingleton() {} /** * 加锁粒度大,多线程环境不能同时访问,并发效率低 * @return */ public synchronized ThreadSafeLazySingleton getInstatnce(){ if (null == singleton) { singleton = new ThreadSafeLazySingleton(); } return singleton; }} 双重检验锁模式(double checked locking pattern) 双重检验锁模式也是一种懒加载模式,是一种对安全型懒加载模式的优化,具体如下: 123456789101112131415161718public class Singleton { // 关键点1:声明成 volatile,禁止指令重排序 private volatile static Singleton instance; private Singleton (){} public static Singleton getSingleton() { // 关键点2: 提高并发性 if (instance == null) { synchronized (Singleton.class) { // 关键点3:防止创建多个实例 if (instance == null) { instance = new Singleton(); } } } return instance; }} volatile关键字的原因,new Singleton()JVM的实现三个不步骤,如下图: 由于指令重排,在多线程环境中易于引起使用未初始化完全的的对象,比如下图: 所以使用volatile关键字,其有两个特性:一个是可见性;另外一个是禁止指令重排序优化。而禁止指令重排序优化具体来说,在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完1-2-3 之后或者1-3-2之后,不存在执行到1-3然后取到值的情况。 静态内部类 static nested class Java 5 以前的版本使用volatile的双检锁还是有问题的。其原因是Java 5以前的JMM(Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。所以Bill Pugh提供了一种静态内部类实现方式。 12345678910public class Singleton { private static class SingletonHelper { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHelper.INSTANCE; } } 枚举式 上面单例模式的实现方式都存在两个问题: 序列化与反序列化创建多个实例; 反射,创建多个实例; 序列化与反序列化创建多个实例问题 123456789101112131415161718192021222324252627282930313233public class DoubleCheckSingleton implements Serializable { private static final long serialVersionUID = -7975945444590877513L; // 不用volatile 修饰,会出现不完全初始化的状态的实例 private static volatile DoubleCheckSingleton singleton; private DoubleCheckSingleton() {} public static DoubleCheckSingleton getInstance() { if (null == singleton) { //关键点1: 提高并发效率 synchronized (DoubleCheckSingleton.class) { if (null == singleton) { // 关键点2:防止创建多个实例 // 存在三个步骤,顺利不是固定的[编译器的重排序优化],可能是:1,2,3;1,3,2 // 1.给singleton分配内存空间 // 2.调用Singleton的构造函数等来初始化singleton // 3.将singleton对象指向分配的内存空间(执行完此步singleton就不是null了) singleton = new DoubleCheckSingleton(); } } } return singleton; } private int value = 10; public int getValue() { return value; } public void setValue(int value) { this.value = value; }} 测试结果: 122010 为此在DoubleCheckSingleton需要重写readResolve,它会在反序列化的时候被调用,所以我们可以在此方法中返回已有的对象实例。 123protected Object readResolve() { return singleton;} 测试结果 122020 反射创建多个实例 创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。 1234567891011121314151617public class SingletonReflectionDemo { public static void main(String[] args) throws Exception { DoubleCheckSingleton singleton = DoubleCheckSingleton.getInstance(); Constructor constructor = singleton.getClass().getDeclaredConstructor(new Class[0]); constructor.setAccessible(true); DoubleCheckSingleton singleton2 = (DoubleCheckSingleton) constructor.newInstance(); if (singleton == singleton2) { System.out.println(\"Two objects are same\"); } else { System.out.println(\"Two objects are not same\"); } singleton.setValue(1); singleton2.setValue(2); System.out.println(singleton.getValue()); System.out.println(singleton2.getValue()); }} 测试结果 123Two objects are not same12 我们再来看看枚举方式的单例实现方式,虽然在加载类的时候实例化,并且只有一个实例对象。存在的问题达不到懒加载的作用的。但是绝对解决上述提到的两个问题。首先,我们来看看如何使用枚举的方式来实现单例模式,然后再一一测试。 123456789101112public enum EnumSingleton { INSTANCE; int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; }} 验证序列化与反序列化创建多个实例的问题 123456789101112131415161718192021222324252627public class EnumSingletonSerializeDemo { private static EnumSingleton instanceOne = EnumSingleton.INSTANCE; public static void main(String[] args) { try { // Serialize to a file ObjectOutput out = new ObjectOutputStream(new FileOutputStream( \"filename.ser\")); out.writeObject(instanceOne); out.close(); instanceOne.setValue(20); // deserialize from a file ObjectInput in = new ObjectInputStream(new FileInputStream( \"filename.ser\")); EnumSingleton instanceTwo = (EnumSingleton) in.readObject(); in.close(); System.out.println(instanceOne.getValue()); System.out.println(instanceTwo.getValue()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }} 验证反射创建多个实例问题 反射进行创建枚举类的会直接报错,无法创建的。原因:枚举被设计成是单例模式,即枚举类型会由JVM在加载的时候,实例化枚举对象,你在枚举类中定义了多少个就会实例化多少个,JVM为了保证每一个枚举类元素的唯一实例,是不会允许外部进行new的,所以会把构造函数设计成private,防止用户生成实例,破坏唯一性。 An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type. The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants. 总结 实现方式 优点 缺点 饿汉式/静态代码模块式 简单,在类加载的时完成实例化;无线程同步问题 不使用此实例,也会在类加载的时候完成实例化,浪费内存;存在序列化、反射创建多个实例问题 懒加载或者线程安全的懒加载式 获取实例的时候才初始化,但只适合单线程情况下使用 线程不安全或者并发度低;存在序列化、反射创建多个实例问题 双重检验锁模式 线程安全;懒加载; 代码复杂度高,易写错;存在序列化、反射创建多个实例问题 枚举式 线程安全;代码简单,流行度不高 不是懒加载,不存在序列化,反射创建多个实例问题 参考资料 ava Singleton Design Pattern Best Practices with Examples Is enum Really Best Singletons? Implementing Singleton with an Enum (in Java)","comments":true,"tags":[{"name":"单例模式, singleton pattern","slug":"单例模式-singleton-pattern","permalink":"https://cloudfeng.github.io/tags/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F-singleton-pattern/"}]},{"title":"Maven项目搭建Log4j日志(带颜色)环境","date":"2020-02-08T16:00:00.000Z","path":"2020/02/09/2020/02/intelli_log4j_20200209/","text":"Maven项目搭建Log4j日志环境 介绍一下Maven项目中如何搭建Log4j日志,具体如下: 构建一个Maven项目; 在项目的POM文件中引入Log4j的相关依赖包: 12345678910111213141516<properties> <slf4j-api-version>2.13.0</slf4j-api-version> <log4j-core-version>2.13.0</log4j-core-version></properties><dependencies> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${slf4j-api-version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j-core-version}</version> </dependency></dependencies> 构建一个测试类: 123456789101112131415161718192021public class Main { private static Logger logger = LogManager.getLogger(Main.class.getName()); public static void main(String[] args) { add(1, 2); } public static int add(int a , int b) { logger.entry(a+b); logger.info(\"我是info信息\"); logger.warn(\"我是warn信息\"); logger.error(\"我是error信息\"); logger.fatal(\"我是fatal信息\"); logger.printf(Level.INFO, \"%d+%d=%d\", a, b, a + b); logger.exit(a + b); logger.debug(\"one={}, two={}, three={}\", 1, 2, 3); return a + b; }} 配置Log4j的配置文件(log4j2.xml),配置文件放在根目录 12345678910111213<?xml version=\"1.0\" encoding=\"UTF-8\"?><configuration status=\"off\"> <appenders> <Console name=\"Console\" target=\"SYSTEM_OUT\"> <PatternLayout pattern=\"%highlight{%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n%throwable}{FATAL=white, ERROR=red, WARN=blue, INFO=black, DEBUG=green, TRACE=blue}\"/> </Console> </appenders> <loggers> <root level=\"trace\"> <appender-ref ref=\"Console\"/> </root> </loggers></configuration> 测试 参考资料 log4j-layout Log4j 2配置与IntelliJ IDEA控制台颜色","comments":true,"tags":[{"name":"log","slug":"log","permalink":"https://cloudfeng.github.io/tags/log/"}]},{"title":"macOS升级Nodejs版本","date":"2020-02-02T16:00:00.000Z","path":"2020/02/03/2020/02/macos_nodejs_update_20200203/","text":"Nodejs版本升级 今天开始上班,但由于新肺炎只能远程,在家办公。学习前端开发,所以把之前的nodejs版本太老了,准备 把版本升级一下,这里把升级的步骤写一下,做一个备份: 先查看本机node.js版本:node -v; 清除node.js的cache:sudo npm cache clean -f; 安装 n 工具,这个工具是专门用来管理node.js版本的:sudo npm install -g n; 安装最新版本的node.js: sudo n stable; 再次查看本机的node.js版本:node -v; hexo运行报错 升级完Nodejs之后发现,hexo各种报错: 找不到hexo命令,后面重新装了:npm install hexo-cli -g npm install 报: 123In file included from ../src/binding.cpp:3:../src/sass_context_wrapper.h:8:10: fatal error: 'sass/context.h' file not found#include <sass/context.h> 看了很多资料说要回退nodejs版本,但我没有,继续找资料解决了,记录一下: 安装libsass: brew install libsass 删除package-lock.json:rm package-lock.json 删除node_modules: rm -rf node_nodules 执行:npm install 根据提示可能会执行:npm audit fix --force 参考资料 Team blog install tutorial GYP ERR! build error. stack Error: ‘make’ failed with exit code 2","comments":true,"tags":[{"name":"nodejs","slug":"nodejs","permalink":"https://cloudfeng.github.io/tags/nodejs/"}]},{"title":"展望2020,回顾2019","date":"2019-12-31T16:00:00.000Z","path":"2020/01/01/summary/20200101_2019summary/","text":"概要 今天是2020年的第一天,回顾2019年,平时没有做多少的记录总结。生活上最大的收获就是娃在出来了。 陪伴的时候出了很多问题,大都是家庭问题,与老婆拌嘴更多了。总之各种辛酸不为外人道。工作上,一直 打算换份工作,出于成长和心脏,但出去面了几次,没有拿到过offer。其实最大的问题自己也清楚,就是 去外面看看,根本没有下定决心要走。学的东西也是杂乱的,不成体系,不够深度。2019年就这样浑浑噩噩 过了。下面具体说说2019年经历了啥。 喜忧参半的陪产假 2019年的春节前娃出来,是一件高兴的事。由于媳妇是剖腹产的,所以孩子体质比其他小孩子要差一点。 加上媳妇也没有怎么恢复,所以月子里面很辛苦没有恢复好。另外一方面是父母兄弟之间各种比较什么的, 所以媳妇过得心比较累。 温水的职场 已经出来工作了三年了,三年期间整个技术水平还没有太多深入的东西。入职之前是Java小白, 现在对Java的理解也不算是深入。工作上的技术栈是Spring,前面两年都是适应职场和了解 业务,特别容易激动。后面慢慢改变,但没有彻底改造完。工作以来学了大量乱七八糟的玩意Scheme, 为了读懂SCIP。在各种知识变现吹嘘焦虑下学习算法,但没有坚持下来。2018年的时候参加左耳听风 提出的ARTS活动,只坚持了20周,后面就没有了。到了2019的时候开始反思,再这么下去会把自己变为 青蛙,都后面怎么死都不知道。着手分解工作上用的相关技术: DB方面:Oracle、Redis和MongoDB; 框架:Spring MVC、Dubbo、RocketMQ和ZK 语言: Java 为此2019年就读一些书: 《Redis 设计与实现》★★★★ 《Redis 实战》★★★★ 《Spring 实战》★★★★ 《深入理解Apache Dubbo 与实战》★★★ 《RocketMQ实战与原理解析》 未读完 ★★★ 《从Paxos到Zookeeper》★★★★ 《Go语言实战》★★★★ 《Go语言编程》★★★★ 《深入理解Nginx》 未读完 ★★★★ 《Netty实战》未读完 ★★★★ 《设计数据密集型应用》★★★★★ 《学习力》 ★★★★ 《自主教养》 ★★★★ 《深入理解JVM》★★★★ 《Java并发编程实战》★★★★ 《Java并发编程艺术》★★★★ 现在回想读这些书我的技术上来了么,并没有。阅读书并不代表你的技术就会上来,或者说你就对某个技术 深入理解了。其实相差太远了,没有输出就没有任何留下来的痕迹。年末的时候自己做了Redis监控的小web 应用,才开始慢慢慢慢沉淀下来。 圈子 认识了高渐离兄、codedump老师、轩脉刃还有老王,但缺乏深入的沟通,没有对自己产生的影响特别大。 一则自己的技术不够高;二则自己看问题的角度以及思考都不是很深入。 展望2020 多花时间陪娃和老婆,多学习技术以及和前辈沟通,希望自己能够换一份工作。 祝愿读者们新的一年有新的进步,实现新的愿望。","comments":true,"tags":[{"name":"年度总结","slug":"年度总结","permalink":"https://cloudfeng.github.io/tags/%E5%B9%B4%E5%BA%A6%E6%80%BB%E7%BB%93/"}]},{"title":"Java Thread Dump 日志分析","date":"2019-07-30T16:00:00.000Z","path":"2019/07/31/java/java_thread_dump_analysis/","text":"Thread Dump是什么 Thread Dump(javacore文件)是当前虚拟机内每一条线程正在执行的方法堆栈集合,生成线程快照的主要目的 定位线程出现长时间停顿的原因: 线程间死锁 死循环 请求外部资源导致长时间等待等 如何生成Thread Dump 可以使用jstack(Stack Trace for Java)命令,它用于生成虚拟机当前时刻的线程快照,就可以查 看各个线程的调用栈。以部署在Tomcat上Java Web应用为例,当我们遇到CPU飙升或者100%时,执行如下步骤 获取Thread Dump: 获取Tomcat线程号(tomcatPID):ps aux | grep tomcat; 获取每个线程的负载情况:top -Hp tomcatPID, SHIFT+T 按CPU耗时总时间倒序排序,找到的top几个是最耗CPU时间的。注意这个步骤十分关键,用于后面分析Thread Dump日志; 生成 Thread Dump: jstack tomcatPID > thread_dump_tomcatPID.log。关于 jstack命令可以参考:jstack: stack trace 线程状态 Java 线程状态以及状态之间转换 为了能够读懂Thread Dump日志,首先要熟悉Java线程的状态以及状态之间的转换。首先我们来看一下Java 线程状态,并且在任意一个时间点,一个线程只能有且只有其中的一种状态: 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。 运行(RUNNABLE):从OS(操作系统)层面看,JVM的运行状态可以由:就绪(ready)和运行中(running)两种状态构成。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态”在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。 无限期等待(WAITING):进入该状态的线程不会被分配CPU执行时间,需要等待其他线程做出一些特定动作(通知或中断)。下方法会让线程陷入无限期的等待状态: 没有设置Timeout参数的Object.wait()方法; 没有设置Timeout参数的Thread.join()方法; LockSupport.park()方法; 限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态: Thread.sleep()方法; 设置了Timeout参数的Object.wait()方法; 设置了Timeout参数的Thread.join()方法; LockSupport.parkNanos()方法; LockSupport.parkUntil()方法; 终止(TERMINATED):已终止线程的线程状态,线程已经结束执行。 然后我们来看看Java线程状态之间是如何转换,如下图: Thread Dump文件中的线程状态含义及注意事项 在Thread Dump打印出来的线程状态与我们上面提到的线程状态还是有点差别的,下面看看Thread Dump文件中的线程状态含义及注意事项: Deadlock:死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。 Runnable:一般指该线程正在执行状态中,该线程占用了资源,正在处理某个请求,有可能正在传递SQL到数据库执行,有可能在对某个文件操作,有可能进行数据类型等转换。 Waiting on condition:等待资源,或等待某个条件的发生。具体原因需结合 stacktrace来分析: 如果堆栈信息明确是应用代码,则证明该线程正在等待资源。一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取。 正在等待其他线程的执行等。 如果发现有大量的线程都在处在Wait on condition,从线程stack看,正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。 一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写; 另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。 另外一种出现Wait on condition的常见情况是该线程在sleep,等待 sleep的时间到了时候,将被唤醒。 Blocked:线程阻塞,是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。 Waiting for monitor entry 和 in Object.wait():Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。每一个对象都有,也仅有一个 monitor。从下图中可以看出,每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 Active Thread,而其它线程都是 Waiting Thread,分别在两个队列Entry Set和 Wait Set里面等候。在Entry Set中等待的线程状态是 Waiting for monitor entry,而在Wait Set中等待的线程状态是in Object.wait()。 所以我们重点关注的线程状态是: 死锁Deadlock 执行中Runnable (CPU飙升的时候关注的重点对象) 等待资源,Waiting on condition 等待获取监视器,Waiting on monitor entry 暂停,Suspended 阻塞,Blocked Thread Dump日志结构 当你打开Thread Dump文件,就会发现其包含的内容结构如下图: Thread Dump三个例子 下面举三个例子进行分析,更多例子可以参考 How to Analyze Java Thread Dumps。 Waiting to lock 和 Blocked “RMI TCP Connection(267865)-172.16.5.25” daemon prio=10 tid=0x00007fd508371000 nid=0x55ae waiting for monitor entry [0x00007fd4f8684000] java.lang.Thread.State: BLOCKED (on object monitor) at org.apache.log4j.Category.callAppenders(Category.java:201) waiting to lock <0x00000000acf4d0c0> (a org.apache.log4j.Logger) at org.apache.log4j.Category.forcedLog(Category.java:388) at org.apache.log4j.Category.log(Category.java:853) at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234) at com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110) 1) 第一行里,`RMI TCP Connection(267865)-172.16.5.25`是 Thread Name 。`tid`指Java Thread id。nid指native线程的id。prio是线程优先级。`[0x00007fd4f8684000]`是线程栈起始地址。 2)线程状态是 `Blocked`,阻塞状态。说明线程等待资源超时! 3)`waiting to lock <0x00000000acf4d0c0>`指,线程在等待给这个 `0x00000000acf4d0c0` 地址上锁(英文可描述为:trying to obtain `0x00000000acf4d0c0` lock)。 4)在 dump 日志里查找字符串 `0x00000000acf4d0c0`,发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁(如`locked < 0x00000000acf4d0c0>`),就可以顺藤摸瓜了。 5)`waiting for monitor entry`说明此线程通过 `synchronized(obj) {……}` 申请进入了临界区,从而进入了`Entry Set`队列,但该 obj 对应的 monitor 被其他线程拥有,所以本线程在`Entry Set`队列中等待。 Waiting on condition 和 TIMED_WAITING “RMI TCP Connection(idle)” daemon prio=10 tid=0x00007fd50834e800 nid=0x56b2 waiting on condition [0x00007fd4f1a59000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198) at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:424) at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:323) at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:874) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:945) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907) at java.lang.Thread.run(Thread.java:662) 1)“TIMED_WAITING (parking)”中的 timed_waiting 指等待状态,但这里指定了时间,到达指定的时间后自动退出等待状态;parking指线程处于挂起中。 2)`waiting on condition`需要与堆栈中的`parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)`结合来看。 - 本线程肯定是在等待某个条件的发生,来把自己唤醒 - `SynchronousQueue` 并不是一个队列,只是线程之间移交信息的机制,当我们把一个元素放入到 `SynchronousQueue` 中时必须有另一个线程正在等待接受移交的任务,因此这就是本线程在等待的条件。 in Obejct.wait() 和 TIMED_WAITING “RMI RenewClean-[172.16.5.19:28475]” daemon prio=10 tid=0x0000000041428800 nid=0xb09 in Object.wait() [0x00007f34f4bd0000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) waiting on <0x00000000aa672478> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118) locked <0x00000000aa672478> (a java.lang.ref.ReferenceQueue$Lock) at sun.rmi.transport.DGCClient$EndpointEntry$RenewCleanThread.run(DGCClient.java:516) at java.lang.Thread.run(Thread.java:662) 1)`TIMED_WAITING (on object monitor)`,对于本例而言,是因为本线程调用了 `java.lang.Object.wait(long timeout)` 而进入等待状态。 2)`Wait Set`中等待的线程状态就是`in Object.wait()`。当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 `Wait Set`队列。只有当别的线程在该对象上调用了 notify() 或者 notifyAll() ,`Wait Set`队列中线程才得到机会去竞争,但是只有一个线程获得对象的 Monitor,恢复到运行态。 3)`RMI RenewClean` 是 `DGCClient` 的一部分。`DGC` 指的是 Distributed GC,即分布式垃圾回收。 4)请注意,是先`locked <0x00000000aa672478>`,后 `waiting on <0x00000000aa672478>`,之所以先锁再等同一个对象,请看下面它的代码实现: 12345678910111213static private class Lock { };private Lock lock = new Lock();public Reference<? extends T> remove(long timeout){ synchronized (lock) { Reference<? extends T> r = reallyPoll(); if (r != null) return r; for (;;) { lock.wait(timeout); r = reallyPoll(); …… }} 即,线程的执行中,先用 synchronized 获得了这个对象的 Monitor(对应于locked <0x00000000aa672478> );当执行到 lock.wait(timeout);,线程就放弃了 Monitor 的所有权,进入Wait Set队列(对应于 waiting on <0x00000000aa672478> )。 5)从堆栈信息看,是正在清理 remote references to remote objects,引用的租约到了,分布式垃圾回收在逐一清理呢。 Thread Dump使用常见场景 首先要说明的是thread dump 是个瞬时数据,需要多个采样进行对比才能更好的发现问题,所以要多次采样。下面是常见场景与排查思路: cpu飙高,load高,响应很慢 一个请求过程中多次dump,对比多次dump文件的runnable线程,如果执行的方法有比较大变化,说明比较正常。如果在执行同一个方法,就有一些问题了。 cpu使用率不高但是响应很慢 进行dump,查看是否有很多thread struck在了i/o、数据库等地方,定位瓶颈原因。 请求无法响应 多次dump,对比是否所有的runnable线程都一直在执行相同的方法,如果是的,恭喜你,锁住了! Thread DUMP 分析工具 进制转换 TDA Online Java Thread Dump Analyzer 参考资料 tomcat thread dump 分析 虚拟机stack全分析 分析三个实例演示Java Thread Dump日志分析 Thread Dump分析 JVM性能调优jstack和线程dump分析 性能分析之–JAVA Thread Dump 分析综述 Java线程转换图 说明:本文很多来自参考资料,更多做了汇总与编辑。","comments":true,"tags":[{"name":"thread dump","slug":"thread-dump","permalink":"https://cloudfeng.github.io/tags/thread-dump/"}]},{"title":"分布式缓存遇到的问题汇总","date":"2019-04-07T16:00:00.000Z","path":"2019/04/08/distributed_system/distributed_cache/20190408_cache_problem_int/","text":"概述 在我们的平常的项目中多多少少都会使用到缓存,因为一些数据我们没有必要每次查询的时候都直接查数据库或者调用第三方接口。特别是出现内存数据库之后,使用缓存场景更多了。而对于高 QPS 的系统尤为如此,如果每次都去查数据库,对数据库来说将是灾难。使用缓存业务系统一般的流程如下: 问题汇总 使用缓存遇到的问题汇总如下: 缓存雪崩 概念 由于原有缓存失效,新缓存未到期间所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。 三种处理办法: 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。 为 key 设置不同的缓存失效时间:在一个基础的时间上加上或者减去一个范围内的随机值 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。具体:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。 另外一个更为接地气的方案 事前:使用集群缓存,保证缓存服务的高可用 在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。 事中:ehcache本地缓存 + Hystrix限流&降级,避免MySQL被打死 使用 ehcache 本地缓存的目的也是考虑在Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵。 使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。 然后去调用我们自己开发的降级组件(降级),比如设置的一些默认值呀之类的。以此来保护最后的 MySQL 不会被大量的请求给打死。 事后:开启Redis持久化机制,尽快恢复缓存集群 缓存穿透 概念 指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。【只要是空的数据,查缓存又查db。造成数据库压力】 处理方案 提供一个能迅速判断请求是否有效的拦截机制,比如布隆过滤器 将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存 储系统的查询压力。【先看一下bitmap中有木有,没有直接返回;有就查缓存】 简单粗暴的方法 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。【注意:过期时间要短,否则会缓存大量不存在key的数据】 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。【吞吐量明显下降】 采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。 缓存预热 概念 系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!【小量常用的数据】 缓存更新 概念 缓存更新除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰策略。 处理方案 定时去清理过期的缓存; 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 备注:Redis采用如下策略进行数据淘汰: noeviction:客户端若要使用更多的内存,直接返回错误(主要是write) allkeys-lru: 将最近未使用key删除 volatile-lru:在 expire 集合中,将最近未使用的key删除 allkeys-random: 随机删除key volatile-random:在 expire 集合中随机删除key volatile-ttl: 在 expire 集合中,优先删除ttl 最短的key 缓存降级 概念 当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。 处理方案 系统可以根据一些关键数据进行自动降级 可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的 (如加入购物车、结算)。 【保证服务的可用性】 缓存击穿 概念 在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。 处理方案: 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库,在把结果放入缓存,后续请求直接拿缓存。没得到锁,则休眠一段时间重试。 参考资料 为什么我们做分布式使用 Redis? 乔二爷:阿里一面:关于【缓存穿透、缓存击穿、缓存雪崩、热点数据失效】问题的解决方案 云枫随笔:Redis中数据淘汰算法总结 也输:【存储系统架构设计】缓存穿透,缓存击穿,缓存雪崩,热点数据集中失效,redis 缓存系统四连击怎么解决? 猿人课堂:关于Redis中缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等概念的入门及简单解决方案","comments":true,"tags":[{"name":"缓存","slug":"缓存","permalink":"https://cloudfeng.github.io/tags/%E7%BC%93%E5%AD%98/"}]},{"title":"把《程序员的职业素养》读薄","date":"2019-03-31T16:00:00.000Z","path":"2019/04/01/book_note/2019_04_01_the_clean_coder/","text":"引言 这一节作者介绍了自己的经历,从不上大学,17岁开始实习,由于编程经验不足,而被开除。然后自学,两年后成为正式员工,那年19岁,后面因交付系统后老板没有按照之前的约定加薪,而与老板大吵,然后失业。浑浑噩噩过了半年,后面其母亲。对其说: 你的生活状态糟透了,只有傻瓜才会没有找好下家就辞职,才会这么冲动辞职,才会和同事一起闹事。辞职前一定要找好下家,要非常冷静,非常沉着,不要拉上其他人。 后面在母亲的指导下向老板求情,回去工作,生活和工作慢慢走上正轨,后面也有很多经历。作者也是一个传奇性人物呀。所以作者给本书的定位: 请你把这本书看成我的错误大全,它记录了我干过的所有蠢事;也请你把这本书当成一份指导,靠它绕过我曾经绕过的弯路。 第1章 专业主义 作者给出什么是专业主义以及如何做到专业主义。 第2章 说"不" 专业人士敢于说明真相而不屈从权势。专业人士有勇气对他们的经理说“不”。什么时候说不,如何说?看完之后内心比较压抑,在国内的环境真的能如此? 千万别说:“好的,我们会试试看”。没有试试看这回事。尝试意味着:付出额外的精力。许诺尝试就意味着你承认自己之前未尽全力,承认自己还有余力可施。许诺尝试意味着只要你再加把劲还是可以达成目标的;而且,这也是一种表示你将再接再厉去实现目标的承诺。因此,只要你许诺自己回去尝试,你其实是在承诺你会确保成功。这样,压力就要你自己来扛了。如果你的尝试没有达成预期的效果,那就表示你失败了。 第3章 说“是” 说“是”,就是那种确定一下的,而且一定要完成的。一旦发现完成不了就需要提前告之。 做出承诺的三个步骤: 口头上说自己将会去做 心里认真对待做出的承诺 真正付诸行动 第4章 编码 必须承认,编码时无可避免地会受到各种干扰。编码原则:若感到疲劳或者心烦意乱,千万不要编码。强而为之,最终只能再回头返工。去找到一种方法来消除干扰,让心绪平静下来。 第5章 测试驱动开发 此章介绍了测试驱动开发知识。 第6章 练习 任何事情,只要想做得快,都离不开练习。要想尽可能快地重复编码/测试过程,就必须能迅速做出决定。这需要识别各种各样的环境和问题,并懂得应付。 第7章 验收测试 P83 做业务的人和写程序的人都容易陷入一个陷阱,即过早进行精细化。业务方还没有启动项目,就要精确制知道最后能得到什么;开发方还没有评估整个项目,就希望精确要交付什么。双方都贪求不现实的精确性,而且经常愿意花钱来追求这种精确。 不确定原则:业务看到展现的功能就会获新的信息,从而会影响其对整个系统的看法 预估焦虑:评估只是评估而已,需要加入误差棒,以防止需求的变更 P84 相比解决分歧,更好的办法是换一种说法,所以会寻找各方都同意的关于需求的表述,而不是去解决争端。 P86 完成意味着所有的代码都写完了,所有的测试都通过了,QA和需求方已经认可 P88 验收测试的目的是沟通、澄清、精确化。与业务方、测试方协同工作,确保大家都明白要做的是什么,是自己的责任。 第8章 测试策略 P98 对QA找到的每一个问题,开发团队都应该高度重视、认真对待。应该反思为什么会出现这种错误,并采取措施避免今后重犯。 第9章 时间管理 这一章如何管理自己的时间,特别是如何参加或者拒绝会议。 将时间分为番茄时间和非番茄时间 番茄时间是生产率的,你可以真正做点事情 非番茄时间:用于应付干扰、参加会议、休息等非工作事宜的时间 一旦注意力不集中,需要恢复起来 与朋友聊天、看看窗外 反省,小睡、翻看杂志,听音乐 肌肉注意:运动、做非编程、哪种不是心智注意力的活动 保证每天睡够7个小时 到公司时候,安排每日生活,每一个小时留15分钟对付意外情况 对于会议,若无帮助或者无效,拒绝参加;如果会议让人厌烦,就离席。 第10章 预估 此章主要是讲什么是预估,如何预估。特别主义承诺与预估的区别: 承诺是必须做到的。如果你承诺在某天做成某事,就必须按时完成。即使它意味着你必须每天工作12小时,放弃周末的休假,也不得不如此。既然承诺了,就必须兑现。 【承诺是确定性,也就是言必行,行必果。不要随便承诺,那样你会死得很惨】 预估是一种猜测。不包含任何承诺的色彩。 【对不确定的任务,也就是那种不知道到底要花多少时间,做预估】 预估方法:三元分析法: O乐观预估,一切都很顺利 N:标称预估 P:悲观预估 第11章 压力 如何面对压力,主要是避免压力,无可避免就直面而上。 避免压力 谨慎承诺 保持整洁:快而脏;脏只会导致缓慢 危机中的纪律:测试驱动开发、流程图 应对压力 不要惊慌失措:保证充足睡眠、冷静思考问题,然后慢慢前进 沟通 依靠你的纪律原则 寻求帮助 第12章 协作 与人协作,而不是一个人单打独斗,多了解业务知识。 专业程序员的首要职责是满足雇主的需求,深入理解业务目标,了解手头正在编写的代码的业务价值是什么。 专业程序员最糟糕的表现是两耳不闻窗外事,只顾一头将自己埋在技术堆里,甚至连公司业务火烧眉毛也不闻不问。 【技术脱离了业务场景,终究是空中楼阁。】 作者的因没有注意业务和内部人事结构,虽然技术很好,但仍旧丢了工作。","comments":true,"tags":[{"name":"职业素养","slug":"职业素养","permalink":"https://cloudfeng.github.io/tags/%E8%81%8C%E4%B8%9A%E7%B4%A0%E5%85%BB/"}]},{"title":"ZK系列之ZooKeeper使用入门","date":"2019-03-18T16:00:00.000Z","path":"2019/03/19/zookeeper/zk_starting/","text":"准本条件 操作系统 ZK包含了很多组件,有些组件支持很多系统,而有些只支持部分。组件如下: Client: Java客户端库,用于连接ZK。 Server: Java服务端运行在ZK集群节点。 Native Client:C实现的客户端,类似于Java客户端,应用程序连接ZK。 Contrib:可选择的插件组件。 操作系统 Client Server Native Client Contrib GNU/Linux 支持 支持 支持 支持 Solaris 支持 支持 不支持 不支持 FreeBSD 支持 支持 不支持 不支持 Windows 支持 支持 不支持 不支持 Mac OS X 仅支持开发 仅支持开发 不支持 不支持 需要说明的是支持指的是开发和生产环境。从上面的表可以看出,需要根据需求选择合适的操作系统,以便使用ZK。 软件要求 ZK是使用Java语言编写,所以操作系统需要安装Java环境,JDK版本必须是1.6以上。若是采用zk集群方式,至少需要3个节点,最好运行在不同的机器上。 安装ZK 下载zookeeper-3.4.12.tar.gz,然后解压到你喜欢的目录下即可。 单机模式与集群模式 ZK支持单机模式也支持集群模式,还有伪集群模式(一种退化的集群模式)。 配置文件 在conf目录下面,拷贝一份 zoo_sample.cfg,然后把名字改为 zoo.cfg。下面是一个简单的配置项: 123456789tickTime=2000initLimit=5syncLimit=2dataDir=../../../tmp/zk/datadataLogDir=../../../tmp/zk/logclientPort=2181server.1=IP1:2888:3888server.2=IP2:2888:3888server.3=IP2:2888:3888 注意:将IPi修改为服务器1,在单机模式中无须配置; 参数说明: tickTime:Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳。 initLimit:用来配置Zookeeper接受客户端(ZK服务集群中连接到Leader的Follower服务器)初始化连接时最长能忍受多少个心跳时间间隔数;时间长度:initLimit*tickTime。 syncLimit:Leader与Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,syncLimit*tickTime dataDir:Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。 dataLogDir:zk保存日志的地方。 clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。 server.A=B:C:D:其中 A 是一个数字(1~255),表示这个是第几号服务器,;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。 需要在dataDir目录下创建myid文件,里面的内容为A,注意每台机器需要一一对应 脚本说明 在 ZooKeeper的bin目录下的脚本内容: 脚本 说明 zkCleanup 清理ZooKeeper历史数据,包括事务日志文件和快照数据文件 zkCli ZooKeeper的一个简易客户端 zkEnv 设置ZooKeeper的环境变量 zkServer ZooKeeper服务器的启动、停止和重启脚本 单机模式 配置zoo.cfg 单机模式只适合在开发模式,在conf/zoo.cfg配置如下内容: 1234tickTime=2000dataDir=../../../tmp/zk/datadataLogDir=../../../tmp/zk/logclientPort=2181 启动服务 进入到bin目录,执行脚本:zkServer.sh(linux) 或 zkServer.cmd(win)。 检测 使用 netstat -ano 命令 启动服务之后,使用netstat -ano查看,比如在win中的命令:netstat -ano | findstr 2181;在linux下使用:netstat -nao | grep 2181 使用 telnet 127.0.0.1 2181, 输入 stat: 1234567891011Zookeeper version: 3.4.12-e5259e437540f349646870ea94dc2658c4e44b3b, built on 03/27/2018 03:55 GMTClients:/127.0.0.1:56800[0](queued=0,recved=1,sent=0)Latency min/avg/max: 0/0/0Received: 1Sent: 0Connections: 1Outstanding: 0Zxid: 0x23Mode: standaloneNode count: 11 注意: Mode: standalone,表示单机模式。 集群模式 我们在一台机器上构建3个节点的伪集群模式。真正的集群模式与之类似,只是在配置项中有少许不同。构建三个节点: 123zookeeper-1zookeeper-2zookeeper-3 配置zoo.cfg 在zookeeper-1的conf/zoo.cfg: 12345678910tickTime=2000initLimit=5syncLimit=2dataDir=../../../tmp/zk1/datadataLogDir=../../../tmp/zk1/logclientPort=2181server.1=127.0.0.1:2888:3888server.2=127.0.0.1:2889:3889server.3=127.0.0.1:2890:3890 在zookeeper-2的conf/zoo.cfg: 12345678910tickTime=2000initLimit=5syncLimit=2dataDir=../../../tmp/zk2/datadataLogDir=../../../tmp/zk2/logclientPort=2182server.1=127.0.0.1:2888:3888server.2=127.0.0.1:2889:3889server.3=127.0.0.1:2890:3890 在zookeeper-3的conf/zoo.cfg: 12345678910tickTime=2000initLimit=5syncLimit=2dataDir=../../../tmp/zk3/datadataLogDir=../../../tmp/zk3/logclientPort=2183server.1=127.0.0.1:2888:3888server.2=127.0.0.1:2889:3889server.3=127.0.0.1:2890:3890 从上面的配置可以看出,由于在同一台机器上,所以需要配置不同的clientPort, 每个server中的端口配置不同。若是在不同的机器上,clientPort配置一样,每个server中配置的IP不同,但后面两个端口一样,也就是说不同机器构建集群的话,三份配置文件是一样的。 在dataDir目录下新建myid文件,文件内容是server.x中的x,此示例中分别为1,2,3。 启动服务 分别进入:zookeeper-x\\bin,执行zkServer脚本,启动服务。 检查是否启动成功 使用 netstat 命令: netstat -ano | findstr 218[1,2,3] 123456TCP 0.0.0.0:2181 0.0.0.0:0 LISTENING 8520TCP 0.0.0.0:2182 0.0.0.0:0 LISTENING 9212TCP 0.0.0.0:2183 0.0.0.0:0 LISTENING 8736TCP [::]:2181 [::]:0 LISTENING 8520TCP [::]:2182 [::]:0 LISTENING 9212TCP [::]:2183 [::]:0 LISTENING 8736 使用telnet telnet 127.0.0.1 2181, 然后输入 stat 12345678910Zookeeper version: 3.4.12-e5259e437540f349646870ea94dc2658c4e44b3b, built on 03/27/2018 03:55 GMTClients: /127.0.0.1:58669[0](queued=0,recved=1,sent=0)Latency min/avg/max: 0/0/0Received: 1Sent: 0Connections: 1Outstanding: 0Zxid: 0x300000002Mode: followerNode count: 13 telnet 127.0.0.1 2182, 然后输入 stat 1234567891011Zookeeper version: 3.4.12-e5259e437540f349646870ea94dc2658c4e44b3b, built on 03/27/2018 03:55 GMTClients:/127.0.0.1:58691[0](queued=0,recved=1,sent=0)Latency min/avg/max: 0/0/0Received: 1Sent: 0Connections: 1Outstanding: 0Zxid: 0xb00000000Mode: leaderNode count: 13 telnet 127.0.0.1 2183, 然后输入 stat 1234567891011121314Zookeeper version: 3.4.12-e5259e437540f349646870ea94dc2658c4e44b3b, built on03/27/2018 03:55 GMTClients:/127.0.0.1:58706[0](queued=0,recved=1,sent=0)Latency min/avg/max: 0/0/0Received: 1Sent: 0Connections: 1Outstanding: 0Zxid: 0x300000002Mode: followerNode count: 13 构建集群结构 从Mode来看,可以看出构建的集群结构是: node2 --- leader / \\ / \\ Follower --- Node1 Node3 --- Follower zk基于层次型的目录树数据结构,并对树中的节点进行有效管理,为此提供了一套很好的分布式集群管理的机制。 客户端玩zk 连接server:zkCli.cmd -server 127.0.0.1:2181 创建节点 查看节点 12[zk: 127.0.0.1:2181(CONNECTED) 2] ls /[dubbo, zookeeper, app2, app1, app3] 创建节点 创建一个节点zk_test,关联到的数据是 my_data 1234[zk: 127.0.0.1:2181(CONNECTED) 3] create /zk_test my_dataCreated /zk_test[zk: 127.0.0.1:2181(CONNECTED) 4] ls /[dubbo, zookeeper, app2, app1, app3, zk_test] 查看节点信息 1234567891011121314[zk: 127.0.0.1:2181(CONNECTED) 5] get /zk_testmy_datacZxid = 0xb00000002ctime = Tue Mar 19 19:20:00 CST 2019mZxid = 0xb00000002mtime = Tue Mar 19 19:20:00 CST 2019pZxid = 0xb00000002cversion = 0dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 7numChildren = 0[zk: 127.0.0.1:2181(CONNECTED) 6] 更新节点信息 123456789101112[zk: 127.0.0.1:2181(CONNECTED) 6] set /zk_test junkcZxid = 0xb00000002ctime = Tue Mar 19 19:20:00 CST 2019mZxid = 0xb00000003mtime = Tue Mar 19 19:57:15 CST 2019pZxid = 0xb00000002cversion = 0dataVersion = 1aclVersion = 0ephemeralOwner = 0x0dataLength = 4numChildren = 0 删除节点 1234[zk: 127.0.0.1:2181(CONNECTED) 7] delete /zk_test[zk: 127.0.0.1:2181(CONNECTED) 8] ls /[dubbo, zookeeper, app2, app1, app3][zk: 127.0.0.1:2181(CONNECTED) 9] 注意删除非空节点报:Node not empty: xxx 创建节点 12[zk: 127.0.0.1:2181(CONNECTED) 11] create /zk_test my_testCreated /zk_test 创建子节点 12345678[zk: 127.0.0.1:2181(CONNECTED) 12] ls /[dubbo, zookeeper, app2, app1, app3, zk_test][zk: 127.0.0.1:2181(CONNECTED) 13] create /zk_test/child junkCreated /zk_test/child[zk: 127.0.0.1:2181(CONNECTED) 14] ls /[dubbo, zookeeper, app2, app1, app3, zk_test][zk: 127.0.0.1:2181(CONNECTED) 15] ls /zk_test[child] 查看节点信息 12345678910111213[zk: 127.0.0.1:2181(CONNECTED) 16] get /zk_testmy_testcZxid = 0xb00000005ctime = Tue Mar 19 20:20:45 CST 2019mZxid = 0xb00000005mtime = Tue Mar 19 20:20:45 CST 2019pZxid = 0xb00000006cversion = 1dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 7numChildren = 1 12345678910111213[zk: 127.0.0.1:2181(CONNECTED) 17] get /zk_test/childjunkcZxid = 0xb00000006ctime = Tue Mar 19 20:21:05 CST 2019mZxid = 0xb00000006mtime = Tue Mar 19 20:21:05 CST 2019pZxid = 0xb00000006cversion = 0dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 4numChildren = 0 删除非空节点 123[zk: 127.0.0.1:2181(CONNECTED) 18] delete /zk_testNode not empty: /zk_test[zk: 127.0.0.1:2181(CONNECTED) 19] 参考资料 ZooKeeper Getting Started Guide 系统支持 zookeeper-3.4.12.tar.gz 分布式服务框架 Zookeeper – 管理分布式环境中的数据 zookeeper复制模式","comments":true,"tags":[{"name":"ZooKeeper","slug":"ZooKeeper","permalink":"https://cloudfeng.github.io/tags/ZooKeeper/"}]},{"title":"网络周志第三期","date":"2019-03-08T16:00:00.000Z","path":"2019/03/09/networking_reading_note/reading_note_3/","text":"写这个系列的目的: 了解一下自己拿着手机再看啥,看看能否整理所看的; 看看自己能能够坚持写多少期 标题:最伟大的计算机程序员是如何诞生的?——解读高德纳(Donald E.Knuth) 阅读笔记 本文是高德纳的小传,看完之后,他是一个极为聪明和勤奋细心的人。成长路上离不开家庭和所接触的朋友。死磕细节可以看他在写算法圣书得到诠释。还有具体下面的例子: 与朋友相交 他与戴克斯彻相处的思考: 他的长处在于永不妥协的审美品味,我呢,总是意志不坚,摇摆不定。如果他对我说他喜欢我做的某件事,那么他就是真的喜欢;如果他说不喜欢,那么就是真的不喜欢。所以我视他为难得的诤友。 死磕细节 1.写书的例子 《计算机程序设计艺术》书中对提及的每一个理论,书中都会巨细无遗地讨论所有细节。在解释某个算法后,还会再给出一个程序实例——目的是确保读者不会产生误解。这是其对待课题一丝不苟的态度结果,也是他不厌其烦求证结果。(ps:本人木有看过此书,只粗略的翻了一下,确实如此。) 2.编程竞赛的例子 艾伦.凯在斯坦福大学从事AI项目时(20世纪60年代末),每个感恩节我们都会与在湾区 做研究项目的人们进行一次编程竞赛。奖品是一只火鸡。 麦卡锡为竞赛出题。高德纳参加的那一年,他一举拿下了两个奖项:程序调试所用的 时间最少、算法执行效率最高。而且他用的是所有参赛者中最烂的系统,叫做Wilbur系统,只能远程批处理。可以说他把所有人都打得屁滚尿流。然后他们问他:“你怎么这么牛?”他回答说:“我学编程的时候,一天能摸5分钟计算机就不错了。想让程序跑起来,就必须写得没有错误。所以编程就像在石头上雕刻一样,必须小心翼翼。我就是这样学编程的。” HashMap的死循环总结 JDK7中的HashMap在多线程环境下可能造成CPU 100%的现象,这个由于在扩容的时候transfer时产生了死链,由此会在get时造成了CPU 100%。 误用的例子与分析 使用hashmap作缓存 代码中使用HashMap的方式类似如下1: 1234567891011121314public class xxxxx{ private static Map<String,List<String>> caches = new HashMap<String,List<String>>(); public void load(){ caches = new HashMap<String,List<String>>(); // 往此caches放东西 for...{ caches.put(... } } public List<String> get(String key){ return caches.get(key); }} 是因为出现这种场景:可能会出现正在往caches里put东西的时候,其他的线程在get,直接就导致并发问题,HashMap.get的并发问题有可能会导致get方法中的下面这段代码进入死循环: 1234567for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value;} 还有一个例子也是作缓存,代码类似如下: 123456private static Map... cache = new HashMap...Object object = cache.get(key);if(object == null){ object = **一个比较耗资源的获取Object动作...** cache.put(key,object);} 为什么会出现死循环 是什么情况下才会让for循环变成死循环呢?如下解释:2 出现死循环是因为map中的桶链表出现了循环链表,循环链表是因为并发put操作造成的,同时进行了resize方法而触发调用transfer方法在rehash的时候,恰巧又在同一个桶中,此时修改链表的指向,导致出现环。如果只有一个线程进行put操作,是不会出现循环链表,所以读写并发不会出现死循环,只有写写并发才有可能出现死循环。 在资料3,4,有大量的图描述如何产生环而构成死循环。比如: 解决方案 hashmap出现死循环,并非其bug,别人已经明确告诉你:不支持并发。那我们对于想利用hashmap特性,咋整。用ConcurrentHashMap来替换hashmap,比如例一,但在例二中还要考虑使用Future,一是等待资源,二是get/put不是原子操作。 标题:婴儿教育,最重要的是什么 阅读笔记: 什么是婴儿期最重要的事情呢? 答:让宝宝听你的话,做他的男神女神,并且理解他懂他。 第一条:少或者不朝宝宝发火、大喊大叫、粗暴错怪他,构建你的影响力。 第二条:理解孩子、懂孩子。这就不仅仅需要我们温柔高雅,还需要我们具备一些有关孩子成长的知识,至少让我们比算命的离他的心更近。 更为重要的是多回应宝宝,那么什么是回应呢。 回应就是观察、跟随孩子的状态,做他想做的。跟随孩子的眼睛,学会领会孩子的精神状态。听他想说的,在他想要安静的时候给他空间,在他想要爱的时候给他拥抱。 1.又一起并发场景下错误使用HashMap的Case ↩2.并发场景下HashMap.get导致cpu耗光的原因分析 ↩3.老生常谈,HashMap的死循环 ↩4.疫苗:JAVA HASHMAP的死循环 ↩","comments":true,"tags":[{"name":"网络阅读","slug":"网络阅读","permalink":"https://cloudfeng.github.io/tags/%E7%BD%91%E7%BB%9C%E9%98%85%E8%AF%BB/"}]},{"title":"160. Intersection of Two Linked Lists","date":"2018-11-29T16:00:00.000Z","path":"2018/11/30/arts/algorithm/160_Intersection_of_Two_Linked_Lists/","text":"Algorithm 本周做的算法题是 160. Intersection of Two Linked Lists。 问题描述 给定两个单链表,写一个函数找出其第一个相交的点。 例子: A: a1 → a2 ↘ c1 → c2 → c3 ↗ B: b1 → b2 → b3 begin to intersect at node c1. 思路 \b使用哈希表法,将\b遍历单\b链表A将其每个节点的地址存入哈希表中,再遍历单链表B,检测其节点是否在哈希表中,若存在则返回第一个相交的点。空间复杂度O(n),时间复杂度:O(n+m),其中n是链表A的长度,m是链表B的长度。 12345678910111213141516171819public ListNode getIntersectionNode(ListNode headA, ListNode headB) { Map<ListNode, ListNode> mapListNode = new HashMap<ListNode, ListNode>(); if (null == headA || null == headB) { return null; } ListNode currHeadA = headA; ListNode currHeadB = headB; while (currHeadA != null) { mapListNode.put(currHeadA, currHeadA); currHeadA = currHeadA.next; } while (currHeadB != null) { if (mapListNode.get(currHeadB) != null) { return currHeadB; } currHeadB = currHeadB.next; } return null;} 若能知道两个链表的长度差为diffLen,然后让长度长的链表先走diffLen部,接着遍历两个链表。若找到 第一个相等的节点,\b即为相交的点。否则没有相交。\b时间复杂度O(n+m),空间复杂度为O(1)。 12345678910111213141516171819202122232425262728293031323334353637 private int getLinkedLen(ListNode head) { int len = 0; while (head != null) { ++len; head = head.next; } return len;} public ListNode getIntersectionNode3(ListNode headA, ListNode headB) { if (null == headA || null == headB) { return null; } int headALen = getLinkedLen(headA); int headBLen = getLinkedLen(headB); int diffLen = headALen - headBLen; ListNode currA = headA; ListNode currB = headB; if (diffLen > 0) { while (diffLen != 0) { currA = currA.next; --diffLen; } } else { while (diffLen != 0) { currB = currB.next; ++diffLen; } } // process two list has no intersection node while (currA != null && currA != currB) { currA = currA.next; currB = currB.next; } return currA == null ? null : currA;}","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"21. Merge Two Sorted Lists","date":"2018-11-17T16:00:00.000Z","path":"2018/11/18/arts/algorithm/21_Merge_Two_Sorted_Lists/","text":"Algorithm 本周做的算法题是 21. Merge Two Sorted Lists。 问题描述 合并两个有序单链表。例子: Input: 1->2->4, 1->3->4 Output: 1->1->2->3->4->4 思路 直接借鉴归并排序算法,时间复杂度O(min(n,m))。 1234567891011121314151617181920212223242526272829303132333435363738public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (null == l1) { return l2; } if (null == l2) { return l1; } ListNode res = null; ListNode newL = null; while (l1 != null && l2 != null) { int valInL1 = l1.val; int valInL2 = l2.val; if (valInL1 <= valInL2) { if (newL == null) { res = newL = l1; } else { newL.next = l1; newL = l1; } l1 = l1.next; } else { if (newL == null) { res = newL = l2; } else { newL.next = l2; newL = l2; } l2 = l2.next; } } if (l1 != null) { newL.next = l1; } if (l2 != null) { newL.next = l2; } return res;} 上面的代码比较啰嗦,能否简化一点,我们可以借助哑节点,进行处理。 123456789101112131415161718192021222324252627public ListNode mergeTwoLists2(ListNode l1, ListNode l2) { if (null == l1) { return l2; } if (null == l2) { return l1; } ListNode head = new ListNode(-1); ListNode res = head; while (l1 != null && l2 != null) { if (l1.val <= l2.val) { res.next = l1; l1 = l1.next; } else { res.next = l2; l2 = l2.next; } res = res.next; } if (l1 != null) { res.next = l1; } if (l2 != null) { res.next = l2; } return head.next;}","comments":true,"tags":[{"name":"alg","slug":"alg","permalink":"https://cloudfeng.github.io/tags/alg/"}]},{"title":"141. Linked List Cycle","date":"2018-11-10T16:00:00.000Z","path":"2018/11/11/arts/algorithm/141_Linked_List_Cycle/","text":"Algorithm 本周做的算法题是 141. Linked List Cycle。 问题描述 给定一个单链表,判断是否其是否存在环路。 思路 将要删除的节点之后向节点的值赋值到删除的节点移动,后续的节点补上,若删除是倒数第二个节点,时间复杂度为 O(n-1), 其中n为链表的长度。 1234567891011121314public void deleteNode(ListNode node) { if (null == node) { return; } while (node != null && node.next != null) { node.val = node.next.val; // delete duplicate last node, if (node.next.next == null) { node.next = null; } else { node = node.next; } }} 将要删除的节点的值替换为后面一个节点的值,然后删除节点下一个指针,指向删除节点的下一个节点再下一个节点。时间复杂度为 O(n)。 123456789101112public void deleteNode(ListNode node) { if (null == node) { return; } node.val = node.next.val; // node.next = node.next.next; ListNode nextNode = node.next; node.next = nextNode.next; nextNode = null;} Review: 每周一篇英文文章 Tip Share","comments":true,"tags":[{"name":"linkedlist","slug":"linkedlist","permalink":"https://cloudfeng.github.io/tags/linkedlist/"}]},{"title":"237. Delete Node in a Linked List","date":"2018-11-03T16:00:00.000Z","path":"2018/11/04/arts/algorithm/237_Delete_Node_in_a_Linked_List/","text":"Algorithm 本周做的算法题是 237. Delete Node in a Linked List。 问题描述 写一个函数删除单链表中得一个节点(非尾节点),并假定给定的节点是可以获取的。 例子:给定一个单链表:4 -> 5 -> 1 -> 9 若删除节点 5, 链表变为: 4 -> 1 -> 9 若删除节点 1, 链表变为: 4 -> 5 -> 9 思路 将要删除的节点之后向节点的值赋值到删除的节点移动,后续的节点补上,若删除是倒数第二个节点,时间复杂度为 O(n-1), 其中n为链表的长度。 1234567891011121314public void deleteNode(ListNode node) { if (null == node) { return; } while (node != null && node.next != null) { node.val = node.next.val; // delete duplicate last node, if (node.next.next == null) { node.next = null; } else { node = node.next; } }} 将要删除的节点的值替换为后面一个节点的值,然后删除节点下一个指针,指向删除节点的下一个节点再下一个节点。时间复杂度为 O(n)。 123456789101112public void deleteNode2(ListNode node) { if (null == node) { return; } node.val = node.next.val; // node.next = node.next.next; ListNode nextNode = node.next; node.next = nextNode.next; nextNode = null;}","comments":true,"tags":[{"name":"linkedlist","slug":"linkedlist","permalink":"https://cloudfeng.github.io/tags/linkedlist/"}]},{"title":"876. Middle of the Linked List","date":"2018-10-26T16:00:00.000Z","path":"2018/10/27/arts/algorithm/876_Middle_of_the_Linked_List/","text":"Algorithm 本周做的算法题是 876. Middle of the Linked List。 问题描述 给一个非空的链表,找到其中间节点。若有两个中间节点,返回第二个。例子: [1,2,3,4,5] 返回 3 [1,2,3,4,5] 返回 4 思路 将遍历链表,将元素放入一个数组arr里面,然后直接返回arr[len/2],时间复杂度是O(len)(链表长度),空间复杂度O(len)。 快慢指针法,快慢指针遍历链表,快的比慢多走一倍。快指针遍历完链表,慢指针就是中间节点。时间复杂度O(len)(链表长度),空间复杂度O(1)。 12345678910111213public ListNode middleNode(ListNode head) { if (null == head) { return null; } ListNode fastNode = head; ListNode slowNode = head; while (fastNode != null && fastNode.next != null) { fastNode = fastNode.next.next; slowNode = slowNode.next; } return slowNode;}","comments":true,"tags":[{"name":"linkedlist","slug":"linkedlist","permalink":"https://cloudfeng.github.io/tags/linkedlist/"}]},{"title":"ARTS-10月3周","date":"2018-10-18T16:00:00.000Z","path":"2018/10/19/arts/10m3w-summary/","text":"Algorithm 本周开始做链表类的题目,设计一个单链表。 Review: 每周一篇英文文章 本周阅读的英文信息如下: 标题:The Math Behind The 5-Hour Rule: Why You Need To Learn 1 Hour Per Day Just To Stay Relevant 来源:medium 作者为了说明5小时原则的必要性,从两个方面来说明: 所学的知识会不断的淘汰 所处时代高学历的人越来越多,竞争更为激烈 最后作者提出了一些如何进行5小时法则,重点就是利用时间的边角料,比如开车可以听音频啥的。但对于难的材料还是需要集中精力和时间处理。 读完之后,比较同意作者说法,特别是文章开头给出的一副图,此图还忽略了某些人学习的情况和我们自己遗忘知识的情况。但是漏了一个更为重要的原因:我们现在所处的时代面对太多了诱惑了,被很多外物所消费,比如电视剧、微博。注意力被割裂不成样,几乎没有任何一块整体时间来学习或者阅读。所以若要保持不变,你都需要努力学习。若你一直呆在原地,那你就会落后,会变成温水煮青蛙。","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"707. Design Linked List","date":"2018-10-18T16:00:00.000Z","path":"2018/10/19/arts/algorithm/A-707-Design-Linked-List/","text":"本周做一个单链表操作的题目。边写代码边测试,弄了近一个小时,中间有被中断,虽然一次就是acc了,但速度还是不够快。而且运行时间也不够快,分析了一下,可以使用头尾指针和长度运行时间会更快。比如插表尾法可以从O(n)降为O(1)。 这次给一个粗糙的算法。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127public class MyLinkedList { private class ListNode { public int val; public ListNode next; public ListNode(int x) { val = x; next = null; } } private ListNode myLinkedList; /** Initialize your data structure here. */ public MyLinkedList() { myLinkedList = null; } /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ public int get(int index) { if (null == myLinkedList) { return -1; } int i = 0; ListNode currNode = myLinkedList; while (currNode != null) { if (i == index) { return currNode.val; } currNode = currNode.next; ++i; } return -1; } /** Add a node of value val before the first element of the linked list. * After the insertion, the new node will be the first node of the linked list. **/ public void addAtHead(int val) { ListNode newNode = new ListNode(val); ListNode oldHeadNode = myLinkedList; newNode.next = oldHeadNode; myLinkedList = newNode; } /** Append a node of value val to the last element of the linked list. */ public void addAtTail(int val) { ListNode newNode = new ListNode(val); // first node if (null == myLinkedList) { myLinkedList = newNode; } ListNode preNode = null; ListNode currNode = myLinkedList; while (currNode != null) { preNode = currNode; currNode = currNode.next; } preNode.next = newNode; } /** Add a node of value val before the index-th node in the linked list. * If index equals to the length of linked list, the node will be appended to the end of linked list. * If index is greater than the length, the node will not be inserted. */ public void addAtIndex(int index, int val) { if (index < 0) { return; } if (0 == index) { this.addAtHead(val); return; } // linked list is empty, but index is greater than the length if (null == myLinkedList) { return; } ListNode preNode = null; ListNode currNode = myLinkedList; int i = 0; while (currNode != null) { if (i == index) { break; } preNode = currNode; currNode = currNode.next; ++i; } // when the index in linked list, then i == index, other is the length if (index == i) { ListNode newNode = new ListNode(val); newNode.next = preNode.next; preNode.next = newNode; } return; } /** Delete the index-th node in the linked list, if the index is valid. */ public void deleteAtIndex(int index) { if (index < 0) { return; } if (null == myLinkedList) { return; } ListNode preNode = null; ListNode currNode = myLinkedList; while (currNode != null) { if (0 == index) { if (preNode != null) { // find index node preNode.next = currNode.next; } else { // first node myLinkedList = currNode.next; } currNode = null; break; } preNode = currNode; currNode = currNode.next; --index; } }}","comments":true,"tags":[{"name":"Linked list","slug":"Linked-list","permalink":"https://cloudfeng.github.io/tags/Linked-list/"}]},{"title":"ARTS-10月2周","date":"2018-10-11T16:00:00.000Z","path":"2018/10/12/arts/10m2w-summary/","text":"Algorithm 本周继续做二分查找类型的题目:69.Sqrt(x)。 Review:The 2018 DevOps RoadMap 本周分享的文章来源是Medium,副标题:An illustrated guide to becoming a DevOps Engineer with links to relevant courses。本文主要根据kamranahmedse提供的DevOps图(如下),给出相应地学习视频,具体的相关的视频地址就不提供了,具体可以参考作者的原文。 Tip 本周分享一个linux中查看域名信息的命令:nslookup。 nslookup是 "name server lookup"的简写,顾名思义此命令可用于获取域名的信息。默认情况下,它可以将域名与IP地址相互解析。下面是一个简单地查看微软官网的域名信息的例子。思考:nslookup的运作原理是什么?与DNS服务有啥关系呢? 12345678910111213141516171819202122232425262728293031323334353637383940$ nslookup -type=any microsoft.comServer: 8.8.8.8Address: 8.8.8.8#53Non-authoritative answer:microsoft.com mail exchanger = 10 microsoft-com.mail.protection.outlook.com.Name: microsoft.comAddress: 104.40.211.35Name: microsoft.comAddress: 104.43.195.251Name: microsoft.comAddress: 191.239.213.197Name: microsoft.comAddress: 23.100.122.175Name: microsoft.comAddress: 23.96.52.53microsoft.com origin = ns1.msft.net mail addr = msnhst.microsoft.com serial = 2018101212 refresh = 7200 retry = 600 expire = 2419200 minimum = 3600microsoft.com nameserver = ns1.msft.net.microsoft.com nameserver = ns2.msft.net.microsoft.com nameserver = ns4.msft.net.microsoft.com nameserver = ns3.msft.net.Authoritative answers can be found from:microsoft.com nameserver = ns4.msft.net.microsoft.com nameserver = ns2.msft.net.microsoft.com nameserver = ns3.msft.net.microsoft.com nameserver = ns1.msft.net.microsoft-com.mail.protection.outlook.com internet address = 23.103.156.74ns1.msft.net internet address = 208.84.0.53ns2.msft.net internet address = 208.84.2.53ns2.msft.net has AAAA address 2620:0:32::53ns3.msft.net internet address = 193.221.113.53ns4.msft.net internet address = 208.76.45.53 Share 自问几个学习技术的问题 在做地铁的时候看到一篇文章,提出几个问题,觉得蛮有趣的也是自己需要反思的。 在工作之外,还会坚持技术学习吗? 如果会,一般通过哪些方式来学习? 针对你说的学习方式,可以给出详细的来源吗? 最近两周内,有没有哪些技术上的收获给你印象比较深? 上面几个问题,当时看到的时候会一脸懵的。后面慢慢想一会,给出自己答案。 在工作之外,还会坚持技术学习吗? 会学习,一直以为自己所做这一行是靠技艺吃饭的,不学习迟早会给\b自己留坑。 如果会,一般通过哪些方式来学习? 看书、网页等。 针对你说的学习方式,可以给出详细的来源吗? 看书,比较杂: java方面看TIJ,此书理论,\b补补自己的基础;\bl 算法第四版,学习基础的算法知识,配合leetcode做题; 育儿方面的书:法国妈妈的育儿经,讲述了\b一个美国人在法国养育孩子的书; 网页 看文档学习,比如spring 看Medium 最近两周内,有没有哪些技术上的收获给你印象比较深? 有,一个\b使用redis实现的登陆会话共享问题,\b使用ThreadLocal和封装request导致会话信息没有得到刷新。","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"69.Sqrt(x)","date":"2018-10-11T16:00:00.000Z","path":"2018/10/12/arts/algorithm/A-69-sqrt/","text":"题目描述 Implement int sqrt(int x). Compute and return the square root of x, where x is guaranteed to be a non-negative integer. Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned. Example 1: Input: 4 Output: 2 Example 2: Input: 8 Output: 2 Explanation: The square root of 8 is 2.82842…, and since the decimal part is truncated, 2 is returned. 思路 看到此题目的直觉想法就是使用牛顿迭代法求某个数的平方,关键思想就是使用直线逼近曲线。具体公式:Xn+1 = Xn - f(x)/f(x)'。做完之后,此题目在leetCode上是的标签是二分查找。若要使用二分查找,根据题目意思,low为1,high值最大为x/2+1,后面的思路就是直接借用二分查找算法的思想解题即可。 代码 牛顿法解题 12345678910111213141516/** * using Newton'Method to implement sqrt * @param x * @return */public int mySqrtByNT(int x) { if (x <= 0) { return x; } double diff = 1e-9; double guess = 1.0; while (Math.abs((guess * guess - x)) > diff) { guess = (guess + x / guess) / 2.0; } return (int)guess;} 二分查找法解题 123456789101112131415161718192021222324252627/** * using binary search to implement sqrt * @param x * @return */ public int mySqrtByBinarySearch(int x) { if (x <= 0) { return x; } if (x < 4) { return 1; } int lo = 1; int hi = x / 2 + 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; int tmp = x / mid; if (tmp == mid) { return mid; } else if (tmp > mid) { lo = mid + 1; } else { hi = mid - 1; } } return hi; }","comments":true,"tags":[{"name":"Binary Search","slug":"Binary-Search","permalink":"https://cloudfeng.github.io/tags/Binary-Search/"}]},{"title":"ARTS-10月1周","date":"2018-10-06T16:00:00.000Z","path":"2018/10/07/arts/10m1w-summary/","text":"Algorithm 本周继续做二分查找类型的题目:35. Search Insert Position。 Review:Introduction to Java Bytecode 本周分享的文章来源是DZone,标题:Introduction to Java Bytecode。文章首先介绍了JVM的数据类型,不同的数据类型对字节码指令也不同;接着介绍了基于栈架构的java运行时内存区域以及相关的构成,比如使用下图中解释stack元素frame的构成;后面就是几个简单例子,但是作者图文并茂的解释,十分通俗易懂,推荐阅读。 Tip 分享一个查看java字节码的命令和相关的文档。 命令:javap -v xxx.class 字节码文档:The Java Virtual Machine Instruction Set Share 带新人的反思(一) 前段时间带着刚毕业没多久的小伙伴,有些小思考。我个人的性格比较急躁,所以在整个过程中会出现下面这种对话: 我:“之前不是跟你讲过流程和业务逻辑么?” 小伙伴:“我再看看吧” 我,语气开始加重并且声音调高:“你到底在干嘛呀,事情都说过很多次” 小伙伴心情沮丧并且声音开始沙哑了。 慢慢地心里面就开始存在一些疑问了: 带新入职的同事是否值得自己去带? 是否需要复合一定的条件或者基础呢? 如何去带? 作为指导者,需要怎样的心态呢?如何调节自己呢? 个人思考 带新入职的同事是否值得自己去带? 是否需要复合一定的条件或者基础呢? 毕竟不是自己招人进来的,招聘流程中缺少足够的沟通,只看工作经验,很多时候并没有 考察面试者的陈述问题的能力。但很多时候会忽略这个条件,特别是在版本要上线的时候。 记得上次带新人做需求的时候,每次都是主动问他做得怎么样了,是否遇到什么难题。每次问都说没有,但是在上线的时候,就出现各种问题,那会忽略新人的基础,忽略新所做的工作,自己会直接“撸起袖子”开始干。 如何去带? 不知道是引导还是处处细细指导?按理来说应该是引导为主,然而我却是细细指导,弄得自己超级累。而且效果不太好。新人的态度和基础让人不舒服,所以心里多少有些偏见。 作为指导者,需要怎样的心态呢?如何调节自己呢? 最为关键的是心态,首先要放开手,让其去做,让其去负责。还有就是需要培养自己的耐心。","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"35. Search Insert Position","date":"2018-10-06T16:00:00.000Z","path":"2018/10/07/arts/algorithm/A-35-Search-Insert-Position/","text":"题目描述 Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. You may assume no duplicates in the array. Example 1: Input: [1,3,5,6], 5 Output: 2 Example 2: Input: [1,3,5,6], 2 Output: 1 Example 3: Input: [1,3,5,6], 7 Output: 4 Example 4: Input: [1,3,5,6], 0 Output: 0 思路 看到此题目的直觉想法就是使用二分查找法解决,然后就是根据上面的例子,好好在草稿子上画一画就可以搞定了。 代码 123456789101112131415161718192021222324public int searchInsert(int[] nums, int target) { if (null == nums || nums.length == 0) { return 0; } if (nums[0] > target) { return 0; } if (nums[nums.length - 1] < target) { return nums.length; } int lo = 0; int hi = nums.length - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] > target) { hi = mid - 1; } else { lo = mid + 1; } } return lo; }","comments":true,"tags":[{"name":"Binary Search","slug":"Binary-Search","permalink":"https://cloudfeng.github.io/tags/Binary-Search/"}]},{"title":"每周ARTS分享第12期","date":"2018-09-08T16:00:00.000Z","path":"2018/09/09/arts/9m2w-summary/","text":"Algorithm: 每周至少做一个leetcode的算法题 Review:情商 阅读了两篇关于情商的文章,自己是一个比较易怒的人。稍有点不顺心就会表现出来,而且说话也 是口无遮拦的。刚好学习一下如何管理自己的情绪,提高一下。文章给了三个步骤: 明确情绪 解释情绪 管理情绪 总之,就是不断反思自己的行为和想法。 整理的思维导图 来源: Medium:How To Improve Emotional Intelligence Medium:5 simple Hacks to sharpen your emotional intelligence Tip:前端开发工具 跟着教程在学习前端的基础知识,看到几个好的工具或者网站 检验HTML语法: Markup Validation Service 前端开发参考:CodePen Share:蔡志忠:努力是没有用的 今日读了《蔡志忠:努力是没有用的》。 一看标题明显就是一个标题党是不?但是阅读看完整个文章之后,你就会有自己的感觉, 也会想起一个观点:刻意练习和全身心工作。读完之后有几点感触分享给大家: 学会自己找问题的答案,而不是仅仅从老师那里。 人生不是走斜坡,你持续走就可以走到巅峰;人生像走阶梯,每一阶有每一阶的难点,学物理有物理的难点,学漫画有漫画的难点,你没有克服难点,再怎么努力都是原地跳。所以当你克服难点,你跳上去就不会下来了。 找到自己擅长的,然后all in。","comments":true,"tags":[]},{"title":"ARTS-8月1周","date":"2018-08-04T16:00:00.000Z","path":"2018/08/05/arts/8m1w-summary/","text":"8月1周ARTS总结 分享第7个ARTS的内容: 1.A:189.Rotate Array 2.R:PM的12条经验 3.T:Nginx静态资源服务器无法加载CSS文件 4.S:心理账户","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"Algorithm:189. Rotate Array","date":"2018-08-04T16:00:00.000Z","path":"2018/08/05/arts/algorithm/A-189-Rotate-Array/","text":"问题 Given an array, rotate the array to the right by k steps, where k is non-negative. Example 1: Input: [1,2,3,4,5,6,7] and k = 3 Output: [5,6,7,1,2,3,4] Explanation: rotate 1 steps to the right: [7,1,2,3,4,5,6] rotate 2 steps to the right: [6,7,1,2,3,4,5] rotate 3 steps to the right: [5,6,7,1,2,3,4] Example 2: Input: [-1,-100,3,99] and k = 2 Output: [3,99,-1,-100] Explanation: rotate 1 steps to the right: [99,-1,-100,3] rotate 2 steps to the right: [3,99,-1,-100] Note: Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem. Could you do it in-place with O(1) extra space? 解题思路 直接根据编程珠玑一书中得思路而来的,双手旋转法,具体如下: 12345678910111213141516171819public void rotate(int[] nums, int k) { if (null == nums || nums.length <= 0 || k <= 0) { return; } k = k % nums.length; reverse(nums, 0, nums.length - 1); reverse(nums, 0, k - 1); reverse(nums, k, nums.length - 1);}private void reverse(int[] nums, int start, int end) { while (start < end) { int tmp = nums[start]; nums[start] = nums[end]; nums[end] = tmp; ++start; --end; }} 分析 时间复杂度为O(n),空间复杂度为O(1);","comments":true,"tags":[{"name":"algorithm","slug":"algorithm","permalink":"https://cloudfeng.github.io/tags/algorithm/"},{"name":"array","slug":"array","permalink":"https://cloudfeng.github.io/tags/array/"}]},{"title":"Review - Observations on Product Management","date":"2018-08-04T16:00:00.000Z","path":"2018/08/05/arts/review/R-Obserations-on-PM/","text":"概述 本周分享的文章来自于Airbnb一位前PM,Dan Hill 关于PM的12条经验。觉得蛮有趣的,分享给大家。 12条PM经验 The job of Product Management is to deliver good products to end users. The sheer number of possible definitions of good, product and user mean there’s no standard look to a Product Manager. But if you don’t deliver, the product is not good, or no-one uses it, you’ve done it wrong. Your product, especially if you manage people directly, is not the product. It’s the process that builds the product. Designers output interfaces/interactions, engineers output code, product managers output process. ‘Process’ includes the frameworks for reasoning about a problem, how the team interacts and communicates, expectations of the product, expectations of the team, timelines, what success and quality look like, how decisions are made. Process is not inherently bad. There’s good process and bad process. Good process is any that allows the team to produce better work faster, with joy and elegance. Bad process is anything else. Be wary of people who hate process. Sometimes they have been burned by bad process, often though they fear process is a) the same as bureaucracy or b) will limit their contributions. If you are a new Product Manager to a team that hasn’t worked with one before, the first order of business is proving to the team that your process increases their contributions and joy, not decreases it. It almost doesn’t matter what the product is. The greatest attribute of a Product Manager is a strongly developed ability for metacognition. First, to be able to introspect oneself and see how your words, actions, choices are affecting an outcome. Second, to introspect a process and understand why it is or is not working. Most Product Managers (and everyone probably) spend too little time thinking about how to solve a problem. They jump straight into solving it. Problems come in different shapes, and not all need the same process. The process that shipped the last product is unlikely to be the one that you need for the next. It is easier to macrobullshit than to microbullshit. It is impossible to bullshit good engineers, designers, data scientists, researchers. If the team isn’t calling you out on something fairly regularly, you’re doing it wrong. Where there is skill to move mountains, there is no need for the faith that moves mountains. Faith cannot last — eventually someone has to build something. Incremental development and vision are not orthogonal; they both require the other. All product must start with a vision — a point of view — but then be built critically step by step. It’s ok to learn something new as you go. Being data-driven is not vision. People who cling to being data-driven rarely create anything new or interesting. I also personally find it hard to explain why to them.","comments":true,"tags":[{"name":"review","slug":"review","permalink":"https://cloudfeng.github.io/tags/review/"},{"name":"PM","slug":"PM","permalink":"https://cloudfeng.github.io/tags/PM/"}]},{"title":"Share:5分钟商学院-心理账户","date":"2018-08-04T16:00:00.000Z","path":"2018/08/05/arts/share/S-five-mins-buss-accout/","text":"心理账户概念 我们会把钱分门别类的存在不同的心理账户里面。比如说,生活必要的开支账户,家庭建设和个人发展账户,情感维系账户,享乐休闲账户等等。虽然这些账户都是在一个大账户之下,但其实各个子账户都是独立存在的。 例子 听音乐会前,丢失了200块。当丢失的是价值200块的公交卡时,大多数人会选择继续前往观看;当丢失的是打算用来购买音乐会门票的200块时,大多数人选择不去看了。 总结 我们还有时间账户,空间账户等等账户。有的账户可供他人使用,有些绝对不行。只有替自己利益最大化之后才会心动,花费相关的账户。 注意:5分钟商学院的内容来自得到刘润老师的课程","comments":true,"tags":[{"name":"share","slug":"share","permalink":"https://cloudfeng.github.io/tags/share/"},{"name":"消费心理学","slug":"消费心理学","permalink":"https://cloudfeng.github.io/tags/%E6%B6%88%E8%B4%B9%E5%BF%83%E7%90%86%E5%AD%A6/"}]},{"title":"Nginx 不能解析CSS文件","date":"2018-08-04T16:00:00.000Z","path":"2018/08/05/arts/tip/T-nginx-css-not-interpreted/","text":"问题描述 最近学习在补前端知识了,使用Nginx做静态内容服务器1。遇到了一个小问题: Resource interpreted as Stylesheet but transferred with MIME type text/plain: “http://localhost/web-projects/the-html-head/styles/styles.css” \b解决方法 \b若不对于css文件解析进行配置,nginx默认文件都是text/plain类型进行解析2,为此我们需要对此进行 简单配置。将下面这段代码放入到location模块下面,然后重启nginx。记住一定要清理浏览器的缓存。 12include mime.types;default_type application/octet-stream; 参考资料 1.http://nginx.org/en/docs/beginners_guide.html ↩2.https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Configuring_server_MIME_types ↩","comments":true,"tags":[{"name":"linux","slug":"linux","permalink":"https://cloudfeng.github.io/tags/linux/"},{"name":"tip","slug":"tip","permalink":"https://cloudfeng.github.io/tags/tip/"}]},{"title":"ARTS-7月4周","date":"2018-07-28T16:00:00.000Z","path":"2018/07/29/arts/7m4w-summary/","text":"7月4周ARTS总结 说出的诚诺,需要好好执行,做到。正如刘润老师所说:经营自己。补上六周的ARTS,分享第六个ARTS的内容: 1.A:Algorithm:88.Merge Sorted Array 2.R:日记法则:TLC 3.T:Oracle插入数据报日期数据无效 4.S:本周开始分享刘润的5分钟商学院的内容,经营自己","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"Algorithm:88.Merge Sorted Array","date":"2018-07-28T16:00:00.000Z","path":"2018/07/29/arts/algorithm/A-258-add-digits/","text":"问题 Given a non-negative integer num, repeatedly add all its digits until the result has only one digit1. Note: The number of elements initialized in nums1 and nums2 are m and n respectively. You may assume that nums1 has enough space (size that is greater or equal to m + n) to hold additional elements from nums2. Example: Input: 38 Output: 2 Explanation: The process is like: 3 + 8 = 11, 1 + 1 = 2. Since 2 has only one digit, return it. Follow up: Could you do it without any loop/recursion in O(1) runtime? Level: Easy 看完题目,之后列举了几个。分别是\b小于9的,\b是9的倍数,不是9的倍数。进行归纳如下: n < 9 直接返回n; n mod 9 = 0 直接返回9, 比如n=18 n mod 9 != 0 直接返回 n mod 9 \b后面看到讨论中,说是 digit root problem2。 根据上述的分析,\b可以直接写出解法,具体如下: 12345678910public int addDigits(int num) { if (num < 10) { return num; } int result = num % 9; if (result != 0) { return result; } return 9;} 分析 时间复杂度为O(1); 相关资料 1.https://leetcode.com/problems/add-digits/description/ ↩2.https://en.wikipedia.org/wiki/Digital_root ↩","comments":true,"tags":[{"name":"algorithm","slug":"algorithm","permalink":"https://cloudfeng.github.io/tags/algorithm/"},{"name":"math","slug":"math","permalink":"https://cloudfeng.github.io/tags/math/"}]},{"title":"Review - A Better, Easier Way to Journal","date":"2018-07-28T16:00:00.000Z","path":"2018/07/29/arts/review/R-Better-easier-way-to-journal/","text":"概述 作者提出了TLC法则来记录每天的生活记录,其中TLC: Thank:具体到事件,不要泛泛而谈。目的就是为了让自己生活充满希望和感恩之心 Learn:不仅学习书本上知识,还要从每个人身边去学习,去发现; Connect:连接以前学习的知识;连接所接触的人。 TLC 法则 具体的TLC法则描述如下: T:Thank What are you thankful about today? Be specific. So talk about some event that happened to you today that you are grateful about. There’s no escape from the things to be thankful for in your life each day. Make the effort to seek them out, think about them, and express a brief thanks. It will make your daily experience that much richer. 【】 L:Learn What did you learn today? You learn something every day, especially if you interact with other people. you can learn something from everybody you meet each day, and if you don’t think so, you’re not trying. C:Connect What things did you connect? In other words, what concepts did you make an analogy between or otherwise find an intellectual string tying one to the other? Learning is all about connecting things in your mind. Take new information, and tie it in to well-worn knowledge. Find similarities and patterns. With whom did you connect? What conversations did you have, what were they about? What was the takeaway from each? What is that person excited about? What can you talk with them about in the future? What could you work on with them?","comments":true,"tags":[{"name":"rule","slug":"rule","permalink":"https://cloudfeng.github.io/tags/rule/"},{"name":"review","slug":"review","permalink":"https://cloudfeng.github.io/tags/review/"}]},{"title":"Share:刘润-5分钟商学院","date":"2018-07-28T16:00:00.000Z","path":"2018/07/29/arts/share/S-five-mins-buss-start/","text":"背景 在得到上买了刘润的《5分钟商学院》,一直没有怎么看。借助 ARTS 每周分享一下,所读的笔记。 5分钟商学院开篇 开篇的内容主要讲解事人人都要开始经营自己,要对自己负责,时间长度是一生。相关的摘句如下: 你必须像经营公司一样经营自己:构建自己的协作关系、塑造自己的产品和服务、呵护自己的名声、把注意力投放到产出更高的地方。 你“自己”这家公司是无限责任。你需要用自己一生的时间和信用来为它担保。 总结:关注自己的成长,好好经营自己。","comments":true,"tags":[{"name":"share","slug":"share","permalink":"https://cloudfeng.github.io/tags/share/"},{"name":"5分钟商学院","slug":"5分钟商学院","permalink":"https://cloudfeng.github.io/tags/5%E5%88%86%E9%92%9F%E5%95%86%E5%AD%A6%E9%99%A2/"}]},{"title":"Oracle插入数据报错","date":"2018-07-28T16:00:00.000Z","path":"2018/07/29/arts/tip/T-oracle-date-error/","text":"背景 最近接手一个老系统,DB使用的是Oracle,很多流程都是用了存储过程和函数。现在不是很熟悉,虽然以前在大学的时候学习Oracle,后面再也没有碰过。现在感慨,知识学了没有用,但一旦需要用到的时候,会发现至少了解一点,不至于那么被动。所以多学习一点技术或者其他生活知识,都是十分必要的,说不定哪天就用上了。 问题描述 在看书的时候,敲入书中的代码,一条简单插入语句,如下: 1INSERT INTO EMP VALUES (7521, 'WARD', 'SALESMAN', 7698,TO_DATE('22-FEB-1981', 'DD-MON-YYYY'), 1250, 500, 30); 提示:ORA-01843: 无效的月份 \b解决方法 看了一下Oracle中关于 TO_DATE函数的文档1 The default date format is determined implicitly by the NLS_TERRITORY initialization parameter or can be set explicitly by the NLS_DATE_FORMAT parameter. 在Datetime格式2中也进行了说明: The default datetime format template is specified either explicitly with the initialization parameter NLS_DATE_FORMAT or implicitly with the initialization parameter NLS_TERRITORY. You can change the default datetime formats for your session with the ALTER SESSION statement. 方法一: 1INSERT INTO EMP VALUES (7521, 'WARD', 'SALESMAN', 7698,TO_DATE('22-FEB-1981', 'DD-MON-YYYY','NLS_DATE_LANGUAGE = American'), 1250, 500, 30); 但是需要修改\b每条语句,比较麻烦,\b方法二可以批量搞定。 方法二: 修改当前\b会话的所\b使用的语言,\b具体如下,然后再执行所有的插入语句。 1alter session set nls_date_language='american' 参考资料 1.https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions183.htm ↩2.https://docs.oracle.com/cd/B28359_01/olap.111/b28126/dml_commands_1029.htm#OLADM780 ↩","comments":true,"tags":[{"name":"linux","slug":"linux","permalink":"https://cloudfeng.github.io/tags/linux/"},{"name":"tip","slug":"tip","permalink":"https://cloudfeng.github.io/tags/tip/"}]},{"title":"ARTS-7月3周","date":"2018-07-21T16:00:00.000Z","path":"2018/07/22/arts/7m3w-summary/","text":"7月3周ARTS总结 希望自己继续保持下去,分享第五个ARTS的内容: 1.A:按照类型做算法题,并复习算法。来源是leetco的算法题目Algorithm:88.Merge Sorted Array,倒着来的归并排序。 2.R:本周试着翻译RedisLock:Redis中的分布式锁,是关于Redis分布式锁实现。 3.T: 继续学习Linux,Linux 文件基本属性笔记 4.S: 开始学习Python,Python 列表操作","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"Algorithm:88.Merge Sorted Array","date":"2018-07-21T16:00:00.000Z","path":"2018/07/22/arts/algorithm/A-88-merge-sorted-array/","text":"问题 Description:Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. Note: The number of elements initialized in nums1 and nums2 are m and n respectively. You may assume that nums1 has enough space (size that is greater or equal to m + n) to hold additional elements from nums2. Example: Input: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 Output: [1,2,2,3,5,6] Level: Easy 解题思路 初看题目很简单,而且一看就是归并排序。\b\b所以开始想着就是顺着归并排序的思路来做,也就是从头开始 遍历;会发现\b需要移动\b数组nums1,导致时间复杂度超过 O(n+m),不满足归并排序。后面想想,\b居然形成了思维定 势。\b为了满足O(n+m),可以从后面开始遍历,\b只是我们学习的归并排序\b都是从\b头开始,而不是从尾部开始。一旦由从 尾部开始遍历的x想法,问题就解决了。具体见下面的详细代码。 \b详细代码如下: 123456789101112131415public void merge(int[] nums1, int m, int[] nums2, int n) { int i = m-1; int j = n-1; int total = m + n - 1; while (i >= 0 && j >= 0) { if (nums1[i] >= nums2[j]) { nums1[total--] = nums1[i--]; } else { nums1[total--] = nums2[j--]; } } while (j >= 0) { nums1[total--] = nums2[j--]; }} 分析 时间复杂度为O(n+m),其中n为排序数组的长度;","comments":true,"tags":[{"name":"algorithm","slug":"algorithm","permalink":"https://cloudfeng.github.io/tags/algorithm/"},{"name":"array","slug":"array","permalink":"https://cloudfeng.github.io/tags/array/"}]},{"title":"Share:Redis中的分布式锁","date":"2018-07-21T16:00:00.000Z","path":"2018/07/22/arts/review/R-Distributed-locks-with-redis/","text":"概述 在多核处理器中,互斥性访问共享资源的环境下,分布式锁是一项十分有用的手段(技术)]。有很多库和博客都有描述基于Redis如何实现一个分布式锁管理器(DLM:Distributed Lock Manager),但是每个库都使用不同的方案,相比复杂方案,简单方案有较低保障。 本文试图提供一个经典的方法实现基于Redis的分布式锁。我们提出一个算法,称之为 Redlock,它实现一个DLM。我们相信它比普通的单个实例方法更为安全。我们希望,社区分析它并且提供反馈,同时希望基于Redislock实现或者更为复杂或者其他可选的方案。 实现 在描述Redlock算法之前,列举目前基于Redis设计的分布式锁,具体如下: Redlock-rb (Ruby 实现)。 有一个 Redlock-rb的fork,它新增gem便于发布或者更多 Redlock-py (Python 实现)。 Aioredlock (Asyncio Python 实现)。 Redlock-php (PHP 实现)。 PHPRedisMutex (further PHP 实现) cheprasov/php-redis-lock (PHP 库) Redsync.go (Go 实现)。 Redisson (Java 实现)。 Redis::DistLock (Perl 实现)。 Redlock-cpp (C++ 实现)。 Redlock-cs (C#/.NET 实现)。 RedLock.net (C#/.NET 实现), 包括异步和支持锁的扩展。 ScarletLock (C# .NET 实现 with configurable datastore) node-redlock (NodeJS 实现),支持锁的扩展。 安全性和活跃性保证 从我们自己的视角来看,构建我们的设计模型具有三个特性,最低限度地保证高效使用分布式锁。 安全性:互斥性,在任何时刻,仅有一个客户端能否获得锁。 活跃性A:无死锁。不管客户端锁定资源宕或者被分割,最终就可能获得一个锁, 活跃性B:容错性。只要有大多数Redis节点正常工作,客户端就可以获得和释放锁。 为何基于故障转移1实现不够 让我们分析一下目前基于Redis分布式锁库的工作,以便了解我们所做的改进。 使用Redis锁住资源最为简单的方法是在一个实例中创建一个key。所创建的key通常带有一个过期时间,使用了Redis过期的特性,因此它最终会释放锁(安全性和活跃性保证的第二点)。若客户端需要释放资源,只需要删除此key即可。 粗看起来,此机制很好,但是存在一个问题:在我们的体系架构中是一个单点故障。Redis主节点宕了会发生什么? 当然,我们可以加入一个从节点!并且在主节点不可用时使用从节点。不幸的是,此方案不可行。这么做会导致我们不能实现互斥安全性,是因为Redis主从复制是异步的。 使用上述的模型存在一个很明显的竞态条件,如下: 客户 A 在主节点获取一个锁; 主节点在客户 A 锁住资源的key没能及时写入到从节点情况下宕了; 从节点被选举为主节点; 客户 B 获得客户A锁住的资源的锁。安全性被破坏! 在特定的场景下,比如在失败的场景下,多个客户端能在同一时刻获得同一个锁,此模型是很好的。如果上述场景符合你的需求,你可以使用基于主从复制解决方案。否则,我们建议实现本文中描述的方案。 单实例正确实现方式 在我们尝试解决上述单实例实现方案缺陷之前,如何在此场景下,如何确保它是正确的。当竞态条件可以接收的时候,此方案是一个可行的方案。另外,在单实例中锁机制是我们在本文中描述分布式算法的基础。为了获得锁,可以使用下面的命令:SET resource_name my_random_value NX PX 30000 此命令会在当key不存在的时候(NX 选项),就会创建一个key,带有一个30000毫秒过期时间(PX 选项)。与key关联的值为“my_random_value”。此值在所有客户端和所有请求锁中,必须是唯一的。使用随机值是为了安全释放锁,使用脚本告诉Redis:只有在key存在并且对应的value值是我们期待的时候才删除它。下面实现此方案的Lua脚本: 12345if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1])else return 0end 为了避免误释放其他线程创建的锁是十分重要,比如一个客户端可能获得锁,因某操作所用时间超过锁有效时间(key失效的时间)而阻塞,而另外一个线程在此期间获得锁,然后删除此锁(注:把相同的key删除)。所以,仅仅使用DEL命令是不安全的,会删除其他客户端获得的锁。使用上述脚本,判断每个锁被一个随机的字串串做了标记,所以只有设置此key的客户端才能删除此锁。 随机字符串的值是什么?我假定是来自 /dev/urandom的20字节,不过你也可以使用其他的方式生成一个唯一的值。比如 在/dev/urandom中安全的挑选RC4种子,然后生成一个伪随机流。一个简单地方案:以毫秒为精度的unix系统时间拼接客户端ID,组成一个随机字符串的可以。虽然不完全安全,但在很多场景下是可以使用的。 key有效时间称之为“锁的有效时间”。它既是自动释放锁的时间,同时也是客户端在另外一个客户端再次获得此锁之前的操作时间,然而并没有在技术上确保互斥性,仅仅是给客户端获得锁的一个窗口时间。 到目前为止,由单个一直可用的实例构成的非分布式系统上,我们有一个好的方法获取和释放锁。让我们把此概念扩展到分布式系统中,而在分布式系统中,我们不能确保给出安全性和活跃性的保障。 Redlock算法 算法的分布式版本,我们假定有N个Redis主节点,这些节点完全独立,因此我们不使用复制或者其他隐式协作系统。我们已经描述了单节点下,如何安全地获得和释放锁。同时也保证了单实例中使用此方法获得和释放锁安全性和活跃性。示例中,我们假定N=5,它是一个合理的值。【为啥这么说?】因此,我们在不同机器或者虚拟机上运行5个Redis主节点,以确保他们若是宕了也是完全独立。 为了获得锁,客户端需要进行如下操作: 获得当前时间,以毫秒计;即为currentTimeInMs N个实例使用相同的key和随机值,顺序地尝试获得锁。在此步中,每个实例获取锁时,客户端会用一个超时时间,它比所有锁自动释放的时间小。比如,若自动释放锁时间为10s,那么超时时间在5ms~50ms之间。这样做事为了避免客户端为了等待一个宕机Redis节点,长时间处于阻塞状态:若一个实例不可用,我们会尽快与下一个实例“通话”。 客户端计算获得锁的时间,通过减去步骤1得到的当前时间。即为acqTimeInMS = acqLockCurrentTimeInMS-currentTimeInMs。当且仅当客户端从多个实例获得锁,设为n(n >= N/2+1);并且总共获得锁的时间为totalAcqTime,如果totalAcqTime小于锁的有效时间(intitValidtyTimeInMS),那么就认为客户端获得了锁。 若锁获得,那么有效时间validtyTimeInMS,可以认为是初始的有效时间减去步骤2中计算获得锁耗费的时间。 如果客户端因某种原因不能获得锁失败(不管客户端不能锁住N/2+1实例还是有效时间为负数),它将释放所有的实例(包括哪些客户端并没有获得锁的实例)。 所有步骤的伪代码如下: 123456789101112131415161718if n < N/2+1 then noendfor i : n then acqTimeInMS = acqLockCurrentTimeInMS-currentTimeInMs totalAcqTime += acqTimeInMSendvalidtyTimeInMS = intitValidtyTimeInMS - totalAcqTimeif validtyTimeInMS > 0 then yeselse noend if no then unlock all instance 算法是异步的吗? 算法所依赖的假设,一个是进程之间没有同步时钟,每个进程中本地时间按照近似相同频率;另外一个是自动释放时钟存在一点小误差。这些假设近似一台真实地电脑:每台电脑都有本地时钟,同时我们一般依赖不同电脑之间存在一点时钟偏移。 基于此,我们需要制定我们互斥性规则:只要确保客户端所占锁将在锁有效时间(第三步所得)减去一定的时间(为了弥补进程间的是时钟偏移)。 有关相似系统需要一定的时钟偏移更多信息,可以参考此论文:Leases: an efficient fault-tolerant mechanism for distributed file cache consistency。 【疑问:依赖于系统时间,并非是个好事情,万一被改了呢?】 失败重试 当一个客户端不能够获得锁,它会等待一个随机时延之后重新获取。这么做事为了缓解多个客户端在同一时刻对同一个资源同时加锁(此种情况下可能会导致一个脑裂2问题使得谁也不能取胜)。另外,客户端从多个Redis实例获取锁的时间越快,脑裂发生的机会就越小。因此在理想情况下,客户端采用多路复用技术在同一时刻给N个实例发送SET命令。 需要强调的是,当客户端获取大部分的锁失败时,应尽快释放所获得的锁是如此的重要。是因为没有必要等待key过期之后再去获取锁(然而,如果发生网络分区和客户端不能与Redis实例通信,对于等待key过期,将会有一点可用性的惩罚)。 释放锁 释放锁十分简单,仅涉及到在所有实例中是否锁,不管怎么样,客户端相信能够成功地锁住一个实例。 安全性讨论 此算法是否安全?我们从不同场景来理解。 首先我们假设一个客户端已经从多个实例中获得锁。因此,所有的实例都包含一个相同时效的key。然而,key在不同的时刻设置的,所以会在不同的时间失效。若第一个key在T1时刻设置(假设时间在连接第一个实例),最后一个key在T2时刻设置(从最后一个实例返回的时间);那么第一个key至少在MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT之后时效,其他key将在MIN_VALIDITY之后失效。因此我们所有的key时效时间至少为MIN_VALIDITY。 在key设置期间,另外一个客户端不能获得锁,是因为N/2+1key已经存在,导致 N/2+1节点操作SET NX命令不会成功。所以,若一个锁已经被占用,那么在同一个时刻是不能被其他客户端占用的。(验证了互斥性) 然而我们也了解,多个客户端在同一时刻试图获取锁,不会相继成功的。若一个客户端锁住大多数实例耗费了相近,甚至比最大有效时间还大(使用SET命令设置的TTL),可以认为锁是失效的,并且会释放所有实例的锁,因此我们仅需要考虑的场景是一个客户端锁住大多数实例所用的时间比TTL小的情况。对于此场景,我们在上面已经计算了MIN_VALIDITY。在MIN_VALIDITY时间内,没有客户端能够重新获得锁。所以,多个客户端会在同一个时刻锁住N/2+1实例,若MIN_VALIDITY大于TLL,则锁是失效的。 你能为此提供一个形式化的证明或者指出现有那些类似的算法或者找出一个不管,我们为此十分感激。 活跃性讨论 系统活跃性基于下面三个主要特性: 锁的自动释放(因为key失效):keys最终可以重新被锁住。 事实上,客户端会在获得锁失败时溢出锁,或者当锁被占用以及工作结束时,我们也没有必要等待锁失效才重新获取锁。 实际上,当一个客户端需要重新获得锁,它会等待一小段时间,此时间会比获得大多数锁的时间要长,以便减少在资源竞态下发生脑裂问题的概率。 在发生网路分区是,我们加罚一个等于TLL的惩罚时间,所以若是有持续的分区,我们可以让惩罚时无限大。此时,每个客户端移除锁之前,获取锁并且被分区化。 一般情况下,若长时间的网络分区,也会导致系统变得不可用。 性能、宕机恢复、文件同步 很多用户使用Redis作为一个锁服务器,获取锁和释放锁都是高性能,低时延的。同时,也需要在每秒中多次获取、释放锁。为了满足此需求,采用多路复用策略与N个Redis服务器通信以便减少时延(或者假定每个客户端与实例之间的RTT都相近,弱化多路复用以非阻塞的方式发送socket,发送所有的命令以及后面读取所有的命令)。 然而,如果我们想要实现“崩溃-恢复”系统模式,需要考虑另外一个持久化功能。 在此我们来看问题所在,我们假定Redis没有配置持久化,一个客户端从5个实例中获取了3个实例的锁,当其中某一台需要重启之后获得锁,此时还是从3个实例锁住相同的资源,其他客户端若能获得锁,那么将违背锁的互斥性原则。 假如我们设置了AOF持久化,事情就会变得好一点了。比如,我们通过发送 SHUTDOWN 命令和重启一台Redis服务器。因为Redis失效在语义上是实现的,所有当Redis服务停机时,key的有效时间也一直再慢慢流逝,所有的要求都是满足的。然而,只要Redis服务是干净的停下,那么一切都是好的。当断电的情况下又如何呢?若在默认情况下,配置Redis,让其每秒将数据同步到磁盘,那么重启一次服务,key就可能会丢失。理论上,若要不管任何情况下实例重启都保证锁是安全的,我们需要设置持久化模式为: fsync=always。这样的话,相比于同级别的分布式锁系统,性能会受到严重损害。 然而,初看起来事情比看起来要好一些。只要一个实例因崩溃后重启不在参与到任何一个活动的锁,那么当前活动锁集合会被实例锁住的而不是重新加入系统的实例占用,从保证了算法安全性。 为了保证这一点,我们一个实例,在比最大的TLL时间后仍然不可用,其中TLL是所有key有效时间,那么此实例就变得非法并且自动释放所有资源。 在不使用任何Redis持有化,使用延时重启在一般情况下能够实现安全性。但是需要注意的是,会转变为可用而受到惩罚时间。如果很多节点崩溃,系统将在TLL时间内变得全部不可用(全部的意思在TLL内,没有任何资源被锁住)。 让算法更可靠:扩展锁 如果客户端的工作是由一些小步骤组成的,那就可能使用默认的限制时间的小型锁,并实现锁扩展机制的算法。一般来说,如果客户端在计算过程的中途,而锁的有效期抵达了一个较小的值,那么就可能扩展锁机制。通过发送一个Lua脚本,通知所有实例,若key存在且对应的value值仍旧就是客户端获得锁之前的值,就让它们延长对应此Key的TTL值。 客户端仅需要考虑锁的重新获取,若客户端扩展锁可能在短时间内有大量的实例获得此锁。 虽然这并不是从技术的角度改变算法,因此重新获得锁的最大值是要限制的,否则一个活跃性就会遭到破坏。 需要帮助? 如果你对分布式系统有深入的研究,若能提供你的观点或者分析,十分不错的。若能同时使用其他语言实现,也是不错的。在此表示感谢。 Redlock 的分析 Martin Kleppmann 分析的文章。我不是很同意他的意见,并且发表了 我对其分析的回应。 参考资料 1 : https://en.wikipedia.org/wiki/Failover 2 : https://en.wikipedia.org/wiki/Split-brain_(computing)","comments":true,"tags":[{"name":"array","slug":"array","permalink":"https://cloudfeng.github.io/tags/array/"},{"name":"distributed lock","slug":"distributed-lock","permalink":"https://cloudfeng.github.io/tags/distributed-lock/"},{"name":"redis","slug":"redis","permalink":"https://cloudfeng.github.io/tags/redis/"}]},{"title":"Share:python中list操作","date":"2018-07-21T16:00:00.000Z","path":"2018/07/22/arts/share/S-python-list-note/","text":"创建数值列表 使用 range函数, 得到的元素属于:[start, stop) 12for value in range(1, 10, 2): print(value) 使用list函数,构建一个列表 12for elem in list(range(1,10,3)): print(elem) 使用append函数 12345squares = []for value in range(1, 5): squares.append(value ** 2)print(squares) 使用切片构建一个新的列表 123456789my_foods = ['pizza', 'falafel', 'carrot cake']friend_foods = my_foods[:]friend_foods.append('ice cream')print(\"My favorite foods are:\")print(my_foods)print(\"\\nMy friend's favorite foods are:\")print(friend_foods) 列表操作 列表排序操作 改变原来list的排序法: sort属性 正序排 123cars = ['bmw', 'audi', 'toyota', 'subra']cars.sort()print(cars) 逆序排 123cars = ['bmw', 'audi', 'toyota', 'subra']cars.sort(reverse=True)print(cars) 不改变原来list的排序法:sorted 正序排 12cars = ['bmw', 'audi', 'toyota', 'subra']print(sorted(cars)) 逆序排 12cars = ['bmw', 'audi', 'toyota', 'subra']print(sorted(car,reverse=True)) 倒着打印列表 1234cars = ['bmw','audi','toyota','subaru']print(cars)cars.reverse()print(cars) 列表长度 获取列表的长度: len(cars) 列表索引是从0开始的,在不为空的情况下可以使用 -1表示访问最后一个元素 列表遍历 遍历列表,可以使用类似java的foreach语法 12345magicians = ['alice', 'david', 'carolina']for magician in magicians: print(magician.title() + \", that was great trick!\") print(\"I can't wait to see your next trick, \" + magician.title() + \".\\n\")print(\"Thank you everyone,that was a great magic show!\") 简单统计 对数字列表元素惊醒简单地统计 min max sum 123digits = [1, 2, 4, 0, 10, 8, 7, 9]print(str(digits) + \"'s min:\" + str(min(digits))+ \" max:\" + str(max(digits)) + \" sum:\" + str(sum(digits))) 切片操作 语法:var_list[start:end] 构建的列表元素属于 var_list[start],...,var_list[end-1] 说明: var_list[:end]:从头开始到end-1 var_list[start:]:从start开始到len(var_list)-1 var_list[:]:整个var_list 12345players = ['charles', 'matina', 'michael', 'florence', 'eli']print(players[0:3])print(players[:3])print(players[:])print(players[2:]) 应用:分页展示 元组 元组:不可变得列表;不能修改元组中的元素,但可以改变元组变量 使用园括号定义 使用下标或者foreach遍历元组 123456789dimensions = (200, 50)print(dimensions[0])print(str(dimensions[1]) + \"\\n\")for dimension in dimensions: print(dimension)dimensions = (400, 100)print(dimensions)","comments":true,"tags":[{"name":"python","slug":"python","permalink":"https://cloudfeng.github.io/tags/python/"},{"name":"share","slug":"share","permalink":"https://cloudfeng.github.io/tags/share/"}]},{"title":"Linux 文件基本属性笔记","date":"2018-07-21T16:00:00.000Z","path":"2018/07/22/arts/tip/T-linux-file-basic-ma/","text":"缘由 最近学习linux系统中相关的命令,补一下这块知识。每次需要用相关的命令,都是各种搜索,并且不知道本后 的原理。 Linux 文件基本属性 使用 ls -l,查看的当前目录下地文件或目录。第一个字符代表这个文件是目录、文件或链接文件等等。 d:目录; -:文件; l:链接文档(link file); b:装置文件里面的可供储存的接口设备(可随机存取装置); c:装置文件里面的串行端口设备,例如键盘、鼠标(一次性读取装置)。 接下来的3组,每组以RWX或者-组合。 R:read; W:write; X:execute; -:RWX中没有权限。 第一组表示:文件的所有者的权限; 第二组表示:文件所属组的权限; 第三组表示:他用户拥有该文件的权限 更改文件相关属性的命令 更改文件属组: chgrp [-R] 属组文件名 更改文件属主,也可以同时更改文件属组:chown [–R] 属主名 文件名 或者 chown [-R] 属主名:属组名 文件名 更改文件9个属性:通过数字或者字符设置 Linux文件的基本权限就有九个,分别是owner/group/others三种身份各有自己的read/write/execute权限。 RWX-对应的数字:R:4;W:2;X:1;-:0 命令:chmod [-R] xyz 文件或目录, xyz是RWX-数字之和; 使用下面的命令\b操作: 命令 所有者 操作 文件或目录 chmod u +(加入) r g -(除去) w o =(设定) x a 其中,(1)u:user (2)g:group (3)o:others (4)a:all 参考资料 [Linux文件基本属性]:https://www.w3cschool.cn/linux/linux-file-attr-permission.html","comments":true,"tags":[{"name":"linux","slug":"linux","permalink":"https://cloudfeng.github.io/tags/linux/"},{"name":"tip","slug":"tip","permalink":"https://cloudfeng.github.io/tags/tip/"}]},{"title":"ARTS-7月2周","date":"2018-07-14T16:00:00.000Z","path":"2018/07/15/arts/7m2w-summary/","text":"7月2周ARTS总结 希望自己继续保持下去,分享第四个ARTS的内容: 1.A:按照类型做算法题,并复习算法。来源是leetco的算法题目Remove Element,利用“双指针”思想解决之。 2.R:分享一下本周看的英文文章:The Key to Accelerating Your Skills While You Learn to Code,是关于如何学习编程的文章,类似于十年编程。 3.T: 分享Linux下磁盘管理的命令 4.S: 分享从不中听的话中找到合理性","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"Algorithm:Remove Element","date":"2018-07-14T16:00:00.000Z","path":"2018/07/15/arts/algorithm/A-remove-element/","text":"问题 Given an array nums and a value val, remove all instances of that value in-place and return the new length. Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory. The order of elements can be changed. It doesn’t matter what you leave beyond the new length. Example 1: Given nums = [3,2,2,3], val = 3, Your function should return length = 2, with the first two elements of nums being 2. It doesn’t matter what you leave beyond the returned length. Example 2: Given nums = [0,1,2,2,3,0,4,2], val = 2, Your function should return length = 5, with the first five elements of nums containing 0, 1, 3, 0, and 4. Note that the order of those five elements can be arbitrary. It doesn’t matter what values are set beyond the returned length. Level: Easy 解题思路 如果没有\bO(1)的空间复杂度,并且不关注修改后原来数组的结果。我们可以利用\bset\b或者map来解决此题。\b\b现在题目既然要求O(1)的\b空间复杂度解决,那么\b可以使用\b“双指针”的思路来解决此题,具体思路如下: 设pos为新数组下标值,初始化为0;\bidx为\b原数组的下标,\b[1, n); 若arr[pos] != arr[i],则 \barr[pos++] = arr[i] \b重复2,直到 i = n; 返回 新数组的长度:pos+1 \b详细代码如下: 12345678910111213public int removeElement(int[] nums, int val) { if (null == nums || nums.length <= 0) { return 0; } int len = nums.length; int pos = 0; for (int idx = 1; idx < len; ++idx) { if (nums[pos] != val) { nums[++pos] = nums[idx]; } } return (pos + 1);} 第一种解法保留了元素在数组中的顺序,题目也说明了可以改变数组的元素的位置,那么我们可以使用最后一位 元素来替换重复的元素。相关的代码如下: 12345678910111213141516public int removeElement2(int[] nums, int val) { if (null == nums || nums.length <= 0) { return 0; } int len = nums.length; int idx = 0; while (idx < len) { if (nums[idx] == val) { nums[idx] = nums[len-1]; --len; } else { ++idx; } } return len;} 复杂度分析 两种算法时间复杂度和空间复杂度如下: 时间复杂度为O(n),其中n为排序数组的长度; 空间复杂度为O(1)","comments":true,"tags":[{"name":"algorithm","slug":"algorithm","permalink":"https://cloudfeng.github.io/tags/algorithm/"},{"name":"array","slug":"array","permalink":"https://cloudfeng.github.io/tags/array/"}]},{"title":"Review - The Key to Accelerating Your Skills While You Learn to Code","date":"2018-07-14T16:00:00.000Z","path":"2018/07/15/arts/review/R-the-key-to-code/","text":"概述 本周阅读了KEN MAZAIKA写的一篇文章《The Key to Accelerating Your Skills While You Learn to Code》1。主要讲解了如何快速学习编程,我们既要学习领域知识,也要学习过程知识。还有一点就是编程心态 和编程是一个持续性学习。作者将其分为三个阶段: 跟着手册学习或者练习阶段; 心态练习阶段,解决具体问题; 跳出舒适区 Tutorial phase(练习阶段) 此阶段就是跟着手册或者文档一步一步的走,注意文档提到细节,以及遇到的错误。主要完成: Learn domain-specific knowledge. Learn how to do specific task. 【放耐心解决遇到的问题,记录是什么原因导致的以及如何解决的。】 Building right mentality phase(构建心态阶段) Understand that this is a difficult process and go easy on yourself. If you’re struggling with self-confidence, know that what you’re feeling is completely normal. Keep working. Expanding skills(扩展阶段) Rather than trying to pull coding projects into your comfort zone, you should be seeking out problems that are outside your current skill set. Web development’s two inflection points(Web应用两个关键点) web开发的编程的两个转折点: mastering CRUD:\b熟练的编写与数据库操作相关的web应用; 算法 Write sorting algorithms Implement and reverse linked lists Understand and write programs leveraging stacks, queues, and trees Write computer programs using recursive or iterative solutions 认同的观点 Don’t sweat the stack. When you’re learning how to program, your singular goal should be to find the inflection point and annihilate it. Once you do, learning that new, sexy fad won’t be a difficult task at all. 【不跟风,快速找到转折点,也就是那些基础知识,然后消灭掉他们。】 Become self-reliant. 【碰到问题或者新的技术,学会自己去找食吃,而不是一直依赖于他人。】 Programming is a life-long learning experience. 参考资料 1.http://blog.thefirehoseproject.com/posts/learn-to-code-and-be-self-reliant/ ↩","comments":true,"tags":[{"name":"review","slug":"review","permalink":"https://cloudfeng.github.io/tags/review/"},{"name":"learning","slug":"learning","permalink":"https://cloudfeng.github.io/tags/learning/"}]},{"title":"Share:遇到不中听的话,也要找到合理之处","date":"2018-07-14T16:00:00.000Z","path":"2018/07/15/arts/share/S-self-think/","text":"背景 最近看吴军老师的《硅谷来信》,其中有一段讨论“即便遇到不中听的话,也要试着找出其中的合理之处。” 现在每次反思自己的行为都会想起这句话,现在每次听到不中听的话,都会反驳回去。对人对己都不是特别好, 今日将其摘抄一下,以便再次理解一次。 四层含义 “即便遇到不中听的话,也要试着找出其中的合理之处。”的四层含义: 换位思考 凡事要习惯回过头来三思。 可能觉得他是胡说八道 是否我错了,他对了 是否是我境界不够 即使对方真的是胡说八道,要思考它为什么这么说,找出其中的合理性 总之,每次遇到别人和我有不同意见时,就立即启动找对方合理性的开关。","comments":true,"tags":[{"name":"share","slug":"share","permalink":"https://cloudfeng.github.io/tags/share/"},{"name":"心智","slug":"心智","permalink":"https://cloudfeng.github.io/tags/%E5%BF%83%E6%99%BA/"}]},{"title":"linux 磁盘管理的几个小命令","date":"2018-07-14T16:00:00.000Z","path":"2018/07/15/arts/tip/T-linux-file-command/","text":"linux 磁盘管理的小命令 工作上经常要清理一些文件以及查看磁盘的使用量,本周分享一下常用的几个磁盘管理命令。 df:列出文件系统的整体磁盘使用量 du:检查磁盘空间使用量,文件和目录磁盘使用空间 fdisk:用于磁盘分区","comments":true,"tags":[{"name":"linux","slug":"linux","permalink":"https://cloudfeng.github.io/tags/linux/"},{"name":"tip","slug":"tip","permalink":"https://cloudfeng.github.io/tags/tip/"}]},{"title":"ARTS-7月1周","date":"2018-07-06T16:00:00.000Z","path":"2018/07/07/arts/7m1w-summary/","text":"7月1周ARTS总结 希望自己继续保持下去,分享第三个ARTS的内容: 1.A:来源是leetcode在做了一个难点的算法题目Remove Duplicates from Sorted Array,利用排序数组的特性以及“双指针”思想解决之。 2.R:分享一下本周看的英文文章:How you can improve your workflow using the JavaScript consolen,是关于JavaScript中console函数的技巧。 3.T: 分享slf4和log4j入门。 4.S: 分享grep和less命令乱码。","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"Algorithm:Remove Duplicates from Sorted Array","date":"2018-07-06T16:00:00.000Z","path":"2018/07/07/arts/algorithm/A-Remove-Duplicates-from-Sorted-Array/","text":"问题 Given a sorted array nums, remove the duplicates in-place such that each element appear only once and return the new length. Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory. Example 1: Given nums = [1,1,2], Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. It doesn’t matter what you leave beyond the returned length. Example 2: Given nums = [0,0,1,1,1,2,2,3,3,4], Your function should return length = 5, with the first five elements of nums being modified to 0, 1, 2, 3, and 4 respectively. It doesn’t matter what values are set beyond the returned length. Level: Hard 解题思路 如果没有\bO(1)的空间复杂度,并且不关注修改后原来数组的结果。我们可以利用\bset\b或者map来解决此题。\b\b现在题目既然要求O(1)的\b空间复杂度解决,那么\b可以使用\b“双指针”的思路和排序数组的特性来解决此题,具体思路如下: 设pos为新数组下标值,初始化为0;\bidx为\b原数组的下标,\b[1, n); 若arr[pos] != arr[i],则 \barr[pos++] = arr[i] \b重复2,直到 i = n; 返回 新数组的长度:pos+1 \b详细代码如下: 12345678910111213public int removeDuplicates(int[] nums) { if (null == nums || nums.length <= 0) { return 0; } int len = nums.length; int pos = 0; for (int idx = 1; idx < len; ++idx) { if (nums[pos] != nums[idx]) { nums[++pos] = nums[idx]; } } return pos + 1;} 时间复杂度为O(n),其中n为排序数组的长度; 空间复杂度为O(1)","comments":true,"tags":[{"name":"algorithm","slug":"algorithm","permalink":"https://cloudfeng.github.io/tags/algorithm/"},{"name":"array","slug":"array","permalink":"https://cloudfeng.github.io/tags/array/"}]},{"title":"Review《How you can improve your workflow using the JavaScript console》","date":"2018-07-06T16:00:00.000Z","path":"2018/07/07/arts/review/R-using-js-console/","text":"概述 本周分享review的文章是JavaScript中Console函数的使用,觉得很棒所以将其整理出来分享给大家。 Console的作用 JS中的console是每个浏览器必备的一个特性。它允许开发者: View a log of errors and warnings that occur on a web page. 查看出现在web页面中错误和警告 Interact with the web page using JavaScript commands. 使用JS命令与web页面进行交互 Debug applications and traverse the DOM directly in the browser 直接绕过DOM调试应用 Inspect and analyze network activity 监测和分析网络活动 调试类 使用如下函数,可以传入多个参数,它们会自动拼接,并以空格分隔,最终显示要查看的内容。 Console.log Console.error Console.warn Console.info 示例: 1234const niceJson = {a:1, b:2, c:3};console.log(\"log\", niceJson, new Date());console.warn(\"warn\", niceJson, new Date());console.info(\"info\", niceJson, new Date()); 归类 使用console.group把log(或者info或者warn)归为一起。 示例: 12345678910111213function doSomething(obj) { console.group('doSomething Profile'); const _data = new Date(); console.log('evauating data:', _data); const _fullName = `${obj.firstName} ${obj.lastName}` console.log('fullName:', _fullName); const _id = Math.random(1); console.log('id:', _id); console.groupEnd();}doSomething({\"firstName\":\"yun\", \"lastName\":\"feng\"}); doSomething Profile evauating data: Sun Jul 08 2018 15:46:11 GMT+0800 (China Standard Time) fullName: yun feng id: 0.26763583139597613 数据表格化 可以使用console.table将对象或者对象数组表格化。 示例: 1234567const typeOfConsole = [ {name:'log', type:'standard'}, {name:'info', type:'standard'}, {name:'table', type:'wow'}];console.table(typeOfConsole); 结果: (index) name type 0 “log” “standard” 1 “info” “standard” 2 “table” “wow” 示例2: 123456789const mySocial = { facebook:true, linkedin:false, flickr:false, instagram:true, Vkontaktebadoo:false};console.table(mySocial); (index) Value facebook true linkedin false flickr false instagram true Vkontaktebadoo false 统计类 测试函数的运行时间或者统计某一行代码执行的总数。 console.count console.time console.timeEnd 示例: 1234567891011console.time('total');console.time('init arr');const arr = new Array(20);console.timeEnd('init arr');for (var i = 0; i < arr.length; ++i) { arr[i] = new Object(); const _type = (i % 2 === 0) ? 'even' : 'odd'; console.count(_type + \" added\");}console.timeEnd('total'); 结果: init arr: 0.003662109375ms even added: 1 odd added: 1 even added: 2 odd added: 2 even added: 3 odd added: 3 even added: 4 odd added: 4 even added: 5 odd added: 5 even added: 6 odd added: 6 even added: 7 odd added: 7 even added: 8 odd added: 8 even added: 9 odd added: 9 even added: 10 odd added: 10 total: 2.650146484375ms 堆栈跟踪类 console.trace console.assert 示例: 12345678910111213function lesserThan(a, b) { console.assert(a < b, {\"message\":\"a is not lesser than b\", \"a\":a, \"b:\":b});}lesserThan(6, 5);console.trace(\"End\");// resultVM634:2 Assertion failed: {message: \"a is not lesser than b\", a: 6, b:: 5}lesserThan @ VM634:2(anonymous) @ VM634:5VM634:6 End(anonymous) @ VM634:6 清除console 使用uglifyjs-webpack-plugin清除所有的console。 123456789101112131415161718192021const UglifyJsPlugin = require('uglify-webpack-plugin')var debug = process.env.NODE_ENV !== 'production'// ...optimization:{ minimizer:!debug ? [ new UglifyJsPlugin({ // compression specific options uglifyOption: { // Eliminate comments comments:false, compress: { // removing warnings warnings:false, // drop console statements drop_console: true }, } })] :[]} 总结 毕竟使用console是用来调试代码的,不能让这些代码上生产版本,所以需要注意之处。另外,就是在十分明确的地方就没有必要使用console,以免滥用误用。","comments":true,"tags":[{"name":"review","slug":"review","permalink":"https://cloudfeng.github.io/tags/review/"},{"name":"javascript","slug":"javascript","permalink":"https://cloudfeng.github.io/tags/javascript/"}]},{"title":"Share:grep命令查日志和日志内容乱码","date":"2018-07-06T16:00:00.000Z","path":"2018/07/07/arts/share/S-grep/","text":"grep命令查日志 本周突然接到通知,开发同事需要周六日轮流值班处理生产问题。本周六我做了支持。 查询日志的时候,使用最多的就是grep命令。周六碰到一个客户不能提前还款。 使用如下参数查找: 查询 关键字词 匹配所在的行:grep -n '关键字词' 文件 查询关键字词 前面或者后面多少行,grep -Anum1 -Bnum2 关键字词 文件 日志内容乱码 问题描述:使用grep找出的部分日志没有乱码,中文显示好的。而使用less命令 发现乱码,后面使用export LANG=zh_CN.UTF8;,仍旧没有解决问题。 最后使用export LESSCHARSET=latin1,解决乱码问题。可以参考less - Unix, Linux Command","comments":true,"tags":[{"name":"share","slug":"share","permalink":"https://cloudfeng.github.io/tags/share/"},{"name":"grep","slug":"grep","permalink":"https://cloudfeng.github.io/tags/grep/"}]},{"title":"SLF4J 和 LOG4J","date":"2018-07-06T16:00:00.000Z","path":"2018/07/07/arts/tip/T-slf4jandlog4j/","text":"\b折腾背景 在公司开发都是使用内部框架的,使用打印日志没怎么关注过。本周在学习Srping的时候采用SLF4J和LOG4J打印日志。分享一下遇到的小问题以及解决方法。 SLF4J是啥 SLF4J是 Simple Logging Facade for Java简称,是为日志框架(比如,java.util.logging, logback 和 log4j)提供接口服务,具体日志打印是怎么实现有日志框架决定的。 小实验 下载SLF4J相关的JAR,引入到lass path。然后运行下面小程序: 123456789import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info(\"Hello World\"); }} 会报如下错误: SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”. SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. 不要担心,到这里说明你向前走了一步,把 slf4j-simple-1.8.0-beta2.jar,在运行一下就出现,你想要的结果了。 [main] INFO HelloWorld - Hello World 使用LOG4J 后面想打印Debug级别的日志,所以下载log4j的JAR包。下载后把其加入class path , 然后执行下面这个测试程序: 12345678910111213141516import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class Wombat { final Logger logger = LoggerFactory.getLogger(Wombat.class); Integer t; Integer oldT; public void setTemperature(Integer temperature) { oldT = t; t = temperature; logger.debug(\"Temperature set to {}. Old temperature was {}.\", t, oldT); if(temperature.intValue() > 50) { logger.info(\"Temperature has risen above 50 degrees.\"); } } } 输入 temperature 大于50则有日志,否则什么也没有。 Temperature has risen above 50 degrees. 然后把slf4j-log4j加入class path 下运行就报如下错误: SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/D:/tools/extraLib/log/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/D:/tools/extraLib/log/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. 先把slf4j-simple-1.8.0-beta2.jar移除,然后在src目前下新建log4j.properties,并敲入如下内容,用于配置log4j.properties和设置debug级别。 12345log4j.rootLogger=DEBUG, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%c{1} - %m%n 最后运行,运行结果如下: Wombat - Temperature set to 20. Old temperature was null. Wombat - Temperature set to 60. Old temperature was 20. Wombat - Temperature has risen above 50 degrees. 为什么遇到上面两个问题,看到手册后面的文档说明就会明白,也是我们注意的地方。现摘录如下: To switch logging frameworks, just replace slf4j bindings on your class path. For example, to switch from java.util.logging to log4j, just replace slf4j-jdk14-1.8.0-beta2.jar with slf4j-log4j12-1.8.0-beta2.jar. SLF4J does not rely on any special class loader machinery. In fact, each SLF4J binding is hardwired at compile time to use one and only one specific logging framework. For example, the slf4j-log4j12-1.8.0-beta2.jar binding is bound at compile time to use log4j. In your code, in addition to slf4j-api-1.8.0-beta2.jar, you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path. 特别是下面张图,就会明了SLF4J的是干嘛的了,解决了啥问题。 小思考 阅读了一下manual,以及捣鼓了一下两个示例,对SLF4J有点小想法如下: SLF4J易于维护和升级 SLF4J是一个日志API,具体日志怎么整的是日志框架的事情,将日志与日志框架解耦,也即是后面如果项目需要升级比如将 LOG4J到LogBack。 高效且易于阅读 现在组里面都是使用+拼接打印的日志的,而SLF4J中可以使用占位符{},比如第二个例子就是。","comments":true,"tags":[{"name":"tip","slug":"tip","permalink":"https://cloudfeng.github.io/tags/tip/"},{"name":"slf4j","slug":"slf4j","permalink":"https://cloudfeng.github.io/tags/slf4j/"},{"name":"log4j","slug":"log4j","permalink":"https://cloudfeng.github.io/tags/log4j/"}]},{"title":"ARTS-6月4周","date":"2018-06-28T16:00:00.000Z","path":"2018/06/29/arts/6m4w-summary/","text":"本周ARTS总结 6月最后一周的ARTS一直没有写完,主要原因有两点: 算法题目一直没有想到一个很好的想法解决; 这周家里和工作上都有很多事情,只能零碎的写写; 希望自己继续保持下去,分享第二个ARTS的内容: leetcode在做了一个难点的算法题目Median of Two Sorted Arrays,使用归并排序的算法解决之,但不是最好的,到目前还没有想到到最优解。 分享一下本周看的英文文章,主要是Peter Noring 和 Alan Skorkin的文章,主要是关于学习态度的问题,然后分享跟着MDN Web教程修改程序做成mntfun的介绍页面; 分享本周使用Junit测试多线程代码时,发现的一个小问题; 分享一下最近对TDD使用的一些小想法。","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"Algorithm:Median of Two Sorted Arrays","date":"2018-06-28T16:00:00.000Z","path":"2018/06/29/arts/algorithm/A-Find-median/","text":"问题描述 There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be O(log(m+n)). Example 1: nums1 = [1, 3] nums2 = [2] The median is 2.0 Example 2: nums1 = [1, 2] nums2 = [3, 4] The median is (2 + 3)/2 = 2.5 Level: Hard 解题思路与状态 问题分析 已知条件 两个排序数组,数组num1,元素个数为m;数组num2,元素个数为n; 问题 找到num1和num2的中位数 约束 算法的运行最坏时间为 O(log(m+n)) 解题思路 归并排序法 如果没有时间约束,就可以使用归并排序,若排序后的结果为nums3,元素个数为k=m+n;最终将问题转化为: 求数组nums3的中位数。 若k为奇数,直接为nums3[k/2]; 若k为偶数,则为(nums3[k/2]+nums3[k/2-1])/2; 此解法的时间复杂度为O(m+n),主要是归并排序上;空间复杂度为O(m+n),用于暂存归并排序后的结果。 具体的代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445// find the median of a sorted arrrayprivate double findMedianSortedSignleArray(int[] nums) { if (null == nums || 0 == nums.length) { throw new IllegalArgumentException(\"sorted arrays are illegal.\"); } int len = nums.length; if (0 == len % 2) { return (nums[len/2] + nums[len/2 - 1]) / 2.0; } return nums[len/2];}public double findMedianSortedArrays(int[] nums1, int[] nums2) { if (null == nums1 || 0 == nums1.length) { return findMedianSortedSignleArray(nums2); } if (null == nums2 || 0 == nums2.length) { return findMedianSortedSignleArray(nums1); } // using merge sort int n = nums1.length; int m = nums2.length; int[] mergeNum = new int[n + m]; int i = 0; int j = 0; int k = 0; while (i < n & j < m) { if (nums1[i] > nums2[j]) { mergeNum[k++] = nums2[j++]; } else { mergeNum[k++] = nums1[i++]; } } while (i < n) { mergeNum[k++] = nums1[i++]; } while (j < m) { mergeNum[k++] = nums2[j++]; } return findMedianSortedSignleArray(mergeNum);} 递归算法 目前递归算法还未写出来,断断续续思考使用递归的想法有几天了,一直未果。现在留着不写,后续解决 之后在补上。 总结 对于使用递归算法解决此问题没能解决,内心是十分痛苦的。目前的思路是能否借助于二分查找算法解决, 最为关键的是递归结束的条件是什么。","comments":true,"tags":[{"name":"algorithm","slug":"algorithm","permalink":"https://cloudfeng.github.io/tags/algorithm/"},{"name":"array","slug":"array","permalink":"https://cloudfeng.github.io/tags/array/"}]},{"title":"脚踏实地学习","date":"2018-06-28T16:00:00.000Z","path":"2018/06/29/arts/review/R-learn-by-hard-way/","text":"概述 本周阅读了Peter Norving 写的一篇文章《Teach Yourself Programming in Ten Years》1 和 Alan Sorkin 写的一篇文章《The Greatest Developer Fallacy Or The Wisest Words You’ll Ever Hear?》2 。这两篇文章给我的感触比较深,所以本周就分享一下文章大概内容以及本周根据 MDN一步一步做的一个mntfun静态页面。 态度:无捷径可走 主要讲我们学习编程所需要的态度不要企图一蹴而就,也不要好高骛远,没有捷径可走。 刻意练习 The key is deliberative practice: not just doing it again and again, but challenging yourself with a task that is just beyond your current ability, trying it, analyzing your performance while and after doing it, and correcting any mistakes. Then repeat. And repeat again. ** There appear to be no real shortcuts **: even Mozart, who was a musical prodigy at age 4, took 13 more years before he began to produce world-class music. In another genre, the Beatles seemed to burst onto the scene with a string of #1 hits and an appearance on the Ed Sullivan show in 1964. But they had been playing small clubs in Liverpool and Hamburg since 1957, and while they had mass appeal early on, their first great critical success, Sgt. Peppers, was released in 1967. 不要相信:用到的时候再学习,注意平时的积累; 两篇文章都提到要与高手一起合作,沟通; 保持激情 Maybe it is just that people don’t know how to build expertise (there is an element of truth to this), but I have a sneaking suspicion that it’s more about lack of desire rather than lack of knowledge. 成为程序员的步骤 我是一个初学者,没有啥资格说如何成为一个程序员,一个好的程序员,直接引用 Pert Noring的经验。如下: Get interested in programming, and do some because it is fun. Program. The best kind of learning is learning by doing. Talk with other programmers; read other programs. If you want, put in four years at a college (or more at a graduate school). Work on projects with other programmers. Learn at least a half dozen programming languages. Remember that there is a “computer” in “computer science”. Get involved in a language standardization effort. Have the good sense to get off the language standardization effort as quickly as possible. 分享mntfun介绍网页 本周业余时间跟着MDN开发网站3,学习web开发,主要是跟着入门教程,然后改写成mntfun一个介绍网 站。如下图,比较简陋。 参考资料 1.https://www.skorks.com/2011/02/the-greatest-developer-fallacy-or-the-wisest-words-youll-ever-hear/ ↩2.https://www.skorks.com/2011/02/the-greatest-developer-fallacy-or-the-wisest-words-youll-ever-hear/ ↩3.https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web ↩","comments":true,"tags":[{"name":"review","slug":"review","permalink":"https://cloudfeng.github.io/tags/review/"},{"name":"learning","slug":"learning","permalink":"https://cloudfeng.github.io/tags/learning/"}]},{"title":"测试驱动开发小思考","date":"2018-06-28T16:00:00.000Z","path":"2018/06/29/arts/share/S-TDD-is-perfect/","text":"TDD疑问 最近学习TDD开发,用在平时开发的时候,就会发现TDD并不完美,特别是测试先行和各种需要覆盖 的测试。存在疑问如下: 是否真的需要一开始就写测试,然后一步一步驱动去开发呢; 后续测试用例的维护,特别与关联其他部门的接口,各种mock和stub; 耗费的时候比先写好代码再测试更大; 一些观点 对TDD有一些不成熟的想法,不一定非等需要严格按照TDD开发流程进行项目开发,找到适合自己的就行。TDD 对我来说很难掌握,但可以从TDD中学习和借鉴的是: 开发自己列好测试清单,以便我们单元测试做得足够; 重构自己写的代码; 后面在StackoverFlow上看到一个帖子How deep are your unit tests?,附上问题和比较喜欢的答案: 问题 The thing I’ve found about TDD is that its takes time to get your tests set up and being naturally lazy I always want to write as little code as possible. The first thing I seem do is test my constructor has set all the properties but is this overkill? My question is to what level of granularity do you write you unit tests at? …and is there a case of testing too much? 比较喜欢的两个回答: TDD创始人Kent Beck回答: I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence (I suspect this level of confidence is high compared to industry standards, but that could just be hubris). If I don’t typically make a kind of mistake (like setting the wrong variables in a constructor), I don’t test for it. I do tend to make sense of test errors, so I’m extra careful when I have logic with complicated conditionals. When coding on a team, I modify my strategy to carefully test code that we, collectively, tend to get wrong. Different people will have different testing strategies based on this philosophy, but that seems reasonable to me given the immature state of understanding of how tests can best fit into the inner loop of coding. Ten or twenty years from now we’ll likely have a more universal theory of which tests to write, which tests not to write, and how to tell the difference. In the meantime, experimentation seems in order. kitofr的回答 Everything should be made as simple as possible, but not simpler. - A. Einstein One of the most misunderstood things about TDD is the first word in it. Test. That’s why BDD came along. Because people didn’t really understand that the first D was the important one, namely Driven. We all tend to think a little bit to much about the Testing, and a little bit to little about the driving of design. And I guess that this is a vague answer to your question, but you should probably consider how to drive your code, instead of what you actually are testing; that is something a Coverage-tool can help you with. Design is a quite bigger and more problematic issue. 后来看到耗子叔写了一篇TDD并不是看上去的那么美,比我写的好,思考的深。 另外关于Unit test VS TDD讨论也是十分精彩的,推荐阅读。","comments":true,"tags":[{"name":"TDD","slug":"TDD","permalink":"https://cloudfeng.github.io/tags/TDD/"},{"name":"share","slug":"share","permalink":"https://cloudfeng.github.io/tags/share/"}]},{"title":"Junit测试Java多线程小疑","date":"2018-06-28T16:00:00.000Z","path":"2018/06/29/arts/tip/T-java-thread-unit/","text":"遇到背景 本周分享一个小问题,在看TIJ(Java编程思想)中多线程机制一节时,把书中的示例使用Junit来测试。发 现与书中使用main方法来测试的结果是不一样的。 问题描述 为了描述问题,先引用书中的例子:定义一个简单地任务,代码如下: 123456789101112131415161718192021public class LiftOff implements Runnable { protected int countDown = 10; private static int taskCount = 0; private final int id = taskCount++; public LiftOff() {} public LiftOff(int countDown) { this.countDown = countDown; } public String status() { return \"#\" + id + \"(\" + (countDown > 0 ? countDown : \"liftoff!\") + \"),\"; } @Override public void run() { while (countDown-- > 0) { System.out.println(status()); Thread.yield(); } } } 使用Junit测试代码如下: 123456789@Testpublic void testMoreBasicThread() throws InterruptedException { for (int i = 0; i < 5; ++i) { Thread t = new Thread(new LiftOff()); //t.join(); t.start(); } System.out.println(\"Waiting for LiftOff\");} 使用main测试的结果与Junit测试的结果相比对,就会发现两者不同。 main函数测试的结果 Waiting for LiftOff#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(liftoff!), Junit测试的结果 Waiting for LiftOff#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2), 为什么使用Junit输出的结果会比使用main方法直接测试的要少。 分析与解决 一般情况下,只有当所有的守护线程完结之后JVM才会结束。为此,你可以在执行任务的线程上调用 t.setDaemon(false)来防止JVM在任务未执行完成之前就结束生命了。但是Junit会在主线程结束时调用 System.exit()。具体的见JunitCore.java中代码,下面是与此问题相关的代码1。解决方法就是在 调用t.start()之前调用t.jion(),让Junit线程等待任务的完成2。 123public static Result runClasses(Class<?>... classes) { return runClasses(defaultComputer(), classes);} 参考资料 1.https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runner/JUnitCore.java#L48 ↩2.https://stackoverflow.com/questions/16616590/thread-behaving-strangely-in-junit ↩","comments":true,"tags":[{"name":"tip","slug":"tip","permalink":"https://cloudfeng.github.io/tags/tip/"},{"name":"junit","slug":"junit","permalink":"https://cloudfeng.github.io/tags/junit/"},{"name":"thread","slug":"thread","permalink":"https://cloudfeng.github.io/tags/thread/"}]},{"title":"ARTS之旅","date":"2018-06-22T16:00:00.000Z","path":"2018/06/23/arts/6m3w-summary/","text":"本周ARTS总结 ARTS是左耳朵耗子发起的一个活动,具体如下: Algorithm:每周至少做一个leetcode的算法题; Review:阅读并点评至少一篇英文技术文章; Tip:学习至少一个技术技巧; Share:分享一篇有观点和思考的技术文章; 你需要坚持至少一年! 在微信群里面看到此活动的时候,晚上还失眠了,回想自己读书以及工作一年,没有什么习惯坚持下来, 都是两天打鱼三天晒网。现在一直都生活在舒适区或着看了比较多材料也没有什么输出,内心忒崩溃, 越想越烦躁,最后失眠了,后面想将ARTS作为自己的一个项目去维护,既然遇到左耳朵耗子哥,有契机, 就好好把握,也想看看自己能走多远,也希望藉此机会好好锻炼自己。想那么多,又不能改变啥,去做就是。 我自己理解的ARTS目的 A:我学习比较浮躁,夯实以前看算法书,锻炼一下自己的思维能力; R:督促自己学习英语,去总结和思考; T:不要放弃技术,相信技术,积累; S:学会分享,与人沟通,而不是一个人独自学习; 总之,积少成多,说了这么多,该谈谈本周开启ARTS之旅,分享的内容如下: leetcode在做了一个简单地算法题目Two Sum,给定一个数组和一个值,找出两个值之和等于给定的值; 分享一下本周看的英文文章,主要是多兴趣以及一周5小时法则; 分享本周遇到一个简单线上bug以及处理流程; 本周学习TDD开发,分享一下阅读测试驱动开发笔记一; ARTS活动内容来源:左耳朵耗子 20180615 发得的微博。","comments":true,"tags":[{"name":"ARTS","slug":"ARTS","permalink":"https://cloudfeng.github.io/tags/ARTS/"}]},{"title":"Algorithm:Two Sum","date":"2018-06-22T16:00:00.000Z","path":"2018/06/23/arts/algorithm/A-TwoSum/","text":"问题描述 Given an array of integers, return indices of the two numbers such that they add up to a specific target.You may assume that each input would have ** exactly one solution**, and you may not use the same element twice. Example Given nums = [2, 7, 11, 15], target = 9, Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1]. 解题思路与状态 当时看完题目给自己定了一个30分钟的闹钟,可以在这段时间中搞定它。存在疑问: 若数组排序,可以使用两边夹方法解决之;若不是怎么处理。 数组中是否存在重复元素,若有怎么处理。 未找到满足条件的元素,是抛出异常还是给定特殊的标志,比如[-1,-1]。 看完题目之后头脑中形成的解题思路暴力破解法,一下搞完,本地测试通过了,时间复杂度为 O(n^2)。 不是自己想要的结果,接着想能都把元素的值与下标对应起来,然后再去查找,利用空间换时间,以缩短查询 时间。后面出现了使用hashmap做处理的元素值,这种情况在数组中存在重复元素有问题。后面想了很久 仍旧没有想到使用一个hashmap来解决此问题。这个时候已经过去40分钟,心里面开始出现焦急状态, 无奈之下看答案了。两种hashmap的方法的空间复杂度为O(n),时间复杂度为O(n)。下面就将每个 解法的关键点列出来。 暴力破解法 1234567891011121314public int[] twoSum(int[] nums, int target) { if (null == nums || nums.length <= 1) { return new int[] {-1, -1}; } int len = nums.length; for (int i = 0; i < len; i++) { for (int j = i + 1; j < len; j++) { if (nums[j] == target - nums[i]) { return new int[] { i, j }; } } } return new int[] {-1, -1};} 双哈希法 1234567891011121314151617public int[] twoSum(int[] nums, int target) { if (null == nums || nums.length <= 1) { return new int[] {-1, -1}; } Map<Integer, Integer> map = new HashMap<Integer,Integer>(); int len = nums.length; for (int i = 0; i < len; i++) { map.put(nums[i], i); } for (int i = 0; i < len; i++) { int right = target - nums[i]; if (map.containsKey(right) && map.get(right) != i) { return new int[] { i, map.get(right) }; } } return new int[] {-1, -1};} 单哈希法 1234567891011121314public int[] twoSum(int[] nums, int target) { if (null == nums || nums.length <= 1) { return new int[] {-1, -1}; } Map<Integer, Integer> map = new HashMap<Integer,Integer>(); for (int i = 0; i < len; i++) { int right = target - nums[i]; if (map.containsKey(right)) { return new int[] { i, map.get(right) }; } map.put(right, i); } return new int[] {-1, -1};} 总结 以后做算法题或者解决难题的时候,要懂得放一放,不要非得在一定的时间之内完成,特别是在做算法题。 毕竟在leetcode上做算法题不是为了刷题目,而是为了锻炼自己的思维还有夯实算法知识。","comments":true,"tags":[{"name":"algorithm","slug":"algorithm","permalink":"https://cloudfeng.github.io/tags/algorithm/"}]},{"title":"Review:多兴趣成功以及5小时法则","date":"2018-06-22T16:00:00.000Z","path":"2018/06/23/arts/review/R-mutil-interests/","text":"概述 本周阅读了多兴趣与成功以及5小时法则,将其中要点以及自己的感悟分享给大家。 多兴趣与成功 《People Who Have “Too Many Interests” Are More Likely To Be Successful According To Research》一文表明的观点是在现在或者未来,通才更容易成功。读完之后有如下感触: 多视角看问题,更容易解决复杂问题; 保持好奇心,心态要开放不要鄙视或者轻视任何新事物; 学习的东西必须用起来; 读完此文还有吴军老师的《见识》中关于跨界的介绍。加上自己这几年读书和近一年的工作经历,这也想学习, 那也想学习,但是啥没有深入进去。都是学了点皮毛。就如同猴子摘棒子一般。培养的兴趣要为某个核心领域 服务,能够将兴趣结合在一起才行。所以给我自己的告诫: 不要过多兴趣,除非那些天赋和精力超级旺盛的人; 读书或者做事情一定要持续投入; 5小时法则 5小时法则的来源是富兰克林,他每日都抽取至少1小时投入学习,每周5个小时。作者也列举了很多例子: Warren Buffett spends five or six hours per day reading five newspapers and 500 pages of corperate reports. Bill Gates reads 50 books per year. Mark Cuban read more than 3 hours every day. Mark Zuckerberg reads at least one book two weeks. … 作者建议如下: Reading:每日不断阅读; Refection: 反思所学以及自己的行为; Experimentation: 解决遇到的问题,利用你学到知识。 Don’t be lazy. Don’t make excuses. Just get it done. 参考资料 Why Constant Learners All Embrace the 5-Hour Rule Bill Gates, Warren Buffett And Oprah All Use The 5-Hour Rule 5-Hour Rule: If you’re not spending 5 hours per week learning, you’re being irresponsible People Who Have “Too Many Interests” Are More Likely To Be Successful According To Research 吴军 《见识》关于跨界的介绍","comments":true,"tags":[{"name":"review","slug":"review","permalink":"https://cloudfeng.github.io/tags/review/"},{"name":"5小时法则","slug":"5小时法则","permalink":"https://cloudfeng.github.io/tags/5%E5%B0%8F%E6%97%B6%E6%B3%95%E5%88%99/"},{"name":"兴趣","slug":"兴趣","permalink":"https://cloudfeng.github.io/tags/%E5%85%B4%E8%B6%A3/"}]},{"title":"测试驱动开发笔记之入门","date":"2018-06-22T16:00:00.000Z","path":"2018/06/23/arts/share/S-TDD-note/","text":"无意间接触TDD 以前写个小函数之后,一直用main函数来做单元测试。看看是否有其他的方法,不用老这么整。 后面听到过xUnit啥,然后在网上也看一部分资料,Junit简要入门,junit-tutorials等。 弄完之后觉得好奇,想知道Junit是谁先提出的,无意间就找到 Kent Beck 的一本关于测试驱动开发的书: 《Test-Driven Development By Example》。 TDD 小结 后面每天抽点时间把第一章的知识看完,主要围绕资金实例一步一步的介绍测试驱动开发,作者忒细心, 深怕介绍的过快而没有讲清楚。今日把相关的要点列出来,最近慢慢在开始练习开发模式写代码。示例代码 可以参考TDD。 测试驱动开发的目标 每一种开发方法都是为了解决某个问题而来的,而测试驱动开发的目的就是为让人写出整洁可用的代码。 测试驱动开发的态度 测试优先 只有自动测试失败时,才重写代码 不可运行/可运行/重构 不断构建测试列表 测试驱动开发的过程 加入一个小的测试; 运行所有测试,运行测试; 适当修改; 运行测试且成功; 重构,消除重复设计,优化设计结构。 三个测试驱动开发小技巧 让测试利落运行的三种方法 伪方法:返回一个常量并逐渐用变量代替常量,直至伪实现代码成为真实地代码 三角法:在例子达到2个或更多时才对代码实施一般化 显明实现:实现真实的代码 把消除代码与测试间的重复设计作为驱动设计的一种手段 控制测试间隙的能力,不知如何下手,就放慢,在状况好时就开快一些 参考资料 http://www.vogella.com/tutorials/JUnit/article.html http://www.mkyong.com/tutorials/junit-tutorials/","comments":true,"tags":[{"name":"TDD","slug":"TDD","permalink":"https://cloudfeng.github.io/tags/TDD/"},{"name":"share","slug":"share","permalink":"https://cloudfeng.github.io/tags/share/"}]},{"title":"简单线上bug以及处理流程","date":"2018-06-22T16:00:00.000Z","path":"2018/06/23/arts/tip/T-onlineproblem/","text":"一个bug背景 在周三快下班的时候,临时接到需求,需要修改协议书模板,后端只需要修改脚本即可。由于周四是版本 上线的最后一天,所以建议不要修改,原因如下: 每次发版当天的环境会很差,各个关联方环境极其不稳定; 本身有很多需求都没全部覆盖测试; 改完之后都需要走回归测试,测试同事有时间么; 但是组长觉得小小需求,测试负责人也说支持。最后妥协,修改了,当天就搞定了。周四开始回归测试,测试 同事测试出一个bug了,为说明此bug产生的缘由,说明一些简称: 1.客户级别L:(0:低;1:高); 2.数据源: 客户OCR扫描完证件信息(A) 本地数据库(B) 客户预留信息(C) 关系方的信息(D) 3.渠道:A 和 B 需求做一个比对,判断是否同一个人,此需求是小灰同事负责开发(同事之间需要和谐相处说,随意取名小 灰。 ),在此将简单写成伪代码,如下: 12345678910111213141516171819202122232425262728293031 res = false // 块1 if A then if A == B then res = true else res = false END END// 块2 if L == 0 then // 块2.1 if D 存在 then if A == D then res = true else res = false END else res = true END // 块2.2 else if A == C then res = true else res = false END END return res 周四测试同事发现低级别的客户,OCR扫的与本地库不同,居然也认为是本人,所以将bug给小灰和我。当 时我看着日志,把数据对比的数据拿出来,写单元测试,发现确实如此。然后就叫小灰过来,给他讲了一下, 发现的问题,A渠道来的在块1比较是不同的人,客户又是低级的而D数据不存,覆盖了之前校验的值,将其 变为是本人了。后面他说:“不是很明白,回桌上自己想想”。最后他说改完了,移交测试了。由于我负责其 他的需求开发,负责查日志,忙着就忘记去看小灰怎么改的。后面就上线了,由于小灰需要支持版本发布, 周五调休。 另一个bug背景 周五一上班,大概10点多,收到很多邮件,一会查这个一会查另外一个。当天看到6笔报不是本人的异常, 赶紧查日志。拉取最新的代码,查完分析代码。小灰修改后的代码,改写伪代码如下: 1234567891011121314151617181920212223242526272829303132res = false // 块1 if A then if A == B then res = true else res = false END END// 块2 if L == 0 then // 块2.1 if D 存在 then if A == D then res = true else res = false END //else // 小灰修改处 // res = true END // 块2.2 else if A == C then res = true else res = false END END return res 分析出来是,B渠道,低级别客户,在数据D不存在的情况下,一直是非本人。所以与测试沟通造相关的数 据,复现生成问题。 bug修复处理 最后跟领导申请紧急版本处理修复,由于是由一个bug引起bug,问了如下三个问题: 修改的代码是否有review 测试同事是否有案例review 是否负责人报备 我的回答都是否定的,当时发现第一个bug的时候,我跟测试负责人打了招呼,但是没有跟组长说,后面也没 有review小灰修改的代码。处理此事我司步骤如下: 发邮件申请紧急版本给部门长; 开发直属领导与部门长说明bug问题; 修复bug 测试回归 发布版本 生成验证 修改小灰的代码如下: 12345678910111213141516171819202122232425262728293031res = false // 块1 if A then if A == B then res = true else return false // 修1 END END// 块2 if L == 0 then // 块2.1 if D 存在 then if A == D then res = true else return false // 修改2 END else // 修改3 仍旧加上 res = true END // 块2.2 else if A == C then res = true else res = false END END return res 然后与组长一起review代码,影响业务比较大,所以需要快速解决,没有重构代码。好了之后,画好流程 图,给测试同事讲解,再告诉测试同事在测试环境造什么样的数据,让其一个分支一个分支的测试。上线之 后,自己又找人生产验证。 总结 问责的时候,心里面确实不舒服,不是我的错,为啥要我承担。 后面想想,自己也有责任,发现了bug, 没有和小灰一同修复。今天就把此两个bug回顾,总结如下: 复杂的业务逻辑画好流程图,与测试同事一同案例评审; 开发单元测试一定做足; 不要逃避责任。","comments":true,"tags":[{"name":"linux","slug":"linux","permalink":"https://cloudfeng.github.io/tags/linux/"},{"name":"Tip","slug":"Tip","permalink":"https://cloudfeng.github.io/tags/Tip/"}]},{"title":"mock和RAP入门教程","date":"2017-07-30T16:00:00.000Z","path":"2017/07/31/mock-rap-basic-uasge/","text":"在很早以前公司里面就接入了rap在线管理接口文档平台,通过图形化书写接口文档,给前端同事使用。但是之前也仅仅是用于写接口文档,从来没有研究过它还有其他用途。最近与自己合作的一个前端同事说,你为啥不利用好rap来写接口文档,它会自动生成mock数据,我们就不用将rap默认生成的数据修改后,再本地测试一下。弄完之后再与你们联调。今儿就花了2个小时左右研究一下rap,顺藤摸瓜出来mock。下面简单介绍一下,mock以及如何在rap中使用mock,生成比较好的模拟数据,提高前后端协作效率。 mock.js1 mock.js随机生成数据,让前端开发人员在开发阶段时独立于后端,使用mockjs可以自测代码。 语法规范 数据模板定义规范(DTD:Data Template Definition) 数据占位符规范(DPD: Data Placeholder definition) 数据模板定义规范 格式 'name|rule':value (属性名|生成规则:属性值),其中生成规则是可选的 生成规则 7种生成规则 'name|min-max':value 'name|count':value 'name|min-max.dmin-dmax':value 'name|+step':value 'name|min-max.dcount':value 'name|count.dmin-dmax':value 'name|count.dcount':value 生成规则的含义依赖属性值的类型才能确认,这一点特别重要。也是mock的关键所在。 比如: 'name|min-max':string: 通过重复[min,max]次string生成新字符串。 'name|min-max':number: 生成一个number,返回在[min,max]。 属性值 属性值可含@(占位符) 指定最终的值和类型 属性值类型 String Number Boolean Object Array Function RegExp Path 学过js看着十分简单,只是多了RegExp 和 Path,其他简直一模一样。 数据占位符定义规范DPD 占位符 1.占位符只是在属性字符串中占个位置,并不出现在最终的属性值中 2.格式:@占位符或者@占位符(参数[,参数]) 3.占位符 用@标识后面的字符串是占位符 引用的是Mock.Random中的方法 扩展定义占位符:Mock.Random.extend() 引用数据模板中的属性 支持相对路径和绝对路径 4.数据占位符类型 数据占位符一共有如下几种类型: Basic Date Image Color Text Name Web Address Helper Miscellaneous 具体mock语法示例,请参考mock示例2,里面详细的介绍了各个类型使用方法。 rap写接口文档中运用mock3 什么是rap RAP是前后端沟通桥梁的通信接口,是一个图形化的接口文档管理的软件。它可以自动生成mock数据,在开发时候前端同事可以不依赖于后端的数据,而是根据mock规则自动生成的模拟数据进行测试。 怎么在rap中是使用mock 有很多资料,且是图文并茂写博文4,5。总结起来,在rap中运用mock方法如下: 左边变量写 name|rule,此处相当于就是mock中的'name|rule'; 右边备注写 @mock=value。其语法规则mock一样。 1.https://github.com/nuysoft/Mock/wiki ↩2.http://mockjs.com/examples.html ↩3.https://github.com/thx/RAP/wiki/ ↩4.http://www.imooc.com/article/17588 ↩5.http://blog.sina.com.cn/s/blog_c00ccc680102x0ue.html ↩","comments":true,"tags":[{"name":"mock","slug":"mock","permalink":"https://cloudfeng.github.io/tags/mock/"},{"name":"RAP","slug":"RAP","permalink":"https://cloudfeng.github.io/tags/RAP/"}]},{"title":"python实现http发送POST请求","date":"2017-07-24T16:00:00.000Z","path":"2017/07/25/python-http-post/","text":"现在开发是前后端完全分离的,在公司里面很多接口都和登陆态相关联起来。从而使得我在需求开发的时候依赖于前端同事打包完之后才开始测试自己的需求功能。所以比较耗费时间和精力。但是我们的服务接口是不依赖登陆态的,为了测试自己的服务接口,需要模拟发送HTTP请求,以便后面偷懒。本文就此介绍一下如何解决此问题。 语言选择以及依赖包 为了能够快速解决此问题,首先需要找到合适的工具,俗话说,“工欲善其事,必先利其器。” 在程序设计中首先就是语言的选择,相比于java,对python更为熟悉一点。说句皮外话, 虽然在公司用java开发,但是对java熟悉程度不如python,汗颜啦。回归正题,选好了 语言之后,紧接着就是如何模拟发送HTTP请求。google一番找到一个第三方包urllib3。 如何安装urllib3以及使用文档教程。 实现 现在我们来看看,如何使用urllib3包来模拟发送HTTP的POST请求,请求参数是JSON格式。为了能够使用urllib3模块,需要在使用导入urllib3模块,代码:import urllib3。使用PoolManager 实例来发送请求,它已经为我们封装处理好了连接池以及线程安全问题,我们没有必要再次徒手开发一套。其实,urllib3中最为核心关键的方法是request方法,它指定发送请求的方式、请求地址请求的参数。具体步骤如下: 指定请求方法以及url; 将请求的参数编码,然后作为body的值; 设置请求头部的Content-Type为JSON格式. 下面是一个示例代码: 12345678910111213#-*- coding:utf-8 -*-import urllib3from urllib import urlencodeimport jsondef testEsaInterface(url, param): http = urllib3.PoolManager() try: r = http.request('POST',url, body=json.dumps(param).encode('utf-8'),headers={'Content-Type': 'application/json'}) if r.status == 200: print r.data except urllib3.exceptions.MaxRetryError as e: print repr(e) 如果想对返回的结果进行处理,先将返回的数据进行解码以及反序列化,可以使用decode函数解码,loads函数反序列化。然后采用类似于下标的访问方式获取相关字段的值。示例代码如下: 123res = json.loads(r.data.decode('utf-8'))if (\"906\" == res['data']['returnCode']): # to do something 下图是我测试人脸识别功能一个测试结果: postman发送post请求 弄完工具之后,本来想再做一个网页版的,后面发现chrome中有一个插件postman, 可以解决我的问题,突然发现重复造轮子咯。下面简单介绍一下,如何用postman发送 post请求,其中请求的参数为JSON格式。步骤如下: 选择发送请求的方式为 POST; 输入访问的URL 在headers标签页输入 Content-type, 对应的值为 application/json; 在body标签页面,选择raw,然后在输入JSON格式的请求参数; 最后点击send按钮即可. 是不是发现很简单,如果你觉得还是不清楚,可以参考此博文。 本文仅仅简单介绍了urllib3模块发送HTTP的POST请求方式,还有很多功能需要大家自己去发掘。 参考 安装urllib3: https://pypi.python.org/pypi/urllib3/ urllib3的使用教程: https://urllib3.readthedocs.io/en/latest/user-guide.html postman以JSON格式发送POST请求: http://www.cnblogs.com/shimh/p/6093229.html","comments":true,"tags":[{"name":"python","slug":"python","permalink":"https://cloudfeng.github.io/tags/python/"},{"name":"http","slug":"http","permalink":"https://cloudfeng.github.io/tags/http/"}]},{"title":"Hello World","date":"2017-07-21T16:00:00.000Z","path":"2017/07/22/hello-world/","text":"大家好: 本文是使用Hexo的第一篇博文,主要是给大家报个到。欢迎来到我的菜园子。","comments":true,"tags":[{"name":"随记","slug":"随记","permalink":"https://cloudfeng.github.io/tags/%E9%9A%8F%E8%AE%B0/"}]},{"title":"sicp 数据对象之集合操作","date":"2017-07-21T16:00:00.000Z","path":"2017/07/22/sicp-dataobject-set/","text":"本文主要介绍不同的数据抽象,对应用程序在使用公共函数时会造成何种影响。这里主要是使用集合使用三种数据来表示:集合作为未排序的表、集合作为排序的表、集合作为有序二叉树,探讨不同的数据对象形式表示集合时,对使用集合的程序性能的影响。 基本概念 选择函数与构造函数 构造函数:说明一个数据对象是由哪些原始组成的。 选择函数:怎么将数据对象中的组成元素抽取出来。 数据抽象定义 数据定义就是一组选择函数与构造函数,以及为了使它们成为一套合法的表示。因此它们需要满足一组特定的条件。 看着上面两个定义是不是很拗口,不知道在说啥。举个例子来说,如果一个有理数p是由整数 n 和 整数 d 构造而成。也就是 p = n /d。 12345;构造函数 p = n / dp = (make-rat n d);选择函数n = (numer p)d = (denom p) 集合操作 在高中的数学我们学习过,集合表示一组无重复的元素聚集而成。在数据结构中,集合表示一组具有相同属性的元素,并且满足数学中的集合属性:无序性。 常见集合的操作,判断某个元素是否属于某个集合、求两个集合的交集、求两个集合的并集。假设集合A={1,2,3}、B={2,3,4},以及元素m=2,n=5,则: 判断某个元素是否属于某个集合:m 属于A,也属于B,而n不属于A,也不属于B; A 与 B 的交集为:{2, 3}; A 与 B 的并集为:{1, 2, 3, 4} 下面我们来看看使用三种不同的数据表示,会对写集合常见操作造成什么样影响。 集合作为未排序的表 构造函数与选择函数 直接使用list 函数,但是集合中元素是排序的,比如集合A={1, 10, 2, 4, 3}。 判断某个元素是否属于某个集合 1234(define (element-of-set? x set) (cond ((null? set) #f) (else (or (equal? x (car set)) (element-of-set? x (cdr set)))))) 求两个集合的交集 12345(define (intersection-set set1 set2) (cond ((or (null? set1) (null? set2)) '()) ((element-of-set? (car set1) set2) (cons (car set1) (intersection-set (cdr set1) set2))) (else (intersection-set (cdr set1) set2)))) 求两个集合的并集 123456(define (union-set set1 set2) (cond ((null? set1) set2) ((null? set2) set1) ((element-of-set? (car set1) set2) (union-set (cdr set1) set2)) (else (cons (car set1) (union-set (cdr set1) set2))))) 将某个元素加入集合中 1234(define (adjion-set x set) (if (element-of-set? x set) set (cons x set))) 集合作为排序的表 构造函数与选择函数 直接使用list 函数,但是集合中元素是排序的,比如集合A={1, 2, 3, 4, 5, 10}。 判断某个元素是否属于某个集合 12345(define (element-of-set? x set) (cond ((null? set) #f) ((equal? x (car set)) #t) ((< x (car set)) #f) (else (element-of-set? x (cdr set))))) 求两个集合的交集 12345678(define (intersection-sort-set set1 set2) (if (or (null? set1) (null? set2)) '() (let ((x (car set1)) (y (car set2))) (cond ((= x y) (cons x (ntersection-sort-set (cdr set1) (cdr set2)))) ((< x y) (intersection-sort-set (cdr set1) set2)) (else (intersection-sort-set set1 (cdr set2))))))) 将某个元素加入到集合中 12345(define (adjoin-set x set) (cond ((null? set) (cons x set)) ((equal? x (car set)) set) ((< x (car set)) (cons x set)) (else (cons (car set) (adjoin-set x (cdr set)))))) 求两个集合的并集 12345678(define (union-sort-set set1 set2) (cond ((null? set1) set2) ((null? set2) set1) (else (let ((x (car set1)) (y (car set2))) (cond ((< x y) (cons x (nion-sort-set (cdr set1) set2))) ((> x y) (cons y (union-sort-set set1 (cdr set2)))) (else (cons x (union-sort-set(cdr set1) (cdr set2))))))))) 集合作为有序二叉树 构造函数与选择函数 1234567891011;(dataItem, left-branch, right-branch)(define (entry tree) (car tree))(define (left-branch tree) (cadr tree))(define (right-branch tree) (caddr tree))(define (make-tree entry left-branch right-branch) (list entry left-branch right-branch)) 判断某个元素是否属于某个集合 1234567(define (element-of-set? x set) (if (null? set) #f (let ((dataItem (entry set))) (cond ((= dataItem x) #t) ((> dataItem x) (element-of-set x (left-branch set))) (else (element-of-set x (right-branch set))))))) 求两个集合的交集 12345(define (intersection-set set1 set2) (let ((list1 (tree->list set1)) (list2 (tree->list set2))) (let ((res (intersection-sort-set list1 list2))) (list-tree res)))) 求两个集合的并集 1234567891011121314151617181920212223242526272829303132333435; 辅助函数:将树形结构转为列表结构(define (tree->list tree) (define (copy-to-list tree result-list) (if (null? tree) result-list (copy-to-list (left-branch tree) (cons (entry tree) (copy-to-list (right-branch tree) result-list))))) (copy-to-list tree '()));辅助函数:将列表结构转位树形结构(define (list-tree elements) (car (partial-tree elements (length elements))))(define (partial-tree elts n) (if (= n 0) (cons '() elts) (let ((left-size (quotient (- n 1) 2))) (let ((left-result (partial-tree elts left-size))) (let ((left-tree (car left-result)) (non-left-elts (cdr left-result)) (right-size (- n (+ left-size 1)))) (let ((this-entry (car non-left-elts)) (right-result (partial-tree (cdr non-left-elts) right-size))) (let ((right-tree (car right-result)) (remaining-elts (cdr right-result))) (cons (make-tree this-entry left-tree right-tree) remaining-elts))))))))(define (union-set set1 set2) (let ((list1 (tree->list set1)) (list2 (tree->list set2))) (let ((res (union-sort-set list1 list2))) (list-tree res)))) 添加元素到集合 123456789(define (adjoin-set x set) (cond ((null? set) (make-tree x '() '())) ((= x (entry set)) set) ((< x (entry set)) (make-tree (entry set) (adjoin-set x (left-branch set)) (right-branch set))) (else (make-tree (entry set) (left-branch set) (adjoin-set x (right-branch set)))))) 总结 通过三种不同的数据表示集合,分析了程序在使用集合操作,比如检查是否属于集合、集合的交集、集合并集等操作,在时间复杂度上是存在差异的。如下表格: 表示法 检查元素是否属于集合 添加元素 交集 并集 未排序的表 O(n) O(n) O(n^2) O(n^2) 排序的表 O(n) O(n) O(n) O(n) 有序二叉树 O(log n) O(log n) O(n) O(n) 也就是我们常说: 程序=数据结构+算法","comments":true,"tags":[{"name":"sicp","slug":"sicp","permalink":"https://cloudfeng.github.io/tags/sicp/"},{"name":"scheme","slug":"scheme","permalink":"https://cloudfeng.github.io/tags/scheme/"}]}]