前言

这个月我想学一下go语言,于是决定学习一个go实现的缓存服务。

首先本文基于golang的http包实现一个简单http的缓存服务,因为用golang自带的http包实现一个处理请求的服务端十分便利,我们只需要写一个简单的map保存数据,写一个http的handler处理请求即可,你不需要考虑任何复杂的并发问题,因为golang的http服务框架会帮你处理好底层的一切。

cache

缓存服务接口

本文实现的简单缓存具备三种基本接口 : SET GET DEL 分别通过http协议的PUTGETDELETE、操作进行。

put

  1. PUT /cache/<key>
  2. content
  3. <value>

GET

  1. GET /cache/<key>
  2. content
  3. <value>

DELETE

  1. DELETE /cache/<key>

cache包实现

本缓存服务里面通过一个cache包实现缓存功能。

cache包接口定义

  1. package cache
  2. type Cache interface {
  3. Set(string, []byte) error
  4. Get(string) ([]byte, error)
  5. Del(string) error
  6. GetStat() Stat
  7. }

cache 接口实现

Cache 结构很简单,一张map,另加一把锁保护即可.

  1. package cache
  2. import "sync"
  3. type SimpleCache struct {
  4. c map[string][]byte
  5. mutex sync.RWMutex
  6. Stat
  7. }
  8. func (c *SimpleCache) Set(k string, v []byte) error {
  9. c.mutex.Lock()
  10. defer c.mutex.Unlock()
  11. tmp, exist := c.c[k]
  12. if exist {
  13. c.del(k, tmp)
  14. }
  15. c.c[k] = v
  16. c.add(k, v)
  17. return nil
  18. }
  19. func (c *SimpleCache) Get(k string) ([]byte, error) {
  20. c.mutex.RLock()
  21. defer c.mutex.RUnlock()
  22. return c.c[k], nil
  23. }
  24. func (c *SimpleCache) Del(k string) error {
  25. c.mutex.Lock()
  26. defer c.mutex.Unlock()
  27. v, exist := c.c[k]
  28. if exist {
  29. delete(c.c, k)
  30. c.del(k, v)
  31. }
  32. return nil
  33. }
  34. func (c *SimpleCache) GetStat() Stat {
  35. return c.Stat
  36. }
  37. func newInMemoryCache() *SimpleCache {
  38. return &SimpleCache{make(map[string][]byte), sync.RWMutex{}, Stat{}}
  39. }

cache包测试:

  1. package main
  2. import (
  3. "./cache"
  4. "fmt"
  5. )
  6. func main() {
  7. c := cache.New("inmemory")
  8. k, v := "sola", []byte{'a','i','l','u','m','i','y','a'}
  9. c.Set(k, v)
  10. tmp, _ := c.Get(k)
  11. fmt.Println("key: ", k, " value: ", tmp)
  12. c.Del(k)
  13. tmp, _ = c.Get(k)
  14. fmt.Println("key: ", k, " value: ", tmp)
  15. }
  1. sola@sola:~/Coder/GitHub/go-cache/http-cache/server$ go run main.go
  2. 2019/02/10 00:07:15 inmemory ready to serve
  3. key: sola value: [97 105 108 117 109 105 121 97]
  4. sola@sola:~/Coder/GitHub/go-cache/http-cache/server$ go run main.go
  5. 2019/02/10 00:07:28 inmemory ready to serve
  6. key: sola value: [97 105 108 117 109 105 121 97]
  7. key: sola value: []

golang http包使用介绍

Golang自带的http包已经实现了htpp客户端和服务端,我们可以利用它更为快速的开发http服务。本章仅介绍一下http包服务端的使用。

Golang中处理 HTTP 请求主要跟两个东西相关:ServeMux 和 Handler。

ServrMux 本质上是一个 HTTP 请求路由器(或者叫多路复用器,Multiplexor)。它把收到的请求与一组预先定义的 URL 路径列表做对比,然后在匹配到路径的时候调用关联的处理器(Handler)。

