Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

如何克服面对Bug时的焦虑

$
0
0

调试是一门科学,任何不懂原理就进行的操作都是耍流氓

1947年,一只小飞蛾飞进了继电器,导致设备发生故障,于是,现场一位计算机科学家Grace Hopper 把导致程序发生故障的问题称为Bug,把消灭臭虫的行为,称为Debug(在一个单词前面加上De,通常表示反面的意思,如compress和decompress),后来这个叫法渐渐成了计算机术语。

这里写图片描述

今天的我们,如果遵循TDD原则进行开发,那么我们的开发的过程,其实就是先写不能通过的测试用例,制造Bug,然后再写生产代码,然所有测试用例都可以通过,从而消灭所有已知的Bug。由此看来,说Debug是软件开发非常重要的一个过程,一点都不为过。

自学会编程以来,消灭的臭虫不计其数,有的只需几秒钟即可将其消灭,有的则持续数日,而且似乎每个十分难缠的Bug,都会在定位了很久很久之后,在自己某个灵(ren)光(pin)一(bao)现(zha)的时刻,突然就work了!以至于现在遇到Bug,都会告诉自己,别慌,解决它只是时间问题。

直到最近偶然看到了这本书——《调试九法》,这是我看到的第一本讲调试方法论的书,回想自己之前在解决Bug时的经常遇到的手足无措,像无头苍蝇一般四处乱撞,最后瞎猫碰上死耗子般地解决了Bug,我毫不犹豫地买下这本书。

是时候需要一套系统的调试理论了

希望本文介绍的这套调试学的知识,可以帮助你在今后遇到Bug时,思路更加清晰,心情更加淡定,从容不迫地去解决问题。

Bug的种类

Bug,按照其产生的原因,可以分为两种:

  • 由于设计错误导致的Bug,比如由于桥梁建筑师设计时的失误导致的桥梁崩塌,又比如软件开发人员未进行输入校验导致的Bug,我们通常管解决这种Bug的过程叫做调试
  • 在设计没有问题的情况下,由外部环境引起的Bug,比如设备老化导致的问题,又比如内存不够导致的Bug,我们通常把解决这种Bug的过程叫做故障维修

我们这里要介绍的调试方法,既适用于调试,也适用于故障检修,这些技术不关心问题如何产生,而是告诉你如何找到问题发生的具体原因。

下面我将分享我读完这本书之后,形成的一套调试方法论,如果你已经迫不及待地想去读这本书,不妨把下面这段内容作为书的导读。

调试法则总览

这套方法论可以总结成一句话:
调试是一门科学,任何不懂原理就进行的操作都是耍流氓。

这句话可以分为两个步骤进行实践:

准备工作:

  • 如果你要调试这个系统,首先你必须先理解它。
  • 在开始调试之前,检查一下“插头”,别因为一些简单的问题而瞎忙活半天。
  • 确定“插头”没有问题之后,你需要重现Bug

寻找原因并解决Bug

  • 不要认为你猜测的原因是对的,要去观察,验证你的猜测。
  • 采用分而治之的方法,定位多模块的系统问题和修复由多个子问题引发的问题。
  • 一次只修改一个地方
  • 做好修改记录
  • 如果正向调试走不通,不妨试一下反向调试法
  • 最后一招:求助他人

下面对这些法则逐一进行详细地介绍。

理解系统

理解系统是定位Bug的前提条件。

我之前遇到程序抛出异常时,经常就把异常信息贴到网上搜,然后把网上的解决方案执行一遍,有时work了,就大吉大利,可大多数时候,是不work的,原因很简单,也许对方的JDK版本跟你的不一样,也许你们俩只是报错信息相同,但是抛异常的原因不同,更大的可能,对方的解决方案本来就行不通。
这就能理解为什么理解系统是定位Bug的前提了,如果你在定位一个JDK异常,那么至少你要掌握Java SE吧,如果你能掌握JVM的垃圾回收原理、类加载机制,自然更好;而如果你在定位一个支付系统为什么没有把账打到客户的账户,那么你得了解支付的流程吧。

总之,在开始调试之前,我们得弄清楚,调试是一门科学,而不是一门概率学,你需要理解整个系统,才能够进行调试。有以下方法可以帮助你理解这个系统:

  • 阅读手册:阅读需求设计文档、产品文档、使用手册等
  • 仔细阅读手册的每一个细节:说不定解决Bug的方案就在某个段落里
  • 掌握基础知识:最后你总是要看源码的,至少你要掌握相应的编程语言把
  • 了解工作流程:从整体的角度来观察,而不是做井底之蛙

可以说,理解系统的最终目的就是为了了解工作流程,了解了工作流程,你才能够从整体的角度来观察,不然就像井底之蛙,以为问题一定出在自己这个模块,而其实问题是在上游的某个模块里。这一点,和《程序员的思维修炼》中提到的,“专家从整体进行思考”的观点不谋而合。

检查插头

这里当然不是让你去问人家插头插了没,插头在这里是泛指一切让产品正常运行的基本要求。这些基本要求通常我们都认为理所当然是正常的,可事实有时并非如此。
比如你的一个系统,需要在配置界面配置白名单,不然上游的请求就会被拒绝,那么当出现问题时,你应该首先去检查一下这个白名单配置了没,因为对方有可能是个新手。
甚至当出现一些不可理喻的错误时,你要去软件的运行目录下,比如Tomcat的webapps目录,看看软件包是不是完整。
当你替换新代码上去后,发现Bug依然存在时,不妨上去看看正在运行着的,是不是还是旧代码。

书中把这条规则放到了倒数第三条,我这里把它放到第二条,原因很简单,通常我们在发现Bug或者别人跟自己说这里有Bug时,心里都会慌,都会紧张,所以不妨先检查一下插头,缓解一下自己紧张的心情,同时也强迫你从整体的视角进行观察,不会局限在一个小模块里。

重现失败

这几乎是一个下意识的动作,就算你之前没读过这本书,在遇到Bug时,你也会去尝试重现它,原因很简单:

  • 重现失败让你可以观察失败发生时的上下文信息,进而找到失败的原因
  • 重现失败让你可以判断是否已经修复了问题

有些问题很好重现,而有些呢,却是要在特定的输入的情况下才会出现的。
我们犯的绝大多数错误是在重现的方式上,作者对重现提出了两条原则:

  • 模拟失败发生的条件,但是不要模拟失败的机理,因为你认为的导致失败的机理很可能是错误的。举个例子,你认为是高并发导致的bug,于是你模拟了高并发的环境,问题重现了,然后你就说是高并发导致的,其实呢,只不过是高并发提高了问题发生的几率。
  • 只影响错误发生的频率,不影响错误发生的方式。其实高并发的环境可以用来提高错误发生的频率,只不过你要在问题重现时,要找到相应的日志信息,然后定位出问题发生的原因,而不是直接认为就是并发导致的。

不要猜,要观察

现在我们可以重现Bug了,直觉告诉我要在那个地方进行一个字符串编码的转换,且慢,在进行这个武断的尝试之前,先来看看《福尔摩斯》是怎么说的:

主观臆断的人,总是为了套用理论而扭曲事实,而不是用理论来解释事实。

猜测只是为了确定搜索的重点,但是在开始修复之前要观察确认你的猜测。所以在我们修改代码之前,还是看一下发生错误时的日志信息,还可以调试一下代码,在必要的时候打开源码深入研究一下,确定确实是字符串编码的问题,再去修改代码。

有人说,那我直接改代码,然后看结果不就知道是不是字符串编码的问题了。当然不是,要知道一个问题的产生可能是由多处地方的代码引起的,也许解决完这个字符串编码的问题,还需要解决另一个问题,才能把整个问题解决呢?如果你在修改前就没进行观察,就会认为这次修改毫无意义,这样整个调试过程就会陷入死局。

记住,调试是一门科学,任何不懂原理就进行的操作都是耍流氓

分而治之

系统通常都是由很多个模块组成的,这也就要求我们要检查很多个模块的日志才能够确定问题发生的原因。尤其是现在流行的微服务框架,一笔业务出现问题,你需要到很多个服务的机器上去找日志。

但是,如果你的业务执行是线性的,也就是说如果节点A执行失败,那么节点A之后的也都会执行失败,那么你就可以采用二分法的方式来定位了。要知道,在1到100里猜一个数字,最多也就需要7次。
采用二分法的方式,你将逐步缩小嫌疑的范围,最终找到问题的根因。

当然,如果问题是由多个子问题引起的,那么记住,找到一个,消灭一个,这就是所谓的分而治之

一次只修改一个地方

通过观察,你认为你的修改方法会起作用,但如果实际上你修改完代码之后,并没有起到任何作用,那么请你马上改回去,以免这个修改引入了新的Bug

做好修改记录

把你调试过程中的操作和结果按顺序全部记录下来,方便你在发现做了那么多处修改依然没有解决问题时,进行回溯,反思自己的操作有没有不对的地方。

反向调试法

上面的调试规则,都是从问题出发,去寻找犯错的代码。但有时候反过来也许会更好。
你可以找到最新一个可以正常运行的版本,然后对比现在这个版本和那个版本之间的差别,通过分析改动的代码,来分析是哪块代码导致的问题。

求助他人

有时候问题比较紧急,这时候不妨问一下专家,正如《程序员的思维修炼》中提到的,专家依靠直觉,他们往往会一针见血的给你指出问题的地方。
如果你对系统有一定理解的情况下,可以上软件供应商的官网、谷歌、StackOverflow等网站寻找相关的资料。
在求助的过程中,你只需要描述问题的症状,如果对方没有要求,那么不要给他讲自己的理论,以免将对方带入自己的思维定式。
而在给他人描述问题的同时,你自己可能也会得到启发。

总结

以上就是我看完《调试九法》这本书之后总结的一套调试方法论,当然还是建议大家看一下原著,说不定会有新的收获。不过书中列举了大量的例子,多的让我感觉有些冗余,建议大家看的时候,先看每一章节的开头,和每章结尾的小总结,看完之后有不理解的,再去看每章中间的案例。

本书的例子虽然大多数都是关于工程技术的,但是里面的一些想法还是可以借鉴到生活中去。比如,夫妻吵架了,表面看上去是因为丈夫不愿意洗碗,但是如果你能从全局的角度去观察,你就知道,其实是因为丈夫情人节时没有给夫人买礼物。

最后再回过头来看这些规则,其实我们在工作和生活中时不时都会用到,但是我们之前一直没有一个系统的理论体系,在掌握了书中介绍的调试规则之后,我们在今后定位错误根源时,会更加井井有条,从容不迫。

参考内容

作者:hzy38324 发表于2017/11/27 8:00:48 原文链接
阅读:66 评论:0 查看评论

spark学习-39-Spark的StaticMemoryManager

$
0
0

1.StaticMemoryManager继承与MemoryManager,它是静态的内存分配,是1.6版本以前的实现,就像是建筑商建造好了房子,用户来到直接住进去就好了(弊端:有的人多住了小房子,有的人少住了大房子)。而UnifiedMemoryManager是自由分配内存的实现,相当于组装房,你要多大我给你多大。

package org.apache.spark.memory

import org.apache.spark.SparkConf
import org.apache.spark.storage.BlockId

/**
 * A [[MemoryManager]] that statically partitions the heap space into disjoint regions.
  * 一个[[MemoryManager]],静态地将堆空间划分为不相交的区域。
 *
 * The sizes of the execution and storage regions are determined through
 * `spark.shuffle.memoryFraction` and `spark.storage.memoryFraction` respectively. The two
 * regions are cleanly separated such that neither usage can borrow memory from the other.
  *
  * 执行和存储区域的大小由“spark.shuffle.memoryFraction”和“spark.storage.memoryFraction”决定。
  * 这两个区域是干净的分开,这样两个使用都不能从另一个地方借用内存。
  *
  * 这个类就是Spark-1.6.0之前版本中主要使用的,对各部分内存静态划分好后便不可变化。
  *
  * StaticMemoryManager表示是静态的内存管理器,何谓静态,就是按照某种算法确定内存的分配后,其整体分布不会随便改变
 */
private[spark] class StaticMemoryManager(
    conf: SparkConf,
    maxOnHeapExecutionMemory: Long,
    override val maxOnHeapStorageMemory: Long,
    numCores: Int)
  extends MemoryManager(
    conf,
    numCores,
    maxOnHeapStorageMemory,
    maxOnHeapExecutionMemory) {

  def this(conf: SparkConf, numCores: Int) {
    this(
      conf,
      StaticMemoryManager.getMaxExecutionMemory(conf),
      StaticMemoryManager.getMaxStorageMemory(conf),
      numCores)
  }

  // The StaticMemoryManager does not support off-heap storage memory:
  offHeapExecutionMemoryPool.incrementPoolSize(offHeapStorageMemoryPool.poolSize)
  offHeapStorageMemoryPool.decrementPoolSize(offHeapStorageMemoryPool.poolSize)

  // Max number of bytes worth of blocks to evict when unrolling
  /** 当展开时,块的最大字节数被驱逐
    * 由maxStorageMemory(该方法在MemoryManager中被定义)乘以spark.storage.unrollFraction(默认值0.2)来确定。
    * 也就是说在storage内存中,有一部分会被用于unroll。由于Spark允许序列化和非序列化两种方式存储数据,对于序列化的数据,
    * 必须要先展开后才能使用。unroll部分空间就是用于展开序列化数据的。这部分空间是动态分配的
    */
  private val maxUnrollMemory: Long = {
    (maxOnHeapStorageMemory * conf.getDouble("spark.storage.unrollFraction", 0.2)).toLong
  }

  override def maxOffHeapStorageMemory: Long = 0L

  /**
    * 申请storage部分内存。在保证申请的内存数numBytes小于maxStorageMemory后,向storage内存池申请numBytes内存。
    * 进一步调用StorageMemoryPool的acquireMemory方法进行内存的申请。
    *
    *   就是这么简单。如果需要申请的内存超过Storage区域内存最大值的上限,则表明没有足够的内存进行存储,否则,
    * 调用storageMemoryPool的acquireMemory()方法分配内存,正是这里体现了static一词。至于具体分配内存的
    * storageMemoryPool,我们放到最后和Execution区域时的onHeapExecutionMemoryPool、offHeapExecutionMemoryPool
    * 一起讲,这里先了解下它的概念即可,它实际上是对应某种区域的内存池,是对内存总大小、可用内存、已用内存等内存
    * 使用情况的一种记账的专用对象。
    * */
  override def acquireStorageMemory(
      blockId: BlockId,
      numBytes: Long,
      memoryMode: MemoryMode): Boolean = synchronized {
    require(memoryMode != MemoryMode.OFF_HEAP,
      "StaticMemoryManager does not support off-heap storage memory")
    // 如果需要的大小numBytes超过Storage区域内存的上限,直接返回false,说明内存不够
    if (numBytes > maxOnHeapStorageMemory) {
      // Fail fast if the block simply won't fit
      logInfo(s"Will not store $blockId as the required space ($numBytes bytes) exceeds our " +
        s"memory limit ($maxOnHeapStorageMemory bytes)")
      false
    } else {
      // 否则,调用storageMemoryPool的acquireMemory()方法,申请内存
      onHeapStorageMemoryPool.acquireMemory(blockId, numBytes)
    }
  }

  /**
    *    根据传入numBytes,申请unroll部分内存。首先获取当前storage内存池中unroll部分使用的内存数currentUnrollMemory,
    * 以及当前storage内存池剩余内存数freeMemory。内存足够时,直接从storage内存池分配numBytes内存。如果内存不足,
    * 则会从storage内存池先释放出一部分内存。整个unroll部分使用的内存不能超过maxUnrollMemory。
    * */
  override def acquireUnrollMemory(
      blockId: BlockId,
      numBytes: Long,
      memoryMode: MemoryMode): Boolean = synchronized {
    require(memoryMode != MemoryMode.OFF_HEAP,
      "StaticMemoryManager does not support off-heap unroll memory")
    val currentUnrollMemory = onHeapStorageMemoryPool.memoryStore.currentUnrollMemory
    val freeMemory = onHeapStorageMemoryPool.memoryFree
    // When unrolling, we will use all of the existing free memory, and, if necessary,
    // some extra space freed from evicting cached blocks. We must place a cap on the
    // amount of memory to be evicted by unrolling, however, otherwise unrolling one
    // big block can blow away the entire cache.
    /**
      * 当展开时,我们将使用所有现有的空闲内存,如果有必要的话,还可以腾出一些额外的空间来释放缓存块。
      * 我们必须对未滚动的内存数量设置一个上限,否则,打开一个大块就可以将整个缓存吹走。
      * */
    val maxNumBytesToFree = math.max(0, maxUnrollMemory - currentUnrollMemory - freeMemory)
    // Keep it within the range 0 <= X <= maxNumBytesToFree
    val numBytesToFree = math.max(0, math.min(maxNumBytesToFree, numBytes - freeMemory))
    onHeapStorageMemoryPool.acquireMemory(blockId, numBytes, numBytesToFree)
  }

  /**
    * 申请execution部分内存。根据传入的taskAttemptId,以及需要的内存数numBytes,和当前的MemoryMode是ON_HEAP还是OFF_HEAP,
    * 从对应的execution内存池中申请内存。这里进一步调用ExecutionMemoryPool的acquireMemory方法进行内存的申请。
    * */
  private[memory]
  override def acquireExecutionMemory(
      numBytes: Long,
      taskAttemptId: Long,
      memoryMode: MemoryMode): Long = synchronized {
    // 根据MemoryMode的种类决定如何分配内存
    memoryMode match {
      // 如果是堆内,即ON_HEAP,则通过onHeapExecutionMemoryPool的acquireMemory对Task进行Execution区域内存分配
      case MemoryMode.ON_HEAP => onHeapExecutionMemoryPool.acquireMemory(numBytes, taskAttemptId)
      // 如果是堆外,即OFF_HEAP,则通过offHeapExecutionMemoryPool的acquireMemory对Task进行Execution区域内存分配
      case MemoryMode.OFF_HEAP => offHeapExecutionMemoryPool.acquireMemory(numBytes, taskAttemptId)
    }
  }
}


