什么是持久化缓存?

原文链接https://sebastianblade.com/using-webpack-to-achieve-long-term-cache/

缓存(cache)一直是前端性能优化的重头戏,利用好静态资源的缓存机制,可以使我们的 web 应用更加快速和稳定。仅仅简单的资源缓存是不够的,我们还要为不断更新的资源做持久化缓存(Long term cache)。以前我们能利用服务端模板和构建打包,来给资源增加版本标记,如 app.js?v=1.0.0,但在大流量的网站中,这种更新部署方式会引起下面的问题:

大公司里怎样开发和部署前端代码? - 回答作者: 张云龙

上述回答中针对前端代码部署的最终方案是:

  • 细粒度文件名替换(文件内容摘要)
  • 细粒度资源依赖追踪和摘要
  • 非覆盖式更新

但想从头实现一整套完整的前端部署方案,对于小公司来说还是非常难的。不仅如此,从目前 Web 发展趋势来看,如今前端早已不是传统 Web 应用架构能够 Hold 住的了,前后端分离,前端应用化、工程化的需求在迅速增加:模块化开发、模块依赖解析、代码压缩、图片压缩、请求数最小化、雪碧图、字体压缩、CSS 预处理、ES2015/6/7 编译、模板引擎等,都是在构建过程中要实现的功能。

自从 Node.js 和 npm 问世后,最理解前端优化需求的前端架构师/工程师也可以用自己最熟悉的 JavaScript 来实现自己想要的工程化工具了,社区也先后创造出了 Grunt、gulp、fis、webpack、rollup 等工程化工具,它们作用及架构各不相同,如 gulp 专注流程化任务,rollup 专注模块打包……

对于今天提出的问题:持久化缓存,它涉及了模块化,模块依赖,静态资源优化,模块打包,文件摘要处理等问题,如今(2016+)能把这些问题解决并做的最好的社区驱动工具有且只有 webpack

同类模块打包工具横向对比表 -> Comparison - Why webpack?

目前 webpack 2.2.0 已正式发布,是时候用最新的工具来创建更完善的前端构建了。

通过配置,逐步实现持久化缓存

一、文件 Hash 摘要

  1. webpack 配置
  2. 不稳定的 chunkhash
  3. webpack-md5-hash 的问题
  4. 如何生成稳定的模块 ID?

二、如何避免频繁的 chunk 内容变动

  1. 合理划分公共模块
  2. 代码分割
  3. import()
  4. 提取公共模块
  5. 提取 CSS
  6. 对 chunks 做最后的优化

一、文件 Hash 摘要

Hash 文件名(vendor.f02bc2.js)是实现持久化缓存的第一步,目前 webpack 有两种计算 hash 的方式:

  1. 计算所有 chunks 的 hash —— [hash]
  2. 为每个 chunk 计算 hash —— [chunkhash]

第一种是每次编译生成一个唯一 hash,适合 chunk 拆分不多的小项目,但所有资源全打上同一个 hash,无法完成持久化缓存的需求。

第二种是 webpack 为每个 chunk 资源都生成与其内容相关的 hash 摘要,为不同的资源打上不同的 hash。

相关官方文档:

webpack 配置

JS 资源的 [chunkhash] 由 webpack 计算,Images/Fonts 的 [hash] 由webpack/file-loader 计算,提取的 CSS 的 [contenthash] 由 webpack/extract-text-webpack-plugin 计算。避免冗杂,这里只写出了部分 webpack 2 配置:

JavaScript
// production
output: {
filename: '[name].[chunkhash:8].bundle.js',
chunkFilename: '[name].[chunkhash:8].js'
},
module: {
rules: [{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'url-loader',
options: {
limit: 1000,
name: 'assets/imgs/[name].[hash:8].[ext]'
}
}, {
test: /\.(woff2?|eot|ttf|otf)$/i,
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/fonts/[name].[hash:8].[ext]'
}
}]
},
plugins: [
new ExtractTextPlugin('[name].[contenthash:8].css')
]