处理器(Handler)负责输出HTTP响应的头和正文。任何满足了http.Handler接口的对象都可作为一个处理器。通俗的说,对象只要有个如下签名的ServeHTTP方法即可:

  1. ServeHTTP(http.ResponseWriter, *http.Request)

Golang的 HTTP 包自带了几个函数用作常用处理器,比如NotFoundHandler 和 RedirectHandler。

NotFoundHandler返回一个简单的请求处理器,该处理器会对每个请求都回复"404 page not found"。

RedirectHandler返回一个请求处理器,该处理器会对每个请求都使用状态码code重定向到网址url。

接着,我们来看两个简单的样例:

hello.go

  1. package main
  2. import (
  3. "io"
  4. "log"
  5. "net/http"
  6. )
  7. func HelloGoServer(w http.ResponseWriter, req *http.Request) {
  8. io.WriteString(w, "Hello, this is a GoServer")
  9. }
  10. func main() {
  11. http.HandleFunc("/", HelloGoServer)
  12. err := http.ListenAndServe(":9090", nil)
  13. if err != nil {
  14. log.Fatal("ListenAndServer ", err)
  15. }
  16. }

浏览器看看我们的hello程序:



1、 http.HandleFunc("/", HelloGoServer)

http提供的外部方法HandleFunc实际也是调用ServeMux的内部方法,只是它使用的是http包默认的ServeMux,注册一个处理器函数handler(HelloGoServer)和对应的模式pattern(/)(注册到DefaultServeMux)。ServeMux的文档解释了模式的匹配机制。

2、http.ListenAndServe(":9090", nil)

ListenAndServe同字面意思监听并服务。这里是监听9090端口,它其实也是一个外部方法,调用内部Server类型的ListenAndServe。

Redirect.go

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. )
  6. func main() {
  7. mux := http.NewServeMux()
  8. rh := http.RedirectHandler("http://www.baidu.com", 307)
  9. mux.Handle("/foo", rh)
  10. log.Println("Listening...")
  11. http.ListenAndServe(":3000", mux)
  12. }

1、这个样例中我们没用默认的ServeMux,而是通过 http.NewServeMux 函数来创建一个空的 ServeMux。

2、http.RedirectHandler 函数创建了一个重定向处理器,这个处理器会对收到的所有请求,都执行307重定向操作到 http://www.baidu.com

3、ServeMux.Handle 函数将处理器注册到新创建的 ServeMux,所以它在 URL 路径/foo 上收到所有的请求都交给这个处理器。

4、最后通过 http.ListenAndServe 函数启动服务处理请求,通过传递刚才创建的 ServeMux来为请求去匹配对应处理器。

键入后你会跳转到百度。

http-cache-server 实现

最后来实现我们的cache-server

cache已经有了,我们只需要写一个http的Handler来分别处理GETPUT,DELETE请求即可。

上面提过任何满足了http.Handler接口的对象即ServeHTTP(http.ResponseWriter, *http.Request)都可作为一个处理器,那么我们先来看看这个接口的参数.

ResponseWriter接口被HTTP处理器用于构造HTTP回复。

  1. type ResponseWriter interface {
  2. // Header返回一个Header类型值,该值会被WriteHeader方法发送。
  3. // 在调用WriteHeader或Write方法后再改变该对象是没有意义的。
  4. Header() Header
  5. // WriteHeader该方法发送HTTP回复的头域和状态码。
  6. // 如果没有被显式调用,第一次调用Write时会触发隐式调用WriteHeader(http.StatusOK)
  7. // WriterHeader的显式调用主要用于发送错误码。
  8. WriteHeader(int)
  9. // Write向连接中写入作为HTTP的一部分回复的数据。
  10. // 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)
  11. // 如果Header中没有"Content-Type"键,
  12. // 本方法会使用包函数DetectContentType检查数据的前512字节,将返回值作为该键的值。
  13. Write([]byte) (int, error)
  14. }