private[spark] object StaticMemoryManager {

  private val MIN_MEMORY_BYTES = 32 * 1024 * 1024

  /**
   * Return the total amount of memory available for the storage region, in bytes.
    * 返回存储区域可用的内存总量,以字节为单位。
    * 返回为storage区域(即存储区域)分配的可用内存总大小,单位为bytes
    *
    * 伴生对象中的方法,用于获取storage部分内存大小,计算过程如下:
    *    systemMaxMemory是当前Executor的内存大小,虽然可以由参数spark.testing.memory来设定,但是这个参数一般用于做测试,
    * 在生产上不建议设置。
    *   memoryFraction是storage内存占整个systemMaxMemory内存的比例,由参数spark.storage.memoryFraction(默认值0.6)来设定。
    * 同时为了避免出现OOM的情况,会设定一个安全系数spark.storage.safetyFraction(默认值0.9)
   */
  private def getMaxStorageMemory(conf: SparkConf): Long = {
    // systemMaxMemory :Runtime.getRuntime.maxMemory,即JVM能获得的最大内存空间。
    // 系统可用最大内存,取参数spark.testing.memory,未配置的话取运行时环境中的最大内存
    val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)

    // memoryFraction:由参数spark.storage.memoryFraction控制,默认0.6。
    // 取storage区域(即存储区域)在总内存中所占比重,由参数spark.storage.memoryFraction确定,默认为0.6
    val memoryFraction = conf.getDouble("spark.storage.memoryFraction", 0.6)

    // safetyFraction:由参数spark.storage.safetyFraction控制,默认是0.9,因为cache block都是估算的,所以需要一个安全系数来保证安全。
    // 取storage区域(即存储区域)在系统为其可分配最大内存的安全系数,主要为了防止OOM,取参数spark.storage.safetyFraction,默认
    val safetyFraction = conf.getDouble("spark.storage.safetyFraction", 0.9)

    // storageMemory能分到的内存是
    // 返回storage区域(即存储区域)分配的可用内存总大小,计算公式:
    // 系统可用最大内存*在系统可用最大内存中所占比重*安全系数
    (systemMaxMemory * memoryFraction * safetyFraction).toLong
  }

  /**
   * Return the total amount of memory available for the execution region, in bytes.
    * 以字节为单位,返回执行区域可用的内存总量。
    * 返回为Execution区域(即运行区域,为shuffle使用)分配的可用内存总大小,单位为bytes
    *
    * 伴生对象中的方法。用于获取execution部分内存大小。
    * memoryFraction即execution部分占所有能使用内存的百分比,由参数spark.shuffle.memoryFraction(默认值是0.2)来确定。
    * safetyFraction是execution部分的一个安全阈值,由参数spark.shuffle.safetyFraction(默认值是0.8)来确定。
   */
  private def getMaxExecutionMemory(conf: SparkConf): Long = {
    // 系统可用最大内存,取参数spark.testing.memory,未配置的话取运行时环境中的最大内存
    val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)

    if (systemMaxMemory < MIN_MEMORY_BYTES) {
      throw new IllegalArgumentException(s"System memory $systemMaxMemory must " +
        s"be at least $MIN_MEMORY_BYTES. Please increase heap size using the --driver-memory " +
        s"option or spark.driver.memory in Spark configuration.")
    }
    if (conf.contains("spark.executor.memory")) {
      val executorMemory = conf.getSizeAsBytes("spark.executor.memory")
      if (executorMemory < MIN_MEMORY_BYTES) {
        throw new IllegalArgumentException(s"Executor memory $executorMemory must be at least " +
          s"$MIN_MEMORY_BYTES. Please increase executor memory using the " +
          s"--executor-memory option or spark.executor.memory in Spark configuration.")
      }
    }

    /**
      * 取Execution区域(即运行区域,为shuffle使用)在总内存中所占比重,由参数spark.shuffle.memoryFraction确定,默认为0.2
      *
      * spark.shuffle.memoryFraction
      *
      * 默认值:0.2参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
      * 调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read
      * 的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。
      */
    val memoryFraction = conf.getDouble("spark.shuffle.memoryFraction", 0.2)

    // 取Execution区域(即运行区域,为shuffle使用)在系统为其可分配最大内存的安全系数,主要为了防止OOM,
    // 取参数spark.shuffle.safetyFraction,默认为0.8
    val safetyFraction = conf.getDouble("spark.shuffle.safetyFraction", 0.8)

    // 返回为Execution区域(即运行区域,为shuffle使用)分配的可用内存总大小,计算公式:系统可用最大内存 * 在系统可用最大内存中所占比重 * 安全系数
    (systemMaxMemory * memoryFraction * safetyFraction).toLong
  }

}
作者:qq_21383435 发表于2017/11/27 9:05:16 原文链接
阅读:48 评论:0 查看评论

spark学习-40-Spark的UnifiedMemoryManager

$
0
0

1。StaticMemoryManager继承与MemoryManager,它是静态的内存分配,是1.6版本以前的实现,就像是建筑商建造好了房子,用户来到直接住进去就好了(弊端:有的人多住了小房子,有的人少住了大房子)。而UnifiedMemoryManager是自由分配内存的实现,相当于组装房,你要多大我给你多大。

package org.apache.spark.memory

import org.apache.spark.SparkConf
import org.apache.spark.storage.BlockId

/**
 * A [[MemoryManager]] that enforces a soft boundary between execution and storage such that
 * either side can borrow memory from the other.
  *
  * 一个[[MemoryManager]],它强制执行和存储之间的软边界,这样任何一方都可以从另一方借用内存。
 *
 * The region shared between execution and storage is a fraction of (the total heap space - 300MB)
 * configurable through `spark.memory.fraction` (default 0.6). The position of the boundary
 * within this space is further determined by `spark.memory.storageFraction` (default 0.5).
 * This means the size of the storage region is 0.6 * 0.5 = 0.3 of the heap space by default.
  *
  * 执行和存储之间共享的区域是通过“spark.memory.fraction”(默认0.6)配置的(总堆空间- 300MB)的一小部分。
  * 在这个空间内的边界位置由“spark.memory.storageFraction”进一步确定(默认0.5)。这意味着默认情况下,
  * 存储区域的大小为0.6 * 0.5 = 0.3。
 *
 * Storage can borrow as much execution memory as is free until execution reclaims its space.
 * When this happens, cached blocks will be evicted from memory until sufficient borrowed
 * memory is released to satisfy the execution memory request.
  *
  * Storage can borrow as much execution memory as is free,直到执行重新声明它的空间。当这种情况发生时,
  * 缓存的块将被从内存中删除,直到释放出足够的内存,以满足执行内存请求。
 *
 * Similarly, execution can borrow as much storage memory as is free. However, execution
 * memory is *never* evicted by storage due to the complexities involved in implementing this.
 * The implication is that attempts to cache blocks may fail if execution has already eaten
 * up most of the storage space, in which case the new blocks will be evicted immediately
 * according to their respective storage levels.
  *
  * 类似地,execution可以以自由的方式借用大量存储内存。然而,由于execution此操作的复杂性,执行内存永远不会被存储。
  * 其含义是,如果执行已经耗尽了大部分存储空间,那么尝试缓存块可能会失败,在这种情况下,新的块将根据它们各自的存储
  * 级别立即被驱逐。
 *
 * @param onHeapStorageRegionSize Size of the storage region, in bytes.
 *                          This region is not statically reserved; execution can borrow from
 *                          it if necessary. Cached blocks can be evicted only if actual
 *                          storage memory usage exceeds this region.
  *
  *                          存储区域的大小,以字节为单位。
  *                          这个区域不是静态保留的;如果有必要,execution可以向它借。
  *                          只有在实际的存储内存使用超过该区域时,缓存的块才可以被驱逐。
  *
  *  该memoryManager主要是使得execution部分和storage部分的内存不像之前由比例参数限定住,而是两者可以互相借用内存。
  *  execution和storage总的内存上限由参数`spark.memory.fraction(默认0.75)来设定的,这个比例是相对于整个JVM heap来说的。
  *  Storage部分可以申请Execution部分的所有空闲内存,直到Execution内存不足时向Storage发出信号为止。当Execution需要
  *  更多内存时,Storage部分会向磁盘spill数据,直到把借用的内存都还上为止。同样的Execution部分也能向Storage部分借用内存,
  *  当Storage需要内存时,Execution中的数据不会马上spill到磁盘,因为Execution使用的内存发生在计算过程中,如果数据丢失就
  *  会到账task计算失败。Storage部分只能等待Execution部分主动释放占用的内存。
  *
  *
  *  UnifiedMemoryManager代表的是统一的内存管理器,统一么,是不是有共享和变动的意思。
 */
private[spark] class UnifiedMemoryManager private[memory] (
    conf: SparkConf,
    val maxHeapMemory: Long,
    onHeapStorageRegionSize: Long,
    numCores: Int)
  extends MemoryManager(
    conf,
    numCores,
    onHeapStorageRegionSize,
    maxHeapMemory - onHeapStorageRegionSize) {

  /**
    * 这个函数传入的memoryMode可选择是使用堆内存还是直接使用本地内存,默认是使用堆内存.
    *
    * // 确保onHeapExecutionMemoryPool和storageMemoryPool大小之和等于二者共享内存区域maxMemory大小
    * */
  private def assertInvariants(): Unit = {
    assert(onHeapExecutionMemoryPool.poolSize + onHeapStorageMemoryPool.poolSize == maxHeapMemory)
    assert(
      offHeapExecutionMemoryPool.poolSize + offHeapStorageMemoryPool.poolSize == maxOffHeapMemory)
  }

  assertInvariants()

  /**
    * 以前的版本是:maxStorageMemory
    * maxOnHeapStorageMemory为execution和storage区域共享的最大内存减去Execution已用内存
     */
  override def maxOnHeapStorageMemory: Long = synchronized {
    maxHeapMemory - onHeapExecutionMemoryPool.memoryUsed
  }

  override def maxOffHeapStorageMemory: Long = synchronized {
    maxOffHeapMemory - offHeapExecutionMemoryPool.memoryUsed
  }

  /**
   * Try to acquire up to `numBytes` of execution memory for the current task and return the
   * number of bytes obtained, or 0 if none can be allocated.
    *
    * 尝试获取当前任务的执行内存的“numBytes”,并返回所获得的字节数,如果没有可以分配的话,则返回0。
   *
   * This call may block until there is enough free memory in some situations, to make sure each
   * task has a chance to ramp up to at least 1 / 2N of the total memory pool (where N is the # of
   * active tasks) before it is forced to spill. This can happen if the number of tasks increase
   * but an older task had a lot of memory already.
    *
    * 这个调用可能会阻塞,直到在某些情况下有足够的空闲内存,以确保每个任务都有机会到达总内存池的至少1 / 2N
    * (其中N是活动任务的#),然后才会被迫溢写。如果任务的数量增加,但较老的任务有很多内存,这可能会发生。
    *
    * 为当前的taskAttemptId申请最多numBytes的内存,如果内存不足则返回0。
    * 由于这里涉及到的都是Executor JVM Heap中的内存,所以如果是OFF_HEAP模式,直接从offHeapExecution内存池分配。
    * 对memoryMode为ON_HEAP的进行如下处理。
   */
  override private[memory] def acquireExecutionMemory(
      numBytes: Long,
      taskAttemptId: Long,
      memoryMode: MemoryMode): Long = synchronized {
    // 这个函数传入的memoryMode可选择是使用堆内存还是直接使用本地内存,默认是使用堆内存.
    // 确保onHeapExecutionMemoryPool和storageMemoryPool大小之和等于二者共享内存区域maxMemory大小
    assertInvariants()
    assert(numBytes >= 0)
    val (executionPool, storagePool, storageRegionSize, maxMemory) = memoryMode match {
      // 这里定义的这个函数,用于判断numBytes(需要申请的内存大小)减去当前内存池中可用的内存大小是否够用,
      // 如果不够用,这个函数的传入值是一个正数
      case MemoryMode.ON_HEAP => (
        onHeapExecutionMemoryPool,
        onHeapStorageMemoryPool,
        onHeapStorageRegionSize,
        maxHeapMemory)
      case MemoryMode.OFF_HEAP => (
        offHeapExecutionMemoryPool,
        offHeapStorageMemoryPool,
        offHeapStorageMemory,
        maxOffHeapMemory)
    }

    /**
     * Grow the execution pool by evicting cached blocks, thereby shrinking the storage pool.
      * 通过驱逐缓存块来增加执行池,从而减少存储池。
     *
     * When acquiring memory for a task, the execution pool may need to make multiple
     * attempts. Each attempt must be able to evict storage in case another task jumps in
     * and caches a large block between the attempts. This is called once per attempt.
      *
      * 在为任务获取内存时,执行池可能需要多次尝试。每次尝试都必须能够驱逐存储,以防另一个任务跳跃,
      * 并在尝试之间缓存一个很大的块。这是每次尝试调用一次。
     */
    def maybeGrowExecutionPool(extraMemoryNeeded: Long): Unit = {
      if (extraMemoryNeeded > 0) {
        // There is not enough free memory in the execution pool, so try to reclaim memory from
        // storage. We can reclaim any free memory from the storage pool. If the storage pool
        // has grown to become larger than `storageRegionSize`, we can evict blocks and reclaim
        // the memory that storage has borrowed from execution.
        val memoryReclaimableFromStorage = math.max(
          storagePool.memoryFree,
          storagePool.poolSize - storageRegionSize)
        if (memoryReclaimableFromStorage > 0) {
          // Only reclaim as much space as is necessary and available:
          val spaceToReclaim = storagePool.freeSpaceToShrinkPool(
            math.min(extraMemoryNeeded, memoryReclaimableFromStorage))
          storagePool.decrementPoolSize(spaceToReclaim)
          executionPool.incrementPoolSize(spaceToReclaim)
        }
      }
    }

    /**
     * The size the execution pool would have after evicting storage memory.
      * 执行池在清除存储内存后的大小。
     *
     * The execution memory pool divides this quantity among the active tasks evenly to cap
     * the execution memory allocation for each task. It is important to keep this greater
     * than the execution pool size, which doesn't take into account potential memory that
     * could be freed by evicting storage. Otherwise we may hit SPARK-12155.
      *
      * 执行内存池将此数量分配到活动任务中,平均分配每个任务的执行内存分配。保持这个大于执行池大小是很重要的,
      * 因为它没有考虑到可能通过驱逐存储而释放的潜在内存。否则,我们可能打到spark - 12155。
     *
     * Additionally, this quantity should be kept below `maxMemory` to arbitrate fairness
     * in execution memory allocation across tasks, Otherwise, a task may occupy more than
     * its fair share of execution memory, mistakenly thinking that other tasks can acquire
     * the portion of storage memory that cannot be evicted.
      *
      * 此外,这个数量应该保持在“maxMemory”之下,在执行内存分配中对任务进行仲裁,否则,一个任务可能会占用更多
      * 的执行内存,错误地认为其他任务可以获得无法被驱逐的存储内存部分。
     */
    def computeMaxExecutionPoolSize(): Long = {
      maxMemory - math.min(storagePool.memoryUsed, storageRegionSize)
    }

    executionPool.acquireMemory(
      numBytes, taskAttemptId, maybeGrowExecutionPool, computeMaxExecutionPoolSize)
  }







  /**
    * 首先申请的storage内存numBytes不能超过storage部分内存的最大值maxStorageMemory。
    * 然后当storage部分内存不足以满足此次申请时,尝试向execution内存池借用内存,借到的内存大小为min(execution内存池剩余
    * 内存,numBytes),并且实时调整execution和storage内存池的大小,如下面的代码所描述的。
    *
    * 若申请的numBytes比两者总共的内存还大,直接返回false,说明申请失败。
    * 若numBytes比storage空闲的内存大,则需要向executionPool借用
    * 借用的大小为此时execution的空闲内存和numBytes的较小值(个人观点应该是和(numBytes-storage空闲内存)的较小值)
    * 减小execution的poolSize
    * 增加storage的poolSize
    * */
  override def acquireStorageMemory(
      blockId: BlockId,
      numBytes: Long,
      memoryMode: MemoryMode): Boolean = synchronized {
    assertInvariants()
    assert(numBytes >= 0)
    val (executionPool, storagePool, maxMemory) = memoryMode match {
      case MemoryMode.ON_HEAP => (
        onHeapExecutionMemoryPool,
        onHeapStorageMemoryPool,
        maxOnHeapStorageMemory)
      case MemoryMode.OFF_HEAP => (
        offHeapExecutionMemoryPool,
        offHeapStorageMemoryPool,
        maxOffHeapStorageMemory)
    }
    // 申请的内存大于storage和execution内存之和
    // 如果需要申请的内存大小超过maxStorageMemory,即execution和storage区域共享的最大内存减去Execution已用内存,
    // 快速返回, 这里是将execution和storage区域一起考虑的
    if (numBytes > maxMemory) {
      // Fail fast if the block simply won't fit
      logInfo(s"Will not store $blockId as the required space ($numBytes bytes) exceeds our " +
        s"memory limit ($maxMemory bytes)")
      return false
    }
    // 大于storage空闲内存
    // 如果需要申请的内存大小超过预分配storage区域中可用大小memoryFree
    if (numBytes > storagePool.memoryFree) {
      // There is not enough free memory in the storage pool, so try to borrow free memory from
      // the execution pool.
      // 从Execution区域借调的内存大小,为需要申请内存大小和预分配的Execution区域可用大小memoryFree的较小者
      val memoryBorrowedFromExecution = Math.min(executionPool.memoryFree,
        numBytes - storagePool.memoryFree)

      // Execution区域减小相应的值
      executionPool.decrementPoolSize(memoryBorrowedFromExecution)

      // Storage区域增大相应的值
      storagePool.incrementPoolSize(memoryBorrowedFromExecution)
    }
    // 通过storageMemoryPool完成内存分配
    storagePool.acquireMemory(blockId, numBytes)
  }

  override def acquireUnrollMemory(
      blockId: BlockId,
      numBytes: Long,
      memoryMode: MemoryMode): Boolean = synchronized {
    acquireStorageMemory(blockId, numBytes, memoryMode)
  }
}

