这部分将会教你上手第一个Vagrant程序,同时让你了解Vagrant提供的大多数功能。如果你很好奇使用Vagrant能带来什么好处,你可以详细了解为什么使用Vagrant?这个上手教程将会使用virtualbox。请安装好它。
有参考书籍吗?如果你喜欢实体书籍那么由Vagrant写作,由O’Reilly发版的Vagrant: Up and Running可以帮助你。
|
|
当你运行完这两行命令,你会得到一个运行在VirtualBox上完整的Ubuntu 12.04 LTS 32-bit系统。你能使用SSH命令登录虚拟机vagrant ssh
,你想删除这个虚拟机vagrant destroy
。
现在想象一下,每个项目你都可以这样来创建,是不是很nice。
只用vagrant up
,就能完成项目依赖安装,设置网络,并建立任何网络和同步文件夹,您可以继续在自己的机器的舒适性的工作。
教程的其余部分将引导你完成一个更完整的项目,涵盖了Vagrant的更多功能。
设置Vagrant项目的第一步是创建Vagrantfile文件。创建Vagrantfile有两个目的:
Vagrant可以使用命令vagrant init
来初始化目录。为了完成下面的练习,在你的终端使用如下命令:
|
|
它会当前目录给你创建一个Vagrantfile
文件。你可以看看Vagrant这个文件,里面有很多注释,方便你理解配置。不要被这些配置吓到,我们已经配置足够你使用了。
你同样可以使用vagrant init
来再次初始化当前目录,即使这个目录已经存在项目。
在你的项目中,如果你使用版本控制,那么Vagrantfile意味着是你的版本控制文件。
从零构建一个虚拟机是枯燥无味的,Vagrant使用基础镜像快速的克隆虚拟机。这基础的镜像在Vagrant中叫boxes
。而你环境中使用的具体镜像是你Vagrantfile文件首先定义的。
如果你是练习了[UP 和 RUNNING][],那么你已经安装了一个box了,你可以不是再运行下面的命令了。但是,这小节还说明了box的管理,所以你还是需要看的。
使用vagrant box add
命令可以把BOX添加到Vagrant中。存储了box后,多个Vagrant环境都可以使用了。
|
|
这命令将会从HashiCorp’s Atlas box catalog下载box名称为hashicorp/precise32的镜像。HashiCorp’s Atlas box catalog是一个你可以寻找和下载镜像的地方。它使一个很方便的地方,你可以使用本地文件,URL等等添加box。
添加的box可以使用于多个项目。每个项目clone基础镜像作为初始化镜像,基础镜像并不会改变。这静意味着,如果你有两个项目都是使用了刚才添加的hashicorp/precise32
镜像,只用添加一次就可以了。
好现在BOX已经添加入Vagrant了,我们需要设置我们项目需要的镜像。打开Vagrantfile
修改内容为下:
|
|
“hashicorp/precise32” 必须是box中包括的。这是为了保证Vagrant知道你需要的box。如果这box之前没有添加,Vagrant会在box运行的、时候自动下载。
下一节,我们会会更多涉及到Vagrant环境和交互。
教程中我们只是使用了”hashicorp/precise32”box。但是当学完这个教程后你马上会想到“我可以从哪里找到boxes?”
找boxes最好的地方是HashiCorp’s Atlas box catalog。HashiCorp’s Atlas 有一个公共的目录来免费提供boxes,也提供搜索。
In addition to finding free boxes, HashiCorp’s Atlas lets you host your own boxes, as well as private boxes if you intend on creating boxes for your own organization.
为了找到免费的boxes,HashiCorp’s Atlas也提供私有的boxes服务。
是时候启动你的Vagrant环境了。运行如下命令:
|
|
不到一分钟,这命令会运行完成,你会得到一个ubuntu系统的机器。你将看不到任何东西,因为Vagrant运行的虚拟机没有UI。为了使用终端你可以使用SSH进入这个机器:
|
|
这命令会让你通过SSH登录。现在你可以随意输入命令交互一下或者做一点你想做的事。虽然看起来很诱人,但是小心别使用了rm -rf /
, 因为Vagrant共享目录是/vagrant
,对应是机器上的Vagrantfile目录,它能删除掉所有的这些文件。共享文件夹会在下一节说明。
当你不在使用这个机器的时候,你可以使用vagrant destroy
来删除掉虚拟机。
现在创建一个虚拟机太方便了,大多数人不想编辑文件是使用通过终端SSH来进行编辑。Vagrant可以进行文件夹共享。使用同步文件夹,Vagrant会自动同步你的文件。
默认的,Vagrant共享你的项目目录(记住,就是Vagrantfile所在的目录)到/vagrant目录。运行vagrant up
然后SSH进行看看:
|
|
Vagrantfile就是你本机的文件,你可以尝试改变它看看:
|
|
看“foo”已经在你的本机了。所以Vagrant保证了文件夹的同步。
使用同步文件夹,你能使用你本地的编辑器编辑文件,然后作用于虚拟机。
好现在我们运行了一个基于Ubuntu镜像的虚拟机,同时我们可以在我们本地编辑文件同步到虚拟机上。接下来我们搭建一个web server。
我们现在只能使用SSH进入,然后安装一个web Server。这样比较麻烦,如果每个人都需要Web Server他都得重复操作。当你启动的Vagrant的时候,Vagrant支持自动安装软件。
我们使用Apache来练习,我们使用Shell脚本来进行。创建下面的脚本,然后保存为bootstrap.sh
放在和Vagrantfile用一个目录。
|
|
Next, we configure Vagrant to run this shell script when setting up our machine. We do this by editing the Vagrantfile, which should now look like this:
接下来我们将在Vagrant中配置这个shell 脚本,以便启动的时候运行。编辑Vagrantfile,内容如下:
|
|
好使用下面命令启动看看
|
|
我们已经有一个运行的Web Server了,同时我们可以在本机上编辑文件。然后,我们访问这个机上的Web Server还必须通过虚拟机的ip来访问。接下来我们学习通过本机访问虚拟机。
使用端口跳转是一个不错的选择。端口跳转让你可以在本机设置一个端口来访问虚拟机的端口。
好让我们来设置吧:
运行vagrant reload
或者 vagrant up
。待启动完成,
访问http://127.0.0.1:4567
看看效果。
Vagrant可以设置静态ip地址,详细可以访问网络设置
好了现在我们已经有一个运行的Web Server了,同时也可以通过本机端口访问了。但是对于开发环境来说,我们还需要分享,Vagrant可以很方便的进行分享。这小节主要说的是Vagrant的分享。
Vagrant让你很方便的分享你的环境给任何人。使用URL就可以进行分享了。
Before being able to share your Vagrant environment, you’ll need an account on HashiCorp’s Atlas. Don’t worry, it’s free.
在开始分享Vagrant环境之前,你需要一个HashiCorp’s Atlasd 的账号。别担心,免费的。
一旦你有一个账号,用vagrant login
登录:
|
|
一旦你登录了可以运行vagrant share
:
|
|
你的URL是不一样的,所以对应使用你的URL。把个URL分享出去,当有人需要这个Web Server的时候,他将会很方便。
如果你更新文件然后刷新这个URL,你会发现更新了。很明显这个是通过英特网来连接的你机器环境了。
Vagrant Share is much more powerful than simply HTTP sharing. For more details, see the complete Vagrant Share documentation.
Vagrant的分享有更多功能,不只是简单的HTTP分享。更多细节完整Vagrant分享文档
现在我们知道了所有基于Web开发的虚拟机的功能。但是是时间休息一会了,也许你需要开发另外的一个项目,也许你需要出去玩玩呢,或者下班回家了。那么怎么清除我们的开发环境呢?
使用Vagrant,你可以 suspend,halt 或者 destroy虚拟机。每个命令都有期特点。你需要选择合适的。
Suspending 虚拟机会挂起,它保存了运行时的状态然后停止。当你需要再次运行的适合,只用运行vagrant up
,它将会恢复到你挂起时候之前的状态。这个方式非常的快,通常就是5-10秒钟进行停止或者启动。不好的地方时这个虚拟机还在消耗你的磁盘空间,需要更多的磁盘空间进行保存状态到磁盘的RAM区。
Halting 这个命令将会优雅的关闭虚拟机。你可以在你需要的时候进行启动。这方式的好处是虚拟机会和关机一样停止。在启动就和冷启动一样。
Destroying 消耗虚拟机,清除虚拟机后不会占用你的的硬盘。但是你想再次启动,必须从新下载依赖等等。
当你想再次构建你的项目的时候你可以使用如下命令进行:
|
|
这就就会完全的从Vagrantfile中构建出虚拟机环境,这样非常的方便。
读到这里,我们项目都是以VirtualBox进行的。但是Vagrant也可以使用其他的提供方式,比如:VMware, AWS等等。接下来我们将了解如何使用它们。
Once you have a provider installed, you don’t need to make any modifications to your Vagrantfile, just vagrant up with the proper provider and Vagrant will do the rest:
一旦你安装了一个提供方式,你不需要再安装别的东西了,对于Vagrantfile来说已经可以了,比如下面的命令:
|
|
使用AWS:
|
|
一旦你使用了别的提供方式,你就不需要再指定提供方式。但是destroy,方式还需要再次指定的。
JMH是新的microbenchmark(微基准测试)框架(2013年首次发布)。与其他众多框架相比它的特色优势在于,它是由Oracle实现JIT的相同人员开发的。特别是我想提一下Aleksey Shipilev和他优秀的博客文章。JMH可能与最新的Oracle JRE同步,其结果可信度很高。
学习JMH的第一步当然是完成一个简单的Hello World。如何来完成呢?满足3个条件:
@Benchmark
来注解测试方法
|
|
然后写一个测试例子,参考于JMH Samples:
|
|
这里来解释代码的意义,首先排除JMH相关的注解:
|
|
这样就好理解了,BaselineBenchmarks类中有1个int的字段i及两个方法noop()
和increment()
其中increment()
方法中调用了i++
。
好了接下来说明JMH的注解,首先是@Benchmark
, 这个很好理解代表该注解的方法是一个基准测试方法,你可以想象和单元测试的@Test
一样。
@BenchmarkMode
注解表示使用特定的测试模式,相关参数见下表:
名称 | 描述 |
---|---|
Mode.Throughput | 计算一个时间单位内操作数量 |
Mode.AverageTime | 计算平均运行时间 |
Mode.SampleTime | 计算一个方法的运行时间(包括百分位) |
Mode.SingleShotTime | 方法仅运行一次(用于冷测试模式)。或者特定批量大小的迭代多次运行(具体查看后面的@Measurement 注解)——这种情况下JMH将计算批处理运行时间(一次批处理所有调用的总时间) |
这些模式的任意组合 | 可以指定这些模式的任意组合——该测试运行多次(取决于请求模式的数量) |
Mode.All | 所有模式依次运行 |
@OutputTimeUnit
是用来指定时间单位,它用一个标准Java类型java.util.concurrent.TimeUnit作为参数。如果在一个测试中指定了多种测试模式,给定的时间单位将用于所有的测试。
@State
注解定义了给定类实例的可用范围。JMH可以在多线程同时运行的环境测试,因此需要选择正确的状态。
名称 | 描述 |
---|---|
Scope.Thread | 默认状态。实例将分配给运行给定测试的每个线程。 |
Scope.Benchmark | 运行相同测试的所有线程将共享实例。可以用来测试状态对象的多线程性能(或者仅标记该范围的基准)。 |
Scope.Group | 实例分配给每个线程组(查看后面的线程组部分) |
安装完成了Nginx,迫不及待的想尝试它的各种功能。
|
|
以上是一些基本的配置,使用Nginx最大的好处就是负载均衡
如果要使用负载均衡的话,可以修改配置http节点如下:
|
|
快速测验:你开车上班30km/h,开车回家60km/h。你的平均速度是多少?
提示: 结果并不是45mk/h,因为它并没有关注到你上班究竟有多远。看看下表的平均数用途吧。
名称&含义 | 公式/例子 | 使用情况 |
---|---|---|
算术平均数[平均数] | $$\frac{sum}{size}=\frac{a + b + c}{3}$$ | 数据集中趋势指标(平均数量) |
中位数[中值] | 比如list的中间值(2个中间值?值有偶数个,则中位数不唯一) | 使用广泛(比如房子,收入) |
众数[比较多] | 出现次数最多的变量值 | 主要用于分类数据,也可用于顺序数据和数值型数据。 |
几何平均数[平均因子] | $$\sqrt[3]{abc}$$ | 增长率相关,比如:面积,声音 |
调和平均数[平均数据] | $$\frac{3}{\frac{1}{a}+\frac{1}{b}+\frac{1}{c}}$$ | 速率相关,比如:速度,产量,花费 |
平方平均数[方均根] | $$M=\sqrt{\frac{\sum_{i=1}^{n}x_i^2}{n}}$$ | 数值分布呈现正态分布时才适用,比如:交流电电压,电流数值和匀加速运动的位移中点平均速度 |
我们有必要了解一下,“平均”是怎么一回事呢?
对于我们大多数人来说,它是一个中间数或者是一个“平衡点”的数。我通常习惯使用多观点看问题,这里给出平均数的另一种解释:
平均数是可以用其表现现在每个数的结果。如果你我吧其中一个数替换成平均值,那会怎么样呢?
平均数的一个目标是让我们从采集样本出了解“代表性”数据。但是怎么计算通常需要更具实际情况来定。接下来我先了解算术平均数。
算术平均数是最常见的平均数了:
$$avaerage = \frac{sum}{number}$$
举个栗子:在电梯里,你重150斤,还有一个同事重100斤,以及一个海象重350斤。那平均体重是多少?
比如:“你能克隆3个一样的人来代替这3个,那么你每个克隆的人需要多重?”
这样情况下我们每个人都能换算出克隆人需要的重量是200斤[(150 + 100 + 350)/3].
优点:
缺点:
算术平均数80%都是适用的,但是也有20%的特殊情况,这样情况下算术平均数是不合适的。
中位数是“数的中间项”。但是不平均(用算术平均数来看)表示同样的数?怎么办?
幽默故事《我第二》:这是数字的中间数是什么?
很容易看出来,3是中间数。尽管22是在这些数的中间,但是并非是真正的分布。在这个数列中我们更可能得到一个接近3的数,而非22. 这都是100拉大了平均数,其实看来100并不相关。
中位数解决了数列中间项的问题。如果有两个中间数(偶数项),只取它们的平均值。比如:1 2 3 4 2.5就是中位数。
优点:
缺点:
关于开车的,”所有驾驶员的一半技术都是低于平均水平的。很吓人对不?“但是在你头脑中你知道这个意思是说:”有一半的司机技术都比中位数那个技术低“。
房价和收入用中位数来代表也比较合适,因为我们希望知道一个中间的位置。比如马云一年有数10亿的收入,但是他的收入和普通人的收入就不太相关了。而且我们也”不感兴趣“,我们只想知道的时”收入与房价的中位数“。
再次说明,使用那种类型的平均数,还是得依靠数据来决定。
众数听起来可能和平均数无关。它是指在统计中出现次数最多的值。就像投票表决一样,这样更难代表人们想要什么。
比方说你将举行一个聚会,是选择周一好还是周末好?哪天好其实就是满足大多数人的需求,平均在这就没有意义了。(难道说张三喜欢周五,李四喜欢周末,那你就选择周六了!)
同样,颜色,电影等等的喜好等更多可以使用勾股定理来衡量.同样的理想的选择是众数,而非平均数。“the “average” color or “average” movie could be… unsatisfactory”——Rambo meets《傲慢与偏见》。
优点:
缺点:
众数并不常见,但是设计一个什么样的按钮比较合适,你通常会用到。
“平均项”取决于我们如何利用现有的数据。大锁时候把每项相加然后除的算是平均数能解决。但是有的时候我们只这样做是不够的。比如在投资、面积、和体积上,我们不是用加,而是用乘。
再来一个例子。下面的投资组合你比较喜欢哪个?哪个这年会更优特点?
它们看起了基本一样。每天平均(算术平均值)告诉我们,它们两个都像过山车一样,出现盈利和亏损。也许B会跟好,因为一年中它获利是最多的,这样对吗?
死胡同了!这样做只会让你烧掉股市:投资与回报率是相乘的而不是相加!我们不能不管三七二十一,就用算术平均值——我们需要找到回报的实际速率。
投资组合A:
投资组合B:
A 2% VS B 17%的损失?这是一个巨大的差距。我很想远离这两个投资组合,非要选择那就是A。我们不能只是把收益相加,这是不知道指数增长的做法。
更多的例子:
我敢肯定你能找到更多的例子:几何平均数处理问题是用乘。我想了很久为什么几何平均数对我们来说很有用,原来就是这样啊。
调和平均数很难形象化,但是也很有用。(随便说一句,“调和”是指1/2, 1/3 ··· 1的任何东西。)当多个数度一起的时候,调和平均数帮助我们来计算出平均速度。我们一起来看看文章开头的快速测验。
我开车30km/h,这意味着我获得了30km/h的录入.平均速度受多个速度(X & Y)影响,你需要考虑录入(input)与产出(output),而并非原始数据(路程等)。
$$average rate = total output/total input$$
如果我们吧X和Y放入同一个环境,就速度不同。那我们推理结果是:
最终我们得到:
列入公式:
$$\frac{2}{\frac{1}{X} + \frac{1}{Y}}$$
如果是3个速度:
$$\frac{3}{\frac{1}{X} + \frac{1}{Y} + \frac{1}{Z}}$$
这是不错的,不用每次都做代数 - 即使是5个速度的平均也不那么麻烦了。我们的例子中,我们去工作是30km/h回来是60km/h。要得到平均速度,我们只用公式就可以了。
这样不需要我们知道有多远的路程?而且!不管路多远,X和Y具有相同的输出; 也就是说,我们去X km的速度的X,和另一条X km的速度Y的平均速度和去1 km的速度的X和1km的速度Y一样:
$$\frac{2X}{\frac{X}{30} + \frac{X}{60}} = \frac{2}{\frac{1}{30} + \frac{1}{60}}$$
这是合理的,一般是朝着较慢的速度(相比60来说更接近30)倾斜。毕竟,相比30km/h和60km/h相比,我们花了两倍的时间:如果工作60(km)远,它的2个小时去,1小时回来。
核心思想:用单个元素去替换多个元素。在我们的例子中,我们开车40km/h,(途中有,而不是30km/h),并在路上开车40km/h回(而不是60)。重要的是要记住,我们需要将平均速度中的每个“相关”替换。
几个例子:
调和平均数同样可以用于衡量利率。
平方平均数只有在数值分布呈现正态分布时才适用。高中的数学题目中常常会出现以方均根值计算班级平均成绩的题目, 这是预先假设全班成绩为正态分布的结果,实际情况不一定完全适用。 如成绩分布极为平均或呈现多峰状(如 30分、70分的人数远超过其它分数的人数), 方均根值就无法真实表现出该班级的平均成绩。
平方平均数常用来计算一组数据和某个数据的“平均差”。像交流电的电压、电流数值以及均匀加速直线运动的位移中点平均速度,都是以其实际数值的方均根表示。例如,交流电 220V 表示电压信号的均方根(又称为有效值),即 220V,为交流电瞬时值(瞬时值又称暂态值)的最大值的$$\frac{1}{\sqrt{2}}$$。
即使一个简单的方法有会有多种用途,还有好多有用的我们没有覆盖
(加权平均值)。关键还是方法理解:
数学是多么让人着迷,就一个平均值都千变万化。Happy math.
map是最重要的数据结构之一。在这篇文章中,我会向你展示如:HashMap, TreeMap, HashTable 和 LinkedHashMap的使用以及它们的不同。
HashMap, TreeMap, Hashtable 和 LinkedHashMap在Java SE中通常有4个相同实现点。简单一句话描述如下:
这就告诉我们如果是线程安全的,我们就应该使用HashMap,因为Hashtable同步是回增加开销的。
如果HashMap 的key是自定义对象,遵循规范应该重写 equals() 和 hashCode() 方法。看如下代码:
|
|
输出:
4
white dog - 5
black dog - 15
red dog - 10
white dog - 20
注意,我们错误的添加了”white dogs” 2次,但是HashMap 同样保存了它。这是没有意义的,因为现在我们都搞不清楚了,白色的狗到底有多少是真的存在的。
正确的写法如下:
|
|
现在的结果如下:
3
red dog - 10
white dog - 20
black dog - 15
其原因是HashMap不允许两个相同主键的元素。默认情况下,hashCode()和equals()方法在Object类中实现使用。默认的hashCode()方法用不同的整数来区别对象,这时只有当两个对象指向同一个引用,equals() 才会返回true。如果你不是很了解,请了解hashCode() 和 equals()。
了解常用的HashMap方法,例如:迭代,打印等等。
TreeMap是通过key来排序的。让我们先来看看下面的例子就明白了“根据key来排序”的想法。
|
|
输出:
Exception in thread "main" java.lang.ClassCastException: collection.Dog cannot be cast to java.lang.Comparable
at java.util.TreeMap.put(Unknown Source)
at collection.TestHashMap.main(TestHashMap.java:35)
因为TreeMaps 根据 key值来排序,作为key的对象相互之间必须是可比较的,这就是为什么要实现 Comparable接口。比如你用String作为key,String是实现了 Comparable接口的。
好,没我们改变Dog类,实现 Comparable接口:
|
|
输出:
red dog - 10
black dog - 15
white dog - 20
它由key来排序,这里就是根据dog的数量来排序。
如果使用 “Dog d4 = new Dog(“white”, 10);” 替换 “Dog d4 = new Dog(“white”, 40);”,结果如下:
white dog - 20
red dog - 10
black dog - 15
white dog - 5
其原因是,TreeMap中使用compareTo()方法对键进行比较。不同大小被识别为不同的dog!
在 Java Doc写到:
HashMap大致相当于Hashtable,不同之处在于HashMap是不同步的,并且允许null值。
LinkedHashMap 是 HashMap 的一个子类。这就意味着LinkedHashMap 继承了 HashMap 的所有功能。额外的链表插入是有序的。
我们把上面的HashMap的例子改成LinkedHashMap:
|
|
输出:
red dog - 10
black dog - 15
white dog - 20
HashMap和LinkedHashMap 所不同的是,插入顺序不保留。输出如下:
red dog - 10
white dog - 20
black dog - 15
了解一下ArrayList vs. LinkedList vs. Vector
在JDK6和JDK7中substring(int beginIndex, int endIndex)这个方法是有区别的。知道它的区别有助于你用好它。最简单的时候,我们会用substring()来代替substring(int beginIndex, int endIndex)方法
substring(int beginIndex, int endIndex) 方法返回一个以beginIndex开始endIndex-1结束的字串。
|
|
输出是
bc
你可能知道字符串x是不变的。当x被赋予x.substring(1,3)结果的时候,它的指针通常是指向了一个新的字符串。如下图:
然而这图表示并不完全对,或者它只是表示了在堆里真实的情况。在 JDK 6 和 JDK 7中,调用substring()方法实际处理是不同的。
字符串是由一个char的数组表示。在JDK 6中,String类有3个字段:char value[], int offset, int count。它们是用来储存实际的字符阵列(char value[]),该阵列的第一个索引(int offset),该字符串中的字符数(int count)。
当substring()被调用的时候,它创建了一个新的字符串,但是这个字符串的值还是指向的相同数组中的堆。两个字符串不同的地方的地方只是count值和offset值。
下面的代码可以简单解释这个问题。
|
|
如果你有一个非常非常长的的字符串,但是当你使用substring()的时候,你仅需要一小部分。这将导致性能问题,当你只需要一小部分的时候,你还是保存了全部的值。对于DK 6来说通常使用下面的方法解决,它将指针指向实际的子串:
|
|
在JDK 7中改善了上述问题。在JDK 7中,substring()会创建一个新的数组在堆里。
|
|
如果你对Java String感兴趣还可以读一下下面的内容
在Java中String是一个不可变类。不可变类是一个简单的类,它的实例不能被修改。创建实例的时候在一个实例中的所有信息被初始化并且信息不能被修改。不可变类有许多的优点。本文总结了为什么字符串被设计成不可变的。一个很好的答案是:取决于对内存,同步,数据结构等的深刻理解。
String pool (String intern pool)在方法区是一种特殊的存储区域。当创建一个字符串,如果字符串已经存在于池中,现有字符串的引用将被退回,而不是创建一个新的对象,并返回它的引用。
下面的代码将只创建一个字符串堆对象。
|
|
下图是它的运作方式:
如果字符串不是一成不变的,不断变化的字符串与一个参考值将导致对其他引用得到了错误的值。
在java中是经常使用hashcode的。例如,在一个HashMap中。不可改变保证了哈希码总是相同的,所以,它可以无需担心改变。这意味着,没有必要每次使都计算哈希码。这是更有效的方式。
在String 类中, 它包括了下面的代码:
|
|
为了使这个问题更加具体,看一下下面的代码:
|
|
在这个例子中,如果字符串是可变的,它的值是可以改变这将违反set的设计(set集包含不重复的元素)。这个例子的目的是为了简单起见,在实际中String类是没有value 的域。
在java类中,字符串是被广泛地作为用参数,例如:网络连接,打开文件等等。如果字符串不是不变的,连接或文件将被改变,并导致严重的安全威胁。该方法还以为是连接到同一台机器,但实际上市没有。可变字符串可能会导致反射的安全问题也一样,这些参数都是字符串。
下面是例子代码:
|
|
由于不可变对象不能改变,就可以在多个线程之间自由共享。这消除了执行同步的要求。
总之,字符串被设计为不可变的,为了效率和安全性。这也是为什么一般情况下不可变的类是优选的。
List正如其名,是一个一组有序的元素。当我说List,最好比较一下。Set 中的元素是唯一、有序的。下图是集合的层次结构图,从中你可以了解到集合的概念。
从上面的继承图我们了解到它们都实现了List接口。它们使用方式都很相似。最主要是的实现区别是当使用不同操作性能会不同。
ArrayList是可变大小(容量)数组的实现。当有更多的元素加入ArrayList,ArrayList是会自动增加大小的。它的元素可以使用get 和 set方法来访问,因为ArrayList 本质还是地址连续的数组。
LinkedList是双链表的实现。它add 和 remove操作的性能会比Arraylist高不少,但是get 和 set操作就比 Arraylist差。
Vector和ArrayList很像,但是它是同步的。
当你的程序是线程安全的,那么ArrayList 是比较好的选择。当添加比较多的元素的时候Vector 和 ArrayList 需要更多的空间。Vector的增长方式是双倍其大小,ArrayList 是50% 的增长。LinkedList也实现Queue 接口所以功能比ArrayList 和 Vector较多,比如:offer(), peek(), poll()等等。
注意:默认初始化ArrayList 的大小是非常小的,如果你想创建一个有很大容量的ArrayList,好的习惯是使用构造方法指定大小。这样会减少增加容量的时间花费。
|
|
|
|
和上面的例子一样,它们使用方式相同。实际不同是他们的实现的方式,和操作的复杂性。
Vector 几乎和ArrayList相同,不同的是Vector是同步的。正因为如此,它比ArrayList开销多。通常情况下,大多数Java程序员使用的ArrayList,而不是Vector,因为他们可以自己的显式控制同步。
时间复杂度比较如下:
add() 在表中代表 add(E e), 同样 remove() 是说 remove(int index)
我使用了下面的代码测试了其性能:
|
|
输出结果是:
ArrayList add: 13265642
LinkedList add: 9550057
ArrayList get: 1543352
LinkedList get: 85085551
ArrayList remove: 199961301
LinkedList remove: 85768810
对于这两者,很明显LinkedList在add 和 remove比较快,但是get比较慢。基于这个表的测试结果,我们可以知道什么时使用的ArrayList,什么时候使用LinkedList。总之,如果满足如下情况LinkedList应该优先考虑:
我们知道函数在内存中是实现是栈。同时我们知道java方法实现是在JVM栈中的帧,对象就是被分配到堆上的。
那再堆中java对象到底是什么样的?一旦对象放入内存,它就只是一些字节数据了。
然后我们怎么找到我们需要的东西呢?它保存了一个内部的字段偏移量表。
这是一个类“Base”的结构,它内没有任何方法。下一节我们才讨论方法的结构。
如果我们有另外一个对象”Derived”继承于”Base”。那内存结构如下:
子类有和父类同样的内存结构,除此之外还需要新的空间存储新添加的字段。这样的结构哟个好处是,当A的指针指向D对象的时候,这样仍然和指向B是一样的。因此,通过操作B对象操作D是很安全的,而且也没有必要的动态监测B的指针了。
因为有相同的逻辑,方法可以仿制对象的开头。
然而这样的方法不是很有效。如果一个类有很多方法(如M),那么每个对象都必须有空间来存储M的指针,这样使得创建对象慢,对象大。
改进方式是创建一个虚函数表(或者vtable),这是一个数组,指向特殊类的,这样使得每穿件一个对象的指针都存储到虚函数表。
Arrays.sort(T[], Comparator < ? super T > c)
是用户用来排序自定义对象的方法。官方的java doc简要的描述了她如何使用,但是没有更多的深入理解。在这篇文章中,我将会带你深入理解其关键。
阅读下面的例子,你能很快知道如何使用。一个Comparator用来根据Dogs的大小来比较,这个作为排序的方法的参数。
|
|
输出:
2 1 3
1 2 3
这是一个经典的策略模式(Strategy)例子,这是非常值得说明的,为什么策略模式是最好的解决方法。总的看来,策略模式在运行时候使用了不同的算法选择。在这样的情况下,通过传递不同的值进行比较,不同的算法可以选择。基于这个例子,现在假设你有另外一个Comparator用来比较Dog的重量而不是大小,你可以简单的创建一个 Comparator如下:
|
|
size=2 weight=50 size=1 weight=30 size=3 weight=40
size=1 weight=30 size=2 weight=50 size=3 weight=40
size=1 weight=30 size=3 weight=40 size=2 weight=50
Comparator 只是一个接口。任何Comparator 都需要实现这个接口,然后运行时候就可以使用。这就是策略模式的关键。
它很简单,如果“Comparator < T > c”是参数,但是第二参数是“Comparator< ? super T > c”。< ? super T >
意味着类型可以是T或者是子类的类型。为什么可以是超类?回答是:这样可以让所有子类方法使用相同的比较器。在下面的例子几乎是显而易见的。
|
|
size=2 size=1 size=3
size=1 size=2 size=3
size=2 size=1 size=3
size=1 size=2 size=3
总之,记得Arrays.sort()的如下特性:
下面是关于Java数组的10大方法,他们都是在stackoverflow上投票出来的。
不知道为什么这个是问的最多的,难道因为最常见所以最容易忘?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
java中数组有2中情况:基本类型(如:int,char等等)或者引用(或者叫指针)。
当使用”new” 创建一个对象的时候,内存是分配在堆上同时返回一个索引。当数组是对象的时候,对于它来说也是一样的。
|
|
int[] 数组只是有3个整数的索引。如果你创建10个整数,它也是相同的-分配一个数组然后返回索引。
二维数组又是什么样的呢?实际上,在java中我们只能有一维数组。2D数组都是基于一维数组的。
|
|
多维数组使用相同的规则。
在java中Arrays 和对象一样,所以一个对象在内存中如起来像什么,那么数组也一样。
我们知道JVM Run-Time Data Areas包括堆、JVM栈和别的。下面是一个简单的例子,让我们来看看数组的索引是怎么存储的。
|
|
根据上面的申明,我们调用m1() 来看看发生了什么:
1.当m1() 调用时,一个新的帧(Frame-1)推入栈,同时Frame-1中创建了局部变量等。
2.然后m1中的m2()被调用,一个新的帧(Frame-2)被压入栈。在m2中,类A的一个对象在堆中被创建同时索引变量被压入Frame-2。现在在这个点,栈和堆类似如下:
数组和对象是很像的,所以可以直截了当的定位数组在内存的分配。
这是非常基础的的java问题。很多相同的问题在stackoverflow都被问,同时有很多不正确或者未完成的回答。这个问题很简单,但是你不好好想想,你会感到疑惑,使用你最好好好想想。
|
|
这将会输出ab
。
在C++中,这代码如下:
|
|
但是输出是cd
。
在堆中x存储的索引是指向字符串”ab”的。所以当x以参数传递给change()方法,在堆中它仍然指向”ab”,如下图:
因为,在java是通过值进行传递的,x的值指向”ab”。当 change()调用的时候,将会创建”cd”对象,同时x指向了”cd”。如图:
这看起来似乎是一个很合理的解释。java都是使用值传递,但是这错在哪了?
上面的解释有几个错误。为了更加好明白,我们来简单走走这个过程。
当”ab”创建时候,java花费时间申请内存来储存对象。然后这个对象被赋予了x变量,这个变量就实际指向了这个对象。这个索引是存储这个对象内存的地址。
x变量包含了一个string对象的索引。x并没有自己的索引。它只是一个存储索引的变量(内存地址)。
java是只通过值传递的。所以当x通过change()传递的时候,拷贝了x的值(一个索引)来传递。在change()方法中创建了另外一个对象”cd”并且它有不同的索引。这时是变量x改变了它的索引(b变成了”cd”),而不是索引自身。
过程可以用下图表示:
这个问题来源于他的第一行代码使用了不可变的string。即使String换成StringBuilder,借输出结果还是一样的。最关键的是变量存储了索引,但是它没有索引自己。
如果我们确实需要改变对象的值,首先对象是可变的,比如: StringBuilder,其次我们需要确保没有新创建对象赋予传递参数,因为java本来就是通过值传递的。
|
|
该部分是为了hacker准备的,如果你想在各种环境上尝试Docker的话。在安装着版本之前最好先检查一下你系统的版本是否支持包安装。我们提供了许多版本的安装包,并且都保持了更新。
为了运行docker你必须拥有下面的软件环境:
Docker守护进程模式需要特殊的内核依赖。详细请了解你的系统版本安装。
总的来说,一个3.8(或者更高)内核的版本是很好的。它可以是运行在虚拟机上的或者如何linux版本(甚至是 OS X)
$ wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O docker
$ chmod +x docker
注意:如果你下载二进制文件比较麻烦,你也可以下载较小的压缩版本:https://get.docker.io/builds/Linux/x86_64/docker-latest.tgz。
# 在你解压文件处运行docker的守护进程模式
$ sudo ./docker -d &
docker守护进程运行的时候总是需要root角色,同时docker守护进程绑定了一个 Unix socket 而不是一个TCP端口。默认的Unix socket 是属于root的,默认的,你可以使用sudo命令访问它。
如果你(或者你的docker安装程序)创建了一个用户组叫docker同时也有docker的用户,那docker守护进程启动时将属于docker用户组,同时有Unix socket read/writable权限。docker守护进程必须以root运行,但是如果你使用docker用户组的用户运行docker客户端,你需要在运行命令的时候添加sudo命令。
警告: docker用户组(或者是-G的用户组)是和root一样的。详细请看Docker Daemon Attack Surface 。
为了更新你手动安装的docker,首先需要kill掉docker的守护进程:
$ killall docker
然后按照安装步骤进行就可以。
# 检查你的docker版本
$ sudo ./docker version
# 运行一个容器然后在容器中打开一个交互脚本窗口
$ sudo ./docker run -i -t ubuntu /bin/bash
下一步,使用指导。
本文主要以CentOS为例说明安装过程:
在 CentOS-7中Docker的相关包已经是默认提供了的。在 CentOS-6中由EPEL仓库提供。请注意两种版本的安装方式是不同的。如果你需要最新的版本,你应该使用最新的二进制文件在内核3.8及以上进行编译安装。
Docker是工作在CentOS6及以上版本。其他的版本或许因为EL6二进制编译分发获得,但是他们是没有经过测试的。
请注意由于当前Docker的限制,Docker只能运行在64位的架构上。
为了运行Docker你需要CentOS6或者更高版本,它的内核必须是2.6.32-431或者更高版本,因为它有Docker需要的特定补丁。
因为Docker一个包含在了默认的 CentOS-Extras仓库,安装使用下面的命令就可以了
$ sudo yum install docker
虽然使用包安装是Docker推荐的安装方式,但是这个包有可能不是最新的版本。如果你需要最新版本的,你可以使用二进制编译安装。
当使用不带包安装二进制的时候,你可能要用systemd整合Docker。因为这样只是简单的安装了2个单元文件(service 和 socket),安装地方 /etc/systemd/system
github 仓库。
CentOS-7 引入了 firewalld。大致可以理解为iptables的封装,这是可能和Docker引起冲突的。
当firewalld启动或者重启的时候会移除DOCKER的iptables链,导致了Docker不能正常的工作。
当使用systemd时,firewalld 必须在Docker启动之前启动,但是如果你启动或者重启firewalld 在Docker启动之后,你需要重启Docker的守护进程。
请注意这部分安装是针对CentOS-6d1,安装包在 Extra Packages for Enterprise Linux (EPEL)中。它是社区版本。
首先你必须确保你有 EPEL repository 仓库并且可用。 请访问 安装 EPEL。
rpm -ivh http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
rpm –import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
在EPEL中已经提供了Docker的安装包docker-io。
如果你安装过没有发布的docker包,那可能会和docker-io引起冲突。最是发现的bug报告。为了安装docker-io,请先移除docker。
然后接下来安装
$ sudo yum install docker-io
一旦Docker安装完成,你需要以守护进程启动它。
$ sudo service docker start
如果我们想开机就启动Docker,添加如下命令:
$ sudo chkconfig docker on
好了现在我们来验证一下Docker可以正常的使用了。首先我们需要获取一个最新的centos镜像。
$ sudo docker pull centos
下一步,我们将确保我们可以看见运行的镜像:
$ sudo docker images centos
它会输出类似下面的结果:
$ sudo docker images centos
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
centos latest 0b443ba03958 2 hours ago 297.6 MB
使用镜像运行一个简单的shell脚本:
$ sudo docker run -i -t centos /bin/bash
如果一切正常,你会得到一个简单的bash提示符。输入exit继续。
CentOS提供了大量的Dockerfile模板,为了方便你熟悉docker的使用。模板都放在GitHub上https://github.com/CentOS/CentOS-Dockerfiles。
一切ok!您可以继续使用Docker 户指南或自己探索创建自己的的镜像。
如果您有任何问题 - 请直接报告给我们CentOS bug跟踪。
在任何地方都可以开发、部署运行任何程序
Docker是一个为开发者和系统管理员开发、运输和运行程序的平台。Docker可以让你快速的组装程序的组件,并且消除了运行带来的额外影响。Docker可以使你的代码测试部署带生产环境更加快速。
Docker由一下组成:
更加快速的分发你的程序。
部署和扩展更加容易
获取更高密度运算和运行更多工作负载
容易管理所以使得部署更加快速
理解Docker章节会帮助你
在安装指导会帮助你在各种系统上安装Docker。
学习更多的Docker细节,并且提问与回到相关使用与实现。访问Docker使用指导。
Version 1.2.0
该版本修复了一些bug和问题,并增加了新的功能和其他改进。这些包括:
我们增加了一个--restart
标志来重启运行指定的容器重启动。目前,有三种可用的策略:
no
- 不重新启动容器,如果它死了。 (默认)on-failure
- 当非零代码退出,重新启动容器。这也是可以接受的。可选最大重启次数(例如,n-failure:5)。always
- 总是重新启动容器,无论返回是什么的退出代码。在Docker守护进程中,这将弃用--restart
。docker run
:--cap-add
和–-cap-drop
。在以前的版本中,Docker容器既可以给出完整的功能,也可以设置白名单,而丢弃其他人。二现在,使用--privileged
将给予所有功能的容器内,而不是应用白名单。这是不建议在生产中使用,因为它是不安全的;这就好比因你是直接放在主机上。
对于docker run新版本增加了2个参数, --cap-add
和 --cap-drop
, 这给你了你想授予特定容器特定能力的细粒度控制。
docker run
增加新标识-–device
之前,你只能把设备挂载你的--privileged
的容器(使用-v)。现在可以使用--device
来运行Docker并不需要特定的容器的。
现在你能编辑/etc/hosts文件,/etc/hostname和/etc/resolv.conf文件,在容器运行的时候。这非常有用,如果你需要绑定别的服务,来覆盖的话。
注意,这些修改文件,如果不保存,那么docker build的时候是没有效果的。这改变只会在运行的容器中产生效果。
Docker的使用的代理将是对外的容器,它有自己单独的进程(每个连接一个进程)。这大大降低了守护程序的加载,从而增加了稳定性、效率和负载。
docker rm -
时候,Docker现在会在移除之前kill掉容器(不是原来的停止)。如果你想先停止,你应该适应docker stop。--dns
。这是阅读JVM规范的笔记。我画了一幅图,它能帮我更好的理解JVM。
每个线程的数据区包括程序计数器、JVM栈和本地方法栈。他们都是在线程创建的时候创建的。
程序计数器:它是用来控制每个线程的执行的。
JVM 栈: Stack里存放的是Frame(帧)(如下图所示)。
Native Method Stack(本地方法栈): 用来支持native methods (非Java语言method)。
所有的线程共享数据区有Heap和Method Area.
Heap(堆)是与我们平时编程最直接打交道的区域。它存放所有的对象和数组。在JVM启动时划分生成。常说的Garbage Collector垃圾回收器就是对这个区域工作的。
Method Area(方法区)存储类的结构信息,包括 run-time constant pool, field and method data, 和methods and constructors代码。
Runtime Constant Pool(运行时常量池)存放编译时可知的数值字面量和运行期解析后才能获得的method或field的引用。
Stack中的包含一些Frame, 这些Frame在method调动的时候生成。每一个Frame包括:local variable array, Operand Stack, Reference to Constant Pool.
以下是排名前10位的常见关于Java字符串的问题。
简单来说,如果”==”测试是相等的,那么使用 equals()也是相等的。如果你想知道这两个对象是不是相同,你应该使用equals()。
如果你知道string interning那会让你更好理解。
字符串是不可变的,这意味着一旦被创建,他们将保持不变,直到垃圾回收。但是数组话,你可以明确地改变它的元素。以这种方式,安全敏感信息(如密码)将不存在于系统中的任何地方。
JDK7可以。从JDK 7中,我们可以使用字符串作为switch的条件。 JDK6版本(包括6)之前,我们不能使用字符串作为switch的条件。
|
|
|
|
简单,如此频繁的使用,但是有时会被忽略。
我们可以使用正则表达式简单做分割。”\s”代表空格字符如”” “, “\t”, “\r”, “\n”。
|
|
在JDK6中,substring()方法提供了一个窗口给字符数组,它代表着现有的字符串,但不创建一个新的。要创建一个新的字符数组代表一个新的字符串,你可以做添加类似下面的一个空字符串:
|
|
这将创建一个新的字符数组,用来表示新的字符串。上述方法有时可以使你的代码更加快速,因为垃圾收集器收集未使用的大串,只保留了子字符串。
在Oracle的JDK7中,substring()创建一个新的字符数组,而不是使用现有的。JDK6和JDK7的substring()的区别。
String vs StringBuilder: StringBuilder是可变的,这意味着你可以在创建后修改。
StringBuilder vs StringBuffer: StringBuffer是同步的,这意味着它是线程安全的,但是比StringBuilder的慢。
在Python中,我们可以只乘了一个重复的数字就可以替换字符串。在Java中,我们可以使用来自Apache Commons Lang包中的StringUtils的repeat()方法。
|
|
|
|
可以使用来自Apache Commons Lang包中的StringUtils的countMatches()方法。
|
|
该文总结了排名前10位的错误,这些都是Java开发人员经常犯的。
把数组转成ArrayList,开发者通常这样做:
|
|
Arrays.asList()将会返回一个ArrayList 它是一个private static class在Arrays中,并非java.util.ArrayList类。java.util.Arrays.ArrayList类有set(), get(), contains()等方法,但是没有任何添加元素的方法,所以它的大小是固定的。看下面这个例子:
|
|
为了创建一个实际的ArrayList,你应该这样做:
|
|
ArrayList的构造函数可以接受一个集合类型,这包括了超类是java.util.Arrays.ArrayList的类。
开发者通常这样做:
|
|
这代码可以达到效果,但是在这里list根本没有必要转换成set。转换list成为set也是要花费时间的。使用下面代码就可以了:
|
|
或者:
|
|
第一个可读性比第二个好很多。
考虑一下下面的代码,在迭代过程中删除元素:
|
|
该方法存在一个严重的问题。当一个元素被移除时,该列表的大小减小,而index数的却是变化的。所以,如果你想通过使用索引来删除一个循环中的多个元素,那将是无法正常工作。
你可能知道,使用迭代器是正确的方式来删除循环中的元素,你知道foreach循环中的Java就像一个迭代器,但实际上它不是。考虑下面的代码:
|
|
它会抛出ConcurrentModificationException异常。
使用下面方法可以解决:
|
|
.next() 必须在 .remove()之前调用。在foreach循环中,编译器调用.next() 在.remove()元素之后,这引起了ConcurrentModificationException异常。你最好看看 ArrayList.iterator() 源码深度理解,这将有助于你的理解。
通常,在算法中Hashtable是一种数据结构的名称。但是在java中这种数据结构的名称是HashMap。最主要的一个不同是Hashtable是同步的。所以很多时候你是不需要使用Hashtable的,通常使用HashMap。
在java中,原始类型和无界通配符类型很容易混合在一起。用set来举个例子,Set是原始类型,当设置Set<?>
是无界通配符类型的时候。
考虑下面的代码,它使用原始类型列表作为参数:
|
|
此代码将抛出一个异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at ...
使用原始类型的集合是很危险的,因为在原始类型的集合中跳过了泛型类型的检查,这样是不是安全的。对于Set
, Set<?>
, 和 Set<Object>
是有很多不同的。Set vs. Set<?>和Java Type Erasure Mechanism
很多时候开发人员会使用public的字段级别。这很容易通过引用来获得字段的值,但是这是一个很糟糕的设计。经验法则是会给成员尽可能低的访问级别。
Java 访问级别: public, protected, private
当开发者不知道ArrayList 和 LinkedList的区别的时候,他们通常使用ArrayList,因为ArrayList看起来更加熟悉。然而,这直接将是一个很严重的性能问题。通常来说,LinkedList应该是首选的情况是,如果有大量的添加/删除操作和没有大量的随机访问操作。如果这对你有用的话,访问ArrayList vs. LinkedList获得有关他们的性能的详细信息。
不可改变的对象有许多优点,例如简单性,安全性等,但它需要对每个不同的值穿件一个单独的对象,对象太多可能导致无用GC,成本过高。可变和不可变之间进行选择时应该有一个平衡点。
在一般情况下,使用可变对象,以避免产生过多的中间对象。一个典型的例子是连接大量的字符串。如果您使用的是不可变的字符串,你会产生很多有资格立即垃圾回收的对象。这浪费时间和精力在CPU上,使用可变对象来解决这个问题(如StringBuilder)。
|
|
还有,可变对象在一些情况也是是可取的。例如通过可变对象的方法,可以让你收集多个结果,而无需通过太多的语法钻圈。另一个例子是排序和筛选:当然,你可以做一个方法,它传入原来的集合,返回一个新的有序的集合,但在大集合中会变得极其浪费空间。(来自dasblinkenlight在Stack Overflow的回答)
发生这种编译错误,是因为默认的超级构造函数是不确定的。在Java中,如果一个类没有定义构造函数,编译器会插入一个默认的无参数构造函数。如果构造函数是在超类中定义的,在Super(String s)情况下,编译器将不插入默认的无参数构造函数。Super 类就是以上的情况。
Sub类中的构造函数,无论是带参数还是不带参数,都需要调用 Super 类的无参构造函数。但Super类的默认构造函数是没有定义的,使用编译器报告了这个错误信息。
要解决此问题,只需在 Super中增加一个Super()构造函数,如下:
|
|
或删除自定义的Super构造函数,或增加 super(value)在sub类的构造方法中。
String 能通过两种方式创建:
|
|
那他们有什么不同?
下面的例子可以提供一个快速的答案:
|
|
关于它们是如何分配的内存的详细信息,请看java中创建字符串使用”” 或者 构造函数?
这份名单是根据我对大量在GitHub上、Stack Overflow问题和流行的Google查询分析。没有评估,难以证明他们恰恰是前10名,但绝他们对是很常见的。请留下您的评论,如果你有不同的意见。如果你能指出一些错误是十分常见的,我将会十分感激。
迭代器经常会出现问题,因为开发人员往往不知道它是如何工作的。下面的代码来源是ArrayList的源码:
最常见的问题是抛出java.util.ConcurrentModificationException异常。这异常实际通常是由remove方法抛出。当remove()调用是应该在 next()调用之后调用。如果remove()在next()调用之前调用,arraylist 的容量改变了,modCount != expectedModCount条件不满足,ConcurrentModificationException 异常就产生了。
|
|