Request类型代表一个服务端接受到的或者客户端发送出去的HTTP请求。Request各字段的意义和用途在服务端和客户端是不同的。

  1. type Request struct {
  2. // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
  3. Method string
  4. // URL在服务端表示被请求的URI,在客户端表示要访问的URL。
  5. //
  6. // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
  7. // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
  8. // (参见RFC 2616, Section 5.1.2)
  9. //
  10. // 在客户端,URL的Host字段指定了要连接的服务器,
  11. // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
  12. URL *url.URL
  13. // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
  14. Proto string // "HTTP/1.0"
  15. ProtoMajor int // 1
  16. ProtoMinor int // 0
  17. // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
  18. // accept-encoding: gzip, deflate
  19. // Accept-Language: en-us
  20. // Connection: keep-alive
  21. // 则:
  22. // Header = map[string][]string{
  23. // "Accept-Encoding": {"gzip, deflate"},
  24. // "Accept-Language": {"en-us"},
  25. // "Connection": {"keep-alive"},
  26. // }
  27. // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
  28. // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
  29. Header Header
  30. // Body是请求的主体。
  31. //
  32. // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
  33. // Client的Transport字段会负责调用Body的Close方法。
  34. //
  35. // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
  36. // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
  37. Body io.ReadCloser
  38. // ContentLength记录相关内容的长度。
  39. // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
  40. // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
  41. ContentLength int64
  42. // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
  43. // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
  44. TransferEncoding []string
  45. // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
  46. Close bool
  47. // 在服务端,Host指定URL会在其上寻找资源的主机。
  48. // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
  49. // Host的格式可以是"host:port"。
  50. //
  51. // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
  52. // 如过该字段为"",Request.Write方法会使用URL字段的Host。
  53. Host string
  54. // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
  55. // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
  56. Form url.Values
  57. // PostForm是解析好的POST或PUT的表单数据。
  58. // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
  59. PostForm url.Values
  60. // MultipartForm是解析好的多部件表单,包括上传的文件。
  61. // 本字段只有在调用ParseMultipartForm后才有效。
  62. // 在客户端,会忽略请求中的本字段而使用Body替代。
  63. MultipartForm *multipart.Form
  64. // Trailer指定了会在请求主体之后发送的额外的头域。
  65. //
  66. // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
  67. // (客户端会声明哪些trailer会发送)
  68. // 在处理器从Body读取时,不能使用本字段。
  69. // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
  70. // (如果客户端发送了这些键值对),此时才可以访问本字段。
  71. //
  72. // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
  73. // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
  74. // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
  75. // 一旦请求主体返回EOF,调用者就不可再修改Trailer。
  76. //
  77. // 很少有HTTP客户端、服务端或代理支持HTTP trailer。
  78. Trailer Header
  79. // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
  80. // 本字段不是ReadRequest函数填写的,也没有定义格式。
  81. // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
  82. // 客户端会忽略请求中的RemoteAddr字段。
  83. RemoteAddr string
  84. // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
  85. // (参见RFC 2616, Section 5.1)
  86. // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
  87. RequestURI string
  88. // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
  89. // 本字段不是ReadRequest函数填写的。
  90. // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
  91. // 客户端会忽略请求中的TLS字段。
  92. TLS *tls.ConnectionState
  93. }

golang请求及应答中涉及到的常量.

