Apache Kafka 用起来:一个构建流式数据平台的指导(Part 1)(译)

data-flow-ugly

这些天你听到了许多关于“数据流处理”,“事件数据”和“实时数据”的技术,比如 kafka,Storm,Samza 和 Spark的数据流模块。虽然它们都一些非常激动人心地方,但是并不是每个人都知道如何用这些技术去适应你的技术栈或者或者在实际应用中使用。

这个指导将要讨论我们在处理实时数据流中的经验:如何在公司中为实时数据构建一个家和如何构建一个能够利用这些数据的应用。所有的这些都是基于我们真实的经验:我们花了过去五年时间构建Apache Kafka 将 Linkedin 转变成一个完全基于流式数据架构并且帮助了许多硅谷的企业干同样的事情。

指导的第一部分会在高层次视角我们所谓的流式数据平台,这是一个流式数据的总线。这个指导会给出这个平台是什么,我们为什么会有这样的想法。

指导的第二部分会给出具体的技术细节并且如何有效地将这些付诸实践。

但是,首先到底什么是流式数据平台?

流式数据平台:一个干净,明亮的存放数据的地方

我们是抱着非常明确的目标在LinkedIn构建着Apache Kafka:让它作为数据流的仓库。我们为什么这么做?我们有如下两个动机:

第一个问题是我们如何传输数据。我们拥有许多数据系统:关系型OLTP数据库,Hadoop,Teradata,搜索引擎,监控系统,OLAP 存储和KV存储。这些都需要稳定的从分布式环境中获取数据。我们称之为“数据集成”,当然我们也可以叫它ETL。

第二个问题是我们需要丰富的数据处理过程,这些过程一般发生在一个数据仓库或者Hadoop集群中,但是我们需要这些处理过程有比较短的延迟。我们称之为“数据流处理”,当然有些人称之为CEP或者其他类似的东西。

我将谈谈这些想法在LinkedIn如何发展的。一开始,我们根本没有意识到这些问题是相互关联的。我们的方法比较即时:我们按需地构建了一个偷工减料的管道,连接着系统和应用并且将所有的异步处理填充到请求-响应式的网页服务器之中。随着时间推移这变得越来越复杂,接着我们就停止了在不同的系统之间构建这样的管道:
data-flow-ugly

每个这样的管道以不同的方式存在着问题。对于日志数据,我们的管道是可扩展且松散的和高延迟的。在Oracle实例之间是高速,精确和实时的,但是其他系统获取不到数据。在Oracle和Hadoop之间,数据是周期,高吞吐量和批次的CSV。在搜索服务,数据是低延迟,无法扩展,并且直接与数据绑定的。在消息系统中,这个管道的数据是低延迟但是不稳定和无法扩展的。

随着我们在全球各地部署着数据中心,我们必须为这些数据流构建不同的地理备份。随着系统的扩展,这些管道也需要扩展。构建一个简单的输送管道是非常简单的,但是扩展和运维这些管道是非常消耗精力的。我感觉到我们的分布式系统工程师真的非常像分布式系统管道工。

更糟糕的是,高复杂性意味着数据不再可靠。我们的报告不再可信,存储和索引存在着问题,所有每天面对着是各种各样的数据问题。我记得有一次我们遇到了一次事故,在这次事故中我们检查了两个系统中的相同的数据并且找到了一些不一致,我们尝试着找出那一份是正确的数据,但是最后我们发现哪一份数据都是不对的。

与此同时,我们不仅仅是要将数据从一个地方输送到另一个地方,我们还需要使用这些数据干些事情。Hadoop平台给了我们一种批量处理数据和即时处理数据的平台,这是一个空前的成功。但是我们却需要一个类似的低延迟的平台。许多的应用,尤其是监控系统,搜索引擎索引管道,分析,安全和错误分析,都需要低延迟数据。我们基础设施技术栈并不能为它们提供一个很好的平台。

所以,在2010年,我们决定致力于构建一个专门用于获取以流的方式捕获数据的系统,并将这个系统作为系统间集成的方式,以及允许能够实时处理这些流式数据。这就是Apacha Kafka的由来。

我们认为事情应该是这样的:
stream_data_platform