不要在开发环境使用 [chunkhash]/[hash]/[contenthash],因为不需要在开发环境做持久缓存,而且这样会增加编译时间,开发环境用 [name] 就可以了。

不稳定的 chunkhash

不过,只是计算 chunk MD5 摘要并修改 chunk 资源文件名是不够的。Chunk 的生成还涉及到依赖解析和模块 ID 分配,这是无法稳定实质上没有变化的 chunk 文件的 chunkhash 变动问题的本源,附一个未关闭的相关 issue:

Vendor chunkhash changes when app code changes #1315

正如问题 [#1315] 描述的那样:虽然只修改了 app.js 的代码,但在最终的构建结果中,vendor.js 的 chunkhash 也被修改了,尽管 vendor.js 的内容没有实质变化。

其实这个场景比较简单,只生成了 entry 和 vendor 两个 chunk,造成上述问题的原因有两个:

  1. webpack runtime 中包含 chunks ID 及其对应 chunkhash 的对象,但 runtime 被集成到 vendor.js 中;
  2. entry 内容修改后,由于 webpack 的依赖收集规则导致构建产生的 entry chunk 对应的 ID 发生变化,webpack runtime 也因此被改变。

webpack runtime(webpackBootstrap代码不多,主要包含几个功能:

  • 全局 webpackJsonp 方法:模块读取函数,用来区分模块是否加载,并调用 __webpack_require__ 函数;
  • 私有 __webpack_require__ 方法:模块初始化执行函数,并给执行过的模块做标记;
  • 异步 chunk 加载函数(用 script 标签异步加载),加载的 chunk 内容均被 webpackJsonp 包裹的,script 加载成功会直接执行。这个函数还包含了所有生成的 chunks 的路径。在 webpack 2 中这个函数用到了 Promise,因此可能需要提供 Promise Polyfill;
  • 对 ES6 Modules 的默认导出(export default)做处理。

对于复杂项目的构建,由于模块间互相依赖,这种问题影响更为巨大:可能只改动了一个小模块,但在构建后,会发现所有与之直接或间接相关的 chunk 及其 chunkhash 都被更新了……这与我们期望的持久化缓存的需求不符。

解决这个问题的核心在于生成稳定的模块 ID,避免频繁的 chunk 内容变动。

如果你看过 #1315 的回复,可能会了解到 webpack-md5-hash 插件可以解决这个问题,甚至 webpack 2 的文档中也提示用这个插件解决。但我可以负责任的告诉你,这个插件有缺陷……不要使用它,除非你想背黑锅。

webpack-md5-hash 的问题

erm0l0v/webpack-md5-hash相关源码) 通过模块路径来排序 chunk 的所有依赖模块(仅这个 chunk 中的模块,不含被 CommonsChunkPlugin 剔除的模块),并将这些排序后的模块源代码拼接,最后用 MD5 拼接后内容的 chunkhash。插件这么做的好处是,使 chunkhash 与该 chunk 内代码做直接关联,让 chunk 与其依赖的模块 ID 无关化,无论模块 ID 如何变化,都不会影响父 chunk 的实质内容及 chunkhash。

这个方法比较有效,但在一些情景下,会使 webpack-md5-hash 失效,使构建变得不可信:

Hash does not change when only imported modules IDs change #7

比如一个简单场景:有两个入口 vendor 和 app。 
当 app.js 被修改后,其 chunk ID 随之改变,vendor.js 中 app 对应的 chunk ID 也会改变,即 vendor 内容有变动,其 chunkhash 也理应改变。但 webpack-md5-hash 是根据 chunk 内实际包含模块而生成的 chunkhash,和仅有 ID 引用的 chunk 内容无关,vendor 只包含 app chunk ID 的引用,并不包含其代码,所以此次构建中 vendor 的 chunkhash 并不会改变。这样造成的结果便是:浏览器依然会下载旧的 vendor,直接导致发版失误!

因此 webpack-md5-hash 并没有解决之前的问题:

  1. 如何生成稳定的模块ID?
  2. 如何避免频繁的 chunk 内容变动?

我们先来解决第一个问题,第二个下一节解决。

如何生成稳定的模块 ID?

默认,模块的 ID 是 webpack 根据依赖的收集顺序递增的正整数,这种 ID 分配方式不太稳定,因为修改一个被依赖较多的模块,依赖这个模块的 chunks 内容均会跟着模块的新 ID 一起改变,但实际上我们只想让用户下载有真正改动的 chunk,而不是所有依赖这个新模块的 chunk 都重新更新。

因此 webpack (1) 默认的模块 ID 分配不是很合适,我们需要其他工具来帮我们稳定 ID:

  1. OccurrenceOrderPlugin 
    这个插件可以改变默认的 ID 决定方式,让 webpack 以依赖模块出现的次数决定 ID 的值,次数越多 ID 越小。在依赖项变动不大情况下,还是一个比较好的方法,但当依赖出现次数有变化时,输出的模块 ID 则可能会有大幅变动(级联)。(目前 webpack 2 已经将此插件默认启用 ��)

  2. recordsPath 配置 
    它会输出每次构建的「模块路径(loaders + module path)」与 ID 键值对 JSON,在下次构建时直接使用 JSON 中的 ID。但当修改模块路径或 loader 时,ID 会更新。 
    同时,需要注意的是 webpack.optimize.DedupePlugin() 插件不可与 recordsPath 共存,它会改变存下来的模块 ID。

  3. NamedModulesPlugin 
    这个模块可以将依赖模块的正整数 ID 替换为相对路径(如:将 4 替换为 ./node_modules/es6-promise/dist/es6-promise.js)。

    • 开发模式,它可以让 webpack-dev-server 和 HMR 进行热更新时在控制台输出模块名字而不是纯数字;
    • 生产构建环境,它可以避免因修改内容导致的 ID 变化,从而实现持久化缓存。

    但是有两个缺点:

    • 递增 ID 替换为模块相对路径,构建出来的 chunk 会充满各种路径,使文件增大;
    • 模块(npm 和自己的模块)路径会泄露,可能导致安全问题。
  4. HashedModuleIdsPlugin 
    这是 NamedModulesPlugin 的进阶模块,它在其基础上对模块路径进行 MD5 摘要,不仅可以实现持久化缓存,同时还避免了它引起的两个问题(文件增大,路径泄露)。用 HashedModuleIdsPlugin 可以轻松地实现 chunkhash 的稳定化!

    不过这个插件只被添加到了 webpack 2 中,可能是因为 webpack 2 正式版还没有发布,HashedModuleIdsPlugin 一直没有文档,所以这里有必要指明如何使用:

    new webpack.HashedModuleIdsPlugin()

    如果使用了 HashedModuleIdsPlugin,NamedModulesPlugin 就不要再添加了。

    幸运的是,我们可以通过直接添加 HashedModuleIdsPlugin.js 为模块到 webpack 1 的配置中,也能达到同样稳定 chunkhash 的功能。

    const HashedModuleIdsPlugin = require('./HashedModuleIdsPlugin')
    // ...
    new HashedModuleIdsPlugin()

至此 chunkhash 已经稳定,是时候解决另一个问题了……

二、如何避免频繁的 chunk 内容变动?

一般场景下,我们可能不需要做太多的优化,也不用追求持久化缓存,常规配置即可:

为了节省篇幅,所有配置代码我会尽量缩减,文章最后会提供 DEMO,包含完整配置。

JavaScript
{
entry: { entry },
plugins: [
new HtmlWebpackPlugin({
chunks: ['vendor', 'entry']
}),
new webpack.optimize.CommonsChunkPlugin({
names: 'vendor',
minChunks: Infinity
})
]
}

但随着业务需求变化,最初的单页模式可能无法满足需求,而且把公共模块全部提取到 vendor 中,也无法做到较好的持久化缓存,我们需要更合理地划分并提取公共模块。

合理划分公共模块

稍大型的应用通常会包含这几个部分:

类型 公用率 使用频率 更新频率
库和工具 vue/react/redux/whatwg-fetch 等
定制 UI 库和工具 UI 组件/私有工具/语法 Polyfill/页面初始化脚本等
低频库/工具/代码 富文本编辑器/图表库/微信 JSSDK/省市 JSON 等
业务模块 包含业务逻辑的模块/View

根据公用/使用/更新率来做公共模块的划分是比较科学:

  • 库和工具 - libs
  • 定制 UI 库和工具 - vendor
  • 业务模块 - entries
  • 低频库/工具/代码 - 分割为 chunk

我们可通过指定模块的入口 chunk,来直接分离模块。以 Vue 搭建的多入口单页应用为例:

JavaScript
{
entry: {
libs: [
'es6-promise/auto',
'whatwg-fetch',
'vue',
'vue-router'
],
vendor: [
/*
* vendor 中均是非 npm 模块,
* 用 resolve.alias 修改路径,
* 避免冗长的相对路径。
*/
'assets/libs/fastclick',
'components/request',
'components/ui',
'components/bootstrap' // 初始化脚本
],
page1: 'src/pages/page1',
page2: 'src/pages/page2'
},
plugins: [
new HtmlWebpackPlugin({
// 省略部分配置
template: 'src/pages/page1/index.html',
chunks: ['libs', 'vendor', 'page1']
}),
new HtmlWebpackPlugin({
template: 'src/pages/page2/index.html',
chunks: ['libs', 'vendor', 'page2']
})
]
}

多页入口最好用脚本来扫描目录并生成,手动添加维护性较差,可参考 multi-vue

代码分割

除了入口代码的分离,我们还缺少对「低频库/工具/代码」的处理,对于这类代码最好的办法是做代码分割(Code Splitting),做到按需加载,进一步加速应用。

webpack 提供了几种添加分割点的方法:

  • CommonJs: require.ensure
  • AMD: require
  • ES6 Modules (webpack 1 不支持)

添加分割点可以主动将指定的模块分离成另一个 chunk,而不是随当前 chunk 一起打包。对于这几种情况处理非常好:

  • 比较大,且不常用的库/工具,如 D3.js、Draft.js、微信 JSSDK、querystring 等;
  • 单页应用中不常用的 router view,即某些不常访问的介面。

CommonJs 和 AMD 添加分割点的方法就不再赘述了,详情请查看文档:

注意

如果你使用了 babili (babel-minify) 来压缩你的 ES6+ 代码,请不要使用 require.ensure/require,因为 babili 会把 require 关键字压缩,导致 webpack 无法识别,造成构建问题。

import()

webpack 2 在 1.x 的基础上增加了对 ES6 模块(ES6 Modules)的支持,这意味着在webpack 2 环境下,import 导入模块语法不再需要编译为 require 了。还优化了 ES6 模块依赖(Tree-shaking,后面会谈到),并实现了 JS Loader Standard 规范定义中的 import(path) 方法。

注意

在 webpack v2.1.0-beta.28 中,System.import 方法已被废弃,因为 System.import 不在提案中了,被 import() 代替。

由于 import() 仅仅是个语法,不涉及转换,因此我们需要使用 babel 插件 syntax-dynamic-import 来让 babel 可以识别这个语法。另外 import() 也依赖编译环境,要想让运行环境通过 import() 进行按需加载,需要额外的插件:

JavaScript
const { search } = window.location
import('./components/querystring.js')
.then(querystring => {
const searchquery = querystring.parse(search)
// ...
})
.catch(err => {
Toast.error(err)
console.error(err)
})

配合 react-router:

React JSX
import { Router, Route, hashHistory } from 'react-router'
import App from './App' const lazyLoad = moduleName => _ =>
import(`./components/${moduleName}`)
.then(module => module.default)
.catch(err => console.error(err)) export default function Root () {
return (
<Router history={hashHistory}>
<Route path='/' component={App}>
<Route path='/home' getComponent={lazyLoad('Home')} />
<Route path='/posts' getComponent={lazyLoad('Posts')}>
<Route path=':id' getComponent={lazyLoad('Article')} />
</Route>
<Route path='/about' getComponent={lazyLoad('About')} />
</Route>
</Router>
)
}

用模板字符串来动态加载模块时,webpack 在编译阶段会把可能加载的模块打包,并用正则匹配加载,懒加载示例代码可见 blade254353074/react-router-lazy-import

提取公共模块

在上述例子中,我们划分了公共模块,并进行了代码分割,下面我们要做的是:提取频繁共用的模块,将 webpack runtime 构建为内联 script。

提取频繁共用的模块

提取公共模块要使用 Commons-chunk-plugin,对于持久化缓存来说,我们只需要将共用的模块打包到 libs/vendor 中即可。

模块有两种共用情况:

  • libs/vendor 与其他 chunk 共用的模块,如:vue/react/moment/whatwg-fetch
  • 多个 chunks 间共用的模块,如 page1 和 page2 共用 Header 组件

对于想把所有共用的模块全部提取的需求,我们可以做如下配置:

JavaScript
new webpack.optimize.CommonsChunkPlugin({
names: ['libs', 'vendor'].reverse()
})

用上述配置构建时,webpack 会将 webpack runtime 打包到 libs 中(names 数组末尾的 chunk),而 chunks 间共用的模块会打包到 vendor中。

如果你不想让仅有两个 chunks 共用的模块被提取到 vendor 中,而想让 n 个 chunks 共用的模块被提取出来时,可以借助 minChunks 实现。 
minChunks 是指限定模块被 chunks 依赖的最少次数,低于设定值(2 ≤ n ≤ chunks 总数)将不会被提取到公共 chunk 中。如果 chunks 太多,又不想让所有公共模块被分离到 vendor 中,可以将 minChunks 设为 Infinity,则公共 chunk 仅仅包含在 entry 中指定的模块,而不会把其他共用的模块提取进去。

JavaScript
new webpack.optimize.CommonsChunkPlugin({
names: ['libs', 'vendor'].reverse(),
// minChunks: 3
minChunks: Infinity
})

CommonsChunkPlugin 似乎还是有些 Bug,当我用 vue-style-loader 时,其中的 addStyle.js 会被添加到依赖中,但在以下配置中,addStyle.js 在打包后会被 CommonsChunkPlugin 漏掉,导致无法正常运行:

new webpack.optimize.CommonsChunkPlugin({
names: ['libs', 'vendor'].reverse()
})
manifest (清单)

尽管我们已经划分好了 chunks,也提取了公共的模块,但仅改动一个模块的代码还是会造成 Initial chunk (libs) 的变化。原因是这个初始块包含着 webpack runtime,而 runtime 还包含 chunks ID 及其对应 chunkhash 的对象。因此当任何 chunks 内容发生变化,webpack runtime 均会随之改变。

webpack runtime 中的 chunks 清单

正如文档 # Manifest File - Code Splitting - Libraries中描述的那样,我们可以通过增加一个指定的公共 chunk 来提取 runtime,从而进一步实现持久化缓存:

JavaScript
new webpack.optimize.CommonsChunkPlugin({
// 将 `manifest` 优先于 libs 进行提取,
// 则可以将 webpack runtime 分离到这个块中。
names: ['manifest', 'libs', 'vendor'].reverse()
// manifest 只是个有意义的名字,也可以改成其他名字。
})

manifest 只是个特定的名字(可能是包含了 chunks 清单,所以起名 manifest),如果仅仅是为了分离 webpack runtime,可以将 manifest 替换成任意你想要的名字。

这样在我们构建之后,就会多打包一个特别小(不足 2kb)的 manifest.js,解决了 libs 经常「被」更新的问题。不过,你可能发现了一个问题 —— manifest.js 实在是太小了,以至于不值得再为一个小 js 增加资源请求数量。

这时候我们可以引入另一个插件:inline-manifest-webpack-plugin。 
它可以将 manifest 转为内联在 html 内的 inline script,因为 manifest 经常随着构建而变化,写入到 html 中便不需要每次构建再下载新的 manifest 了,从而减少了一个小文件请求。此插件依赖 html-webpack-plugin 和 manifest 公共块,因此我们要配置 HtmlWebpackPlugin 且保持 manifest 的命名:

JavaScript
{
module: {
rules: [{
test: /\.ejs$/,
loader: 'ejs-loader'
}]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['manifest', 'libs', 'vendor'].reverse()
}),
new HtmlWebpackPlugin({
template: 'src/pages/page1/index.ejs',
chunks: ['manifest', 'libs', 'vendor', 'page1']
}),
new InlineManifestWebpackPlugin()
]
}

