程序员
董欣欣的个人博客

Springboot 开箱即用(源码分析)

阅读(1159)评论(1)

开箱即用:

Spring Boot提供了很多”开箱即用“的依赖模块,都是以spring-boot-starter-xx作为命名的。例如,之前提到的 spring-boot-starter-redis、spring-boot-starter-data-mongodb、spring-boot-starter-data-elasticsearch 等。

Spring Boot 的开箱即用是一个很棒的设计,给开发者带来很大的便利。开发者只要在 Maven 的 pom 文件中添加相关依赖后,Spring Boot 就会针对这个应用自动创建和注入需要的 Spring Bean 到上下文中。

那么,Spring Boot 如何巧妙的做到开箱即用,自动配置的呢?

现在,我们通过源码深入分析下 Spring Boot 的实现原理吧。

自动注入的核心在于 spring-boot-autoconfigure.jar 这个类库。在分析之前,我们先来看几个文件。

上面三个源码分别对应Redis、MongoDB、ElasticSearch。通过对比会发现它们都有一个特点,都存在 @ConditionalOnClass 注解。

Spring Boot 内部提供了很多自动化配置的类,例如,RedisAutoConfiguration 、MongoRepositoriesAutoConfiguration 、ElasticsearchAutoConfiguration , 这些自动化配置的类会判断 classpath 中是否存在自己需要的那个类,如果存在则会自动配置相关的配置,否则就不会自动配置,因此,开发者在 Maven 的 pom 文件中添加相关依赖后,这些依赖就会下载很多 jar 包到 classpath 中,有了这些 lib 就会触发自动化配置,所以,我们就能很便捷地使用对于的模块功能了。

Spring Boot 如何巧妙的做到开箱即用,自动配置的呢?实际上,Spring Boot 内部提供了很多自动化配置的类这些自动化配置的类会判断 classpath 中是否存在自己需要的那个类,如果存在则会自动配置相关的配置,否则就不会自动配置,因此,开发者在 Maven 的 pom 文件中添加相关依赖后,这些依赖就会下载很多 jar 包到 classpath 中,有了这些 lib 就会触发自动化配置。

Java爬取Json类型数据并解析

阅读(1373)评论(1)

本文主要讲解使用Java语言爬取Json类型数据并解析

爬取内容为字符串,格式如下:

代码分为两步:

1.根据接口爬出字符

2.解析爬取字符串内容,本次处理主要解析subjectTitle部分,并将其存储至数据库

 

Java 静态代理示例

阅读(743)评论(1)

常听到动态代理,了解动态代理之前首先要弄清楚什么是静态代理,下面通过代码来展示

静态代理模式如下图:

 

 

 

 

静态代理代码如下:

接口

目标对象

代理对象

 

客户端方法

 

运行结果

before
This is real subject!
after

Mac系统 安装和配置tomcat步骤详解

阅读(714)评论(0)

一:下载

打开Apache Tomcat官网,选择需要的版本下载:

这里写图片描述

二:存放到本地

三:启动

文件夹重名民为ApacheTomcat,放到/Users/计算机名/Library/目录下

这里写图片描述

打开终端,cd /Users/apple/Library/Tomcat/bin (注释:切换到我们Tomcat的bin目录)


打开终端输入 “cd”+”空格”,然后把bin文件夹拖到终端里,快速输入,点击回车

再输入: ./startup.sh  ,回车,就可以启动我们自己的Tomcat了,如下图:


启动成功

打开我们的浏览器,然后网址输入  http://localhost:8080/,如果出现一只猫,则证明配置成功~


实例界面

四、关闭Tomcat

同样是在bin 目录下,在终端输入: ./shutdown.sh + 回车,就可以了。


关闭Tomcat

五、配置的过程中可能遇到的问题