很长一段时间,我们都没有为我们所做的东西取一个名字(我们仅仅是叫它“Kafka的东西”或者“全局日志提交的东西”),后来我们称这类数据为“流数据”和有了中心化管理“流数据平台”的概念。

我们系统从乱的一团糟的管道变成了干净的以流数据为中心的系统:
stream-centric-architecture1

一个现代的以数据流为中心的系统围绕着Apache Kafka 构建了起来。

在这种系统设置之下,Kafka扮演者全局数据管道的角色。所有系统可以为这个中心管道提供数据也可以从中消费数据。应用和流数据处理过程可以接入其中产生新的数据,这些数据可供其他系统使用。这种连续的结构良好的数据供给就像跨系统间,跨应用间和数据中心间的通用语。

比方说,一个用户更新了他的基本信息,这些基本信息流入了我们的流式数据处理层,在这里公司信息,地理信息和其他属性会被标准化。从那里,那些流式数据会流入搜索引擎和社交网络以供查询,还会流入推荐系统用于工作匹配。所有的这些都会发生在毫秒级。同样的数据流还会被导入Hadoop数据仓库。

这种使用方式在Linkdin逐渐地开始显著地普及起来。今天,在LinKedin Kafka 每天在多个数据中心处理这5000亿的事件。它变成了各系统的数据骨干,Hadoop的中心管道以及流处理过程的总线。

由于Kakfa是开源的软件,所以它不仅在Linkedin得到传播,同样也流行在所有做着同样事情的公司。

下面的文章我会介绍一些关于以流数据为中心的系统,它的工作原理,它解决了哪些问题。

流数据

(未完待续)

linux下svn 增加 ignore 属性(忽略文件的处理)

一直不知道svn的忽略命令如何使用,经过google的查找,使用方法还是有的,做个记录好了。如果想在SVN提交时,忽略某个文件,也就是某个文件不提交,可以使用svn propedit svn:ignore 命令。

下面详细介绍一下使用步骤。

单纯的看svn官方文档和一些网上搜索的资料,有时候真的不如亲自试验的好。

svn propedit svn:ignore 目录名称

注意,在使用这个SVN的属性编辑前,你得确保后面的“目录名称”是SVN版本控制的目录。

如果要忽略此目录下的文件,可以如下操作。

比如,想忽略/product目录下的test.php文件。前提是/product目录必须在svn版本控制下,而test.php文件不在svn版本控制。

svn st先看一下状态,会显示如下:

我们需要将test.php文件加入忽略列表。

此时先设置SVN默认的编辑器

然后,使用svn propedit svn:ignore ,用法如下

此时会出现一个VIM的编辑窗口,表示需要将某个文件加入到忽略列表里

我们在编辑窗口中,写入test.php然后保存,并退出VIM编辑器。

这时候会有一个提示:属性 svn:ignoreproduct 被设为新值。

表示文件test.phpsvn:ignore属性设置成功。

然后使用svn st查看,会显示:

我们需要提交,然后这个svn:ignore属性才会起作用

这时候,无论你如何修改test.php文件,再使用svn st时,也不会出现修改提示符合M了。

Bias Vs. Variance

Bias

  • Symptom
    J_{cv}(\theta) and J_{train}(\theta) are high.
  • Prescription
  1. Getting additional features
  2. Adding polynomial features(x^{2}_{1}, x^{2}_{2},x_1x_2, etc )
  3. Decreasing \lambda

Variance

  • Symptom
    J_{cv}(\theta) \gg J_{train}(\theta) and J_{train}(\theta) is low.
  • Prescription
    1. Getting more training samples
    2. Getting rid of some features
    3. Increasing \lambda

9048de55-a5a3-4deb-a645-60cd3629d5e3

Regularization

Very big \lambda \rightarrow Bias(underfiiting)
very small \lambda \rightarrow Variance(overfilling)

\lambda selection : use the same training set and select the lambda that leads to the smallest CV Error and to check the Test Error.
e0d19705-0d6b-44a1-9e77-d660788cc76a

Learning Curve

  • High Bias
    J_{train}(\theta) is close to J_{cv}(\theta) .
    Getting more data is useless!
    a0bbb206-472b-410c-bff6-b861a70df9ef

  • High Variance
    There is a gap between J_{train}(\theta) and J_{cv}(\theta) .
    Getting more data may give a better result.
    5bb1cf3b-0085-4f65-a77b-e03e6f710072