EJS Template:

HTML
<!-- ejs template -->
<!DOCTYPE html>
<html lang="zh-CN"> <head>
<meta charset="UTF-8">
<title>Template</title>
<%= htmlWebpackPlugin.files.webpackManifest %>
</head> <body>
<div id="app"></div>
</body> </html>

在 inline-manifest-webpack-plugin 的帮助下进行构建,最终我们的 html 便内联了 webpack runtime 脚本,提高了页面的加载速度:内联 manifest 的 html

提取 CSS

这篇文章主要针对 JS 资源的持久化缓存优化,关于 CSS 提取请看 webpack/extract-text-webpack-plugin

对 chunks 做最后的优化

webpack 中对 chunks 做优化的还有这几个插件:

  • DedupePlugin (webpack 2 已废弃)
  • OccurrenceOrderPlugin (webpack 2 默认启用)
  • LimitChunkCountPlugin
  • MinChunkSizePlugin
  • UglifyJsPlugin
  • AggressiveMergingPlugin
  • DllPlugin
  • DllReferencePlugin

关于 Tree Shaking

尽管 webpack 2 还未大量使用,但现在我们有一个不得不用 webpack 2 的理由 —— Tree Shaking

注意

为了避免 import x from 'foo' 被 babel 转换为 require,我们需要在 .babelrc 的 presets 配置中标明 "modules": false

