All Posts

8年前有位小伙花了1w个比特币买了两披萨

8年前,有个小伙子,花了 1w 个比特币,买了两个披萨,那一天是 2010年5月22号,是一个值得纪念的日子,被比特币爱好者们当做「比特币披萨节」。

推特上有个叫 Bitcoin Pizza  的用户很无聊,每天都记录着:当时这两块披萨,以现在的价格来看,值多少钱。关注者居然有7k+,好吧,算我一个。

image

可以看到现在这两披萨价值 $63,441,375, 换算成人民币就是4亿+,妈的按现在的话这披萨舔一下都应该有好几万了。

image

这小伙是美国程序员来的,后来有人采访他,他谈起这件事的时候表示:这披萨吃起来不错,就是有点贵~

不过说起比特币,刚开始的时候谁也不知道比特币现在价值这么高,那时候每 10 分钟就会被挖出一个新的区块,最早奖励50个比特币,不过现在奖励比特币越来越少了,因为每四年会减半,也就是一开始最早奖励50个比特币,四年后奖励25个,再过四年就12.5个,以此类推,直到 2140 年,比特币会达到发行上限——2100w个。

刚看了一下,现在已经发行了 1700w+ 个比特币了。

image

之前我还纳闷,为什么每四年奖励的比特币减半,越往后奖励比特币越少,为什么还有人要挖呢?这不是亏么?

后来我在 Youtube 看到搜狗CEO王小川对比特币的解说中明白了,也就是在记账过程中,记账的人可以从中获取交易费。

image

image

image

image

image

image

image

image

image

image

我们都知道比特币相对来说安全,并且可以进行匿名交易,所以之前那些黑客把别人电脑黑了,然后要别人交点比特币才能解锁,还有些灰色的产业比如做一些赌博网站,非法交易的黑市等等。

然而对于比特币这样新的技术产物,本身不存在对错,还记得王欣那句话,技术本无罪。真正善恶在于人,而不在于技术本身,比特币也是如此。

中国政策对比特币的态度是它是一种虚拟商品,不具备和货币一样的法律地位。不可以当做货币在市场上使用。但是比特币作为互联网上的商品进行买卖,只要个人在能够承担风险的前提下就可以自由的参与。

每一个新生的事物不可能做到完美,比特币也是这样,比如现在每个区块的大小是 1M,所以比特币系统中每秒最大只能交易 7 笔数量,这是人们还在一直争论的扩容问题。

因为比特币的影响太牛逼了,而且区块链的原理是公开的,所以有许多人在比特币原理的基础上,做出了许多其它的币种,比如和比特币一样使用工作量证明但是算法不同的莱特币,狗狗币。

莱特币和比特币的不同除了刚说的算法不同之外(比特币用的是SHA-256算法而莱特币用的是scrypt算法),莱特币是每2.5分钟产生一个区块,总发行量是8400w个,是比特币的 4 倍之多。

当然还有许多其它的比如:点点币,比特股,未来币,以太坊等等。

不过比特币在市直上还是远超其它的币,现在处于第一的比特币就甩了第二名的以太坊好多条街:

image

虽然比特币包括其它的数字货币只是区块链的一个产物,但是比特币对人们的影响和价值,已经是不可磨灭的事实。

如果哪天,你也卖披萨,记得叫上我。

我的相关文章:

1.通过苍老师教程理解什么是区块链比特币挖矿

2.谈谈文字和货币

3.真心希望区块链和智能交易越来越牛逼

written in 区块链

也不知道再过二三十年,技术会让社会变成什么样

最近看了些文章,有点感慨,越来越发现,在接下来的日子,会发生许多变化,而这些变化,都将会是由技术引起的。

很庆幸能够活在这个互联网的时代,能够感知技术带来的变化,就在自己的身边,在自己的眼前,看着一个个产品的兴起,一个个产品的衰败。看着自己喜欢的科技公司上市等等。

未来会变成什么样子,谁也不知道,但是大趋势一定是技术带来机会。只有去拥抱技术,了解技术,才能够应对接下来的变化。

所以我的愚见是不管做什么都好,多往技术那边靠,会是一个明智的选择。

看着那些高考完申报专业的准大学生们,有一些报翻译专业的,一些报中医专业的,一些报会计专业的等等,我都挺替他们感到可惜的。

如果他们学完这四年,然后毕业了,然后发现原来他们的专业都被人工智能代替了,到时候不就扑街了么?

可能以后有些人想要努力,都找不到个地方努力了吧,以前我一直认为努力一定会成功,后来发现错了,我看到那些流水线的人特么努力,一天上班十几个钟,看起来比我们努力多了,但是做的这些事情,除了那点微博的工资之外,就是浪费生命。可怕的是,将来可能连这种浪费生命的机会都不给了。

如果 Google 的无人驾驶技术成功了,到时候推上市场,驾校只能去开碰碰车娱乐场了吧?

也不知道到时候那些保安,服务员,售货员等的劳动者们,会有什么机会做什么事情呢?

也不知道再过二三十年,技术会让社会变成什么样?

可能正是因为未知,才有那么多的期待,才有那么多的精彩,也才有那么点希望吧。

written in 个人观点

编程人生中的一些经验分享

1. 怎么让自己比别人牛逼一点点?

其实说实话,现在不管是培训出身还是院校出身,现在开始去互联网公司上班,相对来说起步有一点晚了,工作也没有以往那么好找了。

不过你应该可以看到一个普遍的现象,就是现在大多的程序员在编程的时候,所借助的资源大部分来源于百度,CSDN博客,还有一些其它的论坛。 不是说这些不好,只是大部分质量参差不齐,有些博客抄来抄去,你很难去费很多时间精力去筛选,去辨别。

那么这个时候应该怎么办呢?最好的方式就是走比别人相对难一点点的路,怎么说呢?就比如别人在百度的时候,你已经花了点钱自己搭了个vpn上google搜索第一手资料,别人在看国内的博客的时候,你已经一边翻译一边看着官方文档,虽然很多英语都不懂,但是如果坚持一点一点坑下去的话,其实差距慢慢就拉开了。当别人还在把遇到的报错截图发给别人问怎么办的时候,你已经学会试着看看源代码别人是怎么写的。你会去github上pull跟自己最近水平相当的项目进行学习了等。

这以上举的例子要说明的就是:虽然起步比别人晚,但是不代表结果就是人家先到终点,别人在一小步一小步的走甚至停留在原地的时候,我们可以让自己的步伐跨的大一点,也就是提高自己的起点,当然刚开始很难,但是能够转变这种思维,一直坚持下去的话,到时候回头看的时候,才发现,原来你已经走了这么远了,而别人,还在原地。

2.选择是很重要,但是要学会选择很难

我们都知道,选择比努力重要,如果你一不小心选择错了方向,那么很可能就南辕北辙了,这是非常不值当的,如果能在对的方向努力下去,那么有一天才能收获你的果实。举个不恰当的例子:你去追一个女孩子,人家对你一点兴趣都没有,你一个劲的给对方自以为的感动,人家只会觉得很烦,离你越远越好。

所以选择是很重要的,你需要要有辨别能力,需要对信息有足够的敏感度,比如现在新手,想要开始学编程,从事互联网的事业,那么选择什么方向呢? 是Android,还是iOS,还是java,还是 python? 如果这时候你没有一个好的选择,真的不同的努力结果差别会很大。

就拿 Android 和 python 来说,现在 Android 开发已经没有那么火了,为什么?市场已经很饱和了,而且现在Android对中高级的人才需要比较多,而如果你现在开始学Android,学个一两年,到时候可能你已经算是Android中级开发者了,可是人家不需要了。

而现在的人工智能,数据分析,区块链等都是趋势,这时候 python 借势火了一把,很多公司都需要 python 的人才了, 所以从现在来说,一个学习 Android,一个学习 python,一年后,谁更容易找到工作,谁更被需要? 答案肯定显而易见了。

选择是很重要,但是要学会选择很难,因为你需要信息的敏感认知,而这些信息不是说你看书就能找到了,你需要有自己的圈子,有一个环境,有一个耳渲目染的地方,而这些,除了靠自己的人品之外,还需要自己多提升自己,打入更大的圈子,去学习,去交流。

written in 个人观点

Java Ssm 02

Java SSM 教程第2期

maven

  1. 项目管理 提供了一套完整的构建生命周期的框架

  2. 整合工具 自动的完成一些工程的基础构建配置

maven安装

  1. 配置好jdk的环境变量的
  2. 可以在http://maven.apache.org/下载maven
  3. 配置一下maven的环境变量
  4. 运行 mvn –version 校验是否安装成功。

maven配置文件

  1. 配置镜像:
1
2
3
4
5
6
 <mirror>
  <id>alimaven</id>
  <mirrorOf>central</mirrorOf>
  <name>aliyun maven mirror</name>
  <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
  1. 配置本地仓库
1
 <localRepository>E:/mavenrepository</localRepository>

maven特性

  1. 依赖管理
  • jar包依赖
  • 工程间的依赖
  • 继承
  • 聚合
  1. 工程类型
  • war包的项目
  • jar包的项目
  • pom工程

written

推荐几个比较高质量的java学习博客

1.主要涉及到java基础内容和设计模式,SSM框架教程:

java ssm 教程

2.作者从事过Java开发,近几年来主要做iOS技术相关的工作,对无线产品相关和iOS平台相关开发有粗浅理解,关注新兴技术。喜欢不断深入某技术领域的原理研究,喜欢解决有挑战性的问题:

[Java文章列表 三石·道](https://link.zhihu.com/?target=http%3A//www.molotang.com/java)

3.随着这几年的发展,并发网组织翻译和原创了几百篇技术精品文章,包括Java,C++,JS,开源框架,管理和架构等,所以并发网从最初致力于并发编程的研究和传播,进化成现在的致力于精品技术的研究和传播:

并发编程网 - ifeve.com

4.egg,一个疯狂的Java爱好者!这里会分享一个程序员成长所需的点点滴滴,以Java为主体,从基础到高级知识都会不断的出炉:

智慧演绎,无处不在 - CSDN博客

5.阿里巴巴网络技术有限公司 资深研发工程师的blog:

HollisChuang’s Blog

6.满满的java干货:

java_my_life - 博客园

7.ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站:

专注Java & Android 技术分享

8.我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理。awesome-java就是 akullpp 发起维护的 Java 资源列表,内容包括:构建工具、数据库、框架、模板、安全、代码分析、日志、第三方库、书籍、Java 站点等等:

jobbole/awesome-java-cn

written in java

什么是区块链,真心希望区块链和智能交易越来越牛逼

身处互联网时代的我们已经对这么高效的信息传递司空见惯。以前要下载一部小黄片或者想听听周杰伦新出的歌都特么很心疼自己的 2g 手机流量。

有些小黄片网上找不到,还得花点钱找手机店的老板帮忙下载。但是现在不一样了,互联网已经牛逼的把这些信息不对称给打破了。想要利用信息不对称赚点小钱越来越难了,所以现在的手机店老板还是乖乖的卖手机,修手机,忽悠一下大爷大妈比较实际一点。

虽然互联网在一定程度上打破了信息不对称,也给我们的生活带来许许多多的便利。不过对于信息本身的价值,却很难得到保护,比如说前一阵子的差评洗稿,现在云盘上的一系列付费教程都被分享个精光等。许多版权都得不到很好的保护,虽然现在很多官方都在保护版权方面加强了力度,不过呢,问题多多少少还是有。

信息与价值是密不可分的,然而互联网做不到将信息和价值一起传递。还有我们现在的数字电子货币,互联网也做不到不依赖第三方。

在《谈谈文字和货币》中有说到我们现在依赖的第三方产生的一些信任问题,成本问题。

1.中本聪的区块链

所以中本聪大哥认为这样不对劲,如果有一种去中心化的系统来记账,不依赖第三方的情况下能够可靠没毛病就好了。

但是要做成这样的系统是很复杂的,首先要确保以下两个问题:

第一,账本的数据,每个参与的人都可以存储一份。不能够被第三方掌控着。

第二,每个参与的人都有权利记账。不能够被第三方掌控着。

对于第一点来说有点类似于我们的某一项目,项目里的数据库,用户产生的数据,在每一台服务器都放一份。也就是分布式。并不是很复杂,只要能保证参与的每个人都能保存到完整的账本就可以了。

不过对于第二点来说就不是那么简单了,想想每个参与的人都可以记账,那岂不是乱了套?如果有人花了 100 块钱去爽了一把却在账本上记了 1000 元怎么办?且不说这种恶意乱来的,即使每一个人都本着善良诚实来记账,但是每个人所处的环境不一样,接收到的记账信息也会有偏差的,所以账本会出现不一致的情况!

记账,账本都不一致。那还记个毛线?

所以要同时满足以上两个条件,难!不过对于中本聪大哥来说,难,不代表不能。男人,总不能因为遇到一点难题就退缩吧?

那么怎么办呢?

2.竞争记账

中本聪大哥构思了一个叫区块链的系统,这系统有个核心的东西叫「竞争记账」。正是因为这一关键才解决了上述的问题。

那么是怎么解决的呢?

竞争记账说白了就是让每个计算机的算力来竞争,谁的算力牛逼就拥有一次记账的权利。

在比特币系统里面,大概每十分钟就会有一次比赛,比如你的计算机很是牛逼,通过算力跟别的计算机pk,最终你赢了,那么恭喜你,你拥有一次记账的权利。那么你就可以在这一轮中向账本记账,然后同步到其它人的账本中去。那么多只眼睛在看着你记账,所以由不得你乱来。

不过你这时候可能会想,老子辛辛苦苦通过竞争赢得了比赛,还要去记账,这何必呢?

中本聪大哥早就看出了你的心思,不给点动力刺激一下,怎么会有人去竞争记账呢?所以会给每一轮优胜者奖励比特币。

而这一奖励过程实际上也解决了去中心化系统中比特币的发行难题。

不过你可能会想,那有人作弊怎么办,怎么能保证大家的算力竞争都是平等公平参与的呢?

3.工作量证明

区块链有个叫做「工作量证明」的机制,简单点说就是我扔给你一堆碎片,让你拼成一张图,虽然我没有一直盯着你在拼图,但是最后能看到一整张图被拼好了,那么就证明你确实做了这么多工作了。所以区块链也是这样,通过一个又一个特定的结果来确认每个参与者是不是完成了相应的工作量。

不得不佩服中本聪大哥,搞这么一套「奖励 算力竞争 记账 奖励」循环系统,把去中心化记账难题给解决了。还不知道他本人是谁。可能中本聪是崔健的粉丝,假行僧听太多了:

我要从南走到北, 我还要从白走到黑

我要人们都看到我, 却不知我是谁

我不愿相信真的有魔鬼, 也不愿与任何人作对

你别想知道我到底是谁, 也别想看到我的虚伪

4.为什么那么多人都觉得区块链牛逼?

因为区块链这种「共识机制」使得去中心化成为可能,在区块链面前,中介什么的无法恶心我们。此外,本文提到的互联网不能同时传递信息和价值,而区块链可以通过脚本语言来转移价值,所以那些以数字形式进行价值传递的都可以用区块链实现。

在需要信任的地方,就可以有区块链。

传统的记账方式说白了就是记录,仅仅是记录一下,但是区块链就不一样了,它是可编程的,也就是说区块链不仅仅是记录,它可以通过程序自动进行交易。

比特币的一个典型的例子就是「多重签名技术」,通过脚本实现智能交易,例如在一个多人共同管理的账户里,系统本身已经事先写好了脚本,如果达到了事先约定数量的人同意了,那么才能对账本的钱进行交易,这个过程完全是系统本身执行,完全不依赖于第三方。

if(超过50%人同意){ 可以动用账户的钱; }else{ 滚蛋; }

5.智能合约

区块链上的智能合约也大概如此,系统把合同用代码的形式搬到区块链上来,这合同不需要任何人来监管,系统自动执行,只要符合条件,就执行合同的内容。比如说最近世界杯比赛,你赌阿根廷会赢,只要通过智能合约事先通过脚本规定好,你把钱扔进系统里,只要阿根廷赢了你就赚大钱,迎娶白富美,那么这时候系统等比赛完之后,会自行根据比赛结果判定,完全不需要第三方参与。当然了,结果阿根廷输了,梅西都不慌了,你还在凉凉。

真心希望区块链和智能交易越来越牛逼,到时候我们的生活又是一种翻天覆地的变化。就像互联网越来越牛逼一样,我们再也不用花钱去买小黄片了。

5.相关文章

聊区块链之前,先谈谈文字与货币

区块链入门教程

https://www.zhihu.com/question/37290469

written in 区块链

Java进阶SSM(Spring+SpringMVC+MyBatis)框架分布式高并发java Web项目实战教程

第一期

第一节 课程介绍

面向的学习人群

  1. 具备有一定的java基础
  2. 初级开发者 -> 中高级开发者
  3. 具备一定的自学能力和执行能力!

技术选型

  1. Spring
  2. Spring MVC
  3. Mybatis
  4. redis
  5. solor
  6. EasyUI
  7. UEditor
  8. JQuery
  9. Freemark
  10. activMQ
  11. httpClient
  12. MySQL

开发环境

  1. InteliJ IDEA
  2. Maven
  3. Tomcat7
  4. JDK
  5. Nginx
  6. Git
  7. postman
  8. sqlyog
  9. win7

计划

  • 技术架构 (集群和分布式架构的区别)
  • 工程搭建 (maven)
  • SSM框架的整合
  • Mybatis逆向工程以及使用
  • 日志的添加与使用
  • 拦截器
  • 后端功能 (系统的开发,图片系统,数据等等)
  • 前端功能 (商品浏览,下订单,购物车等等。。)
  • redis使用以及集群搭建
  • solor使用和集群搭建
  • JMS 消息队列 (activMQ)
  • sso单点登录
  • restful服务
  • 在Linux上部署

第二节 架构

传统的集群架构 和 分布式架构 区别

集群架构

缺点:

  • 耦合度太高了
  • 增加了团队的合作成本
  • 不能够去灵活的部署

分布式架构

优点:

  • 项目拆分成多个模块,耦合度降低
  • 单点运行,团队合作效率高了
  • 可以灵活部署

缺点: 需要去额外的开发,让各个模块之间能够通信!

第三节 代码托管Git的使用

Git

代码的版本控制管理系统

Git可以干什么呢?

  1. 防止代码丢失
  2. 远程同步代码
  3. 团队合作利器
  4. 代码还原
  5. 记录代码版本

安装Git

Git的常用的操作命令

  • git init 初始化git仓库
  • git status 查看状态
  • git add 将文件添加到git仓库的暂存区
  • git commit 将添加到暂存区的文件提交到git仓库
  • git log 查看日志
  • git branch 查看当前的分支
  • git branch hi 创建一个叫做“hi”的分支
  • git checkout hi 切换分支到“hi”分支
  • git branch -d hi 删除“hi”分支

第四节 GitHub使用指南

Github

全球最大的社交编程网站!

Github 有什么用?

  1. 代码托管
  2. 多人协作
  3. 个人博客
  4. 个人简历

使用Github

  • star 收藏
  • fork “复制”
  • repository 仓库
  • watch 观察
  • Gist 代码片段
  • Pull Request 请求合并
  • Issue 提问题/bug

  • clone 克隆
  • git config 配置信息
  • git push 将本地代码推送到github上去
  • git pull 将远程的代码更新下来
  • git branch 分支
  • git merge 合并

ssh授权

生成秘钥:ssh-keygen -t rsa
验证是否授权成功: ssh -T git@github.com

```
	Hi testWistbean! You've successfully authenticated, 
	but GitHub does not provide shell access.

```

SSM框架相关文章

SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)

【肯定会】1-1课程项目介绍 (从0到1开发java高并发分布式SSM项目教程)

如何快速学习ssm 框架?

written in java

聊区块链之前,先谈谈文字与货币

在聊什么是区块链之前,先谈谈文字与货币

有这么一个故事,说的是一群只说一种语言的人在“大洪水”之后从东方来到了示拿地区,并且决定在这修建一座城市和一座「能够通天的」高塔;上帝见此情形,发现人类太特么牛逼了,如果他们一旦建立起来,那么人类一定吊炸天,所以上帝就把他们的语言打乱,让他们再也不能明白对方的意思,还把他们分散到了世界各地。

他们要建的这个塔的名字叫做「巴别塔」,故事来自《希伯来圣经》。

想想就牛逼,如果整个人类真的能够零障碍的交流起来的话,那真的能上天!

文字承载着信息

而人类交流过程中的载体就是文字和货币,文字语言承载的是信息,而货币承载的则是信用。

文字在精神层面得到发展,货币在物质方面得到传承。文字出现之前人类的交流主要靠口授,他们的记忆也只能来自传说,到了文字的出现,人类开始有了深层次的记忆,有了思考,有了逻辑,有了思想等等。

印刷术的出现更是让人类的知识传播得到了质的飞跃,再到第二次工业革命的电报,电话的出现,人们的信息时代出现了雏形。

互联网的发明,标志着信息时代的到来,人们的信息竟然可以被互联网量化,将信息量化为一个又一个的「比特」。想想以前人们知道用「米」来量化距离,「克」来量化种类,「牛」来量化力。哪里还知道有个叫做「比特」的东西这么牛逼的存在。甚至还迎来了信息爆炸的时代。

信息的不断演化,信息的革命已经深刻的改变了人们的精神层面的交流。

货币与信任

而货币和信用的关系是密不可分的,可以把货币当做是信任系统。

不过货币在一开始的时候并没有建立信任,人们要求货币本身要有实际上的价值。比如说你需要拿「两根棒棒糖」才能换我的 one night stand。你拿一张画着数字的纸没用,老子不认。

这是传统的货币理论,他们认为货币的本质就是商品,就是一般等价物。

而现在呢,货币已经是信用的象征,货币不需要有什么实际上的价值,但是它具有一定的购买力,而这种购买力的背后就是人们都相信:「货币是具有价值的」。哪怕它实际上一文不值,但是只要人们都相信它有价值,它就是有价值的了。所以现在货币的最本质的属性就是信用。

现在我们已经很少使用现金了,出门带个手机,手机已经绑定了银行卡,微信支付,支付宝支付现在国人用的最6,这些电子货币的产生本质上也是因为我们都相信,这些数字就是有价值,就是有购买的能力。

不过现在的电子货币还是处于初级阶段,我们知道我们都依赖银行,依赖微信,依赖支付宝这些第三方的机构才能够顺畅的去买买买。这些第三方是不是值得信赖,传输成本是不是不利于我们,这些信用与价值的问题能否得到改善?

相信你应该知道答案了!

written in 区块链

为什么我会推荐新手从Python开始学习编程

人生苦短,为什么要学习python呢?

1.现在 Python 已经成为世界上最流行的编程语言之一了,而且大部分的Linux系统,MacOS系统都直接内置了 Python ,就问你牛逼不?

2.现在连小学生都开始学习 Python 了,Python 已经纳入了某地区小学的教材了。Pyhon 已然成为了编程界的 “网红”,现在程序员们可能不知道Cobol,Basic,Pascal,Perl,Ruby,但没有一个程序员不知道Python的。

3.上手简单,现在很多从来没接触过编程的人都着手开始学习Python 了,我有一朋友,之前没有任何编程基础,学了半年多找到了份工作,工资12k妥妥的,不过不要去羡慕别人的数字,人家背后的努力你没看到而已,如果你现在也是没有任何基础或者想要从0学习 Python ,那么你来对地方了!因为人生苦短,我们一起搞 Python。

4.web开发,科学计算,3D建模,人工智能,嵌入式开发,云计算,大数据等等都特么能看到Python的身影,不知道你知不知道NASA(美国宇航局)使用Python来开发用于系统集成和卫星,火箭的测试自动化么?还有网易,腾讯,搜狐,金山,豆瓣,YouTube ,google,雅虎(太多,举例不完)都在用Python。所以这么牛逼,何不 pick up python 呢?

5.用 Python 可以做很多事情,可以爬取你想要的数据,可以做外挂,之前的微信跳一跳,12306抢票等都可以用Python实现,还有很多数据分析,项目系统,聊天系统,游戏等等多了去了。所以这么牛逼,何不 pick up Python 呢?

written

微信,你凭什么监控我的奶子

前两天看到阿里的产品经理发表了一篇名为《微信,你凭什么监控我的聊天记录?》的文章。他谈到了「隐私问题」,质疑微信监控了他的聊天记录。

他在刷朋友圈的时候,看到一条「中街1946」的广告,也就是这条广告让他怀疑这是微信通过他的微信聊天记录监控而分析推送的,他的理由是这样的:

突然觉得阿里的产品经理也没有想象中的牛逼,如果通过技术手段,通过用户聊天记录去过滤关键词,然后推送相应的广告给用户,这特么不是傻逼操作吗?

一般产品经理都懂的用户画像吧,用户的性别年龄地址,支付行为,经常支付的商家,微信好友之间的关系,用户常关注的领域,用户经常看的文章方向,微信授权的其它操作行为等等,这些数据足以构成清晰的用户画像。

放着这些不做,去监控聊天记录再投放广告?

当然,微信有没有去监控用户的聊天记录咱们不知道,毕竟微信不想监控,可能也有人要他们监控。但是如果仅仅凭着存活者偏差的问题就对着微信说「你凭什么监控我的聊天记录」,那么就有点可笑了。

我发现有些人很容易从自身出发,对事情本身还没有了解其逻辑,就直接下定论:看!大数据真的很恐怖。

突然想到之前有个朋友好搞笑,他说有一天他跟朋友喝喝茶吹吹牛逼,谈到自己要买某个品牌的鞋子,结果第二天淘宝就特么在首页推介给他那个品牌的鞋子。他说现在淘宝真的好牛逼啊,能通过手机的话筒监听到用户的行为进行数据手机分析了。

如果哪一天,我们能在心里想需要什么东西,然后微信通过我们的意念知道我们的想法,然后推送给我们广告,那应该很牛逼,到时候我们就可以说:微信,你凭什么监控我的奶子?哦不,脑子!

written in 个人观点

使用搬瓦工快速搭建自己的VPN翻墙

1.使用搬瓦工搭建自己的VPN

很多人想要使用 VPN ,不过去购买第三方的「VPN账号」除了不稳定外还怕不安全,有些第三方 VPN 会获取用户的数据做一些坏事,还特么限制网速,不能忍!

所以想要自己买一台服务器,然后搭建一个完全属于自己的 VPN ,稳定快速又安全!

那么就在这里教大家使用搬瓦工官网(性价比很高的云服务器提供商)来快速的搭建自己的VPN。而且我还 会告诉大家怎么花比别人少的钱购买服务器,下文将会提到怎么获取优惠码,一般人很少知道的!

2.使用搬瓦工搭建VPN前的准备

  1. 一台可以上网的电脑。

  2. 支付宝或者PayPal账号, 搬瓦工支持支付宝付款。

3.购买搬瓦工VPS云服务器

3.1选择搬瓦工VPS服务器

点击搬瓦工官网进入搬瓦工官网(待会在这里可以从中拿到优惠码)。进入之后可以看到可以购买的VPS云服务器,个人使用的话购买最便宜的就好了, 比如这里有个19.99刀一年就不错:

使用搬瓦工搭建VPN

不过便宜的很抢手,很快就会被人买光,比如这里 no stock 就说明被人买光了:

使用搬瓦工搭建VPN

3.2拿到搬瓦工优惠码

ok,我们点击「Order KVM」:

使用搬瓦工搭建VPN

当你进到这个页面的时候呢,别急着点击「Add to Cart」添加到购物车,这里面暗藏着一个优惠码,很多人不知道,使用浏览器查看源代码, chrome浏览器的话按F12,然后搜索「code」,你会发现有一个 「Try this promo code: xxxx 」,这个xxxx就是优惠码,你把他复制下来,待会有用。

使用搬瓦工搭建VPN

页面的 Location 就是选择服务器的地址,到时访问谷歌的时候会显示你当前访问的地址。好了,我们点击「Add to Cart」。

接下来,进入结算页面,我们刚才复制的优惠码就派上用场了,将你刚刚复制的优惠码复制进去然后点击 「Validate Code」,看!是不是优惠了!一般人不知道这种操作:

使用搬瓦工搭建VPN

接着点击「CheckOut」完成付款即可。付款的时候选择 Alipay 就可以使用支付宝付款。

使用搬瓦工搭建VPN

4.快速搭建搬瓦工VPN

购买完毕后你就拥有一台你自己的服务器了,接着点击Services下的MyServices,可以看到你的服务器:

使用搬瓦工搭建VPN

我们点击「KiwiVM Control Panel」进入管理界面:

使用搬瓦工搭建VPN

可以看到你服务器的信息:

使用搬瓦工搭建VPN

接着我们点击 「OpenVPN Server」:

使用搬瓦工搭建VPN

接着点击:「Install and configer OpenVPN Server」,搬瓦工会自动帮我们在服务器安装和配置,不需要自己手动敲命令了:

使用搬瓦工搭建VPN

等一会就搭建好VPN了,就问你快不?

使用搬瓦工搭建VPN

5.使用搬瓦工openVPN

PC端使用搬瓦工VPN

首先我们下载我们的VPN文件所需的配置文件,点击「Download key Files」下载,然后再下载openVPN的客户端:

使用搬瓦工搭建VPN

接着将配置文件解压到openVPN客户端的config文件夹下:

使用搬瓦工搭建VPN 使用搬瓦工搭建VPN

接着再openvpn目录下的bin目录双击打开openvpn-gui,然后Connect连接,连接完成右下角的openVPN会成为绿色状态,然后就可以访问外网了,速度很快:

使用搬瓦工搭建VPN 使用搬瓦工搭建VPN 使用搬瓦工搭建VPN

iPhone端使用搬瓦工VPN

同样,手机也需要安装一个openVPN的客户端,不过你在中国区的 AppleStore 下载不到 openVPN 的,你需要注册一个美区的苹果账号,然后搜索 openVPN 下载:

使用搬瓦工搭建VPN

接着电脑端下载 iTunes ,打开 iTunes ,手机用数据线连接电脑,点击文件共享:

使用搬瓦工搭建VPN

将刚刚下载的vpn文件解压,然后添加到OpenVPN客户端:

使用搬瓦工搭建VPN

手机打开OpenVPN APP, 点击 + 这个按钮:

使用搬瓦工搭建VPN

然后就可以连接了:

使用搬瓦工搭建VPN

访问速度杠杠的:

使用搬瓦工搭建VPN 使用搬瓦工搭建VPN

Android端使用搬瓦工VPN

Android手机使用搬瓦工openVpn同上,下载openVpn App ,然后把配置文件传上去就行了。

相关文章

利用搬瓦工VPS自建高速丝滑VPN

在搬瓦工中搭建个人vpn

搬瓦工VPS搭建VPN轻松访问Google等

written in vpn

GitHub把自己卖给微软50亿,对开发者未必是好事

今天看到新闻说微软收购 GitHub 已经完成了,就差最后的公布了!

其实在 2016年 的时候,微软也有个开放源代码托管网站来的,名字叫做 CodePlex ,意在为工程师建立一个可供代码下载和共享的社区。

不过在2年后 GitHub 诞生了,很多人都转移阵地,投向 GitHub 的怀抱。微软那时候不爽啊,那时候频繁对开发人员施压,让他们在CodePlex上做开源项目。

不过没什么鸟用,因为全世界的开放源码几乎都已经汇集到GitHub。所以微软在2017年4月,宣布将其对抗 GitHub 的代码开源库 CodePlex 关闭。

现在微软是 GitHub 的最大贡献者,有超过 1000 名员工积极地将代码推送到 GitHub 的存储库上。

其实 GitHub 早就想把自家卖了,只不过一直找不到合适的人选!听说这次被收购的部分原因是微软的CEO 「Satya Nadella」。

有趣的是今天 GitLab 连发两篇博客,一篇恭喜 GitHub 被微软收购,另一篇教大家怎么把代码迁移到 GitLab 上来。哈哈GitLab居心何在?

但是很多人关心的是,微软收购了GitHub,以后国人使用GitHub会不会是个问题,比如是不是GitHub上的某些特殊项目访问不了,接下来有可能强制实名,绑上手机或者绑上微软的office365 ID,还是会像「必应」那样,搞个国内版和国际版,那就特么恶心了!

可能这次对于 GitHub 来说是好事,但对于我们,未必是好事!

written in 个人观点

高效点,自学的方式高效点

我们以前在上学的时候,被老师逼过背课文,背单词,抄课文,写作业,那时候我们只是知道要完成这些任务, 如果没完成,老师就会对我们不爽,老师不爽就会告诉我们的父母,父母知道了,我们就会被他们打屁股。

这样的学习方式我理解为「被动学习」,说白了就是老子不情愿,被迫的,不爽,学不起劲,这样的学习收获极少。

与「被动学习」相对应的就是「自我驱动学习」,这完全是不同的概念,自我驱动的学习是老子想要干,老子想要 完成某件事!

那么怎么样的自学才高效呢?这个问题我之前也常常想过,看了许多学习方法,最后总结出来了自己的一套自认为 比较高效的学习方法,在这里跟大家分享一下。

老子想要完成什么事?

当我们心血来潮,想要学习某些东西的时候,不是马上去找这方面的书或资料然后直接”啃”,而是要想想,我要完成什么事情, 比如你想要做一个个人网站,或者想要做个APP?

首先要做的是确立一个「目标」,这个目标确保是可行的并且清晰的。哪怕我们要学习画画,用情感和想象力来泡妞, 也算是一个清晰的「目标」。 所以自学某一块知识的第一件事就是要明确:老子想要完成什么事!?

画个思维导图

明确了自己想要完成什么事情之后,别急着去找相应的资料”啃”先,这时候要做的是,先想想我要完成的这件事需要什么样的知识点, 上网搜索也好请教朋友也罢,把要完成这件事情需要掌握的知识点列出来,最好画个思维导图,建立自己的知识结构!就好比我们要去旅行, 首先应该是规划好路线,画个地图,绘制自己想去的地方的路线。

比如我们想要学习怎么去组装一台电脑,那么我们可以上网搜索发现需要学习的知识点:CPU、主板、显卡、存储、机箱、电源、内存、硬盘 等,我们可以画这样的思维导图:

定计划学习并实践

ok,现在我们已经有了思维导图了,这时候我们对我们要做的事情,有了明确的学习方向,那么这个时候我们就可以根据思维导图,给自己 定一个学习计划,这时候就可以”啃”资料了,重要的一点就是要实践,学以致用,就比如我们学习编程,如果学了之后一定要自己做 项目。

知识输出

当我们按计划学完了知识点,并且实践了,完成了自己想做的事情了,这还没完! 需要将自己学到的知识输出,也就是总结,方式很多,你可以写博客,写笔记,当你能真正的把知识点说明白,通俗易懂的让别人明白你在讲什么, 那么才说明你掌握了知识,我一般在写文章,写干货的时候,如果发现自己写起来不是很容易,或者不能通俗的说出来,那么我就知道这个知识点 其实我还没有真正的掌握。所以知识输出很重要,很多人都忽略了这一点。

以上便是我自己总结出来的认为较高效的学习方法,希望对你有用!

written

我在b站讲了关于如何使用Git和GitHub

Git

代码的版本控制管理系统

Git可以干什么呢?

  1. 防止代码丢失
  2. 远程同步代码
  3. 团队合作利器
  4. 代码还原
  5. 记录代码版本

安装Git

Git的常用的操作命令

  • git init 初始化git仓库
  • git status 查看状态
  • git add 将文件添加到git仓库的暂存区
  • git commit 将添加到暂存区的文件提交到git仓库
  • git log 查看日志
  • git branch 查看当前的分支
  • git branch hi 创建一个叫做“hi”的分支
  • git checkout hi 切换分支到“hi”分支
  • git branch -d hi 删除“hi”分支

Github

全球最大的社交编程网站!

Github 有什么用?

  1. 代码托管
  2. 多人协作
  3. 个人博客
  4. 个人简历

使用Github

  • star 收藏
  • fork “复制”
  • repository 仓库
  • watch 观察
  • Gist 代码片段
  • Pull Request 请求合并
  • Issue 提问题/bug

  • clone 克隆
  • git config 配置信息
  • git push 将本地代码推送到github上去
  • git pull 将远程的代码更新下来
  • git branch 分支
  • git merge 合并

ssh授权

生成秘钥:ssh-keygen -t rsa
验证是否授权成功: ssh -T git@github.com

Hi testWistbean! You've successfully authenticated, but GitHub does not provide shell access.

written

王尼玛被封,这次成了自己的暴走大事件

那个带着蠢萌头套,圆肚子,斜挂着纸巾,公鸭嗓的「王尼玛」,这两天应该很不好过吧…

现在时不时还会从口中说出 “shenmegui”,“你TM在逗我”,“Why are you so diao?” 的话, 可以看得出来暴走每一期都很精心的策划:笑点,正能量,节操满满!

不过暴走被封了,用他的话来说就是:“我和我的小伙伴都惊呆了”

暴走早期有一期视频内容主要是“讽刺植入广告太多”,不过其中的段子中的

“董存瑞瞪着敌人的碉堡,眼中迸发出仇恨的光芒,他坚定的说,连长,让我去炸那个碉堡吧。我是八分青年,这是我的八分堡。” “为人进出的门紧锁着!为狗爬出的洞敞开着!一个声音高叫着,爬出来吧!无痛人流!” 被认为是侮辱英雄先烈的句子,所以被多个平台封掉了账号,下架了视频。

突然有感而发,说几点想法:

  1. 王尼玛说了好多次小孩子不要看暴漫,可是有时候太有思想了,小孩子也会看!
  2. 像这样的三观正,讽刺的视频会越来越少了!
  3. 不得反思:在这个自由的时代,在边界里面玩没事,不要老是越界跳出去,那里不是自由区域!
  4. 就算你真的是“小粉红”,做好自己就是了,别告诉隔壁的小王!
  5. 暴漫被封与之前的事联想起来,好像知道点什么了!
  6. 王尼玛,祝好!

written

自学java的经验分享

我是大学的时候才开始接触编程的,不过我上的是一所三流的大学,高三的时候,因为我们班是重点班,所以有一些复读生也来我们班学习, 高三开始不久我就和一个高四的学姐谈恋爱了,还是我的初恋,所以我高考就GG了… 偏题了,回来说说编程的事情吧,虽然说大学 是个三流的学校,不过老师都是博士硕士级别的工程师,对于当时的我会觉得他们好像很厉害的样子,不过现在觉得有时候标签这种东西并 不能说明什么吧。

那时候老师说「移动应用开发」会火,到时毕业一定可以找到工作,所以我就想着主攻「移动应用开发」,也就是 Android ,因为 Android 的应用层是用 Java 写的,所以一开始学的是「Java基础」。

虽然说去上课,但是真正听老师讲课的人寥寥无几,我也不例外,电脑前演示着老师讲课的PPT,但是视线一直停留在自己手机里的美女上! 不过有一次老师讲到一个「使用for循环打印星星的例子」,我觉得好好玩,这时候我才发现Java并非那么无聊,我可以通过代码写出打印多少颗星星, 打印成正三角形的形状,倒三角形的形状,正方形形状等等。第一次有了一种「有想法可以被Java实现的感觉」,从那天起开始对编程产生了兴趣。 那一天虽然知道了「for循环」怎么写(跟着老师的例子照猫画虎),但是对于Java环境变量怎么配置,二进制是什么,什么是Byte,Long都不懂, 那时候可以说对于 Java 只知道怎么拼写而已。

因为有些知识点之前没认真听,也发觉老师讲的很无聊,后来我决定自学,自我驱动的学习和被动的学习是两个完全不同的概念, 我到现在依然认为:真正牛逼的人,都是自学能力极强的人!

我开始上网查询学习「Java基础知识」的资料,无意间发现了某培训机构(是哪个机构就不说了,免得说我打广告)有免费的讲课视频资料,看了一下目录发现 很全面,于是我下载下来试着看一下,没想到被讲师的幽默讲课方式深深吸引,感觉他不做相声演员都可惜了,于是边笑着看他的视频,边跟着他敲代码,自己 也做一些笔记。那段时间有时候逃课其实是在宿舍里看他的视频哈哈!

那个视频分为三十来天,不过我是除了跟着他敲代码外,我还会自己做一些笔记,所以我大概花了两个多月才把视频学完的,也就这几个月的时间我从如何打印Hello World,到常量,变量,注释, 运算,逻辑语句,再到面向对象(可能比较笨,当时想了好几天才明白这一概念),数组,函数,继承,内部类,多态,GUI等,再到集合,多线程,IO流,反射等等有一个 全面的了解了。

感觉自己把 Java 比较基础的知识学的差不多了,就想着能不能做点好玩的东西出来,记得有一个情人节,那时候我还是有女朋友的人,想给女朋友做一个程序,作为礼物送给她, 有那么一个夜晚,我突然想到能不能用 Java 做一款小游戏,游戏的主角就是她呢?

想想还是有点激动的,后来我模仿了「坦克游戏」,不过玩家的坦克是我女票的头像,而很多敌方坦克就是我的头像,然后子弹被我改成❤️(爱心子弹,害羞)。 用了几天的时间把它做出来觉得好有成就感,最后我把这个游戏打成jar包送给我女票当作礼物,然后看她玩的时候一直在互相发射爱心,不过敌方的“我”又多,射出的爱心子弹又快 每次玩不久她就“挂”了,老是叫我去修改一下游戏速度,让她赢哈哈。不过现在的她已经不属于我了,哎,想想还是有点小伤感呢,我是不是又跑题了 - -

因为我一开始就知道我学 Java基础 是为了去开发 Android 应用,那时候觉得能在手机上把玩自己开发的 APP 是一种牛逼的存在。所以我又去找了一些「Android入门教程」的视频 来学习,因为自己有了一定的 Java基础知识 ,所以对「Android入门教程」视频学起来倒不觉得很难,反而是想着快点学完然后做个有意思的APP出来,所以那时候对Activity,Service的生命周期、 一些诸如TextView,EditView基础的组件、诸如LinearLayout的容器组件、广播、Intent、内容提供者消费者、Sqlite等基础内容都学的比较认真。

慢慢的我开始写了一些demo,但是发现很不满意,原因是我感觉自己写的东西很简单,几个界面跳来跳去,几个增删改查,好像就没什么了,感觉自己没有什么项目经验,而且停留在很基础的层面。 于是我上网去搜索一些Android相关的资料,发现了CSDN上的郭霖大佬,发现他写的博客通俗易懂,那时候他每篇博客我都看,慢慢的我又认识到了老罗,他写的东西很深,有时候我看不懂 - -, 发现了阮一峰的博客,再到后来我又发现在知乎上的 stormzhang , 我这才发现当时自己有多low,这些大佬们经验满满,我从他们身上学到了自定义View,性能优化,网络编程,第三方框架等等比较进阶的东西, 不过更加让我学习到的是他们的执行力,他们的认知,他们对信息的敏感。

举个例子:记得 Android Studio 刚出来那会,我只是觉得 Android Studio 应该和 Eclipse 差不多吧,不就是个编辑器,而且Android Studio 刚出来的时候我下载体验了一下卡的要死,我二话不说就把它给卸载了, 什么垃圾玩意。不过后来我发现大佬们都在用 Android Studio ,而且一直在建议更换为Android Studio,我记得之前大佬们还专门写了Android Studio的教程。受他们的影响,我把Android Studio下载回来, 随着Android Studio的迭代,我才发现这编辑器简直了,这快捷键,这体验,简直完爆 Eclipse。现在还看到有些人还用Eclipse开发,没用过Android Studio或者InteliJ IDEA,说实话,我面试到这样的人一般会直接pass掉, 并不是说Eclipse不好,而是认为,如果不拥抱新的事物,总是固步自封,那么很快会被淘汰!

也是那个时候,我发现了原来还有一个叫做「GitHub」的东西,简直打开了新天地,上面有许多大牛分享代码,聚集了全世界的程序员,吓得我赶紧去学习了Git的操作命令, clone了几个觉得适合我当时学习的Android项目源码!

后来和朋友参加软件杯比赛,能写出比较像样的APP了,看着APP从想法到实现,确实能带来很多成就感,也许对别人来说并不完美,但是自己亲手写出来的,自己debug出来的APP,就 是有不一样的感觉,觉得像自己的孩子一样哈哈!

在大学期间,自己用PS画UI,使用第三方的接口,琢磨着做了几个小众的APP,后来将这些APP都装在自己的手机上,面试的时候一边演示一边跟面试官吹吹牛逼,倒也拿了几个实习offer。 后来自己选了初创公司,刚开始就让我接手一个比较大的项目,功能挺多的,商品上下架,第三方支付,商家入驻,到后来甚至还做了附近的人,聊天约炮交友,抽奖等功能,我和另一个小伙伴负责这个项目 的Android端,虽然期间遇到了许多问题,如代码混淆打包闪退,不同手机兼容适配,支付配置不正确等等,也曾怀疑自己是不是能行过,但还是一一解决了,前前后后花了快一年时间也在应用市场上架了好几版,不过用户寥寥, 可能你应该想到了,产品还没运营,就夭折了。现在回头想想,产品思维确实重要。

接着也做了几款别的APP,表现也是平平 - -

可能因为表现的还行,后来老大说有个公众号的小项目,后端让我来搞怎么样,当时自己也发现原生APP的开发趋势下降了,H5越来越火, 然后跟老大说给我三天熟悉一下Java后端的,他说没问题,没想到这一决定让我开始转向后端开发了…

其实这三天主要研究了下 JSP , Servlet ,还看了点 struts2 框架,然后也算按计划时间倒腾出来了那个小项目,也是因为这个小项目我对 后端开发产生了些许兴趣,我觉得写逻辑接口提供给前端调用好像比较牛逼。

那段时间开始研究 SSH2 框架,使用它做了一个电商项目,接着发现很少人用 SSH2 了,然后研究了 SpringMVC 框架,JDBCTemplate ,进一步学习 Mysql的索引、视图,使用 PowerDesigner 设计数据库,Nginx ,Linux 操作系统环境搭建,操作命令,网络协议等等

不过之前做的很少涉及到高并发,后来比较流行SSM框架了,也使用过它来做一些分布式的项目,慢慢的了解到了连接池、消息队列、定时、缓存、搜索引擎、 渲染模板引擎等等。

由于SSM配置繁琐,现在Spring Boot/Spring Cloud这样的高效框架也越来越多人开始使用了。

越往后越发现基础的东西很重要,比如多线程的同步异步,各种集合的实现原理,面向对象,反射技术等!当然我非常不推崇那些对刚入门想学的同学 推荐一堆诸如:各种设计模式,JVM调优,负载均衡,各种数据结构和算法,链表、排序树,分布式存储,对象池,连接池,网络三次握手过程等等。 人家还没开始学,就给吓跑了 - -

所以我建议Java自学入门先从基础开始,再进阶到会使用各种框架,再慢慢了解其原理,加以项目辅助练习,最后再回来补充自己的弱项例如操作系统,数据结构,算法,网络!

列个大纲吧:

初级入门知识点: - 基本的语法 - 数组 - 面向对象 - IDEA工具使用,相信我,别用eclipse了! - 常见对象的使用 - 集合框架 - IO流 - 多线程 - GUI(可选) - Java网络编程 - 反射 - 《疯狂Java讲义》类似书籍或视频教程

进阶知识点: - 《Effective Java》类似书籍 - 数据序列化 JSON - 项目构建管理 Maven - 托管利器 Git - Spring 容器 - Spring MVC框架 - 安全框架 Shiro - ORM框架 MyBatis - 数据库连接池 - 模板引擎 - 分布式缓存数据库 Redis - 分布式全文搜索引擎 Solr - 任务调度框架 Quartz - 消息队列 MQ - 日志组件 Log4J - 分布式服务框架 Dubbo - 分布式协调服务 ZooKeeper - 微服务 Spring boot 、 Spring Clound

完善知识点: - 《深入分析Java Web技术内幕》类似书籍 - JVM - NIO - 设计模式 - Linux操作系统 - 数据结构 - 算法 - 计算机网络 - 数据库优化

我相信一个人去关注这一问题,或者看到这里,都是有一颗学习的心,不过很多人被所谓的过来人扔一堆「晦涩难懂的技术名词」吓到, 我是非常讨厌这样的人的,自以为很牛逼,摧毁初学者的信心, 凸!

建议入门的童鞋可以先看视频教程,因为有老师在演示会比较好理解,记得总结,总结很重要! 还有一点就是不要加入所谓的Java开发讨论群,Java学习群,因为十个有九个是在里面瞎逼逼,开车的! 还有一点就是使用Google,不要用百度!

希望以上的内容能给你带来帮助,如果能,我会很开心,如果不能,当做有个傻逼瞎逼逼一顿就好了。

对了,我有个公众号叫「肯定会」,如果你愿意的话,来一起玩!

written

应届毕业生,活该你找不到工作?

那个时候,你都背着书包,穿着校服! 那个时候,你有同桌,有老师,有领导! 那个时候,你想象着朱自清的爸爸给他买橘子的画面!

后来班主任告诉我们要参加高考,才可以上大学!大学很美好,那里很自由! 很多人相信了,于是拼了命的学习,大家都一副很努力的样子,好像对「压力」这个词有了进一步的认识。 每天做着卷子,背着单词,记着公式… 我们将这些狗日的日子称为「高三」,有些人甚至称为 「高四」!

虽然我到现在还是不明白为什么为了那几天的高考,把日子过得那般“酸爽”? 是因为穷还是梦想还是无知?

不管怎么样,你还是努力了!

“开始的开始 我们都是孩子 最后的最后 渴望变成天使 歌谣的歌谣 藏着童话的影子 孩子的孩子… 该要飞往哪去”

后来你飞到大学去了, 你再也看不到满街的试卷了, 看不到逼着你学习的班主任了, 看不到那位给你手抖夹菜的食堂阿姨了, 看不到不让你出去玩的门卫叔叔了。

你短暂的伤感,又很快融入自由的大学生活,你格外的轻松,好像对「轻松」这个词有了进一步的认识! 你觉得上课无聊,睡觉去了,你学会了找别人帮我们答到! 你发现无聊寂寞,恋爱去了,你学会了打扮自己! 你发现不能堕落,喝酒去了,你学会了假装谈理想,说未来! 你发现不能挂科,自习去了,你学会了欺骗自己,学习2分钟,上网2小时!

好了,快毕业了,你觉得时间过得太快了吧,怎么就毕业了? 你买了礼服,戴起了学士帽,邀请了亲朋好友,把自己打扮的人模狗样, 咔擦~ 毕业照里的你竖起了个yeah!

你突然想起来,你的梦想是什么了! 你突然回头望,怎么所有人都不见了? 你突然发现,怎么「应届毕业生」成了你的标签了?

你开始写起了简历,你在简历里备注:“我虽然项目经验不是很多,能力不是很强,但我愿意学习,与公司共同发展!” 你的简历沉入大海! 你四处打听,哪里要应届毕业生! 怎么别人都拿到offer了?

也许你现在很迷茫,很焦虑,很后悔?

不过现在努力依然来得及,不要怕,大部分人都和你一样! 这都是青春,不是吗? 所以现在开始持续的努力做自己喜欢的事,因为现在才刚开始! 拿offer是迟早的事 找到自己喜欢的事情,并持之以恒的干就是了!

加油!

written

产品经理,你不懂技术还老是跟我BB

在互联网公司有这么一个角色,叫 产品经理,简称PM!

话说程序员在上辈子就欠了PM五毛钱,所以你可以看到在很多地方的程序员都会吐槽PM:连技术都不懂,怎么当PM? 这时候PM不仅要向你要回五毛钱,还怼你一句:“老子要是会技术,要你何用?”

产品经理是不是一定要懂技术呢?

一般情况下,产品经理在负责一个项目的时候,会通过这么几个阶段:

1.需求收集,通过内部需求和对竞争对手的产品分析等进行数据收集。 2.需求分析,通过收集到的数据进行头脑风暴。 3.需求落地,画原型,和技术沟通需求,确认开发的时间。(也就是在这个时候,往往程序员和PM产生分歧) 4.项目跟踪,项目进度跟进,项目测试,验收。(这个时候也往往程序员和PM产生分歧) 5.项目上线。 6.数据跟踪。

这里还是我精简写到产品经理会涉及到的阶段,还有其他产品迭代,用户反馈,需求文档等等流程.. 所以你可以看到 「产品经理在需求落地与技术讨论开发周期」仅仅是产品经理需要做的一个阶段,产品经理 还有很多事情要做。

当然我并不是在替PM说话,只是认为PM需要知道的知识面应该要广,懂的技术会更好, 但并不是说PM在技术方面懂的越深入越好,而是在整个行业的知识层面认知程度、行业深度的了解程度是否更高。

所以程序员吐槽产品经理的最主要原因还是出现在沟通和相互信任上。 如果抛开个人情绪不谈,那么程序员在与产品经理首先应该互相建立信任,因为信任是最高级的情感(哇哦,这句话怎么会从我口中说出?), 在沟通过程中应该相互理解,尽量让对方懂!

PM不懂你的算法实现,不懂你的SQL语句,不懂你的多线程,那么你就简单通俗的解释给PM听,我这么做是为了性能更稳定。 PM也不要说什么老子不管,你就要在下班前把结果给我弄出来!最好的方式是面带微笑的说,如果这个做不了,我们可不可以用其它的方式?

当然还有一种“假PM”,技术不懂不说,自己该懂的东西一知半解,那对于这种“假PM”来说我们不需要还他5毛钱!

不过我相信这种“假PM”存活不久,所以珍惜你现在的PM,毕竟有些人或者事,错过了才后悔莫及!

written

个人简历

我记得我小的时候,总有那么几个小伙伴跟我玩的很好,一起在田野里面玩泥巴,打稻草人,放风筝… 到了饭点妈妈就会去喊我回家吃饭。愉快的跑回家,小小的手端着盛满饭菜的碗儿,坐在四合院门口边吃边 把吃剩下的骨头丢给邻居家的狗儿。

狗儿很听话,我吹个口哨,他就来了,我吃完饭,他就走了…

我很庆幸小的时候没有在城市生活,而是在乡村,让我体验了太多太多儿童的快乐。又让我想起了沈复的《童趣》,

“余忆童稚时,能张目对日,明察秋毫,见藐小之物必细察其纹理,故时有物外之趣。 夏蚊成雷,私拟作群鹤舞于空中,心之所向,则或千或百,果然鹤也;昂首观之,项为之强。 又留蚊于素帐中,徐喷以烟,使之冲烟而飞鸣,作青云白鹤观,果如鹤唳云端,为之怡然称快。”

后来到了所谓的城里上了学,偶尔还成绩好点给老师夸一下,也叛逆过,逃了课,抽了烟,泡了妞,被老师叼…

到了大学,各种部门社团,才发现我也要写简历,去面试..

那是我第一次写简历,很认真的思考,自己会些什么。不过后来才发现,社团这种东西, 简历不是主要的,主要还是要靠我的颜值,毕竟师兄师姐知道你才刚上大学,能有什么能耐?

后来步入社会要开始找工作了,又要写简历了…

那时候写简历很简单:就是打开word文档,然后想想自己做了什么项目,一股脑的写上去,在最后面说几句自己有多吃苦,多想为公司带来利益。 然后在网上开始一顿投简历,结果找我去面试的人寥寥无几 - -

我上次就看到一个国外的简历,简直刷新了我的三观,地址在这:http://rleonardi.com/interactive-resume/ 人家把简历弄得跟玩一样 - -

wistbean简历

所以我认为在写简历的时候呢,你应该站在面试官的角度去想,如果我是他,我想要看到的是什么,是项目经验?是工作年限?是简历上的照片? 简历的格式排版?

不管怎样,我认为你应该很想让他对你的简历留下深刻的印象,并且看完会迫不及待的找你面试的吧~

不妨看看以下这样的简历,或许能给你带来一些思考:

wistbean简历

做什么事情,比别人多花一点心思,就更容易脱颖而出!

地址在这:http://www.wistbean.com/public/index.html

written

有时候我们是在被动的乐观着

今天刷了一下 Youtube ,看到了一个 TED 演讲,因为演讲者长得挺帅的,所以我就看了下去,发现他的演讲挺幽默的, 他讲了一个故事,说是有一天他带着侄子去野炊,侄子4岁,是那种天真活泼的熊孩子,他和侄子玩着玩着发现在不远的 地方有位看起来年纪挺大的女人,于是熊孩子就直接跑去跟这位女人打招呼,女人问熊孩子多少岁了,熊孩子说:“你猜!”, 女人说我猜你应该4岁。然后熊孩子问了这位女人多少岁,女人也让熊孩子猜,熊孩子眉眼紧锁,想了一会认真的对女人说: “1000岁”。

(观众都笑了,我也傻逼的看着视频跟着笑出了声…)

女人笑了,侄子笑了,他也笑了,结果女人和他们成为了好朋友!演讲者说,这熊孩子给他们带来了快乐,也让他有了思考: “我们在小的时候,很容易满足,很容易就能感到快乐,可是我们长大了,却很难乐观,很容易去对自己产生思考阻碍的事情!”

我认为这应该跟我们的「性情」和「世界观」有关,熊孩子天性就是乐观,开心就笑,不开心就哭,生气就怒,而熊孩子长大后,开心就笑, 不开心也笑,生气了还笑,这种笑我们能够看得出哪些是发自内心的笑,哪些是出于礼貌的微笑,哪些是无奈的假笑,熊孩子不懂, 看到你笑便跟着笑,大人能懂,看到你笑会知道你有时候是在「被动的乐观」。

我发现大部分人都处在这种「被动的乐观」的状态里面,假如你是一个设计师,根据产品经理的原型画出了你认为非常好看,非常精简大方的 UI,可是你的领导(假设你的领导是个sb)看到之后说:“太丑了,这个完全无法看,今晚加班重新整一个吧!” 此刻你内心一定有很多句 “操你妈”想对他说,可是你发现不行,为了生存,你只好被动的乐观,微笑着说了一句:“好的,我这就去改!”

这种「被动的乐观」怎么破?

我认为这是没有公式可解的,不过我们可以去做一些事情来减少这种状况的发生,那就是去做自己热忱或者擅长的事情,提升自己的认知, 提高自己的实力,延迟自己的满足感。

我相信有一天,当有人说你的不是的时候,你能发自内心的对他冷笑一声:“真TM的傻逼!没想到我们的认知差这么远,我们没法做朋友,祝好!”

written

如果你的男朋友是程序员,请好好珍惜!

最近和团队的朋友经常一起吃晚餐,点几个菜,喝喝茶,也喝喝粥,不喝酒!

几人围一张桌,嘴巴除了用来咀嚼食物,也用来大笑,也用来谩骂,也用来吹牛逼,还用来开车!

大部分人都认为程序员一般挺闷骚,但是骚起来不是人,开起车来一个比一个快,带漂移的那种:

1.走在路上看到地面小卡片很多,小卡片里的美眉甚是诱人,胸跟一块钱两个的馒头那般大,一朋友最近在研究Spring boot, 于是我们调侃他:“快捡起卡片打电话呀,一起去开房,然后你跟她讲Spring boot框架的使用哈哈哈”

现在Spring boot成了我们的梗,动不动就让他去给别人讲Spring boot…

2.你最近学什么呢?推荐一本 深入浅出 的书籍给你可好?

3.你手机壳后面扣的环不要老是转啊,用久了会松的

4.你有没有听过 两只蚂蚁 的故事?

5.你知道为什么 栈 是先进后出, 队列 是先进先出吗?

6.老板,来份小鸡炖蘑菇吧!

7.今晚吃什么,吃鸡 吧!

8.“最近怎么老是换头像? ” “我头像牛逼吧?” “像!”

9.略…(不能再污下去了)

我时常发现,大部分人们会对某一类人或者事物很容易产生固定的看法,可能有点笼统! 比如有人就认为程序员比较宅,木讷,邋遢,就是会修电脑的,电脑遇到问题找他们就对了, 也有人认为程序员很厉害,智商超高,可以随随便便黑入别人的系统,看看别人在干什么,像大神一样!

实际上呢,大多程序员只不过是使用google比大家好一些而已!

也就是因为这种刻板印象,使得我们在刚认识新朋友的时候很容易对其定义为是什么样的人! 所以也就可以理解为什么很多人在自我介绍的时候会给自己添加许多头衔,很多公司在包装员工身份以及自己的企业形象也是如此。

我所认识的程序员,相处久了反而发现,他们是我认识的朋友中最有趣的,他们很幽默,也比较单纯, 同时也很乐意分享,乐意帮助,学习能力也普遍比较强!也是不折不扣的老司机!

如果你的男朋友是程序员,请好好珍惜!因为他对你没有太多的要求,只要你打扮得很漂亮,另外在他敲代码的时候不要频繁的去打扰他即可!

written

我最近发现的两个问题

我最近发现两个问题。

第一,信任一个人

一个人看到的东西和他所处的位置以及观看的角度是有一定的关系的,你哪怕是上帝视角,也不会是所有东西都尽收眼底的, 因为表面的只是一层外壳,有些外壳被装饰的很漂亮,差不多可以称得上是艺术品了,如果你对某一个物体进行细致的观察, 也许会发现,这和你之前看到的有可能会有出入。

举一个不恰当的例子,你看到你的朋友在电脑前看小视频,前前后后花了40分钟了,不过你并没有跟他坐在一起,从头到尾看他 在做什么,或者说没有大部分时间看他在做什么,你只知道他在看视频,花了40分钟。但是殊不知,他是花了39分57秒钟在搜索 好看的视频内容!

所以我个人认为要去信任一个人,应该不是简单的签个协议,也不是嘴上说的很6,我们在一起干,然后一定会怎样怎样,而是应该 把那层表面的东西掰开,站在同等的角度,观察一段时间,不管是为人方面,专业方面,发现靠谱了,才是值得信任的partner!

第二,认知

我以前认为认知就是我学习到的东西,比如我要开发到聊天的功能,那我就会去找相关的资料,使用websocket,websocket怎么去握手, 握手之后怎么通信?什么是双工通信?怎么去存储信息?用到消息队列?怎么用?

但是我后来才发现,这不是认知,这是「知识」。这些知识我都可以通过google搜索得到,我可以看到相关的demo,相关的文档。而认知 应该是你在什么样的场景下可以使用什么样的框架,你在什么样的需求下使用什么样合适的解决方案!

这应该就是思维方式的不同,而这种思维方式是google不到的,这需要经验,需要时间,需要环境,就好像几年前有人叫我去买比特币, 有人告诉我python语言会火,移动原生的APP开发趋势会下降,人工智能和区块链接下来会是个机会等等。这些都是他们的认知,而比特币 的技术原理,python语言怎么去编写等等这些是知识。

so:知识很重要,认知提升更重要!

written

一份程序员大礼包

精心的收集了一些我觉得很有用的开源书籍,相信看完绝对能大有所益,可以说简直是 「一份程序员大礼包」,废话不多说,进入真题:

  1. 《代码整洁之道》 让自己的代码看起来舒服,整洁。

  2. 《程序员的自我修养》 想要成为一个合格的程序员,扎实的基础是必不可少的。想要成为一个优秀的程序员,对计算机的发展需要有深入浅出的了解。那么不如我们说说其中某些方面的前世今生。

  3. 《Growth 全栈增长工程师指南》 这是一本不止于全栈工程师的学习手册,也包含了如何成为一个「Growth Hacker」。

  4. 《Pro Git》 团队合作利器Git,版本控制管理,这本书希望借助于你新学到的 Git 内部原理的知识,你可以实现自己的有趣的应用,并以更高级便利的方式使用 Git。

  5. 《Linux从入门到不放弃》 如果你从来没接触过Linux系统,正在找一份通俗易懂的入门教程,可能这正是你需要的。

  6. 《零基础学python》 python入门。

  7. 《Java数据结构和算法》 程序员必备,数据结构和算法的经典书籍。

  8. 《MySQL权威指南》 MySQL已经成为了最流行的开源数据库,Linux+MySQL算是标配了,这本书可以作为入门基础。

  9. 《IntelliJ IDEA 简体中文专题教程》 使用IDEA开发过的都知道,用了之后完全知道什么叫做编辑器,强大的插件和快捷操作,开发效率杠杠的。

  10. 《精通比特币》 从技术的角度去理解比特币。

  11. 《LeetCode题解》 面试遇到算法难题?要不在这里刷一刷。

  12. 《像IDE一样使用vim》 vim 是一款面向于程序员的编辑器,即使某些功能 vim 无法直接完成,借助其丰富的插件资源,必定可以达成目标。

书不在多,在于精,以上12本书算是质量很高的了!

以下是截图:

获取方式: 在公众号 「肯定会」 发送 「程序员」 就可以直接免费获取到以上这一份程序员大礼包了。

如果你觉得有用,点个赞,分享给身边的朋友,下次我再精选一些高质量的开源电子书籍,thanks!

written

让你的微信变成机器人

人,有七情六欲,有喜怒哀乐,有少壮不努力然后老大徒伤悲,有错过了然后才懂得珍惜。而机器人,在我们的印象里面, 它是没有灵魂的,它只能学习人类的皮毛,然后装模作样,说几句蹩脚的话,走着不协调的步伐。起初机器人在模仿人, 后来反而有人去模仿机器人。说几句蹩脚的话,走着不协调的步伐,跳着所谓的机械舞。是不是很有趣 :)

张小龙说微信这个用完即走的东西,不止让你走,还会让你时常回来。他的产品理念就是用户至上,非常关心用户的行为和 使用体验。这里的用户就是人,人们的使用行为以及心里变化似乎被他看透,为什么说是似乎?因为背后有大数据,有各种统计。

有时候我在想,如果我的微信不是我在使用,而是一个机器人在使用,也就是我用着微信就不走了,赖在那里,谁给我发微信, 我都能24小时回复,是不是很有趣 :)

可能你应该知道,有个网页版的微信,使用手机扫描二维码就可以在网页上和你的朋友们聊天了,它的地址是 https://wx2.qq.com, 那么我们就可以通过这个网页版的微信,拿到一些「请求规则」,然后接通「机器人的API」玩一玩了,是不是很有趣 :)

ok…进入正题

首先在chrome中打开 https://wx2.qq.com ,打开「开发者模式」,然后我们就可以看到:

二维码登录:

wechat

获取联系人:

wechat

返回联系人信息:

wechat

发送信息:

wechat

数据同步: wechat

通过观察我们可以发现微信网页版的大概流程是这样的:

1.打开首页,分配一个随机uuid

2.根据该uuid获取二维码图片。

3.微信客户端扫描该图片,在客户端确认登录。

4.浏览器不停的调用一个接口,如果返回登录成功,则调用登录接口。

5.此时可以获取联系人列表,可以发送消息。然后不断调用同步接口。

6.如果同步接口有返回,则可以获取新消息,然后继续调用同步接口。

我们从开发者工具获取到微信的API之后,我们就可以用代码来调用这些API来使用了,接着我们再找一个 机器人API(网上有许多机器人的免费API,申请一个KEY就可以使用了)。

那么我们可以当接收到朋友发来的消息之后,将消息作为参数调用机器人API,然后将返回的回复内容,发送给 对应的朋友就好了(这来截取部分代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 if(msgType == 1){
		if(SpecialUsers.contains(msg.getString("ToUserName"))){
			continue;
		} else if(msg.getString("FromUserName").equals(User.getString("UserName"))){
			continue;
		} else if (msg.getString("ToUserName").indexOf("@@") != -1) {
			String[] peopleContent = content.split(":<br/>");
			LOGGER.info("|" + name + "| " + peopleContent[0] + ":\n" + peopleContent[1].replace("<br/>", "\n"));
		} else {
			LOGGER.info(name + ": " + content);
			    String ans = tulinresult(content);
			    webwxsendmsg(ans, msg.getString("FromUserName"));
				LOGGER.info("自动回复 " + ans);
			}
		}
	}

效果:

wechat

wechat

当然你可以利用微信的API去做一些有意思的事情,比如收到用户的图片信息,然后去调用 一些识别图片信息的API,然后将信息发送给朋友,或者朋友问你天气,你可以通过天气的API 获取到天气信息发送给朋友等等。

最后,如果你要源码,想玩玩,可以在我的公众号「肯定会」中发送「微信机器人」获取!

written

三年多没打篮球也不忘科比的黑曼巴精神

今天去打篮球了,算了一下,大概有三年多的时间没碰过篮球了,当我拿着篮球,走进球场的时候, 已经是下午四点多了,阳光轻轻的洒落在篮球架上,我内心突然产生了错觉,好似回到了中学时期, 回到了校园,看到了那个穿着白色T恤和短裤在球场上打球的自己,那个深深迷恋科比的自己!

basketball

中学时期,对于我们班的男生来说,每天最喜欢的就是下午放学后的一段时间, 我们每天晚上打到七点多才一起回家,每天的校服都湿的可以拧出两桶水来,那时候我们 的兄弟情感很是纯粹,也很深,我想这应该就是篮球的魅力了吧,所谓 「无兄弟,不篮球」。

高中的时候我是住校生,第一次体会到和同学一起住,也是因为篮球让我们更加熟络起来,我记得 以前我们为了打比赛,很多时候早上五点多就起床,天还没亮我们就冲到操场上打全场,输了的一队 负责接下来一个星期的宿舍卫生打扫。有了这么一点小小的赌注,就会让我们很想赢!

那时候看NBA很多时候都是看湖人队,24号科比,玩NBA游戏的时候也是一直选湖人队,使用科比 这个角色,我算不上科比的脑残粉,但是他的「黑曼巴精神」让我学到了很多。

basketball

科比脚受伤的时候,手指受伤的时候,他完全没有逃避和放弃,我看到他无奈的表情,眼睛里泛着泪光, 可是他总是能很快的恢复,很快的复出,每一次出场,也总是没让喜欢他的人失望!

他的背后付出了许多,他曾说过 「你们知道每天早上4点钟的时候洛杉矶的样子吗?」

“如果你有梦想,你必须保护他。当别人做不到一件事情的时候,他们会告诉你, 你也不会做到,但如果你想做一件事,就去做吧!不要害怕失败,你不能总赢, 但不要害怕做决定!你要相信一些不同的东西是有可能发生的!” 这就是科比教会我的。

basketball

后来大学,工作了,就没怎么碰篮球了,但是时不时还是会关注科比,科比也退役了, 虽然他的8号球衣和24号球衣在斯台普斯中心退役,但是他的精神对我的影响永不退役!

written

GitHub:全球最大的同性gaoji社区

GitHub是什么?

我们总是喜欢美好的东西,如果你经常玩微博或者推特,你应该有关注你感兴趣的人,你喜欢的人,或者你暗恋的人,不过你不会去关注一个 “二狗子”,因为他啥也不是,给你带不来任何价值。

GitHub就有点像微博,我们可以在上面看一些牛逼的人物写着开源的牛逼代码,觉得喜欢的话就给他们点赞,关注他们,甚至可以直接把他们 的源代码拉下来,看着他们的代码,忘了那个她!

GitHub的首页有这样介绍:A better way to work together,也就是说我们除了去关注牛逼的人物还可以在上面一起合作写点东西出来, 给我们提供一种更好的合作方式。

github

GitHub简单来说就是基于git的版本托管服务系统,是全球最大的社交编程及代码托管网站,因为里面大多都是男的在玩,所以说是全球最大的同性gaoji社区,哈哈哈!

GitHub有什么用?

其实GitHub除了可以让我们代码托管和多人在上面一起协作完成项目之外呢,我们还可以在上面建立自己的博客网站,官方称为GitHub Pages,你可以在GitHub上面 创建一个仓库,以github.io为后缀,可以托管你自己的博客网站,直接通过你的仓库名就能在线的访问你的网站了,而且完全免费,我自己搭了个网站托管在上面好久啦, 感觉棒棒哒!地址在这:http://www.wistbean.com (以前不知道听谁说,嫁人就要嫁给有自己网站的人,所以我就建了一个,嘿嘿嘿!)

github

如果你有什么优秀的开源项目在上面被人点赞(star)或者folllow,那就很666的了,因为在github上面被人点赞要比你在朋友圈被人点赞要难的多的多。如果在上面 托管你自己的比较有影响力的开源项目,在面试的时候提供给对方看,也是一个很不错的加分项!

GitHub的基本概念

Respository

我们通常说在GitHub上面新建一个项目,这个项目就会在Respository里,Respository也就是仓库的意思,可以通过git使我们本地的项目与远程仓库保持同步,这样妈妈就不用担心我们的 代码消失不见了!

Issue

随着我们的代码提交的越来越多,当有人发现你的开源项目不错,就把你的代码给拉(clone)到他们本地,他们运行后发现有bug或者觉得你的代码有问题,那么他就会给你提 Issue,我们可以发现他们的Issue,然后我们解决完bug之后可以将其close掉,表示我们已经修复了这个bug!

Star

我们去github看别人的开源项目的时候,发现很是不错,那我们就可以给这个项目star一下,也就是点赞,同时也是收藏的意思,在我们自己的star列表可以看到我们star过的项目

Fork

如果你对别人的项目感兴趣,或者对他感兴趣,你可以去fork他的项目,fork之后你就会发现,你自己的仓库里面竟然有一个和他一模一样的项目,更加爽的是,你竟然可以在他原有代码的 基础上进行修改或者添加功能!

Pull Request

你给他添加完功能之后你发现,哇靠,我简直就是天才啊,这时候你是不是想告诉他,你这样的功能有多牛逼,那么这时候你就可以给他Pull Request,这时候他就看到你的代码, 如果他觉得不错,接收了你的Pull Request之后,他就可以合并起来,这样他的项目就有你的一部分了!

Watch

Watch 就是观察,当你Watch了某个项目,就相当于你关注了它,那么这个项目有什么动态,你都可以实时的获取更新

Gist

如果你想分享部分代码片段,可以使用Gist,专门来分享代码片段的!

GitHub上一些不错的开源项目

free-programming-books

这个项目被star了10w+,有我一份力量哈哈,这个项目之所以这么多star,是因为它含有许多免费的编程书籍,并且支持许多语言的版本! 中文版地址在这:https://github.com/EbookFoundation/free-programming-books/blob/master/free-programming-books-zh.md#%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86

LearningNotes

这个项目含有许多android,java,设计模式,算法等面试相关的知识与经验,受益匪浅! 地址:https://github.com/francistao/LearningNotes

ResumeSample

这个项目是专门为程序员提供的简历模板,包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、 NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 ! 地址:https://github.com/geekcompany/ResumeSample

当然了还有许多牛逼的(比如google,Apache等开源项目)项目在上面等着你去发现!

还可以在GitHub上买东西呢!

GitHub上有个shop,里面有许多含有github logo的杯子呀,衣服呀,电脑贴纸呀,真的很酷炫和可爱!

github

written

编程的时候发现给变量或方法命名词穷了怎么办?

我记得以前上学的时候,同学们都喜欢互相起外号,有时候也会偷偷的给老师起外号! 比如:二狗子,大胸妹,装逼佬…

当过去多年再回想起某位同学的模样时,已经记不得她的名字了,但是对于她的外号 却牢牢记在心中,毕竟人家胸大嘛!

所以有时候父母给我们起名的时候,他们想的太多,比如想要你成才,就让你的名字中 有个“才”字,比如想要你有钱,就让你的名字有个“金”字,有些父母还去找那些带着黑色 墨镜的老头算命,问问他们的孩子五行缺什么,比如五行缺金,那么他们的孩子的名字 很有可能有个“鑫”字,如果五行缺水,那么他们的孩子的名字就很有可能有个“淼”字,当然 有的人名字有个“晶”字,就不知道人家缺什么了!

为什么要有一个好的命名

话说回来,我们在编程的时候,给变量名或者方法起一个好的名字,还是很重要的, 毕竟我们是人,要面子的嘛,代码还是要给人看的,不要让人看了你的变量名或方法 名称时候cao声连连就不好了。

再者,代码可能会经过多人,也就是二手代码,三手代码等,好的名字能让别人方便 接你的锅,大家都是程序员,就别为难别人了。如果你打算自己搞一个开源项目丢到 github上,也最好斟酌一下你的变量名或者方法名,不要丢脸啦!

不好的命名,可能过了一两个月自己回来看自己的代码都是一脸懵逼的,在这名称中连 自己都不知道什么意思,那这算什么意思呢?无意识的增加了代码的维护成本。

怎么起一个好的变量名或方法名

有些人很随意的给变量命名,例如 int a ,String myData,这样的命名方式 一时爽,可是到后面就会发现,一个好的命名的重要性。

而好的命名应该是顾名思义的,具有可读性,必须清晰,精确,比如我们有一个叫 做当前时间的变量,我们应该怎么对其进行命名呢?

首先我们可以查一下单词,当前就是current,时间就是Time,接着我们还可以比较 英文单词的语义,然后结合代码上下文确定命名,比如我们把当前时间这个变量命名 为 currentTime,相比于myTime,或者 cTime等就好很多!

在这里推荐一个不错的开源网站,这个网站是专门为你的命名时词穷而建立的,它的名字 叫做CODELF

他支持直接搜索中文,当你查中文的时候,Codelf 会直接查好单词和单词的近义词给你, 然后再搜索Github, Bitbucket, Google Code, Codeplex, Sourceforge, Fedora Project上的 开源项目的源码匹配出与这些词汇相关的变量名和函数名。 Codelf 可以选择开发语言进行搜索,结果会把同个源码文件里匹配的变量名排在一起

written

如何在冲顶大会中答对12题,瓜分百万现金

有一天我在刷微博,看到王思聪发了一条微博,在推广冲顶大会这个app,我第一时间 就下载下来体验了一把,确实不错,这是直播界的一股清流啊,答题,分享知识,关键 是答对12道题目还有现金拿。所以这个app一下子就火了起来,以至于 百万英雄、 芝士超人 等同类的app也纷至沓来,有点类似于以前的共享单车,什么小黄车,小蓝车, 摩拜等等,竞争也是相当的激烈。

有朋友问我,这个app一直分享知识,还每一期都投这么多钱,那它们怎么盈利啊? 我想说,在互联网时代,有流量就能盈利,你可以看到,每一期至少都有60w用户 参与,这么大的流量搞过来了,想要怎么盈利还难么?在直播中嵌入广告,在题目 中嵌入广告,购买复活次数,购买vip会员等等。这些他们想做,都是可以盈利的!

那怎么才能提高答题正确率,拿到钱呢? 千万别指望在这答题app中赚什么大钱,你可以想像一下之前春节支付宝集五福瓜分现金 就知道了,你辛苦收集到了五福,最后获得多少钱? 反倒是在感到学习累了或者工作累了,和朋友一起拿来消遣一下,放松一下,而且还能从中 学到一些冷门知识点,还是不错的!

但是你还是想问有没有什么黑科技可以提高答题正确率是吧?答案是有的。 实现思路是这样子的: 1. ADB 获取手机截屏 首先利用android手机的adb工具,每当题目出现的时候,就可以使用adb迅速截取含有题目的图片! 2. OCR识别题目与选项文字 我们已经拿到了题目的图片,那么就可以通过OCR接口来识别图中的文字,OCR的接口有很多, 例如百度OCR。 3. 搜索判断 我们已经把题目和选项识别出来了,那么就可以通过爬虫把问题放到搜索引擎中搜索题目 和选项,从网页代码中提取搜索结果计数,通过判断的数据就可以很清楚的知道哪个答案 相对正确了!

其实现在网上已经有人写了这样子的程序了。 地址在这:https://github.com/Skyexu/TopSup 有空就去玩玩吧!赚了钱分我一毛!

written in 个人观点

在Linux的CentOS上安装Tomcat

安装jdk

要装Tomcat,需要先安装JDK,然后配置环境变量。

oracle官网下载 rpm文件!

可以使用wget命令,文件下载位置在 /usr/local 下,这里我下载的是jdk8。 接着就可以执行命令:

rpm -ivh jdk-8u151-linux-x64.rpm

安装完之后执行javac,如果有以下内容说明安装成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@localhost local]# javac
用法: javac <options> <source files>
其中, 可能的选项包括:
  -g                         生成所有调试信息
  -g:none                    不生成任何调试信息
  -g:{lines,vars,source}     只生成某些调试信息
  -nowarn                    不生成任何警告
  -verbose                   输出有关编译器正在执行的操作的消息
  -deprecation               输出使用已过时的 API 的源位置
  -classpath <路径>            指定查找用户类文件和注释处理程序的位置
  -cp <路径>                   指定查找用户类文件和注释处理程序的位置
  -sourcepath <路径>           指定查找输入源文件的位置
  -bootclasspath <路径>        覆盖引导类文件的位置
  -extdirs <目录>              覆盖所安装扩展的位置
  -endorseddirs <目录>         覆盖签名的标准路径的位置
  -proc:{none,only}          控制是否执行注释处理和/或编译。
  -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
  -processorpath <路径>        指定查找注释处理程序的位置
  -parameters                生成元数据以用于方法参数的反射
  -d <目录>                    指定放置生成的类文件的位置
  -s <目录>                    指定放置生成的源文件的位置
  -h <目录>                    指定放置生成的本机标头文件的位置
  -implicit:{none,class}     指定是否为隐式引用文件生成类文件
  -encoding <编码>             指定源文件使用的字符编码
  -source <发行版>              提供与指定发行版的源兼容性
  -target <发行版>              生成特定 VM 版本的类文件
  -profile <配置文件>            请确保使用的 API 在指定的配置文件中可用
  -version                   版本信息
  -help                      输出标准选项的提要
  -A关键字[=值]                  传递给注释处理程序的选项
  -X                         输出非标准选项的提要
  -J<标记>                     直接将 <标记> 传递给运行时系统
  -Werror                    出现警告时终止编译
  @<文件名>                     从文件读取选项和文件名

配置jdk环境变量

首先使用vi编辑文件:

vi /etc/profile

在文中编辑:

1
2
3
4
5
JAVA_HOME=/usr/java/jdk1.8.0_151
JRE_HOME=/usr/java/jdk1.8.0_151/jre
PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
CLASSPATH=:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
export JAVA_HOME JRE_HOME PATH CLASSPATH

使文件生效:

1
2
3
[root@localhost jdk1.8.0_151]# source /etc/profile
[root@localhost jdk1.8.0_151]# echo $PATH
/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/root/bin:/usr/local/git/bin:/root/bin:/usr/java/jdk1.8.0_151/bin:/usr/java/jdk1.8.0_151/jre/bin

安装tomcat

  1. 在 /usr/local 下:

下载tomcat:wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz

  1. 解压文件:

tar -zxvf apache-tomcat-8.5.24.tar.gz

  1. 删除tar.gz

rm -rf apache-tomcat-8.5.24.tar.gz

  1. 将 apache-tomcat-8.5.24.tar.gz 重名名为tomcat

mv apache-tomcat-8.5.24.tar.gz tomcat

  1. 启动tomcat [root@localhost local]# cd tomcat/ [root@localhost tomcat]# cd bin/ [root@localhost bin]# ./startup.sh Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/java/jdk1.8.0_151/jre Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar Tomcat started.

  2. 开放8080端口

firewall-cmd --zone=public --add-port=8080/tcp --permanent

firewall-cmd --reload

written

用node.js,socket.io搞一个IM即时通讯

安装node.js

—> https://nodejs.org/en/

安装express

Express 是一种保持最低程度规模的灵活 Node.js Web 应用程序框架,为 Web 和移动应用程序提供一组强大的功能。

—> http://expressjs.com/zh-cn/

使用以下命令安装 express:

$ npm install express-generator -g

使用express新建一个工程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

  G:\study>express wechat

  warning: the default view engine will not be jade in future releases
  warning: use `--view=jade' or `--help' for additional options


   create : wechat
   create : wechat/package.json
   create : wechat/app.js
   create : wechat/routes
   create : wechat/routes/index.js
   create : wechat/routes/users.js
   create : wechat/public
   create : wechat/views
   create : wechat/views/index.jade
   create : wechat/views/layout.jade
   create : wechat/views/error.jade
   create : wechat/bin
   create : wechat/bin/www
   create : wechat/public/javascripts
   create : wechat/public/images
   create : wechat/public/stylesheets
   create : wechat/public/stylesheets/style.css

   install dependencies:
     > cd wechat && npm install

   run the app:
     > SET DEBUG=wechat:* & npm start

在生成的package.json中添加依赖:

1
2
3
4
"dependencies": {
    "express": "3.0.6",
    "socket.io": "*"
  }

接着执行命令 npm install 安装模块。

1
G:\study\wechat>npm install

首页和登陆页面

通过 https://github.com/thesadboy/ChatRoom 获取相关的样式复制到自己的工程中:

IM

index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>聊天室</title>
<script type="text/javascript" src="javascripts/jquery.min.js"></script>
<script type="text/javascript" src="javascripts/jquery.cookie.js"></script>
<link type="text/css" rel="stylesheet" href="stylesheets/common.css">
<link type="text/css" rel="stylesheet" href="stylesheets/chat.css">
</head>
<body>
  <div id="contain">
    <div id="content">
      <div id="content_show">
        <div id="contents"></div>
      </div>
      <div id="content_saying">
        <div id="toolbar"></div>
        <div id="input_content" contenteditable="true"></div>
        <div id="sayingto">你好 <span id="from"></span> ,你正在对 <span id="to"></span> 说</div>
        <div id="say">发送</div>
      </div>
    </div>
    <div id="users_online">
      <div id="usersbar">
        <div id="user_label">在线用户</div>
        <div id="users_list">
          <ul id="list"></ul>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

signin.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>登陆</title>
<link type="text/css" rel="stylesheet" href="stylesheets/signin.css">
<link type="text/css" rel="stylesheet" href="stylesheets/common.css">
</head>
<body>
  <div id="content">
    <form style="margin-top:70px;" method="post">
      <div class="form_line">
        <div class="form_label">昵称:</div>
        <input type="text" name="name" />
      </div>
      <div class="form_line">
        <div class="form_label"></div>
        <button class="button gray" type="submit">登录</button>
        <button class="button gray" type="reset">取消</button>
      </div>
    </form>
  </div>
</body>
</html>

登陆注册

在app.js中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var users = {};//存储在线用户列表的对象

app.get('/', function (req, res) {
  if (req.cookies.user == null) {
    res.redirect('/signin'); //跳转到登陆页面
  } else {
    res.sendfile('views/index.html'); //跳转到index
  }
});
app.get('/signin', function (req, res) {
  res.sendfile('views/signin.html'); //登陆
});
app.post('/signin', function (req, res) {
  if (users[req.body.name]) {
    //存在,则不允许登陆
    res.redirect('/signin');
  } else {
    //不存在,把用户名存入 cookie 并跳转到主页
    res.cookie("user", req.body.name, {maxAge: 1000*60*60*24*30});
    res.redirect('/');
  }
});

使用cookie:

1
app.use(express.cookieParser());

用户上线提醒

在app.js中使用socket.io

1
2
3
4
var server = http.createServer(app);
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
});

在index中添加js:

1
2
 <script src="/socket.io/socket.io.js"></script>
 <script type="text/javascript" src="javascripts/chat.js"></script>

在 public/javascripts 文件夹下新建 chat.js ,内容如下:

1
2
3
$(document).ready(function() {
  var socket = io.connect();
});

运行报错:

1
2
3
4
5
6
7
8
9
10
11
Error: Most middleware (like cookieParser) is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.
    at Function.get (G:\study\wechat\node_modules\express\lib\express.js:107:13)
    at Object.<anonymous> (G:\study\wechat\app.js:36:17)
    at Module._compile (module.js:643:30)
    at Object.Module._extensions..js (module.js:654:10)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)
    at Function.Module.runMain (module.js:684:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

解决:

1
npm install express@">=3.0.0 <4.0.0" --save

written in internet

通过苍老师教程理解什么是区块链比特币挖矿

各位兄弟们从一个稚嫩的男生,到成长为一名成熟稳重的男人,都需要经历一些事情,当然也需要不断的学习, 而苍老师,在我们的成长道路上,为我们指点迷津,用实践的方式,告诉我们,什么样才是对的,怎么样才是强大的。 所以为了致敬我们可敬的苍老师,我们这次就通过苍老师的教程来理解什么是区块链,什么是比特币,什么是挖矿!

中心化

当你感到“知识”缺乏,渴望学习的时候,你会怎么做呢?

首先你会打开你收藏已久的网站,这个网站充满了广告弹框,但是你并不厌烦,因为你知道苍老师的教程就躺在菜单栏里,等着你!

其它成千上万的兄弟也和你一样,收藏着这个网站,每当渴望学习“知识”的时候,就会去网站中找苍老师的教程!

中心化

不过你可能会遇到这样的问题:

  • 如果这个网站的服务器挂了,你就看不到苍老师的教程了。
  • 如果有人在这个网站上传病毒,你下载苍老师的视频后可能会中毒。
  • 如果这个网站被封了,你又得频繁的去寻找新的资源了。
  • 如果这个网站中的苍老师教程被他们移除了,怎么办?

这里指的网站就是中心化,要解决这些问题,我们应该深深的思考一下, 是不是可以由我们自己来掌握苍老师教程这些资源,而不是让第三方网站掌握呢?

区块链去中心化

你为了万千兄弟在渴望“知识”的时候,随时随地都能获取到苍老师的教程,于是你创建了一个共享文件夹, 名称叫做「苍老师教程」。 你规定了这样的协议:“想要学习苍老师的知识,可以,不过你不能随意破坏「苍老师教程」文件夹中的视频, 每个人在「苍老师教程」这个文件夹中操作的所有行为都会按时间戳记录!” 凡是遵循这个协议的兄弟都可以获取这个文件夹!

区块链去中心化

例如:“小明在2018年1月13号的凌晨两点看了苍老师第50集的教程,看完后偷偷的把它给删了”

这时候小明产生的行为会被记录起来,并且成千上万的兄弟们都会接收到一条广播, 小明的电脑中的「苍老师教程」文件夹会同步成千上万的兄弟们的电脑里面的数据,并且 小明的电脑会产生一条最新的记录。

小明休想破坏「苍老师教程」文件夹里面的数据,一个人是干不过成千上万的人的。 除非成千上万的兄弟的电脑都坏掉。

这就是区块链的去中心化,分布式存储。

所以以后兄弟们就不会担心苍老师的教程会消失,也不会被小广告弹窗,不怕电脑中毒了 当渴望学习的时候,就可以去学习了!

挖矿和比特币

因为这些视频是唯一的,而且不会被破坏,所以它们可以产生价值,对它们进行估值,你可以发行“苍老师币”! 接着你就规定:“兄弟们想发布苍老师的教程,就必须要满足多种条件(加密算法),视频的质量要很高才可以发布。并且每隔10分钟 才可以发布一个视频!”

只要兄弟们满足条件成功发布高质量的苍老师视频,就奖励给他“苍老师币”!

拥有苍老师币的人越来越多,这些人就开始炒这种虚拟货币, 不断扩大市场对苍老师币的需求,从而提高苍老师币的价值。

你可以把兄弟们制作视频的过程理解为挖矿,把“苍老师币”理解为比特币!

区块链收割韭菜?

是的,有人发现这样子可以赚钱啊,挖槽,老子自己也来按照这个模式搞一套“波多野教程”,发行“波多币”, (也就是我们现在所说的山寨币),然后去收割韭菜去了!

written in 个人观点

2018年计划

我不知道我是谁?

我不太知道我在别人眼里中的我是什么样的,我有什么缺点?我有什么优点? 可能在不同的人眼中都不一样吧。朋友眼中的我?爸妈眼中的我?兄弟眼中的我? 很难去统一,也很难去从别人眼中的“我”通过函数计算“我”是什么样的“我”!

在我自己眼中的我,我是个什么样的人呢?我以前觉得自己很帅,以前觉得自己很牛逼, 我也不知道我哪来的自信?或者说哪来的自卑? 不过随着这一两年发生的事情,我改变了,我发现了我的无知,我发现了的自大,我也发现我的懦弱…

有时候去问问自己我是谁?我都答不出来! 我希望我是个什么样的人?我觉得我也无法说的很清楚!

我用我两只脚,踩在地球的表面上,地球无痛无痒,我用力踩,依然如此!反倒是 地球稍微颤动一下,周围便鸡飞狗跳,不只是我,每位兄弟,每位朋友,也是如此!我们很卑微? 或许我们都知道,只是不约而同的选择沉默,我们习惯了!

2018年计划

给自己定个计划,虽然现实变化多端,赶不上计划,不过计划能让我知道我想干什么、我想做点什么、我能做些什么! 给自己的2018年一个标语,三个字:就是干!

1. 精读20本书

我要在2018年中精读20本书,我会用到一些工具,例如思维导图、笔记、画图等等,我要记录并分享认为有用的东西, 在这20本书中按6:4的比例来选读,6代表技术书籍,4代表除了技术方面的其它书籍!

2. 英语

我目前的英语水平算挺烂的,所以需要提高读写听说方面能力,希望通过今年的学习积累,能阅读文章少用翻译工具, 能达到1w+单词量(不多不少刚刚好哈),能用英语与老外对话装逼。

3. podcast

当初也不知道自己为啥给自己挖了个坑,搞了个电台《肯定会软件技术》,每周都更新一期,此刻已经更新到17期, 希望能做到100期吧,感觉吧,做什么内容不是最重要,最重要的是执行力,我不想搞一下就扔掉不玩,既然做了 就把它做完,做好!一周一期,今年应该能搞40+期吧 - -

4. 课程

一样给自己挖了个坑,录了一小节课程放在b站上,这个课程名称叫做《【免费】从0到1开发java高并发分布式SSM项目教程》, 规划是做个十几期,每一期多个小节,不过这个不定期更新吧,希望今年能够录完它哈!

5. 做一个产品

之前自己就有些idea想做一款小程序来着,不过做了一阵子就放着了,晾在我的电脑里面跟苍老师待挺久了,这个非得一定完成吧, 建立在有时间,有心情,有feel的情况下 :)

6. 增重

现在我偏瘦,也缺乏锻炼,老是做在电脑前,所以18年我要锻炼好自己,调整好自己,在饮食方面以及作息方面多注意,之前下了个 锻炼的app,也是三天打鱼两天晒网,现在要重视起来(妈蛋这两天就有点感冒),坚持锻炼吧,希望能拥有硬邦邦的肌肉, 更强更持久 :)

ok,2019年的这个时候来回看这篇文章, 看看自己能否做到!

written in 个人观点

2017最后一天

2017-1990=17,今天是2017的最后一天,过了今天,整个90后都成年了!

不知道你2017年的计划,完成了没? 不知道你2017年的愿望,实现了没?

地球永不停息的自转着,昼夜更替着,四季变化着。

当地球自转到某个点,我们踏上了旅途,带着些许的企图,希望地球再转到这个点的时候,能带回些许幸福。

于是开始上了车,发现速度飞快,虽然知道老司机经验满满,但是还是有一点担心,万一车翻了,头破血流,如何继续? 这一点点的焦虑,或许就是少了旁边有个互相扶持,互相鼓励,互相给予安全感的人。又或许是其它,每个人都不同。

2017即将过去,将要翻开新的一章,那么就让一些遗憾随之翻去,接下来掌控好自己的方向盘,做自己的司机,什么时候 停车,什么时候转弯,自己说了算!

在牛逼的路上,有许多分叉口,有许多弯路,希望自己能在分叉口之前,稍微停下来思考一下,往哪边走,不管对错, 走了弯路,也许不一样的风景。

2018,来吧,让我们相见! 2018,就是干!

written in 个人观点

如何给你的微信头像戴上圣诞帽

微信官方,请赐予我一顶圣诞帽

这两天我的朋友圈都纷纷在@微信官方,请求微信官方给他们的微信头像加上一顶圣诞帽! 场面壮观,哈哈,朋友圈再一次被刷屏!

微信官方,请赐予我一顶圣诞帽

微信官方,请赐予我一顶圣诞帽

不过我就不一样了,野心比较大,我是@了官方,希望他们能帮我的朋友们戴上一顶绿色的圣诞帽!

微信官方,请赐予我一顶圣诞帽

“微信圣诞帽”的搜索指数直线上升,这要是股票就好了!

微信圣诞帽

有朋友发了朋友圈表示等了好久都没有圣诞帽,这是为什么呢?是不是微信系统出了问题了? 这压根就是一场群体忽悠好吗?微信什么时候闲的蛋疼去给你戴帽子了? 人家那些有帽子的都是自己P上去的好吗?哈哈

知道真相的你是不是眼泪都掉下来了?

猜想实现方案

如果微信真的给用户戴上圣诞帽,我们来猜想一下是怎么实现的吧!

首先可以从用户发的朋友圈中监听用户发送的文字中是否含有关键字:「请给我一个圣诞帽!@微信官方」。 然后从识别到的用户获取用户的openID,将这些用户的ID添加到消息队列中来给他们添加圣诞帽子, 怎么添加圣诞帽呢?

首先准备一张圣诞帽的图片,接着通过用户的id获取用户当前的头像,然后利用人脸检测技术获取头像中人脸的位置,接着用 图像处理功能,将圣诞帽按一定的比例合成到头像中,如果识别不到人脸那么就另做处理!最后将成功合成图片来update用户的 头像!

猜想起来好像挺easy的,不过那些人脸检测,图片处理中的背后算法是很复杂的! 不过如果感兴趣,有时间可以自己写一个,调用一下第三方的API,还是可以玩玩的!

想想人工智能还是未来吗?已经到来了,瑟瑟发抖!

written in 个人观点

分布式电商项目介绍

介绍

面向的学习人群

  1. 具备有一定的java基础
  2. 初级开发者 -> 中高级开发者
  3. 具备一定的自学能力和执行能力!

技术选型

  1. Spring
  2. Spring MVC
  3. Mybatis
  4. redis
  5. solor
  6. EasyUI
  7. UEditor
  8. JQuery
  9. Freemark
  10. activMQ
  11. httpClient
  12. MySQL

开发环境

  1. InteliJ IDEA
  2. Maven
  3. Tomcat7
  4. JDK
  5. Nginx
  6. Git
  7. postman
  8. sqlyog
  9. win7

计划

  • 技术架构 (集群和分布式架构的区别)
  • 工程搭建 (maven)
  • SSM框架的整合
  • Mybatis逆向工程以及使用
  • 日志的添加与使用
  • 拦截器
  • 后端功能 (系统的开发,图片系统,数据等等)
  • 前端功能 (商品浏览,下订单,购物车等等。。)
  • redis使用以及集群搭建
  • solor使用和集群搭建
  • JMS 消息队列 (activMQ)
  • sso单点登录
  • restful服务
  • 在Linux上部署

架构

传统的集群架构 和 分布式架构 区别

集群架构

缺点:

  • 耦合度太高了
  • 增加了团队的合作成本
  • 不能够去灵活的部署

分布式架构

优点:

  • 项目拆分成多个模块,耦合度降低
  • 单点运行,团队合作效率高了
  • 可以灵活部署

缺点: 需要去额外的开发,让各个模块之间能够通信!

written in 分布式项目

游戏开的了挂,可是人生不能!

五五开开挂

最近有个挺火的事件就是电竞圈网络主播卢本伟(五五开)被质疑在”吃鸡”游戏中使用游戏外挂,而卢本伟(五五开)的一个小号已经被封禁,但是卢本伟否认是自己使用外挂。 可能有些不玩或比较少玩游戏的人不太清楚五五开直播游戏中开挂的事件,事情是这样的(wikipedia):

2017年11月28日,卢本伟在微博发出他在《绝地求生》中以29杀获得第一名的视频, 而卢本伟此前从未出现过一个人杀29人的表现,因此被怀疑使用外挂。 卢本伟的女朋友赵梦玥在微博上解释称卢本伟一开始实力不佳,但是他直播完后每天会练习到中午11点, 除了上厕所和拿外卖以外其他时间都在练习并持续几个月。

五五开29杀

11月29日,哔哩哔哩UP主“松鼠打不过仓鼠”发表视频,通过逐帧分析称卢本伟使用外挂, 依据是卢本伟八倍sks不屏息、自瞄及透视。其后卢本伟在直播间进行澄清,声称自己没有使用外挂并会送上律师函, 如果他被查实使用外挂,愿意给对方500万人民币,否则赔他1块钱并当众道歉。

仓鼠实锤 (仓鼠实锤)

12月3日,松鼠打不过仓鼠在斗鱼TV开启直播,称不畏惧官司,并想与卢本伟进行对质,但直播间被斗鱼封禁。其后全民TV主播帝师邀请松鼠打不过仓鼠前往全民与他进行联合直播,帝师在12月4日直播中使用外挂完成了松鼠打不过仓鼠视频中的卢本伟所有操作。

12月5日,卢本伟发表微博(否认开外挂),表示约松鼠打不过仓鼠在线下展示操作,并会报销仓鼠的全部费用。

12月6日凌晨,卢本伟《绝地求生》小号“LuBenWeiWuDI55”被发现于2个月前已被steam反作弊系统封禁。卢本伟在直播中解释,是他的一个朋友用了他的账号给他演示外挂,并再次强调自己没有使用外挂。

LuBenWeiWuDI55

12月7日,松鼠打不过仓鼠发表发布了一个视频,称他被斗鱼直封禁的原因是因为他在直播中出现一些针对斗鱼平台的不当言论,所以斗鱼给他12小时的封禁和1分的处罚,他对斗鱼的封禁没有任何异议。松鼠打不过仓鼠还称在封禁后的第二天下午,斗鱼平台联系了他,详细解释封禁原因,并对他质疑的行为表示肯定,且愿意提供帮助与支持。

虽然五五开在直播期间对外挂进行了解析,并且否认自己开外挂,不过由于疑点实在太重:

自瞄拉枪

透视

许多网友并不买账,认为他推脱给朋友,并且认为他的演技有待提高,争取早日登上《演员的诞生》这个舞台 :)

这里暂且先不去评论开挂带来的不公平与欺骗,而先来谈谈开挂的基本原理是什么?

外挂的基本原理

一个复杂的游戏程序是背后的逻辑算法是相当复杂了,这里从简的说,其实游戏本质上就是一堆数据。这些数据显示到 客户端的时候,通过UI界面的渲染最后呈现给用户。

一般外挂也会被称为 “辅助” 、”作弊器”!

辅助就是用户可以实现的正常操作,只不过这些正常操作可以不需要用户自己手动去操作,举个例子:有些人安装了微信的抢红包外挂,那么 当他的微信收到红包的时候,他就不需要去手动的点开红包,而让「辅助」去帮他实现这一操作,直接把钱收入囊中(简直可耻,想象一下你年会的时候红包为什么抢到的那么少)

作弊器是用户无法实现的正常操作,例如可以通过作弊器设置自己拥有无数子弹,对对面的敌人就是一阵射(雅蠛蝶~~)

一般情况下用户是通过游戏客户端(游戏界面)操作指令,然后客户端会将指令发送给服务器,进而做出相关的操作,但是如果有外挂的话就可以直接绕过 客户端,直接和服务器进行交互,前提是你掌握了对数据的交互请求格式:

外挂

所以外挂的难点就在于你怎么去分析游戏的逻辑和抓包请求数据,对逻辑的把握等等。

如何看到使用外挂的人

分为两种人进行讨论:1.开发外挂的人 2.购买并使用外挂的人。

相比较来说前者是有“价值”的,这里的“价值”并不是说他有多可贵,也不是鼓励别人去开发外挂,而是相对于技术来说, 有专研能力和分析能力,如果一些单机游戏本身就存在缺陷,需要开发一个辅助来提高游戏的进度,这是无可厚非的, 而且也觉得没什么可吐槽的,反而觉得是有“价值”的!当然为了利益而开发外挂就另当别论了。

而相对于后者,一般都是在网络游戏中,这种人就是素质问题了,损坏了游戏的公平性,瞎几把寻找什么成就感,心里有缺陷!

在游戏竞技中,需要付出时间,精力,需要思考策略,甚至金钱和智力!能够明白有些人缺乏这些东西,于是开外挂来满足自己需求, 从而达到满满的成就感!

但是!游戏开的了挂,可是人生不能!人生比游戏需要付出时间,精力,需要思考策略,智力,金钱等等要多的多

所以,努力吧,只有不断的努力,才能让别人觉得你的人生看起来像开挂了一样,毫不费力 :)

written in 个人观点

单点登录SSO的实现流程

传统的登录方式

kiri开发了一个项目,这个项目只有一个web工程,所以他在做系统登录的时候呢?是这样做的:

UML sequence diagram

“当用户进行登录的时候,他把登录请求交给LoginController,这时候就会去数据库中根据用户 传来的用户名查询查询密码,判断密码是否正确,如果密码正确的话就会把用户信息放到session里, 如果不正确就提示用户密码不正确。

当用户在该系统中需要访问用户相关信息的时候,那么这个时候就可以直接到session中判断用户 是否登录,如果从session获取到有该用户的信息,那么就直接展示就可以了,不需要再次登录了。”

kiri突然觉得自己很是牛逼,就开始吭哧吭哧的写代码了… 不到半个小时kiri就写完了,然后部署好了系统就出去happy了!

后来,系统并发量大了起来,在一个Tomcat中已经无法支撑了,这时候kiri开始做起集群来, 把web工程部署多了一台Tomcat,然后用nginx做负载均衡。

不到半小时kiri就部署完了,然后又出去happy去了…

但是正当kirihappy的时候,许多用户反映在这个系统里需要多次登录,有时候登录了还需要再次登录! kiri有点纳闷,怎么会这样,于是只能扫兴的回去打开电脑看看怎么回事!

kiri看了5分钟后发现了问题:

原来,把web部署在不同的Tomcat上,他们的session是不同的,也就是每个tomcat都有她自己独立的session。 当用户登录系统的时候,nginx将用户请求代理到第一个tomcat进行登录,将用户信息存放到session中,当用户再次请求系统的时候,nginx可能会 将用户的请求代理转发给第二个tomcat进行处理,这个时候第二个tomcat的session并没有该用户的信息,所以就需要用户去登录!

于是kiri就开始配置tomcat,让它们之间的session进行共享,也就是当tomcat的session发生改变的时候,就会广播给配置好的另一个tomcat,让它们的 session保持同步!

这样子用户就不需要再进行多次登录了,kiri搞完之后又出去happy了…

SSO单点登录

kiri团队后来做了个比之前大一点的系统,这个系统是采用分布式系统架构的,kiri这次负责的用户相关的功能模块。 kiri发现系统被分成了多个子系统,这些子系统将来可能会被部署到多个不同的服务器上,如果采用之前的session 共享进行用户登录的话,会非常占用系统资源,而且非常影响性能!

于是kiri就发现了原来有个叫做单点登录的玩意,上Google搜了一下

单点登录

发现维基百科对它的定义是这样的:

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统, 提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。

发现这正是他想要的!

于是kiri就创建了一个子模块,叫做SSO,这个模块就是专门来管理用户的登录的,它将用户的session数据用Redis存放,因为 Redis可以全局管理数据,可以设置key值的生存有效期,而且访问效率很快!

单点登录实现流程

kiri展开了思路:

  1. 用户在每个系统中对用户的登录请求会发送给SSO系统,SSO系统显示登录页面,在SSO系统中接收用户名和密码;

  2. 根据用户名密码去查询数据库是否存在,如果存在就会生成Token,这个Token对应的便是存放到Redis中的key, 用户信息就是对应的Value,然后存放到Redis中,接着设置登录有效期;

  3. 接着返回登录成功,并将token写入cookie中,这个时候再判断是否有回调的URL,如果有就重定向到用户需要的页面, 没有就跳转到门户首页;

  4. 当用户在另一个系统中需要请求到用户信息的时候,就会通过拦截器判断用户中的请求的cookie中的token是否在redis中有数据, 如果不存在就会返回登录url,如果存在说明已经登录,那么就可以刷新登录有效期,并将用户信息进行返回!

kiri搞完之后发现不错,Redis存取速度快,不会出现多个session共享影响性能问题。更加高效,所以认为 创建一个SSO系统来做单点登录是很有必要的!

我的相关文章

相关资料

written in java, 分布式系统

我在团队协作中基本上都会使用到的Git方法

接触编程的人应该都知道Git吧,一款分布式管理的工具,对于团队协作项目和项目的版本管理来说就是利器,这篇文章就说说我在团队协作中基本上都会使用到的Git方法。

常用操作

  • 初始化仓库: git init
  • 添加新增的代码或文件到暂存区中: git add
  • 提交我们添加的内容到仓库中: git commit
  • 从远程仓库拉去master分支的内容: git pull origin master
  • 从本地master分支的内容上传到远程仓库中: git push origin master
  • 查看git状态: git status

git分支使用

在开发过程中,我们会有一个主分支master,这个分支是代码的主要内容,当不同的人负责不同模块功能开发的时候,就需要用到分支,比如有三个人分别负责A/B/C三个不同模块的功能,那么就可以各自创建分支:A分支,B分支,C分支,当他们各自完成了自己的功能后在合并到主分支上去。

查看当前的分支

G:\test>git branch
  a
* master

可以看到,当前有两个分支,一个是a分支,一个是master分支,master分支前有一个*号,代表此刻的内容正处于master分支上。

分支的使用

比如我需要负责B功能的模块,那么我会新建一个b分支,怎么新建呢,分两步:

第一:切换到master分支 第二:在master分支的基础上新建分支

切换到master分支上新建分支b,那么此刻b分支里的内容就和master一模一样!

1.当我们的分支不在master上的时候,那么切换分支是这样的:

G:\test>git checkout master
M       m.md
Switched to branch 'master'

checkout 可以理解为切换的意思

2.新建b分支:

G:\test>git branch b

那么现在b分支上的内容就和master上的分支一样了,在b分支上完成B功能后合并到master分支上即可。

3.合并b分支到master上

首先切换到master:

git checkout master

接着合并:

G:\test>git merge b
Already up-to-date.

当合并完分之后,b分支可能不需要了,那么删掉b分支:

G:\test>git branch -d b
Deleted branch b (was 0095e88).

当然了,如果想要强行删除b分支的话可以git branch -D b。

4.把b分支上传到远程仓库

git push origin b

5.删除远程b分支

git push origin : b

6.将远程的b分支迁到本地:

git chekcout b origin/b

冲突的解决

可能在团队协作的时候,他们都不约而同的修改同一个代码的地方,这时候合并的时候会产生冲突提示,因为git并不知道要用哪个人写的才是对的,需要我们自己解决冲突才能合并。

比如这样的冲突:

<<<<<<< HEAD
hahhahahahah  this is a master branch~~~
=======

hahhahahahah  this is a b branch~~~
>>>>>>> b

「«««< HEAD」 和 「=======」是原本的代码。 「=======」和「»»»> b」是分支的代码。

可以看到masert被改成b了,这时候git不知道是master对,还是b对,所以就要自己解决,这里比如我们觉得是master对,那么就将

<<<<<<< HEAD

=======

hahhahahahah  this is a b branch~~~
>>>>>>> b

删掉,然后再add–>commit–>merge!

git stash

如果当前的代码还没写好没有commit,又想切换到别的分支上进行操作,那么可以先将没commit的代码stash暂存起来:

git stash

在别的分支完成操作并发布之后,就可以切换到刚刚没有写好的代码进行:

git stash apply

这时候之前没commit的代码就回来了,就可以继续写完再commit了。

此外,还可以通过git stash list 看看你stash的记录,可以使用git stash drop 删除记录!

git ignore

有时候自己电脑上的配置文件不需要上传上去,可以使用ignore文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/src/main/webapp/WEB-INF/classes/
/src/main/webapp/WEB-INF/lib/
/src/main/webapp/META-INF/
/src/main/webapp/logs/
/src/main/webapp/node_modules/
/src/main/webapp/.idea/
/target/
/build/
/.settings/
/.idea/
.classpath
.project
.buildpath
.gitignore

当遇到.gitingnore不生效的时候,可以执行以下命令:

git rm -r --cached .
git add .
git commit -m 'update .gitignore'

我的相关文章

相关资料

-git - 简明指南 -15 分钟学会使用 Git 和远程代码库 -Git使用教程 -Git 使用规范流程

written in tool

解析java中的String源码

话说之前对代码的编写大多都是直接使用jdk里面的对象,有时候也对一些对象直接封装之后就使用了, 很少去了解源码中具体细节是怎么实现的,这样显然不符合我这么帅的人的做事风格,所以我现在就来 对源码进行学习学习,可能篇幅略长,不过会慢慢记录下我学习的过程和总结一下,希望对自己有帮助的同时, 也能够帮助到和我一样,希望更进一步去理解java的小伙伴们!!

String类的大概面貌

废话不多说了,先打开我的 InteliJ IDEA , 创建一个学习源码的项目,就叫做「learnJavaSourceCode」吧。 打开String.java之后可以发现了String实现了java.io.Serializable, Comparable, CharSequence :

String.java

有4个成员变量:

  1. private final char value[];

  2. private int hash; // Default to 0

  3. private static final long serialVersionUID = -6849794470754667710L;

  4. private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

  5. public static final Comparator CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

有一个内部类CaseInsensitiveComparator,还有其它就是构造函数和方法了!

String的概要描述总结

也就是在一开始的一大段注释:

String describe

我对它总结一下就是以下几点:

  1. 1.在java中所有的字符字面值(例如:”abc”)都是String的一个实例;

  2. 2.Strings是常量,它们的值在被创建之后就不可以改变了,字符缓冲支持改变,因为String的对象是不可变的,所以它们可以被共享;

  3. 3.String包含了一些方法,例如字符的大小写转换等(有点废话 - -);

  4. 4.java语言提供了支持字符间的连接操作和将对象转化为字符串的操作,字符间的连接是通过 “+” 来操作的, 它们之所以可以连接是因为通过 StringBuffrer 或者 StringBuilder 的 append 方法实现的,而将对象转化成字符串 是通过Object方法中的toString方法。

  5. 5.携带 null 这个参数给String的构造函数或者方法,String会抛出NullPointerException,这不是见惯不惯了吗:)

  6. 6.String 表示一个 UTF-16 格式的字符串。其中的 增补字符 由 代理项对 表示,索引值是指 char 代码单元,因此增补字符在 String 中占用两个位置。

对String的主要描述进行演示

  1. 在java中所有的字符字面值(例如:”abc”)都是String的一个实例:

很好理解,我们经常就是这样做的,java这么规定,我们也就这么写了:

1
2
String s = "abc"; //这里的 "abc" 就是一个对象
System.out.println("abc");
  1. Strings是常量,它们的值在被创建之后就不可以改变了,字符缓冲支持改变,因为String的对象是不可变的,所以它们可以被共享:

    那是不是这样:

1
2
3
4
5
public static void main (String[] args){
    String s = "abc"; //s是常量,abc被创建了,那么s对应的值就不可以改变了
    s = "def"; // 我就把它改成def看看
    System.out.println(s); // 输出 def
}

奇怪,不是说不能被改变了吗?为毛可以是def? 其实不然,我们一开始是创建的 “abc” , 我们从1中 「在java中所有的字符字面值(例如:”abc”)都是String的一个实例」 可以 知道,其实”abc”就是一个String的实例,我们的String s 只不过是指向这个”abc”的String对象了,而我们的 s = “def” 则是将s指向”def”这个对象!

心情好,画个图吧:

首先我们写了这样一句 String s = “abc”; 那么是这样的:

String

s 这个引用会去常量池里面找有没有”abc”,发现卧槽,没有,那么就创建一个:

String

这时候 s 就可以指向 “abc” 了!

接着我们把 s = “def” , 同样的道理,它会去常量池找有没有 “def”, 有就指向它,没有就在常量池创建一个:

String

所以我们现在应该知道 「String的对象是不可变的,所以它们可以被共享」 这句话是什么意思了吧 - -

  1. Java语言提供了支持字符间的连接操作和将对象转化为字符串的操作,字符间的连接是通过 “+” 来操作的, 它们之所以可以连接是因为通过 StringBuffrer 或者 StringBuilder 的 append 方法实现的,而将对象转化成字符串 是通过Object方法中的toString方法。

    写段代码:

1
2
3
4
        String s1 = "I ";
        String s2 = "Love ";
        String s3 = "You ";
        System.out.println(s1 + s2 + s3 );

运行后理所当然是 I Love You :) 接着使用jad反编译下上面这段代码会发现:

1
2
3
4
       String s1 = "I ";
       String s2 = "Love ";
       String s3 = "You ";
       System.out.println((new StringBuilder()).append(s1).append(s2).append(s3).toString());

可以看到它真的用StringBuilder对象用append方法把我们的s1 + s2 + s3 拼接起来了,然后用toString方法得到 I Love You … 害羞 - -

对String常用构造方法解析

String(String original);

首先来个问题思考:

1
2
String msg1 = "I Love You !" ;
String msg2 = new String ("I Love You !");

上面的 msg1 和 msg2 是一样的吗? (我们不一样~~)

我们已经知道msg1是从常量池去取的,而我们new String() 这时候应该在堆内存产生一个String对象,而通过源码可以看到这个 String对象方法是将我们传入的这个 “I Love You !” 对象的value和hash进行复制:

1
2
3
4
 public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

画个图那就是这个样子的:

new String()

String(char[] value)

我们创建个char[],然后传给String构造函数:

1
2
3
4
5
6
        char[] c = new char[3];
        c[0] = 'I';
        c[1] = 'L';
        c[2] = 'U';
        String msg = new String(c);
        System.out.println(msg);

通过debug后可以发现:

new String()

原来它通过 Arrays.copyOf 把我们的char直接复制给value了! 其实我们应该也猜到了,String里面的value就是char数组!

String(byte[] bytes)

String提供了好几个含有byte[]参数的构造函数,那么我们对byte[]和String间的转化就容易许多了。 可能你之前应该遇到过乱码问题,你应该是这么解决的:

1
2
3
4
5
try {
        String s = new String("乱码".getBytes(),"utf-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

其实String里面是含有一个char[]来存放这些字符,而这些字符是以Unicode码来存储的,字节是通过网络传输信息的单位, 所以我们在传入byte[]转化为String的时候,是需要对其进行编码的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 static char[] decode(String charsetName, byte[] ba, int off, int len)
        throws UnsupportedEncodingException
    {
        StringDecoder sd = deref(decoder);
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;//我们指定了utf-8,默认是ISO-8859-1
        if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
                              || csn.equals(sd.charsetName()))) {
            sd = null;
            try {
                Charset cs = lookupCharset(csn);
                if (cs != null)
                    sd = new StringDecoder(cs, csn);
            } catch (IllegalCharsetNameException x) {}
            if (sd == null)
                throw new UnsupportedEncodingException(csn);
            set(decoder, sd);
        }
        return sd.decode(ba, off, len);
    }

对String常用方法解析

  1. charAt(int index)

1
2
3
    String msg = "I love you !";
    char c = msg.charAt(3);
    System.out.println(c); // 输出的是 o;

可以看到这个方法是根据索引返回Char值!

进去源码看看:

1
2
3
4
5
6
 public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

可以看到当我们传进去的索引值是小于0 或者 大于 这个字符串的长度,就会抛出异常。否则就返回value数组中对应的值! 通过debug可以看到其实我们刚刚定义的 String msg = “I love you !” 中的值被放到了value这个数组中了!

String

而我们 char c = msg.charAt(3) 传入的这个3 就是这个数组对应的下标3,所以呢,输出就是o啦! String

  1. equals(Object anObject)

equals我们通常用于比较是否相同,那么你知道下面这段代码分别输出的是什么吗?

1
2
3
4
5
6
7
    String s = "I Love You !";
    String s1 = "I Love You !";
    String s2 = new String("I Love You !");
    System.out.println(s == s1);
    System.out.println(s.equals(s1));
    System.out.println(s == s2);
    System.out.println(s.equals(s2));

他们分别输出的: - true - true - false - true

可能有些人会奇怪会什么 s == s2 是false? 他们不是都是是 ““I Love You !” 吗? 这时候我们就要来看看 == 和 equals 的区别了!

其实 == 比较的是他们的地址值(hashcode),我们知道String是不可变的,我们可以知道s 和 s1 指向的都是 “I Love You !”;所以他们的hashcode是一样的。所以返回true; 而s 和 s2 他们指向的地址是不一样的所以是false;

可能此刻有人会疑惑那么为什么s.equals(s2)返回的是true了。这时候我们应该可以猜到equals应该判断的不是 两个对象间的hashcode吧,我们看下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean equals(Object anObject) {
        if (this == anObject) {  //如果对象的hashcode一样就直接返回true
            return true;
        }
        if (anObject instanceof String) {  // 判断传进来的对象是不是String类型
            String anotherString = (String)anObject; // 将对象强转为String
            int n = value.length; //获取我们本身拿来对比的String对象的长度
            if (n == anotherString.value.length) { // 判断它们的长度是否一样
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])  //逐一判断字符使用相等
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

很明显我们可以发现,equals判断的不是hashcode,而是判断它们的值是否相同,所以s.equals(s2)返回的是true!

  1. endsWith(String suffix)

有时候我们可能会判断字符串是否以指定的后缀结束。例如我们有获取的图片路径,判断他是不是以.jpg结尾的:

1
2
   String s = "canglaoshi.jpg";
   System.out.println(s.endsWith(".jpg")); //true

debug一下就明白了它是怎么判断的: String

它会去调用 startsWith 方法。 - 用 ta[] 这个数组来存放 “canglaoshi.jpg”; - 用 int to 来接收 “canglaoshi.jpg”的长度(14) 减去 “.jpg” 的长度(4) = 10; - 用 pa[] 来存放 “.jpg”; - 用int pc 来接收 “.jpg”的长度;

最后就是以pc为次数进行遍历ta[]从下标为to开始,pa[] 从下标为0开始逐一判断,如果相同就返回true!

  1. replace(char oldChar, char newChar)

    返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 那么问题来了,下面这一段输出的是什么呢? String s = "I Love You !"; s.replace("Love", "Fxxk"); System.out.println(s);

如果你说是 I Fxxk You !, 那就真的是欠fxxk了~ 别忘了,String 是不可变的。所以呢, s 还是 I Love You, 而String s1 = s.replace(“Love”, “Fxxk”); 这样的s1 才是 “I Fxxk You !”

  1. hashCode()

    返回此字符串的哈希码。 哈希码是怎么算出来的?

1
 System.out.println("I Love You !".hashCode()); //-1710377367

我们知道我们的 “I Love You !” 被放到了char数组中。 hashcode的算法是这样的:

1
2
3
4
31 * 0 + val[0]
31 * (31 * 0 + val[0]) +  val[1]
31 * (31 * (31 * 0 + val[0]) +  val[1]) + val[2]
...

它的代码实现是:

1
2
3
4
5
6
7
8
9
10
11
12
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

可以看到它的算法,其中char值就是字符对应的ACII编码: String

  1. substring(int beginIndex)

    返回一个新的字符串,它是此字符串的一个子字符串。 可以理解为截取字符串,它的实现就是用数组的copyOfRange将指定数组的指定范围复制到一个新数组:

1
this.value = Arrays.copyOfRange(value, offset, offset+count);

相关文章

written in java, 源码解析

你真的了解URL是个啥么?

什么是URL

我们经常使用浏览器上网,在想要查询某些网站的时候,我们都会在浏览器的地址栏中输入一段字符串, 也就是我们常说的“网址”,其实我们说的这个“网址”,指的就是URL,URL的全称是Uniform Resource Location, 叫做 「统一资源定位」!

Protocol协议和资源名称

比如我们想要访问google,应该会在浏览器的地址栏中输入 : http://www.google.com!

这时候我们可以看到 URL 中两个比较重要的部分,一个是协议,一个是资源名称,它们被 「://」分隔开, 左边的 http 就是协议,右边的 www.google.com 就是资源名称。

我们这里用到的协议是HTTP协议,他的全称叫做超文本传输协议,网络上的请求协议要许多,例如我们比较熟悉 的其它协议有ftp协议,https协议等等。

为什么要用Protocol(协议)呢?

因为没有规矩,不成方圆,我们在传输数据的时候,需要通过相应的规则才能获取相应的资源,比如你在家里,突然 饿了,想要点外卖,那么你需要找到你想吃的,然后付钱,商家才给你做,外卖小哥哥才笑嘻嘻的给你送, 如果你不遵循这一规则,你不给钱,你能吃到吗?外卖小哥会笑嘻嘻给你送吗?

所以我们这里使用HTTP协议,可以给我们提供超文本文档。

资源名称

资源名称是一个完整的地址,它的格式是完全取决于协议而定,不过在大多数的协议里面,资源名称都 包括以下几个东东:

  1. Host Name :主机名,也就是服务器的名称,一般是域名,也就是域名对应的这台服务器的ip地址, 例如:http://www.google.com 中的 www.google.com 就是主机名。

  2. FileName : 文件名,也就是我门要访问服务器上某个位置上的文件,这个文件所在的路径名就是FileName, 例如我们要访问a服务器上的 老师目录下的波多野结衣.jpg ,那么我们就可以这样访问: http://www.a.com/teacher/boduoyejieyi.jpg, 所以这里的teacher/boduoyejieyi.jpg就是FileName!

  3. Port Number : 端口号,这个是用于连接的端口,我们默认访问不需要输入端口,是因为80是默认的连接端口, 一般服务器上有0-65535端口,他开放哪个端口给你访问,你就只能通过它给你的端口进行访问,就好比你要去开房, 发现酒店有65536个房间,然后客服人员告诉你第8000个房间可以住,那么你就交完钱拿着房卡去8000号的房玩耍, 总不会去65536个房间玩个遍吧!

这个端口后一般是在尾部添加冒号,在冒号写上端口号。例如:http://www.google.com:80

  1. Parameters : 请求参数,我们可以通过参数去访问特定的资源,一般在访问的地址后面添加key-value的相识 的值去访问,例如我们要访问a这个网站的老师目录下第一到第十张波多野结衣的照片就可以这样呀访问: http://www.a.com/teacher/pic/boduoyejieyi?start=1&end=10

written in internet

通过王者荣耀学习抽象工厂方法模式

说起王者荣耀,之前有一段时间我掉进了这个坑之后,久久不能自拔,每天至少要撸上好几盘才肯罢休。 为了段位,不断的去打排位上分,现在已经跳出这个坑了,很少去玩了,除非跟朋友无聊会一起开一下黑! 可是,今天要说的抽象工厂方法的设计模式和王者荣耀要什么关系呢?

别着急,听我慢慢瞎扯即可!

产品等级结构

我们都知道王者荣耀里面的角色类型有好几种,比如:坦克,刺客,法师,射手,辅助…

  • 坦克有:张飞,程咬金,刘禅等等
  • 刺客有:李白,阿珂,赵云等等
  • 法师有:安其拉,貂蝉(说到貂蝉我就流口水),妲己等等。 …

我们可以把这种继承关系看成一个产品等级结构

我们刚刚说的 “坦克有:张飞,程咬金,刘禅等等” 就是一个产品等级结构……

可以理解为张飞,程咬金,刘禅继承于坦克,所以这个体系就是一个等级结构!

像这样就可以看成是一个产品等级结构:

Vimium

产品族

我们知道,在王者荣耀这些角色中,有男也有女,也有怪物: - 男:韩信,李白,张飞,吕布… - 女:不知火舞,貂蝉,安其拉… - 怪物:猴子,牛魔…

这些角色中不在同一产品等级结构中,例如不知火舞是刺客,安其拉是法师。但是他们却有共同的属性,例不知火舞和安其拉都是女的。 所以我们可以认为他们是一个族,也就是产品族

抽象工厂方法模式

我们已经知道产品等级结构和产品族是什么了,那么接下来就可以说说抽象工厂方法模式。

在抽象工厂方法模式里边,每一个工厂都提供了多个工厂方法,用来产生多种不同的类型的产品,而这些产品就构成了一个产品族。

例如有一个工厂,产生了不知火舞和安其拉,他们属于不同类型的角色,但是他们都是女的,工厂产生的这两个人物就构成了女的这个族! 这就是工厂方法模式的基本原理。

抽象工厂方法模式的定义是:“提供接口,创建一系列相关或独立的对象,而不指定这些对象的具体类。”

画个uml你就明白了:

抽象工厂方法模式

可以看到:

  1. AbstractFactory抽象工厂:它声明创建了产品族的接口,每一个方法对应一个产品,在这里我的产品族就是“男族”和“女族”,第一个方法创造的是法师,第二个 方法创造的是刺客。

  2. GirlFactory和BoyFactory具体工厂:它们实现了抽象工厂,产生具体的产品,这里的GirlFactory产生了安其拉和阿珂,组成的就是一个产品族:“女族”, BoyFactory产生了诸葛亮和赵云,组成了一个产品族:“男族”,而GirlFactory和BoyFactory产生的每个产品都位于不同的产品等级结构中,比如 这里的GirlFactory产生了安其拉和阿珂,安其拉是在法师这个“产品等级结构”中,阿珂是在刺客这个“产品等级结构”中!

  3. AbstractFashi和AbstractCiKe抽象产品:声明产品的接口,声明产品具有的业务方法,例如这里的AbstractFashi声明了法师,定义一些法师的功能。

  4. AnQiLa,ZhuGeLiang,Ake,ZhaoYun 具体产品:实现了抽象产品,实现相关的业务方法。

我们用代码来体现一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
 * Created by wistbean on 2017/11/22.
 * 抽象产品 法师
 */
public interface AbstractFashi {
  ra  //加蓝
    public void jiaLan();
}

/**
 * Created by wistbean on 2017/11/22.
 * 具体产品  安其拉
 */
public class AnQiLa implements AbstractFashi {
    @Override
    public void jiaLan() {
        System.out.println("安其拉加蓝咯");
    }
}


/**
 * Created by wistbean on 2017/11/22.
 * 具体产品 诸葛亮
 */
public class ZhuGeLiang implements AbstractFashi {
    @Override
    public void jiaLan() {
        System.out.println("诸葛亮加蓝咯!");
    }
}


/**
 * Created by wistbean on 2017/11/22.
 * 抽象产品 刺客
 */
public interface AbstractCiKe {
    //加伤害
    public void jiaShanHai();
}


/**
 * Created by wistbean on 2017/11/22.
 * 具体产品 阿珂
 */
public class AKe implements AbstractCiKe {
    @Override
    public void jiaShanHai() {
        System.out.println("阿珂加伤害了");
    }
}


/**
 * Created by wistbean on 2017/11/22.
 * 具体产品 赵云
 */
public class ZhaoYun implements AbstractCiKe {
    @Override
    public void jiaShanHai() {
        System.out.println("赵云加伤害咯!");
    }
}


/**
 * Created by wistbean on 2017/11/22.
 * 抽象工厂 声明一组创建产品族的方法
 */
 public interface AbstractFactory {
    AbstractFashi createFashi();
    AbstractCiKe createCiKe();
}


/**
 * Created by wistbean on 2017/11/22.
 * 具体工厂 实现抽象工厂的方法,生成具体产品 构成产品族
 */
public class BoyFactory implements AbstractFactory {
    @Override
    public AbstractFashi createFashi() {
        return new ZhuGeLiang();
    }

    @Override
    public AbstractCiKe createCiKe() {
        return new ZhaoYun();
    }
}


/**
 * Created by wistbean on 2017/11/22.
 * 具体工厂 实现抽象工厂的方法,生成具体产品 构成产品族
 */
public class GirlFactory implements AbstractFactory {
    @Override
    public AbstractFashi createFashi() {
        return new AnQiLa();
    }

    @Override
    public AbstractCiKe createCiKe() {
        return new AKe();
    }
}


public class Client {
    public static void main(String[] args){
        //要从工厂里边取女的人物
         AbstractFactory girlFactory;
         AbstractCiKe ciKe;
         AbstractFashi fashi;
        //获取工厂
        girlFactory = new GirlFactory();
        //获取刺客中女的人物
        ciKe = girlFactory.createCiKe();
        //获取法师中女的人物
        fashi = girlFactory.createFashi();

        ciKe.jiaShanHai();
        fashi.jiaLan();
    }
}

输出:
阿珂加伤害了
安其拉加蓝咯

抽象工厂方法模式和工厂方法模式的区别

以上就是抽象工厂方法模式的实现,我们可以看到它和工厂方法模式的区别:

工厂方法模式中的工厂对应一类产品,我们有许多产品需要创建的时候就会增加许多相应的工厂,会增加系统的 开销,而抽象工厂方法模式是将一些相关的产品(产品族)使用同一个工厂就可以创建。减少了系统的开销。

工厂方法模式只是针对一个产品等级结构,而抽象工厂可以同时针对多个产品等级结构,所以当我们需要一个工厂 来创建不同等级结构的一个产品族的所有对象的时候,抽象工厂方法模式比工厂方法模式更加的高效!

抽象工厂方法模式的优缺点

优点

  1. 增加新的产品族的时候非常方便,我们只需要对抽象工厂进行扩展就可以了,不需要修改系统本身的其它东西。
  2. 对统一约束产品有很好的解决方式,尽管产品之间没有很大的联系,但是它们都属于同一个操作系统中,可以 对其进行很好的规范约束!
  3. 用户无需关心对象的创建过程,低耦合!

缺点

如果我们要增加新的产品结构,那对系统的扩展来说会是比较麻烦的,需要对系统里边的代码进行修改,带来不便, 违背了设计模式中的开闭原则

抽象工厂方法模式的使用场景

  1. 同一产品族的产品被系统一起使用
  2. 不需要知道产品是如何被创建的
  3. 产品等级结构设计稳定,后续只增加产品族

我的相关文章

written in java, 设计模式

把我常使用的chrome插件推荐给你

现在PC端的浏览器我只使用chrome,他支持各种插件的扩展,还有调试的Developer Tools,速度快,界面简洁大方等等。让我用了就爱不释手了。 我猜80%的程序员都用chrome的吧。而我接下来推荐你的这些插件,是我用过后真心觉得不错的,如果你对这些插件使用熟悉了,那我觉得应该会对你的 使用效率有很大的提升。

可以通过访问 chrome.google.com/webstore/category/extensions?hl=zh-CN 搜索添加插件,不过要科学上网哦!

1. Vimium

这款插件的牛逼之处在于脱离鼠标,上网全程使用的是键盘,各种快捷键,可以通过快捷键上下移动网页,标签切换,快速定位链接,只要是你需要操作的, 都能通过快捷键快速操作!

Vimium

2. Google文档、表格及幻灯片的Office

你在网上看到word,ppt或者excel,你是不是第一步先下载到本地,然后打开相关的office软件查看呢?现在有他之后,你直接点击文件,就能直接在网页上查看, 也可以从中下载!

google office

3. Get Postman

这个插件我从下载后就一直使用,没有断过,因为它真的很不错,我的一些接口的调试,一些API的使用都是通过他来测试的,界面简洁,http的所有请求,以及返回的数据格式 设置,用户体验都非常不错,所以这款我是极力推荐的!

postman

4. jsonView

当你去调用API的时候,获取到json数据,是不是一团糟,那么这个插件就可以格式化json数据,你就可以很直观的使用这些数据去解析了! jsonView

5. Smart TOC

当你查看网页内容的时候,特别是一些文档,如果他没有设置菜单栏,那么去看这些内容的时候就会很麻烦,因为你可能只是需要观看某个API, 难道每次都要去整个网页搜索吗?所以这个插件解决了这个问题,直接帮你生成菜单标题栏,点击后直接就可以跳到相应的位置!

Smart TOC

好了,以上就是本期的内容,希望分享的这些插件,对你有用。

written in tool

什么是因特网?

什么是网络?

网络是由多个节点和链路组成的。 其中节点可以包括 计算机,集线器,路由器,交换机等。

网络

我们可以看到这是一个五个节点(4个计算机与1个集线器)与四条链路组成的网络,这就是网络!

什么是互联网?

我们已经知道什么是网络了,那么互联网顾名思义就是把网络与网络连接起来,就叫做互联网了!

互联网

将网络连接起来的是路由器,也就是说,互联网是由多个网络通过路由器连接而成的,互联网是网络的网络!

什么是因特网?

你已经知道了什么是互联网,那么我告诉你:世界上最大的互联网就是因特网!

因特网

可以看到,连接到因特网上的计算机,我们称为主机(host)!

** 所以我们可以这样理解:网络把计算机连接在一起,互联网把网络连接在一起,因特网是世界上最大的互联网! **

written in internet

说说我用Ngnix来做些什么

小明和小王因为分布式系统架构的事情,结果…中,提到了小明因为并发量太大用Nginx代理转发服务器,然后有小伙伴在我的公众号后台留言问我关于Nginx的东西,所以就索性写一篇关于我使用到Nginx的内容吧。

Nginx可以做什么呢?

1.Nginx可以做HTTP服务器,可以利用它来做一些静态资源的访问服务器,例如我们系统用到的图片,可以用Nginx服务器来专门访问我们的图片系统。

例如,有一个电商的网站,当有商品在系统中上架时,可以将上架的商品图片存放到我们的图片服务器,当我们需要这个商品的详情的时候,就可以通过Nginx去访问相应的图片。nginx比apache占用的cpu资源少,在高并发的情况下能保持低资源低消耗高性能。

2.Nginx可以做虚拟主机,可以在同一台服务器上运行多个网站,而且呢,这些不同网站是不会互相干扰的,在区分不同的网站我们可以通过不同的IP,不同的端口和不同的域名三种方式区分,但是一般使用的最多的就是使用域名来区分。例如通过www.a.com来访问a网站,通过www.b.com来访问b网站,并且a.com和b.com是基于同一个ip地址的。

虚拟主机的配置可以在nginx的conf文件中进行配置,在配置文件中每个server节点就代表一个虚拟主机。例如我想要配置www.a.com来访问a网站,通过www.b.com来访问b网站就可以这样子:

 server {
        listen       80;
        server_name  www.a.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   a/html; //指向a.com的网站
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }



 server {
	        listen       80;
	        server_name  www.b.com;
	
	        #charset koi8-r;
	
	        #access_log  logs/host.access.log  main;
	
	        location / {
	            root   b/html; //指向b.com的网站
	            index  index.html index.htm;
	        }
	
	        error_page   500 502 503 504  /50x.html;
	        location = /50x.html {
	            root   html;
	        }
	
	    }

配置好文件我们保存然后将Nginx reload一下之后,我们这时候访问www.a.com时Nginx就会去请求a网站给我们,同样访问www.b.com就会去请求b的网站,他们之间互不影响,且又绑定在同一个ip上。这种对于一些多个小的网站,绑定在同一个ip上是很好的实现方式。

3.Nginx可以进行负载均衡的反向代理,反向代理就是当你的网站因为并发量太大,部署在了多个服务器上,那么当用户去访问你的网站的时候,不知道去访问哪个服务器,这时候Nginx就是来帮忙进行反向代理请求的,也就是用户访问Nginx,然后Nginx帮用户去请求某一个Nginx认为该访问的服务器。

负载均衡反向代理的使用也是通过Nginx的文件进行配置的:

在conf中通过upstream来配置要代理的服务器:

	upstream tomcatserver1{
		server 192.168.88.61;
		server 192.168.88.62;
	}

其中还可以定义这些服务器的负载权重,是否参与负载等,可以在ip地址后面添加定义:

down 表示单前的server暂时不参与负载

weight 默认为1,weight越大,负载的权重就越大

backup 其它所有的非backup机器down或者忙的时候,就会请求backup机器。所以这台机器压力会是最轻的。

max_fails 允许请求失败的次数默认为1.当超过最大次时,返回proxy_next_upstream 模块定义的错误

fail_timeout: max_fails次失败后,暂停的时间。

比如 server 192.168.88.61 weight=2; 就代表192.168.88.61这个服务器会被访问的权重更多。

在server中配置域名和端口号,然后用proxy_pass去访问我们刚刚定义的upstream:

	server {
    listen       80;
    server_name  wistbean.server1.com;

   
    location / {
       proxy_pass http://tomcatserver1;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

}

 Nginx在Linux上安装

1.首先需要安装Nginx的依赖环境:

yum install -y openssl openssl-devel > OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。 > nginx不仅支持http协议,还支持https(即在ssl协议上传输http),所以需要在linux安装openssl库。

yum install -y zlib zlib-devel > 多种压缩和解压缩的方式,nginx使用zlib对http包的内容进行gzip

yum install -y pcre pcre-devel > nginx的http模块使用pcre来解析正则表达式

yum install gcc-c++ > 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境

2.下载Nginx:http://nginx.org/en/download.html ,例如下载1.8.1版本可以 wget http://nginx.org/download/nginx-1.8.1.tar.gz

3.解压Nginx tar -zxvf nginx-1.8.0.tar.gz

4.参数配置

	./configure \
	--prefix=/usr/local/nginx \
	--pid-path=/var/run/nginx/nginx.pid \
	--lock-path=/var/lock/nginx.lock \
	--error-log-path=/var/log/nginx/error.log \
	--http-log-path=/var/log/nginx/access.log \
	--with-http_gzip_static_module \
	--http-client-body-temp-path=/var/temp/nginx/client \
	--http-proxy-temp-path=/var/temp/nginx/proxy \
	--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
	--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
	--http-scgi-temp-path=/var/temp/nginx/scgi

将以上复制到Linux执行一遍。然后需要到/var/temp/中创建nginx目录。

mkdir nginx

5.编译安装 make make install

6.启动

进入nginx的sbin目录: cd /usr/local/nginx/sbin ./nginx 开始启动了…

7.关闭 ./nginx -s stop

8.重新加载配置文件 ./nginx -s reload

written in tool

收录我在Java开发中遇到的错误及解决方法

Intelij中maven依赖失效

Error:

新建module的时候发现maven里面依赖的jar包没有import进来,点击reimport也没有反应。

Fix

  1. 打开maven Project窗口
  2. 点击「Excute Maven Goal」图标。
  3. 输入 -U idea:idea

使用postMan请求SpringMVC的@RequestBody接口发现状态 HTTP Status 415 -

Error:

在发送请求的时候,没有去选中Content-Type:application/json

Fix

设置Mybatis插入数据的时候返回id

QUESTION:

设置Mybatis插入数据的时候返回id

Fix

在mapper的xml文件中添加 useGeneratedKeys="true" keyProperty="id"

## Mybatis自定义查询的时候 **ERROR**: Type handler was null on parameter mapping for property 'XXXX' **Fix**: 在mapper中添加Param标签: List selectList(@Param("XXXX")String XXXX);

written in bug, java

收录我在Linux中遇到的错误及解决方法

使用yum安装的时候404

Error: Could not retrieve mirrorlist http://poptop.sourceforge.net/yum/stable/mirrorlist-poptop-stable-rhel7 error ….

Fix

  1. rm /etc/yum.repos.d/pptp.repo
  2. yum update

tomcat启动很慢

INFO: At least one JAR was scanned for TLDs yet contained no TLDs….

Fix

vi /usr/local/jdk1.8/jre/lib/security/java.security

修改: #securerandom.source=file:/dev/random securerandom.source=file:/dev/./urandom

Linux设置Tomcat/MySQL开机自启动

Fix

  1. vi /etc/rc.d/rc.local

``` sudo service mysqld start

 sudo /usr/local/tomcat/bin/startup.sh

```

  1. chmod +x /etc/rc.d/rc.local

written in bug

小明和小王因为分布式系统架构的事情,结果...

在开始本文之前,先说说本周我被刷屏的三个玩意儿:

1.1024程序员节

在本周二,也就是10.24的时候,是程序员节!

可能大多数人(老司机)知道的1024是从草榴社区来的…

在计算机是以二进制的形式记录数据的,2的10次方就是1024,1G=1024M,1M=1024kb,1k=1024bit..

所以在草榴社区评论1024的时候,可以认为是1024M=1GB,也就是“一级棒”!

慢慢的1024就成了程序员节。虽然元宵节吃汤圆,中秋节吃月饼,但是不知道程序员节吃什么?

1024程序员节

虽然没有狂欢没有法律的支持也没有公司的特殊待遇,但是至少,这个世界上有这么一个群体存在,他们努力着,被世界关注着,也在影响着世界。

2.麦当劳改名金拱门

当我听到“金拱门”的时候,以为又有什么“门事件”发生,有什么福利可看了。 后来才发现是麦当劳公司名改为“金拱门”,有点失望的同时我也和吃瓜群众一样:“挖槽!真尼玛的土!!”。 不过在这里我不是要来吐槽这个名字的..

当我们还在为“金拱(gǒng)门”这个名字讲段子说笑料的时候,有人已经第一时间就盯上了www.jingongmen.com这个域名,然后先是9.8k元被交易,半小时被2.5w元易主,瞬间捞金1.5w+。然后现在竟然被抬价¥888,888。 人家对这个域名的介绍是这样的:“此名由高人测八字看风水算出,庚金座三会金局,金气一支独旺。果然现在传遍全国。价格多一分不取,少一分不卖。 ”

麦当劳改名金拱门

哈哈,我想说的是:有时候想想,对聪明的人来说,赚钱一点都不难。

3.左右脑测试随机

我被下面这张图刷屏了…

左右脑测试随机

好多人转发,然后配上一句类似的话:“不靠谱,尼玛这代码随机的”! 但是,图上的random并不是随机年龄啊,这个随机数是%2(是否被2整除),是的话就取one,不是就取two,至于result是怎么算的,这里没有体现出来!为毛很多程序员也在666?

OK…进入正题:

传统的架构与分布式架构

有一天,隔壁小王睡前突然有一个大胆的插法,哦不~ 大胆的想法,他要做一个在线B2C的美女网站,突然发现接下来要发了,可以赢取白富美了,不过他又想到了一个问题:

“想法都有了,就差一个程序员了”!

翻了一会通讯录后发现了楼下的小明就是个程序员啊!于是也不管几点就直接从床上跳起来,穿着拖鞋跑到楼下猛敲小明的房门。

小明这时候正在写代码,被连续的敲门声吓得写了一个bug后就去开门了。

“什么事啊?”

“呀,我是你楼上的小王啊,你还没睡啊?有事找你有事找你”

小王不管不顾的走进去,小明一阵错愕!

“小明”,小王放低了声音,“我有一个项目,绝对能发,现在就差开发了,听说你的技术很牛逼!”

“什么项目?”,小明被小王的神秘语气和一种不知哪来的自信引起了兴趣!

“一个B2C的美女网站,用户可以购买美女的时间,比如买11月15号的下午14:00-16:00,然后下订单,美女可以在这两个钟陪用户读书学习 :) ……” 小王绘声绘色,小明却一脸无奈。。

小王知道小明的意思,就继续吹:“到时一定赚钱的,你就是我的合伙人啊,你拿30%”。

小明不为所动…

这时候小王冒出了些许冷汗,转念一想,做出了猥琐的表情说道:“我可是楼上小王哦!”

总之,在小王的威逼利诱下,小明勉强答应了!

小明跟小王聊了5个晚上的需求,有时候聊着聊着就一起睡着了,慢慢理清了思路,就开始干了。

他像以前的开发那样,建立一个web工程,不断往web里边添加功能,比如订单功能,用户管理功能,商品信息管理功能等都丢到web工程里边。

在小王的催促和监督下,经过两个多月,小明终于搞完第一个初始版本!

小明和小王都测试了一遍发现没什么问题,就打算弄个服务器,然后把web项目和数据库都扔到一个tomcat里去。

上线了!!!

小王很开心,看到了自己的想法实现了,并且已经在网上可以找到!

过了好几天,网站的用户量是2,也就是只有他们2个。小王开始急了 - -

小王发现推广很重要,于是去跟他爸爸拿了几百万投放广告,他爸爸刚开始不肯,说不懂互联网,于是小王把网站发给他爸看,他爸就同意了。

小王有钱之后,就去找广告商了,于是慢慢的电视上的综艺节目有了他的美女网站的广告了!

理所当然的用户量开始越来越高了,小明发现,服务器崩了!!

并发量太大,小明觉得一个tomcat已经不行了,于是小明就告诉小王,咱们用户量越来越多了,一个服务器不行,买多两台服务器吧,小王听到用户量增加,开心的答应了。

小明把项目在每个服务器里边都放了一份,然后用nginx代理转发。

传统架构

就这样可以顶了一段时间…

最近小王在后台上架了一个非常漂亮的美女,导致太多用户访问,服务器又崩了…

小明对小王说:“我们得加强一下服务器配置了,把带宽,内存和cpu都升级吧!”

于是,又顶了一段时间…

过不久又崩了!

小王开始不爽了,对小明说:“怎么搞的?怎么服务器老是不行??”

小明说:“我他妈怎么知道你是个富二代?一开始以为你是闹着玩的,谁知道用户量会增多?”

小王发现小明有点生气了,他想着不能得罪程序员,于是轻声说:“那怎么办?”

“我得重构了!每个tomcat都放着整个web工程,后台访问也就我们两个,没有并发的问题,浪费资源了。模块之间耦合度太高了,其中一个功能升级其他的也都得升级,系统扩展性也差,不能够灵活的去部署”,小明如是说!

小王有点似懂非懂的问:“那怎么重构呢?”

“用分布式!我们把整个项目工程拆分成多个子项目,每个子项目负责自己的功能,例如订单这个功能就是一个单独的系统项目,会员系统也是一个单独的系统。” 小明边说边在纸上画了一张图:

分布式系统

小王依然似懂非懂的问:“这样比之前有多好?”

“这样的话,我们把每个模块都拆分出来,可以灵活的部署了,比如美女商品信息这个模块被访问的量比较大,那么我们就可以单独对这个模块进行服务性能的提升,不用全部都一起提升。也降低了代码的耦合度,模块之间互不影响,这样如果以后有人加入开发,他只要负责他的模块去开发就可以了,合作也高效!” 小明说道。

“那有什么缺点没?”

“有吧,就是各个模块之间需要通信,这时候需要开发接口,增加了些工作量!不过这是值得,总比花钱去买更多服务器配置好吧!”

“恩,有道理有道理!”

于是小明就这样开始重构了他的项目,慢慢的项目的稳定性比之前的好多了。

过了6个月,项目开始盈利了,于是小王开始招兵买马,把小明踢出去。

(哈哈,没有啦,开玩笑的,最后他们在一起了!)

written in 分布式系统

程序员应该具备什么样的特质

程序员常常自嘲码农,或者说自己是搬砖的,对于大部分程序员来说事实如此,因为在工作上对业务的代码写的比较多,有些业务之间的代码存在共同性,完全没有再去造轮子的必要了,对现有的”轮子”拿来就用似乎成为了我们的习惯!但是在慢慢的成长中我们会发现,即使有一些框架可以供我们使用,但是对我们自己来说,如何才能将代码进行写的更好?如何去优化?性能怎么提升?设计模式为什么好?框架是怎么实现的?如何在拿到需求的时候能够高效实现等等,都需要我们具备以下特质:

自学能力

我们经常听到别人说这个社会环境发展迅速,特别是科技这一块,我们也可以发现,新的语言新的特性新的工具不断在更新,比如Android开发,之前用的eclipse工具,java语言,现在工具都已经用上AndroidStudio,语言也在向Kotlin发展。再比如人工智能,大数据,深度学习等都是发展趋势,如果没有很强的自学能力来适应社会环境的发展,那么就很快会被淘汰。而且我也发现,很多牛人都有很强的自学能力。所以作为码农,没理由一直不去学会自学,这可以说是我们的生存技能了,其实不仅仅是码农,你说哪一个行业的优秀人员不是拥有超强的自学能力的呢?

获取资源

有时候我们要学习某一块知识点,或者遇到问题的时候,有些人可能会去问别人,有人会百度,有人也会google等去获取资源,对于好的资源对我们来说事半功倍,所以对资源的筛选和搜索技巧对于我们来说挺重要的,比如我想学习网络框架这一块的资源,那么我首先会去github上搜索「java http 」,然后根据最多的收藏数排序,就很明显看到比较流行的框架:

获取资源

可以看到retrofit和okhttp都有20+k的start,然后在google一下发现可以看到很多人在使用: 获取资源

,那么就将他的源代码clone下来,然后在结合一些文章,看看如何使用,甚至对源码进行学习。当然了,这里只是提供我自己的获取资源的某一方法,获取途径有许多种,适合自己的才是最好的吧。推荐去学学Google搜索技巧。

总结思考

总结的好处是将自己对资源的整合和资料的学习过程有一个好的归纳和学习思考,就好像孔子爷爷说的学而时习之,不亦乐乎!学而不思则罔!所以我觉得建立自己的一个博客是一个非常好的东西,常常对自己的生活,代码,想法等等进行总结在自己的博客上,时不时看看,发现自己的不足之处,也对自己有很大的提升,而且也可以让别人少走一些弯路。

Code Review

经常听小伙伴说,看我过去写的代码就是一坨翔啊,是的!没错,大多数人都是这样,因为自己在成长学习,有了新的编程思想,或者自己对写代码的要求越来越高等,都会产生这样的心里,我们对代码进行审查,能发现自己的不足,也可能会发现之前不易发现的bug等,这都是可以提升软件质量和技术的。

英语

许多文档,第一手资料都是英文,我们的开发语言也是英文,学好英文也是关键!

耐心

当我们学习一些新的知识点,可能一开始接触会有点懵逼,也有抵触的心里,这时候我们需要点耐心去接收它。 当我们发现程序bug时,也常常会不知道怎么解决,这时候我们需要点耐心,去搜索去咨询去解决它。 当需求变更了,我们也需要点耐心,跟产品经理吵吵架,锻炼口才!

好身体

身体健康,懂的人自然懂!


以上,便是我认为程序员应该具备的特质,观点如有雷同,纯属你爱我,当然欢迎补充!

written in 个人观点

你在chrome浏览器地址栏中输入baidu.com回车后发生了什么

本文目录

1.你在chrome的地址栏输入baidu.com

2.chrome通过DNS去查找baidu.com这个域名对应的IP地址

3.浏览器给baidu服务器发送一个HTTP请求

4.baidu服务器301重定向响应

5.chrome浏览器请求重定向来的地址

6.baidu服务器处理请求

7.baidu服务器返回HTML响应

8.chrome浏览器显示baidu页面

浏览器是我们上网必备的工具之一,每当我们需要查询资料的时候,我们就会通过浏览器去访问相应的网站,或者当你寂寞难耐的时候,也许会关上窗门,然后通过浏览器去访问你收藏已久的域名,打开他的在线网站…

url

emmmmmm..

回到正题,当你在chrome浏览器地址栏中输入baidu.com回车后会发生什么呢?

1.你在chrome的地址栏输入baidu.com

chrome的地址栏输入baidu.com

2.chrome通过DNS去查找baidu.com这个域名对应的IP地址

chrome通过DNS去查找baidu.com这个域名对应的IP地址

DNS的查找过程是这样的:

chrome浏览器会先查找有没有缓存的DNS记录,如果在浏览器缓存没有找到需要的记录,就会去做一个系统的调用,获取系统缓存的记录;

如果没有记录请求会继续到路由器上,路由器上有自己的DNS缓存;

如果没有记录就会到ISP的DNS缓存中查看记录;

如果没有记录就会在ISP的DNS服务器从根服务器域名服务器开始递归搜索最后得到IP地址。

3.浏览器给baidu服务器发送一个HTTP请求

浏览器给baidu服务器发送一个HTTP请求

获取到baidu的ip地址之后,就可以给baidu这个服务器发送HTTP请求了,我们通过URL地址去发送的时候是一个GET的请求,这时候会向baidu服务器发送一个header信息:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Cookie:PSTM=1506157985; BIDUPSID=DA662DF344C147D17FB4828CCD795292; ...
Host:www.baidu.com
Pragma:no-cache
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36

其中

  • User-Agent是向baidu服务器提供浏览器的类型,操作系统版本,浏览插件,浏览器语言等信息。
  • Accept是告诉服务器说我们需要接收的类型是什么样子的
  • Connection:keep-alive 是为了后边请求不要关闭TCP连接
  • Cookie 是以文本形式存储,每次请求的时候就会发送给服务器,它可以存储用户的状态,用户名等信息

4.baidu服务器301重定向响应

baidu服务器301重定向响应

因为刚刚我们在chrome浏览器中输入的是baidu.com而不是www.baidu.com,这时候baidu服务器就会将我们的请求响应一个301永久重定向到www.baidu.com。

5.chrome浏览器请求重定向来的地址

浏览器给baidu服务器发送一个HTTP请求

这时候chrome浏览器知道www.baidu.com才是baidu希望访问的地址,那么这时候chrome浏览器就会给baidu服务器发送另一个请求。

6.baidu服务器处理请求

baidu服务器在这个时候接收到了请求,它会去查看请求它的参数还有cookie信息,然后会进行一些操作处理,例如对数据进行存储,从数据库中获取需要被请求的数据等。

7.baidu服务器返回HTML响应

baidu服务器返回HTML响应

当baidu服务器处理好之后,就会给浏览器返回数据了,这个时候会有一个Response Headers发送给浏览器:

Bdpagetype:1
Bdqid:0xddf2be49000b5995
Bduserid:0
Cache-Control:private
Connection:Keep-Alive
Content-Encoding:gzip
Content-Type:text/html; charset=utf-8
Cxy_all:baidu+09720a4fa84e5493ae7506a57de6bc05
Date:Sat, 14 Oct 2017 09:39:32 GMT
Expires:Sat, 14 Oct 2017 09:39:32 GMT
Server:BWS/1.1
Set-Cookie:BDSVRTM=49; path=/
Set-Cookie:BD_HOME=0; path=/
Set-Cookie:H_PS_PSSID=1440_13551_21103_24658; path=/; domain=.baidu.com
Strict-Transport-Security:max-age=172800
Transfer-Encoding:chunked
Vary:Accept-Encoding
X-Powered-By:HPHP
X-Ua-Compatible:IE=Edge,chrome=1

Response Headers说明了是否缓存这个页面,怎么去解释响应信息,cookie的设置,隐私信息等。 其中

  • Content-Encoding:gzip告诉浏览器整个响应体是用gzip算法压缩的。
  • Content-Type:text/html; charset=utf-8 告诉浏览器将响应的内容以HTML的形式呈现出来,字符集是utf-8。

8.chrome浏览器显示baidu页面

这时候chrome浏览器获得了响应内容,就开始显示baidu的HTML页面了,浏览器在显示的时候,发现需要获取其他的标签内容,比如图片、css样式表、JavaScript文件,那么浏览器就会继续对baidu服务器去发送请求这些内容,这些静态的文件baidu会把它进行缓存,利用内容分发网络(CDN)进行分发,所以这些静态文件在很多CDN数据中心都有备份,所以浏览器很快就能获取到这些静态文件。

从而进行页面吧的完整的显示:

baidu完整的显示

我的相关文章

相关资料

what-really-happens-when-you-navigate-to-a-url

written in internet

Java创建型设计模式:工厂方法模式

本文目录

1.工厂方法模式的使用例子

2.工厂方法模式的主要代码实现

3.工厂方法模式的优点

4.工厂方法模式的使用场景

5.我的相关文章

6.工厂方法模式的相关资料

工厂方法模式的使用例子

假设电脑由主机、键盘、显示器、鼠标组成,现在需要组装一台这样的电脑交给调用者去使用,那么可能我们会这样写代码:

public class Host {

    public void describe()
    {
        System.out.println("这是电脑主机");
    }

}


public class Monitor {

    public void describe()
    {
        System.out.println("这是显示器");
    }

}


public class Mouse {

    public void describe()
    {
        System.out.println("这是鼠标");
    }

}

public class _keyboard {

    public void describe()
    {
        System.out.println("这是键盘");
    }

}


public class Computer {

    private Host host;
    private _keyboard _keyboard;
    private Monitor monitor;
    private Mouse mouse;

    public Computer(Host host,_keyboard _keyboard,Monitor monitor,Mouse mouse){
        this.host = host;
        this._keyboard = _keyboard;
        this.monitor = monitor;
        this.mouse = mouse;
    }

    public void show()
    {
        this.host.describe();
        this._keyboard.describe();
        this.monitor.describe();
        this.mouse.describe();
    }
   
}


调用者:

 public static void main(String[] args)
    {
        Host host = new Host();
        _keyboard keyboard = new _keyboard();
        Monitor monitor = new Monitor();
        Mouse mouse = new Mouse();

        Computer computer = new Computer(host,keyboard,monitor,mouse);
        computer.show();
    }

	
输出:

	这是电脑主机
	这是键盘
	这是显示器
	这是鼠标

我们可以看到,调用者为了搞一台电脑,还需要自己去创建主机,键盘,显示器,鼠标这些组件,但是这些组件是和调用者无关的,而且这些组件实际情况下可能是抽象的,调用者根本不知道如何去使用这些。

另外假设调用者需要不同品牌的电脑,例如苹果电脑,联想电脑,三星电脑等等,这样对代码来说不仅耦合度太高了,而且很是不利于之后的优化扩展。

调用者只希望有一个创建电脑的工厂,使用它直接造出一台电脑就好了。

比如想要苹果电脑,那就直接告诉 「苹果工厂」 : “给我造台电脑来” ,然后 「苹果工厂」 就屁颠屁颠的将苹果电脑送过来了。

同样的,如果你想要联想电脑,那就直接告诉 「联想工厂」 : “快,搞台出来给我” ,于是 「联想工厂」 就把崭新的联想电脑送到你手里了。

想想是不是简直不要太爽?

那这时候,我们就可以这样子,先画个类图:

工厂方法设计模式

如果对类图不太了解可以先看这:轻松理解UML用例图时序图类图的教程

可以看到:

  • ComputerFactory 是一个抽象的工厂(接口),它有一个createComputer的方法,这个方法返回的是Computer这个接口类。
  • AppleFactory 和 LenovoFactory是具体的工厂类,分别实现了ComputerFactory,它们重写父类的createComputer方法,其中 AppleFactory的createComputer方法返回的是AppleComputer,LenovoFactory的createComputer方法返回的是LenovoComputer
  • Computer是一个抽象的产品类
  • AppleComputer和LenovoComputer是具体的产品类,分别实现了Computer。

代码体现如下:

 /**
 * Created by wistbean on 2017/10/7.
 * 抽象工厂
 */
public interface ComputerFactory {

    public Computer createComputer();

}

/**
 * Created by wistbean on 2017/10/7.
 * 具体工厂
 */
public class AppleFactory implements ComputerFactory {
    @Override
    public Computer createComputer() {

        //这里主要为了演示工厂方法模式,组装电脑的代码省略...

        Computer appleComputer = new AppleComputer();

        return appleComputer;
    }
}


/**
 * Created by wistbean on 2017/10/7.
 * 具体工厂
 */
public class LenovoFactory implements ComputerFactory {
    @Override
    public Computer createComputer() {

        //这里主要为了演示工厂方法模式,组装电脑的代码省略...

        Computer lenovoComputer = new LenovoComputer();

        return lenovoComputer;
    }
}


/**
 * Created by wistbean on 2017/10/7.
 * 抽象产品
 */
public interface Computer {

    public void describe();
}


/**
 * Created by wistbean on 2017/10/7.
 * 具体产品
 */
public class AppleComputer implements Computer {

    @Override
    public void describe() {
        System.out.println("苹果电脑");
    }
}

/**
 * Created by wistbean on 2017/10/7.
 * 具体产品
 */
public class LenovoComputer implements Computer {

    @Override
    public void describe() {
        System.out.println("联想电脑");
    }
}


/**
 * Created by wistbean on 2017/10/7.
 * 调用
 */
public class Client {

    public static void main(String args[])
    {
        ComputerFactory computerFactory;
        Computer computer;

        //让工厂给我创建一台苹果电脑
        computerFactory = new AppleFactory();
        computer = computerFactory.createComputer();
        computer.describe();

        //让工厂给我创建一台联想电脑
        computerFactory = new LenovoFactory();
        computer = computerFactory.createComputer();
        computer.describe();
    }
}


输出:

	苹果电脑
	联想电脑

以上,便是一个工厂方法模式的实现过程,其中的:

工厂抽象接口用来返回一个产品,所有创建对象的工厂类都必须实现它;

抽象工厂的子类(具体工厂类)则实现了抽象工厂中定义的方法,在这里返回具体的对象,还可以做一些初始化操作,环境配置等等。

抽象的产品类则是所有具体产品的父类,主要定义一些产品的规范。

具体产品类与具体工厂类一一对应,决定了产品的具体行为。

从列子中也可以看到,针对不同的产品提供了不同的工厂,例如苹果电脑对应于苹果工厂;我们定义了一个抽象的工厂,让实例化这个抽象工厂的具体工厂类来决定实例化哪个类,这样让类的实例化延迟到子类中进行了!

工厂方法模式的主要代码实现

public interface IFactory {

    public IProduct createProduct();
}

public class ProductFactory implements IFactory {
    @Override
    public IProduct createProduct() {
        IProduct product = new Product();
        return product;
    }
}


public interface IProduct {

    public void productMethod();

}


public class Product implements IProduct {
    @Override
    public void productMethod() {
        System.out.println("产品行为");
    }
}


public class Client {

    public static void main(String args[])
    {
        IFactory factory;
        IProduct product;

        factory = new ProductFactory();
        product = factory.createProduct();

        product.productMethod();
    }

}

工厂方法模式的优点

  1. 使得代码结构清晰,降低了代码的耦合性,工厂方法模式把复杂的产品实现封装了起来,调用者无需关心产品实例的具体实现,直接通过工厂获取实例即可。

  2. 易于扩展,当我们需要新的产品的时候,只需要实现新的抽象产品类和实现抽象工厂类即可。

工厂方法模式的使用场景

  1. 对于一些较为复杂的对象创建我们可以使用工厂方法模式。
  2. 调用者不知道子类的名称时,只知道对应的工厂,直接从工厂获取可以用工厂方法模式。
  3. 调用者需要组装产品的时候产生过多依赖的关系时,可以用工厂方法模式。

我的相关文章

相关资料

written in java, 设计模式

轻松理解UML用例图时序图类图的教程

写在前面

当你老大扔给你这样的图,或者你需要完成某些功能而去看文档的时候发现以下类似这样的图会不会不(一)知(脸)所(懵)措(逼):

UML 用例图

(图片来至wikipedia

UML 时序图

(图片来至微信内网页支付时序图

UML 类图

(图片来至wikipedia

如果你看了都不会一脸懵逼,那么可以出门左转啦,这篇文章就是来说明这些图的意思,让你在工作交流中,或者在看一些文档,或者看我的一些关于设计模式的文章,甚至架构建模中,都能轻松理解,毫无压力!

UML的用例图

刚刚我们看到的第一张图片就是用例图,用例图有以下几个东东:

  • 用例
  • 参与者
  • 关联
  • 系统边界

用例使用椭圆来表示,椭圆里边写上用例的名称:

UML use case

参与者用一个小人儿,在小人儿下面写上参与者名称,例如学生:

UML actor

关联用一条线儿表示:

UML relation

把用例围起来,系统边界就用个矩形啦:

UML boundary

举个例子:

UML usecase

我们可以看到这个

系统叫做 「购物系统」 ; 有注册账号、登录系统、生成订单的用例(这里只列举几个用例,其它用例省略); 参与者有顾客和管理员; 顾客关联到了注册账号和登录系统的用例; 管理员关联到了登录系统和生成订单的用例!

UML的时序图

时序图就是我们刚刚看到的第二个图,时序图有以下几个东东:

  • 对象
  • 生命线
  • 活动条
  • 消息
  • 控制流
    • 顺序
    • 分支
    • 循环

对象在矩形里边,左边是对象名称,右边是对象类型,下方还有一条线:

UML 时序图

用虚线代表的是对象的生命线:

UML 时序图

活动条是一个竖着的矩形,当接收到消息的时候,这个对象就会有活动条:

UML 时序图

消息用一根箭头,箭头上面写上消息信息,例如一个登录方法 login(userName,passWord):

UML 时序图

控制流有三种,普通就是按顺序的一个流程,还有分支就相当于if else:

UML 时序图

矩形里边有一条虚线,左上角用ALT表示,当C为true的时候,就执行虚线上方的内容,当C为false的时候就执行虚线下方的内容。

循环控制流:

UML loop

左上角有LOOP表示循环,当C为true的时候,就循环执行方框里边的内容!

举个UML时序图例子:

UML sequence diagram

可以看到,每个对象都有它们的生命线(虚线);

  1. 一开始用户给类型为GUI(界面)的Login对象发送一个登录信息 「 login(userName,passWord)」 ;

  2. Login这个对象接收到了就会产生活动条,这时候Login这个界面会给类型为Controller的loginController发送登录信息「 login(userName,passWord)」 ;

  3. 此时loginController接收到信息产生活动条,这时候loginController去找类型为DB的UserDao,给它发送了一个获取用户密码的信息 「getUserPassword(userName)」;

  4. UserDao接收到信息后产生活动条,返回用户的密码;

  5. 此时loginController接收到信息(用户的密码 userPassword),loginController此刻将信息发送给自己,通过verify去校验密码并且用result接收结果;

  6. 校验就产生了分支控制流,当resul为true的时候loginController会发送success给Login,当result为false的时候,loginController会发送failure给Login;

  7. 最后Login最后给用户发送messag信息。

好了,当你看到这里的时候,你就会发现,以后关于这类的时序图对你来说已经不在话下了。例如本文一开始的第二张图。

UML的类图

类图有以下几个东东:

  • 类名
  • 属性
  • 方法
  • 可见性
  • 数据类型
  • 关联关系
  • 依赖关系
  • 继承/实现关系
  • 组合/聚合关系

类名、属性、方法在一个矩形中,分为上中下:

UML class

可见性:

  • private的时候在前方用 - 表示
  • public的时候在前方用 + 表示
  • protected的时候在前方用 # 表示

数据类型表示形式:

名字 : 类型

例如:

uml

userName 和 passWord 这两个属性是protected修饰的,数据类型都是String; login() 和 register() 这两个方法是public修饰的,数据类型都是boolean;

关联关系,用实线表示,例如A关联B:

uml 关联关系

依赖关系,用虚线表示,例如A依赖B:

uml 依赖关系

继承关系,用一个△ + 一条实线表示,例如A继承B:

uml 继承关系

依赖关系,用过一个△ + 一条虚线表示,例如A实现B:

uml 依赖关系

组合关系,用一个 ♦ + 一条实线表示,例如A组合B:

uml 组合关系

聚合关系,用一个 ◇ + 一条实线表示,例如A组合B:

uml 聚合关系

聚合:表示两个对象之间是整体和部分的弱关系,部分的生命周期可以超越整体。如电脑和鼠标。

组合:表示两个对象之间是整体和部分的强关系,部分的生命周期不能超越整体,或者说不能脱离整体而存在。组合关系的“部分”,是不能在整体之间进行共享的。

类图举例子:

UML 类图

可以看到,有User类,Customer类,Address类,Role类,Payment类,aliPay类,wechatPay类。

其中Customer 继承 User,User类的userName 和 passWord 这两个属性是protected修饰的,数据类型都是String; login() 和 register() 这两个方法是public修饰的,数据类型都是boolean;

Address和Role聚合User;

Customer依赖Payment;

aliPay 和 wechatPay 实现了 Payment!

以上,就是关于UML的用例图,时序图,类图!当然可以自己手动画几个试试,感觉挺不错哦,相信你在工作交流中,或者在看一些文档,或者看我的一些关于设计模式的文章,甚至架构建模中,都能轻松理解,毫无压力!

相关文章

参考资料

written in 设计模式

Java创建型单例设计模式:全局唯一对象

回收站的例子说明单例设计模式

当你打开你桌面上的 「回收站」 时,你可以看到里面有你删除的文件,当你再次或者多次打开桌面的「回收站」时,你会发现无论你打开多少次,系统都只会有一个「回收站」ze的窗口存在,而不会出现多个,这是为什么呢?

试想一下,如果每次打开都会有一个新的「回收站」窗口,那么当你在某一个窗口中清除了垃圾文件,那么另一个窗口依然存在这些文件,这样子的话:

  • 一:用户体验不好,有一种删除了还存在文件的错觉,有歧义。
  • 二:打开多个窗口,创建了多个对象,浪费了资源。

单例设计模式

所以这个时候整个系统就只允许一个「回收站」对象存在,让它具有唯一性,从而统一对它进行管理操作。

那么如何做到确保整个系统只有一个对象,而且能让这个唯一的对象能够提供公共的访问方法呢?那么这时候就可以用到单例设计模式了。

实现思路是这样的:

  1. 让该类的构造函数定义为私有(private),这样其它代码没办法去实例化(new)这个对象
  2. 让该类提供一个静态的方法,且让这个静态方法可以得到这个类的实例。

我们来模拟回收站这个对象:

public class RecycleBin {

    private final static RecycleBin instance = new RecycleBin();

    //让该类的构造函数定义为私有(private),这样其它代码没办法去实例化(new)这个对象
    private RecycleBin(){}

    //让该类提供一个静态的方法,且让这个静态方法可以得到这个类的实例。
    public static RecycleBin getInstance()
    {
        return instance;
    }

    public void showDeletedFile()
    {
        System.out.println("显示被删除的文件");
    }

    public void restoreFile()
    {
        System.out.println("还原被删除的文件");
    }

    public void deletFile()
    {
        System.out.println("彻底删除文件");
    }


}

在这里我们模拟创建了「回收站」这个对象,它拥有显示被删除的文件、还原被删除的文件、彻底删除文件这三个方法,当然这里只是模拟打印出一句话来,真正的程序是很复杂的。而这里以通俗的言语来描述,根据我们刚刚的思路,这里让该类的构造函数定义为私有(private),提供一个静态的方法,让这个静态方法可以得到这个类的实例。

这时候我们调用的时候就是这样的:

public class Test {

    public static void main(String args[])
    {
        RecycleBin recycleBin = RecycleBin.getInstance();
        RecycleBin recycleBin1 = RecycleBin.getInstance();
        RecycleBin recycleBin2 = RecycleBin.getInstance();

        System.out.println("是否用一个实例:" + (recycleBin == recycleBin1 && recycleBin1== recycleBin2));

        recycleBin.showDeletedFile();
        recycleBin.restoreFile();
        recycleBin.deletFile();
    }
}

输出:

是否用一个实例:true
显示被删除的文件
还原被删除的文件
彻底删除文件

我们通过判断可以知道每次getInstance()出来的实例都是同一个,也就是说这个recycleBin具有唯一性,接着便是调用recycleBin的方法了。

这就是一个单例的简单具体实现了,可以说RecycleBin这个类是单例类。

我们可以看到这个类的对象引用永远是同一个,而且这个类提供一个获得该实例的静态方法,通常情况下用的是getInstance()这个名称。

刚刚我们的RecycleBin类是在类加载的时候便去初始化:

 private final static RecycleBin instance = new RecycleBin();

有人也许会觉得这样类一加载就要去实例化对象,会耗费时间且一直占用资源,能不能在使用的时候才初始化呢?

答案是肯定的,我们将我们的RecycleBin修改一下:

public class RecyleBin {

    private volatile static RecyleBin instance;

    //让该类的构造函数定义为私有(private),这样其它代码没办法去实例化(new)这个对象
    private RecyleBin(){}

    //需要的时候才去加载实例
    public static RecyleBin getInstance()
    {
        if(instance==null)
        {
            instance = new RecyleBin();
        }

        return instance;
    }

    public void showDeletedFile()
    {
        System.out.println("显示被删除的文件");
    }

    public void restoreFile()
    {
        System.out.println("还原被删除的文件");
    }

    public void deletFile()
    {
        System.out.println("彻底删除文件");
    }
}

可以看到,这一次我们是先判断instance是否为空,空的话就实例对象再返回,不为空就直接返回对象了,类加载的时候不进行实例化,其实这种方式称为:延迟加载或者懒加载(lazy load),也就是我们在需要的时候才去加载实例。

但是这样会存在一个隐患,就是当多个线程去执行调用这个实例的时候,可能会产生多个实例,例如我这里创建一个MyThread,主要是调用RecyleBin的获取实例方法,然后打印出这个实例的哈希值:

public class MyThread implements Runnable {
    @Override
    public void run() {
        RecyleBin instance = RecyleBin.getInstance();
        System.out.println(instance.hashCode());
    }
}

接着我们在测试类中:

	public static void main(String args[])
    {
       while(true)
       {
           new Thread(new MyThread()).start();
       }
    }

从打印输出中可以看到:

...
1780742980
1780742980
603764719
1780742980
1780742980
1780742980
...

发现哈希值有不一样的,这也是刚刚说的存在这样的隐患,假设当A、B两个线程进入:

	 if(instance==null)
     {
          instance = new RecyleBin();
     }

A线程判断instance不为空的时候,开始进入实例化,不过在这个时候,A还没来得及实例化,B线程就进来判断了,这时候B线程发现instance是空的,于是它也进入实例化。那么这样就会出现多个实例了,违背了我们之前说的单例原则的唯一性了。

解决方法:

如果你看过Java惹人爱的多线程,那么应该会想到用锁来解决这一问题,没错!就是这样:

 	//需要的时候才去加载实例
    public static RecyleBin getInstance()
    {
        if(instance==null)
        {
            synchronized (RecyleBin.class)
            {
                if(instance==null){

                    instance = new RecyleBin();
                }
            }
        }

        return instance;
    }

这里对instance进行了双重判断且加了一把锁,这样解决了线程安全问题:

如果如果A、B线程同时进入getInstance(),当两者都判断为null的时候,这时候它们遇到了synchronized锁,如果A先进入锁里面的代码,那么B只能先在外面等待,当A执行完之后,instance也被实例化了,这时候锁被释放,B进去的时候判断instance不为null,于是不实例化对象,直接返回。这样就实现了单例原则了!

单例设计模式的实现方式

从上面的例子中,以两种方式实现,一种是在类加载的时候就对对象进行实例化,一种是在需要的时候才创建对象,其实这两种都有各自的名字,第一种叫做:饿汉式,第二种叫做懒汉式

以代码提现就是这样的:

java单例模式中的饿汉式

/**
 * Created by wistbean on 2017/9/27.
 * 单例模式:饿汉式方式实现
 */
public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){}

    public static Singleton getInstance()
    {
        return INSTANCE;
    }

}

java单例模式中的懒汉式

/**
 * Created by wistbean on 2017/9/27.
 * 单例模式:懒汉式方式实现
 */
public class LazySingleton {

    private volatile static LazySingleton instance = null;

    private LazySingleton(){}

    public static LazySingleton getInstance()
    {
        if(instance == null)
        {
            synchronized (LazySingleton.class)
            {
                if(instance==null)
                {
                    instance = new LazySingleton();
                }
            }
        }

        return instance;
    }

}

java单例模式中的饿汉式与懒汉式的比较

饿汉式在类一加载就实例化了对象,这样就一直存在,比较占用资源,浪费时间,但是它不需要考虑线程安全问题就可以确保对象的唯一性,相对于懒汉式的方式来说获取速度稍微快一些。

懒汉式是在需要的时候才创建对象,不会一直存在占用着资源,不过它对线程方面需要添加线程锁来保证对象的唯一性,相对于饿汉式的方式来说影响了性能。

更好的单例实现方式

饿汉式速度快,懒汉式不会一直占用资源,但是它们又都有缺点,能不能对他们“取其精华去其糟粕”呢?

能!

那就是以静态内部类的方式,他有个名称叫:Initialization-on-demand holder idiom,它的实现方式是这样的:

/**
 * Created by wistbean on 2017/9/27.
 * 单例模式 以内部类的形式实现
 */
public class IodhiSigleton {

    private IodhiSigleton(){}

    private static class LazyHolder
    {
        private final static IodhiSigleton INSTANCE = new IodhiSigleton();
    }

    public static IodhiSigleton getInstance()
    {
        return LazyHolder.INSTANCE;
    }

}

当JVM加载IodhiSigleton的时候,内部类一开始不会被初始化,只有当java虚拟机(JVM)执行才会初始化,也就是当执行getInstance()的时候,jvm才会去执行LazyHolder并且初始化,初始化的时候会实例化一个外部的IodhiSigleton类。

因为这样的初始化阶段是依赖于java虚拟机(JVM)的语言规范 Java Language Specification (JLS),它确保的是类的初始化阶段是串行的,不是并发的。所以不需要线程锁synchronized。

这样的话它就不需要像饿汉式那样一开始就去实例对象占用资源,又做到了懒加载的方式,并且不需要添加线程锁就能确保线程安全,提高了性能。可以算是有了饿汉式和懒汉式的精华,但是去除了它们的缺点,so cool!

单例模式应用场景

  1. 对共享资源进行统一管理的时候,如系统的配置文件,系统的日志管理等。

  2. 当我们只允许一个对象实例来使用的时候,例如系统需要唯一的对象来管理资源,系统需要唯一的对象来生成序列单号。

相关文章

相关资料

written in java, 设计模式

软件工程中的GoF设计模式

今天来谈谈设计模式,软件模式和数据结构,算法,操作系统,网络,编程思想等可以算是程序员的“内功”,因为这些内容和编程语言无关,它们有点“道”感觉,就像老子说的那样:“道可道,非常道;名可名,非常名”。

设计模式

设计模式可以说是前人走过的路,走的多了,就有了他们总结出来的“套路”,也就是他们总结出了的一些设计方案,使我们可以写出更优雅的代码,灵活的代码,可重用的代码,可扩展的代码…

设计模式的由来

其实一开始,设计模式并不是从软件设计中研发出来的,而是在建筑中借鉴过来的!在79年的时候,有一位很牛逼的建筑师克里斯托佛·亚历山大(Christopher Alexander)编著了设计模式的一本书。

在87年的时候 肯特·贝克沃德·坎宁安 克里斯托佛·亚历山大(Christopher Alexander)的建筑领域的设计模式思想应用在了Smalltalk的图形应用接口(GUI)里边。

88年埃里希·伽玛把这种设计模式思想写为使用于软件开发的论文中。

89-91年James Coplien 利用相同的思想在C++中开发。

差不多在这个时候出现了“四人帮”(Gang of Four,简称GoF),他们分别是Erich Gamma ,Richard Helm,Ralph Johnson ,John Vlissides 。他们用23种设计模式合作出版了《设计模式:可复用面向对象软件的基础》,从而闻名整个软件领域,突破了软件的设计模式,所以现在Gof也会代指为他们出版的这本书。

Gof设计模式

Gof设计模式有23个,分为三类:创建型(如何创建对象),结构型(如何实现对象的组合),行为型(对象如何交互以及怎么分配职责)。

其中创建型有5个:

  • 单例模式 Singleton Pattern
  • 工厂方法模式 Factory Method Pattern
  • 抽象工厂模式 Abstract Factory Pattern
  • 原型模式 Prototype Pattern
  • 建造者模式 Builder Pattern

结构型有7个:

  • 适配器模式 Adapter Pattern
  • 桥接模式 Bridge Pattern
  • 组合模式 Composite Pattern
  • 装饰模式 Decorator Pattern
  • 外观模式 Façade Pattern
  • 享元模式 Flyweight Pattern
  • 代理模式 Proxy Pattern

行为型有11个:

  • 职责链模式 Chain of Responsibility Pattern
  • 命令模式 Command Pattern
  • 解释器模式 Interpreter Pattern
  • 迭代器模式 Iterator Pattern
  • 中介者模式 Mediator Pattern
  • 备忘录模式 Memento Pattern
  • 观察者模式 Observer Pattern
  • 状态模式 State Pattern
  • 策略模式 Strategy Pattern
  • 模板方法模式 Template Method Pattern
  • 访问者模式 Visitor Pattern

花些时间学习设计模式

拓宽自己的视野,站的更高,才会看得更远。 不管你是使用哪种面向对象的语言,如果一点设计模式都不懂的话,那么就有点low了。 设计模式也不是很神秘,揭开它的面纱,摸摸他的底,理解它的套路,到一种“阅片无数,心中自然无码”的感觉,灵活应用于我们的项目代码中,也许会有一种格外的爽,当然这需要一些时间去理解设计模式是什么,在什么时候应用什么模式,什么情况要使用,以及它的优缺点,能够应用它的实例,记住其中的关键代码。

接下来..

就从单例模式开始。

相关文章

相关资料

written in 设计模式

软件架构中的MVC模式

MVC 模式

MVC是一种软件的架构模式,也就是分成了三层,

  • M 模型层 (Model)
  • V 视图层 (View)
  • C 控制层 (Controller)

想象一下这样的一个场景,你发现今天非常热,室外的温度达到30好几,于是你拿起空调的遥控,按了一下 开/关 按钮,这个时候空调开启,你选择了 制冷 功能,这时候空调开始往室内运送冷气,不过你觉得温度还不够给力,于是你调到了16度,你觉得很爽,开始美美的睡上一觉!

那么,说这个和软件架构中的MVC模式有什么关系呢?

其实…

我们可以从这个场景中解释什么是MVC模式,当你拿起空调遥控的时候,看到遥控上有按钮供你选择,比如 「开/关」 ,「制冷」 ,「左右扫风」等, 还有温度,风力等的显示。 我们可以把遥控的这些直观的东西看成是View(视图)层。

而空调内部的机制,比如产生冷气的装置, 我们就可以看成是Model(模型)层

当你 按了一下 开/关 按钮来开启空调,你选择了 制冷 功能,调到了16度 这些输入信息转化成空调产生冷气的操作,可以用 控制层(Controller)来实现。

MVC

所谓视图(View)就是我们可以直观看到的,一个操作界面的样子,我们可以直观感受得到,看的见的。我们上网看到的网页里面的内容,app的界面,这都是视图(View)。

而模型(Model)是程序里面应有的功能,里面实现了算法,还有数据库的管理以及实现了具体数据操作等功能。

控制(Controller)则是负责转发请求,对请求进行处理,控制应用程序的流程,处理用户的行为和数据的改变,并作出响应。

它们三者之间互相独立,是一种模块化的架构思想。这样做的目的是使后续对程序的修改和扩展得到了简化,使程序的某一部分代码的重复利用成为一种可能。

这样的好处在于:

低耦合,MVC模式使得这三个模块互相独立,我们改变其中一个模块都不会影响到其它两个,使程序的复杂度得到了简化,程序结构更加直观易懂。

可复用,多个View可以共享一个Model,比如你在PC端上某网站看视频,看到一半想用手机看,这时候你会下载它对应的app进行观看视频,这就要求他们能够提供PC端的视频网站的界面和手机app上的界面给你使用。这时候他们只需要改变View层上的界面而已,对于Model层的响应用户请求和返回数据一点影响也没有。View只是负责将Model传来的数据格式化然后渲染到响应的界面即可。这样就能让业务逻辑和表示层分离,同一个Model可以给N个不同的View重用。大大的提高了代码的重用!

这种MVC的架构思想不仅可以用于软件架构上,生活上许多方面其实也可以用MVC模块化的方式去管理,肯定会大大的提高效率,只要你细心发现,从MVC的角度去架构,然后针对不同的层去优化,相信能有一个美好的未来,再也不用从快递员的手中接过你的“女朋友”了!

TAG:MVC模式低耦合可复用模型层ModelView视图控制层Controller

相关文章

相关资料

written in 设计模式

谈谈java中的面向对象编程思想

java对象

英语:Object-oriented programming,缩写:OOP;

我们开发的程序是给用户使用的,而我们的程序是使用对象来完成产生的。对象可以看成是对问题的描述的一种解决方案,它与问题空间之间一一映射。也就是将问题抽取成为对象集合

在java中以class关键字来定义类的对象,类包含了元素和功能,是一种引用类型,和Int、Float等类型是一样的道理,只不过Int、Float等这些基本类型是java内置了的,所以我们可以拿来即用,而我们自定义的对象需要我们进行实例化后使用。

java面向对象的服务提供者

我们可以将对象看作服务提供者,比如手机这个对象,我们可以用它来聊天,打开闪光灯,砸核桃等。

OOP 面向对象

当每个对象都有各自独特的服务的时候,我们在使用的时候就很方便高效了,我们知道要实现什么功能,就拿什么对象来使用,如果我们对多个对象的各个方面都“组合”的很好的话,那么对代码来说是高内聚的,我们都约定俗成开发程序高内聚是我们追求你之一。

java面向对象的封装

假设在合作开发的时候,有人负责创建类来提供服务,有人收集使用类来快速实现程序开发,我们把第一种人暂且叫类(服务)创建者,第二种人叫做使用服务(类)者。

那么在这种情况下,创建者对自己的类只需要暴露必需的功能给使用者就可以了,使用者无需关心创建者是怎么样实现类的细节,拿来用就好了。这样创建者会对比较“脆弱”的代码进行封装,使用者无法触及,防止使用者随意修改,减少了bug的产生,还有就是创建者对封装的这些代码修改不会对使用者造成影响,比如类创建者在一开始创建类的时候某功能写的简单了,后来为了提高性能,那么修改这部分代码,实际上对使用者是毫无影响的。

java用private关键字来限定只有类创建者和类内部方法可以使用,其它人都无法使用。

如果是使用public修饰的话就相当于完全暴露了,任何人都可以使用。

还有protected是让继承的子类可以使用,但是不能使用父类被private修饰的属性。

默认不指定修饰的时候,同一包下的其它成员都可以访问。

所以封装是有必要的,在这种分离的情况下,提高了安全性。

java对象组合

一个对象含有另一个对象,这就是组合。 比如手机这个对象含有相机对象,我们创建了相机对象,可以内置于手机对象中,这样做的好处是使用对象间灵活高效的。

OOP

代码提现:

public class Phone {

    private Camera camera;

}

java面向对象的继承

当你发现对象之间存在诸多相似性的时候,并不需要每个都创建独立的对象,可以找出他们的相同点作为基类(父类),进而在衍生出差异的类(子类),这样子父类就包含子类所有共享的行为特性了,子类就以不同的实现方式存在。

关于继承可以在Java 继承是什么龟查看。

java面向对象的多态

狗可以称为动物,猫可以称为动物。多态在于你可以不在意具体对象类型而使用它们共同的属性:

代码提现:

public class Animal {

    public void eat()
    {
        System.out.print("吃东西。。");
    }
}


public class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("吃骨头");
    }
}


public class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("吃鱼");
    }
}


public class Main {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        eatSome(dog);
        eatSome(cat);
    }

    static void eatSome(Animal animal)
    {
        animal.eat();
    }
}

输出:

吃骨头
吃鱼

在这里可以看到dog和cat向上转型为Animal。向上转型是安全的,向下转型却是危险的,例如将Animal转为它的子类,因为此刻Animal既不知道是Dog还是Cat,所以向下转型还需要专门指定对象,即强制转换

更多关于多态可以参考:Java 多态可以吃吗?

对象存储

因为java是在运行时才能确定对象的多少,所以有了动态存储对象的集合,集合提供了不同的接口和行为,它们之间效率不同,应用场景不同:Java 细数各种常见的集合

对象生命周期

对象的生成会占用内存资源的,所以需要销毁来释放内存资源,对象是在堆的内存池中动态创建的,java有一个专门的垃圾回收器,它知道对象在什么时候可以销毁,会自动的销毁从而释放资源且不会影响程序的正常运行。

我的相关文章

相关资料

written in java

配置tomcat使不同端口号不同域名访问不同项目位置

这么一个需求: 有不同域名指向一个ip,希望通过访问不同域名可以访问不同的项目,有些项目需要8080端口访问,有些项目需要80端口访问。

例如:

www.aaaa.com  --> webA
www.bbbb.com  --> webB  
localhost:8080/webC --> webC
localhost:80/webD --> webD 

接下来就通过tomcat的配置来实现这样的需求,首先打开tomcat目录下的conf/中的server.xml,在中可以看到原来是这样的:

	<Service name="Catalina">
  
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
       <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">

     <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>

这个是通过8080端口访问webapps下的项目的配置。

添加别的端口访问

那么如果我们需要添加一个80的端口,使得我们可以用8080访问也可以用80访问相关的项目,那么可以增加一个

<Service name="**Catalina1**">
  
<Connector port="**80**" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
   <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

<Engine name="**Catalina1**" defaultHost="localhost">

 <Realm className="org.apache.catalina.realm.LockOutRealm">
    <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
           resourceName="UserDatabase"/>
  </Realm>

  <Host name="www.aaa.com"  appBase="**webapps1**"
        unpackWARs="true" autoDeploy="true">
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
           prefix="localhost_access_log." suffix=".txt"
           pattern="%h %l %u %t &quot;%r&quot; %s %b" />
	<Context path="" docBase="/usr/local/tomcat/apache-tomcat-7.0.64/webapps1/aaa"/>
		   
  </Host>

</Engine>
 	</Service>

可以看到这里将 - Service name –> Catalina1 - Connector port –> 80 - Engine name –> Catalina1 - Host name –> 自己指定的域名,比如你想指定www.aaa.com访问aaa项目 - appBase –> webapps1 - docBase –> 项目的位置

配置好了之后,需要在tomcat的根目录下创建webapps1,在conf目录下创建Catalina1,配置好之后那么你就可以在 8080端口下访问webapp中的项目,还可以通过域名访问80端口中的aaa项目了。

多个域名访问不同的项目

我们刚刚配置了www.aaa.com且用80端口去访问aaa项目,如果现在我还想通过www.bbb.com去访问bbb项目呢,那么这时候就可以在这里面添加:

<Service name="Catalina1">
  
<Connector port="80" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
   <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

<Engine name="Catalina1" defaultHost="localhost">

 <Realm className="org.apache.catalina.realm.LockOutRealm">
    <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
           resourceName="UserDatabase"/>
  </Realm>

  <Host name="www.aaa.com"  appBase="webapps1"
        unpackWARs="true" autoDeploy="true">
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
           prefix="localhost_access_log." suffix=".txt"
           pattern="%h %l %u %t &quot;%r&quot; %s %b" />
	<Context path="" docBase="/usr/local/tomcat/apache-tomcat-7.0.64/webapps/aaa"/>
		   
  </Host>
  
  <Host name="www.bbb.com"  appBase="webapps1"
        unpackWARs="true" autoDeploy="true">
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
           prefix="localhost_access_log." suffix=".txt"
           pattern="%h %l %u %t &quot;%r&quot; %s %b" />
		   <Context path="" docBase="/usr/local/tomcat/apache-tomcat-7.0.64/webapps/bbb"/>
  </Host>
</Engine>
  	</Service>

可以看到我们添加了一个host,然后名称为www.bbb.com,docBase指向bbb的项目路径。这样就可以多域名访问了!

written in tomcat, tool

Linux的centos7系统安装与配置java和tomcat7

java安装配置

安装

  1. java下载页面获取jdk下载链接,下载linux的rpm文件到本地。
  2. 通过rz命令将jdk.rpm上传到Linux,关于rz使用:Linux与Windows文件互传

  3. rpm安装:rpm -ivh jdk-8u102-linux-i586.rpm,关于rpm使用:使用RPM方式安装Linux软件

配置环境:

  1. 1.创建:

    vi /etc/profile.d/java.sh

  2. 2.输入:

     JAVA_HOME=/usr/java/jdk1.8.0_144/
    
    PATH=$JAVA_HOME/bin:$PATH export PATH JAVA_HOME export CLASSPATH=.
  3. 3.保存退出后执行:

     chmod +x /etc/profile.d/java.sh
    
    source /etc/profile.d/java.sh
  4. 4.检查是否设置完:

     [root@iZ94z04vef5Z java]# javac
    
    Usage: javac where possible options include: -g Generate all debugging info -g:noneGenerate no debugging info -g:{lines,vars,source} Generate only some debugging info -nowarnGenerate no warnings -verbose Output messages about what the compiler is doing -deprecation Output source locations where deprecated APIs are used -classpath Specify where to find user class files and annotation processors

有以上信息说明配置完成java!

tomcat安装配置

安装

  1. 创建tomcat目录 mkdir -p /usr/local/tomcat
  2. 下载tomcat7,可以在tomcat.apache.org获取。
  3. 解压: tar zxvf apache-tomcat-7.0.64.tar.gz

配置

 vi /usr/local/tomcat/apache-tomcat-7.0.64/conf/server.xml

这里可以更改端口号,和项目访问位置等。

开启和关闭tomcat

开启:

/usr/local/tomcat/apache-tomcat-7.0.64/bin/startup.sh

关闭:

/usr/local/tomcat/apache-tomcat-7.0.64/bin/shutdown.sh

written in linux, tomcat

Linux的centos7系统安装与配置java和tomcat7

java安装配置

安装

  1. java下载页面获取jdk下载链接,下载linux的rpm文件到本地。
  2. 通过rz命令将jdk.rpm上传到Linux,关于rz使用:Linux与Windows文件互传

  3. rpm安装:rpm -ivh jdk-8u102-linux-i586.rpm,关于rpm使用:使用RPM方式安装Linux软件

配置环境:

  1. 1.创建:

    vi /etc/profile.d/java.sh

  2. 2.输入:

     JAVA_HOME=/usr/java/jdk1.8.0_144/
    
    PATH=$JAVA_HOME/bin:$PATH export PATH JAVA_HOME export CLASSPATH=.
  3. 3.保存退出后执行:

     chmod +x /etc/profile.d/java.sh
    
    source /etc/profile.d/java.sh
  4. 4.检查是否设置完:

    [root@iZ94z04vef5Z java]# javac

    Usage: javac where possible options include: -g Generate all debugging info -g:noneGenerate no debugging info -g:{lines,vars,source} Generate only some debugging info -nowarnGenerate no warnings -verbose Output messages about what the compiler is doing -deprecation Output source locations where deprecated APIs are used -classpath Specify where to find user class files and annotation processors

有以上信息说明配置完成java!

tomcat安装配置

安装

  1. 创建tomcat目录 mkdir -p /usr/local/tomcat
  2. 下载tomcat7,可以在tomcat.apache.org获取。
  3. 解压: tar zxvf apache-tomcat-7.0.64.tar.gz

配置

` vi /usr/local/tomcat/apache-tomcat-7.0.64/conf/server.xml`

这里可以更改端口号,和项目访问位置等。

开启和关闭tomcat

开启: /usr/local/tomcat/apache-tomcat-7.0.64/bin/startup.sh 关闭: /usr/local/tomcat/apache-tomcat-7.0.64/bin/shutdown.sh

written in linux

Linux的centos7系统安装MySQL数据库软件与配置授权远程登录

安装

  1. yum install mysql

  2. yum install mysql-devel

  3. wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum install mysql-community-server

检查是否安装成功

  1. 重启mysql服务: service mysqld restart
  2. 登入mysql(刚安装完是没有密码的): mysql -u root

如果显示以下信息说明安装完成:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.6.37 MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

设置数据库密码

设置密码为root:

set password for 'root'@'localhost' =password('password');

下次登录mysql时需要密码:

[root@wistbean ~]# mysql -u root -p
Enter password: 

配置字符编码:

vi /etc/my.cnf

添加:

[mysql]
default-character-set =utf8

配置远程连接:

 grant all privileges on *.* to root@'%'identified by 'password';

意思是通过root账号和root密码可以使用所有的MySQL权限。这样子就可以通过远程登录这个MySQL了!

written in linux

使用yum的安装方式安装Linux软件

通过

使用源码方式安装Linux软件Git

使用RPM方式安装Linux软件

中我们已经对Linux软件的源码安装方式和RPM安装方式有了了解,本篇文章要写的是我们Linux经常会用到的yum安装方式。

我们已经知道RPM的安装方式依赖性太强了,我们要安装某个软件的时候经常出现依赖问题,我们得一步一步的去先安装依赖的软件,才能安装我们想要安装的软件,这样对于我们来说太繁琐了,于是yum就是来解决我们这问题的,它会自动去下载我们所需要的依赖,对于我们来说,非常方便高效了!

那么首先我们要检查自己的Linux是否安装了yum:

rpm -qa|grep yum

如果有显示:

yum-plugin-fastestmirror-1.1.31-40.el7.noarch
yum-metadata-parser-1.1.4-10.el7.x86_64
yum-3.4.3-150.el7.centos.noarch 说明我们的yum已经安装,如果什么都没有输出,那么你就需要去安装yum了,至于这么安装yum,相信你看完[使用RPM方式安装Linux软件](http://www.wistbean.com/blog/2017/08/08/rpm-insatll/ "使用RPM方式安装Linux软件")这篇文章会很快安装完yum。

yum安装nginx

接下来通过用yum来安装nginx为例子,介绍yum:

yum -y install nginx

...
	
Installed:
  nginx.x86_64 1:1.10.2-1.el7                                                                                                          

  Dependency Installed:
  fontconfig.x86_64 0:2.10.95-10.el7                                    fontpackages-filesystem.noarch 0:1.44-8.el7                    
  gd.x86_64 0:2.0.35-26.el7                                             gperftools-libs.x86_64 0:2.4-8.el7                             
  libX11.x86_64 0:1.6.3-3.el7                                           libX11-common.noarch 0:1.6.3-3.el7                             
  libXau.x86_64 0:1.0.8-2.1.el7                                         libXpm.x86_64 0:3.5.11-3.el7                                   
  libjpeg-turbo.x86_64 0:1.2.90-5.el7                                   libpng.x86_64 2:1.5.13-7.el7_2                                 
  libunwind.x86_64 2:1.1-5.el7_2.2                                      libxcb.x86_64 0:1.11-4.el7                                     
  libxslt.x86_64 0:1.1.28-5.el7                                         nginx-all-modules.noarch 1:1.10.2-1.el7                        
  nginx-filesystem.noarch 1:1.10.2-1.el7                                nginx-mod-http-geoip.x86_64 1:1.10.2-1.el7                     
  nginx-mod-http-image-filter.x86_64 1:1.10.2-1.el7                     nginx-mod-http-perl.x86_64 1:1.10.2-1.el7                      
  nginx-mod-http-xslt-filter.x86_64 1:1.10.2-1.el7                      nginx-mod-mail.x86_64 1:1.10.2-1.el7                           
  nginx-mod-stream.x86_64 1:1.10.2-1.el7                               

Complete!

可以看到yum的安装很方便,直接帮我们把依赖的也安装好了。

删除安装包

yum remove nginx

remove

可以看到依赖的也一起被删除了。

列出安装的信息

[root@wistbean ~]# yum list nginx
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * epel: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Installed Packages
nginx.x86_64                                                    1:1.10.2-1.el7               @epe

更新安装包

检查更新
yum check-update nginx
更新
yum update nginx

written in linux

使用RPM方式安装Linux软件

通过使用源码方式安装Linux软件Git中我们已经会以源码方式安装Linux软件了,本文继续对Linux软件的安装进行介绍。

什么是RPM

RPM就是一个软件安装包,他将源码进行编译,封装,打包成了一个安装包,以”.rpm”结尾。每个RPM文件里面都包含了可执行文件和运行可执行文件所需的其它文件。

所以呢,RPM安装方式就很简单了,就是检查一下安装环境然后对其进行解压。不过rpm方式的安装依赖性很强,比如你需要安装一个A软件,但是这个A软件还需要Linux有B软件和C软件的依赖,那么我们还需要先安装B软件和C软件。

RPM的封装格式

RPM的封装格式有两种,一种是RPM,一种是SRPM! SRPM也是RPM的一种,不过SRPM包含了编译时的编译文件和编译的指定参数,所以在使用SRPM的时候还需要对它进行编译。

RPM文件的后缀是: xxx.rpm。 SPRM文件的后缀是: xxx.src.rpm。

RPM文件安装

rpm的安装通常使用如下命令

rpm -ivh xxx.rpm

SPRM的安装方式:

1.rpm -i xxx.src.rpm
2.cd /root/rpmbuild/SPECS
3.rpmbuild-bb xxx.specs
此时在/root/rpmbuild/RPMS/x86_64会生成rpm包,对其进行安装:
4.rpm -ivh xxx.rpm

通常在安装一个rpm的时候不是一步到位的,都会出现依赖问题,也就是说你在安装这个rpm的时候,它可能还需要别的库文件,那么你得先安装这些依赖文件,才能安装你想安装的rpm。

查询rpm安装包

rpm -q [辅助选项] 安装包名称

例如 查询安装包中有什么内容:

rpm -qpi nginx-1.13.4-1.el6.ngx.x86_64.rpm 

Name        : nginx
Version     : 1.13.4
Release     : 1.el6.ngx
Architecture: x86_64
Install Date: (not installed)
Group       : System Environment/Daemons
Size        : 2613962
License     : 2-clause BSD-like license
Signature   : RSA/SHA1, Wed 09 Aug 2017 01:52:52 AM CST, Key ID abf5bd827bd9bf62
Source RPM  : nginx-1.13.4-1.el6.ngx.src.rpm
Build Date  : Wed 09 Aug 2017 01:13:32 AM CST
Build Host  : centos6-amd64-builder-builder.gnt.nginx.com
Relocations : (not relocatable)
Vendor      : Nginx, Inc.
URL         : http://nginx.org/
Summary     : High performance web server
Description :
nginx [engine x] is an HTTP and reverse proxy server, as well as
a mail proxy server.

检查rpm安装包

对rpm安装包进行检查,主要检查我们下载下来的软件包和官方发布的软件包是不是一样,会不会被篡改文件的大小,权限,MD5值等。

对未安装的软件包进行检查:

rpm -K xxx.rpm

对已经安装的软件包进行检查:

rpm -V xxx.rpm

更新rpm软件包

rpm -U 要更新的rpm

删除rpm

rpm -e 要删除的rpm

written in linux

Java对微信网页授权登录的实现

开发微信网页的童鞋应该都会涉及到微信授权登录了,微信官方文档对微信使用的步骤已经很明确的进行了说明了:

1 第一步:用户同意授权,获取code

2 第二步:通过code换取网页授权access_token

3 第三步:刷新access_token(如果需要)

4 第四步:拉取用户信息(需scope为 snsapi_userinfo)

5 附:检验授权凭证(access_token)是否有效

我们主要是用java来对微信用户进行授权的实现,获取用户的信息保存到数据库中,之前的做法是在将授权页面交给前端,让前端获取code然后请求后端接口获取openid来获取用户信息,那样子的做法不是很好,因为对前端来说授权页面可能不仅仅是首页,而且还要与后端做多一步的数据对接,很是麻烦,所以现在是用后端直接授权获取用户信息然后再重定向到前端的页面。

具体操作如下:

首先创建一个Servlet:

public class wechatOAuthServlet extends HttpServlet {
	 
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//TODO
    }
}

这里用wechatOAuthServlet继承HttpServlet并重写doGet和doPost方法,doGet直接调用doPost,所以我们对网页授权的具体操作在doPost中操作!

这个wechatOAuthServlet在web.xml中设置:

	<servlet>
        <servlet-name>getWechatUser</servlet-name>
        <servlet-class>com.wistbean.action.app.wechat.wechatOAuthServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>getWechatUser</servlet-name>
        <url-pattern>/app/wechat/wechatOAuthServlet.do</url-pattern>
    </servlet-mapping>

很简单,当客户端发起/app/wechat/wechatOAuthServlet.do请求时就调用我们的wechatOAuthServlet这个类的doPost方法。

我们对获取微信code的回调url也设置是这个/app/wechat/wechatOAuthServlet.do的urlEncode:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60 &redirect_uri=这里是urlEncode后的链接(www.wistbean.com/app/wechat/wechatOAuthServlet.do)&response_type= code&scope=snsapi_userinfo&state=STATE#wechat_redirect

doPost方法如下代码解释:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		//获取客户端请求是否有code参数,如果有,说明用户已经同意授权,微信请求了我们的redirect_uri并携带了code参数,如果没有,说明用户还没有同意授权,仅仅是访问了我们的url。
        String code = request.getParameter("code");
        if(code == null || code.equals(""))
        {
			//这里是没有code的情况,那么将我们的url进行encode,然后根据微信文档将参数传进去,然后将链接回调到我们设置好的redierctURL,这时候用户就会看到微信登录授权页面,如果此时用户点击确认登录,那么微信就会请求我们这个uri并携带code参数。
            String wx_redirect_uri = URLEncoder.encode("http://www.wistbean.com/pro/app/wechat/getWechatUserServelet.do");
            String redierctURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+ ConfigUtil.API_ID+"&redirect_uri="+wx_redirect_uri+"&response_type=" +
                    "code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
            response.sendRedirect(redierctURL);
        }else
        {

			//用户同意了授权,我们获得了code参数,那么就可以根据code获取用户信息了。然后保存或更新用户的信息,返回用户的id加到cookie给前端,将连接回调到前端的页面去。
            AppFunctionDao dao = (AppFunctionDao) SpringUtil.getBean("appFunctionDaoImpl");
            String sjson = "{name:'memberlogin', data:{code:'" + code + "'}}";
            JSONObject json = JSONObject.fromObject(sjson);
            String data = dao.updateData(json);//保存完用户信息后返回用户id
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setCharacterEncoding("UTF-8");
            Cookie cookie = new Cookie("userinfo",data);//添加cookie传入用户id给前端
            cookie.setMaxAge(24*60*60*15);
            cookie.setDomain(".wisteban.com");
            cookie.setPath("/");
            response.addCookie(cookie);
            response.sendRedirect("http://www.wistbaen.com/pro-wx/");
        }


    }

这样子前端就可以通过cookie判断用户是否登录了,若需要用户登录那么前端只要请求/app/wechat/wechatOAuthServlet.do这个链接就可以了!

written in java

使用源码方式安装Linux软件Git

Linux常见的软件安装方式有:源码安装、RPM安装、yum安装、二进制软件安装,本篇文章先对源码安装方式进行学习使用。

我们使用的Linux是开源的系统,所以在Linux里边运行的软件大多都是开源的,比如Java,Tomcat,Apache,Git等等,我们使用源码安装方式的好处就是我们直接对软件的源码下载下来,然后可以根据自己的需要进行安装相对应的模块,对于我们不需要的模块可以不用安装,而且我们可以指定软件安装的目录,当我们要卸载的时候直接将软件所在的目录删掉就可以了。简单又方便。

源码安装方式的步骤如下:

  1. 下载解压源码 (Linux软件一般源码是压缩的,其后缀大多是 tar.gz)
  2. 分析安装平台的环境
  3. 编译安装

接下来就以在Linux上用源码方式安装Git为例子,理解并学会使用源码安装方式来安装软件:

git的源码可以在这里获取:
https://www.kernel.org/pub/software/scm/git/

首先我们进入目录「/usr/local/」,然后创建git目录,接着用wget下载git源码压缩包,这里使用的是git-2.9.4.tar.gz这个版本:

[root@iz8vb4poq27pwkhxgnysthz /]# cd /usr/local/
[root@iz8vb4poq27pwkhxgnysthz local]# mkdir git
[root@iz8vb4poq27pwkhxgnysthz local]# cd git/
[root@iz8vb4poq27pwkhxgnysthz git]# wget https://www.kernel.org/pub/software/scm/git/git-2.9.4.tar.gz
--2017-07-30 20:44:30--  https://www.kernel.org/pub/software/scm/git/git-2.9.4.tar.gz
Resolving www.kernel.org (www.kernel.org)... 147.75.110.187, 2604:1380:3000:3500::3
Connecting to www.kernel.org (www.kernel.org)|147.75.110.187|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5921866 (5.6M) [application/x-gzip]
Saving to: ‘git-2.9.4.tar.gz’

100%[=============================================================================================>] 5,921,866   22.0KB/s   in 4m 46s 

2017-07-30 20:49:17 (20.2 KB/s) - ‘git-2.9.4.tar.gz’ saved [5921866/5921866]

接下来我们对下载下来的git-2.9.4.tar.gz进行解压:

tar -zxvf git-2.9.4.tar.gz

当我们解压完后就会在git目录下生成git-2.9.4目录,这里边含有README和configure文件,README文件是对这个软件的安装说明,而configure就是用来分析我们平台环境,看看当前系统是不是有安装软件所需要的文件和工具,还可以指定安装目录和我们所需要的安装模块。

./configure

我们发现我们的系统没毛病之后,就可以对源码进行编译了。

[root@iz8vb4poq27pwkhxgnysthz git-2.9.4]# make
    CC credential-store.o
In file included from credential-store.c:1:0:
cache.h:40:18: fatal error: zlib.h: No such file or directory
 #include <zlib.h>
                  ^
compilation terminated.
make: *** [credential-store.o] Error 1

当执行make编译的时候出现错误,需要安装对应的工具,这里先用yum安装,之后会讲到哈:

# yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel
# yum install  gcc perl-ExtUtils-MakeMaker

然后我们就可以使用make来编译了(这里指定编译到):

[root@iz8vb4poq27pwkhxgnysthz git-2.9.4]# make
[root@iz8vb4poq27pwkhxgnysthz git-2.9.4]# make install

接下来我们对git设置环境变量:

[root@iz8vb4poq27pwkhxgnysthz bin]# echo "export PATH=$PATH:/usr/local/git/git-2.9.4/bin" >> /etc/bashrc
[root@iz8vb4poq27pwkhxgnysthz bin]# source /etc/bashrc

我们检验下是否安装成功:

[root@iz8vb4poq27pwkhxgnysthz /]# git --version
git version 2.9.4

可以看到我们安装成功,版本号为2.9.4。

至此,我们使用源码方式成功安装了Linux软件git啦!

written in linux

学会使用Linux的文本编辑工具vi

什么是vi?

vi是Linux的文本编辑器,vi编辑器有两种工作方式,那就是命令模式文本模式,这两种模式都可以进行切换,在命令模式下,我们输入的字符就会被解释为vi命令,用来执行我们想要操作文本内容的方式,比如替换搜索,插入等等在接下来会讲到,当我们切换到文本模式的时候,那么我们输入的字符就是以文本本身的内容来作为文件的接收了。正是vi有了命令模式和文本模式,使vi编辑器非常强大,而且它占用的资源很小,所以vi虽然短小,但且强悍有力。不像某些人,既短小,又无力!

怎么使用vi?

内容编辑和保存

接下来我就用一个例子,把所有vi常用的使用命令都使用起来。从这个例子中看完会发现,原来vi这么牛逼,而且这么好用…

现在,我要打开或创建一个文本myViFile,因为我现在还没有myViFile这个文件,所以我就用vi来创建一个myViFile文件:

vi myViFile

当我按下回车的时候,就进入vi的命令模式里面去了:

vi myViFile

可以看到现在里面什么东西都没有,这时我来往里写点东西吧,那么就用插入命令来切换到文本模式才能往文件里面写内容了,按一下 i,就是切换到文本模式,可以看到左下角变成–INSERT–了。

然后我就输入一段文字:

vi myViFile

假设现在我输入完了,想要保存一下,这时候就可以用 Esc 切换到命令模式(当你按下Esc的时候会发现左下角的 INSERT 消失了,所以这个时候vi是属于命令模式的),然后输入 「:w」 然后按回车来保存内容。

如果这个时候想退出vi,那么我们可以输入 「:q」退出。

当然了,聪明的你肯定想着说能不能直接保存并退出了,那就用 「:wq」就可以对我们刚刚输入的内容保存起来并且退出vi了。

现在我们重新打开myViFile文件吧: vi myViFile。然后输入命令「i」进入文本模式,我们如果不小心把freestyle删掉了,想要撤回怎么办?像这样:

vi myViFile

那就按「Esc」切换到命令模式,然后输入「u」就可以撤回啦!

我们现在的文本只是一句话,我们想要另起一行继续书写,怎么办呢? 那么就键入命令「o」,光标就会跳到下一行开始让我们书写啦:

vi myViFile

接下来要说的就是我们常常会用到的复制粘贴了,这两个命令分别对应于 「yy/yw」(yy复制整行,yw复制光标所在的单词) 和 「p」,比如现在我复制最后一行,然后粘贴,那么在最后一行按Esc切换为命令模式,然后输入yy,再按p,每按一次p就粘贴一次了:

vi myViFile

yw复制单词和yy同理,这里就不演示了 - -

我们还可以对一整行删除,也可以对光标所在的单词删除,命令分别对应于「dd」和「dw」。

光标移动

好了 现在我们的文本已经有些内容了,如果我们要对文本里面的内容进行编辑操作,怎么样能高效的使用呢?那么我们对一些常用的光标移动命令要会使用。

  • 「h」「j」「k」「l」 分别是光标向左、上、下、右移动一个字符。
  • 「H」「L」分别是光标移动到最顶、末一行。
  • 「w」「b」 分别是 向右、左移动一个字。
  • 「n+」「n-」分别是向下、上移动n行数。

ok..光标移动就以上这几个就够用的了。

搜索替换

当然了,文本编辑器怎么能没有搜索替换呢?那么接下来就对搜索替换进行操作。

我们继续用刚刚创建的myViFile文件,我们复制第一行的内容到后面粘贴一些,用来演示我们的搜索替换,现在我们的文件内容是这样的:

vi myViFile

现在我们要向下搜索 freestyle这个单词,那么就可以用 「?freestyle」:

vi myViFile

然后我们按「n」就可以跳到每一个有freestyle的地方了。

接下来我们对内容显示行号「:set nu」:

vi myViFile

如果想把我们光标的当前行的freestyle改为heiheihei就可以这样「:s/freesyle/heiheihei/g」

vi myViFile

可以看到第一行的freestyle就被替换为heiheihei了。

如果我们想替换整个文本的freestyle为heiheihei的话就可以这样「:1,23s/freestyle/heiheihei/g」,表示从第1行到23行的所有freestyle都换成heiheihei。

written in linux

Linux的netstat命令常用实例

netstat命令是用来显示网络连接,运行端口,和路由表等所有网络套接字连接情况,可以查看监听状态,比如你开了tomcat的后你可以看看8080端口有没打开,在哪里被使用到,这都可以用强大netstat命令来查看使用。

netstat的使用

netstat [选项]

具体选项可以用netstat -h 查看:

-a (all)显示所有选项,默认不显示LISTEN相关 -t (tcp)仅显示tcp相关选项 -u (udp)仅显示udp相关选项 -n 拒绝显示别名,能显示数字的全部转化成数字。 -l 仅列出有在 Listen (监听) 的服務状态

-p 显示建立相关链接的程序名 -r 显示路由信息,路由表 -e 显示扩展信息,例如uid等 -s 按各个协议进行统计 -c 每隔一个固定时间,执行该netstat命令。

在这里列举netstat常用的使用实例: ##显示当前系统的所有有效的TCP连接:

[root@wistbean ~]# netstat -ant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:8005          0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:8009            0.0.0.0:*               LISTEN     
tcp        0      0 192.168.22.212:42866    45.59.77.61:3306        TIME_WAIT  
tcp        0     96 192.168.22.212:22       116.22.163.220:4442   ESTABLISHED
tcp        0      0 192.168.22.212:42862    45.59.77.61:3306        TIME_WAIT  
tcp        0      0 192.168.22.212:37974    100.100.25.3:80       ESTABLISHED
tcp      401      0 192.168.22.212:36626    106.11.68.13:80        CLOSE_WAIT 
tcp        0      0 192.168.22.212:42868    45.59.77.61:3306      ESTABLISHED
tcp        0      0 192.168.22.212:42864    45.59.77.61:3306        TIME_WAIT  
tcp      401      0 192.168.22.212:32908    106.11.68.13:80        CLOSE_WAIT 
tcp6       0      0 :::3306                 :::*                    LISTEN     
tcp6       0      0 192.168.22.212:3306     116.22.163.220:3187   ESTABLISHED
tcp6       0      0 192.168.22.212:3306     45.59.77.61:42868     ESTABLISHED
tcp6       0      0 192.168.22.212:3306     116.22.163.220:4676   ESTABLISHED
tcp6       0      0 192.168.22.212:3306     116.22.163.220:3371   ESTABLISHED
tcp6       0      0 192.168.22.212:3306     116.22.163.220:4992   ESTABLISHED
tcp6       0      0 192.168.22.212:3306     116.22.163.220:4767   ESTABLISHED
tcp6       0      0 192.168.22.212:3306     116.22.163.220:4680   ESTABLISHED

对以上的输出信息进行解释:

  • Proto 就是连接协议的种类,主要有tcp和udp。可以看到我们这里的连接协议类型是tcp。
  • Recv-Q 接收队列
  • Send-Q 发送队列(由远端主机发送过来的字节数)
  • Local Address 本地ip地址
  • Foreign Address 远端主机ip地址
  • State 显示状态 有以下几种
    • LISTEN 服务监听
    • TIME_WAIT 连接已经中断,但是套接字还在等待结束中
    • ESTABLISHED 连接成功
    • CLOSE_WAIT 被动关闭

显示已经开启的网络连接和对应的端口

[root@wistbean ~]# netstat -tlnpu
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      16823/java          
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      2385/sshd           
tcp        0      0 127.0.0.1:8005          0.0.0.0:*               LISTEN      16823/java          
tcp        0      0 0.0.0.0:8009            0.0.0.0:*               LISTEN      16823/java          
tcp6       0      0 :::3306                 :::*                    LISTEN      3728/mysqld         
udp        0      0 172.26.209.127:123      0.0.0.0:*                           886/ntpd            
udp        0      0 127.0.0.1:123           0.0.0.0:*                           886/ntpd            
udp        0      0 0.0.0.0:123             0.0.0.0:*                           886/ntpd            
udp6       0      0 :::123                  :::*                                886/ntpd    

可以看到开放了80,22,8009,123端口,可以看到端口对应的进程PID和名字,这样子我们就可以对应进行处理了。

written in linux

Linux系统之间的文件互传

让一个Linux系统与另一个Linux系统相互传输数据

有时候我们可能需要在一个Linux系统中与另一个Linux之间传输文件,那么这个时候我们就可以用到「scp」命令,scp是基于ssh协议的,保证了我们传输文件的安全性!

scp命令的使用:

从远程Linux中获取文件到我们本地:
scp 远程用户名称@ip地址:文件地址  本地路径

从本地Linux上传文件到远程Linux:
scp 本地文件路径 远程用户名称@ip地址:文件存放的地址

举个例子

1.从本地获取另一个Linux的一个文件

scp root@192.168.12.55:/usr/local/web/index.html /usr/local/
root@192.168.12.55's password: //这里输入另一个Linux的root密码
index.html			100%  13kb	12.6kb/s  00:00

这样子我们就获取到了ip地址为192.168.12.55的Linux的index.html文件 到我们的 /usr/local/下了。

2.我们上传一个文件到另一个Linux上

scp /usr/local/index.html root@192.168.12.55:/tmp
root@192.168.12.55's password: //这里输入另一个Linux的root密码
index.html			100%  13kb	12.6kb/s  00:00

这样,我们就把本地的文件传输到另一个Linux的/tmp目录下了!

written in linux

Linux的ifconfig命令

ifconfig

ifconfig需要在root下才能使用,它是用来配置网络和显示当前网络接口状态的命令,它的使用使这样子的:

ifconfig [选项] [interface] [inet|up|down|netmask|addr|broadcast]

[选项]

  • a 显示所有网络接口信息
  • s 仅显示每个接口的摘要数据
  • v 如果某个接口有错误就会返回相关的错误信息

[interface]: 网络的接口名字,如eth0,eth1,lo,这个选项是可选的,如果你没写这个选项的话,那就会显示所有网络接口的信息,如果你指定了就会显示你指定的网络接口的信息。

  • up 激活一个你指定的网络接口
  • down 使你指定的网络接口失效
  • netmask 为你指定的网络接口添加子网掩码
  • addr 为你指定的网络接口设置ip地址
  • brodcast 为你指定的网络接口设置广播地址

使用实例

我们来显示所有的网络接口

[root@wistbean ~]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.12.111  netmask 255.255.240.0  broadcast 192.168.12.255
        ether 00:16:3e:00:4e:31  txqueuelen 1000  (Ethernet)
        RX packets 3098106  bytes 1264308026 (1.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2513278  bytes 1230432102 (1.1 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1  (Local Loopback)
        RX packets 169114  bytes 12443628 (11.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 169114  bytes 12443628 (11.8 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0	

可以看到有两个网络接口信息,我们来看看这具体是什么意思,首先看第一行

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
  • UP 代表这个网卡是开启状态
  • RUNNING 代表这个网卡是处于网络连接状态
  • MULTICAST 代表这个网卡是支持组播的
  • mtu 1500 代表这个网卡最大的单元传输为1500字节

接下来我们看第二行

 inet 192.168.12.111  netmask 255.255.240.0  broadcast 192.168.12.255
  • inet 192.168.12.111 网卡的ip地址为192.168.12.111
  • netmask 255.255.240.0 网卡的子网掩码为255.255.240.0
  • broadcast 192.168.12.255 网卡的广播地址为192.168.12.255

第三行

ether 00:16:3e:00:4e:31  txqueuelen 1000  (Ethernet)
  • ether 00:16:3e:00:4e:31 : 网卡的MAC的地址 00:16:3e:00:4e:31
  • Ethernet 连接类型为以太网

第四、五行

 RX packets 3098106  bytes 1264308026 (1.1 GiB)
 RX errors 0  dropped 0  overruns 0  frame 0
  • RX packets 网卡接收数据包的的信息
  • RX errors 网卡接收数据时错误的信息

最后两行

 TX packets 169114  bytes 12443628 (11.8 MiB)
 TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • TX packets 网卡发送数据包的信息
  • TX errors 网卡发送数据是错误的信息

给网卡配置ip地址

[root@wistbean ~]# ifconfig eth0 192.168.12.123 netmask 255.255.240.0 ##修改网卡的MAC地址

[root@wistbean ~]# ifconfig eth0 ether 00:16:3e:00:4e:30

禁用网卡

[root@wistbean ~]# ifconfig eth0 down

开启网卡

[root@wistbean ~]# ifconfig eth0 up

written in linux

12 Linux 压缩与解压缩常用命令行

这一篇对文件的压缩和解压缩常用命令进行学习。

zip

作用: 对文件或者目录进行压缩,生成“.zip”的后缀压缩包。

zip [选项] 压缩文件名 需要压缩的文件

举例: 将 /tmp 下的文件进行压缩,设置压缩级别为9,然后保存到 /opt/tmp.zip

zip -9r /opt/tmp.zip /tmp

这时候opt下就会有压缩文件tmp.zip了。

如果这时候tmp下的文件修改了,可以将文件更新到zip那边去:

zip -ru /opt/tmp.zip /tmp

unzip

作用 : 解压文件。

unzip [选项] 压缩文件名 -d 解压到的目录名

举例 : 将刚刚的 /opt/tmp.zip 解压到 /tmp 下,并且含相同的文件进行覆盖。

unzip -o /opt/tmp.zip -d /tmp/

gzip/gunzip

作用 : 对一般文件进行压缩/解压,不对目录进行压缩,如果指定的是目录,也是将目录下的文件进行压缩,压缩后的文件扩展名为 “.gz”,因为gunzip是gzip的硬链接,所以用gzip就可以进行压缩和解压操作。

gzip [选项] 压缩(解压)的文件名

举例: 对 /opt/tmp.zip 用gzip进行压缩,并显示压缩比。然后再对其解压:

gzip

bzip2/bunzip2

作用: 类似于gzip/gunzip,压缩后的扩展名为“.bz2”,bunzip2是bzip2的符号链接,所以用bzip2就可以进行压缩和解压操作。

bzip2 [选项] 压缩或解压的文件

这个命令和gzip差不多,就不举例了,对于选项不清楚的可以用man查看: man bzip2

tar

Linux经常使用的命令,用来对文件归档打包,但是不对文件进行压缩。

tar [选项] 目录或者文件名

举例 : 将 /tmp 下的文件进行打包,并且放到opt下:

tar -cvf /opt/tmp.tar /tmp

将 /tmp下的文件打包并且用gzip压缩:

tar -zcvf /opt/tmp.tar.gz /tmp

查看 /opt/tmp.tar.gz的内容信息:

tar -ztvf opt/tmp.tar.gz

在opt下创建一个doc目录,然后将tmp.tar.gz解压到doc目录下:

mkdir /opt/doc
cd /opt/doc
tar -zxvf /opt/tmp.tar.gz

written in linux

11 Linux文件管理与编辑常用命令行

这一篇章对文件的管理命令行进行学习,我们在操作系统的时候,经常会对文件进行操作,所以对文件的操作命令学习与使用尤为重要。

mkdir

作用:创建文件目录。

mkdir [选项] 目录名

对命令的选项不了解的可以用 man 命令进行查看。

举例: 在home目录下创建usera,同时在usera下创建userb:

mkdir -p /home/usera/userb

more

作用: 显示较长文本,比如文本过长,就使用more来分屏显示,按enter可以显示满屏后的下一行,按住空格键可以显示下一屏。

more [选项] 文件名

举例: 显示日志文件,并且每屏显示10行,同时清除屏幕:

more -c -10 /var/log/boot.log

cat

作用:显示文件内容到标准输出,还可以连接合并文件。

cat [选项] 文件名

cat 文件1 文件2 > 文件3

举例:

显示boot.log文件,并且制表符显示为”^I”,并且编号:

cat -Ab /var/log/boot.log

将 filea.txt fileb.txt 合并到filec.txt上

cat filea.txt	fileb.txt	>	filec.txt

touch

作用: 指定文件访问时间和修改时间,如果文件不存在就创建文件

touch [选项] 设定的时间 文件

举例(#后面为注释):

touch test.txt #创建文件
ls -l #显示文件创建时间
-rw-r--r-- 1 root root    0 Apr 25 15:15 test.txt
touch -d "20170101 01:29" test.txt # 将文件创建时间显示为20170101 01:29
ls -l #显示文件创建时间
-rw-r--r-- 1 root root    0 Jan  1 01:29 test.txt

diff

作用: 比较文件间的差异

diff [选项] 文件1 文件2

举例 : 比较 test.txt 和 test2.txt 的差异:

[root@myserver tmp]# cat test.txt 
hi~ this is a test file .
this is a line 2
[root@myserver tmp]# cat test2.txt 
hello! this is a test2 file!
[root@myserver tmp]# diff test.txt test2.txt 
1,2c1
< hi~ this is a test file .
< this is a line 2
---
> hello! this is a test2 file!

grep

作用: 过滤,根据字符串对文件的每一行进行搜索,如果找到该字符串就输出该行的内容。

grep [选项] 搜索的字符串 文件

举例: 列出 test.txt 中 含有 is 的行

[root@myserver tmp]# grep -in is test.txt
1:hi~ this is a test file .
2:this is a line 2

这里的 -i 搜索时忽略大小写 -n 搜索结果中输出行数

rm

作用: 删除某个目录和该目录下的所有文件

rm [选项] 文件或者目录

举例: 删除test.txt

rm -i test.txt
rm: remove regular file `test.txt'? y > 注意: 要慎用 rm -rf ,这是强制删除且不给提示是否删除,如果文件被删除了就恢复不了,因为Linux没有回收站的。

ln

作用: 为文件或者目录之间创建链接。链接分为硬链接字符链接两种

硬链接: 在Linux系统中的所有文件类型都会分配一个inode号,多个文件可以指向同一个inode,这样的作用是可以让一个文件拥有多个有效路径,防止误删除。只有当最后一个链接被删除,文件才会被真正的删除。

字符链接: 也叫软链接,就像windows系统中的快捷方式,是指向一个真正的文件或者目录位置的连接。

ln [选项] 源文件 目标链接名

举例: 将t1.txt 硬链接到t2.txt,并且备份t2.txt:

ln

file

作用: 显示文件的类型。

file [选项] 文件名

举例:

file

cp

作用: 复制

cp [选项] 源文件或目录 目标文件或目录

举例:将当前目录下的所有文件复制到 /tmp 下

cp ./* /tmp

find

作用: 在指定路径下查找指定的文件。

find 路径 [选项] [-print -exec -ok 命令 {} \;]

  • -print 将结果输出到标准输出
  • -exec 命令 {} \; 对搜索出来的文件执行命令
  • -ok 命令 {} \; 对搜索出来的文件执行命令,在执行命令之前会询问是否执行。

举例: 查找 /tmp 目录下t1的txt文件进行删除:

find

mv

作用 : 将文件或目录移动到另一个文件或目录。

mv [选项] 源文件或者目录 目标文件或者目录

举例:在同一目录下创建 document 目录 和 doc.txt 文件, 然后将doc.txt 文件移动到 document目录中。

mv

split

作用: 分割文件

split [选项] [inputfile] [outputfile]

  • inputfile : 需要分割的文件。
  • outputfile: 分割出来的文件序列。

举例: 将/opt/etc.zip进行分割,指定每个文件大小为2M:

split -b 2M /opt/etc.zip /opt/etc.zip_back

written in linux

10 Linux系统管理和维护常用的命令行

这一篇对Linux系统管理和维护的常用命令的介绍和使用。

ls命令

ls命令是列出指定目录下的子目录和文件。

ls [选项] [路径或文件]

如果要了解这个命令的选项可以输入 man ls:

ls

ls常用命令参数举例

列出 /home 下的文件,同时将文件的权限,使用者和大小等信息列出

ls -l /home

列出所有文件,包含隐藏文件:

ls -al /home

pwd

显示当前工作目录的绝对路径

[root@myserver ~]# pwd
/root
[root@myserver ~]# 

cd

切换目录

[root@myserver ~]# cd /usr/bin
[root@myserver bin]# pwd
/usr/bin
[root@myserver bin]# 

date

显示和修改系统时间。

date [选项] [显示时间格式(以”+”开头)]

显示时间格式可以用 man date 查看:

ls

比如查看日期:

[root@myserver bin]# date "+ today is %D"
 today is 04/19/17
[root@myserver bin]# 

设置时间:

ls

passwd

设置用户密码

pass [用户名] 默认当前用户

连续输入两次密码按回车即可:

ls

su

改变用户的身份

su [选项] [用户名]

比如普通用户改为超级用户

su -

在普通用户中修改root的密码

su -c passwd

clear

清屏!

man

帮助命令,可以查看命令的详细帮助信息。

who

查看当前系统用户的信息

ls

w

查看登录到系统的用户的信息:

[root@myserver ~]# w
 20:14:21 up 41 days,  8:59,  3 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
root     tty1     -                09Mar17 41days  0.07s  0.07s -bash
root     pts/0    113.67.31.159    19:51   15:09   0.02s  0.02s -bash
root     pts/1    113.67.31.159    20:13    0.00s  0.00s  0.00s w

uname

显示当前操作系统的相关信息

[root@myserver ~]# uname -a
Linux myserver 2.6.32-642.11.1.el6.x86_64 #1 SMP Fri Nov 18 19:25:05 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

显示的是 2.6.32-642.11.1.el6 的内核版本,64位的Linux操作系统。

uptime

输出系统的任务信息,系统当前时间,系统启动到现在运行的时间,目前多少用户在线,系统平均负载:

[root@myserver ~]# uptime
 20:21:02 up 41 days,  9:06,  3 users,  load average: 0.00, 0.00, 0.00

last

列出目前和过去登录到系统的用户信息

dmesg

显示开机信息

free

显示内存的信息

例如以M为单位显示系统内存信息:

[root@myserver ~]# free -m
             total       used       free     shared    buffers     cached
Mem:          7872       2587       5285          0        192       1400
-/+ buffers/cache:        994       6878
Swap:            0          0          0

ps

显示系统的进程瞬间运行状态

[root@yptx_web1 ~]# ps
  PID TTY          TIME CMD
 4449 pts/1    00:00:00 bash
 4508 pts/1    00:00:00 ps

top

实时监控处理器状态

ls

第一行 :

  • 20:31:19 : 当前系统时间
  • up 45min : 已经启动了45分钟
  • 1 user : 当前登录系统的用户数量
  • load average : 系统的平均负载

第二行:

  • 127 Total : 127个进程
  • 1 runing : 1个进程在运行
  • 126 slepping : 126个进程在休眠状态
  • 0 stopped : 0个停止进程
  • 0 zombie : 0个僵死进程

第三行:

  • 0.0 us : 用户进程占了cpu的百分之0
  • 0.5 sy : 系统进程占了cpu的百分之0.5
  • 0.0 ni : 用户进程空间内改变过优先进程占了cpu的百分之0
  • 99.5 id : 空闲cpu占了百分之99.5
  • 0.0 wa : 等待输入输出进程占了百分之0

第四、五行:

Mem:

  • 3882124 total 物理内存总量
  • 3456888 free 空闲内存总量
  • 192256 used 使用的物理内存总量
  • 232980 buffers 用作内核缓存的内存量

Swap:

  • 4194300 total 交换区总量
  • 4194300 free 空闲交换区总量
  • 0 used 使用的交换区总量
  • 3435260 cached 缓冲的交换区总量。

下面相关的意思:

  • PID : 进程的id
  • USER : 进程所有者的用户名
  • PR : 进程优先级
  • NI : nice值 负数表示高优先级 正数表示低优先级
  • VIRT: 进程使用虚拟内存总量
  • RES : 进程使用的、未被换出的 物理内存总量
  • SHR : 共享内存大小
  • S : 进程状态(D:不可中断的休眠状态 S:睡眠状态 R:运行状态 T:停止/跟踪 Z:僵死进程)
  • %CPU : 上次更新到现在cpu占用时间比例
  • %MEM : 进程占用物理内存比例
  • TIME+ : 进程总计使用cpu时间
  • COMMAND : 正在进行的进程名

written in linux

09 Linux Shell命令行格式的理解和使用方式

我们或多或少都会接触到命令行,在Linux中,掌握命令是必不可少的,命令行是伴随着我们接下来一直学习到的东西,所以这一篇就来认识一下什么Linux的shell命令行,对shell命令行的语法格式和它的参数以及选项进行一个深入理解,以便接下来的学习研究。

shell命令行长这样:

command [options] [argument]

其中:

  • command : 命令的名字
  • options : 选项,它用来改变命令的执行方式,一般前面会加个“-”符号
  • argument: 参数,指定操作的对象

例如:

ls -l /etc

其中ls就是命令的名字, -l 就是指定要这个命令执行的方式,比如这里是列出目录, /etc就是参数,我们这里是对 /etc的目录进行操作。

所以这句命令行的意思就是 : 使用ls命令列出 /etc 下的所有目录。

shell命令行的用法

命令选项合并使用

比如我们要列出etc目录下的所有文件,包含隐藏文件,那么我们就会这样输入:

ls -a -l /etc

然后选项合并之后可以变成成这样:

ls -al /etc

命令参数通配符的使用

shell命令的通配符的存在是为了我们在使用命令参数的时候对文件目录进行描述,比如我要查询以.sh结尾的所有文件,那么这个时候通配符就会让我们很方便的使用了。

常用的通配符有以下几个:

  • *  一个或任意多个
  • ?  任意单一字符
  • []  包含在[]内的单字符

例如:

列出etc下的 子目录 的所有以 .conf 结尾的文件

ls -al /etc/*/*.conf 

列出当前目录下以a开头,随后一个为随意字母的txt文件:

ls a?.txt

列出当前目录下以a开头,随后三个为随意字母的txt文件:

ls a??.txt

列出当前目录下,以a开头,随后含有1,2,3,4的txt文件:

ls a[1,2,3,4].txt 这里也可以写成 `ls a[1-4].txt`。

shell重定向操作

有时候我们在字符窗口输入命令行的时候,可能输入源不只是我们敲入的命令行,也可能来源于某个文件,在结果输出的时候有时候数据太多,我们可能需要将它的输出定向到指定的文件,然后我们去文件里面看输出结果,还有的就是一些错误信息,我们也可以将它重定向输出,而不仅仅是在屏幕输出。

所以有这三个:

  • 标准输入 对应的重定向操作符为 “<” , “«”;
  • 标准输出 对应的重定向操作符为 “>” , “»”;
  • 标准错误输出 对应的重定向操作符为 “2>” , “2»“。

标准输入

标准输入

我们看到第一个命令: wc < /etc/shadow

这个命令的意思就是:wc是统计命令,来源是/etc/shadow,统计的行数,单词数,字符数输出结果。

第二个命令 :

wc << bb
>...
>...
>...
>bb

这里的 « 就是告诉命令输入源来源于 bb 和 bb之间,这个bb就是内容的分隔符,可以使任意字符,然后我们就统计到我们在bb之间的行数,单词数,字符数。

标准输出

比如我们将结果输出放到 ls.txt 就可以这样:

ls /etc >ls.txt

如果这时候ls.txt文件不存在,那么系统会自动创建一个,如果系统已经存在了ls.txt,那么系统会将ls.txt原有的内容覆盖掉。如果不想被覆盖,那么这时候就可以用到 “»”:

ls /etx >>ls.txt

这个时候将输出的内容追加到ls.txt。

标准错误输出

标准错误输出和标准输出差不多,只不过在输出的时候是 : 2> error.txt;

shell管道

管道,顾名思义,就是将第一个命令的输出当做第二个命令的输入,管道的符号为” “,比如:
ls -l /etc |more

将 /etc 下的目录输出,然后用more的进行分屏显示。

shell的转义

shell的命令有一些特殊的字符是有含义的,我们在使用它们的时候要进行转义才可以使用。比如刚才我们已经知道 是一个通配符,代表一个任意字符, 但是如果我们真的有一个文件叫做 abc?呢,我们就不能把当做通配符来使用,这时候就对它进行转义,让系统知道这是真正文件名。

那么转义字符有: 反斜杠(\) 、双引号(”“)、单引号(’‘)。

比如我要列出abc?这个文件下的所有目录:

ls -l abc/?/*

也可以这样写:

ls -l 'abc?/*'

也可以这样写:

ls -l "abc?/*"

当然了对于一些更特殊的字符即使我们使用了转义字符,它也会保留自己,比如 “$” ,”`” ,”"

比如这里我用 “” 对 ` 进行转义是无效的:

标准输入

好了,我们对命令行已经有了一定的了解,也知道了怎么使用,接下来的篇章会对常用的命令行进行学习,等我们将常用的命令行撸完之后就会对软件的安装管理,解压等进行学习,服务器搭建也紧跟其上O(∩_∩)O~~~~

written in linux

08 Linux的shell是什么鬼

咱们这篇文章开始了解学习Shell,shell是什么鬼,坐好板凳,且听我速速道来。

shell是什么意思,先让Google爸爸翻译一下:

shell

shell就是个「壳」啊!

我们可以理解为shell是Linux内核的“外壳”程序?的确,shell是Linux内核和操作系统的「桥梁」,我们在操作系统的所有任务都可以通过shell来与Linux内核进行交互。shell是一个用C编写而成的程序。

Linux下有许多Shell版本,有Bourne again shell(bash)、C shell(csh)、Korn shell(ksh)、Tenex C shell(tcsh)等等。

虽然有这么多版本,但是shell的功能基本相同,现在许多默认的发行版本用的是默认的bash版本。

shell是一种解释型程序的设计语言

shell有定义了各种变量各种函数以及函数结构等,利用它可以编写出shell脚本程序,有点想windows的dos下的批处理文件。

shell又是一种命令解释程序

我们接下来要经常使用shell是因为它是命令解释程序,shell会解释用户输入的命令,然后提交给Linux内核,最后会把结果返回给用户。

内置命令

shell定义了一些内置命令,比如cd,pwd,exit等等,当用户登录到系统的时候,shell就会和这些内置命令一起加载到内存中,直到用户退出系统才停止运行。

可执行文件

除了内置命令外,Linux上还有可执行文件,这些可执行文件可以作为shell命令来执行,比如存放在/lib/ls 的 ls 就是可执行文件,它和shell内置命令的不同在于:可执行文件的命令只有在被调用的时候哦才会加载到内存中去。

shell命令提示符的用户区别

当用户登录进入系统的时候,如果用的是普通用户,那么shell会以“$”表示,如果是超级用户,那么shell就会以“#”表示!

最后送你一个美腻的壳:

shell

本篇完

written in linux

07 Linux与Windows文件互传

可能你是从windows开始转向Linux系统学习使用,或许你会有这样的疑问:觉得在Linux上传或下载文件很麻烦还需要挂载U盘?如何在windows和Linux之间实现文件的互传呢? 接下来这篇文章就给你解疑答惑。

SecureCRT

如果我们在windows需要操作远程的Linux服务器,这时候我们会用到SecureCRT这款软件,现在大多数负责Linux系统的人都在使用,SecureCRT可以理解为一个终端的仿真程序。利用它你可以在windows下登录Linux系统。因为它支持SSH*(SSH1,SSH2),那么什么是SSH?

SSH

SSH是一种网络服务,就像FTP那样,但是SSH和FTP的不同在于SSH更加安全,FTP的数据和密码的传输是以明文的形式发送的,而SSH的数据传输是经过加密和压缩的。

SSH在CentOS Linux上是默认安装的,所以我们不需要对它进行安装。

使用

下载安装SecureCRT,你可以去官网下载:http://www.vandyke.com/download, 当然你也可以在我的公众号「肯定会」里面喊一句:”哥,给我个SecureCRT破解版”,然后我看到就会给你发。

双击打开SecureCRT软件,点击File – Quick Connect..

connect

  • protocol 选择SSH2
  • Hostname 输入你要远程连接的Linux的ip地址
  • Port SSH的默认端口为22
  • Username 输入要登录的系统名

其它默认即可,然后点击connect。

然后输入用户名密码就可以连接了:

connect

rz,sz

rz,sz是Linux和Windows进行Zmodem文件传输的命令行工具。

  • rz: 从本地上传文件到服务器。
  • sz: 从服务器下载文件到本地。

在使用rz,sz的时候需要安装lrzsz软件包,我们可以用 rpm -q lrzsz来查询是否安装:

connect

可以看到我们还没安装,那么就使用 yum install lrzsz 进行安装:

connect

点击y即可完成安装。

安装好了之后我们就可以使用 rz ,sz 命令了:

比如我们要将本地的文件上传到远程Linux上面,那么我们就只需要输入rz,然后就会弹出对话框给你选择文件进行上传:

connect

如果我们要从Linux上下载文件到本地,那么我们只需要输入 sz + 要下载的路径就可以了,比如我要下载日志文件:

sz /var/log/dmesg

connect

可以看到我们已经将文件下载到相应的目录下了:

connect

本篇完!

written in linux

06 Linux系统的关机过程和方法

可能有些对Linux系统的关闭不以为然,觉得和Windows一样,直接关闭电源就可以了,其实这样做很不好,因为Linux后台运行着许多控制Linux对系统的各种操作进程,如果就直接关闭了的话,就可能会对系统造成进程混乱甚至数据丢失,如果对于正在高负荷工作的系统强制关闭,这样的动作是很危险的,因为这样子极有可能丢失系统里的数据,更严重的还会损坏硬件。所以我们有必要对Linux系统的关机过程有一个了解。

Linux的有关于系统关机命令常用的有这么四个:shutdowninithaltreboot。那么接下来就对其一个一个的认识。

shutdown

shutdown这个命令是用shell编写的程序,只有超级用户才可以对其进行使用。它可以安全的关闭Linux系统

当我们执行shutdown这个命令的时候,告诉你这个时候它会做一些什么事情:shutdown执行后会以广播的形式来通知所有在这个系统的用户,告诉用户系统将在指定的时间里边关闭系统,做好相应的数据保存工作。同时login指令会被禁用,当所有的用户注销了或者指定的时间到了的时候,shutdown就会发信号给init进程,然后init进程就会执行相应的运行级别。比如shutdown 指定的参数是关机命令,那么init就会执行 init 0 的运行级别。 (如果你不知道init运行级别,可以查看我上一篇文章:05 CentOS Linux的运行级别

好了,我们已经明白了它的关机过程,接下来对命令进程了解:

shutdown的详细命令是这样的:

shutdown【-fFhknrc(参数名称)】【-t 秒数】【时间】【警告信息】

参数含义如下:

  • f : 重新启动时不执行fsck(fsck是检查和修复文件的程序)
  • F : 重新启动时执行fsck
  • h : 系统关机
  • k : 发送信息给用户,但是不会真正的关机
  • n : 不调用init进程,直接由shutdown关机
  • r : 关机之后重新启动
  • c : 取消前一个关机的shutdown命令
  • t : 发送给系统用户的警告信息和关机信号的延迟时间
  • 【时间】 : 设置多少时间后执行shutdown命令,有两种,一种是hh:mm,一种是+m,如果想设置22:05的时候执行关机命就是 「shutdown 22:05」 ,而如果要5分钟后执行关机命令就是 「shutdown +5」
  • 【警告信息】 : 传送给正在登陆着的系统用户的信息。

举个例子:

10分钟后关机并且给用户警告信息:

shutdown +10 “hey,System will shutdown after 10 min”

Linux shutdown

可以看到系统发了个广播,并且告知“hey,System will shutdown after 10 min”这信息,然后还说明了将在22:07:13的时候关机。

init

这个init我们在上一篇05 CentOS Linux的运行级别1介绍过了:

关机:将init切换到0级别, 重启:将init切换到6级别。

halt

halt是最简单的关机命令了,和“shutdown -h”差不多,在执行halt的时候会终止所有的应用然后调用系统的指令sync将内存信息写入硬盘,然后停止内核,在执行half命令的时候还会检查系统的运行级别,如果是0或者6就会立即关闭系统,如果是其它的运行级别就会调用shutdown。

halt命令是这样的:

halt 【-finp(参数)】

参数含义如下:

  • f : 不管现在的系统运行级别是哪个,都不调用shutdown而直接强制关机(不建议使用)
  • i : 关机之前,关掉所有的网络的接口。
  • n : 不调用sync
  • p : 关机的时候调用poweroff(关闭电源),这是默认选项。

reboot

reboot和halt基本是一样的,不过reboot是用于关机后重启。

written in linux

05 CentOS Linux的运行级别

为了助于后面的学习,我们在此之前还是需要了解一下Linux的运行机制,这篇就来介绍Linux的运行级别。

Linux的启动

当你启动Linux系统的时候,它首先会从BIOS开始去引导程序将内核映像加载到内存里边,然后进行内核初始化,内核初始化的最后一步就是启动PID为1的进程,这个进程是系统的第一个进程,init进程,它会产生其他所有用户进程。

系统的内核运行起来还是不够的,这时候init系统会定义管理和控制init进程的行为,并且会组织运行很多独立或者相关的初始化工作,让系统进入一个用户设定的运行模式中去。

init系统

大多数Linux的发行版的init系统都会和SystemV相兼容,所以会叫做sysvinit系统,它的好处是概念简单清晰,使用shell脚本,不过它的启动是一次一个串行的启动,这样会导致系统启动缓慢

后来改进了sysvinit系统,就先后出现了upstartsystemd这两个新的init系统。目前ubuntu就是使用的是upstart系统,而我们要学习的CentOS 7.x就是使用的是systemd系统。

运行级别

CentOS7.x之前用的是sysvinit系统,这个init系统用「rublevel」来表示运行级别,sysvinit系统在启动的时候会去寻找 etc/inittab下是否有initdefault的默认启动项,有就会启动默认的级别。

到了CentOS7.x,使用了systemd系统,这个init系统用「target」来表示运行级别。在这个init系统中,系统的默认运行级别是用软链接来实现的。

我们要在CentOS7.x中查看默认的运行级别可以输入:ll /etc/systemd/system/default.target:

target

我们可以看到default target 为 : graphical.targte(完全多用户模式,默认登录到X-windows系统,就是登录到Linux的图形界面)。

接下来我们看看target运行级别的对应,输入 ll /lib/systemd/system/runlevel*.target

target

其中:

  • 0 为 poweroff.target : 关机模式
  • 1 为 rescue.target : 救援模式
  • 2,3,4 为 multi-user.target : 多用户模式
  • 5 为 graphical.target : 图形界面多用户模式
  • 6 为 reboot.target : 重启模式

修改默认级别

我们刚刚用ll /etc/systemd/system/default.target来查看目前系统的默认运行级别为 graphical.targte(完全多用户模式,默认登录到X-windows系统,就是登录到Linux的图形界面),也就是runlevel5。

如果我们要修改默认级别,那么就需要先删除存在的软链接,然后重新建立链接指向你要设置的运行级别即可。

比如我要将默认的运行级别修改为 multi-user.target 多用户模式 :

  1. 删除软链接:rm -rf /etc/systemd/system/default.target
  2. 重新建立软链接: ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target

target

然后查询可以看到,我们已经将默认运行级别改了:

target

written in linux

04 组成Linux核心的5个主要部分

Linux日益发展,现在Linux内核到现在已经是4.1版本了,这都得益于开源的力量。这是一个最坏的时代,也是一个最好的时代,我想最好的时代的其中一个原因就是「开源」。

Linux

好了,通过Linux系统的目录结构我们已经对Linux的目录结构有了了解,接下来我们对Linux更进一步的了解,对Linux的整体有一个整体的大概认识,有助于我们接下来的学习,那么这一篇主要是来了解Linux核心的组成部分:

1.内存管理

内存管理能够有效的管理系统的内存,响应程序对内存的请求,Linux内存管理还支持虚拟内存,就是Linux在运行程序和一些数据可以超过实际的物理内存,超过物理内存的这一部分内存是通过对磁盘申请得到的,一般情况下系统把当前运行的程序保留在内存里边,到了内存紧缺,迫不得已的时候就会去跟磁盘交换程序块。

2.进程管理

Linux使用基于优先级的进程调度算法来控制进程对CPU的访问,当某个进程需要被执行的时候,那么进程调度器就会基于调度算法启动新的程序,Linux支持多任务运行,比如你在Linux上可以“同时”运行多个程序。这样看起来好像Linux可以同时并发的执行多个任务,其实不然,而是在系统运行的时候,每个进程会得到被cpu执行的时间片,当某一个进程的时间片执行完后,调度进程就会去执行另一个进程,这样对每个进程都进行的快速切换执行,这样的快速快到让我们感觉好像cpu在同一时间执行多个程序一样。

3.进程间通信

在不同的进程之间控制数据的交换和共享,由于不同的进程之间的进程空间不同,所以进程间通信会借助内核的中转来实现进程。

4.虚拟文件系统

Linux虚拟文件系统隐藏了各种硬件的具体细节,为所有的设备提供了统一的接口,使得Linux可以支持多种文件类型,虚拟文件分为 逻辑文件设备驱动文件 ,逻辑文件指的是Linux支持的文件系统, 设备文件指的是为每一种硬件控制器所编写的设备驱动程序模块。

5.网络接口

网络接口提供了对各种网络标准的存取实现和各种网络硬件的支持。网络接口可分为网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议。网络设备驱动程序则负责与硬件设备通信,每一种可能的硬件设备都有相应的设备驱动程序。

written in linux

03 Linux的目录结构

既然我们已经安装好了CentOS,也看到了CentOS的桌面的样子了。接下来我们就来对它进行进一步的了解与认识。

桌面控制台与字符控制台

Linux系统由桌面控制台和字符控制台组成的,默认的字符控制台有6个,它们之间相互独立,体现了Linux的 多用户,多任务 特性。

在X-Windows视窗中 按CTRL + ALT + F1~F6即可跳转到字符控制台,在控制台输入startx即可跳转到桌面控制台。

接下来我们的很多操作都是在字符控制台进行,使用命令行操作,这里才是Linux学习的核心。

Linux目录结构

我们切换到了字符控制台,登录号账号密码,然后输入 ls /(你现在可能不知道ls是干嘛的,不过没关系,先知道现在是列出根目录下的目录即可)

Linux directory

我们可以发现根目录下有以上这些目录,其实Linux将所有内容都以文件的形式展现出来,是用树形结构来管理这些文件的:

Linux directory

许多Linux发行版纳的目录结构都是遵循FSSIND标准的,在FSSIND标准中,所有的文件和目录都出现在根目录”/”下,即使他们存储在不同的物理设备中:

接下来我们就对这些目录进行大概了解,解开我们对Linux系统目录结构的迷惑。

/dev : 存放包含系统的所有设备文件,例如软盘、U盘等设备,访问这些设备就像访问文件那样的简单。


/bin : 存放的是可执行的二进制文件,bin是binary的缩写,这里存放的就是常用的Linux命令


/opt : 主机额外安装软件所存放的目录。


/root : 这个是Linux的超级用户root的主目录,其它用户没有权限进入这个目录。


/home : 每个用户的工作目录,这个目录是以用户的名称命名的,比如有个名称叫abc的用户,那么对应的目录就是/home/abc。


/proc : 虚拟的目录,它是系统内存的映射,这个目录的内容不在硬盘上而是在内存里,我们可以在这里获取有关进程的信息,还可以修改系统内核参数等。


/lib : 这个目录存放的是共享程序库和文件,许多程序可以在这里引用。


/sbin : s是super user的意思,这个目录和bin目录的区别在于这里的命令是超级用户才可以使用的。


/usr : 存放我们安装的应用程序软件的。


/tmp : 存放临时文件,这里的文件会被随时的删除。


/var : 存放系统运行的日志文件。这个文件会不断的扩充。


/boot: 这里存放的是Linux的一些启动核心文件,这里的文件很重要,如果破坏了就无法启动系统了。


/mnt: 专门用作临时挂载点的目录,主要用于系统管理员手动临时挂载一些媒体设备。


/etc: 存放系统管理相关的配置文件和子目录。

好了,我们对以上的文件目录有个大概的认识,知道什么目录具体是干什么的,这样我们对Linux的目录结构有一个清晰的认识,有利于我们接下来的学习。

written in linux

02 在虚拟机安装配置CentOS系统

通过:01 创建CentOS虚拟机 我们已经创建好了虚拟机,也下载好了CentOS7的镜像文件。那么接下来就开始在我们的虚拟机安装配置CentOS系统。

01 首先打开我们的虚拟机,选择我们创建好的虚拟机,然后点击设置–存储–添加虚拟光驱:

VirtualBox

02 点击选择磁盘:

VirtualBox

03 选择我们下载好的CentOS镜像文件:

VirtualBox

04 选择完之后,点击ok,再点击Virtual Box的启动按钮,就会引导读取我们选择的iso镜像文件:

VirtualBox

05 运行后我们就可以看到安装引导:

VirtualBox

06 我们选择 Install CentOS Linux7,然后按回车,骚等一会就会出现如下界面,我们选择English,当然你也可以选择中文,但是这里我选择English,毕竟学Linux还是要懂点英文的:

VirtualBox

07 选择完之后我们看到CentOS系统安装总览,分别有「LOCALIZATION(本地化安装)」、「SOFTWARE(软件安装)」、「SYSTEM(系统安装)」:

VirtualBox

注意了,在虚拟机使用和我们自己的电脑之间,按住右边的Ctrl键可以对鼠标的事件进行切换,比如你在虚拟机中使用中,想用鼠标使用自己的电脑时,那么就按右边的Ctrl键来获取鼠标事件。

对LOCALIZATION(本地化安装)进行设置,

08.1 点击DATE&TIME来设置系统时间,这里Region(地区)我们选择Asia(亚洲),城市选择shanghai,然后点击左上角的Done:

VirtualBox

08.2 接下来我们点击LANGUAGE SUPPORT(语言支持),我们可以根据自己的需求选择需要的语言安装包,这里我选择英语和中文,选择完点击左上角的Done:

VirtualBox

对第二部分的SOFTWARE(软件安装)进行设置

09 INSTALLATION SOURCE 这个选项默认即可,接下来我们选择 SOFTWARE SELECTION, 这里面可以供我们选择软件包,因为我们把Linux作为服务器使用,所以在这里我们选择Server with GUI,因为这个环境包含了基础的服务设施和GUI界面,然后在右边可以根据需要选择软件环境:

VirtualBox

对第三部分的SYSTEM(系统)进行设置

10.1 选择 「INSTALLATION DESTINATION」 进行分区操作,在这里说明一下Linux的分区必须有 根分区(用“/”标识) 和 交换分区(用swap标识),swap分区类似于Windows的虚拟内存概念。虽然Linux的必需分区为根分区和交换分区,但是我们在分区的时候可以根据我们的需要进行分区,不仅仅划分根分区和交换区:

  • / (根分区)
  • swap (交换分区)
  • /boot (存储系统内核和引导等信息)
  • /usr (存储应用软件安装信息)
  • /var (存储系统日志信息)

在分区前还再说明一下Linux的分区命名规则:

一般有这样的命名:

/dev/hdb2 、 /dev/sda1

  • /dev : 所有文件的存放目录
  • hd/sd : 代表硬盘,hd是IDE硬盘,sd是SCSI硬盘。
  • b: 分区的第三个字母,代表的是在哪个硬盘上,如 /dev/hdb 就表示在第二个hd盘上, /dev/sda 就代表在第一个sd盘上。
  • 2: 最后这个数字就代表的是分区,Linux在1~4代表主分区或者扩展分区,从5开始就代表逻辑分区,比如 /dev/hdb2/ 就代表在第二个hd盘上的第二个主分区或者扩展分区, /dev/sda6 就代表在第一个sd盘上的第二个逻辑分区。

好了,我们了解好了分区后就点击「INSTALLATION DESTINATION」 ,可以看到我们之前创建好的100G的sda盘,我们在下面选择「I will configure partitioning」 来自定义分区:

VirtualBox

点击完Done之后会让我们选择分区方案,在这里我们选择 Standard partition(标准分区):

VirtualBox

接下来开始分区,首先对根分区进行划分,点击下方的 + , Mount Point选择根分区(“/”),Desired Capacity(容量)输入20G:

VirtualBox

然后我们点击 Add mount point,可以看到我们建立了sda1这个根分区:

VirtualBox

接下来我们划分交换分区,点击下方的 + ,然后选择warp,这里输入4096M:

VirtualBox

然后以同样的方式划分: - /boot (存储系统内核和引导等信息) - /usr (存储应用软件安装信息) - /var (存储系统日志信息)

这里我给boot划分500M,usr划分20G,var划分30G,还有剩余的空间我们可以划分为“/mydata”,这里录入mydata,下面的Desired Capacity不需要填,会自动就剩余的空间划分给mydata:

VirtualBox

至此,我们就把划分好分区了:

VirtualBox

点击完Done后我们点击Accept Cahnge:

VirtualBox

10.2 接下来我们对网络进行配置,KDUMP默认启用即可, 点击「NETWORK & HOST NAME」,可以看到我们左侧有一块网卡,Ethernet是以太网,enpOs3是网卡的标识,右侧可以看到详细MAC信息和连接速率等信息,左下角可以输入主机名:

VirtualBox

然后点击右上角的开关按钮来激活我们的网卡。点击Done。

开始安装

完成了以上的设置后,我们就点击右下角的 Begin Installation 按钮,开始安装到我们的磁盘啦。

VirtualBox

在安装的过程中,我们开始对root账号进行密码设置:

VirtualBox

设置完密码点击done,当然你也可以继续创建账户,然后就是等待安装啦:

VirtualBox

经过一段时间的等待,我们的CentOS终于安装完成了:

VirtualBox

接下来我们点击右下角的Reboot按钮进行重启,重启后我们选择CentOS Linux:

VirtualBox

接下来我们需要同意协议,点击LICENSE,然后勾选accept:

VirtualBox VirtualBox

点击Done之后,如果你在安装的过程中没有创建账户,那么在这个时候可以点击USE CREATION来对用户进行创建:

VirtualBox

创建完之后点击右下角的FINISH CONFIGURETION。

至此我们的CentOS就安装完成了,放几张图给你感受一下:

VirtualBox VirtualBox VirtualBox VirtualBox VirtualBox VirtualBox

那么接下来的篇章会告诉你怎么基本使用CentOS,对系统目录进行了解,对CentOS有一个整体的认识。

written in linux

01 创建CentOS虚拟机

这是Linux学习的第一篇,对于刚开始学习Linux的我们没必要直接马上将Linux装到我们电脑上,我们可以先在虚拟机中使用,等熟练到一定程度的时候,再将Linux安装到我们的电脑上也不迟。那么我们就先将CentOS虚拟机创建起来吧。

在看本篇文章之前你需要下载:

  • VirtualBox虚拟机,地址:https://www.virtualbox.org/wiki/Downloads
  • CentOS7镜像文件,地址:http://mirrors.163.com/centos/7.3.1611/isos/x86_64/

tips:CentOS7有4G大小,VirtualBox有117MB大小。CentOS7镜像文件将会在下一篇文章用到。

为什么使用VirtualBox虚拟机

现在市面上比较流行的虚拟就有VMware,VirtualBox。由于VMware是收费的,而VirtualBox是开源免费的,就凭这一点,我们就选择使用VirtualBox了哈,当然除了开源免费外,它还要诸多优点的,比如它跨平台:可以运行在 Windows, Mac OS X 和 Linux/UNIX平台上,可以虚拟出我们常见系统等等,所以完全够我们使用。

VirtualBox


安装VirtualBox虚拟机

01 双击运行我们下载好的VirtualBox虚拟机:

VirtualBox

02 点击next后,注意这里一般不选择默认的系统盘,我这里选择D盘:

VirtualBox

03 再点击next,next,yes后进行安装,安装过程中会弹出提示安装设备软件,我们选择安装:

VirtualBox

04:安装完成后运行VirtualBox:

VirtualBox


创建虚拟系统

01 点击左上角的新建按钮,弹出对话框,名称输入「CentOS」,类型为Linux,版本为Red Hat(64bit):

VirtualBox

02 点击下一步,选择虚拟内存大小,这里设置:(4096MB)4G:

VirtualBox

03 点击下一步,创建虚拟硬盘,我们选择 “现在创建虚拟硬盘” ,然后点击创建:

VirtualBox

04 点击创建之后,会让我们选择硬盘的类型,这里我们选择:VDI(VitrtualBox 磁盘映像):

VirtualBox

05 设置硬盘的分配方式,这里我们选择:动态分配

VirtualBox

06 选择文件的位置和大小,我们先选择大小,这里设置为100GB:

VirtualBox

07 点击又上角的文件夹按钮,存放到你想放的位置上,这里我放在G:\vmdisk上:

VirtualBox

08 点击创建后,我们的虚拟机系统就创建完成了:

VirtualBox


恩,我们已将将CentOS的虚拟机创建好了,那么下一章就开始对基于我们现在的虚拟机来安装CentOS系统和配置。所以你赶紧把CentOS7镜像文件下载好吧?坐等下一章我们一起安装!

written in linux

Linux 学习启动篇

从这篇文章起,就意味着开始着手学习使用Linux系统了,虽然经常听到Linux操作系统,但是或许正在着手去操作使用的人还是少数的,甚至连什么是Linux都不知道的人也大有所在。那么这篇 「Linux学习启动篇」 将从0开始,首先了解Linux是干嘛的?能在Linux上做些什么事情?接下来再和我一起深入学习!

1.什么是Linux

Linux是一种自由和开放源代码的类UNIX操作系统。

Linux是自由开放源代码的,这个系统的内核是由「Linus Torvalds(中文名:林纳斯·托瓦兹)」在1991年10月5日发布的,其实Linux本来指的是Linux内核本身,但是通常都用了「Linux内核」来称呼,而Linux就代表了 Linux内核 + 用户空间的应用程序 构成的完整操作系统。

Linux

2.开源协议

Linux如此受欢迎的其中一个原因就是它开源,这意味着任何个人或者机构都可以很自由的使用Linux里面所有的源代码,你可以对它进行修改生成新的版本然后再发布出来成为你的东西。但是前提是遵循「GUN GPL协议」。

GUN GPL(GUN General Public License),通用公共许可协议,这是由理查德·斯托曼发起的,许多自由软件和开放源码都采用了这个协议条款,任何个人或者机构使用基于GPL进行衍生发布时必须采用GPL协议许可,而且需要公开源代码

3.Linux使用领域

「服务器领域」: Linux已经在服务器领域占了大半壁江山,随着开源软件的日益强大,Linux服务器操作系统已经越来越多人使用。应该很少人会用windows server了吧。

Linux发行版一直被用来作为服务器的操作系统,并且已经在该领域中占据重要地位。根据2006年9月Netcraft的报告显示,十个最大型的网络托管公司有八个公司在其Web服务器运行Linux发行版。 Linux发行版是构成LAMP(Linux操作系统,Apache,MySQL,Perl / PHP / Python)的重要部分,LAMP是一个常见的网站托管平台,在开发者中已经得到普及。

「移动设备嵌入领域」: Linux的低成本、强大的定制功能以及良好的移植性能,在智能手机、平板电脑等移动设备方面,Android手机就是Google开发的基于Linux平台的开源手机操作系统。现在市场上Android手机仍然是占了最大的移动操作系统份额。阿里云的YunOS系统也是基于Linux的,越来越多的系统都使用了Linux。

「桌面领域」: 现在大多数在Windows平台上广泛使用的自由软件都有相应的Linux版本,现在Linux桌面系统的发展也是越来越多了,像国内的红旗Linux,深度Linux,还有你可能经常听到的Ubuntu系统,都是基于Linux的桌面操作系统。

目前能在Windows或Mac OS上运行的应用软件大部分都没有Linux的版本,不过在Linux平台上通常可以找到类似功能的应用软件。大多数在Windows平台上广泛使用的自由软件都有相应的Linux版本,例如Mozilla Firefox、Apache OpenOffice、Pidgin、VLC、GIMP;部分流行的专有软件也有相应的Linux版本,如Adobe Flash Player、Adobe Reader、Google Earth、Nero Burning ROM、Opera、Maple、MATLAB、Skype、Maya、SPSS、Google Chrome。

4.Linux发行版

Linux的发行版实在太多了,这里列举几个常见的发行版本。

Debian GNU/Linux 7.0:

debian

Gentoo Linux 12.0:

Gentoo

Linux Mint 14:

Linux Mint 14

Fedora:

Fedora

Ubuntu:

Ubuntu

以上列举的这几个都是有桌面版的,但是我们学习Linux主要还是对命令行进行学习,对于学习和使用的系统我们选择不是以上这几个,而是centOS系统,那么为什么选择centOS?

5.选择centOS版本进行学习

早前 Red Hat公司 就发行了「Red Hat Linux」的个人版本,到了Red Hat 9.0版本后,Red Hat公司就不再发行桌面版的发行套件了,Red Hat Linux 也就停止了开发,而开始全力集中转向服务器版本上,也就是Red Hat Enterprise Linux(企业版本)。

后来 Red Hat Linux 的桌面版本与来自开源社区的Fedora进行合并,Red Hat Linux 桌面版就称为了 Fedroa Core。

所以目前Red Hat有:免费的Fedroa Core版本,也有收费的Red Hat Enterprise Linux版本。Red Hat Enterprise Linux都会在Fedroa Core版本的基础上进行升级,大约发布6个Fedroa Core版本就会发布一个Red Hat Enterprise Linux版本。

因为之前我们讲过了,Red Hat的企业版本虽然收费的,但是它依照开原协议必须公开源代码,那么这个是时候CentOS就出现了,它的全名是(Community Enterprise Operating Systeam 社区企业操作系统),centOS和Red Hat企业版本的不同之处在于centOS没有包含封闭源码的软件,所以centOS可以自由使用,而且可以长期的享受它的免费升级和服务。

所以选择centOS版本进行学习是很有必要的:目前网络上80%的Linux资源都是基于centOS的发行版的,可以供我们更多的资源查找和学习帮助。centOS版本在许多开源镜像网站可以轻松获取。centOS也具有经典性和代表性,绝大多数物联网公司的后台服务器都使用的是centOS发行版本。

所以综上:选择centOS版本是比较好的。接下来就和我一起学习Linux吧,就从安装虚拟机开始,如何?

written in linux

计算机网路的体系结构概述

计算机网络是一个复杂的东西,在计算机网络中最重要的就是 协议分层

网络协议

当多个实体之间需要传递信息的时候,我们就会使用协议来规定以怎样的形式来完成信息的传递。

从前面的什么是因特网?一文中,我们已经知道了网络是由节点和连接这些节点的链路组成的,那么这些节点在通过链路传输数据的时候就需要用到协议。

网络协议由三要素组成:

  • 语法: 数据和控制信息的构成或者格式。
  • 语义: 各个控制信息的具体含义。
  • 同步: 事件实现顺序的详细说明。

协议可以理解为事先规定好的规则。比如计算机网络中的节点之间都要进行数据交换与控制信息,它们都要遵守事先规定好的 交换数据的格式和时序,在发送和接收到信息后应该采取什么样的动作等规则,这些规则就是协议。协议即为事先规定好的规则!

层次模型

模块化

我们在设计研发一个产品或者处理一个比较复杂的事情时,我们往往不是一味的从整体出发,而是将复杂的事情划分成多个小的而且相互独立的模块进行处理,这样子我们可以专注的处理特定的事情,而且分工明确,处理起来效率高。

那么计算网络这么的复杂,当然也是要用到模块化思想了。

分层

之前由国际标准化组织(ISO)提出一个试图使各种计算机在世界范围内互连为网络的标准框架,既开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,缩写为 OSI),简称为OSI模型(OSI model)。它定义了网络互联的7层框架:

OSI

虽然后来整套OSI标准制定出来了,但是当时因特网发展非常迅速,已在覆盖了全世界的很大范围了,所以被得到广泛应用的网络框架并不是OSI而是因特网的TCP/IP的4层框架:

TCP/IP

因为OSI复杂又不实用,TCP/IP结构简单而且得到广泛的使用,在这里结合两者的优点,使用5层协议框架的原理体系进行探讨:

five

现在对每层的作用进行概述理解,接下来对每层进行更细致的探究。

  • 应用层(application layer)。 用来完成特定的网络应用进程间的通信与交互。 应用层的协议定义了应用进程之间通信与交互的规则,应用层的协议有很多的,比如我们熟悉的万维网应用的HTTP协议,文件传送的FTP协议,电子邮件的SMTP协议等。应用层传递的数据单元是报文。

  • 运输层(transport layer)。 为应用层的进程提供传送应用报文服务。运输层的服务分为复用和分用,复用是指多个应用进程之间可以同时使用它下面的运输层服务,而分用则是运输层把收到的信息分别交付给上面的应用层中相对应的进程。 运输层的协议有TCP和UDP:
    • TCP(Transmission Control Protocol),传输控制协议,它是面向连接,可靠的数据服务,传输的单位为报文段
    • UDP(User Datagram Protocol), 用户数据协议,它提供的是无连接的,尽最大的努力传输数据服务,不保证可靠性,传输的单位为用户数据段
  • 网络层(network layer)。 负责的是分组交换网上不同主机进行通信,运输层产生的报文段或者用户数据段会被网络层封装成分组或者进行传送,这里会选择会选择合适的路由将分组进行转发,最后达到目的主机。 因特网主要的网络层协议是无连接的网际协议(Internet Protocol,IP)和许多路由的选择协议。所以因特网的网络层也叫网际层或者IP层。

  • 数据链路层(data link layer)。 链路就是连接节点,数据链路层将分组从链路的一端传送到另一端,传送的数据单元是,每一帧包含了数据必要的控制信息。

  • 物理层(Physical layer)。在传输媒体上传输比特流,将链路层中帧的每一个比特从一个节点传输到下一个节点。传送的数据单位是比特。

written in internet

因特网的组成

因特网按照功能可以划分为:核心部分 和 边缘部分!

因特网的组成

其中,核心部分是由大量的网络和连接这些网络的路由器组成,边缘部分是由连接再因特网上的主机组成。

因特网的边缘部分

边缘部分就是连接到因特网上的所有主机,这些主机也可以叫做端系统,他们可以是笔记本电脑,手机,ipad,也可以是大型的计算机等!

所以我们在了解因特网边缘部分的时候主要是了解边缘部分的主机的工作方式,

边缘部分的主机的c/s工作方式

边缘部分的主机有C/S方式 既客户/服务器的方式:

c-s

这里的服务器端通常是性能比较高的计算机并且24小时运行着服务程序,可以给多个客户端程序发送请求并响应。这就是边缘部分的主机的其中一种工作方式。

边缘部分的额主机的p2p工作方式

p2p是peer-to-peer的缩写,就是对等的意思:

p2p

这里每个主机都运行着p2p程序,那么他们相互之间就可以进行通信。这里就没有所谓的服务请求方和服务提供方,这里的主机都运行着对等程序,所以他们是对等方。


因特网的核心部分

因特网的核心部分使边缘部分的大量主机都能进行通信。

核心部分的分组交换

什么叫做分组?

当我们要发送的整块数据就称为报文

报文

因为整块数据太大不利于传输,所以我们就在这个报文中划分成多个数据段,在每个数据段的前面加上必要的控制信息首部,这样的数据段与首部就构成了一个分组!(分组也叫做包,所以首部叫包头!)

分组

存储转发

分组的首部包含了目的地址和源地址等重要的信息,计算机将分组通过链路发送给路由器,路由器根据分组的首部的目的地址查找转发表然后发送到相对应的端口,把分组交给下一个路由器,这样一步一步经过多个路由器的发送最终发送到目的计算机。因为每个路由器接收到分组都先把分组存储下来然后再转发出去,所以我们叫这种方式为存储转发

存储转发

上图中比如我们想在H1主机发送报文到H5主机。这时候我们的报文会被分成多个分组,分组里面含有首部。这时候H1主机会将分组逐个发给离它最近的路由器R1,(此时只有H1—R1这条链路被占用,其它的链路可以供别的主机使用),R1将收到的分组放入缓存中,这时候R1路由器通过查询路由转发表查询到应该把分组转发到链路(R1-R2),然后分组就发送到R2,接着R2按照上述查询路由转发表发送到R3,R3最后把分组直接发送给主机H5。

  • 当路由器查询转发需要从某条链路转发分组的时候,如果这条链路正忙于传输其它的分组,那么该分组需要在路由器的缓存中等待(排队

  • 当一个分组到达路由器的时候发现路由器的缓存已经满了,这个时候已经在排队的分组之一就会被丢弃,这叫做分组丢失

  • 当网络中有大量的分组需要从链路中转发时出现了分组丢失的情况这叫网络拥塞

written in internet

Android模拟点击,让Button自己戳自己

先看看效果,就是不用我手动去点Button,让系统自己去点它,有一种自虐的感觉:

怎么实现?

首先在xml定义一个Button和一个TextView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:gravity="center"
tools:context="com.wistbean.clickman.MainActivity">

<Button
android:id="@+id/bt_clickme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/clickme"
android:gravity="center"
/>

<TextView
android:id="@+id/tv_describe"
android:layout_marginTop="35dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="48sp"
android:layout_below="@id/bt_clickme"
/>
</RelativeLayout>

很简单,就是一个Button和一个TextView,Button用于被点击,TextView我们用来显示按钮被点击的次数。

接下来就在MainActivity中:

先初始化View并监听点击事件:

 private void initView() {
clickView = (Button) findViewById(R.id.bt_clickme);
clickView.getLocationOnScreen(localtions);
tv = (TextView) findViewById(R.id.tv_describe);

clickView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
  i++;
tv.setText("啊~ 疼 +" + i);
}
});
}

通过clickView.getLocationOnScreen(location)来获取当前这个View的位置,传入的参数是一个数组,其中location[0]代表X,location[1]代表Y,然后监听点击事件,当被点击后i就加1,并让TextView显示出来。

因为这是耗时操作,所以我们开个线程:

    private void startAutoClick() {

   new Thread(){

   @Override
   public void run() {
   super.run();
    while(i<87)
    {
    try {
    sleep(500);
    handler.sendEmptyMessage(1);
    
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

   }
   }.start();
}




` 可以看到,我们这里是每隔500毫秒执行一次,直到点击了88次才停止让它点击。

接下来我们在handler调用自动模拟点击方法:

private Handler handler = new Handler(){

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what==1)
{
motionStart(clickView,localtions[0],localtions[1]);
}


}
};

我们的motionStart是核心功能:

  private void motionStart(View clickView, int x, int y) {

long downTime = SystemClock.uptimeMillis();
MotionEvent downEvent = MotionEvent.obtain(downTime,downTime,MotionEvent.ACTION_DOWN,x,y,0);
downTime += 1000;
MotionEvent upEvent = MotionEvent.obtain(downTime, downTime,
MotionEvent.ACTION_UP, x, y, 0);
clickView.onTouchEvent(downEvent);
clickView.onTouchEvent(upEvent);
downEvent.recycle();
upEvent.recycle();
}

可以看到我们主要是用了MotionEvent,MotionEvent可以得到具体位置的操作事件,然后通过View的onTouchEvent方法传入,就可以实现View的自动操作啦!

written in java

通俗易懂理解java的多线程编程

在谈线程之前,先聊下进程!

进程:

是指正在运行的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,是操作系统动态执行的一个基本单位;

多进程:

当操作系统只有单进程的时候,那么它只能做一件事,而当它有了多进程后,那么它可以做多件事情,比如“同时” 听歌,上网…

线程:

就是进程的基本单位,线程是依赖于进程而存在的,在进程中可以执行多任务,而这些任务就是线程,线程是程序使用cpu的基本单位!

为什么使用java多线程?

因为,使用线程提高了cpu的使用率,提高了程序的执行速度!

java多线程的实现方式

一种是继承Thread重写run方法

public class ThreadDemo extends Thread {

        private int num = 5;
        @Override
        public void run() {
            while(num > 0)
            {
                System.out.println(this.getName()
                +"----"+num--);
            }
        }

}

//main
public class ThreadMain {

    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        ThreadDemo td2 = new ThreadDemo();
        td.setName("thread one");
        td2.setName("thread two");
        td.start();
        td2.start();
    }

}

//输出:
thread one----5
thread two----5
thread one----4
thread two----4
thread one----3
thread one----2
thread two----3
thread one----1
thread two----2
thread two----1

一种是实现Runnable接口重写run方法

public class RunnableDemo implements Runnable {

    private int num = 5;

    @Override
    public void run() {
        while(num>0)
        {
            System.out.println(Thread.currentThread().getName() + "----"  +  num--);
        }
    }


}


//测试类:
public class MainDemo {

    public static void main(String[] args) {
        RunnableDemo rd = new RunnableDemo();
        Thread t = new Thread(rd);
        t.start();
    }

}
//输出结果:
runnable thread----5
runnable thread----4
runnable thread----3
runnable thread----2
runnable thread----1

大多数情况下都是用实现Runnable的方式来实现多线程的,Thread的类其实也是实现了Runnable接口,实现Runnable的方式来实现多线程的好处是解决了单继承的局限性,而且它还可以使用相同的代码去处理同一资源!

java的线程THREAD的安全问题

什么原因会导致线程的安全问题呢?

首先来看个例子:

在一个周末的晚上,哆啦A梦邀请了胖虎,小静,大雄去帮他卖铜锣烧,他们三位分别摆起了地摊,而这时候哆啦A梦拿出了30个铜锣烧希望他们一起卖完这30个!

分析:为了提高卖出铜锣烧的效率,那么就为他们三个人每人开启一个线程,这样就能很快的卖完了呀!

代码:

//定义一个线程类
public class SellTongLuoShao implements Runnable{

    private int num = 30;
    @Override
    public void run() {
        while(num>0)
        {
            try {
                Thread.sleep(300);//现实中没有说不休息就狂卖的!
                System.out.println(
                Thread.currentThread().getName()
                 + " 帮哆啦A梦卖出了第" + num--  + "个铜锣烧");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//main
public class MainDemo {

    public static void main(String[] args) {

        SellTongLuoShao luoShao = new SellTongLuoShao();
        Thread DaXiongThread = new Thread(luoShao);
        Thread XiaoJingThread = new Thread(luoShao);
        Thread PanHuThread = new Thread(luoShao);

        DaXiongThread.setName("大雄");
        XiaoJingThread.setName("小静");
        PanHuThread.setName("胖虎");

        DaXiongThread.start();
        XiaoJingThread.start();
        PanHuThread.start();


    }

}
//输出:
大雄 帮哆啦A梦卖出了第29个铜锣烧
小静 帮哆啦A梦卖出了第30个铜锣烧
胖虎 帮哆啦A梦卖出了第28个铜锣烧
小静 帮哆啦A梦卖出了第27个铜锣烧
大雄 帮哆啦A梦卖出了第27个铜锣烧
胖虎 帮哆啦A梦卖出了第26个铜锣烧
大雄 帮哆啦A梦卖出了第25个铜锣烧
小静 帮哆啦A梦卖出了第24个铜锣烧
胖虎 帮哆啦A梦卖出了第23个铜锣烧
大雄 帮哆啦A梦卖出了第22个铜锣烧
小静 帮哆啦A梦卖出了第21个铜锣烧
胖虎 帮哆啦A梦卖出了第20个铜锣烧
小静 帮哆啦A梦卖出了第19个铜锣烧
大雄 帮哆啦A梦卖出了第18个铜锣烧
胖虎 帮哆啦A梦卖出了第17个铜锣烧
小静 帮哆啦A梦卖出了第15个铜锣烧
大雄 帮哆啦A梦卖出了第16个铜锣烧
胖虎 帮哆啦A梦卖出了第14个铜锣烧
大雄 帮哆啦A梦卖出了第13个铜锣烧
小静 帮哆啦A梦卖出了第12个铜锣烧
胖虎 帮哆啦A梦卖出了第11个铜锣烧
小静 帮哆啦A梦卖出了第10个铜锣烧
大雄 帮哆啦A梦卖出了第9个铜锣烧
胖虎 帮哆啦A梦卖出了第8个铜锣烧
大雄 帮哆啦A梦卖出了第6个铜锣烧
小静 帮哆啦A梦卖出了第7个铜锣烧
胖虎 帮哆啦A梦卖出了第5个铜锣烧
小静 帮哆啦A梦卖出了第4个铜锣烧
大雄 帮哆啦A梦卖出了第3个铜锣烧
胖虎 帮哆啦A梦卖出了第2个铜锣烧
小静 帮哆啦A梦卖出了第1个铜锣烧
胖虎 帮哆啦A梦卖出了第0个铜锣烧
大雄 帮哆啦A梦卖出了第-1个铜锣烧

从输出结果可以看出,这是非常不符合逻辑的,不仅卖出了第0个,还有第负个,有些还是重复卖出,这并不是我们想要的结果,这样就导致了线程不安全,可是,这是为什么呢?

因为这段代码:

while(num>0) 
{ 
try { 
Thread.sleep(300);//现实中没有说不休息就狂卖的! 
System.out.println(Thread.currentThread().getName() + ” 帮哆啦A梦卖出了第” + num– + “个铜锣烧”); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
}

当大雄start了线程,开卖的时候,遇到了sleep方法,就在那里睡了300ms,如果这个时候,胖虎把最后一个铜锣烧给卖掉了,然后大雄醒来后,还继续执行接下来的代码! 这就导致了卖出第0个铜锣烧了!

如何解决呢?

java针对这种情况给我们配了一把锁!这把锁是来锁一个被共同操作的代码块,这样子,把:

try { 
Thread.sleep(300);//现实中没有说不休息就狂卖的! 
System.out.println(Thread.currentThread().getName() + ” 帮哆啦A梦卖出了第” + num– + “个铜锣烧”); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 

这段代码给锁住之后,当大雄start后进来此run方法后,尽管他在里面睡了,但是别人也进不来,只有等大雄在run方法执行完,锁才会释放掉,这时候别人才能进来!这样就保证了线程的安全性!

那么。java线程锁长什么样呢?

长这样:

synchronized(对象){被锁住的代码}

将代码进行修复:

public class SellTongLuoShao implements Runnable{

    private int num = 30;
    @Override
    public void run() {
        while(num>0)
        {
            synchronized (new Object()) {//加锁
                try {
                    Thread.sleep(300);//现实中没有说不休息就狂卖的!
                    Thread.currentThread().getName() 
                    + " 帮哆啦A梦卖出了第" + num--  + "个铜锣烧");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

}

//main
public class MainDemo {

    public static void main(String[] args) {

        SellTongLuoShao luoShao = new SellTongLuoShao();
        Thread DaXiongThread = new Thread(luoShao);
        Thread XiaoJingThread = new Thread(luoShao);
        Thread PanHuThread = new Thread(luoShao);

        DaXiongThread.setName("大雄");
        XiaoJingThread.setName("小静");
        PanHuThread.setName("胖虎");

        DaXiongThread.start();
        XiaoJingThread.start();
        PanHuThread.start();


    }

}
//输出:
大雄 帮哆啦A梦卖出了第29个铜锣烧
胖虎 帮哆啦A梦卖出了第28个铜锣烧
小静 帮哆啦A梦卖出了第30个铜锣烧
大雄 帮哆啦A梦卖出了第26个铜锣烧
胖虎 帮哆啦A梦卖出了第25个铜锣烧
小静 帮哆啦A梦卖出了第27个铜锣烧
胖虎 帮哆啦A梦卖出了第24个铜锣烧
大雄 帮哆啦A梦卖出了第22个铜锣烧
小静 帮哆啦A梦卖出了第23个铜锣烧
大雄 帮哆啦A梦卖出了第21个铜锣烧
小静 帮哆啦A梦卖出了第19个铜锣烧
胖虎 帮哆啦A梦卖出了第20个铜锣烧
大雄 帮哆啦A梦卖出了第18个铜锣烧
小静 帮哆啦A梦卖出了第16个铜锣烧
胖虎 帮哆啦A梦卖出了第17个铜锣烧
小静 帮哆啦A梦卖出了第14个铜锣烧
大雄 帮哆啦A梦卖出了第13个铜锣烧
胖虎 帮哆啦A梦卖出了第15个铜锣烧
胖虎 帮哆啦A梦卖出了第11个铜锣烧
小静 帮哆啦A梦卖出了第10个铜锣烧
大雄 帮哆啦A梦卖出了第12个铜锣烧
大雄 帮哆啦A梦卖出了第9个铜锣烧
胖虎 帮哆啦A梦卖出了第7个铜锣烧
小静 帮哆啦A梦卖出了第8个铜锣烧
胖虎 帮哆啦A梦卖出了第6个铜锣烧
小静 帮哆啦A梦卖出了第5个铜锣烧
大雄 帮哆啦A梦卖出了第4个铜锣烧
大雄 帮哆啦A梦卖出了第3个铜锣烧
小静 帮哆啦A梦卖出了第1个铜锣烧
胖虎 帮哆啦A梦卖出了第2个铜锣烧

现在,就不会出现线程的安全性问题了!这也就是同步,虽然同步提高了线程的安全性,但是也降低了线程的运行效率!如果出现同步嵌套就容易会出现死锁问题!

除了这样的锁对象之外呢!还有一种是方法锁(同步方法),这种锁是在方法的修饰符后添加synchronized关键字!它的锁对象是this(当前对象!)

public class SellTongLuoShao implements Runnable{

    private int num = 30;

    @Override
    public synchronized void run() { //方法锁(同步方法)

        while(num>0)
        {
                try {

                    Thread.sleep(300);//现实中没有说不休息就狂卖的!
                    System.out.println(
                     + " 帮哆啦A梦卖出了第" + num--  + "个铜锣烧");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

        }
    }

}

如果这个方法是静态的话,那么锁对象就是class文件,也就是类名.class,不是静态的话就是this(当前对象!)

JDK5之后有了Lock锁,Lock锁和synchronized锁的主要区别是Lock锁可以在等待很长时间锁还没释放而自动放弃锁,synchronized锁是JVM层实现的,所以系统可以知道锁是什么时候释放的,而Lock锁是代码实现的,所以一般在代码的finally处添加unlock方法

public class SellTongLuoShao implements Runnable{

    private int num = 30;
    private Lock rLook = new ReentrantLock(); //创建锁对象
    @Override
    public void run() {

        rLook.lock();  //锁住
        try { 
            while(num>0)
            {
                try {
                    Thread.sleep(300);//现实中没有说不休息就狂卖的!
                    System.out.println(
                    " 帮哆啦A梦卖出了第" + num--  + "个铜锣烧");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
        finally
        {
            rLook.unlock(); //释放锁
        }

    }

}
//main
        public static void main(String[] args) {

        SellTongLuoShao luoShao = new SellTongLuoShao();
        Thread DaXiongThread = new Thread(luoShao);
        Thread XiaoJingThread = new Thread(luoShao);
        Thread PanHuThread = new Thread(luoShao);

        DaXiongThread.setName("大雄");
        XiaoJingThread.setName("小静");
        PanHuThread.setName("胖虎");

        DaXiongThread.start();
        XiaoJingThread.start();
        PanHuThread.start();


    }

}

这样也可以解决线程的安全性!

这里要注意一下线程的死锁问题!

java的线程死锁

出现死锁问题是因为对象在执行同步代码的时候,出现相互等待的情况

代码体现:

//创建两把锁
public class MyLock {

    public static Object obj1 = new Object();
    public static Object obj2 = new Object();

}



public class DeadLockDemo extends Thread{

    private boolean flag;
    public  DeadLockDemo(boolean flag) {

        this.flag = flag;
    }

    public void run() {
        while(true)
        {
            if(flag)
            {
                synchronized (MyLock.obj1) {
                System.out.println("if 语句, 用的是obj1的锁" );
                    synchronized (MyLock.obj2) {
                        System.out.println("if 语句,用的是obj2的锁");
                    }
                }
            }else
            {
                synchronized (MyLock.obj2) {
                    System.out.println("else 语句 用的是obj2的锁");
                    synchronized (MyLock.obj1) {
                        System.out.println("else 语句 用的是obj1的锁");
                    }
                }
            }
        }

    }


}
//main
public class MainDemo {

        public static void main(String[] args) {
            DeadLockDemo dd = new DeadLockDemo(true);
            DeadLockDemo dd2 = new DeadLockDemo(false);

            dd.start();
            dd2.start();
        }
}

//输出:
if 语句, 用的是obj1的锁
else 语句 用的是obj2的锁

这时候就出现死锁的情况,他们在互相等待!

线程是具有随机性的,那个线程先抢到cpu的执行权,那么它就会先执行!那么如果想要让一个线程先执行要咋办呢?

java的Join线程

使用join方法就可以优先执行这个线程! 比如:

public class SellTongLuoShao implements Runnable {

    private int num = 30;

    @Override
    public synchronized void run() {

            while (num > 0) {
                try {
                    Thread.sleep(300);// 现实中没有说不休息就狂卖的!
                    System.out.println(Thread.currentThread().getName()
                            + " 帮哆啦A梦卖出了第" + num-- + "个铜锣烧");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }

}

//main
    public static void main(String[] args) throws InterruptedException {

        SellTongLuoShao luoShao = new SellTongLuoShao();
        Thread DaXiongThread = new Thread(luoShao);
        Thread XiaoJingThread = new Thread(luoShao);
        Thread PanHuThread = new Thread(luoShao);

        DaXiongThread.setName("大雄");
        XiaoJingThread.setName("小静");
        PanHuThread.setName("胖虎");


        PanHuThread.start();
        PanHuThread.join();//join
        DaXiongThread.start();
        XiaoJingThread.start();


    }

}
//输出结果:
胖虎 帮哆啦A梦卖出了第30个铜锣烧
胖虎 帮哆啦A梦卖出了第29个铜锣烧
胖虎 帮哆啦A梦卖出了第28个铜锣烧
胖虎 帮哆啦A梦卖出了第27个铜锣烧
胖虎 帮哆啦A梦卖出了第26个铜锣烧
胖虎 帮哆啦A梦卖出了第25个铜锣烧
胖虎 帮哆啦A梦卖出了第24个铜锣烧
胖虎 帮哆啦A梦卖出了第23个铜锣烧
胖虎 帮哆啦A梦卖出了第22个铜锣烧
胖虎 帮哆啦A梦卖出了第21个铜锣烧
胖虎 帮哆啦A梦卖出了第20个铜锣烧
胖虎 帮哆啦A梦卖出了第19个铜锣烧
胖虎 帮哆啦A梦卖出了第18个铜锣烧
胖虎 帮哆啦A梦卖出了第17个铜锣烧
胖虎 帮哆啦A梦卖出了第16个铜锣烧
胖虎 帮哆啦A梦卖出了第15个铜锣烧
胖虎 帮哆啦A梦卖出了第14个铜锣烧
胖虎 帮哆啦A梦卖出了第13个铜锣烧
胖虎 帮哆啦A梦卖出了第12个铜锣烧
胖虎 帮哆啦A梦卖出了第11个铜锣烧
胖虎 帮哆啦A梦卖出了第10个铜锣烧
胖虎 帮哆啦A梦卖出了第9个铜锣烧
胖虎 帮哆啦A梦卖出了第8个铜锣烧
胖虎 帮哆啦A梦卖出了第7个铜锣烧
胖虎 帮哆啦A梦卖出了第6个铜锣烧
胖虎 帮哆啦A梦卖出了第5个铜锣烧
胖虎 帮哆啦A梦卖出了第4个铜锣烧
胖虎 帮哆啦A梦卖出了第3个铜锣烧
胖虎 帮哆啦A梦卖出了第2个铜锣烧
胖虎 帮哆啦A梦卖出了第1个铜锣烧

java中线程常常提到的就是守护线程,什么是守护线程呢?

java守护线程

就是当线程它成为了守护的时候,那么它就是肩负着守护的使命,当别的线程没了的时候,它也跟着没!!

public class SellTongLuoShao implements Runnable {

    private int num = 30;

    @Override
    public synchronized void run() {

            while (num > 0) {
                try {
                    Thread.sleep(300);// 现实中没有说不休息就狂卖的!
                    System.out.println(Thread.currentThread().getName()
                            + " 帮哆啦A梦卖出了第" + num-- + "个铜锣烧");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }

}
//main

public class MainDemo {

    public static void main(String[] args) throws InterruptedException {

        SellTongLuoShao luoShao = new SellTongLuoShao();
        Thread DaXiongThread = new Thread(luoShao);
        Thread XiaoJingThread = new Thread(luoShao);
        Thread PanHuThread = new Thread(luoShao);

        DaXiongThread.setName("大雄");
        XiaoJingThread.setName("小静");
        PanHuThread.setName("胖虎");
        //设置守护
        PanHuThread.setDaemon(true);
        DaXiongThread.setDaemon(true);
        XiaoJingThread.setDaemon(true);

        PanHuThread.start();
        DaXiongThread.start();
        XiaoJingThread.start();


        for(int i=0;i<4;i++)
        {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "----------------" + i);
        }


    }

}
//结果
胖虎 帮哆啦A梦卖出了第30个铜锣烧
胖虎 帮哆啦A梦卖出了第29个铜锣烧
胖虎 帮哆啦A梦卖出了第28个铜锣烧
main----------------0
胖虎 帮哆啦A梦卖出了第27个铜锣烧
胖虎 帮哆啦A梦卖出了第26个铜锣烧
胖虎 帮哆啦A梦卖出了第25个铜锣烧
main----------------1
胖虎 帮哆啦A梦卖出了第24个铜锣烧
胖虎 帮哆啦A梦卖出了第23个铜锣烧
胖虎 帮哆啦A梦卖出了第22个铜锣烧
main----------------2
胖虎 帮哆啦A梦卖出了第21个铜锣烧
胖虎 帮哆啦A梦卖出了第20个铜锣烧
胖虎 帮哆啦A梦卖出了第19个铜锣烧
胖虎 帮哆啦A梦卖出了第18个铜锣烧
main----------------3

可以看到,当主线程运行完毕之后,守护线程也跟着完毕

java多线程的等待唤醒机制

生产消费的问题:

比如哆啦A梦在生产铜锣烧,大雄在吃,而每当哆啦A梦生产一个,大雄就吃一个,哆啦A梦要先看有没有铜锣烧,有就等待,没有就生产一个(这时候就要用到同步),而大雄也是先看有没有铜锣烧,没有就等待,有就拿去吃(一样也需要用到同步!)。

这个时候如何保证他们可以如此进行下去不出错呢? 这时候哦就要用到等待唤醒机制了,分别是用到Object中的notify(唤醒)和wait(唤醒)方法!这两个方法的使用必须用锁对象来调用!

//铜锣烧类

public class TongLuoShao {

    private String tongluoshao  ;
    public boolean flag;//用来判断

    public String getTongluoshao() {
        return tongluoshao;
    }

    public void setTongluoshao(String tongluoshao) {
        this.tongluoshao = tongluoshao;
    }

}

//哆啦A梦
public class DuoLaAMeng implements Runnable{

    private TongLuoShao tongluoshao;
    private int x = 0;


    public DuoLaAMeng(TongLuoShao tongluoshao) {

        this.tongluoshao = tongluoshao;

    }



    @Override
    public void run() {

        synchronized (tongluoshao) {
            while(true)
            {
                //没有就生产,有就等待
                if(tongluoshao.flag)
                {
                    try {
                        tongluoshao.wait();//wait之后,锁就释放掉了!
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if(x%2==0)
                {
                    tongluoshao.setTongluoshao("热腾腾的铜锣烧");
                }else
                {
                    tongluoshao.setTongluoshao("冰冻冻的铜锣烧");
                }
                x++;
                //修改标记
                tongluoshao.flag = true;
                //唤醒对方!(让大雄吃)
                tongluoshao.notify();
            }
        }

    }

}

//大雄
public class DaXiong implements Runnable {

    private TongLuoShao tongluoshao;

    public DaXiong(TongLuoShao tongluoshao) {
        this.tongluoshao = tongluoshao;
    }

    @Override
    public void run() {
        synchronized (tongluoshao) {
            //没有就等待,有就吃!
            while(true)
            {
                if(!tongluoshao.flag)
                {
                    try {
                        tongluoshao.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("吃----" + tongluoshao.getTongluoshao());
                tongluoshao.flag = false;
				//唤醒对方!(让哆啦A梦生产)
                tongluoshao.notify();
            }
        }
    }

}

//main
public class MainDemo {

    public static void main(String[] args) {
        TongLuoShao tls = new TongLuoShao();

        DuoLaAMeng aMeng = new DuoLaAMeng(tls);
        DaXiong daXiong =  new DaXiong(tls);

        Thread t1 = new Thread(aMeng);
        Thread t2 = new Thread(daXiong);

        t1.start();
        t2.start();
    }

}

输出:
吃----热腾腾的铜锣烧
吃----冰冻冻的铜锣烧
吃----热腾腾的铜锣烧
吃----冰冻冻的铜锣烧
吃----热腾腾的铜锣烧
吃----冰冻冻的铜锣烧
吃----热腾腾的铜锣烧
吃----冰冻冻的铜锣烧
吃----热腾腾的铜锣烧
吃----冰冻冻的铜锣烧
吃----热腾腾的铜锣烧

java线程组

可以将线程列入到一个线程组里面,可以方便管理线程!

	DuoLaAMeng aMeng = new DuoLaAMeng(tls);
    DaXiong daXiong =  new DaXiong(tls);

    ThreadGroup tg = new ThreadGroup("team");

    Thread t1 = new Thread(tg,aMeng);
    Thread t2 = new Thread(tg,daXiong);

    tg.setDaemon(true);

将DaXiong 和 DuoLaAMeng 添加到线程组,那么操作线程组可以统一管理线程!如上就是把线程组中的线程设置为守护线程!

java线程池

线程池中的线程用完之后不会死亡!可以回收,等待下一个对象的使用!提高了性能! JDk5后有了Executors工厂类来产生线程池!

public class RunnableDemo implements Runnable{

    private int num = 10;

    @Override
    public void run() {

        while(num>0)
        {
            System.out.println(Thread.currentThread().getName() + "----------------" + num--);
        }

    }

}

//main
public class ExecutorDemo {

        public static void main(String[] args) {
            ExecutorService pool = Executors.newFixedThreadPool(2);
            pool.submit(new RunnableDemo());
            pool.submit(new RunnableDemo());

        }

}

java线程的第三种创建方式

借助线程池,使用到Callable接口

public class CallableDemo implements Callable<Integer> {


    private int num ;
    public CallableDemo(int num) {
        this.num  = num;
    }

    @Override
    public Integer call() throws Exception {

        num  = num + 90;

        return num;
    }


}

//main
public class MainDemo {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(2);
        Future<Integer> future = pool.submit(new CallableDemo(10));
        Future<Integer> future2 = pool.submit(new CallableDemo(100));

        try {
            System.out.println(future.get());
            System.out.println(future2.get());
        } catch (InterruptedException | ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

java线程的生命周期

新建—> 就绪 —> 运行 —>死亡!

新建:线程对象被创建出来; 就绪:线程被start后,进入就绪状态,此时具有执行的资格,但是还没有 执行的权利; 运行:线程有执行的资格和权利; 死亡:线程变成垃圾,等待被回收!

我的相关文章

相关资料

written in java

Java 细数各种常见的集合:List,Set,Map

为什么会出现java集合?

——这是因为在面向对象编程中,需要用一种容器来装对象这种元素!

那数组不是可以装元素吗?

——数组是可以装元素,但是数组有一定的局限性,它的长度是不可改变的,是固定的,而集合的默认长度是10,但是它是可以动态添加的!

而且数组不但可以装引用类型,还可以装一般的数据类型,而集合是专门来装引用类型的!

java集合继承体系

由于集合的存储方式(数据结构)不同,比如一些需求是要讲元素按顺序的存储,而一些需求是要将元素不能重复的存储,这时候就分出了许多集合来对应这种需求,那么这么多的集合里面,肯定有一些共同的属性,这时候就有了继承的体系!

文档中可以看到Collection:

java.util Interface Collection

Type Parameters: E - the type of elements in this collection

All Superinterfaces: Iterable

All Known Subinterfaces:

BeanContext, BeanContextServices, BlockingDeque, BlockingQueue, Deque, **List**, NavigableSet, Queue, **Set**, SortedSet, TransferQueue

All Known Implementing Classes:

AbstractCollection, AbstractList, AbstractQueue, AbstractSequentialList, AbstractSet, ArrayBlockingQueue, ArrayDeque, ArrayList, AttributeList, BeanContextServicesSupport, BeanContextSupport, ConcurrentHashMap.KeySetView, ConcurrentLinkedDeque, ConcurrentLinkedQueue, ConcurrentSkipListSet, CopyOnWriteArrayList, CopyOnWriteArraySet, DelayQueue, EnumSet,** HashSet, JobStateReasons, LinkedBlockingDeque, LinkedBlockingQueue, LinkedHashSet, **LinkedList, LinkedTransferQueue, PriorityBlockingQueue, PriorityQueue, RoleList, RoleUnresolvedList, Stack, SynchronousQueue, TreeSet, Vector

以上黑体字部分是常见的集合!当然还有Map集合,后面会提到!

简化一下:

  • Collection
  • List - ArrayList(最常用) - LinkedList - Vector
  • Set - HashSet - TreeSet

首先明确List集合和Set集合的区别:

Java的List集合 :

是有序的(存储元素的顺序和取出元素的顺序是相同的) 
可重复的(存储的元素可以使相同的)

代码体现:

public class ListDemo {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("1");
        list.add("1");
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        Iterator i = list.iterator();
        while(i.hasNext())
        {
            String s = (String) i.next();
            System.out.println(s);
        }
    }
}

//输出结果:
1
1
1
2
3
4
5
//从输出结果中可以看出,存入数据的顺序和取出数据的顺序是一样的,而且1是可以重复的

ArrayList、Vector底层的数据结构是数组,具有查询快,增删慢的特点! LinkedList底层的数据结构是链表,具有增删快,查询慢的特点!

Java的Set集合

是无序的(它不保证集合的迭代顺序;特别是它不保证该顺序恒久不变。) 
是唯一的(存储到集合中的元素是不可重复的)

代码体现:

public class SetDemo {

    public static void main(String[] args) {
        Set set = new HashSet();
        set.add("1");
        set.add("1");
        set.add("1");
        set.add("2");
        set.add("3");
        set.add("4");
        set.add("5");

        Iterator iterator = set.iterator();
        while(iterator.hasNext())
        {
            System.out.println(iterator.next());
        }
    }

}

//输出结果:
3
2
1
5
4
//从中可以看出顺序和存储的时候不同,而且1只输出一次(唯一性)

为什么Set集合中的元素是不可重复的呢?

从源代码可以看到:

public HashSet() {
        map = new HashMap<>();
    }
//当new 一个HashSet的时候,它去创建了一个HashMap

//执行add方法时:
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    //返回的是put方法!

//而put方法:(简单讲就是判断添加的值之前是否加过!)
  public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);//这里有个hash方法
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))//这里有个equals的判断{
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

//再来看一下hash方法:
  final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();//这里用了hashCode方法


        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }


//从中可以看到。底层是用了hashcode 和 equals 方法! 所以才会保证了唯一性!

验证一下以上的结论是否正确,我们写一个学生类,里面不用重写hashcode和equals的方法,看看是不是就可以重复了!

//创建学生类:
public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Student() {
        super();
        // TODO 自动生成的构造函数存根
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

}

//main测试:
public class HashSetDemo {
    public static void main(String[] args) {
        //创建集合
        HashSet<Student> hash = new HashSet<Student>();
        //创建学生对对象
        Student s1 = new Student("one",1);
        Student s2 = new Student("two",2);
        Student s3 = new Student("two",2);
        Student s4 = new Student("three",3);
        Student s5 = new Student("four",4);
        Student s6 = new Student("four",4);
        Student s7 = new Student("five",5);
        //添加
        hash.add(s1);
        hash.add(s2);
        hash.add(s3);
        hash.add(s4);
        hash.add(s5);
        hash.add(s6);
        hash.add(s7);
        //遍历
        for(Student s : hash)
        {
            System.out.println(s.getAge() + s.getName());   
        }
    }
}
//输出结果:
2two
3three
2two
4four
1one
5five
4four
//很明显可以看到元素2two重复了!

那重写Student类中的hashcode和equals方法看看:

public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Student() {
        super();
        // TODO 自动生成的构造函数存根
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

}

//main:
public class HashSetDemo {
    public static void main(String[] args) {
        //创建集合
        HashSet<Student> hash = new HashSet<Student>();
        //创建学生对对象
        Student s1 = new Student("one",1);
        Student s2 = new Student("two",2);
        Student s3 = new Student("two",2);
        Student s4 = new Student("three",3);
        Student s5 = new Student("four",4);
        Student s6 = new Student("four",4);
        Student s7 = new Student("five",5);
        //添加
        hash.add(s1);
        hash.add(s2);
        hash.add(s3);
        hash.add(s4);
        hash.add(s5);
        hash.add(s6);
        hash.add(s7);
        //遍历
        for(Student s : hash)
        {
            System.out.println(s.getAge() + s.getName())
        }
    }
}
//输出:
3three
4four
1one
2two
5five
//看,不重复了,因此:以上结论是正确的!

那么,有没有一种是不重复但是有序的特点的集合呢?有的,比如TreeSet集合! 它为什么会有序呢? 因为它的底层用了:comparable的compareto方法! 它的底层数据结构是**二叉树 **

TreeSet有两种排序方式: 1. 自然排序:TreeSet() 构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。

  1. 比较器器排序:TreeSet(Comparator comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。

首先看自然排序:

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Integer> tree = new TreeSet<Integer>();

        tree.add(15);
        tree.add(15);
        tree.add(18);
        tree.add(20);
        tree.add(20);
        tree.add(32);
        tree.add(32);
        tree.add(56);
        tree.add(2);

        for(Integer i : tree)
        {
            System.out.println(i);
        }
    }
}
//输出结果:
2
15
18
20
32
56
//可以发现,它自然排序了而且是唯一的。注意:Iteger重写了compareTo方法!

选择器排序:

//定义学生类对学生成绩进行排序:
public class Student {
    private String name;
    private int YuWenScore;
    private int ShuXueScore;
	private int yingYucore;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getYuWenScore() {
        return YuWenScore;
    }
    public void setYuWenScore(int yuWenScore) {
        YuWenScore = yuWenScore;
    }
    public int getShuXueScore() {
        return ShuXueScore;
    }
    public void setShuXueScore(int shuXueScore) {
        ShuXueScore = shuXueScore;
    }
    public int getYingYucore() {
        return YingYucore;
    }
    public void setYingYucore(int yingYucore) {
        YingYucore = yingYucore;
    }

    public Student(String name, int yuWenScore, int shuXueScore, int yingYucore) {
        super();
        this.name = name;
        this.YuWenScore = yuWenScore;
        this.ShuXueScore = shuXueScore;
        this.YingYucore = yingYucore;
    }

    public Student() {
        super();
        // TODO 自动生成的构造函数存根
    }

    public int getSum()
    {
        return this.getShuXueScore() + this.getYingYucore() + this.getYuWenScore();
    }

}

//mian
public class ArrangeMain {
    public static void main(String[] args) {
		//自定义按成绩排序
        TreeSet<Student> ts = new TreeSet<Student>(
                new Comparator<Student>() {
                    public int compare(Student o1, Student o2) {                        
                        //总分从高到低:主要条件
                        int num = o2.getSum() - o1.getSum();
                        //总分相同不一定数学成绩相同
                        int num2 = num == 0 ? o2.getShuXueScore() - o1.getShuXueScore():num;
                        //总分相同不一定语文成绩相同
                        int num3 = num2 == 0 ? o2.getYuWenScore() - o1.getYuWenScore():num2;
                        //总分相同不一定英语成绩相同
                        int num4 = num3 == 0 ? o2.getYingYucore() - o1.getYingYucore():num3;
                        //姓名不一定相同
                        int num5 = num4 == 0 ? o2.getName().compareTo(o1.getName()):num4;
                        return num5;
                    }
                }

                );
        for(int i=0;i<3;i++)
        {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入姓名:");
            String name = sc.nextLine();
            System.out.println("请输入语文成绩:");
            int Yuwen = sc.nextInt();
            System.out.println("请输入数学成绩:");
            int ShuXue = sc.nextInt();
            System.out.println("请输入英语成绩:");
            int YingYu = sc.nextInt();

            Student s = new Student();
            s.setName(name);
            s.setShuXueScore(ShuXue);
            s.setYingYucore(YingYu);
            s.setYuWenScore(Yuwen);

            ts.add(s);
        }
        int i = 0;
        for(Student stu : ts)
        {
            System.out.println(stu.getName() +" 的数学成绩:"+ stu.getShuXueScore()
                    +" 语文成绩:"+ stu.getYuWenScore() +" 英语成绩:"+ stu.getYingYucore()
                    +"排名第" + ++i);
        }
    }
}


控制台:
请输入姓名:
小明
请输入语文成绩:
100
请输入数学成绩:
100
请输入英语成绩:
100
请输入姓名:
小花
请输入语文成绩:
99
请输入数学成绩:
99
请输入英语成绩:
100
请输入姓名:
小李
请输入语文成绩:
60
请输入数学成绩:
100
请输入英语成绩:
100
小明 的数学成绩:100 语文成绩:100 英语成绩:100排名第1
小花 的数学成绩:99 语文成绩:99 英语成绩:100排名第2
小李 的数学成绩:100 语文成绩:60 英语成绩:100排名第3

再来说说Java的Map集合:

Map集合是元素成对出现的,键值对。键是唯一(set)的,值是可重复(List)的; 通常以put方法存储元素,以get()方法获取数据

与以上对应,HashMap无序,不重复,TreeMap有序,不重复,(针对键说的!)

我的相关文章

相关资料

written in java

Java 内部类--定义在一个类中的类

看完就会明白:

  1. 1.java内部类是什么?

  2. 2.java成员内部类是什么?

  3. 3.java静态内部类是什么?

  4. 4.java内部类的封装是什么?

  5. 5.java局部内部类是什么?

  6. 6.java匿名内部类是什么?

  7. 7.java内部类的特点是什么?

java内部类就是在一个类里面定义类!

像这样:

public class OuterClass {

    class InnerClass
    {

    }

}

java成员内部类:

public class OuterClass {

    class InnerClass
    {

    }

}

成员内部类的使用:

 class OuterClass {

     class InnerClass
     {
         public void method()
         {
             System.out.println("innerclass's method....");
         }
     }

}

//测试类:
public class MainDemo {
    public static void main(String[] args) {
        OuterClass.InnerClass innerdemo 
        = new OuterClass().new InnerClass();
        innerdemo.method();
    }
}

//输出结果:innerclass's method....

所以:成员内部类的实例化特点就是:

外部类名.内部类名 对象名 = new 外部类.new 内部类

java内部类的特点:

  1. 内部类可以访问外部类的成员,包括私有
–java虚拟机给内部类定义了指向外部类的引用!
  1. 外部类想要访问内部类的成员,必须实例化内部类!
–类中的成员想要调用类,就得实例化!

代码体现:

public class OuterClass {

     private String name = "外部类的成员变量";

     public class InnerClass
     {

         void method()
         {
            //内部类可以访问外部类的成员
             System.out.println(name);
             OuterClass.this.hahaha();
         }

     }

     public void function()
     {
        //调用内部类需要实例化
         InnerClass inner = new InnerClass();
         inner.method();
     }

   public void hahaha()
     {
         System.out.println("hahah");
     }


}


//测试类
public class MainDemo {
    public static void main(String[] args) {
        OuterClass.InnerClass innerdemo 
        = new OuterClass().new InnerClass();
        innerdemo.method();
    }
}
//输出结果:外部类的成员变量
           hahah

java静态内部类

成员内部类可以被static、private、protected声明,而普通的类是不可以的,普通的类只能被public或者默认,为什么呢?因为成员内部类要注意到成员二字,就像成员变量成员方法一样! so,当成员内部类被static修饰后就成了静态内部类!

如:

 class OuterClass {

    static class InnerClass
    {

    }

} 

静态内部类的使用: 静态是当类创建的时候就存在了的,所以在静态内部类想用调用外部类的成员,就只能调用外部类的静态成员,因为普通成员还没有被实例化出来!

代码体现:

 public class OuterClass {

     private static String name = "外部类的成员变量";

     static class InnerClass
     {
         void method()
         {
             System.out.println(name);
         }
     }

}

//测试类:
public class MainDemo {
    public static void main(String[] args) {
        OuterClass.InnerClass innerdemo
         = new OuterClass.InnerClass();
        innerdemo.method();
    }
}

//输出结果:外部类的成员变量

静态内部类的实例化:

new 外部类.内部类();

java内部类的封装

当成员内部类被private修饰时:

 public class OuterClass {


    private class InnerClass{
        void method()
        {
            System.out.println("我是被私有了的内部类");
        }
    }


    public void function()
    {

        new InnerClass().method();
    }

}


//测试类:
public class MainDemo {
    public static void main(String[] args) {
            OuterClass out = new OuterClass();
            out.function();
    }
}

//输出结果:我是被私有了的内部类

可以看到,你在调用OuterClass的function方法的时候,它会去调用内部类的方法,而不像之前我们直接去调用InnerClass,因为InnerClass被private了,所以无法new!也就是说:成员内部类被private修饰后就具有封装性!只能通过实例化外部类来实现!

java局部内部类

类定义在方法内:

public class OuterClass {

    public void mathod()
    {
        class InnerClass
        {

        }
    }

}

需要注意的是: 局部内部类访问局部变量必须用final修饰! 局部变量是放在栈里的,当方法用完之后,它就消失了 而对象是在堆内存里的,方法运行完之后,它还没消失 这时候类用的变量不能消失,所以就要加final,加 final之后局部的变量变成常量,常量放在静态区,这时候常量不会消失!

代码体现:

 public class OuterClass {

     public void method()
     {
         final String name = "我是一个局部的成员变量!";
         //此时name就变成常量了!!

         class InnerClass{
             void innerfunction()
             {
                 System.out.println(name);
             }
         }

         InnerClass inner = new InnerClass();
         inner.innerfunction();
     }

}


//测试类:
public class MainDemo {
    public static void main(String[] args) {
        OuterClass out = new OuterClass();
        out.method();

    }
}

//输出结果:我是一个局部的成员变量!

java匿名内部类

匿名内部类的前提是:

  • 有类(可以使抽象类也可以使普通类)或者接口

格式:

  • new 类名或者接口名(){重写方法};

代码体现:

//定义一个接口,接口里面有一个抽象方法onClik
interface OnClickListner {

    public void onClik();

}

//这是一个普通类,setOnClikListner方法传入接口,调用onClik抽象方法。
public class Hand {

        public void setOnClikListner(OnClickListner listner)
        {
            listner.onClik();
        }

}

//测试:
    public class MainDemo {
    public static void main(String[] args) {
        Hand hand = new Hand();
		//匿名内部类的使用
        hand.setOnClikListner(new OnClickListner() {

            @Override
            public void Onclik() {
                // TODO Auto-generated method stub
                System.out.println("hahaha");
            }
        });
    }
}

//输出结果:hahaha

从中可以看到匿名内部类的: 本质:

  • 是一个已经实现了类或者接口的子类对象!

好处:

  • 用完就被垃圾回收机制回收!栈内存不指向它了!

我的相关文章

相关资料

written in java

Java 抽象与接口相似中带点差异与区别

java继承的前提下,抽象和接口就有了!

Java抽象:

为什么要用到抽象呢?

这是由于很多具体类它们有它们各自的方法体现,而父类完全没有必要去写出方法的具体实现,反正子类都想各干各自的事情,那么父类只要把方法声明出来,定义成抽象,让子类自己去实现功能就好了! 抽象的作用就是建立一个宏观的结构,具体实现层只要实现算法和逻辑!

抽象以abstract为关键字修饰。

Java抽象的优点:

在抽象类中,可以在里面定义一些方法的实现!这样,当子类继承了这个抽象类,那么抽象类所实现的方法在子类也可以用!

代码体现:

//抽象类,注意:有抽象方法就一定有抽象类,但是有抽象类不一定要有抽象方法!

public abstract class AbsPerson {

        public abstract void eat();

        public void sleep()
        {
            System.out.println("闭着眼睛睡觉觉");
        }

}


//实现类
public class Student extends AbsPerson {

    @Override
    public void eat() {
        System.out.println("吃肉!");
    }

}

//测试类
public class MainDemo {

    public static void main(String[] args) {
        Student s = new Student();
        s.eat();
        s.sleep();
    }

}

//输出结果:
    //吃肉!
    //闭着眼睛睡觉觉

Java接口

从以上代码就可以看出,抽象类的好处可以在自己里面定义一些功能! 但是,抽象类也有不好的地方,那就是单继承,当一个子类继承了这个抽象类,那么它就不能继承别的类了,这个时候,接口就出现了,它就是功能的扩展,因为java是可以实行多实现的,这也就弥补了抽象类的不足,定义接口让子类实现,使得子类拥有更多更多的类型了!

不过接口也有它的缺点,那就是接口不能在里面定义方法的实现!只能定义抽象的方法!

接口以interface为关键字进行修饰

可以不可以将Java抽象和Java接口的优点结合起来呢?

既然抽象类中可以对具体方法的实现,那么不妨在抽象类中实现接口,并且实现接口中的抽象方法,那么子类再在继承这个抽象类并且实现接口的时候,就可以有选择的实现不实现接口中的方法!

由代码来体现下:

//接口
public interface StudyCode {

    public void study();

}

//抽象类
public abstract class Person implements StudyCode {

    //重写接口中的抽象类并实现
    @Override
    public void study() {
        System.out.println("good good study");
    }
    //定义一个抽象方法
    public abstract void eat();

}

//子类
public class Student extends Person implements StudyCode{

    @Override
    public void eat() {
        System.out.println("吃嘛嘛香");
    }

	//这里没有实现接口中的方法!
}

//测试类
public class MainDemo {

    public static void main(String[] args) {
        Student s = new Student();
        s.eat();
        s.study();
    }


}

输出结果:

吃嘛嘛香
good good study

而当子类要实现接口中的方法只需要重写父类中的重写接口的方法即可:

public class Student extends Person implements StudyCode{

    @Override
    public void eat() {
        System.out.println("吃嘛嘛香");
    }

    @Override
    public void study() {
        // TODO Auto-generated method stub
        System.out.println("study what");
    }

}

我的相关文章

相关资料

written in java

Java 面向对象编程中的多态

  • 啥么是java面向对象的多态咧?
  • java面向对象的多态有啥么特点呢?
  • java面向对象的多态有啥弊端,怎么办?
  • 多态可以给我带来什么好处不?

java面向对象的多态

多态就是呢,某种事物在不同时刻的表现出来的不同状态! 比如说:动物,可以表现为狗,猪,牛等等!

多态的前提是:

  • 有继承关系(比如:狗 extends 动物)
  • 有方法的重写(比如:狗重写了动物的方法)
  • 父类引用指向子类对象(比如:动物 dw = new 狗)

代码体现:

//父类
public class Animal {

    public void eat()
    {
        System.out.println("吃啥呢?");
    }

}

//子类
public class Dog extends Animal {

        @Override
        public void eat() {
            // TODO Auto-generated method stub
            System.out.println("I  am  dog , 你懂的");
        }

}

测试类:
public class MainDemo {

    public static void main(String[] args) {
        Animal a = new Dog();
        a.eat();
    }


}

输出结果:

I  am  dog , 你懂的

以上就是一个简单的多态实现过程:Dog继承了Animal,然后重写了eat方法,然后Animal将引用指向了Dog,调用eat方法,结果打印出的Dog的eat方法里面的内容。

那么java面向对象的多态的特点是啥?

//添加一个年龄的属性
public class Animal {

    public int age = 100;

    public void eat()
    {
        System.out.println("吃啥呢?");
    }

}


//子类也添加一个年龄的属性,并且自己添加一个特有的功能
public class Dog extends Animal {

        public int age = 10;

        @Override
        public void eat() {
            // TODO Auto-generated method stub
            System.out.println("I  am  dog , 你懂的");
        }
        public void run()
        {
            System.out.println("我啪啪啪");
        }

}

//测试类:
public class MainDemo {

    public static void main(String[] args) {
        Animal a = new Dog();
        a.eat();
        System.out.println(a.age);
        a.run();

    }   
}

这时候运行你会发现:The method run() is undefined for the type Animal。

我们把a.run()注释掉再运行一次可以看到以下输出:

I  am  dog , 你懂的
100

从中发现:

  • Animal中根本就没有run功能!
  • 而age的输出的值是100,也就是父类中的age!
  • a.eat(),的输出值是(I am dog , 你懂的)也就是子类的eat()方法!

所以我们可以从中得出多态的特点:

父类引用子类对象,只对子类的重写方法有效!

这也就给多态带来了一个弊端:

父类引用子类对象,不能用子类的特有功能!,比如刚才Animal指向Dog后,用不了Dog的run方法。

那么,这时候就要用一种方法来解决,就是:强制类型转换

什么意思? 就是 Dog d = (Dog) new Animal(); 那么这样子,父类强制转化为子类对象,就可以使用子类的特有功能了!

代码体现:

public class Animal {

    public int age = 100;

    public void eat()
    {
        System.out.println("吃啥呢?");
    }

}

public class Dog extends Animal {

        private int age = 10;

        @Override
        public void eat() {
            // TODO Auto-generated method stub
            System.out.println("I  am  dog , 你懂的");
        }

        public void run()
        {
            System.out.println("我啪啪啪");
        }

}


public class MainDemo {

    public static void main(String[] args) {

        Animal a = new Dog();
        Dog d = new Dog();
        d = (Dog) a;
        d.run();

    }


}

输出结果:我啪啪啪


最后浅谈一下java面向对象的多态的好处:

提高代码的维护性!(当很多子类继承了父类,而他们的共同属性需要修改的时候,只需要修改父类中的代码就可以了,而不用对每个子类都进行修改!)

提高了代码的扩展性!(当要创建很多子类对象的时候,只需要写一个方法,并将父类对象当做参数,那么要创什么子类就传递什么子类对象就o了!)

我的相关文章

相关资料

written in java

Java 面向对象编程中的继承

##对java的疑问:

  • 为什么要用到java继承呢?
  • java继承需要注意什么?
  • java继承有什么好处呢?
  • 有没有什么弊端呢?

继承

继承,就像大象和小象有一些共同的属性,一只大象生了一只小象,而小象拥有了大象的功能属性,然后小象还可以自己学习很多东西,扩展了自己的功能和属性!

代码提现继承

//父类
    public class Elephant_Father {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}


//子类

public class Elephant_Son extends Elephant_Father {

    public void LOL()
    {
        System.out.println("hahahhahahah");
    }   
}


//实现类:
public class MainDemo {

    public static void main(String[] args) {

            Elephant_Son son = new Elephant_Son();
            son.setAge(15);
            son.setName("little elephant");
            System.out.println("son.name:" 
            + son.getName()+ " ,son.age:" + son.getAge());
            son.LOL();  
    }

}

控制台显示结果:

son.name:little elephant ,son.age:15
hahahhahahah

从代码中可以看出:当Elephant_Son extends Elephant_Father的时候,son自己本身并没有setName()、getName()等功能,可是它继承了它的爸爸后就可以用它爸爸身上的东西了。

**由此可见继承简化了代码,son在继承了father的基本功能后,在自己的类中还可以实现自己扩展出来的东西,这也简单的回答了第一个问题(java为什么要用继承!) **


可是,当father拥有了一个吃的方法,而son也有吃的方法,可是他们像吃的东西不同,比如father想吃饭,而son想吃方便面!那么当son继承了father后怎么办呢?难道也要跟着吃饭吗?no!!

代码体现:

//父类
public class Elephant_Father {

     public void eat()
     {
         System.out.println("father:我要吃饭!!!");
     }

}

//子类
public class Elephant_Son extends Elephant_Father {
	//TODO	
}

public class MainDemo {

//实现类
public static void main(String[] args) {

        Elephant_Son son = new Elephant_Son();
        son.eat();

        Elephant_Father father = new Elephant_Father();
        father.eat();
}

}

输出结果:

 father:我要吃饭!!!
 father:我要吃饭!!!

son继承了father后,son.eat()是吃饭啊,可是想吃方便面啊!!怎么办? 这时候son想了个方法,把继承父亲吃的这个方法给重写掉!多么聪明的son呀,说来就来:

 //父类
 public class Elephant_Father {

     public void eat()
     {
         System.out.println("father:我要吃饭!!!");
     }

}

//子类
public class Elephant_Son extends Elephant_Father {
        @Override
        public void eat() {
            System.out.println("son:爸爸要吃饭我又不吃,我改了,我要吃方便面!");
        }

}

//实现类
public class MainDemo {

    public static void main(String[] args) {


            Elephant_Son son = new Elephant_Son();
            son.eat();

            Elephant_Father father = new Elephant_Father();
            father.eat();
    }

}

输出结果:

son:爸爸要吃饭我又不吃,我改了,我要吃方便面!
father:我要吃饭!!!

**由此可见,在继承中有一个东西叫做重写(覆盖),爸爸有的东西不一定是我要的,那么我就改掉!!! **


父亲呢!有时候有些东西是不想给孩子看到(你懂的),有些父亲甚至不想有孩子,想“断子绝孙”,这个时候父亲就想到了把一些东西占为己有,私有(private)掉, 而想“断子绝孙”的父亲就把自己成为最终态(final)!

java的修饰符private

比如,father有一个思考事情的方法,但是他不想让继承他的son知道!就用private私有:

class Elephant_Father {

      private void think()
      {
          System.out.println("father:我一直在思考,让你了解我的好");
      }
}

这个时候,son是继承不到father的think方法的!

java的修饰符final

再比如想要断子绝孙的父亲:

final class Elephant_Father {

}

这个时候,它就没有son了,也就是说,son继承不了他,因为他final了!

java的修饰符static

又:当父亲有static来修饰方法的时候,子类是不能重写的!比如:

//父类
class Elephant_Father {

     //static修饰
     public static void eat()
     {
         System.out.println("吃饭");
     }
}

//子类
public class Elephant_Son extends Elephant_Father {

 public static void eat()
 {
     System.out.println("吃面");
 }
}

//实现类
public class MainDemo {

    public static void main(String[] args) {


            Elephant_Son son = new Elephant_Son();

            son.eat();

            Elephant_Father father = new Elephant_Father();
            father.eat();

    }

}

输出:

 吃面
 吃饭

咋一看好像真的重写了呢!! 可是有没有看到子类的eat方法上面没有@Override字样儿! 其实并不是重写而是子类自己写的一个静态方法,static修饰的方法和一般的方法不同,它是静态绑定的,所属类的!所以是不会有重写的!

所以可以对以上的描述拿来简单回答第二个问题(继承需要注意什么)。

但还有一个细节就是java是不能多继承的,

什么意思? 就是son extends father,mother,…,这样是不行的,

但是java可以传递性的继承,什么意思呢? 就是 son extends father, grandson extends son ….;


最后浅谈一下:

java继承的好处:

除了刚才讲过的简化了代码,提高了代码的复用性,而且还提高了代码的维护性; 再者,使得类与类之间产生了关系,产生了关系的作用在前面的例子可 以看到,还有它的另一作用是多态的前提!

java继承的弊端:

提高了耦合性,一定程度上打破了封装!

我的相关文章

相关资料

written in java