大叔激励|小宫黛雅|上饶熊孩子|法国小羊做新生|体育强健身心|地震回忆

最近机缘巧合和几个中年大叔聊天,从眼神里面能看出来他们是讲的真心话,平时也非常反感长辈们给自己讲的一些大而空的道理,但仔细想想这些话其实并不是完全没用。 不管对方是世俗眼光中的成功人士还是失败人士,他们都会有自己没有实现的理想愿望,成功人士没有好好陪家人孩子,他们可能喜欢特地找个时间,不管接收方是否感动,但至少他自己已经得到心灵的慰藉了;失败人士没有给到家人足够富裕的生活,这时候他们都很喜欢看努力、奋斗、自强不息......一类词汇堆砌的文章。而老人的精神世界已经不是奋斗了,而是人这一生......,所以如果陪老人说话,你只需要把话题引入到佛身上,然后静静的听就可以了。 忘了啥时候无意中加了一个高中生,目前正在读高二,与大多数人不同的是,她属于双性恋人群,并且更偏爱小姐姐,出口成脏,上课偷偷玩手机,宛然一个大家脑中所构想的问题少年。 但是几个月前突然变了,一下子变得有礼貌起来了,我问她是因为什么变了,她告诉我是因为我说的一句话:不说脏话是对人起码的尊重。这句话让她想明白了,我在这里偷偷说一句,我也不知道当时说的这句话对不对,其实就是随口一说,让一个小娃娃变好了,那也好。 在我的循序诱惑下,小姑娘已经不藏手机了,每周日自觉的把手机交上去,每周五手机发下来再玩,所以我和她的聊天记录现在基本上变成了,周日我发一句“加油”,周五下午我会收到一句“突然出现”。 小姑娘自己是同性恋的事情不敢给爸妈说,我想这是不是因为父母与孩子天然就有一种屏障,以至于无法与他们进行心灵上的沟通,要想能够得到孩子的信任,父母应该多站在孩子角度考虑考虑问题,尽量不要拿「我都是为你好」去搪塞孩子。 最近上饶杀熊孩子案很火,女生父亲杀人当然是不对对,但是对于这种校园霸凌这件事,校方与男生家长都摆出无所谓的态度,才导致悲剧的发生,其实校园霸凌的核心不在熊孩子身上,而是在熊家长身上。 吴军在其《大学之路》上有写道:“在我的印象中,父母晚上从来不参加应酬,甚至不看什么电视剧,总是非常有规律的学习,我的母亲现在快 80 岁了,依然每天坚持学习,父母们并不知道,他们在对我们兄弟的教育上最成功之处,是以他们的行为潜移默化地影响了我们,让我们渐渐养成了终身学习的习惯”。借句公益广告词——父母是最好的老师。 最近一个很搞笑的新闻,法国一所小学为了拯救学校不被关闭,招了 15 只羊作为新生。简单来说就是,根据法国政策,当学生数量少于 261 时,学校将被迫停办,而法国人在抗议上也很有创意,反正规定上又没有写招生的物种必须是人,正好我们家有许多只适龄绵羊,都送去上学吧! 牧民说到做到,这周二就和校长及家长达成一致,带领着自己家50只绵羊赶到学校招生办公室报道,招生办的老师热情的接待了羊以及送羊报道的牧羊犬们,在检查了所有绵羊的出生证后,最终有15只年龄在3-6岁之间的适龄绵羊顺利通过合法注册,成为小学的一年级新生,让这所小学不至于被停学。 我们常常把体育锻炼与健身、减肥挂钩,前段时间杭州程序员小伙伴突然精神崩溃,引发了一大波人关注,在校大学生跳楼也是常有的事,但是去细细看一下,是不是很少会看到体育生跳楼。 大学生跳楼无非是意志力脆弱、心理自我调节能力差等方面影响的,而体育锻炼恰恰会无意中去缩短了这些短板,我主要不是想说体育锻炼有多么多么好,因为这是大家都知道的,而是想说一件事的作用范围可能比你想象的要大的多。 今天是母亲节,无意中突然记起了初中的一篇阅读理解——《那支枯萎的康乃馨》,读者大人可以去搜搜读读,从另一个角度看看母亲节送礼这件事,不知道现在的朋友圈孝子还多不多。 同样今天是 5.12 汶川地震纪念日,地震发生时我还在读 6 年级,记得整个床都摇的快要倒了,房子上面的瓦片蹭蹭的往下掉,但是学生中间没有一个害怕了,原因只是因为我们并不知道地震这个词。 去年的 5.28 日,吉林松原发生了一次小的地震,哈尔滨有震感,作为也算经历过地震的人,自然异常的敏感,熟睡中的我一下就惊醒了,立刻意识到地震了,然后在那三四秒的时间里面,时间好像停住了一样,我都数不清那几秒时间脑海里滑过了多少记忆,我体会到了面对死亡的感觉,几秒过后,根据汶川地震的经验,本次只是小地震,所以我又躺下睡觉了,然后室友们都跑出去了。 出来工作也有大半年了,之前以为第一份工作是学习技术的,现在才发现第一份工作首先应该学习的是做事态度,在学校如果某件事不想做或者太困难,那么完全可以选择不做,但是在企业不行,一些恶心的事情必须有人得去做,学生总是缺乏应有的责任感,但责任感是优秀员工的基本条件。 我之前在公司怼过两次领导,怼完领导之后他反倒对我更好了,给我解决问题的速度超快,现在回想起来可能自己实际上做错了,为啥不能心平气和的去解释,而采取暴力沟通,今后我也要学习怎么做一个圆滑的俗人。 保持一个虚心的学习态度是异常重要的,认清自己资质平平的现实。一些人在公司认为经理是个傻逼,总监是个马屁精,董事长屁股决定脑袋,那不过是因为你没到那个位置,没办法理解他们考虑问题的角度而已。
Read More ~