object UnifiedMemoryManager {

  // Set aside a fixed amount of memory for non-storage, non-execution purposes.
  // This serves a function similar to `spark.memory.fraction`, but guarantees that we reserve
  // sufficient memory for the system even for small heaps. E.g. if we have a 1GB JVM, then
  // the memory used for execution and storage will be (1024 - 300) * 0.6 = 434MB by default.
  /**
    * 为非存储、非执行的目的留出一定的内存。这与“spark.memory.fraction”类似,但保证我们为系统保留足够的内存,
    * 即使是小堆。如果我们有1GB的JVM,那么在默认情况下,用于执行和存储的内存将是(1024 - 300)* 0.6 = 434MB。
    *
    * 伴生对象的一个属性,值为300MB,是Execution和Storage之外的一部分内存,为系统保留。
    * */
  private val RESERVED_SYSTEM_MEMORY_BYTES = 300 * 1024 * 1024

  /**
    * 使用apply方法进行初始化
    * */
  def apply(conf: SparkConf, numCores: Int): UnifiedMemoryManager = {
    // 获得execution和storage区域共享的最大内存
    val maxMemory = getMaxMemory(conf)
    // 构造UnifiedMemoryManager对象
    new UnifiedMemoryManager(
      conf,
      maxHeapMemory = maxMemory,
      // storage区域内存大小初始为execution和storage区域共享的最大内存的spark.memory.storageFraction,
      // 默认为0.5,即一半
      onHeapStorageRegionSize =
        (maxMemory * conf.getDouble("spark.memory.storageFraction", 0.5)).toLong,
      numCores = numCores)
  }

  /**
   * Return the total amount of memory shared between execution and storage, in bytes.
    *返回在执行和存储之间共享的内存总量,以字节为单位。
    * 返回execution和storage区域共享的最大内存
    *
    * 伴生对象的方法。获取execution和storage部分能够使用的总内存大小。
    *
    * systemMemory即Executor的内存大小。systemMemory要求最小为reservedMemory的1.5倍,否则直接抛出异常信息。
    * reservedMemory是为系统保留的内存大小,可以由参数spark.testing.reservedMemory确定,默认值为上面的300MB。
    * 如果为默认值的话,那么对应的会要求systemMemory最小为450MB。
    * memoryFraction是整个execution和storage共用的最大内存比例,由参数spark.memory.fraction(默认值0.75)来决定。
    * 那么还剩下0.25的内存作为User Memory部分使用。那么对一个1GB内存的Executor来说,在默认情况下,可使用的内存大小
    * 为(1024 - 300) * 0.75 = 543MB
    *
    * 处理流程大体如下:
    *     1、获取系统可用最大内存systemMemory,取参数spark.testing.memory,未配置的话取运行时环境中的最大内存;
    *     2、获取预留内存reservedMemory,取参数spark.testing.reservedMemory,未配置的话,根据参数spark.testing
    *        来确定默认值,参数spark.testing存在的话,默认为0,否则默认为300M;
    *     3、取最小的系统内存minSystemMemory,为预留内存reservedMemory的1.5倍;
    *     4、如果系统可用最大内存systemMemory小于最小的系统内存minSystemMemory,即预留内存reservedMemory的1.5倍
    *        的话,抛出异常,提醒用户调大JVM堆大小;
    *     5、计算可用内存usableMemory,即系统最大可用内存systemMemory减去预留内存reservedMemory;
    *     6、取可用内存所占比重,即参数spark.memory.fraction,默认为0.75;
    *     7、返回的execution和storage区域共享的最大内存为usableMemory * memoryFraction。
    *
    *   也就是说,UnifiedMemoryManager统一内存存储管理策略中,默认情况下,storage区域和execution区域默认都占其
    * 共享内存区域的一半,而execution和storage区域共享的最大内存为系统最大可用内存systemMemory减去预留内存
    * reservedMemory后的75%。至于在哪里体现的动态调整,则要到真正申请内存时再体现了。
   */
  private def getMaxMemory(conf: SparkConf): Long = {
    // 获取系统可用最大内存systemMemory,取参数spark.testing.memory,未配置的话取运行时环境中的最大内存
    val systemMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)

    // 获取预留内存reservedMemory,取参数spark.testing.reservedMemory,
    // 未配置的话,根据参数spark.testing来确定默认值,参数spark.testing存在的话,默认为0,否则默认为300M
    val reservedMemory = conf.getLong("spark.testing.reservedMemory",
      if (conf.contains("spark.testing")) 0 else RESERVED_SYSTEM_MEMORY_BYTES)

    // 取最小的系统内存minSystemMemory,为预留内存reservedMemory的1.5倍
    val minSystemMemory = (reservedMemory * 1.5).ceil.toLong

    // 如果系统可用最大内存systemMemory小于最小的系统内存minSystemMemory,即预留内存reservedMemory的1.5倍的话,
    // 抛出异常 提醒用户调大JVM堆大小
    if (systemMemory < minSystemMemory) {
      throw new IllegalArgumentException(s"System memory $systemMemory must " +
        s"be at least $minSystemMemory. Please increase heap size using the --driver-memory " +
        s"option or spark.driver.memory in Spark configuration.")
    }

    // SPARK-12759 Check executor memory to fail fast if memory is insufficient
    if (conf.contains("spark.executor.memory")) {
      val executorMemory = conf.getSizeAsBytes("spark.executor.memory")
      if (executorMemory < minSystemMemory) {
        throw new IllegalArgumentException(s"Executor memory $executorMemory must be at least " +
          s"$minSystemMemory. Please increase executor memory using the " +
          s"--executor-memory option or spark.executor.memory in Spark configuration.")
      }
    }

    // 计算可用内存usableMemory,即系统最大可用内存systemMemory减去预留内存reservedMemory
    val usableMemory = systemMemory - reservedMemory

    // 取可用内存所占比重,即参数spark.memory.fraction,默认为0.6
    val memoryFraction = conf.getDouble("spark.memory.fraction", 0.6)

    // 返回的execution和storage区域共享的最大内存为usableMemory * memoryFraction
    (usableMemory * memoryFraction).toLong
  }
}
作者:qq_21383435 发表于2017/11/27 9:08:00 原文链接
阅读:55 评论:0 查看评论

讲给Android程序员看的前端教程(01)——HTML5入门

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


HTML、CSS、JavaScript的关系

通常我们说的页面或者前端,一般都包括了:HTML、CSS、JavaScript。那么这三者在页面中分别承担什么角色呢?简单地来讲:

  • HTML负责页面的结构和语义。比如,页面的标题,页面的区域划分,语义化标签等等都是由HTML来实现的。

  • CSS负责页面的样式。比如,背景颜色,文本的大小,边框等等与样式相关的部分都由CSS承担。

  • JavaScript负责页面的行为和交互。比如,点击按钮后改变文本颜色,计算两个数据的相加,存储数据等等;这些都是由JavaScript负责。


HTML5的特点及其优势

至于HTML的发展历史,我们不再赘述;我们要了解的是HTML5有哪些特点和优势。HTML5是新一代开发Web富客户端应用程序整体解决方案 ,从广义上来讲:HTML5=HTML5+CSS3+JavaScript,也就是说HTML5是在原来的HTML,CSS,JavaScript基础上的整合和增强。

  • HTML5解决跨浏览器问题。目前,各大浏览器厂商对HTML5表现出极大的热情,全力支持HTML5的广泛应用。假以时日,HTML5将改变HTML+CSS+JavaScript在跨浏览器时带来的各种诟病。

  • HTML5定义了一系列新元素,比如:智能表单、新的语义标签、多媒体标签等,这些新元素有助于开发者创建富互联网应用

  • HTML5提供一系列JavaScript API;比如:重力感应、文件访问、地理定位、硬件访问等等,这些API可以在浏览器内实现类原生应用

  • HTML5提供Canvas元素用于游戏开发。

  • HTML5提供操作多媒体的相关元素。以往,在HTML中实现音频、视频的播放往往需要借助第三方插件;现在,利用HTML5自身的元素就能实现视频,音频的基本操作。

抛开具体的技术不谈,HTML5的出现也给开发人员带来了极大的便利。在此,主要谈三点。

  • 迭代速度快
    相比IOS和Android的迭代速度,HTML5快了不是一点半点,简直都快飞起来了。变更后上线的时间极大缩短,用户也可毫无感知的情况下获取到最新的变化。

  • HTML5兼容性强
    HTML5的兼容性主要体现在屏幕的大小和小部分极其殊的机型,发现Bug之后改起来也容易得多。这比起让人痛苦不堪的Android碎片化而言确实轻松了不少。

  • 开发成本较低
    比如要做一个移动端应用程序,为了兼顾到大部分用户,那么一般都会分为Android和IOS两个APP。对于公司而言,势必增加开发成本。如果采用THML5开发呢?嗯哼,大部分工作都可以用HTML5完成,从而缩短开发周期,降低开发成本。


HTML5开发工具

目前,THML5的主流开发工具有:Adobe Dreamweaver、sublime text 3、 WebStorm。在本教程中,我们选用sublime text 3作为开发工具。

  • 第一步:下载和安装sublime text 3

    该过程非常简单,按照官方文档进行即可,在此不再赘述。

  • 第二步:为sublime text 3安装插件

    sublime text 3支持众多插件,可极大提高我们的开发效率;作为前端的学习者或者初级开发人员建议安装:Emmet、HTML-CSS-JS Prettify、SublimeCodeIntel

  • 第三步:熟悉常用快捷键

    每个开发工具都有众多的快捷键,不必全部熟记;掌握常用的几个快捷键即可。


HTML5入门示例

在介绍完HTML5及其开发工具后,按照国际惯例,我们就要来写一个Hello World了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
</head>
<body>
    <p>Hello World</p>
</body>
</html>

嗯哼,我们花了少许时间就写好了HTML5的入门示例,运行在浏览器中即可看到文字Hello World

作者:lfdfhl 发表于2017/11/27 9:08:20 原文链接
阅读:82 评论:0 查看评论

讲给Android程序员看的前端教程(02)——HTML5标签(1)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们从最常用的标签开始学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。


HTML常用标签

我们先来看一些最常用的HTML标签

p标签

p标签在HTML中常用于表示段落,它是英文单词paragraph的缩写。p标签的用法非常简单,只需要在标签中放置一段文本即可。

<p>2017,顶着刘海的iPhoneX带着“史上升级变动最大”的iOS11,依然碎片化严重的Android带着“更快、更强大、更安全” 的8.0来到我们面前,忽思十年初,那个触 屏的、没有物理键盘的智能手机惊艳了我 ,但连个复制粘贴功能都没有的时光。回首一顾,从2007到2017,从诺记的Symbian、摩托罗拉的Linux、苹果的iOS、微软的Windows Phone、三星的 Tizen到Google的Android等,移动操作系统也曾百花齐放,但经过十年厮杀各自蚕食,格局已相当明朗,只剩下了iOS和Android两大巨头</p>

在浏览器中运行之后可以发现:p标签的上下均有大约一行宽度的留白,这和我们平时看见的文章的每个段落是一样的。

h标签

h标签用于表示标题,它是英文单词header的缩写。我想这个单词对于Android程序员来说是再熟悉不过了吧;例如,给ListView,Recyclerview设置header和footer。在HTML中h标签细分为h1~h6,请注意:h后的数字越大,那么标题所对应的字体越小。示例如下:

<h1>这里是h1</h1>
<h2>这里是h2</h2>
<h3>这里是h3</h3>
<h4>这里是h4</h4>
<h5>这里是h5</h5>
<h6>这里是h6</h6>

hr标签

hr标签用于表示一条水平横线,它是英文中Horizontal Rule的缩写。它的用法非常简单:

<hr>

在页面中只用写一个hr标签,就可以表示一条水平横线。

br标签

br标签用于表示换行,它在英文所对应的单词是break。它的用法也非常简单:

<br>

nobr标签

会了刚才的br标签,再来看nobr就很简单了;该标签表示不换行。比如,我们想表示一个很长的数学公式,需要将其显示在同一行,从而避免换行后导致可读性变差产生歧义。

<nobr>这里是一个很长的数学公式,不能换行显示,只能在一行显示。这里是一个很长的数学公式,不能换行显示,只能在一行显示。这里是一个很长的数学公式,不能换行显示,只能在一行显示。这里是一个很长的数学公式,不能换行显示,只能在一行显示。</nobr>

