docker pull命令實現與映象儲存(3)
在《pull命令實現與映象儲存(1)》和《pull命令實現與映象儲存(2)》我們分析pull命令在docker客戶端的實現部分,最後我們瞭解到客戶端將結構化引數傳送到服務端的URL:/images/create。接下來我們將分析在服務端的實現部分,將從該URL入手。
我們在《dockerd路由和初始化》中瞭解了docker的API是如何初始化的,實現在docker\cmd\dockerd\daemon.go我們回顧下:
func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
decoder := runconfig.ContainerDecoder {}
routers := []router.Router{}
// we need to add the checkpoint router before the container router or the DELETE gets masked
routers = addExperimentalRouters(routers, d, decoder)
routers = append(routers, []router.Router{
container.NewRouter(d, decoder), //關於容器命令的API
image.NewRouter (d, decoder), //關於鏡命令的API
systemrouter.NewRouter(d, c), //關於繫命令的API api/server/router/system被重新命名了
volume.NewRouter(d), //關於卷命令的API
build.NewRouter(dockerfile.NewBuildManager(d)),//關於構建命令的API
swarmrouter.NewRouter(c),
}...)
if d.NetworkControllerEnabled () {
routers = append(routers, network.NewRouter(d, c))
}
s.InitRouter(utils.IsDebugEnabled(), routers...)
}
可以看到有關於映象的API “ image.NewRouter(d, decoder)”,實現在docker\api\server\router\image\image.go:
// NewRouter initializes a new image router
func NewRouter(backend Backend, decoder httputils.ContainerDecoder) router.Router {
r := &imageRouter{
backend: backend,
decoder: decoder,
}
r.initRoutes()
return r
}
// Routes returns the available routes to the image controller
func (r *imageRouter) Routes() []router.Route {
return r.routes
}
// initRoutes initializes the routes in the image router
func (r *imageRouter) initRoutes() {
r.routes = []router.Route{
// GET
router.NewGetRoute("/images/json", r.getImagesJSON),
router.NewGetRoute("/images/search", r.getImagesSearch),
router.NewGetRoute("/images/get", r.getImagesGet),
router.NewGetRoute("/images/{name:.*}/get", r.getImagesGet),
router.NewGetRoute("/images/{name:.*}/history", r.getImagesHistory),
router.NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
// POST
router.NewPostRoute("/commit", r.postCommit),
router.NewPostRoute("/images/load", r.postImagesLoad),
router.Cancellable(router.NewPostRoute("/images/create", r.postImagesCreate)),
router.Cancellable(router.NewPostRoute("/images/{name:.*}/push", r.postImagesPush)),
router.NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
router.NewPostRoute("/images/prune", r.postImagesPrune),
// DELETE
router.NewDeleteRoute("/images/{name:.*}", r.deleteImages),
}
}
可以看到函式呼叫過程:NewRouter->initRoutes,且我們上面提到的pull命令的API:/images/create赫然在列。這裡已經很明瞭了,pull命令在服務端將由r.postImagesCreate處理,實現在docker\api\server\router\image\image_routes.go,我們分析下該函式:
// Creates an image from Pull or from Import
func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil {
return err
}
// Calling POST /v1.25/images/create?fromImage=gplang&tag=latest
var (
image = r.Form.Get("fromImage")
repo = r.Form.Get("repo")
tag = r.Form.Get("tag")
message = r.Form.Get("message")
err error
output = ioutils.NewWriteFlusher(w)
)
defer output.Close()
//設定迴應的http頭,說明資料是json
w.Header().Set("Content-Type", "application/json")
//映象名不存在
if image != "" { //pull
metaHeaders := map[string][]string{}
for k, v := range r.Header {
if strings.HasPrefix(k, "X-Meta-") {
metaHeaders[k] = v
}
}
authEncoded := r.Header.Get("X-Registry-Auth")
authConfig := &types.AuthConfig{}
if authEncoded != "" {
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
// for a pull it is not an error if no auth was given
// to increase compatibility with the existing api it is defaulting to be empty
authConfig = &types.AuthConfig{}
}
}
// Backend is all the methods that need to be implemented
// to provide image specific functionality(功能).
//在Daemon型別實現了該API介面,在docker/daemon/image_pull.go
err = s.backend.PullImage(ctx, image, tag, metaHeaders, authConfig, output)
} else { //import
src := r.Form.Get("fromSrc")
// 'err' MUST NOT be defined within this block, we need any error
// generated from the download to be available to the output
// stream processing below
err = s.backend.ImportImage(src, repo, tag, message, r.Body, output, r.Form["changes"])
}
if err != nil {
if !output.Flushed() {
return err
}
sf := streamformatter.NewJSONStreamFormatter()
output.Write(sf.FormatError(err))
}
return nil
}
—————————————2016.12.09 22:03 更新—————————————————
可以看到主要是從http引數中解析出映象名和tag,分了有映象名和無映象名兩個分支。我們拉取映象時我們傳入了ubuntu這個映象名,所以走if分支(image 為空的情況不知是什麼情況,暫時不去深究)。從上面的程式碼中我們可以看到以映象名,tag以及授權資訊等引數呼叫函式s.backend.PullImage。可是backend這個是什麼呢?backend是介面Backend的例項,我們要找其實現類。
type Backend interface {
containerBackend
imageBackend
importExportBackend
registryBackend
}
我們回到映象相關的API初始化的程式碼:
// NewRouter initializes a new image router
func NewRouter(backend Backend, decoder httputils.ContainerDecoder) router.Router {
r := &imageRouter{
backend: backend,
decoder: decoder,
}
r.initRoutes()
return r
}
可以看到是NewRouter的時候傳入的,我們看下呼叫程式碼,在docker\cmd\dockerd\daemon.go的 initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) 函式,有:
image.NewRouter(d, decoder),
我們再往上看initRouter的呼叫程式碼,在檔案docker\cmd\dockerd\daemon.go的star函式:
initRouter(api, d, c)
原來是這裡的d,再看下d是如何來的:
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
返回的是一個Daemon物件指標。這下我們我們可以知道s.backend.PullImage實際上呼叫的是Daemon的成員PullImage函式。實際上Daemon不僅實現了image相關的介面,而是實現了所有docker的操作的介面。往後我們分析的介面都可以在那裡找到實現。我現在去看下PullImage函式的實現,在檔案docker\daemon\image_pull.go:
// PullImage initiates a pull operation. image is the repository name to pull, and
// tag may be either empty, or indicate a specific tag to pull.
func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
// Special case: "pull -a" may send an image name with a
// trailing :. This is ugly, but let's not break API
// compatibility.
image = strings.TrimSuffix(image, ":")
//fromImage=gplang&tag=latest
//name格式: xxx:yyy | @zzz xxx 代表映象名,如果沒有加上倉庫地址:docker.io,會使用預設的倉庫地址, yyy :代表版本 zzz: 代表摘要
ref, err := reference.ParseNamed(image)
if err != nil {
return err
}
//如果tag不為空,則要看標籤還是摘要,或者什麼也不是
if tag != "" {
// The "tag" could actually be a digest.
var dgst digest.Digest
dgst, err = digest.ParseDigest(tag)
if err == nil {
ref, err = reference.WithDigest(ref, dgst)
} else {
ref, err = reference.WithTag(ref, tag)
}
if err != nil {
return err
}
}
return daemon.pullImageWithReference(ctx, ref, metaHeaders, authConfig, outStream)
}
是不是看到熟悉的東西,對這裡又將映象名等解析了一遍,如果我們傳入的是tag就得到一個reference.NamedTagged物件ref。然後交給pullImageWithReference:
func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
// Include a buffer so that slow client connections don't affect
// transfer performance.
progressChan := make(chan progress.Progress, 100)
writesDone := make(chan struct{})
ctx, cancelFunc := context.WithCancel(ctx)
go func() {
writeDistributionProgress(cancelFunc, outStream, progressChan)
close(writesDone)
}()
//注意這裡有很多重要的介面
imagePullConfig := &distribution.ImagePullConfig{
MetaHeaders: metaHeaders,
AuthConfig: authConfig,
ProgressOutput: progress.ChanOutput(progressChan),
RegistryService: daemon.RegistryService,//預設regist服務介面實現的例項
ImageEventLogger: daemon.LogImageEvent,
MetadataStore: daemon.distributionMetadataStore,
ImageStore: daemon.imageStore,
ReferenceStore: daemon.referenceStore,
DownloadManager: daemon.downloadManager,
}
err := distribution.Pull(ctx, ref, imagePullConfig)
close(progressChan)
<-writesDone
return err
}
這裡再呼叫distribution.Pull,還有就是要注意這裡構造了一個imagePullConfig 物件,裡面包含了很多拉取映象時要用到的介面(我們暫且先記下,後面分析到的時候再回過頭來看)。如此繞來繞去,想必是有點暈頭轉向了。在繼續之前我們先說下docker的程式碼風格,如果瞭解了docker的程式碼風格,我想我們就知道docker解決問題的套路,這樣即使我們沒有完全掌握docker原始碼,我們也可以根據我們看過的docker原始碼推測出其他邏輯。我們先就以即將要分析的distribution.Pull中的Service為例。
可以看到在檔案docker\registry\service.goService中定義了Service介面,介面中有一些映象倉庫相關的方法。接著在介面定義的檔案中定義了Service介面的預設實現。他們是怎麼關聯在一起的呢(不是指go語法上的關聯)。一般在這個檔案中為定義NewXXX的方法,該方法返回的就是了介面實現物件的指標:
// NewService returns a new instance of DefaultService ready to be
// installed into an engine.
func NewService(options ServiceOptions) *DefaultService {
return &DefaultService{
config: newServiceConfig(options),
}
}
明白了這個套路,我們接著分析distribution.Pull:
/ Pull initiates a pull operation. image is the repository name to pull, and
// tag may be either empty, or indicate a specific tag to pull.
func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig) error {
// Resolve the Repository name from fqn to RepositoryInfo
//在/docker/registry/config.go的 newServiceConfig初始化倉庫地址和倉庫映象地址,其中有官方的和通過選項insecure-registry自定義的私有倉庫,實質是通過IndexName找到IndexInfo,有用的也只有IndexName
//這裡的imagePullConfig.RegistryService為daemon.RegistryService,也即是docker\registry\service.go的DefaultService
//初始化時,會將insecure-registry選項和registry-mirrors存入ServiceOptions,在NewService函式被呼叫時,作為參入傳入
//repoInfo為RepositoryInfo物件,其實是對reference.Named物件的封裝,添加了映象成員和官方標示
repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref)
if err != nil {
return err
}
// makes sure name is not empty or `scratch`
//為了確保不為空或?
if err := ValidateRepoName(repoInfo.Name()); err != nil {
return err
}
// APIEndpoint represents a remote API endpoint
// /docker/cmd/dockerddaemon.go----大約125 和 248
//如果沒有映象倉庫伺服器地址,預設使用V2倉庫地址registry-1.docker.io
//Hostname()函式來源於Named
//實質上如果Hostname()返回的是官方倉庫地址,則endpoint的URL將是registry-1.docker.io,如果有映象則會新增映象作為endpoint
// 否則就是私有地址的兩種型別:http和https
//V2的介面具體程式碼在Zdocker\registry\service_v2.go的函式lookupV2Endpoints
//
logrus.Debugf("Get endpoint from:%s", repoInfo.Hostname())
endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(repoInfo.Hostname())
if err != nil {
return err
}
var (
lastErr error
// discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
// By default it is false, which means that if an ErrNoSupport error is encountered, it will be saved in lastErr.
// As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
// any subsequent ErrNoSupport errors in lastErr.
// It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
// error is the ones from v2 endpoints not v1.
discardNoSupportErrors bool
// confirmedV2 is set to true if a pull attempt managed to
// confirm that it was talking to a v2 registry. This will
// prevent fallback to the v1 protocol.
confirmedV2 bool
// confirmedTLSRegistries is a map indicating which registries
// are known to be using TLS. There should never be a plaintext
// retry for any of these.
confirmedTLSRegistries = make(map[string]struct{})
)
//如果設定了映象伺服器地址,且使用了官方預設的映象倉庫,則endpoints包含官方倉庫地址和映象伺服器地址,否則就是私有倉庫地址的http和https形式
for _, endpoint := range endpoints {
logrus.Debugf("Endpoint API version:%d", endpoint.Version)
if confirmedV2 && endpoint.Version == registry.APIVersion1 {
logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
continue
}
if endpoint.URL.Scheme != "https" {
if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
continue
}
}
logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
//針對每一個endpoint,建立一個Puller,newPuller會根據endpoint的形式(endpoint應該遵循restful api的設計,url中含有版本號),決定採用version1還是version2版本
//imagePullConfig是個很重要的物件,包含了很多映象操作相關的物件
puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
if err != nil {
lastErr = err
continue
}
if err := puller.Pull(ctx, ref); err != nil {
// Was this pull cancelled? If so, don't try to fall
// back.
fallback := false
select {
case <-ctx.Done():
default:
if fallbackErr, ok := err.(fallbackError); ok {
fallback = true
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
}
err = fallbackErr.err
}
}
if fallback {
if _, ok := err.(ErrNoSupport); !ok {
// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
discardNoSupportErrors = true
// append subsequent errors
lastErr = err
} else if !discardNoSupportErrors {
// Save the ErrNoSupport error, because it's either the first error or all encountered errors
// were also ErrNoSupport errors.
// append subsequent errors
lastErr = err
}
logrus.Errorf("Attempting next endpoint for pull after error: %v", err)
continue
}
logrus.Errorf("Not continuing with pull after error: %v", err)
return err
}
imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull")
return nil
}
if lastErr == nil {
lastErr = fmt.Errorf("no endpoints found for %s", ref.String())
}
return lastErr
}
程式碼比較多,總結起來就是映象倉庫資訊repoInfo–>端點資訊endpoints–>puller拉取映象。這是應該有很多疑問,映象倉庫資訊是個什麼東西?端點資訊是什麼?如何拉取?我們逐個分析。首先我們看下映象倉庫資訊的定義以及例子(在docker\api\types\registry\registry.go):
type RepositoryInfo struct {
reference.Named
// Index points to registry information
Index *registrytypes.IndexInfo
// Official indicates whether the repository is considered official.
// If the registry is official, and the normalized name does not
// contain a '/' (e.g. "foo"), then it is considered an official repo.
//表示是否官方的地址,實際上只要拉取映象時只傳入映象的資訊
//而沒有倉庫的資訊,就會使用官方預設的倉庫地址,這時Official成員就是true
Official bool
}
// RepositoryInfo Examples:
// {
// "Index" : {
// "Name" : "docker.io",
// "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"],
// "Secure" : true,
// "Official" : true,
// },
// "RemoteName" : "library/debian",
// "LocalName" : "debian",
// "CanonicalName" : "docker.io/debian"
// "Official" : true,
// }
//
// {
// "Index" : {
// "Name" : "127.0.0.1:5000",
// "Mirrors" : [],
// "Secure" : false,
// "Official" : false,
// },
// "RemoteName" : "user/repo",
// "LocalName" : "127.0.0.1:5000/user/repo",
// "CanonicalName" : "127.0.0.1:5000/user/repo",
// "Official" : false,
// }
結合程式碼中的註釋,我想我們可以知道RepositoryInfo其實是就是包含了所有可用倉庫地址(倉庫映象地址也算)的結構.
———————2016.12.10 22:31更新————————————-
好了,現在我們看下這個結構式如何被填充的.RegistryService實際上是DefaultService.看下imagePullConfig.RegistryService.ResolveRepository(ref),實現在docker\registry\service.go:
func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
return newRepositoryInfo(s.config, name)
}
// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) {
// newIndexInfo returns IndexInfo configuration from indexName
index, err := newIndexInfo(config, name.Hostname())
if err != nil {
return nil, err
}
official := !strings.ContainsRune(name.Name(), '/')
return &RepositoryInfo{name, index, official}, nil
}
// newIndexInfo returns IndexInfo configuration from indexName
func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.IndexInfo, error) {
var err error
indexName, err = ValidateIndexName(indexName)
if err != nil {
return nil, err
}
// Return any configured index info, first.
//config是在上面NewService函式中通過傳入的ServiceOptions選項生成的
//serviceConfig,在docker\registry\config.go的InstallCliFlags被初始化
//index其實就是映象的倉庫地址,或倉庫的映象地址
//
if index, ok := config.IndexConfigs[indexName]; ok {
return index, nil
}
// Construct a non-configured index info.
index := ®istrytypes.IndexInfo{
Name: indexName,
Mirrors: make([]string, 0),
Official: false,
}
index.Secure = isSecureIndex(config, indexName)
return index, nil
}
三個成員,Name就是根據引數(ubuntu:latest)解析出來的Named物件,Official 如果我們只傳入類似ubuntu:latest則使用官方預設的,該成員就是true,就剩下Index了.可以看到Index來源於config.IndexConfigs.那config.IndexConfigs是什麼呢?容易發現config.IndexConfigs來源於DefaultService的config。DefaultService的config則來源於NewService時的ServiceOptions。先看下ServiceOptions,實現在docker\registry\config.go:
// ServiceOptions holds command line options.
type ServiceOptions struct {
Mirrors []string `json:"registry-mirrors,omitempty"`
InsecureRegistries []string `json:"insecure-registries,omitempty"`
// V2Only controls access to legacy registries. If it is set to true via the
// command line flag the daemon will not attempt to contact v1 legacy registries
V2Only bool `json:"disable-legacy-registry,omitempty"`
}
相關推薦
docker pull命令實現與映象儲存(3)
在《pull命令實現與映象儲存(1)》和《pull命令實現與映象儲存(2)》我們分析pull命令在docker客戶端的實現部分,最後我們瞭解到客戶端將結構化引數傳送到服務端的URL:/images/create。接下來我們將分析在服務端的實現部分,將從該URL入
虛擬機器下安裝docker,並且ssh與的連線(centos6)--docker筆記
當前環境:win10->vmware->centos6.5(86_64) 不用管太多,先安裝docker。後面會有注意點。 1. yum install http://mirrors.yun-idc.com/epel/6/i386/epel-release-
Linux命令列與shell指令碼(20)--例項:備份檔案
建立一個配置檔案,該檔案包含了要備份的每個目錄或檔案 $ cat files_backup_config /Users/chenhong/Desktop/shell_workspace/my
Linux命令列與shell指令碼(12)--控制指令碼
處理訊號 Ctrl+C組合鍵會產生SIGINT訊號,會停止shell中當前執行的程序 Crtl+Z組建鍵會產生SIGTSTP訊號,停止shell中執行的任何程序,停止程序會讓程式繼續保留在記憶體中,
Linux命令列與shell指令碼(14)--在函式中使用陣列
陣列變數和函式 傳遞陣列給函式 function testit(){ local new_array; new_array=(`echo [email
Docker埠對映及建立映象演示(二)--技術流ken
前言 在上一篇部落格《Docker介紹及常用操作演示--技術流ken》中,已經詳細介紹了docker相關內容以及有關映象和容器的使用命令演示。 現在我們已經可以自己下載映象,以及建立容器了。 但是現在有這樣一個問題,我們建立的容器可以被其他人或者另外一臺伺服器訪問嗎? 基於上一篇部落格中容器的
(十二) web服務與javaweb結合(3)
pack ppi web工程 配置 time star con http ont 一、需求 上一章節雖然將webservice和web項目綁定在了一起,但是還是不能共同一個端口,本章講解webservice和web項目綁定且共同端口。 二、案例 2.1 創建w
25、【opencv入門】輪廓查找與繪制(3)——凸包
ise 技術分享 bool and s函數 span spa push_back 返回 一簡介 1、凸包 凸包(Convex Hull)是一個計算機幾何圖形學中的概念, 簡單來說, 給定二維平面點集, 凸包就是能夠將最外層的點連接起來構成的凸多邊形, 它能夠包含點集中所
python與zmq系列(3)
本篇部落格將介紹zmq應答模式,所謂應答模式,就是一問一答,規則有這麼幾條 1、 必須先提問,後回答 2、 對於一個提問,只能回答一次
android的資料儲存(3)(LitePal)
在上一章的SQLiteDatebase來操作資料庫好用嗎?不同的人有不同的答案,接下來你將接觸一個開源庫LitePal,它採用了物件關係對映的(ORM)的模式,並將我們平常用到的資料庫功能進行封裝,使用一行sql語句就可以完成各種建表和增刪改查的操作。 一、配置LitePal
C語言面向物件程式設計:虛擬函式與多型(3)
在《 C++ 程式設計思想》一書中對虛擬函式的實現機制有詳細的描述,一般的編譯器通過虛擬函式表,在編譯時插入一段隱藏的程式碼,儲存型別資訊和虛擬函式地址,而在呼叫時,這段隱藏的程式碼可以找到和實際物件一致的虛擬函式實現。 我們在這裡提供
python之類與對象(3)
target 實例化 定義 方法 int () a* 創建 但是 4. 類的初始化函數__init__(): 本章參考:https://blog.csdn.net/hellocsz/article/details/82795514 原作者: hellocsz 總結
BPMN-JS與Angular整合(3)
前面兩章已將原生BPMN 與Angular整合完成,這章介紹一下如何定製左側的調色盤,使其新增或減少可拖拽生成的元素。 有時業務開發需要,如只需要定製工作流中的部分任務,同時,有些元素如Service Task原生的操作太隱蔽,需要直接放到調色盤上面可以操作
第3章 處理機排程與死鎖(3)
LLF演算法例題: 一個實時系統中兩個週期型實時任務A、B 週期 執行時間長 任務A:20ms, 10ms; 任務B:50ms, 25ms; t=0時 :A1的鬆弛度為20-10=10ms,B1的鬆弛度為50-25=25ms,所以執行A1任務; t=10時
基於雙端堆實現的優先順序佇列(3):外觀
本文講述雙端堆的5個公開泛型操作演算法:make_dheap(原位構造雙端堆)、push_dheap(插入元素)、pop_max_dheap(刪除最大元素)、pop_min_dheap(刪除最小元素),is_dheap(堆驗證),每個演算法都提供<小於運算子和仿函式比較2個版本,要注意
資料結構與演算法複習(3)—— 線段樹
http://www.cppblog.com/MemoryGarden/archive/2009/04/11/79565.aspx http://www.notonlysuccess.com/?p=59 http://edu.codepub.com/2009/1125/18163.php POJ 2104,
python3理解寫程式碼與基本型別(3)
3.1 什麼是程式碼 什麼是寫程式碼 程式碼是現實世界事物在計算機世界中的對映 寫程式碼是將現實世界中的事物用計算機語言來描述 程式碼和寫程式碼就像畫家畫畫,攝影師拍照 如果我們需要在一個世界中描述另外一個世界裡面的一些事物,我們就需要一些基本的元素和素材,在計
SVD與 LSI教程(3): 計算矩陣的全部奇異值
/**********************作者資訊****************/ Dr. E. Garcia Mi Islita.com Email | Last Update: 01/07/07 /**********************
Android的程序與執行緒(3)執行緒安全問題
當一個程式啟動的時候,系統會為程式建立一個名為main的執行緒。這個執行緒重要性在於它負責把事件分發給適合的使用者元件,這些事件包括繪製事件。並且這個執行緒也是你的程式與Android UI工具包中的元件(比如android.widget和android.view包中的元件
基於Redis實現分散式訊息佇列(3)
1、Redis是什麼鬼? Redis是一個簡單的,高效的,分散式的,基於記憶體的快取工具。 假設好伺服器後,通過網路連線(類似資料庫),提供Key-Value式快取服務。 簡單,是Redis突出的特色。 簡單可以保證核心功能的穩定和優異。 2、效能