知识图谱如何构建?——经济责任审计知识图谱构建案例实战

参考: 汉语言处理包 HanLP:https://github.com/hankcs/HanLP 中文文本分类:https://github.com/gaussic/text-classification-cnn-rnn 农业知识图谱:https://github.com/qq547276542/Agriculture_KnowledgeGraph 事实三元组抽取:https://github.com/twjiang/fact_triple_extraction 中文自然语言处理相关资料:https://github.com/mengxiaoxu/Awesome-Chinese-NLP 开放中文实体关系抽取:http://www.docin.com/p-1715877509.html 自 2012 年 Google 提出“知识图谱”的概念以来,知识图谱就一直是学术研究的重要方向,现在有很多高校、企业都致力于将这项技术应用到医疗、教育、商业等领域,并且已经取得了些许成果。Google 也宣布将以知识图谱为基础,构建下一代智能搜索引擎。 现在已经可以在谷歌、百度、搜狗等搜索引擎上面看到知识图谱的应用了。比如在 Google 搜索某个关键词时,会在其结果页面的右边显示该关键字的详细信息。在几个常用的搜索引擎中搜索知识时,返回的答案也变得更加精确,比如搜索“汪涵的妻子”,搜索引擎会直接给出答案“杨乐乐”,方便了用户快速精准的获取想要的信息。不过目前的搜索引擎只有少部分搜索问题能达到这种效果。 关于知识图谱是什么,我想就不用介绍了,这种通过搜索引擎就能轻松得到的结果写在这里有点浪费篇章,并且我对知识图谱的理解也不深,不敢夸夸其谈,只是把自己这一段时间以来的工作做一个总结。 本文只相当于以经济责任审计这一特定领域构建了一个知识图谱,仅仅是走了一遍流程,当作入门项目,构建过程中参考甚至抄袭了别人的很多方法与代码,末尾都会给出参考的项目等等。 上图是我构建经济责任审计知识图谱的流程,看起来很繁琐,但只要静下心看,个人觉得相对还算清晰,箭头都有指向。下面就一步一步进行说明。 数据获取 数据获取主要分为两部分数据,一部分是新闻类数据,我把它用作文本分类模型的训练集;另一部分是实体数据,为了方便,我直接把互动百科抓取的词条文件作为实体,省了属性抽取这一环节。 因为本文构建的是一个经济责任审计领域的知识图谱,所以作为文本分类模型训练集的数据也应该是经济责任审计领域的。这里主要抓取了审计署、纪检委、新浪网的部分新闻。 像上面的图一样,新闻类网站一般都有搜索框,为了简单,所以我直接用搜索框搜索“经济责任审计”,然后从搜索结果中抓取新闻数据,即认为是经济责任审计相关的文本。抓取新闻类网站使用了 chrome 模拟用户进行访问。最终获得了 3500 多条新闻文本。 领域词汇判定 领域词汇判定,本文构建的不是开放领域的知识图谱,所以需要采用一种方法来判定所抓取的内容是否属于经济责任审计领域。领域词汇本文的方法实际上是领域句子判定,直接使用了大神的项目。CNN-RNN中文文本分类,基于tensorflow。也看到有人通过改进逻辑回归算法,在进行领域词汇的判定。 我判定领域词汇的逻辑是这样的,一个词语即使是人类也不一定能确定它是否属于经济责任审计领域,但是每个词语都会有它的含义解释对不对,一个词语的解释就是一段话。我用网上的新闻训练出一个判断一段话属于哪个领域的模型,然后把词语的解释放到模型了里面去,如果模型给出的结果是属于经济责任审计领域,那则认为这个词语属于经济责任审计领域。 实体关系抽取 知识图谱的基本单位为(实体1,关系,实体2)这样的三元组,实体是直接从互动百科获取的词条,关系由两部分组成,一部分来自 wikidata 所提供的关系,这一部分直接从 wikidata 爬取即可得到,另一部分使用的是基于依存句法分析的开放式中文实体关系抽取,已经在前面的文章发过了。 知识存储 有了实体和实体关系,那么把这些数据进行筛选,然后入库,通过直观的页面展示,就可以了。这里使用的数据库是 neo4j,它作为图形数据库,用于知识图谱的存储非常方便。知识的展示使用了别人的项目,仅仅是把里面的数据换掉了而已,感谢大神的无私。 当然你也可以选择使用关系型数据库,因为我做的经济责任审计知识图谱不够深入,所以做到最后展示的时候,发现其实用我比较熟悉的 MySql 更好,相比 NOSql 我更熟悉关系型数据库,而且 MySql 有更大的社区在维护,它的 Bug 少、性能也更好。 最后放几张效果图 下面是以“职业”为关系查询条件所得出的结果。 总结一下 只是对几个月工作的梳理,大多数核心代码都改自现有的代码,所有的数据都来自于网络,与知识图谱相关的公开技术较少,我也只是尝试着做了一下,虽然很菜,也可以对大致的技术路线、流程有一个简单的了解,主要工作都是自然语言处理的内容。后期可以利用现在的知识图谱构建智能问答系统,实现从 what 到 why 的转换。 以下内容更新于 2020 年 3 月。 在毕业前收到了电子工业出版社和另一家出版社的写书邀请,我和电子工业出版社签订了写书合同,从还未毕业开始断断续续写作了一年的时间,因为自己的懒惰,加上内容中涉及到大量爬虫,而且爬目标网站是政府网站(不允许爬),另外 19 年网上时不时曝出某某程序员因爬虫而入狱的故事,出版社和我难免不会恐惧,我也正好找到了不再继续写下去的理由。 花了点时间把以前的程序,书籍已经写成的内容整理了一下,放在了 economic_audit_knowledge_graph 中,所有资料都在里面,希望能帮助到自然语言入门的小伙伴,我自己已经不做这个领域了!
Read More ~