center标签

center标签表示居中显示,比如我们想将一句话显示在页面的水平方向的中间,可以这么做:

<center>测试center标签</center>

marquee标签

marquee标签用于表示跑马灯效果。做Android开发的童鞋还记得不,在TextView中也有类似的属性:android:ellipsize=”marquee”;它们是非常类似的。

<marquee behavior="scroll" direction="left">
        <p>测试marquee标签</p>
</marquee>

在marquee标签中可以通过behavior和direction属性控制跑马灯的不同效果。

button标签

button标签用于表示按钮,这和我们在Android开发中常用的Button控件是完全一样的。

<button type="button" onclick="onButtonClick()">This is a button</button>
    <script type="text/javascript">
        function onButtonClick(){
            alert('You click button');
        }
    </script>

此处,我们给button标签设置一个监听器,当用户点击button后利用JavaScript弹出一个对话框。

a标签

a标签在THML中常用于表示锚点和超链接,它是英文中anchor的缩写;在此,我们主要来瞅瞅利用a标签实现超链接。

<a href="http://blog.csdn.net/lfdfhl" title="谷哥的小弟" target="_blank">请点击此处的超链</a>

在a标签中利用href属性指明超链接的地址,利用title表示当鼠标悬停在超链接时的提示文字,利用target属性表示打开超链接的方式。如果target的取值为_blank表示在新窗口中打开超链接;假若target的取值为_self表示在当前窗口中打开超链接。

img标签

img标签在HTML中常用于表示图像,它是英文中image的缩写。请看如下示例:

<img src="myblog.jpg" title="这是我的博客头像">

源码和页面

在此,附上刚才所讲标签时涉及到的源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML常用标签</title>
</head>
<body>

    <!-- p标签,用于表示段落 -->
    <p>2017,顶着刘海的iPhoneX带着“史上升级变动最大”的iOS11,依然碎片化严重的Android带着“更快、更强大、更安全” 的8.0来到我们面前,忽思十年初,那个触 屏的、没有物理键盘的智能手机惊艳了我 ,但连个复制粘贴功能都没有的时光。回首一顾,从2007到2017,从诺记的Symbian、摩托罗拉的Linux、苹果的iOS、微软的Windows Phone、三星的 Tizen到Google的Android等,移动操作系统也曾百花齐放,但经过十年厮杀各自蚕食,格局已相当明朗,只剩下了iOS和Android两大巨头</p>

    <!-- h1至h6标签,用于表示标题 -->
    <h1>这里是h1</h1>
    <h2>这里是h2</h2>
    <h3>这里是h3</h3>
    <h4>这里是h4</h4>
    <h5>这里是h5</h5>
    <h6>这里是h6</h6>

    <!-- hr标签用于水平线 -->
    <hr>

    <!-- br标签用于换行 -->
    <br>

    <!-- nobr标签表示禁止换行 -->
    <nobr>这里是一个很长的数学公式,不能换行显示,只能在一行显示。这里是一个很长的数学公式,不能换行显示,只能在一行显示。这里是一个很长的数学公式,不能换行显示,只能在一行显示。这里是一个很长的数学公式,不能换行显示,只能在一行显示。</nobr>
    <br>
    <br>

    <!-- center标签,表示居中 -->
    <center>测试center标签</center>
    <br>
    <br>

    <!-- marquee标签,用于显示跑马灯效果 -->
    <marquee behavior="scroll" direction="left">
        <p>测试marquee标签</p>
    </marquee>
    <br>
    <br>

    <!-- button标签 -->
    <button type="button" onclick="onButtonClick()">This is a button</button>
    <script type="text/javascript">
        function onButtonClick(){
            alert('You click button');
        }
    </script>
    <br>
    <br>
    <br>

    <!-- span用于组合文档中的行内元素;它常结合CSS为文本中的某些部分进行特殊处理 -->
    <span style="color:#FA0">大家好</span><span style="color:#F00">我是谷哥的小弟</span>
    <br>
    <br>

    <!-- a标签用于超链接 -->
    <a href="http://blog.csdn.net/lfdfhl">请点击此处的超链接</a>
    <br>
    <a href="http://blog.csdn.net/lfdfhl" target="_blank">请点击此处的超链接</a>
    <br>
    <a href="http://blog.csdn.net/lfdfhl" title="谷哥的小弟" target="_blank">请点击此处的超链接</a>
    <br>
    <br>

    <!-- img标签用于显示图像-->
    <img src="myblog.jpg" title="这是我的博客头像">
    <br>

    <!-- img标签结合a标签实现超链接 -->
    <a href="http://blog.csdn.net/lfdfhl" title="谷哥的小弟" target="_blank"><img src="myblog.jpg"></a>

</body>
</html>

页面在谷歌浏览器的显示效果如下图:


这里写图片描述

作者:lfdfhl 发表于2017/11/27 9:11:34 原文链接
阅读:64 评论:0 查看评论

讲给Android程序员看的前端教程(03)——HTML5标签(2)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们继续HTML标签的学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像你看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。

上次已经讲完了HTML常用标签,这次我们来学习HTML文本标签。


HTML文本标签

文本标签,顾名思义,它是用来显示文本的。在此,我们来瞅瞅HTML中经常使用的文本标签。

b标签

b标签常用于文本加粗,它对应于英文中的bold。

<b>b标签用于粗体显示文字</b>

strong标签

strong标签的作用和用法与b标签基本相同,但是在HTML5中为strong标签增加了语义,用其表示重要的文本。

<strong>strong标签用于粗体显示文本,表示重点内容</strong>

small标签

small标签用于显示小号字体,比如:版权信息,法律信息,免责声明

<small>本文的原创作者是谷哥的小弟</small>

有人在想:既然有了samll标签,那么是不是有对应的big标签呢?嗯哼,以前确实是有这个标签的,但是在HTML5中已经将其删除了。

i标签

i标签用于将文本斜体显示,它源于英语单词italic;常用于显示专业词汇,术语,谚语。

<i>service</i>

em标签

em标签表示将文本斜体显示,它源于英语单词emphasize.

<em>这里是考试的重点</em>

看到这里,有的童鞋就有疑问了:i标签和em标签都将文本斜体显示,它们有什么区别么?b标签和strong标签都将文本粗体显示,它们有什么区别呢?

b标签和i标签仅仅表示”此处应该用粗体显示”或者”此处应该用斜体显示”,例如,要突出合同的价格那么可以用b标签粗体显示;要表达一句谚语,可以用i标签将其斜体显示。
strong标签和em标签是为了强调内容的重要意义而显示粗体或者斜体;对于搜索引擎,爬虫,SEO而言更受重视。例如,我们将”打倒法西斯!”这句话置于strong标签中;那么,语音阅读器时读到此strong标签就会重读。
概括地来说:b标签和i标签是物理元素 ;strong标签和em标签是逻辑元素。物理元素强调的是一种物理行为。比如说,把一段文字用b标签加粗,意思是告诉浏览器应该加粗显示,没有其他作用;而strong标签不但加粗了字体还起到了强调的作用。同理,i标签和em标签类似,故不再赘述。

u标签

u标签用于表示文本下划线,它源于英文单词underline;请看如下示例:

<u>u标签标示文本的下划线</u>

sup标签

sup标签用于表示文本的上标,它是英文单词superscript的缩写;请看如下示例:

这里是上标<sup>1</sup>

sub标签

sub标签用于表示文本的下标,它是英文单词subscript的缩写;请看如下示例:

这里是下标<sub>2</sub>

span标签

span用于组合文档中的行内元素;它常结合CSS为文本中的某些部分进行特殊处理。说到这里,大家是不是猛然想起来Android里也有类似的东西!比如,要把一部分文字改变颜色,还记得我们在Android里面怎么做的呢?是不是利用SpannableString就可以了?!你瞅瞅,它是不是也是个span呢?所以,这两者是互通的,知道了其中一个,另外一个自然也理解了。

<span style="color:#FA0">大家好</span><span style="color:#F00">我是谷哥的小弟</span>

font标签

font标签用于给文本设置文字大小和颜色等属性。示例如下:

<font color="red" size="15">测试font</font>

虽然font标签可以给文本设置样式,但是在HTML5中建议不再使用该标签,可采用CSS实现相同的功能。


源码和页面

在此,附上刚才所讲标签时涉及到的源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML常用文本标签</title>
</head>
<body>

    <!-- b标签,用于将内容加粗显示-->
    <b>b标签用于粗体显示文字</b>
    <br>
    <br>

    <!-- strong标签,用于表示重点内容-->
    <strong>strong标签用于粗体显示文本,表示重点内容</strong>
    <br>
    <br>

    <!-- small标签用于显示小号字体,常用于表示:版权信息,法律信息,免责声明 -->
    <small>本文的原创作者是谷哥的小弟</small>
    <br>
    <br>

    <!-- i标签用于将文本斜体显示-->
    <i>service</i>
    <br>
    <br>

    <!--em标签表示将文本斜体显示 -->
    <em>这里是考试的重点</em>
    <br>
    <br>

    <!-- u标签 -->
    <u>u标签标示文本的下划线</u>
    <br>
    <br>

    <!-- sup标签,表示上标-->
    这里是上标<sup>1</sup>
    <br>
    <br>

    <!-- sub标签,表示下标 -->
    这里是下标<sub>2</sub>
    <br>
    <br>

    <!-- font标签用于给文本设置文字大小和颜色等属性-->
    <font color="red" size="15">测试font</font>
    <br>
    <br>

</body>
</html>

页面在谷歌浏览器的显示效果如下图:


这里写图片描述

作者:lfdfhl 发表于2017/11/27 9:13:43 原文链接
阅读:56 评论:0 查看评论

讲给Android程序员看的前端教程(04)——HTML5标签(3)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们继续HTML5标签的学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。

之前,我们已经学习了HTML常用标签、HTML文本标签;现在我们来继续学习HTML语义标签。


HTML语义标签

在讲这类标签之前,我们先来聊聊标签的语义化。
HTML5标签语义化的目的:让程序员(甚至是非IT人士)能够直观地认识到标签及其属性的用途和作用。比如,当我们看到h1~h6时就知道:这个标签是用来显示标题的。当然,语义化还有其他非常重要的作用。通过语义化标签可以让爬虫,搜索引擎,SEO读懂我们的页面。比如,我们利用HTML5开发一款新闻朗读软件给盲人朋友用,如果我们把重点内容放入strong标签中,那么该内容会被重读从而突出重点。

blockquote标签

blockquote用于表示文本的引用。引用的文本会在左、右两侧同时缩进;请看如下示例:

<blockquote cite="http://blog.csdn.net/lfdfhl/article/details/77825765">代理模式(Proxy Pattern)是面向对象中一种非常常见的设计模式。其实,不单是在软件开发领域,在我们的日常生活中对于代理也时常可见。比如:房东要将自家的房租出售,于是到房地产中介公司找一个代理,由他来帮自己完成销售房屋,签订合同等等事宜。</blockquote>

在该标签中,可使用cite属性标明引用内容的来源。

cite标签

刚才我们看到cite是blockquote标签的中的一个属性;其实,cite还可以单独作为一个标签使用。cite标签用于表示文本对某个参考文献的引用;比如书籍或者杂志的标题;请看如下示例:

这段话出自<cite>《java编程思想》</cite>

address标签

address标签用于表示地址,显示效果通常为斜体,请看如下示例:

<address>中国四川省成都市高新区</address>

code标签

code标签用于表示计算机代码,请看如下示例:

<code>system.out.println()</code>

var标签

var标签用于表示变量,请看如下示例:

<var>count</var>

dfn标签

dfn标签用于定义专业术语,它源于短语defining instance,请看如下示例:

<dfn>量子网络通信</dfn>

del标签

del标签用于表示删除,在该标签中的文本会被画一条横线,请看如下示例:

<del>该方法已经废弃</del>

pre标签

pre标签表示预先的格式化,它源自于英语单词preformatted,该标签中的空格,回车等格式字符都会被保留。请看如下示例:

<pre>
        <p>       第一行文字</p>


        <p>第二行文字</p>
</pre>

mark标签

mark标签用于标记文本中的重点内容,默认采用荧光色标记。请看如下示例:

<mark>排序算法是我们面试的重点</mark>

details和summary标签

details标签用于表示详细信息;summary标签常用于表示摘要信息;两者常结合起来使用。请看如下示例:

<details>
    <summary>java编程思想</summary>
    这本书写得非常好,值得一看
</details>

源码和页面

在此,附上刚才所讲标签时涉及到的源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML常用语义标签</title>
</head>
<body>

    <!-- blockquote标签用于文本引用-->
    <blockquote cite="http://blog.csdn.net/lfdfhl/article/details/77825765">代理模式(Proxy Pattern)是面向对象中一种非常常见的设计模式。其实,不单是在软件开发领域,在我们的日常生活中对于代理也时常可见。比如:房东要将自家的房租出售,于是到房地产中介公司找一个代理,由他来帮自己完成销售房屋,签订合同等等事宜。</blockquote>


    <!-- cite标签用于表示作品的引用 -->
    这段话出自<cite>《java编程思想》</cite>
    <br>
    <br>

    <!-- address标签用于表示地址 -->
    <address>中国四川省成都市高新区</address>
    <br>
    <br>

    <!-- code标签用于表示计算机代码 -->
    <code>system.out.println()</code>
    <br>
    <br>

    <!-- var标签用于表示变量 -->
    <var>count</var>
    <br>
    <br>

    <!-- dfn标签用于定义专业术语 -->
    <dfn>量子网络通信</dfn>
    <br>
    <br>

    <!-- del标签用于显示被删除的文本 -->
    <del>该方法已经废弃</del>
    <br>
    <br>


    <!-- pre标签表示预先的格式化-->
    <pre>
        <p>       第一行文字</p>


        <p>第二行文字</p>
    </pre>
    <br>
    <br>

    <!-- mark标签用于标记文本中的重点内容,默认用荧光色标记-->
    <mark>排序算法是我们面试的重点</mark>
    <br>
    <br>

    <!-- details和summary标签 -->
    <details>
        <summary>java编程思想</summary>
        这本书写得非常好,值得一看
    </details>
    <br>
    <br>

</body>
</html>

页面在谷歌浏览器的显示效果如下图:


这里写图片描述

作者:lfdfhl 发表于2017/11/27 9:15:38 原文链接
阅读:59 评论:0 查看评论

讲给Android程序员看的前端教程(05)——HTML5标签(4)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们继续HTML5标签的学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。

之前,我们已经学习了HTML常用标签、HTML文本标签、HTML语义标签;现在我们来继续学习HTML结构标签。


HTML结构标签

我们在HTML页面中常用一些标签将页面划分为不同的区域用以表示页面结构。比如,可使用div标签将整个页面分为header,body,footer三部分。现在我们就来学习这些与页面结构有关的标签。

div标签

div标签在页面中非常常见,也常将其称为标签容器。我们可以将一组功能相关的标签放到同一个div中,也可以对该标签内的元素作统一处理,比如设置对齐方式,背景颜色。请看如下示例:

<strong>学习div标签</strong>
<div align="center" style="color:#0000FF">
      <p>这是div中一个p标签</p>
      <p>这是div中另外一个p标签</p>
</div>

但是,请注意:div标签本身没有任何语义,多用作布局以及样式化或脚本的钩子(hook)。正如,官方文档所言:

The div element has no special meaning at all

所以,在页面中大量使用div标签导致页面的语义性下降。因此HTML5中引入了新的结构标签article和section

section标签

先来瞅瞅section标签的文档释义:

The section element represents a generic section of a document or application. A section , in this context, is a thematic grouping of content, typically with a heading

这段话的大概含义是:section不仅仅是一个标签容器,它带有明显的语义。该标签常用于对网站或者应用程序中页面上的内容进行分块。比如,网站的主页可以分成简介、新闻和联系方式等几部分,那么每一部分都可以放到一个section里面;类似地,文章的章节、标签对话框中的标签页、或者论文中有编号的部分也可以放到一个section中。请注意:官方文档建议,在使用section时每个section标签中应带一个标题标签(h1-h6),从而表达更清晰的语义。

