React单元测试策略及落地

单元测试是现代软件开发最基本,也普遍落地不力的实践。市面关于React单元测试的文章,普遍停留在“可以如何写”和介绍工具的层面,既未回答“为何必须做单元测试”,也未回答“单元测试的最佳实践”两个关键问题。本文正是要对这两个问题作出回答。

本文所用技术栈为前端React栈,测试框架与断言工具是jest。文章将略过对测试框架本身的语法介绍,着眼于“为何做”与“最佳实践”的部分。阅读第二部分的代码,需要读者对JavaScript与React语法有一定了解,但第一部分的原理并不对读者做任何语言上的假设。

目录

  1. 第一部分:为什么必须做单元测试
    1. 单元测试的上下文
    2. 测试策略——测试金字塔
    3. TDD——单元测试的核心灵魂
  2. 第二部分:什么是好的单元测试
  3. 第三部分:React 单元测试策略
  4. 第四部分:React 单元测试落地
    1. actions 测试
    2. reducer 测试
    3. selector 测试
    4. saga 测试
      • 来自官方的错误姿势
      • 正确姿势
    5. component 测试
      • 业务型组件 - 分支渲染
      • 业务型组件 - 事件调用
      • 功能型组件 - children 型高阶组件
    6. utils 测试
  5. 总结
  6. 未尽话题
  7. 参考

第一部分:为什么必须做单元测试

对于单元测试有保障质量之利益一事,业界已多有共识,但谈及其实施,则有种种“浪费时间”“很难写”“作用不大”顾及成本之声,他们板起一副编写单元测试将付出巨大成本的严肃脸。在“单元测试能保障质量”已成政治正确的今日,又有一种中庸的声音,说“单元测试固然有用,但也要根据项目情况进行裁剪,要写对开发者真正有用的测试”,一副放之四海皆准的样子,于是终于能够安心地根据项目情况将单元测试裁剪掉。

这种态度我一贯旗帜鲜明地反对:上来就谈裁剪,不是正确的导向。与产品代码一并交付高质量的测试代码,是每个开发者日常交付软件的基本职责。

单元测试的上下文

“为什么我们需要做单元测试”,这是一个关键的问题。往小了说,不做单元测试的代码无法保证后续不被破坏,无法重构,只能看着代码腐化;往大了说,不做单元测试的团队响应力不可能提高

image

image

实际上,自动化测试是实现“敏捷”的基本保障。现代企业数字化竞争日益激烈,业务端快速上线、快速验证、快速失败的思路对技术端的响应力提出了更高的要求:更快上线持续上线。怎么样衡量这个“更快”呢?第一图给出了一个指标:lead time。它度量的是一个想法从提出并被验证,到最终上生产环境面对用户获取反馈的时间。显然,这个时间越短,软件就能越快获得反馈,对价值的验证就越快发生,软件对反馈的响应能力就越强。这个结论对我们写不写单元测试有什么影响呢?答案是,不写单元测试、不写好的单元测试,你就快不起来。为啥呢?因为每次发布,你都要投入人力来进行手工测试;因为没有自动化测试,你倾向于不敢随意重构,这又导致代码逐渐腐化,复杂度使得你的开发速度降低。

再考虑到以下两大事实:人员会流动,应用会变大。人员一定会流动,需求一定会增加,直至再也没有一个人能够了解应用的所有功能,那时对应用做出修改的成本将变得很高。因此,意图依赖人、依赖手工的方式来应对响应力的挑战首先是低效的,从时间维度上来讲也是不可能的。因此,为了服务于“高响应力”这个目标,随时重构整理代码是必须的,这就需要我们有一套自动化的测试套件,它能帮我们提供快速反馈,做质量的守卫者。唯解决了人工、质量的这一环,开发效率才能稳步提升,团队和企业的高响应力才可能达到。

在“响应力”和“随时重构”这个上下文中来谈要不要单元测试,我们就可以很有根据了,而不是含糊不清地回答“看项目的具体情况”了。显然,写出易于理解、易于修改、可以重构的代码,是每个开发者的本来职责,而单元测试正是达成此一目的的唯一途径。

测试策略——测试金字塔

上面我直接从高响应力谈到单元测试,可能有的同学会问,高响应力这个事情我认可,也认可快速开发的同时,质量也很重要。但是,为了达到“保障质量”的目的,不一定得通过测试呀,就算需要测试,也不一定得通过单元测试鸭。

这是个好的问题。为了达到保障质量这个目标,测试当然只是其中一个方式,稳定的自动化部署、集成流水线、良好的代码架构、甚至于团队架构的必要调整等,都是必须跟上的设施。自动化测试不是解决质量问题的银弹,多方共同提升才可能起到效果。

即便我们谈自动化测试,也未必全部都是单元测试。我们对自动化测试套件寄予的厚望是,它能帮我们安全重构已有代码快速回归已有功能保存业务上下文。测试种类多种多样,为什么我要重点谈单元测试呢?因为它写起来相对最容易、运行速度最快、反馈效果又最直接。下面这个图,想必大家都有所耳闻:

image

这就是有名的测试金字塔。对于一个自动化测试套件,应该包含种类不同、关注点不同的测试,比如关注单元的单元测试、关注集成和契约的集成测试和契约测试、关注业务验收点的端到端测试等。正常来说,我们会受到资源的限制,无法应用所有层级的测试,效果也未必最佳。因此,我们需要有策略性地根据收益-成本的原则,考虑项目的实际情况和痛点来定制测试策略:比如三方依赖多的项目可以多写些契约测试,业务场景多、复杂或经常回归的场景可以多写些端到端测试,等。但不论如何,整个测试金字塔体系中,你还是应该拥有更多低层次的单元测试,因为它们成本相对最低,运行速度最快(通常是毫秒级别),而对单元的保护价值相对更大。

TDD——单元测试的核心灵魂

以上回答了“为何要有单元测试”的问题,却没有回答“如何得到这些单元测试”。有同学可能问,你说要写单元测试,那么什么时候写这些单元测试呢?让谁来写呢(开发人员还是测试人员)?代码实现那么烂,我根本写不出强壮的测试,怎么办呢?

回答是,这些单元测试应该由开发者,在开发软件的同时编写对应的单元测试。它应该是内建的,而不是后补的:也即在编写实现的同时完成单元测试,而不是写完代码再一次性补足。测试先行,这正是TDD的做法。使用TDD开发方法是得到可靠单元测试的唯一途径。

长久以来,大家都认为单测是单测,TDD是TDD,说单测必须要有,但是否使用TDD(测试先行)应该尊重开发者的习惯爱好。但事实是,且不说测试很难补,补出来的测试也几乎不可能完整覆盖我们对重构和质量的要求。TDD和单元测试是全有或全无:不做TDD,难以得到好的单元测试;TDD是获得可靠的单元测试的的唯一途径。除此之外别无捷径,想抛开TDD而获得一个好的单元测试套件是迷思,难以成功。

那么如何掌握TDD呢?事实上非常简单,多练即可。你可关注微信公众号“程序员练功房”,也可扫码直接开始你的十四天编程训练营,刻意练习,打好TDD基础。

image

第二部分:什么是好的单元测试

好,相信看到这里,你已经愿意为一套好的单元测试集而奋斗了。下一个摆在我们眼前的问题就是,“什么才是好的单元测试”,以及“如何写出这样的单元测试”了。开始之前,我们先来看个例子,即一个最简单的JavaScript单元测试长什么样:

// production code
const computeTotalAmount = (products) => {
  return products.reduce((total, product) => total + product.price, 0); 
}

// testing code
it('should return summed up total amount 1000 when there are three products priced 200, 300, 500', () => {
  // given - 准备数据
  const products = [
    { name: 'nike', price: 200 },
    { name: 'adidas', price: 300 },
    { name: 'lining', price: 500 },
  ]

  // when - 调用被测函数
  const result = computeTotalAmount(products)

  // then - 断言结果
  expect(result).toBe(1000)
})

这个例子虽小,五脏却基本齐全。遵循这个given-when-then的结构,可以让你写出比较清晰的测试结构,既易于阅读,也易于编写。此外,编写容易维护的单元测试还有一些原则,这些原则对于任何语言、任何层级的测试都适用。这些原则不是新东西,但总是需要时时温故知新,笔者总结于此,可以此为镜,时时检验你的单元测试套件是否高效:

  • 只关注输入输出,不关注内部实现
  • 只测一条分支
  • 表达力极强
  • 不包含逻辑
  • 运行速度快

只关注输入输出,不关注内部实现

比如上面那个例子,你是如何完成“求总价格”的,测试本身不关注,因此你可以用reduce实现,也可以自己写for循环实现。只要测试输入没有变,输出就不应该变。这个特性,是测试支撑重构的基础。因为重构指的是,在不改变软件外部可观测行为的基础上,调整软件内部的实现。由此也可以看出,如果你是后补的测试,加之实现本身就写得细节横陈,就很难补出这种能够支撑重构、结构又清晰的测试代码。测试先行本身就会驱动你写出易于测试的代码。

另外,还有一些测试(比如下文要看到的 saga 官方推荐的测试),它需要测试实现代码的执行次序。这也是一种“关注内部实现”的测试,这就使得除了输入输出外,还有“执行次序”这个因素可能使测试挂掉。显然,这样的测试也不利于重构的开展。

此外,对外部依赖采取mock策略,同样是某种程度上的“关注内部实现”,因为mock的失败同样将导致测试的失败,而非真正业务场景的失败。对待mock的态度,我认为是谨慎使用,但本文未做展开。肖鹏有篇文章Mock的七宗罪对此展开了详细描述,我还没细看,这里只能先分享给读者。

只测一条分支

通常来说,一条分支就是一个业务场景,是你做任务分解过程的一个细粒度的task。为什么测试只测一条分支呢?很显然,如此你才能给它一个好的描述,这个测试才能保护这个特定的业务场景,挂了的时候能给你细致到输入输出级别的业务反馈。

常见的反模式是,实现本身就做了太多的事情,不符合SRP原则。

表达力极强

表达力强的测试,能在失败的时候给你非常迅速的反馈。它讲的是两方面:

  • 看到测试时,你就知道它测的业务点是啥
  • 测试挂掉时,能清楚地知道失败的业务场景、期望数据与实际输出的差异

总结起来,这些表达力主要体现在以下的方面:

  • 测试描述。遵循上一条原则(一个单元测试只测一个分支)的情况下,描述通常能写出一个相当详细的业务场景。这为测试的读者提供了极佳的业务上下文
  • 测试数据准备。无关的测试数据(比如对象中的很多无关字段)不应该写出来,应只准备能体现测试业务的最小数据
  • 输出报告。选用断言工具时,应注意除了要提供测试结果,还要能准确提供“期望值”与“实际值”的差异

上述第三点有些反例,比如说chai和sinon提供的断言API就不如jest友好,体现在:

  • expect(array).to.eql(array)出错的时候,只能报告说expect [Array (42)] to equal [Array (42)],具体是哪个数据不匹配,根本没报告
  • expect(sinonStub.calledWith(args)).to.be.true出错的时候,会报告说expect false to be true。废话,我还不知道挂了么,但是那个stub究竟被什么参数调用则没有报告

这些细节,在阅读本文后面的任意测试,以及您自己编写单元测试的时候应该时常对照和雕琢。

不包含逻辑