如何抽取实体关系?——基于依存句法分析的事实三元组抽取

参考: HanLP 自然语言处理 基于依存分析的开放式中文实体关系抽取方法 命名实体三元组抽取参考自fact_triple_extraction 这一段时间一直在做知识图谱,卡在实体关系抽取这里几个月了,在 Github 上面看到有人使用卷积神经网络训练模型进行抽取,自己也尝试了一下,但是一直苦于没有像样数据去训练,而标注训练集又太费时间了,我不太愿意干体力活。另外自己也不会什么机器学习、深度学习之类的技术,而且毕业设计都是有时间要求的,所以采用了一个低档次的方法,基于依存句法分析的实体关系抽取,记录一下心得,方便日后忘记可以再找回来。 论文给出了 8 种中文关系的表达方式,并且最后给出了一个采用正则表达式语法指出表达,核心就是谓语动词表示关系,即关系表述中一定得有动词。 状语*动词+补语?宾语? 我不太赞同把宾语也当作关系表述的一部分,论文指出“p4生于山西”应该抽出(p4,山西,生于山西),我认为关系不应该表述为“生于山西”,所以我把关系表述改为下面的样子了。 状语*动词+补语? 这篇文章只是作为一个方法介绍,我自己先看了一遍,能够保证我下次看到这篇文章,可以立马回忆起自己的实现方法,希望你看了也能了解方法,看不懂的话,我表示抱歉,浪费您的时间了,我已经尽可能写到简单了。 先来看几个简单句子吧: 主谓宾关系:刘小绪 生于 四川 // 这个三元组很明显:(刘小绪,生于,四川) 动补结构:刘小绪 洗 干净 了 衣服 // 如果套用主谓宾关系就是:(刘小绪,洗,衣服) // 但是这里描述的是一个状态,是刘小绪把衣服洗干净了 // “干净”是动词“洗”的补语,所以还应该提取出一个如下三元组 // (刘小绪,洗干净了,衣服) 状动结构:父亲 非常 喜欢 跑步 // 这句和上面很像,主谓宾关系是:父亲喜欢跑步 // “非常”用于修饰“喜欢” // (父亲,非常喜欢,跑步) 介宾关系:刘小绪 就职 于 学校 // 如果直接把这个三元组抽取为(刘小绪,就职,学校),很别扭 // “于”和“学校”是介宾关系,它们的关系应该是:就职于 // (刘小绪,就职于,学校) 宾语前置:海洋 由 水 组成 // “海洋”是“组成”的前置宾语 // “由”是“组成”的状语 // “水”和“由”是介宾关系 // 所以上面的句子没有明确的主谓关系,需要我们判断 // 抽出的三元组应该为:(水,组成,海洋) HanLP 提供了两种依存句法分析的器,默认采用的是基于神经网络的依存句法分析器。依存句法分析就是将句子分析成一棵依存句法树,描述各个词语之间的依存关系,即指出词语之间在句法上的搭配关系。 有了上面所说的依存句法树,其实我们只需要进行各种判断就可以了。先做出下面的一点说明,就拿第一个例子来说。 原文:刘小绪生于四川 # 这是分词结果 [刘小绪/nr, 生于/v, 四川/ns] #这是句法分析结果 刘小绪 --(主谓关系)--> 生于 生于 --(核心关系)--> ##核心## 四川 --(动宾关系)--> 生于 为了方便理解,也为了方便程序的编写,我把他们组织成了下面的形式,为每一个词语都建一个依存句法字典。 刘小绪:{} 生于:{主谓关系=[刘小绪], 动宾关系=[四川]} 四川:{} 然后只需要写出类似于下面的程序段就可以抽出关系了。 // 主谓宾关系:刘小绪生于四川 // dic是这个词语的依存句法字典 if (dic.containsKey("主谓关系") && dic.containsKey("动宾关系")){ // 当前的词语,用上面的例子来说,relation=“生于” String relation = curWord.LEMMA; // 用循环遍历,是因为关系列表里面不一定只有一个词语 for (CoNLLWord entity1: dic.get("主谓关系")) { for (CoNLLWord entity2: dic.get("动宾关系")) { System.out.println(entity1.LEMMA + "," + relation + "," + entity2.LEMMA); } } } 对于分词后的每个词语都进行上面程序段的操作。“刘小绪”和“四川”,关系字典都为空。而对于“生于”,关系列表里面既有主谓也有动宾,而自己本身就是动词,主谓宾就出来了。直接从主谓关系中拿出来词语作为 entity1,再拿上自己作为关系,最后拿出动宾关系中的词语作为 entity2。很明确的三元组(刘小绪,生于,四川)就出来了。 最后给出一个程序运行结果图吧。 我个人觉得效果还行,在简单句子上面表现的差强人意,在长句子上面表现的差劲。注意上文使用的第三方包随着时间的推移肯定会改一些接口,源码链接:entity_relation_extraction
Read More ~