article标签

先来瞅瞅article标签的文档释义:

The article element represents a self-contained composition in a document, page, application, or site and that is, in principle,independently distributable or reusable, e.g. in syndication.

这段话的大概含义是:article是一个特殊的section标签,它比section更具有明确的语义。也就是说:无论从结构上还是内容上来说,它代表一个独立的、完整的相关内容块。例如:博客中的一篇文章,论坛中的一个帖子或者一段浏览者的评论等。一般来说,article也有标题部分(通常包含在header内),类似地它还有footer部分。

示例

嗯哼,现在我们来看一个关于section标签和article标签的示例,代码如下:

<article>
        <header><h2>有心课堂课程介绍</h2></header>
        <p>我们需要从每天的复制粘贴,盲目的debug中解放出来,找到解决问题的本质。在有心课堂,我们不仅仅是告诉你需求如何实现,还会教你如何分析,如何选择解决方案以及为什么要这样来实现。有心课堂,初衷是为了引导在职开发人员高效开发,养成良好的思维习惯,而不是简单的教你基础API调用。当然优质的教学体系还需要时下热门的市场需求来做辅助。所以我们在此基础上设计来三套阶梯式课程。</p>
        <section>
            <h3>Android架构设计方法、技巧与实践</h3>
            <p>这里是《Android架构设计方法、技巧与实践》的课程简介</p>
        </section>

        <section>
            <h3>三杆火枪干掉自定义View</h3>
            <p>这里是《三杆火枪干掉自定义View》的课程介绍。目前有1068位同学参加该课程</p>
        </section>

        <footer>课程内容版权均归上海有心网络科技有限公司所有</footer>
    </article>

运行后页面如下图所示:


这里写图片描述

小结

div、section、article这三个标签的语义是从无到有,逐渐增强的。div无任何语义,仅仅用作样式化或者脚本化的钩子(hook)。鉴于div标签的不足,HTML5中新增的section和article比较明确地定义了文档结构,便于搜索引擎、浏览器等工具解析该页面;推荐开发人员在工作中广泛使用。对于一段主题性的内容,可以采用section;假如这段内容可以脱离上下文,作为完整的独立存在的一段内容,那么就采用article。

作者:lfdfhl 发表于2017/11/27 9:17:08 原文链接
阅读:64 评论:0 查看评论

讲给Android程序员看的前端教程(06)——HTML5标签(5)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们继续HTML5标签的学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。

之前,我们已经学习了HTML常用标签、HTML文本标签、HTML语义标签、HTML结构标签;现在我们来继续学习HTML列表标签。


HTML列表标签

在此学习HTML中常用的列表标签

ul标签

可能猛地一下看到ul不知道它是干嘛的。可是,如果我告诉你它源自于英语短句unordered list,你是否就反应过来了呢?对的,它用于表示无序列表。请看如下示例:

<ul>
        <li>华为</li>
        <li>三星</li>
        <li>小米</li>
        <li>锤子</li>
</ul>

ol标签

看到ol,调皮的同学一下子就想到了office lady,于是乎就开始莫名地兴奋了,白领,制服,丝袜。。。。。浮想联翩。嗯哼,不好意思,你想多了,此处的ol源自于英语短句ordered list,它用于表示有序列表。请看如下示例:

<ol>
        <li>华为</li>
        <li>三星</li>
        <li>小米</li>
        <li>锤子</li>
</ol>

dl标签

dl标签表示定义一个定义列表,它源于英语短句definition list。请看如下示例:

<dl>
        <dt>Android四大组件</dt>
        <dd>Activity</dd>
        <dd>广播接收者</dd>
        <dd>内容提供者</dd>
        <dd>服务</dd>
        <dt>Android常用布局</dt>
        <dd>线性布局</dd>
        <dd>相对布局</dd>
        <dd>百分比布局</dd>
</dl>

源码和页面

在此,附上刚才所讲标签时涉及到的源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML的列表标签</title>
</head>
<body>

    <!-- ul标签用于无序列表 -->
    <ul>
        <li>华为</li>
        <li>三星</li>
        <li>小米</li>
        <li>锤子</li>
    </ul>
    <br>


    <!-- ol标签用于有序列表 -->
    <ol>
        <li>华为</li>
        <li>三星</li>
        <li>小米</li>
        <li>锤子</li>
    </ol>
    <br>


    <!-- dl标签用于定义术语列表-->
    <dl>
        <dt>Android四大组件</dt>
        <dd>Activity</dd>
        <dd>广播接收者</dd>
        <dd>内容提供者</dd>
        <dd>服务</dd>
        <dt>Android常用布局</dt>
        <dd>线性布局</dd>
        <dd>相对布局</dd>
        <dd>百分比布局</dd>
    </dl>

</body>
</html>

页面在谷歌浏览器的显示效果如下图:
这里写图片描述

作者:lfdfhl 发表于2017/11/27 9:18:52 原文链接
阅读:59 评论:0 查看评论

讲给Android程序员看的前端教程(07)——HTML5标签(6)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们继续HTML5标签的学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。

之前,我们已经学习了HTML常用标签、HTML文本标签、HTML语义标签、HTML结构标签、HTML列表标签;现在我们来继续学习HTML表格标签。


HTML表格标签

在HTML中与表格相关的标签简述如下:

  • table标签用于展示表格

  • caption标签用于显示表格的标题

  • tr标签用于表示表格的行

  • th标签用于表示表格的表头单元格

  • td标签用于表示单元格

OK,来吧,我们写个表格瞅瞅。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML表格标签</title>
</head>
<body>

    <table  border="1" width="600"  height="400" align="center" 
    bgcolor="pink" cellspacing="0" cellpadding="0">

    <caption><h2>中国著名演员</h2></caption>

    <tr>
        <th>姓名</th>
        <th>年龄</th>
        <th>性别</th>
        <th>城市</th>
    </tr>

    <tr align="center">
        <td>李冰冰</td>
        <td>32</td>
        <td></td>
        <td>北京</td>
    </tr>

    <tr align="center">
        <td>范冰冰</td>
        <td>20</td>
        <td></td>
        <td>上海</td>
    </tr>

    <tr align="center">
        <td>刘德华</td>
        <td>47</td>
        <td></td>
        <td>香港</td>
    </tr>

</table>
</body>
</html>

运行代码后,在浏览器中的效果图如下所示:

这里写图片描述

在该示例中,我们还用到了不少与表格标签相关的属性,比如利用border表示表格的边框,利用cellspacing 设置单元格之间的距离,利用cellpadding设置单元格中文字距离单元格边框的距离,利用align设置对齐方式,利用bgcolor设置背景颜色。请注意:刚才提到的这些属性在THML5中建议开发人员不再在HTML中直接使用,而应该把这些与样式相关的属性全部放到CSSS中去。

作者:lfdfhl 发表于2017/11/27 9:21:18 原文链接
阅读:53 评论:0 查看评论

讲给Android程序员看的前端教程(08)——HTML5标签(7)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们继续HTML5标签的学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。

之前,我们已经学习了HTML常用标签、HTML文本标签、HTML语义标签、HTML结构标签、HTML列表标签、HTML表格标签;现在我们来继续学习HTML表单标签。


HTML表单标签

HTML表单用于搜集用户输入的不同类型的数据并将其上传至服务端。嗯哼,了解完表单的作用,我们就来一起学习表单中最常用的标签。

input标签

input标签是表单中功能最丰富的标签,以下几种输入元素均可使用input实现。

  • 单行文本框
    只需将input标签的type属性设置为text即可

  • 密码输入框
    只需将input标签的type属性设置为password即可

  • 数字输入框
    只需将input标签的type属性设置为number即可

  • 邮箱输入框
    只需将input标签的type属性设置为email即可

  • 日期输入框
    只需将input标签的type属性设置为date即可

  • 时间输入框
    只需将input标签的type属性设置为time即可

  • 颜色输入框
    只需将input标签的type属性设置为color即可

  • 单选框
    只需将input标签的type属性设置为radio即可

  • 复选框
    只需将input标签的type属性设置为checkbox即可

  • 文件上传
    只需将input标签的type属性设置为file即可

  • 提交
    只需将input标签的type属性设置为submit即可

  • 重置
    只需将input标签的type属性设置为reset即可

select和option标签

利用select和option标签可实现下拉选择,比如用户注册时的省份选择。

textarea标签

利用textarea标签可在HTML中创建供用户输入的文本区域


表单的提交

嗯哼,利用刚才提到的这些标签就可以实现简单的表单页面了;在此之后我们需要将表单提交至服务器。在此介绍与表单提交有关的几个属性。

action

处理表单数据的服务器地址

method

提交表单的方式,常用的为get和post

enctype

enctype表示将表单数据发送到服务器之前对表单数据进行编码。它有三种取值:

  • application/x-www-form-urlencoded:此为默认方式,在发送数据前将数据中的特殊字符进行URL编码处理。比如,将空格变为+号,将特殊符号转换为 ASCII HEX 值。

  • text/plain:该取值的作用与application/x-www-form-urlencoded非常类似,它也将表示将空格转换为 “+” 加号,但不对特殊字符编码

  • multipart/form-data:表示不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。

    其实,这和我们之前写Android代码是非常类似的,是不是觉得很眼熟?比如,在APP中上传图片,我们会设置:

    multipartBodyBuilder.setType(MultipartBody.FORM);

    点开源码就会发现MultipartBody.FORM的值正是multipart/form-data.所以,这不是什么新鲜玩意,它是我们的老朋友啦!

target

提交表单数据后,服务器会作出相应的响应;所以,我们可以在浏览器中显示服务器返回的数据。那么,是在原来的窗口显示数据呢?还是新打开一个窗口呢?此时可通过target属性来指定显示方式。target属性值常用的有:

  • _self
    它表示在原窗口中显示数据

  • _blank
    它表示在新窗口中显示数据


表单示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML表单</title>
</head>
<body>
    <form id="userform" action="your url" method="post" 
    title="用户注册表单" target="_self" enctype="multipart/form-data">
        <fieldset>
            <legend>用户注册信息</legend>
            <br>
            昵称:<input type="text" name="un" maxlength="15" value="Tom">
            <br>
            <br>
            密码:<input type="password" name="pw" maxlength="10">
            <br>
            <br>
            性别:<input type="radio" name="gender" value="m" checked="checked"><input type="radio" name="gender" value="w"><br>
            <br>
            头像:<input id="userphoto" type="file" name="profile">
            <br>
            <br>
            籍贯:<select name="province">
                <option >河北</option>
                <option >辽宁</option>
                <option >吉林</option>
                <option >云南</option>
                <option selected="selected">广西</option>
            </select>
            <br>
            <br>
            爱好:<input name="hobby" type="checkbox">读书
            <input name="hobby" type="checkbox">写字
            <input name="hobby" type="checkbox" checked="checked">弹琴
            <br>
            <br>
            个人简介:
            <br>
            <br>
            <textarea name="introduce" cols="30" rows="10">请在此输入简介</textarea>
            <br>
            <br>
            个人网站:<input name="userurl" type="url">
            <br>
            <br>
            个人邮箱:<input name="useremail" type="email">
            <br>
            <br>
            身体体重:<input name="userweight" type="number">
            <br>
            <br>
            出生日期:<input name="userdate" type="date">
            <br>
            <br>
            详细时间:<input name="usertime" type="time">
            <br>
            <br>
            性格颜色:<input type="color" name="usercolor">
            <br>
            <br>    
            <input type="submit" value="开始注册">
            <input type="reset" value="重置信息">
            <br>
            <br>

        </fieldset>
    </form>

</body>
</html>

运行后效果图如下所示:

这里写图片描述

结合刚才的示例,在此强调一些需要注意的地方:

  • 上传的表单中含有文件时,请选用post方式提交

  • 上传的表单中含有文件时,请将enctype属性值设置为multipart/form-data

  • 利用多个input标签组合在一起实现单选时,请将它们的 type均设置为radio;并将它们的name均设置为同一值。多选的情况,亦类似;不再赘述


HTML5中表单的新特性

form属性

在HTML5之前,所有的表单标签都必须放在form标签中。但是,在HTML5中新增了form属性,用于表示该标签所属的form标签。所以,每个标签不必必须放在form标签中也能成为表单的一部分,只需把该标签的form属性的值设置为其所属表单的id即可。例如,在刚才的示例中再添加一个输入框用于记录毕业院校:

毕业院校:<input type="text" name="school" form="userform">

代码如上所示,那么该input标签也属于了userform表单;亦会被提交至服务端。

datalist标签

datalist标签用于展示文本框与下拉菜单组合在一起的效果,请注意datalist的id值必须是form表单的list属性值。请看如下示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>datalist标签</title>
</head>
<body>
    <form action="url" method="get">
        请输入你最喜欢的女明星:<input type="text" name="name" list="namesList">
    </form>
    <datalist id="namesList">
        <option value="lbb">李冰冰</option>
        <option value="fbb">范冰冰</option>
        <option value="gyy">高圆圆</option>
    </datalist>
</body>
</html>

运行后效果如下图所示:

这里写图片描述

formxxxx属性

为了更加方便的操控表单标签,在HTML5中新增了几个formxxxx属性,简介如下:

  • formaction属性用于指定表单提交的地址

  • formmethod属性用于指定表单提交的方式

  • formtarget属性用于指定打开服务端响应URL的方式

  • formenctype属性用于指定表单数据提交时的编码方式

请看如下示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML表单中的formxxx属性</title>
</head>
<body>
    <form>
        username:<input type="text" name="un">
        <br>
        <br>
        password:<input type="password" name="pw">
        <br>
        <br>
        <input type="submit" value="注册" formaction="regist url" formmethod="get" formtarget="_self" formenctype="application/x-www-form-urlencoded" >

        <input type="submit" value="登录" formaction="login url" formmethod="post" formtarget="_blank" formenctype="multipart/form-data">
    </form>
</body>
</html>

在该示例中,有两个功能:登录和注册;不同的功能那么就有不同的action、method、target、enctype。在此通过formaction、formmethod、formtarget、formenctype属性灵活指定了在不同的操作下不同的表单提交方式。

作者:lfdfhl 发表于2017/11/27 9:23:23 原文链接
阅读:69 评论:0 查看评论

讲给Android程序员看的前端教程(09)——HTML5标签(8)

$
0
0

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


版权声明


课程安排

HTML不是程序设计语言,而是一种标记语言,它用一些标记、标签来说明文本的显示效果。要制作网页和建立网站,就必须对HTML语言有所了解。客观地讲:HTML标签没有多少逻辑性而言,为了大家更好的理解和记忆,我们对这些标签进行分门别类的讲解,主要分为:

  • HTML常用标签

  • HTML文本标签

  • HTML语义标签

  • HTML结构标签

  • HTML列表标签

  • HTML表格标签

  • HTML表单标签

  • HTML新增标签和API

嗯哼,来吧,我们继续HTML5标签的学习。咋了?你竟然有点担心学不会!?别逗了,这部分和我们常写的Java比起来简单多了,它的逻辑性也弱得多。所以,大胸弟你大可放心,就像看电影那样:一手拿着爆米花,一手拿着可乐,也完全可以看懂本教程。

之前,我们已经学习了HTML常用标签、HTML文本标签、HTML语义标签、HTML结构标签、HTML列表标签、HTML表格标签、HTML表单标签;今天我们来瞅瞅HTML5中新增的标签和一些好玩又有用的API。


HTML5新增标签

meter标签

meter标签用于表示度量结果,请看如下示例:

笔记本剩余电量:<meter value="7" min="0" max="10"></meter>

运行后结果如下图所示:

这里写图片描述

progress标签

progress标签用于表示进度,请看如下示例:

本月已完成工作:<progress value="80" max="100"></progress>

运行后结果如下图所示:

这里写图片描述

audio标签和video标签

在HTML5之前若想在网页中播放音频和视频都需要借助第三方插件。现在,HTML5直接提供了audio标签和video标签实现音频(推荐采用ogg格式),视频(推荐采用VP8格式)的播放。请看如下示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML的多媒体标签</title>
</head>
<body>

    <h3>利用audio标签播放音频</h3>
    <audio src="word.mp3" controls="true">
        当您看到这行文字时,意味着您的设备不支持audio标签
    </audio>

    <br>
    <br>

    <h3>利用video标签播放视频</h3>
    <video src="movie.mp4" controls="true">
        当您看到这行文字时,意味着您的设备不支持video标签
    </video>
