{"payload":{"allShortcutsEnabled":false,"fileTree":{"":{"items":[{"name":".deploy","path":".deploy","contentType":"submodule","submoduleUrl":null,"submoduleDisplayName":".deploy"},{"name":"public","path":"public","contentType":"directory"},{"name":"scaffolds","path":"scaffolds","contentType":"directory"},{"name":"source","path":"source","contentType":"directory"},{"name":"themes","path":"themes","contentType":"directory"},{"name":"404.html","path":"404.html","contentType":"file"},{"name":"_config.yml","path":"_config.yml","contentType":"file"},{"name":"db.json","path":"db.json","contentType":"file"},{"name":"package.json","path":"package.json","contentType":"file"}],"totalCount":9}},"fileTreeProcessingTime":10.048580999999999,"foldersToFetch":[],"reducedMotionEnabled":null,"repo":{"id":19771859,"defaultBranch":"master","name":"myHexoBlog","ownerLogin":"waakaakaa","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2014-05-14T08:22:15.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/1912279?v=4","public":true,"private":false,"isOrgOwned":false},"refInfo":{"name":"master","listCacheKey":"v0:1400056123.0","canEdit":false,"refType":"branch","currentOid":"86e65b65f901d115ae2d816273b212c50f2899a9"},"path":"db.json","currentUser":null,"blob":{"rawLines":["{\"Asset\":{\"doe6jt3v63dymz60\":{\"_id\":\"doe6jt3v63dymz60\",\"source\":\"themes/modernist/source/fancybox/blank.gif\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/fancybox/blank.gif\",\"path\":\"fancybox/blank.gif\"},\"10rowtpnnmr46lu9\":{\"_id\":\"10rowtpnnmr46lu9\",\"source\":\"themes/modernist/source/fancybox/fancybox_loading.gif\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/fancybox/fancybox_loading.gif\",\"path\":\"fancybox/fancybox_loading.gif\"},\"4z4tzspwyxxyl05x\":{\"_id\":\"4z4tzspwyxxyl05x\",\"source\":\"themes/modernist/source/fancybox/fancybox_overlay.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/fancybox/fancybox_overlay.png\",\"path\":\"fancybox/fancybox_overlay.png\"},\"716yg3k186xxyrv9\":{\"_id\":\"716yg3k186xxyrv9\",\"source\":\"themes/modernist/source/fancybox/fancybox_sprite.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/fancybox/fancybox_sprite.png\",\"path\":\"fancybox/fancybox_sprite.png\"},\"s40ps38y58lg5yeo\":{\"_id\":\"s40ps38y58lg5yeo\",\"source\":\"themes/modernist/source/fancybox/jquery.fancybox.css\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/fancybox/jquery.fancybox.css\",\"path\":\"fancybox/jquery.fancybox.css\"},\"qo35sgu8cr7ht6ib\":{\"_id\":\"qo35sgu8cr7ht6ib\",\"source\":\"themes/modernist/source/fancybox/jquery.fancybox.pack.js\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/fancybox/jquery.fancybox.pack.js\",\"path\":\"fancybox/jquery.fancybox.pack.js\"},\"dlapkk2m85vqjxfc\":{\"_id\":\"dlapkk2m85vqjxfc\",\"source\":\"themes/modernist/source/js/gallery.js\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/js/gallery.js\",\"path\":\"js/gallery.js\"},\"2z100d9ly42rj4z8\":{\"_id\":\"2z100d9ly42rj4z8\",\"source\":\"themes/modernist/source/js/jquery.imagesloaded.min.js\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/js/jquery.imagesloaded.min.js\",\"path\":\"js/jquery.imagesloaded.min.js\"},\"017b2mxnv2zb4seo\":{\"_id\":\"017b2mxnv2zb4seo\",\"source\":\"themes/modernist/source/js/moment.min.js\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/js/moment.min.js\",\"path\":\"js/moment.min.js\"},\"edfwbvx0cl1wh3ju\":{\"_id\":\"edfwbvx0cl1wh3ju\",\"source\":\"themes/modernist/source/js/scale.fix.js\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/js/scale.fix.js\",\"path\":\"js/scale.fix.js\"},\"xhn8bu1oarugucqa\":{\"_id\":\"xhn8bu1oarugucqa\",\"source\":\"themes/modernist/source/css/style.styl\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/css/style.styl\",\"path\":\"css/style.styl\"},\"n5wedm8snzoty9az\":{\"_id\":\"n5wedm8snzoty9az\",\"source\":\"themes/modernist/source/css/fonts/FontAwesome.otf\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/css/fonts/FontAwesome.otf\",\"path\":\"css/fonts/FontAwesome.otf\"},\"we0uy113jbk6e42p\":{\"_id\":\"we0uy113jbk6e42p\",\"source\":\"themes/modernist/source/css/fonts/fontawesome-webfont.eot\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/css/fonts/fontawesome-webfont.eot\",\"path\":\"css/fonts/fontawesome-webfont.eot\"},\"g1qaa0yo426cr5r7\":{\"_id\":\"g1qaa0yo426cr5r7\",\"source\":\"themes/modernist/source/css/fonts/fontawesome-webfont.svg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/css/fonts/fontawesome-webfont.svg\",\"path\":\"css/fonts/fontawesome-webfont.svg\"},\"n6oxeg5qr5eql9w4\":{\"_id\":\"n6oxeg5qr5eql9w4\",\"source\":\"themes/modernist/source/css/fonts/fontawesome-webfont.ttf\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/css/fonts/fontawesome-webfont.ttf\",\"path\":\"css/fonts/fontawesome-webfont.ttf\"},\"sg42auwk01mcvoty\":{\"_id\":\"sg42auwk01mcvoty\",\"source\":\"themes/modernist/source/css/fonts/fontawesome-webfont.woff\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/css/fonts/fontawesome-webfont.woff\",\"path\":\"css/fonts/fontawesome-webfont.woff\"},\"jwiadryr09nk37gx\":{\"_id\":\"jwiadryr09nk37gx\",\"source\":\"themes/modernist/source/css/images/checker.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/css/images/checker.png\",\"path\":\"css/images/checker.png\"},\"njyhrn54jhck3hsf\":{\"_id\":\"njyhrn54jhck3hsf\",\"source\":\"source/img/代码审查最佳实践/1.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/代码审查最佳实践/1.png\",\"path\":\"img/代码审查最佳实践/1.png\"},\"aq150o42igozpasa\":{\"_id\":\"aq150o42igozpasa\",\"source\":\"source/img/龙纹身女孩和SQL/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/龙纹身女孩和SQL/1.jpg\",\"path\":\"img/龙纹身女孩和SQL/1.jpg\"},\"hcp78vf15o7bu8x2\":{\"_id\":\"hcp78vf15o7bu8x2\",\"source\":\"source/img/龙纹身女孩和SQL/2.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/龙纹身女孩和SQL/2.jpg\",\"path\":\"img/龙纹身女孩和SQL/2.jpg\"},\"uwt55n23ot625eth\":{\"_id\":\"uwt55n23ot625eth\",\"source\":\"source/img/龙纹身女孩和SQL/3.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/龙纹身女孩和SQL/3.jpg\",\"path\":\"img/龙纹身女孩和SQL/3.jpg\"},\"24x2inbkmlbokh5o\":{\"_id\":\"24x2inbkmlbokh5o\",\"source\":\"source/img/龙纹身女孩和SQL/4.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/龙纹身女孩和SQL/4.jpg\",\"path\":\"img/龙纹身女孩和SQL/4.jpg\"},\"zqvhn7ryinfaogri\":{\"_id\":\"zqvhn7ryinfaogri\",\"source\":\"source/img/龙纹身女孩和SQL/5.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/龙纹身女孩和SQL/5.jpg\",\"path\":\"img/龙纹身女孩和SQL/5.jpg\"},\"em6xkeciuoztg1t5\":{\"_id\":\"em6xkeciuoztg1t5\",\"source\":\"source/img/漫画:如果用编程语言来写作文/1.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/漫画:如果用编程语言来写作文/1.png\",\"path\":\"img/漫画:如果用编程语言来写作文/1.png\"},\"3ow1l21fu9d6uci9\":{\"_id\":\"3ow1l21fu9d6uci9\",\"source\":\"source/img/通天塔导游 - 细数各种编程语言优缺点/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/通天塔导游 - 细数各种编程语言优缺点/1.jpg\",\"path\":\"img/通天塔导游 - 细数各种编程语言优缺点/1.jpg\"},\"kh6u5ix62aik3y2j\":{\"_id\":\"kh6u5ix62aik3y2j\",\"source\":\"source/img/转载:hexo你的博客/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/转载:hexo你的博客/1.jpg\",\"path\":\"img/转载:hexo你的博客/1.jpg\"},\"4i3t21ljj6wvid0w\":{\"_id\":\"4i3t21ljj6wvid0w\",\"source\":\"source/img/转载:hexo你的博客/2.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/转载:hexo你的博客/2.jpg\",\"path\":\"img/转载:hexo你的博客/2.jpg\"},\"8swer9mchykev332\":{\"_id\":\"8swer9mchykev332\",\"source\":\"source/img/AOP那点事儿/2.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/AOP那点事儿/2.png\",\"path\":\"img/AOP那点事儿/2.png\"},\"vtev7vfk7yiutbar\":{\"_id\":\"vtev7vfk7yiutbar\",\"source\":\"source/img/AOP那点事儿/1.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/AOP那点事儿/1.png\",\"path\":\"img/AOP那点事儿/1.png\"},\"dx5zkkknkkm4vfgb\":{\"_id\":\"dx5zkkknkkm4vfgb\",\"source\":\"source/img/Every Programmer Should Know These Latency Numbers/1.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Every Programmer Should Know These Latency Numbers/1.png\",\"path\":\"img/Every Programmer Should Know These Latency Numbers/1.png\"},\"im65ec7hsmi7o6aa\":{\"_id\":\"im65ec7hsmi7o6aa\",\"source\":\"source/img/Peter Norvig:编程语言的选择并不重要/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Peter Norvig:编程语言的选择并不重要/1.jpg\",\"path\":\"img/Peter Norvig:编程语言的选择并不重要/1.jpg\"},\"rtm6194omjlzrny6\":{\"_id\":\"rtm6194omjlzrny6\",\"source\":\"themes/modernist/source/1.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/1.png\",\"path\":\"1.png\"},\"wte78btj7bzdg79w\":{\"_id\":\"wte78btj7bzdg79w\",\"source\":\"themes/modernist/source/2.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/themes/modernist/source/2.png\",\"path\":\"2.png\"},\"6t5rbm8jdx3x1g0m\":{\"_id\":\"6t5rbm8jdx3x1g0m\",\"source\":\"source/img/Android之Adapter用法总结/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之Adapter用法总结/1.jpg\",\"path\":\"img/Android之Adapter用法总结/1.jpg\"},\"aucgnqjdf2q0kjoi\":{\"_id\":\"aucgnqjdf2q0kjoi\",\"source\":\"source/img/Android之Adapter用法总结/2.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之Adapter用法总结/2.png\",\"path\":\"img/Android之Adapter用法总结/2.png\"},\"bgdrbvp4rq0lel2x\":{\"_id\":\"bgdrbvp4rq0lel2x\",\"source\":\"source/img/Android之Adapter用法总结/3.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之Adapter用法总结/3.png\",\"path\":\"img/Android之Adapter用法总结/3.png\"},\"vel2vnmemcdfugg8\":{\"_id\":\"vel2vnmemcdfugg8\",\"source\":\"source/img/Android之Adapter用法总结/4.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之Adapter用法总结/4.png\",\"path\":\"img/Android之Adapter用法总结/4.png\"},\"e09a8wt3fyr3c718\":{\"_id\":\"e09a8wt3fyr3c718\",\"source\":\"source/img/Android之Adapter用法总结/5.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之Adapter用法总结/5.jpg\",\"path\":\"img/Android之Adapter用法总结/5.jpg\"},\"ma6yjnvvxr7l0bbs\":{\"_id\":\"ma6yjnvvxr7l0bbs\",\"source\":\"source/img/Android之Adapter用法总结/6.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之Adapter用法总结/6.png\",\"path\":\"img/Android之Adapter用法总结/6.png\"},\"3h2eqs1f627wuxih\":{\"_id\":\"3h2eqs1f627wuxih\",\"source\":\"source/img/Android之TabHost布局/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之TabHost布局/1.jpg\",\"path\":\"img/Android之TabHost布局/1.jpg\"},\"iyug6wbwd2yc62gz\":{\"_id\":\"iyug6wbwd2yc62gz\",\"source\":\"source/img/Android之TabHost布局/2.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之TabHost布局/2.jpg\",\"path\":\"img/Android之TabHost布局/2.jpg\"},\"3tcie9ajcd9mgqau\":{\"_id\":\"3tcie9ajcd9mgqau\",\"source\":\"source/img/Android之TabHost布局/3.gif\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android之TabHost布局/3.gif\",\"path\":\"img/Android之TabHost布局/3.gif\"},\"h2311j1mnmx29wa2\":{\"_id\":\"h2311j1mnmx29wa2\",\"source\":\"source/img/Android布局大全/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android布局大全/1.jpg\",\"path\":\"img/Android布局大全/1.jpg\"},\"f96dva38ofikpnpg\":{\"_id\":\"f96dva38ofikpnpg\",\"source\":\"source/img/Android异步处理/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android异步处理/1.jpg\",\"path\":\"img/Android异步处理/1.jpg\"},\"v6rxwcwyng7a3w9b\":{\"_id\":\"v6rxwcwyng7a3w9b\",\"source\":\"source/img/Android异步处理/2.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android异步处理/2.jpg\",\"path\":\"img/Android异步处理/2.jpg\"},\"7uaswuubzj42jr59\":{\"_id\":\"7uaswuubzj42jr59\",\"source\":\"source/img/Android异步处理/3.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android异步处理/3.png\",\"path\":\"img/Android异步处理/3.png\"},\"07jiwiz24o32dy1s\":{\"_id\":\"07jiwiz24o32dy1s\",\"source\":\"source/img/Android异步处理/4.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android异步处理/4.jpg\",\"path\":\"img/Android异步处理/4.jpg\"},\"vyctd9lihvi961nv\":{\"_id\":\"vyctd9lihvi961nv\",\"source\":\"source/img/android自定义listview实现圆角/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/android自定义listview实现圆角/1.jpg\",\"path\":\"img/android自定义listview实现圆角/1.jpg\"},\"5mj2phcwci0g3lmi\":{\"_id\":\"5mj2phcwci0g3lmi\",\"source\":\"source/img/android自定义listview实现圆角/2.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/android自定义listview实现圆角/2.jpg\",\"path\":\"img/android自定义listview实现圆角/2.jpg\"},\"ve1sj30ryx7yfc52\":{\"_id\":\"ve1sj30ryx7yfc52\",\"source\":\"source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/1.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/1.png\",\"path\":\"img/Android UI--自定义ListView(实现下拉刷新+加载更多)/1.png\"},\"znw2yse2vcamk96u\":{\"_id\":\"znw2yse2vcamk96u\",\"source\":\"source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/2.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/2.png\",\"path\":\"img/Android UI--自定义ListView(实现下拉刷新+加载更多)/2.png\"},\"7eeokkr5bybmp72h\":{\"_id\":\"7eeokkr5bybmp72h\",\"source\":\"source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/3.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/3.png\",\"path\":\"img/Android UI--自定义ListView(实现下拉刷新+加载更多)/3.png\"},\"17u35gipgtbu81pz\":{\"_id\":\"17u35gipgtbu81pz\",\"source\":\"source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/4.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/4.png\",\"path\":\"img/Android UI--自定义ListView(实现下拉刷新+加载更多)/4.png\"},\"t1x35yc7h7w2fgx0\":{\"_id\":\"t1x35yc7h7w2fgx0\",\"source\":\"source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/5.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/5.png\",\"path\":\"img/Android UI--自定义ListView(实现下拉刷新+加载更多)/5.png\"},\"cec2degd2v6wpnot\":{\"_id\":\"cec2degd2v6wpnot\",\"source\":\"source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/6.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/Android UI--自定义ListView(实现下拉刷新+加载更多)/6.png\",\"path\":\"img/Android UI--自定义ListView(实现下拉刷新+加载更多)/6.png\"},\"ovekzcfttdgzoa4a\":{\"_id\":\"ovekzcfttdgzoa4a\",\"source\":\"source/img/android自定义View/1.png\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/android自定义View/1.png\",\"path\":\"img/android自定义View/1.png\"},\"v1l6jmf87qahjw8d\":{\"_id\":\"v1l6jmf87qahjw8d\",\"source\":\"source/img/android自定义View(2)/1.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/android自定义View(2)/1.jpg\",\"path\":\"img/android自定义View(2)/1.jpg\"},\"imxbhlelg6by9uw7\":{\"_id\":\"imxbhlelg6by9uw7\",\"source\":\"source/img/android自定义View(2)/2.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/android自定义View(2)/2.jpg\",\"path\":\"img/android自定义View(2)/2.jpg\"},\"9mxiuriv1k7jobg2\":{\"_id\":\"9mxiuriv1k7jobg2\",\"source\":\"source/img/android自定义View(2)/3.jpg\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/android自定义View(2)/3.jpg\",\"path\":\"img/android自定义View(2)/3.jpg\"},\"6ddskcjrr32m4twf\":{\"_id\":\"6ddskcjrr32m4twf\",\"source\":\"source/img/android自定义View(2)/4.gif\",\"mtime\":1444880072000,\"modified\":false,\"full_source\":\"/Users/apple/Documents/github/myHexoBlog/source/img/android自定义View(2)/4.gif\",\"path\":\"img/android自定义View(2)/4.gif\"}},\"Cache\":{\"o9kdznwcz6t02z6s\":{\"_id\":\"o9kdznwcz6t02z6s\",\"content\":\"title: 'AOP那点事儿'\\ndate: 2014-05-14 15:08:13\\n\\n---\\n又是一个周末,刚给宝宝喂完牛奶,终于让她睡着了。所以现在我才能腾出手来,坐在电脑面前给大家写这篇文章。\\n\\n今天我要和大家分享的是 AOP(Aspect-Oriented Programming)这个东西,名字与 OOP 仅差一个字母,其实它是对 OOP 编程方式的一种补充,并非是取而代之。翻译过来就是“面向方面编程”,可我更倾向于翻译为“面向切面编程”。它听起有些的神秘,为什么呢?当你看完这篇文章的时候,就会知道,我们做的很重要的工作就是去写这个“切面” 。那么什么是“切面”呢?\\n\\n没错!就是用一把刀来切一坨面。注意,相对于面而言,我们一定是横着来切它,这简称为“横切”。可以把一段代码想象成一坨面,同样也可以用一把刀来横切它,下面要做的就是如何去实现这把刀!\\n\\n
\\n\\n需要澄清的是,这个概念不是由 Rod Johnson(老罗)提出的。其实很早以前就有了,目前最知名最强大的 Java 开源项目就是 AspectJ 了,然而它的前身是 AspectWerkz(该项目已经在 2005 年停止更新),这才是 AOP 的老祖宗。老罗(一个头发秃得和我老爸有一拼的天才)写了一个叫做 Spring 框架,从此一炮走红,成为了 Spring 之父。他在自己的 IOC 的基础之上,又实现了一套 AOP 的框架,后来仿佛发现自己越来越走进深渊里,在不能自拔的时候,有人建议他还是集成 AspectJ 吧,他在万般无奈之下才接受了该建议。于是,我们现在用得最多的想必就是 Spring + AspectJ 这种 AOP 框架了。\\n\\n那么 AOP 到底是什么?如何去使用它?本文将逐步带您进入 AOP 的世界,让您感受到前所未有的畅快!\\n\\n不过在开始讲解 AOP 之前,我想有必要回忆一下这段代码:\\n\\n**1. 写死代码**\\n\\n先来一个接口:\\n\\n\\tpublic interface Greeting {\\n\\t\\n\\t void sayHello(String name);\\n\\t\\n\\t}\\n\\n还有一个实现类:\\n\\n\\tpublic class GreetingImpl implements Greeting {\\n\\t\\n\\t @Override\\n\\t public void sayHello(String name) {\\n\\t before();\\n\\t System.out.println(\\\"Hello! \\\" + name);\\n\\t after();\\n\\t }\\n\\t\\n\\t private void before() {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t private void after() {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\nbefore() 与 after() 方法写死在 sayHello() 方法体中了,这样的代码的味道非常不好。如果哪位仁兄大量写了这样的代码,肯定要被你的架构师骂个够呛。\\n\\n比如:我们要统计每个方法的执行时间,以对性能作出评估,那是不是要在每个方法的一头一尾都做点手脚呢?\\n\\n再比如:我们要写一个 JDBC 程序,那是不是也要在方法的开头去连接数据库,方法的末尾去关闭数据库连接呢?\\n\\n这样的代码只会把程序员累死,把架构师气死!\\n\\n一定要想办法对上面的代码进行重构,首先给出三个解决方案:\\n\\n**2. 静态代理**\\n\\n最简单的解决方案就是使用静态代理模式了,我们单独为 GreetingImpl 这个类写一个代理类:\\n\\n\\tpublic class GreetingProxy implements Greeting {\\n\\t\\n\\t private GreetingImpl greetingImpl;\\n\\t\\n\\t public GreetingProxy(GreetingImpl greetingImpl) {\\n\\t this.greetingImpl = greetingImpl;\\n\\t }\\n\\t\\n\\t @Override\\n\\t public void sayHello(String name) {\\n\\t before();\\n\\t greetingImpl.sayHello(name);\\n\\t after();\\n\\t }\\n\\t\\n\\t private void before() {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t private void after() {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n就用这个 GreetingProxy 去代理 GreetingImpl,下面看看客户端如何来调用:\\n\\n\\tpublic class Client {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t Greeting greetingProxy = new GreetingProxy(new GreetingImpl());\\n\\t greetingProxy.sayHello(\\\"Jack\\\");\\n\\t }\\n\\t}\\n\\n这样写没错,但是有个问题,XxxProxy 这样的类会越来越多,如何才能将这些代理类尽可能减少呢?最好只有一个代理类。\\n\\n这时我们就需要使用 JDK 提供的动态代理了。 \\n\\n**3. JDK 动态代理**\\n\\n\\tpublic class JDKDynamicProxy implements InvocationHandler {\\n\\t\\n\\t private Object target;\\n\\t\\n\\t public JDKDynamicProxy(Object target) {\\n\\t this.target = target;\\n\\t }\\n\\t\\n\\t @SuppressWarnings(\\\"unchecked\\\")\\n\\t public
T getProxy() {\\n\\t return (T) Proxy.newProxyInstance(\\n\\t target.getClass().getClassLoader(),\\n\\t target.getClass().getInterfaces(),\\n\\t this\\n\\t );\\n\\t }\\n\\t\\n\\t @Override\\n\\t public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\\n\\t before();\\n\\t Object result = method.invoke(target, args);\\n\\t after();\\n\\t return result;\\n\\t }\\n\\t\\n\\t private void before() {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t private void after() {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n客户端是这样调用的:\\n\\n\\tpublic class Client {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t Greeting greeting = new JDKDynamicProxy(new GreetingImpl()).getProxy();\\n\\t greeting.sayHello(\\\"Jack\\\");\\n\\t }\\n\\t}\\n\\n这样所有的代理类都合并到动态代理类中了,但这样做仍然存在一个问题:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类。有什么方法可以解决呢?\\n\\n**4. CGLib 动态代理**\\n\\n我们使用开源的 CGLib 类库可以代理没有接口的类,这样就弥补了 JDK 的不足。CGLib 动态代理类是这样玩的:\\n\\n\\tpublic class CGLibDynamicProxy implements MethodInterceptor {\\n\\t\\n\\t private static CGLibDynamicProxy instance = new CGLibDynamicProxy();\\n\\t\\n\\t private CGLibDynamicProxy() {\\n\\t }\\n\\t\\n\\t public static CGLibDynamicProxy getInstance() {\\n\\t return instance;\\n\\t }\\n\\t\\n\\t @SuppressWarnings(\\\"unchecked\\\")\\n\\t public
T getProxy(Class
cls) {\\n\\t return (T) Enhancer.create(cls, this);\\n\\t }\\n\\t\\n\\t @Override\\n\\t public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {\\n\\t before();\\n\\t Object result = proxy.invokeSuper(target, args);\\n\\t after();\\n\\t return result;\\n\\t }\\n\\t\\n\\t private void before() {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t private void after() {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n以上代码中了 Singleton 模式,那么客户端调用也更加轻松了:\\n\\n\\tpublic class Client {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t Greeting greeting = CGLibDynamicProxy.getInstance().getProxy(GreetingImpl.class);\\n\\t greeting.sayHello(\\\"Jack\\\");\\n\\t }\\n\\t}\\n\\n到此为止,我们能做的都做了,问题似乎全部都解决了。但事情总不会那么完美,而我们一定要追求完美!\\n\\n老罗搞出了一个 AOP 框架,能否做到完美而优雅呢?请大家继续往下看吧!\\n\\n**5. Spring AOP:前置增强、后置增强、环绕增强(编程式)**\\n\\n在 Spring AOP 的世界里,与 AOP 相关的术语实在太多,往往也是我们的“拦路虎”,不管是看那本书或是技术文档,在开头都要将这些术语逐个灌输给读者。我想这完全是在吓唬人了,其实没那么复杂的,大家放轻松一点。\\n\\n我们上面例子中提到的 before() 方法,在 Spring AOP 里就叫 Before Advice(前置增强)。有些人将 Advice 直译为“通知”,我想这是不太合适的,因为它根本就没有“通知”的含义,而是对原有代码功能的一种“增强”。再说,CGLib 中也有一个 Enhancer 类,它就是一个增强类。\\n\\n此外,像 after() 这样的方法就叫 After Advice(后置增强),因为它放在后面来增强代码的功能。\\n\\n如果能把 before() 与 after() 合并在一起,那就叫 Around Advice(环绕增强),就像汉堡一样,中间夹一根火腿。\\n\\n这三个概念是不是轻松地理解了呢?如果是,那就继续吧!\\n\\n我们下面要做的就是去实现这些所谓的“增强类”,让他们横切到代码中,而不是将这些写死在代码中。\\n\\n先来一个前置增强类吧:\\n\\n\\tpublic class GreetingBeforeAdvice implements MethodBeforeAdvice {\\n\\t\\n\\t @Override\\n\\t public void before(Method method, Object[] args, Object target) throws Throwable {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t}\\n\\n注意:这个类实现了 org.springframework.aop.MethodBeforeAdvice 接口,我们将需要增强的代码放入其中。\\n\\n再来一个后置增强类吧:\\n\\n\\tpublic class GreetingAfterAdvice implements AfterReturningAdvice {\\n\\t\\n\\t @Override\\n\\t public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n类似地,这个类实现了 org.springframework.aop.AfterReturningAdvice 接口。\\n\\n最后用一个客户端来把它们集成起来,看看如何调用吧:\\n\\n\\tpublic class Client {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t ProxyFactory proxyFactory = new ProxyFactory(); // 创建代理工厂\\n\\t proxyFactory.setTarget(new GreetingImpl()); // 射入目标类对象\\n\\t proxyFactory.addAdvice(new GreetingBeforeAdvice()); // 添加前置增强\\n\\t proxyFactory.addAdvice(new GreetingAfterAdvice()); // 添加后置增强 \\n\\t\\n\\t Greeting greeting = (Greeting) proxyFactory.getProxy(); // 从代理工厂中获取代理\\n\\t greeting.sayHello(\\\"Jack\\\"); // 调用代理的方法\\n\\t }\\n\\t}\\n\\n请仔细阅读以上代码及其注释,您会发现,其实 Spring AOP 还是挺简单的,对吗?\\n\\n当然,我们完全可以只定义一个增强类,让它同时实现 MethodBeforeAdvice 与 AfterReturningAdvice 这两个接口,如下:\\n\\n\\tpublic class GreetingBeforeAndAfterAdvice implements MethodBeforeAdvice, AfterReturningAdvice {\\n\\t\\n\\t @Override\\n\\t public void before(Method method, Object[] args, Object target) throws Throwable {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t @Override\\n\\t public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n这样我们只需要使用一行代码,同时就可以添加前置与后置增强:\\n\\n\\tproxyFactory.addAdvice(new GreetingBeforeAndAfterAdvice());\\n\\n刚才有提到“环绕增强”,其实这个东西可以把“前置增强”与“后置增强”的功能给合并起来,无需让我们同时实现以上两个接口。\\n\\n\\tpublic class GreetingAroundAdvice implements MethodInterceptor {\\n\\t\\n\\t @Override\\n\\t public Object invoke(MethodInvocation invocation) throws Throwable {\\n\\t before();\\n\\t Object result = invocation.proceed();\\n\\t after();\\n\\t return result;\\n\\t }\\n\\t\\n\\t private void before() {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t private void after() {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n环绕增强类需要实现 org.aopalliance.intercept.MethodInterceptor 接口。注意,这个接口不是 Spring 提供的,它是 AOP 联盟(一个很牛逼的联盟)写的,Spring 只是借用了它。\\n\\n在客户端中同样也需要将该增强类的对象添加到代理工厂中:\\n\\n\\tproxyFactory.addAdvice(new GreetingAroundAdvice());\\n\\n好了,这就是 Spring AOP 的基本用法,但这只是“编程式”而已。Spring AOP 如果只是这样,那就太傻逼了,它曾经也是一度宣传用 Spring 配置文件的方式来定义 Bean 对象,把代码中的 new 操作全部解脱出来。\\n\\n**6. Spring AOP:前置增强、后置增强、环绕增强(声明式)**\\n\\n先看 Spring 配置文件是如何写的吧:\\n\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
greetingAroundAdvice
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\n一定要阅读以上代码的注释,其实使用 ProxyFactoryBean 就可以取代前面的 ProxyFactory,其实它们俩就一回事儿。我认为 interceptorNames 应该改名为 adviceNames 或许会更容易让人理解,不就是往这个属性里面添加增强类吗?\\n\\n此外,如果只有一个增强类,可以使用以下方法来简化:\\n\\n\\t...\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t...\\n\\n还需要注意的是,这里使用了 Spring 2.5+ 的特性“Bean 扫描”,这样我们就无需在 Spring 配置文件里不断地定义
了,从而解脱了我们的双手。\\n\\n看看这是有多么的简单:\\n\\n\\t@Component\\n\\tpublic class GreetingImpl implements Greeting {\\n\\t\\n\\t ...\\n\\t}\\n\\t\\n\\t@Component\\n\\tpublic class GreetingAroundAdvice implements MethodInterceptor {\\n\\t\\n\\t ...\\n\\t}\\n\\n最后看看客户端吧:\\n\\n\\tpublic class Client {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t ApplicationContext context = new ClassPathXmlApplicationContext(\\\"aop/demo/spring.xml\\\"); // 获取 Spring Context\\n\\t Greeting greeting = (Greeting) context.getBean(\\\"greetingProxy\\\"); // 从 Context 中根据 id 获取 Bean 对象(其实就是一个代理)\\n\\t greeting.sayHello(\\\"Jack\\\"); // 调用代理的方法\\n\\t }\\n\\t}\\n\\n代码量确实少了,我们将配置性的代码放入配置文件,这样也有助于后期维护。更重要的是,代码只关注于业务逻辑,而将配置放入文件中。这是一条最佳实践!\\n\\n除了上面提到的那三类增强以外,其实还有两类增强也需要了解一下,关键的时候您要能想得到它们才行。 \\n\\n**7. Spring AOP:抛出增强**\\n\\n程序报错,抛出异常了,一般的做法是打印到控制台或日志文件中,这样很多地方都得去处理,有没有一个一劳永逸的方法呢?那就是 Throws Advice(抛出增强),它确实很强,不信你就继续往下看:\\n\\n\\t@Component\\n\\tpublic class GreetingImpl implements Greeting {\\n\\t\\n\\t @Override\\n\\t public void sayHello(String name) {\\n\\t System.out.println(\\\"Hello! \\\" + name);\\n\\t\\n\\t throw new RuntimeException(\\\"Error\\\"); // 故意抛出一个异常,看看异常信息能否被拦截到\\n\\t }\\n\\t}\\n\\n下面是抛出增强类的代码:\\n\\n\\t@Component\\n\\tpublic class GreetingThrowAdvice implements ThrowsAdvice {\\n\\t\\n\\t public void afterThrowing(Method method, Object[] args, Object target, Exception e) {\\n\\t System.out.println(\\\"---------- Throw Exception ----------\\\");\\n\\t System.out.println(\\\"Target Class: \\\" + target.getClass().getName());\\n\\t System.out.println(\\\"Method Name: \\\" + method.getName());\\n\\t System.out.println(\\\"Exception Message: \\\" + e.getMessage());\\n\\t System.out.println(\\\"-------------------------------------\\\");\\n\\t }\\n\\t}\\n\\n抛出增强类需要实现 org.springframework.aop.ThrowsAdvice 接口,在接口方法中可获取方法、参数、目标对象、异常对象等信息。我们可以把这些信息统一写入到日志中,当然也可以持久化到数据库中。\\n\\n这个功能确实太棒了!但还有一个更厉害的增强。如果某个类实现了 A 接口,但没有实现 B 接口,那么该类可以调用 B 接口的方法吗?如果您没有看到下面的内容,一定不敢相信原来这是可行的!\\n\\n**8. Spring AOP:引入增强**\\n\\n以上提到的都是对方法的增强,那能否对类进行增强呢?用 AOP 的行话来讲,对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)。而 Introduction Advice(引入增强)就是对类的功能增强,它也是 Spring AOP 提供的最后一种增强。建议您一开始千万不要去看《Spring Reference》,否则您一定会后悔的。因为当您看了以下的代码示例后,一定会彻底明白什么才是引入增强。\\n\\n定义了一个新接口 Apology(道歉):\\n\\n\\tpublic interface Apology {\\n\\t\\n\\t void saySorry(String name);\\n\\t}\\n\\n但我不想在代码中让 GreetingImpl 直接去实现这个接口,我想在程序运行的时候动态地实现它。因为假如我实现了这个接口,那么我就一定要改写 GreetingImpl 这个类,关键是我不想改它,或许在真实场景中,这个类有1万行代码,我实在是不敢动了。于是,我需要借助 Spring 的引入增强。这个有点意思了!\\n\\n\\t@Component\\n\\tpublic class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology {\\n\\t\\n\\t @Override\\n\\t public Object invoke(MethodInvocation invocation) throws Throwable {\\n\\t return super.invoke(invocation);\\n\\t }\\n\\t\\n\\t @Override\\n\\t public void saySorry(String name) {\\n\\t System.out.println(\\\"Sorry! \\\" + name);\\n\\t }\\n\\t}\\n\\n以上定义了一个引入增强类,扩展了 org.springframework.aop.support.DelegatingIntroductionInterceptor 类,同时也实现了新定义的 Apology 接口。在类中首先覆盖了父类的 invoke() 方法,然后实现了 Apology 接口的方法。我就是想用这个增强类去丰富 GreetingImpl 类的功能,那么这个 GreetingImpl 类无需直接实现 Apology 接口,就可以在程序运行的时候调用 Apology 接口的方法了。这简直是太神奇的!\\n\\n看看是如何配置的吧:\\n\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\n需要注意 proxyTargetClass 属性,它表明是否代理目标类,默认为 false,也就是代理接口了,此时 Spring 就用 JDK 动态代理。如果为 true,那么 Spring 就用 CGLib 动态代理。这简直就是太方便了!Spring 封装了这一切,让程序员不在关心那么多的细节。我们要向老罗同志致敬,您是我们心中永远的 idol!\\n\\n当您看完下面的客户端代码,一定会完全明白以上的这一切:\\n\\n\\tpublic class Client {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t ApplicationContext context = new ClassPathXmlApplicationContext(\\\"aop/demo/spring.xml\\\");\\n\\t GreetingImpl greetingImpl = (GreetingImpl) context.getBean(\\\"greetingProxy\\\"); // 注意:转型为目标类,而并非它的 Greeting 接口\\n\\t greetingImpl.sayHello(\\\"Jack\\\");\\n\\t\\n\\t Apology apology = (Apology) greetingImpl; // 将目标类强制向上转型为 Apology 接口(这是引入增强给我们带来的特性,也就是“接口动态实现”功能)\\n\\t apology.saySorry(\\\"Jack\\\");\\n\\t }\\n\\t}\\n\\n没想到 saySorry() 方法原来是可以被 greetingImpl 对象来直接调用的,只需将其强制转换为该接口即可。\\n\\n我们再次感谢 Spring AOP,感谢老罗给我们提供了这么强大的特性!\\n\\n其实,Spring AOP 还有很多精彩的地方,下一篇将介绍更多更有价值的 AOP 技术,让大家得到更多的收获。\\n\\n未完,待续...\\n\\n***\\n\\n本文是《AOP 那点事儿》的续集。\\n\\n在上篇中,我们从写死代码,到使用代理;从编程式 Spring AOP 到声明式 Spring AOP。一切都朝着简单实用主义的方向在发展。沿着 Spring AOP 的方向,Rod Johnson(老罗)花了不少心思,都是为了让我们使用 Spring 框架时不会感受到麻烦,但事实却并非如此。那么,后来老罗究竟对 Spring AOP 做了哪些改进呢?\\n\\n现在继续!\\n\\n**9. Spring AOP:切面**\\n\\n之前谈到的 AOP 框架其实可以将它理解为一个拦截器框架,但这个拦截器似乎非常武断。比如说,如果它拦截了一个类,那么它就拦截了这个类中所有的方法。类似地,当我们在使用动态代理的时候,其实也遇到了这个问题。需要在代码中对所拦截的方法名加以判断,才能过滤出我们需要拦截的方法,想想这种做法确实不太优雅。在大量的真实项目中,似乎我们只需要拦截特定的方法就行了,没必要拦截所有的方法。于是,老罗同志借助了 AOP 的一个很重要的工具,Advisor(切面),来解决这个问题。它也是 AOP 中的核心!是我们关注的重点!\\n\\n也就是说,我们可以通过切面,将增强类与拦截匹配条件组合在一起,然后将这个切面配置到 ProxyFactory 中,从而生成代理。\\n\\n这里提到这个“拦截匹配条件”在 AOP 中就叫做 Pointcut(切点),其实说白了就是一个基于表达式的拦截条件罢了。\\n\\n归纳一下,Advisor(切面)封装了 Advice(增强)与 Pointcut(切点 )。当您理解了这句话后,就往下看吧。\\n\\n我在 GreetingImpl 类中故意增加了两个方法,都以“good”开头。下面要做的就是拦截这两个新增的方法,而对 sayHello() 方法不作拦截。\\n\\n\\t@Component\\n\\tpublic class GreetingImpl implements Greeting {\\n\\t\\n\\t @Override\\n\\t public void sayHello(String name) {\\n\\t System.out.println(\\\"Hello! \\\" + name);\\n\\t }\\n\\t\\n\\t public void goodMorning(String name) {\\n\\t System.out.println(\\\"Good Morning! \\\" + name);\\n\\t }\\n\\t\\n\\t public void goodNight(String name) {\\n\\t System.out.println(\\\"Good Night! \\\" + name);\\n\\t }\\n\\t}\\n\\n在 Spring AOP 中,老罗已经给我们提供了许多切面类了,这些切面类我个人感觉最好用的就是基于正则表达式的切面类。看看您就明白了:\\n\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\n注意以上代理对象的配置中的 interceptorNames,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。\\n\\n需要强调的是,这里的切点表达式是基于正则表达式的。示例中的“aop.demo.GreetingImpl.good.*”表达式后面的“.*”表示匹配所有字符,翻译过来就是“匹配 aop.demo.GreetingImpl 类中以 good 开头的方法”。\\n\\n除了 RegexpMethodPointcutAdvisor 以外,在 Spring AOP 中还提供了几个切面类,比如:\\n\\n* DefaultPointcutAdvisor:默认切面(可扩展它来自定义切面)\\n\\n* NameMatchMethodPointcutAdvisor:根据方法名称进行匹配的切面\\n\\n* StaticMethodMatcherPointcutAdvisor:用于匹配静态方法的切面\\n\\n总的来说,让用户去配置一个或少数几个代理,似乎还可以接受,但随着项目的扩大,代理配置就会越来越多,配置的重复劳动就多了,麻烦不说,还很容易出错。能否让 Spring 框架为我们自动生成代理呢?\\n\\n**10. Spring AOP:自动代理(扫描 Bean 名称)**\\n\\nSpring AOP 提供了一个可根据 Bean 名称来自动生成代理的工具,它就是 BeanNameAutoProxyCreator。是这样配置的:\\n\\n\\t
\\n\\t
\\n\\t\\n\\t ...\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\n以上使用 BeanNameAutoProxyCreator 只为后缀为“Impl”的 Bean 生成代理。需要注意的是,这个地方我们不能定义代理接口,也就是 interfaces 属性,因为我们根本就不知道这些 Bean 到底实现了多少接口。此时不能代理接口,而只能代理类。所以这里提供了一个新的配置项,它就是 optimize。若为 true 时,则可对代理生成策略进行优化(默认是 false 的)。也就是说,如果该类有接口,就代理接口(使用 JDK 动态代理);如果没有接口,就代理类(使用 CGLib 动态代理)。而并非像之前使用的 proxyTargetClass 属性那样,强制代理类,而不考虑代理接口的方式。可见 Spring AOP 确实为我们提供了很多很好地服务!\\n\\n既然 CGLib 可以代理任何的类了,那为什么还要用 JDK 的动态代理呢?肯定您会这样问。\\n\\n根据多年来实际项目经验得知:CGLib 创建代理的速度比较慢,但创建代理后运行的速度却非常快,而 JDK 动态代理正好相反。如果在运行的时候不断地用 CGLib 去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用 CGLib 去创建代理,并放入 Spring 的 ApplicationContext 中以备后用。\\n\\n以上这个例子只能匹配目标类,而不能进一步匹配其中指定的方法,要匹配方法,就要考虑使用切面与切点了。Spring AOP 基于切面也提供了一个自动代理生成器:DefaultAdvisorAutoProxyCreator。\\n\\n**11. Spring AOP:自动代理(扫描切面配置)**\\n\\n为了匹配目标类中的指定方法,我们仍然需要在 Spring 中配置切面与切点:\\n\\n\\t
\\n\\t
\\n\\t\\n\\t ...\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\n这里无需再配置代理了,因为代理将会由 DefaultAdvisorAutoProxyCreator 自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。\\n\\n看来不管怎样简化,老罗始终解决不了切面的配置,这件繁重的手工劳动。在 Spring 配置文件中,仍然会存在大量的切面配置。然而在有很多情况下 Spring AOP 所提供的切面类真的不太够用了,比如:想拦截指定注解的方法,我们就必须扩展 DefaultPointcutAdvisor 类,自定义一个切面类,然后在 Spring 配置文件中进行切面配置。不做不知道,做了您就知道相当麻烦了。\\n\\n老罗的解决方案似乎已经掉进了切面类的深渊,这还真是所谓的“面向切面编程”了,最重要的是切面,最麻烦的也是切面。\\n\\n必须要把切面配置给简化掉,Spring 才能有所突破! \\n\\n神一样的老罗总算认识到了这一点,接受了网友们的建议,集成了 AspectJ,同时也保留了以上提到的切面与代理配置方式(为了兼容老的项目,更为了维护自己的面子)。将 Spring 与 AspectJ 集成与直接使用 AspectJ 是不同的,我们不需要定义 AspectJ 类(它是扩展了 Java 语法的一种新的语言,还需要特定的编译器),只需要使用 AspectJ 切点表达式即可(它是比正则表达式更加友好的表现形式)。\\n\\n**12. Spring + AspectJ(基于注解:通过 AspectJ execution 表达式拦截方法)**\\n\\n下面以一个最简单的例子,实现之前提到的环绕增强。先定义一个 Aspect 切面类:\\n\\n\\t@Aspect\\n\\t@Component\\n\\tpublic class GreetingAspect {\\n\\t\\n\\t @Around(\\\"execution(* aop.demo.GreetingImpl.*(..))\\\")\\n\\t public Object around(ProceedingJoinPoint pjp) throws Throwable {\\n\\t before();\\n\\t Object result = pjp.proceed();\\n\\t after();\\n\\t return result;\\n\\t }\\n\\t\\n\\t private void before() {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t private void after() {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n注意:类上面标注的 @Aspect 注解,这表明该类是一个 Aspect(其实就是 Advisor)。该类无需实现任何的接口,只需定义一个方法(方法叫什么名字都无所谓),只需在方法上标注 @Around 注解,在注解中使用了 AspectJ 切点表达式。方法的参数中包括一个 ProceedingJoinPoint 对象,它在 AOP 中称为 Joinpoint(连接点),可以通过该对象获取方法的任何信息,例如:方法名、参数等。\\n\\n下面重点来分析一下这个切点表达式:\\n\\nexecution(* aop.demo.GreetingImpl.*(..))\\n\\nexecution():表示拦截方法,括号中可定义需要匹配的规则。\\n\\n第一个“*”:表示方法的返回值是任意的。\\n\\n第二个“*”:表示匹配该类中所有的方法。\\n\\n(..):表示方法的参数是任意的。\\n\\n是不是比正则表达式的可读性更强呢?如果想匹配指定的方法,只需将第二个“*”改为指定的方法名称即可。\\n\\n如何配置呢?看看是有多简单吧:\\n\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t\\n\\t
\\n\\t\\n\\t
\\n\\n两行配置就行了,不需要配置大量的代理,更不需要配置大量的切面,真是太棒了!需要注意的是 proxy-target-class=\\\"true\\\" 属性,它的默认值是 false,默认只能代理接口(使用 JDK 动态代理),当为 true 时,才能代理目标类(使用 CGLib 动态代理)。\\n\\nSpring 与 AspectJ 结合的威力远远不止这些,我们来点时尚的吧,拦截指定注解的方法怎么样?\\n\\n**13. Spring + AspectJ(基于注解:通过 AspectJ @annotation 表达式拦截方法)** \\n\\n为了拦截指定的注解的方法,我们首先需要来自定义一个注解:\\n\\n\\t@Target(ElementType.METHOD)\\n\\t@Retention(RetentionPolicy.RUNTIME)\\n\\tpublic @interface Tag {\\n\\t}\\n\\n以上定义了一个 @Tag 注解,此注解可标注在方法上,在运行时生效。\\n\\n只需将前面的 Aspect 类的切点表达式稍作改动:\\n\\n\\t@Aspect\\n\\t@Component\\n\\tpublic class GreetingAspect {\\n\\t\\n\\t @Around(\\\"@annotation(aop.demo.Tag)\\\")\\n\\t public Object around(ProceedingJoinPoint pjp) throws Throwable {\\n\\t ...\\n\\t }\\n\\t\\n\\t ...\\n\\t}\\n\\n这次使用了 @annotation() 表达式,只需在括号内定义需要拦截的注解名称即可。\\n\\n直接将 @Tag 注解定义在您想要拦截的方法上,就这么简单:\\n\\n\\t@Component\\n\\tpublic class GreetingImpl implements Greeting {\\n\\t\\n\\t @Tag\\n\\t @Override\\n\\t public void sayHello(String name) {\\n\\t System.out.println(\\\"Hello! \\\" + name);\\n\\t }\\n\\t}\\n\\n以上示例中只有一个方法,如果有多个方法,我们只想拦截其中某些时,这种解决方案会更加有价值。\\n\\n除了 @Around 注解外,其实还有几个相关的注解,稍微归纳一下吧:\\n\\n* @Before:前置增强\\n\\n* @After:后置增强\\n\\n* @Around:环绕增强\\n\\n* @AfterThrowing:抛出增强\\n\\n* @DeclareParents:引入增强\\n\\n此外还有一个 @AfterReturning(返回后增强),也可理解为 Finally 增强,相当于 finally 语句,它是在方法结束后执行的,也就说说,它比 @After 还要晚一些。\\n\\n最后一个 @DeclareParents 竟然就是引入增强!为什么不叫做 @Introduction 呢?我也不知道为什么,但它干的活就是引入增强。\\n\\n**14. Spring + AspectJ(引入增强)**\\n\\n为了实现基于 AspectJ 的引入增强,我们同样需要定义一个 Aspect 类:\\n\\n\\t@Aspect\\n\\t@Component\\n\\tpublic class GreetingAspect {\\n\\t\\n\\t @DeclareParents(value = \\\"aop.demo.GreetingImpl\\\", defaultImpl = ApologyImpl.class)\\n\\t private Apology apology;\\n\\t}\\n\\n只需要在 Aspect 类中定义一个需要引入增强的接口,它也就是运行时需要动态实现的接口。在这个接口上标注了 @DeclareParents 注解,该注解有两个属性:\\n\\n* value:目标类\\n\\n* defaultImpl:引入接口的默认实现类\\n\\n我们只需要对引入的接口提供一个默认实现类即可完成引入增强:\\n\\n\\tpublic class ApologyImpl implements Apology {\\n\\t\\n\\t @Override\\n\\t public void saySorry(String name) {\\n\\t System.out.println(\\\"Sorry! \\\" + name);\\n\\t }\\n\\t}\\n\\n以上这个实现会在运行时自动增强到 GreetingImpl 类中,也就是说,无需修改 GreetingImpl 类的代码,让它去实现 Apology 接口,我们单独为该接口提供一个实现类(ApologyImpl),来做 GreetingImpl 想做的事情。\\n\\n还是用一个客户端来尝试一下吧:\\n\\n\\tpublic class Client {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t ApplicationContext context = new ClassPathXmlApplicationContext(\\\"aop/demo/spring.xml\\\");\\n\\t Greeting greeting = (Greeting) context.getBean(\\\"greetingImpl\\\");\\n\\t greeting.sayHello(\\\"Jack\\\");\\n\\t\\n\\t Apology apology = (Apology) greeting; // 强制转型为 Apology 接口\\n\\t apology.saySorry(\\\"Jack\\\");\\n\\t }\\n\\t}\\n\\n从 Spring ApplicationContext 中获取 greetingImpl 对象(其实是个代理对象),可转型为自己静态实现的接口 Greeting,也可转型为自己动态实现的接口 Apology,切换起来非常方便。\\n\\n使用 AspectJ 的引入增强比原来的 Spring AOP 的引入增强更加方便了,而且还可面向接口编程(以前只能面向实现类),这也算一个非常巨大的突破。\\n\\n这一切真的已经非常强大也非常灵活了!但仍然还是有用户不能尝试这些特性,因为他们还在使用 JDK 1.4(根本就没有注解这个东西),怎么办呢?没想到 Spring AOP 为那些遗留系统也考虑到了。\\n\\n**15. Spring + AspectJ(基于配置)**\\n\\n除了使用 @Aspect 注解来定义切面类以外,Spring AOP 也提供了基于配置的方式来定义切面类:\\n\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\t\\n\\t
\\n\\t\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t
\\n\\t\\n\\t
\\n\\n使用
元素来进行 AOP 配置,在其子元素中配置切面,包括增强类型、目标方法、切点等信息。\\n\\n无论您是不能使用注解,还是不愿意使用注解,Spring AOP 都能为您提供全方位的服务。\\n\\n好了,我所知道的比较实用的 AOP 技术都在这里了,当然还有一些更为高级的特性,由于个人精力有限,这里就不再深入了。\\n\\n还是依照惯例,给一张牛逼的高清无码思维导图,总结一下以上各个知识点:\\n\\n\\n\\n最后给一张 UML 类图描述一下 Spring AOP 的整体架构:\\n\\n\",\"mtime\":1444880072000,\"source\":\"source/_posts/aop-那点事儿.md\"},\"yxpd5pgyse6ar7y4\":{\"_id\":\"yxpd5pgyse6ar7y4\",\"content\":\"title: 'introduction-to-mou'\\ndate: 2014-12-4 00:57:45\\n\\n---\\n# Mou\\n\\n\\n\\n## Overview\\n\\n**Mou**, the missing Markdown editor for *web developers*.\\n\\n
\\n\\n### Syntax\\n\\n#### Strong and Emphasize \\n\\n**strong** or __strong__ ( Cmd + B )\\n\\n*emphasize* or _emphasize_ ( Cmd + I )\\n\\n**Sometimes I want a lot of text to be bold.\\nLike, seriously, a _LOT_ of text**\\n\\n#### Blockquotes\\n\\n> Right angle brackets > are used for block quotes.\\n\\n#### Links and Email\\n\\nAn email
link.\\n\\nSimple inline link
, another inline link [Smaller](http://smallerapp.com), one more inline link with title [Resize](http://resizesafari.com \\\"a Safari extension\\\").\\n\\nA [reference style][id] link. Input id, then anywhere in the doc, define the link with corresponding id:\\n\\n[id]: http://mouapp.com \\\"Markdown editor on Mac OS X\\\"\\n\\nTitles ( or called tool tips ) in the links are optional.\\n\\n#### Images\\n\\nAn inline image , title is optional.\\n\\nA ![Resize icon][2] reference style image.\\n\\n[2]: http://resizesafari.com/favicon.ico \\\"Title\\\"\\n\\n#### Inline code and Block code\\n\\nInline code are surround by `backtick` key. To create a block code:\\n\\n\\tIndent each line by at least 1 tab, or 4 spaces.\\n var Mou = exactlyTheAppIwant; \\n\\n#### Ordered Lists\\n\\nOrdered lists are created using \\\"1.\\\" + Space:\\n\\n1. Ordered list item\\n2. Ordered list item\\n3. Ordered list item\\n\\n#### Unordered Lists\\n\\nUnordered list are created using \\\"*\\\" + Space:\\n\\n* Unordered list item\\n* Unordered list item\\n* Unordered list item \\n\\nOr using \\\"-\\\" + Space:\\n\\n- Unordered list item\\n- Unordered list item\\n- Unordered list item\\n\\n#### Hard Linebreak\\n\\nEnd a line with two or more spaces will create a hard linebreak, called `
` in HTML. ( Control + Return ) \\nAbove line ended with 2 spaces.\\n\\n#### Horizontal Rules\\n\\nThree or more asterisks or dashes:\\n\\n***\\n\\n---\\n\\n- - - -\\n\\n#### Headers\\n\\nSetext-style:\\n\\nThis is H1\\n==========\\n\\nThis is H2\\n----------\\n\\natx-style:\\n\\n# This is H1\\n## This is H2\\n### This is H3\\n#### This is H4\\n##### This is H5\\n###### This is H6\\n\\n\\n### Extra Syntax\\n\\n#### Footnotes\\n\\nFootnotes work mostly like reference-style links. A footnote is made of two things: a marker in the text that will become a superscript number; a footnote definition that will be placed in a list of footnotes at the end of the document. A footnote looks like this:\\n\\nThat's some text with a footnote.[^1]\\n\\n[^1]: And that's the footnote.\\n\\n\\n#### Strikethrough\\n\\nWrap with 2 tilde characters:\\n\\n~~Strikethrough~~\\n\\n\\n#### Fenced Code Blocks\\n\\nStart with a line containing 3 or more backticks, and ends with the first line with the same number of backticks:\\n\\n```\\nFenced code blocks are like Stardard Markdown’s regular code\\nblocks, except that they’re not indented and instead rely on\\na start and end fence lines to delimit the code block.\\n```\\n\\n#### Tables\\n\\nA simple table looks like this:\\n\\nFirst Header | Second Header | Third Header\\n------------ | ------------- | ------------\\nContent Cell | Content Cell | Content Cell\\nContent Cell | Content Cell | Content Cell\\n\\nIf you wish, you can add a leading and tailing pipe to each line of the table:\\n\\n| First Header | Second Header | Third Header |\\n| ------------ | ------------- | ------------ |\\n| Content Cell | Content Cell | Content Cell |\\n| Content Cell | Content Cell | Content Cell |\\n\\nSpecify alignement for each column by adding colons to separator lines:\\n\\nFirst Header | Second Header | Third Header\\n:----------- | :-----------: | -----------:\\nLeft | Center | Right\\nLeft | Center | Right\\n\\n\\n### Shortcuts\\n\\n#### View\\n\\n* Toggle live preview: Shift + Cmd + I\\n* Toggle Words Counter: Shift + Cmd + W\\n* Toggle Transparent: Shift + Cmd + T\\n* Toggle Floating: Shift + Cmd + F\\n* Left/Right = 1/1: Cmd + 0\\n* Left/Right = 3/1: Cmd + +\\n* Left/Right = 1/3: Cmd + -\\n* Toggle Writing orientation: Cmd + L\\n* Toggle fullscreen: Control + Cmd + F\\n\\n#### Actions\\n\\n* Copy HTML: Option + Cmd + C\\n* Strong: Select text, Cmd + B\\n* Emphasize: Select text, Cmd + I\\n* Inline Code: Select text, Cmd + K\\n* Strikethrough: Select text, Cmd + U\\n* Link: Select text, Control + Shift + L\\n* Image: Select text, Control + Shift + I\\n* Select Word: Control + Option + W\\n* Select Line: Shift + Cmd + L\\n* Select All: Cmd + A\\n* Deselect All: Cmd + D\\n* Convert to Uppercase: Select text, Control + U\\n* Convert to Lowercase: Select text, Control + Shift + U\\n* Convert to Titlecase: Select text, Control + Option + U\\n* Convert to List: Select lines, Control + L\\n* Convert to Blockquote: Select lines, Control + Q\\n* Convert to H1: Cmd + 1\\n* Convert to H2: Cmd + 2\\n* Convert to H3: Cmd + 3\\n* Convert to H4: Cmd + 4\\n* Convert to H5: Cmd + 5\\n* Convert to H6: Cmd + 6\\n* Convert Spaces to Tabs: Control + [\\n* Convert Tabs to Spaces: Control + ]\\n* Insert Current Date: Control + Shift + 1\\n* Insert Current Time: Control + Shift + 2\\n* Insert entity <: control="" shift="" insert="" entity="">: Control + Shift + .\\n* Insert entity &: Control + Shift + 7\\n* Insert entity Space: Control + Shift + Space\\n* Insert Scriptogr.am Header: Control + Shift + G\\n* Shift Line Left: Select lines, Cmd + [\\n* Shift Line Right: Select lines, Cmd + ]\\n* New Line: Cmd + Return\\n* Comment: Cmd + /\\n* Hard Linebreak: Control + Return\\n\\n#### Edit\\n\\n* Auto complete current word: Esc\\n* Find: Cmd + F\\n* Close find bar: Esc\\n\\n#### Post\\n\\n* Post on Scriptogr.am: Control + Shift + S\\n* Post on Tumblr: Control + Shift + T\\n\\n#### Export\\n\\n* Export HTML: Option + Cmd + E\\n* Export PDF: Option + Cmd + P\\n\\n\\n### And more?\\n\\nDon't forget to check Preferences, lots of useful options are there.\\n\\nFollow [@chenluois](http://twitter.com/chenluois) on Twitter for the latest news.\\n\\nFor feedback, use the menu `Help` - `Send Feedback`\",\"mtime\":1444880072000,\"source\":\"source/_posts/introduction-to-mou.md\"},\"opqnkqp8nzhbf21a\":{\"_id\":\"opqnkqp8nzhbf21a\",\"content\":\"title: 'Learn X in Y minutes:Where X=clojure'\\ndate: 2014-05-22 11:46:32\\n\\n---\\nGet the code: [learnclojure.clj](http://learnxinyminutes.com/docs/files/learnclojure.clj)\\n\\nClojure is a Lisp family language developed for the Java Virtual Machine. It has a much stronger emphasis on pure [functional programming](https://en.wikipedia.org/wiki/Functional_programming) than Common Lisp, but includes several [STM](https://en.wikipedia.org/wiki/Software_transactional_memory) utilities to handle state as it comes up.\\n\\n
\\n\\nThis combination allows it to handle concurrent processing very simply, and often automatically.\\n\\n(You need a version of Clojure 1.2 or newer)\\n\\n\\t; Comments start with semicolons.\\n\\t\\n\\t; Clojure is written in \\\"forms\\\", which are just\\n\\t; lists of things inside parentheses, separated by whitespace.\\n\\t;\\n\\t; The clojure reader assumes that the first thing is a\\n\\t; function or macro to call, and the rest are arguments.\\n\\t\\n\\t; The first call in a file should be ns, to set the namespace\\n\\t(ns learnclojure)\\n\\t\\n\\t; More basic examples:\\n\\t\\n\\t; str will create a string out of all its arguments\\n\\t(str \\\"Hello\\\" \\\" \\\" \\\"World\\\") ; => \\\"Hello World\\\"\\n\\t\\n\\t; Math is straightforward\\n\\t(+ 1 1) ; => 2\\n\\t(- 2 1) ; => 1\\n\\t(* 1 2) ; => 2\\n\\t(/ 2 1) ; => 2\\n\\t\\n\\t; Equality is =\\n\\t(= 1 1) ; => true\\n\\t(= 2 1) ; => false\\n\\t\\n\\t; You need not for logic, too\\n\\t(not true) ; => false\\n\\t\\n\\t; Nesting forms works as you expect\\n\\t(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2\\n\\t\\n\\t; Types\\n\\t;;;;;;;;;;;;;\\n\\t\\n\\t; Clojure uses Java's object types for booleans, strings and numbers.\\n\\t; Use `class` to inspect them.\\n\\t(class 1) ; Integer literals are java.lang.Long by default\\n\\t(class 1.); Float literals are java.lang.Double\\n\\t(class \\\"\\\"); Strings always double-quoted, and are java.lang.String\\n\\t(class false) ; Booleans are java.lang.Boolean\\n\\t(class nil); The \\\"null\\\" value is called nil\\n\\t\\n\\t; If you want to create a literal list of data, use ' to stop it from\\n\\t; being evaluated\\n\\t'(+ 1 2) ; => (+ 1 2)\\n\\t; (shorthand for (quote (+ 1 2)))\\n\\t\\n\\t; You can eval a quoted list\\n\\t(eval '(+ 1 2)) ; => 3\\n\\t\\n\\t; Collections & Sequences\\n\\t;;;;;;;;;;;;;;;;;;;\\n\\t\\n\\t; Lists are linked-list data structures, while Vectors are array-backed.\\n\\t; Vectors and Lists are java classes too!\\n\\t(class [1 2 3]); => clojure.lang.PersistentVector\\n\\t(class '(1 2 3)); => clojure.lang.PersistentList\\n\\t\\n\\t; A list would be written as just (1 2 3), but we have to quote\\n\\t; it to stop the reader thinking it's a function.\\n\\t; Also, (list 1 2 3) is the same as '(1 2 3)\\n\\t\\n\\t; \\\"Collections\\\" are just groups of data\\n\\t; Both lists and vectors are collections:\\n\\t(coll? '(1 2 3)) ; => true\\n\\t(coll? [1 2 3]) ; => true\\n\\t\\n\\t; \\\"Sequences\\\" (seqs) are abstract descriptions of lists of data.\\n\\t; Only lists are seqs.\\n\\t(seq? '(1 2 3)) ; => true\\n\\t(seq? [1 2 3]) ; => false\\n\\t\\n\\t; A seq need only provide an entry when it is accessed.\\n\\t; So, seqs which can be lazy -- they can define infinite series:\\n\\t(range 4) ; => (0 1 2 3)\\n\\t(range) ; => (0 1 2 3 4 ...) (an infinite series)\\n\\t(take 4 (range)) ; (0 1 2 3)\\n\\t\\n\\t; Use cons to add an item to the beginning of a list or vector\\n\\t(cons 4 [1 2 3]) ; => (4 1 2 3)\\n\\t(cons 4 '(1 2 3)) ; => (4 1 2 3)\\n\\t\\n\\t; Conj will add an item to a collection in the most efficient way.\\n\\t; For lists, they insert at the beginning. For vectors, they insert at the end.\\n\\t(conj [1 2 3] 4) ; => [1 2 3 4]\\n\\t(conj '(1 2 3) 4) ; => (4 1 2 3)\\n\\t\\n\\t; Use concat to add lists or vectors together\\n\\t(concat [1 2] '(3 4)) ; => (1 2 3 4)\\n\\t\\n\\t; Use filter, map to interact with collections\\n\\t(map inc [1 2 3]) ; => (2 3 4)\\n\\t(filter even? [1 2 3]) ; => (2)\\n\\t\\n\\t; Use reduce to reduce them\\n\\t(reduce + [1 2 3 4])\\n\\t; = (+ (+ (+ 1 2) 3) 4)\\n\\t; => 10\\n\\t\\n\\t; Reduce can take an initial-value argument too\\n\\t(reduce conj [] '(3 2 1))\\n\\t; = (conj (conj (conj [] 3) 2) 1)\\n\\t; => [3 2 1]\\n\\t\\n\\t; Functions\\n\\t;;;;;;;;;;;;;;;;;;;;;\\n\\t\\n\\t; Use fn to create new functions. A function always returns\\n\\t; its last statement.\\n\\t(fn [] \\\"Hello World\\\") ; => fn\\n\\t\\n\\t; (You need extra parens to call it)\\n\\t((fn [] \\\"Hello World\\\")) ; => \\\"Hello World\\\"\\n\\t\\n\\t; You can create a var using def\\n\\t(def x 1)\\n\\tx ; => 1\\n\\t\\n\\t; Assign a function to a var\\n\\t(def hello-world (fn [] \\\"Hello World\\\"))\\n\\t(hello-world) ; => \\\"Hello World\\\"\\n\\t\\n\\t; You can shorten this process by using defn\\n\\t(defn hello-world [] \\\"Hello World\\\")\\n\\t\\n\\t; The [] is the list of arguments for the function.\\n\\t(defn hello [name]\\n\\t (str \\\"Hello \\\" name))\\n\\t(hello \\\"Steve\\\") ; => \\\"Hello Steve\\\"\\n\\t\\n\\t; You can also use this shorthand to create functions:\\n\\t(def hello2 #(str \\\"Hello \\\" %1))\\n\\t(hello2 \\\"Fanny\\\") ; => \\\"Hello Fanny\\\"\\n\\t\\n\\t; You can have multi-variadic functions, too\\n\\t(defn hello3\\n\\t ([] \\\"Hello World\\\")\\n\\t ([name] (str \\\"Hello \\\" name)))\\n\\t(hello3 \\\"Jake\\\") ; => \\\"Hello Jake\\\"\\n\\t(hello3) ; => \\\"Hello World\\\"\\n\\t\\n\\t; Functions can pack extra arguments up in a seq for you\\n\\t(defn count-args [& args]\\n\\t (str \\\"You passed \\\" (count args) \\\" args: \\\" args))\\n\\t(count-args 1 2 3) ; => \\\"You passed 3 args: (1 2 3)\\\"\\n\\t\\n\\t; You can mix regular and packed arguments\\n\\t(defn hello-count [name & args]\\n\\t (str \\\"Hello \\\" name \\\", you passed \\\" (count args) \\\" extra args\\\"))\\n\\t(hello-count \\\"Finn\\\" 1 2 3)\\n\\t; => \\\"Hello Finn, you passed 3 extra args\\\"\\n\\t\\n\\t\\n\\t; Maps\\n\\t;;;;;;;;;;\\n\\t\\n\\t; Hash maps and array maps share an interface. Hash maps have faster lookups\\n\\t; but don't retain key order.\\n\\t(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap\\n\\t(class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap\\n\\t\\n\\t; Arraymaps will automatically become hashmaps through most operations\\n\\t; if they get big enough, so you don't need to worry.\\n\\t\\n\\t; Maps can use any hashable type as a key, but usually keywords are best\\n\\t; Keywords are like strings with some efficiency bonuses\\n\\t(class :a) ; => clojure.lang.Keyword\\n\\t\\n\\t(def stringmap {\\\"a\\\" 1, \\\"b\\\" 2, \\\"c\\\" 3})\\n\\tstringmap ; => {\\\"a\\\" 1, \\\"b\\\" 2, \\\"c\\\" 3}\\n\\t\\n\\t(def keymap {:a 1, :b 2, :c 3})\\n\\tkeymap ; => {:a 1, :c 3, :b 2}\\n\\t\\n\\t; By the way, commas are always treated as whitespace and do nothing.\\n\\t\\n\\t; Retrieve a value from a map by calling it as a function\\n\\t(stringmap \\\"a\\\") ; => 1\\n\\t(keymap :a) ; => 1\\n\\t\\n\\t; Keywords can be used to retrieve their value from a map, too!\\n\\t(:b keymap) ; => 2\\n\\t\\n\\t; Don't try this with strings.\\n\\t;(\\\"a\\\" stringmap)\\n\\t; => Exception: java.lang.String cannot be cast to clojure.lang.IFn\\n\\t\\n\\t; Retrieving a non-present key returns nil\\n\\t(stringmap \\\"d\\\") ; => nil\\n\\t\\n\\t; Use assoc to add new keys to hash-maps\\n\\t(def newkeymap (assoc keymap :d 4))\\n\\tnewkeymap ; => {:a 1, :b 2, :c 3, :d 4}\\n\\t\\n\\t; But remember, clojure types are immutable!\\n\\tkeymap ; => {:a 1, :b 2, :c 3}\\n\\t\\n\\t; Use dissoc to remove keys\\n\\t(dissoc keymap :a :b) ; => {:c 3}\\n\\t\\n\\t; Sets\\n\\t;;;;;;\\n\\t\\n\\t(class #{1 2 3}) ; => clojure.lang.PersistentHashSet\\n\\t(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}\\n\\t\\n\\t; Add a member with conj\\n\\t(conj #{1 2 3} 4) ; => #{1 2 3 4}\\n\\t\\n\\t; Remove one with disj\\n\\t(disj #{1 2 3} 1) ; => #{2 3}\\n\\t\\n\\t; Test for existence by using the set as a function:\\n\\t(#{1 2 3} 1) ; => 1\\n\\t(#{1 2 3} 4) ; => nil\\n\\t\\n\\t; There are more functions in the clojure.sets namespace.\\n\\t\\n\\t; Useful forms\\n\\t;;;;;;;;;;;;;;;;;\\n\\t\\n\\t; Logic constructs in clojure are just macros, and look like\\n\\t; everything else\\n\\t(if false \\\"a\\\" \\\"b\\\") ; => \\\"b\\\"\\n\\t(if false \\\"a\\\") ; => nil\\n\\t\\n\\t; Use let to create temporary bindings\\n\\t(let [a 1 b 2]\\n\\t (> a b)) ; => false\\n\\t\\n\\t; Group statements together with do\\n\\t(do\\n\\t (print \\\"Hello\\\")\\n\\t \\\"World\\\") ; => \\\"World\\\" (prints \\\"Hello\\\")\\n\\t\\n\\t; Functions have an implicit do\\n\\t(defn print-and-say-hello [name]\\n\\t (print \\\"Saying hello to \\\" name)\\n\\t (str \\\"Hello \\\" name))\\n\\t(print-and-say-hello \\\"Jeff\\\") ;=> \\\"Hello Jeff\\\" (prints \\\"Saying hello to Jeff\\\")\\n\\t\\n\\t; So does let\\n\\t(let [name \\\"Urkel\\\"]\\n\\t (print \\\"Saying hello to \\\" name)\\n\\t (str \\\"Hello \\\" name)) ; => \\\"Hello Urkel\\\" (prints \\\"Saying hello to Urkel\\\")\\n\\t\\n\\t; Modules\\n\\t;;;;;;;;;;;;;;;\\n\\t\\n\\t; Use \\\"use\\\" to get all functions from the module\\n\\t(use 'clojure.set)\\n\\t\\n\\t; Now we can use set operations\\n\\t(intersection #{1 2 3} #{2 3 4}) ; => #{2 3}\\n\\t(difference #{1 2 3} #{2 3 4}) ; => #{1}\\n\\t\\n\\t; You can choose a subset of functions to import, too\\n\\t(use '[clojure.set :only [intersection]])\\n\\t\\n\\t; Use require to import a module\\n\\t(require 'clojure.string)\\n\\t\\n\\t; Use / to call functions from a module\\n\\t; Here, the module is clojure.string and the function is blank?\\n\\t(clojure.string/blank? \\\"\\\") ; => true\\n\\t\\n\\t; You can give a module a shorter name on import\\n\\t(require '[clojure.string :as str])\\n\\t(str/replace \\\"This is a test.\\\" #\\\"[a-o]\\\" str/upper-case) ; => \\\"THIs Is A tEst.\\\"\\n\\t; (#\\\"\\\" denotes a regular expression literal)\\n\\t\\n\\t; You can use require (and use, but don't) from a namespace using :require.\\n\\t; You don't need to quote your modules if you do it this way.\\n\\t(ns test\\n\\t (:require\\n\\t [clojure.string :as str]\\n\\t [clojure.set :as set]))\\n\\t\\n\\t; Java\\n\\t;;;;;;;;;;;;;;;;;\\n\\t\\n\\t; Java has a huge and useful standard library, so\\n\\t; you'll want to learn how to get at it.\\n\\t\\n\\t; Use import to load a java module\\n\\t(import java.util.Date)\\n\\t\\n\\t; You can import from an ns too.\\n\\t(ns test\\n\\t (:import java.util.Date\\n\\t java.util.Calendar))\\n\\t\\n\\t; Use the class name with a \\\".\\\" at the end to make a new instance\\n\\t(Date.) ;
\\n\\t\\n\\t; Use . to call methods. Or, use the \\\".method\\\" shortcut\\n\\t(. (Date.) getTime) ;
\\n\\t(.getTime (Date.)) ; exactly the same thing.\\n\\t\\n\\t; Use / to call static methods\\n\\t(System/currentTimeMillis) ;
(system is always present)\\n\\t\\n\\t; Use doto to make dealing with (mutable) classes more tolerable\\n\\t(import java.util.Calendar)\\n\\t(doto (Calendar/getInstance)\\n\\t (.set 2000 1 1 0 0 0)\\n\\t .getTime) ; => A Date. set to 2000-01-01 00:00:00\\n\\t\\n\\t; STM\\n\\t;;;;;;;;;;;;;;;;;\\n\\t\\n\\t; Software Transactional Memory is the mechanism clojure uses to handle\\n\\t; persistent state. There are a few constructs in clojure that use this.\\n\\t\\n\\t; An atom is the simplest. Pass it an initial value\\n\\t(def my-atom (atom {}))\\n\\t\\n\\t; Update an atom with swap!.\\n\\t; swap! takes a function and calls it with the current value of the atom\\n\\t; as the first argument, and any trailing arguments as the second\\n\\t(swap! my-atom assoc :a 1) ; Sets my-atom to the result of (assoc {} :a 1)\\n\\t(swap! my-atom assoc :b 2) ; Sets my-atom to the result of (assoc {:a 1} :b 2)\\n\\t\\n\\t; Use '@' to dereference the atom and get the value\\n\\tmy-atom ;=> Atom (Returns the Atom object)\\n\\t@my-atom ; => {:a 1 :b 2}\\n\\t\\n\\t; Here's a simple counter using an atom\\n\\t(def counter (atom 0))\\n\\t(defn inc-counter []\\n\\t (swap! counter inc))\\n\\t\\n\\t(inc-counter)\\n\\t(inc-counter)\\n\\t(inc-counter)\\n\\t(inc-counter)\\n\\t(inc-counter)\\n\\t\\n\\t@counter ; => 5\\n\\t\\n\\t; Other STM constructs are refs and agents.\\n\\t; Refs: http://clojure.org/refs\\n\\t; Agents: http://clojure.org/agents\\n\\n**Further Reading**\\n\\nThis is far from exhaustive, but hopefully it’s enough to get you on your feet.\\n\\nClojure.org has lots of articles:
\\n\\nClojuredocs.org has documentation with examples for most core functions:
\\n\\n4Clojure is a great way to build your clojure/FP skills:
\\n\\nClojure-doc.org (yes, really) has a number of getting started articles:
\",\"mtime\":1444880072000,\"source\":\"source/_posts/learn-x-in-y-minuteswhere-x=clojure.md\"},\"4k2hrry04x79ddk4\":{\"_id\":\"4k2hrry04x79ddk4\",\"content\":\"title: 'Java 8新特性探究'\\ndate: 2014-05-15 12:02:25\\n\\n---\\n前言\\n===\\n自2013年6月13日,oracle就已经发布的Java 8特性完备版本(M7),但最终GA版本将在2014年3月18日(已第二次跳票,原计划今年9月发布的,据官网宣称是为了解决安全问题......),相信大家多多少少都听闻了关于Java 8的改进,总得来说,Java 8从语言,核心库,国际化、虚拟机,安全性,平台等方面一共有55个特性,本系列博文将带大家对这55个特性进行探究,不求做到最好,只求详细,深入浅出,通俗易懂。\\n\\n
\\n\\njava几个重大版本\\n---\\n\\njava从1995年发布到现在,也走过18年了,个人认为,其中几个java版本都肩负着重大使命,影响甚远;\\njdk1.0 1995年5月23日诞生,Oak语言改名为Java,并提出“Write Once ,Run anywhere\\\";\\njdk1.2 1999年6月发布,将java划分为J2SE,J2ME,J2EE三大平台;\\njdk1.4 主要是性能提升,在2000年时候JAVA成为世界上最流行的电脑语言,跟这个版本离不开关系,估计国内还有大量的java应用是运行在此版本上;\\njdk5 诞生于2004年,他的使命就是易用,加入1. 泛型 2 自动装箱/拆箱 3 for-each 4 static import 5 变长参数等, 为了表示该版本的重要性,J2SE1.5更名为Java SE 5.0;\\njdk8 将在2014年3月份发布,迄今为止,可能是最大更新的java版本,也是令人期待的一个版本,在Java中引入闭包概念对Java程序开发方法的影响甚至会大于Java5中引入的泛型特征对编程方式带来的影响。\\n可以看出,jdk 8跟jdk 5之间,整整相差10年,这10年期间,相继发布jdk 6、7都是改动不大,这也说明,java发展确实有点缓慢了,以致曾经的跟随者.net在某方面超越了java,还有基于jvm上的动态语言崛起,比如Groovy、Scala等,2013年java one大会上,java平台首席架构师Mark Reinhold在会上说Java 8 is Revolutionary, Java is back(Java 8是革命性的,Java回来了),至于java 8能否能给我们带来眼前一亮的感觉呢,我们拭目以待吧\\n\\n学习java8的理由\\n---\\n\\n1. 提高java开发效率(更少的代码,更强的功能,主要是lambda表达式带来便利)\\n2. 提高java程序的运行速度(批量数据处理,多核运行利用,更高的性能)\\n3. 更安全,漏洞更少(为啥GA版本跳票,oracle解释说近来大量安全问题困扰着平台,所以推迟发布时间解决安全问题)\\n4. 面试需要,跳槽的哥们,个人预言渐渐会被问及java 8的特性,想当初java 5出来一两年都会\\n5. 逼格高,想在同事面前炫耀一般,但请注意,别以为java是向下兼容的就能随便把旧java系统升级到java最新版本\\n6. 还在靠java开发养家糊口的程序员,不想知识陈旧落后\\n\\n关于本系列文章说明\\n---\\n\\n本系列博文不是java入门教程,期望读者具有一定的java基础;\\n简短,排版良好,每一篇都精心准备,迎合国人的阅读习惯 (大家对长篇大论且毫无排版的文章肯定不感兴趣);\\n降低jdk8的学习曲线,不需要到处搜索了,英文不好,或者官网上讲了,只是粗略带过,本系列文章将所有java 8的特性系统地探究;\\n最后一点,写博客比较辛苦,尊重别人的劳动成果,希望大家转载时候注明出处。 http://my.oschina.net/benhaile\\n\\n欢迎大家提出意见和问题,本人将尽量抽时间回答,谢谢。\\n\\n***\\n\\n(一)通往lambda之路_语法篇\\n===\\n\\n现在开始要灌输一些概念性的东西了,这能帮助你理解lambda更加透彻一点,如果你之前听说过,也可当是温习,所谓温故而知新......\\n\\n在开始之前,可以同步下载jdk 8 和 IDE,IDE根据个人习惯了,不过eclipse官方版本还没出来,所以目前看的话,netbean7.4是首选的,毕竟前段子刚刚出的正式版本,以下是他们的下载地址。\\n\\njdk 8:https://jdk8.java.net/download.html\\n\\nNetbeans 7.4正式版:https://netbeans.org/downloads/(推荐,oracle官方发布)\\n\\nIDEA 12 EAP http://confluence.jetbrains.net/display/IDEADEV/IDEA+12+EAP\\n\\nUnofficial builds of Eclipse :http://downloads.efxclipse.org/eclipse-java8/\\n\\n函数式接口\\n---\\n\\n函数式接口(functional interface 也叫功能性接口,其实是同一个东西)。简单来说,函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断,但 最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。 \\n\\nJava中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现,下面讲到语法会讲到 \\n\\nLambda语法\\n---\\n\\n包含三个部分\\n\\n1. 一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数\\n\\n2. 一个箭头符号:->\\n\\n3. 方法体,可以是表达式和代码块,方法体函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}\\n\\n总体看起来像这样\\n\\n\\t(parameters) -> expression 或者 (parameters) -> { statements; }\\n\\n看一个完整的例子,方便理解\\n\\n\\t/**\\n\\t * 测试lambda表达式\\n\\t *\\n\\t * @author benhail\\n\\t */\\n\\tpublic class TestLambda {\\n\\t\\n\\t public static void runThreadUseLambda() {\\n\\t //Runnable是一个函数接口,只包含了有个无参数的,返回void的run方法;\\n\\t //所以lambda表达式左边没有参数,右边也没有return,只是单纯的打印一句话\\n\\t new Thread(() ->System.out.println(\\\"lambda实现的线程\\\")).start(); \\n\\t }\\n\\t\\n\\t public static void runThreadUseInnerClass() {\\n\\t //这种方式就不多讲了,以前旧版本比较常见的做法\\n\\t new Thread(new Runnable() {\\n\\t @Override\\n\\t public void run() {\\n\\t System.out.println(\\\"内部类实现的线程\\\");\\n\\t }\\n\\t }).start();\\n\\t }\\n\\t\\n\\t public static void main(String[] args) {\\n\\t TestLambda.runThreadUseLambda();\\n\\t TestLambda.runThreadUseInnerClass();\\n\\t }\\n\\t}\\n\\n可以看出,使用lambda表达式设计的代码会更加简洁,而且还可读。\\n\\n方法引用\\n---\\n\\n其实是lambda表达式的一个简化写法,所引用的方法其实是lambda表达式的方法体实现,语法也很简单,左边是容器(可以是类名,实例名),中间是\\\"::\\\",右边是相应的方法名。如下所示:\\n\\n\\tObjectReference::methodName\\n\\n一般方法的引用格式是\\n\\n1. 如果是静态方法,则是ClassName::methodName。如 Object ::equals\\n\\n2. 如果是实例方法,则是Instance::methodName。如Object obj=new Object();obj::equals;\\n\\n3. 构造函数.则是ClassName::new\\n\\n再来看一个完整的例子,方便理解\\n\\n\\timport java.awt.FlowLayout;\\n\\timport java.awt.event.ActionEvent;\\n\\timport javax.swing.JButton;\\n\\timport javax.swing.JFrame;\\n\\t\\n\\t/**\\n\\t *\\n\\t * @author benhail\\n\\t */\\n\\tpublic class TestMethodReference {\\n\\t\\n\\t public static void main(String[] args) {\\n\\t\\n\\t JFrame frame = new JFrame();\\n\\t frame.setLayout(new FlowLayout());\\n\\t frame.setVisible(true);\\n\\t\\t\\t\\n\\t JButton button1 = new JButton(\\\"点我!\\\");\\n\\t JButton button2 = new JButton(\\\"也点我!\\\");\\n\\t\\t\\t\\n\\t frame.getContentPane().add(button1);\\n\\t frame.getContentPane().add(button2);\\n\\t //这里addActionListener方法的参数是ActionListener,是一个函数式接口\\n\\t //使用lambda表达式方式\\n\\t button1.addActionListener(e -> { System.out.println(\\\"这里是Lambda实现方式\\\"); });\\n\\t //使用方法引用方式\\n\\t button2.addActionListener(TestMethodReference::doSomething);\\n\\t \\n\\t }\\n\\t /**\\n\\t * 这里是函数式接口ActionListener的实现方法\\n\\t * @param e \\n\\t */\\n\\t public static void doSomething(ActionEvent e) {\\n\\t\\t\\t\\n\\t System.out.println(\\\"这里是方法引用实现方式\\\");\\n\\t \\n\\t }\\n\\t}\\n\\n可以看出,doSomething方法就是lambda表达式的实现,这样的好处就是,如果你觉得lambda的方法体会很长,影响代码可读性,方法引用就是个解决办法\\n\\n总结\\n---\\n\\n以上就是lambda表达式语法的全部内容了,相信大家对lambda表达式都有一定的理解了,但只是代码简洁了这个好处的话,并不能打动很多观众,java 8也不会这么令人期待,其实java 8引入lambda迫切需求是因为lambda 表达式能简化集合上数据的多线程或者多核的处理,提供更快的集合处理速度 ,这个后续会讲到,关于JEP126的这一特性,将分3部分,之所以分开,是因为这一特性可写的东西太多了,这部分让读者熟悉lambda表达式以及方法引用的语法和概念,第二部分则是虚拟扩展方法(default method)的内容,最后一部分则是大数据集合的处理,解开lambda表达式的最强作用的神秘面纱。敬请期待。。。。\\n\\n如果你有建议或者疑问,欢迎在评论里面留言\\n\\n最后一点,写博客比较辛苦,尊重别人的劳动成果,希望大家转载时候注明出处。 http://my.oschina.net/benhaile\\n\\n***\\n\\n(二)深入解析默认方法\\n===\\n\\n上篇讲了 lambda表达式的语法,但只是 JEP126 特性的一部分,另一部分就是默认方法(也称为虚拟扩展方法或防护方法) \\n\\n什么是默认方法,为什么要有默认方法\\n---\\n\\n简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。 \\n\\n为什么要有这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。\\n\\n简单的例子\\n\\n一个接口A,Clazz类实现了接口A。\\n\\n\\tpublic interface A {\\n\\t default void foo(){\\n\\t System.out.println(\\\"Calling A.foo()\\\");\\n\\t }\\n\\t}\\n\\t \\n\\tpublic class Clazz implements A {\\n\\t public static void main(String[] args){\\n\\t Clazz clazz = new Clazz();\\n\\t clazz.foo();//调用A.foo()\\n\\t }\\n\\t}\\n\\n代码是可以编译的,即使Clazz类并没有实现foo()方法。在接口A中提供了foo()方法的默认实现。\\n\\njava 8抽象类与接口对比\\n---\\n\\n这一个功能特性出来后,很多同学都反应了,java 8的接口都有实现方法了,跟抽象类还有什么区别?其实还是有的,请看下表对比。。\\n\\n相同点 | 不同点 \\n----- | -----\\n1. 都是抽象类型;2. 都可以有实现方法(以前接口不行);3. 都可以不需要实现类或者继承者去实现所有方法,(以前不行,现在接口中默认方法不需要实现者实现) | 1.抽象类不可以多重继承,接口可以(无论是多重类型继承还是多重行为继承);2.抽象类和接口所反映出的设计理念不同。其实抽象类表示的是\\\"is-a\\\"关系,接口表示的是\\\"like-a\\\"关系;3.接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值;抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。 \\n\\n多重继承的冲突说明\\n---\\n\\n由于同一个方法可以从不同接口引入,自然而然的会有冲突的现象,默认方法判断冲突的规则如下:\\n\\n1. 一个声明在类里面的方法优先于任何默认方法(classes always win)\\n\\n2. 否则,则会优先选取最具体的实现,比如下面的例子 B重写了A的hello方法。\\n\\n\\n\\n输出结果是:Hello World from B\\n\\n如果想调用A的默认函数,则用到新语法X.super.m(...),下面修改C类,实现A接口,重写一个hello方法,如下所示:\\n\\n\\tpublic class C implements A{\\n\\t \\n\\t @Override\\n\\t public void hello(){\\n\\t A.super.hello();\\n\\t }\\n\\t \\n\\t public static void main(String[] args){\\n\\t new C().hello();\\n\\t }\\n\\t}\\n\\n输出结果是:Hello World from A\\n\\n总结\\n---\\n\\n默认方法给予我们修改接口而不破坏原来的实现类的结构提供了便利,目前java 8的集合框架已经大量使用了默认方法来改进了,当我们最终开始使用Java 8的lambdas表达式时,提供给我们一个平滑的过渡体验。也许将来我们会在API设计中看到更多的默认方法的应用。 \\n\\n跟上篇博文结合起来,就是JEP126的全部了,后面还有54个特性等着我们去探究,为了让大家比较深刻了解lambda,学以致用,下一篇还是lambda的内容,预告一下下篇的标题:《Java 8特性探究(三)解开lambda表达式最强作用的神秘面纱》,第二个特性 将从第四篇开始,谢谢大家支持,敬请期待。。。\\n\\n转载时候请注明出处。 http://my.oschina.net/benhaile \\n\\n***\\n\\n(三)解开lambda最强作用的神秘面纱\\n===\\n\\n我们期待了很久lambda为java带来闭包的概念,但是如果我们不在集合中使用它的话,就损失了很大价值。现有接口迁移成为lambda风格的问题已经通过default methods解决了,在这篇文章将深入解析Java集合里面的批量数据操作(bulk operation),解开lambda最强作用的神秘面纱。\\n\\n1.关于JSR335\\n---\\n\\nJSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其目的是使 Java 更易于为多核处理器编写代码。JSR 335=lambda表达式+接口改进(默认方法)+批量数据操作。加上前面两篇,我们已是完整的学习了JSR335的相关内容了。\\n\\n2.外部VS内部迭代\\n---\\n\\n以前Java集合是不能够表达内部迭代的,而只提供了一种外部迭代的方式,也就是for或者while循环。\\n\\n\\tList persons = asList(new Person(\\\"Joe\\\"), new Person(\\\"Jim\\\"), new Person(\\\"John\\\"));\\n\\tfor (Person p : persons) {\\n\\t p.setLastName(\\\"Doe\\\");\\n\\t}\\n\\n上面的例子是我们以前的做法,也就是所谓的外部迭代,循环是固定的顺序循环。在现在多核的时代,如果我们想并行循环,不得不修改以上代码。效率能有多大提升还说定,且会带来一定的风险(线程安全问题等等)。 \\n要描述内部迭代,我们需要用到Lambda这样的类库,下面利用lambda和Collection.forEach重写上面的循环\\n\\n\\tpersons.forEach(p->p.setLastName(\\\"Doe\\\"));\\n\\n现在是由jdk 库来控制循环了,我们不需要关心last name是怎么被设置到每一个person对象里面去的,库可以根据运行环境来决定怎么做,并行,乱序或者懒加载方式。这就是内部迭代,客户端将行为p.setLastName当做数据传入api里面。 \\n\\n内部迭代其实和集合的批量操作并没有密切的联系,借助它我们感受到语法表达上的变化。真正有意思的和批量操作相关的是新的流(stream)API。新的java.util.stream包已经添加进JDK 8了。\\n\\n3.Stream API\\n---\\n\\n流(Stream)仅仅代表着数据流,并没有数据结构,所以他遍历完一次之后便再也无法遍历(这点在编程时候需要注意,不像Collection,遍历多少次里面都还有数据),它的来源可以是Collection、array、io等等。\\n\\n3.1中间与终点方法\\n\\n流作用是提供了一种操作大数据接口,让数据操作更容易和更快。它具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法和终端方法,“流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话,必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值,如果是Stream则是中间方法,否则是终点方法。具体请参照Stream的api。\\n\\n简单介绍下几个中间方法(filter、map)以及终点方法(collect、sum)\\n\\n3.1.1Filter\\n\\n在数据流中实现过滤功能是首先我们可以想到的最自然的操作了。Stream接口暴露了一个filter方法,它可以接受表示操作的Predicate实现来使用定义了过滤条件的lambda表达式。\\n\\n\\tList persons = …\\n\\tStream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//过滤18岁以上的人\\n\\n3.1.2Map\\n\\n假使我们现在过滤了一些数据,比如转换对象的时候。Map操作允许我们执行一个Function的实现(Function
的泛型T,R分别表示执行输入和执行结果),它接受入参并返回。首先,让我们来看看怎样以匿名内部类的方式来描述它:\\n\\n\\tStream adult= persons\\n\\t .stream()\\n\\t .filter(p -> p.getAge() > 18)\\n\\t .map(new Function() {\\n\\t @Override\\n\\t public Adult apply(Person person) {\\n\\t return new Adult(person);//将大于18岁的人转为成年人\\n\\t }\\n\\t });\\n\\n现在,把上述例子转换成使用lambda表达式的写法:\\n\\n\\tStream map = persons.stream()\\n\\t .filter(p -> p.getAge() > 18)\\n\\t .map(person -> new Adult(person));\\n\\n3.1.3Count\\n\\ncount方法是一个流的终点方法,可使流的结果最终统计,返回int,比如我们计算一下满足18岁的总人数\\n\\n\\tint countOfAdult=persons.stream()\\n\\t .filter(p -> p.getAge() > 18)\\n\\t .map(person -> new Adult(person))\\n\\t .count();\\n\\n3.1.4Collect\\n\\ncollect方法也是一个流的终点方法,可收集最终的结果\\n\\n\\tList adultList= persons.stream()\\n\\t .filter(p -> p.getAge() > 18)\\n\\t .map(person -> new Adult(person))\\n\\t .collect(Collectors.toList());\\n\\n或者,如果我们想使用特定的实现类来收集结果:\\n\\n\\tList adultList = persons\\n\\t .stream()\\n\\t .filter(p -> p.getAge() > 18)\\n\\t .map(person -> new Adult(person))\\n\\t .collect(Collectors.toCollection(ArrayList::new));\\n\\n篇幅有限,其他的中间方法和终点方法就不一一介绍了,看了上面几个例子,大家明白这两种方法的区别即可,后面可根据需求来决定使用。\\n\\n3.2顺序流与并行流\\n\\n每个Stream都有两种模式:顺序执行和并行执行。\\n\\n顺序流:\\n\\n\\tList
people = list.getStream.collect(Collectors.toList());\\n\\n并行流:\\n\\n\\tList
people = list.getStream.parallel().collect(Collectors.toList());\\n\\n顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。\\n\\n3.2.1并行流原理:\\n\\n\\tList originalList = someData;\\n\\tsplit1 = originalList(0, mid);//将数据分小部分\\n\\tsplit2 = originalList(mid,end);\\n\\tnew Runnable(split1.process());//小部分执行操作\\n\\tnew Runnable(split2.process());\\n\\tList revisedList = split1 + split2;//将结果合并\\n\\n大家对hadoop有稍微了解就知道,里面的 MapReduce 本身就是用于并行处理大数据集的软件框架,其 处理大数据的核心思想就是大而化小,分配到不同机器去运行map,最终通过reduce将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。\\n\\n3.2.2顺序与并行性能测试对比\\n\\n如果是多核机器,理论上并行流则会比顺序流快上一倍,下面是测试代码\\n\\n\\tlong t0 = System.nanoTime();\\n\\t\\n\\t//初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法\\n\\t\\n\\tint a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();\\n\\t\\n\\tlong t1 = System.nanoTime();\\n\\t\\n\\t//和上面功能一样,这里是用并行流来计算\\n\\t\\n\\tint b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();\\n\\t\\n\\tlong t2 = System.nanoTime();\\n\\t\\n\\t//我本机的结果是serial: 0.06s, parallel 0.02s,证明并行流确实比顺序流快\\n\\t\\n\\tSystem.out.printf(\\\"serial: %.2fs, parallel %.2fs%n\\\", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);\\n\\t\\n3.3关于Folk/Join框架\\n\\n应用硬件的并行性在java 7就有了,那就是 java.util.concurrent 包的新增功能之一是一个 fork-join 风格的并行分解框架,同样也很强大高效,有兴趣的同学去研究,这里不详谈了,相比Stream.parallel()这种方式,我更倾向于后者。\\n\\n4.总结\\n---\\n\\n如果没有lambda,Stream用起来相当别扭,他会产生大量的匿名内部类,比如上面的3.1.2map例子,如果没有default method,集合框架更改势必会引起大量的改动,所以lambda+default method使得jdk库更加强大,以及灵活,Stream以及集合框架的改进便是最好的证明。\\n\\njava 8特性探究系列写了3篇了,作为大餐,将java 8的重量级特性lambda与default method写在前面,下篇上个小菜,荤素搭配,也是语言相关的,JEP104 Java 类型的注解的探究,同时谢谢大家的支持,欢迎提出建议。如果你想了解哪些特性,欢迎给我发留言。\\n\\n转载时候请注明出处。 http://my.oschina.net/benhaile \\n\\n***\\n\\n(四)类型注解 复杂还是便捷\\n===\\n\\n本文将介绍java 8的第二个特性:类型注解。 \\n\\n注解大家都知道,从java5开始加入这一特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置。那充满争议的类型注解究竟是什么?复杂还是便捷?\\n\\n什么是类型注解\\n---\\n\\n在java 8之前,注解只能是在声明的地方所使用,比如类,方法,属性;java 8里面,注解可以应用在任何地方,比如:\\n\\n* 创建类实例:\\n\\tnew @Interned MyObject();\\n* 类型映射:\\n\\tmyString = (@NonNull String) str;\\n* implements 语句中:\\n\\tclass UnmodifiableList
implements @Readonly List { ... }\\n* throw exception声明:\\n\\tvoid monitorTemperature() throws @Critical TemperatureException { ... }\\n\\n需要注意的是,类型注解只是语法而不是语义,并不会影响java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。\\n\\n类型注解的作用\\n---\\n\\n先看看下面代码\\n\\n\\tCollections.emptyList().add(\\\"One\\\");\\n\\tint i=Integer.parseInt(\\\"hello\\\");\\n\\tSystem.console().readLine();\\n\\n上面的代码编译是通过的,但运行是会分别报UnsupportedOperationException; NumberFormatException;NullPointerException异常,这些都是runtime error;\\n\\n类型注解被用来支持在Java的程序中做强类型检查。配合插件式的check framework,可以在编译的时候检测出runtime error,以提高代码质量。这就是类型注解的作用了。\\n\\ncheck framework\\n---\\n\\ncheck framework是第三方工具,配合Java的类型注解效果就是1+1>2。它可以嵌入到javac编译器里面,可以配合ant和maven使用,也可以作为eclipse插件。地址是http://types.cs.washington.edu/checker-framework/。\\n\\ncheck framework可以找到类型注解出现的地方并检查,举个简单的例子:\\n\\n\\timport checkers.nullness.quals.*;\\n\\tpublic class GetStarted {\\n\\t void sample() {\\n\\t @NonNull Object ref = new Object();\\n\\t }\\n\\t}\\n\\n使用javac编译上面的类 \\n\\n\\tjavac -processor checkers.nullness.NullnessChecker GetStarted.java\\n\\n编译是通过,但如果修改成\\n\\n\\t@NonNull Object ref = null;\\n\\n再次编译,则出现 \\n\\n\\tGetStarted.java:5: incompatible types.\\n\\tfound : @Nullable
\\n\\trequired: @NonNull Object\\n\\t @NonNull Object ref = null;\\n\\t ^\\n\\t1 error\\n\\n如果你不想使用类型注解检测出来错误,则不需要processor,直接javac GetStarted.java是可以编译通过的,这是在java 8 with Type Annotation Support版本里面可以,但java 5,6,7版本都不行,因为javac编译器不知道@NonNull是什么东西,但check framework 有个向下兼容的解决方案,就是将类型注解nonnull用/**/注释起来,比如上面例子修改为\\n\\n\\timport checkers.nullness.quals.*;\\n\\tpublic class GetStarted {\\n\\t void sample() {\\n\\t /*@NonNull*/ Object ref = null;\\n\\t }\\n\\t}\\n\\n这样javac编译器就会忽略掉注释块,但用check framework里面的javac编译器同样能够检测出nonnull错误。\\n\\n通过类型注解+check framework我们可以看到,现在runtime error可以在编译时候就能找到。\\n\\n\\n关于JSR 308\\n---\\n\\nJSR 308想要解决在Java 1.5注解中出现的两个问题:\\n\\n* 在句法上对注解的限制:只能把注解写在声明的地方 \\n* 类型系统在语义上的限制:类型系统还做不到预防所有的bug \\n\\nJSR 308 通过如下方法解决上述两个问题: \\n\\n* 对Java语言的句法进行扩充,允许注解出现在更多的位置上。包括:方法接收器(method receivers,译注:例public int size() @Readonly { ... }),泛型参数,数组,类型转换,类型测试,对象创建,类型参数绑定,类继承和throws子句。其实就是类型注解,现在是java 8的一个特性\\n* 通过引入可插拔的类型系统(pluggable type systems)能够创建功能更强大的注解处理器。类型检查器对带有类型限定注解的源码进行分析,一旦发现不匹配等错误之处就会产生警告信息。其实就是check framework\\n\\n对JSR308,有人反对,觉得更复杂更静态了,比如 \\n@NotEmpty List strings = new ArrayList()> \\n换成动态语言为 \\nvar strings = [\\\"one\\\", \\\"two\\\"]; \\n\\n有人赞成,说到底,代码才是“最根本”的文档。代码中包含的注解清楚表明了代码编写者的意图。当没有及时更新或者有遗漏的时候,恰恰是注解中包含的意图信息,最容易在其他文档中被丢失。而且将运行时的错误转到编译阶段,不但可以加速开发进程,还可以节省测试时检查bug的时间。\\n\\n总结\\n---\\n\\n并不是人人都喜欢这个特性,特别是动态语言比较流行的今天,所幸,java 8并不强求大家使用这个特性,反对的人可以不使用这一特性,而对代码质量有些要求比较高的人或公司可以采用JSR 308,毕竟代码才是“最基本”的文档,这句话我是赞同的。虽然代码会增多,但可以使你的代码更具有表达意义。对这个特性有何看法,大家各抒己见。。。。\\n\\n\\n转载时候请注明出处。 http://my.oschina.net/benhaile \\n\\n***\\n\\n(五)重复注解(repeating annotations)\\n===\\n\\n知识回顾\\n---\\n\\n前面介绍了:\\nlambda表达式和默认方法 (JEP 126)\\n批量数据操作(JEP 107)\\n类型注解(JEP 104) \\n注:JEP=JDK Enhancement-Proposal (JDK 增强建议 ),每个JEP即一个新特性。\\n\\n在java 8里面,注解一共有2个改进,一个是类型注解,在上篇已经介绍了,本篇将介绍另外一个注解的改进:重复注解(JEP 120)。\\n\\n什么是重复注解\\n---\\n\\n允许在同一申明类型(类,属性,或方法)的多次使用同一个注解\\n\\n一个简单的例子\\n---\\n\\njava 8之前也有重复使用注解的解决方案,但可读性不是很好,比如下面的代码:\\n\\n\\tpublic @interface Authority {\\n\\t String role();\\n\\t}\\n\\t\\n\\tpublic @interface Authorities {\\n\\t Authority[] value();\\n\\t}\\n\\t\\n\\tpublic class RepeatAnnotationUseOldVersion {\\n\\t \\n\\t @Authorities({@Authority(role=\\\"Admin\\\"),@Authority(role=\\\"Manager\\\")})\\n\\t public void doSomeThing(){\\n\\t }\\n\\t}\\n\\n由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解,我们再来看看java 8里面的做法:\\n\\n\\t@Repeatable(Authorities.class)\\n\\tpublic @interface Authority {\\n\\t String role();\\n\\t}\\n\\t\\n\\tpublic @interface Authorities {\\n\\t Authority[] value();\\n\\t}\\n\\t\\n\\tpublic class RepeatAnnotationUseNewVersion {\\n\\t @Authority(role=\\\"Admin\\\")\\n\\t @Authority(role=\\\"Manager\\\")\\n\\t public void doSomeThing(){ }\\n\\t}\\n\\n不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点\\n\\n总结\\n---\\n\\nJEP120没有太多内容,是一个小特性,仅仅是为了提高代码可读性。这次java 8对注解做了2个方面的改进(JEP 104,JEP120),相信注解会比以前使用得更加频繁了。\\n\\n\\n转载时候请注明出处。 http://my.oschina.net/benhaile \\n\\n***\\n\\n(六)泛型的目标类型推断\\n===\\n\\n简单理解泛型\\n---\\n\\n泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。通俗点将就是“类型的变量”。这种类型变量可以用在类、接口和方法的创建中。\\n\\n理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:\\n\\n\\tList
box = new ArrayList
();box.add(new Apple());Apple apple =box.get(0);\\n\\n上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:\\n\\n\\tApple apple = (Apple)box.get(0);\\n\\n泛型的尴尬\\n---\\n\\n泛型的最大优点是提供了程序的类型安全同时可以向后兼容,但也有尴尬的地方,就是每次定义时都要写明泛型的类型,这样显示指定不仅感觉有些冗长,最主要是很多程序员不熟悉泛型,因此很多时候不能够给出正确的类型参数,现在通过编译器自动推断泛型的参数类型,能够减少这样的情况,并提高代码可读性。\\n\\njava7的泛型类型推断改进\\n---\\n\\n在以前的版本中使用泛型类型,需要在声明并赋值的时候,两侧都加上泛型类型。例如:\\n\\n\\tMap
myMap = new HashMap
();\\n\\n你可能觉得:老子在声明变量的的时候已经指明了参数类型,为毛还要在初始化对象时再指定?幸好,在Java SE 7中,这种方式得以改进,现在你可以使用如下语句进行声明并赋值:\\n\\n\\tMap
myMap = new HashMap<>(); //注意后面的\\\"<>\\\"\\n\\n在这条语句中,编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。再次提醒一定要注意new HashMap后面的“<>”,只有加上这个“<>”才表示是自动类型推断,否则就是非泛型类型的HashMap,并且在使用编译器编译源代码时会给出一个警告提示。\\n\\n但是:Java SE 7在创建泛型实例时的类型推断是有限制的:只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。例如:下面的例子在java 7无法正确编译(但现在在java8里面可以编译,因为根据方法参数来自动推断泛型的类型):\\n\\n\\tList
list = new ArrayList<>();\\n\\tlist.add(\\\"A\\\");// 由于addAll期望获得Collection extends String>类型的参数,因此下面的语句无法通过\\n\\tlist.addAll(new ArrayList<>());\\n\\nJava8的泛型类型推断改进\\n---\\n\\njava8里面泛型的目标类型推断主要2个:\\n\\n1. 支持通过方法上下文推断泛型目标类型\\n\\n2. 支持在方法调用链路当中,泛型类型推断传递到最后一个方法\\n\\n让我们看看官网的例子\\n\\n\\tclass List
{\\n\\t static
List
nil() { ... };\\n\\t static
List
cons(Z head, List
tail) { ... };\\n\\t E head() { ... }\\n\\t}\\n\\n根据JEP101的特性,我们在调用上面方法的时候可以这样写\\n\\n\\t//通过方法赋值的目标参数来自动推断泛型的类型\\n\\tList
l = List.nil();\\n\\t//而不是显示的指定类型\\n\\t//List
l = List.
nil();\\n\\t//通过前面方法参数类型推断泛型的类型\\n\\tList.cons(42, List.nil());\\n\\t//而不是显示的指定类型\\n\\t//List.cons(42, List.
nil());\\n\\n总结\\n---\\n\\n以上是JEP101的特性内容了,Java作为静态语言的代表者,可以说类型系统相当丰富。导致类型间互相转换的问题困扰着每个java程序员,通过编译器自动推断类型的东西可以稍微缓解一下类型转换太复杂的问题。 虽然说是小进步,但对于我们天天写代码的程序员,肯定能带来巨大的作用,至少心情更愉悦了~~说不定在java 9里面,我们会得到一个通用的类型var,像js或者scala的一些动态语言那样^_^\\n\\n***\\n\\n(七)深入解析日期和时间-JSR310\\n===\\n\\n博客一个月没更新了,这次给大家讲下java8时间与日期API。\\n\\n众所周知,日期是商业逻辑计算一个关键的部分,任何企业应用程序都需要处理时间问题。应用程序需要知道当前的时间点和下一个时间点,有时它们还必须计算这两个时间点之间的路径。但java之前的日期做法太令人恶心了,我们先来吐槽一下\\n\\n吐槽java.util.Date跟Calendar\\n---\\n\\nTiago Fernandez做过一次投票,选举最烂的JAVA API,排第一的EJB2.X,第二的就是日期API。\\n\\n槽点一\\n\\n最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂(不懂单一职责,你妈妈知道吗?纯属恶搞~哈哈)\\n\\n后来从JDK 1.1 开始,这三项职责分开了:\\n\\n* 使用Calendar类实现日期和时间字段之间转换;\\n\\n* 使用DateFormat类来格式化和分析日期字符串;\\n\\n* 而Date只用来承载日期和时间信息。\\n\\n原有Date中的相应方法已废弃。不过,无论是Date,还是Calendar,都用着太不方便了,这是API没有设计好的地方。\\n\\n槽点二\\n\\n坑爹的year和month\\n\\n\\tDate date = new Date(2012,1,1);\\n\\tSystem.out.println(date);\\n\\t输出Thu Feb 01 00:00:00 CST 3912\\n\\n观察输出结果,year是2012+1900,而month,月份参数我不是给了1吗?怎么输出二月(Feb)了?\\n\\n应该曾有人告诉你,如果你要设置日期,应该使用 java.util.Calendar,像这样...\\n\\n\\tCalendar calendar = Calendar.getInstance();\\n\\tcalendar.set(2013, 8, 2);\\n\\n这样写又不对了,calendar的month也是从0开始的,表达8月份应该用7这个数字,要么就干脆用枚举\\n\\n\\tcalendar.set(2013, Calendar.AUGUST, 2);\\n\\n注意上面的代码,Calendar年份的传值不需要减去1900(当然月份的定义和Date还是一样),这种不一致真是让人抓狂!\\n\\n有些人可能知道,Calendar相关的API是IBM捐出去的,所以才导致不一致。\\n\\n槽点三\\n\\njava.util.Date与java.util.Calendar中的所有属性都是可变的\\n\\n下面的代码,计算两个日期之间的天数....\\n\\n\\tpublic static void main(String[] args) {\\n\\t Calendar birth = Calendar.getInstance();\\n\\t birth.set(1975, Calendar.MAY, 26);\\n\\t Calendar now = Calendar.getInstance();\\n\\t System.out.println(daysBetween(birth, now));\\n\\t System.out.println(daysBetween(birth, now)); // 显示 0?\\n\\t } \\n\\t \\n\\tpublic static long daysBetween(Calendar begin, Calendar end) {\\n\\t long daysBetween = 0;\\n\\t while(begin.before(end)) {\\n\\t begin.add(Calendar.DAY_OF_MONTH, 1);\\n\\t daysBetween++;\\n\\t }\\n\\t return daysBetween;\\n\\t}\\n\\ndaysBetween有点问题,如果连续计算两个Date实例的话,第二次会取得0,因为Calendar状态是可变的,考虑到重复计算的场合,最好复制一个新的Calendar\\n\\n\\tpublic static long daysBetween(Calendar begin, Calendar end) {\\n\\t Calendar calendar = (Calendar) begin.clone(); // 复制\\n\\t long daysBetween = 0;\\n\\t while(calendar.before(end)) {\\n\\t calendar.add(Calendar.DAY_OF_MONTH, 1);\\n\\t daysBetween++;\\n\\t }\\n\\t return daysBetween;\\n\\t}\\n\\nJSR310\\n---\\n\\n以上种种,导致目前有些第三方的java日期库诞生,比如广泛使用的JODA-TIME,还有Date4j等,虽然第三方库已经足够强大,好用,但还是有兼容问题的,比如标准的JSF日期转换器与joda-time API就不兼容,你需要编写自己的转换器,所以标准的API还是必须的,于是就有了JSR310。\\n\\nJSR 310实际上有两个日期概念。第一个是Instant,它大致对应于java.util.Date类,因为它代表了一个确定的时间点,即相对于标准Java纪元(1970年1月1日)的偏移量;但与java.util.Date类不同的是其精确到了纳秒级别。\\n\\n第二个对应于人类自身的观念,比如LocalDate和LocalTime。他们代表了一般的时区概念,要么是日期(不包含时间),要么是时间(不包含日期),类似于java.sql的表示方式。此外,还有一个MonthDay,它可以存储某人的生日(不包含年份)。每个类都在内部存储正确的数据而不是像java.util.Date那样利用午夜12点来区分日期,利用1970-01-01来表示时间。\\n\\n目前Java8已经实现了JSR310的全部内容。新增了java.time包定义的类表示了日期-时间概念的规则,包括instants, durations, dates, times, time-zones and periods。这些都是基于ISO日历系统,它又是遵循 Gregorian规则的。最重要的一点是值不可变,且线程安全,通过下面一张图,我们快速看下java.time包下的一些主要的类的值的格式,方便理解。\\n\\n\\n\\n方法概览\\n---\\n\\n该包的API提供了大量相关的方法,这些方法一般有一致的方法前缀:\\n\\nof:静态工厂方法。\\n\\nparse:静态工厂方法,关注于解析。\\n\\nget:获取某些东西的值。\\n\\nis:检查某些东西的是否是true。\\n\\nwith:不可变的setter等价物。\\n\\nplus:加一些量到某个对象。\\n\\nminus:从某个对象减去一些量。\\n\\nto:转换到另一个类型。\\n\\nat:把这个对象与另一个对象组合起来,例如: date.atTime(time)。\\n\\n与旧的API对应关系\\n---\\n\\n\\n\\n简单使用java.time的API\\n---\\n\\n参考http://jinnianshilongnian.iteye.com/blog/1994164 被我揉在一起,可读性很差,相应的代码都有注释了,我就不过多解释了。\\n\\n\\tpublic class TimeIntroduction {\\n\\t public static void testClock() throws InterruptedException {\\n\\t //时钟提供给我们用于访问某个特定 时区的 瞬时时间、日期 和 时间的。 \\n\\t Clock c1 = Clock.systemUTC(); //系统默认UTC时钟(当前瞬时时间 System.currentTimeMillis()) \\n\\t System.out.println(c1.millis()); //每次调用将返回当前瞬时时间(UTC) \\n\\t Clock c2 = Clock.systemDefaultZone(); //系统默认时区时钟(当前瞬时时间) \\n\\t Clock c31 = Clock.system(ZoneId.of(\\\"Europe/Paris\\\")); //巴黎时区 \\n\\t System.out.println(c31.millis()); //每次调用将返回当前瞬时时间(UTC) \\n\\t Clock c32 = Clock.system(ZoneId.of(\\\"Asia/Shanghai\\\"));//上海时区 \\n\\t System.out.println(c32.millis());//每次调用将返回当前瞬时时间(UTC) \\n\\t Clock c4 = Clock.fixed(Instant.now(), ZoneId.of(\\\"Asia/Shanghai\\\"));//固定上海时区时钟 \\n\\t System.out.println(c4.millis());\\n\\t Thread.sleep(1000);\\n\\t System.out.println(c4.millis()); //不变 即时钟时钟在那一个点不动 \\n\\t Clock c5 = Clock.offset(c1, Duration.ofSeconds(2)); //相对于系统默认时钟两秒的时钟 \\n\\t System.out.println(c1.millis());\\n\\t System.out.println(c5.millis());\\n\\t }\\n\\t public static void testInstant() {\\n\\t //瞬时时间 相当于以前的System.currentTimeMillis() \\n\\t Instant instant1 = Instant.now();\\n\\t System.out.println(instant1.getEpochSecond());//精确到秒 得到相对于1970-01-01 00:00:00 UTC的一个时间 \\n\\t System.out.println(instant1.toEpochMilli()); //精确到毫秒 \\n\\t Clock clock1 = Clock.systemUTC(); //获取系统UTC默认时钟 \\n\\t Instant instant2 = Instant.now(clock1);//得到时钟的瞬时时间 \\n\\t System.out.println(instant2.toEpochMilli());\\n\\t Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault()); //固定瞬时时间时钟 \\n\\t Instant instant3 = Instant.now(clock2);//得到时钟的瞬时时间 \\n\\t System.out.println(instant3.toEpochMilli());//equals instant1 \\n\\t }\\n\\t public static void testLocalDateTime() {\\n\\t //使用默认时区时钟瞬时时间创建 Clock.systemDefaultZone() -->即相对于 ZoneId.systemDefault()默认时区 \\n\\t LocalDateTime now = LocalDateTime.now();\\n\\t System.out.println(now);\\n\\t//自定义时区 \\n\\t LocalDateTime now2 = LocalDateTime.now(ZoneId.of(\\\"Europe/Paris\\\"));\\n\\t System.out.println(now2);//会以相应的时区显示日期 \\n\\t//自定义时钟 \\n\\t Clock clock = Clock.system(ZoneId.of(\\\"Asia/Dhaka\\\"));\\n\\t LocalDateTime now3 = LocalDateTime.now(clock);\\n\\t System.out.println(now3);//会以相应的时区显示日期 \\n\\t//不需要写什么相对时间 如java.util.Date 年是相对于1900 月是从0开始 \\n\\t//2013-12-31 23:59 \\n\\t LocalDateTime d1 = LocalDateTime.of(2013, 12, 31, 23, 59);\\n\\t//年月日 时分秒 纳秒 \\n\\t LocalDateTime d2 = LocalDateTime.of(2013, 12, 31, 23, 59, 59, 11);\\n\\t//使用瞬时时间 + 时区 \\n\\t Instant instant = Instant.now();\\n\\t LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());\\n\\t System.out.println(d3);\\n\\t//解析String--->LocalDateTime \\n\\t LocalDateTime d4 = LocalDateTime.parse(\\\"2013-12-31T23:59\\\");\\n\\t System.out.println(d4);\\n\\t LocalDateTime d5 = LocalDateTime.parse(\\\"2013-12-31T23:59:59.999\\\");//999毫秒 等价于999000000纳秒 \\n\\t System.out.println(d5);\\n\\t//使用DateTimeFormatter API 解析 和 格式化 \\n\\t DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\\\"yyyy/MM/dd HH:mm:ss\\\");\\n\\t LocalDateTime d6 = LocalDateTime.parse(\\\"2013/12/31 23:59:59\\\", formatter);\\n\\t System.out.println(formatter.format(d6));\\n\\t//时间获取 \\n\\t System.out.println(d6.getYear());\\n\\t System.out.println(d6.getMonth());\\n\\t System.out.println(d6.getDayOfYear());\\n\\t System.out.println(d6.getDayOfMonth());\\n\\t System.out.println(d6.getDayOfWeek());\\n\\t System.out.println(d6.getHour());\\n\\t System.out.println(d6.getMinute());\\n\\t System.out.println(d6.getSecond());\\n\\t System.out.println(d6.getNano());\\n\\t//时间增减 \\n\\t LocalDateTime d7 = d6.minusDays(1);\\n\\t LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);\\n\\t//LocalDate 即年月日 无时分秒 \\n\\t//LocalTime即时分秒 无年月日 \\n\\t//API和LocalDateTime类似就不演示了 \\n\\t }\\n\\t public static void testZonedDateTime() {\\n\\t //即带有时区的date-time 存储纳秒、时区和时差(避免与本地date-time歧义)。 \\n\\t//API和LocalDateTime类似,只是多了时差(如2013-12-20T10:35:50.711+08:00[Asia/Shanghai]) \\n\\t ZonedDateTime now = ZonedDateTime.now();\\n\\t System.out.println(now);\\n\\t ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of(\\\"Europe/Paris\\\"));\\n\\t System.out.println(now2);\\n\\t//其他的用法也是类似的 就不介绍了 \\n\\t ZonedDateTime z1 = ZonedDateTime.parse(\\\"2013-12-31T23:59:59Z[Europe/Paris]\\\");\\n\\t System.out.println(z1);\\n\\t }\\n\\t public static void testDuration() {\\n\\t //表示两个瞬时时间的时间段 \\n\\t Duration d1 = Duration.between(Instant.ofEpochMilli(System.currentTimeMillis() - 12323123), Instant.now());\\n\\t//得到相应的时差 \\n\\t System.out.println(d1.toDays());\\n\\t System.out.println(d1.toHours());\\n\\t System.out.println(d1.toMinutes());\\n\\t System.out.println(d1.toMillis());\\n\\t System.out.println(d1.toNanos());\\n\\t//1天时差 类似的还有如ofHours() \\n\\t Duration d2 = Duration.ofDays(1);\\n\\t System.out.println(d2.toDays());\\n\\t }\\n\\t public static void testChronology() {\\n\\t //提供对java.util.Calendar的替换,提供对年历系统的支持 \\n\\t Chronology c = HijrahChronology.INSTANCE;\\n\\t ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());\\n\\t System.out.println(d);\\n\\t }\\n\\t /**\\n\\t * 新旧日期转换\\n\\t */\\n\\t public static void testNewOldDateConversion(){\\n\\t Instant instant=new Date().toInstant();\\n\\t Date date=Date.from(instant);\\n\\t System.out.println(instant);\\n\\t System.out.println(date);\\n\\t }\\n\\t public static void main(String[] args) throws InterruptedException {\\n\\t testClock();\\n\\t testInstant();\\n\\t testLocalDateTime();\\n\\t testZonedDateTime();\\n\\t testDuration();\\n\\t testChronology();\\n\\t testNewOldDateConversion();\\n\\t }\\n\\t}\\n\\n\\n与Joda-Time的区别\\n---\\n\\n其实JSR310的规范领导者Stephen Colebourne,同时也是Joda-Time的创建者,JSR310是在Joda-Time的基础上建立的,参考了绝大部分的API,但并不是说JSR310=JODA-Time,下面几个比较明显的区别是\\n\\n1. 最明显的变化就是包名(从org.joda.time以及java.time)\\n\\n2. JSR310不接受NULL值,Joda-Time视NULL值为0\\n\\n3. JSR310的计算机相关的时间(Instant)和与人类相关的时间(DateTime)之间的差别变得更明显\\n\\n4. JSR310所有抛出的异常都是DateTimeException的子类。虽然DateTimeException是一个RuntimeException\\n\\n总结\\n---\\n\\n对比旧的日期API\\n\\nJava.time | java.util.Calendar以及Date\\n------------ | -------------\\n流畅的API | 实例可变\\n实例不可变 | Content Cell\\n线程安全 | 非线程安全\\n\\n日期与时间处理API,在各种语言中,可能都只是个不起眼的API,如果你没有较复杂的时间处理需求,可能只是利用日期与时间处理API取得系统时间,简单做些显示罢了,然而如果认真看待日期与时间,其复杂程度可能会远超过你的想象,天文、地理、历史、政治、文化等因素,都会影响到你对时间的处理。所以在处理时间上,最好选用JSR310(如果你用java8的话就实现310了),或者Joda-Time。\\n\\n不止是java面临时间处理的尴尬,其他语言同样也遇到过类似的问题,比如\\n\\nArrow:Python 中更好的日期与时间处理库\\n\\nMoment.js:JavaScript 中的日期库\\n\\nNoda-Time:.NET 阵营的 Joda-Time 的复制\\n\\n转载时请注明出处\\n\\n***\\n\\n(八)精简的JRE详解\\n===\\n\\nOracle公司如期发布了Java 8正式版!没有让广大javaer失望。对于一个人来说,18岁是人生的转折点,从稚嫩走向成熟,法律意味着你是完全民事行为能力人,不再收益于未成年人保护法,到今年为止,java也走过了18年,java8是一个新的里程碑,带来了前所未有的诸多特性,lambda表达式,Stream API,新的Date time api,多核并发支持,重大安全问题改进等,相信java会越来越好,丰富的类库以及庞大的开源生态环境是其他语言所不具备的,说起丰富的类库,很多同学就吐槽了,java该减肥了,确实是该减肥,java8有个很好的特性,即JEP161(http://openjdk.java.net/jeps/161 ),该特性定义了Java SE平台规范的一些子集,使java应用程序不需要整个JRE平台即可部署和运行在小型设备上。开发人员可以基于目标硬件的可用资源选择一个合适的JRE运行环境。\\n\\n好处\\n---\\n\\n1. 更小的Java环境需要更少的计算资源。\\n2. 一个较小的运行时环境可以更好的优化性能和启动时间。\\n3. 消除未使用的代码从安全的角度总是好的。\\n4. 这些打包的应用程序可以下载速度更快。\\n\\n概念\\n---\\n\\n紧凑的JRE分3种,分别是compact1、compact2、compact3,他们的关系是compact1
\\n\\n如果不符合compact的api,则报错。\\n\\n\\t$ javac -profile compact2 Test.java\\n\\tTest.java:7: error: ThreadMXBean is not available in profile 'compact2'\\n\\t ThreadMXBean bean = ManagementFactory.getThreadMXBean();\\n\\t ^\\n\\tTest.java:7: error: ManagementFactory is not available in profile 'compact2'\\n\\t ThreadMXBean bean = ManagementFactory.getThreadMXBean();\\n\\t ^\\n\\t2 errors\\n\\n使用工具开发的效果\\n---\\n\\n\\n\\nJPEDS工具使用\\n---\\n\\njava8新增一个工具,用来分析应用程序所依赖的profile,有三个参数比较常用 -p,-v,-r\\n\\n\\timport java.util.Set;\\n\\timport java.util.HashSet;\\n\\t\\n\\tpublic class Deps {\\n\\t public static void main(String[] args) {\\n\\t System.out.println(Math.random());\\n\\t Set
set = new HashSet<>();\\n\\t }\\n\\t}\\n\\t\\n\\t************** PROFILE ********************\\n\\tjdeps -P Deps.class \\n\\tDeps.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar\\n\\t
(Deps.class)\\n\\t -> java.io compact1\\n\\t -> java.lang compact1\\n\\t -> java.util compact1\\n\\t\\n\\t************** VERBOSE ********************\\n\\tjdeps -v Deps.class \\n\\tDeps.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar\\n\\t Deps (Deps.class)\\n\\t -> java.io.PrintStream \\n\\t -> java.lang.Math \\n\\t -> java.lang.Object \\n\\t -> java.lang.String \\n\\t -> java.lang.System \\n\\t -> java.util.HashSet \\n\\t\\n\\t************** RECURSIVE ********************\\n\\tjdeps -R Deps.class \\n\\tDeps.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar\\n\\t
(Deps.class)\\n\\t -> java.io \\n\\t -> java.lang \\n\\t -> java.util \\n\\t/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/jce.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar\\n\\t javax.crypto (jce.jar)\\n\\t -> java.io \\n\\t -> java.lang \\n\\t -> java.lang.reflect \\n\\t -> java.net \\n\\t -> java.nio \\n\\t -> java.security \\n\\t -> java.security.cert \\n\\t -> java.security.spec \\n\\t -> java.util \\n\\t -> java.util.concurrent \\n\\t -> java.util.jar \\n\\t -> java.util.regex \\n\\t -> java.util.zip \\n\\t -> javax.security.auth \\n\\t -> sun.security.jca JDK internal API (rt.jar)\\n\\t -> sun.security.util JDK internal API (rt.jar)\\n\\t -> sun.security.validator JDK internal API (rt.jar)\\n\\t javax.crypto.interfaces (jce.jar)\\n\\t -> java.lang \\n\\t -> java.math \\n\\t -> java.security \\n\\t javax.crypto.spec (jce.jar)\\n\\t -> java.lang \\n\\t -> java.math \\n\\t -> java.security.spec \\n\\t -> java.util \\n\\t/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/jce.jar\\n\\t java.security (rt.jar)\\n\\t -> javax.crypto JDK internal API (jce.jar)\\n\\t sun.security.util (rt.jar)\\n\\t -> javax.crypto JDK internal API (jce.jar)\\n\\t -> javax.crypto.interfaces JDK internal API (jce.jar)\\n\\t -> javax.crypto.spec JDK internal API (jce.jar)\\n\\n在linux上构建profile\\n---\\n\\n\\t$ hg clone http://hg.openjdk.java.net/jdk8/jdk8/\\n\\t$ cd jdk8\\n\\t$ make images profiles : \\n\\t## Finished profiles (build time 00:00:27)\\n\\t----- Build times -------\\n\\tStart 2013-03-17 14:47:35\\n\\tEnd 2013-03-17 14:58:26\\n\\t00:00:25 corba\\n\\t00:00:15 demos\\n\\t00:01:50 hotspot\\n\\t00:00:24 images\\n\\t00:00:21 jaxp\\n\\t00:00:31 jaxws\\n\\t00:05:37 jdk\\n\\t00:00:43 langtools\\n\\t00:00:18 nashorn\\n\\t00:00:27 profiles\\n\\t00:10:51 TOTAL\\n\\t-------------------------\\n\\tFinished building Java(TM) for target 'images profiles'\\n\\t$ cd images\\n\\t$ ls -d *image\\n\\tj2re-compact1-image j2re-compact2-image j2re-compact3-image j2re-image j2sdk-image\\n\\n编译后compact大致的占用空间\\n---\\n\\n\\n\\n总结\\n---\\n\\n如今,物联网正风行一时。我们看到大量不同的设备在市场上出现,每一种的更新速度都越来越快。java需要一个占用资源少的JRE运行环境,紧凑的JRE特性的出现,希望能带来以后的物联网的发展,甚至还是会有大量的java应用程序出现在物联网上面。目前oracle也发布了针对raspberry pi的JRE了。\\n\\n另外该特性也是为java9的模块化项目做准备,模块化特性是javaer所期待的特性。他是解决业务系统复杂度的一个利器,当然OSGI也是相当的出色。但osgi对于新学者来说未免太复杂了。\\n\\n***\\n\\n(九)跟OOM:Permgen说再见吧\\n===\\n\\n很多开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署时。相对于正式产品,该问题在开发机上出现的频率更高,在产品中最常见的“问题”是默认值太低了。常用的解决方法是将其设置为256MB或更高。\\n\\nPermGen space简单介绍\\n---\\n\\nPermGen space的全称是Permanent Generation space,是指内存的永久保存区域,说说为什么会内存益出:这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和和存放Instance的Heap区域不同,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。\\n\\nJVM 种类有很多,比如 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘宝好样的!)等等。当然武林盟主是Hotspot了,这个毫无争议。需要注意的是,PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是没有这个区域。\\n\\n元空间(MetaSpace)一种新的内存空间诞生\\n---\\n\\nJDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace);这与Oracle JRockit 和IBM JVM’s很相似,如下图所示\\n\\n\\n\\n这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要你进行调优及监控内存空间的使用……但请等等,这么说还为时过早。在默认情况下,这些改变是透明的,接下来我们的展示将使你知道仍然要关注类元数据内存的占用。请一定要牢记,这个新特性也不能神奇地消除类和类加载器导致的内存泄漏。\\n\\njava8中metaspace总结如下:\\n\\nPermGen 空间的状况\\n---\\n\\n这部分内存空间将全部移除。\\n\\nJVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。\\n\\nMetaspace 内存分配模型\\n---\\n\\n大部分类元数据都在本地内存中分配。\\n\\n用于描述类元数据的“klasses”已经被移除。\\n\\nMetaspace 容量\\n---\\n\\n默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。\\n\\n新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。\\n\\nMetaspace 垃圾回收\\n---\\n\\n对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。\\n\\n适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。\\n\\nJava 堆内存的影响\\n---\\n\\n一些杂项数据已经移到Java堆空间中。升级到JDK8之后,会发现Java堆 空间有所增长。\\n\\nMetaspace 监控\\n---\\n\\n元空间的使用情况可以从HotSpot1.8的详细GC日志输出中得到。\\n\\nJstat 和 JVisualVM两个工具,在使用b75版本进行测试时,已经更新了,但是还是能看到老的PermGen空间的出现。\\n\\n前面已经从理论上充分说明,下面让我们通过“泄漏”程序进行新内存空间的观察……\\n\\nPermGen vs. Metaspace 运行时比较\\n---\\n\\n为了更好地理解Metaspace内存空间的运行时行为,\\n\\n将进行以下几种场景的测试:\\n\\n1. 使用JDK1.7运行Java程序,监控并耗尽默认设定的85MB大小的PermGen内存空间。\\n2. 使用JDK1.8运行Java程序,监控新Metaspace内存空间的动态增长和垃圾回收过程。\\n3. 使用JDK1.8运行Java程序,模拟耗尽通过“MaxMetaspaceSize”参数设定的128MB大小的Metaspace内存空间。\\n\\n首先建立了一个模拟PermGen OOM的代码\\n\\n\\tpublic class ClassA {\\n\\t public void method(String name) {\\n\\t // do nothing\\n\\t }\\n\\t}\\n\\n上面是一个简单的ClassA,把他编译成class字节码放到D:/classes下面,测试代码中用URLClassLoader来加载此类型上面类编译成class\\n\\n\\t/**\\n\\t * 模拟PermGen OOM\\n\\t * @author benhail\\n\\t */\\n\\tpublic class OOMTest {\\n\\t public static void main(String[] args) {\\n\\t try {\\n\\t //准备url\\n\\t URL url = new File(\\\"D:/classes\\\").toURI().toURL();\\n\\t URL[] urls = {url};\\n\\t //获取有关类型加载的JMX接口\\n\\t ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();\\n\\t //用于缓存类加载器\\n\\t List
classLoaders = new ArrayList
();\\n\\t while (true) {\\n\\t //加载类型并缓存类加载器实例\\n\\t ClassLoader classLoader = new URLClassLoader(urls);\\n\\t classLoaders.add(classLoader);\\n\\t classLoader.loadClass(\\\"ClassA\\\");\\n\\t //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)\\n\\t System.out.println(\\\"total: \\\" + loadingBean.getTotalLoadedClassCount());\\n\\t System.out.println(\\\"active: \\\" + loadingBean.getLoadedClassCount());\\n\\t System.out.println(\\\"unloaded: \\\" + loadingBean.getUnloadedClassCount());\\n\\t }\\n\\t } catch (Exception e) {\\n\\t e.printStackTrace();\\n\\t }\\n\\t }\\n\\t}\\n\\n虚拟机器参数设置如下:-verbose -verbose:gc\\n\\n设置-verbose参数是为了获取类型加载和卸载的信息\\n\\n设置-verbose:gc是为了获取垃圾收集的相关信息\\n\\nJDK 1.7 @64-bit – PermGen 耗尽测试\\n---\\n\\nJava1.7的PermGen默认空间为85 MB(或者可以通过-XX:MaxPermSize=XXXm指定)\\n\\n\\n\\n可以从上面的JVisualVM的截图看出:当加载超过6万个类之后,PermGen被耗尽。我们也能通过程序和GC的输出观察耗尽的过程。\\n\\n程序输出(摘取了部分)\\n\\n\\t......\\n\\t[Loaded ClassA from file:/D:/classes/]\\n\\ttotal: 64887\\n\\tactive: 64887\\n\\tunloaded: 0\\n\\t[GC 245041K->213978K(536768K), 0.0597188 secs]\\n\\t[Full GC 213978K->211425K(644992K), 0.6456638 secs]\\n\\t[GC 211425K->211425K(656448K), 0.0086696 secs]\\n\\t[Full GC 211425K->211411K(731008K), 0.6924754 secs]\\n\\t[GC 211411K->211411K(726528K), 0.0088992 secs]\\n\\t...............\\n\\tjava.lang.OutOfMemoryError: PermGen space\\n\\nJDK 1.8 @64-bit – Metaspace大小动态调整测试\\n---\\n\\nJava的Metaspace空间:不受限制 (默认)\\n\\n\\n\\n从上面的截图可以看到,JVM Metaspace进行了动态扩展,本地内存的使用由20MB增长到646MB,以满足程序中不断增长的类数据内存占用需求。我们也能观察到JVM的垃圾回收事件—试图销毁僵死的类或类加载器对象。但是,由于我们程序的泄漏,JVM别无选择只能动态扩展Metaspace内存空间。程序加载超过10万个类,而没有出现OOM事件。\\n\\nJDK 1.8 @64-bit – Metaspace 受限测试\\n---\\n\\nJava的Metaspace空间:128MB(-XX:MaxMetaspaceSize=128m)\\n\\n\\n\\n可以从上面的JVisualVM的截图看出:当加载超过2万个类之后,Metaspace被耗尽;与JDK1.7运行时非常相似。我们也能通过程序和GC的输出观察耗尽的过程。另一个有趣的现象是,保留的原生内存占用量是设定的最大大小两倍之多。这可能表明,如果可能的话,可微调元空间容量大小策略,来避免本地内存的浪费。\\n\\n从Java程序的输出中看到如下异常。\\n\\n\\t[Loaded ClassA from file:/D:/classes/]\\n\\ttotal: 21393\\n\\tactive: 21393\\n\\tunloaded: 0\\n\\t[GC (Metadata GC Threshold) 64306K->57010K(111616K), 0.0145502 secs]\\n\\t[Full GC (Metadata GC Threshold) 57010K->56810K(122368K), 0.1068084 secs]\\n\\tjava.lang.OutOfMemoryError: Metaspace\\n\\n在设置了MaxMetaspaceSize的情况下,该空间的内存仍然会耗尽,进而引发“java.lang.OutOfMemoryError: Metadata space”错误。因为类加载器的泄漏仍然存在,而通常Java又不希望无限制地消耗本机内存,因此设置一个类似于MaxPermSize的限制看起来也是合理的。\\n\\n总结\\n---\\n\\n1. 之前不管是不是需要,JVM都会吃掉那块空间……如果设置得太小,JVM会死掉;如果设置得太大,这块内存就被JVM浪费了。理论上说,现在你完全可以不关注这个,因为JVM会在运行时自动调校为“合适的大小”;\\n2. 提高Full GC的性能,在Full GC期间,Metadata到Metadata pointers之间不需要扫描了,别小看这几纳秒时间;\\n3. 隐患就是如果程序存在内存泄露,像OOMTest那样,不停的扩展metaspace的空间,会导致机器的内存不足,所以还是要有必要的调试和监控。\\n\\n***\\n\\n(十)StampedLock将是解决同步问题的新宠\\n===\\n\\nJava8就像一个宝藏,一个小的API改进,也足与写一篇文章,比如同步,一直是多线程并发编程的一个老话题,相信没有人喜欢同步的代码,这会降低应用的吞吐量等性能指标,最坏的时候会挂起死机,但是即使这样你也没得选择,因为要保证信息的正确性。所以本文决定将从synchronized、Lock到Java8新增的StampedLock进行对比分析,相信StampedLock不会让大家失望。\\n\\nsynchronized\\n---\\n\\n在java5之前,实现同步主要是使用synchronized。它是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。\\n\\n\\n有四种不同的同步块:\\n\\n1. 实例方法\\n2. 静态方法\\n3. 实例方法中的同步块\\n4. 静态方法中的同步块\\n\\n大家对此应该不陌生,所以不多讲了,以下是代码示例\\n\\n\\tsynchronized(this)\\n\\t// do operation\\n\\t}\\n\\n小结:在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,性能上也有所提升。\\n\\nLock\\n---\\n\\n它是Java 5在java.util.concurrent.locks新增的一个API。\\n\\nLock是一个接口,核心方法是lock(),unlock(),tryLock(),实现类有ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;\\n\\nReentrantReadWriteLock, ReentrantLock 和synchronized锁都有相同的内存语义。\\n\\n与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的。Lock提供更灵活的锁机制,很多synchronized 没有提供的许多特性,比如锁投票,定时锁等候和中断锁等候,但因为lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中\\n\\n下面是Lock的一个代码示例\\n\\n\\trwlock.writeLock().lock();\\n\\ttry {\\n\\t// do operation\\n\\t} finally {\\n\\trwlock.writeLock().unlock();\\n\\t}\\n\\n小结:比synchronized更灵活、更具可伸缩性的锁定机制,但不管怎么说还是synchronized代码要更容易书写些\\n\\nStampedLock\\n---\\n\\n它是java8在java.util.concurrent.locks新增的一个API。\\n\\nReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。\\n\\n然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。\\n\\nStampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。\\n\\n所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!\\n\\n下面是java doc提供的StampedLock一个例子\\n\\n\\tclass Point {\\n\\t private double x, y;\\n\\t private final StampedLock sl = new StampedLock();\\n\\t void move(double deltaX, double deltaY) { // an exclusively locked method\\n\\t long stamp = sl.writeLock();\\n\\t try {\\n\\t x += deltaX;\\n\\t y += deltaY;\\n\\t } finally {\\n\\t sl.unlockWrite(stamp);\\n\\t }\\n\\t }\\n\\t //下面看看乐观读锁案例\\n\\t double distanceFromOrigin() { // A read-only method\\n\\t long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁\\n\\t double currentX = x, currentY = y; //将两个字段读入本地局部变量\\n\\t if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?\\n\\t stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁\\n\\t try {\\n\\t currentX = x; // 将两个字段读入本地局部变量\\n\\t currentY = y; // 将两个字段读入本地局部变量\\n\\t } finally {\\n\\t sl.unlockRead(stamp);\\n\\t }\\n\\t }\\n\\t return Math.sqrt(currentX * currentX + currentY * currentY);\\n\\t }\\n\\t//下面是悲观读锁案例\\n\\t void moveIfAtOrigin(double newX, double newY) { // upgrade\\n\\t // Could instead start with optimistic, not read mode\\n\\t long stamp = sl.readLock();\\n\\t try {\\n\\t while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合\\n\\t long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁\\n\\t if (ws != 0L) { //这是确认转为写锁是否成功\\n\\t stamp = ws; //如果成功 替换票据\\n\\t x = newX; //进行状态改变\\n\\t y = newY; //进行状态改变\\n\\t break;\\n\\t }\\n\\t else { //如果不能成功转换为写锁\\n\\t sl.unlockRead(stamp); //我们显式释放读锁\\n\\t stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试\\n\\t }\\n\\t }\\n\\t } finally {\\n\\t sl.unlock(stamp); //释放读锁或写锁\\n\\t }\\n\\t }\\n\\t }\\n\\n小结:\\n\\nStampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小。\\n\\nStampedLock与ReadWriteLock性能对比\\n---\\n\\n下图是和ReadWritLock相比,在一个线程情况下,是读速度其4倍左右,写是1倍。\\n\\n\\n\\n下图是六个线程情况下,读性能是其几十倍,写性能也是近10倍左右:\\n\\n\\n\\n下图是吞吐量提高:\\n\\n\\n\\n总结\\n---\\n\\n1. synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;\\n2. ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;\\n3. StampedLock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;\\n4. StampedLock有一个复杂的API,对于加锁操作,很容易误用其他方法;\\n5. 当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;\\n6. 当线程增长能够预估,ReentrantLock是一个很好的通用的锁实现;\\n\\nStampedLock 可以说是Lock的一个很好的补充,吞吐量以及性能上的提升足以打动很多人了,但并不是说要替代之前Lock的东西,毕竟他还是有些应用场景的,起码API比StampedLock容易入手,下篇博文争取更新快一点,可能会是Nashorn的内容,这里允许我先卖个关子。。。\",\"mtime\":1444880072000,\"source\":\"source/_posts/java-8新特性探究.md\"},\"pema2is3ma5qoqf7\":{\"_id\":\"pema2is3ma5qoqf7\",\"content\":\"title: 'Every Programmer Should Know These Latency Numbers'\\ndate: 2014-05-14 11:56:45\\n\\n---\\n### Latency numbers every programmer should know\\n L1 cache reference ......................... 0.5 ns\\n Branch mispredict ............................ 5 ns\\n L2 cache reference ........................... 7 ns\\n Mutex lock/unlock ........................... 25 ns\\n Main memory reference ...................... 100 ns\\n Compress 1K bytes with Zippy ............. 3,000 ns = 3 µs\\n Send 2K bytes over 1 Gbps network ....... 20,000 ns = 20 µs\\n SSD random read ........................ 150,000 ns = 150 µs\\n Read 1 MB sequentially from memory ..... 250,000 ns = 250 µs\\n Round trip within same datacenter ...... 500,000 ns = 0.5 ms\\n Read 1 MB sequentially from SSD* ..... 1,000,000 ns = 1 ms\\n Disk seek ........................... 10,000,000 ns = 10 ms\\n Read 1 MB sequentially from disk .... 20,000,000 ns = 20 ms\\n Send packet CA->Netherlands->CA .... 150,000,000 ns = 150 ms\\n\\nAssuming ~1GB/sec SSD\\n\\n
\\n\\n\\n\\nVisual chart provided by [ayshen](https://gist.github.com/ayshen)\\n\\nData by [Jeff Dean](http://research.google.com/people/jeff/)\\n\\nOriginally by [Peter Norvig](http://norvig.com/21-days.html#answers)\\n\\nLets multiply all these durations by a billion:\\n\\nMagnitudes:\\n\\n### Minute:\\n L1 cache reference 0.5 s One heart beat (0.5 s)\\n Branch mispredict 5 s Yawn\\n L2 cache reference 7 s Long yawn\\n Mutex lock/unlock 25 s Making a coffee\\n\\n### Hour:\\n Main memory reference 100 s Brushing your teeth\\n Compress 1K bytes with Zippy 50 min One episode of a TV show (including ad breaks)\\n\\n### Day:\\n Send 2K bytes over 1 Gbps network 5.5 hr From lunch to end of work day\\n\\n### Week\\n SSD random read 1.7 days A normal weekend\\n Read 1 MB sequentially from memory 2.9 days A long weekend\\n Round trip within same datacenter 5.8 days A medium vacation\\n Read 1 MB sequentially from SSD 11.6 days Waiting for almost 2 weeks for a delivery\\n\\n### Year\\n Disk seek 16.5 weeks A semester in university\\n Read 1 MB sequentially from disk 7.8 months Almost producing a new human being\\n The above 2 together 1 year\\n\\n### Decade\\n Send packet CA->Netherlands->CA 4.8 years Average time it takes to complete a bachelor's degree\\n\\n\",\"mtime\":1444880072000,\"source\":\"source/_posts/every-programmer-should-know-these-latency-numbers.md\"},\"4wua9n0abzw9seya\":{\"_id\":\"4wua9n0abzw9seya\",\"content\":\"title: 'Proxy 那点事儿'\\ndate: 2014-05-14 15:02:44\\n\\n---\\nProxy,也就是“代理”了。意思就是,你不用去做,别人代替你去处理。比如说:赚钱方面,我就是我老婆的 Proxy;带小孩方面,我老婆就是我的 Proxy;家务事方面,没有 Proxy。 \\n\\n
\\n\\n它在程序开发中起到了非常重要的作用,比如传说中的 AOP(面向切面编程),就是针对代理的一种应用。此外,在设计模式中,还有一个“代理模式”。在公司里要上外网,要在浏览器里设置一个 HTTP 代理。wocao,代理无处不在啊,不搞明白,岂不是很没面子?\\n\\n凡是都要由浅入深,学习也不例外。先来一个 Hello World 吧: \\n\\n\\tpublic interface Hello {\\n\\t\\n\\t void say(String name);\\n\\t}\\n\\n这是一个 Hello 接口,不用解释了,大家都懂的。赶紧上实现类吧:\\n\\n\\tpublic class HelloImpl implements Hello {\\n\\t\\n\\t @Override\\n\\t public void say(String name) {\\n\\t System.out.println(\\\"Hello! \\\" + name);\\n\\t }\\n\\t}\\n\\nwocao!这简直就是 So easy 嘛!但是有个问题:如果要在 println() 方法前面和后面分别需要处理一些逻辑,怎么做呢?把这些逻辑写死在 say() 方法里面吗?肯定不够优雅啦,菜鸟一般这样干,作为一名资深的程序员,我坚决不能这样做!\\n\\n我要用代理!写一个 HelloProxy 类,让它去调用 HelloImpl 的 say() 方法,在调用的前后分别进行逻辑处理不就行了吗?赶紧搞一个吧:\\n\\n\\tpublic class HelloProxy implements Hello {\\n\\t\\n\\t private HelloImpl helloImpl;\\n\\t\\n\\t public HelloProxy() {\\n\\t helloImpl = new HelloImpl();\\n\\t }\\n\\t\\n\\t @Override\\n\\t public void say(String name) {\\n\\t before();\\n\\t helloImpl.say(name);\\n\\t after();\\n\\t }\\n\\t\\n\\t private void before() {\\n\\t System.out.println(\\\"Before\\\");\\n\\t }\\n\\t\\n\\t private void after() {\\n\\t System.out.println(\\\"After\\\");\\n\\t }\\n\\t}\\n\\n我将 HelloProxy 类实现了 Hello 接口(和 HelloImpl 实现相同的接口),并且在构造方法中 new 出一个 HelloImpl 类的实例。这样一来,我就可以在 HelloProxy 的 say() 方法里面去调用 HelloImpl 的 say() 方法了。更重要的是,我还可以在调用的前后分别加上 before() 与 after() 方法,在这两个方法里去实现那些前后逻辑。\\n\\n用一个 main 方法来测试一下吧:\\n\\n\\tpublic static void main(String[] args) {\\n\\t Hello helloProxy = new HelloProxy();\\n\\t helloProxy.say(\\\"Jack\\\");\\n\\t}\\n\\n运行后,打印出:\\n\\n\\tBefore\\n\\tHello! Jack\\n\\tAfter\\n\\n轻而易举,我就写出了这么优雅的代码(暗自小嗨了一把)。\\n\\n不久后,我在一本设计模式的书上看到,原来我写的这个 HelloProxy 就是所谓的“代理模式”啊!我只能说,自己和 GoF(四人帮)的距离有接近了一点。 \\n\\n于是我疯狂的使用“代理模式”,项目中到处都有 XxxProxy 的声影。直到有一天,架构师看到了我的代码,他惊呆了!他对我说:“你怎么这么喜欢用静态代理呢?你就不会用动态代理吗?给我全都重构了!”。\\n\\n我表面上点了点头,说:“好的!”。其实我根本都不知道什么是“静态代理”,什么又是“动态代理”。我继续翻开我那本垫桌脚的设计模式,深入得研究了一番,最后我才明白,原来我一直用的都是“静态代理”啊,怪不得架构师说我到处都是 XxxProxy 类了。我好恨我自己!一定要将这些垃圾 Proxy 都重构为“动态代理”。\\n\\n于是我就是用 JDK 给我们提供的动态代理方案,写了一个 DynamicProxy:\\n\\n\\tpublic class DynamicProxy implements InvocationHandler {\\n\\t\\n\\t private Object target;\\n\\t\\n\\t public DynamicProxy(Object target) {\\n\\t this.target = target;\\n\\t }\\n\\t\\n\\t @Override\\n\\t public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\\n\\t before();\\n\\t Object result = method.invoke(target, args);\\n\\t after();\\n\\t return result;\\n\\t }\\n\\t\\n\\t ...\\n\\t}\\n\\n在 DynamicProxy 类中,我定义了一个 Object 类型的 target 变量,它就是被代理的目标对象,通过构造函数来初始化(现在流行叫“注入”了,我觉得叫“射入”也不错哦!构造函数初始化叫“正着射”,所以 reflect 方式就叫“反着射”,简称“反射”)。\\n\\n言归正传,DynamicProxy 实现了 InvocationHandler 接口,那么必须实现该接口的 invoke 方法,参数不做解释,望文生义吧,是 JRE 给我们“射”进来的。在该方法中,直接通过反射去 invoke method,在调用前后分别处理 before 与 after,最后将 result 返回。\\n\\n写一个 main() 方法看看实际怎么用吧:\\n\\n\\tpublic static void main(String[] args) {\\n\\t Hello hello = new HelloImpl();\\n\\t\\n\\t DynamicProxy dynamicProxy = new DynamicProxy(hello);\\n\\t\\n\\t Hello helloProxy = (Hello) Proxy.newProxyInstance(\\n\\t hello.getClass().getClassLoader(),\\n\\t hello.getClass().getInterfaces(),\\n\\t dynamicProxy\\n\\t );\\n\\t\\n\\t helloProxy.say(\\\"Jack\\\");\\n\\t}\\n\\n没错,意思就是,用我写的这个通用的 DynamicProxy 类去包装 HelloImpl 实例,然后再调用 JDK 给我们提供的 Proxy 类的工厂方法 newProxyInstance() 去动态地创建一个 Hello 接口的代理类,最后调用这个代理类的 say() 方法。\\n\\n运行一下,结果和以前一样,动态代理成功了。其实,动态代理就是帮我们自动生成 XxxProxy 类的法宝啊!\\n\\n要注意的是,Proxy.newProxyInstance() 方法的参数实在是让我“蛋碎一地”!\\n\\n参数1:ClassLoader\\n参数2:该实现类的所有接口\\n参数3:动态代理对象\\n\\n调用完了还要来一个强制类型转换一下。\\n\\nwocao!这一坨 shi 一定要想办法封装一下,避免再次发生到处都是 Proxy.newProxyInstance(),这样架构师又要骂我了。于是我将这个 DynamicProxy 重构了:\\n\\n\\tpublic class DynamicProxy implements InvocationHandler {\\n\\t\\n\\t ...\\n\\t\\n\\t @SuppressWarnings(\\\"unchecked\\\")\\n\\t public
T getProxy() {\\n\\t return (T) Proxy.newProxyInstance(\\n\\t target.getClass().getClassLoader(),\\n\\t target.getClass().getInterfaces(),\\n\\t this\\n\\t );\\n\\t }\\n\\t\\n\\t ...\\n\\t}\\n\\n我在 DynamicProxy 里添加了一个 getProxy() 方法,无需传入任何参数,将刚才所说的那一坨 shi,放在这个方法中,并且该方法返回一个泛型类型,就不会强制类型转换了。方法头上加那个 @SuppressWarnings(\\\"unchecked\\\") 注解表示忽略编译时的警告(因为 Proxy.newProxyInstance() 方法返回的是一个 Object,这里我强制转换为 T 了,这是向下转型,IDE 中就会有警告,编译时也会出现提示,很烦)。\\n\\n好了,这下子使用 DynamicProxy 就简单了吧:\\n\\n\\tpublic static void main(String[] args) {\\n\\t DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());\\n\\t Hello helloProxy = dynamicProxy.getProxy();\\n\\t\\n\\t helloProxy.say(\\\"Jack\\\");\\n\\t}\\n\\n确实简单用 2 行代理就去掉了前面的 7 行代码(省了 5 行),架构师看到了这样的代码肯定会表扬我!\\n\\n经过一番代码重构后,我提交了所有的代码,架构师看到了,没有吱声…… 可我总算学会了动态代理。\\n\\n用了这个 DynamicProxy 以后,我觉得它还是非常爽的,爽的地方是,接口变了,这个动态代理类不用动。而静态代理就不一样了,接口变了,实现类还要动,代理类也要动。但我也发现动态代理并不是“万灵丹”,它也有搞不定的时候,比如说,我要代理一个没有任何接口的类,它就没有勇武之地了!这就是 JDK 给我们提供的动态代理,让我不知道该说什么了。\\n\\n于是我又开始调研,能否代理没有接口的类呢?终于让我找到了这颗“银弹”!那就是 CGLib 这个类库。虽然它看起来不太起眼,但 Spring、Hibernate 这样牛逼的开源框架都用到了它。它就是一个在运行期间动态生成字节码的工具,也就是动态生成代理类了。说起来好高深,实际用起来一点都不难。我再搞一个 CGLibProxy 吧:\\n\\n\\tpublic class CGLibProxy implements MethodInterceptor {\\n\\t\\n\\t public
T getProxy(Class
cls) {\\n\\t return (T) Enhancer.create(cls, this);\\n\\t }\\n\\t\\n\\t public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {\\n\\t before();\\n\\t Object result = proxy.invokeSuper(obj, args);\\n\\t after();\\n\\t return result;\\n\\t }\\n\\t\\n\\t ...\\n\\t}\\n\\n需要实现 CGLib 给我们提供的 MethodInterceptor 实现类,并填充 intercept() 方法。方法中最后一个 MethodProxy 类型的参数 proxy,值得注意!CGLib 给我们提供的是方法级别的代理,也可以理解为对方法的拦截(这不就是传说中的“方法拦截器”吗?)。这个功能对于我们这群屌丝程序员而言,如同雪中送炭啊,此乃神器也!我们直接调用 proxy 的 invokeSuper() 方法,将被代理的对象 obj 以及方法参数 args 传入其中即可。\\n\\n与 DynamicProxy 类似,我在 CGlibProxy 中也添加了一个泛型的 getProxy() 方法,便于我们可以快速地获取自动生成的代理对象。还是用一个 main() 方法来描述吧:\\n\\n\\tpublic static void main(String[] args) {\\n\\t CGLibProxy cgLibProxy = new CGLibProxy();\\n\\t HelloImpl helloProxy = cgLibProxy.getProxy(HelloImpl.class);\\n\\t\\n\\t helloProxy.say(\\\"Jack\\\");\\n\\t}\\n\\n仍然通过 2 行代码就可以返回代理对象了,与 JDK 动态代理不同的是,这里不需要任何的接口信息,对谁都可以生成动态代理对象(不管它是“屌丝”还是“高富帅”)。说它是神器,过分吗?\\n\\n我一向都是以追求完美而著称,2 行代码返回代理对象,我觉得还是有些多余,我不想总是去 new 这个 CGLibProxy 对象,最好 new 一次,以后随时拿随时用。于是我想到了“单例模式”:\\n\\n\\tpublic class CGLibProxy implements MethodInterceptor {\\n\\t\\n\\t private static CGLibProxy instance = new CGLibProxy();\\n\\t\\n\\t private CGLibProxy() {\\n\\t }\\n\\t\\n\\t public static CGLibProxy getInstance() {\\n\\t return instance;\\n\\t }\\n\\t\\n\\t ...\\n\\t}\\n\\n我加了以上几行代码,就搞定了!需要说明的是:这里有一个 private 的构造方法,就是为了限制外界不能再去 new 它了,换句话说,我在这里把它给“阉”了。\\n\\n用一个 main() 方法来证明我的简单主义思想:\\n\\n\\tpublic static void main(String[] args) {\\n\\t HelloImpl helloImpl = CGLibProxy.getInstance().getProxy(HelloImpl.class);\\n\\t\\n\\t helloImpl.say(\\\"Jack\\\");\\n\\t}\\n\\n没错吧?只需 1 行代码就可以获取代理对象了!\\n\\n总结一下,我们今天谈到了无代理、静态代理、JDK 动态代理、CGLib 动态代理,其实代理的世界远不止这么小,还有很多实际的应用场景。本文一开始谈到的 AOP 是一个最为典型的案例,所以有必要再进行继续下去。\\n\\n我的宝宝突然尿了,在那里狂哭,我得去换块尿布了,老婆这个 Proxy 还是靠不住啊!\\n\\n今天就到此为止,祝大家有个美好的周末!同样也期待您的留言。\",\"mtime\":1444880072000,\"source\":\"source/_posts/proxy-那点事儿.md\"},\"hwhgf62mmtqx23nx\":{\"_id\":\"hwhgf62mmtqx23nx\",\"content\":\"title: 'SQL 中不同类型的表连接'\\ndate: 2014-05-15 11:15:40\\n\\n---\\n**1、简介**\\n\\n在关系型数据库中,join操作是将不同的表中的数据联合在一起时非常通用的一种做法。首先让我们看看join是如何操作的,然后我们探索一下当join和where语句同时存在的时候的执行顺序问题,最后来谈一谈不同类型的join的顺序问题。\\n\\n
\\n\\n**2、建立初始的测试表结构([建表语句到这里下载](http://www.codeproject.com/Articles/435694/Understanding-Table-Joins-using-SQL))**\\n\\n表建立完之后,将会看到如下三个表。\\n\\n\\n\\n我们将通过以上三个表来演示join操作。这三个表都是用来做演示的,所以我并没有使用主键和外键。\\n\\n**3、表的笛卡尔乘积**\\n\\n一般情况下,我们使用两个表中的相关字段进行join操作,例如,employee表中的DeptId字段对应于Department表中的DepId字段,通过这种方式进行join。\\n\\n下面的一个例子是不使用关联字段来做表的连接。这里TableA和TableB以笛卡尔乘积的方式连在了一起。笛卡尔乘积就是先从第一个表中取出一条记录,和第二章表中的每一条记录配合,然后再取出第二条记录,同样和第二张表的所有记录配合,直至第一张表中的所有记录都取完。所以最终的结果数量将是两张表的乘积。\\n\\n\\n\\n**4、join两张表**\\n\\n当我们想做两个表的连接,而不是像上面的例子一样得到大量的无用的结果的时候,我们就得从两张表中选取一个join列。我下面给出的例子是使用id作为join列的。我们可以通过这种方式使得结果只映射我们需要的那一部分,从而过滤掉了无用数据。\\n\\n\\n\\n注意:在笛卡尔乘积表中的第一行和第五行满足了join的映射关系,从而被作为结果,其他的都被过滤掉了。\\n\\n**5、join多张表**\\n\\n上面的例子是join两张表,如果想join多张表,我们需要在上面的结果中选择一列,然后再在新表中选择一列,将这两者作为join字段,然后指定join的规则,这样我们理论上可以join任意多张表。\\n\\n\\n\\n首先,Table_A和Table_B做了连接,就上上面的join两张表的例子,然后将join的结果作为一张表AB。再将AB与Table_C连接。\\n\\n\\n\\n**6、join类型**\\n\\n在两张不同的表做连接有3中join类型。\\n\\n1. full join\\n2. inner join\\n3. outer join(left outer join、right outer join)\\n\\n在上面两个例子中我们看到的是inner join。如果我们连接表自身就叫做self join。这个特殊类型不会混淆连接类型。\\n\\n**7、full join**\\n\\nfull join和笛卡尔有些不同,笛卡尔积会获取所有可能的结果。而full join将匹配的结果与所有左边的表中不匹配右边的行和右边的表中所有不匹配左边的行加在一起,在不匹配的地方使用NULL代替。结果行数=匹配行数+左表剩余行数+右表剩余行数。\\n\\n\\n\\n\\n\\n在上面的图片中,蓝色的行是两个表匹配的行。\\n\\n第二行,左边绿色,右边红色的是不匹配的,左表中的行是存在的,而右表中的字段则被null填充。\\n\\n第三行,左边红色,右边绿色的同样是不匹配的,右表中的行是存在的,而左表中的字段则被null填充。\\n\\n**8、left join**\\n\\n左连接(left join)保证左表中的所有行都有,而当不匹配的时候以NULL填充右表字段。\\n\\n\\n\\n蓝色匹配,红色和绿色不匹配\\n\\n**9、right join**\\n\\n反过来,右连接(right join)保证右表中所有的行都有,而当不匹配的时候以NULL填充左表字段。\\n\\n\\n\\n蓝色匹配,红色和绿色不匹配\\n\\n**10、inner join**\\n\\ninner join就是只列出匹配的行。\\n\\n**11、self join**\\n\\n表连接自身叫做self join。为了解释一下这个让我们看如下图中的employee表。EmployeeID是此表的主键,ReportsTo引用了此表的主键。我们可以想象成这样,ReportTo字段引用代表该雇员的上司,其上司同样也是雇员。\\n\\n\\n\\n看如下例子\\n\\n \\n\\n这里,有ReportTo指向的行是Manager,所以employee是左表,Manager是右表。\\n\\n**12、执行顺序**\\n\\n当连接中有where语句的时候我们需要注意连接和where的执行顺序问题。\\n\\n1. 将where语句先于join执行,因为执行完where查询的结果将会比较少,从而join操作性能会提升。\\n2. 将where语句后与join执行。\\n\\n以上两者将在inner join的时候返回同样的结果,但是当使用outer join的时候至少有一种连接操作的返回结果不同。看下面例子。\\n\\n\\n\\n\\n\\n\\n\\n所以记住当外连接的时候尽量先执行join操作然后执行where语句。\\n\\n**13、连接的顺序**\\n\\n当你想将inner join和outer join同时使用的时候join的顺序也是非常重要的。\\n\\n什么是连接的顺序?如果我像这样连接三张表【X inner Y】left Z,顺序就是先inner join再left join。\\n\\n让我们回到上面的例子中,你想得到的结果是获取所有客户的名字,不管他们是否有订单。如果他们确实有一些订单,还要列出了客户订购的数量。\\n\\n看如下的查询【先outer join再inner join】\\n\\n\\n\\n1. 在Orders和Customers中进行了right join。右连接能保证你获取所有Customer的信息,不管他是否有order。\\n2. 现在上面的结果将和Order Details连接。但是我们需要注意的是,在右连接的结果中有两行roderid为null的,因为这两个customer并没有任何order,而在后面做inner join的时候,由于orderid为null,inner join将跳过这两行,从而导致这两个customer的信息被过滤掉了。\\n再让我们看看下面的这个查询【先inner join再outer join】\\n\\n\\n\\n让我们分析一下为什么这才是我们想要的结果。\\n\\n首先Order和Order Details表做inner join,所有匹配的结果都将被列出来,然后将此结果作为左表,Customer表作为右表,右表的所有行都将被列出来,不管其匹配与否(言外之意,那两个没有order的customer也将被列出来)。\\n\\n所以,在我们同时使用inner join和outer join的时候一定要对连接的顺序做慎重考虑。\\n\\n**14、获取同样数据的其他办法**\\n\\n看如下查询\\n\\n\\n\\n1. 首先查询出Customers将其作为左表\\n2. 然后将Orders表查询出来,仍然作为左表\\n3. 然后查询出Order Details表将其作为右表与Orders表进行inner join。\\n4. 最后Customers表将于第三步查询出的结果进行左连接。别忘了左连接将保证Customers表不丢失任何记录。\\n\\n[OSChina.NET](http://oschina.net/)原创翻译/[原文链接](http://www.codeproject.com/Articles/435694/Understanding-Table-Joins-using-SQL)\",\"mtime\":1444880072000,\"source\":\"source/_posts/sql-中不同类型的表连接.md\"},\"z8em2p5pdbira75k\":{\"_id\":\"z8em2p5pdbira75k\",\"content\":\"title: 'Peter Norvig:编程语言的选择并不重要'\\ndate: 2014-05-14 11:49:25\\n\\n---\\n\\n\\nLisp被许多资深程序员视为编程语言中的圣杯,因为学起来很难。著名程序员、最热门的技术问答网站StackOverflow创始人Joel Spolsky曾经在[“Java语言学校的危险性”](http://www.ruanyifeng.com/blog/2008/12/the_perils_of_javaschools.html)一 文中说,自己当年在大学里学习用Lisp的导论课程也是苦不堪言,而Lisp这样的函数式语言实际开发中并不常用,但是一旦到了体现优势的时候,你如果不 懂,将失之千里。他还认为,Google的核心技术之一MapReduce就来自函数式语言,而且使Google领先微软多年。请注意,Spolsky曾 经1990年代微软的Excel项目经理,VBA的主要创造者。\\n\\n
\\n\\n《Unix编程艺术》的作者Eric Raymond也在[“如何成为黑客”](http://www.aka.org.cn/Docs/hacker-howto_2001.html)(英文版)中说过,Lisp是对黑客特别重要的语言,“掌握了之后,你会得到丰富的启迪和经验。 即使实际上很少使用Lisp,这些经验也会使你在以后的日子里成为更好的程序员。”\\n\\n而Paul Graham更是Lisp的头号吹鼓手,他曾与蠕虫病毒发明者Robert Morris一起,成功地用Lisp开发历史上第一个Web应用——ViaWeb,并高价卖给Yahoo致富。他在2001年写了一篇宣传Lisp的名文“[Beating the Averages](http://www.paulgraham.com/avg.html)”(英文),其中说到:“Lisp之所以极为优秀,并不是因为只有铁杆粉丝才知道的某些魔术般的性质,而在于它确实是最强大的编程语言。大家不用它的原因,在于编程语言不只是技术,而且也是思维习惯,这是改变起来最慢的东西。”\\n\\n有意思的是,美国时间10月18日,著名技术新闻网站Hacker News上,名为kung-fu-master的用户发了一篇名为[“问PG: Lisp与Python (2010)”](http://news.ycombinator.com/item?id=1803351)的帖子,内容很简单:“好像许多Lisp老枪都开始转而用Python的(比如Peter Norvig)。今天你怎么看Lisp与Python?”\\n\\n这里的PG就是指Hacker News的创始人、著名Lisp程序员与吹鼓手Paul Graham。而其中提到的Peter Norvig则是另一个著名Lisp程序员,现任Google研发总监。他有一篇经典的文章[“十年学会编程”](http://daiyuwen.freeshell.org/gb/misc/21-days-cn.html)(英文版),相信大家都已经看过,他的首选推荐语言当然也是Lisp(更准确的说是Lisp的方言Scheme)。\\n\\n面对这样直接的问题,专家们是怎么回答的呢?\\n\\nPaul Graham说:\\n\\n \\n\\n>这问题好像来自2005年而不是2010年。Lisp现在由于Clojure(Lisp的现代方言,可以运行在JVM和.NET上——CSDN编者注)的出现,已经又变得时髦了。当然Python也有很好的库,但是我觉得用缺乏宏的语言编程很受局限。\\n\\n许多网友对Python没有宏的问题进行了讨论。有人说不支持宏是出于代码可读性的考虑。而更多的人则认同宏是一种抽象工具,任何工具都会被误用、滥用,但并不表示工具本身有问题。\\n\\n另一Lisp专家,《Coder at Work》和《Practical Common Lisp》作者>Peter Seibel也加入了讨论(他最近忙于编辑一种杂志Code Quarterly)。他说:自己与Peter Norvig有类似的体验。虽然自己Lisp非常熟练,但是在表达算法方面Python是最佳选择。不过,相比Lisp实现,CPython的运行速度慢得惊人,甚至差5-10倍。\\n\\n而Peter Norvig也罕见地现出真身,给出了自己的回答,内容一如既往地经典:\\n\\n \\n\\n>我是Peter Norvig。我转向Python不是因为它比Lisp更好、更令人满意、更实用,而是因为它用作伪代码更合适。许多学生说,在《人工智能》 (Norvig与Russell合著的经典教材)一书中的伪代码与我们在网上提供的Lisp代码之间进行转换太难了。于是我要寻找一种更近似伪代码的语 言,发现Python是最合适的。然后我就自学了Python,熟练到足以实现教材中示例的地步。我发现Python对于一些类型的小问题非常合适,有些 库也是我与其他的一些代码(包括在Google内和网上其他地方的代码)集成非常需要的。 我认为Lisp对更大的项目和编译速度很重要的应用而言仍然具有优势。但是在主要目的是交流而非编程的时候(比如针对数量众多的学生),Python更有 优势。\\n\\n就更一般意义上的编程而言,在Google和其他地方,我认为语言的选择并不如其他方面的选择那么重要:如果你有了正确的总体架构、正确的程序员团队、正确的开发过程(能够快速开发、持续改善),那么很多语言都能胜任;但如果以上的东西你没有,那无论选择什么语言,你都会陷入一团糟。\\n\\n还有人找出今年2月Norvig在一次采访(无法直接访问)中类似的话,透露了Google内部的一些信息:\\n\\n \\n\\n1. Google最开始似乎核心程序员都是用C++的,效率很高。这部分形成了公司的文化。\\n\\n2. Google早期的Lisp程序员(Erann Gat) 注意到其他程序员和自己的效率差不多,甚至更好。关键还是在人,与20年前相比,现在语言的选择不那么重要了。\\n\\n3. Lisp是为单个程序员或者一小组程序员进行探索性工作而专门设计的……如果我想自己在周末修改代码,会更愿意用Lisp,但是如果有几百个程序员一起改代码,那就不是编程语言的问题,而是社会问题了。\\n\\n4. 库。\\n\\n其他人举出的材料中,最有意思的应该来自Lisp之父、计算机科学巨人约翰麦卡锡。[这篇博客](http://smuglispweeny.blogspot.com/2008/02/ooh-ooh-my-turn-why-lisp.html)(无法直接访问)里生动讲述了这位图灵奖得主在某次听Peter Norvig改而鼓吹Python的演讲后的情景:\\n\\n \\n\\nNorvig演讲后,进入提问环节。出乎我的意料,他点了一位皱巴巴的老头。老头的胡子和头发都花白了,而且乱糟糟的,看 上去像是来此参观但是迷路了,到这里来休息一下,好奇地听听我们在说什么。我第一个念头是,估计他已经被这么艰深的话题弄晕了。但是马上想到,不对啊,这 里离斯坦福很近,这老头的年纪也对,难道是……\\n\\n只听Norvig说:“是,John,你有什么问题?”我记不清Lisp之父当时怎么问的了,但不超过十个词,就是问 Python是否能如Lisp那样优雅地像数据一样处理代码。“不,John,不行。”Norvig回答,然后静等麦卡锡继续发问。但是,老人什么也没有 再说。此时真是无言胜千语啊……\\n\\n看来,在大师眼中,数据与代码等同处理是最重要的语言特性之一。他还曾经在访谈中这样评价Ruby(提问者说Ruby从Lisp中借鉴了很多):“Ruby能像数据那样使用列表结构吗?那每次算加和减的时候,都得进行解析啰?这方面Ruby还不如1960年的Lisp。”\",\"mtime\":1444880072000,\"source\":\"source/_posts/peter-norvig:编程语言的选择并不重要.md\"},\"e71jdvrzum432wwy\":{\"_id\":\"e71jdvrzum432wwy\",\"content\":\"title: 'Java 处理 XML 的三种主流技术及介绍'\\ndate: 2014-05-14 12:38:14\\n\\n---\\nXML (Extensible Markup Language) 意为可扩展标记语言,它已经是软件开发行业中大多数程序员和厂商用以选择作为数据传输的载体。本文作者对于 Java 处理 XML 的几种主流技术进行一些总结和介绍,希望帮助那些有不同需求的开发人员对于 XML 处理技术的作出最优的选择。\\n\\n
\\n\\n最初,XML 语言仅仅是意图用来作为 HTML 语言的替代品而出现的,但是随着该语言的不断发展和完善,人们越来越发现它所具有的优点:例如标记语言可扩展,严格的语法规定,可使用有意义的标记,内容 存储和表现分离等等优势注定了该语言从诞生之日起就会走向辉煌。 XML 语言在成为 W3C 标准之后进入到了一个快速发展的时期,当然它本身所具有的一系列优点和优势也注定了各大技术厂商对它的偏爱,Java 作为软件行业的一种开发技术也迅速作出了反应,出现了多种对 XML 支持的工具,本文将会从这个角度对 Java 处理 XML 的几种主流技术进行介绍,希望能对您有所帮助。在这篇文章中,您将会得到以下信息:\\n\\n1. Java 提供了哪些优秀的类库及工具便于程序员对 XML 进行处理 ?\\n2. 有了 DOM 了,其它工具类库还有必要么 ?\\n3. 几个小例程带你快速了解这三种解析方式\\n\\nJava 有哪些优秀的类库及工具便于程序员对 XML 进行处理 ?\\n\\n* 大名鼎鼎的 DOM\\n* 绿色环保的 SAX\\n* 默默无闻的 Digester\\n\\nXML 三种解析方式简介\\n===\\n\\n大名鼎鼎的 DOM\\n\\n说它大名鼎鼎可是一点不为过,DOM 是 W3C 处理 XML 的标准 API,它是许多其它与 XML 处理相关的标准的基础,不仅是 Java,其它诸如 Javascript,PHP,MS .NET 等等语言都实现了该标准, 成为了应用最为广泛的 XML 处理方式。当然,为了能提供更多更加强大的功能,Java 对于 DOM 直接扩展工具类有很多,比如很多 Java 程序员耳熟能详的 JDOM,DOM4J 等等, 它们基本上属于对 DOM 接口功能的扩充,保留了很多 DOM API 的特性,许多原本的 DOM 程序员甚至都没有任何障碍就熟练掌握了另外两者的使用,直观、易于操作的方式使它深受广大 Java 程序员的喜爱。\\n\\n绿色环保的 SAX\\n\\nSAX 的应运而生有它特殊的需要,为什么说它绿色环保呢,这是因为 SAX 使用了最少的系统资源和最快速的解析方式对 XML 处理提供了支持。 但随之而来繁琐的查找方式也给广大程序员带来许多困扰,常常令人头痛不已,同时它对 XPath 查询功能的支持,令人们对它又爱又恨。\\n\\n默默无闻的 Digester:XML 的 JavaBean 化\\n\\nDigester 是 apache 基金组织下的一个开源项目,笔者对它的了解源于对 Struts 框架的研究,是否有很多程序员想要一解各大开源框架的设计甚至想要自己写一个功能强大的框架时会碰到这样一个难题: 这些形形色色的用 XML 语言标记的框架配置文件,框架底层是用什么技术来解析呢? DOM 解析耗费时间,SAX 解析又过于繁琐,况且每次解析系统开销也会过大, 于是,大家想到需要用与 XML 结构相对应的 JavaBean 来装载这些信息,由此 Digester 应运而生。它的出现为 XML 转换为 JavaBean 对象的需求带来了方便的操作接口,使得更多的类似需求得到了比较完美的解决方法, 不再需要程序员自己实现此类繁琐的解析程序了。与此同时 SUN 也推出了 XML 和 JavaBean 转换工具类 JAXB,有兴趣的读者可以自行了解。\\n\\n三种解析方式比较\\n===\\n\\nDOM\\n\\n优缺点:实现 W3C 标准,有多种编程语言支持这种解析方式,并且这种方法本身操作上简单快捷,十分易于初学者掌握。其处理方式是将 XML 整个作为类似树结构的方式读入内存中以便操作及解析,因此支持应用程序对 XML 数据的内容和结构进行修改,但是同时由于其需要在处理开始时将整个 XML 文件读入到内存中去进行分析,因此其在解析大数据量的 XML 文件时会遇到类似于内存泄露以及程序崩溃的风险,请对这点多加注意。\\n\\n适用范围:小型 XML 文件解析、需要全解析或者大部分解析 XML、需要修改 XML 树内容以生成自己的对象模型\\n\\nSAX\\n\\nSAX 从根本上解决了 DOM 在解析 XML 文档时产生的占用大量资源的问题。其实现是通过类似于流解析的技术,通读整个 XML 文档树,通过事件处理器来响应程序员对于 XML 数据解析的需求。由于其不需要将整个 XML 文档读入内存当中,它对系统资源的节省是十分显而易见的,它在一些需要处理大型 XML 文档以及性能要求较高的场合有起了十分重要的作用。支持 XPath 查询的 SAX 使得开发人员更加灵活,处理起 XML 来更加的得心应手。但是同时,其仍然有一些不足之处也困扰广大的开发人员:首先是它十分复杂的 API 接口令人望而生畏,其次由于其是属于类似流解析的文件扫描方式,因此不支持应用程序对于 XML 树内容结构等的修改,可能会有不便之处。\\n\\n适用范围:大型 XML 文件解析、只需要部分解析或者只想取得部分 XML 树内容、有 XPath 查询需求、有自己生成特定 XML 树对象模型的需求\\n\\nDigester/JAXB\\n\\n优缺点 : 由于其是在上述两者的基础上衍生出来的工具类,为的是满足将 XML 转换为 JavaBean 的特殊需求,故而没有什么特别明显的优缺点。作为大名鼎鼎的开源框架 Struts 的 XML 解析工具 Digester,为我们带来了将 XML 转换为 JavaBean 的可靠方法。\\n\\n适用范围 : 有将 XML 文档直接转换为 JavaBean 需求。\\n\\n应用示例\\n\\n下面给出一段用于解析的 XML 片段:\\n\\n\\t
\\n\\t
\\n\\t
\\n\\t
Harry Potter
\\n\\t
J K. Rowling
\\n\\t
\\n\\t
\\n\\t
Learning XML
\\n\\t
Erik T. Ray
\\n\\t
\\n\\t
\\n\\nDOM 解析 XML\\n===\\n\\nJava 中的 DOM 接口简介: JDK 中的 DOM API 遵循 W3C DOM 规范,其中 org.w3c.dom 包提供了 Document、DocumentType、Node、NodeList、Element 等接口, 这些接口均是访问 DOM 文档所必须的。我们可以利用这些接口创建、遍历、修改 DOM 文档。\\n\\njavax.xml.parsers 包中的 DoumentBuilder 和 DocumentBuilderFactory 用于解析 XML 文档生成对应的 DOM Document 对象。\\n\\njavax.xml.transform.dom 和 javax.xml.transform.stream 包中 DOMSource 类和 StreamSource 类,用于将更新后的 DOM 文档写入 XML 文件。\\n\\n下面给出一个运用 DOM 解析 XML 的例子:\\n\\n\\timport java.io.File; \\n\\t import java.io.IOException; \\n\\t import javax.xml.parsers.DocumentBuilder; \\n\\t import javax.xml.parsers.DocumentBuilderFactory; \\n\\t import javax.xml.parsers.ParserConfigurationException; \\n\\t import org.w3c.dom.Document; \\n\\t import org.w3c.dom.Element; \\n\\t import org.w3c.dom.Node; \\n\\t import org.w3c.dom.NodeList; \\n\\t import org.xml.sax.SAXException; \\n\\n\\t public class DOMParser { \\n\\t DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); \\n\\t //Load and parse XML file into DOM \\n\\t public Document parse(String filePath) { \\n\\t Document document = null; \\n\\t try { \\n\\t //DOM parser instance \\n\\t DocumentBuilder builder = builderFactory.newDocumentBuilder(); \\n\\t //parse an XML file into a DOM tree \\n\\t document = builder.parse(new File(filePath)); \\n\\t } catch (ParserConfigurationException e) { \\n\\t e.printStackTrace(); \\n\\t } catch (SAXException e) { \\n\\t e.printStackTrace(); \\n\\t } catch (IOException e) { \\n\\t e.printStackTrace(); \\n\\t } \\n\\t return document; \\n\\t } \\n\\t\\n\\t public static void main(String[] args) { \\n\\t DOMParser parser = new DOMParser(); \\n\\t Document document = parser.parse(\\\"books.xml\\\"); \\n\\t //get root element \\n\\t Element rootElement = document.getDocumentElement(); \\n\\n\\t //traverse child elements \\n\\t NodeList nodes = rootElement.getChildNodes(); \\n\\t for (int i=0; i < nodes.getLength(); i++) \\n\\t { \\n\\t Node node = nodes.item(i); \\n\\t if (node.getNodeType() == Node.ELEMENT_NODE) { \\n\\t Element child = (Element) node; \\n\\t //process child element \\n\\t } \\n\\t } \\n\\n\\t NodeList nodeList = rootElement.getElementsByTagName(\\\"book\\\"); \\n\\t if(nodeList != null) \\n\\t { \\n\\t for (int i = 0 ; i < nodeList.getLength(); i++) \\n\\t {\\n\\t \\tElement element = (Element)nodeList.item(i); \\n\\t\\t\\t\\t\\tString id = element.getAttribute(\\\"id\\\"); \\n\\t } \\n\\t } \\n\\t } \\n\\t } \\n \\n在上面的例子中,DOMParser 的 Parse() 方法负责解析 XML 文件并生成对应的 DOM Document 对象。其中 DocumentBuilderFactory 用于生成 DOM 文档解析器以便解析 XML 文档。 在获取了 XML 文件对应的 Document 对象之后,我们可以调用一系列的 API 方便的对文档对象模型中的元素进行访问和处理。 需要注意的是调用 Element 对象的 getChildNodes() 方法时将返回其下所有的子节点,其中包括空白节点,因此需要在处理子 Element 之前对节点类型加以判断。\\n\\n可以看出 DOM 解析 XML 易于开发,只需要通过解析器建立起 XML 对应的 DOM 树型结构后便可以方便的使用 API 对节点进行访问和处理,支持节点的删除和修改等。 但是 DOM 解析 XML 文件时会将整个 XML 文件的内容解析成树型结构存放在内存中,因此不适合用 DOM 解析很大的 XML 文件。\\n\\nSAX 解析 XML\\n===\\n\\n与 DOM 建立树形结构的方式不同,SAX 采用事件模型来解析 XML 文档,是解析 XML 文档的一种更快速、更轻量的方法。 利用 SAX 可以对 XML 文档进行有选择的解析和访问,而不必像 DOM 那样加载整个文档,因此它对内存的要求较低。 但 SAX 对 XML 文档的解析为一次性读取,不创建任何文档对象,很难同时访问文档中的多处数据。\\n\\n下面是一个 SAX 解析 XML 的例子:\\n\\n\\timport org.xml.sax.Attributes; \\n\\t import org.xml.sax.SAXException; \\n\\t import org.xml.sax.XMLReader; \\n\\t import org.xml.sax.helpers.DefaultHandler; \\n\\t import org.xml.sax.helpers.XMLReaderFactory; \\n\\n\\t public class SAXParser { \\n\\n\\t class BookHandler extends DefaultHandler { \\n\\t private List
nameList; \\n\\t private boolean title = false; \\n \\n\\t public List
getNameList() { \\n\\t\\t\\treturn nameList; \\n\\t } \\n\\t // Called at start of an XML document \\n\\t @Override \\n\\t public void startDocument() throws SAXException { \\n\\t System.out.println(\\\"Start parsing document...\\\"); \\n\\t nameList = new ArrayList
(); \\n\\t } \\n\\t // Called at end of an XML document \\n\\t @Override \\n\\t public void endDocument() throws SAXException { \\n\\t System.out.println(\\\"End\\\"); \\n\\t } \\n \\n\\t /** \\n\\t * Start processing of an element. \\n\\t * @param namespaceURI Namespace URI \\n\\t * @param localName The local name, without prefix \\n\\t * @param qName The qualified name, with prefix \\n\\t * @param atts The attributes of the element \\n\\t */ \\n\\t @Override \\n\\t public void startElement(String uri, String localName, String qName, \\n\\t\\t Attributes atts) throws SAXException { \\n\\t // Using qualified name because we are not using xmlns prefixes here. \\n\\t if (qName.equals(\\\"title\\\")) { \\n\\t title = true; \\n\\t } \\n\\t } \\n \\n\\t @Override \\n\\t public void endElement(String namespaceURI, String localName, String qName) \\n\\t \\t throws SAXException { \\n\\t // End of processing current element \\n\\t if (title) { \\n\\t title = false; \\n\\t } \\n\\t } \\n \\t\\t\\t\\n\\t @Override \\n\\t public void characters(char[] ch, int start, int length) { \\n\\t // Processing character data inside an element \\n\\t if (title) { \\n\\t String bookTitle = new String(ch, start, length); \\n\\t System.out.println(\\\"Book title: \\\" + bookTitle); \\n\\t nameList.add(bookTitle); \\n\\t } \\n\\t } \\t\\t\\t\\n\\t } \\n\\t\\n\\t public static void main(String[] args) throws SAXException, IOException { \\n\\t XMLReader parser = XMLReaderFactory.createXMLReader(); \\n\\t BookHandler bookHandler = (new SAXParser()).new BookHandler(); \\n\\t parser.setContentHandler(bookHandler); \\n\\t parser.parse(\\\"books.xml\\\"); \\n\\t System.out.println(bookHandler.getNameList()); \\n\\t } \\n\\t } \\n \\nSAX 解析器接口和事件处理器接口定义在 org.xml.sax 包中。主要的接口包括 ContentHandler、DTDHandler、EntityResolver 及 ErrorHandler。 其中 ContentHandler 是主要的处理器接口,用于处理基本的文档解析事件;DTDHandler 和 EntityResolver 接口用于处理与 DTD 验证和实体解析相关的事件; ErrorHandler 是基本的错误处理接口。DefaultHandler 类实现了上述四个事件处理接口。上面的例子中 BookHandler 继承了 DefaultHandler 类, 并覆盖了其中的五个回调方法 startDocument()、endDocument()、startElement()、endElement() 及 characters() 以加入自己的事件处理逻辑。\\n\\nDigester 解析 XML\\n===\\n\\n为了满足将 XML 转换为 JavaBean 的特殊需求,Apache 旗下的一个名为 Digester 的工具为我们提供了这么一个选择。由于最终是将 XML 转化为 JavaBean 存储在内存当中, 故而解析性能等方面其实与使用者并没有多大关系。解析的关键在于用以匹配 XML 的模式以及规则等,由于该工具较为复杂,限于篇幅,作者只能给予简单的介绍。\\n\\n下面是一个 Digester 解析 XML 的例子片段:\\n\\n\\t// 定义要解析的 XML 的路径,并初始化工具类\\n\\tFile input = new File(\\\"books.xml\\\"); \\n\\tDigester digester = new Digester(); \\n\\n\\t// 如果碰到了
这个标签,应该初始化 test.myBean.Books 这个 JavaBean 并填装相关内容\\n\\tdigester.addObjectCreate(\\\"books\\\", \\\"test.myBean.Books\\\"); \\n\\tdigester.addSetProperties(\\\"books\\\"); \\n\\t// 如果碰到了
这个标签,同上初始化 test.myBean.Book 这个 JavaBean \\n\\tdigester.addObjectCreate(\\\"books/book\\\", \\\"test.myBean.Book\\\"); \\n\\tdigester.addSetProperties(\\\"books/book\\\"); \\n\\t// 通过调用上面已经初始化过的 JavaBean 的 addBook() 方法来把多个
加到一个集合中\\n\\tdigester.addSetNext(\\\"books/book\\\", \\\"addBook\\\", \\\"test.myBean.Book\\\"); \\n\\n\\t// 定义好了上面的解析规则后,就可以开始进行解析工作了\\n\\tBooks books = (Books) digester.parse(input); \\n\\n上述代码简单的向读者展示了 Digester 处理 XML 的一些要点,主要是说明了一些模式以及规则的匹配。 简言之,Digester 就是一种用来把一个 XML 转化为一个与该 XML 结构类似的 JavaBean。你可以把 XML 根元素想象成一个 JavaBean, 该根元素的 attribute 就是这个 JavaBean 的各种 Field,当该根元素有其他子 tag 时,又要把这个子 tag 想象成一个个新的 XML,将其视为一个新的 JavaBean, 并作为一个 Field 加入到父 Bean 当中,然后以此类推,通过循环的方式将整个 XML 进行解析。\\n\\n\\n结束语\\n===\\n\\n本文介绍了 Java 解析 XML 的三种常用技术,其中 DOM 易于上手,程序易于理解,但缺点在于占用内存大,不适合于解析较大的 XML 文件; SAX 基于事件模型占用系统资源少,能够胜任较大的 XML 文件解析,但解析过程较为繁琐查找元素不方便; Digester/JAXB 基于上述两种技术衍生而来。文中的实例向读者展示了三种 API 的基本使用方法, 在实际开发过程中使用那种技术解析 XML 更好要依据各自的优缺点视具体情况而定。\\n原文出处:[IBM developerWorks](http://www.ibm.com/developerworks/cn/xml/dm-1208gub/index.html) \",\"mtime\":1444880072000,\"source\":\"source/_posts/java-处理-xml-的三种主流技术及介绍.md\"},\"h9sxouwae5adkmvf\":{\"_id\":\"h9sxouwae5adkmvf\",\"content\":\"title: about\\ndate: 2014-05-07 16:17:33\\n---\\n\",\"mtime\":1444880072000,\"source\":\"source/about/index.md\"},\"xa3zru2vmqiys8s9\":{\"_id\":\"xa3zru2vmqiys8s9\",\"content\":\"title: TCP三次握手/四次挥手详解\\ndate: 2014-05-14 15:51:16\\n\\n---\\n**一,TCP(Transmission Control Protocol) 传输控制协议**\\n\\nTCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:\\n\\n
\\n\\n**二,三次握手过程**\\n\\n第一次握手:主机A发送位码为SYN=1,随机产生seq =X 的数据包到服务器,主机B由SYN=1知道,A要求建立联机。(A处于SYN - SENT,B处于LISTEN)\\n\\n\\n\\n第二次握手: 主机B收到请求(根据SYN=1)后要确认联机信息,向A发送ack =(X+1),SYN=1,ACK=1,随机产生seq=Y的包 。(B处于SYN -- RECEIVED)\\n\\n\\n\\n第三次握手:主机A收到后检查ack是否正确,即第一次发送的seq+1,以及位码ACK是否为1,若正确,主机A会再发送ack =(Y+1),ACK=1,主机B收到后确认seq值与ack=1则连接建立成功。(记住此时SYN!=1) (A发送完ESTABLISHED,B接受到为ESTABLISHED)\\n\\n\\n\\n完成三次握手,主机A与主机B开始传送数据。一个完整的三次握手也就是 请求---应答---再次确认\\n\\n**三,四次挥手:**\\n\\n由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。\\n\\n收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。\\n\\n1. 客户端A发送一个FIN=1,用来关闭客户A到服务器B的数据传送。 (此时A处于FIN - WAIT-1,等待B的FIN=1)\\n\\n2. 服务器B收到这个FIN=1,它发回一个ACK=1,确认序号:X+1。 (此时B处于CLOSE - WAIT-1)\\n\\n3. 服务器B发送完数据,发送一个FIN=1给客户端A。(此时B处于LAST - ACK,A处于FIN - WAIT-2)\\n\\n4. 客户端A发回ACK=1报文确认,并将确认序号设置为收到序号加1 (此时A处于TIME - WAIT,B处于CLOSEED)\\n\\n**四,问题汇总**\\n\\n**1\\\\. 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?**\\n\\n这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。\\n\\n但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送,但未必所有的数据都全部发送给对方,所以你未必会马上关闭SOCKET,也即你可能还需要发送一些数据给对方,之后再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。\\n\\n**2\\\\. 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?**\\n\\n这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);\\n\\n但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。\\n\\n**3\\\\. 什么是2MSL**\\n\\nMSL是Maximum Segment Lifetime,译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。\\n\\n因为TCP报文(segment)是IP数据报(datagram)的数据部分,而IP头中有一个TTL域,TTL是time to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个IP数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。\\n\\nRFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等\\n\\n2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间。\\n\\n等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。\\n\\n在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。\\n\\n当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。\\n\\nTTL与MSL是有关系的但不是简单的相等的关系,MSL要大于等于TTL。\\n\\n**4\\\\. SYN攻击**\\n\\n在三次握手过程中,服务器【发送SYN-ACK之后,收到客户端的ACK之前】的TCP连接称为半连接(half-open connect)。此时服务器处于SYN_RECV状态,当收到ACK后,服务器转入ESTABLISHED状态.\\n\\nSYN攻击就是:攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复ACK确认包,并等待客户的确认从而建立连接。由于源地址是不存在的,不会再发送ACK确认包,所以服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。\\n\\nSYN攻击是一个典型的DDOS攻击。检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。\\n\\n在Linux下可以如下命令检测是否被Syn攻击:netstat -n -p TCP | grep SYN_RECV\\n\\n一般较新的TCP/IP协议栈都对这一过程进行修正来防范SYN攻击,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等。但是不能完全防范syn攻击。\",\"mtime\":1444880072000,\"source\":\"source/_posts/tcp三次握手四次挥手详解.md\"},\"6sx7dxix0lszafke\":{\"_id\":\"6sx7dxix0lszafke\",\"content\":\"title: 'ThoughtWorks(中国) 程序员读书雷达'\\ndate: 2014-05-14 14:13:08\\n\\n---\\n\\n\\n软件业的特点是变化。若要提高软件开发的技能,就必须跟上技术发展的步伐。埋首醉心于项目开发与实战,固然能够锤炼自己的开发技巧,却难免受限于经验与学识。世界上并不存在速成的终南捷径,但阅读好的技术书籍,尤其是阅读大师们的经典著作,总能收到事半功倍之效。一位优秀的程序员,或许就是一名好的阅读者。好的阅读者,总是知道如何选择好的书籍。书海浩繁,良莠不齐。阅读技术好书,如与智者交谈,“与君一席话胜读十年书”;遭遇技术烂书,如被拐卖,“少小离家老大回,乡音无改鬓毛衰”。\\n\\n
\\n\\nThoughtWorks作为一家学习型组织,颇为看重每一位员工的学习能力。事实上,大多数ThoughtWorker的骨子里,都溢满了读书的基因。与书相伴,与书为伍,既是一种乐趣,又是一种习惯。当习惯成为自然时,书籍就成为生活和工作不可或缺的一部分了。如果说人文历史哲学等书籍是一碗心灵鸡汤,技术书籍大抵算得上是一味营养素,读之可以直接带来养分;可若是不了解自己究竟缺了哪一种营养,乱吃乱补,结果就可能适得其反了。有鉴于此,我和同事刘龙军结合自身的阅读经验,为新入职ThoughtWorks的程序员制作了一份读书路线图。我们将范围明确为程序员,是因为作为程序员的我们,它是我们最了解的工作角色。我们筛选出了一些大家公认的经典书籍,再结合自己的阅读体会,并广泛征集了更多ThoughtWorker的意见,包括徐昊,熊节,郑晔等资深阅读者,得到了这样一份草稿。在中国公司内部推出时,这份读书路线图得到了多数人的认可和欢迎。继而,我们在成都办公室陆续针对此读书路线图展开了读书俱乐部的活动,算是做了一次全方位大面积的试水。\\n\\n现在,我们觉得是推出正式版本,并面向更多受众的时机了。这就是大家现在看到的ThoughtWorks(中国)程序员读书雷达。它相较于之前的读书路线图草稿,分类更为专业,书籍筛选更为合理,并以雷达独有的优势,来展现不同的阅读维度与阅读深度。显然,读书雷达更能表达我们对所选书籍的深度认识。它可以作为学习型组织的一份知识输入,每一位程序员都可以根据自身水平与能力,选择适合自己的书籍,甚至组成一份表达阅读履历的读书路线图,在获得知识完善与汲取的过程中,提高自身能力,达至各个维度的巅峰。\\n\\n该读书雷达将书籍分为了如下四个维度:\\n\\n>Coding Practice(编程实践)\\n\\n>Architecture & Design(架构与设计)\\n\\n>Methodology(方法学)\\n\\n>Thought & Leadership(思想与领导力)\\n\\n我们之所以将方法学、思想与领导力放入到这个为程序员设计的读书雷达中,乃是因为软件开发不仅仅是个人的活动,也不仅仅是编码技巧和设计能力的体现。我们认为,开发技能其实是一项综合的系统工程。了解方法学,可以促进你对开发过程的理解;关于思想,则涉及大脑思维的修炼,可以提高程序员的抽象能力、学习能力,有助于帮助程序员发现事物发展的规律和本质;至于领导力,则有助于程序员在开发团队中发挥更大的作用,并能作为很好的团队成员,提升团队的整体能力。我们没有在这个读书雷达中包括语言类的书籍,原因在于这类书籍太过于发散,且更新较快;我们也不可能奢望在一张读书雷达图中,为不同的语言平台展现不同的优秀书籍。相比较语言的学习,我们更看重程序员在编程实践上的基本功。\\n\\n每个维度皆分为三个等级,分别为Fundamental、Medium和Advanced,对应初学、进阶和高级。在雷达图中,读者可以根据该书在图中距离圆心的远近,判断它的难度级别。我们还使用了不同的图示来表达对每本书的倾向性意见,其中,橙色的三角形图示代表“强烈推荐”,蓝色的圆形图示代表“推荐”。我们希望这类书籍对于程序员而言,可以根据自己目前的水平,选择适合自己的书籍。在这个层次上,强烈推荐可能就意味着必读。\\n\\n或许有人会质疑读书雷达的挑选标准。正所谓“每个人心中都有自己的哈姆雷特”,我们认同的未必能得到读者的认同,这是我们这个世界关于一种观点或态度的常态。我们发布这个读书雷达,只是希望能提供更多有用的信息与参考,而非代表一种读书权利的绝对化。事实上,我们也不可能做到。此外,”吾生也有涯,而知也无涯”,我们阅读的书籍毕竟有限,且读书雷达的容量也有限,难免会有遗珠之叹,这也是一件无可奈何之事。然而,我们欢迎更多的批评与建议,或许你推荐的好书恰恰是我们遗忘的,又或者激起了我们的共识,那么我们也会毫不吝啬地更新读书雷达,以求做到我们能力范围内的尽善尽美。当然,正如你看到的,我们在Methodolog(方法学)维度,更多地推荐了与敏捷和精益相关的书籍。这是因为我们对敏捷技能与方法更有心得和信心。对于我和刘龙军还不够擅长的领域,我们不敢也不愿做出可能误导人的推荐。\\n\\n本读书雷达虽然冠以ThoughtWorks(中国)之名,但选书的倾向与意见并非ThoughtWorks(中国)官方的态度,而是作为制作者(龙军和我)个人表达的意见而已。如果你也加入了ThoughtWorks,你一定会发现这种自发组织的民间活动,在ThoughtWorks可谓俯拾皆是,不用诧异。\\n\\n我将在后面的文章中,根据每个维度为每一本书给出我们的推荐理由。此时,书籍成为了我们的臣民,我想对这个王国做一次不吝惜赞赏的巡视。最后,列出读书雷达的中英文书单,如下所示(感谢凯峰为这个雷达图建立的豆列,可以访问[ThoughtWorks(中国)程序员读书雷达](http://book.douban.com/doulist/2012097/)):\\n\\n**Coding Practice | 编程实践**\\n\\n基础篇\\n\\n>Clean Code《代码整洁之道》\\n\\n>Pragmatic Unit Testing《单元测试之道》\\n\\n>The Productive Programmer《卓有成效的程序员》\\n\\n>Test-Driven Development By Example《测试驱动开发》\\n\\n>Clean Coder《程序员的职业修养》\\n\\n>The Art of Readable Code《编写可读代码的艺术》\\n\\n进阶篇\\n\\n>Refactoring To Patterns《重构与模式》\\n\\n>Implementation Patterns《实现模式》\\n\\n>Code Completed《代码大全》\\n\\n>The Pragmatic Programmer《程序员修炼之道》\\n\\n高级篇\\n\\n>Structure and Interpretation of Computer Programs《计算机程序的构造和解释》\\n\\n>Working Effectively with Legacy Code《修改代码的艺术》\\n\\n**Architecture & Design | 架构与设计**\\n\\n基础篇\\n\\n>Agile Software Development 《敏捷软件开发:原则、实践与模式》\\n\\n>Head First Design Patterns《深入浅出设计模式》\\n\\n>Design Patterns 《设计模式》\\n\\n进阶篇\\n\\n>The Art of UNIX Programming 《Unix编程艺术》\\n\\n>Practical API Design 《框架设计的艺术》\\n\\n>Domain Specific Languages 《领域特定语言》\\n\\n>Patterns of Enterprise Application Architecture 《企业应用架构模式》\\n\\n高级篇\\n\\n>Release It \\n\\n>Domain-Driven Design 《领域驱动设计》\\n\\n>Enterprise Integration Patterns《企业集成模式》\\n\\n>Beautiful Architecture《架构之美》\\n\\n>Pattern-Oriented Software Architecture《面向模式的软件架构》\\n\\n**Methodology | 方法学**\\n\\n基础篇\\n\\n>User Stories Applied《用户故事与敏捷方法》\\n\\n>The Gold Mine《金矿》\\n\\n>Scrum and XP From the Trenches《硝烟中的Scrum和XP》\\n\\n>Continuous Integration《持续集成》\\n\\n>Extreme Programming Explained《解析极限编程》\\n\\n进阶篇\\n\\n>Lean Thinking《精益思想》\\n\\n>Continuous Delivery《持续交付》\\n\\n>How Google Tests Software\\n\\n>Agile Testing\\n\\n>Extreme Programming Refactored《重构极限编程》\\n\\n高级篇\\n\\n>Specification By Example\\n\\n**Thought & Leadership | 思想与领导力**\\n\\n基础篇\\n\\n>The Effective Executive《卓有成效的管理者》\\n\\n>Are Your Lights On?《你的灯亮着吗》\\n\\n>Becoming A Technical Leader《成为技术领导者》\\n\\n进阶篇\\n\\n>The Fifth Discipline《第五项修炼》\\n\\n>The Design Of Business\\n\\n>Management 3.0《管理3.0:培养和提升敏捷领导力》\\n\\n>Presentation To Win\\n\\n>The McKinsey Way《麦肯锡方法》\\n\\n高级篇\\n\\n>Thinking, Fast and Slow《思考快与慢》\",\"mtime\":1444880072000,\"source\":\"source/_posts/thoughtworks(中国)-程序员读书雷达.md\"},\"xq244zu2ico75egd\":{\"_id\":\"xq244zu2ico75egd\",\"content\":\"title: 'ThreadLocal 那点事儿'\\ndate: 2014-05-14 14:51:37\\n\\n---\\nThreadLocal,直译为“线程本地”或“本地线程”,如果你真的这么认为,那就错了!其实,它就是一个容器,用于存放线程的局部变量,我认为应该叫做 ThreadLocalVariable(线程局部变量)才对,真不理解为什么当初 Sun 公司的工程师这样命名。\\n\\n
\\n\\n早在 JDK 1.2 的时代,java.lang.ThreadLocal 就诞生了,它是为了解决多线程并发问题而设计的,只不过设计得有些难用,所以至今没有得到广泛使用。其实它还是挺有用的,不相信的话,我们一起来看看这个例子吧。\\n\\n一个序列号生成器的程序,可能同时会有多个线程并发访问它,要保证每个线程得到的序列号都是自增的,而不能相互干扰。\\n\\n先定义一个接口:\\n\\n\\tpublic interface Sequence {\\n\\t\\n\\t int getNumber();\\n\\t}\\n\\n每次调用 getNumber() 方法可获取一个序列号,下次再调用时,序列号会自增。\\n\\n再做一个线程类:\\n\\n\\tpublic class ClientThread extends Thread {\\n\\t\\n\\t private Sequence sequence;\\n\\t\\n\\t public ClientThread(Sequence sequence) {\\n\\t this.sequence = sequence;\\n\\t }\\n\\t\\n\\t @Override\\n\\t public void run() {\\n\\t\\t\\tfor (int i = 0; i < 3; i++) {\\n\\t\\t\\t\\tSystem.out.println(Thread.currentThread().getName() + \\\" => \\\" + sequence.getNumber());\\n\\t\\t\\t}\\n\\t }\\n\\t}\\n\\n在线程中连续输出三次线程名与其对应的序列号。\\n\\n我们先不用 ThreadLocal,来做一个实现类吧。\\n\\n\\tpublic class SequenceA implements Sequence {\\n\\t\\n\\t private static int number = 0;\\n\\t\\n\\t public int getNumber() {\\n\\t number = number + 1;\\n\\t return number;\\n\\t }\\n\\t\\n\\t public static void main(String[] args) {\\n\\t Sequence sequence = new SequenceA();\\n\\t\\n\\t ClientThread thread1 = new ClientThread(sequence);\\n\\t ClientThread thread2 = new ClientThread(sequence);\\n\\t ClientThread thread3 = new ClientThread(sequence);\\n\\t\\n\\t thread1.start();\\n\\t thread2.start();\\n\\t thread3.start();\\n\\t }\\n\\t}\\n\\n序列号初始值是0,在 main() 方法中模拟了三个线程,运行后结果如下:\\n\\n\\tThread-0 => 1\\n\\tThread-0 => 2\\n\\tThread-0 => 3\\n\\tThread-2 => 4\\n\\tThread-2 => 5\\n\\tThread-2 => 6\\n\\tThread-1 => 7\\n\\tThread-1 => 8\\n\\tThread-1 => 9\\n\\n由于线程启动顺序是随机的,所以并不是0、1、2这样的顺序,这个好理解。为什么当 Thread-0 输出了1、2、3之后,而 Thread-2 却输出了4、5、6呢?线程之间竟然共享了 static 变量!这就是所谓的“非线程安全”问题了。\\n\\n那么如何来保证“线程安全”呢?对应于这个案例,就是说不同的线程可拥有自己的 static 变量,如何实现呢?下面看看另外一个实现吧。\\n\\n\\tpublic class SequenceB implements Sequence {\\n\\t\\n\\t\\tprivate static ThreadLocal
numberContainer = new ThreadLocal
() {\\n\\t\\t\\t@Override\\n\\t\\t\\tprotected Integer initialValue() {\\n\\t\\t\\t\\treturn 0;\\n\\t\\t\\t}\\n\\t\\t};\\n\\t\\n\\tpublic int getNumber() {\\n\\t\\t numberContainer.set(numberContainer.get() + 1);\\n\\t\\t return numberContainer.get();\\n\\t }\\n\\t\\n\\t public static void main(String[] args) {\\n\\t\\t Sequence sequence = new SequenceB();\\n\\t\\n\\t\\t ClientThread thread1 = new ClientThread(sequence);\\n\\t \\t ClientThread thread2 = new ClientThread(sequence);\\n\\t\\t ClientThread thread3 = new ClientThread(sequence);\\n\\t\\n\\t\\t thread1.start();\\n\\t\\t thread2.start();\\n\\t\\t thread3.start();\\n\\t\\t}\\n\\t}\\n\\n通过 ThreadLocal 封装了一个 Integer 类型的 numberContainer 静态成员变量,并且初始值是0。再看 getNumber() 方法,首先从 numberContainer 中 get 出当前的值,加1,随后 set 到 numberContainer 中,最后将 numberContainer 中 get 出当前的值并返回。\\n\\n是不是很恶心?但是很强大!确实稍微饶了一下,我们不妨把 ThreadLocal 看成是一个容器,这样理解就简单了。所以,这里故意用 Container 这个单词作为后缀来命名 ThreadLocal 变量。\\n\\n运行结果如何呢?看看吧。\\n\\n\\tThread-0 => 1\\n\\tThread-0 => 2\\n\\tThread-0 => 3\\n\\tThread-2 => 1\\n\\tThread-2 => 2\\n\\tThread-2 => 3\\n\\tThread-1 => 1\\n\\tThread-1 => 2\\n\\tThread-1 => 3\\n\\n每个线程相互独立了,同样是 static 变量,对于不同的线程而言,它没有被共享,而是每个线程各一份,这样也就保证了线程安全。 也就是说,TheadLocal 为每一个线程提供了一个独立的副本!\\n\\n搞清楚 ThreadLocal 的原理之后,有必要总结一下 ThreadLocal 的 API,其实很简单。\\n\\n1. public void set(T value):将值放入线程局部变量中\\n2. public T get():从线程局部变量中获取值\\n3. public void remove():从线程局部变量中移除值(有助于 JVM 垃圾回收)\\n4. protected T initialValue():返回线程局部变量中的初始值(默认为 null) \\n\\n为什么 initialValue() 方法是 protected 的呢?就是为了提醒程序员们,这个方法是要你们来实现的,请给这个线程局部变量一个初始值吧。\\n\\n了解了原理与这些 API,其实想想 ThreadLocal 里面不就是封装了一个 Map 吗?自己都可以写一个 ThreadLocal 了,尝试一下吧。\\n\\n\\tpublic class MyThreadLocal
{\\n\\t\\n\\t private Map
container = Collections.synchronizedMap(new HashMap
());\\n\\t\\n\\t public void set(T value) {\\n\\t container.put(Thread.currentThread(), value);\\n\\t }\\n\\t\\n\\t public T get() {\\n\\t Thread thread = Thread.currentThread();\\n\\t T value = container.get(thread);\\n\\t if (value == null && !container.containsKey(thread)) {\\n\\t value = initialValue();\\n\\t container.put(thread, value);\\n\\t }\\n\\t return value;\\n\\t }\\n\\t\\n\\t public void remove() {\\n\\t container.remove(Thread.currentThread());\\n\\t }\\n\\t\\n\\t protected T initialValue() {\\n\\t return null;\\n\\t }\\n\\t}\\n\\n以上完全山寨了一个 ThreadLocal,其中中定义了一个同步 Map(为什么要这样?请读者自行思考),代码应该非常容易读懂。\\n\\n下面用这 MyThreadLocal 再来实现一把看看。\\n\\n\\tpublic class SequenceC implements Sequence {\\n\\t\\n\\t private static MyThreadLocal
numberContainer = new MyThreadLocal
() {\\n\\t @Override\\n\\t protected Integer initialValue() {\\n\\t return 0;\\n\\t }\\n\\t };\\n\\t\\n\\t public int getNumber() {\\n\\t numberContainer.set(numberContainer.get() + 1);\\n\\t return numberContainer.get();\\n\\t }\\n\\t\\n\\t public static void main(String[] args) {\\n\\t Sequence sequence = new SequenceC();\\n\\t\\n\\t ClientThread thread1 = new ClientThread(sequence);\\n\\t ClientThread thread2 = new ClientThread(sequence);\\n\\t ClientThread thread3 = new ClientThread(sequence);\\n\\t\\n\\t thread1.start();\\n\\t thread2.start();\\n\\t thread3.start();\\n\\t }\\n\\t}\\n\\n以上代码其实就是将 ThreadLocal 替换成了 MyThreadLocal,仅此而已,运行效果和之前的一样,也是正确的。\\n\\n其实 ThreadLocal 可以单独成为一种设计模式,就看你怎么看了。 \\n\\nThreadLocal 具体有哪些使用案例呢?\\n\\n我想首先要说的就是:通过 ThreadLocal 存放 JDBC Connection,以达到事务控制的能力。\\n\\n如何实现呢?下回分解!\\n\\nThreadLocal 那点事儿(续集)\\n\\n注意:当您在一个类中使用了 static 成员变量的时候,一定要多问问自己,这个 static 成员变量需要考虑“线程安全”吗?(也就是说,多个线程需要独享自己的 static 成员变量吗?)如果需要考虑,那就请用 ThreadLocal 吧!\\n\\n***\\n\\n本篇是《ThreadLocal 那点事儿》的续集,如果您没看上一篇,就就有点亏了。如果您错过了这一篇,那亏得就更大了。\\n\\n还是保持我一贯的 Style,用一个 Demo 来说话吧。用户提出一个需求:当修改产品价格的时候,需要记录操作日志,什么时候做了什么事情。\\n\\n想必这个案例,只要是做过应用系统的小伙伴们,都应该遇到过吧?无外乎数据库里就两张表:product 与 log,用两条 SQL 语句应该可以解决问题:\\n\\n\\tupdate product set price = ? where id = ?\\n\\tinsert into log (created, description) values (?, ?)\\n\\nBut!要确保这两条 SQL 语句必须在同一个事务里进行提交,否则有可能 update 提交了,但 insert 却没有提交。如果这样的事情真的发生了,我们肯定会被用户指着鼻子狂骂:“为什么产品价格改了,却看不到什么时候改的呢?”。\\n\\n聪明的我在接到这个需求以后,是这样做的:\\n\\n首先,我写一个 DBUtil 的工具类,封装了数据库的常用操作: \\n\\n\\tpublic class DBUtil {\\n\\t // 数据库配置\\n\\t private static final String driver = \\\"com.mysql.jdbc.Driver\\\";\\n\\t private static final String url = \\\"jdbc:mysql://localhost:3306/demo\\\";\\n\\t private static final String username = \\\"root\\\";\\n\\t private static final String password = \\\"root\\\";\\n\\t\\n\\t // 定义一个数据库连接\\n\\t private static Connection conn = null;\\n\\t\\n\\t // 获取连接\\n\\t public static Connection getConnection() {\\n\\t try {\\n\\t Class.forName(driver);\\n\\t conn = DriverManager.getConnection(url, username, password);\\n\\t } catch (Exception e) {\\n\\t e.printStackTrace();\\n\\t }\\n\\t return conn;\\n\\t }\\n\\t\\n\\t // 关闭连接\\n\\t public static void closeConnection() {\\n\\t try {\\n\\t if (conn != null) {\\n\\t conn.close();\\n\\t }\\n\\t } catch (Exception e) {\\n\\t e.printStackTrace();\\n\\t }\\n\\t }\\n\\t}\\n\\n里面搞了一个 static 的 Connection,这下子数据库连接就好操作了,牛逼吧!\\n\\n然后,我定义了一个接口,用于给逻辑层来调用:\\n\\n\\tpublic interface ProductService {\\n\\t\\n\\t void updateProductPrice(long productId, int price);\\n\\t}\\n\\n根据用户提出的需求,我想这个接口完全够用了。根据 productId 去更新对应 Product 的 price,然后再插入一条数据到 log 表中。\\n\\n其实业务逻辑也不太复杂,于是我快速地完成了 ProductService 接口的实现类:\\n\\n\\tpublic class ProductServiceImpl implements ProductService {\\n\\t\\n\\t private static final String UPDATE_PRODUCT_SQL = \\\"update product set price = ? where id = ?\\\";\\n\\t private static final String INSERT_LOG_SQL = \\\"insert into log (created, description) values (?, ?)\\\";\\n\\t\\n\\t public void updateProductPrice(long productId, int price) {\\n\\t try {\\n\\t // 获取连接\\n\\t Connection conn = DBUtil.getConnection();\\n\\t conn.setAutoCommit(false); // 关闭自动提交事务(开启事务)\\n\\t\\n\\t // 执行操作\\n\\t updateProduct(conn, UPDATE_PRODUCT_SQL, productId, price); // 更新产品\\n\\t insertLog(conn, INSERT_LOG_SQL, \\\"Create product.\\\"); // 插入日志\\n\\t\\n\\t // 提交事务\\n\\t conn.commit();\\n\\t } catch (Exception e) {\\n\\t e.printStackTrace();\\n\\t } finally {\\n\\t // 关闭连接\\n\\t DBUtil.closeConnection();\\n\\t }\\n\\t }\\n\\t\\n\\t private void updateProduct(Connection conn, String updateProductSQL, long productId, int productPrice) throws Exception {\\n\\t PreparedStatement pstmt = conn.prepareStatement(updateProductSQL);\\n\\t pstmt.setInt(1, productPrice);\\n\\t pstmt.setLong(2, productId);\\n\\t int rows = pstmt.executeUpdate();\\n\\t if (rows != 0) {\\n\\t System.out.println(\\\"Update product success!\\\");\\n\\t }\\n\\t }\\n\\t\\n\\t private void insertLog(Connection conn, String insertLogSQL, String logDescription) throws Exception {\\n\\t PreparedStatement pstmt = conn.prepareStatement(insertLogSQL);\\n\\t pstmt.setString(1, new SimpleDateFormat(\\\"yyyy-MM-dd HH:mm:ss SSS\\\").format(new Date()));\\n\\t pstmt.setString(2, logDescription);\\n\\t int rows = pstmt.executeUpdate();\\n\\t if (rows != 0) {\\n\\t System.out.println(\\\"Insert log success!\\\");\\n\\t }\\n\\t }\\n\\t}\\n\\n代码的可读性还算不错吧?这里我用到了 JDBC 的高级特性 Transaction 了。暗自庆幸了一番之后,我想是不是有必要写一个客户端,来测试一下执行结果是不是我想要的呢? 于是我偷懒,直接在 ProductServiceImpl 中增加了一个 main() 方法:\\n\\n\\tpublic static void main(String[] args) {\\n\\t ProductService productService = new ProductServiceImpl();\\n\\t productService.updateProductPrice(1, 3000);\\n\\t}\\n\\n我想让 productId 为 1 的产品的价格修改为 3000。于是我把程序跑了一遍,控制台输出:\\n\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\n应该是对了。作为一名专业的程序员,为了万无一失,我一定要到数据库里在看看。没错!product 表对应的记录更新了,log 表也插入了一条记录。这样就可以将 ProductService 接口交付给别人来调用了。\\n\\n几个小时过去了,QA 妹妹开始骂我:“我靠!我才模拟了 10 个请求,你这个接口怎么就挂了?说是数据库连接关闭了!”。\\n\\n听到这样的叫声,让我浑身打颤,立马中断了我的小视频,赶紧打开 IDE,找到了这个 ProductServiceImpl 这个实现类。好像没有 Bug 吧?但我现在不敢给她任何回应,我确实有点怕她的。\\n\\n我突然想起,她是用工具模拟的,也就是模拟多个线程了!那我自己也可以模拟啊,于是我写了一个线程类:\\n\\n\\tpublic class ClientThread extends Thread {\\n\\t\\n\\t private ProductService productService;\\n\\t\\n\\t public ClientThread(ProductService productService) {\\n\\t this.productService = productService;\\n\\t }\\n\\t\\n\\t @Override\\n\\t public void run() {\\n\\t System.out.println(Thread.currentThread().getName());\\n\\t productService.updateProductPrice(1, 3000);\\n\\t }\\n\\t}\\n\\n我用这线程去调用 ProduceService 的方法,看看是不是有问题。此时,我还要再修改一下 main() 方法:\\n\\n\\t// public static void main(String[] args) {\\n\\t// ProductService productService = new ProductServiceImpl();\\n\\t// productService.updateProductPrice(1, 3000);\\n\\t// }\\n\\t \\n\\tpublic static void main(String[] args) {\\n\\t for (int i = 0; i < 10; i++) {\\n\\t ProductService productService = new ProductServiceImpl();\\n\\t ClientThread thread = new ClientThread(productService);\\n\\t thread.start();\\n\\t }\\n\\t}\\n\\n我也模拟 10 个线程吧,我就不信那个邪了!\\n\\n运行结果真的让我很晕、很晕:\\n\\n\\tThread-1\\n\\tThread-3\\n\\tThread-5\\n\\tThread-7\\n\\tThread-9\\n\\tThread-0\\n\\tThread-2\\n\\tThread-4\\n\\tThread-6\\n\\tThread-8\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tcom.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.\\n\\tat sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)\\n\\tat sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)\\n\\tat sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)\\n\\tat java.lang.reflect.Constructor.newInstance(Constructor.java:513)\\n\\tat com.mysql.jdbc.Util.handleNewInstance(Util.java:411)\\n\\tat com.mysql.jdbc.Util.getInstance(Util.java:386)\\n\\tat com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015)\\n\\tat com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)\\n\\tat com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975)\\n\\tat com.mysql.jdbc.SQLError.createSQLException(SQLError.java:920)\\n\\tat com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1304)\\n\\tat com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1296)\\n\\tat com.mysql.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1699)\\n\\tat com.smart.sample.test.transaction.solution1.ProductServiceImpl.updateProductPrice(ProductServiceImpl.java:25)\\n\\tat com.smart.sample.test.transaction.ClientThread.run(ClientThread.java:18)\\n\\n我靠!竟然在多线程的环境下报错了,果然是数据库连接关闭了。怎么回事呢?我陷入了沉思中。于是我 Copy 了一把那句报错信息,在百度、Google,还有 OSC 里都找了,解答实在是千奇百怪。\\n\\n我突然想起,既然是跟 Connection 有关系,那我就将主要精力放在检查 Connection 相关的代码上吧。是不是 Connection 不应该是 static 的呢?我当初设计成 static 的主要是为了让 DBUtil 的 static 方法访问起来更加方便,用 static 变量来存放 Connection 也提高了性能啊。怎么搞呢?\\n\\n于是我看到了 OSC 上非常火爆的一篇文章《ThreadLocal 那点事儿》,终于才让我明白了!原来要使每个线程都拥有自己的连接,而不是共享同一个连接,否则线程1有可能会关闭线程2的连接,所以线程2就报错了。一定是这样!\\n\\n我赶紧将 DBUtil 给重构了:\\n\\n\\tpublic class DBUtil {\\n\\t // 数据库配置\\n\\t private static final String driver = \\\"com.mysql.jdbc.Driver\\\";\\n\\t private static final String url = \\\"jdbc:mysql://localhost:3306/demo\\\";\\n\\t private static final String username = \\\"root\\\";\\n\\t private static final String password = \\\"root\\\";\\n\\t\\n\\t // 定义一个用于放置数据库连接的局部线程变量(使每个线程都拥有自己的连接)\\n\\t private static ThreadLocal
connContainer = new ThreadLocal
();\\n\\t\\n\\t // 获取连接\\n\\t public static Connection getConnection() {\\n\\t Connection conn = connContainer.get();\\n\\t try {\\n\\t if (conn == null) {\\n\\t Class.forName(driver);\\n\\t conn = DriverManager.getConnection(url, username, password);\\n\\t }\\n\\t } catch (Exception e) {\\n\\t e.printStackTrace();\\n\\t } finally {\\n\\t connContainer.set(conn);\\n\\t }\\n\\t return conn;\\n\\t }\\n\\t\\n\\t // 关闭连接\\n\\t public static void closeConnection() {\\n\\t Connection conn = connContainer.get();\\n\\t try {\\n\\t if (conn != null) {\\n\\t conn.close();\\n\\t }\\n\\t } catch (Exception e) {\\n\\t e.printStackTrace();\\n\\t } finally {\\n\\t connContainer.remove();\\n\\t }\\n\\t }\\n\\t}\\n\\n我把 Connection 放到了 ThreadLocal 中,这样每个线程之间就隔离了,不会相互干扰了。\\n\\n此外,在 getConnection() 方法中,首先从 ThreadLocal 中(也就是 connContainer 中) 获取 Connection,如果没有,就通过 JDBC 来创建连接,最后再把创建好的连接放入这个 ThreadLocal 中。可以把 ThreadLocal 看做是一个容器,一点不假。\\n\\n同样,我也对 closeConnection() 方法做了重构,先从容器中获取 Connection,拿到了就 close 掉,最后从容器中将其 remove 掉,以保持容器的清洁。\\n\\n这下应该行了吧?我再次运行 main() 方法:\\n\\n\\tThread-0\\n\\tThread-2\\n\\tThread-4\\n\\tThread-6\\n\\tThread-8\\n\\tThread-1\\n\\tThread-3\\n\\tThread-5\\n\\tThread-7\\n\\tThread-9\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\tUpdate product success!\\n\\tInsert log success!\\n\\n我去!总算是解决了,QA 妹妹,你应该会对我微笑一下吧?\\n\\n感谢您的关注,分享是一种快乐,也希望得到您的支持与批评!\\n\\n注意:该示例仅用于说明 TheadLocal 的基本用法。在实际工作中,推荐使用连接池来管理数据库连接。示例中的代码仅作参考,使用前请酌情考虑。\",\"mtime\":1444880072000,\"source\":\"source/_posts/threadlocal-那点事儿.md\"},\"gg84wl1lc0qd5c1k\":{\"_id\":\"gg84wl1lc0qd5c1k\",\"content\":\"title: 一种在图片里隐藏你的程序代码的技术\\ndate: 2014-05-14 13:00:19\\n\\n---\\n我最近开发了我的第一个网页游戏:[一个HTML5的视频智力游戏](http://www.patrick-wied.at/projects/html5-video-puzzle)。 开发的过程很有趣,我喜欢编程,但当实现了游戏逻辑后,我有了一个有趣的想法:为什么不想个办法把代码隐藏起来?起初我想到的是一些很简单的做法,比如禁 止上下文菜单,以防右键点击时可以查看页面源代码。但这毫无意义,右键菜单不能用,人们仍然可以通过键盘快捷键或菜单栏里的“查看源文件”来观看源代码。\\n\\n
\\n\\n**一张图片能隐含千言万语**。\\n\\n这依赖于图片的体积。但我决定要把源代码加密存放到一张图片里。HTML5的画布(canvas)组件很适合干这种事情,因为它支持针对图像像素的 操作。一个像素由四个值(通道)来表示:红,绿,蓝和alpha通道。它们的值的分布范围是从0到255。我的Javascript代码就是一个个的字 符,每个字符都有一个ASCII对应值。ASCII值的范围也是0-255,所以,我想做的是,遍历画布上的每个像素,给每个像素设置3个代码字符的 ASCII值作为它的RGB值,你可以通过charCodeAt函数轻松的取出这些字符。\\n\\n>.charCodeAt(0)\\n\\n生成的是一张色彩斑斓、很小的图片,它就是我的程序代码 。看看吧:\\n\\n\\n解码的时候,我只需要把这个图片画到画布上,遍历像素点,取出r,g,b值所代表的字符:\\n\\n>String.fromCharCode(code)\\n\\n把它们连接成一个大的字符串,这就是你的代码了——可执行的代码。 \\n\\n这样就能保护你的源代码了吗?\\n\\n其实不能——一个有经验的(甚至没有经验的)程序员仍然能够知道如何去解码图片,取出里面的代码,但我想这是能防止那些怀着不良商业目的人偷盗你的的代码的第一步措施——而那些能够想出如何解码的程序员(大部分)都不是来剽窃的 \\n\\n**这种方法的主要缺陷**\\n\\n这种技术只能应用在支持HTML5画布(canvas)技术的现代浏览器里,IE6、IE8 里肯定是不行的。甚至有些现代的浏览器对于图片的alpha通道的编码也有支持问题,所以,每个像素点你只能放3个字符——一个100×100大小的图片 可以存放3万个文本字符。 \\n\\n你还有其它简单的能防止别人拷贝你的代码的方法吗?我们当然可以把字符进行加密,但如何保证你的解密步骤能不被人轻易的破解呢?告诉我你的想法吧!\\n\\n[本文英文原文链接:[A technique for hiding your JavaScript Code](http://www.patrick-wied.at/blog/a-technique-for-hiding-your-javascript-code) ]\",\"mtime\":1444880072000,\"source\":\"source/_posts/一种在图片里隐藏你的程序代码的技术.md\"},\"rmatpsj5rvfj42g7\":{\"_id\":\"rmatpsj5rvfj42g7\",\"content\":\"title: 如何使用MacVim\\ndate: 2014-05-25 01:21:00\\n\\n---\\n命令模式 按下ESC 高效率移动\\n===\\n\\n1、在插入模式之外\\n---\\n\\n基本上来说,你应该尽可能少的呆在插入模式里面,因为在插入模式里面 VIM 就像一个“哑巴”编辑器一样。很多新手都会一直呆在插入模式里面,因为这样易于使用。但 VIM 的强大之处在于他的命令行模式!你会发现,在你越来越了解 VIM 之后,你就会花越来越少的时间使用插入模式了。\\n\\n
\\n\\n2、使用 h、j、k、l\\n---\\n\\n使用 VIM 高效率编辑的第一步,就是放弃使用箭头键。使用 VIM,你就不用频繁的在箭头键和字母键之间移来移去了,这会节省你很多时间。当你在命令模式时,你可以用 h、j、k、l 来分别实现左、下、上、右箭头的功能。一开始可能需要适应一下,但一旦习惯这种方式,你就会发现这样操作的高效之处了。\\n\\n在你编辑你的电子邮件或者其他有段落的文本时,你可能会发现使用方向键和你预期的效果不一样,有时候可能会一次跳过了很多行。这是因为你的段落在 VIM 看来是一个大的长长的行。这时你可以在按 h、j、k 或者 l 之前键入一个 g,这样 VIM 就会按屏幕上面的行如你所愿的移动了。\\n\\n3、在当前行里面有效的移动光标\\n---\\n\\n很多编辑器只提供了简单的命令来控制光标的移动(比如左、上、右、下、到行首/尾等)。VIM 则提供了很多强大的命令来满足你控制光标的欲望。当光标从一点移动到另外一点,在这两点之间的文本(包括这两个点)称作被“跨过”,这里的命令也被称作是 motion。(简单说明一下,后面会用到这个重要的概念)\\n\\n4、常用到的一些命令(motion):\\n---\\n\\nfx:移动光标到当前行的下一个 x 处。很明显,x 可以是任意一个字母,而且你可以使用 ; 来重复你的上一个 f 命令。\\n\\ntx:和上面的命令类似,但是是移动到 x 的左边一个位置。(这真的很有用)\\n\\nFx:和 fx 类似,不过是往回找。\\n\\nw:光标往前移动一个词。\\n\\nb:光标往后移动一个词。\\n\\n0:移动光标到当前行首。\\n\\n^:移动光标到当前行的第一个字母位置。\\n\\n$:移动光标到行尾。\\n\\n):移动光标到下一个句子。\\n\\n( :移动光标到上一个句子。\\n\\n5、在整个文件里面有效移动光标\\n---\\n\\nVIM 有很多命令,可以用来到达文件里面你想到达的地方。下面是一些在文件里面移动的命令:\\n\\n
:向下移动一屏。\\n\\n
:向上移动一屏。\\n\\nG:到文件尾\\n\\nnumG:移动光标到指定的行(num)。(比如 10G 就是到第 10 行)\\n\\ngg:到文件首\\n\\nH:移动光标到屏幕上面\\n\\nM:移动光标到屏幕中间\\n\\nL:移动光标到屏幕下面\\n\\n*:读取光标处的字符串,并且移动光标到它再次出现的地方。\\n\\n\\\\#:和上面的类似,但是是往反方向寻找。\\n\\n/text:从当前光标处开始搜索字符串 text,并且到达 text 出现的地方。必须使用回车来开始这个搜索命令。如果想重复上次的搜索的话,按 n。\\n\\n?text:和上面类似,但是是反方向。\\n\\nma:在当前光标的位置标记一个书签,名字为 a。书签名只能是小写字母。你看不见书签的存在,但它确实已经在那里了。\\n\\n`a:到书签 a 处。注意这个不是单引号,它一般位于大部分键盘的 1 的左边。\\n\\n`.:到你上次编辑文件的地方。这个命令很有用,而且你不用自己去标记它。\\n\\n高效的输入 \\n===\\n\\n1、使用关键词自动完成\\n---\\n\\nVIM 有一个非常漂亮的关键词自动完成系统。这表示,你可以输入一个长词的一部分,然后按一下某个键,然后 VIM 就替你完成了这个长词的输入了。举个例子:你有一个变量名为 iAmALongAndAwkwardVarName 在你写的代码的某个地方。也许你不想每回都自己一个一个字母的去输入它。\\n\\n使用关键词自动完成功能,你只需要输入开始几个字母(比如 iAmAL),然后按
(按住 Ctrl,再按 N)或者
。如果 VIM 没有给出你想要的词,继续按,直到你满意为止,VIM 会一直循环它找到的匹配的字符串。\\n\\n2、聪明的进入插入模式\\n---\\n\\n很多新手进入插入模式都只是用 i。这样当然可以进入插入模式,但通常不是那么合适,因为 VIM 提供了很多进入插入模式的命令。下面是最常用的一些:\\n\\ni:在当前字符的左边插入\\n\\nI:在当前行首插入\\n\\na:在当前字符的右边插入\\n\\nA:在当前行尾插入\\n\\no:在当前行下面插入一个新行\\n\\nO:在当前行上面插入一个新行\\n\\nc{motion}:删除 motion 命令跨过的字符,并且进入插入模式。比如:c$,这将会删除从光标位置到行尾的字符并且进入插入模式。ct!,这会删除从光标位置到下一个叹号(但不包括),然后进入插入模式。被删除的字符被存在了剪贴板里面,并且可以再粘贴出来。\\n\\nd{motion}:和上面差不多,但是不进入插入模式。\\n\\n3、有效的移动大段的文本\\n---\\n\\n使用可视选择(visual selections)和合适的选择模式\\n\\n不像最初的 VI,VIM 允许你高亮(选择)一些文本,并且进行操作。这里有三种可视选择模式:\\n\\nv:按字符选择。经常使用的模式,所以亲自尝试一下它。\\n\\nV:按行选择。这在你想拷贝或者移动很多行的文本的时候特别有用。\\n\\n
:按块选择。非常强大,只在很少的编辑器中才有这样的功能。你可以选择一个矩形块,并且在这个矩形里面的文本会被高亮。\\n\\n在选择模式的时候使用上面所述的方向键和命令(motion)。比如,vwww,会高亮光标前面的三个词。Vjj 将会高亮当前行以及下面两行。\\n\\n4、在可视选择模式下剪切和拷贝\\n---\\n\\n一旦你高亮了选区,你或许想进行一些操作:\\n\\nd:剪贴选择的内容到剪贴板。\\n\\ny:拷贝选择的内容到剪贴板。\\n\\nc:剪贴选择的内容到剪贴板并且进入插入模式。\\n\\n在非可视选择模式下剪切和拷贝\\n\\n如果你很清楚的知道你想拷贝或者剪切什么,那你根本就不需要进入可视选择模式。这样也会节省时间:\\n\\nd{motion}:剪切 motion 命令跨过的字符到剪贴板。比如,dw 会剪切一个词而 dfS 会将从当前光标到下一个 S 之间的字符剪切至剪贴板。\\n\\ny{motion}:和上面类似,不过是拷贝。\\n\\nc{motion}:和 d{motion} 类似,不过最后进入插入模式。\\n\\ndd:剪切当前行。\\n\\nyy:拷贝当前行。\\n\\ncc:剪切当前行并且进入插入模式。\\n\\nD:剪切从光标位置到行尾到剪贴板。\\n\\nY:拷贝当前行。\\n\\nC:和 D 类似,最后进入插入模式。\\n\\nx:剪切当前字符到剪贴板。\\n\\ns:和x类似,不过最后进入插入模式。\\n\\n5、粘贴\\n---\\n\\n粘贴很简单,按 p。\\n\\n6、使用多重剪贴板\\n---\\n\\n很多编辑器都只提供了一个剪贴板。VIM 有很多。剪贴板在 VIM 里面被称为寄存器(Registers)。你可以列出当前定义的所有寄存器名和它们的内容,命令为“:reg”。最好使用小写字母来作为寄存器的名称,因为大写的有些被 VIM 占用了。\\n\\n使用寄存器的命令为双引号 “。\\n\\n比如:我们要拷贝当前行到寄存器 k。你应该按 “kyy。(你也可以使用 V”ky。为什么这样也可以呢?)现在当前行应该已经存在了寄存器 k 里面直到你又拷贝了一些东西进入寄存器 k。现在你可以使用命令 “kp 来粘贴寄存器 k 里面的内容到你想要的位置。\\n\\n7、避免重复\\n---\\n\\n令人惊奇的 . 命令\\n\\n在 VI 里面,输入 . (小数点符号),将会重复你输入的上一个命令。比如,你上个命令为“dw”(删除一个词),VI 将会接着再删除一个词。\\n\\n8、使用数字\\n---\\n\\n使用数字也是 VIM 强大的而且很节省时间的重要特性之一。在很多 VIM 的命令之前都可以使用一个数字,这个数字将会告诉 VIM 这个命令需要执行几次。比如:\\n\\n3j 将会把光标向下移动三行。\\n\\n10dd 将会删除十行。\\n\\ny3″ 将会拷贝从当前光标到第三个出现的引号之间的内容到剪贴板。\\n\\n数字是扩展 motion 命令作用域非常有效的方法。\",\"mtime\":1444880072000,\"source\":\"source/_posts/如何使用macvim.md\"},\"p5zjrxqxy3vh18yt\":{\"_id\":\"p5zjrxqxy3vh18yt\",\"content\":\"title: 代码审查最佳实践\\ndate: 2014-05-14 11:59:45\\n\\n---\\n\\n\\n代码审查可以帮助提高代码质量,避免由于代码习惯而造成的 bug。下面列出的这些要点因该可以作为大部分代码审查的指导,如果是 Java 应用的话,这些建议应该被视作最佳实践。\\n\\n文档\\n\\n1. Javadoc 应该在每一个类和方法中添加。\\n2. 如果是修复某个 bug,应该添加 bug ID。\\n3. 走捷径的方法或者复杂的逻辑要有解释。\\n4. 如果代码会被公开,每个文件头都要标注版权信息。\\n5. 复杂的 HTML,JavaScript,CSS 应该包含文档。\\n\\n
\\n\\n功能\\n\\n1. 如果类似的逻辑被使用了多次,应该把它写成一个帮助类,然后在多出调用。\\n2. 鼓励使用 API 而不是重复编写代码解决相同的问题。\\n3. 要强调代码的单元测试。\\n4. 任何新加的代码不应该破坏已有的代码。\\n5. 假如是 Web 应用,JSP 不应该包含 Java 代码。\\n\\n安全\\n\\n1. 任何代码都不能执行用户的输入,除非转义过了。这个常常包含 JavaScript 的 eval 函数和 SQL 语句。\\n2. 禁止那些在短时间内提交非常多请求的 IP。\\n3. 任何类,变量,还有方法都应该有正确的访问域。\\n4. 尽量避免使用 iframe。\\n\\n性能\\n\\n1. 所有数据库和文件操句柄在不需要的时候都应该被关闭。\\n2. SQL 语句的写法会导致性能千差万别。\\n3. 鼓励创建不可变(immutable)的类。\\n4. 类似的逻辑代码,尽量通过 if else 语句来实现更多的重用。\\n5. 尽量避免使用重对象(heavy objects)。\\n6. 如果是 Web 项目,请检查是否使用了合适的图片尺寸,CSS sprites 和浏览器缓存等技术。\\n7. 全局都需要的信息保存在 application context 中。\\n\\n编码习惯\\n\\n1. 没有被使用的变量要删除。\\n2. 针对不同的 Exception 要用不同的 catch 语句,而不是一个 Exception 解决所有问题。\\n3. 针对变量,方法和类要用相同的命名方法。\\n4. 常量应该被写在独立的常量类中。\\n5. 每行代码的尾部不要有多余的空格。\\n6. 对于括号,循环,if语句等等要用统一的格式。\\n7. 每一个单独的方法不应该超过100行。\\n8. 一个单独的语句不应该超过编辑器的可视区域,它可以被拆分成几行。\\n9. 检查 String 对象既不是null也不是空的最好方法是 if(“”.equals(str))\\n10. 假如类有很多成员变量,并且实例化的时候只需要少数变量传入的话,最好使用静态工厂方法,而不是重载构造函数。\\n11. 给方法添加适当的访问控制,而不是所有都是 public。\\n12. 遵守项目中使用的框架的最佳实践建议,例如 Spring,Struts,Hibernate,jQuery。\\n\\n以上的某些注意点可以通过静态代码检查工具完成,例如 CheckStyle,FindBugs 和 JTest。\\n\\n \\n\\n[原文链接](http://javaexperience.com/code-review-checklist/),[OSChina.NET](http://www.oschina.net/news/31722/code-review-best-practices) 编译\",\"mtime\":1444880072000,\"source\":\"source/_posts/代码审查最佳实践.md\"},\"8u8ngcbr6uzfthv7\":{\"_id\":\"8u8ngcbr6uzfthv7\",\"content\":\"title: 图解Git\\ndate: 2014-05-15 11:34:41\\n\\n---\\n再次感谢原著作者和中文翻译者。\\n\\n此页图解git中的最常用命令。如果你稍微理解git的工作原理,这篇文章能够让你理解的更透彻。 如果你想知道这个站点怎样产生,请前往[GitHub repository](http://github.com/MarkLodato/visual-git-guide)。\\n\\n
\\n\\n正文\\n===\\n\\n1. 基本用法\\n2. 约定\\n3. 命令详解\\n\\t1. Diff\\n\\t2. Commit\\n\\t3. Checkout\\n\\t4. Detached HEAD(匿名分支提交)\\n\\t5. Reset\\n\\t6. Merge\\n\\t7. Cherry Pick\\n\\t8. Rebase\\n4. 技术说明\\n\\n基本用法\\n===\\n\\n\\n\\n上面的四条命令在工作目录、暂存目录(也叫做索引)和仓库之间复制文件。\\n\\n* git add files把当前文件放入暂存区域。\\n* git commit给暂存区域生成快照并提交。\\n* git reset -- files用来撤销最后一次git add files,你也可以用git reset撤销所有暂存区域文件。\\n* git checkout -- files把文件从暂存区域复制到工作目录,用来丢弃本地修改。\\n\\n你可以用git reset -p,git checkout -p, orgit add -p进入交互模式。\\n\\n也可以跳过暂存区域直接从仓库取出文件或者直接提交代码。\\n\\n\\n\\n* git commit -a相当于运行git add把所有当前目录下的文件加入暂存区域再运行。git commit.\\n* git commit files进行一次包含最后一次提交加上工作目录中文件快照的提交。并且文件被添加到暂存区域。\\n* git checkout HEAD -- files回滚到复制最后一次提交。\\n\\n约定\\n===\\n\\n后文中以下面的形式使用图片。\\n\\n\\n\\n绿色的5位字符表示提交的ID,分别指向父节点。分支用橘色显示,分别指向特定的提交。当前分支由附在其上的HEAD标识。 这张图片里显示最后5次提交,ed489是最新提交。 master分支指向此次提交,另一个maint分支指向祖父提交节点。\\n\\n命令详解\\n===\\n\\nDiff\\n---\\n\\n有许多种方法查看两次提交之间的变动。下面是一些示例。\\n\\n\\n\\nCommit\\n---\\n\\n提交时,git用暂存区域的文件创建一个新的提交,并把此时的节点设为父节点。然后把当前分支指向新的提交节点。下图中,当前分支是master。 在运行命令之前,master指向ed489,提交后,master指向新的节点f0cec并以ed489作为父节点。\\n\\n\\n\\n即便当前分支是某次提交的祖父节点,git会同样操作。下图中,在master分支的祖父节点maint分支进行一次提交,生成了1800b。 这样,maint分支就不再是master分支的祖父节点。此时,合并 (或者 衍合) 是必须的。\\n\\n\\n\\n如果想更改一次提交,使用git commit --amend。git会使用与当前提交相同的父节点进行一次新提交,旧的提交会被取消。\\n\\n\\n\\n另一个例子是分离HEAD提交,后文讲。\\n\\nCheckout\\n---\\n\\ncheckout命令通常用来从仓库中取出文件,或者在分支中切换。\\n\\ncheckout命令让git把文件复制到工作目录和暂存区域。比如git checkout HEAD~ foo.c把文件从foo.c提交节点HEAD~ (当前提交节点的父节点)复制到工作目录并且生成索引。注意当前分支没有变化。\\n\\n\\n\\n如果没有指定文件名,而是一个本地分支,那么将切换到那个分支去。同时把索引和工作目录切换到那个分支对应的状态。\\n\\n\\n\\n如果既没有指定文件名,也没有指定分支名,而是一个标签、远程分支、SHA-1值或者是像master~3类似的东西,就得到一个匿名分支,称作detached HEAD。 这样可以很方便的在历史版本之间互相切换。但是,这样的提交是完全不同的,详细的在下面。\\n\\n\\n\\n用分离HEAD提交(找不到好的译法)\\n---\\n\\nHEAD是分离的时候, 提交可以正常进行, 但是没有更新已命名的分支. 。(可以看作是匿名分支。)\\n\\n\\n\\n如果此时切换到别的分支,那么所作的工作会全部丢失。注意这个命令之后就不存在2eecb了。\\n\\n\\n\\n如果你想保存当前的状态,可以用这个命令创建一个新的分支:git checkout -b name。\\n\\n\\n\\nReset\\n---\\n\\nreset命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。\\n\\n如果不给选项,那么当前分支指向到那个提交。如果用--hard选项,那么工作目录也更新,如果用--soft选项,那么都不变。\\n\\n\\n\\n如果没有给出提交点的版本号,那么默认用HEAD。这样,分支指向不变,但是索引会回滚到最后一次提交,如果用--hard选项,工作目录也同样。\\n\\n\\n\\n如果给了文件名(或者-p选项), 那么工作效果和带文件名的checkout差不多,除了索引被更新。\\n\\n\\n\\nMerge\\n---\\n\\nmerge 命令把不同分支合并起来。合并前,索引必须和当前提交相同。如果另一个分支是当前提交的祖父节点,那么合并命令将什么也不做。 另一种情况是如果当前提交是另一个分支的祖父节点,就导致fast-forward合并。指向只是简单的移动,并生成一个新的提交。\\n\\n\\n\\n否则就是一次真正的合并。默认把当前提交(ed489 如下所示)和另一个提交(33104)以及他们的共同祖父节点(b325c)进行一次三方合并。结果是先保存当前目录和索引,然后和父节点33104一起做一次新提交。\\n\\n\\n\\nCherry Pick\\n---\\n\\ncherry-pick命令\\\"复制\\\"一个提交节点并在当前复制做一次完全一样的新提交。\\n\\n\\n\\nRebase\\n---\\n\\n衍合是合并命令的另一种选择。合并把两个父分支合并进行一次提交,提交历史不是线性的。衍合在当前分支上重演另一个分支的历史,提交历史是线性的。 本质上,这是线性化的自动的 cherry-pick\\n\\n\\n\\n上面的命令都在topic分支中进行,而不是master分支,在master分支上重演,并且把分支指向新的节点。注意旧提交没有被引用,将被回收。\\n\\n要限制回滚范围,使用--onto选项。下面的命令在master分支上重演当前分支从169a6以来的最近几个提交,即2c33a。\\n\\n\\n\\n同样有git rebase --interactive让你更方便的完成一些复杂操组,比如丢弃、重排、修改、合并提交。没有图片体现着下,细节看这里:git-rebase(1)\\n\\n技术说明\\n===\\n\\n文件内容并没有真正存储在索引(.git/index)或者提交对象中,而是以blob的形式分别存储在数据库中(.git/objects),并用SHA-1值来校验。 索引文件用识别码列出相关的blob文件以及别的数据。对于提交来说,以树(tree)的形式存储,同样用对于的哈希值识别。树对应着工作目录中的文件夹,树中包含的 树或者blob对象对应着相应的子目录和文件。每次提交都存储下它的上一级树的识别码。\\n\\n如果用detached HEAD提交,那么最后一次提交会被the reflog for HEAD引用。但是过一段时间就失效,最终被回收,与git commit --amend或者git rebase很像。\",\"mtime\":1444880072000,\"source\":\"source/_posts/图解git.md\"},\"pdyplxupu5jcuo87\":{\"_id\":\"pdyplxupu5jcuo87\",\"content\":\"title: '单个 C 文件实现的 Web 服务器'\\ndate: 2014-05-14 15:58:31\\n\\n---\\n\\n转自osc\\n
\\n\\n\\t/*\\n\\t * WebServer.c\\n\\t *\\n\\t * Created on: Nov 3, 2012\\n\\t * Author: pavithra\\n\\t *\\n\\t * A web server in C language using only the standard libraries.\\n\\t * The port number is passed as an argument.\\n\\t *\\n\\t */\\n\\t\\n\\t#include
\\n\\t#include
\\n\\t#include
\\n\\t#include
\\n\\t#include
\\n\\t#include
\\n\\t#include
\\n\\t#include
\\n\\t#include
\\n\\t\\n\\t#define EOL \\\"\\\\r\\\\n\\\"\\n\\t#define EOL_SIZE 2\\n\\t\\n\\ttypedef struct {\\n\\t char *ext;\\n\\t char *mediatype;\\n\\t} extn;\\n\\t\\n\\t//Possible media types\\n\\textn extensions[] ={\\n\\t {\\\"gif\\\", \\\"image/gif\\\" },\\n\\t {\\\"txt\\\", \\\"text/plain\\\" },\\n\\t {\\\"jpg\\\", \\\"image/jpg\\\" },\\n\\t {\\\"jpeg\\\",\\\"image/jpeg\\\"},\\n\\t {\\\"png\\\", \\\"image/png\\\" },\\n\\t {\\\"ico\\\", \\\"image/ico\\\" },\\n\\t {\\\"zip\\\", \\\"image/zip\\\" },\\n\\t {\\\"gz\\\", \\\"image/gz\\\" },\\n\\t {\\\"tar\\\", \\\"image/tar\\\" },\\n\\t {\\\"htm\\\", \\\"text/html\\\" },\\n\\t {\\\"html\\\",\\\"text/html\\\" },\\n\\t {\\\"php\\\", \\\"text/html\\\" },\\n\\t {\\\"pdf\\\",\\\"application/pdf\\\"},\\n\\t {\\\"zip\\\",\\\"application/octet-stream\\\"},\\n\\t {\\\"rar\\\",\\\"application/octet-stream\\\"},\\n\\t {0,0} };\\n\\t\\n\\t/*\\n\\t A helper function\\n\\t */\\n\\tvoid error(const char *msg) {\\n\\t perror(msg);\\n\\t exit(1);\\n\\t}\\n\\t\\n\\t/*\\n\\t A helper function\\n\\t */\\n\\tint get_file_size(int fd) {\\n\\t struct stat stat_struct;\\n\\t if (fstat(fd, &stat_struct) == -1)\\n\\t return (1);\\n\\t return (int) stat_struct.st_size;\\n\\t}\\n\\t\\n\\t/*\\n\\t A helper function\\n\\t */\\n\\tvoid send_new(int fd, char *msg) {\\n\\t int len = strlen(msg);\\n\\t if (send(fd, msg, len, 0) == -1) {\\n\\t printf(\\\"Error in send\\\\n\\\");\\n\\t }\\n\\t}\\n\\t\\n\\t/*\\n\\t This function recieves the buffer\\n\\t until an \\\"End of line(EOL)\\\" byte is recieved\\n\\t */\\n\\tint recv_new(int fd, char *buffer) {\\n\\t char *p = buffer; // Use of a pointer to the buffer rather than dealing with the buffer directly\\n\\t int eol_matched = 0; // Use to check whether the recieved byte is matched with the buffer byte or not\\n\\t while (recv(fd, p, 1, 0) != 0) // Start receiving 1 byte at a time\\n\\t {\\n\\t if (*p == EOL[eol_matched]) // if the byte matches with the first eol byte that is '\\\\r'\\n\\t {\\n\\t ++eol_matched;\\n\\t if (eol_matched == EOL_SIZE) // if both the bytes matches with the EOL\\n\\t {\\n\\t *(p + 1 - EOL_SIZE) = '\\\\0'; // End the string\\n\\t return (strlen(buffer)); // Return the bytes recieved\\n\\t }\\n\\t } else {\\n\\t eol_matched = 0;\\n\\t }\\n\\t p++; // Increment the pointer to receive next byte\\n\\t }\\n\\t return (0);\\n\\t}\\n\\t\\n\\t/*\\n\\t A helper function: Returns the\\n\\t web root location.\\n\\t */\\n\\tchar* webroot() {\\n\\t // open the file \\\"conf\\\" for reading\\n\\t FILE *in = fopen(\\\"conf\\\", \\\"rt\\\");\\n\\t // read the first line from the file\\n\\t char buff[1000];\\n\\t fgets(buff, 1000, in);\\n\\t // close the stream\\n\\t fclose(in);\\n\\t char* nl_ptr = strrchr(buff, '\\\\n');\\n\\t if (nl_ptr != NULL)\\n\\t *nl_ptr = '\\\\0';\\n\\t return strdup(buff);\\n\\t}\\n\\t\\n\\t/*\\n\\t Handles php requests\\n\\t */\\n\\tvoid php_cgi(char* script_path, int fd) {\\n\\t send_new(fd, \\\"HTTP/1.1 200 OK\\\\n Server: Web Server in C\\\\n Connection: close\\\\n\\\");\\n\\t dup2(fd, STDOUT_FILENO);\\n\\t char script[500];\\n\\t strcpy(script, \\\"SCRIPT_FILENAME=\\\");\\n\\t strcat(script, script_path);\\n\\t putenv(\\\"GATEWAY_INTERFACE=CGI/1.1\\\");\\n\\t putenv(script);\\n\\t putenv(\\\"QUERY_STRING=\\\");\\n\\t putenv(\\\"REQUEST_METHOD=GET\\\");\\n\\t putenv(\\\"REDIRECT_STATUS=true\\\");\\n\\t putenv(\\\"SERVER_PROTOCOL=HTTP/1.1\\\");\\n\\t putenv(\\\"REMOTE_HOST=127.0.0.1\\\");\\n\\t execl(\\\"/usr/bin/php-cgi\\\", \\\"php-cgi\\\", NULL);\\n\\t}\\n\\t\\n\\t/*\\n\\t This function parses the HTTP requests,\\n\\t arrange resource locations,\\n\\t check for supported media types,\\n\\t serves files in a web root,\\n\\t sends the HTTP error codes.\\n\\t */\\n\\tint connection(int fd) {\\n\\t char request[500], resource[500], *ptr;\\n\\t int fd1, length;\\n\\t if (recv_new(fd, request) == 0) {\\n\\t printf(\\\"Recieve Failed\\\\n\\\");\\n\\t }\\n\\t printf(\\\"%s\\\\n\\\", request);\\n\\t // Check for a valid browser request\\n\\t ptr = strstr(request, \\\" HTTP/\\\");\\n\\t if (ptr == NULL) {\\n\\t printf(\\\"NOT HTTP !\\\\n\\\");\\n\\t } else {\\n\\t *ptr = 0;\\n\\t ptr = NULL;\\n\\t\\n\\t if (strncmp(request, \\\"GET \\\", 4) == 0) {\\n\\t ptr = request + 4;\\n\\t }\\n\\t if (ptr == NULL) {\\n\\t printf(\\\"Unknown Request ! \\\\n\\\");\\n\\t } else {\\n\\t if (ptr[strlen(ptr) - 1] == '/') {\\n\\t strcat(ptr, \\\"index.html\\\");\\n\\t }\\n\\t strcpy(resource, webroot());\\n\\t strcat(resource, ptr);\\n\\t char* s = strchr(ptr, '.');\\n\\t int i;\\n\\t for (i = 0; extensions[i].ext != NULL; i++) {\\n\\t if (strcmp(s + 1, extensions[i].ext) == 0) {\\n\\t fd1 = open(resource, O_RDONLY, 0);\\n\\t printf(\\\"Opening \\\\\\\"%s\\\\\\\"\\\\n\\\", resource);\\n\\t if (fd1 == -1) {\\n\\t printf(\\\"404 File not found Error\\\\n\\\");\\n\\t send_new(fd, \\\"HTTP/1.1 404 Not Found\\\\r\\\\n\\\");\\n\\t send_new(fd, \\\"Server : Web Server in C\\\\r\\\\n\\\\r\\\\n\\\");\\n\\t send_new(fd, \\\"
404 Not Found
\\\");\\n\\t send_new(fd, \\\"
404 Not Found: The requested resource could not be found!
\\\");\\n\\t //Handling php requests\\n\\t } else if (strcmp(extensions[i].ext, \\\"php\\\") == 0) {\\n\\t php_cgi(resource, fd);\\n\\t sleep(1);\\n\\t close(fd);\\n\\t exit(1);\\n\\t } else {\\n\\t printf(\\\"200 OK, Content-Type: %s\\\\n\\\\n\\\",\\n\\t extensions[i].mediatype);\\n\\t send_new(fd, \\\"HTTP/1.1 200 OK\\\\r\\\\n\\\");\\n\\t send_new(fd, \\\"Server : Web Server in C\\\\r\\\\n\\\\r\\\\n\\\");\\n\\t if (ptr == request + 4) // if it is a GET request\\n\\t {\\n\\t if ((length = get_file_size(fd1)) == -1)\\n\\t printf(\\\"Error in getting size !\\\\n\\\");\\n\\t size_t total_bytes_sent = 0;\\n\\t ssize_t bytes_sent;\\n\\t while (total_bytes_sent < length) {\\n\\t //Zero copy optimization\\n\\t if ((bytes_sent = sendfile(fd, fd1, 0,\\n\\t length - total_bytes_sent)) <= 0) {\\n\\t if (errno == EINTR || errno == EAGAIN) {\\n\\t continue;\\n\\t }\\n\\t perror(\\\"sendfile\\\");\\n\\t return -1;\\n\\t }\\n\\t total_bytes_sent += bytes_sent;\\n\\t }\\n\\t\\n\\t }\\n\\t }\\n\\t break;\\n\\t }\\n\\t int size = sizeof(extensions) / sizeof(extensions[0]);\\n\\t if (i == size - 2) {\\n\\t printf(\\\"415 Unsupported Media Type\\\\n\\\");\\n\\t send_new(fd, \\\"HTTP/1.1 415 Unsupported Media Type\\\\r\\\\n\\\");\\n\\t send_new(fd, \\\"Server : Web Server in C\\\\r\\\\n\\\\r\\\\n\\\");\\n\\t send_new(fd, \\\"
415 Unsupported Media Type
\\\");\\n\\t send_new(fd, \\\"
415 Unsupported Media Type!
\\\");\\n\\t }\\n\\t }\\n\\t\\n\\t close(fd);\\n\\t }\\n\\t }\\n\\t shutdown(fd, SHUT_RDWR);\\n\\t}\\n\\t\\n\\tint main(int argc, char *argv[]) {\\n\\t int sockfd, newsockfd, portno, pid;\\n\\t socklen_t clilen;\\n\\t struct sockaddr_in serv_addr, cli_addr;\\n\\t\\n\\t if (argc < 2) {\\n\\t fprintf(stderr, \\\"ERROR, no port provided\\\\n\\\");\\n\\t exit(1);\\n\\t }\\n\\t sockfd = socket(AF_INET, SOCK_STREAM, 0);\\n\\t if (sockfd < 0)\\n\\t error(\\\"ERROR opening socket\\\");\\n\\t bzero((char *) &serv_addr, sizeof(serv_addr));\\n\\t portno = atoi(argv[1]);\\n\\t serv_addr.sin_family = AF_INET;\\n\\t serv_addr.sin_addr.s_addr = INADDR_ANY;\\n\\t serv_addr.sin_port = htons(portno);\\n\\t if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)\\n\\t error(\\\"ERROR on binding\\\");\\n\\t listen(sockfd, 5);\\n\\t clilen = sizeof(cli_addr);\\n\\t /*\\n\\t Server runs forever, forking off a separate\\n\\t process for each connection.\\n\\t */\\n\\t while (1) {\\n\\t newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);\\n\\t if (newsockfd < 0)\\n\\t error(\\\"ERROR on accept\\\");\\n\\t pid = fork();\\n\\t if (pid < 0)\\n\\t error(\\\"ERROR on fork\\\");\\n\\t if (pid == 0) {\\n\\t close(sockfd);\\n\\t connection(newsockfd);\\n\\t exit(0);\\n\\t } else\\n\\t close(newsockfd);\\n\\t } /* end of while */\\n\\t close(sockfd);\\n\\t return 0; /* we never get here */\\n\\t}\",\"mtime\":1444880072000,\"source\":\"source/_posts/单个-c-文件实现的-web-服务器.md\"},\"od4z16wg53bvn6hx\":{\"_id\":\"od4z16wg53bvn6hx\",\"content\":\"title: '妈咪,我找到了! -- 15个实用的Linux find命令示例'\\ndate: 2014-05-14 14:20:06\\n\\n---\\n除了在一个目录结构下查找文件这种基本的操作,你还可以用find命令实现一些实用的操作,使你的命令行之旅更加简易。\\n\\n本文将介绍15种无论是于新手还是老鸟都非常有用的Linux find命令。\\n\\n首先,在你的home目录下面创建下面的空文件,来测试下面的find命令示例。\\n\\n
\\n\\n\\t# vim create_sample_files.sh\\n\\ttouch MybashProgram.sh\\n\\ttouch mycprogram.c\\n\\ttouch MyCProgram.c\\n\\ttouch Program.c\\n\\t\\n\\tmkdir backup\\n\\tcd backup\\n\\t\\n\\ttouch MybashProgram.sh\\n\\ttouch mycprogram.c\\n\\ttouch MyCProgram.c\\n\\ttouch Program.c\\n\\t\\n\\t# chmod +x create_sample_files.sh\\n\\t\\n\\t# ./create_sample_files.sh\\n\\t\\n\\t# ls -R\\n\\t.:\\n\\tbackup MybashProgram.sh MyCProgram.c\\n\\tcreate_sample_files.sh mycprogram.c Program.c\\n\\t\\n\\t./backup:\\n\\tMybashProgram.sh mycprogram.c MyCProgram.c Program.c\\n\\n**1. 用文件名查找文件**\\n\\n这是find命令的一个基本用法。下面的例子展示了用MyCProgram.c作为查找名在当前目录及其子目录中查找文件的方法。\\n\\n\\t# find -name \\\"MyCProgram.c\\\"\\n\\t./backup/MyCProgram.c\\n\\t./MyCProgram.c\\n\\n**2.用文件名查找文件,忽略大小写**\\n\\n这是find命令的一个基本用法。下面的例子展示了用MyCProgram.c作为查找名在当前目录及其子目录中查找文件的方法,忽略了大小写。\\n\\n\\t# find -iname \\\"MyCProgram.c\\\"\\n\\t./mycprogram.c\\n\\t./backup/mycprogram.c\\n\\t./backup/MyCProgram.c\\n\\t./MyCProgram.c\\n\\n**3. 使用mindepth和maxdepth限定搜索指定目录的深度**\\n\\n在root目录及其子目录下查找passwd文件。\\n\\n\\t# find / -name passwd\\n\\t./usr/share/doc/nss_ldap-253/pam.d/passwd\\n\\t./usr/bin/passwd\\n\\t./etc/pam.d/passwd\\n\\t./etc/passwd\\n\\n在root目录及其1层深的子目录中查找passwd. (例如root — level 1, and one sub-directory — level 2)\\n\\n\\t# find -maxdepth 2 -name passwd\\n\\t./etc/passwd\\n\\n在root目录下及其最大两层深度的子目录中查找passwd文件. (例如 root — level 1, and two sub-directories — level 2 and 3 )\\n\\n\\t# find / -maxdepth 3 -name passwd\\n\\t./usr/bin/passwd\\n\\t./etc/pam.d/passwd\\n\\t./etc/passwd\\n\\n在第二层子目录和第四层子目录之间查找passwd文件。\\n\\n\\t# find -mindepth 3 -maxdepth 5 -name passwd\\n\\t./usr/bin/passwd\\n\\t./etc/pam.d/passwd\\n\\n**4. 在find命令查找到的文件上执行命令**\\n\\n下面的例子展示了find命令来计算所有不区分大小写的文件名为“MyCProgram.c”的文件的MD5验证和。{}将会被当前文件名取代。\\n\\n\\tfind -iname \\\"MyCProgram.c\\\" -exec md5sum {} \\\\;\\n\\td41d8cd98f00b204e9800998ecf8427e ./mycprogram.c\\n\\td41d8cd98f00b204e9800998ecf8427e ./backup/mycprogram.c\\n\\td41d8cd98f00b204e9800998ecf8427e ./backup/MyCProgram.c\\n\\td41d8cd98f00b204e9800998ecf8427e ./MyCProgram.c\\n\\n**5. 相反匹配**\\n\\n显示所有的名字不是MyCProgram.c的文件或者目录。由于maxdepth是1,所以只会显示当前目录下的文件和目录。\\n\\n\\tfind -maxdepth 1 -not -iname \\\"MyCProgram.c\\\"\\n\\t.\\n\\t./MybashProgram.sh\\n\\t./create_sample_files.sh\\n\\t./backup\\n\\t./Program.c\\n\\n**6. 使用inode编号查找文件**\\n\\n任何一个文件都有一个独一无二的inode编号,借此我们可以区分文件。创建两个名字相似的文件,例如一个有空格结尾,一个没有。\\n\\n\\ttouch \\\"test-file-name\\\"\\n\\n\\t# touch \\\"test-file-name \\\"\\n\\t[Note: There is a space at the end]\\n\\t\\n\\t# ls -1 test*\\n\\ttest-file-name\\n\\ttest-file-name\\n\\n从ls的输出不能区分哪个文件有空格结尾。使用选项-i,可以看到文件的inode编号,借此可以区分这两个文件。\\n\\n\\tls -i1 test*\\n\\t16187429 test-file-name\\n\\t16187430 test-file-name\\n\\n你可以如下面所示在find命令中指定inode编号。在此,find命令用inode编号重命名了一个文件。\\n\\n\\tfind -inum 16187430 -exec mv {} new-test-file-name \\\\;\\n\\t\\n\\t# ls -i1 *test*\\n\\t16187430 new-test-file-name\\n\\t16187429 test-file-name\\n\\n你可以在你想对那些像上面一样的糟糕命名的文件做某些操作时使用这一技术。例如,名为file?.txt的文件名字中有一个特殊字符。若你想执行“rm file?.txt”,下面所示的所有三个文件都会被删除。所以,采用下面的步骤来删除\\\"file?.txt\\\"文件。\\n\\n\\tls\\n\\tfile1.txt file2.txt file?.txt\\n\\n找到每一个文件的inode编号。\\n\\n\\tls -i1\\n\\t804178 file1.txt\\n\\t804179 file2.txt\\n\\t804180 file?.txt\\n\\n如下所示: 使用inode编号来删除那些具有特殊符号的文件名。\\n\\n\\tfind -inum 804180 -exec rm {} \\\\;\\n\\t\\n\\t# ls\\n\\tfile1.txt file2.txt\\n\\t[Note: The file with name \\\"file?.txt\\\" is now removed]\\n\\n**7. 根据文件权限查找文件**\\n\\n下面的操作时合理的:\\n\\n* 找到具有指定权限的文件\\n* 忽略其他权限位,检查是否和指定权限匹配\\n* 根据给定的八进制/符号表达的权限搜索\\n\\n此例中,假设目录包含以下文件。注意这些文件的权限不同。\\n\\n\\tls -l\\n\\ttotal 0\\n\\t-rwxrwxrwx 1 root root 0 2009-02-19 20:31 all_for_all\\n\\t-rw-r--r-- 1 root root 0 2009-02-19 20:30 everybody_read\\n\\t---------- 1 root root 0 2009-02-19 20:31 no_for_all\\n\\t-rw------- 1 root root 0 2009-02-19 20:29 ordinary_file\\n\\t-rw-r----- 1 root root 0 2009-02-19 20:27 others_can_also_read\\n\\t----r----- 1 root root 0 2009-02-19 20:27 others_can_only_read\\n\\n找到具有组读权限的文件。使用下面的命令来找到当前目录下对同组用户具有读权限的文件,忽略该文件的其他权限。\\n\\n\\tfind . -perm -g=r -type f -exec ls -l {} \\\\;\\n\\t-rw-r--r-- 1 root root 0 2009-02-19 20:30 ./everybody_read\\n\\t-rwxrwxrwx 1 root root 0 2009-02-19 20:31 ./all_for_all\\n\\t----r----- 1 root root 0 2009-02-19 20:27 ./others_can_only_read\\n\\t-rw-r----- 1 root root 0 2009-02-19 20:27 ./others_can_also_read\\n\\n找到对组用户具有只读权限的文件。\\n\\n\\tfind . -perm g=r -type f -exec ls -l {} \\\\;\\n\\t----r----- 1 root root 0 2009-02-19 20:27 ./others_can_only_read\\n\\t\\n找到对组用户具有只读权限的文件(使用八进制权限形式)。\\n\\n\\tfind . -perm 040 -type f -exec ls -l {} \\\\;\\n\\t----r----- 1 root root 0 2009-02-19 20:27 ./others_can_only_read\\n\\n**8. 找到home目录及子目录下所有的空文件(0字节文件)**\\n\\n下面命令的输出文件绝大多数都是锁定文件盒其他程序创建的place hoders\\n\\n\\tfind ~ -empty\\n\\n只列出你home目录里的空文件。\\n\\n\\tfind . -maxdepth 1 -empty\\n\\n只列出当年目录下的非隐藏空文件。\\n\\n\\tfind . -maxdepth 1 -empty -not -name \\\".*\\\"\\n\\n**9. 查找5个最大的文件**\\n\\n下面的命令列出当前目录及子目录下的5个最大的文件。这会需要一点时间,取决于命令需要处理的文件数量。\\n\\n\\tfind . -type f -exec ls -s {} \\\\; | sort -n -r | head -5\\n\\n**10. 查找5个最小的文件**\\n\\n方法同查找5个最大的文件类似,区别只是sort的顺序是降序。\\n\\n\\tfind . -type f -exec ls -s {} \\\\; | sort -n | head -5\\n\\n上面的命令中,很可能你看到的只是空文件(0字节文件)。如此,你可以使用下面的命令列出最小的文件,而不是0字节文件。\\n\\n\\tfind . -not -empty -type f -exec ls -s {} \\\\; | sort -n | head -5\\n\\n**11. 使用-type查找指定文件类型的文件**\\n\\n只查找socket文件\\n\\n\\tfind . -type s\\n\\n查找所有的目录\\n\\n\\tfind . -type d\\n\\n查找所有的一般文件\\n\\n\\tfind . -type f\\n\\n查找所有的隐藏文件\\n\\n\\tfind . -type f -name \\\".*\\\"\\n\\n查找所有的隐藏目录\\n\\n\\tfind -type d -name \\\".*\\\"\\n\\n**12. 通过和其他文件比较修改时间查找文件**\\n\\n显示在指定文件之后做出修改的文件。下面的find命令将显示所有的在ordinary_file之后创建修改的文件。\\n\\n\\tls -lrt\\n\\ttotal 0\\n\\t-rw-r----- 1 root root 0 2009-02-19 20:27 others_can_also_read\\n\\t----r----- 1 root root 0 2009-02-19 20:27 others_can_only_read\\n\\t-rw------- 1 root root 0 2009-02-19 20:29 ordinary_file\\n\\t-rw-r--r-- 1 root root 0 2009-02-19 20:30 everybody_read\\n\\t-rwxrwxrwx 1 root root 0 2009-02-19 20:31 all_for_all\\n\\t---------- 1 root root 0 2009-02-19 20:31 no_for_all\\n\\t\\n\\t# find -newer ordinary_file\\n\\t.\\n\\t./everybody_read\\n\\t./all_for_all\\n\\t./no_for_all\\n\\n**13. 通过文件大小查找文件**\\n\\n使用-size选项可以通过文件大小查找文件。\\n\\n查找比指定文件大的文件\\n\\n\\tfind ~ -size +100M\\n\\n查找比指定文件小的文件\\n\\n\\tfind ~ -size -100M\\n\\n查找符合给定大小的文件\\n\\n\\tfind ~ -size 100M\\n\\n注意: – 指比给定尺寸小,+ 指比给定尺寸大。没有符号代表和给定尺寸完全一样大。\\n\\n**14. 给常用find操作取别名**\\n\\n若你发现有些东西很有用,你可以给他取别名。并且在任何你希望的地方执行。\\n\\n常用的删除a.out文件。\\n\\n\\talias rmao=\\\"find . -iname a.out -exec rm {} \\\\;\\\"\\n\\t# rmao\\n\\n删除c程序产生的core文件。\\n\\n\\talias rmc=\\\"find . -iname core -exec rm {} \\\\;\\\"\\n\\t# rmc\\n\\n**15. 用find命令删除大型打包文件**\\n\\n下面的命令删除大于100M的*.zip文件。\\n\\n\\tfind / -type f -name *.zip -size +100M -exec rm -i {} \\\\;\\\"\\n\\n用别名rm100m删除所有大雨100M的*.tar文件。使用同样的思想可以创建rm1g,rm2g,rm5g的一类别名来删除所有大于1G,2G,5G的文件。\\n\\n\\talias rm100m=\\\"find / -type f -name *.tar -size +100M -exec rm -i {} \\\\;\\\"\\n\\t# alias rm1g=\\\"find / -type f -name *.tar -size +1G -exec rm -i {} \\\\;\\\"\\n\\t# alias rm2g=\\\"find / -type f -name *.tar -size +2G -exec rm -i {} \\\\;\\\"\\n\\t# alias rm5g=\\\"find / -type f -name *.tar -size +5G -exec rm -i {} \\\\;\\\"\\n\\t\\n\\t# rm100m\\n\\t# rm1g\\n\\t# rm2g\\n\\t# rm5g\\n\\nFind命令示例(第二部分)\\n\\n若你喜欢这篇关于find命令的Mommy文章,别忘了看看第二部分的关于find命令的Daddy文章。[爹地,我找到了!, 15个极好的Linux find命令示例](http://www.oschina.net/translate/15-practical-unix-linux-find-command-examples-part-2)\",\"mtime\":1444880072000,\"source\":\"source/_posts/妈咪,我找到了!----15个实用的linux-find命令示例.md\"},\"loa6n9qbqr0xjxuc\":{\"_id\":\"loa6n9qbqr0xjxuc\",\"content\":\"title: '检查和收集 Linux 硬件信息的 7 个命令'\\ndate: 2014-05-15 10:25:27\\n\\n---\\n英文原文:[7 Linux commands to check/gather hardware information in linux system](http://www.itsprite.com/linux-7-linux-commands-to-checkgather-hardware-information-in-linux-system/)\\n\\n在Linux系统中,有许多命令可用于查询主机的硬件信息。一些命令只针对特定的硬件组件,比如CPU、内存,一些命令可以查询多个硬件信息。\\n\\n这篇文章只是简单的让每个人了解查询硬件信息的基本命令使用,包括lscpu、hwinfo、lshw、lspci、lsblk、lsusb等等。\\n\\n
\\n\\n**1. lscpu用于查询CPU信息**\\n\\n\\t[root@devops ~]# lscpu\\n\\tArchitecture: x86_64\\n\\tCPU op-mode(s): 32-bit, 64-bit\\n\\tByte Order: Little Endian\\n\\tCPU(s): 1\\n\\tOn-line CPU(s) list: 0\\n\\tThread(s) per core: 1\\n\\tCore(s) per socket: 1\\n\\tCPU socket(s): 1\\n\\tNUMA node(s): 1\\n\\tVendor ID: GenuineIntel\\n\\tCPU family: 6\\n\\tModel: 45\\n\\tStepping: 7\\n\\tCPU MHz: 2194.842\\n\\tBogoMIPS: 4389.68\\n\\tHypervisor vendor: Xen\\n\\tVirtualization type: full\\n\\tL1d cache: 32K\\n\\tL1i cache: 32K\\n\\tL2 cache: 256K\\n\\tL3 cache: 15360K\\n\\tNUMA node0 CPU(s): 0\\n\\n**2. lshw显示硬件信息表**\\n\\n这个命令应用普遍,它可通过个人需求而列出多种不同的硬件参数:CPU、内存、硬盘、USB控制器、lshw卡片等等,本质上就是从/proc目录不同文件中中提取对应的硬件信息。\\n\\n按照下面的步骤去安装lshw工具,然后就可以使用了。\\n\\n\\t wget http://ezix.org/software/files/lshw-B.02.14.tar.gz\\n\\t tar -zxvf lshw-B.02.14.tar.gz\\n\\t cd lshw-B.02.14\\n\\t make && make install\\n\\n示例:\\n\\n\\t[root@devops lshw-B.02.14]# lshw -short\\n\\tH/W path Device Class Description\\n\\t=====================================================\\n\\t system HVM domU\\n\\t/0 bus Motherboard\\n\\t/0/0 memory 96KiB BIOS\\n\\t/0/1 processor Intel(R) Xeon(R) CPU E5-2430 0 @ 2.20GHz\\n\\t/0/2 memory System Memory\\n\\t/0/2/0 memory 512MiB DIMM RAM\\n\\t/0/2/1 memory 512MiB DIMM RAM\\n\\t/0/3 memory 96KiB BIOS\\n\\t/0/4 processor CPU\\n\\t/0/5 memory System Memory\\n\\t/0/6 memory \\n\\t/0/7 memory \\n\\t/0/100 bridge 440FX - 82441FX PMC [Natoma]\\n\\t/0/100/1 bridge 82371SB PIIX3 ISA [Natoma/Triton II]\\n\\t/0/100/1.1 scsi1 storage 82371SB PIIX3 IDE [Natoma/Triton II]\\n\\t/0/100/1.1/0.0.0 /dev/cdrom1 disk SCSI CD-ROM\\n\\t/0/100/1.2 bus 82371SB PIIX3 USB [Natoma/Triton II]\\n\\t/0/100/1.2/1 usb1 bus UHCI Host Controller\\n\\t/0/100/1.2/1/2 input QEMU USB Tablet\\n\\t/0/100/1.3 bridge 82371AB/EB/MB PIIX4 ACPI\\n\\t/0/100/2 display GD 5446\\n\\t/0/100/3 generic Xen Platform Device\\n\\t/1 eth0 network Ethernet interface\\n\\t/2 eth1 network Ethernet interface\\n\\t[root@devops lshw-B.02.14]#\\n\\n**3. hwinfo-硬件信息**\\n\\nhwinfo类似于lshw,也能查询硬件信息,且应用广泛。它也能输出多个硬件部分的详细或者简要信息,但是不同的是有时hwinfo比lshw的信息更详细。\\n\\n默认情况下,Linux系统没有安装hwinfo工具,所以你需要按照以下步骤自己安装:\\n\\n**CentOS 6**\\n\\n\\t#rpm -Uvh http://mirror.symnds.com/distributions/gf/el/6/gf/x86_64/gf-release-6-6.gf.el6.noarch.rpm\\n\\t#yum list hwinfo\\n\\t#yum install hwinfo\\n\\n**CentOS 5**\\n\\n\\t#rpm -Uvh http://mirror.symnds.com/distributions/gf/el/5/gf/x86_64/gf-release-5-6.gf.el5.noarch.rpm\\n\\t#yum list hwinfo\\n\\t#yum install hwinfo\\n\\n\\t[root@devops tmp]# rpm -Uvh http://mirror.symnds.com/distributions/gf/el/6/gf/x86_64/gf-release-6-6.gf.el6.noarch.rpm\\n\\tRetrieving http://mirror.symnds.com/distributions/gf/el/6/gf/x86_64/gf-release-6-6.gf.el6.noarch.rpm\\n\\twarning: /var/tmp/rpm-tmp.m2mMAO: Header V4 RSA/SHA1 Signature, key ID 13a4d2a9: NOKEY\\n\\tPreparing... ########################################### [100%]\\n\\t 1:gf-release ########################################### [100%]\\n\\t[root@devops tmp]# yum list hwinfo\\n\\tLoaded plugins: fastestmirror\\n\\tLoading mirror speeds from cached hostfile\\n\\t * base: mirrors.aliyun.com\\n\\t * extras: mirrors.aliyun.com\\n\\t * updates: mirrors.aliyun.com\\n\\tgf \\n\\t\\n\\t 00:00 \\n\\tgf/primary_db \\n\\t\\n\\t 00:00 \\n\\tAvailable Packages\\n\\thwinfo.x86_64\\n\\t[root@devops tmp]# yum list hwinfo\\n\\tLoaded plugins: fastestmirror\\n\\tLoading mirror speeds from cached hostfile\\n\\t * base: mirrors.aliyun.com\\n\\t * extras: mirrors.aliyun.com\\n\\t * updates: mirrors.aliyun.com\\n\\tgf \\n\\t\\n\\t 00:00 \\n\\tgf/primary_db \\n\\t\\n\\t 00:00 \\n\\tAvailable Packages\\n\\thwinfo.x86_64 20.2-1.gf.el6\\n\\n示例:\\n\\n\\t[root@devops tmp]# hwinfo -short\\n\\toops: don't know what to do with \\\"short\\\"\\n\\t[root@devops tmp]# hwinfo --short\\n\\tcpu: \\n\\t Intel(R) Xeon(R) CPU E5-2430 0 @ 2.20GHz, 2194 MHz\\n\\tkeyboard:\\n\\t /dev/input/event3 AT Translated Set 2 keyboard\\n\\t /dev/ttyS0 serial console\\n\\tmouse:\\n\\t /dev/input/mice Adomax QEMU USB Tablet\\n\\t /dev/input/mice Macintosh mouse button emulation\\n\\t /dev/input/mice ImExPS/2 Generic Explorer Mouse\\n\\tgraphics card:\\n\\t Cirrus Logic GD 5446\\n\\tstorage:\\n\\t Intel 82371SB PIIX3 IDE [Natoma/Triton II]\\n\\t Xen Virtual Storage 0\\n\\t Xen Virtual Storage 1\\n\\t Xen Virtual Storage 2\\n\\tnetwork:\\n\\t eth0 Xen Virtual Ethernet Card 0\\n\\t eth1 Xen Virtual Ethernet Card 1\\n\\tnetwork interface:\\n\\t lo Loopback network interface\\n\\t eth0 Ethernet network interface\\n\\t eth1 Ethernet network interface\\n\\tdisk:\\n\\t /dev/xvda Disk\\n\\t /dev/xvdb Disk\\n\\tpartition:\\n\\t /dev/xvda1 Partition\\n\\t /dev/xvdb1 Partition\\n\\tcdrom:\\n\\t /dev/sr0 QEMU DVD-ROM\\n\\tusb controller:\\n\\t Qumranet Qemu virtual machine\\n\\tbios:\\n\\t BIOS\\n\\tbridge:\\n\\t Qumranet Qemu virtual machine\\n\\t Qumranet Qemu virtual machine\\n\\t Qumranet Qemu virtual machine\\n\\thub:\\n\\t Linux 2.6.32-279.el6.x86_64 uhci_hcd UHCI Host Controller\\n\\tmemory:\\n\\t Main Memory\\n\\tunknown:\\n\\t FPU\\n\\t DMA controller\\n\\t PIC\\n\\t Timer\\n\\t Keyboard controller\\n\\t XenSource Xen Platform Device\\n\\t[root@devops tmp]#\\n\\n**4. lspci**\\n\\nlsppci命令可列出PCI总线的信息以及连接到PCI总线上的设备信息,比如VGA适配器、SATA控制器、其他模块等等。lspci工具是pciutils包的一部分,所以在安装lspci之前,你需要安装pciutils包。\\n\\n安装pciutils包使用下面的命令:\\n\\n\\t#yum install pciutils\\n\\n\\t[root@devops tmp]# yum install pciutils\\n\\tLoaded plugins: fastestmirror\\n\\tLoading mirror speeds from cached hostfile\\n\\t * base: mirrors.aliyun.com\\n\\t * extras: mirrors.aliyun.com\\n\\t * updates: mirrors.aliyun.com\\n\\tSetting up Install Process\\n\\tResolving Dependencies\\n\\t--> Running transaction check\\n\\t---> Package pciutils.x86_64 0:3.1.10-2.el6 will be installed\\n\\t--> Processing Dependency: pciutils-libs = 3.1.10-2.el6 for package: pciutils-3.1.10-2.el6.x86_64\\n\\t--> Running transaction check\\n\\t---> Package pciutils-libs.x86_64 0:3.1.4-11.el6 will be updated\\n\\t---> Package pciutils-libs.x86_64 0:3.1.10-2.el6 will be an update\\n\\t--> Finished Dependency Resolution\\n\\t\\n\\tDependencies Resolved\\n\\t\\n\\tInstalling:\\n\\t pciutils x86_64 3.1.10-2.el6 \\n\\t\\n\\t 85 k\\n\\tUpdating for dependencies:\\n\\t pciutils-libs x86_64 3.1.10-2.el6 \\n\\t\\n\\t 34 k\\n\\t\\n\\t..... \\n\\t\\n\\tDependency Updated:\\n\\t pciutils-libs.x86_64 0:3.1.10-2.el6 \\n\\t\\n\\tComplete!\\n\\n示例:\\n\\n\\t[root@devops tmp]# lspci \\n\\t00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)\\n\\t00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]\\n\\t00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]\\n\\t00:01.2 USB controller: Intel Corporation 82371SB PIIX3 USB [Natoma/Triton II] (rev 01)\\n\\t00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 01)\\n\\t00:02.0 VGA compatible controller: Cirrus Logic GD 5446\\n\\t00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)\\n\\t[root@devops tmp]#\\n\\n**5. lsusb-列出USB总线信息**\\n\\n这个命令可列出USB控制器的设备信息。\\n\\nlsusb工具是usbutils包的一部分,所以你需要按照如下命令安装:\\n\\n\\t#yum install usbutils\\n\\n\\t[root@devops tmp]# yum install usbutils\\n\\tLoaded plugins: fastestmirror\\n\\tLoading mirror speeds from cached hostfile\\n\\t * base: mirrors.aliyun.com\\n\\t * extras: mirrors.aliyun.com\\n\\t * updates: mirrors.aliyun.com\\n\\tSetting up Install Process\\n\\tResolving Dependencies\\n\\t--> Running transaction check\\n\\t---> Package usbutils.x86_64 0:003-4.el6 will be installed\\n\\t--> Processing Dependency: libusb-1.0.so.0()(64bit) for package: usbutils-003-4.el6.x86_64\\n\\t--> Running transaction check\\n\\t---> Package libusb1.x86_64 0:1.0.9-0.6.rc1.el6 will be installed\\n\\t--> Finished Dependency Resolution\\n\\t\\n\\tDependencies Resolved\\n\\t\\n\\t============================================\\n\\t\\n\\t============\\n\\t Package Arch Version \\n\\t\\n\\t Size\\n\\t=======================================================\\n\\t============\\n\\tInstalling:\\n\\t usbutils x86_64 003-4.el6 \\n\\t\\n\\t 71 k\\n\\tInstalling for dependencies:\\n\\t libusb1 x86_64 1.0.9-0.6.rc1.el6 \\n\\t\\n\\t 80 k\\n\\t\\n\\tTransaction Summary\\n\\t================================================================\\n\\t\\n\\t============\\n\\tInstall 2 Package(s)\\n\\t\\n\\tTotal download size: 152 k\\n\\tInstalled size: 377 k\\n\\tIs this ok [y/N]: y\\n\\tDownloading Packages:\\n\\t(1/2): libusb1-1.0.9-0.6.rc1.el6.x86_64.rpm \\n\\t\\n\\t 00:00 \\n\\t(2/2): usbutils-003-4.el6.x86_64.rpm \\n\\t\\n\\t 00:00 \\n\\t--------------------------------------------------------\\n\\t\\n\\t------------\\n\\tTotal \\n\\t\\n\\t 00:00 \\n\\tRunning rpm_check_debug\\n\\tRunning Transaction Test\\n\\tTransaction Test Succeeded\\n\\tRunning Transaction\\n\\t Installing : libusb1-1.0.9-0.6.rc1.el6.x86_64 \\n\\t\\n\\t 1/2 \\n\\t Installing : usbutils-003-4.el6.x86_64 \\n\\t\\n\\t 2/2 \\n\\t Verifying : usbutils-003-4.el6.x86_64 \\n\\t\\n\\t 1/2 \\n\\t Verifying : libusb1-1.0.9-0.6.rc1.el6.x86_64 \\n\\t\\n\\t 2/2 \\n\\t\\n\\tInstalled:\\n\\t usbutils.x86_64 0:003-4.el6 \\n\\t\\n\\tDependency Installed:\\n\\t libusb1.x86_64 0:1.0.9-0.6.rc1.el6 \\n\\t\\n\\tComplete!\\n\\n示例:\\n\\n\\t[root@devops tmp]# lsusb\\n\\tBus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub\\n\\tBus 001 Device 002: ID 0627:0001 Adomax Technology Co., Ltd \\n\\t[root@devops tmp]#\\n\\n**6. lsblk-列出块设备的信息**\\n\\n\\t[root@devops tmp]# lsblk\\n\\tNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT\\n\\txvda 202:0 0 20G 0 disk \\n\\t└─xvda1 202:1 0 20G 0 part /\\n\\txvdb 202:16 0 10G 0 disk \\n\\t└─xvdb1 202:17 0 10G 0 part /alidata\\n\\tsr0 11:0 1 362K 0 rom\\n\\n**7. lsscsi-列出SCSI的设备信息**\\n\\n列出SCSI/SDAT设备的信息,比如硬盘驱动器、光盘驱动器。\\n\\n\\t[root@devops tmp]# lsscsi\\n\\t[1:0:0:0] cd/dvd QEMU QEMU DVD-ROM 0.10 /dev/sr0 \\n\\t[root@devops tmp]#\\n\\t\\n完毕!Enjoy this!\",\"mtime\":1444880072000,\"source\":\"source/_posts/检查和收集-linux-硬件信息的-7-个命令.md\"},\"c3dfjur8xz81a9ka\":{\"_id\":\"c3dfjur8xz81a9ka\",\"content\":\"title: 想提升工作效率,就別再做这七件事\\ndate: 2014-05-13 22:05:07\\ntags:\\n\\n---\\n原文出处: [medium](https://medium.com/business-marketing/a988c17383a6) 译文出处: [inside - elaine](http://www.inside.com.tw/2014/05/06/7-things-you-need-to-stop-doing-to-be-more-productive)。\\n\\n试想一位小公司老板每日孜孜不倦地工作,为何不能从为数众多的竞争者脱颖而出?\\n\\n一位创业家可以不眠不休一天工作24 小时,整整一周不休假。 然而,时间有限,且竞争者却永远可以投入更多钱与心力,让竞争更剧烈。 那么,为什么某些小型新创公司可以完成许多大型企业无法完成的事情呢?\\n\\n* Instagram──只有13 名员工却被Facebook 用几十亿买下的公司。\\n* Snapchat──只有30 名员工却拒绝科技巨人Facebook、Google 的并购交易。\\n\\n它们的成功部分来自于幸运──其余则是因为效率 。\\n\\n
\\n\\n\\n\\n>成功的关键不是努力工作,而是用聪明的方式工作。\\n\\n有效率并不是指良好的时间管理,而是──管理您的精力。 人们都需要学习如何花费最少的精力能够得到最大效益。\\n\\n要提升生产力,下面这些事情绝对要避免:\\n\\n一、停止加班,提升效率!\\n===\\n\\n1926年,福特汽车工业亨利福特(Henry Ford)执行了一项实验发现有趣的结果:当把一日的工时从10 小时降到8 小时,从一周工作6 天减少至一周5 天,人们的生产力将会提高(注一)。\\n\\n\\n\\n当您工作得越多,不论是短期或长期而言,您的效率、生产力都会降低。 1980 年由The Business Roundtable 的一篇研究〈Scheduled Overtime Effect on Construction Projects〉指出(注二):\\n\\n>当每周工作时间超过60 小时,并持续超过两个月,生产力下降的累积效应将使完工日期推迟,而人数相同但每周只工作40 小时的团队执行同样工作,甚至还会更早完工。\\n\\n在AlterNet的一篇文章中,Sara Robinson回顾美军执行的一项研究,这项发现「每晚都减少1小时睡眠,持续一周,将导致认知功能退化,等同于喝酒使血液酒精浓度升高至0.10 。\\n\\n>当个人过于劳累,使其以比平常还要负面的角度看事情,导致普遍地心情低落。 比心情更重要的是,其思维往往伴随着减少「主动思考与行动」──包括控制冲动、自我感觉良好、同情他人与情绪智力──的意愿(注三)。\\n\\n维持高程度的生产力,避免让自己过度工作并睡眠充足很重要。 下次您思想为何工作缺乏生产力,原因很简单,您有可能缺乏睡眠。\\n\\n二、不要太常说「yes」\\n===\\n\\n根据80/20 法则(Pareto Principle),20% 的努力创造80% 的成果,20% 的成果消耗80% 的时间。 不是努力工作,我们应该把注意力放在创造80% 的结果并放弃其余的。 我们应该把更多时间放在最重要的任务。 我们应该停止对低效能与无效能的任务说:「好的,我来做」 。\\n\\n>「成功的人」与「非常成功的人」之间的差异在于,非常成功的人对几乎所有的事情都说不。\\n— Warren Buffet\\n\\n激发一个问题:什么事情要说yes,什么事情说no? 如果您不能想出某些值得花时间的地方,就考虑作个小测试找出答案:追踪您作的所有事情,可能的话持续优化处理的过程。\\n\\n通常人们说「yes」的频率比实际上应该的次数还要多,因为说「yes」比说「no」要简单太多,没有想要当坏人。\\n\\n\\n\\n2012 研究发表在Journal of Consumer Research,研究者将120 个学生分成两组。 一组被训练为说「我不能(I can’t)」,然而其他被训练为「我不要(I don’t)」。 结果很有趣:\\n\\n>对自己说「我不能吃X」的学生,61% 都会选择吃巧克力糖,同时,对自己说「我不要吃X」的学生,只有36 % 会选择吃巧克力糖。 光是简单的词汇替换,就能显著增加人们选择更健康食品的机率。 (延伸阅读: [拒当YES MAN,说「不」让生活更美好](http://translate.googleusercontent.com/translate_c?depth=1&hl=zh-CN&ie=UTF8&prev=_t&rurl=translate.google.com.hk&sl=zh-CN&tl=zh-CN&u=http://www.inside.com.tw/2013/10/29/a-scientific-guide-to-effectively-saying-no&usg=ALkJrhgMw7cVfeXo4d_hKEbt95D-zMiMGQ) 。)\\n\\n三、停止什么都事必躬亲,让其他人帮忙\\n===\\n\\n在我职涯的某个点,我管理一个非常大的社群,而我无法处理得很好。 我试图每一件事情都自己来。 我疲倦不堪,但社群最终接管我的职责并自行治理。 因此我也学习到社群的力量,以及为什么品牌需要使用者创造的内容(user-generated content)。\\n\\n消费者知道他们想要什么,以及他们想要如何让它更好,更甚于任何行销人员。 根据[Octoly](http://translate.googleusercontent.com/translate_c?depth=1&hl=zh-CN&ie=UTF8&prev=_t&rurl=translate.google.com.hk&sl=zh-CN&tl=zh-CN&u=http://www.reelseo.com/earned-media-rankings-youtube-octoly/&usg=ALkJrhgzhU-mCRf3wGDSLoVVuvTFdroUwg) ,一支使用者自制影片的观看次数要比品牌自制影片多上十倍。 当寻找关于一个特定品牌的资讯,超过半数(51 %)的美国人相信使用者自制内容大过于品牌官网(16%)与媒体报导(14 %),对行销人员来说,寻求社群的帮忙至关重要。\\n\\n\\n\\n除此之外, 当您需要时,很重要地是愿意求助,让其他人来帮您。 因为这样可以让您有更多时间将焦点放在更重要的任务上。\\n\\n大多数的时候,即使您的朋友不能帮助您提升生产力。 光是陪在您身边就足够, 研究证实:分心的人在其他人也在场的情况,尽管那些人没有协助或指导,也可以完成更多的工作事项。\\n\\n四、停止完美主义\\n===\\n\\nDalhousie University心理学教授Simon Sherry博士Simon Sherry执行一项完美主义与生产力的研究 ,她指出:我们发现完美主义是绊倒教授的研究生产力的大石头。 完美主义倾向越高的教授就越没有效率。\\n\\n当一位完美主义者有以下问题:\\n\\n他们在一项花费的时间比任务要求所花费的时间还多。\\n他们会拖延并等到最佳的时刻。 在企业中,如果这是最完美的时刻,就代表已经太迟了。\\n他们过度聚焦在细节,反而忽略整体。\\n\\n五、停止作重复的事情,并使它自动化\\n===\\n\\n根据一项Tethy Solutions的研究 ,一个5人团队分别花3%、20%、25%、30%与70%的时间处理相同的事情,导入工作自动化软体两个月后,分别将处理重复事情的时间降至3%、10%、15%、15% 与10%。\\n\\n\\n\\n人们时常忘记时间就是金钱,因此经常土法炼钢地处理事情,因为这样比较容易,且不需要花费心力研究。 假设您办了一个Instagram 活动,号召网友上传的照片总数只有30 张,您可以手动一张一张处理。 但如果总共有从5个不同平台上传的30000张照片与影片时,您就需要一个好的数位管理系统了(例如[Filemobile](http://translate.googleusercontent.com/translate_c?depth=1&hl=zh-CN&ie=UTF8&prev=_t&rurl=translate.google.com.hk&sl=zh-CN&tl=zh-CN&u=http://www.filemobile.com/&usg=ALkJrhiH_0Hf9V6834m_3cfnbJrlPZ0B4Q) )。\\n\\n给行销人员的小撇步 :到GitHub或Google app script library,通常会发现免费且可马上使用的开源程式码,让您找到省去手动处理重覆工作的软体/程式。\\n\\n六、停止猜测,并开始用数据支撑决策\\n===\\n\\n如果您可以在搜寻引擎上优化网站,那么您也可以优化您的人生,让它成长并发挥最大的潜能。\\n\\n不同领域的研究其实都可供参考,举例来说,宾州州立大学心理系助理教授Robert Matchock 所执行的研究发现,大部分的人们最容易分心的时间是从中午到下午四点。 此数据便提供您作工作相关决策的考量。 假使您无法从现有研究找到答案,那么您也可以自行执行简单的测试。\\n\\n举例来说,Filemobile就制定了许多测试,试图找出[如何优化Twitter的图片呈现](http://translate.googleusercontent.com/translate_c?depth=1&hl=zh-CN&ie=UTF8&prev=_t&rurl=translate.google.com.hk&sl=zh-CN&tl=zh-CN&u=http://blog.filemobile.com/twitter-image-preview/&usg=ALkJrhhq8qKSuTXA5DdkH3vMrob9nwYdcQ) 。\\n\\n不断询问您自己:要如何测量并优化所有事情呢?\\n\\n七、停止工作,并拥有无所事事的时间\\n===\\n\\n大部分的人都没有了解到,当我们专注在某件事上,基本上就像是把自己锁在一个箱子里。 很重要的是要每隔一段时间离开工作现场,享受独处的时光。 独处时光对大脑与灵魂都有益处(注四):\\n\\n>一项哈佛研究指出,当人们相信他们是单独经验某项事情时,脑海的记忆会更持久且更精确。 另外一个研究也指出体验相当程度的孤独会让一个人更能够同理他人……相当程度的孤独可以帮助青少年改善心情与获得好成绩。\\n\\n我们不会因为熬夜而更有效率。 就像是生命中的每件事情,需要耗费心力。 如果您什么都不做只是坐着等,不会有什么改变,所以我们要更了解自己的限制与潜能,并将精力作有效的配置,过一个更成功、更快乐的人生。\\n\\n注释:\\n\\n注一、注二:Calculating Loss of Productivity Due to Overtime Using Published Charts — Fact or Fiction\\n\\n注三: [The Secret World of Sleep: The Surprising Science of the Mind at Rest](http://translate.googleusercontent.com/translate_c?depth=1&hl=zh-CN&ie=UTF8&prev=_t&rurl=translate.google.com.hk&sl=zh-CN&tl=zh-CN&u=http://www.amazon.ca/The-Secret-World-Sleep-Surprising/dp/0230107591&usg=ALkJrhgXdjeqJJv7m0F5gnGL2Gv2WMOF_w)\\n\\n注四: [The power of lonely](http://translate.googleusercontent.com/translate_c?depth=1&hl=zh-CN&ie=UTF8&prev=_t&rurl=translate.google.com.hk&sl=zh-CN&tl=zh-CN&u=http://www.boston.com/bostonglobe/ideas/articles/2011/03/06/the_power_of_lonely/&usg=ALkJrhgbCWPtJybh1b8ljBHlC7hyg2yXYA)\",\"mtime\":1444880072000,\"source\":\"source/_posts/想提升工作效率,就別再做这七件事.md\"},\"q727gpkmdnsi7mhn\":{\"_id\":\"q727gpkmdnsi7mhn\",\"content\":\"title: 最常被程序员们谎称读过的计算机书籍\\ndate: 2014-05-14 11:12:05\\n\\n---\\n马克·吐温曾经说过,所谓经典小说,就是指很多人希望读过,但很少人真正花时间去读的小说。这种说法同样适用于“经典”的计算机书籍。\\n\\n在[Stack Overflow](http://stackoverflow.com/)(以及其它很多软件论坛)上,诸如”[程序员最应该读的计算机书籍有哪些?](http://www.aqee.net/what-is-the-single-most-influential-book-every-programmer-should-read/)“这样的问题会周期性的出现。这样的问题不断的被提出、被回答,只是形式不同罢了。相同的几本书总是会出现在清单的前几名内,所以,如果想知道人们谈论的都是些什么,你有必要去读一读这些书的。\\n\\n
\\n\\n大多数程序员真正读过的计算机书籍\\n\\n1. 代码大全(Code Complete)——两届Software Jolt Award震撼大奖得主!\\n2. 程序员修炼之道(The Pragmatic Programmer)\\n3. C程序设计语言( C Programming Language)(第2版)\\n4. 重构:改善既有代码的设计(Refactoring: Improving the Design of Existing Code)\\n5. 人月神话(The Mythical Man-Month)\\n6. 编码——隐匿在计算机软硬件背后的语言(Code: The Hidden Language of Computer Hardware and Software)\\n7. Head First 设计模式(Head First Design Patterns)\\n8. 编程珠玑(Programming Pearls)\\n9. Effective Java中文版(Effective Java (2nd Edition))or Effective C++(第三版)中文版\\n10. Test Driven Development: By Example\\n\\n上面的这些书我自己都读过,所以我不难相信很多不是很优秀的程序员也都读过它们。如果你对编程有足够的兴趣,能够来到这里读这篇博客,你很可能读过 其中的大部分,甚至还有很多不在这个清单中的,所以我就不浪费时间每本书都评论一番了。我想说的是,这个清单上的每本书都是它各自领域里的奇书。所以,很 多有愿望不断提高自己的编程技术的程序员都读过这些书,这就不足为怪了。\\n\\n在人们备受推崇的计算机书籍中,还有一类书受到了独特的待遇。我称下面这个清单为“最常被程序员们谎称读过的计算机书籍”。这并不是说推荐这些书的 人都没有真正读过它们。我只是有相当的信心怀疑更多的人只是在口头上宣称读过下列书籍,而实际上很少人真正读过它们。下面就是这个清单。\\n\\n**最常被程序员们谎称读过的计算机书籍**\\n\\n1. **算法导论(Introduction to Algorithms)(CLRS)** 这本书的名称是所有出版过的计算机书籍中最让人误解一个。它被广泛的使用在很多大学里,通常被当作毕业生必需的算法课程。于是,只要在大学里上过计算机课 程的学生几乎都有一本这样的书。然而,除非你拥有计算机硕士学位(而且是算法研究领域的),我怀疑你顶多只读过算法导论(Introduction to Algorithms)里节选的几章内容。这个书名让人误解,是因为”Introduction”这个词让人以为它很适合初级程序员。实际上不是。这本书对算法做尽可能详尽综合的介绍,就像其它一些随处可见的类似的书一样。请不要再把这本书推荐给初学者。\\n2. **编译原理(Compilers: Principles, Techniques, and Tools)(the Dragon Book)**.这本恐龙封面的书涵盖了开发一个编译器你所需要的全部的知识。它的内容包括词汇分析,语法分析,类型检查,代码优化,以及其它很多高深的题 目。请不要把这本书推荐给初级程序员,他们需要的只是分析简单的包含数学公式或HTML的字符串。除非你真的需要实现一个能够实用的编译器(或解释器), 你根本不需要掌握这本“恐龙”书的全部强大威力。把它推荐给一个遇到简单文本分析问题的人,这证明你根本没有读过它。\\n3. **计算机程序设计艺术(The Art of Computer Programming)(TAOCP)** 我经常听到人们把这本书描述为“每个程序员必读”的系列计算机书籍。我认为这明显不是实情。在我说出这样大不敬的话、被你们用板砖拍死之前,请让我做解释 一下。这不是一本让你一页一页翻着读的书。这是一本参考大全书。把它放在你的书架上看起来会很不错(实际上也它确实很好),但如果想把它通读一遍,你需要 几年时间,而且最后什么都没记住。这并不是说手边放这样一本书没有什么价值。它是一本参考书,当我遇到难题,走投无路时,很多次我都在这本书里找到办法。 但这本书终究是被我当作参考书。它复杂难懂,很理论,里面的例子都是汇编语言的。好的一面是,如果你想在这本书里寻找针对某一问题的解决方案,如果你找不 到,那就说明这个问题无解。它是一本对它所涉及到的领域做了最最详尽介绍的一本书。\\n4. **Design Patterns: Elements of Reusable Object-Oriented Software(Gang of Four)**这本书是唯一一本在这个清单里我从头到尾读过的书,读的结果是,我不知道该把这本书归到哪个类别。它出现在这个清单里,并不是因为我认为只有很少人真正读过它。很多人都读过。只是因为有更多推荐过这本书的人自己却没有读过。Design Patterns这边书的问题在于,很多书里给出的信息,你在其它很多地方都能看到。这样就使得一个初学者在维基百科上读了几篇关于设计模式的内容后,就敢在面试中宣称自己看过这本书。这就是为什么Singleton成 了一种新的全局变量的原因。如果有更多的人花时间读过这本也叫做Gang of Four的书的原著,那世界上就不会有这么多人会把17种设计模式硬塞到一个日志(logging)框架里了。这本书最精彩的部分是每章里描述如何正确的 使用一种模式的段落。遗憾的是,这些精华却在很多其它设计模式资料里被漏掉了。\\n5. **C++程序设计语言(The C++ Programming Language)**这本书不像一本编程教材,更像一本编程语言参考。有很多的迹象表明有人确实读过这本书,否则我们不可能有这么多的C++ 编译器可选择。编程初学者(或者甚至其它语言的专家),如果想学C++,不应该直接去啃C++程序设计语言(The C++ Programming Language)这本书。告诉他们去读《C++ Primer中文版》。\\n\\n正如我之前说的,我知道你们当中会有一些人真正的读过这些书。那这篇文章不是针对你的,针对的是那些企图通过假装读过这些书来表现自己的民众。 如果你自己没有读过这些计算机书籍,请不要推荐给别人。这样做会耽误别人的时间,误人子弟,因为一些阅历更丰富的人可能会有更好的书(更针对某一领域,更容易理解,跟某种编程语言或某种编程水平更契合的书)来推荐。除此之外,你也能避免被那些真正读过计算机程序设计艺术(The Art of Computer Programming)的人用MMIX知识给拷问住造成的尴尬(如果你不知道我在说什么,那我指的就是你)。\\n\\n[本文英文原文链接:[Books Programmers Claim to Have Read](http://www.billthelizard.com/2008/12/books-programmers-dont-really-read.html) ]\",\"mtime\":1444880072000,\"source\":\"source/_posts/最常被程序员们谎称读过的计算机书籍.md\"},\"fjhsm5erq165h07k\":{\"_id\":\"fjhsm5erq165h07k\",\"content\":\"title: Vim学习指南\\ndate: 2014-05-14 13:38:38\\n\\n---\\n英文原文:[Learn Vim Progressively](http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/)\\n\\n你想尽可能快地自学vim(为大家所熟知的最好的编辑器) 。这是我学习的方法:从细处入手然后慢慢掌握所有技巧。\\n\\n
\\n\\nVim 六十亿美元的编辑器\\n\\n>设计优良,强壮,快速。\\n\\n学习vim并把它作为你的下一个文本编辑器,据我所知没有比它更好的编辑器,学起来难但出奇的好用。\\n\\n我建议你按下附4个步骤学习Vim:\\n\\n1. 适应\\n2. 感觉舒适\\n3. 感觉很好,强壮,快速\\n4. 使用vim的高级技能\\n\\n本课程结束时你将成为一个vim高手。\\n\\n但在我们开始之前,先提醒一下。刚开始学习vim会有点痛苦,它很费时间,有点像玩乐器一样。不要指望能像其他编辑器一样花3天以内的时间便能很好的使用它,事实上,这肯定要花2周而不是3天。\\n\\n第一层次 – 适应\\n===\\n\\n1. 安装vim\\n2. 运行vim\\n3. 什么也别做,读!\\n\\n在标准的编辑器里,写一些东西到屏幕上只需要在键盘上输入就足够了,在Vim中不是这样。Vim在普通模式里,让我们转到插入模式,键入字符i。\\n\\n你应该感觉好一点了,能像在标准编辑器里输入了,回到普通模式只需按ESC键即可。\\n\\n你现在知道了如何在插入和普通模式之间转换,下面是你在普通模式下需要记住的指令:\\n\\n>\\n* i→ 插入模式. 按ESC键返回普通模式\\n* x→ 删除光标所在的字符\\n* :wq→ 保存退出(:w 保存,:q 退出)\\n* dd→ 删除(拷贝)当前行\\n* p→ 粘贴\\n>\\n推荐\\n>\\n* hjkl(高度推荐) → 基本的光标移动 (←↓↑→), hj按行移动。\\n* :help
→ 显示关于
的帮助,可以使用help不带
获得常规帮助。\\n\\n只有5个命令,一开始只需掌握这些,当适应了这些命令后(大概需要一天或更多的诗句),你可以转到第二层级了。\\n\\n但首先,稍回顾一下普通模式。在标准编辑器里,要复制的话你需要使用ctrl键(Ctrl-c)。实际上,当你按\\n下ctrl键时所有的按键的意义都改变了。在vim普通模式下就像在ctrl键自动按下的情况下使用编辑器。\\n\\n最后的补充:\\n\\n* 我使用
代替Ctrl-λ\\n* 命令后附带回车,比如我若是写:q,我是指:q
\\n\\n第二层 - 爽一把\\n===\\n\\n你懂的,命令是由需求所定。是时候学习更多命令了。下面这些是我的建议:\\n\\n1. 插入模式变种命令:\\n>\\n\\t* a→ 在光标后插入\\n\\t* o→ 在当前行之后插入新行\\n\\t* O→ 在当前行之前插入新行\\n\\t* cw→ 替换从光标到单词结束\\n2. 基本移动命令\\n>\\n\\t* 0→ 跳到第一列\\n\\t* ^→ 跳到本行第一个非空字符\\n\\t* $→ 跳到本行末尾\\n\\t* g_→ 跳到本行最后一个非空字符\\n\\t* /pattern→ 搜索pattern\\n3. 复制/粘贴\\n>\\n\\t* P→ 在当前位置之前粘贴,记住 p 是在当前位置之前粘贴.\\n\\t* yy→ 复制当前行,与dd和P命令相比更简单。\\n4. 取消/恢复\\n>\\n\\t* u→ 取消\\n\\t*
→ 恢复\\n5. 加载/保存/退出/修改 文件(缓存)\\n>\\n\\t* :e → 打开\\n\\t* :w→ 保存\\n\\t* :saveas → 保存到这个文件\\n\\t* :x,ZZ或者:wq→ 保存和退出 (:x 如果可能的话,只保存)\\n\\t* :q!→ 退出但不保存,使用:qa!,即使在缓存中还有已经修改的也会退出。\\n\\t* :bn(对比:bp) → 显示下一个(上一个)文件缓存\\n\\n花些时间学习所有这些命令。一旦完成后,你应该就能完成在像在其它编辑器中所有事情。也许,你可能还会觉得有点笨手笨脚。但是跟着我进入下一个层次,你就会发现为什么vim值得你额外付出的努力。\\n\\n第三层次 - 更好,更强,更快\\n===\\n\\n恭喜进入第三层次!现在我们开始一些有趣的事。在第三层次中,我们只会谈论那些和vi编辑器兼容的命令\\n\\n**更好**\\n\\n让我们看看vim是如何帮助我们做重复工作的:\\n\\n1. .→ (点)会重做最后一个命令,\\n2. N → 会重做这个命令N次.\\n\\n一些例子,打开文件然后输入:\\n\\n>\\n* 2dd→ 会删除2行\\n* 3p→ 会粘贴文本3次\\n* 100idesu [ESC]→ 会写入“desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu”\\n* .→ 在最后一个命令之后会在此写入100个\\\"desu\\\".\\n* 3.→ 会写入3个“desu”(并不是300个, 聪明极了).\\n\\n**更强**\\n\\n掌握高效率使用vim是十分重要的。请不要跳过这节。\\n\\n1. NG→ 跳到第N行\\n2. gg→ 是1G的快捷方式 - 跳到文件的开始\\n3. G→ 跳到最后一行\\n4. 单词间移动:\\n\\n>\\n1. w→ 跳到下一个单词的开头,\\n2. e→ 跳到这个单词的末尾.\\n>\\n默认,单词是有字母和下划线组成。这里我们所谓的单词就是由空格分隔的。如果你只想考虑单词,就使用大写的字符吧:\\n>\\n1. W→跳到下一个单词的开头,\\n2. E→ 跳到这个单词的末尾.\\n>\\n\\n\\n现在,我们谈论高效率移动:\\n\\n>\\n* %: 跳到对应的(, (, [ 处.\\n* *(对比#) : 跳到当前光标的下一个(上一个) 相同单词的地方\\n\\n信我,最后3个命令价比黄金。\\n\\n**更快**\\n\\n记住了vi移动的重要性?这里就是原因。大多数命令使用下面这种通用格式:\\n\\n\\n\\n例如: 0y$意味着\\n\\n* 0→ 跳到本行开头\\n* y→ 从这里开始复制\\n* $→ 直到本行结束\\n\\n我们也可以使用ye,从当前位置复制到单词的末尾。但是y2/foo 会一直复制到第二个foo出现的地方。\\n\\n但对于y(复制), d(删除), v(visual 选择), gU(大写), gu(小写)等等命令都是真实的。\\n\\n第四层次 – Vim 超能量\\n===\\n\\n使用上述的命令,是否觉得很爽了。但是现在,这里才是杀手锏。正因为其中有些特性,我才使用vim的。\\n\\n**在当前行移动:0^$g_fFtT,;**\\n\\n>\\n* 0→ 跳到第一列\\n* ^→ 跳到当前行的第一个字符\\n* $→ 跳到最后一列\\n* g_→ 跳到这行的最后一个字符\\n* fa→ 跳到这行a字母的下一个出现的地方。(对比 ; )会查找下一个(上一个)地方\\n* t,→ 跳到,字符的前一个字符.\\n* 3fa→ 在这行中查找a出现的第三个位置.\\n* F 和 T→ 与f和t相似, 但是方向相反.\\n>\\n\\n\\n一个有用的提示:dt\\\"→删除所有的直到”。\\n\\n**区域选择 : a 或 i**\\n\\n这些命令仅仅能用在可视化模式的一个操作后。但是他们相当的有用。他们的主要模式有:\\n\\na 和 i\\n\\n命令可以是任何命令,例如 , d(删除),y(拉取),v(进入可视化模式)。对象可以是: w一个单词,W一个单词 (扩展的),s一个句子,p一个段落。而且也可以是普通的字符, 例如\\\",',),},]。\\n\\n假设现在光标在 (map (+) (\\\"foo\\\")) 这个字符串的第一个o上。\\n\\n>\\n* vi\\\"→ 将会选择 foo\\n* va\\\"→ 将会选择 \\\"foo\\\"\\n* vi)→ 将会选择 \\\"foo\\\"\\n* va)→ 将会选择 (\\\"foo\\\")\\n* v2i)→ 将会选择 map (+) (\\\"foo\\\")\\n* v2a)→ 将会选择 (map (+) (\\\"foo\\\"))\\n>\\n\\n\\n**矩形选择块:
**\\n\\n矩形选择块能够方便的对多行代码进行注释。 如:0
I-- [ESC]\\n\\n>\\n* ^→ 将光标定位到这行第一个非空格字符\\n*
→ 选择开始位置\\n*
→ 向下移动 (也可使用 jjj 或者 % , 其他…)\\n* I-- [ESC]→ 用 -- 来注释每一行\\n>\\n\\n\\n提示:如果你的剪贴板在Windows中是非空的话,那么你就不得不使用
来代替
。\\n\\n**实现:
和
.**\\n\\n在插入模式中:只要打出单词的首字母,然后按
,神奇的事情就发生了...\\n\\n\\n\\n**宏命令 : qa 做的一些事,@a,@@**\\n\\nqa 把你的动作记录在了寄存器 a 中。然后@a 就会将存在寄存器中的动作重现,就好像你从新打了一边。@@是将你最后一次执行的宏命令再次执行的快捷键。\\n\\n>\\n事例\\n>\\n在只有数字1的一行上, 像这样键入命令:\\n>\\n* qaYp
q→\\n\\t* qa开始记录。\\n\\t* Yp记录这一行。\\n\\t*
增加数字大小。\\n\\t* q停止记录。\\n* @a→ 在1下面写2\\n* @@→ 在2下面写3\\n* 现在键入 100@@ 就将会产生一个到 103 的自增列。\\n>\\n\\n\\n**可视化模式选择: v, V,
**\\n\\n我们已经看到过
例子,这里还有v和V。一旦已经选择好了,你可以:\\n\\n>\\n* J→ 让所有行连接在一起\\n* ) → 向左(右)对齐.\\n* =→ 自动对齐\\n>\\n\\n\\n在可视化模式所选择的行的末尾添加一些东西:\\n\\n>\\n*
\\n* 跳到目的行 (jjj 或者
或者 /patternor%等等…)\\n* $跳到行末尾\\n* A, 写入一些文本,[按] ESC 键.\\n>\\n\\n\\n**分割:: split 和 vsplit**\\n\\n这些是很重要的命令,所以你应该在:help split看看。\\n\\n>\\n* :split→ 创建一个水平分割窗口t (:vsplit 创建一个竖直分割窗口)\\n*
: dir是hjklor←↓↑→中任何一个。用来切换分割窗口。\\n*
_(对比
|) : 用来最大化水平(竖直)分割窗口的大小\\n*
+(对比
-) : 增加 (减小) 分割窗口\\n>\\n\\n\\n小结\\n===\\n\\n这些是我经常使用的90%的命令。我建议你每天学习一到两个命令。在两到三周之后,你将会感觉到vim在你手上使用的是如此的强大。\\n\\n学习vim更多的是记忆训练。值得高兴的是vim诞生了一些非常好的工具和优秀的文档。当你非常熟悉大部分基本命令之后就可以使用vimtutor了。另外,你应该仔细的阅读这页 ::help usr_02.txt.\\n\\n然后,你要去学习了解寄存器,插件已经其它的功能。学习vim就像学钢琴一样,所有的都要掌握好。\",\"mtime\":1444880072000,\"source\":\"source/_posts/vim学习指南.md\"},\"4585fmuxcsd4fyqu\":{\"_id\":\"4585fmuxcsd4fyqu\",\"content\":\"title: 漫画:如果用编程语言来写作文\\ndate: 2014-05-14 16:02:40\\n\\n---\\n如果说这世界上有一种东西太多了,那它就是广告。如果说这世界上还有另外一种东西太多了,那就是对软件开发的类比。\\n\\n
\\n\\n这是《软件开发如同越狱》这篇文章里的一段话,说的不假,人们喜欢拿编程语言说事儿,但可惜这篇文章的这个类比的并不形象。不过也有好的类比,比如《如果编程语言是一条船… 》,这篇文章就很有意思,而且还配了图。今天的这个漫画书有点纯搞笑的意思了,用编程语言来写作文,会写出什么效果?看看老师的反应就知道了。\\n\\n\",\"mtime\":1444880072000,\"source\":\"source/_posts/漫画:如果用编程语言来写作文.md\"},\"js7icghie9xyacqx\":{\"_id\":\"js7icghie9xyacqx\",\"content\":\"title: '爹地,我找到了! -- 15个极好的Linux find命令示例'\\ndate: 2014-05-14 14:34:41\\n\\n---\\n前阵子,我们审查了15件实事 find命令的例子(第一部分)。查找命令可以做很多比只是在寻找基于名称的文件 (第2部分)在这篇文章中,让我们来讨论15高级find命令的例子, 包括-根据它访问,修改或改变的时间查找文件,查找文件相比之下,执行操作找到的文件等, 拉梅什纳塔拉詹:这是我的照片中的可爱的小女儿。她很高兴地发现在加州长滩水族馆海狮。 \\n\\n**基于访问/修改/更改时间查找文件**\\n\\n你可以找到基于以下三个文件的时间属性的文件。\\n\\n* 访问时间的文件。文件访问时,访问时间得到更新。\\n* 的文件的修改时间。文件内容修改时,修改时间得到更新。\\n* 更改文件的时间。更改时间时,被更新的inode数据的变化。\\n\\n在下面的例子中,min选项之间的差异和时间选项是参数。\\n\\n* 分论点将它的参数为分钟。例如,60分钟(1小时)= 60分钟。\\n* 时间参数,将它的参数为24小时。例如,时间2 = 2 * 24小时(2天)。\\n* 虽然这样做的24个小时计算,小数部分都将被忽略,所以25小时为24小时,和47小时取为24小时,仅48小时为48小时。要获得更清晰的参考atime的部分find命令的手册页。\\n\\n
\\n\\n**例1:找到在1个小时内被更改的文件**\\n\\n想要通过文件修改时间找出文件,可以使用参数 -mmin -mtime。下面是man手册中有关mmin和mtime的定义。\\n\\n* -mmin n 文件最后一次修改是在n分钟之内\\n* -mtime n 文件最后一次修改是在 n*24小时之内(译者注:也就是n天了呗)\\n\\n执行下面例子中的命令,将会找到当前目录以及其子目录下,最近一次修改时间在1个小时(60分钟)之内的文件或目录\\n\\n\\t# find . -mmin -60\\n\\n同样的方式,执行下面例子中的命令,将会找到24小时(1天)内修改了的文件(文件系统根目录 / 下)\\n\\n\\t# find / -mtime -1\\n\\n**例2:找到1个小时内被访问过的文件**\\n\\n想要通过文件访问时间找出文件,可以使用参数 -amin -atime。下面是man手册中有关amin和atime的定义。\\n\\n* -amin n 文件最后一次访问是在n分钟之内\\n* -atime n 文件最后一次访问是在 n*24小时之内\\n\\n执行下面例子中的命令,将会找到当前目录以及其子目录下,最近一次访问时间在1个小时(60分钟)之内的文件或目录\\n\\n\\t# find . -amin -60\\n\\n同样的方式,执行下面例子中的命令,将会找到24小时(1天)内被访问了的文件(文件系统根目录 / 下)\\n\\n\\t# find / -atime -1\\n\\n**例3:查找一个小时内状态被改变的文件**\\n\\n(译者注:这里的改变更第1个例子的更改文件内容时间是不同概念,这里是更改的是文件inode的数据,比如文件的权限,所属人等等信息)\\n\\n要查找文件的inode的更改时间,使用-cmin和-ctime选项\\n\\n* -cmin n 文件的状态在n分钟内被改变\\n* -ctime n 文件状态在n*24小时内(也就是n天内)被改变\\n\\n(译者注:如果上面的n为-n形式,则表示n分钟/天之内,n为+n则表示n分钟/天之前)\\n\\n下面的例子在当前目录和其子目录下面查找一个小时内文件状态改变的文件(也就是60分钟内):\\n\\n\\t# find . -cmin -60\\n\\n同样的道理,下面的例子在根目录/及其子目录下一天内(24小时内)文件状态被改变的文件列表:\\n\\n\\t# find / -ctime -1\\n\\n**例4:搜索仅仅限定于文件,不显示文件夹**\\n\\n上面的例子搜索出来不仅仅有文件,还会显示文件夹。因为当一个文件被访问的时候,它所处的文件夹也会被访问,如果你对文件夹不感兴趣,那么可以使用 -type f 选项\\n\\n下面的例子会显示30分钟内被修改过的文件,文件夹不显示:\\n\\n\\t# find /etc/sysconfig -amin -30\\n\\t.\\n\\t./console\\n\\t./network-scripts\\n\\t./i18n\\n\\t./rhn\\n\\t./rhn/clientCaps.d\\n\\t./networking\\n\\t./networking/profiles\\n\\t./networking/profiles/default\\n\\t./networking/profiles/default/resolv.conf\\n\\t./networking/profiles/default/hosts\\n\\t./networking/devices\\n\\t./apm-scripts\\n\\t[注: 上面的输出包含了文件和文件夹]\\n\\t\\n\\t# find /etc/sysconfig -amin -30 -type f\\n\\t./i18n\\n\\t./networking/profiles/default/resolv.conf\\n\\t./networking/profiles/default/hosts\\n\\t[注: 上面的输出仅仅包含文件]\\n\\n**例5: 仅仅查找非隐藏的文件(不显示隐藏文件):**\\n\\n如果我们查找的时候不想隐藏文件也显示出来,可以使用下面的正则式查找:\\n\\n下面的命令会显示当前目录及其子目录下15分钟内文件内容被修改过的文件,并且只列出非隐藏文件。也就是说,以.开头的文件时不会显示出来的\\n\\n\\t# find . -mmin -15 \\\\( ! -regex \\\".*/\\\\..*\\\" \\\\)\\n\\n基于文件比较的查找命令\\n\\n我们平时通过更别的东西进行比较,会更容易记住一些事情。比如说我想找出在我编辑test文件之后编辑过的文件。你可以通过test这个文件的编辑时间作为比较基准去查找之后编辑过的文件:\\n\\n**例6: 查找文件修改时间在某一文件修改后的文件:**\\n\\n\\t语法: find -newer FILE\\n\\n下面的例子显示在/etc/passwd修改之后被修改过的文件。对于系统管理员,想知道你新增了一个用户后去跟踪系统的活动状态是很有帮助的(万一那新用户不老实,一上来就乱搞,你很快就知道了 ^_^):\\n\\n\\t# find -newer /etc/passwd\\n\\n**例7:查找文件访问时间在某一文件的修改时间之后的文件:**\\n\\n\\t# find -newer /etc/passwd\\n\\n下面的例子显示所有在/etc/hosts文件被修改后被访问到的文件。如果你新增了一个主机/端口记录在/etc/hosts文件中,你很可能很想知道在那之后有什么文件被访问到了,下面是这个命令:\\n\\n\\t# find -anewer /etc/hosts\\n\\n**例8:查找状态改变时间在某个文件修改时间之后的文件:**\\n\\n\\t语法: find -cnewer FILE\\n\\n下面的例子显示在修改文件/etc/fstab之后所有文件状态改变过的文件。如果你在/etc/fstab新增了一个挂载点,你很可能想知道之后哪些文件的状态发生了改变,这时候你可以使用如下命令:\\n\\n\\t# find -cnewer /etc/fstab\\n\\n在查找到的文件列表结果上直接执行命令:\\n\\n这之前你已经看到了如果通过find命令去查找各种条件的文件列表。如果你对这些find命令还不熟悉,我建议你看完上面的第一部分\\n\\n接下来这部分我们向你介绍如果在find命令上执行各种不同的命令,也就是说如何去操作find命令查找出来的文件列表。\\n\\n我们能在find命令查找出来的文件名列表上指定任意的操作:\\n\\n\\t# find
-exec
\\\\;\\n\\n其中的OPERATION可以是任意的命令,下面列举一下比较常用的:\\n\\n* rm 命令,用于删除find查找出来的文件\\n* mv 命令,用于重命名查找出的文件\\n* ls -l 命令,显示查找出的文件的详细信息\\n* md5sum, 对查找出的文件进行md5sum运算,可以获得一个字符串,用于检测文件内容的合法性\\n* wc 命令,用于统计计算文件的单词数量,文件大小等待\\n* 执行任何Unix的shell命令\\n* 执行你自己写的shell脚本,参数就是每个查找出来的文件名\\n\\n**例9:在find命令输出上使用 ls -l, 列举出1小时内被编辑过的文件的详细信息**\\n\\n\\t# find -mmin -60\\n\\t./cron\\n\\t./secure\\n\\t\\n\\t# find -mmin -60 -exec ls -l {} \\\\;\\n\\t-rw------- 1 root root 1028 Jun 21 15:01 ./cron\\n\\t-rw------- 1 root root 831752 Jun 21 15:42 ./secure\\n\\n**例10:仅仅在当前文件系统中搜索**\\n\\n系统管理员有时候仅仅想在/挂载的文件系统分区上搜索,而不想去搜索其他的挂载分区,比如/home/挂载分区。如果你有多个分区被挂载了,你想在/下搜索,一般可以按下面的这样做\\n\\n下面这个命令会搜索根目录/及其子目录下所有.log结尾的文件名。如果你有多个分区在/下面,那么这个搜索会去搜索所有的被挂载的分区:\\n\\n\\t# find / -name \\\"*.log\\\"\\n\\n如果我们使用-xdev选项,那么仅仅会在在当前文件系统中搜索,下面是在xdev的man page上面找到的一段-xdev的定义:\\n\\n* -xdev Don’t descend directories on other filesystems.\\n\\n下面的命令会在/目录及其子目录下搜索当前文件系统(也就是/挂载的文件系统)中所有以.log结尾的文件,也就是说如果你有多个分区挂载在/下面,下面的搜索不会去搜索其他的分区的(比如/home/)\\n\\n\\t# find / -xdev -name \\\"*.log\\\"\\n\\n**例11: 在同一个命令中使用多个{}**\\n\\nlinux手册说命令中只能使用一个{},不过你可以像下面这样在同一个命令中使用多个{}\\n\\n\\t# find -name \\\"*.txt\\\" cp {} {}.bkup \\\\;\\n\\n注意,在同一个命令中使用这个{}是可以的,但是在不同的命令里就不行了,也就是说,如果你想象下面这样重命名文件是行不通的\\n\\n\\tfind -name \\\"*.txt\\\" -exec mv {} `basename {} .htm`.html \\\\;\\n\\n**例12: 使用多个{}实例**\\n\\n你可以像下面这样写一个shell脚本去模拟上面那个重命名的例子\\n\\n\\t# mv \\\"$1\\\" \\\"`basename \\\"$1\\\" .htm`.html\\\"\\n\\n上面的双引号是为了防止文件名中出现的空格,不加的话会有问题。然后你把这个shell脚本保存为mv.sh,你可以像下面这样使用find命令了\\n\\n\\tfind -name \\\"*.html\\\" -exec ./mv.sh '{}' \\\\;\\n\\n所以,任何情况下你在find命令执行中想使用同一个文件名多次的话,先写一个脚本,然后在find中通过-exec执行这个脚本,把文件名参数传递进去就行,这是最简单的办法\\n\\n**例13: 将错误重定向到/dev/nul**\\n\\n重定向错误输出一般不是什么好的想法。一个有经验的程序员懂得在终端显示错误并及时修正它是很重要的。\\n\\n尤其是在find命令中重定向错误不是个好的实践。 但是如果你确实不想看到那些烦人的错误,想把错误都重定向到null设备中(也就是linux上的黑洞装置,任何丢进去的东西消失的无影无踪了)。你可以像下面这样做\\n\\n\\tfind -name \\\"*.txt\\\" 2>>/dev/null\\n\\n有时候这是很有用的。比如,如果你想通过你自己的账号在/目录下查找所有的*.conf文件,你会得到很多很多的\\\"Permission denied\\\"的错误消息, 就像下面这样:\\n\\n\\t$ find / -name \\\"*.conf\\\"\\n\\t/sbin/generate-modprobe.conf\\n\\tfind: /tmp/orbit-root: Permission denied\\n\\tfind: /tmp/ssh-gccBMp5019: Permission denied\\n\\tfind: /tmp/keyring-5iqiGo: Permission denied\\n\\tfind: /var/log/httpd: Permission denied\\n\\tfind: /var/log/ppp: Permission denied\\n\\t/boot/grub/grub.conf\\n\\tfind: /var/log/audit: Permission denied\\n\\tfind: /var/log/squid: Permission denied\\n\\tfind: /var/log/samba: Permission denied\\n\\tfind: /var/cache/alchemist/printconf.rpm/wm: Permission denied\\n\\t[Note: There are two valid *.conf files burned in the \\\"Permission denied\\\" messages]\\n\\n你说烦人不?所以,如果你只想看到find命令真实的查找结果而不是这些\\\"Permission denied\\\"错误消息,你可以将这些错误消息重定向到/dev/null中去\\n\\n\\t$ find / -name \\\"*.conf\\\" 2>>/dev/null\\n\\t/sbin/generate-modprobe.conf\\n\\t/boot/grub/grub.conf\\n\\t[Note: All the \\\"Permission denied\\\" messages are not displayed]\\n\\n**例14: 将文件名中的空格换成下划线**\\n\\n你从网上下载下来的音频文件的文件名很多都带有空格。但是带有空格的文件名在linux(类Unix)系统里面是很不好的。你可以使用find然后后面加上rename命令的替换功能去重命名这些文件,将空格转换成下划线\\n\\n下面显示怎样将所有mp3文件的文件名中的空格换成_\\n\\n\\t$ find . -type f -iname “*.mp3″ -exec rename “s/ /_/g” {} \\\\;\\n\\n**例15: 在find结果中同时执行两条命令**\\n\\n在find的man page页面中,下面是一次文件查找遍历中使用两条命令的语法举例\\n\\n下面的find命令的例子,遍历文件系统一次,列出拥有setuid属性的文件和目录,写入/root/suid.txt文件, 如果文件大小超过100M,将其记录到/root/big.txt中\\n\\n\\t# find / \\\\( -perm -4000 -fprintf /root/suid.txt '%#m %u %p\\\\n' \\\\) , \\\\\\n\\t\\\\( -size +100M -fprintf /root/big.txt '%-10s %p\\\\n' \\\\)\\n\",\"mtime\":1444880072000,\"source\":\"source/_posts/爹地,我找到了!----15个极好的linux-find命令示例.md\"},\"rhmob0xtnp3mikhx\":{\"_id\":\"rhmob0xtnp3mikhx\",\"content\":\"title: 转载:hexo你的博客\\ndate: 2014-05-07 14:09:48\\n\\n\\n---\\n原文链接:
\\n\\n一直纠结于哪里写博客,曾用过新浪博客,百度空间,JavaEye,LOFTER,OSChina,cnblogs,CSDN,自搭WordPress,都不满意。再后来也弄过Jekyll和Octopress,觉累不爱。\\n\\n直到多看了一眼hexo,这个逼格极高的程序猿写作方式,我喜欢。就连hexo的发音都像是**黑客哦**!如果你跟我一样纠结哪里写博,那就来到GitHub吧,让我们一起hexo!H人希绝对不会让你失望,相信很快hexo就会流行起来。\\n\\nhexo出自台湾大学生[tommy351](http://twitter.com/tommy351)之手,是一个基于Node.js的静态博客程序,其编译上百篇文字只需要几秒。hexo生成的静态网页可以直接放到GitHub Pages,BAE,SAE等平台上。先看看tommy是如何吐槽Octopress的 →_→ [Hexo颯爽登場](http://zespia.tw/blog/2012/10/11/hexo-debut)。\\n\\n
\\n\\n* *如果你对默认配置满意,只需几个命令便可秒搭一个hexo。*\\n* *如果你跟我一样喜欢折腾下,30分钟也足够个性化。*\\n* *如果你过于喜欢折腾,可以折腾个把星期,尽情的玩。* \\n\\n搭建过程你或许觉得有那么点小繁琐,但一旦搭建完成,写文章是极简单,极舒服的。\\n\\n只需要几个简单命令,你就可以完成一切。\\n\\n\\thexo n #写文章\\n\\thexo g #生成\\n\\thexo d #部署 # 可与hexo g合并为 hexo d -g\\n\\n下面逐步介绍,进入正题。\\n\\n
\\n\\n***\\n\\n环境准备\\n=======\\n\\n**安装Node**\\n\\n到[Node.js](http://nodejs.org/)官网下载相应平台的[最新版本](http://nodejs.org/download),一路安装即可。我用的是[node-v0.10.22-x86.msi](http://nodejs.org/dist/v0.10.22/node-v0.10.22-x86.msi)\\n\\n**安装Git**\\n\\nGit的客户端很多,我用的是[msysgit](http://code.google.com/p/msysgit),喜欢用绿色版本[Portable application for official Git for Windows 1.8.4](http://code.google.com/p/msysgit/downloads/detail?name=PortableGit-1.8.4-preview20130916.7z),下载下来设置一下环境变量即可,Git_HOME,%Git_HOME%\\\\bin之类的,不多说。\\n\\n**安装Sublime(可选)**\\n\\n[Sublime Text 2](http://www.sublimetext.com/)在这里仅仅作为一个文本编辑器使用,支持各种编程语言和文件格式,当然也支持Markdown语法,实在是个不可多得的练码奇才。喜欢追鲜的也可以尝试处于beta版本的[Sublime Text 3](http://www.sublimetext.com/3)。我用绿色版本[Portable Sublime Text 2.0.2.zip](http://c758482.r82.cf2.rackcdn.com/Sublime%20Text%202.0.2.zip)。\\n\\n> 本屌是穷码畜,对于高大上的Mac码帅用户请移步:[hexo installation](http://zespia.tw/hexo/docs/installation.html)\\n\\nGitHub\\n======\\n\\n> GitHub账号和GitHub Pages 一般都应该有吧,已有的请自动无视这一部分。\\n\\n* 首先注册一个『GitHub』帐号,已有的默认默认请忽略\\n* 建立与你用户名对应的仓库,仓库名必须为『your_user_name.github.com』\\n* 添加SSH公钥到『Account settings -> SSH Keys -> Add SSH Key』\\n\\n\\n\\n前两步忽略,只说第三步,**添加SSH-Key**。\\n\\n首先设置你的用户名密码:\\n\\n\\tgit config --global user.email \\\"bu.ru@qq.com\\\"\\n\\tgit config --global user.name \\\"bruce-sha\\\"\\n\\n生成密钥:\\n\\n\\tssh-keygen -t rsa -C \\\"bu.ru@qq.com\\\"\\n\\n输入文件路径:\\n\\n\\tH:\\\\hexo\\\\blog>ssh-keygen -t rsa -C \\\"bu.ru@qq.com\\\"\\n\\tGenerating public/private rsa key pair.\\n\\tEnter file in which to save the key (//.ssh/id_rsa): H:\\\\git\\\\myssh\\\\ssh\\n\\tEnter passphrase (empty for no passphrase):\\n\\tEnter same passphrase again:\\n\\tYour identification has been saved in H:\\\\git\\\\myssh\\\\ssh.\\n\\tYour public key has been saved in H:\\\\git\\\\myssh\\\\ssh.pub.\\n\\tThe key fingerprint is:\\n\\tb0:0c:2e:67:33:ab:c1:50:10:40:0a:ba:c1:80:59:22 bu.ru@qq.com\\n\\n> 有个bug,文件路径中的盘符H必须大写,否则会报错。\\n\\n上述命令若执行成功,会在H:\\\\git\\\\myssh目录下生成两个文件id_rsa和id_rsa.pub,最后两步:\\n\\n1. 用文本编辑器打开ssh.pub文件,拷贝其中的内容,将其添加到[Add SSH Key](http://github.com/settings/ssh)\\n2. 将id_rsa和id_rsa.pub拷贝至你Git安装目录下的.ssh目录,如H:\\\\PortableGit-1.8.4\\\\.ssh\\n\\n\\n\\n最后可以验证一下:\\n\\n\\tssh -T git@github.com\\n\\t\\n若有问题,请重新设置。常见错误请参考:\\n\\n[GitHub Help - Generating SSH Keys](http://help.github.com/articles/generating-ssh-keys)\\n\\n[GitHub Help - Error Permission denied (publickey)](http://help.github.com/articles/error-permission-denied-publickey)\\n\\n安装\\n====\\n\\nNode和Git都安装好后,可执行如下命令安装hexo:\\n\\n\\tnpm install -g hexo\\n\\t\\n初始化\\n=====\\n\\n然后,执行init命令初始化hexo到你指定的目录:\\n\\n\\thexo init
\\n\\n> 也可以cd到目标目录,执行hexo init。\\n\\n好啦,至此,全部安装工作已经完成!\\n\\n生成静态页面\\n=====\\n\\ncd 到你的init目录,执行如下命令,生成静态页面至hexo\\\\public\\\\目录。\\n\\n\\thexo generate\\n\\t\\n> 命令必须在init目录下执行,否则不成功,但是也不报错。\\n\\n> 当你修改文章Tag或内容,不能正确重新生成内容,可以删除hexo\\\\db.json后重试,还不行就到public目录删除对应的文件,重新生成。\\n\\n本地启动\\n====\\n\\n执行如下命令,启动本地服务,进行文章预览调试。\\n\\n\\thexo server\\n\\t\\n浏览器输入[http://localhost:4000](http://localhost:4000/)就可以看到效果。\\n\\n> 请使用高级浏览器,否则可能…你懂的!\\n\\n写文章\\n====\\n\\n执行new命令,生成指定名称的文章至hexo\\\\source\\\\_posts\\\\postName.md。\\n\\n\\thexo new [layout] \\\"postName\\\" #新建文章\\n\\t\\n其中layout是可选参数,默认值为post。有哪些layout呢,请到scaffolds目录下查看,这些文件名称就是layout名称。当然你可以添加自己的layout,方法就是添加一个文件即可,同时你也可以编辑现有的layout,比如post的layout默认是hexo\\\\scaffolds\\\\post.md\\n\\n\\ttitle: { { title } }\\n\\tdate: { { date } }\\n\\ttags:\\n\\t---\\n\\n> 请注意,大括号与大括号之间我多加了个空格,否则会被转义,不能正常显示。\\n\\n我想添加categories,以免每次手工输入,只需要修改这个文件添加一行,如下:\\n\\n\\ttitle: { { title } }\\n\\tdate: { { date } }\\n\\tcategories: \\n\\ttags: \\n\\t---\\n\\npostName是md文件的名字,同时也出现在你文章的URL中,postName如果包含空格,必须用\\\"将其包围,postName可以为中文。\\n\\n> 注意,所有文件:后面都必须有个空格,不然会报错。\\n\\n看一下刚才生成的文件hexo\\\\source\\\\_posts\\\\postName.md,内容如下:\\n\\n\\ttitle: postName #文章页面上的显示名称,可以任意修改,不会出现在URL中\\n\\tdate: 2013-12-02 15:30:16 #文章生成时间,一般不改,当然也可以任意修改\\n\\tcategories: #文章分类目录,可以为空,注意:后面有个空格\\n\\ttags: #文章标签,可空,多标签请用格式[tag1,tag2,tag3],注意:后面有个空格\\n\\t---\\n\\t这里开始使用markdown格式输入你的正文。\\n\\n接下来,你就可以用喜爱的编辑器尽情书写你的文章。关于markdown语法,可以参考我的文章[Markdown简明语法](http://bruce-sha.github.io/2013/11/26/markdown/)。\\n\\n**fancybox**\\n\\n可能有人对这个[Reading](http://bruce-sha.github.io/reading/)页面中图片的[fancybox](http://fancyapps.com/fancybox/)效果感兴趣,这个是怎么做的呢。\\n\\n很简单,只需要在你的文章*.md文件的头上添加photos项即可,然后一行行添加你要展示的照片:\\n\\n\\tlayout: photo\\n\\ttitle: 我的阅历\\n\\tdate: 2085-01-16 07:33:44\\n\\ttags: [hexo]\\n\\tphotos:\\n\\t- http://bruce.u.qiniudn.com/2013/11/27/reading/photos-0.jpg\\n\\t- http://bruce.u.qiniudn.com/2013/11/27/reading/photos-1.jpg\\n\\n> 经过测试,文件头上的layout: photo可以省略。\\n\\n不想每次都手动添加怎么办?同样的,打开您的hexo\\\\scaffolds\\\\photo.md\\n\\n\\tlayout: { { layout } }\\n\\ttitle: { { title } }\\n\\tdate: { { date } }\\n\\ttags: \\n\\tphotos: \\n\\t- \\n\\t---\\n\\n然后每次可以执行带layout的new命令生成照片文章:\\n\\n\\thexo new photo \\\"photoPostName\\\" #新建照片文章\\n\\t\\n**description**\\n\\nmarkdown文件头中也可以添加description,以覆盖全局配置文件中的description内容,请参考下文_config.yml的介绍。\\n\\n\\ttitle: hexo你的博客\\n\\tdate: 2013-11-22 17:11:54\\n\\tcategories: default\\n\\ttags: [hexo]\\n\\tdescription: 你对本页的描述\\n\\t---\\n\\n> hexo默认会处理全部markdown和html文件,如果不想让hexo处理你的文件,可以在文件头中加入layout: false。\\n\\n**文章摘要**\\n\\n在需要显示摘要的地方添加如下代码即可:\\n\\n\\t以上是摘要\\n\\t
\\n\\t以下是余下全文\\n\\nmore以上内容即是文章摘要,在主页显示,more以下内容点击『> Read More』链接打开全文才显示。\\n\\n>hexo中所有文件的编码格式均是UTF-8。\\n\\n主题安装\\n====\\n\\n萝卜白菜各有所爱,玩博客换主题是必不可少的,hexo的主题列表[Hexo Themes](http://github.com/tommy351/hexo/wiki/Themes)。\\n\\n我比较喜欢[pacman](http://github.com/A-limon/pacman),[modernist](http://github.com/heroicyang/hexo-theme-modernist)、[ishgo](http://github.com/DavidKk/Hexo.ishgo),[raytaylorism](http://github.com/raytaylorlin/hexo-theme-raytaylorism)。Pacman最为优秀,简洁大方小清新,同时移动版本支持的也很好,但作者并没有把很多参数分离出来给出可配置项,我最终选择了modernist。\\n\\n安装主题的方法就是一句git命令:\\n\\n\\tgit clone https://github.com/heroicyang/hexo-theme-modernist.git themes/modernist\\n\\t\\n> 目录是否是modernist无所谓,只要与_config.yml文件一致即可。\\n\\n安装完成后,打开hexo\\\\_config.yml,修改主题为modernist\\n\\n\\ttheme: modernist\\n\\n打开hexo\\\\themes\\\\modernist目录,编辑主题配置文件_config.yml:\\n\\n\\tmenu: #配置页头显示哪些菜单\\n\\t# Home: /\\n\\t Archives: /archives\\n\\t Reading: /reading\\n\\t About: /about\\n\\t# Guestbook: /about\\n\\n\\texcerpt_link: Read More #摘要链接文字\\n\\tarchive_yearly: false #按年存档\\n\\n\\twidgets: #配置页脚显示哪些小挂件\\n\\t - category\\n\\t# - tag\\n\\t - tagcloud\\n\\t - recent_posts\\n\\t# - blogroll\\n\\n\\tblogrolls: #友情链接\\n\\t - bruce sha's duapp wordpress: http://ibruce.duapp.com\\n\\t - bruce sha's javaeye: http://buru.iteye.com\\n\\t - bruce sha's oschina blog: http://my.oschina.net/buru\\n\\t - bruce sha's baidu space: http://hi.baidu.com/iburu\\n\\n\\tfancybox: true #是否开启fancybox效果\\n\\n\\tduoshuo_shortname: buru #多说账号\\n\\n\\tgoogle_analytics:\\n\\trss:\\n\\n更新主题\\n\\n\\tcd themes/modernist\\n\\tgit pull\\n\\n评论框\\n====\\n\\n静态博客要使用第三方评论系统,hexo默认集成的是[Disqus](http://disqus.com/),因为你懂的,所以国内的话还是建议用[多说](http://duoshuo.com/)。\\n\\n直接用你的微博/豆瓣/人人/百度/开心网帐号登录多说,做一下基本设置。如果使用modernist主题,在modernist_config.yml中配置duoshuo_shortname为多说的**基本设置->域名**中的shortname即可。你也可以在多说后台自定义一下多说评论框的格式,比如评论框的位置,对于css设置,可以[参考这里](http://dev.duoshuo.com/docs/4ff1cfd0397309552c000017),我是在[HeroicYang](http://gist.github.com/heroicyang/5644407)的基础上修改的。\\n\\n如果你是有的其他第三方评论系统,将通用代码粘贴到hexo\\\\themes\\\\modernist\\\\layout\\\\_partial\\\\comment.ejs里面,如下:\\n\\n\\t\\n\\t