跟写声明式的代码一样的道理,测试需要都是简单的声明:准备数据、调用函数、断言,让人一眼就明白这个测试在测什么。如果含有逻辑,你读的时候就要多花时间理解;一旦测试挂掉,你咋知道是实现挂了还是测试本身就挂了呢?

运行速度快

单元测试只有在毫秒级别内完成,开发者才会愿意频繁地运行它,将其作为快速反馈的手段也才能成立。那么为了使单元测试更快,我们需要:

  • 尽可能地避免依赖。除了恰当设计好对象,关于避免依赖我已知有两种不同的看法:
    • 使用mock适当隔离掉三方的依赖(如数据库、网络、文件等)
    • 避免mock,换用更快速的数据库、启动轻量级服务器、重点测试文件内容等来迂回
  • 将依赖、集成等耗时、依赖三方返回的地方放到更高层级的测试中,有策略性地去做

在如何避免依赖的问题上,截止我下笔此文章时仍在采用第一种方案,如何才能“适当”隔离掉三方依赖也难以在此详细表述,好在并不影响本文行文;近期可能会考察下第二种方法。

在后面的介绍中,我会将这些原则落实到我们写的每个单元测试中去。大家可以时时翻到这个章节来对照,是不是遵循了我们说的这几点原则,不遵循是不是确实会带来问题。时时勤拂拭,莫使惹尘埃啊。

第三部分:React 单元测试策略

image

上个项目上的 React(-Native) 应用架构如上所述。它涉及一个常见 React 应用的几个层面:组件、数据管理、redux、副作用管理等,是一个常见的 React、Redux 应用架构,对于不同的项目应该有一定的适应性。架构中的不同元素有不同的特点,因此即便是单元测试,我们也有针对性的测试策略:

架构层级 测试内容 测试策略 解释
action(creator)层 是否正确创建 action 对象 一般不需要测试,视信心而定 这个层级架构上非常简单,设施搭好以后一般不可能出错
reducer 层 是否正确完成计算 有逻辑的 reducer 要求 100%覆盖率 这个层级输入输出明确,又包含业务计算,非常适合单元测试
selector 层 是否正确完成计算 有逻辑的 selector 要求 100%覆盖率 这个层级输入输出明确,又包含业务计算,非常适合单元测试
saga 层 是否获取了正确的参数 这五个业务点建议 100% 覆盖 这个层级主要包含前述 5 大方面的业务逻辑,进行测试很有重构价值
是否正确地调用了 API
是否使用了正确的返回值存取回 redux 中
业务分支逻辑
异常逻辑
component 层 组件分支渲染逻辑 要求 100%覆盖 这个层级最为复杂,还是以「代价最低,收益最高」为指导原则进行
交互事件是否以正确的参数被调用 要求 100%覆盖
redux connect 过的组件 不测
UI 层 组件是否渲染了正确的样式 目前不测 这个层级以我目前理解来说测试较难稳定,成本又较高
utils 层 各种帮助函数 没有副作用的必须 100% 覆盖

对于这个策略,这里做一些其他补充:

关于不测 redux connect 过的组件策略:理由是成本高于收益,得不偿失:

  • 要配置依赖(配置 store、;如果是补测试还可能遇到 @connect 组件里套 @connect 组件的场景);
  • 牺牲了开发体验,搞起来没那么快了;
  • 收益只是可能覆盖到了几个偶尔出现的场景(比如接入错误的字段、写字段时写错等);

关于 UI 测试策略:团队之前尝试过 snapshot 测试,对它寄予希望,主要理由是成本低,看起来又像万能药。实质上其整个机制的工作基础依赖于开发者在每次运行测试时耐心做好“确认比对”这个事情,这会打断日常的开发节奏(特别是依赖于TDD的红绿循环进行快速反馈的项目);其次还有些小的问题,比如其难以提供精确的快照比对,而只是代码层面的近似快照。我个人目前对此种测试类型持保留态度。

第四部分:React 单元测试落地

actions 测试

这一层获益于架构的简单性,甚至都可以不用测试。当然,如果有些经常出错的action,可以针对性地对这些action creator补充测试。其测试方法如下:

export const saveUserComments = (comments) => ({
  type: 'saveUserComments',
  payload: {
    comments,
  },
})
import * as actions from './actions'

test('should dispatch saveUserComments action with fetched user comments', () => {
  const comments = []
  const expected = {
    type: 'saveUserComments',
    payload: {
      comments: [],
    },
  }
  
  const result = actions.saveUserComments(comments)

  expect(result).toEqual(expected)
})

reducer 测试

reducer 大概有两种:一种比较简单,仅一一保存对应的数据切片;一种复杂一些,里面具有一些计算逻辑。对于第一种 reducer,写起来非常简单,简单到甚至可以不需要用测试去覆盖,其正确性基本由简单的架构和逻辑去保证。下面是对一个简单 reducer 做测试的例子:

import Immutable from 'seamless-immutable'

const initialState = Immutable.from({
  isLoadingProducts: false,
})

export default createReducer((on) => {
  on(actions.isLoadingProducts, (state, action) => {
    return state.merge({
      isLoadingProducts: action.payload.isLoadingProducts,
    })
  })
}, initialState)
import reducers from './reducers'
import actions from './actions'

test('should save loading start indicator when action isLoadingProducts is dispatched given isLoadingProducts is true', () => {
  const state = { isLoadingProducts: false }
  const expected = { isLoadingProducts: true }

  const result = reducers(state, actions.isLoadingProducts(true))

  expect(result).toEqual(expected)
})

下面是一个较为复杂、更具备测试价值的 reducer 例子,它在保存数据的同时,还进行了合并、去重的操作:

import uniqBy from 'lodash/uniqBy'

export default createReducers((on) => {
  on(actions.saveUserComments, (state, action) => {
    return state.merge({
      comments: uniqBy(
        state.comments.concat(action.payload.comments), 
        'id',
      ),
    })
  })
})
import reducers from './reducers'
import actions from './actions'

test(`
  should merge user comments and remove duplicated comments by comment id 
  when action saveUserComments is dispatched with new fetched comments
`, () => {
  const state = {
    comments: [{ id: 1, content: 'comments-1' }],
  }
  const comments = [
    { id: 1, content: 'comments-1' },
    { id: 2, content: 'comments-2' },
  ]

  const expected = {
    comments: [
      { id: 1, content: 'comments-1' },
      { id: 2, content: 'comments-2' },
    ],
  }

  const result = reducers(state, actions.saveUserComments(comments))

  expect(result).toEqual(expected)
})

reducer 作为纯函数,非常适合做单元测试,加之一般在 reducer 中做重逻辑处理,此处做单元测试保护的价值很大。请留意,上面所说的单元测试,是不是符合我们描述的单元测试基本原则:

  • 只关注输入输出,不关注内部实现:在输入不变时,仅可能因为“合并去重”的业务操作不符预期时才会挂测试
  • 表达力极强:测试描述已经写得清楚“当使用新获取到的留言数据分发 action saveUserComments 时,应该与已有留言合并并去除重复的部分”;此外,测试数据只准备了足够体现“合并”这个操作的两条 id 的数据,而没有放很多的数据,形成杂音;
  • 不包含逻辑:测试代码不包含准备数据、调用、断言外的任何逻辑
  • 运行速度快:没有任何依赖

selector 测试

selector 同样是重逻辑的地方,可以认为是 reducer 到组件的延伸。它也是一个纯函数,测起来与 reducer 一样方便、价值不菲,也是应该重点照顾的部分。况且,稍微大型一点的项目,应该说必然会用到 selector。原因我讲在这里。下面看一个 selector 的测试用例:

import { createSelector } from 'reselect'

// for performant access/filtering in React component
export const labelArrayToObjectSelector = createSelector(
  [(store, ownProps) => store.products[ownProps.id].labels],
  (labels) => {
    return labels.reduce(
      (result, { code, active }) => ({
        ...result,
        [code]: active,
      }),
      {}
    )
  }
)
import { labelArrayToObjectSelector } from './selector'

test('should transform label array to object', () => {
  const store = {
    products: {
      10085: {
        labels: [
          { code: 'canvas', name: '帆布鞋', active: false },
          { code: 'casual', name: '休闲鞋', active: false },
          { code: 'oxford', name: '牛津鞋', active: false },
          { code: 'bullock', name: '布洛克', active: true },
          { code: 'ankle', name: '高帮鞋', active: true },
        ],
      },
    },
  }
  const expectedActiveness = {
    canvas: false,
    casual: false,
    oxford: false,
    bullock: true,
    ankle: false,
  }

  const productLabels = labelArrayToObjectSelector(store, { id: 10085 })

  expect(productLabels).toEqual(expectedActiveness)
})

saga 测试

saga 是负责调用 API、处理副作用的一层。在实际的项目上副作用还有其他的中间层进行处理,比如 redux-thunk、redux-promise 等,本质是一样的,只不过 saga 在测试性上要好一些。这一层副作用怎么测试呢?首先为了保证单元测试的速度和稳定性,像 API 调用这种不确定性的依赖我们一定是要 mock 掉的。经过仔细总结,我认为这一层主要的测试内容有五点:

  • 是否使用正确的参数(通常是从 action payload 或 redux 中来),调用了正确的 API
  • 对于 mock 的 API 返回,是否保存了正确的数据(通常是通过 action 保存到 redux 中去)
  • 主要的业务逻辑(比如仅当用户满足某些权限时才调用 API 等分支逻辑)
  • 异常逻辑(比如找不到用户等异常逻辑)
  • 其他副作用是否发生(比如有时有需要 Emit 的事件、需要保存到 IndexDB 中去的数据等)

来自官方的错误姿势

redux-saga 官方提供了一个 util: CloneableGenerator 用以帮我们写 saga 的测试。这是我们项目使用的第一种测法,大概会写出来的测试如下:

import chunk from 'lodash/chunk'

export function* onEnterProductDetailPage(action) {
  yield put(actions.notImportantAction1('loading-stuff'))
  yield put(actions.notImportantAction2('analytics-stuff'))
  yield put(actions.notImportantAction3('http-stuff'))
  yield put(actions.notImportantAction4('other-stuff'))

  const recommendations = yield call(Api.get, 'products/recommended')
  const MAX_RECOMMENDATIONS = 3
  const [products = []] = chunk(recommendations, MAX_RECOMMENDATIONS)

  yield put(actions.importantActionToSaveRecommendedProducts(products))

  const { payload: { userId } } = action
  const { vipList } = yield select((store) => store.credentails)
  if (!vipList.includes(userId)) {
    yield put(actions.importantActionToFetchAds())
  }
}
import { put, call } from 'saga-effects'
import { cloneableGenerator } from 'redux-saga/utils'
import { Api } from 'src/utils/axios'
import { onEnterProductDetailPage } from './saga'

const product = (productId) => ({ productId })

test(`
  should only save the three recommended products and show ads 
  when user enters the product detail page 
  given the user is not a VIP
`, () => {
  const action = { payload: { userId: 233 } }
  const credentials = { vipList: [2333] }
  const recommendedProducts = [product(1), product(2), product(3), product(4)]
  const firstThreeRecommendations = [product(1), product(2), product(3)]
  const generator = cloneableGenerator(onEnterProductDetailPage)(action)

  expect(generator.next().value).toEqual(
    actions.notImportantAction1('loading-stuff')
  )
  expect(generator.next().value).toEqual(
    actions.notImportantAction2('analytics-stuff')
  )
  expect(generator.next().value).toEqual(
    actions.notImportantAction3('http-stuff')
  )
  expect(generator.next().value).toEqual(
    actions.notImportantAction4('other-stuff')
  )

  expect(generator.next().value).toEqual(call(Api.get, 'products/recommended'))
  expect(generator.next(recommendedProducts).value).toEqual(
    firstThreeRecommendations
  )
  generator.next()
  expect(generator.next(credentials).value).toEqual(
    put(actions.importantActionToFetchAds())
  )
})