阅读高于自己的作品,远离精神毒品

关于阅读与写作的重要性,可能每个人都多少有一些概念,关于写作的重要性可以看我之前系的谈一下写作的重要性。阅读是打开世界的大门,写作可以提升语言组织能力。 自己就读的专业是计算机科学与技术,生活在山区一直都没有接触过电脑。读大一大二的时候,我非常崇拜那些随随便便就能做一个网站、写一个复杂算法的大神。那时候在觉得只要专业能力足够强,只要技术掌握的足够深入,那就是 NB,所以我在大二之前从来都没有读过一本技术之外的书籍。 现在回忆起那时候的想法真是太狭隘了,一个人掌握了某项专业技能,我们可以把这个人称之为「手艺人」,大学能教给我们一门手艺(可笑的是很多学生连手艺都没有学到),这门手艺可以帮助我们得以生存或者赚到一些钱,但是生活肯定不仅仅是钱,还有很多比钱更重要的事情。 白领及以下阶层基本没有自己想去哪里就去哪里的自由,那怎么拓宽自己的视野呢?阅读和交朋友是个很不错的方式,但并不是所有阅读都是有效的,也不是所有朋友都是值得信任的,有的甚至是一种精神毒品。 阅读需要挑选高于自己的作品,要能仰视它,才能攀登。阅读那些比自己低下的作品只会让自己更 low。现在的生活节奏很快,碎片化阅读成了很多人的阅读方式;有人抨击碎片化阅读,也有人提倡碎片化阅读,每个人有不同的观点,我个人是赞同碎片化阅读的,像得到、喜马拉雅等平台也把一个大的知识点切分的足够小了,一个小的知识点也就 10 来分钟,很适合碎片化的阅读,还不耽误其它事情。 来自互联网的阅读内容和自己大多是平等的,每个人都在使用微信,但我相信很多人都有屏蔽一些人朋友圈的习惯,因为你会选择跟你脾气相同、你喜欢的、跟你水平接近内容去阅读;现在的 APP 也很多,而且很多首次注册都会让你选择自己感兴趣的内容,所以也就会失去挑战自我的机会。 阅读是提升认知的重要手段,人与人之间的根本差距在于认知,如果读了一本书之后能让自己的认知得到提升,那么这就没白读;当然,如果读完一本书或是一篇文章之后能让有很大程度的转变,那这种认知的提升我相信是宝贵的财富,而且认识是伴随终身的。能达到这样效果的好文章、好书肯定少之又少,对我个人影响的最大的一本书是《把时间当作朋友》,我很推荐这本书,接触这本书算我认知上的一个转折点。 也有一些阅读是日积月累对自己产生影响的。我曾经关注了近 150 个公众号,从里面精挑细选了一部分原创公众号留下,涉及技术、新闻、产品、理财等方面。我现在每天的阅读主要就是公众号阅读和付费加入的几个知识星球,以及得到上面的付费知识。 日积月累,我筛选有效信息的能力也更强了,筛选信息能力在这个信息爆炸的时代是很重要的。 有一次听到朋友说:“文章太长了,我不想看”。我没说话,只给了一个无奈的眼神,因为他只想要一个结论,因为这可以很轻松的获得一种愉悦感,就像打游戏一样可以快速的获得喜悦,而且对大脑来说,也是最容易接受的,但是慢慢地,大脑就会失去独立思考的能力。 从产品角度讲,不得不佩服头条对人性的洞察,为了“懂你”,在自己的产品上加入推荐算法,但实际上是让你把时间花在他们产品的身上,把产品做到这种程度,不得不承认是很牛的。最近奈飞出了一步很棒的纪录片,叫做监视资本主义:智能陷阱 The Social Dilemma,片中将科技的负面清楚的呈现给我们,网络科技在某些方面已经逐渐演变为操纵社会的巨兽。 像抖音、快手、头条、微博这些产品我认为基本都是在浪费用户时间(我还没有用过快手和微博,评价它们有点冒昧了),他们无异于精神毒品,吞噬你的时间。我并不是讨厌这些产品,我自己也喜欢体验新的产品,我只是觉得把宝贵的时间放到更有价值的事情上去,那么就会比周边人更加优秀。 需要选择的是高于自己内容阅读,而不是把时间都花在那种不需要思考就能得到的愉悦上去;如果把大部分时间都花在轻易就能获得的愉悦感上,那么你应该正在一步步颓废。 最后推荐自己写的另一篇文章大学生书单推荐
Read More ~