</body>
</html>

运行后界面效果如下图所示:

这里写图片描述


HTML5强大的API

在HTML5中融入了众多非常实用的功能,比如:控件的拖拽,绘图,多媒体,地理位置,网络状态,数据存储,全屏等等。这部分功能多涉及到JavaScript,但是呢?嘿嘿,我们还没有讲JavaScript呢!在此,我们先体验一把,待我们学完JavaScript再来深入学习这部分知识。

HTML5监听网络状态

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>HTML监听网络状态</title>
</head>

<body>
    <script type="text/javascript">

    window.addEventListener('online', function() {
        alert('网络连接已建立!');
    });

    window.addEventListener('offline', function() {
        alert('网络连接已断开!');
    })

    </script>
</body>

</html>

这玩意儿咋们熟悉不?太熟悉了!咋们在Android里面是不是可以通过监听系统广播判断网络的状态?!

HTML5定位功能

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>HTML5定位</title>
</head>
<body>
    <script>
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
    } else {
        alert("您的浏览器不支持地理定位");
    }

    // 获取地理位置成功的回调函数
    function successCallback(position) {
        var longitude = position.coords.longitude;
        var latitude = position.coords.latitude;
        alert("经度=" + longitude + ",纬度=" + latitude);
    }

    // 获取地理位置失败的回调函数
    function errorCallback(error) {
        alert("获取用户位置失败");
    }
    </script>
</body>
</html>

这玩意陌生不?一点都不陌生!我们在Android里面也经常利用高德地图,百度地图实现定位功能;在HTML5中依然可以!

作者:lfdfhl 发表于2017/11/27 9:25:46 原文链接
阅读:72 评论:0 查看评论

流式处理界的新贵 Kafka Stream - Kafka设计解析(七)

$
0
0

原创文章,首发自作者个人博客,转载请务必将下面这段话置于文章开头处。
本文转发自技术世界原文链接 http://www.jasongj.com/kafka/kafka_stream/

1 Kafka Stream背景

1.1 Kafka Stream是什么

Kafka Stream是Apache Kafka从0.10版本引入的一个新Feature。它是提供了对存储于Kafka内的数据进行流式处理和分析的功能。

Kafka Stream的特点如下:

  • Kafka Stream提供了一个非常简单而轻量的Library,它可以非常方便地嵌入任意Java应用中,也可以任意方式打包和部署
  • 除了Kafka外,无任何外部依赖
  • 充分利用Kafka分区机制实现水平扩展和顺序性保证
  • 通过可容错的state store实现高效的状态操作(如windowed join和aggregation)
  • 支持正好一次处理语义
  • 提供记录级的处理能力,从而实现毫秒级的低延迟
  • 支持基于事件时间的窗口操作,并且可处理晚到的数据(late arrival of records)
  • 同时提供底层的处理原语Processor(类似于Storm的spout和bolt),以及高层抽象的DSL(类似于Spark的map/group/reduce)

1.2 什么是流式计算

一般流式计算会与批量计算相比较。在流式计算模型中,输入是持续的,可以认为在时间上是无界的,也就意味着,永远拿不到全量数据去做计算。同时,计算结果是持续输出的,也即计算结果在时间上也是无界的。流式计算一般对实时性要求较高,同时一般是先定义目标计算,然后数据到来之后将计算逻辑应用于数据。同时为了提高计算效率,往往尽可能采用增量计算代替全量计算。
Stream Processing

批量处理模型中,一般先有全量数据集,然后定义计算逻辑,并将计算应用于全量数据。特点是全量计算,并且计算结果一次性全量输出。
Batch Processing

1.3 为什么要有Kafka Stream

当前已经有非常多的流式处理系统,最知名且应用最多的开源流式处理系统有Spark Streaming和Apache Storm。Apache Storm发展多年,应用广泛,提供记录级别的处理能力,当前也支持SQL on Stream。而Spark Streaming基于Apache Spark,可以非常方便与图计算,SQL处理等集成,功能强大,对于熟悉其它Spark应用开发的用户而言使用门槛低。另外,目前主流的Hadoop发行版,如MapR,Cloudera和Hortonworks,都集成了Apache Storm和Apache Spark,使得部署更容易。

既然Apache Spark与Apache Storm拥用如此多的优势,那为何还需要Kafka Stream呢?笔者认为主要有如下原因。

第一,Spark和Storm都是流式处理框架,而Kafka Stream提供的是一个基于Kafka的流式处理类库。框架要求开发者按照特定的方式去开发逻辑部分,供框架调用。开发者很难了解框架的具体运行方式,从而使得调试成本高,并且使用受限。而Kafka Stream作为流式处理类库,直接提供具体的类给开发者调用,整个应用的运行方式主要由开发者控制,方便使用和调试。
Library vs. Framework

第二,虽然Cloudera与Hortonworks方便了Storm和Spark的部署,但是这些框架的部署仍然相对复杂。而Kafka Stream作为类库,可以非常方便的嵌入应用程序中,它对应用的打包和部署基本没有任何要求。更为重要的是,Kafka Stream充分利用了Kafka的分区机制Consumer的Rebalance机制,使得Kafka Stream可以非常方便的水平扩展,并且各个实例可以使用不同的部署方式。具体来说,每个运行Kafka Stream的应用程序实例都包含了Kafka Consumer实例,多个同一应用的实例之间并行处理数据集。而不同实例之间的部署方式并不要求一致,比如部分实例可以运行在Web容器中,部分实例可运行在Docker或Kubernetes中。

第三,就流式处理系统而言,基本都支持Kafka作为数据源。例如Storm具有专门的kafka-spout,而Spark也提供专门的spark-streaming-kafka模块。事实上,Kafka基本上是主流的流式处理系统的标准数据源。换言之,大部分流式系统中都已部署了Kafka,此时使用Kafka Stream的成本非常低。

第四,使用Storm或Spark Streaming时,需要为框架本身的进程预留资源,如Storm的supervisor和Spark on YARN的node manager。即使对于应用实例而言,框架本身也会占用部分资源,如Spark Streaming需要为shuffle和storage预留内存。

第五,由于Kafka本身提供数据持久化,因此Kafka Stream提供滚动部署和滚动升级以及重新计算的能力。

第六,由于Kafka Consumer Rebalance机制,Kafka Stream可以在线动态调整并行度。

2 Kafka Stream架构

2.1 Kafka Stream整体架构

Kafka Stream的整体架构图如下所示。
Kafka Stream Architecture

目前(Kafka 0.11.0.0)Kafka Stream的数据源只能如上图所示是Kafka。但是处理结果并不一定要如上图所示输出到Kafka。实际上KStream和Ktable的实例化都需要指定Topic。

KStream<String, String> stream = builder.stream("words-stream");

KTable<String, String> table = builder.table("words-table", "words-store");

另外,上图中的Consumer和Producer并不需要开发者在应用中显示实例化,而是由Kafka Stream根据参数隐式实例化和管理,从而降低了使用门槛。开发者只需要专注于开发核心业务逻辑,也即上图中Task内的部分。

2.2 Processor Topology

基于Kafka Stream的流式应用的业务逻辑全部通过一个被称为Processor Topology的地方执行。它与Storm的Topology和Spark的DAG类似,都定义了数据在各个处理单元(在Kafka Stream中被称作Processor)间的流动方式,或者说定义了数据的处理逻辑。

下面是一个Processor的示例,它实现了Word Count功能,并且每秒输出一次结果。

public class WordCountProcessor implements Processor<String, String> {

  private ProcessorContext context;
  private KeyValueStore<String, Integer> kvStore;

  @SuppressWarnings("unchecked")
  @Override
  public void init(ProcessorContext context) {
    this.context = context;
    this.context.schedule(1000);
    this.kvStore = (KeyValueStore<String, Integer>) context.getStateStore("Counts");
  }

  @Override
  public void process(String key, String value) {
    Stream.of(value.toLowerCase().split(" ")).forEach((String word) -> {
      Optional<Integer> counts = Optional.ofNullable(kvStore.get(word));
      int count = counts.map(wordcount -> wordcount + 1).orElse(1);
      kvStore.put(word, count);
    });
  }

  @Override
  public void punctuate(long timestamp) {
    KeyValueIterator<String, Integer> iterator = this.kvStore.all();
    iterator.forEachRemaining(entry -> {
      context.forward(entry.key, entry.value);
      this.kvStore.delete(entry.key);
    });
    context.commit();
  }

  @Override
  public void close() {
    this.kvStore.close();
  }

}

从上述代码中可见

  • process定义了对每条记录的处理逻辑,也印证了Kafka可具有记录级的数据处理能力。
  • context.scheduler定义了punctuate被执行的周期,从而提供了实现窗口操作的能力。
  • context.getStateStore提供的状态存储为有状态计算(如窗口,聚合)提供了可能。

2.3 Kafka Stream并行模型

Kafka Stream的并行模型中,最小粒度为Task,而每个Task包含一个特定子Topology的所有Processor。因此每个Task所执行的代码完全一样,唯一的不同在于所处理的数据集互补。这一点跟Storm的Topology完全不一样。Storm的Topology的每一个Task只包含一个Spout或Bolt的实例。因此Storm的一个Topology内的不同Task之间需要通过网络通信传递数据,而Kafka Stream的Task包含了完整的子Topology,所以Task之间不需要传递数据,也就不需要网络通信。这一点降低了系统复杂度,也提高了处理效率。

如果某个Stream的输入Topic有多个(比如2个Topic,1个Partition数为4,另一个Partition数为3),则总的Task数等于Partition数最多的那个Topic的Partition数(max(4,3)=4)。这是因为Kafka Stream使用了Consumer的Rebalance机制,每个Partition对应一个Task。

下图展示了在一个进程(Instance)中以2个Topic(Partition数均为4)为数据源的Kafka Stream应用的并行模型。从图中可以看到,由于Kafka Stream应用的默认线程数为1,所以4个Task全部在一个线程中运行。
1 thread

为了充分利用多线程的优势,可以设置Kafka Stream的线程数。下图展示了线程数为2时的并行模型。
2 threads

前文有提到,Kafka Stream可被嵌入任意Java应用(理论上基于JVM的应用都可以)中,下图展示了在同一台机器的不同进程中同时启动同一Kafka Stream应用时的并行模型。注意,这里要保证两个进程的StreamsConfig.APPLICATION_ID_CONFIG完全一样。因为Kafka Stream将APPLICATION_ID_CONFIG作为隐式启动的Consumer的Group ID。只有保证APPLICATION_ID_CONFIG相同,才能保证这两个进程的Consumer属于同一个Group,从而可以通过Consumer Rebalance机制拿到互补的数据集。
2 instances

既然实现了多进程部署,可以以同样的方式实现多机器部署。该部署方式也要求所有进程的APPLICATION_ID_CONFIG完全一样。从图上也可以看到,每个实例中的线程数并不要求一样。但是无论如何部署,Task总数总会保证一致。
2 servers

注意:Kafka Stream的并行模型,非常依赖于《Kafka设计解析(一)- Kafka背景及架构介绍》一文中介绍的Kafka分区机制和《Kafka设计解析(四)- Kafka Consumer设计解析》中介绍的Consumer的Rebalance机制。强烈建议不太熟悉这两种机制的朋友,先行阅读这两篇文章。

这里对比一下Kafka Stream的Processor Topology与Storm的Topology。

  • Storm的Topology由Spout和Bolt组成,Spout提供数据源,而Bolt提供计算和数据导出。Kafka Stream的Processor Topology完全由Processor组成,因为它的数据固定由Kafka的Topic提供。
  • Storm的不同Bolt运行在不同的Executor中,很可能位于不同的机器,需要通过网络通信传输数据。而Kafka Stream的Processor Topology的不同Processor完全运行于同一个Task中,也就完全处于同一个线程,无需网络通信。
  • Storm的Topology可以同时包含Shuffle部分和非Shuffle部分,并且往往一个Topology就是一个完整的应用。而Kafka Stream的一个物理Topology只包含非Shuffle部分,而Shuffle部分需要通过through操作显示完成,该操作将一个大的Topology分成了2个子Topology。
  • Storm的Topology内,不同Bolt/Spout的并行度可以不一样,而Kafka Stream的子Topology内,所有Processor的并行度完全一样。
  • Storm的一个Task只包含一个Spout或者Bolt的实例,而Kafka Stream的一个Task包含了一个子Topology的所有Processor。

2.4 KTable vs. KStream

KTable和KStream是Kafka Stream中非常重要的两个概念,它们是Kafka实现各种语义的基础。因此这里有必要分析下二者的区别。

KStream是一个数据流,可以认为所有记录都通过Insert only的方式插入进这个数据流里。而KTable代表一个完整的数据集,可以理解为数据库中的表。由于每条记录都是Key-Value对,这里可以将Key理解为数据库中的Primary Key,而Value可以理解为一行记录。可以认为KTable中的数据都是通过Update only的方式进入的。也就意味着,如果KTable对应的Topic中新进入的数据的Key已经存在,那么从KTable只会取出同一Key对应的最后一条数据,相当于新的数据更新了旧的数据。

以下图为例,假设有一个KStream和KTable,基于同一个Topic创建,并且该Topic中包含如下图所示5条数据。此时遍历KStream将得到与Topic内数据完全一样的所有5条数据,且顺序不变。而此时遍历KTable时,因为这5条记录中有3个不同的Key,所以将得到3条记录,每个Key对应最新的值,并且这三条数据之间的顺序与原来在Topic中的顺序保持一致。这一点与Kafka的日志compact相同。
KStream vs. KTable

此时如果对该KStream和KTable分别基于key做Group,对Value进行Sum,得到的结果将会不同。对KStream的计算结果是<Jack,4><Lily,7><Mike,4>。而对Ktable的计算结果是<Mike,4><Jack,3><Lily,5>

2.5 State store

流式处理中,部分操作是无状态的,例如过滤操作(Kafka Stream DSL中用filer方法实现)。而部分操作是有状态的,需要记录中间状态,如Window操作和聚合计算。State store被用来存储中间状态。它可以是一个持久化的Key-Value存储,也可以是内存中的HashMap,或者是数据库。Kafka提供了基于Topic的状态存储。

Topic中存储的数据记录本身是Key-Value形式的,同时Kafka的log compaction机制可对历史数据做compact操作,保留每个Key对应的最后一个Value,从而在保证Key不丢失的前提下,减少总数据量,从而提高查询效率。

构造KTable时,需要指定其state store name。默认情况下,该名字也即用于存储该KTable的状态的Topic的名字,遍历KTable的过程,实际就是遍历它对应的state store,或者说遍历Topic的所有key,并取每个Key最新值的过程。为了使得该过程更加高效,默认情况下会对该Topic进行compact操作。

另外,除了KTable,所有状态计算,都需要指定state store name,从而记录中间状态。

3 Kafka Stream如何解决流式系统中关键问题

3.1 时间

在流式数据处理中,时间是数据的一个非常重要的属性。从Kafka 0.10开始,每条记录除了Key和Value外,还增加了 timestamp 属性。目前Kafka Stream支持三种时间

  • 事件发生时间。事件发生的时间,包含在数据记录中。发生时间由Producer在构造ProducerRecord时指定。并且需要Broker或者Topic将 message.timestamp.type 设置为 CreateTime(默认值)才能生效。
  • 消息接收时间,也即消息存入Broker的时间。当Broker或Topic将message.timestamp.type设置为LogAppendTime时生效。此时Broker会在接收到消息后,存入磁盘前,将其 timestamp 属性值设置为当前机器时间。一般消息接收时间比较接近于事件发生时间,部分场景下可代替事件发生时间。
  • 消息处理时间,也即Kafka Stream处理消息时的时间。