golang中的HTTP状态码

  1. const (
  2. StatusContinue = 100
  3. StatusSwitchingProtocols = 101
  4. StatusOK = 200
  5. StatusCreated = 201
  6. StatusAccepted = 202
  7. StatusNonAuthoritativeInfo = 203
  8. StatusNoContent = 204
  9. StatusResetContent = 205
  10. StatusPartialContent = 206
  11. StatusMultipleChoices = 300
  12. StatusMovedPermanently = 301
  13. StatusFound = 302
  14. StatusSeeOther = 303
  15. StatusNotModified = 304
  16. StatusUseProxy = 305
  17. StatusTemporaryRedirect = 307
  18. StatusBadRequest = 400
  19. StatusUnauthorized = 401
  20. StatusPaymentRequired = 402
  21. StatusForbidden = 403
  22. StatusNotFound = 404
  23. StatusMethodNotAllowed = 405
  24. StatusNotAcceptable = 406
  25. StatusProxyAuthRequired = 407
  26. StatusRequestTimeout = 408
  27. StatusConflict = 409
  28. StatusGone = 410
  29. StatusLengthRequired = 411
  30. StatusPreconditionFailed = 412
  31. StatusRequestEntityTooLarge = 413
  32. StatusRequestURITooLong = 414
  33. StatusUnsupportedMediaType = 415
  34. StatusRequestedRangeNotSatisfiable = 416
  35. StatusExpectationFailed = 417
  36. StatusTeapot = 418
  37. StatusInternalServerError = 500
  38. StatusNotImplemented = 501
  39. StatusBadGateway = 502
  40. StatusServiceUnavailable = 503
  41. StatusGatewayTimeout = 504
  42. StatusHTTPVersionNotSupported = 505
  43. )

golang 中的HTTP行为常量定义

  1. 5 package http
  2. 6
  3. 7 // Common HTTP methods.
  4. 8 //
  5. 9 // Unless otherwise noted, these are defined in RFC 7231 section 4.3.
  6. 10 const (
  7. 11 MethodGet = "GET"
  8. 12 MethodHead = "HEAD"
  9. 13 MethodPost = "POST"
  10. 14 MethodPut = "PUT"
  11. 15 MethodPatch = "PATCH" // RFC 5789
  12. 16 MethodDelete = "DELETE"
  13. 17 MethodConnect = "CONNECT"
  14. 18 MethodOptions = "OPTIONS"
  15. 19 MethodTrace = "TRACE"
  16. 20 )

cacheHandler

到这里所有用到的http包中结构都已经说明了,开始写main包,

我们定义一个cacheHandler类型,用我们的inMemoryCache接口初始化它,并实现他的ServeHTTP方法。

最后将cacheHandler类型的CacheHandler方法注册到http包默认的ServeMux路由,绑定端口26316,启动服务。

  1. package main
  2. import (
  3. "./cache"
  4. "io/ioutil"
  5. "net/http"
  6. "log"
  7. "strings"
  8. )
  9. type cacheHandler struct {
  10. cache.Cache
  11. }
  12. func (h *cacheHandler) CacheHandler(w http.ResponseWriter, r *http.Request) {
  13. log.Println("url ", r.URL, " Method ", r.Method)
  14. //Split Get Key
  15. key := strings.Split(r.URL.EscapedPath(), "/")[2]
  16. if len(key) == 0 {
  17. w.WriteHeader(http.StatusBadRequest)
  18. return
  19. }
  20. m := r.Method
  21. if m == http.MethodPut {
  22. h.HandlePut(key, w, r)
  23. return
  24. } else if m == http.MethodGet {
  25. h.HandleGet(key, w, r)
  26. return
  27. } else if m == http.MethodDelete {
  28. h.HandleDelete(key, w, r)
  29. return
  30. }
  31. w.WriteHeader(http.StatusMethodNotAllowed)
  32. }
  33. func (h *cacheHandler) HandlePut(k string, w http.ResponseWriter, r *http.Request){
  34. b, _ := ioutil.ReadAll(r.Body)
  35. if len(b) != 0 {
  36. e := h.Set(k, b)
  37. if e != nil {
  38. log.Println(e)
  39. w.WriteHeader(http.StatusInternalServerError)
  40. } else {
  41. w.Write([]byte("successful"))
  42. }
  43. }
  44. }
  45. func (h *cacheHandler) HandleGet(k string, w http.ResponseWriter, r *http.Request){
  46. b, e := h.Get(k)
  47. if e != nil {
  48. log.Println(e)
  49. w.WriteHeader(http.StatusInternalServerError)
  50. return
  51. }
  52. if len(b) == 0 {
  53. w.WriteHeader(http.StatusNotFound)
  54. return
  55. }
  56. w.Write(b)
  57. }
  58. func (h *cacheHandler) HandleDelete(k string, w http.ResponseWriter, r *http.Request){
  59. e := h.Del(k)
  60. if e != nil {
  61. log.Println(e)
  62. w.WriteHeader(http.StatusInternalServerError)
  63. } else {
  64. w.Write([]byte("successful"))
  65. }
  66. }
  67. func main() {
  68. c := cache.New("inmemory")
  69. h := cacheHandler{c}
  70. http.HandleFunc("/cache/", h.CacheHandler)
  71. http.ListenAndServe(":26316", nil)
  72. }