正则表达式入门,基础语法详解

这两天一直在时不时的和 Neo4j 图数据库打交道。它的查询语句可以使用正则表达式,有一段时间没有自己写过正则表达式了,现在处于能看懂别人写的正则表达式,但是自己写不出来,语法规则都忘了。为了方便接下来的工作,所以特地复习复习正则表达式的语法。 正则表达式简介 正则表达式是用来匹配字符串的一系列匹配符,具备简介高效的特点,在很多语言中都有支持(java、python、javascript、php 等等)。在 windows 的 cmd 命令中也同样支持,例如使用命令 dir j*,那么只会罗列出所有以j开头的文件和文件夹。 正则表达式基本语法 正则表达式在在不同语言的支持语法略有不同,本文采用js的进行说明。js 中使用正则表达式的方法为str.match(/表达式/),即需要加两个斜杠。以下所有的代码段第一行为代码,第二行为返回结果,实验是在 chrome 控制台进行的。 一直认为最好的学习方式就是实际操作,理论谁都能讲一大堆,但是实际做没做出来还真不知道。一个奇葩现象就是教软件工程的老师可能并没有在软件行业待过。 普通匹配符 普通匹配符能匹配与之对应的字符,默认区分大小写。 "Hello Regx".match(/H/) ["H", index: 0, input: "Hello Regx", groups: undefined] 正则标记符 i :不区分大小写 g :全局匹配 m :多行匹配(暂不管它,我用的少) 参数直接加在最后一个斜杠的后面,比如"Hello Regx".match(/regx/i),可以加多个参数。 "Hello Regx".match(/regx/i) ["Regx", index: 6, input: "Hello Regx", groups: undefined] 之前是表达式一旦匹配成功,就不再向字符串后面查找了,加上 g 后,表示进行全局查找。最后返回的是一个数组。 "Hello Regx".match(/e/g) (2) ["e", "e"] 多匹配符 \d :匹配数字,即 0~9 \w :匹配数字、字母、下划线 . :匹配除换行的所有字符 需要注意的是,上面所有的匹配符都只能匹配一个字符。 "Hello 2018".match(/\d/g) // 使用\d,匹配字符串中的所有数字 (4) ["2", "0", "1", "8"] "Hello 2018".match(/\w/g) // 使用\w,匹配所有的数字和字母,需要注意没有匹配到空格 (9) ["H", "e", "l", "l", "o", "2", "0", "1", "8"] "Hello 2018".match(/./g) // 使用.,匹配所有字符,包括空格 (10) ["H", "e", "l", "l", "o", " ", "2", "0", "1", "8"] "Hello 2018".match(/\d\w./g) // 分析一下这个为什么匹配到的是201, // 首先\d找到第一个数字2,匹配成功,紧接着\w匹配到0,然后.匹配到1 // 整个正则表达式匹配成功,返回201 ["201"] "Hello 20\n18".match(/\d\w./g) // 这里匹配不成功,因为.不能匹配换行符,所以返回null null "Hello 2018".match(/\w.\d/g) // 首先看这个正则式,\w.\d,它要求最后一个字符是数字 // \w.能一直匹配到空格,但是因为得满足\d,所以第一个匹配成功的是0 2 // 因为是全局匹配,所以会接着匹配后面的018,也匹配成功 (2) ["o 2", "018"] 自定义匹配符 比如中国的手机号都是以 1 开头,第二位只能是 3、4、5、7、8,第 3 位只要是数字就行。如何匹配这样的字符串? [] :匹配[]中的任意一个字符 "152".match(/1[34578]\d/) // 第二个字符可以选择中括号中的任意一个 ["152", index: 0, input: "152", groups: undefined] 如果在 [] 添加了 ^,代表取反。即 [^] 表示除了中括号中的字符都满足。 "152".match(/1[^34578]\d/) null "1a2".match(/1[^34578]\d/) // 只要不是[]中的字符,都满足,包括回车符 ["1a2", index: 0, input: "1a2", groups: undefined] 修饰匹配次数 我们的手机号有 11 位,除了前 2 位有要求,其他9位度没有要求,那么是不是正则表达式就应该这样写呢? 1[^34578]\d\d\d\d\d\d\d\d\d 很明显,这样写太麻烦,肯定有更好的方式,这里就可以修饰一下匹配次数啦。 ? :最多出现 1 次 + :至少出现 1 次 * :出现任意次数 {} :分下面四种情况 {n}代表前面的匹配符出现 n 次 {n, m}出现次数在 n~m 之间 {n, }至少出现 n 次 {, m}最多出现 m 次 例子很简单,一看就懂,不浪费时间。 "15284750845".match(/1[34578]\d{9}/) ["15284750845", index: 0, input: "15284750845", groups: undefined] "15".match(/1[34578]\d?/) ["15", index: 0, input: "15", groups: undefined] "152".match(/1[34578]\d?/) ["152", index: 0, input: "152", groups: undefined] "152".match(/1[34578]\d+/) ["152", index: 0, input: "152", groups: undefined] "15".match(/1[34578]\d+/) null 完整匹配 按照上面的写法会出现下面的问题。 "ya15284750845".match(/1[34578]\d{9}/) // 不是电话号码,也能匹配成功,需要进一步改进 ["15284750845", index: 2, input: "ya15284750845", groups: undefined] ^ :在 [] 中代表取反,但在外面代表从开始匹配 "ya15284750845".match(/^1[34578]\d{9}/) // 现在就能从一开始匹配而且还得符合正则式才算匹配成功 null // 但是依旧会出现下面的问题 "1528475084523255".match(/^1[34578]\d{9}/) // 不是电话号码也能匹配成功,还要改进 ["15284750845", index: 0, input: "1528475084523255", groups: undefined] $ :代表持续匹配到结束 "1528475084523255".match(/^1[34578]\d{9}$/) // 现在就能保证正确了,有^表示从开始匹配; // 有$表示持续匹配到结束,即完全匹配 null /* 需要注意的是,一个字符串从开始匹配和从结束匹配都没问题, 不代表整个字符串就没问题,比如 15284750845-15284750845 这个字符串从开始和从结束匹配都能成功,但实际上是错的 */ 特殊符号 到这里发现正则表达式确实很强大,仅仅几个简单的符号就能匹配字符串,但是如果我们要匹配的字符本身就是前面用到的符号怎么办呢? 匹配像$、^等特殊符号时,需要加转义字符\ "1.".match(/./) //因为.能匹配除换行的所有字符,所以匹配到1 //但实际上我们想匹配.这个字符 ["1", index: 0, input: "1.", groups: undefined] "1.".match(/\./) // 只需要加一个转义字符就可以了,其他类似 [".", index: 1, input: "1.", groups: undefined] 条件分支 比如现在想匹配图片的文件名,包括 jpg、png、jpeg、gif 等等,这是多个选项,所以需要像编程语言一样,应该具备条件分支结构。 | :条件分支 () :有两层含义 括号中的内容成为一个独立的整体 括号的内容可以进行分组,单独匹配,若不需要此功能,则( ?: ) "1.jpg".match(/.+\.jpe?g|gif|png/) // 这样就可以满足条件分支了,不过下面又出问题了 ["1.jpg", index: 0, input: "1.jpg", groups: undefined] "1.png".match(/.+\.jpe?g|gif|png/) // 这里没有匹配到.和前面的文件名 ["png", index: 2, input: "1.png", groups: undefined] /* 其实我们想告诉它的是,.和后面的每一个条件分支的值都是一个独立的整体 但是它把.+\.jpe?g、gif、png当成了各自独立的整体 我们并不想让它这样切分,所以我们来告诉它怎么分才是正确的 */ "1.png".match(/.+\.(jpe?g|gif|png)/) // 现在可以匹配成功了,但是它多匹配了一个 // 因为括号的内容可以进行分组,单独匹配 (2) ["1.png", "png", index: 0, input: "1.png", groups: undefined] // 所以最终写法如下 "1.png".match(/.+\.(?:jpe?g|gif|png)/) ["1.png", index: 0, input: "1.png", groups: undefined] 贪婪与懒惰 // 首先看一个例子 "aabab".match(/a.*b/) ["aabab", index: 0, input: "aabab", groups: undefined] /* 上面的匹配没有什么问题,但实际上aab也是可以的 也就是aab也是符合条件的,那又是为什么呢? */ 因为在正则表达式中,默认是贪婪模式,尽可能多的匹配,可以在修饰数量的匹配符后面添加 ?,则代表懒惰。 // like this (^__^) "aabab".match(/a.*?b/) ["aab", index: 0, input: "aabab", groups: undefined] 到这里应该就差不多了,再深入的,就自我查询知识了。配一张正则表达式速查表。
Read More ~