Neural networks and overfitting

  • Using “large” neural network with good regularization to address overfiting is usually better than “small” neural network, but the computation cost is more expensive.

教你如何用SSH方式登陆远程服务器

前提条件

  • 一个客户端(Mac OS,Ubuntu,CentOS 或者其他Unix系统
  • 一个已知的远程服务器用户名,密码和地址。例如:luning   123   10.0.109.33

解决方案

  • 创建RSA密钥

  • 拷贝公钥

如果你的电脑是Mac OS, 可以通过Brew命令来安装ssh-copy-id. 然而,你也可以这样:

  • 修改权限
    要想使得你上传的密钥能够正确识别,.ssh 文件夹和.ssh/authorized_keys 文件的权限一定要设置正确。通过如下命令设置:

  • 测试登录

通过以上步骤就可以通过SSH登录和访问远程服务器安全且不用密码,值得注意是请妥善保管你私钥,并且在生成密钥的时候最好设置密码。

以上内容如有错误,请于作者联系,谢谢。

Rooted-Join-Tree Construction

For computing multiple marginals efficiently in the Shenoy-Shafer architecture, a binary join tree has been proposed in [1].

For a given valuation network $$ \{ \Psi, \{ \Omega_X \}_{X \in \Psi},\{\tau_1,\dots,\tau_m \}, \downarrow, \otimes \}$$, if we want to compute marginal for a subset $$ t \subseteq \Psi$$, we can use the Fusion algorithm  .

Fusion algorithm: Suppose $$\{\{\tau_1,\dots,\tau_m\},\downarrow,\otimes\} $$is a VN where $$\tau_i$$ is a valuation for $$t_i$$, and suppose $$\downarrow$$ and $$\otimes$$ satisfy Axioms A1-A3 [1] . Let $$ \Psi $$ denote $$t_1~\cup\dots\cup ~t_m$$. Suppose $$t \subseteq \Psi$$, and suppose $$X_1X_2\dots~X_n$$ is a sequence of variables in $$\Psi – t$$. Then

$$( \tau_1 \otimes \dots \otimes \tau_m)^{\downarrow~t} =~\large\otimes \bigg\{Fus_{X_n} \Big\{ \dots Fus_{X_2} \Big\{ Fus_{X_1} \{ \tau_{1} ,\dots, \tau_{m}\}~\Big\}~\Big\}~\bigg\}$$ 

With this fusion Algorithm, we can compute the marginal of the joint valuation one by one. It is obviously inefficient that it involves much repetition of effort. Then they use a join tree with message passing to mimic the fusion algorithm.

A join tree is a tree whose nodes are subsets of $$\Psi$$ such that if a variable is in two distinct nodes, then it is in every node on the path between the two nodes. In [1], a procedure is given out. I follow the algorithm and implement it with Python. The input $$\Psi$$ and $$\Phi$$ are given by the Chest clinic example in [1]. It is worth to noting that the deletion sequence is $$XASDBLE$$ which is derived by the one-step lookahead heuristic approach. Below is the source code.

By using the visualization tool GraphViz, you can get the graph:

Digraph.gv_1

 

You can also download the source code from here !

[1] P. P. Shenoy, “Binary join trees for computing marginals in the Shenoy-Shafer architecture,” Int. J. Approx. Reason., vol. 17, no. 2–3, pp. 239–263, Aug. 1997.

Neo4j在社交媒体挖掘中的应用

最近在做一个关于社交网络中家庭关系挖掘的项目,其中有用到Neo4j,下面我就来介绍一下Neo4j在整个项目中的应用,并且分享在应用过程中的一些经验。

图形数据库,属性图和Neo4j

维基百科对图形数据库或称为图数据库的定义是:图形数据库是一种使用图结构或者图模型来对节点和边进行语义查询,存储数据和表示数据的数据库。图形数据库使用图模型来表示数据以满足表达关系语义的需求。Neo4j 是一款用Java语言编写开源图形数据库。它使用 Property Graph Model 作为数据模型,它将现实中的实体建模成节点,将实体间的联系建模成边,并且点和边都可以有相应的属性,这是一种非常灵活的语义表示模型。同时,Neo4j 使用 Cypher 作为查询语言。Cypher 是一种宣言式的语言,它有点像SQL语言,Neo4j使用它来表达图形中的pattern。我们可以使用Cypher来表达我们要查询什么,插入什么,修改或者删除什么。(具体详细的介绍我不过多重复介绍,因为你可以在 http://www.infoq.com/cn/articles/graph-nosql-neo4j 找到更加详尽且专业的入门介绍。)

Neo4j 与社交网络的天然契合

Neo4j 的强大的图形表示和存储功能与社交网络数据可以说是天造地设的一对。在我们的项目中我们使用的是新浪微博数据,新浪微博是一种类似Twitter的微博客社交网站,人与人之间有关注和被关注的关系,这就构成了非常简单的图形关系,人作为节点,由于关注与被关注是对称关系所以可以作为有向边来表示。例如:A用户关注B用户,在Neo4j 可以表示为两点一条边,如下图。我最近做的项目是一个关于社交关系分类问题,也就是通过数据挖掘技术对
Presentation1新浪微博中人与人之间的关注关系进行分类,以识别出哪些关系是家庭成员之间的关注关系,最后要将挖掘出来的结果以图形的形式在web端展示出来。由于 Neo4j 使用的 Cypher 查询语言具有 human-readable 的特性这使得我们在开发服务器端时能够很方便地表达我们的查询语义。比如我要找到一个名为“小小小小小小小子XW”的用户的关系网我们可以进行如下查询: match (n:User {screenname:"小小小小小小小子XW"})-[r:Follow]->(m:User) return n,r limit 10 。这段代码表示一种关系模式,这种模式是:一个类型为 User 的节点,它的属性sreenname的值为“小小小小小小小子XW”字符串,它通过关系类型 Follow 有向地与另一个类型为 User 的节点相连接的路径,并且在结果集中限制输出10个。很清晰地,这种模式表达了微博中用户名为“小小小小小小小子XW”所关注的人10个人。最终查询结果在 Neo4j 后端管理平台所展示的图形是这样的:

可见,neo4j 可以通过简单的易读的查询来表示图论中的路径,并且通过路径上点点之间的边来传达语义,这样方式非常地直观,使得一些复杂逻辑可以很好地,清晰地和明确地表达出来。这也是RDBMS无法比拟的,因为随着查询逻辑的复杂,RDBMS需要通过复杂且代价高昂的连接语法操作才可以完成,而通过 Cypher 查询复杂度只跟数据规模相关。

《Passbook 编程指南》 (译) — 第5篇 更新Pass

Pass 的更新

Pass 允许用户在现实中使用,并且能够反应出现实世界的状态。当现实世界发生了变化,比如飞机延误等,Pass 就需要更新。同时若Pass是代表现实世界中多个动作时,它也需要更新。比如,有一个比赛的赛季票,它能够让你观看这个比赛某个赛季的所有比赛。这个时候我们为此赛季票做一个Pass,我们就需要时刻更新Pass。

交互概述

更新一个Pass是需要用户设备,苹果服务器,和你的服务器进行协同才可以完成的。从高层角度来看它包括以下4个步骤:

  1. 在用户设备上安装Pass并且向你的服务器注册以获得更新。
  2. 必须要有触发更新的事件,然后你的服务器向客户端发出推送通知。
  3. 用户设备收到推送通知,然后向服务器器查询获得一个更新列表。
  4. 用户设备向服务器请求那些发生变化的新的Pass。


更新过程将会用到的信息:

信息 目的
Web service URL 在Pass中定义 告诉Passbook如何与你的服务器交互
Pass type identifierSerial number 在Pass中定义 一起用于标识Pass
Device library identifier 由设备定义 标识设备并且对请求授权
Authentication token 在Pass中定义 对请求授权
Push token 由设备定义 允许服务器给用户设备发送推送通知
Update tag 由你的服务器定义 用于描述更新状态

        你的服务器需要实现一个特定功能的web service来使得这种交互成为可能。它不像推送通知的web service,你只需要提供一个应用就能无代价地和服务器交互。在这里你有一个Pass,所以我们需要能够与你的服务器交互的标准化方法。

        Web Service URL  在Pass更新过程中告诉Passbook该如何与你的服务器交互。它必须包含一个协议,同时可选择包含一个端口号。正式的Passbook 需要一个HTTPS连接,然而在开发阶段你只需要用一个普通的HTTP连接就行。

        在对更新请求进行响应之前,你的服务器必须要检查这个请求是已经被授权的。我们可以使用两种共享秘密来对请求授权,具体参见Security Overview

        Device Library identifier 是由Passbook约定的,用于用户设备和你的web服务器交互的共享秘密。它跟设备标识符(UDID)没有联系。设备通过在不同服务中的不同ID来辨别自己,这个ID可能会发生改变。它仅仅是用于设备与服务器之间高效地交互。而不是用于你来辨别设备的。Device Library identifier 唯一地标识一个设备并且表明当前发起该请求的设备是经过授权才发起该请求的。

        Authentification Token是用户设备与服务器之间的共享秘密。它说明一个Pass更新的请求的确是来自于拥有该Pass的用户,而不是别人。

        你的服务器需要永久性的记录一些信息– 设备,Pass,和它们之间的注册关系。假设你的服务器使用关系数据库,那么你需要维护3张表:

设备表

该表存储了不同的设备,它们通过Device library Identifier来标识。他们并且拥有推送令牌(push token)

Pass表

        该表记录了所有的Pass。它们通过Pass Type ID和 序列号(Serial Number)来标识。这个表还应该包含一个最后更新标签(更新时间戳)和一切生成实际Pass所需要的数据。

注册关系表

注册关系就是设备和Pass之间的关系。你需要实现双向查询的功能:1 通过给定的设备查找相应的Pass 2 通过给定的Pass 来查找相应的设备。 注册关系明显是一个多对多关系,一个设备对应多个Pass, 一个Pass可以被多个设备注册(IPad IPhone Itouch )

注册设备

在安装了一个Pass之后,这个ios设备就向你发起一个注册并请求更新。你的服务器存储设备的Library ID 和它的推送令牌(Push Token)。

一个设备将发送下列信息:

  • Device library identifier (在URL中)
  • Push token (在JSON payload中)
  • Pass Type ID (在URL中)
  • Serial Number(在URL中)
  • Authentification token (在header中)

为了处理设备的注册例程,我们要做下面的事:

  1. 验证Authentification Token 是否正确,若不正确立即返回HTTP 401未授权并且抛弃请求
  2. 在设备表中存储device library identifier 和 push token 的映射
  3. 在注册表中存储 Pass和device library identifier 的注册信息

         当一个设备要取消注册,它只需要向同样的服务器发送同样的数据就可以了,唯一需要改动就是请求的类型需要将POST类型改为DELETE类型。当一个设备请求移除注册时,你可以立即将记录从注册表中移除。若注册表中没有该设备,你可以将其从设备表中移除。

当发生变化你的服务器发送推送通知

当一个Pass 需要得到更新的时候,你的服务器需要向设备推送一个通知以告知设备有变化发生。你可以向一个设备发送推送通知,当且仅当该设备的确为这个Pass 注册了更新并且该Pass的确发生了改变,否则你不可以发送不必要的更新。

你的服务器需要发送如下信息:

Pass Type Identifier(在证书中)

Push Token(与苹果推送通知服务交互)

为了发送推送通知,你需要做:

  1.   更新Pass表,并且获得那些发生改变的Pass的Pass Type Identifier 和序列号(Serial Number)。
  2. 查询注册信息表以获得那些注册了这些Pass更新的设备。
  3. 查询设备表,获取每台设备的推送令牌(Push Token)
  4. 将一个空的JSON作为一个推送通知的附件发送到每个推送令牌

           你也许要问,为什么我的服务器只包含这么一点信息在推送通知中。这里有两重理由:1. 推送通知不能保证被送到每台设备 2. 同一个源的多个推送通知会被汇总到一个通知中。如果信息过多,则容易被遗漏。这个推送通知仅仅是用来表面有Pass发生改变了,所以不会有信息被遗漏如果他们被汇总成一个通知。

设备请求变化的序列号

当一台设备接受一个推送通知,它会在某个时刻向服务器请求发生改变的Pass的序列号。

这个设备会发送如下信息:

设备ID(在URL中)

从推送通知中获取的Pass Type ID (在URL中)

最新的更新标签(latest update tag)

授权令牌是由每个Pass 指定的,这里我们没有合适的令牌。所以设备ID足可以用来证明这个请求是合法的。

你的服务器将根据更新标签(updated tag)来判断哪些Pass发生了变化,并且将这些Pass的序列号和新的更新标签(updated tag)。

为了发送这个序列号列表,你需要做:

  1. 查询注册信息表确定这个设备注册了哪些Pass
  2. 查询Pass表,更具给定的更新标签确定哪些Pass发生了改变。记住,不要包含那些发送了改变而设备未与之注册的Pass。如果没有提供更新标签,那么返回所有与该设备注册的Pass。这种情况往往发生在第一次设备与服务器交互的时候。
  3. 比较所有更新标签,得到一个最新的更新标签,该标签将会返回给设备。
  4. 返回一个由序列号和最新的更新标签组成的JSON 表,例如:

{
“serialNumbers” : [001, 020, 3019],
“lastUpdated” : “1351981923”
}

        更新标签对于Passbook来说是透明的,它不懂这是什么意思。你的服务器需要有能够判断更新标签是否为最新的功能,两个更新标签能够判断出他们哪个比哪个更新,并且要保证后面的更新标签一定要比前面的所有更新标签要新,所以时间戳是比较好的更新标签。

这个标签系统有点类似于HTTP 请求头的If-Modified-Since 机制,但是还有很大不同。标准HTTP机制是单个统一的资源,其中一小点发生改变,整体将会被返回。但是我们使用的标签机制是为Pass 集合准备的,它只返回发生改变的Pass而不是Pass 整体。

设备请求最新的Pass

设备向服务器请求发生改变的Pass的最新版本。为了证明这个请求是合法的,这个设备必须包含授权令牌在发送的信息中。

以下是它需要发送的信息:

Pass Type Identifier(在URL中)

序列号(Serial Number)(在URL中)

授权令牌(在HTTP头中)

你的服务器要么返回Pass数据,要么返回HTTP 304 响应告诉设备没有发生修改。这里终端看起来就像支持IMS机制一样。

设备显示更新信息

设备通过与之前老版本比较来确定那些发生修改的域,如果某个域的值发生了修改,并且该域还指定了修改信息,那么设备将会显示这个修改信息以告知用户这次更新。

更新信息将会打扰用户并且必须立即阅读。所以只有那些时间敏感的信息才能指定更新信息,如航班登机口变更,起飞时间变更等。

宝贵的经验

如果你要实现你的web service,下面有些宝贵的经验请谨记于心:

  • 备份你的私有秘钥和证书,并保证他们的在安全不被盗。
  • 不要将你的私有秘钥存储在你的web服务器上,因为web服务器是很容易收到攻击的。你也许需要另外一个服务器用来创建和签名Pass,然后再传送给web服务器。
  • 当你与web 服务器器交互时,序列号(serial number),pass type identifier,和更新标签都应该被包含于URL中,所以合理规划他们的长度,以规避URL长度限制。
  • 不要在更新时轻易改变授权令牌。因为我们不能确保同一个Pass,在不同的设备上都能得到及时的更新,你的服务器要去检查并维护以前所使用过的所有授权令牌。

魔兽世界 国服 mac版 熊猫人

由于 网易 并没有放出 魔兽世界mac 导致 mac 用户玩魔兽很不方便需要安装bootcamp 双系统才行,为了最高性能地使用mac 显卡,我们现在给出魔兽世界mac版的改造方法。

本方法以熊猫人 5.0.4 为例

 

首先,必须有一份pc版本下的完全更新好的客户端(16016),就是在pc下运行launcher后不再提示有任何需要更新的内容了,而且最好能确定能进入游戏。

下载这个文件 http://dl.vmall.com/c0ed03obbp

随便在某个地方建立一个文件夹 如 “熊猫人”,将下载的文件全部拷贝放在该文件夹中,然后双击运行 world of warcrarft.app 会出现配置界面 无论结果如何都会产生新的文件夹,其中有一个data 文件夹,将里面的东西全部删除,然后将pc版的客户端文件夹中的data 文件夹所有内容拷贝至你的mac 中魔兽世界中的data,然后运行launcher 如果失败直接运行world of warcraft.app 或者 world of warcraft-64.app

如有疑问请直接评论

C++中的指针,以及指针的指针 扫盲(献给那些被指针弄头大的同学)

大家可以先复制此代码,运行后,再看解释。

1. *,& 在指针操作中的意义

(1)*

大家都知道 在写int *p 时,*可以声明一个指针。很少人知道*在C/C++中还有一个名字就是“解引用”。他的意思就是解释引用,说的通俗一点就是,直接去寻找指针所指的地址里面的内容,此内容可以是任何数据类型,当然也可以是指针(这就是双重指针,后面将会讨论)。需要注意的是,在变量声明的时候,*不能当做解引用使用,只是表示你声明的变量是一个指针类型。

example1:

int a=50;

int *p=&a;// ‘&’的作用就是把a变量在内存中的地址给提取出来,具体后面解释。

*p=5;//这就是解引用,*解释了 p对常量 50的内存地址的引用,解释结果就是直接去寻找p所指内容,因为p指向a,所以a的内容将被修改为5,而不是50。

(2) &

& 在C/C++中具体 3种语义, 按位与,取地址 和 声明一个引用。

int a=0x0000002 & 0x00000003;//按位与

int *p=&a;//取地址,取出a在内存中的地址给p,这样就建立一个指针映射,使得p指a

int &a=b;//声明a是b的引用,对a操作修改也就是直接对b修改。

在指针操作中,&还有一种叫法叫做“脱去解引用”,在这个表达式中&*p(假设p是指针),&脱去了*对a的解引用,从而得到抵消的效果,得到应该是p所指的变量的地址。

example2:

接example1。

println(“%x ||||| %x”,&p,&*p);

输出结果:

p在内存中的地址|||||a在内存中的地址

为什么呢?

首先&去指针p的地址,然后&脱去了p对a中5的引用,解引用和脱去解引用效果抵消,从而得到了p所指的变量的地址,也就是a在内存中的地址。

2. 指针的指针(双重指针)

通俗的说,也就是在双重指针中存放的是另一个指针在内存中的地址,从而构成了一个数据链。pp->p->a->5,这就是这个数据链的简化模型。相信大家搞懂了普通指针,双重指针应该是piece of cake。

3  实例程序的解析

注意:由于内存地址是动态分配的,所以每次运行得到的地址都是不同的。指针大小在32位机器中是32位的数据,也就是8个0在16进制表示法中0x00000000。

这是在进入find()方法之前的,数据检视表。

可以发现 &p value 是指针p在内存中的地址。他的子节点(也就是下面那个蓝色没有名称的立体的长方形)也就是p所指的内容的地址,由于我给它赋了0,所以他的value是0x00000000。由于p所指的地址是0,0不对应内存中的数据,所以是一个红色的感叹号。

&pp1是一个双重指针,&pp1是它自己在内存中的地址。下一个子数也就是pp1,它的value是指针p在内存中的地址,下面级联菜单的结构就跟p的结构一致了,因为pp1是一个指向p的双重指针。

这里需要提一下的就是,数组。 数组和指针其实十分类似,都是一种地址的体现。数组是一串连续的内存地址,通过index来访问内部数据。str 就是这个数组的首地址,也就是第一个元素所在的内存地址,只要知道数组长度,就通过index的加减来访问你想要的数据。

所以str+3就是第4个字符 的地址,也就是str[]数组中的第一个字符’d’所在的内存地址, str[3]就是取这个地址中数据的操作。

这是在退出方法find()方法之后,数据检视表。

不难发现,在成功发现字符’d’后,*pp1也就是p的内容发生了变化,value变成了第一个字符’d’在str中的地址了,0x002dfe1f。**pp1的内容也就是*p内容都是’d’,100是ASCII码。所以成功完成了find()方法的要求,即在目标字符数组中,寻找指定的字符,并返回其在数组中的具体位置也就是字符‘d’的index的内存地址。

这是程序运行的最终结果

Conclusion:

结论我用一副图来表示

References:

C/C++中文教程,http://download.csdn.net/source/424357#acomment,CSDN.net