-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathcontent.json
1 lines (1 loc) · 412 KB
/
content.json
1
{"meta":{"title":"Giraffe's Home","subtitle":"A Stupid Giraffe","description":"Add more ~ing into your life","author":"Mengying Ye","url":"http://yemengying.com"},"pages":[{"title":"关于我","date":"2016-04-02T05:20:37.000Z","updated":"2018-12-15T07:32:16.000Z","comments":true,"path":"about/index.html","permalink":"http://yemengying.com/about/index.html","excerpt":"","keywords":null,"text":"普通程序员,毕业于东中国正常大学(校友都懂哈),目前从事Java开发。生活大爆炸和Running man脑残粉,因为很喜欢李光洙(外号长颈鹿),所以博客取名为Giraffe’s Home,用来记录学习思考中的一些收获,也许会有错误,也许并不完美,但这不就是成长的过程么。没什么宏伟的目标,只希望每天都能有所收获,add more ing into my life。😜😜😜 联系方式:[email protected] 前方高能预警,非战斗人员抓紧撤离 。。。。。。。。。。。。。。。。。。。。。。。。。。。。 既然不撤离就关注一下吧🌝[捂脸]","raw":null,"content":null},{"title":"Categories","date":"2016-03-19T01:28:47.000Z","updated":"2016-02-02T05:59:56.000Z","comments":true,"path":"categories/index.html","permalink":"http://yemengying.com/categories/index.html","excerpt":"","keywords":null,"text":"","raw":null,"content":null},{"title":"留言","date":"2018-12-15T07:35:34.000Z","updated":"2018-12-15T07:33:11.000Z","comments":true,"path":"message/index.html","permalink":"http://yemengying.com/message/index.html","excerpt":"","keywords":null,"text":"欢迎大家留言~ 一起交流,学习,努力,成长吧 🎉🎉🎉~~","raw":null,"content":null},{"title":"友情链接","date":"2019-03-30T09:59:11.000Z","updated":"2019-03-30T09:59:11.000Z","comments":true,"path":"friends/index.html","permalink":"http://yemengying.com/friends/index.html","excerpt":"","keywords":null,"text":"三”观”茅庐(一个做过后端,前端,目前在做移动端的程序猴的博客)默默小屋(性情温顺,主业写代码)James Pan’s Blog(真★大神的博客)脚手架与轮子(😘)ookamiAntD’s Blog(Java攻城狮)始终(Liam Huang‘s blog)crossoverjie’s blog(You never know what you can do till you try.)御坂研究所","raw":null,"content":null},{"title":"RESULT","date":"2016-03-19T01:28:47.000Z","updated":"2016-01-18T05:59:08.000Z","comments":false,"path":"search/index.html","permalink":"http://yemengying.com/search/index.html","excerpt":"","keywords":null,"text":"","raw":null,"content":null},{"title":"正在读...","date":"2017-07-02T14:11:30.000Z","updated":"2017-07-02T14:11:30.000Z","comments":true,"path":"reading/index.html","permalink":"http://yemengying.com/reading/index.html","excerpt":"","keywords":null,"text":"《黑客与画家》作者:Paul Graham 已看完 《我们仨》 《人类简史》 《腾讯传》 《哈利波特与死亡圣器》 《哈利波特与混血王子》 《哈利波特与凤凰社》 《哈利波特与火焰杯》 《哈利波特与阿兹卡班囚徒》 《哈利波特与密室》 《哈利波特与魔法石》","raw":null,"content":null},{"title":"Tags","date":"2016-03-19T01:28:47.000Z","updated":"2016-02-02T05:59:56.000Z","comments":true,"path":"tags/index.html","permalink":"http://yemengying.com/tags/index.html","excerpt":"","keywords":null,"text":"","raw":null,"content":null}],"posts":[{"title":"哈希与区块链","slug":"hash-blockchain","date":"2018-02-11T14:49:44.000Z","updated":"2018-12-13T03:33:20.000Z","comments":true,"path":"2018/02/11/hash-blockchain/","link":"","permalink":"http://yemengying.com/2018/02/11/hash-blockchain/","excerpt":"为什么要了解下区块链呢?因为区块链最近实在是太火了,火到我爸都听说了,总让我给他科普一下。。。。","keywords":null,"text":"为什么要了解下区块链呢?因为区块链最近实在是太火了,火到我爸都听说了,总让我给他科普一下。。。。 每次跟他说我也不知道,他就一脸不信,可能觉得和软件,计算机搭边的我都能知道。 马上要过年回家了,为了我的压岁钱,我准备先简单了解下,以便过年被问的时候不会再一脸懵逼。要了解区块链,就要先从在区块链技术中起着重要作用的哈希开始。 相关文档 What Is Hashing? Under The Hood Of Blockchain - Blockgeeks https://www.cs.upc.edu/~mjserna/docencia/grauA/P17/Crypto.pdf http://chimera.labs.oreilly.com/books/1234000001802/ch07.html https://blockgeeks.com/guides/what-is-bitcoin-cash/ 《区块链技术驱动金融》 什么是哈希?简单来说,哈希就是输入任意长度的字符串都可以产生固定大小的输出。在比特币这种加密货币中,交易就是输入,然后经过哈希算法(比特币采用的是 SHA - 256),产生固定长度的输出。 下面就是使用 SHA-256 算法的例子: 通过上面的例子可以看出,无论输入大或者小,输出都是固定256比特的长度。这一特性在处理大量数据和交易时是至关重要的。基于哈希这一特性,我们不用记输入数据是多么大,只需要记住hash值即可。 在我们进一步讨论之前,我们首先需要看看哈希函数的各种属性以及它们在区块链中的实现方式。 加密哈希函数加密哈希函数是一类特殊的哈希函数。为了让哈希函数达到密码安全,需要有以下几个特性: 确定性(Deterministic)对于同一个输入,无论用哈希函数计算多少次,都会得到相同的结果。 快速计算对于输入的字符串,能在合理的时间内算出哈希函数的输出,否则会影响系统的性能。 隐秘性如果我们已知字符串 A 的哈希值是 H(A),那么我们没有可行的办法算出 A 是什么。注意,这里说的是 “不可行” 而不是 “不可能”。 比如下面的例子中,知道输出哈希值是可以算出输入的。 假如我们掷骰子🎲,输出就是骰子上数字的哈希值。那么在知道输出的哈希值情况下,我们能否知道骰子上的数字呢?因为哈希函数是具有确定性的,相同输入的哈希值一定相同,所以我们只需计算 1-6 的哈希值是什么,然后对比就能知道骰子上的数字是什么了。 当然,我们能够根据哈希值猜出骰子的数字,是因为输入值只有 6 种可能性。如果我们的输入值来自一个分散的集合,那么想要通过输出推导出输入的唯一方法可能就是“暴力破解法”了。暴力破解就是,任意选择一个输入,计算其哈希值,与现有哈希值对比是否一致,不断重复这一过程,直到找到一个输入的哈希值与现有哈希值一致。 那么暴力破解法是否可行呢?假设我们现在处理的是128位的哈希值。最好的情况:第一次尝试就找到了答案,但这种情况可以说是几乎不可能的,比中大乐透还难。最坏的情况:在尝试 2^128 -1 次后得到了答案,也就是试过了所有可能的输入才找到。平均的情况: 在平均情况下,我们要尝试 2^128 / 2 = 2^127 次之后才能找到答案。2^127 = 1.7 X 10^38 , 也可以说是个天文数字了。 所以,在已知哈希值的情况下, 尽管可以通过暴力破解的方法找到输入的字符串是什么,但这会花费很长长长长长的时间,所以不用担心。 抗篡改能力对于任意一个输入,哪怕是很小的改动,其哈希改变也会非常大。比如 “This is a test” 对应的哈希值是C7BE1ED902FB8DD4D48897C6452F5D7E509FBCDBE2808B16BCF4EDCE4C07D14E, 而 “this is a test” 对应的哈希值是 2E99758548972A8E8822AD47FA1017FF72F06F3FF6A016851F45C398732BC50C。 看上面的例子,即便只改变了输入字符串第一个字母的大小写,输出hash值也是完全不同的。用前段时间比较流行的区块链撸猫游戏类比一下,“This is a test” 的哈希值对应猫可能是这样的: 而只改了个大小写,“this is a test” 的哈希值对应猫可能就变成下面这样了: 这一特性对于区块链来说十分重要,因为它决定了区块链是 immutable 的(不变的)。 抗碰撞能力碰撞是指,对于相同的输入,经过哈希计算后产生了不同的输出。具有抗碰撞能力就是对于大部分的输入都有独一无二的输入。 这里说的是”大部分”,因为找不到碰撞,并不意味不存在碰撞。概率学中的生日悖论可以证明这一点。 生日悖论: 指如果一个房间里有23个或23个以上的人,那么至少有两个人的生日相同的概率要大于50%。这就意味着在一个典型的标准小学班级(30人)中,存在两人生日相同的可能性更高。对于60或者更多的人,这种概率要大于99%。从引起逻辑矛盾的角度来说生日悖论并不是一种悖论,从这个数学事实与一般直觉相抵触的意义上,它才称得上是一个悖论。大多数人会认为,23人中有2人生日相同的概率应该远远小于50%。 没有哪个哈希函数是完全具有防碰撞特性,但对于 SHA -256 之类的哈希函数,需要花费很长的时间来找到碰撞。所以我们完全可以认为 if H(A) = H(B) 那么 A=B. 谜题友好这一特性对加密货币来说至关重要(特别是在挖矿过程中)。先定义下什么是谜题友好。 谜题友好: 如果对于任意 n 位输出值 y, 假定 k 选自高阶最小熵分布,如果无法找到一个可行的办法,在比 2^n 小很多的时间内找到 x , 保证 H(k|x) = y 成立,那么我们称哈希函数 H 具有谜题友好的特性。 什么是高阶最小熵?高阶最小熵描述了分布的分散程度。在这样的分布中,任意数值被选定的概率的小到可以忽略不计的。举个例子,如果要从 1-5 中选择一个数,就是低阶最小熵的分布。如果从 1-无穷大中选择一个数,就是高阶最小熵的分布。 ’k|x’代表了什么?‘|’ 是连接符的意思,将两个字符串连接起来。举个例子’cute|giraffe’ = ‘cutegiraffe’。 再来回顾下谜题友好的定义。假设有一个 n 位输出值 y, 从高阶分布中选取一个任意值 k, 那么没有一个可行的办法,比 2^n 小很多的时间内找到 x ,使得 H(k| x) = y。 这里说的还是 “不可行”,而不是 “不可能”。整个的比特币采矿的过程就基于解谜。 下面是几个典型的加密哈希函数: MD5:产生 128 位哈希。 SHA-1:产生 160 位哈希。 SHA-256:产生 256 位哈希。也是比特币中使用的哈希函数。 Keccak-256:产生 256 位哈希,在 Ethereum 中使用。 哈希与数据结构如果想要理解区块链是怎样工作的,就必须要理解其中 3 种重要的数据结构 哈希指针 , 区块链 和 梅克尔树。 哈希指针与不同指针不同的是,哈希指针的值是通过数据计算出来的且指向数据所在位置,所以哈希指针可以告诉我们数据存储位置及数据的哈希值。通过哈希指针,我们可以很容易判断出数据是否有被篡改。 哈希指针在区块链中极为重要。区块链的结构就是由创世区块开始,之后的每个区块通过哈希指针进行连接。每一个区块中都包含了前一个区块的哈希指针,这样后面区块不仅可以查找到前面所有区块,也可以验证前面区块数据有没有被更改,从而保证了区块链不易篡改的特性。 哈希指针在区块链中第二个用处就是构建Merkle Tree(梅克尔树),下文会详细讲。 区块链区块链是一个基于哈希指针构建的一个有序的,反向链接的交易块链表,也就是说在区块链中每个区块都通过哈希指针连接到前一个区块上。大致结构如下图: 区块链也常被看做一个垂直的堆栈,区块在栈顶一次追加,第一个区块也就是整个堆栈的基础。所以也常常用“高度”(height) 这个词来描述某个区块到第一个区块的距离。 区块链中的每一个区块都有一个对区块头部进行 SHA256 加密哈希函数计算得出的哈希值作为标识。由于每个区块需要连接到前一个区块,所以每个区块头部专门有一个字段用来存储前一个区块(也叫父区块)的哈希值。这样每个区块都连接到了他们的父区块,从而创建了区块链。 尽管每一个区块只能由一个父区块,但却可能短时间内拥有多个子区块。也就是说可能存在多个区块头部中存储的父区块的哈希值是一样的。这种情况一般发生在不同的区块在同一时间被不同的矿工找到。这样就会造成区块链的分叉,如下图: 不过区块链的分叉只是暂时,会根据“最长链原则”来解决分叉。不是本文重点不再赘述。 刚刚第二节提到哈希指针可以保证区块链不易被篡改,下面来分析下原因。 假设有一个黑客想要篡改上图中 区块 2 的数据。由于哈希函数具有抗篡改能力,很小的改动,输出的哈希值会大不一样。所以如果改动区块2上的数据,那么 区块3 自身的哈希会发生变化。 而 区块3 的头部中存储了 区块2 的哈希值,所以 对区块2 的改动必然会影响到 区块3,以此类推,区块3后面的区块也会受到影响。所以一个区块的修改会级联影响到它之后的所有区块,而要修改之后的所有区块需要强大的算力,可以说是不可能的,这也就保证了区块链的不变性。 仔细看下区块链中每个区块的结构。 区块的结构区块的内部结构分为 头部, 元数据,和一系列的交易记录。头部大小是 80个字节,而一个交易记录至少要50个字节,平均每个区块包含超过500个交易记录。所以,一个完整的区块的大小一般是它头部大小的1000倍。 下图是一个区块的大致结构。 区块的头部如上图,区块的头部的组成分为3大块。第一块是前面区块的哈希值,用于连接到父区块。第二块包含了一个随机数,一个点数(用来表示找到这个区块的难度),和一个时间戳,这三个字段都与挖矿的过程息息相关。第三块是梅克尔树(merkle tree )的树根,merkle tree 用来将区块内的所有交易以一种非常高效的形式组织起来。 merkle tree 相关内容下文会有涉及,随机数、点数和时间戳都是与挖矿相关的。 区块的标识:区块头部哈希值和区块高度一个区块最主要的标识就是区块自身头部进行二次哈希计算产生的加密哈希值。区块链中第一个区块的哈希值就是 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 。 区块的另一个标识是它在区块链当中的位置。需要注意的是一个区块只能有一个高度,但在区块链存在分叉的情况下,可能存在多个区块具有一样的高度。这时,区块的高度就不能作为唯一标识了。 第一个区块 : 创世区块区块链中的第一个区块是2009年创建的,叫做“创世区块”。它是区块链中所有区块的祖先,从任何一个区块向前追溯,最终都会到达创世区块。 通过 blockchain.info 之类的网站查看创世区块的内容 https://blockchain.info/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f12345678910111213141516{ \"hash\" : \"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f\", \"confirmations\" : 308321, \"size\" : 285, \"height\" : 0, \"version\" : 1, \"merkleroot\" : \"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b\", \"tx\" : [ \"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b\" ], \"time\" : 1231006505, \"nonce\" : 2083236893, \"bits\" : \"1d00ffff\", \"difficulty\" : 1.00000000, \"nextblockhash\" : \"00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048\"} 比特币的创始人中本聪还在创世区块里留下一句永不可修改的话“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks” (2009年1月3日,财政大臣正处于实施第二轮银行紧急援助的边缘), 这句话是泰晤士报当天的头版文章标题。 连接区块到区块链比特币中每一个节点都存储了从创世区块开始的区块链的本地副本,本地区块链的副本会在新的区块被发现并且添加到区块链后更新。当一个节点从网络接收到一个区块时,会验证该区块,验证通过后将其添加到已有区块链上。 为了建立连接,每个节点都会检验接收到的区块的头部,找到头部中存储的父区块的哈希值。举个例子,一个节点的区块链中有 277314 个区块,最后一个区块头部的哈希值计算出是00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249。 这时节点接收到一个新的区块,内容如下:123456789101112131415161718{ \"size\" : 43560, \"version\" : 2, \"previousblockhash\" : \"00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249\", \"merkleroot\" : \"5e049f4030e0ab2debb92378f53c0a6e09548aea083f3ab25e1d94ea1155e29d\", \"time\" : 1388185038, \"difficulty\" : 1180923195.25802612, \"nonce\" : 4215469401, \"tx\" : [ \"257e7497fb8bc68421eb2c7b699dbab234831600e7352f0d9e6522c7cf3f6c77\", #[... many more transactions omitted ...] \"05cfd38f6ae6aa83674cc99e4d75a1458c165b7ab84725eda41d018a09176634\" ]} 在验证完成之后,节点会先找到新区块头部中存储的 父区块 的哈希值,比较与最后一个区块的哈希值是否一致。是一致的就把新区块连接到区块链上,这时区块链的高度就变为了277315。 什么是梅克尔树?在区块的头部中,有存储一个梅克尔树根的 hash 值。所以先来了解下什么是梅克尔树。 上图就是梅克尔树的样子。 梅克尔树在区块链中用于组织和记录存储在区块中的交易,以便高效的验证某个交易是否存在在区块中。梅克尔树是通过不断的递归计算节点的哈希值直到只有一个hash值来构建的。 当梅克尔树中有N个数据时,最多只需要2*log2(N)计算就可以验证某个特定数据是否存在,所以梅克尔树是相当高效的。 梅克尔树是自底向上构建的。举个例子,假设我们现在有 A, B, C,D四笔交易需要存储记录在区块中,来看下是如何构建梅克尔树的。 首先要用 A,B,C,D 来构建树的叶子节点,将他们二次哈希后的哈希值存储在是叶子节点,就是上图中的 HA,HB,HC,HD。1H(A) = SHA256(SHA256(A)) 接着再用相邻两个结点的hash值连接在一起经过二次哈希计算来构建它们的父节点1H(AB) = SHA256(SHA256(H(A) + H(B))) 这一过程一直重复到只剩下顶层的一个节点,也就是存储在区块头部的树的根节点。 因为梅克尔树是一棵二叉树,它需要偶数个叶子节点。如果恰好是奇数个交易,那么最后一笔交易会被复制一遍,来创造偶数个叶子节点,以便达到平衡。下图中的交易 C 就被复制了一遍。 根据上面的方式我们可以为任意个数的交易构建梅克尔树,一个区块通常要记录几百到上千的交易。 为了验证一笔交易是否包含在区块中,节点只需要 计算 log2(N) 个哈希值,组成该交易到merkle树根的认证路径即可。正因为梅克尔树,区块链中的节点可以快速的产生一条包含10或12个哈希值的认证路径,来证明在区块中上千笔交易中某笔交易的存在。 哈希与挖矿我们说的“挖矿”,就是指找到可以加入区块链的新的区块。矿工们要不断的工作在确保区块链的增长的同时获到新区块的奖励。早些时候,人们往往使用笔记本电脑挖矿,但随着时间的推移,大家开始组成矿池以便集中算力更高效的挖矿。 这里存在一个问题,每种加密货币都有一个上限,比如,比特币一共有2100万个。如果找到新区块的速度过快,那么很快所有的比特币就被挖完了。所以,需要控制找到新区块的速度。对于比特币,新区块创建的时间间隔被控制在10分钟左右。 为了控制区块的创建速度,设置了一个目标值。一个有效区块的头部哈希值必须要小于目标值。目标值是一个以一串0开头的64位的字符串,开头的0越多难度越大,每新产生2016个区块之后目标值会调整一次。区块的头部有个随机数的字段,其实挖矿的过程也就是找到一个可以使区块头部哈希值小于目标值的随机数的过程,也叫做解迷。 总结哈希在区块链技术中是最基础的。如果想要了解区块链是什么,就必须要了解什么是哈希,它有什么特性,在区块链中起着什么作用。","raw":null,"content":null,"categories":[{"name":"哈希","slug":"哈希","permalink":"http://yemengying.com/categories/哈希/"}],"tags":[{"name":"哈希","slug":"哈希","permalink":"http://yemengying.com/tags/哈希/"}]},{"title":"【译】Spring MVC 中的 DispatcherServlet","slug":"spring-dispatcherServlet","date":"2017-10-07T12:58:19.000Z","updated":"2018-12-13T03:46:31.000Z","comments":true,"path":"2017/10/07/spring-dispatcherServlet/","link":"","permalink":"http://yemengying.com/2017/10/07/spring-dispatcherServlet/","excerpt":"看白夜追凶看的,写个博客总感觉身后有人。。。。。","keywords":null,"text":"看白夜追凶看的,写个博客总感觉身后有人。。。。。 原文链接原文 如果经常与 Spring MVC 打交道,那么很有必要了解什么是 DispatcherServlet。它是 Spring MVC 的核心,准确的说就是 MVC 设计模式中的 C 或 Controller。每个由 Spring MVC 处理的请求都要经过 DispatcherServlet。一般而言,它是前端控制器模式的实现,为应用提供一个统一入口。DispatcherServlet 是连接 Java 与 Spring 的桥梁,处理所有传入的请求。并且与其他声明在 web.xml 中的 Servlet 一样,也是通过一个 URL pattern 将每个请求映射到 DispatcherServlet。 DispatcherServlet 负责将请求委派给 Spring MVC 中其他的组件处理,比如注有 @Controller 或 @RestController 的 Controller类,Handler Mappers(处理器映射),View Resolvers(视图解析器) 等等。 尽管,请求映射是由 @ResquestMapping 注解完成的,但实际上是由 DispatcherServlet 将请求委派给相应的 Controller 来处理的。 在 RESTFul 的 web 服务中, DispatcherServlet 还负责选择正确的信息转换器,以便将响应结果转换成客户端期望的格式(JSON, XML 或 TEXT)。比如,如果客户端期望 JSON 格式,那么会使用 MappingJacksonHttpMessageConverter 或 MappingJackson2HttpMessageConverter (取决于 classpath 中可用的是 Jackson 1 还是 Jackson 2) 来将响应结果转成 JSON 字符串的格式。 DispatcherServlet 如何处理请求正如上面所说,DispatcherServlet 被用来处理所有传入的请求,并将它们路由到不同的 Controller 来进行进一步处理。它决定了由哪个 Controller 处理请求。 DispatcherServlet 使用处理器映射来将传入的请求路由到处理器。默认情况下,使用 BeanNameUrlHandlerMapping 和 由 @RequestMapping 注解驱动的DefaultAnnotationHandlerMapping。 为了找到正确的方法来处理请求,它会扫描所有声明了 @Controller 注解的类,并且通过 @RequestMapping 注解找到负责处理该请求的方法。@RequestMapping 注解可以通过路径来映射请求(比如: @RequestMapping(“path”)), 也可以通过 HTTP 方法(比如: @RequestMapping("path", method=RequestMethod.GET)), 也可以通过请求参数(比如: @RequestMapping("path"”, method=RequestMethod.POST, params="param1”)),还可以通过 HTTP 请求头(比如: @RequestMapping("path", header="content-type=text/*”))。我们也可以在类级别声明 @RequestMapping 注解来过滤传入的请求。 在请求处理之后,Controller 会将逻辑视图的名字和 model 返回给 DispatcherServlet。之后利用视图解析器定位到真正的 View 以便渲染结果。我们可以指定使用的视图解析器,默认情况下,DispatcherServlet 使用 InternalResourceViewResolver来将逻辑视图的名字转换成真正的视图,比如 JSP。 选定视图之后,DispatcherServlet 会将数据模型与视图相结合,并将结果返回给客户端。并不是任何时候都需要视图,比如一个 RESTful 的 web 服务就不需要,它们的处理方法会利用 @ResponseBody 注解直接将请求结果返回给客户端。可以看REST with Spring course了解更多关于如何使用 Spring MVC 开发和测试 RESTful 服务的知识。 总结在这篇文章中,我分享了一些关于 DispatcherServlet 比较重要的一些知识点。这些不仅可以帮助大家更好的理解 DispatcherServlet,也可以鼓励大家进一步去学习相关的知识。 DispatcherServlet 是 Spring MVC 应用中主要的控制器。所有的请求都会先经由 DispatcherServlet 处理,再由 Controller (声明有 @Controller 注解的类) 处理。 DispatcherServlet 是前端控制器模式的实现。前端控制器就是个用来处理网站所有请求的控制器。 就像其他的 Servlet, DispatcherServlet 也是声明和配置在 web.xml 文件中的: 1234567891011<web-app> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>*</url-pattern> </servlet-mapping> </web-app> DispatcherServlet 继承自 HttpServlet 基类。Servlet 引擎(比如 Tomcat) 创建该类的实例,并且调用它不同的方法,比如:init(), service(), destroy()。 DispatcherServlet 为 Spring MVC 应用提供统一入口,处理所有的请求。 DispatcherServlet 也完全与 Spring IoC 容器集成,可以使用 Spring 框架的每一个特性,比如依赖注入。 当 DispatcherServlet 被配置为 load-on-startup = 1,意味着该 servlet 会在启动时由容器创建,而不是在请求到达时。这样做会降低第一次请求的响应时间,因为DispatcherServlet 会在启动时做大量工作,包括扫描和查找所有的 Controller 和 RequestMapping。 在 DispatcherServlet 初始化期间,Spring 框架会在 WEB-INF 文件夹中查找名为 [servlet-name]-servlet.xml 的文件,并创建相应的 bean。比如,如果 servlet 像上面 web.xml 文件中配置的一样,名为 “SpringMVC”,那么会查找 “SpringMVC-Servlet.xml”的文件。如果全局作用域中有相同名字的bean,会被覆盖。可以用 servlet 初始化参数 contextConfigLocation更改配置文件的位置。 在 Spring MVC 框架中,每个 DispatcherServlet 都有它自己的 WebApplicationContext ,并且继承了根 WebApplicationContext 中定义的所有 bean。这些继承的 bean 在 servlet 指定的作用域中可以被重载,也可以在其指定作用域中定义新的 bean。 Spring MVC 中的 DispatcherServlet也允许返回 Servlet API 定义的 last-modification-date。为了决定请求最后修改时间,DispatcherServlet会先查找合适的 handler mapping,然后检测处理器是否实现了 LastModified 接口。如果实现了,就调用接口的 getLastModified(request) 方法,并将该值返回给客户端。 以上就是关于 DispatcherSerlvet 的内容。正如上面所讲,DispacherServlet 是 Spring MVC 的骨干,是主要的控制器,用来将不同的 HTTP 请求路由当相应的 Controller。它是前端控制器设计模式的实现,并且为应用提供单一入口。可以在 web.xml 中配置 DispatcherServlet,但建议将 load-on-startup 设置为 1。这样容器会在启动时加载该 Serlvet 而不是请求到达时。这样能减少第一个请求的响应时间。 出租车司机终于可以下了,等的花儿都谢了,宋康昊千万不要太帅~","raw":null,"content":null,"categories":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/categories/spring/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"},{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"Oracle 中的外键与锁","slug":"oracle-foreignkey-lock","date":"2017-09-04T10:31:07.000Z","updated":"2018-12-13T03:49:20.000Z","comments":true,"path":"2017/09/04/oracle-foreignkey-lock/","link":"","permalink":"http://yemengying.com/2017/09/04/oracle-foreignkey-lock/","excerpt":"算是接上篇吧。。。 内容主要来自 Oracle 官方文档,自己重新画了下图。图中配色来自大神 draveness 的文章,小清新的配色真是美美哒。看来我在学画图的路上还要修炼很久啊。。。。。","keywords":null,"text":"算是接上篇吧。。。 内容主要来自 Oracle 官方文档,自己重新画了下图。图中配色来自大神 draveness 的文章,小清新的配色真是美美哒。看来我在学画图的路上还要修炼很久啊。。。。。 锁是一种可以防止多个事务错误的更新共享数据的机制,在维护数据库并发性和一致性方面起着关键的作用。在 Oracle 堆组织表中, 数据库锁的行为与外键列是否有索引有关。如果外键未加索引,那么子表可能会被频繁锁住,从而导致死锁,降低并发性。所以,Oracle 官方建议绝对多数情况都为外键加索引,除非父表的唯一键/主键绝对不会更新或删除。 未加索引的外键与锁当以下条件都满足时,数据库会获取子表的全表锁: 子表的外键列未加索引 父表的主键被修改(比如:删除一行或主键被修改)或者合并到父表。在父表插入一条记录是不会锁住子表的。 假设 hr.departments 是父表,hr.employees 是子表, hr.employees 中的 department_id 是未加索引的外键列。下图展现了修改父表 department_id = 60 这一记录的主键时,数据库加锁的情况: 在上图中,数据库在更新父表 department 60 这一记录的主键时,会获得子表 employees 的全表锁。这样其他会话可以查询子表但不允许更新子表。子表的表锁会在父表更新完成后立刻释放。如果修改父表多条记录,那么修改每一条都会获得子表的表锁并释放。 在 Oracle 9i 及以上版本中,全表锁都是短期的,仅在 DML 操作期间存在,而不是整个事务。但即便如此,还是要避免,因为全表锁可能会导致死锁。Tom 也曾说过,导致死锁的头号原因就是外键未加索引(第二号原因是表上的位图索引遭到并发更新)。 需要注意,子表的 DML 不会获得父表的表锁 加索引的外键与锁当以下条件都满足时,数据库不会获得子表的全表锁。 子表的外键列已加索引 父表的主键正在被修改(比如:删除一行或主键被修改)或合并到父表 下面的图展示了子表 employees 的外键列 department_id 加了索引。 当一个事务从父表 department 中删除 department 280 时, 这一操作不会引起数据库获得子表的全表锁。 父表上的锁是为了防止其他事务获取表级排他锁,但不会阻止父表或子表上的 DML 操作。 如果子表指明了 ON DELETE CASCADE, 那么删除父表会导致删除子表对应的记录。比如删除父表 department 280 这一记录,那么子表 employees 中 department_id 为 280 的记录也会被删除。 在这种情况下,加锁的规则与删除完主表后再删除子表的记录是一样的。 这篇是不是有点水啊。。。。是时候放出我姑家的小霸王 cookie 宝宝撑下场了。 虽然我又咬人,又乱叫,还喜欢对着窗帘小便。但我知道我是个好狗狗。 ————cookie","raw":null,"content":null,"categories":[{"name":"Oracle","slug":"Oracle","permalink":"http://yemengying.com/categories/Oracle/"}],"tags":[{"name":"Oracle","slug":"Oracle","permalink":"http://yemengying.com/tags/Oracle/"}]},{"title":"小死锁","slug":"deadlock-in-oracle","date":"2017-07-15T14:52:22.000Z","updated":"2018-12-13T03:56:58.000Z","comments":true,"path":"2017/07/15/deadlock-in-oracle/","link":"","permalink":"http://yemengying.com/2017/07/15/deadlock-in-oracle/","excerpt":"最近线上偶尔就会报个死锁问题,上周终于解决了,周末整理下。虽然问题解决了,但是trace file里的死锁图还是不太理解。要是有人能给我讲讲那真是极好的,要是没人的话我就。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。再翻翻文档。","keywords":null,"text":"最近线上偶尔就会报个死锁问题,上周终于解决了,周末整理下。虽然问题解决了,但是trace file里的死锁图还是不太理解。要是有人能给我讲讲那真是极好的,要是没人的话我就。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。再翻翻文档。 背景近一个月线上偶尔就会报死锁的问题,错误如下图: “ORA-00060: deadlock detected while waiting for resource ”这个错误说明Oracle 数据库监测到了死锁的存在。这时 Oracle 会回滚造成死锁的其中一个事务,另一个事务正常执行(不会察觉到发生了死锁),并向执行了回滚的事务抛出上面的错误信息。 在 DBA 的帮助下定位到了造成死锁的两块代码。由于项目有很多的悲观锁,即利用“SELECT…FOR UPDATE”对资源加排他行级锁,所以第一感觉就是看看这两段代码有没有按照相反的顺序对两个或多个资源进行加锁。 不过分析过代码之后却没有立刻找到可能造成死锁的原因,两块代码对数据库资源的操作如下表。 从表面上看Session 1貌似中只锁了 actor1 并更新,Session 2中依次锁了 actor2 和 actor1,不满足互相等待对方加锁的资源,就算是Session1持有actor1锁时间过长,导致 Session2 一直拿不到 actor1 的锁,也应该报“lock wait timeout”,而不是死锁。 为了验证确实是这两段代码造成的死锁,写了测试代码,开了两个线程,模仿死锁的这两段代码,去掉了与数据库无关的业务逻辑,看看能否重现。毕竟心里还有点小怀疑,会不会是 DBA 搞错了,不是这两段代码的问题。代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051@SpringApplicationConfiguration(classes = DeadLockTest.class)@ImportAutoConfiguration({CommonConfig.class})public class DeadLockTest extends BaseUnitDbTest { Long lenderId = 16642L; Long borrowerId = 16643L; @Autowired private ActorService actorService; @Autowired @Qualifier(CommonConfig.ORACLE_TRANSACTION_MANAGER_NAME) private PlatformTransactionManager platformTransactionManager; private ExecutorService es = Executors.newFixedThreadPool(5, new ThreadFactoryBuilder().setNameFormat(\"Test-Thread-%d\").build()); @Test public void testRefreshAndLockActor() throws Exception { es.invokeAll(Lists.newArrayList(this::lock1, this::lock2)); } public Void lock1() { TransactionTemplate t = new TransactionTemplate(platformTransactionManager); t.execute((s) -> { System.out.println(\"Before Lock \" + Thread.currentThread().getName()); Actor lender = actorService.refreshAndLockActor(lenderId); try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } lender.setLockedForInv(BigDecimal.ONE); actorService.update(lender); System.out.println(\"After Lock \" + Thread.currentThread().getName()); return null; }); return null; } public Void lock2() { TransactionTemplate t = new TransactionTemplate(platformTransactionManager); t.execute((s) -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\"Before Lock \" + Thread.currentThread().getName()); Actor borrower = actorService.refreshAndLockActor(borrowerId); Actor lender = actorService.refreshAndLockActor(lenderId); System.out.println(\"After Lock \" + Thread.currentThread().getName()); return null; }); return null; }} 结果。。。真的报了死锁。。。。控制台的报错如下图 问题原因已经确定了造成死锁的两段代码,接下来就差找出原因了。SELECT ... FOR UPDATE比较直观,就是对资源加行级排他锁,应该没什么猫腻。那就肯定是在UPDATE Actor1的时候有什么不为人知的操作,导致 Session1 需要获取 Actor2 的锁,导致死锁。 第一怀疑的是触发器,虽然目前公司已经禁止使用触发器了,但由于历史原因主库还是遗留着一些触发器。和明佳排查了所有相关的触发器之后,基本排除了是由触发器引起的。 不过虽然这个问题不是触发器的锅,但还是提会到了,在涉及到触发器时,如果不是对系统特别熟,排查错误真的很困难。。。 排除了触发器之后,DBA 提出那就只能是外键导致的了。在 DBA 把 dev环境数据库的外键去掉后,再次执行测试代码,果然就不再报死锁了。 原来 Actor 表上的 refer_id 是一个关联 Actor 表主键的外键(Self-Referential Integrity Constraints),而 actor1 的 refer_id 正好是 actor2 的 id,所以在更新 actor1 的全字段的时候,也更新了 refer_id(其实值没变),由于外键的约束,在将 actor1 的 refer_id 更新为 actor2的 id 时,需要确保 actor2 是存在的,并且在更新过程中,不能被删除,所以 Session1 会申请 actor2 的锁(个人理解不一定准确)。而这时 actor2 的锁已经被 Session2 持有了,并且 Session2 正在等待 actor1 的锁,就发生了死锁。 用图来描述下: 解决办法费了一天多才把问题找出来,用了几分钟就 fix 了。其实我只是需要更新 Actor 上的两个字段,根本不需要更新全部字段,只是当时在写的时候已经有更新全字段的方法了,就偷了个懒。。。。。。 所以解决办法就是不再调用更新全字段的方法,加了个只更新部分字段的方法,这样就不会在更新 actor1 的外键字段了,也就不会造成在更新 actor1 的时候去请求 actor2 的锁了。 总结 虽然这个问题不是触发器引起的,但禁用触发器还是很有道理滴,不然出问题查到吐血 外键这个东东,也能不用就不用吧,由程序控制。其实现在公司已经不让用外键和触发器了,不过由于历史原因,一些老系统只能慢慢重构了。查了下,由于外键引起的死锁还是蛮多的,比较常见的是外键列不加索引,导致更新主表字段时锁住了子表,下篇blog可以学习下外键和死锁不得不说的那些事。算了,还是不立 flag 了,基本上说了下篇要写啥的,都没有下篇了。。。。 不要更新全字段。抛开这个死锁问题,更新全字段也是很影响效率的。还是只更新有改动的字段吧。 不能偷懒,当时省了5分钟,找 bug 花了一天多。。。都是泪 遗留问题问题虽然解决了,但是还有点小疑问的。这个死锁在 trace file 中的死锁图如下: 那么问题来了,两个 session 持有两个资源的 X 锁还是好理解的,但他们等待的为什么是 S 锁呢???至少 Session2 是在等待 actor1 的排他行级锁的,不应该是也是等待 X 么。求好心人的解答。","raw":null,"content":null,"categories":[{"name":"Oracle","slug":"Oracle","permalink":"http://yemengying.com/categories/Oracle/"}],"tags":[{"name":"Oracle","slug":"Oracle","permalink":"http://yemengying.com/tags/Oracle/"}]},{"title":"【译】Reddit如何统计每个帖子的浏览量","slug":"reddit-view-counting","date":"2017-06-04T05:26:17.000Z","updated":"2018-12-13T04:01:09.000Z","comments":true,"path":"2017/06/04/reddit-view-counting/","link":"","permalink":"http://yemengying.com/2017/06/04/reddit-view-counting/","excerpt":"之前没听过也没了解过 HyperLogLog,通过翻译这篇文章正好简单学习下。欢迎指正错误~","keywords":null,"text":"之前没听过也没了解过 HyperLogLog,通过翻译这篇文章正好简单学习下。欢迎指正错误~ 原文链接😁🤗😉 我们想要更好的向用户展示 Reddit 的规模。为了这一点,投票和评论数是一个帖子最重要的指标。然而,在 Reddit 上有相当多的用户只浏览内容,既不投票也不评论。所以我们想要建立一个能够计算一个帖子浏览数的系统。这一数字会被展示给帖子的创作者和版主,以便他们更好的了解某个帖子的活跃程度。 在这篇博客中,我们将讨论我们是如何实现超大数据量的计数。 计数机制对于计数系统我们主要有四种需求: 帖子浏览数必须是实时或者近实时的,而不是每天或者每小时汇总。 同一用户在短时间内多次访问帖子,只算一个浏览量 显示的浏览量与真实浏览量间允许有\b小百分之几的误差 Reddit 是全球访问量第八的网站,系统要能在生产环境的规模上正常运行,仅允许几秒的延迟 要全部满足以上四个需求的困难远远比听上去大的多。为了实时精准计数,我们需要知道某个用户是否曾经访问过这篇帖子。想要知道这个信息,我们就要为每篇帖子维护一个访问用户的集合,然后在每次计算浏览量时检查集合。一个 naive 的实现方式就是将访问用户的集合存储在内存的 hashMap 中,以帖子 Id 为 key。 这种实现方式对于访问量低的帖子是可行的,但一旦一个帖子变得流行,访问量剧增时就很难控制了。甚至有的帖子有超过 100 万的独立访客! 对于这样的帖子,存储独立访客的 ID 并且频繁查询某个用户是否之前曾访问过会给内存和 CPU 造成很大的负担。 因为我们不能提供准确的计数,我们查看了几种不同的基数估计算法。有两个符合我们需求的选择: 一是线性概率计数法,很准确,但当计数集合变大时所需内存会线性变大。 二是基于 HyperLogLog (以下简称 HLL )的计数法。 HLL 空间复杂度较低,但是精确度不如线性计数。 下面看下 HLL 会节省多少内存。如果我们需要存储 100 万个独立访客的 ID, 每个用户 ID 8 字节长,那么为了存储一篇帖子的独立访客我们就需要 8 M的内存。反之,如果采用 HLL 会显著减少内存占用。不同的 HLL 实现方式消耗的内存不同。如果采用这篇文章的实现方法,那么存储 100 万个 ID 仅需 12 KB,是原来的 0.15%!! Big Data Counting: How to count a billion distinct objects using only 1.5KB of Memory - High Scalability -这篇文章很好的总结了上面的算法。 许多 HLL 的实现都是结合了上面两种算法。在集合小的时候采用线性计数,当集合大小到达一定的阈值后切换到 HLL。前者通常被成为 ”稀疏“(sparse) HLL,后者被称为”稠密“(dense) HLL。这种结合了两种算法的实现有很大的好处,因为它对于小集合和大集合都能够保证精确度,同时保证了适度的内存增长。可以在 google 的这篇论文中了解这种实现的详细内容。 现在我们已经确定要采用 HLL 算法了,不过在选择具体的实现时,我们考虑了以下三种不同的实现。因为我们的数据工程团队使用 Java 和 Scala,所以我们只考虑 Java 和 Scala 的实现。 Twitter 提供的 Algebird,采用 Scala 实现。Algebird 有很好的文档,但他们对于 sparse 和 dense HLL 的实现细节不是很容易理解。 stream-lib中提供的 HyperLogLog++, 采用 Java 实现。stream-lib 中的代码文档齐全,但有些难理解如何合适的使用并且改造的符合我们的需求。 Redis HLL 实现,这是我们最终选择的。我们认为 Redis 中 HLLs 的实现文档齐全、容易配置,提供的相关 API 也很容易集成。还有一个好处是,我们可以用一台专门的服务器部署,从而减轻性能上的压力。 Reddit 的数据管道依赖于 Kafka。当一个用户访问了一篇博客,会触发一个事件,事件会被发送到事件收集服务器,并被持久化在 Kafka 中。 之后,计数系统会依次顺序运行两个组件。在我们的计数系统架构中,第一部分是一个 Kafka 的消费者,我们称之为 Nazar。Nazar 会从 Kafka 中读取每个事件,并将它通过一系列配置的规则来判断该事件是否需要被计数。我们取这个名字仅仅是因为 Nazar 是一个眼睛形状的护身符,而 ”Nazar“ 系统就像眼睛一样使我们的计数系统远离不怀好意者的破坏。其中一个我们不将一个事件计算在内的原因就是同一个用户在很短时间内重复访问。Nazar 会修改事件,加上个标明是否应该被计数的布尔标识,并将事件重新放入 Kafka。 下面就到了系统的第二个部分。我们将第二个 Kafka 的消费者称作 Abacus,用来进行真正浏览量的计算,并且将计算结果显示在网站或客户端。Abacus 从 Kafka 中读取经过 Nazar 处理过的事件,并根据 Nazar 的处理结果决定是跳过这个事件还是将其加入计数。如果 Nazar 中的处理结果是可以加入计数,那么 Abacus 首先会检查这个事件所关联的帖子在 Redis 中是否已经存在了一个 HLL 计数器。如果已经存在,Abacus 会给 Redis 发送个 PFADD 的请求。如果不存在,那么 Abacus 会给 Cassandra 集群发送个请求(Cassandra 用来持久化 HLL 计数器和 计数值的),然后向 Redis 发送 SET 请求。这通常会发生在网友访问较老帖子的时候,这时该帖子的计数器很可能已经在 Redis 中过期了。 为了存储存在 Redis 中的计数器过期的老帖子的浏览量。Abacus 会周期性的将 Redis 中全部的 HLL 和 每篇帖子的浏览量写入到 Cassandra 集群中。为了避免集群过载,我们以 10 秒为周期批量写入。 下图是事件流的大致流程: 总结我们希望浏览量可以让发帖者了解帖子全部的访问量,也帮助版主快速定位自己社区中高访问量的帖子。在未来,我们计划利用我们数据管道在实时方面的潜力来为 Reddit 的用户提供更多的有用的反馈。 ————————————————————分割线———————————————————- 这周一定要看完《地球上的星星》💪","raw":null,"content":null,"categories":[{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/categories/翻译/"}],"tags":[{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"MySQL 唯一性约束与 NULL","slug":"mysql-unique-key-null","date":"2017-05-18T14:35:54.000Z","updated":"2018-12-13T04:02:58.000Z","comments":true,"path":"2017/05/18/mysql-unique-key-null/","link":"","permalink":"http://yemengying.com/2017/05/18/mysql-unique-key-null/","excerpt":"很久之前的一个 bug 了,简单记录下。。。","keywords":null,"text":"很久之前的一个 bug 了,简单记录下。。。 之前做的一个需求,简化描述下就是接受其他组的 MQ 的消息,然后在数据库里插入一条记录。为了防止他们重复发消息,插入多条重复记录,所以在表中的几个列上加了个唯一性索引。1CREATE UNIQUE INDEX IDX_UN_LOAN_PLAN_APP ON testTable (A, B, C); 这时 A,B,C 三列都是不允许 NULL 值的,唯一性约束也是 work 的。后来由于需求的变化,修改了以前的唯一性约束,又多加了一列。(至于为什么加就不赘述了)。123ALTER TABLE testTableDROP INDEX IDX_UN_LOAN_PLAN_APP,ADD UNIQUE KEY `IDX_UN_LOAN_PLAN_APP` (A, B, C, D); 新加的 D 是类型是 datetime, 允许为 NULL,默认值为 NULL。之所以默认值为 NULL,是考虑到不是所有记录都有这个时间的, 如果强行设置一个 Magic Value (比如’1970-01-01 08:00:00‘)当做默认值,看起来很奇怪。 蓝后。。。就出问题了。加了 D 之后,唯一性约束基本就失效了。 123Insert into testTable (A,B,C,D) VALUES (1,2,3,NULL); --- OKInsert into testTable (A,B,C,D) VALUES (1,2,3,NULL); --- OKInsert into testTable (A,B,C,D) VALUES (1,2,3,NULL); --- OK 上面的三条 SQL 都是可以执行成功的,数据库中会有多条一样的记录。可按照我们以前的构想,在执行后两条 SQL 时 应该抛出 ‘Duplicate key’ 的异常的。 后来查了一下,才发现其实 MySQL 官方文档上已经明确说了这一点, 唯一性索引是允许多个 NULL 值的存在的: A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. For all engines, a UNIQUE index allows multiple NULL values for columns that can contain NULL. 从下表中也可以看出来不管是采用什么类型的存储引擎,在建立 unique key 的时候都是允许多个 NULL 存在的。。。。细想想,其实也蛮合理,毕竟在 MySQL 中认为 NULL 代表着“未知”。 在 SQL 中,任何值与 NULL 的比较返回值都是 NULL 而不是 TRUE, 就算 NULL 与 NULL 的比较也是返回 NULL。 所以只能 fix 了。。。解决办法也蛮简单粗暴的,直接把线上数据刷了一遍,将“1970-01-01 08:00:00”作为默认值,然后把那列改为不允许为 NULL 的了,咳咳。 MySQL 官网上也有蛮多人讨论过这个问题,一部分人认为这是 MySQL 的 bug, 另一部分则认为是一个 feature,附上链接。 MySQL Bugs: #8173: unique index allows duplicates with null values","raw":null,"content":null,"categories":[{"name":"MySQL","slug":"MySQL","permalink":"http://yemengying.com/categories/MySQL/"}],"tags":[{"name":"MySQL","slug":"MySQL","permalink":"http://yemengying.com/tags/MySQL/"}]},{"title":"【译】Executor, ExecutorService 和 Executors 间的不同","slug":"difference-between-executor-executorService","date":"2017-03-17T06:07:21.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2017/03/17/difference-between-executor-executorService/","link":"","permalink":"http://yemengying.com/2017/03/17/difference-between-executor-executorService/","excerpt":"搁了好久没更博客,再不写要被某人 BS 了,咦。。。。。 原文链接","keywords":null,"text":"搁了好久没更博客,再不写要被某人 BS 了,咦。。。。。 原文链接 java.util.concurrent.Executor, java.util.concurrent.ExecutorService, java.util.concurrent. Executors 这三者均是 Java Executor 框架的一部分,用来提供线程池的功能。因为创建和管理线程非常心累,并且操作系统通常对线程数有限制,所以建议使用线程池来并发执行任务,而不是每次请求进来时创建一个线程。使用线程池不仅可以提高应用的响应时间,还可以避免"java.lang.OutOfMemoryError: unable to create new native thread" 之类的错误。 在 Java 1.5 时,开发者需要关心线程池的创建和管理,但在 Java 1.5 之后 Executor 框架提供了多种内置的线程池,例如:FixedThreadPool(包含固定数目的线程),CachedThreadPool(可根据需要创建新的线程)等等。 ExecutorExecutor, ExecutorService, 和 Executors 最主要的区别是 Executor 是一个抽象层面的核心接口(大致代码如下)。123public interface Executor { void execute(Runnable command);} 不同于 java.lang.Thread 类将任务和执行耦合在一起, Executor 将任务本身和执行任务分离,可以阅读 difference between Thread and Executor 来了解 Thread 和 Executor 间更多的不同。 ExecutorServiceExecutorService 接口 对 Executor 接口进行了扩展,提供了返回 Future 对象,终止,关闭线程池等方法。当调用 shutDown 方法时,线程池会停止接受新的任务,但会完成正在 pending 中的任务。 Future 对象提供了异步执行,这意味着无需等待任务执行的完成,只要提交需要执行的任务,然后在需要时检查 Future 是否已经有了结果,如果任务已经执行完成,就可以通过 Future.get() 方法获得执行结果。需要注意的是,Future.get() 方法是一个阻塞式的方法,如果调用时任务还没有完成,会等待直到任务执行结束。 通过 ExecutorService.submit() 方法返回的 Future 对象,还可以取消任务的执行。Future 提供了 cancel() 方法用来取消执行 pending 中的任务。 ExecutorService 部分代码如下:123456public interface ExecutorService extends Executor { void shutdown(); <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;} ExecutorsExecutors 是一个工具类,类似于 Collections。提供工厂方法来创建不同类型的线程池,比如 FixedThreadPool 或 CachedThreadPool。 Executors 部分代码:12345678910public class Executors { public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }} 下面详细看一下三者的区别: Executor vs ExecutorService vs Executors正如上面所说,这三者均是 Executor 框架中的一部分。Java 开发者很有必要学习和理解他们,以便更高效的使用 Java 提供的不同类型的线程池。总结一下这三者间的区别,以便大家更好的理解: Executor 和 ExecutorService 这两个接口主要的区别是:ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口 Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 execute()方法用来接收一个Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable接口的对象。 Executor 和 ExecutorService 接口第三个区别是 Executor 中的 execute() 方法不返回任何结果,而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。 Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。可以通过 《Java Concurrency in Practice》 一书了解更多关于关闭线程池和如何处理 pending 的任务的知识。 Executors 类提供工厂方法用来创建不同类型的线程池。比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。 总结下表列出了 Executor 和 ExecutorService 的区别: Executor ExecutorService Executor 是 Java 线程池的核心接口,用来并发执行提交的任务 ExecutorService 是 Executor 接口的扩展,提供了异步执行和关闭线程池的方法 提供execute()方法用来提交任务 提供submit()方法用来提交任务 execute()方法无返回值 submit()方法返回Future对象,可用来获取任务执行结果 不能取消任务 可以通过Future.cancel()取消pending中的任务 没有提供和关闭线程池有关的方法 提供了关闭线程池的方法 译者注个人觉得,利用 Executors 类提供的工厂方法来创建一个线程池是很方便,但对于需要根据实际情况自定义线程池某些参数的场景,就不太适用了。 举个例子:当线程池中的线程均处于工作状态,并且线程数已达线程池允许的最大线程数时,就会采取指定的饱和策略来处理新提交的任务。总共有四种策略: AbortPolicy: 直接抛异常 CallerRunsPolicy: 用调用者的线程来运行任务 DiscardOldestPolicy: 丢弃线程队列里最近的一个任务,执行新提交的任务 DiscardPolicy 直接将新任务丢弃 如果使用 Executors 的工厂方法创建的线程池,那么饱和策略都是采用默认的 AbortPolicy,所以如果我们想当线程池已满的情况,使用调用者的线程来运行任务,就要自己创建线程池,指定想要的饱和策略,而不是使用 Executors 了。 所以我们可以根据需要创建 ThreadPoolExecutor(ExecutorService接口的实现类) 对象,自定义一些参数,而不是调用 Executors 的工厂方法创建。 当然,在使用 Spring 框架的项目中,也可以使用 Spring 提供的 ThreadPoolTaskExecutor 类来创建线程池。ThreadPoolTaskExecutor 与 ThreadPoolExecutor 类似,也提供了许多参数用来自定义线程池,比如:核心线程池大小,线程池最大数量,饱和策略,线程活动保持时间等等。 相关文档 Executors java api doc","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"}]},{"title":"谈谈 NoSuchBeanDefinitionException","slug":"something-about-noSuchBeanDefinitionException","date":"2017-02-18T07:55:50.000Z","updated":"2018-12-13T04:05:10.000Z","comments":true,"path":"2017/02/18/something-about-noSuchBeanDefinitionException/","link":"","permalink":"http://yemengying.com/2017/02/18/something-about-noSuchBeanDefinitionException/","excerpt":"这篇博客是来自对两篇文章的翻译,原文链接如下。这两篇文章都总结了在使用 Spring 框架时可能造成 NoSuchBeanDefinitionException 的情况,以及应该如何解决。","keywords":null,"text":"这篇博客是来自对两篇文章的翻译,原文链接如下。这两篇文章都总结了在使用 Spring 框架时可能造成 NoSuchBeanDefinitionException 的情况,以及应该如何解决。 原文链接java - What is a NoSuchBeanDefinitionException and how do I fix it? - Stack OverflowSpring NoSuchBeanDefinitionException | Baeldung 概述org.springframework.beans.factory.NoSuchBeanDefinitionException 是很常见的异常,可以说绝大多数使用过 Spring 的人都曾遇到过它。本文旨在总结下NoSuchBeanDefinitionException(以下简称 NSBDE)的含义,哪些情况下可能抛出 NSBDE,和如何解决(文中配置均用 JavaConfig)。 什么是 NoSuchBeanDefinitionException从字面其实就很好理解,NoSuchBeanDefinitionException 就是没有找到指定 Bean 的 Definition。NoSuchBeanDefinitionException 的 JavaDoc是这样定义的: Exception thrown when a BeanFactory is asked for a bean instance for which it cannot find a definition. This may point to a non-existing bean, a non-unique bean, or a manually registered singleton instance without an associated bean definition. 下面看看可能抛出 NSBDE 的一些情况。 情况1: No qualifying bean of type […] found for dependency最常见的抛出 NSBDE 的情况就是在一个 BeanA 中注入 BeanB 时找不到 BeanB 的定义。例子如下:123456@Componentpublic class BeanA { @Autowired private BeanB dependency; //...} 当在 BeanA 中注入 BeanB 时,如果在 Spring 上下文中找不到 BeanB 的定义,就会抛出 NSBDE。异常信息如下:1234567org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.baeldung.packageB.BeanB] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} 抛异常的原因在异常信息中说的很清楚:expected at least 1 bean which qualifies as autowire candidate for this dependency。所以要么是 BeanB 不存在在 Spring 上下文中(比如没有标注 @ Component,@Repository,@Service, @Controller等注解) ,要么就是 BeanB 所在的包没有被 Spring 扫描到。 解决办法就是先确认 BeanB 有没有被某些注解声明为 Bean:123package org.baeldung.packageB;@Componentpublic class BeanB { ...} 如果 BeanB 已经被声明为一个 Bean,就再确认 BeanB 所在的包有没有被扫描。1234@Configuration@ComponentScan(\"org.baeldung.packageB\")public class ContextWithJavaConfig {} 情况2: No qualifying bean of type […] is defined还有一种可能抛出 NSBDE 的情况是在上下文中存在着两个 Bean,比如有一个接口 IBeanB,它有两个实现类 BeanB1 和 BeanB2。12345678@Componentpublic class BeanB1 implements IBeanB { //}@Componentpublic class BeanB2 implements IBeanB { //} 现在,如果 BeanA 按照下面的方式注入,那么 Spring 将不知道要注入两个实现中的哪一个,就会抛出 NSBDE。12345@Componentpublic class BeanA { @Autowired private IBeanB dependency;} 异常信息如下:1234Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.baeldung.packageB.IBeanB] is defined: expected single matching bean but found 2: beanB1,beanB2 仔细看异常信息会发现,并不是直接抛出 NSBDE,而是它的子类 NoUniqueBeanDefinitionException,这是 Spring 3.2.1 之后引入的新异常,目的就是为了和第一种找不到 Bean Definition 的情况作区分。 解决办法1就是利用 @Qualifier 注解,明确指定要注入的 Bean 的名字(BeanB2 默认的名字就是 beanB2)。123456@Componentpublic class BeanA { @Autowired @Qualifier(\"beanB2\") private IBeanB dependency;} 除了指定名字,我们还可以将其中一个 Bean 加上 @Primary的注解,这样会选择加了 Primary 注解的 Bean 来注入,而不会抛异常:12345@Component@Primarypublic class BeanB1 implements IBeanB { //} 这样 Spring 就能够知道到底应该注入哪个 Bean 了。 情况3: No Bean Named […] is definedNSBDE 还可能在从 Spring 上下文中通过名字获取一个 Bean 时抛出。123456789@Componentpublic class BeanA implements InitializingBean { @Autowired private ApplicationContext context; @Override public void afterPropertiesSet() { context.getBean(\"someBeanName\"); }} 在这种情况中,如果找不到指定名字 Bean 的 Definition,就会抛出如下异常:12Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'someBeanName' is defined 情况4: 代理 BeansSpring 通过 AOP 代理 实现了许多高级功能,比如: 通过 @Transactional完成 事务管理 通过 @Cacheable实现缓存 通过 @Async和 @Scheduled实现任务调度和异步执行 Spring 有两种方式实现代理: 利用 JDK 动态代理机制 ,在运行时为实现了某些接口的类动态创建一个实现了同样接口的代理对象。 使用 CGLIB,CGLIB 可以在运行期扩展Java类与实现Java接口,也就是说当一个类没有实现接口时,必须用 CGLIB 生成代理对象。 所以,当 Spring 上下文中的一个实现了某个接口的 Bean 通过JDK 动态代理机制被代理时,代理类并不是继承了目标类,而是实现同样的接口。 也正因为如此,如果一个 Bean 通过接口注入时,可以成功被注入。但如果是通过真正的类注入,那么 Spring 将无法找到匹配这个类的 Definition——因为代理类并没有继承这个类。 以 Spring 中比较常见的事务管理为例,假设 ServiceA 中要注入 ServiceB,两个 Service 均标注了 @Transactional注解来进行事务管理,那么下面的注入方式是不会正常 work 的。123456789101112@Service@Transactionalpublic class ServiceA implements IServiceA{ @Autowired private ServiceB serviceB; ...} @Service@Transactionalpublic class ServiceB implements IServiceB{} 解决办法就是通过接口来进行注入:1234567891011@Service@Transactionalpublic class ServiceA implements IServiceA{ @Autowired private IServiceB serviceB;} @Service@Transactionalpublic class ServiceB implements IServiceB{} 总结今天天气好好啊~","raw":null,"content":null,"categories":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"RabbitMq 如何处理异常","slug":"how-does-rabbitmq-handle-exception","date":"2017-01-30T11:37:21.000Z","updated":"2018-12-13T04:10:36.000Z","comments":true,"path":"2017/01/30/how-does-rabbitmq-handle-exception/","link":"","permalink":"http://yemengying.com/2017/01/30/how-does-rabbitmq-handle-exception/","excerpt":"这应该是过年假期的最后一篇,如果不是,那你一定看到了假博客。(๑•̀ㅂ•́)و✧","keywords":null,"text":"这应该是过年假期的最后一篇,如果不是,那你一定看到了假博客。(๑•̀ㅂ•́)و✧ 在消费 RabbitMq 中的 Message 时,常常会出现异常,可能是 Message 本身格式不对,或者由于某些原因无法被处理。我一般都是 catch 异常然后抛个 AmqpRejectAndDontRequeueException (以下简称 ARADRE ),也出啥问题,不过还是仔细看下,rabbitmq 是如何对待消费消息时出现的异常,是会将消息直接丢弃还是有其他操作。 其实 Spring-amqp 官方文档上对于 RabbitMq 是如何处理异常的说的已经很明白了,不过都是大段的文字可能不太好理解,还是配着代码看一下。 代码版本: 1.6.3.RELEASE 根据官方文档,当 listener 在消费消息时抛出一个异常的时候,该异常会被包装在 ListenerExecutionFailedException 中抛出,并根据 listenerContainer 中 defaultRequeueRejected 设定的值来决定是否将该消息重新加入队列,默认是会重新加入队列。 需要注意的是,如果抛出的异常是 ARADRE 或其他被 RabbitMq 认为是致命错误的异常,即便 defaultRequeueRejected 的值为 true , 该消息也不会重新加入队列,而是会被直接丢弃或加入 dead-letter-exchange 中(如果有配置 dead-letter-exchange)。 在 1.6.3. RELEASE 中被 RabbitMq 认为是致命错误的异常有以下 6 种: o.s.amqp…MessageConversionException o.s.messaging…MessageConversionException o.s.messaging…MethodArgumentNotValidException o.s.messaging…MethodArgumentTypeMismatchException java.lang.NoSuchMethodException java.lang.ClassCastException 也就是说,当抛出以上异常及 ARADRE 时,该消息一定不会重新入队,即便 defaultRequeueRejected 的值为 true。 下面看看 Spring-RabbitMq 是如何实现的: 在源码中,异常在 AbstractMessageListenerContainer 中被包装在 ListenerExecutionFailedException 中之后还会经由 ErrorHandler 的 handleError 方法处理, 默认的 ErrorHandler 是 ConditionalRejectingErrorHandler 。 我们也可以实现自己的 ErrorHandler 来控制需要丢弃消息的异常,只要实现 org.springframework.util.ErrorHandler 接口,然后将listenerContainer 中的 errorHandler 参数指定我们自定义的 handler 即可。 ConditionalRejectingErrorHandler 中配置有 FatalExceptionStrategy,会调用 FatalExceptionStrategy 中的 isFatal 方法来判断异常是不是属于致命异常。 ConditionalRejectingErrorHandler 的具体实现如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374public class ConditionalRejectingErrorHandler implements ErrorHandler {private final FatalExceptionStrategy exceptionStrategy; @Overridepublic void handleError(Throwable t) { if (this.logger.isWarnEnabled()) { this.logger.warn(\"Execution of Rabbit message listener failed.\", t); } // 如果是致命异常,则转为 AmqpRejectAndDontRequeueException 抛出 if (!this.causeChainContainsARADRE(t) && this.exceptionStrategy.isFatal(t)) { throw new AmqpRejectAndDontRequeueException(\"Error Handler converted exception to fatal\", t); }}/** * @return true if the cause chain already contains an * {@link AmqpRejectAndDontRequeueException}. */private boolean causeChainContainsARADRE(Throwable t) { Throwable cause = t.getCause(); while (cause != null) { if (cause instanceof AmqpRejectAndDontRequeueException) { return true; } cause = cause.getCause(); } return false;}/** * Default implementation of {@link FatalExceptionStrategy}. * @since 1.6.3 */public class DefaultExceptionStrategy implements FatalExceptionStrategy { // 判断传入参数 是不是 致命异常 @Override public boolean isFatal(Throwable t) { if (t instanceof ListenerExecutionFailedException && isCauseFatal(t.getCause())) { if (ConditionalRejectingErrorHandler.this.logger.isWarnEnabled()) { ConditionalRejectingErrorHandler.this.logger.warn( \"Fatal message conversion error; message rejected; \" + \"it will be dropped or routed to a dead letter exchange, if so configured: \" + ((ListenerExecutionFailedException) t).getFailedMessage()); } return true; } return false; } private boolean isCauseFatal(Throwable cause) { return cause instanceof MessageConversionException || cause instanceof org.springframework.messaging.converter.MessageConversionException || cause instanceof MethodArgumentNotValidException || cause instanceof MethodArgumentTypeMismatchException || cause instanceof NoSuchMethodException || cause instanceof ClassCastException || isUserCauseFatal(cause); } /** * 通过重写该方法来添加自定义的异常 * Subclasses can override this to add custom exceptions. * @param cause the cause * @return true if the cause is fatal. */ protected boolean isUserCauseFatal(Throwable cause) { return false; } }} 代码比较长,简单来说,就是 ConditionalRejectingErrorHandler 的 handleError 会先判断接到的异常中的 cause 是不是 ARADRE,如果不是再调用 FatalExceptionStrategy 的 isFatal 方法,判断是不是致命异常中的一种,如果是,则将异常转为 ARADRE 抛出,该消息也就不会重新入队。 如果想要把自定义的异常加入到 fatalException, 一个简单的办法就是提供新的 FatalExceptionStrategy ,只要继承 ConditionalRejectingErrorHandler.DefaultExceptionStrategy 并重写 isUserCauseFatal(Throwable cause) 方法,在方法里对于需要丢弃消息的异常返回 true即可。 再简单看下,RabbitMq 判断是否需要将消息重入队列的部分逻辑。 123456789// We should always requeue if the container was stoppingboolean shouldRequeue = this.defaultRequeuRejected || ex instanceof MessageRejectedWhileStoppingException;Throwable t = ex;while (shouldRequeue && t != null) { if (t instanceof AmqpRejectAndDontRequeueException) { shouldRequeue = false; } t = t.getCause();} 根据上面的代码,如果处理消息时出现异常,在判断是否需要入队时,会将 shouldRequeue 变量等于 this.defaultRequeuRejected ||ex instanceof MessageRejectedWhileStoppingException 的值,然后如果异常是 ARADRE, 不管之前 shouldRequeue 的值是什么,都会被置为 false。最后根据 shouldRequeue 的值来决定是否需要重新入队。 可以用下图总结一下: 最后。。Cookie 宝宝祝大家新春快乐~~","raw":null,"content":null,"categories":[{"name":"rabbitmq","slug":"rabbitmq","permalink":"http://yemengying.com/categories/rabbitmq/"}],"tags":[{"name":"rabbitmq","slug":"rabbitmq","permalink":"http://yemengying.com/tags/rabbitmq/"},{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"为Rabbitmq中的Jackson2JsonMessageConverter自定义ClassMapper","slug":"rabbitmq-classMapper","date":"2017-01-03T14:40:30.000Z","updated":"2018-12-13T04:11:34.000Z","comments":true,"path":"2017/01/03/rabbitmq-classMapper/","link":"","permalink":"http://yemengying.com/2017/01/03/rabbitmq-classMapper/","excerpt":"新年第一篇~~ 🐣🐥🐤🐔","keywords":null,"text":"新年第一篇~~ 🐣🐥🐤🐔 消息队列算是各个系统间通信比较常见的方式了。我们公司用的是是基于 AMQP 协议的 RabbitMq。在 Spring-AMQP 中比较重要的类就是 Message,因为要发送的消息必须要构造成一个 Message 对象来进行传输。Message 对象包括两部分 Body 和 Properties,Body 就是真正要发送的消息内容,Properties 就是和消息相关的一些属性(消息头,要发送的交换机,routingKey等等),主要结构如下:1234567public class Message { private final MessageProperties messageProperties; private final byte[] body;} 消息生产者构造好 Message 之后,就会将 Message 发送到指定的 Exchange (交换机),再根据 Exchange 的类型及 routing-key 将消息路由到相应的 queue 中,最后被监听该 queue 的消费者消费,大致如下图: 不过每次发消息都要自己构造 Message 对象比较麻烦。Spring-AMQP 允许我们直接使用自定义的类,然后会利用指定好的 MessageConverter 将自定义的类转换为 Message 进行发送,在接收时也会利用 MessageConverter 将接收到的 Message 对象转成需要的对象。Spring-AMQP 提供了多种 MessageConverter,比如 SimpleMessageConverter,SerializerMessageConverter,Jackson2JsonMessageConverter,MarshallingMessageConverter等等,如果发送的消息对象不是 Message 实例,并且没有指定 MessageConverter 的话,默认用 SimpleMessageConverter。以上各种 MessageConverter 归根结底都是实现了 MessageConverter 接口,该接口只有两个方法:1234public interface MessageConverter { Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException; Object fromMessage(Message message) throws MessageConversionException;} 这两个方法一个是在发送消息时将我们的消息对象转换成标准的 Message 对象,另一个是在接受消息时将 Message 对象转换为相应的对象。比较常用的 Converter 就是 Jackson2JsonMessageConverter(以下简称 JsonMessageConverter),在发送消息时,它会先将自定义的消息类序列化成json格式,再转成byte构造 Message,在接收消息时,会将接收到的 Message 再反序列化成自定义的类。大致流程如下图: 不过使用 JsonMessageConverter 时有一个小问题,在不对它进行任何改造的前提下,发送消息的类和接受消息的类必须是一样的,不仅是要里面的字段一样,类名一样,连类的包路径都要一样。 所以当系统1使用 JsonMessageConverter 发送消息类A给系统2时,系统2可以有如下几种方式来接收: 1.依赖系统1的jar包,直接使用类A来接收 2.不依赖系统1的jar包,自己建一个和A一模一样的类,连名称,包路径都一样 3.负责监听 queue 的类实现 MessageListener 接口,直接接收 Message 类,再自己转换 上面三个方法都不是很好,按照正常的想法,我们肯定是期望系统2直接使用自己的类来接收就可以了,只要与A类的字段名一样即可。那有没有方法可以让系统2既不依赖无用的jar包,也不用建立个与自己命名规范不相符的类, 也无需自己转换呢? 要解决这个问题,就要先看看 JsonMessageConverter 是如何将 Message 进行反序列化的。在 JsonMessageConverter 的 fromMessage 方法中有这么一段:123456789if (getClassMapper() == null) { JavaType targetJavaType = getJavaTypeMapper() .toJavaType(message.getMessageProperties()); content = convertBytesToObject(message.getBody(), encoding, targetJavaType);} else { Class<?> targetClass = getClassMapper().toClass( message.getMessageProperties()); content = convertBytesToObject(message.getBody(), encoding, targetClass);} 就是说默认情况下,JsonMessageConverter 使用的 ClassMapper 是 DefaultJackson2JavaTypeMapper,在转换时通过 Message 的 Properties 来获取要转换的目标类的类型。通过 Debug 可以发现,目标类的类型是存储在 Message 的 Proterties 的 一个 headers 的 Map 中,Key 叫“__TypeId__”。所以只要想办法在传输消息时更改__TypeId__的值即可。 下面是解决办法,在消息的生产者端为 JsonMessageConverter, 设置一个自定义的 ClassMapper,重写 fromClass 方法,将 __TypeId__ 的值设为消费端用来接收的类的路径+名称。当然了,也可以在消费者端重写toClass方法,直接返回想要转换的目标类的类类型。两种选一种就可以。 123456789101112131415161718@Beanpublic Jackson2JsonMessageConverter customConverter() { Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); converter.setClassMapper(new ClassMapper() { @Override public Class<?> toClass(MessageProperties properties) { throw new UnsupportedOperationException(\"this mapper is only for outbound, do not use for receive message\"); } @Override public void fromClass(Class<?> clazz, MessageProperties properties) { properties.setHeader(\"__TypeId__\", \"com.xxx.B\"); } }); return converter;} 感觉自己语言组织能力退化了。。。。。","raw":null,"content":null,"categories":[{"name":"rabbitmq","slug":"rabbitmq","permalink":"http://yemengying.com/categories/rabbitmq/"}],"tags":[{"name":"rabbitmq","slug":"rabbitmq","permalink":"http://yemengying.com/tags/rabbitmq/"},{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"log 不打印异常堆栈","slug":"log4j-not-printing-stacktrace-for-eception","date":"2016-12-18T06:17:33.000Z","updated":"2018-12-13T04:14:48.000Z","comments":true,"path":"2016/12/18/log4j-not-printing-stacktrace-for-eception/","link":"","permalink":"http://yemengying.com/2016/12/18/log4j-not-printing-stacktrace-for-eception/","excerpt":"和上篇内容并不重复 🙃","keywords":null,"text":"和上篇内容并不重复 🙃 最近由于规则引擎有问题,导致产线上的一个 job 会抛 NullPointerException。本来这是个已知的问题,也没什么,已经联系对应的人去修复了。可由此发现了另外一个问题, fireman 的告警邮件只有异常的名称,而没有异常堆栈。 这就很令人懵圈了,因为不知道是已知的规则引擎的问题还是其他问题。 先看了下对应 job 的代码,确认打印异常的姿势是正确的, 本地也可以正常打印。然后去搜了下对应日期的 log 文件,确实有一堆 NPE 的报错,不过惊喜的发现在一开始的时候其实是有打出堆栈的,只是到后面就没有了。 最后终于在 stackoverflow 上找到了答案。 The compiler in the server VM now provides correct stack backtraces for all “cold” built-in exceptions. For performance purposes, when such an exception is thrown a few times, the method may be recompiled. After recompilation, the compiler may choose a faster tactic using preallocated exceptions that do not provide a stack trace. To disable completely the use of preallocated exceptions, use this new flag: -XX:-OmitStackTraceInFastThrow. 个人理解就是,JVM 为了性能会做优化,如果频繁的抛出某个异常,会重新编译,不再打印异常堆栈。解决这个问题也比较简单,如果不想每次都去查前面的 log 去看堆栈,只要在启动参数加上 -XX:-OmitStackTraceInFastThrow,就可以禁用该优化,强制打印异常堆栈。这样可能会导致,log 文件过大,不过产线上今天之前的 log 文件都会被压缩,所以感觉问题也不大。 Ps:用 iPic 上传图片真是好用到飞起,非常适合我这种喜欢插图星人,多谢洪菊的良心推荐。","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"}]},{"title":"Clean Code, Clean Log","slug":"clean-code-clean-log","date":"2016-11-18T03:14:13.000Z","updated":"2018-12-13T04:20:09.000Z","comments":true,"path":"2016/11/18/clean-code-clean-log/","link":"","permalink":"http://yemengying.com/2016/11/18/clean-code-clean-log/","excerpt":"最近提的 PR 都有关于 Log 的 comment,不能忍,以下内容总结整理自明佳的 Comment 和网络资料,只是为了以后提 PR 之前过来扫一眼,尽量避免 Log 上的疏忽,不一定适用于所有人。","keywords":null,"text":"最近提的 PR 都有关于 Log 的 comment,不能忍,以下内容总结整理自明佳的 Comment 和网络资料,只是为了以后提 PR 之前过来扫一眼,尽量避免 Log 上的疏忽,不一定适用于所有人。 在程序中的适当位置打 Log 的重要性就不用多说了,很多人应该都体会过线上有 Bug 却由于没有打 log 而不好 troubleshooting 的经历。 相关文档(康桑哈密达) Clean code, clean logs(👍很赞) SLF4J VS Log4J有很多关于打 Log 的第三方库,也没有多研究过,接触过的就是 SLF4J 和 Log4J 了,不过墙裂建议用 SLF4J,使用占位符 {} 真的比加号拼接字符串可读性提高N倍啊!!! 感受一下<( ̄︶ ̄)>1234// SLF4J, goodlog.error(\"Invest loan failed, loan Id: {}, error: {}\", loanId, errorMsg);// Log4J, badlog.error(\"Invest loan failed, loan Id:\" + loanId + \" error: \" + errorMsg); 当然,SLF4J 还有其他的优点,比如不用字符串拼接,节省了创建 String 对象所耗费的资源之类的。不过我最看重的就是可读性高了。 Logging Level ERROR - 记录一些比较严重的错误,比如一些严重异常,数据库链接不可用等等 WARN - 记录一些系统可以容忍的异常,或者是一些警示信息。比如:”Current data unavailable, using cached values”。 INFO - 记录一些比较重要的操作,能反映程序运行状态的。比如:”[Who] booked ticket from [Where] to [Where]” DEBUG - 一些帮助调试的信息 TRACE - 嗯,这个级别俺也没用过。 Pay attention Log 信息首字母大写这点完全是为了看上去舒服,至于到底需不需要大写,见仁见智吧~,不过我还是要注意一下,要大写。 1234// goodlog.error(\"Invest loan failed, loan Id: {}, error: {}\", loanId, errorMsg);// badlog.error(\"invest loan failed, loan Id: {}, error: {}\", loanId, errorMsg); 避免 Log 中的 NullPointerException如果像下面这样记 Log,要注意确保 loan 不会为null, 不然打 Log 时抛个 NPE,想想就蛋疼。 1log.info(\"Invest loan : {}\", loan.getId()); Log 的信息简洁有用Log 的内容一定要是有用的,能反映出程序的运行状态,能帮助定位错误。 1234// goodlog.info(\"Invest loan with id:{}\", loanId);// badlog.info(\"Invest loan\"); 记录某些方法的入参和出参记录方法的入参和出参,也可以帮助我们定位问题。特别是调用提供接口给其他系统调用的时候,记录入参可以帮助分辨到底是谁的锅🌚。 123456public String printDocument(Document doc, Mode mode) { log.debug(\"Entering printDocument(doc={}, mode={})\", doc, mode); String id = //Lengthy printing operation log.debug(\"Leaving printDocument(): {}\", id); return id;} 合适的记录异常大家都知道要在记录程序运行中抛出的异常,但有的时候方式可能是不对的。 12345678910111213141516try{ throw new NullPointerException(\"Just for test\"); } catch (Exception e){ log.error(e); //A log.error(e, e); //B log.error(\"\" + e); //C log.error(e.toString()); //D log.error(e.getMessage()); //E log.error(null, e); //F log.error(\"\", e); //G log.error(\"{}\", e); //H log.error(\"{}\", e.getMessage()); //I log.error(\"Error reading configuration file: \" + e); //J log.error(\"Error reading configuration file: \" + e.getMessage()); //K log.error(\"Error reading configuration file\", e); //L } 在上面 12 种打印异常的方式中,只有 G 和 L是正确的。A 和 B 在使用 SLF4J 时会编译不通过, 其他的几种要么不会打印异常堆栈,要么会打印出不正确的信息。比如,E 的方式只会打印”Just for test”的信息,而不会打印异常类型和异常堆栈。在 SLF4J 中,第一个参数是文本信息,简单描述一下异常;第二个参数要传异常本身,而不是e.getMessage()或e.toString(),这样才能打印出异常堆栈,方便定位问题。 希望可以消灭和 Log 有关的 Comment。 欢迎指正错误,欢迎一起讨论~(≧▽≦)/~。","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"}]},{"title":"【转】EVE 早期成员面基","slug":"i-love-eve","date":"2016-11-15T05:12:46.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2016/11/15/i-love-eve/","link":"","permalink":"http://yemengying.com/2016/11/15/i-love-eve/","excerpt":"第一次转别人的博客,想看原文的请移步三“观”茅庐,我才不会告诉你们原文有大神高清无码照的!!!","keywords":null,"text":"第一次转别人的博客,想看原文的请移步三“观”茅庐,我才不会告诉你们原文有大神高清无码照的!!! 先说点自己想说的, EVE 是毕业后第一份工作做的第一个产品(也可以说第一份工作做得唯一的产品,后面的那个我实在不想承认是我写的🙄),做 EVE 的那段日子是到目前为止毕业后最开心的时光,学习到了很多东西也认识了很多很棒的小伙伴,不过也就是因为那段时光太过美好,才导致在部门拆分,EVE 团队大换血之后萌生了离职的念头。唉,没有对比就没有伤害啊。不过还是很感谢前前司,老大还有磊哥收留技艺不精的我。 上周和之前的 EVE 小伙伴搞了次聚餐,见到了一年多没见的 sunshine 大神,之前做 EVE 时,前期一直和大神联调,虽未看过大神的代码,但也在 API 文档评审时深刻感受到了大神的代码洁癖。嗯,有代码洁癖的人代码一定写的好。 最后,希望下次聚餐可以听到大神讲段子😏。 —————————————分割线,以下内容来自hongju’s blog————————————————— 这次面基的成因主要还是因为前两天看微信通讯录,sunshine 大神的头像换成了一只狗。于是就聊了起来,然后就有了今晚的这次聚餐~ sunshine 大神去年离职后,大约在十月一之前聚过一次餐,当时住的也比较近,后来大神搬到浦东,于是一年未见。于是又约到阿姨,卢神,凑了一次烤肉。 未经大神同意,先偷偷的放一张 sunshine 的照片吧~ <此处应该有照片> 大神,阿姨,卢神和我们当时一起做在 * 公司做 EVE 这款产品,阿姨和卢神是EVE的后端主力,sunshine 算是我的 mentor,带着我用 cordova 做客户端,说白了就是用 H5 技术来做客户端。那段日子学到了很多东西,sunshine 大神的代码一直很 clean,给我做了非常好的榜样。以至于这一年多来,没见过比大神更加干净的代码。也就说,当我看那些人代码的时候,抑制不住吐槽的情绪。在 sunshine 的良好影响下,我自己也尽量写最干净的代码。 除了代码之外,sunshine 对技术的追求也给我树立了很好的榜样。其实,在公司就会感觉到很多人对技术仅限于表面,或者能用就行。一点点追求都没有,这点,很致命。 说完技术,再说说 sunshine 大神的 RP,大神不仅技术也好,做朋友也很好。非常踏实,有追求,因此我觉得自己非常幸运,能在自己毕业正式签约的第一家公司就能遇到这么好的 mentor。可惜的是,sunshine 离职,丢下的 EVE 这款美丽的产品于不靠谱的 sjn 之手。这里不再吐槽了。最近得知,EVE 这款产品更名 **。靠! 说完,sunshine , 这篇文章基本就算完事了。 EVE 的早期成员都是非常靠谱,小团队,又能成事,现在很多人离开了公司,一部分人也分布在不同的业务线上,并且都混得很不错。期待着,哪一天能再次聚在一起~也不枉一起为了 EVE 熬了许多夜。","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"},{"name":"转载","slug":"转载","permalink":"http://yemengying.com/tags/转载/"}]},{"title":"Something about Spring Transaction","slug":"something-about-spring-transaction","date":"2016-11-14T13:30:34.000Z","updated":"2018-12-13T04:23:34.000Z","comments":true,"path":"2016/11/14/something-about-spring-transaction/","link":"","permalink":"http://yemengying.com/2016/11/14/something-about-spring-transaction/","excerpt":"记录一个上周遇到的小问题。","keywords":null,"text":"记录一个上周遇到的小问题。 后端开发免不了要和事务打交道,比较常用的就是利用 Spring 框架的声明式事务管理,简单的说就是在需要事务管理的类或方法上添加 @Transactional 注解,然后在配置类上添加 @EnableTransactionManagement注解(这里配置采用 JavaConfig 的方式,如果是 XML, 是要在 XML 文件中添加<tx:annotation-driven/>)。然后 Spring 框架会利用 AOP 在相关方法调用的前后进行事务管理。 一直以来也没出什么岔子,直到。。。。。。。。 上周写了段大概长下面样纸的代码。12345678910111213141516public class GiraffeServiceImpl implements GiraffeService { public void A(List<Giraffe> giraffes) { for (Giraffe giraffe : giraffes) { B(giraffe); } } @Transactional(\"transactionManager\") public void B(Giraffe giraffe) { // Step 1: update something // Step 2: insert something // Step 3: update something }} 大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。 然鹅,通过下面的 Junit 测试方法 A 的时候发现方法 B 的事务并没有开启, 而直接调用方法 B 事务是正常开启的。 1234567891011121314151617public class GiraffeServiceTest{ @Autowired private GiraffeService giraffeService; // 没有开启事务 @Test public void testA() { giraffeService.A(); } // 正常开启事务 @Test public void testB() { giraffeService.B(); }} 问了下明佳和超哥之后,终于有点明白了🤔 Spring 在加载目标 Bean 的时候,会为声明了 @Transactional 的 目标 Bean 创造一个代理类,而目标类本身并不能感知到代理类的存在。调用通过 Spring 上下文注入的 Bean 的方法, 并不是直接调用目标类的方法。而是先调用代理类的方法,再调用目标类的。 对于加了@Transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器TransactionInterceptor开启事务,然后在调用目标类的方法,最后在调用结束后,TransactionInterceptor 会提交或回滚事务,大致流程如下图。 而对于第一段的代码,我在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以。。。事务是不会开启滴。 解决办法也蛮简单,通过实现ApplicationContextAware接口获得 Spring 的上下文,然后获得目标类的代理类,通过代理类的对象,调用方法 B,即可。1234567891011121314151617181920public class GiraffeServiceImpl implements GiraffeService,ApplicationContextAware{ @Setter private ApplicationContext applicationContext; public void A(List<Giraffe> giraffes) { GiraffeService service = applicationContext.getBean(GiraffeService.class); for (Giraffe giraffe : giraffes) { service.B(giraffe); } } @Transactional(\"transactionManager\") public void B(Giraffe giraffe) { // Step 1: update something // Step 2: insert something // Step 3: update something }} stackoverflow 上也有相关的问题:@Transactional - What happens in background?@Transactional Annotation : Self Invocation 唉,都快写完了,还没等来 wuli 悦儿","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"},{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"Running Man 7012","slug":"runningman-7012","date":"2016-10-27T13:47:17.000Z","updated":"2018-12-13T04:34:05.000Z","comments":true,"path":"2016/10/27/runningman-7012/","link":"","permalink":"http://yemengying.com/2016/10/27/runningman-7012/","excerpt":"前两天刷微博时突然看到 Gary 即将从 《Running Man》(以下简称 RM) 下车的消息,有点伤感,却并不惊讶。可能从2016年开始,就隐约感到节目到了瓶颈,收视率也一直上不来,作为一个铁杆粉丝也不得不承认 RM 没有以前好看了,很长时间没有一期看好几遍的情况了。不过追了 RM 四年,看 RM 早已不是为了娱乐搞笑,而变成了自己生活的一部分,只要看到他们七个就开心。也许在不久的将来连 RM 都会停播了,但现在只要 RM 更新一期,就会看一期,哪怕就是他们几个嗑瓜子闲聊天。即便有再火再好看的节目,自己也早没了当初的心境去追了。改编我前女神的一句歌词就是“有的综艺说不清哪里好,但就是谁都替代不了”。","keywords":null,"text":"前两天刷微博时突然看到 Gary 即将从 《Running Man》(以下简称 RM) 下车的消息,有点伤感,却并不惊讶。可能从2016年开始,就隐约感到节目到了瓶颈,收视率也一直上不来,作为一个铁杆粉丝也不得不承认 RM 没有以前好看了,很长时间没有一期看好几遍的情况了。不过追了 RM 四年,看 RM 早已不是为了娱乐搞笑,而变成了自己生活的一部分,只要看到他们七个就开心。也许在不久的将来连 RM 都会停播了,但现在只要 RM 更新一期,就会看一期,哪怕就是他们几个嗑瓜子闲聊天。即便有再火再好看的节目,自己也早没了当初的心境去追了。改编我前女神的一句歌词就是“有的综艺说不清哪里好,但就是谁都替代不了”。 好了,结束伤感的话题。 本来想写篇 RM 科普文,但对 RM 不感冒估计怎么说也不会感兴趣吧。所以就简单写点,纪念下我爱的七只和第一次也是最后一次追了四年看过每一期的 RM。部分资料和图片来源于网络,权侵删。 七只RM 能收获那么的喜爱和关注,虽然离不开制作组的精心制作和创意,但更重要的是七个 MC 的人格魅力。So…..就从介绍七只开始,聊聊 RM 吧。 刘在石: 外号:刘大神、刘鲁斯·威利斯、刘姆斯·邦德、蚂蚱/蚱蜢、刘赫 国民 MC,也应该是目前韩国地位最高的 MC 了。RM 的灵魂人物,美好的一塌糊涂(我是不是和驴得水里面的教育部长一样不会用词🤓)。会记住工作人员的名字,会在录制中去帮助路边的市民,会隐藏自己努力突出其他人,亲民,谦逊,幽默,温暖(此处省略10万字)。跑步很快,所以有个外号叫刘鲁斯·威利斯。如果硬要说一个缺点的话,那就是。。。。。恩,摘了眼睛颜值骤降。 池石镇: 外号:王鼻子、黑斑羚、Race Starter 王👃大叔,RM 中年龄最大的,擅长身体搞笑。是 RM 中的最弱体, 监狱三人组之一。几乎每次撕名牌都是最先去监狱的,所以也被称为“Race Starter”(比赛开始)。其实鼻子大叔对 RM 的贡献是很大的,在前期所有人的定位还不明确的时候,RM 的笑点都是靠大神和鼻子大叔扛起来的,鼻子叔是那种身体素质一般,但口才超棒的人,很会制造笑料。 宋智孝: 外号:懵智、不良智孝、Ace、金智孝、周一情侣 RM 中唯一的女 MC。第一眼感觉就是漂亮,很耐看。但深入了解下去,才发现漂亮在智孝身上已经算不上什么闪光点了。第一次见到刚睡醒顶着一头乱乱的头发就去录影的女演员;第一次见到在车上张着嘴睡的女演员;能摔跤,能劈砖,能下泥潭,能蹦极,完全不输给男生;气急了还会爆两句粗口,瞬间变身不良智孝;很聪明,是 RM 中单独获胜次数最多的;这样的懵智怎么可能不喜欢😍 金钟国: 外号:能力者、老虎、钟淑、斯巴达国 很具有反转魅力的一个人。唱歌时嗓音很细腻温柔,撕名牌时却以一抵十;看身材很剽悍,实际却很爱卖萌。是 RM 中的能力者,撕遍天下无敌手,不过也因为太强,经常在撕名牌时被围剿😭。要说能力者有什么怕的,应该就是“背叛长颈鹿”(李光洙)了,就是本能坑害老虎。 咳咳,只是为了节目效果,国儿和光洙还是很有爱的🙃。 姜gary: 外号:狗哥、平和Gary、偶尔能力者、鱿鱼、周一情侣 本职工作是音乐人,但却因为综艺火了起来。在节目中,总是不经意的爆发一下,所以被称为偶尔能力者,前两期的最强者特辑, gary 都是最后的获胜者。gary 的诸多定位中,最火的应该就是和智孝的周一情侣了。虽然在2016年的节目中,制作组特意淡化了周一cp间的互动,但我一直觉得 gary 就是智孝的“姜盖里”,会偷偷放走她的“姜盖里”。多亏了周一情侣,让我在看综艺的同时,也看了部偶像剧🌚 哈哈: 外号:Haroro、企鹅、花心哈哈 哈哈,本名河东勋,不过还是更习惯哈哈这个名字。早期在 RM 的定位是“花心哈哈”,每次一来女嘉宾,就会说“成为我最后的爱吧 (。♥‿♥。)”。不过这些只是为了活跃气氛,结婚之后“花心哈哈”的定位就没有,变成了“顾家好男人”,最近听说哈哈马上要有第二个孩子了,在渡汉江那期说的愿望也要实现了。 李光洙: 外号:长颈鹿、亚洲王子、李光子、光凡达、光蟾蜍、情景剧发烧友 绝对的男神,我博客的标准结尾。其实在一开始,光洙可以说是最不起眼的,作为一个综艺新手,没有大神,国儿,haha从以前节目积累下的观众缘,没有王鼻子老练,没有 gary 有那么多的歌迷,也没有宋仲基那么明显的颜值优势。但在自己的努力和其他人的帮助下,一点点找到了自己的定位(陷害光洙,easy 兄弟,长颈鹿)。在节目中,光洙最明显的特质应该就是“背叛”了,每次都让人觉得好气啊,然后就又笑的肚子疼。。。。不知道为什么七个 MC 中,最最喜欢光洙,连带着把很多网站的用户名都换成了 giraffe,可能天生就对这种又努力又逗比的人没有抵抗力吧。 放一下女装造型 不过一开始的胡子造型真是亮瞎了我的卡姿兰大眼睛。 哈哈哈哈,原谅我,爱到深处自然黑啊。 墙裂推荐本来想按照好看程度排个最好看的十期,最后发现实在选不出来。每一期都敲好看,都值得看。就随便推几期吧。 20130825 按照电影雪国列车为主题拍摄的一期,玩游戏整理排名,国儿和大神互打手掌那段简直笑屎我了。 20130526 嘉宾是金秀贤和李玹雨,全集高能,我大神叼着接力棒吃洋葱圈简直萌翻我。 20141123 这集造型极其哇撒,放张图,大家憋说话,用心感受 20141012 七个成员都变成了超级英雄,高空测试胆量那段笑的我肚子疼。 20141109 女装特辑,最爱wuli光子和钟淑了。 20130915 很多人应该冲着嘉宾是Bigbang看过这期了,但我推荐这期是因为这期有我周一情侣的荧屏初吻,我的少女心啊😍 20141214 通过一张图就能知道大神为了赢有多拼。 20160403 2016年里的觉得最好看的一期。特别是wuli光洙被haha愚弄的那段。 20111225 第一期最强者特辑。RM 每年都会有一期是最强者特辑,遗憾的是国儿还从来没有赢过,希望下一期最强者战可以是国儿赢。 20120624 RM 第一百期特辑,诸神之战, 嘉宾是金喜善, 结局蛮不错的 20150125 刘姆斯邦德 vs 光佛岩,结尾狗哥真是蠢萌的让人心疼 20110911 狗哥做间谍🕵的一期,不应该说是狗哥以为他做间谍,但是其他成员其实都知道他是间谍,他并不知道其他成员知道他是间谍的一期。。。艾玛啊,说的好绕🙃 又到睡觉觉的时候了写的差不多了,这么点内容根本表达不出 RM 带给我的快乐。生活大爆炸也开始更新第十季了,很有可能是最后一季,说不定过两天又要写篇文章纪念下贱萌贱萌的 Sheldon, 总被压迫的 Leonard 和女汉纸 Penny 了。","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"}]},{"title":"滚蛋吧~ XML 君","slug":"javaConfig-vs-XML","date":"2016-10-15T07:20:20.000Z","updated":"2018-12-13T07:33:20.000Z","comments":true,"path":"2016/10/15/javaConfig-vs-XML/","link":"","permalink":"http://yemengying.com/2016/10/15/javaConfig-vs-XML/","excerpt":"Long long time ago, 在《Spring In Action》一书中看到这么一句话 JavaConfig is the preferred option for explicit configuration because it’s more powerful, type-safe, and refactor-friendly 。不过当时并不知道 JavaConfig 是神马东东就选择性忽略了。最近新的项目采用了 Spring Boot + JavaConfig ,接触了一个星期的 JavaConfig,感觉还不错,简单比较下它和 XML。由于接连看了 4 部韩国灾难片心情比较蓝瘦,就暂时不要指正错误了,错就错吧(づ。◕‿‿◕。)づ","keywords":null,"text":"Long long time ago, 在《Spring In Action》一书中看到这么一句话 JavaConfig is the preferred option for explicit configuration because it’s more powerful, type-safe, and refactor-friendly 。不过当时并不知道 JavaConfig 是神马东东就选择性忽略了。最近新的项目采用了 Spring Boot + JavaConfig ,接触了一个星期的 JavaConfig,感觉还不错,简单比较下它和 XML。由于接连看了 4 部韩国灾难片心情比较蓝瘦,就暂时不要指正错误了,错就错吧(づ。◕‿‿◕。)づ XML虽然早在 Spring 3 中就开始支持以 JavaConfig 的方式配置项目,但感觉目前主流的还是以 XML + Annotation 的方式,很少见到使用 JavaConfig 的。可能有点以偏概全了,但在前司和前前司接触到的项目都是以 XML + Annotation 混合的方式配置的,即在 XML 配置文件中开启注解扫描, 业务 bean 的配置注入采用注解( @Serivce, @Autowire 等等),全局的一些配置(如 MyBatis 的 DataSource,SqlSessionFactory ,web.xml 等等)使用 XML。 虽然 XML + Annotation 的方式比纯用 XML 配置的方式少写了很多 XML,但本质上还是基于 XML 的。 XML 的配置文件比较冗长,不易书写,而且可读性也不高。不知道大家感觉怎么样,反正我看着是挺头疼的╥﹏╥… JavaConfig先简单介绍一下 JavaConfig,JavaConfig即Java Configuration, 即用纯 Java 的方式来配置 Spring IoC 容器,允许开发者用代码来表示配置逻辑,不再需要 XML。粗略翻了两本关于 Spring Boot的书, JavaConfig 都是最推荐的配置方式。 使用 JavaConfig 的好处,Spring 官方文档中说的很清楚: JavaConfig 为依赖注入提供了一个真正面向对象的机制,这意味着开发者可以在配置代码中充分利用 Java 重用、继承和多态性等特性。 开发者可以完全控制实例化和依赖注入,即使是最复杂的对象也可以很优雅地处理。 因为使用 JavaConfig 只需要 Java,可以很容易的 refactor 代码,而无需再 IDE 之外使用特殊的工具或插件。 JavaConfig 其实很简单,主要是通过 @Configuration 和 @Bean 来进行配置。@Configuration 注解的作用是声明当前类是一个配置类, 就相当于一个 XML 文件。 @Bean 注解声明当前方法返回的是一个 bean。 可能这样说对于用惯了 XML 的人还是比较抽象的,下面看看这些注解与 XML 配置的对应关系。 JavaConfig 与 XML1.JavaConfig: @ConfigurationXML: 1234567891011121314<?xml version=\"1.0\" encoding=\"UTF-8\"?><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:tx=\"http://www.springframework.org/schema/tx\" xmlns:util=\"http://www.springframework.org/schema/util\" xmlns:p=\"http://www.springframework.org/schema/p\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd\"> </beans> 2.JavaConfig: @BeanXML: 12345<bean id=\"dataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\"> <property name=\"url\" value=\"jdbc:mysql://127.0.0.1:3307/giraffe\"/> <property name=\"username\" value=\"ymy\"/> <property name=\"password\" value=\"666666\"/></bean> 3.JavaConfig:@ComponentScanXML: 1<context:component-scan> 4.JavaConfig:@ImportXML: 1<import resource=\"XXX.xml\"/> 比如要配置一个 dataSource, 在 XML 中通常的做法是这样的: 123456<bean id=\"drMainDataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\"> <property name=\"url\" value=\"${mysql.datasource.url}\"/> <property name=\"username\" value=\"${mysql.datasource.username}\"/> <property name=\"password\" value=\"${mysql.datasource.password}\"/> <property name=\"maxActive\" value=\"{mysql.datasource.max-active}\"/></bean> 对应的 JavaConfig 是酱紫的: 123456789101112131415161718@Configurationpublic class DataAccessConfig extends ConfigurationSupport { @Bean(name = \"dataSource\") public DataSource mysqlDataSource(@Value(\"${mysql.datasource.url}\") String url, @Value(\"${mysql.datasource.username}\") String username, @Value(\"${mysql.datasource.password}\") String password, @Value(\"${mysql.datasource.driverClass}\") String driver, @Value(\"${mysql.datasource.max-active}\") int maxActive) { DruidAbstractDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setMaxActive(maxActive); return dataSource; }} 总结虽然才接触了一周的 JavaConfig, 但是相见恨晚啊, 个人还是更喜欢 JavaConfig 的配置方式的。JavaConfig 的配置文件可读性更高也更容易学习,记住简单的几个注解即可;借助 IDE 的力量,更不容易出错;而且脑袋再也不用在 Java 和 XML 间来回切换了,在搭配上 Gradle 简直破费科特!!!!! 相关文档 Spring JavaConfig Document 《Spring Boot 揭秘》 ——————————————-片 尾 彩 蛋 🎉🎉🎉—————————————————咳咳,预警预警!!!下面的部分与本文主旨无关。 首先,换了个新锅,可以预约煮粥,再也不用早起煮粥了,幸福感提升 200%。其次,在匿名人士的帮助下上线了新版未翻墙模式下的评论,优化了样式。唉,这是谁的男朋友这么有才华,好羡慕她哦︿( ̄︶ ̄)︿ 最后,嘿嘿,关注个公众号再走吧(^__^)","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"【译】S.O.L.I.D 原则在 Go 中的应用(上)","slug":"solid-go-design-1","date":"2016-09-11T07:09:18.000Z","updated":"2018-12-13T07:36:24.000Z","comments":true,"path":"2016/09/11/solid-go-design-1/","link":"","permalink":"http://yemengying.com/2016/09/11/solid-go-design-1/","excerpt":"最近两个月没有好好的看书学习,导致博客也水了两个月没写什么正经的。上周收到仓鼠🐹君萌萌哒的邮件之后,又激起了我写博客的欲望。由于自己最近灵感枯竭,所以我决定翻译一篇别人的O(∩_∩)O~。作为一个一直想学 Go,但想了好久还没入门的人,我挑了篇写 Go 的,顺便帮自己熟悉一下 Go。原文是作者根据自己 GolangUK 的演讲所整理的,全文以 SOLID 原则为线路讲述了什么样的 Go 代码才算是好代码,当然 SOLID 原则也适用于其他语言。原文比较长,所以准备分成上下两部分,也有十分非常以及特别大的可能是上中下(捂脸)。 咳咳,我果然是打脸体质,下翻译了一句就放弃了。不过,我把它交给了超靠谱的小伙伴。想看下的请移步【译】S.O.L.I.D 原则在 Go 中的应用(下) 捂。。。。。。。。还是不捂了,脸已经丢没了🙈","keywords":null,"text":"最近两个月没有好好的看书学习,导致博客也水了两个月没写什么正经的。上周收到仓鼠🐹君萌萌哒的邮件之后,又激起了我写博客的欲望。由于自己最近灵感枯竭,所以我决定翻译一篇别人的O(∩_∩)O~。作为一个一直想学 Go,但想了好久还没入门的人,我挑了篇写 Go 的,顺便帮自己熟悉一下 Go。原文是作者根据自己 GolangUK 的演讲所整理的,全文以 SOLID 原则为线路讲述了什么样的 Go 代码才算是好代码,当然 SOLID 原则也适用于其他语言。原文比较长,所以准备分成上下两部分,也有十分非常以及特别大的可能是上中下(捂脸)。 咳咳,我果然是打脸体质,下翻译了一句就放弃了。不过,我把它交给了超靠谱的小伙伴。想看下的请移步【译】S.O.L.I.D 原则在 Go 中的应用(下) 捂。。。。。。。。还是不捂了,脸已经丢没了🙈 原文链接:http://dave.cheney.net/2016/08/20/solid-go-design?utm_source=wanqu.co&utm_campaign=Wanqu+Daily&utm_medium=website原文作者:Dave Cheney 世界上有多少个 Go 语言开发者?介个世界上有多少 Go 开发者捏?在脑海中想一个数字,我们会在最后回到这个话题。 Code review有多少人将 code review 当做自己工作的一部分?[听演讲的人都举起了手]。为什么要做 code review?[一些人回答为了阻止不好的代码] 如果 code review 是为了捕捉到不好的代码,那么问题来了,你怎么判断你正在 review 的代码是好还是不好呢? 我们可以很容易的说出“这代码好辣眼睛”或者“这源码写的太吊了”,就像说“这画真美”,“这屋子真大气”一样。但是这些都是主观的,我希望找到一些客观的方法来衡量代码是好还是不好。 Bad code下面看一下在 code review 中,一段代码有哪些特点会被认为是不好的代码。 Rigid 代码是不是很僵硬?是否由于严格的类型和参数导致修改代码的成本提高 Fragile 代码是不是很脆弱?是否一点小的改动就会造成巨大的破坏? Immobile 代码是否难以重构? Complex 代码是否是过度设计? Verbose 当你读这段代码时,能否清楚的知道它是做什么的? 👆这些都不是什么好听的词,没有人希望在别人 review 自己代码时听到这些词。 Good design了解了什么是不好的代码之后,我们可以说“我不喜欢这段代码因为它不易于修改”或者“这段代码并没有清晰的告诉我它要做什么”。但这些并没有带来积极的引导。 如果我们不仅仅可以描述不好的设计,还可以客观的描述好的设计,是不是更有助于提高呢。 SOLID2002年,Robert Martin 出版了《敏捷软件开发:原则、模式与实践》一书,在书中他描述了可重用软件设计的五个原则,他称之为 SOLID 原则(每个原则的首字母组合在一起)。 单一责任原则 开放封闭原则 里氏替换原则 接口分离原则 依赖倒置原则 这本书有点过时了,书中谈论的语言都已经超过了十年之久。尽管如此,在谈论什么样的 Go 代码才是好代码时,SOLID 的原则依然可以给我们一些启发。 So,这也就是我花时间想在本文和大家一起讨论的。 单一责任原则SOLID 原则中的第一个原则就是单一责任原则。Robert C Martin 说过 A class should have one, and only one, reason to change(修改某个类的时候,原因有且只有一个),说白了就是,一个类只负责一项职责。 虽然 Go 语言中并没有类的概念–但我们有更鹅妹子嘤的 composition (组合)的特性。 为什么修改一段代码只负责一项职责如此重要呢?如果一个类有两个职责R1,R2,那么修改R1时,可能会导致也要修改R2。修改代码是痛苦的,但更痛苦的是修改代码的原因是由于修改其他代码引起的。 所以当一个类只负责一个功能领域中的相应职责时,可以修改的它的原因也就最大限度的变少了。 耦合 & 内聚这两个词是用来形容一段代码是否易于修改的。 耦合是指两个东西需要一起修改—对其中一个的改动会影响到另一个。 另一个相关但独立的概念是内聚,一般指相互吸引的迷之力量。 在软件开发领域中,内聚常常用来描述一段代码内各个元素彼此结合的紧密程度。 下面我准备从 Go 的包模型开始,聊聊 Go 开发中的耦合与内聚。 包名在Go中,所有代码都必须有一个所属的包。一个包名要描述它的用途,同时也是命名空间的前缀。下面是 Go 标准库中一些好的包名: net/http,提供 http 的客户端和服务端。 os/exec,可以运行运行外部命令。 encoding/json,实现了 JSON 文件的编码和解码。 不好的包名现在让我们来喷一些不好的包名。这些包名并没有很好的展现出它们的用途,当然了前提是它们有-_-|||。 package server 是提供什么?。。。好吧就当是提供一个服务端吧,但是是什么协议呢? package private 是提供什么?一些我不应该看👀的东西? 还有 package common, package utils,同样无法清楚的表达它们的用途,开发者也不易保持它们功能的专一性。 上面这些包很快就会变成堆放杂七杂八代码的垃圾堆,而且会由于功能太杂乱而频繁修改。 Go 中的 UNIX 哲学在我看来,任何关于解耦设计的讨论如果没有提到 Doug McIlroy 的 UNIX 哲学都是不完整的。UNIX 哲学就是主张将若干简洁,清晰的模块组合起来完成复杂的任务,而且通常情况下这个任务都不是原作者所能预想到的。 我想 Go 中的包正体现了 UNIX 哲学的精神。因为每一个包都是一个拥有单一责任的简洁的 Go 程序。 开放封闭原则第二个原则,也就是 SOLID 当中的 O,是由 Bertrand Meyer 提出的开放封闭原则。1988年,Bertrand Mey 在他的著作《面向对象软件构造》一书中写道:Software entities should be open for extension,but closed for modification(软件实体应当对扩展开放,对修改关闭)。 那么这个n年前的建议在 Go 语言中是如何应用的呢?123456789101112131415161718192021222324252627282930package mainimport ( \"fmt\")type A struct { year int}func (a A) Greet() { fmt.Println(\"Hello GolangUK\", a.year)}type B struct { A}func (b B) Greet() { fmt.Println(\"Welcome to GolangUK\", b.year)}func main(){ var a A a.year = 2016 var b B b.year = 2016 a.Greet()//Hello GolangUK 2016 b.Greet()//Welcome to GolangUK 2016} 上面的代码中,我们有类型A,包含属性 year 和一个方法 Greet。我们还有类型B,B中嵌入(embedding)了类型A,并且B提供了他自己的 Greet 方法,覆盖了A的。 嵌入不仅仅是针对方法,还可以通过嵌入使用被嵌入类型的属性。我们可以看到,在上面的例子中,因为A和B定义在同一个包中,所以B可以像使用自己定义的属性一样使用A中的 private 的属性 year。 所以,嵌入是实现 Go 类型对扩展开放非常鹅妹子嘤的手段。 12345678910111213141516171819202122232425262728293031package mainimport ( \"fmt\")type Cat struct{ Name string}func (c Cat) Legs() int { return 4}func (c Cat) PrintLegs() { fmt.Printf(\"I have %d legs\\n\", c.Legs())}type OctoCat struct { Cat}func (c OctoCat) Legs() int { return 5}func main() { var octo OctoCat fmt.Printf(\"I have %d legs\\n\", octo.Legs())// I have 5 legs octo.PrintLegs()// I have 4 legs} 在这个例子中,我们有一个 Cat 类型,它拥有一个 Legs 方法可以获得腿的数目。我们将 Cat 类型嵌入到一个新类型 OctoCat 中,然后声明 Octocat 有5条腿。然而,尽管 OctoCat 定义了它自己的 Legs 方法返回5,在调用 PrintLegs 方法时依旧会打印“I have 4 legs”。 这是因为 PrintLegs 方法是定义在 Cat 类型中的,它将 Cat 作为接收者,所以会调用 Cat 类型的 Legs 方法。Cat 类型并不会感知到它被嵌入到其他类型中,所以它的方法也不会被更改。 所以,我们可以说 Go 的类型是对扩展开放,对修改关闭的。 实际上,Go 类型中的方法比普通函数多了一点语法糖—-将接收者作为一个预先声明的形参。(译者注:这块理解了好久😖。。。,不懂得可以看这篇参考文档)1234567func (c Cat) PrintLegs() { fmt.Printf(\"I have %d legs\\n\", c.Legs())}func PrintLegs(c Cat) { fmt.Printf(\"I have %d legs\\n\", c.Legs())} 由于 Go 并不支持函数重载,所以 OctoCat 类型并不能替代 Cat 类型。这也将引出下一个原则—里氏替换原则。 且听下回分解。。。。。。。 ——————————————别看我,我只是个傲娇的分割线——————————————————————— 终于完成了上的部分↖(^ω^)↗,尽量在下周完成下。由于并不了解 Go 难免会有错误或翻译生硬的地方,欢迎指正错误,欢迎一起讨论~(≧▽≦)/~。 都看到这了,关注个公众号再走吧🙈","raw":null,"content":null,"categories":[{"name":"go","slug":"go","permalink":"http://yemengying.com/categories/go/"}],"tags":[{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"},{"name":"go","slug":"go","permalink":"http://yemengying.com/tags/go/"}]},{"title":"如何快速拥有产品的sense","slug":"how-to-get-pm-sense","date":"2016-08-23T08:32:31.000Z","updated":"2018-12-13T07:50:46.000Z","comments":true,"path":"2016/08/23/how-to-get-pm-sense/","link":"","permalink":"http://yemengying.com/2016/08/23/how-to-get-pm-sense/","excerpt":"趁着在家葛优躺的几天,培养培养自己在产品方面的技能,经过axure,sketch,xmind的重重磨炼,总结出来这篇文章,从三个方面讲讲如何才能在短时间内快速拥有产品的sense,画出高保真的原型。","keywords":null,"text":"趁着在家葛优躺的几天,培养培养自己在产品方面的技能,经过axure,sketch,xmind的重重磨炼,总结出来这篇文章,从三个方面讲讲如何才能在短时间内快速拥有产品的sense,画出高保真的原型。 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"}]},{"title":"【Spring】Xml解析相关","slug":"spring-xml","date":"2016-07-25T14:37:18.000Z","updated":"2018-12-13T07:51:24.000Z","comments":true,"path":"2016/07/25/spring-xml/","link":"","permalink":"http://yemengying.com/2016/07/25/spring-xml/","excerpt":"先请看下左上角,hiahia,新logo,si不si很漂酿,有个会设计的表哥就是好,又好又快,还不用钱。","keywords":null,"text":"先请看下左上角,hiahia,新logo,si不si很漂酿,有个会设计的表哥就是好,又好又快,还不用钱。 总结下最近看的 Spring Xml 解析相关的一点点东东,还没有看完。。。。 参考文档Spring 资源访问剖析和策略模式应用 题外话先说个在看源码时,发现的一个以前没有关注过的点。大神们在创建集合的时候,大多数都设置了一个预估的初始容量(2的幂数),而不是直接采用默认的初始容量( HashMap 中是16),就像下面这样:1234567891011121314151617/** Map from dependency type to corresponding autowired value */private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<Class<?>, Object>(16);/** Map of bean definition objects, keyed by bean name */private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);/** Map of singleton and non-singleton bean names, keyed by dependency type */private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);/** Map of singleton-only bean names, keyed by dependency type */private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);/** List of bean definition names, in registration order */private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);/** List of names of manually registered singletons, in registration order */private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16); 大神们这样写肯定是有好处的。不太了解其它集合类的实现,就以 HashMap 为例看一下。HashMap 底层的存储结构是一个 Entry 对象的数组(Java 8中是 Node 对象的数组),默认初始容量是16,负载因子是0.75。也就是说当元素个数超过16*0.75=12时,就要进行扩容,将数组大小扩大一倍,并计算元素在新数组中的位置,这个过程是比较耗费性能的。所以,个人觉得大神们这样写是因为如果直接采用默认的初始容量,那么在元素个数较少时,会浪费空间;元素个数较多时,又会造成频繁的扩容,耗费性能。 想起上次的需求,明明确定一定以及肯定评分只有5个,还是new了个默认容量(16)的map。 相关接口先理一理加载xml配置文件的相关接口1.Resource:采用了策略模式,是 Spring 资源访问策略的抽象,该接口有多种实现类,每个实现类代表一种资源访问策略,负责具体的资源访问。2.ResourceLoader:该接口的实现类可以获得一个 Resource 的实例。3.BeanDefinitionReader: 根据指定的 Resource 加载bean definition. 未完待续。。。。。。 本来是想多整理一点的,但是。。。听说新一期RM主角是wuli光洙,这还能忍,滚去看RM了。。。。","raw":null,"content":null,"categories":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"【Spring】Bean的生命周期","slug":"spring-bean-life-cycle","date":"2016-07-14T13:35:53.000Z","updated":"2018-12-13T07:52:27.000Z","comments":true,"path":"2016/07/14/spring-bean-life-cycle/","link":"","permalink":"http://yemengying.com/2016/07/14/spring-bean-life-cycle/","excerpt":"智商捉鸡🐔,实在没办法一下子理解Spring IoC和AOP的实现原理,看的闹心也不太懂,所以。。。决定拆成小的不能在小的一个个问题,一点点啃。今天先来看看Spring中Bean的生命周期。","keywords":null,"text":"智商捉鸡🐔,实在没办法一下子理解Spring IoC和AOP的实现原理,看的闹心也不太懂,所以。。。决定拆成小的不能在小的一个个问题,一点点啃。今天先来看看Spring中Bean的生命周期。 Spring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情,顺序是怎样的,在容器关闭的时候,又会做哪些事情。 示例代码git地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-giraffeInSpring-giraffeInSpring\", \"giraffe0813\", \"giraffeInSpring\", \"giraffeInSpring\", false); spring版本:4.2.3.RELEASE鉴于Spring源码是用gradle构建的,我也决定舍弃我大maven,尝试下洪菊推荐过的gradle。运行beanLifeCycle模块下的junit test即可在控制台看到如下输出,可以清楚了解Spring容器在创建,初始化和销毁Bean的时候依次做了那些事情。 12345678910111213141516171819202122232425Spring容器初始化=====================================调用GiraffeService无参构造函数GiraffeService中利用set方法设置属性值调用setBeanName:: Bean Name defined in context=giraffeService调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true调用setEnvironment调用setResourceLoader:: Resource File Name=spring-beans.xml调用setApplicationEventPublisher调用setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0]执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService调用PostConstruct注解标注的方法执行InitializingBean接口的afterPropertiesSet方法执行配置的init-method执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeServiceSpring容器初始化完毕=====================================从容器中获取Beangiraffe Name=李光洙=====================================调用preDestroy注解标注的方法执行DisposableBean接口的destroy方法执行配置的destroy-methodSpring容器关闭 参考文档life cycle management of a spring beanSpring Bean Life Cycle Spring Bean的生命周期先来看看,Spring在Bean从创建到销毁的生命周期中可能做得事情。 initialization 和 destroy有时我们需要在Bean属性值set好之后和Bean销毁之前做一些事情,比如检查Bean中某个属性是否被正常的设置好值了。Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialization和pre-destroy方法。 1.实现InitializingBean和DisposableBean接口 这两个接口都只包含一个方法。通过实现InitializingBean接口的afterPropertiesSet()方法可以在Bean属性值设置好之后做一些操作,实现DisposableBean接口的destroy()方法可以在销毁Bean之前做一些操作。 🌰如下:123456789101112public class GiraffeService implements InitializingBean,DisposableBean { @Override public void afterPropertiesSet() throws Exception { System.out.println(\"执行InitializingBean接口的afterPropertiesSet方法\"); } @Override public void destroy() throws Exception { System.out.println(\"执行DisposableBean接口的destroy方法\"); }} 这种方法比较简单,但是不建议使用。因为这样会将Bean的实现和Spring框架耦合在一起。 2.在bean的配置文件中指定init-method和destroy-method方法 Spring允许我们创建自己的init方法和destroy方法,只要在Bean的配置文件中指定init-method和destroy-method的值就可以在Bean初始化时和销毁之前执行一些操作。🌰如下:123456789101112public class GiraffeService { //通过<bean>的destroy-method属性指定的销毁方法 public void destroyMethod() throws Exception { System.out.println(\"执行配置的destroy-method\"); } //通过<bean>的init-method属性指定的初始化方法 public void initMethod() throws Exception { System.out.println(\"执行配置的init-method\"); }} 配置文件中的配置: 12<bean name=\"giraffeService\" class=\"com.giraffe.spring.service.GiraffeService\" init-method=\"initMethod\" destroy-method=\"destroyMethod\"></bean> 需要注意的是自定义的init-method和post-method方法可以抛异常但是不能有参数。这种方式比较推荐,因为可以自己创建方法,无需将Bean的实现直接依赖于spring的框架。 3.使用@PostConstruct和@PreDestroy注解 除了xml配置的方式,Spring也支持用@PostConstruct和 @PreDestroy注解来指定init和destroy方法。这两个注解均在javax.annotation包中。为了注解可以生效,需要在配置文件中定义org.springframework.context.annotation.CommonAnnotationBeanPostProcessor或context:annotation-config🌰如下:123456789101112public class GiraffeService { @PostConstruct public void initPostConstruct(){ System.out.println(\"执行PostConstruct注解标注的方法\"); } @PreDestroy public void preDestroy(){ System.out.println(\"执行preDestroy注解标注的方法\"); }} 配置文件:1<bean class=\"org.springframework.context.annotation.CommonAnnotationBeanPostProcessor\" /> 实现*Aware接口 在Bean中使用Spring框架的一些对象有些时候我们需要在Bean的初始化中使用Spring框架自身的一些对象来执行一些操作,比如获取ServletContext的一些参数,获取ApplicaitionContext中的BeanDefinition的名字,获取Bean在容器中的名字等等。为了让Bean可以获取到框架自身的一些对象,Spring提供了一组名为*Aware的接口。这些接口均继承于org.springframework.beans.factory.Aware标记接口,并提供一个将由Bean实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。网上说,这些接口是利用观察者模式实现的,类似于servlet listeners,目前还不明白,不过这也不在本文的讨论范围内。介绍一些重要的Aware接口: ApplicationContextAware: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。 BeanFactoryAware:获得BeanFactory对象,可以用来检测Bean的作用域。 BeanNameAware:获得Bean在配置文件中定义的名字。 ResourceLoaderAware:获得ResourceLoader对象,可以获得classpath中某个文件。 ServletContextAware:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。 ServletConfigAware在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。 🌰如下:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950public class GiraffeService implements ApplicationContextAware, ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{ @Override public void setBeanClassLoader(ClassLoader classLoader) { System.out.println(\"执行setBeanClassLoader,ClassLoader Name = \" + classLoader.getClass().getName()); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println(\"执行setBeanFactory,setBeanFactory:: giraffe bean singleton=\" + beanFactory.isSingleton(\"giraffeService\")); } @Override public void setBeanName(String s) { System.out.println(\"执行setBeanName:: Bean Name defined in context=\" + s); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println(\"执行setApplicationContext:: Bean Definition Names=\" + Arrays.toString(applicationContext.getBeanDefinitionNames())); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { System.out.println(\"执行setApplicationEventPublisher\"); } @Override public void setEnvironment(Environment environment) { System.out.println(\"执行setEnvironment\"); } @Override public void setResourceLoader(ResourceLoader resourceLoader) { Resource resource = resourceLoader.getResource(\"classpath:spring-beans.xml\"); System.out.println(\"执行setResourceLoader:: Resource File Name=\" + resource.getFilename()); } @Override public void setImportMetadata(AnnotationMetadata annotationMetadata) { System.out.println(\"执行setImportMetadata\"); }} BeanPostProcessor上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程,Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,只需提供一个实现BeanPostProcessor接口的类即可。 该接口中包含两个方法,postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行, postProcessAfterInitialization方法在容器中的Bean初始化之后执行。🌰如下:12345678910111213141516public class CustomerBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(\"执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=\" + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(\"执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=\" + beanName); return bean; }} 要将BeanPostProcessor的Bean像其他Bean一样定义在配置文件中1<bean class=\"com.giraffe.spring.service.CustomerBeanPostProcessor\"/> 总结所以。。。结合第一节控制台输出的内容,Spring Bean的生命周期是这样纸的: Bean容器找到配置文件中Spring Bean的定义。 Bean容器利用Java Reflection API创建一个Bean的实例。 如果涉及到一些属性值 利用set方法设置一些属性值。 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 用图表示一下(图来源): 希望今晚能成功玩上pokemon go,好想抓精灵啊 欢迎指正错误,欢迎一起讨论~~","raw":null,"content":null,"categories":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"}]},{"title":"Keep Going","slug":"keep-going","date":"2016-07-03T13:31:34.000Z","updated":"2018-12-13T07:53:53.000Z","comments":true,"path":"2016/07/03/keep-going/","link":"","permalink":"http://yemengying.com/2016/07/03/keep-going/","excerpt":"睡前看了眼Analysis的数据,比以前进步太多,发上来纪念一下,为什么呢?因为我知道。。。。。。下周。。。。。它就会。。。。降下去了😱。","keywords":null,"text":"睡前看了眼Analysis的数据,比以前进步太多,发上来纪念一下,为什么呢?因为我知道。。。。。。下周。。。。。它就会。。。。降下去了😱。 不说废话,直接上图。 这些数据安抚了我因为中午火锅而受伤的弱小心灵。。。。。千万别吐槽这数据其实挺low的,我才不会告诉你们以前都超不过20的。。。 要是周六看到这个数据就好了,周末肯定会写写写,就不会荒废了。。。。 看到种说法,人分为自燃型,助燃型和阻燃型,嗯,我应该就属于助燃型的吧~~ Keep going & 碎觉。。。。😂","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"}]},{"title":"【译】如何给变量取个简短且无歧义的名字","slug":"cleanCode4naming","date":"2016-06-25T08:23:39.000Z","updated":"2018-12-13T07:54:37.000Z","comments":true,"path":"2016/06/25/cleanCode4naming/","link":"","permalink":"http://yemengying.com/2016/06/25/cleanCode4naming/","excerpt":"湾区日报上分享的一篇文章,文章的作者在Google设计Dart语言,就变量命名方面给了4点建议,文中也列出了好变量名、坏变量名的对比。不管作者的看法与你实际中的命名习惯是否一致,看完这篇文章,相信可以在变量命名方面有一些新的思考。","keywords":null,"text":"湾区日报上分享的一篇文章,文章的作者在Google设计Dart语言,就变量命名方面给了4点建议,文中也列出了好变量名、坏变量名的对比。不管作者的看法与你实际中的命名习惯是否一致,看完这篇文章,相信可以在变量命名方面有一些新的思考。 原文地址(康桑阿米达):http://journal.stuffwithstuff.com/2016/06/16/long-names-are-long/?utm_source=wanqu.co&utm_campaign=Wanqu+Daily&utm_medium=website google做的最明智的规定之一就是严格执行code review。每一个改动在上线之前,都要经过两种形式的review。首先,团队中的人会进行常规的review,以确保代码完成了它应该完成的功能。 接下来还会进行可读性层面的review。顾名思义,它是为了确保代码是可读性高的:是否利于理解和维护?是否符合该编程语言的一些惯例?是否有良好的文档? Dart已经开始google内部使用,所以我有幸参与了n次上面类型的code review。作为该语言的设计者,这是一项令人着迷的工作。我可以直接看到人们是如何使用Dart的,这对语言的进一步发展很有帮助。在reivew的同时,我也能够清晰的了解到那些比较常见的错误和使用最多的特性,我就好像是一个记录本地居民生活的人类学者。 当然,上面说的与本文的主旨无关,这并不是一篇关于Dart的文章。本文主要是想讨论我看到过的一些令人抓狂的代码:这些代码的变量命名实在是太尼玛的长了。。。。。 是的,变量的名称可以很短。回到当C语言中外部标识符仅需要由前六个字符来唯一的区分; 自动补全功能还没有发明; 每次按键盘都像在雪地上坡一样艰难的时候,长的命名就会带来很多问题。不过幸运的是,我们现在生活的世界太美好了,键盘操作变得如此简单。 但我们现在似乎走上了另一个极端,我们不应该做海明威,但我们也无需成为田纳西·威廉斯。代码中使用了超长的命名会影响代码的清晰性。同时,超长的变量命名会造成换行,这会影响代码的结构,不易于阅读。 长的类名会使开发者不易声明该类型的变量。 长的方法命名会使它变得晦涩难懂. 长的变量命名不利于代码重用,导致过长的方法链。 我曾见过超过60个字符的变量命名,你甚至可以写首诗。别慌,下面我们来看看如何解决这一问题。 选择一个好的命名命名有两个目标: 清晰:你要知道该命名与什么有关 精确:你要知道该命名与什么无关 当一个命名完成上面两个目标之后,其余的字符就是多余的了。下面是我在开发时的一些命名原则: 命名中无需含有表示变量或参数类型的单词如果使用如Java之类的静态类型语言, 开发者通常知道变量的类型. 由于方法的实现一般都比较简短, 所以即便是在查看一个需要推断才知道类型的本地变量, 或者在code review等静态分析器不可用的情况下, 我们也可以通过多看很少的几行代码就能知道变量的类型. 所以将类型说明加入到变量名中是多余的. 我们应该舍弃匈牙利命名法,如下:1234567// 不好的:String nameString;DockableModelessWindow dockableModelessWindow;// 改进:String name;DockableModelessWindow window; 特别是对于集合来说,最好使用名词的复数形式来描述其内容, 而不是使用名词的单数形式来描述. 如果开发者更在乎集合中存储的内容, 那么变量命名应当反映这一点。1234567// 不好的:List<DateTime> holidayDateList;Map<Employee, Role> employeeRoleHashMap;// 改进:List<DateTime> holidays;Map<Employee, Role> employeeRoles; 这一点也同样适用于方法的命名。方法名不需要描述它的参数及参数的类型–参数列表已经说明了这些。12345678// 不好的:mergeTableCells(List<TableCell> cells)sortEventsUsingComparator(List<Event> events, Comparator<Event> comparator)// 改进:merge(List<TableCell> cells)sort(List<Event> events, Comparator<Event> comparator) 这样可以帮助调用者更好的阅读: 12mergeTableCells(tableCells);sortEventsUsingComparator(events, comparator); 当然,这只是我个人的看法,欢迎大家一起讨论~~ 省略命名中不是用来消除歧义的单词有些开发者倾向于将他们知道的有关这个变量的所有信息都塞到命名里。要记住,命名只是一个标识符:只是告诉你该变量是在哪定义的。并不是用来告诉阅读者所有他们想知道的有关这个对象的详细信息。这是定义应该做的事情的。 命名只是让你找到他的定义。 当我看到一个叫recentlyUpdatedAnnualSalesBid(最近更新的全年销售投标)的标识符时,我会问: 存在不是最近更新的全年销售投标么? 存在没有被更新的最近的全年销售投标么? 存在最近更新的非全年的销售投标么? 存在最近更新的全年非销售的投标么? 存在最近更新的全年销售非投标的东东吗? 上面任何一个问题的回答是“不存在”,就意味着命名中引入了无用的单词。 1234567// 不好的:finalBattleMostDangerousBossMonster;weaklingFirstEncounterMonster;// 改进:boss;firstMonster; 当然,你可能会觉得这有一些过了。比如将第一个例子的标识符简化为bid,会让人觉得有点模糊不清。但你可以放心大胆的这样做,如果在之后的开发中觉得该命名会造成冲突或不明确,可以添加些修饰词来完善它。反之,如果一开始就取了一个很长的命名,你是不可能在之后重新回来简化它的。 省略命名中可以从上下文获取的单词我可以在文章中使用”我”,因为读者都知道这是一篇由Bob Nystrom所做的博客。我蠢萌的脸就挂在那,我无需不停的说我的名字。写代码也是一样,类中的方法/属性和方法中的变量,都是存在在上下文中的,无需重复。 1234567891011// Bad:class AnnualHolidaySale { int _annualSaleRebate; void promoteHolidaySale() { ... }}// Better:class AnnualHolidaySale { int _rebate; void promote() { ... }} 实际上, 一个命名嵌套的层次越多, 它就有更多的相关的上下文,也就更简短。换句话说,一个变量的作用域越小,命名就越短。 省略命名中无任何含义的单词我常常在许多游戏开发中看到包含无任何含义的单词的命名,一些开发者喜欢在命名中添加一些看起来有点严肃的单词。我猜可能他们觉得这样做可以让他们的代码显得重要,或者说让他们觉得自己更重要。 实际上,有一些词语并没有实际意义,只是一些套话。比如:data, state, amount, value, manager, engine, object, entity和instance。 一个好的命名能够在阅读者的脑海中描画出一幅图画。而将某变量命名为”manager”并不能向读者传达任何有关该变量是做什么的信息. 它是用来做绩效评估的吗? 它是管理加薪的吗? 在命名时可以问一下自己,把这个单词去掉含义是不是不变?如果是,那就果断把它剔除吧~~ 实际例子—华夫饼为了让大家了解以上的命名原则在实际中如何应用,这有个违法了以上所有原则的反例。这个例子和我实际上review过的一段代码一样令人心碎。。。。12345// 好吃的比利时华夫饼class DeliciousBelgianWaffleObject { void garnishDeliciousBelgianWaffleWithStrawberryList( List<Strawberry> strawberryList) { ... }} 首先,通过参数列表我们可以知道方法是用来处理一个strawberry的列表,所以可以在方法命名中去掉:1234class DeliciousBelgianWaffleObject { void garnishDeliciousBelgianWaffle( List<Strawberry> strawberries) { ... }} 除非程序中还包含不好吃的比利时华夫饼或者其他国家的华夫饼,不然我们可以将这些无用的形容词去掉:123class WaffleObject { void garnishWaffle(List<Strawberry> strawberries) { ... }} 方法是包含在WaffleObject类中的,所以方法名中无需Waffle的说明:123class WaffleObject { void garnish(List<Strawberry> strawberries) { ... }} 很明显它是一个对象,任何事物都是一个对象,这也就是传说中的“面向对象”的含义,所以命名中无需带有Object:123class Waffle { void garnish(List<Strawberry> strawberries) { ... }} 好了,这样看起来好多了。 以上就是我总结的相当简洁的命名原则。可能有些人会觉得无需在命名上耗费太多的精力,但我认为命名是开发过程中最基本的任务之一。————————————————–我是萌萌哒分界线—————————————————————-感觉变量或者方法的命名,看似简单,实际很难,特别是想一个简洁明了可读性高的命名。自己也经常用什么data,xxxlist来命名,作者说的挺对的,前者没什么意义,后者又有点啰嗦。不过对于集合类型的变量,统一用名词复数命名容易混淆。举个例子对于Apple这个类来说,可能存在List和Map两种集合类型的变量。个人觉得对List类型的变量可以采用名词复数来命名,Map类型的变量可以采用valueByKey格式来命名,比较容易区分。 欢迎指正错误,欢迎一起讨论~~~ 都看到这了,关注个公众号再走吧🙈","raw":null,"content":null,"categories":[{"name":"Clean Code","slug":"Clean-Code","permalink":"http://yemengying.com/categories/Clean-Code/"}],"tags":[{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"面试总结","slug":"interview","date":"2016-06-05T02:12:59.000Z","updated":"2018-12-13T07:54:53.000Z","comments":true,"path":"2016/06/05/interview/","link":"","permalink":"http://yemengying.com/2016/06/05/interview/","excerpt":"从决定离职开始,前前后后面试了几家公司,把还能记得住的面试问题总结一下,帮小伙伴们查漏补缺吧,希望小伙伴们可以一举拿下offer。会简要写一下我觉得问题的关键点,不过有的可能并不是正确的答案,有的问题我到现在也还没明白。。。~~","keywords":null,"text":"从决定离职开始,前前后后面试了几家公司,把还能记得住的面试问题总结一下,帮小伙伴们查漏补缺吧,希望小伙伴们可以一举拿下offer。会简要写一下我觉得问题的关键点,不过有的可能并不是正确的答案,有的问题我到现在也还没明白。。。~~ Java相关Java GC机制(重要程度:★★★★★) 主要从三个方面回答:GC是针对什么对象进行回收(可达性分析法),什么时候开始GC(当新生代满了会进行Minor GC,升到老年代的对象大于老年代剩余空间时会进行Major GC),GC做什么(新生代采用复制算法,老年代采用标记-清除或标记-整理算法),感觉回答这些就差不多了,也可以补充一下可以调优的参数(-XX:newRatio,-Xms,-Xmx等等)。详细的可以看我另一篇博客(Java中的垃圾回收机制)。 如何线程安全的使用HashMap(重要程度:★★★★★) 作为Java程序员还是经常和HashMap打交道的,所以HashMap的一些原理还是搞搞清除比较好。这个问题感觉主要就是问HashMap,HashTable,ConcurrentHashMap,sychronizedMap的原理和区别。具体的可以看我另一篇博客(如何线程安全的使用HashMap)。 HashMap是如何解决冲突的(重要程度:★★★★☆) 其实就是链接法,将索引值相同的元素存放到一个单链表里。但为了解决在频繁冲突时HashMap性能降低的问题,Java 8中做了一个小优化,在冲突的元素个数超过设定的值(默认为8)时,会使用平衡树来替代链表存储冲突的元素。具体的可以看我另一篇博客(Java 8中HashMap和LinkedHashMap如何解决冲突)。 Java创建对象有哪几种(重要程度:★★★★☆) 这个问题还算好回答,大概有四种—new、工厂模式、反射和克隆,不过这个问题有可能衍生出关于设计模式,反射,深克隆,浅克隆等一系列问题。。。要做好准备~参考资料:设计模式Java版Java反射详解深克隆与浅克隆的区别 注解(重要程度:★★★☆☆) 如果简历中有提到过曾自定义过注解,还是了解清楚比较好。主要是了解在自定义注解时需要使用的两个主要的元注解@Retention和@Target。@Retention用来声明注解的保留策略,有CLASS,RUNTIME,SOURCE三种,分别表示注解保存在类文件,JVM运行时刻和源代码中。@Target用来声明注解可以被添加到哪些类型的元素上,如类型,方法和域等。参考资料:Java注解 异常(重要程度:★★★☆☆) 一道笔试题,代码如下,问返回值是什么。1234567891011int ret = 0;try{throw new Exception();}catch(Exception e){ret = 1;return ret;}finally{ret = 2;} 主要的考点就是catch中的return在finally之后执行 但是会将return的值放到一个地方存起来,所以finally中的ret=2会执行,但返回值是1。参考资料:深入理解Java异常处理机制Java异常处理 悲观锁和乐观锁区别,乐观锁适用于什么情况(重要程度:★★★★☆) 悲观锁,就是总觉得有刁民想害朕,每次访问数据的时候都觉得会有别人修改它,所以每次拿数据时都会上锁,确保在自己使用的过程中不会被他人访问。乐观锁就是很单纯,心态好,所以每次拿数据的时候都不会上锁,只是在更新数据的时候去判断该数据是否被别人修改过。大多数的关系数据库写入操作都是基于悲观锁,缺点在于如果持有锁的客户端运行的很慢,那么等待解锁的客户端被阻塞的时间就越长。Redis的事务是基于乐观锁的机制,不会在执行WATCH命令时对数据进行加锁,只是会在数据已经被其他客户端抢先修改了的情况下,通知执行WATCH命令的客户端。乐观锁适用于读多写少的情况,因为在写操作比较频繁的时候,会不断地retry,从而降低性能。参考资料:关于悲观锁和乐观锁的区别乐观锁和悲观锁 单例模式找错误(重要程度:★★★★☆) 错误是没有将构造函数私有化,单例还是比较简单的,把它的饿汉式和懒汉式的两种实现方式看明白了就可以了。单例模式 __ Spring相关关于Spring的问题主要就是围绕着Ioc和AOP,它们真是Spring的核心啊。 Spring Bean的生命周期(重要程度:★★★★★) 就不写我那么low的回答了,直接看参考资料吧。参考资料:Spring Bean的生命周期Top 10 Spring Interview Questions Answers J2EE Spring中用到的设计模式(重要程度:★★★★★) 工厂模式:IOC容器代理模式:AOP策略模式:在spring采取动态代理时,根据代理的类有无实现接口有JDK和CGLIB两种代理方式,就是采用策略模式实现的单例模式:默认情况下spring中的bean只存在一个实例只知道这四个。。。。参考资料:Design Patterns Used in Java Spring Framework 讲一讲Spring IoC和AOP(重要程度:★★★★★) IoC的核心是依赖反转,将创建对象和对象之间的依赖管理交给IoC容器来做,完成对象之间的解耦。AOP主要是利用代理模式,把许多接口都要用的又和接口本身主要的业务逻辑无关的部分抽出来,写成一个切面,单独维护,比如权限验证。这样可以使接口符合“单一职责原则”,只关注主要的业务逻辑,也提高了代码的重用性。 AOP的应用场景(重要程度:★★★★☆) 权限,日志,处理异常,事务等等,个人理解就是把许多接口都要用的又和接口本身主要的业务逻辑无关的部分抽出来,写成一个切面,单独维护,比如权限验证。这样可以使接口符合“单一职责原则”,只关注主要的业务逻辑,也提高了代码的重用性。 Spring中编码统一要如何做(重要程度:★★★☆☆) 配置一个拦截器就行了12345678910111213141516<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 数据库相关Mysql索引的内部结构(重要程度:★★★★☆) B+树,三层,真实的数据存储在叶子节点参考资料:MySQL索引原理及慢查询优化 如果一个SQL执行时间比较长怎么办(重要程度:★★★★☆) 可以利用pt-query-digest等工具分析慢查询日志,也可以用explain查看SQL的执行计划。具体可看我的另一篇博客MySQL调优 如果一张表中有上千万条数据应该怎么做分页(重要程度:★★★☆☆) 肯定不能直接limit,offset,主要就是要想办法避免在数据量大时扫描过多的记录。具体可看我的另一篇博客【译】优化MySQL中的分页 什么样的列适合加索引,如果一个列的值只有1和2,那么它适合加索引么(重要程度:★★★☆☆) 在where从句,group by从句,order by从句,on从句中出现的列 索引的字段越小越好 在建立联合索引时,离散度大的列放大联合索引的前面 只有1和2不适合建索引 Redis相关Redis提供哪几种数据结构(重要程度:★★★★★) 一共有5种,字符串,散列,列表,集合,有序集合。参考资料:Redis中文官网 Redis支持集群么,从哪个版本开始支持集群的(重要程度:★★☆☆☆) 支持集群,从3.0版本开始。当然面试时我也没记住版本。。。 Redis集群中,如何将一个对象映射到对应的缓存服务器(重要程度:★★★★☆) 一般就是hash%N,就是用对象的hash值对缓存服务器的个数取余 接上个问题,缓存集群中如果新增一台服务器,怎么才能不影响大部分缓存数据的命中?(重要程度:★★★★☆) 其实就是一致性Hash算法。以前有看过,可惜面试的时候脑袋就空了,只记得一个环,果然还是要实践啊。参考资料:Consistent Hashing五分钟理解一致性哈希算法(consistent hashing) 项目中具体是怎样使用Redis的(重要程度:★★★★☆) 根据实际情况回答吧。。。。我是主要做权限控制时用到了Redis,将用户Id和权限Code拼接在一起作为一个key,放到Redis的集合中,在验证某一用户是否有指定权限时,只需验证集合中是否有用户Id和权限Code拼接的key即可 算法相关判断一个数字是否为快乐数字(重要程度:★☆☆☆☆) leetcode第202题链接 给定一个乱序数组和一个目标数字 找到和为这个数字的两个数字 时间复杂度是多少(重要程度:★☆☆☆☆) leetcode第一题链接 如何判断一个链表有没有环(重要程度:★☆☆☆☆) 用快慢指针 删除字符串中的空格 只留一个(重要程度:★☆☆☆☆) 这个比较简单。。。。 二叉树层序遍历(重要程度:★★☆☆☆) 利用队列就可以了 地铁票价是如何计算的(重要程度:★★☆☆☆) 不知道正确答案,感觉是图的最短路径算法相关的。 Elasticsearch相关为什么要用Elasticsearch(重要程度:★★★★☆) 其实对Es的了解还是比较少的,因为没做多久就去写坑爹代理商了😖。个人觉得项目中用Es的原因一是可以做分词,二是Es中采用的是倒排索引所以性能比较好,三是Es是个分布式的搜索服务,对各个节点的配置还是很简单方便的 Elasticsearch中的数据来源是什么,如何做同步(重要程度:★★★★☆) 数据是来自其他部门的数据库,会在一开始写python脚本做全量更新,之后利用RabbitMQ做增量更新,就是数据更改之后,数据提供方将更改的数据插入到指定消息队列,由对应的消费者索引到Es中 接上个问题,利用消息队列是会对对方代码造成侵入的,还有没有别的方式(重要程度:★★★☆☆) 还可以读MySQL的binlog 发散思维的题以下题都是没有正确答案的,不知道是想考思维,还是压力面试,就只写题目,不写回答了。。。 画一下心中房树人的关系(重要程度:★☆☆☆☆)给你一块地建房如何规划(重要程度:★☆☆☆☆)估计二号线有几辆车在运行(重要程度:★☆☆☆☆) 其他Thrift通信协议(重要程度:★★★☆☆) 这个问题被问了两遍,然而现在还是不知道。。。什么东西都不能停留在只会用的阶段啊~ git相关(重要程度:★★★★★) 一些git相关的问题,比如如何做分支管理(git flow),rebase和merge的区别(merge操作会生成一个新的节点)等等。。。 如何学习一门新技术(重要程度:★★★☆☆) google+官网+stackoverflow+github 比较爱逛的网站和爱看的书(重要程度:★★★☆☆) 根据实际情况回答吧。。。 了不了解微服务(重要程度:★★☆☆☆) 简单了解过。。。参考资料:基于微服务的软件架构模式 针对简历中的项目问一些问题(重要程度:★★★★★) 就是根据简历上的项目问一些东西,比如权限控制是怎么做的,有没有碰到过比较难解决的问题之类的,不具体列举了,只要简历上的内容是真实的基本都没啥问题 为什么要离职(重要程度:★★★★★) 被问了n遍,挺不好回答的一个问题,毕竟不算实习期工作还没满一年,这个时候跳槽很容易让人觉得不安稳。。。。 对公司还有什么问题(重要程度:★★★★★) 基本每轮面试结束都会问的一个问题,一开始也没当回事,直到有家公司居然挂在四面的这个问题上,我也是蛮醉的😂,果然言多必失啊🌝。。。 在***公司最大的收获是什么(重要程度:★★★★★) 对于我来说,觉得最大的收获就是对企业级的应用有了一个大致的了解,企业里的项目不像学校的课程作业,只要jdbc连接数据库完成功能就可以了,企业的项目要考虑很多东西,比如说为了提高可用性,要部署在多台服务器上,用nginx做负载均衡,还有就是用缓存,异步之类来提高接口性能。然后,也是第一次接触到SOA,这种面向服务的架构。也了解到一个好的应用,除了开发本身,一些自动化发布系统和监控系统也是必不可少的。当然,还认识了一群三观合的小伙伴~~~ 面试真真是件很心累的事情,每次面完都感觉被拔了层皮,希望两年内不要在面试了😂。后天就要入职了,想想还有点小紧张呢,去看学叔推荐的美剧压压惊。。。 都看到这了,关注个公众号再走吧🙈","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"}]},{"title":"【译】优化MySQL中的分页","slug":"optimized-pagiantion-mysql","date":"2016-05-28T01:45:42.000Z","updated":"2018-12-13T07:55:50.000Z","comments":true,"path":"2016/05/28/optimized-pagiantion-mysql/","link":"","permalink":"http://yemengying.com/2016/05/28/optimized-pagiantion-mysql/","excerpt":"一道面试的问题,当MySQL表中有数据量很大的时候如何做分页。。。。当时只知道在数据量很大的时候可以分表,但不知道不分表时可以怎么做。。。。唉,谁让代理商就那么几条数据,一个简单的limit,offset就完全hold住了(捂脸🙈)。。。","keywords":null,"text":"一道面试的问题,当MySQL表中有数据量很大的时候如何做分页。。。。当时只知道在数据量很大的时候可以分表,但不知道不分表时可以怎么做。。。。唉,谁让代理商就那么几条数据,一个简单的limit,offset就完全hold住了(捂脸🙈)。。。 翻译一篇关于优化MySQL中的分页的文章,原文地址:Optimized Pagination using MySQL,谢谢,3Q,康桑阿米达~~~ 很多应用往往只展示最新或最热门的几条记录,但为了旧记录仍然可访问,所以就需要个分页的导航栏。然而,如何通过MySQL更好的实现分页,始终是比较令人头疼的问题。虽然没有拿来就能用的解决办法,但了解数据库的底层或多或少有助于优化分页查询。我们先从一个常用但性能很差的查询来看一看。1234SELECT *FROM cityORDER BY id DESCLIMIT 0, 15 这个查询耗时0.00sec。So,这个查询有什么问题呢?实际上,这个查询语句和参数都没有问题,因为它用到了下面表的主键,而且只读取15条记录。12345CREATE TABLE city ( id int(10) unsigned NOT NULL AUTO_INCREMENT, city varchar(128) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB; 真正的问题在于offset(分页偏移量)很大的时候,像下面这样:1234SELECT *FROM cityORDER BY id DESCLIMIT 100000, 15; 上面的查询在有2M行记录时需要0.22sec,通过EXPLAIN查看SQL的执行计划可以发现该SQL检索了100015行,但最后只需要15行。大的分页偏移量会增加使用的数据,MySQL会将大量最终不会使用的数据加载到内存中。就算我们假设大部分网站的用户只访问前几页数据,但少量的大的分页偏移量的请求也会对整个系统造成危害。Facebook意识到了这一点,但Facebook并没有为了每秒可以处理更多的请求而去优化数据库,而是将重心放在将请求响应时间的方差变小。对于分页请求,还有一个信息也很重要,就是总共的记录数。我们可以通过下面的查询很容易的获取总的记录数。12SELECT COUNT(*)FROM city; 然而,上面的SQL在采用InnoDB为存储引擎时需要耗费9.28sec。一个不正确的优化是采用SQL_CALC_FOUND_ROWS,SQL_CALC_FOUND_ROWS可以在能够在分页查询时事先准备好符合条件的记录数,随后只要执行一句select FOUND_ROWS(); 就能获得总记录数。但是在大多数情况下,查询语句简短并不意味着性能的提高。不幸的是,这种分页查询方式在许多主流框架中都有用到,下面看看这个语句的查询性能。1234SELECT SQL_CALC_FOUND_ROWS *FROM cityORDER BY id DESCLIMIT 100000, 15; 这个语句耗时20.02sec,是上一个的两倍。事实证明使用SQL_CALC_FOUND_ROWS做分页是很糟糕的想法。下面来看看到底如何优化。文章分为两部分,第一部分是如何获取记录的总数目,第二部分是获取真正的记录。 高效的计算行数如果采用的引擎是MyISAM,可以直接执行COUNT(*)去获取行数即可。相似的,在堆表中也会将行数存储到表的元信息中。但如果引擎是InnoDB情况就会复杂一些,因为InnoDB不保存表的具体行数。我们可以将行数缓存起来,然后可以通过一个守护进程定期更新或者用户的某些操作导致缓存失效时,执行下面的语句:123SELECT COUNT(*)FROM cityUSE INDEX(PRIMARY); 获取记录下面进入这篇文章最重要的部分,获取分页要展示的记录。上面已经说过了,大的偏移量会影响性能,所以我们要重写查询语句。为了演示,我们创建一个新的表“news”,按照时事性排序(最新发布的在最前面),实现一个高性能的分页。为了简单,我们就假设最新发布的新闻的Id也是最大的。1234CREATE TABLE news( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL) ENGINE=InnoDB; 一个比较高效的方式是基于用户展示的最后一个新闻Id。查询下一页的语句如下,需要传入当前页面展示的最后一个Id。1234SELECT *FROM news WHERE id < $last_idORDER BY id DESCLIMIT $perpage 查询上一页的语句类似,只不过需要传入当前页的第一个Id,并且要逆序。1234SELECT *FROM news WHERE id > $last_idORDER BY id ASCLIMIT $perpage 上面的查询方式适合实现简易的分页,即不显示具体的页数导航,只显示“上一页”和“下一页”,例如博客中页脚显示“上一页”,“下一页”的按钮。但如果要实现真正的页面导航还是很难的,下面看看另一种方式。12345678910SELECT idFROM ( SELECT id, ((@cnt:= @cnt + 1) + $perpage - 1) % $perpage cnt FROM news JOIN (SELECT @cnt:= 0)T WHERE id < $last_id ORDER BY id DESC LIMIT $perpage * $buttons)CWHERE cnt = 0; 通过上面的语句可以为每一个分页的按钮计算出一个offset对应的id。这种方法还有一个好处。假设,网站上正在发布一片新的文章,那么所有文章的位置都会往后移一位,所以如果用户在发布文章时换页,那么他会看见一篇文章两次。如果固定了每个按钮的offset Id,这个问题就迎刃而解了。Mark Callaghan发表过一篇类似的博客,利用了组合索引和两个位置变量,但是基本思想是一致的。如果表中的记录很少被删除、修改,还可以将记录对应的页码存储到表中,并在该列上创建合适的索引。采用这种方式,当新增一个记录的时候,需要执行下面的查询重新生成对应的页号。12SET @p:= 0;UPDATE news SET page=CEIL((@p:= @p + 1) / $perpage) ORDER BY id DESC; 当然,也可以新增一个专用于分页的表,可以用个后台程序来维护。12345678UPDATE pagination TJOIN ( SELECT id, CEIL((@p:= @p + 1) / $perpage) page FROM news ORDER BY id)CON C.id = T.idSET T.page = C.page; 现在想获取任意一页的元素就很简单了:1234SELECT *FROM news AJOIN pagination B ON A.id=B.IDWHERE page=$offset; 还有另外一种与上种方法比较相似的方法来做分页,这种方式比较试用于数据集相对小,并且没有可用的索引的情况下—比如处理搜索结果时。在一个普通的服务器上执行下面的查询,当有2M条记录时,要耗费2sec左右。这种方式比较简单,创建一个用来存储所有Id的临时表即可(这也是最耗费性能的地方)。12345CREATE TEMPORARY TABLE _tmp (KEY SORT(random))SELECT id, FLOOR(RAND() * 0x8000000) randomFROM city;ALTER TABLE _tmp ADD OFFSET INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, DROP INDEX SORT, ORDER BY random; 接下来就可以向下面一样执行分页查询了。12345SELECT *FROM _tmpWHERE OFFSET >= $offsetORDER BY OFFSETLIMIT $perpage; ——————————————–俺只是个分割线———————————————————-简单来说,对于分页的优化就是。。。避免数据量大时扫描过多的记录。博客比较长,所以翻译的有些粗糙。。。,之后会在好好检查一遍的。在自己做测试时,有些查询时间与作者有点不一致,不过作者这篇博客是写于2011年的,so~不要在意具体数据,领会精神吧~~ 欢迎指正错误,欢迎一起讨论!!! 国际惯例,wuli光洙结尾~~","raw":null,"content":null,"categories":[{"name":"MySQL","slug":"MySQL","permalink":"http://yemengying.com/categories/MySQL/"}],"tags":[{"name":"MySQL","slug":"MySQL","permalink":"http://yemengying.com/tags/MySQL/"}]},{"title":"MySQL调优","slug":"mysql-tuning","date":"2016-05-24T13:16:33.000Z","updated":"2018-12-13T07:58:12.000Z","comments":true,"path":"2016/05/24/mysql-tuning/","link":"","permalink":"http://yemengying.com/2016/05/24/mysql-tuning/","excerpt":"啦啦啦,啦啦啦,我是卖报的小行家~~","keywords":null,"text":"啦啦啦,啦啦啦,我是卖报的小行家~~ 先分享个脑洞打开的mv,coldplay新单up&up,看看会飞的海龟🐢,一点也不精彩,就看了30多遍而已😂。 ———————————-我是预示画风转变分割线————————————————————————-根据视频(链接)整理。 为什么要进行优化? 避免由数据库链接timeout产生页面5xx的错误 避免由于慢查询造成页面无法加载 避免由于阻塞造成数据无法提交 优化用户体验 可以从哪几个方面进行数据库优化?从图中可以看出,SQL及索引的优化是最重要的,成本最低效果最好。下面分别来看看如何优化SQL和索引。 SQL优化慢查询日志配置可以使用慢查询日志对有效率问题的SQL进行监控。下面是关于如何开启慢查询日志和慢查询日志的一些配置。12345show variables like 'slow_query_log'; //查看是否开启了慢查询set global slow_query_log_file='/home/mysql/sql_log/mysql-slow.log'; //设置慢查询日志的位置set global log_queries_not_using_indexes=ON; //是否记录未使用索引的查询set global long_query_time=1;//设置记录超过多长时间的SQL语句set global slow_query_log=ON;//设置慢查询日志是否开启 慢查询日志的格式:详细看一下每一行都是什么意思。查询的执行时间 Time:140606 12:30:17执行SQL的主机信息 User@Host:root[root] @ localhost []SQL的执行信息 Query_time: 0.000024 Lock_time:0.000000 Rows_sent:0 Rows_examined: 0SQL执行时间 SET timestamp=1402389328SQL的内容:show tables 分析慢查日志的工具1.mysqldumpslow可以使用MySQL自带的慢查询分析工具mysqldumpslow,可以通过mysqldumpslow -h来查看具体的使用方法。eg:mysqldumpslow -t 3 /path/to/mysql-slow-query.log | more上面的命令会列出查询时间top 3的SQL语句,具体格式如下图,会列出SQL执行的次数,SQL来执行的时间,锁定的时间,发送的函数,由谁在哪个服务器上执行的和具体的SQL内容。mysqldumpslow是比较常用的慢查询日志分析工具,但是分析结果包含的信息比较少,对于SQL优化来说可能还不太够。下面看看另一种分析工具。 2.pt-query-digestpt-query-digest支持将分析结果保存到文件或数据表中。1234567输出到文件pt-query-digest slow.log > slow_log.report输出到数据库表pt-query-digest slow.log -review \\h=127.0.0.1,D=test,p=root,P=3306,u=root,t=query_review \\--creat-reviewtable \\--review-history t=hostname_slow 通过pt-query-digest --help可以查看具体的使用方式。eg: pt-query-digest /home/mysql/data/mysql-slow.log | more通过上面的命令,会列出慢查询日志的分析结果,分为三个部分。第一部分中包含日志中有多少个SQL,多少个不同的SQL,SQL执行的时间范围,总的执行时间,最短的执行时间,最长的执行时间,平均执行时间,总锁定时间,总发送行数,总检索行数等等。第二部分包含关于 表和执行语句的统计,可以看到哪个表的哪个操作的实行时间是最多的,也可以看到对应的响应时间和调用次数。第三部分就是具体的SQL的分析,包括对应语句执行时间,锁定时间,发送行数,检索行数等等。 定位有问题的SQL通过上面的慢查询日志分析我们可以定位需要优化的SQL,通常有三种: 查询次数多且每次查询占用时间长的SQL:通常为pt-query-digest分析的前几个查询。 IO大的SQL:注意pt-query-digest分析中的Rows examine项 未命中索引的SQL: 注意pt-query-digest分析中Rows examine和Rows Send的对比。 通过Explain查询和分析SQL的执行计划可以通过Explain查询SQL的执行计划,例子如下:explain返回的各列的含义: 列 含义 table 显示查询是关于哪个表的 type 很重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL possible_keys 显示可能应用在这张表中的索引。如果为空,没有可能应用的索引 key 实际使用的索引。如果为NULL,则没有使用索引 key_len 使用的索引的长度。在不损失精确性的情况下,长度越短越好 ref 显示索引的哪一列被使用了 rows MYSQL认为必须检查的用来返回请求的行数 extra 当这一列的值是Using filesort或Using temporary时,说明查询需要优化了 索引优化如何选择合适的列来建立索引 在where从句,group by从句,order by从句,on从句中出现的列 索引的字段越小越好 在建立联合索引时,离散度大的列放大联合索引的前面 如何维护和优化索引要避免重复及冗余索引,重复索引是指相同的列以相同的顺序建立的同类型的索引。冗余索引是指多个索引的前缀列相同,或是在联合索引中包含了主键的索引。可以使用pt-duplicate-key-checker工具可以检查重复及冗余索引。同时还要注意及时删除由于业务变更不再使用的索引。目前MySQL中还没有记录索引的使用情况,但在PerconMuSQL和MariaDB中可以通过INDEX_STATISTICS表来查看哪些索引未使用,在MySQL中目前只能通过慢查询日志配合pt-index-usage工具来进行索引的使用情况的分析。 欢迎指正错误,欢迎一起讨论~~","raw":null,"content":null,"categories":[{"name":"MySQL","slug":"MySQL","permalink":"http://yemengying.com/categories/MySQL/"}],"tags":[{"name":"MySQL","slug":"MySQL","permalink":"http://yemengying.com/tags/MySQL/"}]},{"title":"谈谈破窗理论","slug":"broken-window-theory","date":"2016-05-15T06:02:14.000Z","updated":"2018-12-13T07:58:41.000Z","comments":true,"path":"2016/05/15/broken-window-theory/","link":"","permalink":"http://yemengying.com/2016/05/15/broken-window-theory/","excerpt":"在湾区日报上看到篇关于破窗理论(Broken Window Theory)的文章,真是颇有感触,所以决定写篇博客,结合这几个月开发代理商网站的心(keng)路(die)历程,谈谈为何不能忽视一点点糟糕的代码或者不好的设计。","keywords":null,"text":"在湾区日报上看到篇关于破窗理论(Broken Window Theory)的文章,真是颇有感触,所以决定写篇博客,结合这几个月开发代理商网站的心(keng)路(die)历程,谈谈为何不能忽视一点点糟糕的代码或者不好的设计。 破窗理论先简单解释下什么是“破窗理论”,“破窗理论”是指:如果有人打坏了一幢建筑物的窗户玻璃,而这扇窗户又得不到及时的维修,别人就可能受到某些暗示性的纵容去打烂更多的窗户。湾区日报上的文章中是这样描述破窗理论的: Don’t leave “broken windows” (bad designs, wrong decisions, or poor code) unrepaired. Fix each one as soon as it is discovered. If there is insufficient time to fix it properly, then board it up. Perhaps you can comment out the offending code, or display a “Not Implemented” message, or substitute dummy data instead. Take some action to prevent further damage and to show that you’re on top of the situation.We’ve seen clean, functional systems deteriorate pretty quickly once windows start breaking. There are other factors that can contribute to software rot, and we’ll touch on some of them elsewhere, but neglect accelerates the rot faster than any other factor. 简单翻译一下就是: 不要放任“破窗户”(不好的设计,错误的决定或糟糕的代码)不管。要尽量在发现时立刻修复。如果没有足够的时间进行适当的修复,就先把它保留起来。可以把出问题的代码放到注释中,或是显示“未实现”消息,也可用虚拟数据加以替代。总之,要采取一些措施,防止进一步的恶化。表明局势尚在掌控之中。有许多整洁良好的系统在出现“破窗”之后立马崩溃。虽然促使软件崩溃的原因还有其他因素(我们将在其他地方接触到),但对“破窗”置之不理,肯定会更快地加速系统崩溃。 亲身感受先说说背景,故事要从三个月前开始讲起了,当时刚刚转去搜索组做部门内部的搜索,组内总共3个人。本以为可以远离业务代码,专心技术,可万万没想到Elasticsearch的书还没焐热就被部门leader叫去开会,说要做一个代理商系统,很紧急,是公司P0级别的项目,全公司的资源都要给我们让路(事实证明只是画饼,因为现在连一个固定前端都没了)。其实这个项目一听就是个深坑,从头发到脚都是拒绝的。因为主数据(餐厅,活动,订单)全在别的部门,85%的功能都依赖于其他部门的接口(si不si很神奇),所以做这个项目主要工作就是。。。。。通过SOA或者Thrift调别人的接口。不过即便知道是坑也没办法,只有搜索组刚刚成立比较闲,只能我们做-_-|||。当时部门leader的要求是封闭开发一周半,拿出个可用的版本就行,一定要快!!。所以我们搜索组的三个人加上从别的组借调的两个实习生再加上两个前端就搬去了小黑屋,开始了近两周的封闭开发。 好了,背景聊完了,进入正题,聊聊代理商是怎么变得越来越难维护的。代理商的开发leader是个搜索大牛,但没做过Web开发,对Java Web开发并不是十分了解。因为部门leader要求快快快,所以将许多必要的步骤省略了。比如定义方法参数命名规范,定义api规范,代码review等等。。。每天就是划分下接口,每人开发几个接口,和前端定义接口文档,就开始开发提测了。讲真,其实所有人都是有责任的,可能是对这个项目一开始就很反感,有抵触心理,所以从内心就没打算好好做,一些觉得可以改进的地方也就得过且过了。后面的事实证明,当觉得设计或规范有不合理的时候,一定要及时提出来,不能忍,忍的后果就是一次一次降低自己的底线,然后亲手造就一个难维护的系统,到时候即便有心想重构也是心有余力不足了。 公司项目就不贴实际代码了,简单举几个例子,看看开发前定义必要的规范是多么的重要。由于代理商没有事先规定api的定义要符合RESTFul的规范,所以项目中api的风格有两种,符合RESTFul的和不符合的。比如获取餐厅信息的api定义是GET /restaurant/{id},而创建餐厅的api定义是POST /restaurant/create,so。。。如果后面的人想设计更新餐厅信息的api是PUT /restaurant/{id}还是PUT /restaurant/update呢,真是一脸懵逼。接口的命名就更是五花八门了,因为大家是来自不同组,而且也没有定义统一的命名的规范,比如:一个简单的获取信息,获取餐厅信息的接口是getRestaurant,获取活动信息是activityInfo,获取代理商信息接口是getAgentDto,获取订单信息又变成了getOrderData,so。。。谁能告诉我之后想添加个获取信息的接口到底该叫什么。。。估计只能看当时的心情随便取了。。。还有就是项目中存在大量重复代码,获取餐厅管理员信息基本上每个人都写了一遍,因为管理员的信息在获取餐厅,活动信息时都用的到,由于没有代码review,所以一开始大家也不知道,就各写各的,也是蛮醉的。 上面的例子只是一点点,实际还有很多很多很多的槽点,都是泪啊。由于第一版本为了压缩工时(至今也不明白为啥要那么急。。。)就这样草草交工,导致后面的几个迭代开发也随之变得越来越随意,随意命名,随意定义api,缺乏junit测试,越来越不上心。听说代理商要移交给BD组,但估计情况也只会越来越糟,因为没人愿意去整理一坨坨糟糕的代码,只会在出现问题的时候,随意的打补丁。。。 总结其实也没啥好总结的,一句话,以后一定要写干净,整洁的代码,注意规范,不能忽视一点点糟糕的代码或者设计对项目带来的负面影响。 欢迎一起讨论~~","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"}]},{"title":"【译】Java中的垃圾回收机制","slug":"jvm-GC","date":"2016-05-13T15:28:15.000Z","updated":"2018-12-13T08:01:04.000Z","comments":true,"path":"2016/05/13/jvm-GC/","link":"","permalink":"http://yemengying.com/2016/05/13/jvm-GC/","excerpt":"感觉人生就是while(true){一个选择接着一个选择接着一个选择;},好怕自己一不小心就做了错误的决定啊😭。堵上全部人品,希望新公司技术氛围浓厚,有大牛带我,切拜🙊🙏。 不扯有的没的了,翻译一篇关于垃圾回收(以下简称GC)机制的博客(原文地址)。博客内容包括:Java中GC是如何工作的,常见的GC算法(比如:标记清除),Java中不同的垃圾收集器(比如:serial)。","keywords":null,"text":"感觉人生就是while(true){一个选择接着一个选择接着一个选择;},好怕自己一不小心就做了错误的决定啊😭。堵上全部人品,希望新公司技术氛围浓厚,有大牛带我,切拜🙊🙏。 不扯有的没的了,翻译一篇关于垃圾回收(以下简称GC)机制的博客(原文地址)。博客内容包括:Java中GC是如何工作的,常见的GC算法(比如:标记清除),Java中不同的垃圾收集器(比如:serial)。 关键字约定 Young generation –>新生代 Tenured / Old Generation –>老年代 Perm Area –>永久代 重要的东东 在Java中,对象实例都是在堆上创建。一些类信息,常量,静态变量等存储在方法区。堆和方法区都是线程共享的。 GC机制是由JVM提供,用来清理需要清除的对象,回收堆内存。 GC机制将Java程序员从内存管理中解放了出来,可以更关注于业务逻辑。 在Java中,GC是由一个被称为垃圾回收器的守护线程执行的。 在从内存回收一个对象之前会调用对象的finalize()方法。 作为一个Java开发者不能强制JVM执行GC;GC的触发由JVM依据堆内存的大小来决定。 System.gc()和Runtime.gc()会向JVM发送执行GC的请求,但是JVM不保证一定会执行GC。 如果堆没有内存创建新的对象了,会抛出OutOfMemoryError。 GC针对什么对象?了解GC机制的第一步就是理解什么样的对象会被回收。当一个对象通过一系列根对象(比如:静态属性引用的常量)都不可达时就会被回收。简而言之,当一个对象的所有引用都为null。循环依赖不算做引用,如果对象A有一个指向对象B的引用,对象B也有一个指向对象A的引用,除此之外,它们没有其他引用,那么对象A和对象B都、需要被回收(如下图,ObjA和ObjB需要被回收)。 堆内存是如何划分的?Java中对象都在堆上创建。为了GC,堆内存分为三个部分,也可以说三代,分别称为新生代,老年代和永久代。其中新生代又进一步分为Eden区,Survivor 1区和Survivor 2区(如下图)。新创建的对象会分配在Eden区,在经历一次Minor GC后会被移到Survivor 1区,再经历一次Minor GC后会被移到Survivor 2区,直到升至老年代,需要注意的是,一些大对象(长字符串或数组)可能会直接存放到老年代。永久代有一些特殊,它用来存储类的元信息。对于GC是否发生在永久代有许多不同的看法,在我看来这取决于采用的JVM。大家可以通过创建大量的字符串来观察是发生了GC还是抛出了OutOfMemoryError。 GC算法 标记清除算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。该算法的缺点是效率不高并且会产生不连续的内存碎片。 复制算法把内存空间划为两个区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。优点:实现简单,运行高效。缺点:会浪费一定的内存。一般新生代采用这种算法。 标记整理算法标记阶段与标记清除算法一样。但后续并不是直接对可回收的对象进行清理,而是让所有存活对象都想一端移动,然后清理。优点是不会造成内存碎片。 Java中垃圾回收器的类型Java提供多种类型的垃圾回收器。JVM中的垃圾收集一般都采用“分代收集”,不同的堆内存区域采用不同的收集算法,主要目的就是为了增加吞吐量或降低停顿时间。 Serial收集器:新生代收集器,使用复制算法,使用一个线程进行GC,串行,其它工作线程暂停。 ParNew收集器:新生代收集器,使用复制算法,Serial收集器的多线程版,用多个线程进行GC,并行,其它工作线程暂停。使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。 Parallel Scavenge 收集器:吞吐量优先的垃圾回收器,作用在新生代,使用复制算法,关注CPU吞吐量,即运行用户代码的时间/总时间。使用-XX:+UseParallelGC开关控制使用Parallel Scavenge+Serial Old收集器组合回收垃圾。 Serial Old收集器:老年代收集器,单线程收集器,串行,使用标记整理算法,使用单线程进行GC,其它工作线程暂停。 Parallel Old收集器:吞吐量优先的垃圾回收器,作用在老年代,多线程,并行,多线程机制与Parallel Scavenge差不错,使用标记整理算法,在Parallel Old执行时,仍然需要暂停其它线程。 CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,优先使用ParNew+CMS(原因见Full GC和并发垃圾回收一节),当用户线程内存不足时,采用备用方案Serial Old收集。 可以看Java Performance一书来获取更多关于GC调优的信息。 与GC有关的JVM参数做GC调优需要大量的实践,耐心和对项目的分析。我曾经参与过高容量,低延迟的电商系统,在开发中我们需要通过分析造成Full GC的原因来提高系统性能,在这个过程中我发现做GC的调优很大程度上依赖于对系统的分析,系统拥有怎样的对象以及他们的平均生命周期。举个例子,如果一个应用大多是短生命周期的对象,那么应该确保Eden区足够大,这样可以减少Minor GC的次数。可以通过-XX:NewRatio来控制新生代和老年代的比例,比如-XX:NewRatio=3代表新生代和老年代的比例为1:3。需要注意的是,扩大新生代的大小会减少老年代的大小,这会导致Major GC执行的更频繁,而Major GC可能会造成用户线程的停顿从而降低系统吞吐量。JVM中可以用NewSize和MaxNewSize参数来指定新生代内存最小和最大值,如果两个参数值一样,那么就相当于固定了新生代的大小。个人建议,在做GC调优之前最好深入理解Java中GC机制,推荐阅读Sun Microsystems提供的有关GC的文档。这个链接可能会对理解GC机制提供一些帮助。下面的图列出了各个区可用的一些JVM参数。 Full GC和并发垃圾回收并发垃圾回收器的内存回收过程是与用户线程一起并发执行的。通常情况下,并发垃圾回收器可以在用户线程运行的情况下完成大部分的回收工作,所以应用停顿时间很短。但由于并发垃圾回收时用户线程还在运行,所以会有新的垃圾不断产生。作为担保,如果在老年代内存都被占用之前,如果并发垃圾回收器还没结束工作,那么应用会暂停,在所有用户线程停止的情况下完成回收。这种情况称作Full GC,这意味着需要调整有关并发回收的参数了。由于Full GC很影响应用的性能,要尽量避免或减少。特别是如果对于高容量低延迟的电商系统,要尽量避免在交易时间段发生Full GC。 总结 为了分代垃圾回收,Java堆内存分为3代:新生代,老年代和永久代。 新的对象实例会优先分配在新生代,在经历几次Minor GC后(默认15次),还存活的会被移至老年代(某些大对象会直接在老年代分配)。 永久代是否执行GC,取决于采用的JVM。 Minor GC发生在新生代,当Eden区没有足够空间时,会发起一次Minor GC,将Eden区中的存活对象移至Survivor区。Major GC发生在老年代,当升到老年代的对象大于老年代剩余空间时会发生Major GC。 发生Major GC时用户线程会暂停,会降低系统性能和吞吐量。 JVM的参数-Xmx和-Xms用来设置Java堆内存的初始大小和最大值。依据个人经验这个值的比例最好是1:1或者1:1.5。比如,你可以将-Xmx和-Xms都设为1GB,或者-Xmx和-Xms设为1.2GB和1.8GB。 Java中不能手动触发GC,但可以用不同的引用类来辅助垃圾回收器工作(比如:弱引用或软引用)。 以上就是关于Java中GC的一些内容。通过这篇博客,我们可以知道堆内存是如何划分的;一个对象在没有任何强引用指向他或该对象通过根节点不可达时需要被垃圾回收器回收;当垃圾收集器意识到需要进行GC时会触发Minor GC或Major GC,是自动的,无法强制执行。 参考文档(康桑阿米达~) http://icyfenix.iteye.com/blog/715301 http://www.cnblogs.com/zhguang/p/3257367.html 《深入理解java虚拟机》 ————————————————其实我只是一条分割线——————————————————— 关于永久代到底有没有GC还是很懵逼,很多地方看到的说法都不一致。欢迎指正错误,欢迎一起讨论~~ 按国际惯例,wuli光洙结尾~~","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"jvm","slug":"jvm","permalink":"http://yemengying.com/tags/jvm/"}]},{"title":"如何线程安全的使用 HashMap","slug":"threadsafe-hashmap","date":"2016-05-07T09:21:06.000Z","updated":"2018-12-13T08:02:59.000Z","comments":true,"path":"2016/05/07/threadsafe-hashmap/","link":"","permalink":"http://yemengying.com/2016/05/07/threadsafe-hashmap/","excerpt":"这周真是发生了不少事,脑袋和心里一直都很乱,周二参加了一场面试,经历了笔试+3轮面试,周五正式提交了离职申请。要开始新的征程了,意外的有些失落和不舍,毕竟是毕业后的第一份工作,毕竟在这认识了一群可爱的人,毕竟在这学到了很多东西,毕竟这有8000+的aeron chair!!!。可既然已经做了选择就没有退路了,勇敢往下走吧,希望接下来的三周可以把手头上的工作做好交接善始善终,也希望以后不会后悔今天的选择。","keywords":null,"text":"这周真是发生了不少事,脑袋和心里一直都很乱,周二参加了一场面试,经历了笔试+3轮面试,周五正式提交了离职申请。要开始新的征程了,意外的有些失落和不舍,毕竟是毕业后的第一份工作,毕竟在这认识了一群可爱的人,毕竟在这学到了很多东西,毕竟这有8000+的aeron chair!!!。可既然已经做了选择就没有退路了,勇敢往下走吧,希望接下来的三周可以把手头上的工作做好交接善始善终,也希望以后不会后悔今天的选择。 进入正题,在周二面试时,一面的面试官有问到 HashMap 是否是线程安全的,如何在线程安全的前提下使用 HashMap,其实也就是 HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别。当时有些紧张只是简单说了下HashMap不是线程安全的;Hashtable 线程安全,但效率低,因为是 Hashtable 是使用 synchronized 的,所有线程竞争同一把锁;而 ConcurrentHashMap 不仅线程安全而且效率高,因为它包含一个 segment 数组,将数据分段存储,给每一段数据配一把锁,也就是所谓的锁分段技术。当时忘记了 synchronized Map 和解释一下 HashMap 为什么线程不安全。面试结束后问了下面试官哪里有些不足,面试官说上面这个问题的回答算过关,但可以在深入一些或者自己动手尝试一下。so~~~虽然拿到了 offer,但还是再整理一下,不能得过且过啊。 为什么HashMap是线程不安全的总说 HashMap 是线程不安全的,不安全的,不安全的,那么到底为什么它是线程不安全的呢?要回答这个问题就要先来简单了解一下 HashMap 源码中的使用的存储结构(这里引用的是 Java 8 的源码,与7是不一样的)和它的扩容机制。 HashMap的内部存储结构下面是 HashMap 使用的存储结构:12345678transient Node<K,V>[] table;static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next;} 可以看到 HashMap 内部存储使用了一个 Node 数组(默认大小是16),而 Node 类包含一个类型为 Node 的 next 的变量,也就是相当于一个链表,所有根据 hash 值计算的 bucket 一样的 key 会存储到同一个链表里(即产生了冲突),大概就是下面图的样子(顺便推荐个在线画图的网站Creately)。 需要注意的是,在 Java 8 中如果 hash 值相同的 key 数量大于指定值(默认是8)时使用平衡树来代替链表,这会将get()方法的性能从O(n)提高到O(logn)。具体的可以看我的另一篇博客Java 8中HashMap和LinkedHashMap如何解决冲突。 HashMap的自动扩容机制HashMap 内部的 Node 数组默认的大小是16,假设有100万个元素,那么最好的情况下每个 hash 桶里都有62500个元素😱,这时get(),put(),remove()等方法效率都会降低。为了解决这个问题,HashMap 提供了自动扩容机制,当元素个数达到数组大小 loadFactor 后会扩大数组的大小,在默认情况下,数组大小为16,loadFactor 为0.75,也就是说当 HashMap 中的元素超过16\\0.75=12时,会把数组大小扩展为2*16=32,并且重新计算每个元素在新数组中的位置。如下图所示(图片来源,权侵删)。从图中可以看到没扩容前,获取 EntryE 需要遍历5个元素,扩容之后只需要2次。 为什么线程不安全个人觉得 HashMap 在并发时可能出现的问题主要是两方面,首先如果多个线程同时使用put方法添加元素,而且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 一样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程的 put 的数据被覆盖。第二就是如果多个线程同时检测到元素个数超过数组大小* loadFactor ,这样就会发生多个线程同时对 Node 数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失。关于 HashMap 线程不安全这一点,《Java并发编程的艺术》一书中是这样说的: HashMap 在并发执行 put 操作时会引起死循环,导致 CPU 利用率接近100%。因为多线程会导致 HashMap 的 Node 链表形成环形数据结构,一旦形成环形数据结构,Node 的 next 节点永远不为空,就会在获取 Node 时产生死循环。 哇塞,听上去si不si好神奇,居然会产生死循环。。。。 google 了一下,才知道死循环并不是发生在 put 操作时,而是发生在扩容时。详细的解释可以看下面几篇博客: 酷壳-Java HashMap的死循环 HashMap在java并发中如何发生死循环 How does a HashMap work in JAVA 如何线程安全的使用HashMap了解了 HashMap 为什么线程不安全,那现在看看如何线程安全的使用 HashMap。这个无非就是以下三种方式: Hashtable ConcurrentHashMap Synchronized Map 例子:12345678//HashtableMap<String, String> hashtable = new Hashtable<>();//synchronizedMapMap<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());//ConcurrentHashMapMap<String, String> concurrentHashMap = new ConcurrentHashMap<>(); 依次来看看。 Hashtable先稍微吐槽一下,为啥命名不是 HashTable 啊,看着好难受😖,不管了就装作它叫HashTable 吧。这货已经不常用了,就简单说说吧。HashTable 源码中是使用 synchronized 来保证线程安全的,比如下面的 get 方法和 put 方法:123456public synchronized V get(Object key) { // 省略实现 }public synchronized V put(K key, V value) { // 省略实现 } 所以当一个线程访问 HashTable 的同步方法时,其他线程如果也要访问同步方法,会被阻塞住。举个例子,当一个线程使用 put 方法时,另一个线程不但不可以使用 put 方法,连 get 方法都不可以,好霸道啊!!!so~~,效率很低,现在基本不会选择它了。 ConcurrentHashMapConcurrentHashMap (以下简称CHM)是 JUC 包中的一个类,Spring 的源码中有很多使用 CHM 的地方。之前已经翻译过一篇关于 ConcurrentHashMap 的博客,如何在java中使用ConcurrentHashMap,里面介绍了 CHM 在 Java 中的实现,CHM 的一些重要特性和什么情况下应该使用 CHM。需要注意的是,上面博客是基于 Java 7 的,和8有区别,在8中 CHM 摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法,有时间会重新总结一下。 SynchronizedMap看了一下源码,SynchronizedMap 的实现还是很简单的。12345678910111213141516171819202122232425262728293031323334353637383940414243444546// synchronizedMap方法public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { return new SynchronizedMap<>(m); }// SynchronizedMap类private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable { private static final long serialVersionUID = 1978198479659022715L; private final Map<K,V> m; // Backing Map final Object mutex; // Object on which to synchronize SynchronizedMap(Map<K,V> m) { this.m = Objects.requireNonNull(m); mutex = this; } SynchronizedMap(Map<K,V> m, Object mutex) { this.m = m; this.mutex = mutex; } public int size() { synchronized (mutex) {return m.size();} } public boolean isEmpty() { synchronized (mutex) {return m.isEmpty();} } public boolean containsKey(Object key) { synchronized (mutex) {return m.containsKey(key);} } public boolean containsValue(Object value) { synchronized (mutex) {return m.containsValue(value);} } public V get(Object key) { synchronized (mutex) {return m.get(key);} } public V put(K key, V value) { synchronized (mutex) {return m.put(key, value);} } public V remove(Object key) { synchronized (mutex) {return m.remove(key);} } // 省略其他方法 } 从源码中可以看出调用 synchronizedMap() 方法后会返回一个 SynchronizedMap 类的对象,而在 SynchronizedMap 类中使用了 synchronized 同步关键字来保证对 Map 的操作是线程安全的。 性能对比这是要靠数据说话的时代,所以不能只靠嘴说 CHM 快,它就快了。写个测试用例,实际的比较一下这三种方式的效率(源码来源),下面的代码分别通过三种方式创建 Map 对象,使用 ExecutorService 来并发运行5个线程,每个线程添加/获取500K个元素。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566public class CrunchifyConcurrentHashMapVsSynchronizedMap { public final static int THREAD_POOL_SIZE = 5; public static Map<String, Integer> crunchifyHashTableObject = null; public static Map<String, Integer> crunchifySynchronizedMapObject = null; public static Map<String, Integer> crunchifyConcurrentHashMapObject = null; public static void main(String[] args) throws InterruptedException { // Test with Hashtable Object crunchifyHashTableObject = new Hashtable<>(); crunchifyPerformTest(crunchifyHashTableObject); // Test with synchronizedMap Object crunchifySynchronizedMapObject = Collections.synchronizedMap(new HashMap<String, Integer>()); crunchifyPerformTest(crunchifySynchronizedMapObject); // Test with ConcurrentHashMap Object crunchifyConcurrentHashMapObject = new ConcurrentHashMap<>(); crunchifyPerformTest(crunchifyConcurrentHashMapObject); } public static void crunchifyPerformTest(final Map<String, Integer> crunchifyThreads) throws InterruptedException { System.out.println(\"Test started for: \" + crunchifyThreads.getClass()); long averageTime = 0; for (int i = 0; i < 5; i++) { long startTime = System.nanoTime(); ExecutorService crunchifyExServer = Executors.newFixedThreadPool(THREAD_POOL_SIZE); for (int j = 0; j < THREAD_POOL_SIZE; j++) { crunchifyExServer.execute(new Runnable() { @SuppressWarnings(\"unused\") @Override public void run() { for (int i = 0; i < 500000; i++) { Integer crunchifyRandomNumber = (int) Math.ceil(Math.random() * 550000); // Retrieve value. We are not using it anywhere Integer crunchifyValue = crunchifyThreads.get(String.valueOf(crunchifyRandomNumber)); // Put value crunchifyThreads.put(String.valueOf(crunchifyRandomNumber), crunchifyRandomNumber); } } }); } // Make sure executor stops crunchifyExServer.shutdown(); // Blocks until all tasks have completed execution after a shutdown request crunchifyExServer.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); long entTime = System.nanoTime(); long totalTime = (entTime - startTime) / 1000000L; averageTime += totalTime; System.out.println(\"2500K entried added/retrieved in \" + totalTime + \" ms\"); } System.out.println(\"For \" + crunchifyThreads.getClass() + \" the average time is \" + averageTime / 5 + \" ms\\n\"); }} 测试结果: 1234567891011121314151617181920212223Test started for: class java.util.Hashtable2500K entried added/retrieved in 2018 ms2500K entried added/retrieved in 1746 ms2500K entried added/retrieved in 1806 ms2500K entried added/retrieved in 1801 ms2500K entried added/retrieved in 1804 msFor class java.util.Hashtable the average time is 1835 msTest started for: class java.util.Collections$SynchronizedMap2500K entried added/retrieved in 3041 ms2500K entried added/retrieved in 1690 ms2500K entried added/retrieved in 1740 ms2500K entried added/retrieved in 1649 ms2500K entried added/retrieved in 1696 msFor class java.util.Collections$SynchronizedMap the average time is 1963 msTest started for: class java.util.concurrent.ConcurrentHashMap2500K entried added/retrieved in 738 ms2500K entried added/retrieved in 696 ms2500K entried added/retrieved in 548 ms2500K entried added/retrieved in 1447 ms2500K entried added/retrieved in 531 msFor class java.util.concurrent.ConcurrentHashMap the average time is 792 ms 这个就不用废话了,CHM 性能是明显优于 Hashtable 和 SynchronizedMap 的,CHM 花费的时间比前两个的一半还少,哈哈,以后再有人问就可以甩数据了。 欢迎指正错误,欢迎一起讨论。另外,针对提离职当天发生的一个小插曲,真真是给我上了一课,不是所有人都能接受实话的,只想引用欢乐颂里安迪的一句话:常与同好争高下,不与傻瓜论短长。 都看到这了,关注个公众号再走吧🙈","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"},{"name":"hashmap","slug":"java/hashmap","permalink":"http://yemengying.com/categories/java/hashmap/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"hashMap","slug":"hashMap","permalink":"http://yemengying.com/tags/hashMap/"}]},{"title":"觉得还不错的网站","slug":"good-website","date":"2016-04-24T13:35:44.000Z","updated":"2018-12-13T08:03:28.000Z","comments":true,"path":"2016/04/24/good-website/","link":"","permalink":"http://yemengying.com/2016/04/24/good-website/","excerpt":"整理下收藏夹中觉得还不错的网站或资料,不涉及订阅的博客,主要是因为一开始收藏夹并没有分类所以就乱了,不方便找😂,嫑吐槽我。。。。","keywords":null,"text":"整理下收藏夹中觉得还不错的网站或资料,不涉及订阅的博客,主要是因为一开始收藏夹并没有分类所以就乱了,不方便找😂,嫑吐槽我。。。。 镇楼1.https://www.google.com2.http://stackoverflow.com/3.https://github.com/这三个大家都懂。。把它们放上来的主要目的是。。。撑场子。。。 Redis相关 官网1.http://redis.io/(英文版)2.http://www.redis.cn/(中文版) 其它网站1.http://try.redis.io/(可以在线练习redis常用命令)2.http://redisdoc.com/(查找redis命令的详细信息 包括查看命令的返回值和时间复杂度) 一本短小精悍的电子书1.https://github.com/karlseguin/the-little-redis-book(英文原版)2.https://github.com/JasonLai256/the-little-redis-book/blob/master/cn/redis.md(中文译版) 算法和数据结构相关 公开课http://open.163.com/special/opencourse/(网易公开课上的麻省理工算法导论课程) 做题https://leetcode.com/(leetcode,类似网站有好几个,有选择恐惧症就放一个吧) 其它网站http://algorithms.tutorialhorizon.com/(最近特别爱的一个网站,讲解各种算法的,有图解,有code,简直不能更赞,只能意会不可言传,点进去看看吧)http://visualgo.net/ (一个通过动图帮助理解数据结构的网站 比较适合初学者 英文版)http://zh.visualgo.net/(上面网站的中文版) 什么都能学 教程网站1.http://tutorials.jenkov.com/(关于Java,Web ,Mobile and so on)2.http://www.sitepoint.com(感觉是个不错的网站,不过关于Java的内容不太多,所以也没常看) 不知道怎么分类1.http://overapi.com/(网站收集了很多很有用的手册,开发时可以节省很多时间)2.https://www.reddit.com/(信息量比较大的网站,比较喜欢去上面看些搞笑的图片)3.https://news.ycombinator.com/(不是很喜欢它的页面。。。)4.https://www.v2ex.com/(可以上去找工作 哈哈)5.https://wanqu.co/(湾区日报 每天推送5篇优质英文文章) 突然发现目前最幸福的事就是在重看以前的Running Man时,发现有一集没看过😜。。不写了 去看wuli光洙了😍","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"}]},{"title":"Redis相关---Redis持久化(AOF&Snapshot)","slug":"Redis-Persistence","date":"2016-04-24T07:09:18.000Z","updated":"2018-12-13T08:04:59.000Z","comments":true,"path":"2016/04/24/Redis-Persistence/","link":"","permalink":"http://yemengying.com/2016/04/24/Redis-Persistence/","excerpt":"恩,先说点题外话。上周被小虐了一丢丢,但有很大收获,了解了自己的不足,知道了自己还在哪些方面有欠缺。更坚定了一直以来的想法,应届生或者工作时间不长的人找工作公司规模,福利薪资都是浮云,跟对人才是最重要的,非常及其以及特别的重要,一个人好技术牛的部门leader绝对抵得上5K的薪资。这也完美解释了为何部门拆分,老大和磊哥走了之后这么不舒服,这尼玛相当于给我减了5K的工资啊,扯远了。。。。还是来看Redis吧,整理下Redis持久化的相关内容,加深下印象。不想看文字的可直接看下面的图😂。","keywords":null,"text":"恩,先说点题外话。上周被小虐了一丢丢,但有很大收获,了解了自己的不足,知道了自己还在哪些方面有欠缺。更坚定了一直以来的想法,应届生或者工作时间不长的人找工作公司规模,福利薪资都是浮云,跟对人才是最重要的,非常及其以及特别的重要,一个人好技术牛的部门leader绝对抵得上5K的薪资。这也完美解释了为何部门拆分,老大和磊哥走了之后这么不舒服,这尼玛相当于给我减了5K的工资啊,扯远了。。。。还是来看Redis吧,整理下Redis持久化的相关内容,加深下印象。不想看文字的可直接看下面的图😂。 AOF持久化AOF(append-only file只追加文件)持久化会将执行的写命令追加到AOF文件的末尾。在恢复数据时,只要从头到尾的执行AOF文件中包含的所有写命令即可 AOF可用配置 配置项 含义 可选值 appendonly 是否开启AOF持久化 no,yes appendfsync 将写命令同步到硬盘的间隔 everysec,always,no no-appendfsync-on-rewrite 对文件进行压缩时能否执行同步操作 no,yes auto-aof-rewrite-percentage 当前文件大小是上一次压缩后AOF文件大小的多少时执行自动压缩 auto-aof-rewrite-min-size 当前文件大小是多少时执行自动压缩 dir path/to/appendonly.aof 文件存放位置 其实主要的就是appendfsync配置项,有三个可选值,always(每次执行写操作都要同步写入硬盘),everysec(每秒执行一次同步),no(让系统决定何时执行同步)。虽然选择always可将数据丢失减少到最少,但这种策略会对硬盘进行大量的写入操作,处理命令速度受到硬盘限制。建议选择everysec。 AOF优缺点优点: 比快照方式可靠,默认每秒同步一次,意味着最多丢失一秒的数据 缺点: 相同数据集大小,AOF文件会比快照文件大 AOF文件格式一开始以为Redis就是将写命令原封不动的存储到AOF文件中,自己试了一下才知道,AOF文件是使用Redis网络通讯协议的格式来保存这些命令。举个🌰:12127.0.0.1:6379> set number 21OK 将上面的命令存储到AOF文件就是下面的样子(select 0命令是代表选择id为0的数据库): 123456789101112*2$6SELECT$10*3$3SET$6number$221 来看下AOF文件的格式,了解它的格式可以让我们很容易的解析它,指不定哪天能用到啊🌝。*号代表命令参数的个数(在上面的例子中参数为set、number、21,共3个),$号代表第N个参数的长度,在上面的例子中,三个参数的长度分别为3,6,2。 压缩AOF文件Redis可以自动压缩(也可以叫重写)AOF文件,用户也可以通过BGREWRITEAOF命令来压缩AOF文件。这里的压缩,不是平时说的压缩的意思,是指创建一个新的文件来替换旧的文件,两个文件保存的数据状态完全一致。如果在本地手动执行BGREWRITEAOF命令,可以看到会生成一个temp-rewriteaof-*.aof的临时文件,在结束后替换appendonly.aof文件,从而减小appendonly.aof文件的大小。我知道这样说其实还是不好理解,还是上图吧。为了方便画图,我就默默假装Redis直接将命令按输入的样子存储到AOF文件中,不要拆穿我🙈。假设执行的写命令是下面的样子:那么看看在压缩前AOF文件的样子(就是存储了除get命令外的所有写命令):接着执行BGREWRITEAOF命令,Redis会生成一个新的文件来替换旧的AOF文件,从而达到压缩的目的: 可以看到压缩前文件中存储了7条写命令,压缩后只存储一条。而且执行set number 1和6次incr number命令 ,与执行set number 7命令效果是一样的。从而即保证了数据的正确性又压缩了文件的大小。 需要注意的是,Redis是启用子进程来进行AOF文件的压缩,在这期间主进程还是可以继续处理请求的,如果这时请求有写操作就可能导致当前数据库与压缩后的AOF不一致。Redis增加了一个缓存来解决这个问题,主进程在接收到新的写操作命令之后,会将命令写入现有的AOF文件和缓存中。在子进程完成新的AOF文件之后会将缓存的内容写入到新的AOF文件中,并改名覆盖旧的AOF文件。 快照持久化快照可用配置 配置项 含义 可选值 save m n m秒内有n次写入时创建快照 stop-writes-on-bgsave-error 创建快照失败后是否继续执行写命令 no,yes rdbcomression 是否压缩快照文件 no,yes dbfilename 命名快照文件 dir path/to/dump.rdb 文件存放位置 RDB文件结构RDB文件是一个经过压缩的二进制文件,不同类型的键值对会采用不同的方式来保存它们。具体的结构我也还没理清楚。。可以参考这篇文章http://redisbook.com/preview/rdb/rdb_struct.html 创建快照创建快照的方式有以下几种: 客户端发送BGSAVE命令。与压缩AOF文件一样,Redis会fork出一个子进程,由子进程负责将快照写入硬盘。 客户端发送SAVE命令。Redis会开始创建快照,并且在快照创建完成之前不再处理其他命令。不常使用SAVE命令 在满足配置的save m n选项时。比如,配置了save 60 1000,会在满足60秒内有1000次写入的时候开始创建快照。 当接收到SHUTDOWN请求时,Redis会执行SAVE命令,并且不再执行任何其他命令。 当从服务器向主服务器发送SYNC命令时,如果主服务器不是刚刚执行过BGSAVE命令,就会开始执行BGSAVE来创建快照。 快照优缺点优点: 文件紧凑,适用于做不同版本的数据备份 与AOF相比在恢复大数据集时,更快 很方便传送到另一个数据中心 缺点: 一旦Redis出现问题,上一次创建快照之后的数据就丢失了 参考文档《Redis In Action》http://www.redis.cn/topics/persistence.htmlhttp://redisbook.readthedocs.org/en/latest/internal/aof.html","raw":null,"content":null,"categories":[{"name":"redis","slug":"redis","permalink":"http://yemengying.com/categories/redis/"}],"tags":[{"name":"redis","slug":"redis","permalink":"http://yemengying.com/tags/redis/"}]},{"title":"Redis相关---【译】利用Redis起飞吧","slug":"take-advantage-of-Redis","date":"2016-04-02T02:56:03.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2016/04/02/take-advantage-of-Redis/","link":"","permalink":"http://yemengying.com/2016/04/02/take-advantage-of-Redis/","excerpt":"一直以来对Redis的使用都很是简单粗暴,不得精髓,趁假期好好补补。翻译一篇Redis之父Antirez的博客,文章中讲述了几个利用Redis解决实际问题的例子。并非逐字逐句的翻译,有些不太懂的地方就任性的跳过了😋,翻译这篇博客只做加深印象之用,建议大家还是出门左转看原文吧。。。。。。 原文地址:http://oldblog.antirez.com/post/take-advantage-of-redis-adding-it-to-your-stack.html","keywords":null,"text":"一直以来对Redis的使用都很是简单粗暴,不得精髓,趁假期好好补补。翻译一篇Redis之父Antirez的博客,文章中讲述了几个利用Redis解决实际问题的例子。并非逐字逐句的翻译,有些不太懂的地方就任性的跳过了😋,翻译这篇博客只做加深印象之用,建议大家还是出门左转看原文吧。。。。。。 原文地址:http://oldblog.antirez.com/post/take-advantage-of-redis-adding-it-to-your-stack.html 写在前面 Redis在很多方面不同于MySql等数据库:比如,Redis使用内存做主要的存储,使用磁盘实现数据持久化;数据模型不同;Redis是单线程的等等。但我认为最大的不同是如果开发者想要使用Redis,无需切换到Redis。可以在不把Redis作为数据库的前提下,利用Redis实现一些以前不好实现的功能或者优化遗留的问题。 完全切换到Redis当然也是可行的,许多开发者在想使用Redis的一些特性时,会将Redis当做主数据库,但将一个已经在生产环境中运行的项目切换到Redis显然是个大工程。而且有一些应用并不适合将Redis作为数据库:比如Redis的数据集不能比可用内存大,所以对于大数据量的应用,Redis可能不是一个好的选择。下面会介绍一些在已有项目中加入Redis的例子,会向大家展示如何在不把Redis当做主要数据库的情况下利用Redis的某些特性解决问题。 在主页展示最新的评论列表我敢打赌,如果每次都通过下面的查询语句获取最新评论,那么网站性能会很差。1SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10 展现最新添加的某些东西在网站应用中十分常见,下面看看如何利用Redis优化这类问题。假设网站想要展示最新的20条评论,并且还有个“展示全部评论”的链接,通过这个链接可以通过分页的形式查看历史评论记录。我们还假设每条评论都存储在数据库中,并且有一个自增的Id。我们可以使用一个很简单的Redis同时解决展现最新评论和分页展现历史评论的问题。 每当创建一条新的评论,将评论的Id插入到Redis的一个列表中:LPUSH latest.comments Redis支持将列表修剪到指定长度。所以我们可以通过LTRIM latest.comments 0 5000.操作维持列表中始终存储5000个最新的评论Id. 每次想要获取指定范围内的评论时,可以使用下面的函数(伪代码).1234567FUNCTION get_latest_comments(start,num_items): id_list = redis.lrange(\"latest.comments\",start,start+num_items-1) IF id_list.length < num_items id_list = SQL_DB(\"SELECT ... ORDER BY time LIMIT ...\") END RETURN id_listEND 这段代码的功能很简单。在Redis中维护一个“动态缓存”,经常更新,存储最新评论的Id,缓存中限制最多只存5000个Id。在系统第一次启动时,缓存列表中的Id个数为0。所以我们上面实现的方法会在先访问Redis,如果参数(start/count)的超过了列表范围,再去访问数据库。我们无需刷新缓存,并且只会在用户想要查看最新5000条评论之外的评论时才会访问数据库。也就是说展示最新评论的主页,和查看历史评论前几页都无需访问数据库。 删除和过滤在遇到评论被删除时,我们可以使用LREM命令来删除Redis列表中缓存的评论Id。如果删除评论的情况不常见,也可以在展示指定评论时跳过它,因为我们在根据评论Id去数据库查询评论具体内容时,数据库会告诉我们某条评论已经不存在了。 选手积分榜及相关问题另一个比较常见的,用数据库实现性能较差的需求是按分数排序,展现列表,并且实时更新,一个常见的例子就是在线游戏中的选手积分榜。在在线游戏中,需要接受高频率的来自不同用户的分数更新,通过这些分数实现以下需求: 在积分榜中展现分数最高的100位选手 展现用户的当前排名 这些操作用Redis的有序集合来实现是很简单的,哪怕你的系统每分钟要更新上百万的分数。每当接受到一个用户的新分数时,我们对Redis做如下的操作:1ZADD leaderboard <score> <username> ps:也可以使用userId,而不是用户名, 看开发者的个人喜好。接下来,我们可以很简单的通过下面的操作获得分数排名前100的用户1ZREVRANGE leaderboard 0 99 也可以通过1ZRANK leaderboard <username> 来获取用户的当前排名。除此之外,我们还可以获得排名靠近当前用户的用户,以及等等等等。。。。。 按照用户投票和时间排序下面谈谈选手积分榜问题的一个变种问题。在诸如Reddit和Hacker News这类的网站中,新闻是按照类似下面公式计算得出的分数来排序的。1score = points / time^alpha 也就是说用户的投票会在一定很小程度上提升新闻的排名,而时间流逝则会使新闻的排名呈指数级下降。实际的算法会比我们的例子更复杂,但解决的方式是一样的。首先假设只有最新发布的1000条新闻才有资格出现在首页上,所以我们只关注最新发布的1000条新闻,忽略太老的新闻,大致解决方法如下: 每当新发布一条新闻,将其Id加入到Redis的列表中,通过LPUSH+LTRIM将列表维持到只保存最新发布的1000条新闻的Id。 通过一个定时任务获取Redis中Id列表,并且不断的计算列表中新闻的分数。将计算结果通过ZADD操作存储到一个有序集合中,同时将旧的新闻从有序列表中清除。主要思想就是,有序集合中存储着1000条最新新闻,并按分数排序。分数的排序是通过后台程序完成的,与浏览网站的用户数无关。 将元素过期我们还可以利用有序集合实现将超过指定时间的元素在数据库中删除或置为过期。具体做法如下: 每当数据库中新加入一个元素时,同时将其添加到有序集合中,分数是当前时间加上指定的存活时间 让一个后台任务查询有序集合,利用ZRANGE …WITHSCORES获取元素,如果元素对应的分数小于当前时间,说明元素已过期,在数据库中删除对应记录。 计数器Redis可以用来实现计数器,使用INCRBY操作即可。相信很多人都曾想过在数据库中添加一张计数器表,用来为用户展现一些统计信息,但又考虑到需要对这张表进行大量的写操作而放弃,本宝宝曾经无数次遇到过这个问题。现在,我们可以通过Redis来解决这个问题。通过Redis我们可以为计数器原子性的增加计数,也可以使用GETSET命令重置计数器或者为计数器设置过期时间。比如我们可以按照如下做法实现计数一个用户指定时间内的页面访问量,如果超过了指定值,比如20,就弹出一个提示。12INCR user:<id>EXPIRE user:<id> 60 统计指定时间内不同的用户另一个用数据库实现比较困难,但是用Redis却很简单的功能就是统计指定时间内访问某资源的用户数。比如我想知道访问指定页面的用户数(相同用户访问多次只计算一次),我只需要在新增一个页面浏览(PV)时,执行下面的操作:1SADD page:day1:<page_id> <user_id> 想获得某页面的用户访问数,只需执行SCARD page:day1:即可。 实时分析我们已经看过了几个利用Redis如何实现一些利用数据库不好实现的功能,如果深入学习Redis的命令集,活用Redis中的数据结构,我们可以很容易的实现实时统计的功能,用来增强反垃圾邮件系统,或者通过分析得到的一些数据来提高网站的质量。 发布/订阅Redis中实现了一个高性能的发布/订阅系统,易于使用,稳定,性能高,并且支持模式匹配。详细的信息可以阅读Redis官方文档 队列大家可能已经注意到了,通过Redis对列表插入元素和弹出元素的命令,很适合用来实现一个队列。但Redis能做的远远不止这些,比如Redis弹出列表元素时还有提供BLPOP命令,可以在列表为空时,将连接阻塞。除此之外利用有序集合也可以很容易的实现一个优先队列。Redis在队列方面还有很多用法(比如RPOPLPUSH,Resque),大家可以慢慢发掘。 缓存关于这一节其实就够再写一篇博客了。简单来说,Redis可以作为memcached的替代品,使我们的缓存既可以存储数据又易于更新。 快在Redis的帮助下起飞吧快使用Redis来增强用户体验,降低系统复杂度,加快请求响应吧~~,无需全部切换到Redis,可仅仅利用Redis实现用数据库不好实现或性能不高的新功能。 分享个觉得还不错的视频,拖延症患者可以看看,不过估计也没什么卵用,该拖还得拖,哈哈哈哈哈哈~~~","raw":null,"content":null,"categories":[{"name":"redis","slug":"redis","permalink":"http://yemengying.com/categories/redis/"}],"tags":[{"name":"redis","slug":"redis","permalink":"http://yemengying.com/tags/redis/"},{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"[Elasticsearch配置项(二)]Node,Threadpool模块配置","slug":"elasticsearch-settings2","date":"2016-03-21T13:32:41.000Z","updated":"2018-12-13T08:06:06.000Z","comments":true,"path":"2016/03/21/elasticsearch-settings2/","link":"","permalink":"http://yemengying.com/2016/03/21/elasticsearch-settings2/","excerpt":"接上文。。。。。。按照官方文档(版本2.2)和一些参考资料整理一下elasticsearch的可配置项。先整理了Node,ThreadPool两个模块的可配置项,其他模块(比如Cluster)会在之后慢慢整理的。本文只包含两个模块可用配置项的含义及用法,并不涉及应该如何优化,这是为什么呢?因为俺也不会。。。。。(欢迎指正错误,康桑阿米达) 相关博客:elasticsearch 配置项(一)","keywords":null,"text":"接上文。。。。。。按照官方文档(版本2.2)和一些参考资料整理一下elasticsearch的可配置项。先整理了Node,ThreadPool两个模块的可配置项,其他模块(比如Cluster)会在之后慢慢整理的。本文只包含两个模块可用配置项的含义及用法,并不涉及应该如何优化,这是为什么呢?因为俺也不会。。。。。(欢迎指正错误,康桑阿米达) 相关博客:elasticsearch 配置项(一)wuli光洙镇楼~~ 参考文档(万分感谢) http://www.opscoder.info/es_threadpool.html http://www.voidcn.com/blog/BrotherDong90/article/p-3851633.html Node(节点)每当启动一个Es实例,就是启动了一个节点。多个连接在一起的节点组成的集合就是集群。默认情况下,每个节点都可以通过HTTP和Transport通信。每个节点都知道集群中的其他节点,可以将客户端的请求转发到合适的节点。除此之外,每个节点还有着以下一种或多种具体角色。 Master-eligible node (候选主节点) 当一个节点的node.master被设置为true(默认为true)时,该节点就有资格被选为master节点,控制集群。 Data node (数据节点) 配置项node.data设置为true的节点称为数据节点。数据节点存储数据并且处理和数据相关的一些操作,比如CRUD,查找和聚合等。 Client node (客户端节点) 当一个节点的node.master和node.data均被设置为false时,它既不能存储数据也不能作为一个主节点。它被看做一个“路由器”,负责将集群层面的请求转发到主节点,将数据相关的请求转发到数据节点。 Tribe node (部落节点) 部落节点是一种特殊类型的客户端节点,可通过tribe.*配置项配置。部落节点可以连接多个集群,并且可以跨集群执行查找和其他操作。 默认情况下,每个节点即是主节点也是数据节点。但是当集群扩大后,更好的做法是将主节点和数据节点独立开。 Coordinating node(协调节点):一些请求可能会涉及到多个数据节点,比如搜索或批量索引。搜索请求一般分为两个阶段,由接受客户端请求的节点做协调,称为协调节点。在搜索的第一阶段协调节点会将请求转发到数据节点,每个节点在本地执行请求,并将结果返给协调节点。在第二阶段,协调节点会将各个结果汇总在一起。这意味着负责接受请求的客户端节点(也就是协调节点)需要用足够的内存和CPU来处理查询结果的汇总。 Master-eligible node(候选主节点)主节点主要负责创建索引,删除索引,追踪集群中的节点,分配分片等,所以有一个稳定的主节点对于集群来说非常重要,非常重要,非常重要(说三遍,哈哈哈)。集群中任何有资格成为主节点的节点都可能被选为主节点。由于索引和搜索会对节点资源造成压力,在集群比较大时,最好将主节点和数据节点的角色区分,即不要让主节点同时也是数据节点。通过下面的配置可以设置一个专门的主节点(只是主节点不是数据节点)。12node.master: truenode.data: false 通过minimum_master_nodes来避免脑裂现象 discovery.zen.minimum_master_nodes配置项说明形成集群时,集群中有资格成为主节点的节点数最少是多少,默认为1.脑裂现象:假设集群中有两个有资格成为主节点的候选主节点,discovery.zen.minimum_master_nodes配置默认为1。当由于网络问题中断了两个节点间的通信,这时两个节点都只会发现一个有资格成为主节点的节点(自己本身), 根据配置(minimum_master_nodes = 1),符合组成一个集群的条件,所以每个节点都会成为新的master节点,从而导致形成了两个集群,也就是脑裂。直到其中一个节点重启,才会重新形成集群,并且写入重启节点的数据会丢失。假设集群中有三个有资格成为主节点的候选主节点,而这时minimum_master_nodes设置为2,如果一个节点与其他两个失去了通信,被独立的节点会发现不满足设置的条件(有两个候选主节点),所以不会选举自己为主节点。而剩下两个节点会选举出一个新的主节点,确保正常运行。discovery.zen.minimum_master_nodes最好设置为(候选主节点数/2) + 1, 举个例子,当有三个候选主节点时,该配置项的值为(3/2)+1=2。也可以通过下面的API动态的更新这个值:123456PUT _cluster/settings{ \"transient\": { \"discovery.zen.minimum_master_nodes\": 2 }} Data node(数据节点)数据节点包含着索引文档的分片,负责处理和数据相关的操作,比如CRUD,搜索和聚合。这些操作会涉及到IO,内存和CPU,所以要注意监控这些资源,添加更多的数据节点以防负载过重。通过下面的配置可以设置一个专门的数据节点(只是数据节点不是主节点)。12node.master: falsenode.data: true Client node(客户端节点)客户端节点主要负责路由请求,汇总搜索结果等,本质上来看,客户端节点更像一个负载均衡器。 注意:集群中添加过多的客户端节点会增加整个集群的负担。所以不要过大夸大客户端节点的好处,数据节点也可以像客户端节点一样服务。 通过下面的配置可以设置一个专门的客户端节点(不是数据节点也不是主节点)。 12node.master: falsenode.data: false 设置节点的数据路径path.data每个数据节点和主节点都需要在文件中存储一些关于分片,索引和集群的元数据。elasticsearch.yml文件中的path.data可以配置文件的绝对路径或相对路径,默认值是$ES_HOME/data,也可以通过命令配置。1./bin/elasticsearch --path.data /var/elasticsearch/data node.max_local_storage-nodes上面的设置可以被不同的节点共享(在生产环境下建议一个服务器只运行一个节点)。为了避免多个节点共享同一个路径,可以在elasticsearch.yml中添加如下配置。1node.max_local_storage_nodes: 1 注意,不要将不同类型节点的信息存储到同一个目录下,可能会造称数据丢失。 Thread Pool(线程池)为了提升线程内存消耗的管理,每个Es节点都有多个线程池。 线程池类型下面介绍一下线程池的三种类型和各自的一些参数:cached: cached类型的线程池没有限制大小,当有pending的请求时就会创建一个线程。这类线程池可以防止请求阻塞或被拒绝。未使用的线程会在过期(默认5分钟)之后消亡。cache类型专门为generic线程池保留的。 keep_alive参数定义了未使用的线程的存活时间。 123threadpool: generic: keep_alive: 2m fixed: fixed类型的线程池持有固定个数的线程处理请求队列。size参数用来控制线程的个数,默认为cpu核心数的5倍。queue_size参数用来控制请求队列的大小。默认值为-1,意味着无上限。如果请求队列已满,那么接下来到来的请求会被终止。 1234threadpool: index: size: 30 queue_size: 1000 scaling: scaling线程池中线程数可动态变化。线程数在1和size参数值的中间。 keep_alive参数定义了未使用的线程的存活时间。 1234threadpool: warmer: size: 8 keep_alive: 2m Es中重要的线程池以下是Es中几个比较重要的线程池及他们的类型: 线程池 作用 generic 负责一些诸如发现节点之类的通用操作。该线程池类型为cache。 index 负责索引数据/删除数据操作,类型为fixed,默认线程数为available processors,队列大小为200。 search 负责统计/搜索操作,类型为fixed,默认线程数为int((available_processors * 3) / 2) + 1,队列大小为1000。 suggest 负责获取提示操作,类型为fixed,默认线程数为available processors,队列大小为1000。 get 负责get操作,类型为fixed,默认线程数为available processors,队列大小为1000。 bulk 负责批量操作,类型为fixed,默认线程数为available processors,队列大小为50。 percolate 负责过滤操作,类型为fixed,默认线程数为available processors,队列大小为1000。 snapshot 负责快照/恢复操作,类型为scaling,默认线程数为min(5, (available processors)/2),默认未使用线程的存活时间为5m。 warmer 负责warm-up操作,类型为scaling,默认线程数为min(5, (available processors)/2),默认未使用线程的存活时间为5m。 refresh 负责更新操作,类型为scaling,默认线程数为min(10, (available processors)/2),默认未使用线程的存活时间为5m。 listener 负责java client的执行,类型为scaling,默认线程数为min(10, (available processors)/2),默认未使用线程的存活时间为5m。 处理器设置Es可以自动检测处理器的数量,线程池的配置也会基于这个值。可能存在检测失败的情况,这是可以通过processors配置显式设置这个值。 注意以上这些配置如果不是很了解,还是不要轻易改动,使用默认配置即可。 看累了吧,分享个觉得还不错的TED视频~~","raw":null,"content":null,"categories":[{"name":"elasticsearch","slug":"elasticsearch","permalink":"http://yemengying.com/categories/elasticsearch/"}],"tags":[{"name":"elasticsearch","slug":"elasticsearch","permalink":"http://yemengying.com/tags/elasticsearch/"}]},{"title":"[Elasticsearch配置项(一)]Local gateway,HTTP,Indices,Network Settings模块配置","slug":"Elasticsearch配置项-Local-gateway-HTTP-Indices-Network-Settings模块配置","date":"2016-03-18T09:48:10.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2016/03/18/Elasticsearch配置项-Local-gateway-HTTP-Indices-Network-Settings模块配置/","link":"","permalink":"http://yemengying.com/2016/03/18/Elasticsearch配置项-Local-gateway-HTTP-Indices-Network-Settings模块配置/","excerpt":"近两周线上的搜索接口间隔性莫名其妙的出现莫名其妙的异常,比如神马NoAvailableThread,NoAvailableWorker之类的。可能是由于部门中没有很懂elasticsearch的人,只能摸着石头过河,所以一直使用elasticsearch的默认配置并没有对其线程池和内存分配进行优化造成的。所以按照官方文档(版本2.2)和一些参考资料整理一下elasticsearch的可配置项,看看可不可以优化一下。先整理了Local gateway,HTTP,Indices,Network Settings四个模块的可配置项,其他模块(比如Cluster)会在之后慢慢整理的。本文只包含四个模块可用配置项的含义及用法,并不涉及应该如何优化,这是为什么呢?因为俺也不会。。。。。(欢迎指正错误,康桑阿米达) 相关博客:elasticsearch 配置项(二)","keywords":null,"text":"近两周线上的搜索接口间隔性莫名其妙的出现莫名其妙的异常,比如神马NoAvailableThread,NoAvailableWorker之类的。可能是由于部门中没有很懂elasticsearch的人,只能摸着石头过河,所以一直使用elasticsearch的默认配置并没有对其线程池和内存分配进行优化造成的。所以按照官方文档(版本2.2)和一些参考资料整理一下elasticsearch的可配置项,看看可不可以优化一下。先整理了Local gateway,HTTP,Indices,Network Settings四个模块的可配置项,其他模块(比如Cluster)会在之后慢慢整理的。本文只包含四个模块可用配置项的含义及用法,并不涉及应该如何优化,这是为什么呢?因为俺也不会。。。。。(欢迎指正错误,康桑阿米达) 相关博客:elasticsearch 配置项(二) 参考资料(万分感谢): http://donlianli.iteye.com/blog/2115979 http://aoyouzi.iteye.com/blog/2164820 http://blog.csdn.net/jingkyks/article/details/41081261 http://m.oschina.net/blog/387512 http://my.oschina.net/secisland/blog/618702?fromerr=ZGR1hlby#OSC_h4_5 https://github.com/chenryn/ELKstack-guide-cn/blob/master/elasticsearch/performance/cache.md Local gateway模块官网对应链接:Local gateway该模块用于存储集群信息和分片数据,以便整个集群重启后可以恢复。以下的一些静态配置,需要在集群的每一个节点上设置,用来控制节点需要等待多长时间之后再尝试恢复存储在本地的数据。 配置项 含义 gateway.expected_nodes 期望的集群中节点的数量,当集群中的节点数达到设定值时立即开始启动恢复本地数据的进程(会忽略recover_after_time配置),默认为0 gateway.expected_master_nodes 期望的集群中master节点的数量,当集群中的节点数达到设定值时立即开始启动恢复本地数据的进程(会忽略recover_after_time配置),默认为0 gateway.expected_data_nodes 期望的集群中master节点的数量,当集群中的节点数达到设定值时立即开始启动恢复本地数据的进程(会忽略recover_after_time配置),默认为0 gateway.recover_after_time 当没有达到期望的节点数时,恢复进程会在等待配置时间之后尝试启动。当期望的节点数设置为1时,等待时间默认为5m 一旦到达了recover_after_time设置的时间,还要满足以下的配置条件,恢复进程才会启动。 配置项 含义 gateway.recover_after_nodes 需要多少个节点加入集群 gateway.recover_after_master_nodes 需要多少个master节点加入集群 gateway.recover_after_data_nodes 需要多少个data节点加入集群 注意:这些配置只有在整个集群重启时才会有用。 HTTP模块官网对应链接:HTTPHTTP模块用来通过HTTP暴露Es的API。因为HTTP机制是完全异步的,这意味着等待响应时不会阻塞线程。使用异步的通信可以解决C10k的问题。如果可以,可考虑使用HTTP keep alive来连接以便提升性能。并且不要在客户端使用HTTP chunking。 配置下面表中是关于HTTP模块的一些配置。需要注意的是,它们都不能动态更新,必须配置在elasticsearch.yml文件中。 配置项 含义 http.port 绑定端口的范围 默认9200-9300 http.publish_port 客户端与节点交互时需要使用的端口。这一配置在集群节点处于防火墙后时很有用,默认和http.port中分配的端口一致。 http.bind_host 绑定http服务的host地址,默认和http.host(如果设置了)或者network.bind_host一致 http.publish_host 客户端访问的host地址,默认和http.host(如果设置了)或者network.public_host一致 http.host 用来设置http.bind_host和http.publish_host,默认为http.host或者network.host http.max_content_length 设置请求内容的最大大小。默认100mb。如果设置的数值超过了Integer.MAX_VALUE,会被重置为100mb。 http.max_initial_line_length HTTP请求URL的最大长度,默认4kb http.max_header_size 请求头的最大大小,默认8kb http.compression 是否支持压缩(使用Accept-Encoding),默认false http.compression_level 定义使用的压缩级别,默认为6 http.cors.enabled 是否允许跨域请求。默认为false http.cors.allow-origin 定义允许哪些源请求。可以使用正则表达式,例如/https?:\\/\\/localhost(:[0-9]+)?/可设置支持本地HTTP和HTTPS请求。也可以设置为*,但是会存在安全隐患,因为任何来源都可访问Elasticsearch(以下简称为Es)实例。 http.cors.max-age 浏览器会发送一个“预检”的OPTIONS请求,来检查CORS设置。max-age定义应该缓存多长时间的结果。默认为1728000(20天) http.cors.allow-methods 定义了允许的请求方式,默认允许OPTIONS, HEAD, GET, POST, PUT, DELETE http.cors.allow-headers 定义了允许的请求头信息。默认允许X-Requested-With, Content-Type, Content-Length http.cors.allow-credentials 是否返回设置的Access-Control-Allow-Credentials头信息。默认为false http.detailed_errors.enabled 是否输出详细的错误信息和堆栈。默认为true http.pipelining 是否启用HTTP管道支持, 默认为true http.pipelining.max_events 在一个HTTP连接被关闭之前内存队列中允许的最大的事件数量,默认为10000 该模块也可使用一些公共的网络配置(见网络设置一节)。 禁用HTTPHTTP模块可以通过将http.enable设置为false来禁用。Es节点(和Java客户端)的内部通信使用transport接口,而非HTTP。这意味着我们可以将不接受直接REST请求的节点的HTTP禁用。比如,可以将数据节点的http禁用,创建非数据节点用来处理所有的REST请求。需要注意的是,不能向一个已经禁用了HTTP的节点直接发送任何REST请求。 Indices(索引模块)官网对应链接:Indices这一模块可以对所有索引的索引相关配置进行全局控制。相关的配置如下: Circuit breaker(断路器)官网对应链接:Circuit breaker该模块用来限制内存的使用,避免出现内存溢出。Es中包含多个Circuit breaker(断路器)用来阻止可能造成OutOfMemoryError异常的操作。此外,还有一个父级别断路器限制了可以使用的总内存。这些配置都可通过Cluster-update-settings动态更新。父断路器父级别的断路器可以通过indices.breaker.total.limit来设置,默认是JVM堆的70% Filed data断路器允许Es提前估计加载一个字段需要的内存,然后检查加载需要的fielddata会不会导致总的内存大小超过设置的百分比。这样可以预防在加载的过程中产生异常。默认的限制是60%的JVM堆,可以通过以下参数配置。indices.breaker.fielddata.limit:限制fielddata所能占用的最大内存,默认为JVM堆的60%indices.breaker.fielddata.overhead:一个常量。es将使用这个值乘以field实际的大小作Field估算值,默认为1.03请求断路器允许Es阻止使用内存超过限制的请求。indices.breaker.request.limit:默认JVM堆的40%indices.breaker.request.overhead:一个常量。所有请求的预估内存乘以这个常量就是最终的估计值。默认为1 Fielddata cache(字段数据缓存)限制内存中的数据缓存可以使用多大的堆内存field data缓存主要用于针对一个字段排序和做聚合计算。为了快速的访问某些值,Es会将这些字段值加载到内存中。需要注意的是将字段加载到内存很耗费资源,所以官方建议保证有足够的内存,并且保持所有字段被加载。field data内存的大小可通过indices.fielddata.cache.size配置项控制。要注意的是,当这个缓存不够用时,为了腾出空间给新的缓存,原来缓存的字段会被挤出来,这会导致系统性能下降。indices.fielddata.cache.size:field data缓存的最大值。可以设为节点堆空间的30%,也可设置为一个确定的值,比如12GB。默认无限大,生产环境要注意设置这个值。 注意:这些静态配置需要在集群的每一个节点上设置。 监控field data可以通过Node Stats API监控内存使用情况。 Node query cache(查询缓存)配置缓存查询结果可以使用多少堆内存查询缓存负责对查询结果进行缓存。每一个节点都有一个查询缓存,由所有分片共享。缓存采用LRU机制,将最近最少使用的内容替换成新数据。查询缓存只会缓存filter的内容。下面的配置需要在集群的每一个节点上配置。indices.queries.cache.size: 控制过滤结果缓存的大小。默认是10%。也可设置为一个确定的值,如512mb。 Indexing buffer(索引缓冲)控制分配多少内存给索引进程索引缓冲用来存储新索引的文档。当缓冲区满了时,缓冲的文档会被写到磁盘的一个段,划分到该节点的所有分片上。以下这些静态配置需要在集群的每一个节点上设置。 配置项 含义 indices.memory.index_buffer_size 设置索引缓冲区的大小。可设置一个百分比或者字节大小。默认为10%,意味着节点的10%的 indices.memory.min_index_buffer_size 如果indices.memory.index_buffer_size被设置成了一个百分比,本配置项可以用来代表缓冲区的最小值,默认为48mb。 indices.memory.max_index_buffer_size 如果indices.memory.index_buffer_size被设置成了一个百分比,本配置项可以用来代表缓冲区的最大值,默认无限大。 indices.memory.min_shard_index_buffer_size 为每个分片自己的索引缓冲区设置最小值。默认4mb Shard request cache(分片请求缓存)控制分片级别的查询缓存的行为。当一个搜索请求是针对一个索引或者多个索引的时候,每一个涉及到的分片都会在本地进行搜索,然后把结果返回到协调节点,在由协调节点把这些结果合并到一起。由于分片缓存模块会将本地的查询结果缓存,所以频率高的搜索请求会立刻返回结果。 注意:目前,请求缓存只缓存查询条件size=0的搜索,缓存的内容有hits.total, aggregations,suggestions,而不缓存原始的hits。并且通过now查询的结果也不缓存。 只缓存查询条件size=0的搜索原因如下(引用自ELKstack-guide-cn):ES对请求的处理过程是有不同类型的,默认的叫 query_then_fetch。在这种情况下,各数据节点处理检索请求后,返回的是只包含文档id和相关性分值的结果,这一段处理,叫做query阶段;汇聚到这份结果后,按照分值排序,得到一个全集群最终需要的文档id,再向对应节点发送一次文档获取请求,拿到文档内容,这一段处理,叫做 fetch阶段。两段都结束后才返回响应。此外,还有DFS_query_then_fetch类型,提高小数据量时的精确度;query_and_fetch类型,在有明确routing时可以省略一个数据来回;count类型,不关心文档内容只需要计数时省略 fetch阶段;scan类型,批量获取数据省略query阶段,在reindex时就是使用这种类型。回到query cache,这里这个query,就是处理过程中query阶段的意思。各个节点上的数据分片,会在处理完query阶段时,将得到的本分片有关该请求的计数值,缓存起来。根据上面的请求类型介绍,显然,只有当?search_type=count的时候,这个query cache才能起到作用。缓存失效在分片数据真正发生变化时刷新索引分片,缓存的结果会自动失效。刷新间隔越长缓存的数据越多。当缓存满了的时候,会将最少使用的数据删除。缓存可以通过clear-cache API手动设置过期。 1curl -XPOST 'localhost:9200/kimchy,elasticsearch/_cache/clear?request_cache=true' 默认启动缓存分片请求缓存默认是不启动的,但可以在创建新的索引时通过下面的方式启动: 123456curl -XPUT localhost:9200/my_index -d'{ \"settings\": { \"index.requests.cache.enable\": true }} 也可以通过update-settings API为就索引动态的启动和关闭缓存。 12curl -XPUT localhost:9200/my_index/_settings -d'{ \"index.requests.cache.enable\": true } 为每个请求启动缓存可以在请求时通过请求参数request_cache来为每个请求启动和关闭缓存。1234567891011curl 'localhost:9200/my_index/_search?request_cache=true' -d'{ \"size\": 0, \"aggs\": { \"popular_colors\": { \"terms\": { \"field\": \"colors\" } } }} 缓存设置缓存是在节点级别管理,默认JVM堆内存的1%。可在config/elasticsearch.yml文件中更改。1indices.requests.cache.size: 2% 也可以通过indices.requests.cache.expire设置缓存过期时间,但是没有必要,因为旧的结果会在索引刷新时自动失效。 检测缓存使用可通过indices-stats API检测索引中缓存的使用情况1curl 'localhost:9200/_stats/request_cache?pretty&human' 或通过nodes-stats API查看节点的使用情况。1curl 'localhost:9200/_nodes/stats/indices/request_cache?pretty&human' Indices Recovery(索引恢复)限制分片恢复进程的资源以下的配置用来管理恢复策略: 配置项 含义 indices.recovery.concurrent_streams 默认是3 indices.recovery.concurrent_small_file_streams 默认2 indices.recovery.file_chunk_size 默认512kb indices.recovery.translog_ops 默认1000 indices.recovery.translog_size 默认512kb indices.recovery.compress 默认true indices.recovery.max_bytes_per_sec 默认40mb 这些配置都可通过Cluster-update-settings动态更新。 TTL interval控制过期文档的删除设有ttl值的文档会在过期之后被删除。以下动态配置控制了删除文档的检查间隔,和批量删除的大小。indices.ttl.interval:删除进程启动间隔。默认60sindices.ttl.bulk_size:删除进程的数量。默认为1000 NetWork Settings(网络设置)Es默认只和localhost绑定。如果想要在多个服务器上启动生产环境的集群需要配置一些基本的网络设置。 注意:要小心使用网络配置,不要将不受保护的节点暴露在公共网络中。 常用的网络设置 配置项 含义 network.host 将节点绑定到设置的hostname或ip地址,并通知集群中的其他节点。该配置项接受IP地址,主机名,一些特定值(见下一个表)和由上面几项组成的数组。默认为_local_。 discovery.zen.ping.unicast.hosts 如果一个节点加入一个集群,需要知道集群中其他节点hostname和ip地址。本配置项为节点提供了其他节点的初始列表。默认["127.0.0.1","[::1]"] http.port 为HTTP请求绑定端口,可设置单个端口,也可设置一个范围。如果设定了一个范围,节点会绑定在范围中第一个可用的端口。默认9200-9300。 transport.tcp.port 节点间内部通信绑定的端口,可设置单个端口,也可设置一个范围。如果设定了一个范围,节点会绑定在范围中第一个可用的端口。默认9300-9400 network.host可使用的一些特殊值 特殊值 含义 _[networkInterface]_ 指定网卡的IP地址,如_en0_ _local_ 本机ip地址,如127.0.0.1 _site_ 任意一个site-local地址,如192.168.0.1 _global_ 任意一个globally-scoped地址,如8.8.8.8 IPv4 vs IPv6以上特殊值默认在IPV4和IPv6下均可使用,可以通过:ipv4和:ipv6标识符来做限制。例如,_en0:ipv4_。 高级网络设置常用网络设置一节中提到的network.host配置项只是一个为了同时设置band_host和publish_host的快捷设置。为了一下更复杂的情况,比如在一个代理服务器后运行节点,可能需要一些不同的配置。 配置项 含义 network.bind_host 设置节点绑定的ip地址,用来监听请求。默认network.host。 network.publish_host 配置其他节点与本节点通信的地址,默认为network.bind_host中的最佳地址。 以上配置都和network.host一样,可配置ip地址,hostname和某些特殊值。 高级TCP设置任何使用TCP的模块(如HTTP和Transport)共享以下的配置。 配置项 含义 network.tcp.no_delay 是否启用TCP no delay。默认为true network.tcp.keep_alive 是否启用TCP keep alive。默认为true network.tcp.reuse_address 一个地址是否可以重复使用 network.tcp.send_buffer_size TCP发送缓冲区的大小,默认不设置 network.tcp.receive_buffer_size TCP接收缓冲区的大小,默认不设置 Transport和HTTP协议Es会基于上面的配置暴露两种网络协议,它们也可以进一步独立配置。TCP transport 用于节点之间通信 具体可见Transport模块一节。HTTP 用于暴露基于JSON的http接口。具体可见HTTP模块一节。 都看到这了,听首歌再走吧。分享首适合抖腿的歌,写博客时听这种歌真是分分钟都要把键盘按穿。。。","raw":null,"content":null,"categories":[{"name":"elasticsearch","slug":"elasticsearch","permalink":"http://yemengying.com/categories/elasticsearch/"}],"tags":[{"name":"elasticsearch","slug":"elasticsearch","permalink":"http://yemengying.com/tags/elasticsearch/"}]},{"title":"疯狂动物城","slug":"zootopia","date":"2016-03-15T04:44:12.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2016/03/15/zootopia/","link":"","permalink":"http://yemengying.com/2016/03/15/zootopia/","excerpt":"真的是太喜欢疯狂动物城这部电影了,二刷之后还是想去电影院接着看,对这种毛茸茸的小动物真是一点抵抗力都没有,尤其是苏苏的狐尼克。放点网上搜来的图(权侵删),方便看到它们,哇咔咔咔咔。","keywords":null,"text":"真的是太喜欢疯狂动物城这部电影了,二刷之后还是想去电影院接着看,对这种毛茸茸的小动物真是一点抵抗力都没有,尤其是苏苏的狐尼克。放点网上搜来的图(权侵删),方便看到它们,哇咔咔咔咔。 蠢萌蠢萌,慢的出奇,戳中笑点的树懒flash 被圈饭的兔狐CP 最爱的狐尼克 勇敢的兔朱迪 致敬教父的Mr. Big 少女心的豹警官 大合照 从冰雪奇缘到超能陆战队再到疯狂动物城,不得不说迪斯尼的动画越来越好看了。好的动画片真的是老少皆宜的,让人在捧腹大笑的同时又能有所思考,不管是关于消除偏见,梦想还是成长~~~~。","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"},{"name":"电影","slug":"电影","permalink":"http://yemengying.com/tags/电影/"}]},{"title":"Java 8 --行为参数化(behavior parameterization)","slug":"Java-8-行为参数化-behavior-parameterization","date":"2016-02-20T11:33:21.000Z","updated":"2018-12-13T08:09:04.000Z","comments":true,"path":"2016/02/20/Java-8-行为参数化-behavior-parameterization/","link":"","permalink":"http://yemengying.com/2016/02/20/Java-8-行为参数化-behavior-parameterization/","excerpt":"根据书 << Java 8 in action >>第二章的一个例子整理。书中通过一个例子,讲述了如何利用behavior parameterization来应对不停变化的需求。想想之前自己写的工具类,真是大写的Low啊。。。。 题外话:要时刻谨记 Later equals never,Later equals never,Later equals never!!!!!!!","keywords":null,"text":"根据书 << Java 8 in action >>第二章的一个例子整理。书中通过一个例子,讲述了如何利用behavior parameterization来应对不停变化的需求。想想之前自己写的工具类,真是大写的Low啊。。。。 题外话:要时刻谨记 Later equals never,Later equals never,Later equals never!!!!!!! 行为参数化在软件开发中,一个众所周知的问题就是无论你做什么,用户的需求总会改变。举个栗子,假设要做一个帮助农场主理解库存的应用。一开始,农场主可能想有一个在所有库存中找出所有绿色苹果的功能。但是第二天他可能会告诉你他还想要找到所有重量大于150g的苹果。两天后,他可能又提出找到所有绿色的并且重量大于150g的苹果。在面对这些相似的新功能时,我们都想尽可能的减少开发量。behavior parameterization是用来处理频繁更改的需求的一种软件开发模式,可以将一段代码块当做参数传给另一个方法,之后执行。这样做的好处是,方法的行为可以由传入的代码块控制。 例子下面通过农场应用来看看面对不断改变的需求如何将代码写的更灵活。先实现第一个功能:从一个list中过滤出所有的绿色苹果,听起来是不是很简单。 版本1 : 过滤出绿色苹果最开始想到的解决办法可能长下面的样子: 12345678910public static List<Apple> filterGreenApples(List<Apple> inventory) { List<Apple> result = new ArrayList<>(); // An accumulator list for apples for (Apple apple : inventory) { if ( \"green\".equals(apple.getColor())) { // Select only green apples result.add(apple); } } return result; } 上面的方法可以过滤出绿色的苹果,但是如果农场主还想知道红色的苹果呢?一个很幼稚的做法是将上面的方法复制一遍,命名为filterRedApples,更改一下if语句。但如果还想知道黄色的呢?一个好的做法是:试着抽象。 版本2 : 将颜色作为参数可以在方法中加入颜色作为参数,使代码更灵活。 123456789public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) { List<Apple> result = new ArrayList<>(); for (Apple apple: inventory) { if (apple.getColor().equals(color)) { result.add(apple); } } return result;} 这样就可以灵活的根据颜色来筛选。这时农场主又提出了根据重量筛选,于是参照上面根据颜色筛选的方法又新增了一个根据重量筛选的方法,如下: 123456789public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) { List<Apple> result = new ArrayList<>(); for (Apple apple: inventory) { if (apple.getWeight()>weight) { result.add(apple); } } return result;} 这是一个解决办法,但考虑到苹果有许多其它特性,如果针对每一特性的筛选都复制一个方法,违背了DRY(don’t repeat yourself)原则.我们可以将颜色和重量结合到一个方法,并通过一个标记来指明想要进行过滤的是颜色还是重量(这样做其实很不好,之后会解释)。 版本3 : 在一个方法中过滤想要过滤的属性下面在一个方法中根据flag值的不同过滤不同的属性(这样做法很不好)。 12345678910public static List<Apple> filterApples(List<Apple> inventory,String color, int weight, boolean flag) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if ((flag&&apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight)) { result.add(apple); } } return result; } 上面的代码很丑陋而且也没有解决需求变化的问题,比如如果农场主还想要根据大小,产地,形状来筛选就不适用了。 版本4 : 根据抽象约束过滤一个更好的解决办法是将过滤的标准抽象出来,我们先定义一个接口作为抽象的选择标准.123public interface ApplePredicate { boolean test(Apple apple);} 接下来就可以定义多个ApplePredicate接口的实现类来代表不同的过滤标准。如下图: 12345678910111213//select only heavy applepublic class AppleHeavyWeightPredicate implements ApplePredicate { public boolean test(Apple apple) { return apple.getWeight() > 150; }}//select only green applepublic class AppleGreenColorPredicate implements ApplePredicate { public boolean test(Apple apple) { return \"green\".equals(apple.getColor); }} 上面每一个实现了ApplePredicate接口的类都代表了一种筛选策略。在此基础上,我们可以将筛选方法修改成下面的样子,将ApplePredicate作为参数传入。 123456789public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (p.test(apple)) { result.add(apple); } } return result;} 现在的筛选方法比第一个版本灵活多了,如果想改变筛选标准,只需创建不同的ApplePredicate对象,并传入filterApples方法即可。例如新增了选出红色并且重量大于150g的苹果的需求,我们可以创建一个实现ApplePredicate接口的类即可,代码如下:1234567public class AppleRedAndHeavyPredicate implements ApplePredicate { public boolean test(Apple apple) { return \"red\".equals(apple.getColor()) && apple.getWeight() > 150; }}List<Apple> redAndHeavyApples = filter(inventory, new AppleRedAndHeavyPredicate()); 但是上面的实现有一个缺点,就是太啰嗦了,每新增一个筛选标准都要新增一个类。下面来继续优化一下。 版本5 : 使用匿名类匿名类是没有名字的类,使用匿名类可以创建一个临时的实现。下面的代码展示了如何利用匿名类创建实现了ApplePredicate的对象。12345List<Apple> redApples = filterApples(inventory, new ApplePredicate() { public boolean test(Apple apple) { return \"red\".equals(apple.getColor()); }}); 但是尽管匿名类解决了为一个接口声明多个实现类的问题,使用匿名类还不足够好。使用匿名类代码看起来有些笨重,可读性差,而且有一些开发者对匿名类感到困惑。下面我们使用Java 8引入的lambda表达式使代码看起来更加简洁一点。 版本6 : 使用lambda表达式我们可以使用lambda表达式简化代码. 1List<Apple> result = filterApples(inventory, (Apple apple) -> \"red\".equals(apple.getColor())); 最终版 : 使用泛型,抽象列表的类型我们可以继续做一些抽象。目前,filterApples方法只可以过滤元素类型为Apple的List。我们可以将列表的类型抽象出来,使得我们的过滤方法变得更加通用,代码如下:12345678910111213public interface Predicate<T> { boolean test(T t);}public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> result = new ArrayList<>(); for (T e: list) { if (p.test(e)) { result.add(e); } } return result;} 经评论区提醒,在加个stream版本的(果然清爽不少↖(^ω^)↗): 123456public static <T> List<T> filterWithStream(List<T> list, Predicate<T> p) { List<T> result; result = list.stream().filter( t -> p.test(t)).collect(Collectors.toList()); return result;} 这样就可以对多种类型的list进行过滤了: 123List<Apple> redApples = filter(inventory, (Apple apple) -> \"red\".equals(apple.getColor()));List<Integer> evenNumber = filter(numbers, (Integer i) -> i%2 == 0); 终于over了,拖延癌果真已经到了晚期。。。。。","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"}]},{"title":"[译]Java 8中HashMap和LinkedHashMap如何解决冲突","slug":"译-Java中HashMap和LinkedHashMap如何解决冲突","date":"2016-02-03T08:23:39.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2016/02/03/译-Java中HashMap和LinkedHashMap如何解决冲突/","link":"","permalink":"http://yemengying.com/2016/02/03/译-Java中HashMap和LinkedHashMap如何解决冲突/","excerpt":"原文来自一个java大牛的博客 原文地址http://javarevisited.blogspot.jp/2016/01/how-does-java-hashmap-or-linkedhahsmap-handles.html 博客讲解了Java 8中HashMap和LinkedHashMap是如何解决冲突的。","keywords":null,"text":"原文来自一个java大牛的博客 原文地址http://javarevisited.blogspot.jp/2016/01/how-does-java-hashmap-or-linkedhahsmap-handles.html 博客讲解了Java 8中HashMap和LinkedHashMap是如何解决冲突的。 在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素。在最坏的情况下,这种方式会将HashMap的get方法的性能从O(1)降低到O(n)。为了解决在频繁冲突时hashmap性能降低的问题,Java 8中使用平衡树来替代链表存储冲突的元素。这意味着我们可以将最坏情况下的性能从O(n)提高到O(logn)。在Java 8中使用常量TREEIFY_THRESHOLD来控制是否切换到平衡树来存储。目前,这个常量值是8,这意味着当有超过8个元素的索引一样时,HashMap会使用树来存储它们。这一改变是为了继续优化常用类。大家可能还记得在Java 7中为了优化常用类对ArrayList和HashMap采用了延迟加载的机制,在有元素加入之前不会分配内存,这会减少空的链表和HashMap占用的内存。这一动态的特性使得HashMap一开始使用链表,并在冲突的元素数量超过指定值时用平衡二叉树替换链表。不过这一特性在所有基于hash table的类中并没有,例如Hashtable和WeakHashMap。目前,只有ConcurrentHashMap,LinkedHashMap和HashMap会在频繁冲突的情况下使用平衡树。 什么时候会产生冲突HashMap中调用hashCode()方法来计算hashCode。由于在Java中两个不同的对象可能有一样的hashCode,所以不同的键可能有一样hashCode,从而导致冲突的产生。 总结 HashMap在处理冲突时使用链表存储相同索引的元素。 从Java 8开始,HashMap,ConcurrentHashMap和LinkedHashMap在处理频繁冲突时将使用平衡树来代替链表,当同一hash桶中的元素数量超过特定的值便会由链表切换到平衡树,这会将get()方法的性能从O(n)提高到O(logn)。 当从链表切换到平衡树时,HashMap迭代的顺序将会改变。不过这并不会造成什么问题,因为HashMap并没有对迭代的顺序提供任何保证。 从Java 1中就存在的Hashtable类为了保证迭代顺序不变,即便在频繁冲突的情况下也不会使用平衡树。这一决定是为了不破坏某些较老的需要依赖于Hashtable迭代顺序的Java应用。 除了Hashtable之外,WeakHashMap和IdentityHashMap也不会在频繁冲突的情况下使用平衡树。 使用HashMap之所以会产生冲突是因为使用了键对象的hashCode()方法,而equals()和hashCode()方法不保证不同对象的hashCode是不同的。需要记住的是,相同对象的hashCode一定是相同的,但相同的hashCode不一定是相同的对象。 在HashTable和HashMap中,冲突的产生是由于不同对象的hashCode()方法返回了一样的值。 以上就是Java中HashMap如何处理冲突。这种方法被称为链地址法,因为使用链表存储同一桶内的元素。通常情况HashMap,HashSet,LinkedHashSet,LinkedHashMap,ConcurrentHashMap,HashTable,IdentityHashMap和WeakHashMap均采用这种方法处理冲突。 从JDK 8开始,HashMap,LinkedHashMap和ConcurrentHashMap为了提升性能,在频繁冲突的时候使用平衡树来替代链表。因为HashSet内部使用了HashMap,LinkedHashSet内部使用了LinkedHashMap,所以他们的性能也会得到提升。 相关阅读Data Structure and Algorithm in JavaJava Performance The Definitive Guide","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"},{"name":"hashmap","slug":"java/hashmap","permalink":"http://yemengying.com/categories/java/hashmap/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"最近看的书","slug":"最近看的书","date":"2016-01-19T06:34:13.000Z","updated":"2018-12-13T08:13:34.000Z","comments":true,"path":"2016/01/19/最近看的书/","link":"","permalink":"http://yemengying.com/2016/01/19/最近看的书/","excerpt":"自从去年8月份之后真是变成了个爱看书のgirl,算是*%^&#$带来的唯一好处吧,是时候整理一发啦。不过,要按什么分类呢,技术和非技术,看完和没看完,好看和不好看还是外文和中文?好乱。。。。 算了 还是不分了随便列吧。。。。","keywords":null,"text":"自从去年8月份之后真是变成了个爱看书のgirl,算是*%^&#$带来的唯一好处吧,是时候整理一发啦。不过,要按什么分类呢,技术和非技术,看完和没看完,好看和不好看还是外文和中文?好乱。。。。 算了 还是不分了随便列吧。。。。 Flipped(怦然心动)一本讲两个小学生初恋的英文小说,适合放松的时候看看。虽然已经看过了电影,再看小说还是觉得很美好,成功唤醒了我那颗沉睡已久的少女心。女主性格超级可爱,男主虽然一开始挺烦人的,不过后期的转变还是不错的,撩妹技能满分,还有女的爸爸和男主的爷爷也都是很有哲理的人。里面的句子都挺简单的,用来学英语也不错。 淘宝技术这十年很多地方都有推荐这本书,就买了,不过看完倒也没觉得多好看,里面关于技术的地方讲的很浅,所以指望着学到什么技术还是别看了,当故事书看还是不错的。现在书的内容已经记不太清了,就记得两句话,好的架构都是一步步演化来的和再牛B的人也都有一段苦B的经历。 清醒思考的艺术左耳朵耗子来做技术分享时推荐的,里面是一个个的小故事,通过讲故事的方式来告诉读者思考时容易犯的错。读起来挺轻松的,可以睡前看一个小故事。不过吧。。。。道理都懂,想做到清醒思考还是挺难的。 灿烂千阳(A THOUSAND SPLENDID SUNS)是《追风筝的人》作者的另一本小说,没看过追风筝的人,不过名气好像挺大的。灿烂千阳主要讲了两个阿富汗少女悲惨的一生,虽然莱拉结局还算不错,不过整体看还是挺惨的,都是战争害的。觉得整本书最感人的地方不是莱拉和玛利亚姆互相帮助和莱拉和塔里克的爱情,而是书的结尾莱拉发现了玛利亚姆父亲留给她的东西,戳中泪点啊。。。。。看完了之后觉得现在生活真心很幸福的啊。 深入理解Java虚拟机这本。。。。大概看了两三章,实在看不下去。。。不是书写的不好,是我技术渣看不太懂。。。还是先放放吧 从0到1 利用周末看完了,感觉作者说的都对,但对现在的我帮助不大,可能经历还不够吧。不过喜欢做开发也正是因为从0到1的创造一个产品的过程很interesting。 Linux Bible 9th Edition超实用的技术书,不过由于是英文的还是技术书,看的比较慢,才看到第六章,基本一章一个主题已经从使用shell看到管理进程了。真是每一章都有很大收获,比如以前查看进程就知道用ps aux,并不知道每个命令选项的具体意思。走过路过不要错过,linux学的好的也可以看看,查漏补缺啊。争取以一周一章的速度把它看完。有要电子书的可以留邮箱 哈哈。 Spring in Action 4th edition虽然一直在用spring,但是貌似一直没有系统完整的看过一本关于spring的书,正好从geekbook上爬下了这本就看看吧,查漏补缺,以前注入bean,一直用xml和利用注解自动注入的方式,借这个机会了解下利用JavaConfig是如何实现依赖注入的。令我吃惊的是,作者第二推荐的居然是JavaConfig,一直以为Xml的方式应该排第二呢。。。刚看到第三章,慢慢看吧。。。有要电子书的可以留邮箱。 The Martian(火星救援)看完电影再来看书,觉得书比电影好一点,当然电影也不差。男主实在令人折服,尤其是他遇到困难只想着怎么解决困难的精神。如果把我留在火星上,估计得死好几百回。。。。 偷影子的人一本治愈系的法国小说,喜欢封面的设计,内容挺平淡的,感觉前半部分比后面要好看。","raw":null,"content":null,"categories":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/categories/随笔/"}],"tags":[{"name":"随笔","slug":"随笔","permalink":"http://yemengying.com/tags/随笔/"},{"name":"书单","slug":"书单","permalink":"http://yemengying.com/tags/书单/"}]},{"title":"拥有自己的图书小金库","slug":"拥有自己的图书小金库","date":"2016-01-09T06:15:46.000Z","updated":"2018-12-14T09:16:30.000Z","comments":true,"path":"2016/01/09/拥有自己的图书小金库/","link":"","permalink":"http://yemengying.com/2016/01/09/拥有自己的图书小金库/","excerpt":"在找matering elasticsearch second edition这本书的时候,在许多可以免费下电子书的网站都没有找到,所以就买了Geekbook一个月的会员,为了不浪费充会员的钱,决定撸个脚本把全站的书都下下来,并用django搭个管理后台。和洪菊,卢神利用空闲时间忙活了快两周,终于有了自己的图书小金库,5000+的优质英文原版电子书,哇咔咔咔咔,这辈子的书都有了,放了一部分到github上。geekbook千万不要怪我们啊,谁让你不封我们的~~。项目地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-GeekBook-3d83e8f\", \"giraffe0813\", \"GeekBook\", \"3d83e8f\", false); 部分电子书地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-Geek-Organization-geek-programming-books-643e0bd\", \"Geek-Organization\", \"geek-programming-books\", \"643e0bd\", false);","keywords":null,"text":"在找matering elasticsearch second edition这本书的时候,在许多可以免费下电子书的网站都没有找到,所以就买了Geekbook一个月的会员,为了不浪费充会员的钱,决定撸个脚本把全站的书都下下来,并用django搭个管理后台。和洪菊,卢神利用空闲时间忙活了快两周,终于有了自己的图书小金库,5000+的优质英文原版电子书,哇咔咔咔咔,这辈子的书都有了,放了一部分到github上。geekbook千万不要怪我们啊,谁让你不封我们的~~。项目地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-GeekBook-3d83e8f\", \"giraffe0813\", \"GeekBook\", \"3d83e8f\", false); 部分电子书地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-Geek-Organization-geek-programming-books-643e0bd\", \"Geek-Organization\", \"geek-programming-books\", \"643e0bd\", false); 遇到的坑记录下遇到的坑 不然就忘了😂 只带cookie 无法下载本来以为只要带着登录之后的cookie请求下载的地址(eg:https://www.geekbooks.me/books/56/48/c1955c13518f994167b11f7b7279/amazon_ec2_cookbook.pdf) 就可以下载了,不过发现下下来的并不是书,而是网站上书的详情页的html。受到博客Course抓站小结 的启发,又研究了一下请求的header。发现除了要带cookie之外,还要带有user_agent和Referer,refer是表示从哪个页面访问当前链接。所以修改了下代码,在请求头中加入user-agent和referer之后,问题就解决了,部分代码如下1234567cookie = cookielib.MozillaCookieJar()# get cookie from filecookie.load('../data/cookie4geek.data', ignore_discard=True, ignore_expires=True)handler = urllib2.HTTPCookieProcessor(cookie)opener = urllib2.build_opener(handler)# add headeropener.addheaders = [('User-agent', 'Mozilla/5.0'), (\"Referer\", url)] 下载文件的时候不显示进度可以下载文件之后,希望可以在下载文件的时候显示下载进度,让我们知道他在工作。。。。直接在stackoverflow上抄了段代码,这个问题也愉快的解决了。12345678910111213141516171819202122u = opener.open(\"https://www.geekbooks.me\" + url)print \"Preparing to download...\"# f with directoryif os.path.exists(conf_books_dir + category + \"/\" + file_name) and detect_book( (conf_books_dir + category + \"/\" + file_name)): continuef = open(conf_books_dir + category + \"/\" + file_name, 'wb')meta = u.info()file_size = int(meta.getheaders(\"Content-Length\")[0])print \"Downloading: %s Bytes: %s\" % (file_name, file_size)file_size_dl = 0block_sz = 8192while True: buffer = u.read(block_sz) if not buffer: break file_size_dl += len(buffer) f.write(buffer) status = r\"%10d [%3.2f%%]\" % (file_size_dl, file_size_dl * 100. / file_size) status = status + chr(8) * (len(status) + 1) print status,f.close() 一本一本下载速度太慢这个只能果断上多线程了。。。123456789101112131415161718def download_work(): f = open(\"../data/detailurl.txt\", \"r\") books = [] destDir = \"\" tmp = \"\" for line in f: if not (line.strip()).startswith(\"/\"): tmp += \"/\" + line.strip() destDir = tmp else: # desDir book = Book(destDir, line.strip()) books.append(book) tmp = \"\" pool = threadpool.ThreadPool(conf_thread_count) reqs = threadpool.makeRequests(lambda book: book.download(), books) [pool.putRequest(req) for req in reqs] pool.wait() 无法展示下载书的具体信息除了将书下载下来,还想将书的一些基本信息保存下来,比如:作者,简洁,出版年份,封面,标签等等。。最好可以根据作者,题目进行搜索。本来想自己写个网站出来,但是又没有时间。还好之前学叔推荐过django,用django搭个管理后台简直不要太方便好么,配置nginx,supervisor的时间都比写代码的时间长👅,还可以很方便定义想搜索的字段和想展示的字段。样式是丑了一丢丢,但自己用也无所谓。 成品 Summary依靠空闲时间可以做点想做的事,也是蛮好的~~。用python写脚本真的很方便,基本不用自己造轮子,用它自带的模块就可以完成了。用django搭管理后台也是快的不要不要的。一周多的时间换4000+的书很值啊,但是。。。。服务器+存储平均一天的成本就要10块。。。是不是得想个法子,看能不能用这些电子书挣点钱啊。。。。想到这。。。突然没那么开心了。。。","raw":null,"content":null,"categories":[{"name":"python","slug":"python","permalink":"http://yemengying.com/categories/python/"}],"tags":[{"name":"python","slug":"python","permalink":"http://yemengying.com/tags/python/"},{"name":"爬虫","slug":"爬虫","permalink":"http://yemengying.com/tags/爬虫/"},{"name":"django","slug":"django","permalink":"http://yemengying.com/tags/django/"}]},{"title":"读书笔记-Linux Bible 9th Edition之进程大法好","slug":"读书笔记-Linux-Bible-9th-Edition之进程大法好","date":"2015-12-24T09:56:03.000Z","updated":"2018-12-14T09:17:14.000Z","comments":true,"path":"2015/12/24/读书笔记-Linux-Bible-9th-Edition之进程大法好/","link":"","permalink":"http://yemengying.com/2015/12/24/读书笔记-Linux-Bible-9th-Edition之进程大法好/","excerpt":"根据Linux Bible第九版第六章整理的读书笔记,记录linux系统下如何管理进程 Linux是一个支持多用户和多任务的操作系统,多任务是指可以同时运行多个程序,而每个运行程序的一个实例被称作一个进程。Linux系统提供了可以让我们列出正运行进程,杀死进程,监听系统使用情况的工具。 相关博客:Linux Bible 9th Edition之使用shellLinux Bible 9th Edition之玩转文本文件Linux Bible 9th Edition之文件系统","keywords":null,"text":"根据Linux Bible第九版第六章整理的读书笔记,记录linux系统下如何管理进程 Linux是一个支持多用户和多任务的操作系统,多任务是指可以同时运行多个程序,而每个运行程序的一个实例被称作一个进程。Linux系统提供了可以让我们列出正运行进程,杀死进程,监听系统使用情况的工具。 相关博客:Linux Bible 9th Edition之使用shellLinux Bible 9th Edition之玩转文本文件Linux Bible 9th Edition之文件系统 理解进程一个进程是一个命令正在运行的实例。比如,系统中有一个vi命令,如果vi命令正在被15个不同的用户运行,那么就对应了15个不同的运行进程。一个进程在系统中通过进程Id(process ID)来标识,在当前系统中进程Id是独一无二的。换句话说,如果一个进程正在运行,那么其他进程都不能使用它的进程Id的数字作为自己的进程Id。但如果这个进程已经结束,那么他的进程Id可以被重新使用当做其他进程的进程Id。除了进程Id,进程还有一些其他属性。每个运行的进程都会关联一个指定的用户账号和用户组,这个账号决定了进程可以访问哪些系统资源。所以root用户运行的进程能比普通用户的进程访问更多的文件。对于一个Linux的管理员来说,管理进程的能力至关重要。因为有时,一些运行的进程会严重影响系统的性能,这一章会讲述如果根据内存和CPU的使用情况,定位和处理这些进程。 列出进程如果使用命令行列出当前系统运行的进程,ps命令是最老也是最常用的命令,而top命令不仅可以列出进程还可以更改进程的状态。如果使用的是GNOME,可以使用gnome-system-monitor来通过图形界面管理进程。 通过ps命令列出进程最常用的查看正在运行进程的工具就是ps命令,通过ps命令,我们可以查看正在运行的进程,它们使用的资源以及运行它们的用户。下面是ps命令的一个例子: 1234$ ps uUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND jake 2147 0.0 0.7 1836 1020 tty1 S+ 14:50 0:00 -bash jake 2310 0.0 0.7 2592 912 tty1 R+ 18:22 0:00 ps u 在上面的命令中,使用u选项会显示用户名和一些其他信息,比如进程是什么时候开始运行的,进程占用的内存和Cpu,命令运行的位置(TTY)等。上面的第一个进程说明了jake用户在登录之后打开了一个bash shell。第二进程显示了jake用户正在运行ps u命令。终端设备tty1正在被登录会话使用着。STAT这一列代表了进程的状态,R代表进程正在运行,S代表进程处于休眠状态。 STAT除了R和S之外还可以有别的值,D代表不可中断,R运行,S中断/休眠,T停止,Z僵死。如果后面有+号,代表在前台运行的进程。 USER列显示了运行这个进程的用户。PID列是进程的进程Id,每个进程都有一个独一无二的进程Id,在需要杀死一个进程或者为进程发送信号时使用。%CPU和%MEN两列显示了进程占用的CPU百分比和内存百分比。VSZ(virtual set size)展示了虚拟内存占用大小(单位:kb/kilobytes),RSS(reside set size)展示了实际内存占用大小(单位:kb/kilobytes)。VSZ和RSS的值可能不一样,因为VSZ是分配给进程的内存大小,而RSS是进程实际使用的内存大小,代表了不可交换的物理内存。START代表了进程启动的时间,TIME执行累计时间(如果占用cpu时间非常短不到一秒,会显示 0:00)。在Linux中,有些运行的进程是与终端无关的,这些进程通常在系统启动时开始运行,并且会持续运行,直到系统关闭。可以使用x选项查看与终端无关的进程。 分页查看与当前用户有关的所有运行进程 1$ ps ux | less 分页查看所有用户的运行的进程 1$ ps aux | less 管道符(“|”)会将第一个命令的输出当做第二个命令的输入,上面的例子中ps命令的输出会当做less命令的输入,这样就可以分页查看信息了。按空格键换页,按q退出。我们还可以自定义ps命令展示的信息,并按其中一列排序。使用-o选项,可以通过关键字指定想要展示的列。下面的例子就是指定ps展示进程Id(pid),用户名(username),用户Id(uid),用户组(group),组Id(gid),分配的虚拟内存(vsz),实际使用内存(rss),运行的命令(comm),默认情况下按进程Id排序。123$ ps -eo pid,user,uid,group,gid,vsz,rss,comm | less PID USER GROUP GID VSZ RSS COMMAND 1 root root 0 19324 1320 init 2 root root 0 0 0 kthreadd 如果想要按其他列排序可以使用sort=选项。例如,想查看那个进程占用了最多的内存,可以按rss排列,会按rss从低到高展示进程,如果想从高到底可以在前面加连字符。下面是例子:12345$ ps -eo pid,user,group,gid,vsz,rss,comm --sort=-rss | lessPID USER GROUP GID VSZ RSS COMMAND12005 cnegus cnegus 13597 1271008 522192 firefox5412 cnegus cnegus 13597 949584 157268 thunderbird-bin25870 cnegus cnegus 13597 1332492 112952 swriter.bin 通过top命令列出进程,修改进程使用top命令,会默认按占用CPU的时间展示进程,也可以按其他指标排序。如果定位到了一个异常的进程,可以使用top终止(kill)进程或修改其优先级(reprioritize)。如果想要能够终止所有的进程或更改所有进程优先级需要使用root user来运行top命令。如果只是想要展示进程,或者更改自己的进程,那么可以使用普通用户的身份。下图是使用top的例子,最上面列出了系统的基本信息,下面是每个运行进程的信息。从最上面的输出信息你可以知道系统启动了多长时间,目前用多少用户登入,在过去的1,5,10分钟内分别运行了多少条命令。除此之外还包括了当前有多少进程在运行,CPU占用多少,用多少可用的Swap和RAM。紧接着列出了每个进程的基本信息,按进程占用cpu百分比排列,默认情况下所有信息5秒钟刷新一次。 下面列出了在用top展示、修改进程时可用的一些操作: 按h看帮助选项,按任意键返回 按M按占用内存排列,按P返回按CPU排列 按数字1可以在所有cpu中切换,前提是你的系统中有多于一个cpu 按R将输出倒序排列 按u并输入一个用户名,展示指定用户的进程 下面是运行top命令时,如何终止进程或修改其优先级: 修改优先级:记下想要修改的进程的PID,之后按r输入线程对应的PID,再输入想要调整的值(-19 到 20)。 终止进程: 记下想要修改的进程的PID,之后按k输入线程对应的PID,之后输入15(终止)或9(强迫终止)。 题外话最近负能量太多了 赶紧听首我大少时的歌压压。","raw":null,"content":null,"categories":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/tags/linux/"},{"name":"读书笔记","slug":"读书笔记","permalink":"http://yemengying.com/tags/读书笔记/"}]},{"title":"读书笔记-Linux Bible 9th Edition之玩转文本文件","slug":"读书笔记-Linux-Bible-9th-Edition之玩转文本文件","date":"2015-11-30T07:04:46.000Z","updated":"2018-12-14T09:18:57.000Z","comments":true,"path":"2015/11/30/读书笔记-Linux-Bible-9th-Edition之玩转文本文件/","link":"","permalink":"http://yemengying.com/2015/11/30/读书笔记-Linux-Bible-9th-Edition之玩转文本文件/","excerpt":"根据书的第5章整理一下关于操作文本文件的常用命令。在Linux系统中许多信息都是在文本文件中管理的,所以熟练掌握对文本文件的更改,查找是很重要的。这一点,在出bug查找服务器日志时真是深有体会啊!😼😼 相关博客: Linux Bible 9th Edition之使用shell Linux Bible 9th Edition之文件系统 Linux Bible 9th Edition之进程大法好","keywords":null,"text":"根据书的第5章整理一下关于操作文本文件的常用命令。在Linux系统中许多信息都是在文本文件中管理的,所以熟练掌握对文本文件的更改,查找是很重要的。这一点,在出bug查找服务器日志时真是深有体会啊!😼😼 相关博客: Linux Bible 9th Edition之使用shell Linux Bible 9th Edition之文件系统 Linux Bible 9th Edition之进程大法好 使用vim和vi编辑文件 刚接触vi编辑器可能会觉得有点难,不过当你熟悉了之后可以只用键盘就能快速高效的编辑文件,无需使用鼠标或功能键。(如果觉得vi不适合你,可以选择其它的文本编辑器,比如:nano,gedit,jed,kate,kedit,mcedit,nedit…等等) 从最常见的打开文件的开始了解vi打开文件: 1$ vi test 如果这是一个空的文件,你会看到类似下图的东东。最上面闪烁的东东代表了光标的当前位置,最下面的一行显示了关于文件的一些信息,中间的”~”符号代表没有内容。当你看到这个界面可能会感觉不知所措,因为没有任何菜单,提示和图标来告诉你该做什么。更恐怖的是,你不能直接输入,否则会听见”嘟嘟”的声音。 添加文件内容不要怕,首先,需要了解两种主要的模式:命令模式(command)和编辑模式(input)。vi编辑器以命令模式启动,在添加或改变文本内容之前需输入命令来告诉vi你想要做什么(大小写敏感)。输入下面的命令就可以进入编辑模式,当编辑结束后,按Esc键就可以回到命令模式。 命令 作用 a 可以在光标右侧开始插入文本 A 可以在当前行的最后开始插入文本 i 可以在光标左侧开始插入文本 I 可以在当前行的最前面开始插入文本 o 在当前行下面 插入新的一行 O 在当前行上面 插入新的一行 当进入编辑模式时,屏幕下方会出现– INSERT –;编辑结束后,按Esc键就可以回到命令模式。不过如果输入了”:”符号,需要按两下Esc键 在文本中移动可以使用方向键可以在文本中移动光标,但还有一些小技巧可以让我们更方便的在文本中移动 命令 作用 w 光标移到下一个单词的开头(单词以spaces,tabs,标点界定) W 光标移到下一个单词的开头(单词以spaces,tabs界定) b 光标移到前一个单词的开头(单词以spaces,tabs,标点界定) B 光标移到前一个单词的开头(单词以spaces,tabs界定) 0(zero) 光标移到当前行的最前面 $ 光标移到当前行的最后 H 光标移到屏幕的左上角 M 移到中间行第一个字符 L 光标移到屏幕的左下角 删除,复制,更改文本了解了如何添加文本和移动光标是远远不够的,还需要知道如何删除,复制和更改文本。命令x,d,y,c等可以帮助我们删除和修改文本,这些命令也可以和移动光标的命令(上一个表格中提到的)或者数字配合使用来告诉编辑器确切的操作是什么。 命令 作用 x 删除光标所在位置的字符 X 删除光标所在位置的前一个字符 d? 删除一些文本 c? 更改一些文本 y? 复制一些文本 ?代表这些命令要和移动光标的命令配合着使用,下面是一些例子 命令 作用 dw 删除当前光标位置的后一个单词 db 删除当前光标位置的前一个单词 dd 删除当前一整行 c$ 更改(实际上是擦除)从当前位置到当前行最后的内容,并进入编辑模式 c0 更改(实际上是擦除)从当前位置到当前行最前面的内容,并进入编辑模式 yy 将当前行复制到buffer中 上面这些命令也可以和数字配合使用,下面是栗子🌰 命令 作用 3dd 删除当前行往下的三行 3dw 删除接下来的三个单词 5cl 删除接下来的5个字符,并进入编辑模式 粘贴可以使用命令p和P,将复制到buffer中的内容粘贴到文本中。p是将缓存区的内容粘贴到当前光标所在位置的下方,P是将缓存区的内容粘贴到当前光标所在位置的上方 在文件中跳跃 命令 作用 ctrl+f 向下一页 ctrl+b 向上一页 ctrl+d 向下一页半 ctrl+u 向上一页半 G 跳到最后一行 1G 跳到第一行 35G 跳到第35行 查找文本查找文本时,”/“和”?”分别对应向前和向后查找,也可以使用一些通配符,比如/The.*foot,?[pP]rint,查找之后可以按n和N来重复查找和按相反方向查找 命令 作用 /hello 向前查找单词”hello” ?goodbye 向后查找单词”goodbye” 使用ex模式当输入冒号,并且光标在最下方时就进入了ex模式,下面是一些在ex模式下查找,修改文本的栗子🌰 命令 作用 :g/local 查找local 并打印 :s/local/r 将local第一次出现的位置替换为r 退出vi以下的命令用来保存和退出文件 命令 作用 ZZ 保存修改 并退出vi :w 保存修改 但不退出vi :wq 与ZZ命令一样 :q 退出文件,该命令只有在没有未保存的修改下才起效 :q! 退出文件 不保存对文件的修改 查找文件 为了帮助用户更有效的查找他们的文件,linux系统提供了locate,find,grep三个命令,依次来看看他们的作用。 使用localte命令查找文件 大多数linux系统中,updatedb命令会每天执行一次,将系统中文件的名字存到数据库中。通过locate命令,我们可以查找存在在数据库中的文件的位置。相较于find命令,locate命令效率更高,因为它搜索数据库而不是整个文件系统。不过locate命令也有它的缺点,它并不能找到所有存放在系统的文件,因为并不是所有的文件都会存储于数据库中,/etc/updatedb.conf文件决定了哪些文件将存在于数据库中。另外,普通用户无法通过数据库查找那些他们在文件系统中没有权利查看的文件,比如,普通用户无法在/root目录下执行ls命令,那么他们也无法通过locate查找这个目录下的文件。如果用locate命令查找一个字符串,那么这个字符串可能出现在返回文件的路径的任意位置。举个栗子,查找passwd,结果可能为/etc/passwd,/usr/bin/passwd和其它路径包含passwd的文件。还需要注意的是,如果创建一个文件之后,希望立刻通过locate查找它,最好执行命令updatedb更新下数据库。 123456789101112$ locate .bashrc/etc/skel/.bashrc /home/cnegus/.bashrc# locate ./bashrc (身份不同 查找结果不同)/etc/skel/.bashrc /home/bill/.bashrc /home/joe/.bashrc /root/.bashrc$ locate -i muttrc(-i 忽略大小写) /etc/Muttrc /etc/Muttrc.local /usr/share/doc/mutt-1.5.20/sample.muttrc $ locate services (查找的字符串可能出现在文件路径中) /etc/services /usr/share/services/bmp.kmgio /usr/share/services/data.kmgio 中场休息,看看我家光洙 使用find命令查找文件 由于有许多不同的属性,find命令是查找文件的利器。当执行find命令时,它会搜索整个文件系统,这会造成find命令比locate命令耗时长,但同时也能让用户查找到系统中最新的文件。find命令最大的优点在于所有你能想到的文件的属性,都可以通过它查找,例如名字,拥有者,权限,大小,修改时间等等,也可以进行组合查找。 123456789101112$ find (列出当前目录下所有的文件和目录)$ find /etc (列出/etc目录下所有的文件和目录,权限不足时会报错)$ find -ls (列出文件的拥有者,权限,大小等信息)$ find /etc -name passwd(根据名字查找)$ find /etc -iname '*passwd*' (可以使用通配符)$ find /bigdata -size +10G (查找大小大于10G的文件)$ find /smalldata -size -5M (查找大小小于5M的文件)$ find /home -user chris -ls (输出/home目录下拥有者是chris的文件的详细信息)$ find /home -user chris -or -user joe -ls (输出/home目录下拥有者是chris或joe的文件的详细信息)$ find /home -not -user root -ls (输出/home目录下拥有者不是root的文件的详细信息)$ find /bin -perm 755 -ls(通过权限查找)$ find . -perm -002 -type f -ls (通过类型查找) find命令还有一个很棒的特性,可以使用-excute和-ok选项可以在查找到的任何文件上执行命令。excute选项会直接在每个找到的文件上执行命令,不会询问是否执行。而ok选项会在每个文件执行命令前询问是否执行。 1234$ find /etc -iname iptables -exec echo \"I found {}\" \\; I found /etc/bash_completion.d/iptables I found /etc/sysconfig/iptables # find /var/allusers/ -user joe -ok mv {} /tmp/joe/ \\;mv ... /var/allusers/dict.dat > ? y mv ... /var/allusers/five > ? y 想了解更多关于find命令的信息,可以执行命令 man find 使用grep命令在文件中查找 如果希望查找包含特定内容的文件,可以使用grep命令。通过grep命令,可以搜索单个文件,也可以递归搜索整个目录。默认情况下,grep命令是大小写敏感的 12345$ grep desktop /etc/services desktop-dna 2763/tcp # Desktop DNA desktop-dna 2763/udp # Desktop DNA $ grep -i desktop /etc/services sco-dtmgr 617/tcp # SCO Desktop Administration Serversco-dtmgr 617/udp # SCO Desktop Administration Serverairsync 2175/tcp # Microsoft Desktop AirSync Protocol 第一个例子,是在/etc/services文件中查找字符串desktop.第二个例子通过选项-i,在查找时大小写不敏感-v 是查找不包含指定内容的行,-r是在目录中递归查找,-l是列出文件名 而不是包含内容的具体行 1234$ grep -v desktop /etc/services$ grep -rli peerdns /usr/share/doc/ /usr/share/doc/dnsmasq-2.66/setup.html /usr/share/doc/initscripts-9.49.17/sysconfig.txt 以我大谢耳朵结尾吧,主要看气质,哈哈~~","raw":null,"content":null,"categories":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/tags/linux/"},{"name":"读书笔记","slug":"读书笔记","permalink":"http://yemengying.com/tags/读书笔记/"}]},{"title":"读书笔记-Linux Bible 9th Edition之文件系统","slug":"读书笔记-Linux-Bible-9th-Edition之文件系统","date":"2015-11-26T08:28:15.000Z","updated":"2018-12-14T09:19:46.000Z","comments":true,"path":"2015/11/26/读书笔记-Linux-Bible-9th-Edition之文件系统/","link":"","permalink":"http://yemengying.com/2015/11/26/读书笔记-Linux-Bible-9th-Edition之文件系统/","excerpt":"跟着书,重新梳理一下linux文件系统的有关知识, 最近一天一个接口的节奏真真是极好的,有时间看看书了。😻😻😻 相关博客: Linux Bible 9th Edition之使用shell Linux Bible 9th Edition之玩转文本文件 Linux Bible 9th Edition之进程大法好","keywords":null,"text":"跟着书,重新梳理一下linux文件系统的有关知识, 最近一天一个接口的节奏真真是极好的,有时间看看书了。😻😻😻 相关博客: Linux Bible 9th Edition之使用shell Linux Bible 9th Edition之玩转文本文件 Linux Bible 9th Edition之进程大法好 Linux文件系统结构 在linux中文件组织在一个层级的目录结构中,每个目录可以包含文件和目录,整体看起来就像一个倒过来的树。最上面就是根目录,用”/“符号表示。根目录下面是linux系统中一些常见的目录,比如bin,dev,home,lib等等。下面的图(书上的截图)展示了linux文件系统的层级结构。 基本的一些文件系统命令 命令 作用 cd 进入另一个目录 pwd 打印当前工作目录的路径 mkdir 创建一个目录 chmod 更改文件或目录的权限 ls 列出目录的内容 cd命令cd命令是其中最常用的eg: 12345678# 进入根目录下的usr目录下的share目录 以\"/\"开头,代表在根目录下$ cd /usr/share# 只输入cd 回到home目录$ cd # 进入home目录下的某一目录 \"~\"代表home目录$ cd ~/coffee# 回到上一级目录 \"..\"代表上一级目录$ cd .. 创建目录并查看权限 1234# 创建目录test$ mkdir test# 查看目录权限$ ls -ld test 使用Metacharacters(元字符)和Operators(操作符) 使用metacharacters进行文件匹配 “*”代表任意数量的字符 “?”代表任意一个字符 “[…]”匹配任意一个包含在括号中的字符,也可以用连字符表示一个范围 eg: 123456789101112# 创建5个空文件$ touch apple banana grape grapefruit watermelon$ ls a*apple$ ls g*grape $ ls g???egrape$ ls [abw]*apple banana watermelon$ ls [a-g]*apple banana grape grapefruit 使用metacharacters进行文件重定向 使用管道符号”|”可以将一个命令的标准输出(standard output)作为另一个命令的标准输入(standard input)。对于文件,我们可以用”<”和”>”来将数据从文件中输入或输出。 符号 作用 < 文件的内容输入到命令 > 将一个命令的标准输出输出到文件,如果文件已存在,文件的内容会被覆盖 2> 将错误输出输出到文件 &> 将标准输出和错误输出都输出到文件 >> 将命令的输出到文件中,不覆盖文件原有内容,将输出添加到文件最后 << 后面要跟着一个单词,之后所有的输入都会当做用户输入,直到重复输入符号后的单词 使用大括号 通过使用大括号”{}”,可以在文件名后扩展一组元素。 eg: 123$ touch {a,b,c}-{1,2,3}$ lsa-1 a-2 a-3 b-1 b-2 b-3 c-1 c-2 c-3 列出文件和目录在linux系统中,ls命令用来列出文件和目录的有关信息,ls命令有许多option。在默认情况下,输入ls,会输出当前目录下所有的非隐藏的文件和目录。如果在命令后在上选项”-l”会输出详细的信息(如下),其中total代表了目录中的内容占用了多少磁盘空间;第一列第一个字符代表了文件的类型,”-“代表普通文件,”d”代表是目录,”l”代表是一个符号链接,剩下的9个字符代表了文件的权限(下面会讲);第二列展示了文件硬链接数或目录子目录数;第三列显示了文件或目录的拥有者;第四列代表文件拥有者所在的组;第五列是文件的大小;第六列是文件最后的修改时间;最后一列展示了文件或目录的名字;eg: 12$ ls -ltotal 4 -rw-rw-r--. 1 joe joe 0 Dec 18 13:38 apple lrwxrwxrwx. 1 joe joe 5 Dec 18 13:46 pointer_to_apple -> apple -rwxr-xr-x. 1 joe joe 0 Dec 18 13:37 scriptx.sh drwxrwxr-x. 2 joe joe 4096 Dec 18 13:38 Stuff ls命令的其它选项: 选项 作用 -a 展示包含隐藏文件(以.开头)在内的所有文件 -t 按照最近修改时间展示 -F 在展示时,在目录名后加”/“,在可执行文件后加”*”,在符号链接后加”@” -S 展示时按大小排序 -d 只展示包含的目录 -R 递归的列出当前目录下所有的文件和目录 –hide= 不展示指定目录或文件 理解文件权限 在使用Linux系统时,经常会看到”Permission Denied”(权限不足)的提示。权限控制是为了避免用户访问其他用户的私有文件和保护重要的系统文件。在Linux中,每个文件对应一个9bit的权限信息(eg:rwxrwxrwx)。其中前三位代表了文件拥有者的权限,中间三位代表了拥有者所在组的权限,最后三位代表了其他用户的权限。权限由字母代表,”r”代表读权限,”w”代表写权限,”x”代表执行权限,如果某一位不是字母,而是”-“,则代表没有该位所代表的权限。举个两个栗子🌰🌰,”rw-“代表有读写权限,没有执行权限;”r–”代表只有读权限 使用chmod命令更改权限 使用数字 文件的拥有者可以改变文件的权限,每种权限都对应了一个数字,读权限r对应4,写权限w对应2,执行权限x对应1。可以通过设置数值来建立权限。eg: 12345678# 设置权限 rwxrwxrwx# chmod 777 filename# 设置权限 rwxr-xr-x# chomd 755 filename# 设置权限 rw-r--r--# chomd 644 filename# 设置权限 ---------# chmod 000 filename 使用字母 在linux中,还有另一种改变权限的方式。在这种方式中,”+”和”-“分别代表权限的开和关。字母”u”,”g”,”o”和”a”分别代表拥有者,组,其他用户和全部用户。和上一种方式一样”r”,”w”,”x”分别代表读、写和执行权限。eg: 12345678910# 设置权限 将权限rwxrwxrwx改为r-xr-xr-x# chmod a-w filename# 设置权限 将权限rwxrwxrwx改为rwxrwxrw-# chomd o-x filename# 设置权限 将权限rwxrwxrwx改为rwx------# chmod go-rwx filename# 设置权限 将权限---------改为rw-------# chmod u+rw filename# 更改目录下所有文件和目录的权限# chmod -R o-w myapps 使用umask设置默认权限 普通用户创建文件 默认权限是rw-rw-r–,创建目录 默认权限是rwxrwxr-x。root用户创建文件和目录权限分别是rw-r–r–和rwxr-xr-x。这些默认值由umask的值决定,可通过命令umask查看它的值。与chmod效果刚好相反,umask设置的是权限的补码,umask的值有三位分别对应拥有者,同组用户和其他用户的权限。对于文件来说,每一位的最大值是6,因为系统不允许在创建一个文件时就赋予执行权限,需通过chmod设置;对于目录来说每一位的最大值是7。例如umask值002所对应的文件和目录的创建权限是664和775。可通过umask命令改变默认值。eg: 1234# 查看默认值$ umask# 改变默认值$ umask 022 改变文件的拥有者 作为普通用户,是不能更改文件或目录的拥有者的,只有root user(管理员才可以)。eg: 123456# 修改文件的拥有者# chown giraffe filename.text# 同时修改拥有者和组# chown giraffe:coffee filename.txt# 修改目录下的所有目录和文件的拥有者# chown -R giraffe:coffee /mydic 移动 复制 删除文件 移动、复制和删除文件的命令很简单。如果想要改变文件的位置,使用mv命令。如果想要复制文件,使用cp命令。如果想要删除文件,使用rm命令。这些命令可以使用在单一的文件和目录上,也可以递归的使用在许多文件和目录上。 移动文件eg: 1234# 将文件abc移到home目录$ mv abc ~# 将目录mydemo的全部内容移到目录document中$ mv /home/giraffe/mydemo /home/giraffe/document 复制文件eg: 123456# 将文件abc复制到home目录下$ cp abc ~# 将目录bash-completion*下的内容复制到tmp/a下$ cp -r /usr/share/bash-completion* /tmp/a# 将目录bash-completion*下的内容复制到tmp/a下 并且保留权限$ cp -ra /usr/share/bash-completion* /tmp/a 删除文件eg: 12345678910# 删除文件abc$ rm abc# 删除当前目录下所有文件$ rm *# 删除一个空的目录$ rmdir /home/giraffe/empty# 删除目录和他包含的所有内容$ rm -r /home/giraffe/bigdir# 不提示的删除目录和它包含的所有内容$ rm -rf /home/giraffe/bigdir","raw":null,"content":null,"categories":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/tags/linux/"},{"name":"读书笔记","slug":"读书笔记","permalink":"http://yemengying.com/tags/读书笔记/"}]},{"title":"读书笔记-Linux Bible 9th Edition之使用shell","slug":"读书笔记-Linux-Bible-9th-Edition","date":"2015-11-23T08:23:32.000Z","updated":"2018-12-14T09:20:21.000Z","comments":true,"path":"2015/11/23/读书笔记-Linux-Bible-9th-Edition/","link":"","permalink":"http://yemengying.com/2015/11/23/读书笔记-Linux-Bible-9th-Edition/","excerpt":"其实也不算读书笔记 主要是想整理一下常用的一些linux命令 相关博客: Linux Bible 9th Edition之玩转文本文件 Linux Bible 9th Edition之文件系统 Linux Bible 9th Edition之进程大法好","keywords":null,"text":"其实也不算读书笔记 主要是想整理一下常用的一些linux命令 相关博客: Linux Bible 9th Edition之玩转文本文件 Linux Bible 9th Edition之文件系统 Linux Bible 9th Edition之进程大法好 linux命令的一些语法 $提示符代表普通用户 #提示符代表root用户。 大多数命令都有许多选项 选项通常由单一字符和连字符组成(eg:ls -a),还有选项是由一个单词代表,需在单词前加双连字符(eg:date - -help) 获得当前登录会话的一些信息123$ who $ who am i $ who -uH 查看服务器上的时间12$ date$ date +'%d/%m/%y'(以10/12/14的格式输出) 当前的目录1$ pwd 获得hostname1$ hostname 列出当前目录下的文件和目录12$ ls$ ls -l(列出详细信息) -a(列出包括.开头的隐含文件在内的所有文件) -t(按时间排序) 查看uid gid1$ id LINUX如何定位命令 可通过echo $PATH命令查看PATH环境变量的值,如果命令存放的目录包含在PATH中,可直接输入命令运行。如果不包含则需给出命令的位置(eg:绝对位置:/home/chris/scriptx.sh,相对位置:./scriptx.sh) shell检查输入命令的顺序:1.Aliases(别名) 2.Reserved word(保留的关键字) 3.Function 4.Build-in command(eg:cd/echo/exit/type..) 5.Filesystem 1234#查看一个命令的位置$ type bash#打印PATH环境变量的值$ echo $PATH 在文件系统中查找1$ locate ymy 查看历史输入的命令,修改命令 可以用history命令查看之前输入过的所有命令,之后可以通过!+行号 运行指定一行的命令。向上箭头(↑)可查看最近一条命令,下面是修改命令的一些快捷按键。 快捷键 作用 ctrl+A 将光标定位到命令的最前面 ctrl+E 将光标定位到命令的最后面 ctrl+L 清空屏幕,将命令置为最上面 ctrl+F或 → 将光标后移 ctrl+B或 ← 将光标前移 alt+F 将光标后移一个单词 alt+B 将光标前移一个单词 ctrl+D 删除当前字符 backspace 删除前一个字符 ctrl+T 将当前字符和前一个字符对换 alt+T 将当前单词和前一个单词对换 alt+U 将当前单词变成大写 alt+L 将当前单词变成小写 ctrl+K 剪切从光标位置到最后 ctrl+U 剪切从光标位置到最前 ctrl+W 剪切前一个单词 alt+D 剪切后一个单词 ctrl+Y 粘贴最近复制的内容 alt+Y 粘贴最前复制的内容 tab 补全命令 连接和扩展命令 管道符号 “|”将前一个命令的输出作为下一个命令的输入eg: 1$ cat /etc/passwd | sort |less 命令分隔符 “;”在一行语句中 顺次执行各个命令eg: 12#可获得troff命令的执行时间$ date; troff -me verylargedocument|lpr ; date 后台进程符 “&”如果不希望shell一直被一个命令占用着,可以使用”&”让命令在后台运行eg: 1$ troff -me verylargedocument | lpr & 使用数学表达式/命令的结果 “$[]”/“$()”可以在一个命令中使用数学表达式或另一个命令的结果eg: 12$ echo \"I am $[2015 - 1993] years old\"$ echo \"there are $(ls | wc -w) files in this directory\" 变量调用符号 “$”eg: 1$ echo $USER 创建和使用别名使用alias命令,可以给任何的命令及选项取一个别名eg:123456#为命令pwd取别名ymy$ alias ymy='pwd'#查看所有的别名$ alias#删除别名$ unalias ymy 退出shell1$ exit","raw":null,"content":null,"categories":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yemengying.com/tags/linux/"},{"name":"读书笔记","slug":"读书笔记","permalink":"http://yemengying.com/tags/读书笔记/"}]},{"title":"EsGiraffe-利用注解和反射拼接Elasticsearch查询语句","slug":"EsGiraffe-利用注解和反射拼接Elasticsearch查询语句","date":"2015-11-19T05:56:50.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2015/11/19/EsGiraffe-利用注解和反射拼接Elasticsearch查询语句/","link":"","permalink":"http://yemengying.com/2015/11/19/EsGiraffe-利用注解和反射拼接Elasticsearch查询语句/","excerpt":"EsGiraffe 封闭开发结束,终于有时间可以整理一下了。EsGiraffe是一个利用注解和反射开发一套工具类,用来生成elastisearch的查询语句。为什么要叫Giraffe呢?一是因为我喜欢长颈鹿,二是希望可以通过工具类把像长颈鹿脖子一样长的代码简化一下,三是希望这个工具类可以像桥梁一样连接java和elaticsearch。实在编不下去了,其实就是因为喜欢长颈鹿。目前只适用于简单的查询,不过会在工作学习中慢慢完善的。由于目前在工作中用到最多的就是Bool查询,所以目前生成的查询语句最外层就是bool查询,生成的大致的样子如下: "query": { "bool": { "must": [ ... ], "must_not": [ ... ], "should": [ ... ] } } git地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-EsGiraffe-6afdc51\", \"giraffe0813\", \"EsGiraffe\", \"6afdc51\", false);","keywords":null,"text":"EsGiraffe 封闭开发结束,终于有时间可以整理一下了。EsGiraffe是一个利用注解和反射开发一套工具类,用来生成elastisearch的查询语句。为什么要叫Giraffe呢?一是因为我喜欢长颈鹿,二是希望可以通过工具类把像长颈鹿脖子一样长的代码简化一下,三是希望这个工具类可以像桥梁一样连接java和elaticsearch。实在编不下去了,其实就是因为喜欢长颈鹿。目前只适用于简单的查询,不过会在工作学习中慢慢完善的。由于目前在工作中用到最多的就是Bool查询,所以目前生成的查询语句最外层就是bool查询,生成的大致的样子如下: "query": { "bool": { "must": [ ... ], "must_not": [ ... ], "should": [ ... ] } } git地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-EsGiraffe-6afdc51\", \"giraffe0813\", \"EsGiraffe\", \"6afdc51\", false); 常见的工作需求在我工作比较常见的搜索需求就是对外提供搜索的接口。会按照搜索引擎中的字段定义一个对应的model,然后这些字段的值如果为Null,就代表调用方不想对这个字段进行查询,如果不为Null代表需要对这个字段进行查询。而且还会提前在API文档中定义好查询的类型是must、must_not还是should 未使用工具类的代码Demo假设有一个和订单有关的索引,需要对订单的一系列属性进行查询 其中userName和orderMode是should查询 其余都是must查询model: 1234567891011121314151617181920212223public class OrderModel { private String userName; private Integer restaurantId; private Integer orderMode; private String userPhone; private Integer comeFrom; private String restaurantName; private String createdAtBegin; private String createdAtEnd; private Integer offset = 0; private Integer limit = 10; //省略getter和setter } 查询方法: 123456789101112131415161718192021222324252627282930313233343536373839404142public SearchService implements ISearchService{ public String searchOrder(SearchOrderModel search) throws IllegalAccessException{ BoolQueryBuilder baseQuery = QueryBuilders.boolQuery(); if(search.getUserName() != null){ baseQuery.should(QueryBuilders.termQuery(\"user_name\", search.getUserName())); } if(search.getRestaurantId() != null){ baseQuery.must(QueryBuilders.termQuery(\"restaurant_id\", search.getRestaurantId())); } if(search.getOrderMode() != null){ baseQuery.should(QueryBuilders.termQuery(\"order_mode\", search.getOrderMode())); } if(search.getUserPhone() != null){ baseQuery.must(QueryBuilders.termQuery(\"user_phone\", search.getUserPhone())); } if(search.getComeFrom() != null){ baseQuery.must(QueryBuilders.termQuery(\"come_from\", search.getComeFrom())); } if(search.getRestaurantName() != null){ baseQuery.must(QueryBuilders.queryStringQuery(search.getRestaurantName()).defaultField(\"restaurant_name\")); } if(search.getCreatedAtBegin() != null){ baseQuery.must(QueryBuilders.rangeQuery(\"created_at\").gte(search.getCreatedAtBegin())); } if(search.getCreatedAtEnd() != null){ baseQuery.must(QueryBuilders.rangeQuery(\"created_at\").lte(search.getCreatedAtBegin())); } log.info(\"查询语句:{}\",baseQuery.toString()); SearchResponse response = client.prepareSearch(\"index1\") .setTypes(\"type1\") .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setQuery(baseQuery) .setFrom(search.getOffset()).setSize(search.getLimit()).setExplain(true) .execute() .actionGet(); return response.toString(); }} 下面是针对餐厅名称,用户名和来源同时查询时,打印出来的拼接的查询语句: 123456789101112131415161718{ \"bool\" : { \"must\" : [ { \"term\" : { \"come_from\" : 1 } }, { \"query_string\" : { \"query\" : \"测试餐厅\", \"default_field\" : \"restaurant_name\" } } ], \"should\" : { \"term\" : { \"user_name\" : \"123123123\" } } } } 可是上面只是缩减版的订单model,一个订单的属性有20多个 所以每个属性都判断是否为null,代码就太不漂亮了,而且可读性差,不易维护,所以就想通过注解和反射来精简代码。 EsGiraffe的主要内容EsGiraffe主要是自定义了一些注解,将一些诸如model属性对应的搜索引擎的字段,查询的类型,要查询的index名,要查询的document名用注解标注在model类上,然后在工具类中利用反射获取注解的值 拼接查询语句。下面是几个比较重要的注解: Index注解 DocumentType注解 只能在类上使用 Index代表要在哪个索引中查询, Document代表要查询的文档 可以指定多个索引或文档用”,”分割 例子: 1234@Index(\"index\")@DocumentType(\"document1\")public class model{} EsField注解 在类的属性上使用 值为该属性对应的索引字段名 如果value为空,那么对应的索引名就是该属性名。这个注解主要是考虑到在Java习惯驼峰式的命名,而搜索引擎中往往是下划线,所以需要一个注解将他们对应起来。 例子: 1234567@Index(\"index\")@DocumentType(\"document1\")public class model{ @EsField(\"come_from\") private Integer comeFrom;} 不过,这两个注解只适用于在查询前就确定要查询的索引和文档时使用。如果要根据查询的内容才能确定要查询的文档,目前没有想到什么好的解决办法,这种情况只能不用Index和DocumentType注解了。比如做活动查询时,活动索引到搜索引擎中是按照活动所属的城市存储到不同的文档,举个栗子🌰,如果活动1的城市id是1,那么活动1就存在在文档activity_1,如果活动2的城市id是3,那么活动2就存在在文档activity_3中,这种情况就不能靠通过注解的方式获得查询的文档了。 Bool注解 最重要的注解,里面包含5个元素value,type,escape,fuzziness,prefix。其中value是MatchType(枚举类)类型,代表了该Bool查询是MUST,MUST_NOT还是SHOULD,默认是MUST。type的值是EsSearchType(枚举类)类型,代表对该字段采用什么类型的查询。默认值是TERMS,支持的其他类型还有TERM,RANGE_FROM, RANGE_TO, RANGE_GT,RANGE_LT, RANGE_GTE, RANGE_LTE, FUZZY, SHOULD_TERM, QUERY_STRING, MATCH。escape是布尔类型的,代表是否需要进行特殊字符(eg: !$()等)的转换,默认值是false。fuzziness和prefix是在新版本中针对type=EsSearchType.FUZZY做的扩展而新增的元素。分别代表Fuzzy Query中的fuzziness和prefix_length,这两个参数的意思可以到官方文档上看,fuzziness的值只能为[0,1,2],否则会报错“org.elasticsearch.ElasticsearchIllegalArgumentException: Valid edit distances are [0, 1, 2]”,不要问我为什么知道。要注意的是这两个元素只有在type=EsSearchType.FUZZY时才有效 例子: 123456789101112@Index(\"index\")@DocumentType(\"document1\")public class model{ @EsField(\"come_from\") @Bool(type = EsSearchType.TERM) private Integer comeFrom; @EsField(\"created_at\") @Bool(type = EsSearchType.RANGE_GTE) private String createdAtBegin;} 上面的注解代表的查询语句是 1234567891011121314151617{ \"bool\" : { \"must\" : [ { \"term\" : { \"come_from\" : 1 } }, { \"range\" : { \"created_at\" : { \"from\" : \"2015-02-03\", \"to\" : null, \"include_lower\" : true, \"include_upper\" : true } } } ] } } From,Size,Sort注解 这三个注解也是用到类的属性上的,如果一个属性上标有From注解,代表这个字段的值是查询分页的起始位置;如果一个属性上标有Size注解,代表这个字段的值是分页的长度;如果一个属性上标有Sort注解,代表查询结果按该字段排序,Sort的值有SortType.ASC和SortType.DESC两种。 ElasticBaseSearch中两个工具方法 getIndexAndType和getQueryBuilder 已经在类和属性添加了注解,那么就需要写两个方法分别通过反射获取类和属性上的值 来拼接对应的查询语句。其中getQueryBuilder是根据属性上的注解拼装查询语句并返回一个QueryBuilder对象,getIndexAndType是根据注解获得要查询的索引,文档等信息,并返回一个SearchRequestBuilder对象。 使用EsGiraffe简化代码 下面是使用EsGiraffe简化后的代码 查询model类 12345678910111213141516171819202122232425262728293031323334353637383940414243@Index(\"index1\")@DocumentType(\"type1\")public class OrderModel { @EsField(\"user_name\") @Bool(type = EsSearchType.QUERY_STRING, value = MatchType.SHOULD, escape=true) private String userName; @EsField(\"restaurant_id\") @Bool(type = EsSearchType.TERM) private Integer restaurantId; @EsField(\"order_mode\") @Bool(type = EsSearchType.TERM) private Integer orderMode; @EsField(\"usr_phone\") @Bool(type = EsSearchType.TERM) private String userPhone; @EsField(\"come_from\") @Bool(type = EsSearchType.TERM) private Integer comeFrom; @EsField(\"restaurant_name\") @Bool(type = EsSearchType.QUERY_STRING,escape=true) private String restaurantName; @EsField(\"created_at\") @Bool(type = EsSearchType.RANGE_GTE) private String createdAtBegin; @EsField(\"created_at\") @Bool(type = EsSearchType.RANGE_LTE) private String createdAtEnd; @From private Integer offset = 0; @Size private Integer limit = 10; //省略getter和setter } 接口类,也无需写大量的业务逻辑,只需要调用两个工具方法即可 12345678910111213141516public String searchOrder(SearchOrderModel search) throws IllegalAccessException { log.info(\"查询参数:{}\", search.toString()); QueryBuilder baseQuery = ElasticBaseSearch.getInstance().getQueryBuilder(search); if(baseQuery != null){ log.info(baseQuery.toString()); SearchResponse response = ElasticBaseSearch.getInstance().getIndexAndType(client, search).setQuery(baseQuery).execute().actionGet(); log.info(response.toString()); return response.toString(); } return \"\";} 使用了EsGiraffe之后,大大简化了查询接口的代码,无需挨个属性判断是否为null。而且在model类上使用注解,使得程序变得更加可读,每个属性对应搜索引擎中哪个字段,采用哪种查询方式一目了然。下面是使用EsGiraffe之后,对餐厅名称,用户名和来源查询时打印的查询语句,和不用注解生成的是一样的。 123456789101112131415161718{ \"bool\" : { \"must\" : [ { \"term\" : { \"come_from\" : 1 } }, { \"query_string\" : { \"query\" : \"测试餐厅\", \"default_field\" : \"restaurant_name\" } } ], \"should\" : { \"term\" : { \"user_name\" : \"123123123\" } } } }","raw":null,"content":null,"categories":[{"name":"elasticsearch","slug":"elasticsearch","permalink":"http://yemengying.com/categories/elasticsearch/"}],"tags":[{"name":"elasticsearch","slug":"elasticsearch","permalink":"http://yemengying.com/tags/elasticsearch/"},{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"annotation","slug":"annotation","permalink":"http://yemengying.com/tags/annotation/"},{"name":"reflect","slug":"reflect","permalink":"http://yemengying.com/tags/reflect/"}]},{"title":"读书笔记---深入理解Java虚拟机1","slug":"读书笔记-深入理解Java虚拟机1","date":"2015-11-12T09:23:23.000Z","updated":"2018-12-14T09:21:06.000Z","comments":true,"path":"2015/11/12/读书笔记-深入理解Java虚拟机1/","link":"","permalink":"http://yemengying.com/2015/11/12/读书笔记-深入理解Java虚拟机1/","excerpt":"","keywords":null,"text":"最近看书总是看不进去,所以。。。决定边看边画画图,做个总结。下面是深入理解Java虚拟机这本书的第二,三章的总结。","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"读书笔记","slug":"读书笔记","permalink":"http://yemengying.com/tags/读书笔记/"}]},{"title":"【译】如何在java中使用ConcurrentHashMap","slug":"【译】如何在java中使用ConcurrentHashMap","date":"2015-11-06T05:20:37.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2015/11/06/【译】如何在java中使用ConcurrentHashMap/","link":"","permalink":"http://yemengying.com/2015/11/06/【译】如何在java中使用ConcurrentHashMap/","excerpt":"原文来自一个java大牛的技术博客 地址http://javarevisited.blogspot.com/2013/02/concurrenthashmap-in-java-example-tutorial-working.html 博客讲解了如何在java中使用ConcurrentHashMap。马上要封闭开发10天,连上15天班,真酸爽。下面是原文的翻译:","keywords":null,"text":"原文来自一个java大牛的技术博客 地址http://javarevisited.blogspot.com/2013/02/concurrenthashmap-in-java-example-tutorial-working.html 博客讲解了如何在java中使用ConcurrentHashMap。马上要封闭开发10天,连上15天班,真酸爽。下面是原文的翻译: ConcurrentHashMap(简称CHM)是在Java 1.5作为Hashtable的替代选择新引入的,是concurrent包的重要成员。在Java 1.5之前,如果想要实现一个可以在多线程和并发的程序中安全使用的Map,只能在HashTable和synchronized Map中选择,因为HashMap并不是线程安全的。但再引入了CHM之后,我们有了更好的选择。CHM不但是线程安全的,而且比HashTable和synchronizedMap的性能要好。相对于HashTable和synchronizedMap锁住了整个Map,CHM只锁住部分Map。CHM允许并发的读操作,同时通过同步锁在写操作时保持数据完整性。我们已经在Top 5 Java Concurrent Collections from JDK 5 and 6中学习了CHM的基础知识,在这篇博客中我将介绍以下几点: CHM在Java中如何实现的 什么情况下应该使用CHM 在Java中使用CHM的例子 CHM的一些重要特性 Java中ConcurrentHashMap的实现CHM引入了分割,并提供了HashTable支持的所有的功能。在CHM中,支持多线程对Map做读操作,并且不需要任何的blocking。这得益于CHM将Map分割成了不同的部分,在执行更新操作时只锁住一部分。根据默认的并发级别(concurrency level),Map被分割成16个部分,并且由不同的锁控制。这意味着,同时最多可以有16个写线程操作Map。试想一下,由只能一个线程进入变成同时可由16个写线程同时进入(读线程几乎不受限制),性能的提升是显而易见的。但由于一些更新操作,如put(),remove(),putAll(),clear()只锁住操作的部分,所以在检索操作不能保证返回的是最新的结果。 另一个重要点是在迭代遍历CHM时,keySet返回的iterator是弱一致和fail-safe的,可能不会返回某些最近的改变,并且在遍历过程中,如果已经遍历的数组上的内容变化了,不会抛出ConcurrentModificationExceptoin的异常。 CHM默认的并发级别是16,但可以在创建CHM时通过构造函数改变。毫无疑问,并发级别代表着并发执行更新操作的数目,所以如果只有很少的线程会更新Map,那么建议设置一个低的并发级别。另外,CHM还使用了ReentrantLock来对segments加锁。 Java中ConcurrentHashMap putifAbsent方法的例子很多时候我们希望在元素不存在时插入元素,我们一般会像下面那样写代码 1234567synchronized(map){ if (map.get(key) == null){ return map.put(key, value); } else{ return map.get(key); }} 上面这段代码在HashMap和HashTable中是好用的,但在CHM中是有出错的风险的。这是因为CHM在put操作时并没有对整个Map加锁,所以一个线程正在put(k,v)的时候,另一个线程调用get(k)会得到null,这就会造成一个线程put的值会被另一个线程put的值所覆盖。当然,你可以将代码封装到synchronized代码块中,这样虽然线程安全了,但会使你的代码变成了单线程。CHM提供的putIfAbsent(key,value)方法原子性的实现了同样的功能,同时避免了上面的线程竞争的风险。 什么时候使用ConcurrentHashMapCHM适用于读者数量超过写者时,当写者数量大于等于读者时,CHM的性能是低于Hashtable和synchronized Map的。这是因为当锁住了整个Map时,读操作要等待对同一部分执行写操作的线程结束。CHM适用于做cache,在程序启动时初始化,之后可以被多个请求线程访问。正如Javadoc说明的那样,CHM是HashTable一个很好的替代,但要记住,CHM的比HashTable的同步性稍弱。 总结现在我们知道了什么是ConcurrentHashMap和什么时候该用ConcurrentHashMap,下面我们来复习一下CHM的一些关键点。 CHM允许并发的读和线程安全的更新操作 在执行写操作时,CHM只锁住部分的Map 并发的更新是通过内部根据并发级别将Map分割成小部分实现的 高的并发级别会造成时间和空间的浪费,低的并发级别在写线程多时会引起线程间的竞争 CHM的所有操作都是线程安全 CHM返回的迭代器是弱一致性,fail-safe并且不会抛出ConcurrentModificationException异常 CHM不允许null的键值 可以使用CHM代替HashTable,但要记住CHM不会锁住整个Map 以上就是Java中CHM的实现和使用场景","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"},{"name":"hashmap","slug":"java/hashmap","permalink":"http://yemengying.com/categories/java/hashmap/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"【译】Java8中的扩展(default/extension)方法","slug":"Java-8中的扩展-Default-Defender-Extension-方法","date":"2015-11-01T14:11:06.000Z","updated":"2018-12-14T09:22:06.000Z","comments":true,"path":"2015/11/01/Java-8中的扩展-Default-Defender-Extension-方法/","link":"","permalink":"http://yemengying.com/2015/11/01/Java-8中的扩展-Default-Defender-Extension-方法/","excerpt":"原文来自一个java大牛的技术博客 地址http://javarevisited.blogspot.hk/2014/07/default-defender-or-extension-method-of-Java8-example-tutorial.html#uds-search-results 博客讲解了Java 8中新引入的可以在接口中定义扩展方法。下面是原文的翻译。 Java 8允许开发者使用default和static两个关键字在接口中加入非抽象的方法。带有default关键字的方法在Java中也被称作defender方法或defaul方法。在Java 8之前,想要改变一个已经发布的接口几乎是不可能的,任何改动(例如增加一个新的方法)都会影响该接口现有的实现类。这也是为什么在Java 8想要改变内部iterator的实现,使用forEach()方法时面临了一个巨大的挑战,因为这会破坏了现有的Iterable接口的实现类。毫无疑问,向后兼容是Java工程师最优先考虑的事,所以要破坏现有的实现类是不可行的。因此,他们提出了一个解决办法,引入default方法。这是一个绝妙的想法,因为现在你可以用扩展现有的接口。JDK本身也使用了许多default方法,java.util.Map接口扩展了许多default方法,例如replaceAll(),putIfAbsent(Key k,Value v)….。另外,由于default方法可以扩展现有的接口也被称作extension方法。一个接口中的default方法是数量不受限制的。我相信,在这次改变之后,将不再需要抽象类来提供骨架实现(skeletal implementation),例如List接口有AbstractList,Collection接口有AbstractCollection,Set接口有AbstractSet,Map接口有AbstractMap。我们可以通过在接口中定义default方法来替代创建一个新的抽象类。相似的,static方法的引入也使得接口的工具类变得冗余。例如,Collection接口的Collections类,Path接口的Paths类,因为你可以直接在接口中定义静态工具方法。如果你想了解更多关于Java 8的新特性,我建议阅读Cay S. Horstmann写的Java SE 8 Really Impatient。这是我最喜欢的关于Java 8的书之一,它详细的介绍了Java7与Java 8不同的特性。","keywords":null,"text":"原文来自一个java大牛的技术博客 地址http://javarevisited.blogspot.hk/2014/07/default-defender-or-extension-method-of-Java8-example-tutorial.html#uds-search-results 博客讲解了Java 8中新引入的可以在接口中定义扩展方法。下面是原文的翻译。 Java 8允许开发者使用default和static两个关键字在接口中加入非抽象的方法。带有default关键字的方法在Java中也被称作defender方法或defaul方法。在Java 8之前,想要改变一个已经发布的接口几乎是不可能的,任何改动(例如增加一个新的方法)都会影响该接口现有的实现类。这也是为什么在Java 8想要改变内部iterator的实现,使用forEach()方法时面临了一个巨大的挑战,因为这会破坏了现有的Iterable接口的实现类。毫无疑问,向后兼容是Java工程师最优先考虑的事,所以要破坏现有的实现类是不可行的。因此,他们提出了一个解决办法,引入default方法。这是一个绝妙的想法,因为现在你可以用扩展现有的接口。JDK本身也使用了许多default方法,java.util.Map接口扩展了许多default方法,例如replaceAll(),putIfAbsent(Key k,Value v)….。另外,由于default方法可以扩展现有的接口也被称作extension方法。一个接口中的default方法是数量不受限制的。我相信,在这次改变之后,将不再需要抽象类来提供骨架实现(skeletal implementation),例如List接口有AbstractList,Collection接口有AbstractCollection,Set接口有AbstractSet,Map接口有AbstractMap。我们可以通过在接口中定义default方法来替代创建一个新的抽象类。相似的,static方法的引入也使得接口的工具类变得冗余。例如,Collection接口的Collections类,Path接口的Paths类,因为你可以直接在接口中定义静态工具方法。如果你想了解更多关于Java 8的新特性,我建议阅读Cay S. Horstmann写的Java SE 8 Really Impatient。这是我最喜欢的关于Java 8的书之一,它详细的介绍了Java7与Java 8不同的特性。 Default方法的例子Java 8让我们可以通过default关键字为接口添加非抽象的方法。这一特性也被称作Extension(扩展)方法。下面是第一个例子: 12345678interface Multiplication{ int multiply(int a, int b); default int square(int a){ return multiply(a, a); } } 除了抽象方法multiply()之外,接口Multiplication还包含一个default方法square()。任何实现Multiplication接口的类只需实现抽象方法multiply,default方法square()可以直接使用。 12345678910Multiplication product = new Multiplication(){ @Override public int multiply(int x, int y){ return x*y; }}; int square = product.square(2); int multiplication = product.multiply(2, 3); product是个匿名类。这段代码有点啰嗦了,用了6行实现一个简单地乘法的功能。我们可以利用lambda表达式来简化一下代码,lambda表达式也是Java 8中新引入的。因为我们的接口只包含一个抽象方法,而且lambda表达式也是SAM(Single Abstract method单一抽象方法)类型的。我们可以用lambda表达式来替代匿名类将代码简化成下面的样子。 123Multiplication lambda = (x, y) -> x*y; int product = lambda.multiply(3, 4); int square = lambda.square(4); 以上就是在接口中使用default方法的例子。现在,你可以毫无顾虑的在旧的接口中扩展新的方法,只要这些方法是default或static的就不用担心会破坏接口的实现类。 123456789101112131415161718192021222324252627282930313233343536373839404142/**Java Program to demonstrate use of default method in Java 8. * You can define non-abstract method by using default keyword, and more * than one default method is permitted, which allows you to ship default skeletal * implementation on interface itself. * @author Javin Paul */ public class Java8DefaultMethodDemo{ public static void main(String args[]) { // Implementing interface using Anonymous class Multiplication product = new Multiplication(){ @Override public int multiply(int x, int y){ return x*y; } }; int squareOfTwo = product.square(2); int cubeOfTwo = product.cube(2); System.out.println(\"Square of Two : \" + squareOfTwo); System.out.println(\"Cube of Two : \" + cubeOfTwo); // Since Multiplication has only one abstract method, it can // also be implemented using lambda expression in Java 8 Multiplication lambda = (x, y) -> x*y; int squareOfThree = lambda.square(3); int cubeOfThree = lambda.cube(3); System.out.println(\"Square of Three : \" + squareOfThree); System.out.println(\"Cube of Three : \" + cubeOfThree); } } interface Multiplication{ int multiply(int a, int b); default int square(int a){ return multiply(a, a); } default int cube(int a){ return multiply(multiply(a, a), a); } } Output : Square of Two : 4 Cube of Two : 8 Square of Three : 9 Cube of Three : 27 这是个很好的关于如何使用default方法在接口中方便的添加方法的例子。也展示了如何避免一个额外的帮助类,比如Collections类。它仅仅提供了一些用于Collection的工具方法,而现在我们可以直接在Collection中定义这些方法。在上面的例子中,我们有一个包含一个抽象方法multiply(a,b)的接口Multiplication,接口还包括两个依赖于multiply(a,b)方法的非抽象方法square(a)和cube(b)。接口的实现类只需要实现multiply(a,b)方法,就可以直接使用square(a)和cube(b)方法了。 default方法的关键点现在让我们来复习我们刚刚学到了什么,记一下关于default方法的关键点。 在Java8中你可以在接口中添加default方法 default方法的出现使得接口和抽象类的不同变得模糊。所以,当在面试中被问到这个问题,别忘了提一下,以前只能通过抽象类实现的事情,现在也可以通过default方法实现了。 default并不是一个新的关键字,在JDK1.1中就是保留关键字 接口中default方法的数量没有限制 如果接口C继承了接口A和B,如果A和B中拥有一样的default方法,编译器在编译过程中会报错。为了避免歧义,这在Java 8中是不允许的。所以当default方法有冲突时,是不可以多继承的 在JDK1.8中有许多关于default方法的例子,比如forEach方法。也可以查看java.util.Map中新添的putIfAbsent方法,在JDK1.8之前,我们只能ConcurrentMap来使用它。 以上就是default方法。不得不说,这是一个巨大的突破,使我们可以更好更方便的使用接口。了解CurrentMap的putIfAbsent方法可以帮助我们更好的记住default方法。在JDK1.7中,putIfAbsent方法并不存在于Map接口中,所以为了使用putIfAbsent方法,必须将Map接口指向的ConcurrentMap对象强制转换成ConcurrentMap。但Java 8引入扩展方法之后,Map接口中也有了putIfAbsent方法。想了解更多的关于Java8的新特性,可以阅读Manning's Java 8 in Action","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"interface","slug":"interface","permalink":"http://yemengying.com/tags/interface/"},{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"【译】以生产者消费者为例阐述如何使用wait,notify和notifyAll","slug":"译-以生产者消费者为例阐述如何使用wait,notify和notifyAll","date":"2015-10-29T06:34:55.000Z","updated":"2018-12-14T09:23:02.000Z","comments":true,"path":"2015/10/29/译-以生产者消费者为例阐述如何使用wait,notify和notifyAll/","link":"","permalink":"http://yemengying.com/2015/10/29/译-以生产者消费者为例阐述如何使用wait,notify和notifyAll/","excerpt":"原文来自一个java大牛的技术博客 地址http://javarevisited.blogspot.com/2015/07/how-to-use-wait-notify-and-notifyall-in.html 博客以生产者和消费者为例 讲解了如何使用wait,notify,notifyAll进行多个线程之间的通信。下面是原文的翻译。 在Java中可以利用use,notify,notifyAll来完成线程之间的通信。举个例子,假设你的程序中有两个线程(eg:Producer(生产者)和Consumer(消费者)),Producer要和Consumer通信,通知Consumer队列中有元素了可以开始消费。相似的,Consumer也需要通知Producer队列中有空闲可以插入元素了。一个线程可以可以在一定条件下调用wait方法暂停什么都不做。比如,在Producer和consumer的问题中,当队列满了时Producer需要调用wait,当队列为空时Consumer需要调用wait方法。如果一些线程在等待某些条件变为真,可以在条件改变时使用notify和notifyAll通知他们并唤醒他们。Notify方法和NotifyAll方法都可以发送通知,不同的是,notify只能向等待的线程中的一个发送通知,不保证接受到通知的是哪个线程,而NotifyAll会向所有线程发送通知。所以如果只有一个线程等待对象锁,notify和notifyAll都会通知到它。在这个java多线程的教程中,将利用生产者,消费者的例子讲述在Java中如何使用wait,notify和notifyAll实现线程内部通信。另外,如果大家对掌握多线程和并发很感兴趣,强烈建议大家阅读Brian Goetz写的Java Concurrency in Practice。如果没看过这本书,你的Java多线程之旅是不完整的🙀。","keywords":null,"text":"原文来自一个java大牛的技术博客 地址http://javarevisited.blogspot.com/2015/07/how-to-use-wait-notify-and-notifyall-in.html 博客以生产者和消费者为例 讲解了如何使用wait,notify,notifyAll进行多个线程之间的通信。下面是原文的翻译。 在Java中可以利用use,notify,notifyAll来完成线程之间的通信。举个例子,假设你的程序中有两个线程(eg:Producer(生产者)和Consumer(消费者)),Producer要和Consumer通信,通知Consumer队列中有元素了可以开始消费。相似的,Consumer也需要通知Producer队列中有空闲可以插入元素了。一个线程可以可以在一定条件下调用wait方法暂停什么都不做。比如,在Producer和consumer的问题中,当队列满了时Producer需要调用wait,当队列为空时Consumer需要调用wait方法。如果一些线程在等待某些条件变为真,可以在条件改变时使用notify和notifyAll通知他们并唤醒他们。Notify方法和NotifyAll方法都可以发送通知,不同的是,notify只能向等待的线程中的一个发送通知,不保证接受到通知的是哪个线程,而NotifyAll会向所有线程发送通知。所以如果只有一个线程等待对象锁,notify和notifyAll都会通知到它。在这个java多线程的教程中,将利用生产者,消费者的例子讲述在Java中如何使用wait,notify和notifyAll实现线程内部通信。另外,如果大家对掌握多线程和并发很感兴趣,强烈建议大家阅读Brian Goetz写的Java Concurrency in Practice。如果没看过这本书,你的Java多线程之旅是不完整的🙀。 在代码中展示如何使用wait和notify尽管wait和notify是相当基础的概念,并且他们定义在Object类中,但要想在代码中使用他们并非易事。你可以在面试中让面试者通过手写代码解决Producer者和Consumer者问题来验证,我相信大多数人都会犯在错误的地方同步,没有在正确的对象上调用wait之类的错。讲真,这些常常会困惑许多程序员。第一个困惑点来自怎样调用wait方法,因为wait方法并不是定义在Thread类中,所以不能简单的Thread.wait()。而许多Java开发者习惯于Thread.sleep(),所以常常错误的想用同样的方式调用wait。实际上,wait()方法需要在一个被两个线程共享的对象上调用,例如在Producer者和消费Consumer的问题中,两个线程共享对象是一个队列。第二个困惑点来自wait方法应该在同步块还是同步方法中调用?如果使用同步块,那么哪个对象应该放到同步块中?这个对象和你想要获得锁的对象应该是同一个。在我们的例子中,这个对象就是两个线程共享的队列。 在循环中使用wait和notify,而不是If代码块中在你已经了解需要在一个共享的对象上调用wait方法后,接下来就是学会避免许多java开发者犯的错—在If代码块中调用wait而不是while循环中。因为需要在一定的条件下调用wait,比如Producer线程要在队列满了的情况下调用wait,所以第一反应都是使用If语句。但是,在If代码块中调用wait会产生bug,因为线程存在一定的可能在等待条件没有改变的情况下假唤醒(spurious wake up)。所以如果没有使用循环在线程唤醒后检查等待条件,可能会造成尝试在已经满了的队列中插入元素或者在空了的队列中取元素。这就是为什么我们要在while循环中调用wait而不是if。 12345678 // The standard idiom for calling the wait method in Java synchronized (sharedObject) { while (condition) { sharedObject.wait();// (Releases lock, and reacquires on wakeup) } ...// do action based upon condition e.g. take or put into queue} 正如我建议的,我们应该在一个循环中调用wait。这个循环用于在线程休眠之前和之后检查condition。 Java中使用wait(),notify(),notifyAll()的例子下面是在Java中使用wait(),notify(),notifyAll()的例子。在这个程序中,有两个线程(PRODUCTOR和CONSUMER),用继承了Thread类的Producer和Consumer类实现。Prodcuer和Consumer的业务逻辑写在他们各自的run()方法中。并且实现一个LinkedList,当做共享队列。Producer在一个死循环中不断在队列中插入随机数,直到队列满了。我们会检查while(queue.size == maxSize),需要注意的是在检查之前需要给队列加上同步锁以保证在检查时没有另一个线程修改队列。如果队列满了,PRODUCER线程就会休眠,直到CONSUMER消费了队列中的元素并且调用notify()方法通知PRODUCER线程。wait和notify都是在共享的对象(我们的例子中是队列)上调用的。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113import java.util.LinkedList;import java.util.Queue;import java.util.Random;/** * Simple Java program to demonstrate How to use wait, notify and notifyAll() * method in Java by solving producer consumer problem. * * @author Javin Paul */public class MultipleThread { public static void main(String args[]) { System.out.println(\"How to use wait and notify method in Java\"); System.out.println(\"Solving Producer Consumper Problem\"); Queue<Integer> buffer = new LinkedList<>(); int maxSize = 10; Thread producer = new Producer(buffer, maxSize, \"PRODUCER\"); Thread consumer = new Consumer(buffer, maxSize, \"CONSUMER\"); producer.start(); consumer.start(); }}/** * Producer Thread will keep producing values for Consumer * to consumer. It will use wait() method when Queue is full * and use notify() method to send notification to Consumer * Thread. * @author WINDOWS 8 * */class Producer extends Thread { private Queue<Integer> queue; private int maxSize; public Producer(Queue<Integer> queue, int maxSize, String name) { super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == maxSize) { try { System.out .println(\"Queue is full, \" + \"Producer thread waiting for \" + \"consumer to take something from queue\"); queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } Random random = new Random(); int i = random.nextInt(); System.out.println(\"Producing value : \" + i); queue.add(i); queue.notifyAll(); } } }}/** * Consumer Thread will consumer values form shared queue. * It will also use wait() method to wait if queue is * empty. It will also use notify method to send * notification to producer thread after consuming values * from queue. * @author WINDOWS 8 **/class Consumer extends Thread { private Queue<Integer> queue; private int maxSize; public Consumer(Queue<Integer> queue, int maxSize, String name){ super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.isEmpty()) { System.out.println(\"Queue is empty,\" + \"Consumer thread is waiting\" + \" for producer thread to put something in queue\"); try { queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } System.out.println(\"Consuming value : \" + queue.remove()); queue.notifyAll(); } } }}Output How to use wait and notify method in Java Solving Producer Consumper Problem Queue is empty,Consumer thread is waiting for producer thread to put something in queue Producing value : -1692411980 Producing value : 285310787 Producing value : -1045894970 Producing value : 2140997307 Producing value : 1379699468 Producing value : 912077154 Producing value : -1635438928 Producing value : -500696499 Producing value : -1985700664 Producing value : 961945684 Queue is full, Producer thread waiting for consumer to take something from queue Consuming value : -1692411980 Consuming value : 285310787 Consuming value : -1045894970 Consuming value : 2140997307 Consuming value : 1379699468 Consuming value : 912077154 Consuming value : -1635438928 Consuming value : -500696499 Consuming value : -1985700664 Consuming value : 961945684 Queue is empty,Consumer thread is waiting for producer thread to put something in queue 为了更好的理解这个程序,我建议大家使用debug模式运行。 使用wait,notify,notifyAll需要注意的 在Java中可以使用wait,notify,notifyAll完成多线程(不仅仅是两个线程)的内部通信。 在同步方法或同步块中使用wait,notify,notifyAll,否则JVM会抛出IllegalMonitorStateException 在循环中调用wait,notify。 在线程共享的对象上调用wait 偏向选择notifyAll,而不是notify,原因在这篇文章里","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"},{"name":"thread","slug":"java/thread","permalink":"http://yemengying.com/categories/java/thread/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"使用基于注解的mybatis时,利用反射和注解生成sql语句","slug":"基于注解的mybatis插入对象时利用反射生成sql语句","date":"2015-10-28T05:21:29.000Z","updated":"2018-12-14T09:23:55.000Z","comments":true,"path":"2015/10/28/基于注解的mybatis插入对象时利用反射生成sql语句/","link":"","permalink":"http://yemengying.com/2015/10/28/基于注解的mybatis插入对象时利用反射生成sql语句/","excerpt":"在开发时遇到一个问题,在使用基于注解的mybatis插入一个对象到mysql时,在写sql语句时需要列出对象的所有属性,所以在插入一个拥有10个以上属性的对象时sql语句就会变得很长,写起来也很不方便,也很容易拼错。google了一下也没有找到什么解决方式(可能是姿势不对😜),在stackoverflow上提的问题截止目前还没有人回答。所以自己想了一个基于反射和注解的解决办法git地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-spring-mybatis-utils-a555c91\", \"giraffe0813\", \"spring-mybatis-utils\", \"a555c91\", false);","keywords":null,"text":"在开发时遇到一个问题,在使用基于注解的mybatis插入一个对象到mysql时,在写sql语句时需要列出对象的所有属性,所以在插入一个拥有10个以上属性的对象时sql语句就会变得很长,写起来也很不方便,也很容易拼错。google了一下也没有找到什么解决方式(可能是姿势不对😜),在stackoverflow上提的问题截止目前还没有人回答。所以自己想了一个基于反射和注解的解决办法git地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-spring-mybatis-utils-a555c91\", \"giraffe0813\", \"spring-mybatis-utils\", \"a555c91\", false); 下面是之前的代码片段: 123@Insert(\"insert into poi_shop(name,brand,tags,status,phone,mobile,business_time,address,city,lng,lat,business_type,attribute_json) values(#{name},#{brand},#{tags},#{status},#{phone},#{mobile},#{business_time},#{address},#{city},#{lng},#{lat},#{business_type},#{attribute_json})\")@Options(useGeneratedKeys = true, keyProperty = \"id\", keyColumn = \"id\")public Long insertPoiInfo(PoiBo poiBo); 是不是too looooooooooooong: 第一版(利用反射)首先想到的是可以利用反射获得对象的所有属性,然后拼接成sql语句。所以写了一个基于反射拼装sql语句的方法,然后基于mybatis动态获得sql语句的方式 获得完整的sql 具体的代码如下:接口层改为下面的样子,sql语句的生成放到PoiSqlProvider的insertPoiBo方法中 12@InsertProvider(type = PoiSqlProvider.class, method = \"insertPoiBo\")public Long insertPoiInfo(@Param(\"poiBo\")PoiBo poiBo); PoiSqlProvider.class 1234567891011121314151617181920212223242526272829303132333435363738 public String insertPoiBo(Map<String,Object> map){ PoiBo poiBo = (PoiBo)map.get(\"poiBo\"); StringBuilder sql = new StringBuilder(\"insert into poi_shop \"); //get sql via reflection Map<String,String> sqlMap = getAllPropertiesForSql(poiBo, \"poiBo\"); // sql.append(sqlMap.get(\"field\")).append(sqlMap.get(\"value\")); System.out.println(sql.toString()); return sql.toString(); }//根据传入的对象 基于反射生成两部分sql语句 private Map<String,String> getAllPropertiesForSql(Object obj, String objName){ Map<String,String> map = new HashMap<String,String>(); if(null == obj) return map; StringBuilder filedSql = new StringBuilder(\"(\"); StringBuilder valueSql = new StringBuilder(\"value (\"); Field[] fields = obj.getClass().getDeclaredFields(); for (int i = 0; i < fields.length; i++) { filedSql.append(fields[i].getName() + \",\"); valueSql.append(\"#{\" + objName + \".\" + fields[i].getName() + \"},\"); } //remove last ',' valueSql.deleteCharAt(valueSql.length() - 1); filedSql.deleteCharAt(filedSql.length() - 1); valueSql.append(\") \"); filedSql.append(\") \"); map.put(\"field\",filedSql.toString()); map.put(\"value\", valueSql.toString()); System.out.println(\"database filed sql: \" + filedSql.toString()); System.out.println(\"value sql:\" + valueSql.toString()); return map; } 下面是基于反射生成的两部分sq语句和最后拼接的语句 123456789database filed sql: (id,name,brand,tags,status,phone,mobile,business_time,address,city,lng,lat,business_type,attribute_json,updated_at,created_at) value sql:value(#{poiBo.id},#{poiBo.name},#{poiBo.brand},#{poiBo.tags},#{poiBo.status},#{poiBo.phone},#{poiBo.mobile},#{poiBo.business_time},#{poiBo.address},#{poiBo.city},#{poiBo.lng},#{poiBo.lat},#{poiBo.business_type},#{poiBo.attribute_json},#{poiBo.updated_at},#{poiBo.created_at}) insert into poi_shop (id,name,brand,tags,status,phone,mobile,business_time,address,city,lng,lat,business_type,attribute_json,updated_at,created_at) value (#{poiBo.id},#{poiBo.name},#{poiBo.brand},#{poiBo.tags},#{poiBo.status},#{poiBo.phone},#{poiBo.mobile},#{poiBo.business_time},#{poiBo.address},#{poiBo.city},#{poiBo.lng},#{poiBo.lat},#{poiBo.business_type},#{poiBo.attribute_json},#{poiBo.updated_at},#{poiBo.created_at}) 要注意的是如果数据库的字段名和插入对象的属性名不一致,那么不能使用生成的database filed sql。 最终版(加入注解)上面的getAllPropertiesForSql方法有个缺点,如果数据库的字段名和类的属性名不一致,就不能依靠反射获得sql了。所以借鉴老大的ORM框架也写了一个注解Column,用于model类的属性上,表明属性所对应数据库字段。下面是Column注解的snippet。 123456789101112131415161718import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/* 定义字段的注解*/@Retention(RetentionPolicy.RUNTIME)/*该注解只能用在成员变量上*/@Target(ElementType.FIELD)public @interface Column { /** * 用来存放字段的名字 如果未指定列名,默认列名使用成员变量名 * * @return */ String name() default \"\"; } 之后在model类属性上加入对应的注解,省略getter和setter。Column的name为空时,代表属性名和字段名一致。 1234567891011121314151617181920212223242526272829303132333435363738public class PoiBo { @Column private Long id; @Column(name = \"poi_name\") private String name;//表示name属性对应数据库poi_name字段 @Column(name = \"poi_brand\") private String brand;//表示brand属性对应数据库poi_brand字段 @Column private String tags; @Column private Integer status; @Column private String phone; @Column private String mobile; @Column private String business_time; @Column private Float average_price; @Column private String address; @Column private String city; @Column private Double lng; @Column private Double lat; @Column private String business_type; @Column private String attribute_json; @Column private Timestamp updated_at; @Column private Timestamp created_at; } 修改getAllPropertiesForSql方法,通过获取类属性上的注解获得数据库字段名。 12345678910111213141516171819202122232425262728293031323334353637383940private Map<String,String> getAllPropertiesForSql(Object obj, String objName){ Map<String,String> map = new HashMap<String,String>(); if(null == obj) return map; StringBuilder filedSql = new StringBuilder(\"(\"); StringBuilder valueSql = new StringBuilder(\"value (\"); Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { // 判断该成员变量上是不是存在Column类型的注解 if (!field.isAnnotationPresent(Column.class)) { continue; } Column c = field.getAnnotation(Column.class);// 获取实例 // 获取元素值 String columnName = c.name(); // 如果未指定列名,默认列名使用成员变量名 if (\"\".equals(columnName.trim())) { columnName = field.getName(); } filedSql.append(columnName + \",\"); valueSql.append(\"#{\" + objName + \".\" + field.getName() + \"},\"); } //remove last ',' valueSql.deleteCharAt(valueSql.length() - 1); filedSql.deleteCharAt(filedSql.length() - 1); valueSql.append(\") \"); filedSql.append(\") \"); map.put(\"field\",filedSql.toString()); map.put(\"value\", valueSql.toString()); System.out.println(\"database filed sql: \" + filedSql.toString()); System.out.println(\"value sql:\" + valueSql.toString()); return map; }` 利用反射+注解之后的输出结果,可以看到sql语句正确按照name的Column注解的输出了name属性对应的数据库字段是poi_name. 123456789database filed sql: (id,poi_name,poi_brand,tags,status,phone,mobile,business_time,average_price,address,city,lng,lat,business_type,attribute_json,updated_at,created_at) value sql:value(#{poiBo.id},#{poiBo.name},#{poiBo.brand},#{poiBo.tags},#{poiBo.status},#{poiBo.phone},#{poiBo.mobile},#{poiBo.business_time},#{poiBo.average_price},#{poiBo.address},#{poiBo.city},#{poiBo.lng},#{poiBo.lat},#{poiBo.business_type},#{poiBo.attribute_json},#{poiBo.updated_at},#{poiBo.created_at}) insert into poi_shop (id,poi_name,poi_brand,tags,status,phone,mobile,business_time,average_price,address,city,lng,lat,business_type,attribute_json,updated_at,created_at) value (#{poiBo.id},#{poiBo.name},#{poiBo.brand},#{poiBo.tags},#{poiBo.status},#{poiBo.phone},#{poiBo.mobile},#{poiBo.business_time},#{poiBo.average_price},#{poiBo.address},#{poiBo.city},#{poiBo.lng},#{poiBo.lat},#{poiBo.business_type},#{poiBo.attribute_json},#{poiBo.updated_at},#{poiBo.created_at}) 写的好累放张萌图吧","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"annotation","slug":"annotation","permalink":"http://yemengying.com/tags/annotation/"},{"name":"mybatis","slug":"mybatis","permalink":"http://yemengying.com/tags/mybatis/"},{"name":"reflection","slug":"reflection","permalink":"http://yemengying.com/tags/reflection/"}]},{"title":"【译】如何重置一个ArrayList--clear vs removeAll","slug":"译-如何重置一个ArrayList-clear-vs-removeAll","date":"2015-10-26T15:20:04.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2015/10/26/译-如何重置一个ArrayList-clear-vs-removeAll/","link":"","permalink":"http://yemengying.com/2015/10/26/译-如何重置一个ArrayList-clear-vs-removeAll/","excerpt":"安利一个APP–开发者头条,在上面发现一个不错的英文技术类博客,地址http://javarevisited.blogspot.com/, 会不定期的翻译一些 翻译不好见谅啊😼 原文地址:http://javarevisited.blogspot.co.uk/2015/09/how-to-reset-arraylist-in-java-clear-vs-removeAll-example.html 很多时候为了重用我们会想要重置一个ArrayList,这里的重置是指清空列表或移除列表所有的元素。在Java中,有两个方法可以帮助我们实现重置clear或removeAll。在列表长度很小的情况下(eg:10或100个元素),可以放心的使用这两种方法。但如果列表很大(eg:10M个元素),那么选择clear还是removeAll会对你java应用的性能造成巨大的影响。甚至有时,在列表过大的情况下,重置会耗费许多时间,那么重新创建一个新的列表比将老的列表重置要好。但需要提醒的是,必须要确保老的列表可以被垃圾回收,否则,有很大的风险会出现java.lang.OutOfMemoryError: Java Heap Space。言归正传,让我们看看clear()和removeAll()两个方法。大家应该常常会选择用clear(),因为他的复杂度是O(n),而相比之下,removeAll(Collection C)的性能要差一些,它的复杂度是O(n^2)。这也是为什么在重置大的列表的时候两个方法会有巨大的差异。如果阅读他们的源码并运行下面的例子程序,差异会更明显。","keywords":null,"text":"安利一个APP–开发者头条,在上面发现一个不错的英文技术类博客,地址http://javarevisited.blogspot.com/, 会不定期的翻译一些 翻译不好见谅啊😼 原文地址:http://javarevisited.blogspot.co.uk/2015/09/how-to-reset-arraylist-in-java-clear-vs-removeAll-example.html 很多时候为了重用我们会想要重置一个ArrayList,这里的重置是指清空列表或移除列表所有的元素。在Java中,有两个方法可以帮助我们实现重置clear或removeAll。在列表长度很小的情况下(eg:10或100个元素),可以放心的使用这两种方法。但如果列表很大(eg:10M个元素),那么选择clear还是removeAll会对你java应用的性能造成巨大的影响。甚至有时,在列表过大的情况下,重置会耗费许多时间,那么重新创建一个新的列表比将老的列表重置要好。但需要提醒的是,必须要确保老的列表可以被垃圾回收,否则,有很大的风险会出现java.lang.OutOfMemoryError: Java Heap Space。言归正传,让我们看看clear()和removeAll()两个方法。大家应该常常会选择用clear(),因为他的复杂度是O(n),而相比之下,removeAll(Collection C)的性能要差一些,它的复杂度是O(n^2)。这也是为什么在重置大的列表的时候两个方法会有巨大的差异。如果阅读他们的源码并运行下面的例子程序,差异会更明显。 Clear() vs RemoveAll(Collection c)为了更好的比较这两个方法,阅读他们源码是很重要的。可以在java.utils.ArrayList类中找到clear()方法,不过为了方便我将它引入到了这里。下面的代码来自JDK 1.7.0_40版本。如果你想要学习更多的有关性能监控和调优的知识,我强烈建议阅读Scott Oaks写的Java Performance the Definitive Guide,它包含了java 7和一点java 8。下面是clear()的代码片段: 12345678910/** * Removes all of the elements from this list.The list will * be empty after this call returns. */ public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } 大家可以看出,clear()在循环遍历ArrayList,并且将每一个元素都置为null,使它们在没有被外部引用的情况下可以被垃圾回收。相似的,我们可以在java.util.AbstractCollection类中查看removeAll(Collention c)的代码,下面是代码片段: 12345678910111213public boolean removeAll(Collection<?> c) { //判断对象是否为null Objects.requireNonNull(c); boolean modified = false; Iterator<?> it = iterator(); while (it.hasNext()) { if (c.contains(it.next())) { it.remove(); modified = true; } } return modified; } 这个方法会检查迭代器顺序返回的每个元素是否包含在特定的集合中。如果存在,调用迭代器的remove方法将它从集合中移除。因为会用到contains方法,removeAll的复杂度是O(n^2)。所以在想要重置一个大的ArrayList时,这种方法是绝对不可取的。下面我们比较一下两者在重置一个包含100K个元素时的性能差异。 删除一个包含100k个元素的列表中的所有元素我本来想在例子中尝试重置一个包含10M个元素的列表,不过在超过半个小时等待removeAll()结束后,我决定将元素的数量降为100K。在这种情况下,两个方法的差距也是很明显的。removeAll()比clear()多花费了10000倍的时间。事实上,在API中clear()和removeAll(Collection c)这两个方法的目的是不同的。clear()方法是为了通过删除所有元素而重置列表,而removeAll(Collection c)是为了从集合中删除某些存在于另一个提供的集合中的元素,并不是为了从集合中移除所有元素。所以如果你的目的是删除所有元素,用clear(),如果你的目的是删除某些存在于另一集合的元素,那么选择removeAll(Collection c)方法。 1234567891011121314151617181920212223242526272829303132333435import java.util.ArrayList; /** * Java Program to remove all elements from list in Java and comparing * performance of clearn() and removeAll() method. * * @author Javin Paul */ public class ArrayListResetTest { private static final int SIZE = 100_000; public static void main(String args[]) { // Two ArrayList for clear and removeAll ArrayList numbers = new ArrayList(SIZE); ArrayList integers = new ArrayList(SIZE); // Initialize ArrayList with 10M integers for (int i = 0; i &lt; SIZE; i++) { numbers.add(new Integer(i)); integers.add(new Integer(i)); } // Empty ArrayList using clear method long startTime = System.nanoTime(); numbers.clear(); long elapsed = System.nanoTime() - startTime; System.out.println(\"Time taken by clear to empty ArrayList of 1M elements (ns): \" + elapsed); // Reset ArrayList using removeAll method startTime = System.nanoTime(); integers.removeAll(integers); long time = System.nanoTime() - startTime; System.out.println(\"Time taken by removeAll to reset ArrayList of 1M elements (ns): \" + time); } } Output: Time taken by clear to empty ArrayList of 100000 elements (ns): 889619 Time taken by removeAll to reset ArrayList of 100000 elements (ns): 36633112126 由于程序使用了两个arrayList存储Integers,所以在运行时要确保有足够的内存,尤其是你想比较在列表存有1M个元素时,两种方法的性能差异。另外,由于使用了在数字中加入下划线的特性,所以需要java7来运行。如果没有JDK7,也可以移除SIZE常量中的下划线。 以上就是关于如何重置一个ArrayList的内容。我们不仅仅学到了两种从列表中删除元素的方法,也学到了clear()和removeAll()方法的区别。我们明白了为什么在列表过大时,removeAll()性能很差。 PS:当使用clear()方法也消耗很长的时间时,考虑创建一个新的列表,因为java可以很快的创建一个新的对象。 扩展阅读: ArrayList and HashMap Performance Improvement in JDK 7 How to convert ArrayList to Set? How to sort an ArrayList in reverse order in Java? How to remove duplicate elements from ArrayList in Java? How to clone an ArrayList in Java? How do you convert a Map to List in Java? Performance comparison of contains() vs binarySearch() How to initialize an ArrayList with values in Java? The ArrayList Guide The difference between an ArrayList and a Vector in Java? How to make an ArrayList unmodifiable in Java?","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"},{"name":"list","slug":"java/list","permalink":"http://yemengying.com/categories/java/list/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"翻译","slug":"翻译","permalink":"http://yemengying.com/tags/翻译/"}]},{"title":"Mac系统下Idea和STS的快捷键对比","slug":"idea-sts","date":"2015-10-23T08:15:42.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2015/10/23/idea-sts/","link":"","permalink":"http://yemengying.com/2015/10/23/idea-sts/","excerpt":"","keywords":null,"text":"又被朋友安利了一遍Idea 所以决定尝试着把IDE由STS切换成Idea。不过发现好多快捷键都不一致,所以在熟悉Idea的过程中顺便记录一下两者的常用快捷键对比 STS(Spring Tool Suite) Idea Run shift+command+F11 shift + control + r Debug command + F11 shift + control + d 复制当前行 alt + command + 向下方向键 command + d 剪贴当前行 command + x command + x 删除当前行 command + d command + x 搜索 command + f command + f 搜索替换 command + f command + r Preferences command + , command + , 光标移到代码块最后 option+command+] 光标移到代码块最前 option+command+[ Rename alt+command+r shift+F6 所选语句上移 option+向上方向键 shift+command+向上方向键 所选语句下移 option+向下方向键 shift+command+向下方向键 选择实现父类的方法 control+o generate(setter/constructor/toString) option+command+s command+n step over F6 F8 step into F5 F7 step out F7 shift+F8 stop option+command+s command+F2","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"IDE","slug":"IDE","permalink":"http://yemengying.com/tags/IDE/"}]},{"title":"使代码更简洁(三)---利用Builder Pattern为对象属性赋值","slug":"cleancode-builder","date":"2015-09-26T07:18:12.000Z","updated":"2018-12-14T09:24:54.000Z","comments":true,"path":"2015/09/26/cleancode-builder/","link":"","permalink":"http://yemengying.com/2015/09/26/cleancode-builder/","excerpt":"以前写在segmentFault上的一篇文章,搬移到这里 有一个有很多属性的类,在为它的属性赋值时,通常有两种方式,使用构造函数和使用set方法。可是使用构造函数有时会忘了各个字段的顺序 ,直接使用set方法,又比较麻烦。所以同事提出可以使用方法链。","keywords":null,"text":"以前写在segmentFault上的一篇文章,搬移到这里 有一个有很多属性的类,在为它的属性赋值时,通常有两种方式,使用构造函数和使用set方法。可是使用构造函数有时会忘了各个字段的顺序 ,直接使用set方法,又比较麻烦。所以同事提出可以使用方法链。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273public class User { private int id; private String name; private int age; private int sex; private int cityId; private int buId; private int roleId; private String pinyinName; public String getPinyinName() { return pinyinName; } public void setPinyinName(String pinyinName) { this.pinyinName = pinyinName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public int getCityId() { return cityId; } public void setCityId(int cityId) { this.cityId = cityId; } public int getBuId() { return buId; } public void setBuId(int buId) { this.buId = buId; } public int getRoleId() { return roleId; } public void setRoleId(int roleId) { this.roleId = roleId; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User(int id, String name, int age, int sex, int cityId, int buId, int roleId, String pinyinName) { super(); this.id = id; this.name = name; this.age = age; this.sex = sex; this.cityId = cityId; this.buId = buId; this.roleId = roleId; this.pinyinName = pinyinName; } } 类似于StringBuilder的append方法 12String s = new StringBuilder().append(\"0\").append(1) .append(\" 2 \").append(3).toString(); 让bean的每个属性的set方法都返回一个对象本身的引用,将User类的set方法改写成下面的样子: 123456789101112131415161718192021222324252627282930313233343536373839public User setId(int id) { this.id = id; return this; } public User setName(String name) { this.name = name; return this; } public User setAge(int age) { this.age = age; return this; } public User setSex(int sex) { this.sex = sex; return this; } public User setCityId(int cityId) { this.cityId = cityId; return this; } public User setBuId(int buId) { this.buId = buId; return this; } public User setRoleId(int roleId) { this.roleId = roleId; return this; } public User setPinyinName(String pinyinName) { this.pinyinName = pinyinName; return this; } 这样在对User的属性赋值时就简洁了许多。 1234567User user = new User().setId(1).setAge(18) .setBuId(127) .setRoleId(12) .setName(\"giraffe\") .setCityId(12) .setSex(1) .setPinyinName(\"gif\"); 大部分IDE默认生成无返回值的setter,不过Idea也支持生成返回this对象的方式。在生成setter时把template由default改为builder。如下图: 不过不知道这样写会不会有什么不好的地方~~","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"}]},{"title":"spring结合rabbitmq---简单的demo","slug":"spring-rabbitmq-demo","date":"2015-09-19T06:55:43.000Z","updated":"2018-12-14T09:25:27.000Z","comments":true,"path":"2015/09/19/spring-rabbitmq-demo/","link":"","permalink":"http://yemengying.com/2015/09/19/spring-rabbitmq-demo/","excerpt":"在做搜索服务时,当业务方数据源改变时,需要改变搜索引擎中索引的数据。可以定时拉取也可以实时推送。为了实现同步更新,选择了实时推送。实时推送也有两种方式,一种是提供索引更新接口供业务方调用,在接口中将变化的数据更新到搜索引擎中;另一种是使用消息队列,业务方在数据改变时,将改变的数据插入队列,服务端的消费者实时监听队列,并进行索引更新。考虑到使用消息队列有三点好处,选择了第二种方式。使用消息队列的好处: 一,使用消息队列可以异步处理更新操作降低接口响应时间;二,对于消费端由于不可预测原因导致消息无法处理时,数据可以暂存在队列中,等消费者服务恢复后可以继续处理历史数据,提高可用性;三,将业务方的服务和索引更新服务解耦。下面简单记录一下一个简单的spring+rabbitmq的demo实现,只实现了简单的消息生产和消费。 参考了下面几篇博客:http://syntx.io/getting-started-with-rabbitmq-using-the-spring-framework/https://blog.codecentric.de/en/2011/04/amqp-messaging-with-rabbitmq/http://wb284551926.iteye.com/blog/2212869","keywords":null,"text":"在做搜索服务时,当业务方数据源改变时,需要改变搜索引擎中索引的数据。可以定时拉取也可以实时推送。为了实现同步更新,选择了实时推送。实时推送也有两种方式,一种是提供索引更新接口供业务方调用,在接口中将变化的数据更新到搜索引擎中;另一种是使用消息队列,业务方在数据改变时,将改变的数据插入队列,服务端的消费者实时监听队列,并进行索引更新。考虑到使用消息队列有三点好处,选择了第二种方式。使用消息队列的好处: 一,使用消息队列可以异步处理更新操作降低接口响应时间;二,对于消费端由于不可预测原因导致消息无法处理时,数据可以暂存在队列中,等消费者服务恢复后可以继续处理历史数据,提高可用性;三,将业务方的服务和索引更新服务解耦。下面简单记录一下一个简单的spring+rabbitmq的demo实现,只实现了简单的消息生产和消费。 参考了下面几篇博客:http://syntx.io/getting-started-with-rabbitmq-using-the-spring-framework/https://blog.codecentric.de/en/2011/04/amqp-messaging-with-rabbitmq/http://wb284551926.iteye.com/blog/2212869 本地安装rabbitmq只介绍mac系统下如何安装 安装brew1ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 利用brew安装rabbitmq1.输入命令 1brew install rabbitmq 2.添加配置 1export PATH=$PATH:$(brew --prefix)/sbin 3.可以禁用用不着的插件 12rabbitmq-plugins disable --offline rabbitmq_stomprabbitmq-plugins disable --offline rabbitmq_mqtt 4.启动rabbitmq server 直接输入下面的命令 1rabbitmq-server 5.如果打印出下面的东东就是启动成功了,默认端口是5672 实现demo首先要有个可以运行的spring + maven的项目 添加maven依赖在pom文件中添加rabbitmq相关的依赖 12345678910<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>3.5.4</version></dependency><dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>1.5.0.RELEASE</version></dependency> 添加配置文件12345678910111213141516171819202122232425262728293031323334<?xml version=\"1.0\" encoding=\"UTF-8\"?><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:rabbit=\"http://www.springframework.org/schema/rabbit\" xmlns:p=\"http://www.springframework.org/schema/p\" xsi:schemaLocation=\"http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd\"> <!-- 扫描包 --> <context:component-scan base-package=\"ymy.com.rabbitmq.demo.service.impl.*\" /> <context:annotation-config /> <!-- 连接本地rabbitmq --> <rabbit:connection-factory id=\"connectionFactory\" host=\"localhost\" port=\"5672\" /> <rabbit:admin connection-factory=\"connectionFactory\" id=\"amqpAdmin\" /> <!-- queue 队列声明 --> <rabbit:queue id=\"rabbit_queue_one\" durable=\"true\" auto-delete=\"false\" exclusive=\"false\" name=\"rabbit_queue_one\" /> <!-- exchange queue binging key 绑定 --> <rabbit:direct-exchange name=\"mq-exchange\" durable=\"true\" auto-delete=\"false\" id=\"mq-exchange\"> <rabbit:bindings> <rabbit:binding queue=\"rabbit_queue_one\" key=\"rabbit_queue_one\" /> </rabbit:bindings> </rabbit:direct-exchange> <!-- spring template声明 --> <rabbit:template exchange=\"mq-exchange\" id=\"amqpTemplate\" connection-factory=\"connectionFactory\" /></beans> 生产者开发(插入消息)123456789101112131415161718192021222324package ymy.com.rabbitmq.demo.service.impl;import org.springframework.amqp.core.AmqpAdmin;import org.springframework.amqp.core.AmqpTemplate;import org.springframework.amqp.core.Message;import org.springframework.amqp.rabbit.connection.ConnectionFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service(\"messageProductorService\")public class MessageProductorService { @Autowired private AmqpAdmin admin; @Autowired private AmqpTemplate amqpTemplate; @Autowired private ConnectionFactory connectionFactory; public void pushToMessageQueue(String routingKey, String message) { amqpTemplate.convertAndSend(routingKey, message); }} 消费者开发有两种方式从消息队列中获取消息,一种是自己调用receive方法获得,一种是为队列配置监听类,每当监听的队列中有消息产生,就会被监听的类去除。第一种方式: 12345678910111213141516package ymy.com.rabbitmq.demo.service.impl;import org.springframework.amqp.core.AmqpTemplate;import org.springframework.amqp.core.Message;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service(\"messageConsumerService\")public class MessageConsumerService { @Autowired private AmqpTemplate amqpTemplate; public void popMessage(String destinationQueueName) { Message message = amqpTemplate.receive(destinationQueueName); System.out.println(new String(message.getBody())); }} junit test 12345@Testpublic void testMessageQueueManager(){ messageProductor.pushToMessageQueue(\"rabbit_queue_one\", \"hello giraffe\"); messageConsumer.popMessage(\"rabbit_queue_one\");} 第二种方式配置中添加监听 1234<!-- queue litener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 taskExecutor这个需要自己实现一个连接池 按照官方说法 除非特别大的数据量 一般不需要连接池--> <rabbit:listener-container connection-factory=\"connectionFactory\" acknowledge=\"auto\" > <rabbit:listener queues=\"rabbit_queue_one\" ref=\"messageConsumerService\"/> </rabbit:listener-container> 消费者类需要实现MessageListener 并实现onMessage方法,当监听的队列中有消息进入时,onMessage方法会被调用 1234567891011121314package ymy.com.rabbitmq.demo.service.impl;import org.springframework.amqp.core.Message;import org.springframework.stereotype.Service;import org.springframework.amqp.core.MessageListener;@Service(\"messageConsumerService\")public class MessageConsumerService implements MessageListener{ @Override public void onMessage(Message message) { System.out.println(\"成功取出消息\" + new String(message.getBody())); }} git 地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-spring-rabbit-demo-617348a\", \"giraffe0813\", \"spring-rabbit-demo\", \"617348a\", false);","raw":null,"content":null,"categories":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/categories/spring/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"},{"name":"rabbitMq","slug":"rabbitMq","permalink":"http://yemengying.com/tags/rabbitMq/"}]},{"title":"spring事务管理总结","slug":"spring-transaction","date":"2015-09-12T06:44:29.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2015/09/12/spring-transaction/","link":"","permalink":"http://yemengying.com/2015/09/12/spring-transaction/","excerpt":"以前写在segmentFault上的一篇文章 搬移到这里。 在项目开发过程中经常会使用事务来确保数据的一致性。根据网上的资料整理一下在spring中配置事务的几种方式。无论是哪种方式都需要在配置文件中配置连接池和事务管理器,代码如下。","keywords":null,"text":"以前写在segmentFault上的一篇文章 搬移到这里。 在项目开发过程中经常会使用事务来确保数据的一致性。根据网上的资料整理一下在spring中配置事务的几种方式。无论是哪种方式都需要在配置文件中配置连接池和事务管理器,代码如下。 12345678910111213141516171819202122232425 <!-- 读取配置文件 --> <bean class=\"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer\"> <property name=\"locations\"> <list> <value>classpath:database.properties</value> <value>classpath:service.properties</value> </list> </property> <property name=\"fileEncoding\" value=\"UTF-8\" /> <property name=\"ignoreResourceNotFound\" value=\"false\" /></bean> <!--连接池 --><bean id=\"dataSource\" class=\"org.springframework.jdbc.datasource.DriverManagerDataSource\"> <property name=\"driverClassName\" value=\"${db.driver}\" /> <property name=\"url\" value=\"${db.url}\" /> <property name=\"username\" value=\"${db.username}\" /> <property name=\"password\" value=\"${db.password}\" /></bean><!-- 配置事务管理器 --><bean id=\"transactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <property name=\"dataSource\" ref=\"dataSource\" /></bean> 声明式事务管理基于AspectJ的XML方式的配置这是我觉得最好的方式,基于aop配置,当新增的方法要使用事务管理时,无需修改代码。首先在配置文件xml中引入aop和tx的命名空间123456xmlns:tx=\"http://www.springframework.org/schema/tx\" xmlns:aop=\"http://www.springframework.org/schema/aop\"xsi:schemaLocation=\"http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd\" 然后在xml中加入aop的配置,下面的配置就是在services的切入点上应用txAdvice的增强,services的切入点就是ymy.com.service.impl包下的所有方法应用txAdvice的增强。然后txAdvice是在所有以create,add,delete,update,change开头的方法上加上事务管理。 12345678910111213141516171819202122232425262728 <!-- 定义事务通知 (事务的增强)--><tx:advice id=\"txAdvice\" transaction-manager=\"transactionManager\"> <!-- 定义方法的过滤规则 --> <tx:attributes> <!-- 所有方法都使用事务 --> <!-- propagation:事务传播行为 isolation:事务隔离 read-only:只读 rollback-for:发生哪些异常回滚 no-rollback-for:发生哪些异常不回滚 timeout:过期信息 --> <tx:method name=\"create*\" propagation=\"REQUIRED\"/> <tx:method name=\"add*\" propagation=\"REQUIRED\"/> <tx:method name=\"delete*\" propagation=\"REQUIRED\"/> <tx:method name=\"update*\" propagation=\"REQUIRED\"/> <tx:method name=\"change*\" propagation=\"REQUIRED\"/> </tx:attributes></tx:advice> <!-- 定义AOP配置 配置切面 --><aop:config> <!-- 定义一个切入点 --> <aop:pointcut expression=\"execution (* ymy.com.service.impl.*.*(..))\" id=\"services\"/> <!-- 对切入点和事务的通知,进行适配 --> <aop:advisor advice-ref=\"txAdvice\" pointcut-ref=\"services\"/></aop:config> 采用这种方式配置,当方法是按照事务定义的规则命名时,都会加入事务管理。 基于注解这种方式是我觉得最简单的,第二推荐。要采用注解的方式,需要在配置文件中开启注解事务。 12<!-- 开启注解事务 --><tx:annotation-driven transaction-manager=\"transactionManager\"/> 在使用时只需在对应的类上添加注解@Transactional即可 12345@Service@Transactionalpublic class TaskService implements ITaskService {} 也可在使用注解时定义事物的传播级别 隔离行为等。。 1@Transactional(propagation=Propagation.REQUIRED) 基于TransactionProxyFactoryBean这种方式配置比较麻烦,需要为每一个需要事务管理的类配置一个代理类,不推荐使用。例如我要对taskService进行事务管理,需要如下配置,用代理类对目标类进行增强。 12345678910111213<!-- 配置service层的代理 --><bean id = \"taskServiceProxy\" class=\"org.springframework.transaction.interceptor.TransactionProxyFactoryBean\"> <!-- 配置目标对象 --> <property name = \"target\" ref=\"taskService\"></property> <!-- 注入事务管理器 --> <property name = \"transactionManager\" ref=\"transactionManager\"></property> <!-- 设置需要事务管理的方法 --> <property name=\"transactionAttributes\"> <props> <prop key=\"update*\">PROPAGATION_REQUIRED</prop> </props> </property></bean> 之后在注入service类时,就要注入它的代理类。 12@Resource(name = \"taskServiceProxy\")private ITaskService taskSerivce; 编程式事务管理超级不推荐,需要为每个类注入事务模板,然后在需要事务管理的方法中使用事务模板。 123456789101112private TransactionTemplate transactionTemplate;public void test(){ transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //进行事务相应的操作。。。 //方法一... //方法二... } }); }","raw":null,"content":null,"categories":[{"name":"spring","slug":"spring","permalink":"http://yemengying.com/categories/spring/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://yemengying.com/tags/spring/"},{"name":"transaction","slug":"transaction","permalink":"http://yemengying.com/tags/transaction/"},{"name":"spring-aop","slug":"spring-aop","permalink":"http://yemengying.com/tags/spring-aop/"}]},{"title":"使代码更简洁(二)---集合转换相关","slug":"cleancode-collection","date":"2015-09-11T06:34:33.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2015/09/11/cleancode-collection/","link":"","permalink":"http://yemengying.com/2015/09/11/cleancode-collection/","excerpt":"以前在segmentFault上的一篇文章 搬移到这里。 记录一下在工作开发中封装的一些工具类,使代码看起来更加的简洁。这篇就记录下和集合转换相关的吧。。。。。会持续记录。。。。","keywords":null,"text":"以前在segmentFault上的一篇文章 搬移到这里。 记录一下在工作开发中封装的一些工具类,使代码看起来更加的简洁。这篇就记录下和集合转换相关的吧。。。。。会持续记录。。。。 list转map 开发过程中经常会碰到需要将list转为map的情况,例如有一个User类,有id,name,age等属性。有一个User的list,为了很方便的获取指定id的User,这时就需要将List< User>转换为Map,其中map的key是User的id。一般的做法,是通过for循环将list中的元素put到map中,代码如下: 1234Map<Integer, User> map = new HashMap<Integer, User>();for(User user : userList){ map.put(user.getId(), user);} 这样做,在每个需要将list转为map的地方,都要写一遍for循环,代码不够简洁,所以利用stream和泛型封装了一个通用的工具方法 1234567891011121314public class TransFormUtils { /** * 将list转为map * @param list * @param predicate1 key * @param predicate2 value * @return */ public static<K,V,T> Map<K, V> transformToMap(List<T> list,Function<T, K> predicate1, Function<T,V> predicate2){ return list.stream().collect(Collectors.toMap(predicate1, predicate2)); }} 这样如果需要将List< User>转为Map代码如下 12//省略list构造过程Map<Integer, User> map = TransFormUtils.transformToMap(userList, p->p.getId(), p->p); 如果需要将List< User>转为Map代码如下 12//省略list构造过程Map<Integer, String> map2 = TransFormUtils.transformToMap(userList, p->p.getId(), p->p.getName()); 应用封装好的工具类 只需要一行代码就可以完成list到map的转换,程序简单了许多~~ list< T >转map< K,List< V>>将开发中经常需要根据list中的某个属性将list分类。举个例子,在开发通知中心时需要给用户推送消息,安卓和ios是调用的不同的第三方库,所以要根据设备的类型调用不同的方法。首先根据要推送的用户Id列表获得List< DeviceUser>,DeviceUser类的属性包括devicetype,deviceId,userId,userName,createAt等。接着要获得deviceType是ios的deviceId列表,deviceType是安卓的deviceId列表。即将List< DeviceUser>转为Map< Integer,List< String>>,其中map的key是deviceType,value是deviceId的list。为了解决这个问题,写了一个通用的工具类。 1.利用stream12345678910111213141516public class TransFormUtils { /** * 将list<T>转为Map<K,List<V>> * @param list * @param predicate1 map中的key * @param predicate2 map中的list的元素 * @return */ public static <K,V,T> Map<K, List<V>> transformToMapList(List<T> list, Function<T, K> predicate1, Function<T,V> predicate2){ return list.stream().collect( Collectors.groupingBy(predicate1, Collectors.mapping(predicate2, Collectors.toList()))); }} 使用如下: 123List<DeviceUser> list = new ArrayList<DeviceUser>();//省略list的构造Map<Integer, List<String>> deviceMap = TransFormUtils.transformToMapList(list, p->p.getDeviceType(), p->p.getDeviceId()); 2.普通方法同事也写了一个另一个工具类,这种方法定义了一个新的数据结构,直接使用MapList代替Map 123456789101112131415161718192021222324252627282930/** * Map&List组合数据结构 * * @author jianming.zhou * * @param <K> * @param <V> */public class MapList<K, V> { private Map<K, List<V>> map = new HashMap<K, List<V>>(); public List<V> get(K k) { return map.get(k); } public void put(K k, V v) { if (map.containsKey(k)) { map.get(k).add(v); } else { List<V> list = new ArrayList<V>(); list.add(v); map.put(k, list); } } public Set<K> keySet() { return map.keySet(); }} 使用如下123456List<DeviceUser> list = new ArrayList<DeviceUser>();//省略list的构造MapList<Integer, String> deviceMap = new MapList<Integer,String>();for(DeviceUser device : list){ deviceMap.put(device.getDeviceType(),device.getDeviceId());} 还是喜欢第一种哈哈哈哈哈哈~~题外话:既然对现状不满意 就尝试改变吧 虽然有可能进入另一个坑~~","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"stream","slug":"stream","permalink":"http://yemengying.com/tags/stream/"},{"name":"list","slug":"list","permalink":"http://yemengying.com/tags/list/"},{"name":"map","slug":"map","permalink":"http://yemengying.com/tags/map/"}]},{"title":"使代码更简洁(一)---List相关","slug":"使代码更简洁-一-List相关","date":"2015-09-10T06:07:54.000Z","updated":"2017-06-10T07:56:03.000Z","comments":true,"path":"2015/09/10/使代码更简洁-一-List相关/","link":"","permalink":"http://yemengying.com/2015/09/10/使代码更简洁-一-List相关/","excerpt":"以前写在segmentFault上的一篇文章 搬移到这里。 记录一下在工作开发中封装的一些工具类,使代码看起来更加的简洁。这篇就记录下和list相关的吧。。。。。会持续记录。。。。","keywords":null,"text":"以前写在segmentFault上的一篇文章 搬移到这里。 记录一下在工作开发中封装的一些工具类,使代码看起来更加的简洁。这篇就记录下和list相关的吧。。。。。会持续记录。。。。 利用stream代替for循环 在对list的操作中常常需要for循环来遍历整个list,代码看起来不够简洁。所以利用java8的新特性Stream来代替for循环,提高程序的可读性。从网上coyp了一些stream的介绍:Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。下面是一些利用stream写的工具类 打印list中的元素123456789101112131415/** * * @author yemengying * */public class ListUtils { /** * 打印list中的元素 * @param list */ public static <T> void printList(List<T> list){ if(null == list) list = new ArrayList<T>(); list.stream().forEach(n -> System.out.println(n.toString())); }} 从list中删除指定的元素12345678910111213141516171819202122/** * * @author yemengying * */public class ListUtils { /** * 从list中删除指定的元素 其他类需重写equals方法 * @param list * @param arg 要删除的元素 * @return 返回删除了指定元素的list * eg:list:[1,2,3,1]---removeElementFromList(list,1)---return list:[2,3] */ public static <T> List<T> removeElementFromList(List<T> list, T arg){ if(null == list || list.isEmpty()) return new ArrayList<T>(); if(arg == null) return list; return list.stream().filter(n -> { return !n.equals(arg); }).collect(Collectors.toList()); }} list排序12345678910111213141516171819202122232425262728/** * * @author yemengying * */public class ListUtils { /** * list排序 * @param list * @param comparator * @return 返回按comparator排好序的list * eg:User:id name两个属性 * List<User> userList = new ArrayList<User>(); * userList.add(new User(1,\"abc\")); * userList.add(new User(3, \"ccd\")); * userList.add(new User(2, \"bde\")); * 1.按user名字排序 * userList = ListUtils.sortList(userList, (p1, p2) -> p1.getName().compareTo(p2.getName())); * 2.按user Id排序 * userList = ListUtils.sortList(userList, (p1, p2) -> p1.getId()-p2.getId()); */ public static <T> List<T> sortList(List<T> list, Comparator<? super T> comparator){ if(null == list || list.isEmpty()) return new ArrayList<T>(); if(null == comparator) return list; return list.stream().sorted(comparator).collect(Collectors.toList()); }} 判读list中的元素是不是全部满足 指定条件12345678910111213141516171819202122232425/** * * @author yemengying * */public class ListUtils { /** * 判读list中的元素是不是全部满足 predicate的条件 * @param list * @param predicate * @return 全部满足 true 有不满足的 false * eg:判断list中的user的id是不是均小于4 * List<User> userList = new ArrayList<User>(); * userList.add(new User(1,\"abc\")); * userList.add(new User(3, \"ccd\")); * userList.add(new User(2, \"bde\")); * System.out.println(ListUtils.isAllMatch(userList, u -> u.getId()<4)); * 输出 true */ public static <T> boolean isAllMatch(List<T> list, Predicate<? super T> predicate){ if(null == list || list.isEmpty()) return false; if(null == predicate) return false; return list.stream().allMatch(predicate); }} 判断list中是不是有一个元素满足predicate的条件123456789101112131415161718192021222324/** * * @author yemengying * */public class ListUtils { /** * 只要有一个元素满足predicate的条件 返回true * @param list * @param predicate * @return * eg:判断list中的user的id是不是有一个大于4 * List<User> userList = new ArrayList<User>(); * userList.add(new User(1,\"abc\")); * userList.add(new User(3, \"ccd\")); * userList.add(new User(2, \"bde\")); * System.out.println(ListUtils.isAllMatch(userList, u -> u.getId()>4)); return false */ public static <T> boolean isAnyMatch(List<T> list, Predicate<? super T> predicate){ if(null == list || list.isEmpty()) return false; if(null == predicate) return false; return list.stream().anyMatch(predicate); }} 判断list中是不是没有一个元素满足predicate的条件123456789101112131415161718192021222324/** * * @author yemengying * */public class ListUtils { /** * 没有一个元素满足predicate的条件 返回true * @param list * @param predicate * @return * eg:判断list中的user的id是不是有一个大于4 * List<User> userList = new ArrayList<User>(); * userList.add(new User(1,\"abc\")); * userList.add(new User(3, \"ccd\")); * userList.add(new User(2, \"bde\")); * System.out.println(ListUtils.isAllMatch(userList, u -> u.getId()>4)); return true */ public static <T> boolean isNoneMatch(List<T> list, Predicate<? super T> predicate){ if(null == list || list.isEmpty()) return false; if(null == predicate) return false; return list.stream().noneMatch(predicate); }} list去重123456789101112131415161718/** * * @author yemengying * */public class ListUtils { /** * list去重 * @param list * @return * eg: * list[1,2,2]---distinctList(list)---list[1,2] */ public static <T> List<T> distinctList(List<T> list){ if(null == list || list.isEmpty()) return new ArrayList<T>(); return list.stream().distinct().collect(Collectors.toList()); }} 2利用泛型编写一些通用的方法方便的构造一个list在开发时经常遇到要调用一个接口,接口的参数是list。例如在开发通知中心时发送消息的接口定义如下,其中messageForm是要发送的内容,userList是接受者的用户id 1public int pushMessage(MessageForm messageForm,List<Integer> userList); 这样,在给一个人发送消息的时候也需要构造一个list一般的做法,如下: 123List<Integer> list = new ArrayList<Integer>();list.add(8808);pushService.pushMessage(messageForm,list); 比较麻烦,所以同事封装了一个工具方法: 12345678910111213141516171819public class ListUtils { /** * 构造list * @param args * @return * @author zhoujianming */ @SuppressWarnings(\"unchecked\") public static <T> List<T> toList(T...args) { if (null == args) { return new ArrayList<T>(); } List<T> list = new ArrayList<T>(); for (T t : args) { list.add(t); } return list; }} 这样在调用时,比较简洁: 12//给id 8808和8809发消息pushService.pushMessage(messageForm,ListUtils.toList(8808,8809)); 利用递归获得多个list的笛卡尔积获得多个list的笛卡尔积,代码参考stackoverflow 123456789101112131415161718192021222324252627/** * 递归获得多个list的笛卡尔积 * eg[1],[8808],[1,2,3]-->[[1,8808,1],[1,8808,2]] * 参考:http://stackoverflow.com/questions/714108/cartesian-product-of-arbitrary-sets-in-java * @param lists * @return */public static <T> List<List<T>> cartesianProduct(List<List<T>> lists) { List<List<T>> resultLists = new ArrayList<List<T>>(); if (lists.size() == 0) { resultLists.add(new ArrayList<T>()); return resultLists; } else { List<T> firstList = lists.get(0); List<List<T>> remainingLists = cartesianProduct(lists.subList(1, lists.size())); for (T condition : firstList) { for (List<T> remainingList : remainingLists) { ArrayList<T> resultList = new ArrayList<T>(); resultList.add(condition); resultList.addAll(remainingList); resultLists.add(resultList); } } } return resultLists;} 使用时将需要获得笛卡尔积的多个list放到一个list里,调用上面的方法即可,调用示例如下: 12345List<Integer> list1 = Arrays.asList(1,2,3);List<Integer> list2 = Arrays.asList(8808,8809,8810);List<Integer> list3 = Arrays.asList(4);List<List<Integer>> lists = Arrays.asList(list1,list2,list3);List<List<Integer>> resultLists = ListUtils.cartesianProduct(lists); [1,2,3],[8808,8809,8810],[4]——>[[1, 8808, 4], [1, 8809, 4], [1, 8810, 4], [2, 8808, 4], [2, 8809, 4], [2, 8810, 4], [3, 8808, 4], [3, 8809, 4], [3, 8810, 4]]","raw":null,"content":null,"categories":[{"name":"java","slug":"java","permalink":"http://yemengying.com/categories/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yemengying.com/tags/java/"},{"name":"stream","slug":"stream","permalink":"http://yemengying.com/tags/stream/"},{"name":"list","slug":"list","permalink":"http://yemengying.com/tags/list/"}]},{"title":"angularjs+bootstrap+ngDialog实现模式对话框","slug":"angularjs-bootstrap-ngDialog","date":"2015-09-08T06:07:54.000Z","updated":"2018-12-14T09:26:00.000Z","comments":true,"path":"2015/09/08/angularjs-bootstrap-ngDialog/","link":"","permalink":"http://yemengying.com/2015/09/08/angularjs-bootstrap-ngDialog/","excerpt":"在完成一个后台管理系统时,需要用表格显示注册用户的信息。但是用户地址太长了,不好显示。所以想做一个模式对话框,点击详细地址按钮时,弹出对话框,显示地址。","keywords":null,"text":"在完成一个后台管理系统时,需要用表格显示注册用户的信息。但是用户地址太长了,不好显示。所以想做一个模式对话框,点击详细地址按钮时,弹出对话框,显示地址。效果如下图: 通过查阅资料,选择使用ngDialog来实现,ngDialog是一个用于Angular.js应用的模式对话框和弹出窗口。ngDialog非常小(〜2K),拥有简约的API,通过主题高度可定制的,具有唯一的依赖Angular.js。ngDialog github地址: https://github.com/likeastore/ngDialogngDialog Demo : http://likeastore.github.io/ngDialog/首先引入需要的ngdialog的js和css文件。可通过CDN引入 1234//cdnjs.cloudflare.com/ajax/libs/ng-dialog/0.3.7/css/ngDialog.min.css //cdnjs.cloudflare.com/ajax/libs/ng-dialog/0.3.7/css/ngDialog-theme-default.min.css //cdnjs.cloudflare.com/ajax/libs/ng-dialog/0.3.7/css/ngDialog-theme-plain.min.css //cdnjs.cloudflare.com/ajax/libs/ng-dialog/0.3.7/js/ngDialog.min.js 在user.js里的controller中注入依赖 1234567891011121314151617181920var userControllers = angular.module('userControllers',['ngDialog']); userControllers.controller('userController',['$scope','$http','ngDialog',function($scope,$http, ngDialog){ $scope.name = 'user'; $scope.user = \"\"; $scope.address = \"\"; //获取用户信息 $http.get('http://localhost:3000/users').success(function(data) { $scope.user = data; console.log($scope.user); }); //点击详细地址按钮时,跳出模式对话框 $scope.clickToAddress = function (address) { $scope.address = address; ngDialog.open({ template: 'views/test.html',//模式对话框内容为test.html className: 'ngdialog-theme-plain', scope:$scope //将scope传给test.html,以便显示地址详细信息 }); }; }]) test.html(读取scope中的address并显示,表格样式采用bootstrap) 1234567891011121314151617181920212223242526272829<table class=\"table\"> <thead> <tr> <th> 收件人姓名 </th> <td> {{address.name}} </td> </tr> <tr> <th> 收件地址 </th> <td> {{address.content}} </td> </tr> <tr> <th> 手机号 </th> <td> {{address.phone}} </td> </tr> </thead> </table> user.html (显示用户的信息,当地址不为空时,显示详细地址按钮,并点击按钮时,调用controller中的clickToAddress函数) 123456789101112131415161718192021222324252627282930313233343536373839404142<div> <div class=\"panel panel-warning\"> <div class=\"panel-heading\"> 用户管理 </div> <div class=\"row\"> <div class=\"col-lg-8\"></div> <div class=\"col-lg-4\"> <div class=\"input-group\"> <input type=\"text\" class=\"form-control\" placeholder=\"Search for...\" ng-model='search'> <span class=\"input-group-btn\"> <button class=\"btn btn-default\" type=\"button\">Go!</button> </span> </div> </div> </div> <table class=\"table\"> <thead> <th>姓名</th> <th>余额 <span class=\"glyphicon glyphicon-flash\" aria-hidden=\"true\"> </span></th> <th>头像</th> <th>默认地址</th> <th>操作</th> </thead> <tbody> <tr ng-repeat=\"user in user | filter : search\" > <td>{{user.userName}}</td> <td>{{user.residualPayment}}</td> <td ng-if=\"user.url != 'undefined' \">{{user.url}}</td> <td ng-if=\"user.url == 'undefined' \">系统默认头像</td> <td ng-if=\"user.address.length == 0 \">暂无默认地址</td> <td ng-if=\"user.address.length != 0\"ng-repeat=\"address in user.address \" ng-click=\"clickToAddress(address)\"> <button type=\"button\" class=\"btn btn-info navbar-btn\">详细地址</button> </td> <td> <button type=\"button\" class=\"btn btn-warning navbar-btn\" ng-click=\"remove(user._id)\">删除</button> </td> </tr> </tbody> </table> </div> </div> git地址: loadStyle(\"/hexo-github/style.css\"); loadStyle(\"/hexo-github/octicons/octicons.css\"); new Badge(\"#badge-container-giraffe0813-hellofreshAdmin-278d259\", \"giraffe0813\", \"hellofreshAdmin\", \"278d259\", false);","raw":null,"content":null,"categories":[{"name":"angularJs","slug":"angularJs","permalink":"http://yemengying.com/categories/angularJs/"}],"tags":[{"name":"angularJs","slug":"angularJs","permalink":"http://yemengying.com/tags/angularJs/"},{"name":"ngDialog","slug":"ngDialog","permalink":"http://yemengying.com/tags/ngDialog/"},{"name":"bootstrap","slug":"bootstrap","permalink":"http://yemengying.com/tags/bootstrap/"}]}]}