程序测试

使用postman测试put



浏览器直接测试Get

使用postman测试Delete

再次Get会返回404

与redis的比较

缓存功能的服务已经实现了,那么它的性能怎样呢,键值对缓存服务中比较有名的是redis,我们和它做下比较。

redis是一款in memory数据结构存储,可以被用作数据库、缓存及消息中间件。支持包括字符串、散列、列表及集合在内的多种数据结构、支持范围查询、具备内建的复制功能、lua脚本、LRU缓存淘汰策略、事务处理及两种不同的磁盘持久化方案(RDB和AOF)还能建立redis集群提供高可用性能。

redis的RDB持久化方案会在指定时间点将内存数据集快照存入磁盘。RDB开始工作时,会自己fork出一个持久化进程,此时原服务进程的一切内存数据相当于保存了一份快照、然后持久化进程将它的内存压缩并写入磁盘。

redis的AOF方案则是将服务接受到的所有写操作记入磁盘上的日志文件、将日志文件的格式和redis协议保持一致且只允许添加。

RDB方案对性能的影响比AOF小,因为它不占用原服务进程的磁盘IO、RDB的缺点在于系统死机时丢失的数据比AOF要多,因为它只保留得到数据到上一次持久化进程运行的那个时间点,而AOF可以一直记录到系统死机之前的最后一次写操作的数据。

本篇实现的是一个简单的内存缓存,不包含持久化方案,也不会保存进磁盘,一旦服务器重启所有数据就会丢失。

性能方面只有redis的1/4,主要原因在于REST协议的解析上,REST基于HTTP,HTTP基于TCP,而redis是直接建立在TCP上的。


下一篇文章会实现一个基于TCP的缓存协议规范。本系列笔记最终实现的缓存会是使用HTTP/REST协议和TCP混合的接口规范,其中HTTP/REST只用于各种管理功能。


本文源码 :https://github.com/BethlyRoseDaisley/go-cache-server/tree/master/http-cache/server

参考资料:

Go net/http包

Go 中文标准库

分布式缓存-原理、架构及Go语言实现 ----- 胡世杰

golang实现分布式缓存笔记(一)基于http的缓存服务的更多相关文章

  1. 基于NHibernate二级缓存的MongoDB组件

    设计一套基于NHibernate二级缓存的MongoDB组件(上)   摘要:NHibernate Contrib 支持很多第三方的二级缓存,如SysCache,MemCache,Prevalence ...

  2. Docker笔记--镜像&基于GO项目创建Docker镜像

    Docker笔记--镜像&基于GO项目创建Docker镜像 核心概念 Doker镜像--包含一个基本的操作系统运行环境和应用程序,镜像是创建Docker容器的基础. Docker容器--如果把 ...

  3. 基于.NET CORE微服务框架 -谈谈Cache中间件和缓存降级

    1.前言 surging受到不少.net同学的青睐,也提了不少问题,提的最多的是什么时候集成API 网关,在这里回答大家最近已经开始着手研发,应该在1,2个月内会有个初版API网关,其它像Token身 ...

  4. 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式

    在之前的系列文章中聊过分布式事务的一种实现方案,即通过在集群中暴露actor服务来实现分布式事务的本地原子化.但是actor服务本身有其特殊性,场景上并不通用.所以今天来讲讲分布式事务实现方案之sag ...

  5. 通过Dapr实现一个简单的基于.net的微服务电商系统(十八)——服务保护之多级缓存

    很久没有更新dapr系列了.今天带来的是一个小的组件集成,通过多级缓存框架来实现对服务的缓存保护,依旧是一个简易的演示以及对其设计原理思路的讲解,欢迎大家转发留言和star 目录:一.通过Dapr实现 ...

  6. 基于MemoryCache的缓存辅助类

    背景: 1. 什么是MemoryCache? memoryCache就是用电脑内存做缓存处理 2.使用范围? 可用于不常变的数据,进行保存在内存中,提高处理效率 代码: /// <summary ...

  7. 分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载

    一.分布式消息总线以及基于Socket的实现 在前面的分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载一文之中给大家分享和介绍了一个极其简单也非常容易上的基于.N ...

  8. 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【十】——使用CacheCow和ETag缓存资源

    系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html 前言 本文将使用一个开源框架CacheCow来实现针对Http请求资源缓存,本文主要介绍服务器端的 ...

  9. c#缓存 笔记

    1:缓存. 你需要了解大数据高并发的瓶颈在哪里,一般都是数据库层面的,机械硬盘承载不起非常快速的读写操作,cpu承载不起大量的逻辑运算,所以最基本的解决思路就是:1.换固态硬盘加快硬盘的读写效率.2. ...