这个方案写多了,大家开始感受到了痛点,明显违背我们前面提到的一些原则:

  1. 测试分明就是把实现抄了一遍。这违反上述所说“不关注内部实现”的原则:action的分发顺序也是一种内部实现,改变实现次序也将使测试挂掉
  2. 当在实现中某个部分加入新的语句时,该语句后续所有的测试都会挂掉,并且出错信息非常难以描述原因,导致常常要陷入“调试测试”的境地,这也是依赖于实现次序带来的恶果,根本无法支持重构这种改变内部实现但不改变业务行为的代码清理行为
  3. 为了测试两个重要的业务“只保存获取回来的前三个推荐产品”、“对非 VIP 用户推送广告”,不得不在前面先按次序断言许多个不重要的实现
  4. 测试没有重点,随便改点什么都会挂测试

正确姿势

针对以上痛点,我们认为真正能够保障质量、重构和开发者体验的 saga 测试应该是这样:

  1. 不依赖实现次序;
  2. 允许仅对真正关心的、有价值的业务进行测试;
  3. 支持不改动业务行为的重构;

于是,我们发现官方提供了这么一个跑测试的工具,刚好可以用来完美满足我们的需求:runSaga。我们可以用它将 saga 全部执行一遍,搜集所有发布出去的 action,由开发者自由断言其感兴趣的 action!基于这个发现,我们推出了我们的第二版 saga 测试方案:runSaga + 自定义拓展 jest 的 expect 断言。最终,使用这个工具写出来的 saga 测试,几近完美:

import { put, call } from 'saga-effects'
import { Api } from 'src/utils/axios'
import { testSaga } from '../../../testing-utils'
import { onEnterProductDetailPage } from './saga'

const product = (productId) => ({ productId })

test(`
  should only save the three recommended products and show ads 
  when user enters the product detail page 
  given the user is not a VIP
`, async () => {
  const action = { payload: { userId: 233 } }
  const store = { credentials: { vipList: [2333] } }
  const recommendedProducts = [product(1), product(2), product(3), product(4)]
  const firstThreeRecommendations = [product(1), product(2), product(3)]
  Api.get = jest.fn().mockImplementations(() => recommendedProducts)

  await testSaga(onEnterProductDetailPage, action, store)

  expect(Api.get).toHaveBeenCalledWith('products/recommended')
  expect(
    actions.importantActionToSaveRecommendedProducts
  ).toHaveBeenDispatchedWith(firstThreeRecommendations)
  expect(actions.importantActionToFetchAds).toHaveBeenDispatched()
})

这个测试已经简短了许多,没有了无关断言的杂音,依然遵循 given-when-then 的结构,并且同样是测试“只保存获取回来的前三个推荐产品”、“对非 VIP 用户推送广告”两个关心的业务点:

  • 当输入不变时,无论你怎么优化内部实现、调整内部次序,这个测试关心的业务场景都不会挂,真正做到了测试保护重构、支持重构的作用
  • 可以仅断言你关心的点,忽略不重要或不关心的中间过程(比如上例中,我们就没有断言其他 notImportant 的 action 是否被 dispatch 出去),消除无关断言的杂音,提升了表达力
  • 使用了 product 这样的测试数据创建套件(fixtures),精简测试数据,消除无关数据的杂音,提升了表达力
  • 自定义的 expect(action).toHaveBeenDispatchedWith(payload) matcher 很有表达力,且出错信息友好

这个自定义的 matcher 是通过 jest 的 expect.extend 扩展实现的:

expect.extend({
  toHaveBeenDispatched(action) { ... },
  toHaveBeenDispatchedWith(action, payload) { ... },
})

上面是我们认为比较好的副作用测试工具、测试策略和测试方案。使用时,需要牢记你真正关心的业务价值点(也即本节开始提到的 5 点),以及做到在较为复杂的单元测试中始终坚守几条基本原则。唯如此,单元测试才能真正提升开发速度、支持重构、充当业务上下文的文档。

作者注:本文成文后,社区又有一些简化测试的方案出来。读者也可带着这些测试原则去考察一番:

  • https://github.com/jfairbank/redux-saga-test-plan
  • https://github.com/testing-library/react-testing-library

component 测试

组件测试其实是实践最多、测试实践看法和分歧也最多的地方。React 组件是一个高度自治的单元,从分类上来看,它大概有这么几类:

  • 展示型业务组件
  • 容器型业务组件
  • 通用 UI 组件
  • 功能型组件

先把这个分类放在这里,待会回过头来谈。对于 React 组件测什么不测什么,我有一些思考,也有一些判断标准:除去功能型组件,其他类型的组件一般是以渲染出一个语法树为终点的,它描述了页面的 UI 内容、结构、样式和一些逻辑 component(props) => UI。内容、结构和样式,比起测试,直接在页面上调试反馈效果更好。测也不是不行,但都难免有不稳定的成本在;逻辑这块,有一测的价值,但需要控制好依赖。综合上面提到的测试原则进行考虑,我的建议是:两测两不测。

  • 组件分支渲染逻辑必须测
  • 事件调用和参数传递一般要测
  • 连接 redux 的高阶组件不测
  • 渲染出来的 UI 不在单元测试层级测

组件的分支逻辑,往往也是有业务含义和业务价值的分支,添加单元测试既能保障重构,还可顺便做文档用;事件调用同样也有业务价值和文档作用,而事件调用的参数调用有时可起到保护重构的作用。

纯 UI 不在单元测试级别测试的原因,纯粹就是因为不好断言。所谓快照测试有意义的前提在于两个:必须是视觉级别的比对、必须开发者每次都认真检查。jest 有个 snapshot 测试的概念,但那个 UI 测试是代码级的比对,不是视觉级的比对,最终还是绕了一圈,去除了杂音还不如看 Git 的 commit diff。每次要求开发者自觉检查,既打乱工作流,也难以坚持。考虑到这些成本,我不推荐在单元测试的级别来做 UI 类型的测试。对于我们之前中等规模的项目,诉诸手工还是有一定的可控性。

连接 redux 的高阶组件不测。原因是,connect 过的组件从测试的角度看无非几个测试点:

  • mapStateToProps 中是否从 store 中取得了正确的参数
  • mapDispatchToProps 中是否地从 actions 中取得了正确的参数
  • map 过的 props 是否正确地被传递给了组件
  • redux 对应的数据切片更新时,是否会使用新的 props 触发组件进行一次更新

这四个点,react-redux 已经都帮你测过了,已经证明 work 了,开发者没有必要进行测试。当然,不测这个东西的话,还是有这么一种可能,就是你 export 的纯组件测试都是过的,但是代码实际运行出错。穷尽下来主要可能是这几种问题:

  • 你在 mapStateToProps 中打错了字或打错了变量名
  • 你写了 mapStateToProps 但没有 connect 上去
  • 你在 mapStateToProps 中取的路径是错的,在 redux 中已经被改过

第一、二种可能,如果是小步前进其实发现起来很快。如果某段数据获取的逻辑多处重复,则可以考虑将该逻辑抽取到 selector 中并进行单独测试;第三种可能,确实是问题,但由于在我所在项目发生频率较低(部分因为上个项目没有类型系统我们不会随意改 redux 的数据结构…),所以针对这些少量出现的场景,不必要采取错杀一千的方式进行完全覆盖。默认不测,出了问题或者经常可能出问题的部分,再策略性地补上测试进行固定即可。

综上,@connect 组件默认不测,因为框架本身已做了大部分测试,剩下的场景出 bug 频率不高,而施加测试的话提高成本(准备依赖和数据),降低开发体验,性价比不大,所以建议省了这份心。不测 @connect 过的组件,其实也是 官方文档 推荐的做法。

然后,基于上面第 1、2 个结论,映射回四类组件的结构当中去,我们可以得到下面的表格,然后发现…每种组件都要测渲染分支事件调用,跟组件类型根本没必然的关联…不过,功能型组件有可能会涉及一些其他的模式,因此又大致分出一小节来谈。

组件类型 / 测试内容 分支渲染逻辑 事件调用 @connect 纯 UI
展示型组件 - ✖️
容器型组件 ✖️ ✖️
通用 UI 组件 - ✖️
功能型组件 ✖️ ✖️

业务型组件 - 分支渲染

export const CommentsSection = ({ comments }) => (
  
{comments.length > 0 && (

Comments

)} {comments.map((comment) => ( )}
)

对应的测试如下,测试的是不同的分支渲染逻辑:没有评论时,则不渲染 Comments header。

import { CommentsSection } from './index'
import { Comment } from './Comment'

test('should not render a header and any comment sections when there is no comments', () => {
  const component = shallow()

  const header = component.find('h2')
  const comments = component.find(Comment)

  expect(header).toHaveLength(0)
  expect(comments).toHaveLength(0)
})

test('should render a comments section and a header when there are comments', () => {
  const contents = [
    { id: 1, author: '男***8', comment: '价廉物美,相信奥康旗舰店' },
    { id: 2, author: '雨***成', comment: '所以一双合脚的鞋子...' },
  ]
  const component = shallow()

  const header = component.find('h2')
  const comments = component.find(Comment)

  expect(header.html()).toBe('Comments')
  expect(comments).toHaveLength(2)
})

业务型组件 - 事件调用

测试事件的一个场景如下:当某条产品被点击时,应该将产品相关的信息发送给埋点系统进行埋点。

export const ProductItem = ({
  id,
  productName,
  introduction,
  trackPressEvent,
}) => (
   trackPressEvent(id, productName)}>
    
      
      <Introduction introduction={introduction} />
    </View>
  </TouchableWithoutFeedback>
)
</code></pre> 
 <pre><code class="js">import { ProductItem } from './index'