FastDFS 分布式文件系统简介

文章内容是刘欣大大(《码农翻身》作者,公众号:码农翻身)的直播课内容,主要是了解一下分布式文件系统,学习FastDFS的一些设计思想,学习它怎么实现高效、简洁、轻量级的一个系统的 FastDFS分布式文件系统简介 国内知名的系统级开源软件凤毛菱角,FastDFS就是其中的一个,其用户包括我们所熟知的支付宝、京东商城、迅雷、58同城、赶集网等等,它是个人所开发的软件,作者是余庆。 我们已经进入互联网时代,互联网给我们的生活带来便捷的同时,也给我们带来了诸多挑战。 对于海量文件的存储,一个机器不够,那么就用多台机器来存储。 如果一个文件只存储一份,那么如果存储这个文件的机器坏掉了,文件自然就丢失了,解决办法就是将文件进行备份,相信大多数人都有备份重要文件的习惯。FastDFS也是如此,为了防止单点的失败,肯定是需要冗余备份的。 FastDFS把应用服务器分为若干个组,每一组里面可以有多台机器(一般采用3台),每一台机器叫做存储服务器(storage server)。同一组内之间的数据是互为备份的,也就是说用户把文件传到任一服务器,都会在同组内其它两个服务器进行备份,因此一个组的存储空间大小是由该组内存储空间最小的那台机器是一样的(和木桶原理一样)。为了不造成存储空间的浪费,同一个组内的三台机器最好都一样。 每个存储服务器(storage server)的存储就够又是怎样的呢?展开来看,它可以分为多个目录,每个目录以M开头,用00、01、02......来划分,一般无需划分这么多目录,只用一个目录就可以了。 在每个根目录下面又划分了两级目录。如图所示,在/data/fastdfs0下又划分出两级目录,每一级有256个目录,这样算下来总共就有65535个目录了。存储文件时,就是通过两次哈希来确定放在哪一个目录下面。 那么问题就来了,有这么多组,到底该选择哪个组的服务器进行存储呢?或者说,访问的时候到底访问哪一个组呢? FastDFS提供的解决思路是引入一个跟踪服务器(tracker server),它用于记录每一个组内的存储服务器信息,存储信息是每个storage主动回报给tracker,有了这些信息之后,tracker就可以做调度工作了,看看谁的存储空间大,就把文件放过去。 FastDFS的特点 组与组之间是相互独立的 同一个组内的storage server之间需要相互备份 文件存放到一个storage之后,需要备份到别的服务器 tracker之间是不交互的 每个storgae server都需要向所有的tracker去主动报告信息 tracker与tracker之间是不知道彼此的存在的 如何上传文件 为方便下载文件的理解,这里假设上传的文件为:Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg 如下面的时序图可以看到客户端是如何上传文件到服务器的。首先client向tracker发送上传链接请求,然后由tracker进行调度,查询可用的storage,并把该storgae对应的ip和端口发送给client;拿到了存储服务器信息,client就直接将文件上传到storage即可;storage会生成新的文件名再写入到磁盘,完成之后再把新的文件信息返回给client,client最后把文件信息保存到本地。需要注意的是,storage会定时向tracker回报信息。 如何进行选择服务器 tracker不止一个,客户端选择哪一个做上传文件? tracker之间是对等的,任选一个都可以 tracker如何选择group? round robin(轮询) load balance(选择最大剩余空间group上传) specify group(制定group上传) 如何选定storage? round robin,所有server轮询使用(默认) 根据ip地址进行排序选择第一个storage(ip地址最小者) 根据优先级进行排序(上传优先级由stoage来设置,参数为upload_priority) 如何选择storage path round robin,轮询(默认) load balance,选择使用剩余空间最大的存储路径 如何选择存放目录 选定存放目录? storage会生成一个file_id,采用Base64编码,字段包括:storage ip、文件创建时间、文件大小、文件CRC32校验和随机数 每个存储目录下面有两个256 * 256个子目录,storage会按文件file_id进行两次hash,然后将文件以file_id为文件名存储到子目录下 需要注意的是:file_id由cilent来保存,如果没有保存,你就不知道你上传的文件去那里了 Storage server之间的文件同步 同一组内的storage之间是对等的,文件上传、删除等操作可以在任意一台storage上进行 文件同步只在同组内的stroage之间进行,采用push方式,即源服务器同步给目标服务器 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了 新增一台storage时,由已有的一台storage将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器 Storage的最后最早同步被同步时间 这个标题有一些拗口,现在有三台服务器A、B、C,每个服务器都需要记录其他两台服务器向自己进行同步操作的最后时间。比如下图中的服务器A,B在9:31向A同步了所有的文件、C在9:33向A同步了所有的文件,那么A服务器的最后最早被同步时间就是9:31。其他两个服务器也是一样。 最后最早被同步时间的意义在于判断一个文件是否存在于某个storage上。比如这里的A服务器最后最早被同步时间为9:31,那么如果一个文件的创建时间为9:30,就可以肯定这个文件在服务器A上肯定有。 Stroage会定期将每台机器的同步时间告诉给tracker,tracker在client需要下载一个文件时,要判断一个storage是否有该文件,只需要解析文件的创建时间,然后与该值作比较,若该值大于创建时间,说明storage存在这个文件,可以从该storage下载。 但是这个算法有缺陷,比如下面的情况:W文件的创建时间是9:33,服务器C已经把9:33之前的文件都同步给B了,因此B服务器里面其实已经有W文件了,但是根据最后最早被同步时间,会认为B中没有W文件。因此这个算法虽然简单,但是牺牲了部分文件。 如何下载文件 首先由client发送下载连接请求,请求的东西本质上就是Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg;tracker将查询到的可用storage server(按下文的四个原则进行选择)的ip和端口发送给client;现在client有本地保存的文件信息,也有服务器的地址和端口,那么直接访问对应的服务器下载文件即可。 如何选择一个可供下载的storage server 共下面四个原则,从上到小条件越来越宽松 该文件上传到的源storage(文件直接上传到该服务器上) 文件创建时间戳 < storage被同步到的文件时间戳,这意味着当前文件已经被同步过来了 文件创建时间戳 = storage被同步到的文件时间戳,并且(当前时间-文件创建时间戳)> 一个文件同步完场需要的最大时间(5分钟) (当前时间 - 文件创建时间)> 文件同步延迟阀值,比如我们把阀值设置为1天,表示文件同步在一天内肯定可以完成 FastDFS的使用 用户通过浏览器或者手机端访问web服务器,web服务器把请求转发给应用服务器,应用服务器收到请求后,通过fastDFS API和FastDFS文件系统进行交互。但是这么设计会造成应用服务器的压力,因为上传和下载都经过应用服务器。 为了避免应用服务器压力过大,可以让客户端直接使用Http访问,不通过应用服务器。 FastDFS其他内容 防止盗链 为了防止辛辛苦苦上传的文件被别人盗去,可以通过给URL设置token来解决。FastDFS的防止盗链配置如下: # 是否做tokrn检查,缺省值为false http.anti\_steal.check\_token=true # 生成token的有效时长/秒 http.anti\_steal.token\_ttl=900 # 生成token的密钥,尽量设置长一些 http.anti\_steal.secret\_key=@#$%\*+\*&!~ FastDFS生成token策略为:token = md5(文件名,密钥,时间戳) 合并存储 海量小文件的缺点 元数据管理低效,磁盘文件系统中,目录项、索引节点(inode)和数据(data)保存在介质不同的位置上 数据存储分散 磁盘的大量随机访问降低效率(小文件有可能这个在这个磁道,那个在那个磁道,就会造成大量的随机访问,大量小文件对I/O是非常不友好的) FastDFS提供的合并存储功能 默认大文件64M 每个文件空间称为slot(256bytes = slot = 16MB) 也就是说对于小文件,FastDFS会采用把多个小文件合并为一个大文件的方式来存储,默认建一个大小为64M的大文件,然后再分成多个槽,最小的槽是256bytes,因此如果一个文件小于256bytes,那么它也会占256bytes的大小。就好像我们在医院见到的中药柜子一样,每个抽屉里面再分成多个小格子,根据药材包的大小来选择不同大小的格子。 没有合并时的文件ID 合并时的文件ID 此处不再深入探讨存储合并的机制,因为它带来了一系列新的问题,比如同步时不仅需要记录大文件的名称,还需要进入小文件的名称,一下子变得麻烦多了;原来空闲空间管理直接通过操作系统就能计算出来,但是现在不行了,因为是创建了一个64M的块,这个块里面还有空闲空间,计算起来就很麻烦了。 总结 FastDFS是穷人的解决方案 FastDFS把简洁和高效做到了极致,非常节约资源,中小型网站完全用得起
Read More ~