1. 终端中输入startup.sh后出现类似 “Permission denied” ,这个时候需要对目录进行权限设置:输入  sudo chmod 755 Library/Tomcat/bin/*.sh  回车,设置文件的读写执行权限;

解释:sudo通常为系统超级管理员755 代表用户对该文件拥有读,写,执行的权限,同组其他人员拥有执行和读的权限,没有写的权限,其他用户的权限和同组人员权限一样。777代表,user,group ,others ,都有读写和可执行权限。

六、Tomcat的目录结构及作用

|- bin:存放tomcat的命令。

catalina.bat命令:

startup.bat-> catalina.bat start

shutdown.bat- > catalina.bat stop

|- conf:存放tomcat的配置信息。其中server.xml文件是核心的配置文件。

|-lib:支持tomcat软件运行的jar包。其中还有技术支持包,如servlet,jsp

|-logs:运行过程的日志信息

|-temp:临时目录

|-webapps:共享资源目录。web应用目录。(注意不能以单独的文件进行共享)

|-work:tomcat的运行目录。jsp运行时产生的临时文件就存放在这里

|- WebRoot :web应用的根目录

|-静态资源(html+css+js+image+vedio)

|-WEB-INF:固定写法。

|-classes:(可选)固定写法。存放class字节码文件

|-lib:(可选)固定写法。存放jar包文件。

|-web.xml

注意:

1)WEB-INF目录里面的资源不能通过浏览器直接访问

2)如果希望访问到WEB-INF里面的资源,就必须把资源配置到一个叫web.xml的文件中。

Hash 散列函数的构建方法

阅读(1038)评论(6)

1.直接定址法

  直接定址法是以数据元素关键字k本身或它的线性函数作为它的哈希地址,即:H(k)=k  或 H(k)=a×k+b ; (其中a,b为常数)

  例1,有一个人口统计表,记录了从1岁到100岁的人口数目,其中年龄作为关键字,哈希函数取关键字本身,如图(1):

地址

A1

A2

……

A99

A100

年龄

1

2

……

99

100

人数

980

800

……

495

107

可以看到,当需要查找某一年龄的人数时,直接查找相应的项即可。如查找99岁的老人数,则直接读出第99项即可。这种哈希函数简单,并且对于不同的关键字不会产生冲突,但可以看出这是一种较为特殊的哈希函数,实际生活中,关键字的元素很少是连续的。用该方法产生的哈希表会造成空间大量的浪费,因此这种方法适应性并不强。[2]↑

2.数字分析法

 2.1数字分析法是取数据元素关键字中某些取值较均匀的数字位作为哈希地址的方法。即当关键字的位数很多时,可以通过对关键字的各位进行分析,丢掉分布不均匀的位,作为哈希值。它只适合于所有关键字值已知的情况。通过分析分布情况把关键字取值区间转化为一个较小的关键字取值区间。

   例2,要构造一个数据元素个数n=80,哈希长度m=100的哈希表。不失一般性,我们这里只给出其中8个关键字进行分析,8个关键字如下所示:

K1=61317602      K2=61326875      K3=62739628      K4=61343634

K5=62706815      K6=62774638      K7=61381262      K8=61394220

分析上述8个关键字可知,关键字从左到右的第1、2、3、6位取值比较集中,不宜作为哈希地址,剩余的第4、5、7、8位取值较均匀,可选取其中的两位作为哈希地址。设选取最后两位作为哈希地址,则这8个关键字的哈希地址分别为:2,75,28,34,15,38,62,20。[1]↑

2. 2设有n个d 位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某位上分布均匀些,每种符号出现的机会均等;在某位上分布不均匀,只有某几种符号经常出现。可根据哈希表的大小,选取其中各种符号分布均匀的若干位作为哈希地址。计算各位数字中符号分布均匀度rk的公式为:rk=其中,aki表示第i个符号k位上出现的的期望值。计算出rk值越小,

i=1

表明在该位(第k位)各种符号分布越不均匀。

例3,有一组关键字,对其各位编码如下:

9   2   1   4   8

9   1   2   6   9

9   0   5   2   7

9   1   6   3   0

9   1   8   0   5

9   1   5   5   8

9   2   0   4   7

9   0   0   0   1

①  ②  ③  ④  ⑤

①位仅“9”出现8次r1=(8-8/10)2*1+(0-8/10)2*9=57.60

②位“0,2”各出现2次,“1”出现4次r2=(2-8/10)2*2+(4-8/10)2*1+(0-8/10)2*7=17.60

③位“0,5”各出现2次,“1,2,6,8”各出现1次r3=(2-8/10)2*2+(1-8/10)2*4+(0-8/10)2*4=5.60

④位“0,4”各出现2次,“2,3,5,6”各出现1次

⑤位“7,8”各出现2次,“0,1,5,9”各出现1次

r3 =r4 =r5 =5.60

若哈希表地址范围有3位数字,取各关键字的③④⑤位作为记录的哈希地址。也可以把第①②和第⑤位想加,舍去进位,变成一位数,再与第③④位合起来哈希地址等。显然数字分析法仅适用于事先知道表中所有关键字每一位数值的分布情况,它完全依赖于关键字集合。如果换一个关键字集合,选择哪几位重新决定。

3.折叠法

  所谓折叠法是将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位),这方法称为折叠法。这种方法适用于关键字位数较多,而且关键字中每一位上数字分布大致均匀的情况。

  折叠法中数位折叠又分为移位叠加和边界叠加两种方法,移位叠加是将分割后是每一部分的最低位对齐,然后相加;边界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。

例4,当哈希表长为1000时,关键字key=110108331119891,允许的地址空间为三位十进制数,则这两种叠加情况如图(2):

       移位叠加                                 边界叠加

       8 9 1                                     8 9 1

       1 1 9                                     9 1 1

       3 3 1                                     3 3 1

       1 0 8                                     8 0 1

    +  1 1 0                                   + 1 1 0

   (1) 5 5 9                                  (3)0 4 4

                 图(2)由折叠法求哈希地址

     用移位叠加得到的哈希地址是559,而用边界叠加所得到的哈希地址是44。如果关键字不是数值而是字符串,则可先转化为数。转化的办法可以用ASCⅡ字符或字符的次序值。[3]↑

4.平方取中法

  这是一种常用的哈希函数构造方法。这个方法是先取关键字的平方,然后根据可使用空间的大小,选取平方数是中间几位为哈希地址。

哈希函数 H(key)=“key2的中间几位”因为这种方法的原理是通过取平方扩大差别,平方值的中间几位和这个数的每一位都相关,则对不同的关键字得到的哈希函数值不易产生冲突,由此产生的哈希地址也较为均匀。

例5,若设哈希表长为1000则可取关键字平方值的中间三位,如图(3)所示:

关键字

关键字的平方

哈希函数值

1234

1522756

227

2143

4592449

924

4132

17073424

734

3214

10329796

297

图(3)平方取中哈希函数示例    [4] ↑

有人曾用“轮盘赌”的统计分析方法对它们进行了模拟分析,结论是平方取中法最接近“随机化”。

  例6,设有一组关键字值为ABC,BCD,CDE,DEF其相应的机内码分别为010203,020304,030405,040506。假设可利用地址空间大小为103,平方后取平方数的中间三位作为相当记录的存储地址。如图(4)所示:

关键字

机内码

机内码的平方

哈希地址

ABC

010203

0104101209

101

BCD

020304

0412252416

252

CDE

030405

0924464025

464

DEF

040506

1640736036

736

图(4)平方取中法关键字及其存储地址[6]↑

   下面给出平方取中法的哈希函数

     //平方取中法哈希函数,结设关键字值32位的整数

     //哈希函数将返回key * key的中间10位

       Int  Hash (int key)

         {

     //计算key的平方

      Key * = key ;

     //去掉低11位

     Key>>=11;

     // 返回低10位(即key * key的中间10位)

       Return key %1024;

          }

5.减去法

   减去法是数据的键值减去一个特定的数值以求得数据存储的位置。

例7,公司有一百个员工,而员工的编号介于1001到1100,减去法就是员工编号减去1000后即为数据的位置。编号1001员工的数据在数据中的第一笔。编号1002员工的数据在数据中的第二笔…依次类推。从而获得有关员工的所有信息,因为编号1000以前并没有数据,所有员工编号都从1001开始编号。

6.基数转换法

  将十进制数X看作其他进制,比如十三进制,再按照十三进制数转换成十进制数,提取其中若干为作为X的哈希值。一般取大于原来基数的数作为转换的基数,并且两个基数应该是互素的。

例8,Hash(80127429)=(80127429)13=8*137+0*136+1*135+2*134+7*133+4*132+2*131+9=(502432641)10如果取中间三位作为哈希值,得Hash(80127429)=432

 为了获得良好的哈希函数,可以将几种方法联合起来使用,比如先变基,再折叠或平方取中等等,只要散列均匀,就可以随意拼凑。[5] ↑

7.除留余数法

取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址,即设定哈希函数为  Hash(key)=key mod p (p≤m),其中,除数p称作模。

除留余数法不仅可以对关键字直接取模,也可以在折叠、平方取中等运算后取模。对于除留余数法求哈希地址,关键在于模p的选择。使得数据元素集合中每一个关键字通过该哈希函数映射到内存单元的任意地址上的概率相等,从而尽可能减少发生哈希冲突的可能性。

理论研究表明,除留余数法的模p取不大于表长且最接近表长m素数时效果最好,且p最好取1.1n~1.7n之间的一个素数(n为存在的数据元素个数)。例如:当n=7时,p最好取11、13等素数。 又例图(5):

表长m

8

16

32

64

128

256

512

1000

模p

7

13

31

61

127

251

503

997

由于除留余数法的地址计算方法简单,而且在许多情况下效果较好。[2]↑

例9,公司有236个员工,而员工编号介于1000到9999,除留余数法就是员工编号除以数据个数236后,去余数即为数据的位置。编号5428员工的数据(编号5428除以236取余数得0)放数据中的第一笔,编号3512员工数据(编号3512除以236取余数得8)放数据中的第九笔…依次类推。

8.随机乘数法

  亦称为“乘余取整法”。随机乘数法使用一个随机实数f,0≤f<1,乘积f*k的分数部分在0~1之间,用这个分数部分的值与n(哈希表的长度)相乘,乘积的整数部分就是对应的哈希值,显然这个哈希值落在0~n-1之间。其表达公式为:Hash(k)=「n*(f*k%1)」其中“f*k%1”表示f*k 的小数部分,即f*k%1=f*k-「f*k」[5] ↑

  例10,对下列关键字值集合采用随机乘数法计算哈希值,随机数f=0.103149002 哈希表长度n=100得图(6):

k

f*k

n*((f*k)的小数部分)

Hash(k)

319426

32948.47311

47.78411

47

718309

74092.85648

86.50448

86

629443

64926.41727

42.14427

42

919697

84865.82769

83.59669

83

  此方法的优点是对n的选择不很关键。通常若地址空间为p位就是选n=2p.Knuth对常数f的取法做了仔细的研究,他认为f取任何值都可以,但某些值效果更好。如f=(-1)/2=0.6180329…比较理想。[8] ↑

9.字符串数值哈希法

在很都情况下关键字是字符串,因此这样对字符串设计Hash函数是一个需要讨论的问题。下列函数是取字符串前10个字符来设计的哈希函数

Int Hash _ char (char *X)

{

  int I ,sum

  i=0;

  while (i 10 && X[i])

  Sum +=X[i++];

  sum%=N;      //N是记录的条数

  }

这种函数把字符串的前10个字符的ASCⅡ值之和对N取摸作为Hash地址,只要N较小,Hash地址将较均匀分布[0,N]区间内,因此这个函数还是可用的。对于N很大的情形,可使用下列函数

int ELFhash (char *key )

{

 Unsigned long h=0,g;

whie (*key)

{

h=(h<<4)+ *key;

key++;

g=h & 0 xF0000000L;

if (g) h^=g>>24;

h & =~g;

}

h=h % N

return (h);

}

  这个函数称为ELFHash(Exextable and Linking Format ,ELF,可执行链接格式)函数。它把一个字符串的绝对长度作为输入,并通过一种方式把字符的十进制值结合起来,对长字符串和短字符串都有效,这种方式产生的位置不可能不均匀分布。[7] ↑

10.旋转法

  旋转法是将数据的键值中进行旋转。旋转法通常并不直接使用在哈希函数上,而是搭配其他哈希函数使用。

  例11,某学校同一个系的新生(小于100人)的学号前5位数是相同的,只有最后2位数不同,我们将最后一位数,旋转放置到第一位,其余的往右移。

新生学号

旋转过程

旋转后的新键值

5062101

5062101

1506210

5062102

5062102

2506210

5062103

5062103

3506210

5062104

5062104

4506210

5062105

5062105

5506210

                      如图(7)

 运用这种方法可以只输入一个数值从而快速地查到有关学生的信息。[9] ↑

11.伪随机数法

伪随机数法是将利用数据的键值经过随机数法的运算后的结果作为数据存储的位置。其公式如下(a和c为质数):

Y=(a * Key + c)mod 数组的大小

例12,某公司的某女员工的编号是321547,现该公司共有107个女职工,我们取a=13,c=5则

Y=(13*321547+5)%107

 =(4180111+5)%107

 =54

则取54当作该员工数据存储的位置。[10] ↑

小 结

有许多种不同的哈希函数设计方法,这里主要讨论几种常用的不同类型关键字的希函数设计方法:直接定址法、数字分析法、折叠法、平方取中法、减去法、基数转换法、除留余数法、随机乘数法、字符串数值哈希法、伪随机数法、旋转法。

尽管哈希函数的构造方法有很多,但不同的方法适用于不同的情况。如:当键字是字符串时可以用字符串数值哈希法构造哈希函数;当关键字是整数类型时就可以用除留余数法、直接定址法和数字分析法等设计哈希函数;而关键字是小数类型常用伪随机数法来构造哈希函数等。

java synchronized(非this对象)

阅读(775)评论(2)

说明:

使用synchronized(非this对象)同步代码块时候,和this对象类似,锁的都是当前的对象,即对象监视器必须是同一个对象。如果不是同一个对象监视器,那么就可以异步执行了,因为锁的不是同一个对象,就会导致交叉运行了,所以在写代码时候要注意这个地方。

代码实例:

 

 

 

运行结果:

a begin
b begin
a end
b end

java 访问同一对象同步、非同步方法

阅读(735)评论(0)

同一对象访问其同步和非同步方法:

1、A线程先持有Object对象的lock锁,B线程可以以异步的方式调用Object对象中非synchronized类型的方法。

2、A线程先持有Object对象的lock锁,B线程如果在这时候调用Object对象中synchronized类型的方法,则需要等待,也就是同步。

代码示例:

 

 

 

运行结果:

begin methodA threadName=A
begin methodB threadName=B
end
end Time=1492477179766

 

java 线程方法内变量为安全

阅读(1145)评论(0)

java中,如果方法内部有变量,则不存在非线程安全问题,因为方法内变量不是共有的变量,访问的是不同的地址,即自己运行自己的。下面用例子说明。

 

 

 

运行结果:

a set over!
b set over
a num= 100
b num= 200

虽然传递是一个类,但是这样相当于addI方法执行两遍,num为每个线程私有所以是不会产生非线程安全问题!

如果num不是在方法内部,而是在方法外部,访问即为一个地址下的变量,则会产生非线程安全问题,下面看另一个例子。

 

 

 

运行结果:

a set over!
b set over
b num= 200
a num= 200

因为num为公共的变量,所以会产生非线程安全。

TreeSet 原理及使用

阅读(923)评论(0)

1.TreeSet原理:

TreeSet存储对象的时候, 可以排序, 但是需要指定排序的算法
Integer能排序(有默认顺序), String能排序(有默认顺序), 自定义的类存储的时候出现异常(没有顺序)
如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口
在类上implement Comparable重写compareTo()方法
在方法内定义比较算法, 根据大小关系, 返回正数负数或零
在使用TreeSet存储对象的时候, add()方法内部就会自动调用compareTo()方法进行比较, 根据比较结果使用二叉树形式进行存储

2.TreeSet是依靠TreeMap来实现的。

TreeSet是一个有序集合,TreeSet中的元素将按照升序排列,缺省是按照自然排序进行排列,意味着TreeSet中的元素要实现Comparable接口。或者有一个自定义的比较器。
我们可以在构造TreeSet对象时,传递实现Comparator接口的比较器对象。

输出结果:
 
abc
 
rst
 
xyz
打印结果不是和先前加入的顺序一样,它是按照一个字母的排序法进行排序的。这是因为String 类实现了Comparable接口。
如果我们自己定义的一个类的对象要加入到TreeSet当中,那么这个类必须要实现Comparable接口。

运行结果:
 
学号:4 姓名:wangwu
学号:3 姓名:mazi
学号:3 姓名:wangmazi
学号:2 姓名:lisi
学号:1 姓名:zhangsan

Java interrupt、interrupted、isInterrupted区别

阅读(951)评论(0)

java多线程中会遇见interrupt,下面解释下其功能和不同

interrupt():中断线程,事实上线程执行此句话并不是完全意义上中断,而是继续运行。此句话要想达到真正意义上中断,需要结合return 或者异常结合使用。

interrupted():测试当前线程是否已经是中断状态,且执行后具有将状态标志清除为false功能。此方法是static类型的,所以调用时候this、Thread或者对象都是一样的,获取的都是当前线程。

isInterrupted():测试当前对象的线程是否已经是中断状态,但不清除状态标志,除非线程已经运行完毕结束时候会自动变成false。

代码展示:

线程类:

 

 

 

www.dongxinxin.cn 技术博客

联系我关于我