随机推荐

  1. mysql 原理 ~ sql执行

    一 普通sql执行的具体过程1 连接器  管理连接,权限验证2 分析器  词法分析,语法分析   比如 数据表和数据列是否存在, 别名是否有歧义,是否符合标准sql语法等3 优化器检测   执行计划生 ...

  2. 快速创建SpringBoot2.x应用之工具类自动创建web应用、SpringBoot2.x的依赖默认Maven版本

    快速创建SpringBoot2.x应用之工具类自动创建web应用简介:使用构建工具自动生成项目基本架构 1.工具自动创建:http://start.spring.io/ 2.访问地址:http://l ...

  3. GSON中Java对象与JSON互相转换——(一)

    json的转换插件是通过java的一些工具,直接将java对象或集合转换成json字符串. 常用的json转换工具有如下几种: 1)jsonlib 2)Gson:google 3)fastjson:阿 ...

  4. script & scriptreplay

    script是什么 scirpt就是一个命令,可以制作一份记录输出到终端的记录.对于那些想要真实记录终端会话的人来说,这很有用.该记录可以保存并在以后再打印出来. 怎么用 默认情况下,我们可以通过在终 ...

  5. Linux下clock计时函数学习

    平时在Linux和Winows下都有编码的时候,移植代码的时候免不了发现一些问题.1. 你到底准不准?关于clock()计时函数首先是一段简单的测试代码,功能为测试从文本文件读取数据并赋值给向量最后打 ...

  6. Linux 入门记录:十二、Linux 权限机制【转】

    转自:https://www.cnblogs.com/mingc/p/7591287.html 一.权限 权限是操作系统用来限制资源访问的机制,权限一般分为读.写.执行. 系统中每个文件都拥有特定的权 ...

  7. Vistual Studio Community 2017 账号的许可证过期,公安网激活方法

    方法:   1.外网电脑打开Vistual Studio Community2017   2.在许可证过期弹窗中登陆账号即可自动下载许可证完成激活      许可证下载路径(C:\用户\user\Ap ...

  8. oracle 用户 权限

    一. 概述 与权限,角色相关的视图大概有下面这些: DBA_SYS_PRIVS: 查询某个用户所拥有的系统权限 USER_SYS_PRIVS:   当前用户所拥有的系统权限 SESSION_PRIVS ...

  9. 13-JS中的面向对象

    创建对象的几种常用方式 1.使用Object或对象字面量创建对象 2.工厂模式创建对象 3.构造函数模式创建对象 4.原型模式创建对象 1.使用Object或对象字面量创建对象 JS中最基本创建对象的 ...

  10. Z-index研究 opacity和z-index一起使用

    Z-index研究 opacity和z-index一起使用   关于z-index的真正问题是,很少有人理解它到底是怎么用.其实它并不复杂,但是如果你从来没有花一定时间去看具体的z-index相关文档 ...