test(`
  should send product id and name to analytics system 
  when user press the product item
`, () => {
  const trackPressEvent = jest.fn()
  const component = shallow(
    <productitem id={100832}
      introduction="iMac Pro - Power to the pro."
      trackPressEvent={trackPressEvent}></productitem>
  )

  component.find(TouchableWithoutFeedback).simulate('press')

  expect(trackPressEvent).toHaveBeenCalledWith(
    100832,
    'iMac Pro - Power to the pro.'
  )
})
</code></pre> 
 <p>简单得很吧。这里的几个测试,在你改动了样式相关的东西时,不会挂掉;但是如果你改动了分支逻辑或函数调用的内容时,它就会挂掉了。而分支逻辑或函数调用,恰好是我觉得接近业务的地方,所以它们对保护代码逻辑、保护重构是有价值的。当然,它们多少还是依赖了组件内部的实现细节,比如说 <code>find(TouchableWithoutFeedback)</code>,还是做了“组件内部使用了 <code>TouchableWithoutFeedback</code> 组件”这样的假设,而这个假设很可能是会变的。也就是说,如果我换了一个组件来接受点击事件,尽管点击时的行为依然发生,但这个测试仍然会挂掉。这就违反了我们所说了“不关注内部实现”原则,这对于组件测试来说,确实是不够完美的地方。</p> 
 <p>但这个问题无法避免。因为组件本质是渲染组件树,那么测试中要与组件树关联,必然要通过组件名、id这样的 selector,这些 selector 的关联本身就是一些“内部实现”的细节。但对组件的分支、事件进行测试又有一定的价值,无法避免。所以,我认为这个部分还是要用,只不过同时需要一些限制,以控制这些假设为维护测试带来的额外成本:</p> 
 <ul> 
  <li>不要断言组件内部结构。像那些 <code>expect(component.find('div > div > p').html().toBe('Content')</code> 的真的就算了吧</li> 
  <li>正确拆分组件树。一个组件尽量只负责一个(或一组高度相关的)功能,不允许堆叠太多的函数和功能</li> 
 </ul> 
 <p>也就是说,如果你发现你很难快速地准备对组件的测试,那么有可能是你的组件太复杂了,这也是一个坏味道。多数情况下是组件承担了太多的职责,你应该将它们拆成更小的组件,使其符合单一职责原则。</p> 
 <p>如果你的每个组件都十分清晰直观、逻辑分明,那么像上面这样的组件测起来也就很轻松,一般就遵循 <code>shallow</code> -> <code>find(Component)</code> -> 断言的三段式,哪怕是了解了一些组件的内部细节,通常也在可控的范围内,维护起来成本并不高。这是目前我觉得平衡了表达力、重构意义和测试成本的实践。</p> 
 <p><strong>功能型组件 - <code>children</code> 型高阶组件</strong></p> 
 <p>功能型组件,指的是跟业务无关的另一类组件:它是功能型的,更像是底层支撑着业务组件运作的基础组件,比如路由组件、分页组件等。这些组件一般偏重逻辑多一点,关心 UI 少一些。其本质测法跟业务组件是一致的:不关心 UI 具体渲染,只测分支渲染和事件调用。但由于它偏功能型的特性,使得它在设计上常会出现一些业务型组件不常出现的设计模式,如高阶组件、以函数为子组件等。下面分别针对这几种进行分述。</p> 
 <pre><code class="js">export const FeatureToggle = ({ features, featureName, children }) => {
  if (!features[featureName]) {
    return null
  }

  return children
}

export default connect(
  (store) => ({ features: store.global.features })
)(FeatureToggle)
</code></pre> 
 <pre><code class="js">import React from 'react'
import { shallow } from 'enzyme'
import { View } from 'react-native'

import FeatureToggles from './featureToggleStatus'
import { FeatureToggle } from './index'

const DummyComponent = () => <View />

test('should not render children component when remote toggle does not exist', () => {
  const component = shallow(
    <FeatureToggle features={{}} featureName="promotion618">
      <DummyComponent />
    </FeatureToggle>
  )

  expect(component.find(DummyComponent)).toHaveLength(0)
})

test('should render children component when remote toggle is present and is on', () => {
  const features = {
    promotion618: FeatureToggles.on,
  }

  const component = shallow(
    <FeatureToggle features={features} featureName="promotion618">
      <DummyComponent />
    </FeatureToggle>
  )

  expect(component.find(DummyComponent)).toHaveLength(1)
})

test('should not render children component when remote toggle is present but is off', () => {
  const features = {
    promotion618: FeatureToggles.off,
  }

  const component = shallow(
    <FeatureToggle features={features} featureName="promotion618">
      <DummyComponent />
    </FeatureToggle>
  )

  expect(component.find(DummyComponent)).toHaveLength(0)
})
</code></pre> 
 <h4>utils 测试</h4> 
 <p>每个项目都会有 utils。一般来说,我们期望 util 都是纯函数,即是不依赖外部状态、不改变参数值、不维护内部状态的函数。这样的函数测试效率也非常高。测试原则跟前面所说的也并没什么不同,不再赘述。不过值得一提的是,因为 util 函数多是数据驱动,一个输入对应一个输出,并且不需要准备任何依赖,这使得它多了一种测试的选择,也即是参数化测试的方式。参数化测试可以提升数据准备效率,同时依然能保持详细的用例信息、错误提示等优点。jest 从 23 后就内置了对参数化测试的支持,如下:</p> 
 <pre><code class="javascript">test.each([
  [['0', '99'], 0.99, '(整数部分为0时也应返回)'],
  [['5', '00'], 5, '(小数部分不足时应该补0)'],
  [['5', '10'], 5.1, '(小数部分不足时应该补0)'],
  [['4', '38'], 4.38, '(小数部分不足时应该补0)'],
  [['4', '99'], 4.994, '(超过默认2位的小数的直接截断,不四舍五入)'],
  [['4', '99'], 4.995, '(超过默认2位的小数的直接截断,不四舍五入)'],
  [['4', '99'], 4.996, '(超过默认2位的小数的直接截断,不四舍五入)'],
  [['-0', '50'], -0.5, '(整数部分为负数时应该保留负号)'],
])(
  'should return %s when number is %s (%s)',
  (expected, input, description) => {
    expect(truncateAndPadTrailingZeros(input)).toEqual(expected)
  }
)
</code></pre> 
 <p></p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 321px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image 
  </div> 
 </div> 
 <p></p> 
 <p>当然,对纯数据驱动的测试,也有一些不同的看法,认为这样可能丢失一些描述业务场景的测试描述。所以这种方式还主要看项目组的接受度。</p> 
 <h3>总结</h3> 
 <p>好,到此为止,本文的主要内容也就讲完了。总结下来,本文主要覆盖到的内容如下:</p> 
 <ul> 
  <li>单元测试对于任何 React 项目(及其他任何项目)来说都是必须的</li> 
  <li>我们需要自动化的测试套件,根本目标是支持随时随地的代码调整、持续改进,从而提升团队响应力</li> 
  <li>使用TDD开发是得到好的单元测试的唯一途径</li> 
  <li>好的单元测试具备几大特征:<strong>不关注内部实现</strong>、<strong>只测一条分支</strong>、<strong>表达力极强</strong>、<strong>不包含逻辑</strong>、<strong>运行速度快</strong> </li> 
  <li>单元测试也有测试策略:在 React 的典型架构下,一个典型的测试策略为: 
   <ul> 
    <li>reducer、selector 层的逻辑代码要求 100% 覆盖</li> 
    <li>saga(副作用)层:<strong>是否拿到了正确的参数</strong>、<strong>是否调用了正确的 API</strong>、<strong>是否保存了正确的数据</strong>、<strong>业务逻辑</strong>、<strong>异常逻辑</strong>五个层面要求100%覆盖</li> 
    <li>action 层选择性覆盖:可不测</li> 
    <li>utils 层的纯函数要求 100% 覆盖</li> 
    <li>组件层: 
     <ul> 
      <li> <strong>分支渲染逻辑必测</strong>、<strong>事件、交互调用</strong>要求100%覆盖;</li> 
      <li> <code>@connect</code> 过的高阶组件不测</li> 
      <li>纯 UI 一般不测</li> 
     </ul> </li> 
   </ul> </li> 
  <li>其他高级技巧:定制测试工具(<code>jest.extend</code>)、参数化测试等</li> 
 </ul> 
 <h3>未尽话题</h3> 
 <p>诚然,关于构建一个完整的前端测试体系,有一些点是本文没有涉及到的,或因为没有涉猎,或因为尚未尝试,或因为未有结论,一并罗列于下。有兴趣的读者可来电交流。</p> 
 <ul> 
  <li>其他层级的测试:前端典型的其他层级测试如契约测试、端到端测试等</li> 
  <li>CSS的测试</li> 
  <li>HTML的测试与重构</li> 
 </ul> 
 <h3>参考</h3> 
 <ul> 
  <li>敏捷在中国这十五年</li> 
  <li>为什么TDD是敏捷的核心实践</li> 
  <li>测试金字塔实战</li> 
  <li>Mock的七宗罪</li> 
 </ul> 
 <hr> 
 <p>文/ThoughtWorks林从羽<br> <strong>更多精彩洞见,请关注微信公众号:ThoughtWorks洞见</strong></p> 
</article>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1746848990474813440"></div>
                    <script type="text/javascript" src="/views/front/js/chanyan.js"></script>
                    <!-- 文章页-底部 动态广告位 -->
                    <div class="youdao-fixed-ad" id="detail_ad_bottom"></div>
                </div>
                <div class="col-md-3">
                    <div class="row" id="ad">
                        <!-- 文章页-右侧1 动态广告位 -->
                        <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_1"> </div>
                        </div>
                        <!-- 文章页-右侧2 动态广告位 -->
                        <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_2"></div>
                        </div>
                        <!-- 文章页-右侧3 动态广告位 -->
                        <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_3"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="container">
        <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(React单元测试策略及落地)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1773603579169406976.htm"
                           title="2022年河南省高等职业教育技能大赛云计算赛项竞赛赛卷(样卷)" target="_blank">2022年河南省高等职业教育技能大赛云计算赛项竞赛赛卷(样卷)</a>
                        <span class="text-muted">忘川_ydy</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%91%E8%AE%A1%E7%AE%97/1.htm">云计算</a><a class="tag" taget="_blank" href="/search/%E4%BA%91%E8%AE%A1%E7%AE%97/1.htm">云计算</a><a class="tag" taget="_blank" href="/search/openstack/1.htm">openstack</a><a class="tag" taget="_blank" href="/search/kubernetes/1.htm">kubernetes</a><a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/k8s/1.htm">k8s</a><a class="tag" taget="_blank" href="/search/ansible/1.htm">ansible</a>
                        <div>#需要资源(软件包及镜像)或有问题的,可私博主!!!#需要资源(软件包及镜像)或有问题的,可私博主!!!#需要资源(软件包及镜像)或有问题的,可私博主!!!第一部分:私有云任务1私有云服务搭建(10分)使用提供的用户名密码,登录竞赛用的云计算平台,按要求自行使用镜像创建两台云主机,创建完云主机后确保网络正常通信,然后按要求配置服务器。根据提供安装脚本框架,补充脚本完成OpenStack平台的安装搭</div>
                    </li>
                    <li><a href="/article/1773594136444731392.htm"
                           title="word字号和mathtype磅值关系及批量修改" target="_blank">word字号和mathtype磅值关系及批量修改</a>
                        <span class="text-muted">小铁匠-Ma</span>
<a class="tag" taget="_blank" href="/search/office%E5%B0%8F%E6%8A%80%E5%B7%A7/1.htm">office小技巧</a><a class="tag" taget="_blank" href="/search/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/1.htm">经验分享</a>
                        <div>word字号和mathtype磅值关系及批量修改1.字号与磅值关系字号「八号」对应磅值5字号「七号」对应磅值5.5字号「小六」对应磅值6.5字号「六号」对应磅值7.5字号「小五」对应磅值9字号「五号」对应磅值10.5字号「小四」对应磅值12字号「四号」对应磅值14字号「小三」对应磅值15字号「三号」对应磅值16字号「小二」对应磅值18字号「二号」对应磅值22字号「小一」对应磅值24字号「一号」对应</div>
                    </li>
                    <li><a href="/article/1773552600176721920.htm"
                           title="1.计算机处理器架构+嵌入式处理器架构及知识" target="_blank">1.计算机处理器架构+嵌入式处理器架构及知识</a>
                        <span class="text-muted">vv 啊</span>
<a class="tag" taget="_blank" href="/search/arm-linux%E5%AD%A6%E4%B9%A0/1.htm">arm-linux学习</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84/1.htm">系统架构</a>
                        <div>目录一:x86-64处理器架构二:Intel80386处理器(i386)1.i3862.i686三:嵌入式Linux知识:1.MinGW2.GNU计划2.1GNU工具链概述此次只分享英特尔和ADM处理器有关于x86的架构,至于嵌入式处理器架构请查看https://en.wikipedia.org/wiki/List_of_ARM_processors一:x86-64处理器架构x86-64,也称为x</div>
                    </li>
                    <li><a href="/article/1773496455663779840.htm"
                           title="关于举办第十五届蓝桥杯全国软件和信息技术专业人才大赛项目实战赛的通知" target="_blank">关于举办第十五届蓝桥杯全国软件和信息技术专业人才大赛项目实战赛的通知</a>
                        <span class="text-muted">QSNKJJSW</span>
<a class="tag" taget="_blank" href="/search/%E8%93%9D%E6%A1%A5%E6%9D%AF/1.htm">蓝桥杯</a><a class="tag" taget="_blank" href="/search/%E8%81%8C%E5%9C%BA%E5%92%8C%E5%8F%91%E5%B1%95/1.htm">职场和发展</a><a class="tag" taget="_blank" href="/search/%E9%9D%92%E5%B0%91%E5%B9%B4%E7%BC%96%E7%A8%8B/1.htm">青少年编程</a><a class="tag" taget="_blank" href="/search/%E6%97%A0%E4%BA%BA%E6%9C%BA/1.htm">无人机</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E4%BA%BA/1.htm">机器人</a><a class="tag" taget="_blank" href="/search/%E7%A7%91%E6%8A%80/1.htm">科技</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a>
                        <div>各高等院校及相关单位:为贯彻落实《中国教育现代化2035》和《国务院关于印发新时期促进集成电路产业和软件产业高质量发展若干政策的通知》有关精神,为我国制造强国和网络强国战略提供人才支持,提高学生自主创新意识和工程实践能力,工业和信息化部人才交流中心决定举办第十五届蓝桥杯全国软件和信息技术专业人才大赛——项目实战赛。大赛连续四年入围中国高等教育学会“全国普通高校大学生竞赛排行榜”竞赛项目榜单。现将项</div>
                    </li>
                    <li><a href="/article/1773489909345091584.htm"
                           title="docker基础(一)" target="_blank">docker基础(一)</a>
                        <span class="text-muted">运维搬运工</span>
<a class="tag" taget="_blank" href="/search/%E5%AE%B9%E5%99%A8-docker/1.htm">容器-docker</a><a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/%E5%AE%B9%E5%99%A8/1.htm">容器</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a>
                        <div>相关概念介绍Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖到一个可移植的容器中,然后发布到任何流行的linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,互相之间不会有任何接口。Docker有几个重要概念:dockerfile,配置文件,用来生成dockerimagedockerimage,交付部署的最小单元docker命令与API,定义命令与接口,支持第三方系统集</div>
                    </li>
                    <li><a href="/article/1773457936023093248.htm"
                           title="max_element()和min_element()函数及用法介绍" target="_blank">max_element()和min_element()函数及用法介绍</a>
                        <span class="text-muted">藕粉和藕片</span>
<a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a>
                        <div>头文件:algorithminta[]={1,4,7,2,8,9,3,5};1.max_element(first,last)作用:返回数组区间[first,last)中最大元素的位置用法:intt=max_element(a,a+8)-a;//注意要减去a本身的地址cout<<t<<endl;//t就等于52.min_element(first,last)与上面类似注意,这两个函数返回的都是数组</div>
                    </li>
                    <li><a href="/article/1773442578922930176.htm"
                           title="【数据结构】实验一 实现顺序表各种基本运算的算法" target="_blank">【数据结构】实验一 实现顺序表各种基本运算的算法</a>
                        <span class="text-muted">张鱼·小丸子</span>
<a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%AE%9E%E9%AA%8C/1.htm">数据结构实验</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a>
                        <div>题目:实现顺序表各种基本运算的算法要求:1、建立一个顺序表,输入n个元素并输出;2、查找线性表中的最大元素并输出;3、在线性表的第i个元素前插入一个正整数x;4、删除线性表中的第j个元素;5、将线性表中的元素按升序排列;6、将线性表中的元素就地逆序(只允许用一个暂存单元);#include#defineSIZE1000usingnamespacestd;typedefstruct{int*a;//</div>
                    </li>
                    <li><a href="/article/1773439683024453632.htm"
                           title="python清华大学出版社答案_Python机器学习及实践" target="_blank">python清华大学出版社答案_Python机器学习及实践</a>
                        <span class="text-muted">weixin_39805119</span>
<a class="tag" taget="_blank" href="/search/python%E6%B8%85%E5%8D%8E%E5%A4%A7%E5%AD%A6%E5%87%BA%E7%89%88%E7%A4%BE%E7%AD%94%E6%A1%88/1.htm">python清华大学出版社答案</a>
                        <div>第1章机器学习的基础知识1.1何谓机器学习1.1.1传感器和海量数据1.1.2机器学习的重要性1.1.3机器学习的表现1.1.4机器学习的主要任务1.1.5选择合适的算法1.1.6机器学习程序的步骤1.2综合分类1.3推荐系统和深度学习1.3.1推荐系统1.3.2深度学习1.4何为Python1.4.1使用Python软件的由来1.4.2为什么使用Python1.4.3Python设计定位1.4.</div>
                    </li>
                    <li><a href="/article/1773413750980804608.htm"
                           title="linux安装docker及docker-compose 部署spring boot项目" target="_blank">linux安装docker及docker-compose 部署spring boot项目</a>
                        <span class="text-muted">时而有事儿</span>
<a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a>
                        <div>linux系统环境:centos5.14本篇描述的是在centos系统版本下安装docker,如果是ubuntu版本,请看这篇文章:linuxubuntu20安装docker和docker-compose-CSDN博客正文:安装docker和docker-compose安装docker---------运行命名等待安装完成遇到选择直接输入yyuminstall-yyum-utilsdevice-m</div>
                    </li>
                    <li><a href="/article/1773389809448976384.htm"
                           title="项目管理工具最佳实践" target="_blank">项目管理工具最佳实践</a>
                        <span class="text-muted">水岩</span>

                        <div>各个公司的最佳实践去哪儿jira自定义使用1.jira编号对应git分支命名,后台增加监控程序,新增一个分支,自动解析分支中的jira编号,自动落地到数据库,完成映射2.各个发布系统间信息同步,消息中心(IC)+数据中心(DC),广播消息加一站式查询,持续集成,推进代码检查质量,分钟级反馈质量检查反思:1.项目管好:针对一线研发人员,简单易用,而不是满足管理层的“统计度量”(...)简化分类字段,</div>
                    </li>
                    <li><a href="/article/1773371584279543808.htm"
                           title="自动化测试 —— Pytest fixture及conftest详解" target="_blank">自动化测试 —— Pytest fixture及conftest详解</a>
                        <span class="text-muted">咖啡加剁椒③</span>
<a class="tag" taget="_blank" href="/search/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/1.htm">软件测试</a><a class="tag" taget="_blank" href="/search/pytest/1.htm">pytest</a><a class="tag" taget="_blank" href="/search/%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95/1.htm">功能测试</a><a class="tag" taget="_blank" href="/search/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/1.htm">软件测试</a><a class="tag" taget="_blank" href="/search/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/1.htm">自动化测试</a><a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E4%BA%BA%E7%94%9F/1.htm">程序人生</a><a class="tag" taget="_blank" href="/search/%E8%81%8C%E5%9C%BA%E5%92%8C%E5%8F%91%E5%B1%95/1.htm">职场和发展</a>
                        <div>前言fixture是在测试函数运行前后,由pytest执行的外壳函数。fixture中的代码可以定制,满足多变的测试需求,包括定义传入测试中的数据集、配置测试前系统的初始状态、为批量测试提供数据源等等。fixture是pytest的精髓所在,类似unittest中setup/teardown,但是比它们要强大、灵活很多,它的优势是可以跨文件共享。一、Pytestfixture1.pytestfix</div>
                    </li>
                    <li><a href="/article/1773366674838323200.htm"
                           title="k8s入门到实战(十)—— CronJob详细介绍及使用示例" target="_blank">k8s入门到实战(十)—— CronJob详细介绍及使用示例</a>
                        <span class="text-muted">一弓虽</span>
<a class="tag" taget="_blank" href="/search/k8s%E5%AD%A6%E4%B9%A0/1.htm">k8s学习</a><a class="tag" taget="_blank" href="/search/kubernetes/1.htm">kubernetes</a><a class="tag" taget="_blank" href="/search/%E5%AE%B9%E5%99%A8/1.htm">容器</a><a class="tag" taget="_blank" href="/search/%E4%BA%91%E5%8E%9F%E7%94%9F/1.htm">云原生</a>
                        <div>CronJob什么是CronJob在k8s中,CronJob是一种用于定期执行任务的资源对象。它基于Cron表达式,允许您在指定的时间间隔内自动运行容器化的任务。CronJob可以定义以下属性:schedule:指定任务执行的时间表,使用标准的Cron表达式语法。例如,“0****”表示每小时执行一次任务。jobTemplate:定义要执行的任务的模板,通常是一个Pod模板。这个模板包含了任务所需</div>
                    </li>
                    <li><a href="/article/1773359500359696384.htm"
                           title="Java中HashMap底层数据结构及主要参数?" target="_blank">Java中HashMap底层数据结构及主要参数?</a>
                        <span class="text-muted">山间漫步人生路</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>在Java中,HashMap的底层数据结构主要基于数组和链表,同时在Java8及以后的版本中,当链表长度超过一定阈值时,链表会转换为红黑树来优化性能。这种结构结合了数组和链表的优点,既提供了快速的随机访问,又允许动态地扩展存储桶的大小。HashMap的主要参数包括:初始容量(InitialCapacity):这是HashMap在创建时设定的桶数组的大小。默认值为16。这个值可以根据预计存储的键值对</div>
                    </li>
                    <li><a href="/article/1773351821931249664.htm"
                           title="Java学习笔记01" target="_blank">Java学习笔记01</a>
                        <span class="text-muted">.wsy.</span>
<a class="tag" taget="_blank" href="/search/%E6%97%A5%E5%B8%B8/1.htm">日常</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a>
                        <div>1.1Java简介Java的前身是Oak,詹姆斯·高斯林是java之父。1.2Java体系Java是一种与平台无关的语言,其源代码可以被编译成一种结构中立的中间文件(.class,字节码文件)于Java虚拟机上运行。1.2.3专有名词JDK提供编译、运行Java程序所需要的种种工具及资源。JRE是运行Java所依赖的环境的集合。JVM是一个虚构出来的计算机,通过在实际的计算机上仿真模拟各种计算机功</div>
                    </li>
                    <li><a href="/article/1773301347135848448.htm"
                           title="第七章 索引及执行计划,存储引擎" target="_blank">第七章 索引及执行计划,存储引擎</a>
                        <span class="text-muted">执笔为剑</span>
<a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/MySQL%E8%BF%90%E7%BB%B4%E7%AF%87/1.htm">MySQL运维篇</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E8%BE%91%E5%99%A8/1.htm">编辑器</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a>
                        <div>第七章索引及执行计划,存储引擎1,索引及执行计划1,作用:提供类似书目录的作用,目的是优化查询2,所用的种类(根据算法)B树索引Hash索引R树FulltextGIS3,B树基于不同的查找算法分类介绍B-tree:在范围查询方面提供了更好的性能(>showengines;#存储引擎作用在表上,不同的表可能有不同的存储引擎mysql>select@@default_storage_engine;#查</div>
                    </li>
                    <li><a href="/article/1773299333102370816.htm"
                           title="RabbitMQ的事务机制" target="_blank">RabbitMQ的事务机制</a>
                        <span class="text-muted">一只小小攻城狮</span>
<a class="tag" taget="_blank" href="/search/RabbitMQ/1.htm">RabbitMQ</a><a class="tag" taget="_blank" href="/search/rabbitmq/1.htm">rabbitmq</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a>
                        <div>想要保证发送者一定能把消息发送给RabbitMQ,一种是通过Confirm机制,另一种就是通过事务机制。RabbitMQ的事务机制,允许生产者将一组操作打包成一个原子事务单元,要么全部执行成功,要么全部失败。事务提供了一种确保消息完整性的方法,但需要谨慎使用,因为他们对性能有一定的影响。RabbitMQ是基于AMQP协议实现的,RabbitMQ中,事务是通过在通道(Channel)上启用的,与事务</div>
                    </li>
                    <li><a href="/article/1773240552423227392.htm"
                           title="react native 总结" target="_blank">react native 总结</a>
                        <span class="text-muted">一切顺势而行</span>
<a class="tag" taget="_blank" href="/search/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/native/1.htm">native</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>reactapp.js相当与vueapp.vueimportReactfrom'react';import'./App.css';importReactRoutefrom'./router'import{HashRouterasRouter,Link}from'react-router-dom'classAppextendsReact.Component{constructor(props){su</div>
                    </li>
                    <li><a href="/article/1773237782928490496.htm"
                           title="【Linux】PyCharm无法启动报错及解决方法" target="_blank">【Linux】PyCharm无法启动报错及解决方法</a>
                        <span class="text-muted">不是AI</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E8%BD%AF%E4%BB%B6%E6%93%8D%E4%BD%9C/1.htm">软件操作</a><a class="tag" taget="_blank" href="/search/Linux/1.htm">Linux</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/pycharm/1.htm">pycharm</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a>
                        <div>一、问题描述如图,笔者试图在Ubuntu18.04虚拟机上运行PyCharm开发工具(已安装,安装过程可以参考我的博客Ubuntu安装PyCharm),无法启动,报错:CannotconnecttoalreadyrunningIDEinstance.Exception:Process2574isstillrunning.报错截图如下:二、解决方法通过报错信息看出,出于某种原因,进程(PID为257</div>
                    </li>
                    <li><a href="/article/1773229599585533952.htm"
                           title="【Git安装及使用学习笔记】" target="_blank">【Git安装及使用学习笔记】</a>
                        <span class="text-muted">可可西里啊</span>
<a class="tag" taget="_blank" href="/search/%E9%9B%B6%E9%9B%B6%E6%95%A3%E6%95%A3%E7%9A%84%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/1.htm">零零散散的学习笔记</a><a class="tag" taget="_blank" href="/search/git/1.htm">git</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/qt5/1.htm">qt5</a>
                        <div>Git学习笔记Git安装Git创建本地版本库以及提交文件使用Git提交代码到码云使用Git从码云拉取代码参考博客Git安装这里参考Git详细安装教程(详解Git安装过程的每一个步骤)Git创建本地版本库以及提交文件1.查看git版本信息:git--version2.设置对应用户名与邮箱地址gitconfig--globaluser.name"your_usernamegitconfig--glob</div>
                    </li>
                    <li><a href="/article/1773229094205456384.htm"
                           title="ReactNative应用打包后无网络解决方案" target="_blank">ReactNative应用打包后无网络解决方案</a>
                        <span class="text-muted">程序猿也会飞</span>
<a class="tag" taget="_blank" href="/search/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/1.htm">最佳实践</a><a class="tag" taget="_blank" href="/search/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/native/1.htm">native</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a>
                        <div>ReactNative打包应用后,应用没有网络解决方案:在android\app\src\main\res下创建xml文件夹在xml文件夹中创建network_security_config.xml文件network_security_config.xml内容:在android\app\src\main文件夹AndroidManifest.xml文件内的Application标签中添加属性andr</div>
                    </li>
                    <li><a href="/article/1773218897500110848.htm"
                           title="深入理解 React 中的 children props 和 render props" target="_blank">深入理解 React 中的 children props 和 render props</a>
                        <span class="text-muted">JudithHuang</span>
<a class="tag" taget="_blank" href="/search/React/1.htm">React</a><a class="tag" taget="_blank" href="/search/%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E6%94%BE%E5%BC%83/1.htm">从入门到放弃</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>深入理解React中的childrenprops和renderprops在React中,childrenprops和renderprops是两种常见的组件复用模式,它们都可以帮助我们更好地组织和复用组件代码。虽然它们的实现方式有所不同,但都能够有效地实现组件之间的数据传递和功能共享。childrenpropschildrenprops是React中最常见的一种组件复用模式。通过childrenpr</div>
                    </li>
                    <li><a href="/article/1773212227294265344.htm"
                           title="reactive和ref的异同、toRef和toRefs的使用" target="_blank">reactive和ref的异同、toRef和toRefs的使用</a>
                        <span class="text-muted">Niucode</span>
<a class="tag" taget="_blank" href="/search/vue3/1.htm">vue3</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>一、reactive和ref有了reactive为什么还要ref?ref处理起基本数据类型来更加的方便快捷,性能要更好。ref内部值的变化只会触发订阅它的副作用函数(effect)更新,而reactive(Proxy)内部对象的任何属性变化都会触发整个对象的重新渲染。相同点:都是Vue3提供的用于创建响应式数据的函数;在组件中都能够触发视图更新,实现数据的双向绑定。不同点:数据类型:reactiv</div>
                    </li>
                    <li><a href="/article/1773187555265675264.htm"
                           title="Kafka、ActiveMQ、RabbitMQ 及 RocketMQ区别比较" target="_blank">Kafka、ActiveMQ、RabbitMQ 及 RocketMQ区别比较</a>
                        <span class="text-muted">木西爷</span>
<a class="tag" taget="_blank" href="/search/kafka/1.htm">kafka</a><a class="tag" taget="_blank" href="/search/activemq/1.htm">activemq</a><a class="tag" taget="_blank" href="/search/rabbitmq/1.htm">rabbitmq</a><a class="tag" taget="_blank" href="/search/%E9%98%BF%E9%87%8C%E4%BA%91/1.htm">阿里云</a><a class="tag" taget="_blank" href="/search/rocketmq/1.htm">rocketmq</a>
                        <div>消息队列中间件是分布式系统中重要的组件,主要解决应用耦合、异步消息、流量削锋等问题。它可以实现高性能、高可用、可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。消息队列在电商系统、消息通讯、日志收集等应用中扮演着关键作用,以阿里为例,其研发的消息队列(RocketMQ)在历次天猫“双十一”活动中支撑了万亿级的数据洪峰,为大规模交易提供了有力保障。常见消息中间件对比特性ActiveMQRab</div>
                    </li>
                    <li><a href="/article/1773162507913986048.htm"
                           title="git使用代理解决无法clone的问题及git代理设置" target="_blank">git使用代理解决无法clone的问题及git代理设置</a>
                        <span class="text-muted">yaningli</span>

                        <div>在使用git的时候,经常需要去国外的一些网站clone,由于众所周知的原因,clone失败例如:$gitclonehttps://android.googlesource.com/platform/packages/apps/MessagingCloninginto‘Messaging’…fatal:unabletoaccess‘https://android.googlesource.com/p</div>
                    </li>
                    <li><a href="/article/1772765956330815488.htm"
                           title="大模型的学习 LLaMa和ChatGLM,minichatgpt4" target="_blank">大模型的学习 LLaMa和ChatGLM,minichatgpt4</a>
                        <span class="text-muted">贝猫说python</span>
<a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/llama/1.htm">llama</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a>
                        <div>LLaMa和ChatGLM,minichatgpt4什么情况用Bert模型,什么情况用LLaMA、ChatGLM类大模型,咋选?答:Bert的模型由多层双向的Transformer编码器组成,由12层组成,768隐藏单元,12个head,总参数量110M,约1.15亿参数量。NLU(自然语言理解)任务效果很好,单卡GPU可以部署,速度快,V100GPU下1秒能处理2千条以上。ChatGLM-6B,</div>
                    </li>
                    <li><a href="/article/1772738346825613312.htm"
                           title="javascript的数据类型及转换" target="_blank">javascript的数据类型及转换</a>
                        <span class="text-muted">田小田txt</span>

                        <div>一、JavaScript数据类型:共有string,number,boolean,object,function五种数据类型;其中Object,Date,Array为对象型;2个不包含任何值的数据类型:null,undefined。二、Typeof查看数据类型:typeof"John"//返回stringtypeof3.14//返回numbertypeofNaN//返回numbertypeoffa</div>
                    </li>
                    <li><a href="/article/1772736455160954880.htm"
                           title="淘宝超时发货赔付规则是什么" target="_blank">淘宝超时发货赔付规则是什么</a>
                        <span class="text-muted">氧惠佣金真的高</span>

                        <div>淘宝不发货赔偿规则主要包括以下几点:卖家超时未发货,需向买家支付实际成交金额的10%作为违约金。这个规定适用于大部分商品,除非是特殊商品。通过【氧恵】APP,可以免费领取淘宝及各大电商隐藏优惠券及返利,【氧恵邀请码:000888】(手机应用商店搜索“氧恵”下载,登录填写氧恵邀请码:000888,注册即享超高返利佣金,自用省,分享赚!)如果赠品在店铺中有单独出售的链接,违约金就按照赠品出售链接标明的</div>
                    </li>
                    <li><a href="/article/1772726620646342656.htm"
                           title="网购优惠app都有哪些?大额优惠券的购物app" target="_blank">网购优惠app都有哪些?大额优惠券的购物app</a>
                        <span class="text-muted">万弧导师</span>

                        <div>本大全榜单是根据某应用市场为您提供的手机淘宝优惠券app排行榜本年度榜单、及免费下载,囊括了十大淘宝优惠券app产品的热度数据、图片、用户评价、历史版本下载等信息,淘宝优惠券app有哪些、淘宝优惠券app哪个比较好用,尽在这里!相关专题文章:《淘宝优惠券app有哪些好用?手机淘宝优惠券软件推荐》推荐新出的“高省”app,使用靓号邀请码000111,直升2皇冠,返利更高,省得越多,赚的越多!淘宝下单</div>
                    </li>
                    <li><a href="/article/1772718628706254848.htm"
                           title="【循环神经网络rnn】一篇文章讲透" target="_blank">【循环神经网络rnn】一篇文章讲透</a>
                        <span class="text-muted">CX330的烟花</span>
<a class="tag" taget="_blank" href="/search/rnn/1.htm">rnn</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a>
                        <div>目录引言二、RNN的基本原理代码事例三、RNN的优化方法1长短期记忆网络(LSTM)2门控循环单元(GRU)四、更多优化方法1选择合适的RNN结构2使用并行化技术3优化超参数4使用梯度裁剪5使用混合精度训练6利用分布式训练7使用预训练模型五、RNN的应用场景1自然语言处理2语音识别3时间序列预测六、RNN的未来发展七、结论引言众所周知,CNN与循环神经网络(RNN)或生成对抗网络(GAN)等算法结</div>
                    </li>
                    <li><a href="/article/1772709062681624576.htm"
                           title="html版本号 webpack_html-webpack-plugin详解" target="_blank">html版本号 webpack_html-webpack-plugin详解</a>
                        <span class="text-muted">weixin_39994949</span>
<a class="tag" taget="_blank" href="/search/html%E7%89%88%E6%9C%AC%E5%8F%B7/1.htm">html版本号</a><a class="tag" taget="_blank" href="/search/webpack/1.htm">webpack</a>
                        <div>引言最近在react项目中初次用到了html-webapck-plugin插件,用到该插件的两个主要作用:为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口有了这种插件,那么在项目中遇到类似上面</div>
                    </li>
                                <li><a href="/article/113.htm"
                                       title="windows下源码安装golang" target="_blank">windows下源码安装golang</a>
                                    <span class="text-muted">616050468</span>
<a class="tag" taget="_blank" href="/search/golang%E5%AE%89%E8%A3%85/1.htm">golang安装</a><a class="tag" taget="_blank" href="/search/golang%E7%8E%AF%E5%A2%83/1.htm">golang环境</a><a class="tag" taget="_blank" href="/search/windows/1.htm">windows</a>
                                    <div>         系统: 64位win7, 开发环境:sublime text 2,  go版本: 1.4.1 
  
 1.  安装前准备(gcc, gdb, git) 
       golang在64位系</div>
                                </li>
                                <li><a href="/article/240.htm"
                                       title="redis批量删除带空格的key" target="_blank">redis批量删除带空格的key</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/redis/1.htm">redis</a>
                                    <div>redis批量删除的通常做法: 
 
 
redis-cli keys "blacklist*" | xargs redis-cli del 
 
上面的命令在key的前后没有空格时是可以的,但有空格就不行了: 
 

$redis-cli keys "blacklist*"
1) "blacklist:12: 361942420@qq.com</div>
                                </li>
                                <li><a href="/article/367.htm"
                                       title="oracle正则表达式的用法" target="_blank">oracle正则表达式的用法</a>
                                    <span class="text-muted">0624chenhong</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/1.htm">正则表达式</a>
                                    <div>  方括号表达示 
方括号表达式 
描述 
[[:alnum:]] 
字母和数字混合的字符 
[[:alpha:]] 
字母字符 
[[:cntrl:]] 
控制字符 
[[:digit:]] 
数字字符 
[[:graph:]] 
图像字符 
[[:lower:]] 
小写字母字符 
[[:print:]] 
打印字符 
[[:punct:]] 
标点符号字符 
[[:space:]]</div>
                                </li>
                                <li><a href="/article/494.htm"
                                       title="2048源码(核心算法有,缺少几个anctionbar,以后补上)" target="_blank">2048源码(核心算法有,缺少几个anctionbar,以后补上)</a>
                                    <span class="text-muted">不懂事的小屁孩</span>
<a class="tag" taget="_blank" href="/search/2048/1.htm">2048</a>
                                    <div>2048游戏基本上有四部分组成, 
1:主activity,包含游戏块的16个方格,上面统计分数的模块 
2:底下的gridview,监听上下左右的滑动,进行事件处理, 
3:每一个卡片,里面的内容很简单,只有一个text,记录显示的数字 
4:Actionbar,是游戏用重新开始,设置等功能(这个在底下可以下载的代码里面还没有实现) 
 
写代码的流程 
1:设计游戏的布局,基本是两块,上面是分</div>
                                </li>
                                <li><a href="/article/621.htm"
                                       title="jquery内部链式调用机理" target="_blank">jquery内部链式调用机理</a>
                                    <span class="text-muted">换个号韩国红果果</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a>
                                    <div>只需要在调用该对象合适(比如下列的setStyles)的方法后让该方法返回该对象(通过this  因为一旦一个函数称为一个对象方法的话那么在这个方法内部this(结合下面的setStyles)指向这个对象) 

function  create(type){
var element=document.createElement(type);
    //this=element;
</div>
                                </li>
                                <li><a href="/article/748.htm"
                                       title="你订酒店时的每一次点击 背后都是NoSQL和云计算" target="_blank">你订酒店时的每一次点击 背后都是NoSQL和云计算</a>
                                    <span class="text-muted">蓝儿唯美</span>
<a class="tag" taget="_blank" href="/search/NoSQL/1.htm">NoSQL</a>
                                    <div>全球最大的在线旅游公司Expedia旗下的酒店预订公司,它运营着89个网站,跨越68个国家,三年前开始实验公有云,以求让客户在预订网站上查询假期酒店时得到更快的信息获取体验。 
云端本身是用于驱动网站的部分小功能的,如搜索框的自动推荐功能,还能保证处理Hotels.com服务的季节性需求高峰整体储能。 
Hotels.com的首席技术官Thierry Bedos上个月在伦敦参加“2015 Clou</div>
                                </li>
                                <li><a href="/article/875.htm"
                                       title="java笔记1" target="_blank">java笔记1</a>
                                    <span class="text-muted">a-john</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>1,面向对象程序设计(Object-oriented Propramming,OOP):java就是一种面向对象程序设计。 
2,对象:我们将问题空间中的元素及其在解空间中的表示称为“对象”。简单来说,对象是某个类型的实例。比如狗是一个类型,哈士奇可以是狗的一个实例,也就是对象。 
3,面向对象程序设计方式的特性: 
    3.1 万物皆为对象。 
   </div>
                                </li>
                                <li><a href="/article/1002.htm"
                                       title="C语言 sizeof和strlen之间的那些事 C/C++软件开发求职面试题 必备考点(一)" target="_blank">C语言 sizeof和strlen之间的那些事 C/C++软件开发求职面试题 必备考点(一)</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/C%2FC%2B%2B%E6%B1%82%E8%81%8C%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87%E8%80%83%E7%82%B9/1.htm">C/C++求职面试必备考点</a>
                                    <div>        找工作在即,以后决定每天至少写一个知识点,主要是记录,逼迫自己动手、总结加深印象。当然如果能有一言半语让他人收益,后学幸运之至也。如有错误,还希望大家帮忙指出来。感激不尽。 
       后学保证每个写出来的结果都是自己在电脑上亲自跑过的,咱人笨,以前学的也半吊子。很多时候只能靠运行出来的结果再反过来</div>
                                </li>
                                <li><a href="/article/1129.htm"
                                       title="程序员写代码时就不要管需求了吗?" target="_blank">程序员写代码时就不要管需求了吗?</a>
                                    <span class="text-muted">asia007</span>
<a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E5%91%98%E4%B8%8D%E8%83%BD%E4%B8%80%E5%91%B3%E8%B7%9F%E9%9C%80%E6%B1%82%E8%B5%B0/1.htm">程序员不能一味跟需求走</a>
                                    <div>      编程也有2年了,刚开始不懂的什么都跟需求走,需求是怎样就用代码实现就行,也不管这个需求是否合理,是否为较好的用户体验。当然刚开始编程都会这样,但是如果有了2年以上的工作经验的程序员只知道一味写代码,而不在写的过程中思考一下这个需求是否合理,那么,我想这个程序员就只能一辈写敲敲代码了。 
      我的技术不是很好,但是就不代</div>
                                </li>
                                <li><a href="/article/1256.htm"
                                       title="Activity的四种启动模式" target="_blank">Activity的四种启动模式</a>
                                    <span class="text-muted">百合不是茶</span>
<a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/%E6%A0%88%E6%A8%A1%E5%BC%8F%E5%90%AF%E5%8A%A8/1.htm">栈模式启动</a><a class="tag" taget="_blank" href="/search/Activity%E7%9A%84%E6%A0%87%E5%87%86%E6%A8%A1%E5%BC%8F%E5%90%AF%E5%8A%A8/1.htm">Activity的标准模式启动</a><a class="tag" taget="_blank" href="/search/%E6%A0%88%E9%A1%B6%E6%A8%A1%E5%BC%8F%E5%90%AF%E5%8A%A8/1.htm">栈顶模式启动</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E5%90%AF%E5%8A%A8/1.htm">单例模式启动</a>
                                    <div>android界面的操作就是很多个activity之间的切换,启动模式决定启动的activity的生命周期 ; 
  
启动模式xml中配置 
    <activity android:name=".MainActivity" android:launchMode="standard&quo</div>
                                </li>
                                <li><a href="/article/1383.htm"
                                       title="Spring中@Autowired标签与@Resource标签的区别" target="_blank">Spring中@Autowired标签与@Resource标签的区别</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%40Resource/1.htm">@Resource</a><a class="tag" taget="_blank" href="/search/%40Autowired/1.htm">@Autowired</a><a class="tag" taget="_blank" href="/search/%40Qualifier/1.htm">@Qualifier</a>
                                    <div>Spring不但支持自己定义的@Autowired注解,还支持由JSR-250规范定义的几个注解,如:@Resource、 @PostConstruct及@PreDestroy。 
  
1. @Autowired    @Autowired是Spring 提供的,需导入    Package:org.springframewo</div>
                                </li>
                                <li><a href="/article/1510.htm"
                                       title="Changes Between SOAP 1.1 and SOAP 1.2" target="_blank">Changes Between SOAP 1.1 and SOAP 1.2</a>
                                    <span class="text-muted">sunjing</span>
<a class="tag" taget="_blank" href="/search/Changes/1.htm">Changes</a><a class="tag" taget="_blank" href="/search/Enable/1.htm">Enable</a><a class="tag" taget="_blank" href="/search/SOAP+1.1/1.htm">SOAP 1.1</a><a class="tag" taget="_blank" href="/search/SOAP+1.2/1.htm">SOAP 1.2</a>
                                    <div>JAX-WS 
SOAP Version 1.2 Part 0: Primer (Second Edition) 
SOAP Version 1.2 Part 1: Messaging Framework (Second Edition) 
SOAP Version 1.2 Part 2: Adjuncts (Second Edition) 
  
Which style of WSDL</div>
                                </li>
                                <li><a href="/article/1637.htm"
                                       title="【Hadoop二】Hadoop常用命令" target="_blank">【Hadoop二】Hadoop常用命令</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/hadoop/1.htm">hadoop</a>
                                    <div>以Hadoop运行Hadoop自带的wordcount为例, 
  
hadoop脚本位于/home/hadoop/hadoop-2.5.2/bin/hadoop,需要说明的是,这些命令的使用必须在Hadoop已经运行的情况下才能执行 
  Hadoop HDFS相关命令 
 
  hadoop fs -ls 
 
 列出HDFS文件系统的第一级文件和第一级</div>
                                </li>
                                <li><a href="/article/1764.htm"
                                       title="java异常处理(初级)" target="_blank">java异常处理(初级)</a>
                                    <span class="text-muted">白糖_</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/DAO/1.htm">DAO</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%E8%99%9A%E6%8B%9F%E6%9C%BA/1.htm">虚拟机</a><a class="tag" taget="_blank" href="/search/Ajax/1.htm">Ajax</a>
                                    <div>从学习到现在从事java开发一年多了,个人觉得对java只了解皮毛,很多东西都是用到再去慢慢学习,编程真的是一项艺术,要完成一段好的代码,需要懂得很多。 
最近项目经理让我负责一个组件开发,框架都由自己搭建,最让我头疼的是异常处理,我看了一些网上的源码,发现他们对异常的处理不是很重视,研究了很久都没有找到很好的解决方案。后来有幸看到一个200W美元的项目部分源码,通过他们对异常处理的解决方案,我终</div>
                                </li>
                                <li><a href="/article/1891.htm"
                                       title="记录整理-工作问题" target="_blank">记录整理-工作问题</a>
                                    <span class="text-muted">braveCS</span>
<a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a>
                                    <div>1)那位同学还是CSV文件默认Excel打开看不到全部结果。以为是没写进去。同学甲说文件应该不分大小。后来log一下原来是有写进去。只是Excel有行数限制。那位同学进步好快啊。 
2)今天同学说写文件的时候提示jvm的内存溢出。我马上反应说那就改一下jvm的内存大小。同学说改用分批处理了。果然想问题还是有局限性。改jvm内存大小只能暂时地解决问题,以后要是写更大的文件还是得改内存。想问题要长远啊</div>
                                </li>
                                <li><a href="/article/2018.htm"
                                       title="org.apache.tools.zip实现文件的压缩和解压,支持中文" target="_blank">org.apache.tools.zip实现文件的压缩和解压,支持中文</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/apache/1.htm">apache</a>
                                    <div>刚开始用java.util.Zip,发现不支持中文(网上有修改的方法,但比较麻烦) 
后改用org.apache.tools.zip 
org.apache.tools.zip的使用网上有更简单的例子 
下面的程序根据实际需求,实现了压缩指定目录下指定文件的方法 
 



import java.io.BufferedReader;
import java.io.BufferedWrit</div>
                                </li>
                                <li><a href="/article/2145.htm"
                                       title="读书笔记-4" target="_blank">读书笔记-4</a>
                                    <span class="text-muted">chengxuyuancsdn</span>
<a class="tag" taget="_blank" href="/search/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/1.htm">读书笔记</a>
                                    <div>1、JSTL 核心标签库标签 
2、避免SQL注入 
3、字符串逆转方法 
4、字符串比较compareTo 
5、字符串替换replace 
6、分拆字符串 
 
 
1、JSTL 核心标签库标签共有13个,
学习资料:http://www.cnblogs.com/lihuiyy/archive/2012/02/24/2366806.html
功能上分为4类:
(1)表达式控制标签:out</div>
                                </li>
                                <li><a href="/article/2272.htm"
                                       title="[物理与电子]半导体教材的一个小问题" target="_blank">[物理与电子]半导体教材的一个小问题</a>
                                    <span class="text-muted">comsci</span>
<a class="tag" taget="_blank" href="/search/%E9%97%AE%E9%A2%98/1.htm">问题</a>
                                    <div> 
 
      各种模拟电子和数字电子教材中都有这个词汇-空穴 
 
      书中对这个词汇的解释是; 当电子脱离共价键的束缚成为自由电子之后,共价键中就留下一个空位,这个空位叫做空穴 
 
      我现在回过头翻大学时候的教材,觉得这个</div>
                                </li>
                                <li><a href="/article/2399.htm"
                                       title="Flashback Database --闪回数据库" target="_blank">Flashback Database --闪回数据库</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/%E9%97%AA%E5%9B%9E%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">闪回数据库</a>
                                    <div>Flashback 技术是以Undo segment中的内容为基础的, 因此受限于UNDO_RETENTON参数。要使用flashback 的特性,必须启用自动撤销管理表空间。 
在Oracle 10g中, Flash back家族分为以下成员: Flashback Database, Flashback Drop,Flashback Query(分Flashback Query,Flashbac</div>
                                </li>
                                <li><a href="/article/2526.htm"
                                       title="简单排序:插入排序" target="_blank">简单排序:插入排序</a>
                                    <span class="text-muted">dieslrae</span>
<a class="tag" taget="_blank" href="/search/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F/1.htm">插入排序</a>
                                    <div>
    public void insertSort(int[] array){
        int temp;
        
        for(int i=1;i<array.length;i++){
            temp = array[i];
            
            for(int k=i-1;k>=0;k--)</div>
                                </li>
                                <li><a href="/article/2653.htm"
                                       title="C语言学习六指针小示例、一维数组名含义,定义一个函数输出数组的内容" target="_blank">C语言学习六指针小示例、一维数组名含义,定义一个函数输出数组的内容</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/c/1.htm">c</a>
                                    <div># include <stdio.h>

int main(void)
{
	int * p; //等价于 int *p 也等价于 int* p;
	int i = 5;
	char ch = 'A';

	//p = 5;	//error
	//p = &ch;	//error
	//p = ch;	//error

	p = &i;		// </div>
                                </li>
                                <li><a href="/article/2780.htm"
                                       title="centos下php redis扩展的安装配置3种方法" target="_blank">centos下php redis扩展的安装配置3种方法</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/redis/1.htm">redis</a>
                                    <div>方法一 
1.下载php redis扩展包     代码如下 复制代码    
#wget http://redis.googlecode.com/files/redis-2.4.4.tar.gz     
2 tar -zxvf 解压压缩包,cd /扩展包 (进入扩展包然后 运行phpize 一下是我环境中phpize的目录,/usr/local/php/bin/phpize (一定要</div>
                                </li>
                                <li><a href="/article/2907.htm"
                                       title="线程池(Executors)" target="_blank">线程池(Executors)</a>
                                    <span class="text-muted">shuizhaosi888</span>
<a class="tag" taget="_blank" href="/search/%E7%BA%BF%E7%A8%8B%E6%B1%A0/1.htm">线程池</a>
                                    <div>在java类库中,任务执行的主要抽象不是Thread,而是Executor,将任务的提交过程和执行过程解耦 
	public interface Executor {

	    void execute(Runnable command);
	}
 
  
public class RunMain implements Executor{

	@Override
	pub</div>
                                </li>
                                <li><a href="/article/3034.htm"
                                       title="openstack 快速安装笔记" target="_blank">openstack 快速安装笔记</a>
                                    <span class="text-muted">haoningabc</span>
<a class="tag" taget="_blank" href="/search/openstack/1.htm">openstack</a>
                                    <div>前提是要配置好yum源 
版本icehouse,操作系统redhat6.5 
最简化安装,不要cinder和swift 
三个节点 
172 control节点keystone glance horizon 
173 compute节点nova 
173 network节点neutron 
 

control
/etc/sysctl.conf

net.ipv4.ip_forward =</div>
                                </li>
                                <li><a href="/article/3161.htm"
                                       title="从c面向对象的实现理解c++的对象(二)" target="_blank">从c面向对象的实现理解c++的对象(二)</a>
                                    <span class="text-muted">jimmee</span>
<a class="tag" taget="_blank" href="/search/C%2B%2B/1.htm">C++</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/1.htm">面向对象</a><a class="tag" taget="_blank" href="/search/%E8%99%9A%E5%87%BD%E6%95%B0/1.htm">虚函数</a>
                                    <div>1. 类就可以看作一个struct,类的方法,可以理解为通过函数指针的方式实现的,类对象分配内存时,只分配成员变量的,函数指针并不需要分配额外的内存保存地址。 
2. c++中类的构造函数,就是进行内存分配(malloc),调用构造函数 
3. c++中类的析构函数,就时回收内存(free) 
4. c++是基于栈和全局数据分配内存的,如果是一个方法内创建的对象,就直接在栈上分配内存了。 
专门在</div>
                                </li>
                                <li><a href="/article/3288.htm"
                                       title="如何让那个一个div可以拖动" target="_blank">如何让那个一个div可以拖动</a>
                                    <span class="text-muted">lingfeng520240</span>
<a class="tag" taget="_blank" href="/search/html/1.htm">html</a>
                                    <div>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml</div>
                                </li>
                                <li><a href="/article/3415.htm"
                                       title="第10章 高级事件(中)" target="_blank">第10章 高级事件(中)</a>
                                    <span class="text-muted">onestopweb</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%8B%E4%BB%B6/1.htm">事件</a>
                                    <div>index.html 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/</div>
                                </li>
                                <li><a href="/article/3542.htm"
                                       title="计算两个经纬度之间的距离" target="_blank">计算两个经纬度之间的距离</a>
                                    <span class="text-muted">roadrunners</span>
<a class="tag" taget="_blank" href="/search/%E8%AE%A1%E7%AE%97/1.htm">计算</a><a class="tag" taget="_blank" href="/search/%E7%BA%AC%E5%BA%A6/1.htm">纬度</a><a class="tag" taget="_blank" href="/search/LBS/1.htm">LBS</a><a class="tag" taget="_blank" href="/search/%E7%BB%8F%E5%BA%A6/1.htm">经度</a><a class="tag" taget="_blank" href="/search/%E8%B7%9D%E7%A6%BB/1.htm">距离</a>
                                    <div>要解决这个问题的时候,到网上查了很多方案,最后计算出来的都与百度计算出来的有出入。下面这个公式计算出来的距离和百度计算出来的距离是一致的。 
	/**
	 * 
	 * @param longitudeA
	 *            经度A点
	 * @param latitudeA
	 *            纬度A点
	 * @param longitudeB
	 *    </div>
                                </li>
                                <li><a href="/article/3669.htm"
                                       title="最具争议的10个Java话题" target="_blank">最具争议的10个Java话题</a>
                                    <span class="text-muted">tomcat_oracle</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>1、Java8已经到来。什么!? Java8 支持lambda。哇哦,RIP Scala!     随着Java8 的发布,出现很多关于新发布的Java8是否有潜力干掉Scala的争论,最终的结论是远远没有那么简单。Java8可能已经在Scala的lambda的包围中突围,但Java并非是函数式编程王位的真正觊觎者。     
 2、Java 9 即将到来      
Oracle早在8月份就发布</div>
                                </li>
                                <li><a href="/article/3796.htm"
                                       title="zoj 3826 Hierarchical Notation(模拟)" target="_blank">zoj 3826 Hierarchical Notation(模拟)</a>
                                    <span class="text-muted">阿尔萨斯</span>
<a class="tag" taget="_blank" href="/search/rar/1.htm">rar</a>
                                    <div> 题目链接:zoj 3826 Hierarchical Notation 
 题目大意:给定一些结构体,结构体有value值和key值,Q次询问,输出每个key值对应的value值。 
 解题思路:思路很简单,写个类词法的递归函数,每次将key值映射成一个hash值,用map映射每个key的value起始终止位置,预处理完了查询就很简单了。 这题是最后10分钟出的,因为没有考虑value为{}的情</div>
                                </li>
                </ul>
            </div>
        </div>
    </div>

<div>
    <div class="container">
        <div class="indexes">
            <strong>按字母分类:</strong>
            <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a
                href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a
                href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a
                href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a
                href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a
                href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a
                href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a
                href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a
                href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a>
        </div>
    </div>
</div>
<footer id="footer" class="mb30 mt30">
    <div class="container">
        <div class="footBglm">
            <a target="_blank" href="/">首页</a> -
            <a target="_blank" href="/custom/about.htm">关于我们</a> -
            <a target="_blank" href="/search/Java/1.htm">站内搜索</a> -
            <a target="_blank" href="/sitemap.txt">Sitemap</a> -
            <a target="_blank" href="/custom/delete.htm">侵权投诉</a>
        </div>
        <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.
<!--            <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>-->
        </div>
    </div>
</footer>
<!-- 代码高亮 -->
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script>
<link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/>
<script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script>





</body>

</html>