JSON
{
"presets": [
["latest", {
"es2015": { "modules": false }
}]
],
"plugins": ["transform-runtime", "syntax-dynamic-import"],
"comments": false
}

webpack 在构建过程中只会标记出未使用的 exports,并不会直接将 dead code 去掉,因为为了使工具尽量通用,webpack 被设计为:只标注未使用的 imports/exports。真正的清除死代码工作,交给了 UglifyJS/babili 等工具。

Does webpack include unused imports in the bundle or not?

UglifyJsPlugin 不仅可以将未使用的 exports 清除,还能去掉很多不必要的代码,如无用的条件代码、未使用的变量、不可达代码等。

JavaScript
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: true }
})

如果打开了 UglifyJsPlugin 的 warning 功能,就可以在构建结果中看到清除的代码警告。

因此必须在生产环境中配置 UglifyJsPlugin,并启用 -p (production) 环境,才能真正发挥 Tree Shaking 的作用。

其他资源

引用

个人分类: Webpack打包工具

用 webpack 实现持久化缓存的更多相关文章

  1. webpack5持久化缓存

    Opt-in webpack 旨在注重构建安全而非性能.我们没有打算默认启用这一功能,主要原因在于此功能虽然有 95% 几率提升性能,但仍有 5% 的几率中断你的应用程序/工作流/构建. 什么是缓存失 ...

  2. react中数据持久化缓存redux-persist

    一.安装redux-persist: npm install redux-persist --save 二..babelrc中增加redux-persist配置: "plugins" ...

  3. 四、NOSQL之Redis持久化缓存服务基础实战第三部

    1.NOSQL的理解 NOSQL是不仅仅是SQL,说的就是sql的补充,但是不能替代SQL. nosql库:memcached.memcachedb.redis 2.redis 简介 Redis是一个 ...

  4. webpack 的第三方库分离并持久化缓存

    我们常常需要在浏览器缓存一些稳定的资源,如第三方库等.要达到这个目标,只需要两步: 1.提取出“稳定的资源”: 2.提供稳定的文件hash . 处理后的出的文件就像这样子: app.1w3ad4q4. ...

  5. webpack与浏览器缓存

    根据之前的配置,假设文件上传至服务器中,没有加hash,如果页面内容有更改,浏览器刷新的时候,请求的还是原先的文件,也就是浏览器的缓存,因为名字没有变.现在我们在上线的webpack配置中加上hash ...

  6. vue 优化hash持久化缓存

    公司用的是vue最近在学习react的打包时发现react会额外生成一个runtimeChunk,不知道具体原因所以查资料学习了下, 这里是runtime的功能,文章地址:https://sebast ...

  7. [转] react-router4 + webpack Code Splitting

    项目升级为react-router4后,就尝试着根据官方文档进行代码分割.https://reacttraining.com/react-router/web/guides/code-splittin ...

  8. react-router4 + webpack Code Splitting

    项目升级为react-router4后,就尝试着根据官方文档进行代码分割.https://reacttraining.com/react-router/web/guides/code-splittin ...

  9. 谈谈CommonsChunkPlugin抽取公共模块

    引言 webpack插件CommonsChunkPlugin的主要作用是抽取webpack项目入口chunk的公共部分,具体的用法就不做过多介绍,不太了解可以参考webpack官网介绍: 该插件是we ...