注:Kafka Stream允许通过实`org.apache.kafka.streams.processor.TimestampExtractor接口自定义记录时间。

3.2 窗口

前文提到,流式数据是在时间上无界的数据。而聚合操作只能作用在特定的数据集,也即有界的数据集上。因此需要通过某种方式从无界的数据集上按特定的语义选取出有界的数据。窗口是一种非常常用的设定计算边界的方式。不同的流式处理系统支持的窗口类似,但不尽相同。

Kafka Stream支持的窗口如下。

  • Hopping Time Window 该窗口定义如下图所示。它有两个属性,一个是Window size,一个是Advance interval。Window size指定了窗口的大小,也即每次计算的数据集的大小。而Advance interval定义输出的时间间隔。一个典型的应用场景是,每隔5秒钟输出一次过去1个小时内网站的PV或者UV。
    Hopping Time Window

  • Tumbling Time Window 该窗口定义如下图所示。可以认为它是Hopping Time Window的一种特例,也即Window size和Advance interval相等。它的特点是各个Window之间完全不相交。
    ![Tumbling Time Window](http://www.jasongj.com/img/kafka/KafkaColumn7/Tumbling Time Window.gif)

  • Sliding Window 该窗口只用于2个KStream进行Join计算时。该窗口的大小定义了Join两侧KStream的数据记录被认为在同一个窗口的最大时间差。假设该窗口的大小为5秒,则参与Join的2个KStream中,记录时间差小于5的记录被认为在同一个窗口中,可以进行Join计算。

  • Session Window 该窗口用于对Key做Group后的聚合操作中。它需要对Key做分组,然后对组内的数据根据业务需求定义一个窗口的起始点和结束点。一个典型的案例是,希望通过Session Window计算某个用户访问网站的时间。对于一个特定的用户(用Key表示)而言,当发生登录操作时,该用户(Key)的窗口即开始,当发生退出操作或者超时时,该用户(Key)的窗口即结束。窗口结束时,可计算该用户的访问时间或者点击次数等。

3.3 Join

Kafka Stream由于包含KStream和Ktable两种数据集,因此提供如下Join计算

  • KTable Join KTable 结果仍为KTable。任意一边有更新,结果KTable都会更新。
  • KStream Join KStream 结果为KStream。必须带窗口操作,否则会造成Join操作一直不结束。
  • KStream Join KTable / GlobalKTable 结果为KStream。只有当KStream中有新数据时,才会触发Join计算并输出结果。KStream无新数据时,KTable的更新并不会触发Join计算,也不会输出数据。并且该更新只对下次Join生效。一个典型的使用场景是,KStream中的订单信息与KTable中的用户信息做关联计算。

对于Join操作,如果要得到正确的计算结果,需要保证参与Join的KTable或KStream中Key相同的数据被分配到同一个Task。具体方法是

  • 参与Join的KTable或KStream的Key类型相同(实际上,业务含意也应该相同)
  • 参与Join的KTable或KStream对应的Topic的Partition数相同
  • Partitioner策略的最终结果等效(实现不需要完全一样,只要效果一样即可),也即Key相同的情况下,被分配到ID相同的Partition内

如果上述条件不满足,可通过调用如下方法使得它满足上述条件。

KStream<K, V> through(Serde<K> keySerde, Serde<V> valSerde, StreamPartitioner<K, V> partitioner, String topic)

3.4 聚合与乱序处理

聚合操作可应用于KStream和KTable。当聚合发生在KStream上时必须指定窗口,从而限定计算的目标数据集。

需要说明的是,聚合操作的结果肯定是KTable。因为KTable是可更新的,可以在晚到的数据到来时(也即发生数据乱序时)更新结果KTable。

这里举例说明。假设对KStream以5秒为窗口大小,进行Tumbling Time Window上的Count操作。并且KStream先后出现时间为1秒, 3秒, 5秒的数据,此时5秒的窗口已达上限,Kafka Stream关闭该窗口,触发Count操作并将结果3输出到KTable中(假设该结果表示为<1-5,3>)。若1秒后,又收到了时间为2秒的记录,由于1-5秒的窗口已关闭,若直接抛弃该数据,则可认为之前的结果<1-5,3>不准确。而如果直接将完整的结果<1-5,4>输出到KStream中,则KStream中将会包含该窗口的2条记录,<1-5,3>, <1-5,4>,也会存在肮数据。因此Kafka Stream选择将聚合结果存于KTable中,此时新的结果<1-5,4>会替代旧的结果<1-5,3>。用户可得到完整的正确的结果。

这种方式保证了数据准确性,同时也提高了容错性。

但需要说明的是,Kafka Stream并不会对所有晚到的数据都重新计算并更新结果集,而是让用户设置一个retention period,将每个窗口的结果集在内存中保留一定时间,该窗口内的数据晚到时,直接合并计算,并更新结果KTable。超过retention period后,该窗口结果将从内存中删除,并且晚到的数据即使落入窗口,也会被直接丢弃。

3.5 容错

Kafka Stream从如下几个方面进行容错

  • 高可用的Partition保证无数据丢失。每个Task计算一个Partition,而Kafka数据复制机制保证了Partition内数据的高可用性,故无数据丢失风险。同时由于数据是持久化的,即使任务失败,依然可以重新计算。
  • 状态存储实现快速故障恢复和从故障点继续处理。对于Join和聚合及窗口等有状态计算,状态存储可保存中间状态。即使发生Failover或Consumer Rebalance,仍然可以通过状态存储恢复中间状态,从而可以继续从Failover或Consumer Rebalance前的点继续计算。
  • KTable与retention period提供了对乱序数据的处理能力。

4 Kafka Stream应用示例

下面结合一个案例来讲解如何开发Kafka Stream应用。本例完整代码可从作者Github获取。

订单KStream(名为orderStream),底层Topic的Partition数为3,Key为用户名,Value包含用户名,商品名,订单时间,数量。用户KTable(名为userTable),底层Topic的Partition数为3,Key为用户名,Value包含性别,地址和年龄。商品KTable(名为itemTable),底层Topic的Partition数为6,Key为商品名,价格,种类和产地。现在希望计算每小时购买产地与自己所在地相同的用户总数。

首先由于希望使用订单时间,而它包含在orderStream的Value中,需要通过提供一个实现TimestampExtractor接口的类从orderStream对应的Topic中抽取出订单时间。

public class OrderTimestampExtractor implements TimestampExtractor {

  @Override
  public long extract(ConsumerRecord<Object, Object> record) {
    if(record instanceof Order) {
      return ((Order)record).getTS();
    } else {
      return 0;
    }
  }
}

接着通过将orderStream与userTable进行Join,来获取订单用户所在地。由于二者对应的Topic的Partition数相同,且Key都为用户名,再假设Producer往这两个Topic写数据时所用的Partitioner实现相同,则此时上文所述Join条件满足,可直接进行Join。

orderUserStream = orderStream
    .leftJoin(userTable, 
         // 该lamda表达式定义了如何从orderStream与userTable生成结果集的Value
        (Order order, User user) -> OrderUser.fromOrderUser(order, user), 
         // 结果集Key序列化方式
        Serdes.String(),
         // 结果集Value序列化方式
         SerdesFactory.serdFrom(Order.class))
    .filter((String userName, OrderUser orderUser) -> orderUser.userAddress != null)

从上述代码中,可以看到,Join时需要指定如何从参与Join双方的记录生成结果记录的Value。Key不需要指定,因为结果记录的Key与Join Key相同,故无须指定。Join结果存于名为orderUserStream的KStream中。

接下来需要将orderUserStream与itemTable进行Join,从而获取商品产地。此时orderUserStream的Key仍为用户名,而itemTable对应的Topic的Key为产品名,并且二者的Partition数不一样,因此无法直接Join。此时需要通过through方法,对其中一方或双方进行重新分区,使得二者满足Join条件。这一过程相当于Spark的Shuffle过程和Storm的FieldGrouping。

orderUserStrea
    .through(
        // Key的序列化方式
        Serdes.String(),
        // Value的序列化方式 
        SerdesFactory.serdFrom(OrderUser.class), 
        // 重新按照商品名进行分区,具体取商品名的哈希值,然后对分区数取模
        (String key, OrderUser orderUser, int numPartitions) -> (orderUser.getItemName().hashCode() & 0x7FFFFFFF) % numPartitions, 
        "orderuser-repartition-by-item")
    .leftJoin(itemTable, (OrderUser orderUser, Item item) -> OrderUserItem.fromOrderUser(orderUser, item), Serdes.String(), SerdesFactory.serdFrom(OrderUser.class))

从上述代码可见,through时需要指定Key的序列化器,Value的序列化器,以及分区方式和结果集所在的Topic。这里要注意,该Topic(orderuser-repartition-by-item)的Partition数必须与itemTable对应Topic的Partition数相同,并且through使用的分区方法必须与iteamTable对应Topic的分区方式一样。经过这种through操作,orderUserStream与itemTable满足了Join条件,可直接进行Join。

5总结

  • Kafka Stream的并行模型完全基于Kafka的分区机制和Rebalance机制,实现了在线动态调整并行度
  • 同一Task包含了一个子Topology的所有Processor,使得所有处理逻辑都在同一线程内完成,避免了不必的网络通信开销,从而提高了效率。
  • through方法提供了类似Spark的Shuffle机制,为使用不同分区策略的数据提供了Join的可能
  • log compact提高了基于Kafka的state store的加载效率
  • state store为状态计算提供了可能
  • 基于offset的计算进度管理以及基于state store的中间状态管理为发生Consumer rebalance或Failover时从断点处继续处理提供了可能,并为系统容错性提供了保障
  • KTable的引入,使得聚合计算拥用了处理乱序问题的能力

6 Kafka系列文章

作者:Habren 发表于2017/11/27 9:29:12 原文链接
阅读:70 评论:0 查看评论

数据库六:浅谈数据库的多版本并发控制(MVCC)

$
0
0

多版本并发控制
Multi-Version Concurrency Control

基础理解

事务T1改变数据V1,将其改为数据V2,在堆中,数据如下图

图1

事务T3改变了V2,将其改为V3,在堆中,数据如下图:目前事务T2还在活动中,所以V1和V2属于recently dead状态,而不是真的dead状态。

图2:

从可视性而言,事务T0只能看到数据V1。因为它早于事务T1启动。

图3:

事务T1提交后,事务T2启动,此时事务T3尚未启动,故T2可以看到T1提交后的数据V2。

图4:

事务T3提交后,事务T4启动,故T4只能看到数据V3。

图5:

前面说过,当还有事务活动中访问数据V1和V2,V1和V2的状态是recently dead。

当T0和T2都结束,已经没有事务在访问数据V1和V2了,此时V1和V2为dead状态,所以V1和V2都成为VACUUM的处理对象了。

图6:

设计与实现

数据存储格式

图7

其中版本信息的每一个部分都将用64bits来存储。

Timestamp Ordering (MVTO)

图8

上图就包含了数据存储的样例。

只有当TXN-ID,即锁,没有被占有且事务的ID属于BEGIN-TSEND-TS之间才能够读取该数据。

图9

每次一个事务读取的时候,都会更新一下READ-TS。如果有一个事务11先于事务10读取,那么事务10也可以读取该数据,但是不会更新READ-TS

图10

当事务10对数据进行操作的时候,会先占用锁,并创建一个新的版本。之所以要把老的也锁上,是因为需要更新一下END-TS的时间,同时也要更新一下NEXT-VERSION指针。当然,当对旧的数据不需要操作的时候也可以进行释放,但是在这个例子里新的数据的锁会已知保持到事务提交。

图11

版本存储

使用的是VERSION CHAIN,即版本链进行存储。

  • 可以使得DBMS找到对于特定事务可以读取的版本
  • 索引指向的是链表的头

一般情况链表头指最旧的版本,之所以这么设计的原因是,在大部分情况下,查询的次数远大于修改的次数,所以链的长度并不是特别长,而且这样实现也更简单。

线程将会把版本信息存入内存,从而不必每次都要都从磁盘或者数据结构中获得。
不同的存储方式决定用何种方式何种媒介去存储每个版本的信息和数据。

Append-Only Storage

所有物理级别的版本信息都存在一个表空间中。

图12

Time-Travel Storage

分两个表存储

先把旧的版本存入Time Travel 表,Time Travel表又是用链式存储的。

再将新的版本写入。

图13 图14

Delta Storage

有些类似Git管理代码的时候只记录两个版本的查

图15 图16

这样做的好处是,当你在一张有数千个属性的表里修改一个值的时候,你不需要把整个记录记下来,只需要保存修改过的东西。

垃圾回收

DBMS需要随时从数据库中删除可回收的版本(物理存储)。比如,所有激活的事务都不能看到的版本;由终止并回滚的事务产生的版本。

Tuple级别

数据库不需要存储整个table的版本信息,而是对于每次操作,存储其改变的元组的信息。
所以每次commit之后,我们不需要搜索整个table,而是对于我们修改过的元组我们将不必要的东西删除。

图17

线程1的时间戳为12,线程2的时间戳为25,而右侧的版本中,前两个元组的结束时间戳,小于所有线程中时间戳最小的那一个,说明所有激活的事务都看不到前面两个版本,所以将他们删除。

事务级别

事务级别就是对于每个事务,追踪它的读写情况;通过DBMS可以得知有哪些版本永远不会被用到了。

参考资料

  1. http://www.pgcon.org/2008/schedule/attachments/54_PGCon2008-HOT.ppt
  2. http://15721.courses.cs.cmu.edu/spring2017/slides/05-mvcc.pdf
  3. http://15721.courses.cs.cmu.edu/spring2017/schedule.html#jan-31-2017
作者:u013007900 发表于2017/11/27 9:42:43 原文链接
阅读:20 评论:0 查看评论

cocos-lua学习笔记(十一)事件分发机制

$
0
0


一、Quick框架的事件按照功能和用途分为:

  • 节点事件
  • 桢事件
  • 键盘事件
  • 加速计事件
  • 触摸时间


节点事件

 Node进入和退出场景时触发,加入一个层或者其他的Node,添加清楚数据,也会触发。

function MainScene:ctor()

    --事件分发机制-节点事件
    local function createTestScene(name)
        local scene = display.newScene(name)
        scene:addNodeEventListener(cc.NODE_EVENT, function(event) printf("node in scene [%s] NODE_EVENT:%s",name,event.name)end)
        return scene
    end
    self:performWithDelay(function ()
        local scene1 = createTestScene("scene1")
        display.replaceScene(scene1)
        scene1:performWithDelay(function ()
            print("-----------")
            local scene2 = createTestScene("scene2")
            display.replaceScene(scene2)
        end, 1.0)
    end, 1.0)
end


帧事件

每一帧会触发

   --事件分发机制-节点事件
    local function createTestScene(name)
        local scene = display.newScene(name)
        scene:addNodeEventListener(cc.NODE_EVENT, function(event) printf("node in scene [%s] NODE_EVENT:%s",name,event.name)end)
        return scene
    end
    self:performWithDelay(function ()
        local scene1 = createTestScene("scene1")
        display.replaceScene(scene1)
        scene1:performWithDelay(function ()
            print("-----------")
            local scene2 = createTestScene("scene2")
            display.replaceScene(scene2)
        end, 1.0)
    end, 1.0)

键盘事件

监听代码如下:


function MainScene:ctor()
   self:setKeypadEnabled(true)
   self:addNodeEventListener(cc.KEYPAD_EVENT, function(event)
    print("test"..event.key)
end
    )
end


加速计事件

手机上接收 x,y,z三个方向重力感应

    cc.Device:setAccelerometerEnabled(true)
    local function accelerometerListener(event,x,y,z,timestamp)
        print("TestAccelerateEvent"..x..y..z..timestamp)
    end
    local listener = cc.EventListenerAcceleration:create(accelerometerListener)
    self:getEventDispatcher():addEventListenerWithSceneGraphPriority(listener, self)


触摸事件

--单点触摸

local MainScene = class("MainScene", function()
    return display.newScene("MainScene")
end)

function MainScene:ctor()
    --触摸事件:单点触摸
    local node = display.newSprite("dog.png")
    node:addNodeEventListener(cc.NODE_TOUCH_EVENT,function ( event )
        printf("Sprite:%s | x = %0.2f | y = %0.2f",event.name,event.x,event.y)
        if event.name == "began" then
            return true
        end
    end)

    self:addChild(node)
    node:setPosition(display.cx+100,display.cy+100)
    node:setTouchEnabled(true)    
    node:setTouchMode(cc.TOUCH_MODE_ONE_BY_ONE)

end

function MainScene:onEnter()
end

function MainScene:onExit()
end

return MainScene





--多点触摸

    --触摸事件:多点触摸
    local node = display.newSprite("dog.png")
        node:addNodeEventListener(cc.NODE_TOUCH_EVENT,function (event)
        for id,point in pairs(event.points) do
            printf("event [%s] | id = %s | x = %0.2f | y = %0.2f",event.name,id,point.x,point.y)
        end
        if event.name == "began" then
            return true
        end
    end)
    self:addChild(node)

    node:setPosition(display.cx+100,display.cy+100)
    node:setTouchEnabled(true)

    node:setTouchMode(cc.TOUCH_MODE_ALL_AT_ONCE)




二、事件的过程

触摸事件吞噬

Node响应触摸后(在began状态返回true,表示要响应触摸),就会阻止事件继续传递给Node的父对象(更下层的Node),成为事件吞噬

setTouchSwallowEnabled()可以改变这个行为,默认为true。如果为false,则事件还会继续传递

isTouchSwallowEnabled()检查Node的吞噬状态

禁用触摸

setTouchEnabled()是否允许Node响应触摸,默认为false。尽管该节点被禁止,不会影响Node的子节点响应

isTouchEnabled()判断是否允许触摸


setTouchCaptureEnabled()检查是否允许Node捕获触摸,默认为true,当设置为false时,该Node及所有的子Node都无法触摸事件

isTouchCaptureEnabled()检查是否允许捕获触摸


setTouchEnabled只影响当前节点

setTouchCaptureEnabled会影响当前及所有子节点

触摸事件的三个阶段

(1)捕获

(2)触发

(3)冒泡


用一个手指触碰屏幕

    1.遍历所有响应的触摸Node,找出显示层级最高,并且触摸区域包含触摸的位置 的那个Node。该Node称为TargetNode(目标Node)

    2.检查targetnode 的isTouchCaptureEnabled结果,返回false则重复(1) 这个阶段叫capturing

    3.在targetnode触发事件,这个阶段叫targeting

    4.返回false,表示targetnode不响应事件,重复步骤(1)开始查找符合条件的Node

   5.targetnode触发事件完毕,检查isTouchSwallowEnabled。如果为true取消bubbling阶段

    6.从targetnode开始往其父node触发事件,直到某个Node返回false或者事件吞噬,这个阶段叫bubbling。



事件冒泡补充:

当事件发生后,这个事件就要开始传播(从里到外或者从外向里)。为什么要传播呢?因为事件源本身(可能)并没有处理事件的能力,即处理事件的函数(方法)并未绑定在该事件源上。例如我们点击一个按钮时,就会产生一个click事件,但这个按钮本身可能不能处理这个事件,事件必须从这个按钮传播出去,从而到达能够处理这个事件的代码中(例如我们给按钮的onclick属性赋一个函数的名字,就是让这个函数去处理该按钮的click事件),或者按钮的父级绑定有事件函数,当该点击事件发生在按钮上,按钮本身并无处理事件函数,则传播到父级去处理。


作者:hiwoshixiaoyu 发表于2017/11/27 10:27:29 原文链接
阅读:23 评论:0 查看评论

性能BUG检出、定位实例

leetcode- 442. Find All Duplicates in an Array

$
0
0

442. Find All Duplicates in an Array

Given an array of integers, 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once.

Find all the elements that appear twice in this array.

Could you do it without extra space and in O(n) runtime?

Example:

Input:
[4,3,2,7,8,2,3,1]

Output:
[2,3]


题意:

找出数组中出现2次的数(是否能用O(n) 和 没有额外空间占用解决?)

思路:

自己用了Arrays.sort() 然后再遍历数组, nums[i] == nums[i+1] 就是重复的元素了,用了48ms

看了discuss: 因为数组里的元素都是小于数组长度且大于0的,遍历数组,当找到一个数字 i 时,将位置 i-1 处的数字翻转为负数。如果位置 i-1 的数字已经是负数,那么i是两次出现的数字。只需要16ms.

自己的AC:

class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        Arrays.sort(nums);
        List<Integer> list = new ArrayList<>();
        for(int i = 0;i<nums.length-1;i++){
            if(nums[i] == nums[i+1]) list.add(nums[i]);
        }
        return list;
    }
}//48ms

discuss:

class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < nums.length; ++i) {
            int index = Math.abs(nums[i])-1;
            if (nums[index] < 0)
                res.add(Math.abs(index+1));
            nums[index] = -nums[index];
        }
        return res;
    }
}//16ms


作者:qq_32832023 发表于2017/11/27 12:44:43 原文链接
阅读:0 评论:0 查看评论

[bzoj3732][最小生成树][lca]Network

$
0
0

Description

给你N个点的无向图 (1 <= N <= 15,000),记为:1…N。 图中有M条边 (1 <= M <= 30,000)
,第j条边的长度为: d_j ( 1 < = d_j < = 1,000,000,000).

现在有 K个询问 (1 < = K < = 20,000)。 每个询问的格式是:A
B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?

Input

第一行: N, M, K。 第2..M+1行: 三个正整数:X, Y, and D (1 <= X <=N; 1 <= Y <= N).
表示X与Y之间有一条长度为D的边。 第M+2..M+K+1行: 每行两个整数A
B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?

Output

对每个询问,输出最长的边最小值是多少。

Sample Input

6 6 8
1 2 5
2 3 4
3 4 3
1 4 8
2 5 7
4 6 2
1 2
1 3
1 4
2 3
2 4
5 1
6 2
6 1

Sample Output

5
5
5
4
4
7
4
5

HINT

1 <= N <= 15,000

1 <= M <= 30,000

1 <= d_j <= 1,000,000,000

1 <= K <= 15,000

题解

一开始想了一个二分+bfs的做法。。二分最长边长度之后跑bfs,无情TLE
怎么办??很痛苦
让我们再来观察一下这个题。。好像 好像和4242差不多
一个点到另外一个点的最长边最小,那么,就一定在这个图的最小生成树上啊!
所以说。。对边排序后建最小生成树,树上倍增一下最大权,之后lca询问就好。。
4242弱化版我居然还傻逼了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
struct node
{
    int x,y,c,next;
}e[111000],a[111000];int len,lene,last[21000];
void ins(int x,int y,int c){len++;a[len].x=x;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
void insx(int x,int y,int c){lene++;e[lene].x=x;e[lene].y=y;e[lene].c=c;}
bool cmp(node n1,node n2){return n1.c<n2.c;}
int fa[21000];
int findfa(int x)
{
    if(fa[x]!=x)fa[x]=findfa(fa[x]);
    return fa[x];
}
int n,m,K;
int bin[25];
int f[21000][25],dep[21000],maxn[21000][25];
void pre_tree_node(int x)
{
    for(int i=1;i<=20;i++)if(dep[x]>=bin[i])f[x][i]=f[f[x][i-1]][i-1],maxn[x][i]=max(maxn[x][i-1],maxn[f[x][i-1]][i-1]);
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=f[x][0])
        {
            f[y][0]=x;maxn[y][0]=a[k].c;
            dep[y]=dep[x]+1;
            pre_tree_node(y);
        }
    }
}
int sol(int x,int y)
{
    int ret=0;
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)if(dep[f[x][i]]>=dep[y])ret=max(ret,maxn[x][i]),x=f[x][i];
    if(x==y)return ret;
    for(int i=20;i>=0;i--)if(dep[x]>=bin[i] && f[x][i]!=f[y][i])ret=max(ret,max(maxn[x][i],maxn[y][i])),x=f[x][i],y=f[y][i];
    return max(ret,max(maxn[x][0],maxn[y][0]));
}
int main()
{
    bin[0]=1;for(int i=1;i<=20;i++)bin[i]=bin[i-1]*2;
    scanf("%d%d%d",&n,&m,&K);
    len=0;memset(last,0,sizeof(last));
    lene=0;
    for(int i=1;i<=m;i++)
    {
        int x,y,c;
        scanf("%d%d%d",&x,&y,&c);
        insx(x,y,c);
    }
    sort(e+1,e+1+m,cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int p=findfa(e[i].x),q=findfa(e[i].y);
        if(p!=q)
        {
            fa[p]=q;
            ins(e[i].x,e[i].y,e[i].c);
            ins(e[i].y,e[i].x,e[i].c);
        }
    }
    for(int i=1;i<=n;i++)
        if(fa[i]==i)
        {
            f[i][0]=0;dep[i]=1;pre_tree_node(i);
        }
    while(K--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",sol(x,y));
    }
    return 0;
}
作者:Rose_max 发表于2017/11/27 12:50:25 原文链接
阅读:1 评论:0 查看评论

[bzoj1057][dp]棋盘制作

$
0
0

Description

  国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源
于易经的思想,棋盘是一个8*8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。而我们的主人公小Q,
正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定
将棋盘扩大以适应他们的新规则。小Q找到了一张由N*M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种
颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。不过小Q还没有决定是找
一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他
希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。于是小Q找到了即将参加全 国信息学竞赛的你,你能帮助他么?

Input

  第一行包含两个整数N和M,分别表示矩形纸片的长和宽。接下来的N行包含一个N * M的01矩阵,表示这张矩形
纸片的颜色(0表示白色,1表示黑色)。

Output

  包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋
盘的面积(注意正方形和矩形是可以相交或者包含的)。

Sample Input

3 3
1 0 1
0 1 0
1 0 0

Sample Output

4
6

HINT

N, M ≤ 2000

题解

学了一个新东西叫啥悬线法求最大子矩阵的。。可以O(nm)的时间里处理
大致意思就是以每个点为矩阵的底边上任意一个点,向上延伸的高度要尽量长。这个高度就叫做悬线。将悬线分别向左向右扩展,扩展到不能再扩,于是最大子矩阵就出来了
预处理l[i][j]表示(i,j)向左最多能扩展的格子数,r[i][j]表示(i,j)向右最多能扩展的格子数
up[i][j]表示(i,j)向上最多能扩展的格子数,up[i][j]+1就是(i,j)这条悬线的长度
于是我们O(nm)扫一遍就可以了。。答案的计算可以是(sum[i][j][0]+sum[i][j][1]+1)*(up[i][j]+1)。遇见答案大于ans就更新ans
这道题的限制就是如果(i,j)的颜色与(i,j-1)不同,那么左边可以扩展
右边、上面也是同理。
一个坑点:最大的正方形不一定在最大的矩形里

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
int l[2100][2100],r[2100][2100],up[2100][2100];//(i,j)向左 向右最多延伸的格数  悬线长度
int sum[2100][2100][2];//悬线向左/右延伸最长长度 
int a[2100][2100],n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&a[i][j]);
    memset(l,0,sizeof(l));memset(r,0,sizeof(r));
    memset(up,0,sizeof(up));memset(sum,0,sizeof(sum));
    for(int i=1;i<=n;i++)
        for(int j=2;j<=m;j++)
            if(a[i][j]!=a[i][j-1])l[i][j]=max(l[i][j],l[i][j-1]+1);
    for(int i=1;i<=n;i++)
        for(int j=m-1;j>=1;j--)
            if(a[i][j]!=a[i][j+1])r[i][j]=max(r[i][j],r[i][j+1]+1);
    int u=1,v=1,ret,num;
    ret=num=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(i==1)sum[i][j][0]=l[i][j],sum[i][j][1]=r[i][j];
            else if(a[i][j]!=a[i-1][j])
            {
                up[i][j]=up[i-1][j]+1;
                sum[i][j][0]=min(sum[i-1][j][0],l[i][j]);
                sum[i][j][1]=min(sum[i-1][j][1],r[i][j]);
            }
            else
            {
                up[i][j]=0;
                sum[i][j][0]=l[i][j],sum[i][j][1]=r[i][j];
            }
            if((sum[i][j][0]+sum[i][j][1]+1)*(up[i][j]+1)>ret)
            {
                ret=(sum[i][j][0]+sum[i][j][1]+1)*(up[i][j]+1);
                u=sum[i][j][0]+sum[i][j][1]+1;
                v=up[i][j]+1;
            }
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            int len=min(sum[i][j][0]+sum[i][j][1]+1,up[i][j]+1);
            num=max(num,len*len);       
        }
    printf("%d\n",num);
    printf("%d\n",ret);
    return 0;
}
作者:Rose_max 发表于2017/11/27 13:59:05 原文链接
阅读:0 评论:0 查看评论

BZOJ2005: [Noi2010]能量采集

$
0
0

Description

栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,
栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。 栋栋的植物种得非常整齐,一共有n列,每列
有m棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标(x, y)来表示,其中x的范围是1至n,
表示是在第x列,y的范围是1至m,表示是在第x列的第y棵。 由于能量汇集机器较大,不便移动,栋栋将它放在了
一个角上,坐标正好是(0, 0)。 能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器
连接而成的线段上有k棵植物,则能量的损失为2k + 1。例如,当能量汇集机器收集坐标为(2, 4)的植物时,由于
连接线段上存在一棵植物(1, 2),会产生3的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植
物,则能量损失为1。现在要计算总的能量损失。 下面给出了一个能量采集的例子,其中n = 5,m = 4,一共有20
棵植物,在每棵植物上标明了能量汇集机器收集它的能量时产生的能量损失。 在这个例子中,总共产生了36的能
量损失。
Input

仅包含一行,为两个整数n和m。

Output

仅包含一个整数,表示总共产生的能量损失。

Sample Input

【样例输入1】

5 4

【样例输入2】

3 4

Sample Output

【样例输出1】

36

【样例输出2】

20

对于100%的数据:1 ≤ n, m ≤ 100,000。

题目传送门

昨天晚上AKCqhzdy问我这道题,当时我在上(hun)奥(ri)数(zi)
想了想,不对啊,这道题该怎么做??
因为我的莫比乌斯反演其实不是很精通,导致我想了很久…
简化题意后就变成了这个样子:
首先对于某个点(x,y),k=gcd(x,y)-1:
设gcd(x,y)=t,令x=at,y=bt,那么在这条直线上的整数点可以表示为(a,b)(2a,2b)(3a,3b)……(x,y),由于不算x,y,则答案为gcd(x,y)-1
那么总损耗2k+1=2×gcd(x,y)-1。

然后冥思苦想,是啊,我只学过怎么求方案数,没学过怎么跑和啊..
啊呀,不对啊,和不就是方案数*gcd的结果吗??
那么我们就求一遍每个gcd结果的值不就行了吗??
然后玄学1A(第一次是为了保存代码,结果灵光一闪)

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxp 1000005
using namespace std;
typedef long long ll;
const int maxn = 100005;
const int inf=(1<<28)-1;

bool check[10000005];
int primes[10000005];
ll mu[10000005];
void get_mu()
{
    memset(check,false,sizeof(check));
    primes[0]=0;
    mu[1]=1;
    for(int i=2;i<maxp;++i)
    {
        if(check[i]==0)
        {
            primes[++primes[0]]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=primes[0];++j)
        {
            if((ll)primes[j]*i>=maxp) break;
            check[i*primes[j]]=true;
            if(i%primes[j]!=0)mu[i*primes[j]]=-mu[i];
            else
            {
                mu[i*primes[j]]=0;
                break;
            }
        }
        mu[i]+=mu[i-1];
    }
}
int main()
{
    get_mu();
    int n,m;
    scanf("%d%d",&n,&m);if(n>m)swap(n,m);
    ll Ans=0;ll ans=0;
    int last;
    for(int j=1;j<=n;j++)
    {
        ll N=(ll)n/j,M=(ll)m/j;Ans=0;
        for(int i=1,last=0;i<=N;i=last+1)
        {
            last=min(M/(M/i),N/(N/i));

            Ans+=(ll)(N/i)*(M/i)*(mu[last]-mu[i-1]);
        }
        if(j!=1)ans+=Ans*2*(j-1)+Ans;
        else ans+=Ans;
    }

    printf("%lld\n",ans);
    return 0;
}

by_lmy

作者:HeroDeathes 发表于2017/11/27 14:00:50 原文链接
阅读:35 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>