
暂无个人介绍
根据使用者对读取操作的控制情况,分为两种类型。一个是DefaultMQPushConsumer,由系统控制读取操作,收到消息后自动调用传入的处理方法来处理;另一个是DefaultMQPullConsumer,读取操作中的大部分功能由使用者自主控制。1.DefaultMQPushConsumer的使用使用DefaultMQPushConsumer主要是设置好各种参数和传入处理消息的函数。系统收到消息后自动调用处理函数来处理消息,自动保存Offset,而且加入新的DefaultMQPushConsumer后会自动做负载均衡。下面结合org.apache.rocketmq.example.quickstart包中的源码来介绍。代码清单1-1 DefaultMQPushConsumer示例public class QuickStart { public static void main(String[] args) throws InterruptedException, MQClientException { DefaultMQPushConsumer Consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4"); Consumer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876"); Consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); Consumer.setMessageModel(MessageModel.BROADCASTING); Consumer.subscribe("TopicTest", "*"); Consumer.registerMessageListener(new MessageListenerConcurrently() { public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n"); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); Consumer.start(); } }DefaultMQPushConsumer需要设置三个参数:一是这个Consumer的GroupName,二是NameServer的地址和端口号,三是Topic的名称,下面详细介绍。Consumer的GroupName用于把多个Consumer组织到一起,提高并发处理能力,GroupName需要和消息模式(MessageModel)配合使用。RocketMQ支持两种消息模式:Clustering 和 Broadcasting。 在Clustering 模式下,同一个ConsumerGroup(GroupName相同)里的每个Consumer只消费所订阅消息的一部分内容,同一个ConsumerGroup里所有的Consumer消费的内容合起来才是所订阅Topic内容的整体,从而达到负载均衡的目的。 在Broadcasting模式下,同一个ConsumerGroup里的每个Consumer都能消费到所订阅Topic的全部消息,也就是一个消息会被多次分发,被多个Consumer消费。NameServer的地址和端口号,可以填写多个,用分号隔开,达到消除单点故障的目的,比如 “ip1:port;ip2:port;ip3:port”。Topic名称用来标识消息类型,需要提前创建。如果不需要消费某个Topic下的所有消息,可以通过指定消息的Tag进行消息过滤,比如:Consumer.subscribe("TopicTest", "tag1 || tag2 || tag3"),表示这个Consumer要消费“TopicTest”下带有tag1或tag2或tag3的消息(Tag是在发送消息时设置的标签)。在填写Tag参数的位置,用null或者“*”表示要消费这个Topic的所有消息。2.DefaultMQPushConsumer的处理流程本节通过分析源码来说明DefaultMQPushConsumer的处理流程。DefaultMQPushConsumer主要功能实现在DefaultMQPushConsumerImpl类中,消息的处理逻辑是在pullMessage这个函数里的PullCallBack中。在PullCallBack函数里有个switch语句,根据从Broker返回的消息类型做相应的处理,具体处理逻辑可以查看源码。代码清单1-2 DefaultMQPushConsuer的处理逻辑switch (pullResult.getPullStatus()) { case FOUND: ….. break; case NO_NEW_MSG: …… break; case OFFSET_ILLEGAL: …… break; default: break; }DefaultMQPushConsuer的源码中有很多PullRequest语句,比如DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest),为什么“PushConsumer”中使用“PullRequest”呢?这是通过“长轮询”方式达到Push效果的方法,长轮询方式既有Pull的优点,又兼具Push方式的实时性。Push方式是Server端接收到消息后,主动把消息推送给Client端,实时性高。对于一个提供队列服务的Server来说,用Push方式主动推送有很多弊端;首先是加大Server端的工作量,进而影响Server的性能,其次Client的处理能力各不相同,Client的状态不受Server控制,如果Client不能及时处理Server推送过来的消息,会造成各种潜在问题。Pull方式是Client端循环地从Server端拉取消息,主动权在Client手里,自己拉取到一定量消息后,处理妥当了再接着取。Pull方式的问题是循环拉取消息的间隔不好设定,间隔太短就处在一个“忙等”的状态,浪费资源;每个Pull的时间间隔太长,Server端有消息到来有可能没有被及时处理。“长轮询”方式是通过Client端和Server端的配合,既拥有Pull的优点,又能达到保证实时性的目的。我们结合源码来分析:代码清单1-3 发送Pull消息代码片段PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();requestHeader.setConsumerGroup(this.ConsumerGroup);requestHeader.setTopic(mq.getTopic());requestHeader.setQueueId(mq.getQueueId());requestHeader.setQueueOffset(Offset);requestHeader.setMaxMsgNums(maxNums);requestHeader.setSysFlag(sysFlagInner);requestHeader.setCommitOffset(commitOffset);requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);requestHeader.setSubscription(subExpression);requestHeader.setSubVersion(subVersion);requestHeader.setExpressionType(expressionType); PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage( brokerAddr,requestHeader,timeoutMillis,communicationMode,pullCallback); 源码中有这一行设置语句requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis),设置Broker最长阻塞时间,默认设置是15秒,注意是Broker在没有新消息的时候才阻塞,有消息会立刻返回。 从Broker的源码中可以看出,服务端接到新消息请求后,如果队列里没有新消息,并不急于返回,通过一个循环不断查看状态,每次 waitForRunning一段时候(默认是5秒),然后后再Check。默认情况下当Broker一直没有新消息,第三次Check的时候,等待时间超过Request里面的 BrokerSuspendMaxTimeMillis,就返回空结果。在等待的过程中,Broker收到了新的消息后会直接调用notifyMessageArriving函数返回请求结果。“长轮询”的核心是,Broker端HOLD住客户端过来的请求一小段时间,在这个时间内有新消息到达,就利用现有的连接立刻返回消息给Consumer。“长轮询”的主动权还是掌握在Consumer手中,Broker即使有大量消息积压,也不会主动推送给Consumer。长轮询方式的局限性,是在HOLD住Consumer请求的时候需要占用资源,它适合用在消息队列这种客户端连接数可控的场景中。3.DefaultMQPullConsumer使用DefaultMQPullConsumer像使用DefaultMQPushConsumer一样需要设置各种参数,写处理消息的函数,同时还需要做额外的事情。接下来结合org.apache.rocketmq.example.simple包中的例子源码来介绍。示例代码的处理逻辑是逐个读取某Topic下所有Message Queue的内容,读完一遍后退出,主要处理额外的三件事情:(1) 获取Message Queue并遍历一个Topic包括多个Message Queue,如果这个Consumer需要获取Topic下所有的消息,就要遍历多有的Message Queue。如果有特殊情况,也可以选择某些特定的Message Queue来读取消息。(2) 维护Offsetstore从一个Message Queue里拉取消息的时候,要传入Offset参数(long类型的值),随着不断读取消息,Offset会不断增长。这个时候由用户负责把Offset存储下来,根据具体情况可以存到内存里、写到磁盘或者数据库里等。(3) 根据不同的消息状态做不同的处理拉取消息的请求发出后,会返回:FOUND,NO_MATCHED_MSG,NO_NEW_MSG,OFFSET_ILLEGAL四种状态,要根据每个状态做不同的处理。比较重要的两个状态是FOUNT和NO_NEW_MSG,分别表示获取到消息和没有新的消息实际情况中可以把while(true)放到外层,达到无限循环的目的。因为PullConsumer需要用户自己处理遍历Message Queue、保存Offset,所以PullConsumer有更多的自主性和灵活性。 推荐阅读: RocketMQ实战与原理解析作者:杨开元定价:59.00元•RocketMQ由阿里开源,Apache开源项目,经受多年流量峰值考验,在多个性能指标上远超同类产品•作者是阿里资深数据专家,有多年RocketMQ使用经验,深入研究RocketMQ源代码,写作前与RocketMQ官方团队有深入沟通•云栖社区官方出品,得到RocketMQ官方研发团队以及业界的多位专家的肯定和推荐 阅读原文:http://product.dangdang.com/25290633.html
分布式系统各个角色间的通信效率很关键,通信效率的高低直接影响系统性能,基于Socket实现一个高效的Tcp通信协议是个很有挑战的事情,本节说明RocketMQ是如何解决这个问题的 1.1.1 Remoting模块RocketMQ的通信相关代码在Remoting模块里,先来看看主要类结构。 RemotingService为最上层接口,定义了三个方法:void start();void shutdown();void registerRPCHook(RPCHook rpcHook);RemotingClient,RemotingServer继承RemotingService接口, 并增加了自己特有的方法。代码清单1-1 RemotingClient主要函数定义void registerProcessor(final int requestCode, final NettyRequestProcessor processor,final ExecutorService executor);RemotingCommand invokeSync(final String addr, final RemotingCommand request, final long timeoutMillis);void invokeAsync(final String addr, final RemotingCommand request, final long timeoutMillis,final InvokeCallback invokeCallback);void invokeOneway(final String addr, final RemotingCommand request, final long timeoutMillis);void updateNameServerAddressList(final List addrs); 然后看看具体的实现类,NettyRemotingClient和NettyRemotingServer分别实现了RemotingClient和RemotingServer, 而且都继承了NettyRemotingAbstract类. 通过上面的封装,RocketMQ各个模块间的通信,可以通过发送统一格式的自定义消息(RemotingCommand)来完成的,各个模块间的通信实现简洁明了。比如NameServer模块中,NameServerController有个remotingServer变量,NameServer在启动时初始化好各个变量,然后启动remotingServer即可,剩下NameServer要做的是专心实现好处理RemotingCommand的逻辑。代码清单1-2 NameServer处理主流程代码@Overridepublic RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { if (log.isDebugEnabled()) { log.debug("receive request, {} {} {}", request.getCode(), RemotingHelper.parseChannelRemoteAddr(ctx.channel()), request); } switch (request.getCode()) { case RequestCode.PUT_KV_CONFIG: return this.putKVConfig(ctx, request); case RequestCode.GET_KV_CONFIG: return this.getKVConfig(ctx, request); case RequestCode.DELETE_KV_CONFIG: return this.deleteKVConfig(ctx, request); case RequestCode.REGISTER_BROKER: Version brokerVersion = MQVersion.value2Version(request.getVersion()); if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) { return this.registerBrokerWithFilterServer(ctx, request); } else { return this.registerBroker(ctx, request); } case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST: return this.getHasUnitSubUnUnitTopicList(ctx, request); case RequestCode.UPDATE_NAMESRV_CONFIG: return this.updateConfig(ctx, request); case RequestCode.GET_NAMESRV_CONFIG: return this.getConfig(ctx, request); default: break; } return null; }在Consumer的源码中,获取消息的底层的通信部分也是发送一个RemotingComand 请求,返回的response也是个RemotingCommand类型。代码清单1-3 Consumer请求消息底层实现代码private PullResult pullMessageSync(// final String addr, // 1 final RemotingCommand request, // 2 final long timeoutMillis// 3 ) throws RemotingException, InterruptedException, MQBrokerException { RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; return this.processPullResponse(response); }从源码中可以看出,RocketMQ中复杂的通信过程,被RemotingCommand统一起来,大部分的逻辑都是通过发送Command,接受并处理Command完成。1.1.2 协议设计和编解码RocketMQ自己定义了一个通信协议,使得模块间传输的二进制消息和有意义的内容之间互相转换。协议格式如图4-2所示。 图1-2 RocketMQ的通信协议(1)第一部分是大端4个字节整数,值等于第二,三,四部分长度总和 (2)第二部分是大端4个字节整数,值等于第三部分的长度 (3)第三部分是通过json 序列化的数据 (4)第四部分是通过应用自定义二进制序列化的数据消息的解码过程在RomotingCommand的decode函数里。代码清单1-4 消息解码函数public static RemotingCommand decode(final ByteBuffer byteBuffer) {int length = byteBuffer.limit(); int oriHeaderLen = byteBuffer.getInt(); int headerLength = getHeaderLength(oriHeaderLen); byte[] headerData = new byte[headerLength]; byteBuffer.get(headerData); RemotingCommand cmd = headerDecode(headerData, getProtocolType(oriHeaderLen)); int bodyLength = length - 4 - headerLength; byte[] bodyData = null; if (bodyLength > 0) { bodyData = new byte[bodyLength]; byteBuffer.get(bodyData); } cmd.body = bodyData; return cmd; }对应的消息编码过程在RemotingCommand的encode函数中。代码清单1-5 消息编码函数public ByteBuffer encode() { // 1> header length size int length = 4; // 2> header data length byte[] headerData = this.headerEncode(); length += headerData.length; // 3> body data length if (this.body != null) { length += body.length; } ByteBuffer result = ByteBuffer.allocate(4 + length); // length result.putInt(length); // header length result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC)); // header data result.put(headerData); // body data; if (this.body != null) { result.put(this.body); } result.flip(); return result; } 1.1.3 Netty库RocketMQ是基于Netty库来完成RemotingServer和RemotingClient具体的通信实现的,Netty是个事件驱动的网络编程框架,它屏蔽了Java Socket,Nio等复杂细节,用户只需用好Netty,就可以实现一个网络编程专家+并发编程专家水平的Server、Client网络程序。应用Netty有一定的门槛,需要了解它的EventLoopGroup,Channel,Handler模型以及各种具体的配置。RocketMQ利用Netty实现的通信类是NettyRemotingServer和NettyRemotingClient,用户也可以参考这两个类的实现来学习使用Netty。 推荐阅读: 云栖社区官方出品 RocketMQ实战与原理解析作者:杨开元定价:59.00元•RocketMQ由阿里开源,Apache开源项目,经受多年流量峰值考验,在多个性能指标上远超同类产品•作者是阿里资深数据专家,有多年RocketMQ使用经验,深入研究RocketMQ源代码,写作前与RocketMQ官方团队有深入沟通•云栖社区官方出品,得到RocketMQ官方研发团队以及业界的多位专家的肯定和推荐 阅读原文:http://product.dangdang.com/25290633.html
本节书摘来自华章出版社《大数据系统构建:可扩展实时数据系统构建原理与最佳实践》一书中的第1章,第1.9节,南森·马茨(Nathan Marz) [美] 詹姆斯·沃伦(JamesWarren) 著 马延辉 向 磊 魏东琦 译,更多章节内容可以访问云栖社区“华章计算机”公众号查看。 1.9 示例应用:SuperWebAnalytics.com 在本书中,我们将创建一个大数据应用程序示例来说明一些概念。我们将为Google Analytics构建数据管理层—比如服务。该服务将能够每天追踪数十亿的页面浏览量。该服务将支持多种不同的指标。每个指标都被实时地支持。指标的范围很广—从简单的统计指标,到访客是如何浏览网站的复杂分析指标。示例应用将支持的指标如下:按照时间切片基于URL的页面浏览计数—示例查询是“过去一年中每一天的页面浏览量是多少?”和“过去12小时内有多少页面浏览量?”按照时间切片基于URL的独立访客—示例查询是“2010年有多少独立访客访问这个域名?”和“过去三天内每个小时,有多少访客访问这个域名?”跳出率分析—“用户访问该站点的某个页面,没有访问其他任何页面的百分比是多少?”我们将构建存储、处理并为应用程序提供查询的层。
英国剑桥大学、印度理工学院和印度科学理工学院的一组研究人员发布研究论文指出,即使是用头巾、帽子和墨镜遮挡了面部,软件仍然可以将人正确识别出来。 DFI系统识别面部特征 该研究描述了卷积神经网络(Convolutional Neural Network)能被训练成所谓的伪装面部识别(Disguised Face Identification,简称DFI)系统。 英国剑桥大学工程学院的阿玛贾特·辛格周二向外媒透露,DFI系统有助于执法机构识别罪犯,这也是他们研究的主要目的。 14个点的面部“指纹”研究人员通过数百张用墨镜、胡子和头盔伪装面部的图片训练卷积神经网络。研究人员在每张照片中识别出14个点:10个点标记记忆眉毛和眼部区域,1个点记忆鼻子,另外3个点记录嘴唇。所有点连在一起创建“星形网状结构”。研究人员分析网络中点与点之间的距离和角度了解面部结构。 值得注意的是,系统经过训练从模糊面部像素中识别标记,学习如何处理遮挡的面部并从中创建“指纹”。 这个面部“指纹”之后能用来扫描更大的数据库——DFI机器学习系统中的图片数据库,例如滋事者的驾照图片或面部照片,并进行匹配。换句话讲,借助这种训练模型,显示监控录像的剪裁图片,系统会将14个标记置于遮挡的面部,再将这种标记模式用于匹配数据中的面部。 这种技术仍在实践当中,目前仍是PoC。 研究人员在训练中使用了上千张不同程度遮挡的面部照片。 虽然辛格希望系统对警方有帮助,但专制政权也可能利用这种技术针对无辜平民、合法的抗议者等。 欺骗AI识别有效数据轻而易举虽然这个计算机视觉系统的精准度对早期研究有帮助,但在实践层面的效果可能不尽人意。背景中的建筑物和物体会将精准度从85%降低到56%。 面部越模糊,识别难度就越大。同时使用帽子、头巾和墨镜等遮挡面部,精准度可能会降至43%。 实验与真实应用的巨大差距更为现实的是,所有训练的图片都是对着摄像头拍摄的清晰图片,然而监控录像中的图像通常比较模糊。 另外,还有一个不容忽视的事实:类似V字仇杀队(V for Vendetta)中的全副武装总能起到愚弄的完美效果。 辛格还表示,由于成本昂贵,训练数据集也相当有限。当前的数据集有10种伪装打扮,主要为印度人和高加索人。辛格指出,需要扩大数据范围强化DFI系统的有效性。 到目前为止,DFI还只是一个实验系统,需要训练更多图片以便更精准地识别面部标记,这就要求更高效的算法和代码。 研究人员目前仍在继续这项研究。 本文转自d1net(转载)
如今的组织知道他们需要充分有效地利用所有的数据,这包括日益增长的通信数字化,以及从灯泡到智能手机的所有数据。他们也知道,必须捕获各种各样的数据,以便通过能够访问的方式存储数据,并根据业务快速变化的需求查询数据。他们也知道,他们无法忍受刻板的、预先安排好的模式。然而他们发现,这说起来容易做起来难。 那么是什么妨碍了他们实施?有很多事情,而组织必须克服的五大挑战是为了充分利用其数据以及合作伙伴的数据和其他外部数据源。 (1)无法使用多种数据类型和格式。如今的数据有各种格式,规模和形式,必须实时处理和分析。这包括不适合传统关系数据库系统的行和列的数据。更重要的是,这些不同的形式和类型的数据需要无缝地一起使用。丰富的结构化数据,图形数据,地理空间数据和非结构化数据可能都被视为单个查询或事务。 (2)基于传统系统的创新步伐缓慢。如今,技术和业务需求几乎每天都在变化,组织需要进行创新,以保持竞争力和合规性。许多公司却几乎无法处理他们手头上的数据,更不用说未来会发生什么,例如物联网生成的数据。在创新方面进行投资时,他们常常感到沮丧,因为他们需要处理拥有组织的大量数据资产的传统系统,这些系统将成为减缓其进展以及提高有效竞争能力的阻碍。 (3)企业数据仓库的扩散。各种数据的快速增长和企业为客户提供的服务数量的增长,在造成了企业中数据孤岛的扩散。为了更好地服务于客户、监管者和他们自己,企业需要对客户、产品等业务对象创建360度的视图。但是,创建这种整体景观是一项艰巨而耗资巨大的任务。一直以来,企业正在建立更多的数据孤岛。更糟糕的是,数据质量和这些观点的治理常常是事后的结果,甚至会导致监管处罚。 (4)ETL和模式优先系统的使用。关系数据库实际上是大多数组织中存储数据的标准。一旦填充了关系模式,使用SQL进行查询就很简单。这听起来不错,但这是一个大问题,但是组织必须创建查询将被发布的模式。整合所有现有的模式(可能是主机数据和文本内容)需要在业务部门,主题专家和实施者之间进行大量的时间和协调。然后,一旦模型被各利益相关者最终确定,必须将数据从源系统中提取,转化为适合新的模式,然后加载到新的称之为ETL过程(即数据抽取、转换、装载的过程)。这些过程不需要太长时间(平均6-18个月)。而且,它永远不会结束。数据源发生变化。添加新的来源。提出了不同的问题。ETL一直在接受,而不是给予。 (5)背景缺失。也许当今组织最大的问题是认为他们知道他们不知道的东西。没有背景的数据是无用的。这些数据意味着什么?它与其他数据有什么关系?数据的出处是什么?在什么情况下,可以和谁分享?在大多数情况下,这些问题的答案不会在数据库中捕获。它可能在开发人员的头脑中,或者设计文档中,或者ETL脚本里,或者更糟糕的是在所有的这些地方中,但不是一致的。传统数据库并不侧重于存储,管理和查询元数据,而典型的ETL流程通常会将此信息丢弃在本地。放弃背景意味着放弃从数据中获得的最大价值。 那么组织需要做什么呢?越来越多的组织转向多模型数据库。使用多模型数据库,他们可以捕获数据的背景并将其与数据进行存储,从而提供最大的数据敏捷性和可审计性。并且在将来防范数据库系统对任何新类型的数据,转移数据范例或监管要求,不可避免地出现偏移。 考虑采用多模型数据库平台的公司应该寻求: •多结构的本地存储(结构感知) •按原样加载数据的能力(在加载数据之前不需要模式) •能够有效地对这些不同的模型进行索引 •能够无缝地使用所有模型,并进行组合 •企业级安全性和可用性 当然,数据库技术的转变并不轻松,许多IT专业人员在整个职业生涯只专注一种或几种技术。 但是,如果组织有时间确保他们能够有效地收集,分析和利用他们掌握的数据的话,那么现在正是时候。 本文转自d1net(转载)
3.7.4 广播 广播函数将32位或64位元素的连续块从一个PE(根)拷贝到其他PE。与其他集合函数相同,广播函数允许程序员通过指定开始PE、log2(PE跨步)、PE的数目来选择参与广播的PE子集,以及长度为_SHMEM_BCAST_SYNC_SIZE的长整型对称同步数组,使用者必须在该数组被所有PE使用前将该数组初始化为预定值_SHMEM_SYNC_VALUE,因此需要在初始化和使用之间同步,例如全局同步屏障。然而,如前所述,可以使用两个同步数组,一旦执行初始化后的全局同步,程序可简单地在两个同步数组之间轮换,而不是在每个广播前加同步屏障。如果每次同步数组使用的PE主动集相同,同步数组可以在随后轮换的广播中使用(不需要重新初始化)。
第2章 机器人开发环境和VIPLE入门前面我们介绍了计算机领域的发展和职业选择可能,并且已经组建了团队,为我们的开发工作做好了准备。在正式开发之前,我们要先认识工作环境——VIPLE(Visual IoT/Robotics Programming Language Environment,可视化物联网/机器人编程语言环境),我们后续的工作都将在这个环境中进行,读者应该熟悉并掌握这个环境的使用方法。此外,我们还要先了解什么是工作流、什么是可视化编程,对可视化编程环境和工具有初步认识后,再通过一些例子在VIPLE中进行实践。
前 言 致读者本书是按照Java SE 8完全更新后的《Java核心技术 卷Ⅱ 高级特性(原书第10版)》。卷Ⅰ主要介绍了Java语言的一些关键特性;而本卷主要介绍编程人员进行专业软件开发时需要了解的高级主题。因此,与本书卷Ⅰ和之前的版本一样,我们仍将本书定位于用Java技术进行实际项目开发的编程人员。编写任何一本书籍都难免会有一些错误或不准确的地方。我们非常乐意听到读者的意见。当然,我们更希望对本书问题的报告只听到一次。为此,我们创建了一个FAQ、bug修正以及应急方案的网站http:// horstmann.com/corejava。你可以在bug报告网页(该网页的目的是鼓励读者阅读以前的报告)的末尾处添加bug报告,以此来发布bug和问题并给出建议,以便我们改进本书将来版本的质量。内容提要本书中的章节大部分是相互独立的。你可以研究自己最感兴趣的主题,并可以按照任意顺序阅读这些章节。在第1章中,你将学习Java 8的流库,它带来了现代风格的数据处理机制,即只需指定想要的结果,而无须详细描述应该如何获得该结果。这使得流库可以专注于优化的计算策略,对于优化并发计算来说,这显得特别有利。第2章的主题是输入输出处理。在Java中,所有I/O都是通过输入/输出流来处理的。这些流(不要与第1章的那些流混淆了)使你可以按照统一的方式来处理与各种数据源之间的通信,例如文件、网络连接或内存块。我们对各种读入器和写出器类进行了详细的讨论,它们使得对Unicode的处理变得很容易。我们还展示了如何使用对象序列化机制从而使保存和加载对象变得容易而方便,及其背后的原理。然后,我们讨论了正则表达式和操作文件与路径。第3章介绍XML,介绍怎样解析XML文件,怎样生成XML以及怎样使用XSL转换。在一个实用示例中,我们将展示怎样在XML中指定Swing窗体的布局。我们还讨论了XPath API,它使得“在XML的干草堆中寻找绣花针”变得更加容易。第4章介绍网络API。Java使复杂的网络编程工作变得很容易实现。我们将介绍怎样创建连接到服务器上,怎样实现你自己的服务器,以及怎样创建HTTP连接。第5章介绍数据库编程,重点讲解JDBC,即Java数据库连接API,这是用于将Java程序与关系数据库进行连接的API。我们将介绍怎样通过使用JDBC API的核心子集,编写能够处理实际的数据库日常操作事务的实用程序。(如果要完整介绍JDBC API的功能,可能需要编写一本像本书一样厚的书才行。)最后我们简要介绍了层次数据库,探讨了一下JNDI(Java命名及目录接口)以及LDAP(轻量级目录访问协议)。Java对于处理日期和时间的类库做出过两次设计,而在Java 8中做出的第三次设计则极富魅力。在第6章,你将学习如何使用新的日期和时间库来处理日历和时区的复杂性。第7章讨论了一个我们认为其重要性将会不断提升的特性—国际化。Java编程语言是少数几种一开始就被设计为可以处理Unicode的语言之一,不过Java平台的国际化支持则走得更加深远。因此,你可以对Java应用程序进行国际化,使得它们不仅可以跨平台,而且还可以跨越国界。例如,我们会展示怎样编写一个使用英语、德语和汉语的退休金计算器。第8章讨论了三种处理代码的技术。脚本机制和编译器API允许程序去调用使用诸如JavaScript或Groovy之类的脚本语言编写的代码,并且允许程序去编译Java代码。可以使用注解向Java程序中添加任意信息(有时称为元数据)。我们将展示注解处理器怎样在源码级别或者在类文件级别上收集这些注解,以及怎样运用这些注解来影响运行时的类行为。注解只有在工具的支持下才有用,因此,我们希望我们的讨论能够帮助你根据需要选择有用的注解处理工具。第9章继续介绍Java安全模型。Java平台一开始就是基于安全而设计的,该章会带你深入内部,查看这种设计是怎样实现的。我们将展示怎样编写用于特殊应用的类加载器以及安全管理器。然后介绍允许使用消息、代码签名、授权以及认证和加密等重要特性的安全API。最后,我们用一个使用AES和RSA加密算法的示例进行了总结。第10章涵盖了没有纳入卷Ⅰ的所有Swing知识,尤其是重要但很复杂的树形构件和表格构件。随后我们介绍了编辑面板的基本用法、“多文档”界面的Java实现、在多线程程序中用到的进度指示器,以及诸如闪屏和支持系统托盘这样的“桌面集成特性”。我们仍着重介绍在实际编程中可能遇到的最为有用的构件,因为对Swing类库进行百科全书般的介绍可能会占据好几卷书的篇幅,并且只有专门的分类学家才感兴趣。第11章介绍Java 2D API,你可以用它来创建实际的图形和特殊的效果。该章还介绍了抽象窗口操作工具包(AWT)的一些高级特性,这部分内容看起来过于专业,不适合在卷I中介绍。虽然如此,这些技术还是应该成为每一个编程人员工具包的一部分。这些特性包括打印和用于剪切粘贴及拖放的API。第12章介绍本地方法,这个功能可以让你调用为微软Windows API这样的特殊机制而编写的各种方法。很显然,这种特性具有争议性:使用本地方法,那么Java平台的跨平台特性将会随之消失。虽然如此,每个为特定平台编写Java应用程序的专业开发人员都需要了解这些技术。有时,当你与不支持Java平台的设备或服务进行交互时,为了你的目标平台,你可能需要求助于操作系统API。我们将通过展示如何从某个Java程序访问Windows注册表API来阐明这一点。 目 录 第1章 Java SE 8的流库1.1 从迭代到流的操作1.2 流的创建1.3 filter、map和flatMap方法1.4 抽取子流和连接流1.5 其他的流转换1.6 简单约简1.7 Optional类型1.7.1 如何使用Optional值1.7.2 不适合使用Optional值的方式1.7.3 创建Optional值1.7.4 用flatMap来构建Optional值的函数1.8 收集结果1.9 收集到映射表中1.10 群组和分区1.11 下游收集器1.12 约简操作1.13 基本类型流1.14 并行流第2章 输入与输出2.1 输入/输出流2.1.1 读写字节2.1.2 完整的流家族2.1.3 组合输入/输出流过滤器2.2 文本输入与输出2.2.1 如何写出文本输出2.2.2 如何读入文本输入2.2.3 以文本格式存储对象2.2.4 字符编码方式2.3 读写二进制数据2.3.1 DataInput和DataOutput接口2.3.2 随机访问文件2.3.3 ZIP文档2.4 对象输入/输出流与序列化2.4.1 保存和加载序列化对象2.4.2 理解对象序列化的文件格式2.4.3 修改默认的序列化机制2.4.4 序列化单例和类型安全的枚举2.4.5 版本管理2.4.6 为克隆使用序列化2.5 操作文件2.5.1 Path2.5.2 读写文件2.5.3 创建文件和目录2.5.4 复制、移动和删除文件2.5.5 获取文件信息2.5.6 访问目录中的项2.5.7 使用目录流2.5.8 ZIP文件系统2.6 内存映射文件2.6.1 内存映射文件的性能2.6.2 缓冲区数据结构2.6.3 文件加锁机制2.7 正则表达式第3章 XML3.1 XML概述3.1.1 XML文档的结构3.2 解析XML文档3.3 验证XML文档3.3.1 文档类型定义3.3.2 XML Schema3.3.3 实用示例3.4 使用XPath来定位信息3.5 使用命名空间3.6 流机制解析器3.6.1 使用SAX解析器3.6.2 使用StAX解析器3.7 生成XML文档3.7.1 不带命名空间的文档3.7.2 带命名空间的文档3.7.3 写出文档3.7.4 示例:生成SVG文件3.7.5 使用StAX写出XML文档3.8 XSL转换
2.2 数组 本文讲的是PIC微控制器项目设计:C语言一2.2 数组,数组是通过指定其类型、名称和存储的元素数量进行声明的。例如:创建了一个无符号基本整型数组,名称为Total,有5个元素。数组的第一个元素索引为0。因此,在上面的例子中,Total[0]指的是它的第一个元素。数组Total存储在存储器的5个连续位置中,如下表所示。可以通过指定数组名称和索引将数据存储在数组中。例如,为了将25存储到数组的第二个元素中,我们必须写:同样,可以通过指定数组名称和它的索引对数组中的内容进行读取。例如,为了将第三个数组元素复制到一个叫temp的变量里,我们必须写为:数组的内容可以在其声明期间初始化。下面给出了一个例子,数组months有12个元素,months [0] = 31,months [1] = 28,依此类推。上面的数组也可以不指定数组的大小来进行声明:字符数组也可以类似地声明。在下面的例子中,一个名为Hex_Letters且有6个元素的字符数组声明为:字符串是个具有空终止符的字符数组。字符串要么通过双引号括起来进行声明,要么通过单引号指定数组中的每个字符来进行声明,其后再以空字符结束:或在C编程语言中,我们还可以声明多维数组。在下面的例子中,创建了一个名为P的3行4列的二维数组。数组总共有12个元素。该数组的第一个元素为P[0] [0],最后一个元素为P2。这个数组的结构如下表所示。 原文标题:PIC微控制器项目设计:C语言一2.2 数组
第3章 线性光和感知均匀性 每一个灰度图像的像素值代表了广义上的明亮度。然而明亮度是根据该区域散发光的多少被正式定义为视觉感官的属性。这种定义显然是很主观的:明亮度不能被测量,因此不能作为数据图像的度量。而且,根据色彩表现理论,明亮度没有最大值:明亮度跟任何值没有关系。光强(intensity)是某个方向上的辐射功率,也就是每单位立体角的功率[W·sr-2]; 辐射(radiance)是指每单位投影面积的光强度。这些术语都忽略了波长因素,但是在彩色图像中,波长是非常重要的!这些都不适合作为彩色图像数据的度量单位。参阅附录B有关于辐射度和光度的介绍。声音强度与光强在概念上有很大的不同。照度是指光中与视觉明度感知相关的光谱灵敏度的加权值,照度与光强度成正比;在SI系统中,它代表了每平方米的坎德拉量[cd·m-2],通常称为nit[nt],图像系统中很少采用正比于照度的像素值,而经常采用与照度非线性相关的数值。照度描述了落在物体上的光,从技术上讲,是集成在一个半球上的照度。明度是被CIE定义为某个区域的明亮程度,与被照亮区域的明度相似,其表现为白色或者强放射性。纯化论者可能会说这个判定方法太主观了。然而,客观量L定义为相对照度感观反应的标准估计。它是用相对照度的非线性特征方程模拟在特定情况下的视觉反应来计算的。一些灰度级成像系统中有正比于L的像素值。测量明度的值大致相当于CIE L(通常在0~10,不是100)。在图像科学领域,与标准图像一致的情况很少。遗憾的是,有很多从事于数字图像处理和计算机图形处理的人都对这些条款视而不见。在HSB, HSI, HSL和HSV系统中B代表着明亮度(brightness),I代表着强度(intensity),L代表着明度(lightness),V代表着数值;然而,这里没有一个与色彩科学领域公认的明亮度、强度、照度或者数值相关。彩色图像是以三基色值来感应和传播的,它的幅度正比于强度,其光谱成分是根据彩色图像领域精心挑选的。相对照度可以看做是一个非常有用的三基色值,除了那些特点,三基色值是以三个为一组的。数码相机传感器产生的值,正比于辐射强度,接近于红绿蓝(RGB)三色值。我们把这些值称为线性光。然而,在大部分成像系统,RGB三色值采用非线性编码——伽马修正——来模仿人类的视觉系统。大部分图像编码系统使用的R′G′B′值并不正比于强度,在这里的标记符号表示施加的感知驱动的非线性参数。亮度信号(“Y′”)是R′G′B′的加权和,它是视频、 MPEG、JPEG和类似图像编码系统的亮度/色彩差分编码的基础。在视频中,伽马修正中的非线性变换形成R′G′B′分量随后融入到亮度和色度(Y′CBCR)分量中。
2.4图像获取 人们使用相机获取一个场景的图像数据,期望图片放映时,它能近似真实地再现这个场景。室外白色的亮度能够达到30 000cd·m-2,但是很难找到一个电子显示设备的照度能够达到450cd·m-2,专业的高清电视机能够满足的参考白色值标准是100cd·m-2左右。场景照度线性变换到显示屏——实际上采用0.015或者0.01的因子缩放绝对照度——不能展现与外面场景相同的效果。人们使用相机希望最终在显示屏上能够有外观上的近似匹配。因此图像渲染必须要进行。在高清电视机和消费类静态摄影中,渲染是在相机中进行的,在数字影院和专业的(“原生态”)静态摄影中,渲染是在后期制作中进行的。有些人用“现场到屏幕”来描述这样的目标,即把描述场景的照度和颜色的呈现精确地传输到显示屏。除非适当考虑显示现象——就是说,除非强加图片渲染——要不然这种效果注定是要失败的。
3.10.4 自动添加磁盘到VSAN磁盘组 在过去几个版本发布以来,选择自动模式还是手动模式一直是个热议的话题。我们发现大多数情况下,客户都喜欢自己控制磁盘组的设备构成的选择,而手动模式才能实现这一点。不过某些情况下,客户倾向于让VSAN来进行磁盘管理,这是完全受支持的。如果在VSAN创建流程中选择了自动模式,VSAN会自动发现每台主机上的本地磁盘和本地SSD,并在群集的每台主机上创建磁盘组。注意,这些SSD和磁盘只有在完全空置且没有任何分区信息的情况下才会被VSAN声明(claim)。VSAN不会声明那些正在被使用或者过去曾被用过并携带有数据的磁盘。要让VSAN声明这些磁盘,它们必须先被清空。每一台具有有效存储的主机都会有一个含有本地磁盘和SSD的磁盘组。简单来说,磁盘组可以被当作是一个磁盘和SSD的容器。如前所述,每个磁盘组只能包含一个缓存设备和最多7个容量设备,不过因不同的ESXi主机而异,一台主机可能会有多个磁盘组。最后,当所有这些都完成后,VSAN数据存储就创建好了,它的大小就是集群中所有主机提供的所有这些容量设备的容量总和减去一些元数据开销(metadata overhead)。对于那些不提供存储给VSAN数据存储的VSAN群集中的主机来说,它们仍然可以访问VSAN数据存储。这是VSAN的一个非常有用的特性,因为这样VSAN群集不仅仅可以因为存储需求而横向扩展,也可以仅仅因为计算需求进行扩展。不过请注意,出于更好地负载均衡、可用性和整体性能的考虑,VMware建议群集中的主机都采用完全一样的配置。尽管自动模式会声明本地(local)磁盘,大多数带有SAS控制器的ESXi主机会把它们的硬盘认作远程(Remote),因此VSAN不会自动声明这些磁盘。在这种情况下,即使群集是配置成自动模式的,vSphere管理员仍将不得不手工创建磁盘组,在第2章中我们曾经解释过这一点。
3.9.1 vSphere HA通信网络 在非VSAN部署中,vSphere HA代理的通信是通过管理网络进行的;在VSAN环境中,vSphere HA代理的通信是通过VSAN网络进行的。背后的原因是我们希望当网络故障发生时,vSphere HA主机和VSAN主机是位于同一分区(partition)中的,这就避免了故障时因vSphere HA和VSAN判断的分区不同而造成拥有的存储组件和对象集不同所造成的可能的冲突。在VSAN环境下的vSphere HA在默认情况下仍然将管理网络的默认网关用作隔离检测(isolation detection)。我们估计大多数VSAN环境的管理网络和VSAN网络很可能是使用同一个物理网络基础架构的(尤其是在万兆网络情况下)。但是,如果VSAN网络和管理网络是位于不同的物理网络基础架构,建议将默认的vSphere HA隔离检测地址从管理网络变更为VSAN网络。如前所述,默认情况下隔离地址是管理网络的默认网关。VMware建议在VSAN上的vSphere HA使用VSAN网络上的某个IP地址作为隔离地址。要使用VSAN网络上的某个IP地址而不是管理网络的默认网关作为隔离检测地址,下列的vSphere HA高级设置中的配置需要进行变更:das.useDefaultIsolationAddress=falsedas.isolationAddress0=不过,如果VSAN网络里面没有合适的隔离地址,那么也可以如默认设定那样保留管理网络中的隔离地址。另一个显著的区别和网络重配置有关。vSphere HA不会自动探测到在VSAN层面就VSAN网络执行的变化,因此,为了探测到这些变化,vSphere管理员必须手动触发vSphere HA群集的重配置。
2.3.8 网络I/O控制 尽管建议使用万兆网卡,但是并非要将这些万兆网卡仅仅专用于VSAN网络,它们是可以与其他网络流量共享的。然而,你可能需要考虑使用网络I/O控制(NIOC)来保证在网络拥堵的情况下VSAN流量仍能获得一定数量的网络带宽。尤其是当这块万兆网卡和诸如vMotion之类的流量共享时,因为声名狼藉的vMotion可是会在任何可能的情况下吃掉所有带宽的哦。使用NIOC必须创建分布式交换机(VDS),因为它不支持标准交换机(VSS)。幸运的是,分布式交换机已经包含在VSAN许可证中了。第3章会列举各种例子来说明在不同类型的网络配置的情况下应该怎样配置NIOC。
2.3.3 二层或三层 VSAN支持二层(交换)或三层(路由)网络。最初的发布版本并不支持三层网络,不过到6.0版就完全支持了。必须强调VSAN依赖组播通信,这意味着不管是二层还是三层都必须允许组播流量通过,对于三层来说,还意味着组播流量必须能在网络间被路由。我们注意到在过去的2年间我们和客户的很多对话都提到了三层组播通常默认是不开启的,因此记得在配置之前要先和网络团队沟通好。
2.3.2 受支持的虚拟交换机类型 无论是VMware vSphere Distributed Switch?(VDS)还是VMware标准交换机(VSS)均支持VSAN。使用分布式交换机具有一些优点,这在第3章会详细介绍。其他类型的虚拟交换机类型未被仔细测试过是否可以用于VSAN。VDS的许可证已包含在VSAN之中。
2.1.3 ESXi主机引导的考虑因素 为基于VSAN的基础架构安装ESXi时,把ESXi镜像安装在什么地方有多种选择:本地磁盘、USB闪存驱动器、SD卡或SATADOM设备。注意,对SATADOM设备的支持仅从VSAN 6.0开始,这些设备在最初版本的VSAN中是不被支持的。写作本书时,当前版本的VSAN版本6.2不支持ESXi的无状态启动(自动部署方式)。选择将ESXi安装到USB闪存或SD卡的额外好处是无须为镜像浪费一块磁盘,于是这块磁盘就可以被VSAN用作创建分布式的、共享的VSAN数据存储,来部署虚拟机。不过,这种方法有一些缺点,例如缺乏空间来保存日志文件和VSAN trace文件。对于内存小于等于512GB的主机来说,是可以从USB或SD卡引导的。对于内存配置超过了512GB的主机,ESXi需要安装在一块本地磁盘或SATADOM设备上。这将在第10章中详细探讨。请注意,当将ESXi安装在USB或SD卡上的时候,设备应该至少有8GB内存。如果主机没有USB或SD卡而把ESXi安装在一块本地磁盘上时,这块本地磁盘将无法加入一个磁盘组,因而无法用于提供存储给VSAN数据存储。正因为如此,在磁盘插槽数量有限的环境中,我们建议使用USB/SD或SATADOM。
2.3.1 最坏情况 对于任一特定值n,算法或者程序在处理所有规模为n的样本时的执行时间可能会发生巨大的变化。对于一个给定的程序和一个给定的值,最坏的执行时间就是处理所有规模为n的数据所需要的最长执行时间。之所以关注算法的最坏情况,是因为它通常是最容易分析的情况。此外,它还能够说明程序在各种场景下到底会有多慢。更正式地说,如果Sn是所有规模为n的问题样本si构成的集合,t()代表算法对于每一个问题样本所需要的执行时间,那么算法在最坏情况下的执行时间为:t(si)对于所有si∈Sn的最大值。我们将Sn在最坏情况下的性能记作Twc(n)。Twc(n)的增长率定义了算法在最坏情况下的复杂度。一般来说,通过计算每一份数据si来判定算法在哪份数据上表现最坏,这种做法是不切实际的——没有足够的资源。相反,算法分析人员会精心设计出能让算法的性能落入最坏情况的问题样本。
1.2 “Hello World!”实例程序 本节详细介绍编译和运行“Hello World!”实例程序的指令。1.2.1节介绍利用集成开发环境NetBeans IDE开发该程序的过程。NetBeans IDE在Java平台上运行,也就是说可在任何配置了JDK的操作系统上运行NetBeans IDE,包括Microsoft Windows、Solaris、Linux和OS X。建议尽可能使用NetBeans IDE取代命令行。1.2.2节和1.2.3节依次介绍不使用集成开发环境时在Microsoft Windows、Solaris、Linux等平台上开发该实例程序的详细过程。(关于支持的操作系统版本信息,参见Oracle JDK 8和JRE 8认证的系统配置。)运行时遇到问题,可参考1.4节,这部分给出了新手可能会遇到的大多数问题的解决方案。 1.2.1 用NetBeans IDE开发“Hello World!” 现在写第一个程序!这些指令适用于NetBeans IDE用户。1.软件列表编写该实例程序需要准备下述两个软件:1)Java SE开发工具包。参考Java SE下载页。2)NetBeans IDE。NetBeans IDE下载页给出所有平台的NetBeans IDE列表。2.创建第一个应用程序第一个应用程序HelloWorldApp显示问候语“Hello World!”。按如下过程创建该程序:1)创建IDE项目。创建IDE项目时会创建一个环境,用于构建(build)和运行应用程序。使用IDE项目不会遇到像命令行开发那样的配置问题。在IDE中,只需选择一个菜单项,就可以建立和运行应用程序。2)在生成的源文件中添加代码。源文件包含Java语言写的代码。创建IDE项目时,会自动生成一个源文件框架(skeleton),只需修改该文件添加“Hello World!”消息即可。3)将源文件编译成.class文件。IDE会调用Java编译器(javac)将源文件翻译成Java虚拟机能理解的指令。这些指令通常称为字节码。4)运行程序。IDE调用Java应用程序启动器(java)在Java虚拟机中运行应用程序。3.创建IDE项目按下述步骤创建IDE项目。1)启动NetBeans IDE。 在Microsoft Windows系统中,单击Start菜单中的NetBeans IDE选项。 在Solaris和Linux系统中,进入IDE的bin目录,输入./netbeans执行IDE启动脚本。 在OS X系统中,单击NetBeans IDE图标。 2)在NetBeans IDE中选择File | New Project(如图1-4所示)。 3)在New Project向导中,扩展Categories中的Java,选择Projects中的Java Application,然后单击Next(如图1-5所示)。 4)在Name and Location向导页中,做如下操作(如图1-6所示): 在Project Name字段输入Hello World App。 在Create Main Class字段输入helloworldapp.HelloWorldApp。 5)单击Finish。项目创建完毕,IDE会打开该项目,如图1-7所示。创建的项目包含以下组件: Projects窗口,包含项目组件(如源文件和所需的库等)的树形图。 Source Editor窗口,其中已打开了文件HelloWorldApp.java。 Navigator窗口,快速查阅所选类中的元素。 4.在平台列表中添加JDK 8(如有必要)可能需要在IDE的可用平台列表中添加JDK 8。这可通过选择Tools菜单中的Java Platforms实现,如图1-8所示。 如果已安装的平台列表中没有JDK 8(可能显示为1.8或1.8.0),单击Add Platform,转到JDK 8的安装目录,并单击Finish。会看到新平台添加完毕,如图1-9所示。 如果要将JDK 8设为所有项目的默认平台,只需在命令行使用--jdkhome参数运行IDE,或将JDK的安装路径设为文件“安装目录/etc/netbeans.conf”中的netbeans_j2sdkhome属性的值。如果只需将JDK 8设为当前项目的默认平台,在Projects面板中选择Hello World App,单击File菜单中的Project Properties(Hello World App),单击Libraries,然后选择Java Platform下拉菜单中的JDK 1.8,结果与图1-10类似。至此IDE已成功配置JDK 8。 5.在源文件中添加代码创建项目时,已经选中New Project向导中Create Main Class前面的选择框。因此,IDE会创建一个框架类。将其中代码 这四行是代码注释,不会影响程序运行。本章后续小节会介绍代码注释的使用和格式。注意 如上所述输入所有的代码、命令和文件名。编译器(javac)和启动器(java)都是区分大小写的,所以输入的英文字母大小写必须一致。比如,HelloWorldApp与helloworldapp是不同的。 选择File菜单,单击Save菜单项保存文件,文件内容如下: 6.将源文件编译成.class文件在IDE主菜单中,选择Run | Build Project (Hello World App)菜单项编译源文件。Output窗口及其结果如图1-11所示。如果构建输出中包含语句BUILD SUCCESSFUL,那么恭喜你,编译成功!如果构建输出中包含语句BUILD FAILED,那么代码可能存在语法错误。Output窗口报告的错误都是超链接文件,双击超链接就可定位代码中的错误位置。修正错误后再次选择Run | Build Project重新构建即可。 构建项目时会生成字节码文件HelloWorldApp.class。打开Files窗口,展开节点Hello World App/build/classes/helloworldapp,显示生成的新文件,如图1-12所示。至此,项目构建完毕,接下来就可以运行程序了。 7.运行程序在IDE菜单栏中,选择Run | Run Main Project菜单项。如果运行成功,会显示如 8.NetBeans IDE的使用技巧本章剩余部分会解释该应用程序中的代码。后续小节会深入介绍核心的语言特性,并提供更多例子。尽管本书后面不会介绍使用NetBeans IDE的指令,但使用IDE编写和运行实例代码比较容易。这里介绍IDE的一些使用技巧: 在IDE中创建项目后,就可使用New File向导往项目中添加文件。选择File | New File并在向导中选择一个模板,如Empty Java File模板。 IDE的Compile File(F9)和Run File(Shift+F6)可以分别编译和运行单个文件(相对于整个项目而言)。使用Run Main Project命令时,IDE只会运行IDE关联为主项目的主类的文件。因此,即使在HelloWorldApp项目中创建其他的类并使用Run Main Project命令运行该文件,IDE也只会运行HelloWorldApp文件。 也可以为应用程序创建包含多个源文件的独立IDE项目。 在IDE中输入代码时,可能会弹出代码完成框。此时,可以忽略代码完成框并继续输入代码,也可以选择完成框中的表达式。如果不喜欢自动弹出代码完成框,可以关闭该功能。选择Tools | Options | Editor,单击Code Completion标签,清除Auto Popup Completion Window选择框。 选择IDE菜单栏中的Refactor菜单可以重命名Projects窗口中的源文件节点。弹出的Rename对话框会引导重命名类和更新代码,单击Refactor保存这些更改。如果项目只有一个类,这些操作就看似没有必要;但在大项目中,当更改会影响代码的其他部分时,这些操作会很有用。 关于NetBeans IDE的详细特性,可参考《NetBeans文档页》。 1.2.2 在Microsoft Windows中开发“Hello World!” 本节介绍在Windows系统中基于命令行开发“Hello World!”实例程序的详细过程。(关于支持的操作系统版本的信息,参见Oracle JDK 8和JRE 8认证的系统配置。)1.软件列表编写该程序需要准备下列两个软件:1)JDK 8。下载Windows版本的JDK 8。(注意是下载JDK,而不是下载Java运行时环境JRE)。详情参考安装指南。2)文本编辑器。在这个例子中使用Windows自带的Notepad编辑器。如果使用其他文本编辑器,只需修改相关指令即可。2.创建第一个应用程序按如下过程创建应用程序HelloWorldApp:1)创建源文件。源文件包括用Java写的代码。源文件可以用任意文本编辑器创建和编写。2)将源文件编译成.class文件。Java编译器(javac)将源文件翻译成Java虚拟机能理解的指令。如前所述,.class文件中的指令通常称为字节码。3)运行程序。Java应用程序启动器(java)使用Java虚拟机运行应用程序。3.创建源文件创建源文件有两种方法:①保存文件HelloWorldApp.java;②采用下述方法。首先打开文本编辑器。在Start菜单选择Programs | Accessories | Notepad启动Notepad编辑器。在新文档中输入以下代码: 注意 如上所述输入所有的代码、命令和文件名。编译器(javac)和启动器(java)都是区分大小写的,所以输入的字母大小写必须一致。 将上述代码存入文件HelloWorldApp.java。在Notepad中,选择File | Save As菜单项,然后在弹出的Save As对话框中执行如下操作:1)使用Save In组合框指定保存文件的文件夹(或目录)。在这个例子中,目录是C:myapplication。2)在File name文本字段中输入"HelloWorldApp.java",包括双引号。3)在Save as type组合框中,选择Text Documents(*.txt)。4)在Encoding组合框中,编码类型选为ANSI。完成后,对话框如图1-14所示。单击Save,退出Notepad。 4.?将源文件编译成.class文件在Start菜单中选择Run,然后输入cmd,启动shell窗口或命令行窗口,如图1-15所示。 命令提示符会显示当前目录。打开命令行窗口时,当前目录通常是主目录,如图1-15所示。要编译源文件,需将当前目录定位到文件所在的目录。比如,如果源文件目录是C:myapplication,在提示符下输入下述指令并按回车键: 当前目录就变成C:myapplication>。注意 要切换到不同驱动器上的目录,需要输入其他命令,也就是说要输入驱动器的名称。比如,要切换到D:myapplication,就必须输入D:,如下所示: 在提示符下输入dir并按回车键,可以显示源文件,如下所示: 现在可以编译文件了。在提示符下输入下述命令并按回车键: 编译器会生成字节码文件HelloWorldApp.class。在提示符下输入dir并按回车键就会显示生成的新文件,如下所示: 生成.class文件后,就可以运行程序了。5.运行程序在同一个目录下,在提示符下输入下述命令并按回车键: 恭喜,程序运行成功。上述过程中如果遇到问题,可参考1.4节。 1.2.3 在Solaris和Linux中开发“Hello World!” 本节详细介绍在Solaris和Linux系统中用命令行开发“Hello World!”实例程序的完整过程。1.软件列表编写第一个程序需要准备下述两个工具:1)JDK 8。下载Solaris或Linux版本的JDK 8。(注意是下载JDK,而不是下载Java运行时环境JRE)。详情参考《安装指南》。2)文本编辑器。在这个例子中,采用Pico编辑器(大多数基于UNIX的平台都能用它)。如果使用不同的文本编辑器(如vi或emacs),这些指令很容易改写过来。2.创建第一个应用程序按如下过程创建应用程序HelloWorldApp:1)创建源文件。源文件包括用Java语言写的代码。源文件可以用任意文本编辑器创建和编辑。2)将源文件编译成.class文件。Java编译器(javac)将源文件翻译成Java虚拟机能理解的指令。如前所述,.class文件中的指令通常称为字节码。3)运行程序。Java应用程序启动器(java)使用Java虚拟机运行应用程序。3.创建源文件创建源文件有两种方法:①无需输入代码,保存文件HelloWorldApp.java;②采用下述过程创建。先打开shell窗口或Terminal窗口,如图1-16所示。刚打开时,当前目录通常是主目录。任何时候,只要在提示符下输入cd,然后按Enter键,就可以将当前目录切换成主目录。 创建的源文件保存在独立的目录中。命令mkdir可用于创建目录。比如,使用下述命令可以在/tmp目录中创建examples/java目录: 使用下述代码可将当前目录切换到该新目录: 现在创建源文件。在提示符下输入pico并按Enter键,就可以启动Pico编辑器。如果系统返回消息“pico:command not found”,就说明pico很有可能无法用。此时需要咨询系统管理员或者使用其他编辑器。启动Pico时,会出现新的空缓冲区(buffer)。这就是输入代码的区域。将下述代码输入该缓冲区: 注意 对如上输入的所有代码、命令和文件名,编译器(javac)和启动器(java)都是区分大小写的,所以输入的字母大小写必须一致。 将上述代码存入文件HelloWorldApp.java。在Pico编辑器中,按下Ctrl+O,编辑器的底部就会显示提示符File Name to Write:,输入HelloWorldApp.java文件的保存目录及文件名。例如,如果要将HelloWorldApp.java保存在目录/tmp/examples/java中,只需输入/tmp/examples/java/HelloWorldApp.java并按Enter键即可。最后按Ctrl+X退出Pico。4.?将源文件编译成.class文件打开另一个shell窗口。编译源文件时,要将当前目录定位为源文件所在的目录。比如,如果源文件所在目录是/tmp/examples/java,只需在提示符下输入下述命令并按Enter键即可: 在提示符下输入pwd,就可显示当前目录。在这个例子中,当前目录已经变成/tmp/examples/java。输入ls即可显示创建的文件,如图1-17所示。现在来编译源文件。在提示符下输入下述命令并按Enter键: 编译器会生成字节码文件HelloWorld-App.class。在提示符下输入ls就可显示创建的新文件,如图1-18所示。生成.class文件后,就可以运行程序了。5.?运行程序在相同目录下,在提示符下输入下述命令: 如果结果如图1-19所示,就说明程序可以运行了。在上述过程中如果遇到问题,可参考1.4节。
第3章 关联分析模型 关联分析用于描述多个变量之间的关联。如果两个或多个变量之间存在一定的关联,那么其中一个变量的状态就能通过其他变量进行预测。关联分析的输入是数据集合,输出是数据集合中全部或者某些元素之间的关联关系。例如,房屋的位置和房价之间的关联关系或者气温和空调销量之间的关系。 关联分析主要包括如下分析内容: (1)回归分析回归分析是最灵活最常用的统计分析方法之一,它用于分析变量之间的数量变化规律,即一个因变量与一个或多个自变量之间的关系。特别适用于定量地描述和解释变量之间相互关系或者估测或预测因变量的值。例如,回归分析可以用于发现个人收入和性别、年龄、受教育程度、工作年限的关系,基于数据库中现有的个人收入、性别、年龄、受教育程度和工作年限构造回归模型,基于该模型可以根据输入的性别、年龄、受教育程度和工作年限预测个人收入。 (2)关联规则分析关联规则分析用于发现存在于大量数据集中的关联性或相关性,从而描述了一个事物中某些属性同时出现的规律和模式。关联规则分析的一个典型例子是购物篮分析。该过程通过发现顾客放入其购物篮中的不同商品之间的联系,分析顾客的购买习惯。通过了解哪些商品频繁地被顾客同时购买,这种关联的发现可以帮助零售商制定营销策略。其他的应用还包括价目表设计、商品促销、商品的排放和基于购买模式的顾客划分。 (3)相关分析相关分析是对总体中确实具有联系的指标进行分析。它是描述客观事物相互间关系的密切程度并用适当的统计指标表示出来的过程。例如,在经济学中,如果一段时期内出生率随经济水平上升而上升,这说明两指标间是正相关关系;而在另一时期,随着经济水平进一步发展,出现出生率下降的现象,两指标间就是负相关关系。 相关分析与回归分析在实际应用中有密切关系。然而在回归分析中,所关心的是一个随机变量Y对另一个(或一组)随机变量X的依赖关系的函数形式。而在相关分析中,所讨论的变量的地位一样,分析侧重于变量之间的种种相关特征。例如,以X、Y分别记为高中学生的数学与物理成绩,相关分析感兴趣的是二者的关系如何,而不在于由X去预测Y。
2.1 开发环境准备和快速入门 2.1.1 R语言简介R语言的前身是S语言,S语言是由AT &T Bell实验室的Rick Becker、John Chambers和Allan Wilks开发的一种用来进行数据探索、统计分析、作图的解释型语言。最初S语言的实现版本主要是S-PLUS。S-PLUS是一个商业软件,它基于S语言,并由MathSoft公司的统计科学部进一步完善。而R语言最初由来自新西兰大学的Ross Ihaka和Robert Gentleman开发(由于他们的名字都以R开头,所以该软件被命名为R)。因为R语言是基于S语言的一个GNU项目,所以也可以当作S语言的一种实现,通常用S语言编写的代码都可以不做修改地在R语言环境下运行。R语言是一套开源的数据分析解决方案,几乎可以独立完成数据处理、数据可视化、数据建模及模型评估等工作,而且可以完美配合其他工具进行数据交互。具体来说,R语言具有以下优势:1)R语言作为一种GNU项目,开放了全部源代码,用户可以免费下载使用和修改。2)R语言可以运行在多种平台上,包括Windows、UNIX和Mac OS。3)R语言可以轻松地从各种类型的数据源导入数据,包括文本文件、数据库管理系统、统计系统乃至Hadoop、Spark等。它同样可以将数据输出并写入这些系统中。4)R语言内置多种统计学及数据分析功能。因为有S的血缘,所以R比其他统计学或数学专用的编程语言有更强的面向对象的功能。5)R语言拥有顶尖的制图功能。不仅有 lattcie包、ggplot2包对复杂数据进行可视化,更有rCharts包、recharts包、plotly包实现数据交互可视化,甚至可以利用功能强大的shiny包实现R与Web整合部署,构建网页应用,帮助不懂CSS、HTML的用户利用R快速搭建自己的数据分析App应用。当然,R语言也存在一些固有的缺点,目前主要的问题有如下三点:1)R语言是一种解释型语言,和编程语言相比,速度显得略慢一些,但是随着硬件和R自身的发展,这个问题已经被慢慢弱化了,而且如果能够熟练运用向量化运算,可以大大提高速度,并且若使用R内置的分析函数,效率高很多,因为很多函数都是由C或者Fortran编写的。2)R所有的计算实际是基于内存进行的,这就意味着,在处理数据的过程中,数据必须完整地装入内存当中,这在处理小型数据是没有任何问题的,但是当遇到大数据时,问题就会变得很严重。但是,这个问题也得到了一定的解决,可以利用并行包提升R的性能,或者利用R结合Hadoop的方式进行大数据分析工作。3)由于R语言的自由,各种包的编写者来自不同的领域,所以在一定程度上是比较混乱的,没有统一的命名格式,参数格式不一,源代码和文档质量良莠不齐。 2.1.2 R的安装 截至目前(撰写本书时),R的最新版本是3.4.0,可以在CRAN(Comprehensive R Archive Network)获取最新版本。在https://www.r-project.org/页面点击download R可以进入CRAN镜像站地址https://cran.r-project.org/mirrors.html,其中包含中国大陆地区的7个镜像地址,你可以选择距离最近的地址进入其镜像的详细页面。此外,通过该页面可以下载Linux、MacOS和Windows操作系统的安装包。如果需要安装旧版的R,可以到https://cran.r-project.org/bin/下载对应的版本。本书使用的是Windows操作系统下的R 3.2.2版本。直接双击下载好的R-3.2.2-win.exe进行安装即可。安装完成后,双击桌面图标启动R,打开如图2-1所示的界面。R的界面相当简洁,只有为数不多的几个菜单栏和快捷按钮。快捷按钮下面是主控制台,它是输入脚本和执行结果窗口。 2.1.3 其他辅助工具 与传统的数据挖掘工具SAS、SPSS和IBM SPSS Modeler等软件相比,R的缺点在于没有友好的操作菜单,这会使很多熟悉其他工具的用户起初会觉得很困难。幸好,R自由的特性得到很好的发挥,有用户贡献的R包实现了很多功能的菜单化操作。下面介绍一个比较友好的编辑器和一个可以实现菜单化操作完成数据挖掘工作的包。 虽然现在有很多可用的IDE,但是现在最好用的应该是Rstudio,它是专门用于R语言环境的IDE。Rstudio可用于Windows、Mac和Linux,并且可以在Linux环境中安装Rstudio-Server,它允许用户通过一个Web浏览器的标准Rstudio界面来对RStudio进行多人协同操作。RStudio可以从其官网https://www.rstudio.com/免费下载安装。一般情况下,下载安装桌面版即可。安装完启动RStudio的基本界面如图2-2所示。 左上方的窗口是文本编辑器,具有强大的功能,可以在文本编辑器写好脚本,单击Run按钮(或者利用Ctrl+R快捷键)批量运行代码;右上方的窗口包括当前环境下的信息、历史命令;左下方的窗口是标准的R控制台;右下方的窗口包括文件路径、绘图窗口、已经在本地安装的包信息、帮助文档以及交互绘图时的图形浏览界面。Rattle是一个用于数据挖掘的R的图形交互界面(GUI),可用于快捷处理常见的数据挖掘问题。从数据的整理到模型的评价,Rattle给出了完整的解决方案。Rattle和R平台良好的交互性,又为用户使用R语言解决复杂问题开启了方便之门。Rattle易学易用,不要求很多的R语言基础,如今已被广泛应用于数据挖掘实践和教学之中。Rattle初始界面如图2-3所示。 > library(rattle) Rattle: A free graphical interface for data mining with R. XXXX 4.1.0 Copyright (c) 2006-2015 Togaware Pty Ltd. 键入'rattle()'去轻摇、晃动、翻滚你的数据。 > rattle() Rattle的标签栏已经集成数据导入、数据探索、数据检验、数据转化、数据建模及模型评估功能,可以通过鼠标单击的方式完成一整套的数据挖掘工作,并且可以利用Log日志查看每个操作的R脚本实现,借此来学习R语言的代码规范及编写。 2.1.4 R快速入门 R是一种区分大小写的解释型语言,程序内置的函数可以满足基本的数据分析需求,并有丰富的帮助文档帮助新手快速上手。此外,也有很多用户贡献了高质量的包,极大地扩展了R的功能,例如,用来进行数据处理的resharp2包、用来画图的ggplot2包和R与Web整合部署的shiny包。 1.新手上路 可以在命令提示符(>)后每次输入一条命令,或者一次性执行脚本文件里的一组命令。R语言是解释型语言,输入命令后可以实时响应,就好像计算器一样。 > 1+1 [1] 2 如果R监测到输入的命令行未结束,就会给出一个提示符“+”,提示要在下一行继续输入未完的命令,直到从语法角度来讲命令已经输入完整为止,不然R会有“unexpected end of input”的错误提示。 > 1+ + 错误: unexpected end of input > 1+ + 1 [1] 2 R语言的标准赋值符号是<-,也可以用=。例如,将序列1:10赋予对象a,可以执行如下操作。 > a = 1:10 此时,如果想查看对象a,直接输入小写a即可,但由于R是一种区分大小写的解释型语言,此时如果输入大写A,则会报错: > a [1] 1 2 3 4 5 6 7 8 9 10 > A 错误: 找不到对象'A'不仅仅针对数据对象,对于其他对象,R也是区分大小写的。例如,R自带的一个求相关系数的cor函数,如果我们错写为Cor函数,则会出现“没有"Cor"这个函数”的错误提示。 > cor(iris[,1:4]) Sepal.Length Sepal.Width Petal.Length Petal.Width Sepal.Length 1.0000000 -0.1175698 0.8717538 0.8179411 Sepal.Width -0.1175698 1.0000000 -0.4284401 -0.3661259 Petal.Length 0.8717538 -0.4284401 1.0000000 0.9628654 Petal.Width 0.8179411 -0.3661259 0.9628654 1.0000000 > Cor(iris[,1:4]) 错误: 没有"Cor"这个函数 2.获得帮助 R提供了大量的帮助文档,学会如何使用这些帮助文档可以让你快速上手。如果想知道某个函数或者数据集的信息,可以输入一个问号?,后面加上函数名。如果想查找某个函数,可以输入两个问号??,后面加上与此函数相关的关键词。函数help及help.search分别等同于?及??。例如: ?median # 等价于help("median"),查看中位数函数的帮助文档 ??median # 等价于help.serach("median") 搜索包含median的帮助信息 如果使用的是RStudio,也可以在Help中右上角的搜索框中输入median,查看该函数的帮助文档,如图2-4所示。 默认情况下,help只能查找已经加载到内存中扩展包的函数和数据,如果想查找那些未加载到内存扩展包中的函数和数据,需要指定help函数的package参数中的具体包名或者将try.all.package参数设置为TRUE。例如,想查找shiny包中的runExample函数: > help("runExample") No documentation for 'runExample' in specified packages and libraries: you could try '??runExample' > help("runExample",package = "shiny") > help("runExample",try.all.packages = TRUE) appropos函数能找出所有名字中含有“关键字”的函数,只在载入的包中搜索。例如: > apropos("plot") [1] ".__C__recordedplot" "assocplot" "barplot" [4] "barplot.default" "biplot" "boxplot" [7] "boxplot.default" "boxplot.matrix" "boxplot.stats" [10] "cdplot" "coplot" "fourfoldplot" [13] "interaction.plot" "lag.plot" "matplot" [16] "monthplot" "mosaicplot" "plot" [19] "plot.default" "plot.design" "plot.ecdf" [22] "plot.function" "plot.new" "plot.spec.coherency" [25] "plot.spec.phase" "plot.stepfun" "plot.ts" [28] "plot.window" "plot.xy" "preplot" [31] "qqplot" "recordPlot" "replayPlot" [34] "savePlot" "screeplot" "spineplot" [37] "sunflowerplot" "termplot" "ts.plot" 大多数函数都已经有相应的例子帮助我们了解该函数的工作原理。可以通过example函数来查看它们。例如: > example("median") median> median(1:4) # = 2.5 [even number] [1] 2.5 median> median(c(1:3, 100, 1000)) # = 3 [odd, robust] [1] 3 可以通过data函数查看datasets包中的数据集,如果要查看本地安装包的所有数据集,可以用命令data(package = .packages(all.available = TRUE))查看。 > data() > data(package = .packages(all.available = TRUE)) 3.工作空间 工作空间(workspace)就是当前R的工作环境,它储存所有用户定义的对象(向量、矩阵、函数、数据框、列表、模型、图形等)。例如,通过以下代码创建几个对象。 > # 创建数据对象a,b > a <- 1:10 > b <- 10:1 > # 创建模型对象fit > fit <- lm(Sepal.Length~Sepal.Width,data=iris) > # 创建图形对象q、p > library(ggplot2) > q <- qplot(mpg, wt, data = mtcars) > library(rCharts) > p <- rPlot(Sepal.Length ~ Sepal.Width | Species, data = iris, + type = 'point', color = 'Species') 对象创建完以后,可以通过ls函数查看当前工作空间中的对象。结果如下。 > ls() [1] "a" "b" "fit" "iris" "p" "q" 如果使用的是RStudio,可以直接在右上角查看当前工作空间的对象,如图2-5所示。 因为对象是储存在内存中的,所以可以删除不需要的对象及时释放内存,提高效率。通过rm()函数移除一个或多个对象,比如想删除对象fit,执行以下命令。 > rm(list="fit") > ls() [1] "a" "b" "p" "q" 如果需要删除剩下的全部对象,可以利用list=ls()实现。 > rm(list=ls()) > ls() character(0) 当前的工作目录(working directory)是R用来读取文件和保存结果的默认目录,可以使用函数getwd()来查看当前的工作目录。 > getwd() [1] "C:/Users/Think/Documents" 如果想改变当前的工作目录,可以使用setwd()函数或通过“文件”菜单下的“改变工作目录”命令实现。 4.包 包是R函数、数据、预编译代码以一种定义完善的格式组成的集合。R语言的使用,很大程度上是借助各种各样的R包的辅助,从某种程度上讲,R包就是针对R的插件,不同的插件满足不同的需求,截至2016年5月18日,CRAN已经收录了各类包8417个。计算机上存储包的目录称为库(library),该库位于R软件的安装目录/library目录下。可以通过函数.libPaths()查看库所在的位置,通过函数library()可以显示库中已安装的包。第一次安装一个包,使用命令install.packages("package_name","dir")即可。dir为包安装的路径。默认情况下安装在..library 文件夹中。例如,要安装一个可以快速读取大数据集的扩展包data.table,只需要执行install.packages( "data.table")即可完成安装。 > install.packages("data.table") 试开URL’ https://mirrors.tuna.tsinghua.edu.cn/CRAN/bin/windows/contrib/3.2/data.table_1.9.6.zip' Content type 'application/zip' length 1883523 bytes (1.8 MB) downloaded 1.8 MB 程序包'data.table'打开成功,MD5和检查也通过 下载的二进制程序包在 C:\Users\Think\AppData\Local\Temp\Rtmpoltpbz\downloaded_packages里 也可以选择R的菜单:程序包→安装程序包,在弹出的对话框中,选择要安装的包,然后确定。如果使用的是RStudio,可以选择菜单Tools→Install Packages,调出窗口,包括在线安装和本地安装两种方式。这里我们选择在线安装,只需要在Packages中输入包名后单击Install按钮进行安装即可,如图2-6所示。 包安装后,如果要使用包的功能。必须先把包加载到内存中(默认情况下,R启动后会自动加载基本包),加载包命令:library("包名")或者require("包名")。也可以通过RStudio右下窗口中的Packages加载包。默认情况下,扩展包是未加载到内存中的,如图2-7所示。 直接选中data.table,即可完成包的加载,如图2-8所示。 通过find.package()或者path.package()查看当前环境加载了哪些包。 >find.package() # or path.package() [1] "C:/Program Files/R/R-3.2.2/library/stringi" [2] "C:/Program Files/R/R-3.2.2/library/tidyr" [3] "C:/Program Files/R/R-3.2.2/library/stats" [4] "C:/Program Files/R/R-3.2.2/library/graphics" [5] "C:/Program Files/R/R-3.2.2/library/grDevices" [6] "C:/Program Files/R/R-3.2.2/library/utils" [7] "C:/Program Files/R/R-3.2.2/library/datasets" [8] "C:/Program Files/R/R-3.2.2/library/methods" [9] "C:/PROGRA~1/R/R-32~1.2/library/base" 可以通过detach()函数将已加载的包移出内存。例如,要将ada包从内存中移除,执行detach("package:ada")或取消选中RStudio右下角Packages选项卡中的ada包即可,如图2-9所示。 通过函数remove.packages()将包从本机删除。例如,要从计算机中删除data.table包,需执行以下命令或者单击RStudio右下角Packages选项卡中的data.table包版本号右边的叉,如图2-10所示。 > remove.packages("data.table") Removing package from 'C:/Program Files/R/R-3.2.2/library' (as ‘lib’ is unspecified)
1.7 案例:Netflix在机器学习竞赛中学到的经验 美国领先的付费视频公司 Netflix 在机器学习、系统推荐方面都做出了卓越的贡献, 早在 2007 年,Netflix 就率先提出了百万美元大奖,奖励在 Netflix Prize 竞赛中优胜的队伍。Netflix Prize通过为期三年的竞赛,积累了机器学习宝贵的第一手资料,成为了机器学习中的经典案例,这里我们介绍以下两个方面。 1.7.1 Netflix 用户信息被逆向工程 Netflix Prize进行影片推荐预测时,使用的数据包括用户名、影片名、评价日期、评价等级等信息,为了防止泄露用户个人的隐私信息,Netflix对用户名进行了加密处理。尽管如此,德州大学的研究人员仍然通过逆向工程成功得到了一些用户的个人信息。他们是怎么做到的呢?原来 Netflix 用户在评价一个影片的时候,往往还会去互联网影片库 IMDB 上转载自己的评论。德州大学的研究人员将 Netflix 数据集中的评论和IMDB 中的评论按照评论日期进行配对,很快就发现了具有上面行为的若干用户,其中不乏具有隐秘性取向的用户。这一研究结果一经发出之后,这些用户的生命安全直接受到了威胁,这也直接导致了 Netflix 在 2010 年遭到了以上用户的起诉,并且取消了 2010 年以后的所有竞赛。通过这一案例,我们意识到了在设计机器学习应用的时候一定要把用户隐私保护放在第一位。一些社会边缘个体特别容易因为自己的行为特征与大众不同而被模型泄露。 1.7.2 Netflix 最终胜出者模型无法在生产环境中使用 2009 年 Netflix 最终胜出的队伍为BellKor,该队伍是由四个队伍混合而成的。为什么要混合队伍呢?笔者曾有幸亲自向BellKor 成员之一的 Michael Jahrer 请教。故事是这样的,在比赛进行到了白热化阶段之后,来自雅虎、贝尔实验室、Commendo Research and Consulting 和 Pragmatic Theory 这四个队伍得到的结果都不相上下,这个时候,往往要在进行大量的参数调校后,模型才会有很少一点的提升。2009 年的时候,机器学习领域已经出现了 Emsemble 的概念。Emsemble 的意思是通过混搭来源不同的模型的结果,取长补短,以得到更为强大的模型。很自然的,上面这四支队伍先后决定合并成为一个大集体,最后取得了 Netflix 比赛的最终胜利。比赛确实是结束了,运用 Emsemble 过程带来的负面影响是,最终模型是由上百个小模型组成的,每个小模型都可能是由不同的语言来写成的,需要自己特殊的预处理程序,而且还需要独立的模型训练架构。虽然按照约定,Netflix 享有最终模型的使用权,但是实际上由于训练和运用模型的复杂性,Netflix 至今也没有将上述模型运用到实际应用中去。通过这一案例,我们可以学到,先进、前沿的机器学习模型固然很重要,得在运用的时候仍然要考虑到训练、运用的复杂性。一切从实际出发,也是本书全文的贯穿思想。
1.5 实时机器学习的分类 按照实际应用中采用的方式不同,实时机器学习可以分为硬实时、软实时和批实时三种模式,下面将分别进行介绍。 1.5.1 硬实时机器学习 硬实时的定义是:响应系统在接收到请求之后,能够马上对请求进行响应反馈,做出处理。硬实时机器学习的主要应用场景是网页浏览、在线游戏、高频交易等对时效性要求非常高的领域。在这些领域中,我们往往需要将相应延迟控制在若干毫秒以下。对于高频交易等场景,更是有不少计算机软件、硬件专家,开发出了各种专有模块以在更短的时间内完成交易,获得超额利润。在本书写作之时,计算机网络的传输速度仍然是响应延迟的一大主要因素。硬实时机器学习的响应架构往往会试图尽量减少请求处理过程中的网络传输步骤。与此同时,为了达到硬实时的要求,在请求突然增加的时候,往往会采取负载均衡的方法,靠增加服务器的数量来减少响应延迟。 1.5.2 软实时机器学习 软实时的定义是:响应系统在接收到请求的时候,立即开始对响应进行处理,并且在较短时间内进行反馈。软实时机器学习只要求系统立即对请求开始进行处理,最后处理完成所消耗的时间比较少,但是要求不如硬实时严格。软实时机器学习的主要应用场景是物流运输、较为频繁的数量金融交易等领域。例如某物流企业在接到订单之后需要对运输时间、物品风险进行预估,其中需要和多个系统服务进行交互读取,这个时候我们需要系统能够实时地做出处理,但是处理结果可能需要经过数秒才能得到。由于软实时机器学习对响应延迟的要求有所放松,因此往往会在处理架构中加入分布式队列这一组成部件。处理的任务会被实时地传输到分布式队列中,而后端的处理程序能响应式地对任务进行处理。与此同时,在请求增加的时候,可以通过分布式队列缓冲到达的任务,也可以通过负载均衡的方法增加处理单元,以保证低延迟。 1.5.3 批实时机器学习 硬实时机器学习和软实时机器学习都是针对具体的单个事件进行处理。与此相对应的,批实时机器学习是指对成批到达的数据进行实时的处理。批实时机器学习的应用场景往往处于后端机器学习模型的训练和数据处理加工上。通过实时训练的模型将会被部署到硬、软实时机器学习架构中,对数据进行处理。由于批实时机器学习需要对一定时间窗口内的所有数据进行处理,因此批实时机器学习架构中往往也会有一个分布式队列,对时间窗口内的数据进行缓冲和加工。在数据流向增加的时候,可以通过加大分布式队列的容量,提高分布式队列的处理能力;也可以通过增加处理单元的方法来提高处理能力,以保证低延迟。
1.4 实时是个“万灵丹” 成长会解决一切问题。如果一个企业正在飞速成长,大家步调一致、同心齐力,那么内斗或管理混乱等问题将是难以出现的。而当企业的成长受到了制约,停滞不前的时候,往往就会出现众多非技术性原因造成的悲剧。我们强调机器学习的实时性,就是为了保证应用机器学习的企业能够利用机器学习的资源大踏步向前,而不会被早早地制约,徘徊不前。机器学习就已经够有挑战性的了,为什么还要采用实时机器学习?根据我们的经验,实时机器学习上马应该越早越好,原因具体有以下三点。1.实时架构稳定性可以得到保证Fail fast(快速失败)强调如果有问题,那么应让问题尽早出现,使得问题可以得到尽早修复,这是软件工程里面一个重要的思想。如果系统有问题,就应该让问题尽早暴露,而不是往后拖。实时机器学习架构强调连续运行,设计、实施中的任何问题一般都可以在部署上线后的几个小时内暴露出来,以及时得到更正。非实时架构往往会在每天的某一个固定时刻进行数据处理、建模等工作。如果前一天开发人员部署了问题程序,到了第二天运行的时候才发现,打好补丁就到了第三天,然后验证补丁是否正确又到了第四天……在流程的反复中,宝贵的时间就这样浪费下去了。 代码、架构质量可以得到保证与非实时架构不同,实时架构设计假设数据是无限量连续到来的。这时候系统的设计和开发必须从一开始就设计好全局步骤,而不是走一步算一步,由此可以大大提高架构设计的质量。与此同时,连续交付的要求需要代码能够事先考虑到所有边际情况,这样我们所得到的代码质量也会更高。 3.数据驱动的组织文化可以得到加强由于机器学习具有实时性,因此所有有关业务效果的讨论都可以基于实时数据,而不是凭空根据大佬的主观臆断。与此相对的,没有采用实时机器学习的组织往往只会定期手动进行数据分析,得到真相的速度大大减慢,不利于商业决策的正确执行。另外,非实时架构企业的数据处理往往会经过相关人员之手,数据的原始性和真实性很难得到保证,最终用户拿到数据的时候,数据可能已经失去了使用的价值。
1.3 机器学习领域分类 从方法论的角度来讲,机器学习分为监督式学习、非监督式学习和新兴机器学习课题三大方面。 监督式学习监督式机器学习的主要任务是通过机器学习模型和已有信息,对感兴趣的变量进行预测,或者对相关对象进行分类。监督式机器学习的一些应用场景包括:对网页访问进行分类,通过声音、文字、表情等信息对用户心情进行判断,对天气进行预测等。常用的监督式机器学习方法包括线性模型、最近邻估计、神经网络、决策树等。最近特别火热的深度学习在图像分类等场景的应用也是监督式学习的一种。 非监督式学习非监督式学习的主要任务是对数据进行描述。在非监督式学习的应用场景中,所有变量几乎都处于同等地位,不存在一个需要进行预测和分类的目标。故此非监督式学习主要用于机器学习建模前期对数据的分析和可视化处理,其在生产环境中的应用较少。非监督式学习的主要方法包括聚类分析、隐含因子分析等。 新兴的机器学习课题最近五年,强化学习 (reinforcement learning)领域在深度学习的带领下得到了飞速的发展。强化学习旨在通过对实际事件的观察得到行为优化的结论,例如,AlphaGo 通过强化学习优化下围棋的策略。到目前为止,强化学习暂时还主要停留在学院派研究中,实际应用暂时有限。 本书将着重讲述机器学习方法在实时场景中的应用,我们将会简要介绍主流监督式学习的方法和应用。另外值得一提的是,在 IT 工业界应用中,自然语义处理、推荐系统和搜索引擎由于其专业领域深度和应用的难度,在各种文献中它们往往被列为独立的大方向。本书的第9章和第12章会对自然语言的处理进行简单的介绍。
前 言 本文讲的是工业控制网络安全技术与实践一导读,随着信息技术和网络技术的迅猛发展,国家安全边界已经超越地理空间限制,延伸到信息网络,网络空间成为继陆、海、空、天之后的第五大国家主权空间。作为网络空间安全的重要组成部分,工业控制网络安全涉及国家关键基础设施和经济社会稳定,辐射范围广泛,应当予以充分重视。工业控制系统广泛应用于电力、水利、污水处理、石油化工、冶金、汽车、航空航天等诸多现代工业,其中超过80%涉及国计民生的关键基础设施(如铁路、城市轨道交通、给排水、通信等)。随着工业化与信息化的深度融合、“互联网+”及国务院“中国制造2025”战略的提出,工业控制系统中信息化程度越来越高,通用软硬件和网络设施的广泛使用打破了工业控制系统与信息网络的“隔离”,带来了一系列网络安全风险。其中涉及的不仅仅是信息泄露、信息系统无法使用等“小”问题,而是会对现实世界造成直接的、实质性的影响,如设备故障、环境污染、人员伤亡甚至危害国家安全,其后果是无法预计的。我国政府对工业控制系统的安全性予以高度重视,在国家战略、规范管理、信息共享、技术支撑等方面不断突破,致力于构建完善的工业控制网络安全保障体系。但是,目前国内网络安全研究团队的研究对象多集中在互联网和传统信息系统上,掌握工业控制网络安全知识、了解工业控制网络漏洞分析与安全防御技术的人极少,远不能满足各行业对工业控制网络人才的渴求,不能适应国家的发展战略。本书围绕工业控制系统的安全,对工业控制系统、工业控制网络、工业控制系统整体安全性、SCADA系统安全性、工业控制网络漏洞、工业控制网络协议、工业控制网络安全防御等进行了详细的阐述。最后列举了几个典型工业控制安全案例,旨在帮助读者全面了解工业控制系统安全领域的相关知识,建立防护意识。本书共分为8章,各章主要内容概述如下:第1章介绍了工业控制系统与工业控制网络的概念,描述了国内工业控制行业的现状及工业控制网络安全的趋势,并说明了工业控制系统中常用的术语。第2章介绍了工业控制系统中SCADA与DCS这两个典型系统、控制器、现场设备,着重描述了PLC设备,并介绍了几种典型的工业控制网络。第3章介绍了工业控制网络常见的安全威胁,并针对工业控制系统不同网络层的脆弱性进行了分析。第4章介绍了SCADA系统的组成、安全需求、安全目标及脆弱性,描述了SCADA系统边界防护、异常行为检测、安全通信及密钥管理、风险评估与安全管理,介绍了SCADA系统安全测试平台,最后简要介绍了SCADA系统典型案例及发展趋势。第5章详细介绍了四种常见的工业网络协议,并说明了各协议存在的安全问题,并有针对性地提出安全防护技术。第6章介绍了工业控制网络漏洞的特征、分类、发布平台及态势,描述了针对已知漏洞的检测技术和针对未知漏洞的挖掘技术,分析了上位机、下位机及工业控制网络设备漏洞。第7章全面介绍了工业控制网络安全防护技术,描述了工业控制安全设备的引入和使用方法,详细说明了对已知与未知工业控制安全威胁的处理方法。第8章举例分析了几个典型的工业控制行业现状与安全趋势,并描述了相匹配的安全解决方案。全书逻辑清晰,行文流畅,采用通俗易懂的方式介绍工业控制系统网络安全的相关知识,并提供了适当的图解,具有很强的可读性和实用性。工业控制网络安全是集合了工业控制与网络安全的综合性、应用型方向,要求学习该方向的读者能够将系统知识与专业知识有机结合,在注重提升理论高度的前提下,将理论知识与工程实践紧密联系起来。本书结合工业控制网络安全领域的知识特点,充分考虑其知识体系、教育层次和课程设置,增设各行业典型实际案例,努力做到紧跟前沿技术的发展,使读者能够学以致用。教师可以基于本书增加实验课程,使学生更生动直观、深刻具体地掌握真实有效、切实可行的工业控制网络安全防护手段和思想。无论是初学者还是有一定经验的从业者,都可以从本书中找到所需要的内容。本书可以作为高等院校自动化、计算机科学与技术、信息安全等相关专业的本科生、研究生教学用书,或工业控制系统安全相关人员的培训教材,也可以作为对工业控制网络安全感兴趣的普通读者和相关技术人员的参考资料。希望更多的读者能通过学习本书更清晰、全面地掌握工业控制网络安全知识,为增强工业控制网络安全防护能力打下较为系统和扎实的基础。在编写的过程中,本书除了借鉴多位专家的多年工作和研究内容外,还参考了大量的国内外优秀书籍、论文及网上公布的相关资料,并以参考文献的形式列出,可为读者进一步深入研究提供参考信息。本书从策划到编写,得到了教育部高等学校信息安全专业教学指导委员会秘书长封化民教授的大力支持和指导,他对教材进行了审阅,提出了宝贵的建议,并欣然作序;解放军信息工程大学电子技术学院教授陈性元、北京航空航天大学博士生导师李春升、解放军电子工程学院博士生导师陆余良、国防科技大学电子工程所所长王伟、清华大学网络科学与网络空间研究院副研究员诸葛建伟、北京邮电大学副教授雷敏也审阅了本书,对本书内容提出了大量的意见和建议;机械工业出版社华章公司的各位编辑在本书的出版过程中给予了大力支持与帮助。在此深表感谢。工业控制网络安全是自动化与信息安全结合演变的新兴领域,本书作为该领域的首本教材,在编写时虽力求全面、系统,但随着工业化和信息化大规模发展,工业控制系统网络安全技术日新月异,加之作者能力所限,书中难免有一些错误和不当之处,恳请读者提出宝贵意见,以期再版修订。 作 者2017年5月 目 录 第1章 绪 论1.1.1 什么是工业控制系统1.1.2 什么是工业控制网络1.1.3 工业控制网络与传统IT信息网络1.2 国内工业控制行业现状1.3 国内工业控制网络安全趋势分析1.4 工业控制系统常用术语1.5 本章小结第2章 工业控制系统基础2.1 数据采集与监视控制系统2.1.1 什么是SCADA系统 2.1.2 SCADA 后台子系统的主要功能2.1.3 SCADA 系统未来的技术发展2.2 分布式控制系统2.2.2 DCS的组成2.2.3 DCS的特点2.3 工业控制系统中的常用控制器2.3.1 可编程逻辑控制器2.3.2 可编程自动化控制器2.3.3 远程终端单元2.4.1 智能电子设备2.4.2 人机界面2.5 PLC设备的技术原理2.5.2 PLC的基本组成与工作原理2.5.3 PLC的基本指令系统2.5.4 PLC的通信技术2.5.5 PLC的接口技术2.6 典型工业领域的工业控制网络2.6.2 石化行业的工业控制网络2.6.3 电力行业的工业控制网络2.6.4 市政交通行业的工业控制网络2.7 本章小结第3章 工业控制网络安全威胁3.1.1 现场总线控制网络3.1.2 过程控制与监控网络3.1.3 企业办公网络3.2 工业控制网络常见的安全威胁3.2.1 高级持续性威胁攻击3.2.2 工业控制网络病毒3.2.3 工业控制网络协议安全漏洞3.3 工业控制系统脆弱性分析3.3.1 现场总线控制网络脆弱性分析3.3.2 过程控制与监控网络脆弱性分析3.3.3 企业办公网络脆弱性分析3.4 本章小结 原文标题:工业控制网络安全技术与实践一导读
3.4 本章小结 本文讲的是工业控制网络安全技术与实践一3.4 本章小结,本章首先介绍了工业控制网络的基本结构,从现场总线控制网络、过程控制与监控网络和企业办公网络三个层面描述了各部分网络的控制组件、网络设备、基本功能和运作机制。其次,系统地分析了工业控制系统面临的安全问题,分别从APT攻击、工业控制网络病毒和工业控制网络协议安全漏洞等方面介绍了工控系统常见的安全威胁。最后,结合当前工业控制系统常见的三种网络(现场总线控制网络、过程控制与监控网络和企业办公网络),从不同层面说明了工控系统的脆弱性,分析了现场总线控制网络、过程控制与监控网络和企业办公网络存在的安全弱点。 3.5 本章习题 1.在工控网络安全事故调查取证方面,常用的技术手段是什么?2.工控系统遭受攻击可以造成哪些方面的不良影响?3.工控系统的最主要攻击手段是什么?4.根据对工控系统典型攻击事件的了解,谈谈你对工控系统安全事件的看法。 原文标题:工业控制网络安全技术与实践一3.4 本章小结
3.3.3 企业办公网络脆弱性分析 本文讲的是工业控制网络安全技术与实践一3.3.3 企业办公网络脆弱性分析,随着国家工业化和信息化两化融合深入推进,传统信息技术广泛应用到工业生产的各个环节,信息化成为工业企业经营管理的常规手段。信息化进程和工业化进程不再相互独立进行,不再是单方的带动和促进关系,而是两者在技术、产品、管理等各个层面相互交融,彼此不可分割,传统IT在工业控制系统的广泛应用也势必会给工业控制系统引入更多的安全风险。“震网”、“Duqu”、“火焰”等针对工控系统的网络病毒渐渐为人们所知,工业控制网络安全越来越受到各方的关注。对工业用户而言,其根深蒂固的“物理隔离即绝对安全”的理念正在被慢慢颠覆。在石油、化工企业中,随着ERP、CRM 以及OA等传统信息系统的广泛使用,工业控制网络和企业管理网络的联系越来越紧密,企业在提高公司运营效率、降低企业维护成本的同时,也随之带来了更多的安全问题。 1.信息资产自身漏洞的脆弱性 随着TCP/IP协议、OPC协议、PC和Windows操作系统等通用技术和通用软硬件产品被广泛地用于石油、化工等工业控制系统,随之而来的通信协议漏洞、设备漏洞以及应用软件漏洞问题日益突出。2010年发生的伊朗核电站“震网病毒”事件,就是同时利用了工控系统、Windows系统的多个漏洞。事件发生之后,大量高风险未公开漏洞通过地下经济出卖或被某些国家/组织高价收购,并被利用来开发0day攻击或高级持久威胁(Advanced Persistent Threat,APT)的攻击技术,为未来可能的网络对抗做准备。因此,现有高风险漏洞以及0day漏洞的新型攻击已经成为网络空间安全防护的新挑战,而涉及国计民生的石油、石化、电力、交通、市政等行业的国家关键基础设施及其工业控制系统,在工业化和信息化日益融合的今天,将极大可能成为未来网络战的重要攻击目标。 2.网络互连给系统带来的脆弱性 根据风险敏感程度和企业应用场景的不同,企业办公网络可能存在与外部互联网通信的边界;而企业办公网络信息系统的通信需求主要来自用户请求,多用户、多种应用带来了大量不规律的流量,也导致了不同应用环境下通信流量的难以预测。一旦存在互联网通信,就可能存在来自互联网的安全威胁,例如,来自互联网的网络攻击、僵尸木马、病毒、拒绝服务攻击、未授权的非法访问等。这时通常就需要具有较完备的安全边界防护措施,如防火墙、严格的身份认证及准入控制机制等。 3.内部管理机制缺失带来的脆弱性 工业控制系统的监控及采集数据也需要被企业内部的系统或人员访问或进行数据处理。这样在企业办公网络与工业控制系统的监控网络,甚至现场网络(总线层)之间就存在信息访问路径。由于工业控制系统通信协议的局限,在这些访问过程中多数情况并未能实现基本访问控制及认证机制,也同时存在设备随意接入、非授权访问、越权访问等多种风险。 4.缺乏安全意识带来的脆弱性 由于工业控制系统不像互联网或与传统企业IT网络那样备受黑客的关注,在2010年“震网”事件发生之前很少有黑客攻击工业控制网络的事件发生。工业控制系统在设计时也多考虑系统的可用性,普遍对安全性问题的考虑不足,更不用提制定完善的工业控制系统安全政策、管理制度以及对人员的安全意识培养了。“和平日久”造成人员的安全意识淡薄。而随着工控系统在国计民生中的重要性日益提高,以及IT通用协议和系统在工控系统的逐渐应用,人员安全意识薄弱将是造成工业控制系统安全风险的一个重要因素,特别是社会工程学相关的定向钓鱼攻击可能使重要岗位人员沦为外部威胁入侵的跳板。 原文标题:工业控制网络安全技术与实践一3.3.3 企业办公网络脆弱性分析
3.3.2 过程控制与监控网络脆弱性分析 本文讲的是工业控制网络安全技术与实践一3.3.2 过程控制与监控网络脆弱性分析,过程控制与监控网络中主要部署SCADA 服务器、历史数据库、实时数据库以及人机界面等关键工业控制系统组件。在这个网络中,系统操作人员可以通过HMI 界面、SCADA 系统及其他远程控制设备,对现场控制系统网络中的远程终端单元(RTU)、现场总线的控制和采集设备(PLC 或者RTU)的运行状态进行监控、评估、分析;并依据运行状况对PLC 或RTU 进行调整或控制。监控网络负责工业控制系统的管控,其重要性不言而喻。对现场设备的远程无线控制、监控网络设备维护工作及需要合作伙伴协同等现实需求,在监控网络中就需要考虑相应的安全威胁:1)不安全的移动维护设备(比如笔记本电脑、移动U 盘等)的未授权接入,而造成木马、病毒等恶意代码在网络中的传播。2)监控网络与RTU/PLC之间不安全的无线通信,可能被利用以攻击工业控制系统。《工业控制系统的安全性研究报告》给出了一个典型的利用无线通信进行入侵攻击的攻击场景。因合作的需要,工业控制网络有可能存在外联的第三方合作网络并且在网络之间存在重要的数据信息交换。虽然这些网络之间存在一定的隔离及访问控制策略,但日新月异的新型攻击技术也可能造成这些防护措施的失效,因此来自合作网络的安全威胁也是不容忽视的。3)以SCADA系统为例,该系统的现场设备层和过程控制层主要使用现场总线协议和工业以太网协议。现场总线协议在设计时大多没有考虑安全因素,缺少认证、授权和加密机制,数据与控制系统以明文方式传递,工业以太网协议也只是对控制协议进行简单封装,如CIP封装为EtherNet/IP,Modbus封装为Modbus/TCP,一些协议的设计给攻击者提供了收集SCADA系统信息、发动拒绝服务攻击的条件,而在协议实现中,常常又在处理有效/无效的格式化消息等方面存在缺陷。 原文标题:工业控制网络安全技术与实践一3.3.2 过程控制与监控网络脆弱性分析
前 言本文讲的是网络空间欺骗:构筑欺骗防御的科学基石一导读,本书旨在为构建网络空间欺骗防御的科学基础迈出探索性的一步。在本书中,我们提出了一个最新的基础研究结果,收集了来自世界各地的顶尖研究团队关于网络空间欺骗防御的最新研究进展。本书对网络空间抵赖与欺骗防御工作、网络空间欺骗工具和技术、攻击者身份识别与检测、网络空间欺骗操作量化、无线网络欺骗策略、蜜罐部署、人为因素、匿名和溯源问题进行了严谨的分析。此外,我们不仅对网络空间欺骗的不同方面进行抽样检测,同时更突出了可用于研究此类问题的科学技术。我们真诚地希望,本书可以激发网络安全的研究人员,基于我们现有的知识,进一步构建网络空间欺骗防御的科学基础,从而最终带来一个更加安全、可靠的网络空间环境。目 录第1章 网络空间抵赖与欺骗原理1.1 主动网络空间防御中网络空间抵赖与欺骗的视图1.2 在恶意敌手模型中集成网络空间抵赖与欺骗的关键因素1.3 恶意策略、技术和常识1.4 网络空间抵赖与欺骗的类型和策略1.5 网络空间欺骗链1.6 网络空间欺骗链与网络空间杀伤链1.6.1 目的:合法与被控制的凭证1.6.2 信息收集:合法凭证的策略和技术说明1.6.3 设计封面故事:抵赖与欺骗方法矩阵1.6.4 策划:合法凭证的检测与缓解1.7 总结2.1 简介2.2 发展历史简述2.2.1 基于Honey的工具2.2.2 独立使用欺骗的局限性2.3 欺骗型安全技术2.3.1 在计算机防御中使用欺骗的优势2.3.2 网络空间杀伤链的欺骗2.3.3 欺骗和隐藏2.3.4 进攻性的欺骗2.4 集成化网络空间欺骗与计算机防御框架2.4.1 偏见的角色2.4.2 策划欺骗2.4.3 实施和集成欺骗2.4.4 监控与评估欺骗的使用3.1 简介3.2 防御模型3.3 恶意软件模型3.3.1 恶意样本收集3.3.2 粗略分析与筛选3.3.3 现场分析3.3.4 识别和量化恶意软件的指标3.4 隐蔽微积分3.5 总结 原文标题:网络空间欺骗:构筑欺骗防御的科学基石一导读
3.3.1 现场总线控制网络脆弱性分析 本文讲的是工业控制网络安全技术与实践一3.3.1 现场总线控制网络脆弱性分析,现场总线控制网络利用总线技术(如Profibus等)将传感器/计数器等设备与PLC 以及其他控制器相连,PLC 或者RTU 可以自行处理一些简单的逻辑程序,不需要主系统的介入即能完成现场的大部分控制功能和数据采集功能,如控制流量和温度或者读取传感器数据,使得信息处理工作实现了现场化。现场总线控制网络由于通常处于作业现场,因此环境复杂,部分控制系统网络采用各种接入技术作为现有网络的延伸,如无线和微波,这也将引入一定的安全风险。同时PLC等现场设备在现场维护时,也可能因不安全的串口连接(如缺乏连接认证)或缺乏有效的配置核查,而造成PLC设备运行参数被篡改,从而对整个工业控制系统的运行造成危害(如伊朗核电站离心机转速参数被篡改造成的危害)。该网络包含了大量的工控设备,设备存在大量工控安全漏洞如PLC漏洞、DCS系统漏洞等,同时在该网络内传输的工业控制系统数据没有进行加密,因此存在被篡改和泄露的威胁;也缺少工控网络安全审计与检测及入侵防御的措施,容易对该网络内的设备和系统数据造成破坏。由于现场总线控制网络实时性要求及工业控制系统通信协议私有性的局限,在这些访问过程中多数情况并未能实现基本访问控制及认证机制,即使在企业办公网络与监控网络之间存在物理隔离设备(如防火墙、网闸等),仍然存在因策略配置不当而被穿透的问题。 原文标题:工业控制网络安全技术与实践一3.3.1 现场总线控制网络脆弱性分析
3.3 工业控制系统脆弱性分析 本文讲的是工业控制网络安全技术与实践一3.3 工业控制系统脆弱性分析,工业控制系统(ICS)与传统信息系统(IT)存在着巨大的区别,最鲜明的一个特点即 ICS 与 IT 对信息安全“CIA”三性的关注度不同。IT 系统更看重信息的机密性,而 ICS 为了保证工业过程的可靠、稳定,对可用性的要求达到了极高的程度。在 ICS 中,系统的可用性直接影响的是企业的生产,生产线的停机、简单的误操作都有可能导致不可估量的经济利益损失,在特定的环境下,甚至可能危害人员生命,造成环境污染。因此,工控系统的脆弱性是与生俱来的,每一年新公开的工控系统漏洞数量居高不下。在“两化融合”、“工业 4.0”的背景下,多种技术的融合会给工控安全带来“阵痛”,工控安全事件的频发也会给人们敲响警钟。 原文标题:工业控制网络安全技术与实践一3.3 工业控制系统脆弱性分析
3.2.3 工业控制网络协议安全漏洞 本文讲的是工业控制网络安全技术与实践一3.2.3 工业控制网络协议安全漏洞,工控网络有很多特定的协议,但是特定的行业通常仅使用其中的一种或几种特定的协议,很多企业也有专用的协议,如表3-3和表3-4所示。协议最初设计的时候,为了兼顾工控系统通信的实时性,很多都忽略了协议通信的机密性、可认证性等在当时看起来不必要的附加功能。但是随着工控系统与信息网络的联系日益密切,工控协议的漏洞容易被有攻击意图的黑客所利用。传统观念认为工业内网与互联网物理隔离,所以几乎所有的现场总线协议都是明码通信,虽然明码通信方便设备之间交换数据,快速便捷,但是带来的安全问题也很多。 原文标题:工业控制网络安全技术与实践一3.2.3 工业控制网络协议安全漏洞
前 言 为什么要写这本书本文讲的是C语言程序设计进阶教程一导读,市面上有成百上千种关于编程的书籍,其中有很多都是关于C语言编程的,那么为什么我还要写这本书呢?为什么建议你花时间读它呢?这本书跟其他书有什么不同呢?跟很多作者一样,我写这本书是因为我觉得有必要,觉得这本书中的方法比其他书中的更好。我将现在已有的关于编程的书分为两类:入门和进阶。入门类书是给初学者写的,一般都假设读者没有编程基础,所以主要是介绍基本的概念。通常以“Hello World!”程序开始,也就是将“Hello World!”输出到电脑屏幕的程序。这种类型的书主要是一步步地介绍语言特点,包括关键词、数据类型、控制结构、字符串、文件操作等,而这些书一般都有一个特点:程序很短,一般是1~2页。这很奏效,因为短程序有助于解释编程语言的新概念。如果把学编程语言比作学自然语言,如英语、汉语、法语、韩语等,这些书就相当于教导如何造句和撰写短段落。第二类书是写给有程序开发经验的读者的。这些书主要介绍解决现实中的问题的程序,比如关于电脑游戏或者图像。而这类书的例子一般很长,有些甚至几千行代码,因此不会全部印在书本上。书中只会解释程序的其中一部分,而源程序一般保存在CD或者某个网址上。这类书一般不会再介绍如何编程,而是大多专注于解决特定问题的算法研究,有时包括算法性能的详细信息。读者不可能再找到类似于“Hello World!”这样的例子。再比作自然语言的例子,这类书就是在教导如何撰写可能超过20页的短篇小说。问题是,从写一个段落到写一篇小说,这种跨越太难了。一本针对中级编程能力的学生的书市面上很少有针对中级编程能力学生的书籍。这些学生往往已经掌握了编程的基本知识,在看到if或者while时不会茫然,知道如何创建函数和调用函数,有能力编写几十上百行的短代码,却不知道如何处理上千行的程序。他们经常会犯错误,因为大多数入门级的书籍只教导如何编写正确的程序,却不会教导避免常见的错误。他们往往对大多数的概念和那些可以帮助提高编程能力的工具都不太熟悉,他们需要这样一个台阶:可以帮助他们从有能力编写短代码到有能力编写解决现实问题的程序。现在入门和进阶的空档已经被数据结构和算法的书籍填充了一部分,这类图书一般提供实现数据结构或算法的完整例子。然而这并不是最合适的解决方法,这类图书致力于介绍数据结构和算法,却罕有提供帮助读者编写正确代码的信息。事实上,它们大多只提供程序,而很少解释。它们往往不解释编程概念,比如函数需要一个指针作为实参的原因或者深拷贝与浅拷贝之间的差异等。因此,读者只能自学这些编程技巧。为了迎合这个需求,我写下这本针对中级编程能力的学生的书,本书适合作为学习编程的第二本教材。避免出错和调试的重点我们可以看到有很多关于如何编程的书籍,却很少关于开发软件的书籍。开发软件不是简单地输入代码,它需要更多的知识和技能。为了弥补这种不足,最好就是去研究什么是对的、什么是错的。只解释如何编写正确的程序是不够的,还需要解释常见的错误并将它们与正确的程序进行对比。一次疏忽可能使程序运行出乎意料,甚至是某些情况下运行正确而另一些情况下出错。这种类型的错误往往很难发现,更别说更正了。本书将介绍一些常见的错误以教导读者如何避免这些错误。调试过程在大多数书中都不会涉及,罕有书籍会提到“调试器”这个词,以至于有些读者都不知道这类工具的存在。学会如何使用调试器一般不超过30分钟,这可以帮助程序员节省很多时间。关于如何使用调试器和调试策略的书籍则更少了。程序设计和离散数学程序设计和离散数学是计算机科学中的两个重要学科,然而,大多数书籍都将这两个主题分开,所以很少会在编程的书籍中看到数学公式,同样也很难在离散数学中看到代码。在本书中,这两个主题紧密结合,我相信读者可以从中学到更多的知识。为什么本书使用C语言?C语言诞生于20世纪60年代后期和20世纪70年代早期。在C语言发明之后,很多语言也相继出现,这些语言也深受C语言的影响。除了它的历史影响之外,C语言的简单易用也保证了它在几乎所有现代化平台中的重要地位。与许多操作系统一样,Linux是就用C语言编写的,Android基本都是用Java编写的但仍有叫作JNI(Java Native Interface,Java本地接口)的C语言接口。大多数计算机语言都可以与C语言进行通信或通过C语言进行通信,事实上这对一种编程语言而言是有用的,因为大多数操作系统接口都使用C语言。当一个全新的系统被设计出来,C语言通常是第一种(很多情况下是唯一一种)被系统支持的编程语言。对于具有中级编程能力的学生来说,C语言是一个很好的选择,因为学习C语言需要了解很多计算机概念。langpop.com网站对比了编程语言的受欢迎程度,得出C语言是最受欢迎的语言,紧接着是Java。IEEE Spectrum中的一个报告将编程语言进行排行,主要考虑四类软件:移动应用、企业软件、嵌入式系统和网页。其中嵌入式系统中最受欢迎的就是C语言。四种类型都考虑时,前五名编程语言如下所示: Java(100%) C(99.3%) C++(95.5%) Python(93.4%) C#(92.4%)可以发现,基于C的编程语言(C、C++、C#)占据了前五席的三席。而Java是受C++影响的。 为什么需要读这本书?如果你是计算机科学、计算机工程或者电子工程专业的学生,那么就绝对应该读本书。本书包含了很多基本概念,这些概念对于理解计算机中程序的运行方式十分重要。如果你是工程、科学、数学或者技术专业的学生,在学习工作中就很有可能需要用到计算机,而阅读本书将会有很大帮助。即便你不是上述专业的学生,仍然可以在本书中学到很多有用的概念(比如递归)。作者、审稿人及封面设计师作者简介Yung-Hsiang Lu 是美国印第安纳州西拉法叶普渡大学电子与计算机工程学院的副教授。他是美国计算机协会(ACM)的杰出科学家和演说家。2011年8~12月,曾是新加坡国立大学计算机科学系的客座副教授。他在美国加利福尼亚州斯坦福大学电子工程系取得博士学位。审稿人简介Aaron Michaux是美国印第安纳州西拉法叶普渡大学电子与计算机工程学院的一名研究生。他在澳大利亚昆士兰科技大学取得计算机科学学士学位,在加拿大新布伦瑞克圣托马斯大学获得心理学学士学位。Aaron在重新回到学校攻读博士学位之前已经作为专业程序员工作了10年。他的研究方向主要围绕计算机视觉和人类视觉感知。Pranav Marla是美国印第安纳州西拉法叶普渡大学电子与计算机工程学院的一名本科生。他主修的专业是计算机科学。在计算机工程、心理学和哲学上也稍有涉猎。他希望可以专攻机器学习和人工智能。封面设计师简介本书封面是由Kyong Jo Yoon描绘的。他是一名韩国画家,经常在自然场景中加入英雄人物。他是韩国美术协会的一名顾问,他的作品在美国伊利诺伊州芝加哥市的安内森美术馆中展出。软件开发中的规则如果由于软件错误,银行每天将你的钱减少0.1%,你会满意吗?你能接受每个月都少走40分钟的手表吗?这两种情况都是“成功了99.9%”,但却都让人无法接受。计算机现在用在很多应用程序中,有些甚至会影响人类的安全。即使你编写的程序在99.9%的时间里都可以正确地运行,那也有可能会在剩余的0.1%的时间里危害人类的生命。这是绝对让人无法接受的,这是一个失败的程序。因此,99.9%的成功就是失败。如果你住在加州的帕萨迪纳,现在想去纽约,你会走哪条路呢?也许你会去洛杉矶机场,然后坐飞机去纽约。但是纽约在帕萨迪纳的东边,而机场在帕萨迪纳的西边。你为什么不直接开车去东边呢?你为什么要绕路去机场呢?如果你直接开车去东边,而不是去机场排队的话,你就离纽约越来越近了啊。答案很简单:相对于汽车来讲,飞机这一交通工具更适合长途旅行。在程序开发中,有许多用以管理大型软件的开发工具,你需要学习这些工具。没错,学习使用工具会消耗一些时间,但是如果你使用不合适的工具或者不使用工具的话,将浪费更多的时间。花些时间学习使用编程工具将在软件开发和调试时节省大量时间。尽管经过了数十年的努力,现在的计算机依然没有达到智能化的水平。计算机无法猜测你的想法。如果你写的程序让计算机去做一件错事,那么计算机就会跟随指令去做。如果你的程序是错的,那就是你的责任。计算机无法猜测你的想法。在很多例子中,计算机程序中一个微小的错误就可以造成巨大的财产损失,甚至危及人类的生命。缺少一个分号“;”或者用“,”取代“. ”,程序将无法执行。计算机程序不能容忍任何微小的错误。通过测试方案并不能保证程序的正确性。测试只能得出一个程序有错误的结论但不能表明一个程序是正确的。为什么呢?测试方案能覆盖所有可能的情景吗?覆盖全部情景是非常困难的,而且在很多情况下是不可能的。因为测试方案很难检测特殊行为,所以一些问题可能会隐藏在你的程序中。产生正确的输出并不意味着程序是正确的。你会认为一架已经安全起飞并着陆的飞机是安全的吗?如果飞机在漏油,你会在登机前要求航空公司维修飞机吗?如果航空公司回复:“之前没有人受伤,说明这架飞机是安全的。”你会接受航空公司的回应吗?如果司机闯红灯而没出事故的话,是否意味着闯红灯是安全的呢?一个可以产生正确输出的程序就像是一架安全着陆的飞机。可能有很多问题隐藏在安全的表面之下。很多工具可以检测人类隐藏的健康问题,比如X–射线,核磁共振以及超声波扫描。我们也需要工具来检测出隐藏在电脑程序中的问题。即使程序可以产生正确的输出,我们也要对它们进行修改。你必须假设程序会出问题,并开发出检测和改正问题的策略。在写程序时,应该每次只专注于一小部分。在仔细检查并确保没有问题之后再进行下一部分。对于大多数程序,你需要为了测试这些小的模块而编写额外的程序。虽然这些测试代码并不包含在最终的程序中,但是编写测试程序可以节省大量时间。有时,测试代码可能会比程序本身还要多。我自己的经验建议1:3的比例:最终程序中的1行代码,需要3行测试代码。没有什么工具可以取代一个清醒的大脑。工具可以提供一些帮助,但是对概念清晰深刻的理解才是最重要的。如果你想成为一名优秀的程序员,那么你需要完全理解每一个细节。不要指望工具可以替你思考:它们做不到。源代码本书中所有的程序都可以从github.com上获取。请使用下面的命令获取文件:…………………………………………$ git clone ‘https://github.com/yunghsianglu/IntermediateCProgramming.git’$是Linux终端的shell提示符………………………………………… 目录 1.1 编译1.2 重定向输出2.1 值和地址2.2 栈2.3 调用栈2.3.2 函数实参2.3.3 局部变量2.3.4 值地址2.3.5 数组2.3.6 获取地址2.4 可见度2.5 习题2.5.1 绘制调用栈I2.5.2 绘制调用栈II2.6.1 绘制调用栈I2.6.2 绘制调用栈II2.7 在DDD(命令行调试程序)上检测调用栈第3章 预防、检测及消除bug3.1.1 编程前3.1.2 编程中3.1.3 编程后3.2 常见错误3.2.1 未初始化变量3.2.2 错误数组下标3.3 后执行式和交互式调试3.4 生产代码与测试代码分离 原文标题:C语言程序设计进阶教程一导读
3.4 生产代码与测试代码分离本文讲的是C语言程序设计进阶教程一3.4 生产代码与测试代码分离,你应该编写可以检测出自身bug的程序。如果你想要检查一个数组是否被排序好了,不要在屏幕上打印出元素并用自己眼睛观察。应该写一个检查数组是否排序的函数。代码通常不会打印出调试信息。相反,写一些可以帮助你不用依靠眼睛就能调试的代码。你应该在编写程序之前就考虑编写测试代码。这是一个普遍的做法,叫作测试驱动的开发。怎样去编写测试代码呢?很多书都写了关于软件测试方面的内容。本节给你一个建议。考虑下面两个关于测试代码的例子。假设func是你想去测试的函数,test_func是测试func的代码。 这两种方法的不同在哪里呢?第一种方法(位于左侧)在程序的函数内部调用测试代码。在第二种方法中(位于右侧),测试代码位于程序外部调用func。这个差异是很重要的,因为第一种方法把测试代码与程序实际需要的代码(有时叫作“生产代码”)混在了一起。结果就是,想要把测试代码移除对你来说是很困难的。第二种方法把生产代码与测试代码分离开,所以你就能在随后很容易地移除测试代码。应该在测试你的程序时采取第二种方法。 原文标题:C语言程序设计进阶教程一3.4 生产代码与测试代码分离
3.3 后执行式和交互式调试 本文讲的是C语言程序设计进阶教程一3.3 后执行式和交互式调试,要调试一个程序是需要策略的。你需要把程序分为几阶段,在每一阶段的基础上隔离问题。在把不同部分整合起来之前确保程序在每一阶段都是正确的。例如,把一个程序看作3个阶段:①从一个文件中读取一些整数;②把整数排序;③把排好序的整数存到另一个文件中。在整合之前测试每一部分叫作单元测试。对于单元测试,你经常需要写一些额外的代码作为单独部分的“驱动”。例如,要在还未从文件中获得数据的情况下测试排序是否正常工作了,你就需要编写能够产生数据的代码(也许使用一个随机数生成器)。调试可能是交互式或者是后执行式的。如果一个程序会花费几个小时,你就不会想要用交互式来调试这个程序了。相反,你会想要程序打印出调试信息(叫作日志)。这个信息帮助你知道在长时间的执行期间发生了什么。另一种情况是调试一个与其他程序有着即时通信要求的程序。例如,你调试一个程序,此程序与另一个程序通过网络进行通信。如果你对这个程序进行交互式的调试就会让它慢很多,其他程序会认为网络中断从而停止与你的程序进行通信。另一种情形是你的程序与实体世界进行关联(例如控制一个机器人)。实体世界不会等待你的程序,它也不会慢下来。打印日志也会让程序慢下来,因此不要添加过多的日志。在许多其他情况下,你可以让你的程序慢下来,对程序进行交互式的调试——运行程序的一些部分,观察中间的结果,修改程序,再次运行它们,继续这个过程直至你认为程序正确。对于交互式调试,打印调试信息通常是低效费时的。对交互式调试而言打印调试信息存在多个问题:需要插入代码来打印调试信息。这将会耗费相当的精力。多数情况下,调试信息必须在随后被移除,因为此信息不应该出现在最终的代码和它的输出中。如果只有很少的信息,那么就会缺少足够的信息帮助你判断是哪里错了。如果有太多的信息,一些信息可能会是不相关的,应该被忽略。要得到恰好数量的信息,不多也不少,是很困难的。最糟糕的是,问题可能会出现在意想不到的地方,而那里却并未插入调试信息。结果就是需要添加越来越多的调试信息进来。这将会是耗时耗力的。与在交互式调试中使用调试信息相比,gdb(或DDD)在多数情况下是一个更好的工具。前文已经展示过一些gdb指令了。在本书后续部分会介绍更多指令。 原文标题:C语言程序设计进阶教程一3.3 后执行式和交互式调试
3.2.2 错误数组下标 本文讲的是C语言程序设计进阶教程一3.2.2 错误数组下标,对于一个有n个元素的数组,有效的下标是0,1,2, …, n-1,而n是一个无效的下标。当一个程序有着错误的下标时,这个程序就可能在一些情况下工作,而在其他的情况下崩溃。你不会想去编写一个靠运气工作的程序。 3.2.3 错误数据类型 你能骑自行车。你也可以用钢笔写字。你却不能骑钢笔,也不能用自行车写字。在程序中,类型指定功能。你需要理解并正确地使用类型。编程语言的趋势是让数据类型更有限制性,防止程序员犯意外的错误。有时gcc会把可疑的类型问题看作警告。你应该把这些警告看作严重的错误。 原文标题:C语言程序设计进阶教程一3.2.2 错误数组下标
3.2.1 未初始化变量 本文讲的是C语言程序设计进阶教程一3.2.1 未初始化变量,一个常见的错误就是未初始化变量。一些学生认为所有的变量都会自动地初始化为0,这是错误的。未初始化的变量会储存着未占用值。这个值可能是0,但这是不一定的。这个类型的错误是很难通过测试来发现的。有时,这个值可能碰巧是0,这会让你认为程序是正确的。当值不为0时,程序就会有问题。一些学生认为初始化变量会使程序变慢——然而,这些纳秒级的延迟是可以忽略的。让你的程序慢几纳秒总比花几个小时去调试要好。 原文标题:C语言程序设计进阶教程一3.2.1 未初始化变量
3.2 常见错误 本文讲的是C语言程序设计进阶教程一3.2 常见错误,这里是一系列我所见过我的学生编写程序中的常见错误(有时甚至是我自己也会犯的)。很多学生向我保证他们再也不会犯这些错误。事实上是人们还是会犯这些错误,而且比他们想象中的要更经常。这一节只考虑编程错误,而非设计错误。设计上的错误需要一本另外的关于设计软件方面的书来讲述。 原文标题:C语言程序设计进阶教程一3.2 常见错误
1.2 站在巨人的肩膀上 从上世纪 80 年代开始,数字货币技术就一直是研究的热门,前后经历了几代演进,比较典型的成果包括 e-Cash、HashCash、B-money 等。1983 年,David Chaum 最早在论文《Blind Signature for Untraceable Payments》 中提出了 e-Cash,并于 1989 年创建了 Digicash 公司。e-Cash 系统是首个匿名化的数字加密货币(anonymous cryptographic electronic money或electronic cash system),基于 David Chaum 自己发明的盲签名技术,曾被应用于部分银行的小额支付系统中。e-Cash 依赖于一个中心化的中介机构,这导致它最终失败。1997 年,Adam Back 发明了 HashCash,来解决邮件系统中 DoS 攻击问题。HashCash 首次提出用工作量证明(Proof of Work,PoW)机制来获取额度,该机制后来被随后出现的数字货币技术所采用。1998 年,Wei Dai 提出了 B-money,将 PoW 引入数字货币生成过程中。B-money 同时是首个面向去中心化设计的数字货币。从概念上看B-money已经比较完善,但是很遗憾,其未能提出具体的设计实现。上面这些数字货币都或多或少地依赖于一个第三方的信用担保系统。直到比特币的出现,将 PoW 与共识机制联系在一起,首次从实践意义上实现了一套去中心化的数字货币系统。比特币依托的分布式网络无需任何管理机构,自身通过数学和密码学原理来确保所有交易的成功进行,并且,比特币自身的价值通过背后的计算力进行背书。这也促使人们开始思考,在越来越数字化的世界中,应该如何发行货币,以及如何衡量价值。目前,除了像比特币这样完全丢弃已有体系的分布式技术之外,仍然存在中心化代理模式的数字货币机制,包括类似 PayPal 这样的平台,通过跟已有的支付系统合作,代理完成交易。现在还很难讲哪种模式将会成为日后的主流,未来甚至还可能出现更先进的技术。但毫无疑问,这些成果都为后来的数字货币设计提供了极具价值的参考;而站在巨人们肩膀上的比特币,必将在人类货币史上留下难以磨灭的印记。
1.1 从实体货币到数字货币 区块链最初的思想诞生于无数先哲对于用数字货币替代实体货币的探讨和设计中。1.?货币的历史演化众所周知,货币是人类文明发展过程中的一大发明。其最重要的职能包括价值尺度、流通手段、贮藏手段等。很难想象离开了货币,现代社会庞大而复杂的经济和金融体系如何保持运转。也正是因为它如此重要,货币的设计和发行机制是关系到国计民生的大事。历史上,在自然和人为因素的干预下,货币的形态经历了多个阶段的演化,包括实物货币、金属货币、代用货币、信用货币、电子货币、数字货币等。近代以前相当长的一段时间里,货币的形态一直是以实体的形式存在,可统称为“实体货币”。计算机诞生后,为货币的虚拟化提供了可能性。同时,货币自身的价值依托也不断发生演化,从最早的实物价值、发行方信用价值,直到今天的对科学技术和信息系统(包括算法、数学、密码学、软件等)的信任价值。中国最早关于货币的确切记载“夏后以玄币”,出现在恒宽的《盐铁论·错币》。2.?纸币的缺陷理论上,一般等价物都可以作为货币使用。当今世界最常见的货币制度是纸币本位制,因为纸质货币既方便携带、不易仿制,又相对容易辨伪。或许有人会认为信用卡等电子方式相对于纸币等货币形式使用起来更为方便。确实,信用卡在某些场景下会更为便捷,但它依赖背后的集中式支付体系,一旦碰到支付系统故障、断网、缺乏支付终端等情况,信用卡就无法使用。另外,货币形式相对电子支付方式还可以提供更好的匿名性。目前,无论是货币形式,还是信用卡形式,都需要额外的支持机构(例如银行)来完成生产、分发、管理等操作。中心化的结构固然易于管理,但也带来了额外成本和安全风险。诸如伪造、信用卡诈骗、盗刷、转账骗局等安全事件屡见不鲜。很显然,如果能实现一种数字货币,既有货币方便易用的特性,又能消除纸质货币的缺陷,无疑将极大提高社会整体经济活动的运作效率。让我们来对比一下现有的数字货币(以比特币为例)和现实生活中的纸币,两者的优劣见表1-1。 可见,数字货币并非在所有领域都优于已有的货币形式。要比较两者的优劣应该针对具体情况具体分析。不带前提地鼓吹数字货币并不是一种科学和严谨的态度。实际上,仔细观察数字货币的应用情况就会发现,虽然以比特币为代表的数字货币已在众多领域得到应用,但目前还没有任何一种数字货币能完全替代已有货币。另外,虽然当前的数字货币“实验”已经取得了巨大成功,但局限也很明显:其依赖的区块链和分布式账本技术还缺乏大规模场景的考验;系统的性能和安全性还有待提升;资源的消耗还过高等。这些问题的解决,有待金融科技的进一步发展。严格来讲,货币(money)不等于现金或通货(cash/currency),货币的含义范围更广。3.?“去中心化”的技术难关虽然数字货币带来的预期优势可能很美好,但要设计和实现一套能经得住实用考验的数字货币并非易事。现实生活中常用的纸币具备良好的可转移性,可以相对容易地完成价值的交割。但是对于数字货币来说,数字化内容容易被复制,数字货币持有人可以将同一份货币发给多个接收者,这种攻击称为“双重支付攻击”(double-spend)”。也许有人会想到,银行中的货币实际上也是数字化的,因为通过电子账号里面的数字记录了客户的资产。说的没错,有人称这种电子货币模式为“数字货币1.0”,它实际上依赖于一个前提:假定存在一个安全可靠的第三方记账机构负责记账,这个机构负责所有的担保环节,最终完成交易。中心化控制下,数字货币的实现相对容易。但是,很多时候很难找到一个安全可靠的第三方记账机构来充当这个中心管控的角色。例如,发生贸易的两国可能缺乏足够的外汇储备用以支付;汇率的变化等导致双方对合同有不同意见;网络上的匿名双方进行直接买卖而不通过电子商务平台;交易的两个机构彼此互不信任,找不到双方都认可的第三方担保;使用第三方担保系统,但某些时候可能无法连接;第三方的系统可能会出现故障或受到篡改攻击……这个时候,就只有实现去中心化(de-centralized)或多中心化(multi-centralized)的数字货币系统。在“去中心化”的场景下,实现数字货币存在如下几个难题:货币的防伪:谁来负责对货币的真伪进行鉴定;货币的交易:如何确保货币从一方安全转移到另外一方;避免双重支付:如何避免同一份货币支付给多个接收者。可见,在不存在第三方记账机构的情况下,实现一个数字货币系统的挑战着实不小。能否通过技术创新来解决这个难题呢?众多金融专家、科研人员向着这个方向不懈努力了数十年,创造出了许多具有深远影响的巧妙设计。
第1章 区块链思想的诞生新事物往往不是凭空而生,其发展过程也并非一蹴而就。认识一个从未见过的新事物,最重要的是弄清楚它的来龙去脉,知其出身,方能知其所以然。区块链(blockchain)思想最早出现在大名鼎鼎的比特币(Bitcoin)开源项目中。比特币项目在诞生和发展过程中,借鉴了来自数字货币、密码学、博弈论、分布式系统、控制论等多个领域的技术成果,可谓博采众家之长于一身,作为其核心支撑结构的区块链技术更是令人瞩目的创新成果。本章将从数字货币的历史讲起,简要介绍区块链思想诞生的摇篮——比特币项目的诞生和发展过程,并初步剖析区块链技术带来的潜在商业价值。通过阅读本章内容,读者可以了解区块链技术产生的背景、原因,以及在诸多商业应用场景中的潜在价值。
前言 欢迎阅读本书,这是一本由浅入深的书籍,从初学者到高级开发人员,都可以通过本书了解OpenACC的相关知识。本书由世界各地的24位作者共同编著而成,他们在高度并行编程的教学和实践方面分享了自己的专业知识。书中的例子既有时效性又不会过时。每个章节都是自包含的,可用于自学,也可以作为课堂教学的一部分。 这是一本关于并行编程的书,不仅仅介绍OpenACC语法或从文档中收集的信息,更介绍了如何编写实际的、高性能的以及可移植的程序,这些程序可以运行在从CPU到GPU的大量设备上。具体而言,书中演示了使用PGI、Cray和PathScale等供应商提供的编译器编译示例代码,并在Intel x86处理器、Cavium 96核64位ARMv8处理器芯片集和NVIDIA GPU上运行。性能和分析相辅相成,这也是为什么我们将开源代码和PGI分析器都包含在内。自从2012年6月11日我第一次在Dr. Dobbs网站上看到“Easy GPU Parallelism with OpenACC”以来,OpenACC标准逐步升级,编译器技术逐渐成熟,发展速度令人惊讶。过去几年的突飞猛进意味着我们现在可以在大多数HPC站点上使用OpenACC,甚至免费提供的GNU编译器工具集也支持OpenACC。我们选择在中级和高级编程章节中提供实际应用示例,以方便读者实践,这是因为程序员需要解决实际问题,而不是解决课堂问题。同时,OpenACC还是一个很新的并行编程标准,书中的几个介绍性章节非常值得一读! 目录 第1章 从串行编程到并行编程1.1 简单的数据并行循环1.1.1 OpenACC内核构件与并行构件对比1.1.2 OpenACC并行的多种形式1.1.3 accFill_ex2运行时结果1.2 简单的任务并行示例1.3 Amdahl定律及其扩展1.3.1 大O表示法和数据传输1.3.2 accTask.cpp代码的扩展性1.4 并行执行和竞争条件1.5 无锁编程1.6 控制并行资源1.7 让生活更简单1.8 参考文献 第2章 性能导向开发2.1 测试代码:共轭梯度法2.1.1 代码编译2.1.2 初始测试2.2 描述并行度2.2.1 加速waxpby2.2.2 加速dot2.2.3 加速matvec2.3 描述数据移动2.4 优化循环2.4.1 缩短向量长度2.4.2 增加并行度2.5 在多核系统中并行运行2.6 小结 第3章 使用Score-P和Vampir分析混合应用性能3.1 性能分析技术和术语3.2 逐步性能提升3.3 激光驱动电子束的粒子单元模拟3.4 通过代码插装准备性能测量3.5 在应用程序执行期间记录性能信息3.6 第一个并行PIConGPU实现3.7 释放主机进程3.8 优化GPU内核3.9 增加GPU任务并行3.10 使用Score-P和Vampir记录OpenACC运行时事件3.11 小结3.12 参考文献
3.12 参考文献
3.11 小结 虽然PIConGPU是一个具体示例,但确定性能瓶颈是真实的,并且所呈现的解决方案也可以应用于其他应用。使用CUDA(PIConGPU实现)或OpenACC对加速器编程,两者没有什么区别。涉及基础MPI活动的改进可用于这两种范例。本章学习内容:性能分析是每个程序(并行程序,特别是混合程序)开发的一个组成部分,尽可能高效地利用可用资源。基于采样的分析运行时开销非常低,可以提供程序执行中潜在的热点。基于事件的追踪提供程序执行期间所有并行活动的信息。为了不使I/O子系统过载,应该仔细选择日志级别。通过追踪文件的交互式导航和应用程序各种阶段间歇性分析的可能性,使应用开发人员可以更好地了解应用程序在任何时间点正在做什么。MPI和加速器的异步活动是高性能的关键。
3.10 使用Score-P和Vampir记录OpenACC运行时事件 编译器和运行时在实现OpenACC指令时有一定的自由度。因此,检查编译器和运行时对OpenACC指令转换和最终程序执行非常重要。例如,kernels指令触发设备初始化、设备内存分配和没有明确指定相应操作的数据传输。OpenACC 2.5引入的分析接口定义了一组事件,这些事件揭示了OpenACC指令的实现和执行细节。这使得Score-P之类的工具能够测量OpenACC区域的持续时间、在主机上的等待时间和任务提交开销,以及跟踪加速器上的内存分配。例如GPU内核开始和结束时间、CPU和GPU数据传输等更多的GPU事件可以使用CUDA的CUPTI接口或OpenCL库包装(Dietrich & Tschüter, 2015)。OpenACC事件将低级别加速器事件与应用程序的源代码相关联。OpenACC事件被标记为隐式或显式的,并且根据它们的类型,还可以提供有关数据传输的变量名或内核启动操作的内核名称的信息。图3-12展示了Vampir可视化使用MPI、OpenMP和OpenACC的应用程序的执行间隔。在所选间隔中,两个MPI进程执行相同的程序区域,每个运行两个OpenMP线程和带有两个CUDA流的一个GPU。加速器活动是异步的,但是主机大部分时间在等待加速器活动的完成。例如,右边的调用树显示更新构件触发加速器向CPU的数据拷贝和等待操作。OpenACC和OpenMP区域使用文件名和行号注释以便于源代码相对应。
3.9 增加GPU任务并行 在图3-10中,放大主机-设备的追踪数据,可以看出在一些内核启动和开始执行内核间有时间差。此外,因为同步主机与GPU间的数据拷贝,所以GPU依旧有时空闲。使用CUDA流引入异步GPU活动,来确保PIConGPU可以向GPU发送更多的任务,让GPU找出最好的处理方式。图3-11显示了使用CUDA流的结果。现在每个主机线程都使用CUDA流(每个GPU有5个流),一个流负责主机与GPU间的数据拷贝,剩下的流负责向GPU提交并发任务。为了在GPU上实现极高水平的并发性,PIConGPU实现了一个内部事件系统,可以自动触发活动和映射数据依赖关系。这个事件系统转换为CUDA事件,所以即使输入数据仍在传输也可以启动内核。因此,PIConGPU可以扩展到非常多的GPU上执行,并且仍然保持非常高的GPU利用率。PIConGPU及其改进版本经常在OLCF Titan上整个系统运行,并且在2013年入围戈登贝尔奖(Bussmann et al., 2013)。
3.8 优化GPU内核 现在GPU大部分时刻都是忙碌的,那么是否可以减少GPU计算时间?如图3-9所示,使用函数摘要只显示CUDA函数信息。可以看出,主要耗时的内核是“moveParticles”,第二耗时内核是“cptCurrent”。两个内核的共同部分是都需要遍历粒子列表,先积累对粒子的总电流影响(cptCurrent),然后更新粒子的位置(moveParticles)。可见,原来使用的链表数据结构,即一个C结构存放粒子信息(存放的位置、速度和电荷),这种数据结构适合CPU实现,但并不适合邻近线程需要合并内存访问的GPU。把粒子数据结构变成256个浮点数组的结构链表后,如图3-10所示,性能改善很显著。这也归功于把MPI通信从同步改成异步。
3.7 释放主机进程 下一个PIConGPU改进版本解决了已知问题,并引入了一个额外的Pthread来处理MPI通信活动(线程1-4:2)。如图3-9所示,这释放了主机进程,使其在GPU所需数据可用后立即向GPU发射任务,同时尽快地与周边的进程通信交换数据。由于Infiniband结构减少了消息延时,因此总体GPU使用率有所提高。
3.6 第一个并行PIConGPU实现 下一步,用Vampir打开追踪文件trace.otf2,如图3-8所示。追踪缩略图(右上角)显示只选择了整个程序执行的0.2s,并且重复模式显示了模拟的大概2.5个迭代步骤。中间用颜色编码的活动是主时间轴,它显示了MPI、主机进程(进程1~4)和相应的CUDA上下文(线程1/1-4)。右下角的图例显示了颜色的含义。进程之间的黑线代表MPI消息。进程和线程之间的黑线代表CUDA内存拷贝。可以看出,MPI活动占据了大部分的程序执行时间,而CUDA活动所占时间很少。 通过图3-8左上方的工具栏图标选择Vampir要显示的性能数据。有两组显示方式,时间轴显示和统计显示。“时间轴显示”展示沿着水平方向随着时间演化的活动,本例中的主时间轴显示了颜色编码的所有事件流活动(可以是进程、线程或CUDA流)。默认情况下,颜色代码基于图3-8右下方的功能图例中所示的功能组。对一个事件流,进程时间轴显示调用上下文的时间。计数器时间轴显示与事件流相关联的性能计数器的值。例如,这个值可以是PAPI计数器,也可以是诸如内存分配之类的派生计数器,或者使用Vampir度量编译器创建的任何东西。性能监视器显示所有事件流的时间上下文计数器。第二种组显示包含表示各种分析数据的统计显示。函数摘要实现函数或函数组之间的运行时间或调用次数的分布。信息摘要提供所有数据传输的统计(主机与设备或MPI进程间数据传输)。其他是进程摘要,例如信息度量、I/O摘要和调用关系。Vampir特性详细说明可以在手册里找到。Vampir区别于其他标准分析工具的最重要特性,是可以在程序执行时间轴上以任意时间间隔放大。如图3-8右上方缩略视图所示的轨迹摘要(所选间隔由两条黑色条表示)。包含统计信息的所有显示一直会被更新到当前显示的时间间隔。因此,Vampir可以对应程序的任何阶段和极高时间分辨率进行应用程序分析。通过右键单击显示来配置Vampir如何显示信息。左键单击显示中的任何东西都会出现上下文视图,将会显示所选项目的详细信息(包含开始/结束时间、持续时间、函数名称、文件名、行号、消息大小等)。图3-8所示的追踪文件记录了第一次尝试使用MPI并行化单GPU PIConGPU代码以增加模拟面积(弱扩展)运行的结果。追踪显示了首次并行尝试的常见问题:顺序任务执行。这种情况下,可以看到MPI活动和CUDA存储器拷贝占据主机执行时间,而GPU使用率相当低。MPI活动占据很多时间的原因是用了很长时间传递相当小的消息(可以使用消息摘要来分析)。这也说明在GPU上PIConGPU计算时间很快,不能隐藏MPI传输时间。为了减少传输时间,唯一的方案就是传输延时。这种情况下,用Inifinband网络替换集群中的1GB以太网可提供了更快的MPI通信。另一个问题是相当长的时间用于同步GPU与主机间的数据拷贝(在设备上启动一组内核之前主机进程右边的条)。对于这种情况,计算和数据传输更好地重叠交织将增加总吞吐量。GPU利用率低本身不是问题,而是主机影响不能为GPU提供足够的计算量,因此GPU大部分时间在等待数据。