随机推荐

  1. Netty In Action中文版 - 第四章:Transports(传输)

    本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) U ...

  2. Session管理之ThreadLocal

    ref=toolbar" target="_blank">ref=toolbar_logo" target="_blank"> ...

  3. web 开发之js---js 实现网页中播放wav的一种方法(flash播放器)

    http://blog.csdn.net/whumr1/article/details/6948160

  4. 关于appcompat_v7的说明

    http://blog.csdn.net/crazykbc/article/details/21553699 问题描述: 使用eclipse创建一个Android项目时,发现project列表中会多创 ...

  5. Codevs 1744 格子染色==BZOJ 1296 粉刷匠

    1744 格子染色  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond 题解  查看运行结果     题目描述 Description 有 n 条木板需要被粉 ...

  6. 【hdu3518】Boring counting

    题意:找出一个字符串中至少重复出现两次的字串的个数(重复出现时不能重叠). 后缀数组 枚举字串长度h,对于每一次的h,利用height数组,找出连续的height大于等于h的里面最左端和最右端得为之l ...

  7. H264--1--编码原理以及I帧B帧P帧[4]

    ---------------------- 前言 ----------------------- H264是新一代的编码标准,以高压缩高质量和支持多种网络的流媒体传输著称,在编码方面,我理解的他的理 ...

  8. JfreeChart的使用1

    JfreeChart的使用 来自: 克洛泽大地(DREG) 2008-07-03 14:35:11 先从网上找点介绍. 一.简介 WW 的发展使得基于因特网的应用程序不再局限于静态或者简单的动态内容提 ...

  9. hdoj--2186--悼念512汶川大地震遇难同胞——一定要记住我爱你(模拟水题)

     悼念512汶川大地震遇难同胞--一定要记住我爱你 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (J ...

  10. 51Nod 1522 上下序列 —— 区间DP

    题目:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1522 区间DP,从大往小加: 新加入一种数有3种加法:全加左边,全 ...