什么都只会一点点

什么都只会一点点 查看完整档案

成都编辑成都大学  |  计算机科学与技术 编辑北森云计算  |  后端开发 编辑 blog.csdn.net/qq_36172443 编辑
编辑

非著名的CURD程序员。

csdn博客: https://blog.csdn.net/qq_3617...

个人动态

什么都只会一点点 赞了回答 · 8月3日

如何向一个指定的邮箱发送1W封邮件

当邮箱数量大于1W时收取服务的性能如何,比较好奇的是邮件数量为什么会影响收取服务的性能?这两个基本没什么关联吧。

关注 5 回答 4

什么都只会一点点 提出了问题 · 8月2日

如何向一个指定的邮箱发送1W封邮件

如题:我们现在有一个邮箱收取的一个性能测试的任务,如要测试当邮箱数量大于1W时收取服务的性能如何,所以现在需要伪造一些邮箱数据。但是如何得到这个1W封邮件的邮箱是一个问题,由于是给测试同学写伪造数据的工具,所以不想太耗工时(2天之内)

现在遇到的困难是
1、各大邮箱都有每日发送邮件限制大概(100-500不等)
2、各大邮箱都有每日IP接收限制,固定IP的话,很快就会被邮箱服务器限制住
各位大佬有没有好的思路?

关注 5 回答 4

什么都只会一点点 提出了问题 · 8月2日

如何向一个指定的邮箱发送1W封邮件

如题:我们现在有一个邮箱收取的一个性能测试的任务,如要测试当邮箱数量大于1W时收取服务的性能如何,所以现在需要伪造一些邮箱数据。但是如何得到这个1W封邮件的邮箱是一个问题,由于是给测试同学写伪造数据的工具,所以不想太耗工时(2天之内)

现在遇到的困难是
1、各大邮箱都有每日发送邮件限制大概(100-500不等)
2、各大邮箱都有每日IP接收限制,固定IP的话,很快就会被邮箱服务器限制住
各位大佬有没有好的思路?

关注 5 回答 4

什么都只会一点点 赞了文章 · 2019-12-13

突发!Nginx 之父被拘留,原因竟然是“接私活儿”?

clipboard.png

俄罗斯警方今天突击搜查了 Nginx 在莫斯科的办事处,扣押了公司的设备,并对企业员工进行拘留讯问。

据员工爆料,在这次突袭行动中,Nginx 被拘留的两名成员分别为 Nginx 的创始人& CTO 伊戈尔·赛索耶夫(Igor Sysoev),以及联合创始人马克西姆·科诺瓦洛夫(Maxim Konovalov)。

Nginx 是什么?

clipboard.png

Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在 BSD-like 协议下发行。该软件由伊戈尔·赛索耶夫创建并于 2004 年首次公开发布。

它的特点是占有内存少,并发能力强,事实上 Nginx 的并发能力在同类型的网页服务器中表现较好,中国大陆使用 Nginx 网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

2019 年 2 月,Nginx 第一次超越 Apache HTTPD,成为互联网上部署最广泛的服务器。根据 Netcraft 2019 年 12 月 Web 服务器调查,Nginx 的市场份额为 38%。

同年 3 月份,网络安全和网络设备供应商 F5 Networks 以 6.7 亿美元的价格收购了 Nginx。

为什么被拘留?

clipboard.png

此次是 Rambler 集团对 Nginx 提起了侵权诉讼。Rambler 声称赛索耶夫在担任公司的系统管理员时开发了 Nginx,因此他们才是该项目的合法所有者,拥有 Nginx Web 服务器代码的完全所有权。

Rambler 集团是 rambler.ru 的母公司,rambler.ru 是俄罗斯最大的搜索引擎和互联网门户之一。

赛索耶夫并没有否认在 Rambler 工作期间创建 Nginx。在 2012 年的一次采访中,赛索耶夫也曾公开声明了他在业余时间开发了 Nginx,而 Rambler 甚至多年都没有意识到这一点。

他说,该服务器最初是在 Rate.ee 和 zvuki.ru 网站上部署的,而 Rambler 仅在同事询问后才开始使用它。

开发者业余时间写的代码,版权也归公司?

clipboard.png

其实这个问题在国内外都很普遍,往小了说就是在职期间“接私活儿”。

那么在职期间,我们用业余时间写的代码、做的项目,版权到底归个人还是公司?谈论这个问题前,有几个问题需要先确认一下:

第一,业余时间开发的软件和公司经营范围主营业务的软件是否有竞争关系?第二,开发者是否和公司签署保密协议?公司是否有相关约束性制度?

第三,接私活的过程中是否利用了公司的资源?

法律层面来讲,员工兼职是合法的。《劳动合同法》并未禁止员工兼职,员工兼职是其谋生的权利,他人无正当理由无权干涉。

但值得注意的是,员工兼职的工作内容需与现在职公司的工作内容无关。若员工兼职的工作内容与在职工作内容实质相同,即涉嫌侵犯现公司的商业秘密。

尤其在技术领域,技术人员兼职或者接私活儿虽然使用的是自己的工作经验及劳动技能,但该工作经验及劳动技能是技术人员在现公司的平台上,主要利用现公司的物质技术条件而获得,所以这在很大程度上构成了侵犯公司的商业利益。

其实,员工「接私活」在企业中很常见。

对于员工来讲,可能觉得公司的薪酬制度不合理,或者单纯是因为钱不够花想多挣一些钱;

对于公司来讲,给员工发了工资,就希望员工遵守薪酬制度,否则员工一边拿工资一边又分摊体力和精力接私活,一方面会导致管理混乱,另一方面也相当于直接损害了公司的利益。

我个人对这个的理解很开放,大公司的运营不太了解,可能对企业政策和制度规范化的要求更高;但对创业阶段的企业来说,如果员工能在保证质量的前提下高效产出,且不损害公司利益,接私活儿又能有效缓解他个人的生活压力或者提高生活质量,是可以默许的;

跟这种员工相比,我觉得那些虽然不接私活,每天加班加点在公司看似很勤劳敬业,但毫无绩效、没有产出的员工,可能更“有待商榷”。

部分资料来源:

维基百科:Nginx
ZDNet:《Russian police raid NGINX Moscow office》
Netcraft 《全球 Web 服务器调查》


这事儿你怎么看?你觉得在职员工可以“接私活儿”么?

clipboard.png

查看原文

赞 11 收藏 1 评论 12

什么都只会一点点 赞了文章 · 2019-09-29

redis 过期策略

Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除。你可以想象 Redis 内部有一个死神,时刻盯着所有设置了过期时间的 key,寿命一到就会立即收割。

你还可以进一步站在死神的角度思考,会不会因为同一时间太多的 key 过期,以至于忙不过来。同时因为 Redis 是单线程的,收割的时间也会占用线程的处理时间,如果收割的太过于繁忙,会不会导致线上读写指令出现卡顿。

这些问题 Antirez 早就想到了,所有在过期这件事上,Redis 非常小心。

过期的 key 集合

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。

定时扫描策略

Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

从过期字典中随机 20 个 key;
删除这 20 个 key 中已经过期的 key;
如果过期的 key 比率超过 1/4,那就重复步骤 1;
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

设想一个大型的 Redis 实例中所有的 key 在同一时间过期了,会出现怎样的结果?

毫无疑问,Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得稀疏,才会停止 (循环次数明显下降)。这就会导致线上读写请求出现明显的卡顿现象。导致这种卡顿的另外一种原因是内存管理器需要频繁回收内存页,这也会产生一定的 CPU 消耗。

当客户端请求到来时,服务器如果正好进入过期扫描状态,客户端的请求将会等待至少 25ms 后才会进行处理,如果客户端将超时时间设置的比较短,比如 10ms,那么就会出现大量的链接因为超时而关闭,业务端就会出现很多异常。而且这时你还无法从 Redis 的 slowlog 中看到慢查询记录,因为慢查询指的是逻辑处理过程慢,不包含等待时间。

所以业务开发人员一定要注意过期时间,如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的...

查看原文

赞 2 收藏 1 评论 0

什么都只会一点点 赞了文章 · 2019-09-17

实用小工具推荐 OpenWrite

【实用小工具推荐】给技术同学们推荐一款比较好用的工具,可以实现一稿多发,主流的技术渠道基本涵盖了:https://www.openwrite.cn/

因为工作的关系,认识了很多做技术公众号的小伙伴,同时也在经营自己的各大自媒体矩阵。

  • 如果你喜欢写博记录你的成长点滴
  • 如果你喜欢Markdown多过图文编辑器
  • 如果你因没有精力一文多发而一直被抄袭洗稿

或许你该试试这个:https://www.openwrite.cn/

支持Markdown编写,轻松扩散国内主流科技类媒体,比如你熟知的:思否、思否、思否、CSDN、博客园、开源中国、思否等!

查看原文

赞 15 收藏 4 评论 2

什么都只会一点点 赞了文章 · 2019-09-15

科普:为什么 String hashCode 方法选择数字31作为乘子

1. 背景

某天,我在写代码的时候,无意中点开了 String hashCode 方法。然后大致看了一下 hashCode 的实现,发现并不是很复杂。但是我从源码中发现了一个奇怪的数字,也就是本文的主角31。这个数字居然不是用常量声明的,所以没法从字面意思上推断这个数字的用途。后来带着疑问和好奇心,到网上去找资料查询一下。在看完资料后,默默的感叹了一句,原来是这样啊。那么到底是哪样呢?在接下来章节里,请大家带着好奇心和我揭开数字31的用途之谜。

2. 选择数字31的原因

在详细说明 String hashCode 方法选择数字31的作为乘子的原因之前,我们先来看看 String hashCode 方法是怎样实现的,如下:

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;
}

上面的代码就是 String hashCode 方法的实现,是不是很简单。实际上 hashCode 方法核心的计算逻辑只有三行,也就是代码中的 for 循环。我们可以由上面的 for 循环推导出一个计算公式,hashCode 方法注释中已经给出。如下:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

这里说明一下,上面的 s 数组即源码中的 val 数组,是 String 内部维护的一个 char 类型数组。这里我来简单推导一下这个公式:

假设 n=3
i=0 -> h = 31 * 0 + val[0]
i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]
i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]
       h = 31*31*31*0 + 31*31*val[0] + 31*val[1] + val[2]
       h = 31^(n-1)*val[0] + 31^(n-2)*val[1] + val[2]

上面的公式包括公式的推导并不是本文的重点,大家了解了解即可。接下来来说说本文的重点,即选择31的理由。从网上的资料来看,一般有如下两个原因:

第一,31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。那么为啥偏偏选中了31呢?请看第二个原因。

第二、31可以被 JVM 优化,31 * i = (i << 5) - i

上面两个原因中,第一个需要解释一下,第二个比较简单,就不说了。下面我来解释第一个理由。一般在设计哈希算法时,会选择一个特殊的质数。至于为啥选择质数,我想应该是可以降低哈希算法的冲突率。至于原因,这个就要问数学家了,我几乎可以忽略的数学水平解释不了这个原因。上面说到,31是一个不大不小的质数,是优选乘子。那为啥同是质数的2和101(或者更大的质数)就不是优选乘子呢,分析如下。

这里先分析质数2。首先,假设 n = 6,然后把质数2和 n 带入上面的计算公式。并仅计算公式中次数最高的那一项,结果是2^5 = 32,是不是很小。所以这里可以断定,当字符串长度不是很长时,用质数2做为乘子算出的哈希值,数值不会很大。也就是说,哈希值会分布在一个较小的数值区间内,分布性不佳,最终可能会导致冲突率上升。

上面说了,质数2做为乘子会导致哈希值分布在一个较小区间内,那么如果用一个较大的大质数101会产生什么样的结果呢?根据上面的分析,我想大家应该可以猜出结果了。就是不用再担心哈希值会分布在一个小的区间内了,因为101^5 = 10,510,100,501。但是要注意的是,这个计算结果太大了。如果用 int 类型表示哈希值,结果会溢出,最终导致数值信息丢失。尽管数值信息丢失并不一定会导致冲突率上升,但是我们暂且先认为质数101(或者更大的质数)也不是很好的选择。最后,我们再来看看质数31的计算结果: 31^5 = 28629151,结果值相对于3210,510,100,501来说。是不是很nice,不大不小。

上面用了比较简陋的数学手段证明了数字31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。接下来我会用详细的实验来验证上面的结论,不过在验证前,我们先看看 Stack Overflow 上关于这个问题的讨论,Why does Java's hashCode() in String use 31 as a multiplier?。其中排名第一的答案引用了《Effective Java》中的一段话,这里也引用一下:

The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i`. Modern VMs do this sort of optimization automatically.

简单翻译一下:

选择数字31是因为它是一个奇质数,如果选择一个偶数会在乘法运算中产生溢出,导致数值信息丢失,因为乘二相当于移位运算。选择质数的优势并不是特别的明显,但这是一个传统。同时,数字31有一个很好的特性,即乘法运算可以被移位和减法运算取代,来获取更好的性能:31 * i == (i << 5) - i,现代的 Java 虚拟机可以自动的完成这个优化。

排名第二的答案设这样说的:

As Goodrich and Tamassia point out, If you take over 50,000 English words (formed as the union of the word lists provided in two variants of Unix), using the constants 31, 33, 37, 39, and 41 will produce less than 7 collisions in each case. Knowing this, it should come as no surprise that many Java implementations choose one of these constants.

这段话也翻译一下:

正如 Goodrich 和 Tamassia 指出的那样,如果你对超过 50,000 个英文单词(由两个不同版本的 Unix 字典合并而成)进行 hash code 运算,并使用常数 31, 33, 37, 39 和 41 作为乘子,每个常数算出的哈希值冲突数都小于7个,所以在上面几个常数中,常数 31 被 Java 实现所选用也就不足为奇了。

上面的两个答案完美的解释了 Java 源码中选用数字 31 的原因。接下来,我将针对第二个答案就行验证,请大家继续往下看。

3. 实验及数据可视化

本节,我将使用不同的数字作为乘子,对超过23万个英文单词进行哈希运算,并计算哈希算法的冲突率。同时,我也将针对不同乘子算出的哈希值分布情况进行可视化处理,让大家可以直观的看到数据分布情况。本次实验所使用的数据是 Unix/Linux 平台中的英文字典文件,文件路径为 /usr/share/dict/words

3.1 哈希值冲突率计算

计算哈希算法冲突率并不难,比如可以一次性将所有单词的 hash code 算出,并放入 Set 中去除重复值。之后拿单词数减去 set.size() 即可得出冲突数,有了冲突数,冲突率就可以算出来了。当然,如果使用 JDK8 提供的流式计算 API,则可更方便算出,代码片段如下:

public static Integer hashCode(String str, Integer multiplier) {
    int hash = 0;
    for (int i = 0; i < str.length(); i++) {
        hash = multiplier * hash + str.charAt(i);
    }

    return hash;
}
    
/**
 * 计算 hash code 冲突率,顺便分析一下 hash code 最大值和最小值,并输出
 * @param multiplier
 * @param hashs
 */
public static void calculateConflictRate(Integer multiplier, List<Integer> hashs) {
    Comparator<Integer> cp = (x, y) -> x > y ? 1 : (x < y ? -1 : 0);
    int maxHash = hashs.stream().max(cp).get();
    int minHash = hashs.stream().min(cp).get();

    // 计算冲突数及冲突率
    int uniqueHashNum = (int) hashs.stream().distinct().count();
    int conflictNum = hashs.size() - uniqueHashNum;
    double conflictRate = (conflictNum * 1.0) / hashs.size();

    System.out.println(String.format("multiplier=%4d, minHash=%11d, maxHash=%10d, conflictNum=%6d, conflictRate=%.4f%%",
                multiplier, minHash, maxHash, conflictNum, conflictRate * 100));
}

结果如下:

从上图可以看出,使用较小的质数做为乘子时,冲突率会很高。尤其是质数2,冲突率达到了 55.14%。同时我们注意观察质数2作为乘子时,哈希值的分布情况。可以看得出来,哈希值分布并不是很广,仅仅分布在了整个哈希空间的正半轴部分,即 0 ~ 231-1。而负半轴 -231 ~ -1,则无分布。这也证明了我们上面断言,即质数2作为乘子时,对于短字符串,生成的哈希值分布性不佳。然后再来看看我们之前所说的 31、37、41 这三个不大不小的质数,表现都不错,冲突数都低于7个。而质数 101 和 199 表现的也很不错,冲突率很低,这也说明哈希值溢出并不一定会导致冲突率上升。但是这两个家伙一言不合就溢出,我们认为他们不是哈希算法的优选乘子。最后我们再来看看 32 和 36 这两个偶数的表现,结果并不好,尤其是 32,冲突率超过了了50%。尽管 36 表现的要好一点,不过和 31,37相比,冲突率还是比较高的。当然并非所有的偶数作为乘子时,冲突率都会比较高,大家有兴趣可以自己验证。

3.2 哈希值分布可视化

上一节分析了不同数字作为乘子时的冲突率情况,这一节来分析一下不同数字作为乘子时,哈希值的分布情况。在详细分析之前,我先说说哈希值可视化的过程。我原本是打算将所有的哈希值用一维散点图进行可视化,但是后来找了一圈,也没找到合适的画图工具。加之后来想了想,一维散点图可能不合适做哈希值可视化,因为这里有超过23万个哈希值。也就意味着会在图上显示超过23万个散点,如果不出意外的话,这23万个散点会聚集的很密,有可能会变成一个大黑块,就失去了可视化的意义了。所以这里选择了另一种可视化效果更好的图表,也就是 excel 中的平滑曲线的二维散点图(下面简称散点曲线图)。当然这里同样没有把23万散点都显示在图表上,太多了。所以在实际绘图过程中,我将哈希空间等分成了64个子区间,并统计每个区间内的哈希值数量。最后将分区编号做为X轴,哈希值数量为Y轴,就绘制出了我想要的二维散点曲线图了。这里举个例子说明一下吧,以第0分区为例。第0分区数值区间是[-2147483648, -2080374784),我们统计落在该数值区间内哈希值的数量,得到 <分区编号, 哈希值数量> 数值对,这样就可以绘图了。分区代码如下:

 /**
 * 将整个哈希空间等分成64份,统计每个空间内的哈希值数量
 * @param hashs
 */
public static Map<Integer, Integer> partition(List<Integer> hashs) {
    // step = 2^32 / 64 = 2^26
    final int step = 67108864;
    List<Integer> nums = new ArrayList<>();
    Map<Integer, Integer> statistics = new LinkedHashMap<>();
    int start = 0;
    for (long i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i += step) {
        final long min = i;
        final long max = min + step;
        int num = (int) hashs.parallelStream()
                .filter(x -> x >= min && x < max).count();

        statistics.put(start++, num);
        nums.add(num);
    }

    // 为了防止计算出错,这里验证一下
    int hashNum = nums.stream().reduce((x, y) -> x + y).get();
    assert hashNum == hashs.size();

    return statistics;
}

本文中的哈希值是用整形表示的,整形的数值区间是 [-2147483648, 2147483647],区间大小为 2^32。所以这里可以将区间等分成64个子区间,每个自子区间大小为 2^26。详细的分区对照表如下:

分区编号分区下限分区上限分区编号分区下限分区上限
0-2147483648-208037478432067108864
1-2080374784-20132659203367108864134217728
2-2013265920-194615705634134217728201326592
3-1946157056-187904819235201326592268435456
4-1879048192-181193932836268435456335544320
5-1811939328-174483046437335544320402653184
6-1744830464-167772160038402653184469762048
7-1677721600-161061273639469762048536870912
8-1610612736-154350387240536870912603979776
9-1543503872-147639500841603979776671088640
10-1476395008-140928614442671088640738197504
11-1409286144-134217728043738197504805306368
12-1342177280-127506841644805306368872415232
13-1275068416-120795955245872415232939524096
14-1207959552-1140850688469395240961006632960
15-1140850688-10737418244710066329601073741824
16-1073741824-10066329604810737418241140850688
17-1006632960-9395240964911408506881207959552
18-939524096-8724152325012079595521275068416
19-872415232-8053063685112750684161342177280
20-805306368-7381975045213421772801409286144
21-738197504-6710886405314092861441476395008
22-671088640-6039797765414763950081543503872
23-603979776-5368709125515435038721610612736
24-536870912-4697620485616106127361677721600
25-469762048-4026531845716777216001744830464
26-402653184-3355443205817448304641811939328
27-335544320-2684354565918119393281879048192
28-268435456-2013265926018790481921946157056
29-201326592-1342177286119461570562013265920
30-134217728-671088646220132659202080374784
31-6710886406320803747842147483648

接下来,让我们对照上面的分区表,对数字2、3、17、31、101的散点曲线图进行简单的分析。先从数字2开始,数字2对于的散点曲线图如下:

上面的图还是很一幕了然的,乘子2算出的哈希值几乎全部落在第32分区,也就是 [0, 67108864)数值区间内,落在其他区间内的哈希值数量几乎可以忽略不计。这也就不难解释为什么数字2作为乘子时,算出哈希值的冲突率如此之高的原因了。所以这样的哈希算法要它有何用啊,拖出去斩了吧。接下来看看数字3作为乘子时的表现:

3作为乘子时,算出的哈希值分布情况和2很像,只不过稍微好了那么一点点。从图中可以看出绝大部分的哈希值最终都落在了第32分区里,哈希值的分布性很差。这个也没啥用,拖出去枪毙5分钟吧。在看看数字17的情况怎么样:

数字17作为乘子时的表现,明显比上面两个数字好点了。虽然哈希值在第32分区和第34分区有一定的聚集,但是相比较上面2和3,情况明显好好了很多。除此之外,17作为乘子算出的哈希值在其他区也均有分布,且较为均匀,还算是一个不错的乘子吧。

接下来来看看我们本文的主角31了,31作为乘子算出的哈希值在第33分区有一定的小聚集。不过相比于数字17,主角31的表现又好了一些。首先是哈希值的聚集程度没有17那么严重,其次哈希值在其他区分布的情况也要好于17。总之,选31,准没错啊。


最后再来看看大质数101的表现,不难看出,质数101作为乘子时,算出的哈希值分布情况要好于主角31,有点喧宾夺主的意思。不过不可否认的是,质数101的作为乘子时,哈希值的分布性确实更加均匀。所以如果不在意质数101容易导致数据信息丢失问题,或许其是一个更好的选择。

4.写在最后

经过上面的分析与实践,我想大家应该明白了 String hashCode 方法中选择使用数字31作为乘子的原因了。本文本质是一篇简单的科普文而已,并没有银弹😁。如果大家读完后觉得又涨知识了,那这篇文章的目的就达到了。最后,本篇文章的配图画的还是很辛苦的,所以如果大家觉得文章不错,不妨就给个赞吧,就当是对我的鼓励了。另外,如果文章中有不妥或者错误的地方,也欢迎指出来。如果能不吝赐教,那就更好了。最后祝大家生活愉快,再见。

本文在知识共享许可协议 4.0 下发布,转载请注明出处
作者:coolblog
为了获得更好的分类阅读体验,
请移步至本人的个人博客:http://www.coolblog.xyz

cc
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

查看原文

赞 100 收藏 68 评论 24

什么都只会一点点 赞了文章 · 2019-09-08

Spring IOC 容器源码分析系列文章导读

1. 简介

Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已经非常成熟了。Spring 包含了众多模块,包括但不限于 Core、Bean、Context、AOP 和 Web 等。在今天,我们完全可以使用 Spring 所提供的一站式解决方案开发出我们所需要的应用。作为 Java 程序员,我们会经常和 Spring 框架打交道,所以还是很有必要弄懂 Spring 的原理。

本文是 Spring IOC 容器源码分析系列文章的第一篇文章,将会着重介绍 Spring 的一些使用方法和特性,为后续的源码分析文章做铺垫。另外需要特别说明一下,本系列的源码分析文章是基于Spring 4.3.17.RELEASE版本编写的,而非最新的5.0.6.RELEASE版本。好了,关于简介先说到这里,继续来说说下面的内容。

2. 文章编排

写 Spring IOC 这一块的文章,挺让我纠结的。我原本是打算在一篇文章中分析所有的源码,但是后来发现文章实在太长。主要是因为 Spring IOC 部分的源码实在太长,将这一部分的源码贴在一篇文章中还是很壮观的。当然估计大家也没兴趣读下去,所以决定对文章进行拆分。这里先贴一张文章切分前的目录结构:

如上图,由目录可以看出,假使在一篇文章中写完所有内容,文章的长度将会非常长。所以在经过思考后,我会将文章拆分成一系列的文章,如下:

  1. Spring IOC 容器源码分析 - 获取单例 bean - ✅已更新
  2. Spring IOC 容器源码分析 - 创建单例 bean 的过程 - ✅已更新
  3. Spring IOC 容器源码分析 - 创建原始 bean 对象 - ✅ 已更新
  4. Spring IOC 容器源码分析 - 循环依赖的解决办法 - ✅ 已更新
  5. Spring IOC 容器源码分析 - 填充属性到原始 bean 对象中 - ✅ 已更新
  6. Spring IOC 容器源码分析 - 余下的初始化工作 - ✅ 已更新

上面文章对应的源码分析工作均已经完成,所有的文章将会在近期内进行更新。

3. Spring 模块结构

Spring 是分模块开发的,Spring 包含了很多模块,其中最为核心的是 bean 容器相关模块。像 AOP、MVC、Data 等模块都要依赖 bean 容器。这里先看一下 Spring 框架的结构图:

图片来源:Spring 官方文档

从上图中可以看出Core Container处于整个框架的最底层(忽略 Test 模块),在其之上有 AOP、Data、Web 等模块。既然 Spring 容器是最核心的部分,那么大家如果要读 Spring 的源码,容器部分必须先弄懂。本篇文章作为 Spring IOC 容器的开篇文章,就来简单介绍一下容器方面的知识。请继续往下看。

4. Spring IOC 部分特性介绍

本章将会介绍 IOC 中的部分特性,这些特性均会在后面的源码分析中悉数到场。如果大家不是很熟悉这些特性,这里可以看一下。

4.1 alias

alias 的中文意思是“别名”,在 Spring 中,我们可以使用 alias 标签给 bean 起个别名。比如下面的配置:

<bean id="hello" class="xyz.coolblog.service.Hello">
    <property name="content" value="hello"/>
</bean>
<alias name="hello" alias="alias-hello"/>
<alias name="alias-hello" alias="double-alias-hello"/>

这里我们给hello这个 beanName 起了一个别名alias-hello,然后又给别名alias-hello起了一个别名double-alias-hello。我们可以通过这两个别名获取到hello这个 bean 实例,比如下面的测试代码:

public class ApplicationContextTest {

    @Test
    public void testAlias() {
        String configLocation = "application-alias.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
        System.out.println("    alias-hello -> " + applicationContext.getBean("alias-hello"));
        System.out.println("double-alias-hello -> " + applicationContext.getBean("double-alias-hello"));
    }
}

测试结果如下:

4.2 autowire

本小节,我们来了解一下 autowire 这个特性。autowire 即自动注入的意思,通过使用 autowire 特性,我们就不用再显示的配置 bean 之间的依赖了。把依赖的发现和注入都交给 Spring 去处理,省时又省力。autowire 几个可选项,比如 byName、byType 和 constructor 等。autowire 是一个常用特性,相信大家都比较熟悉了,所以本节我们就 byName 为例,快速结束 autowire 特性的介绍。

当 bean 配置中的 autowire = byName 时,Spring 会首先通过反射获取该 bean 所依赖 bean 的名字(beanName),然后再通过调用 BeanFactory.getName(beanName) 方法即可获取对应的依赖实例。autowire = byName 原理大致就是这样,接下来我们来演示一下。

public class Service {

    private Dao mysqlDao;

    private Dao mongoDao;

    // 忽略 getter/setter

    @Override
    public String toString() {
        return super.toString() + "\n\t\t\t\t\t{" +
            "mysqlDao=" + mysqlDao +
            ", mongoDao=" + mongoDao +
            '}';
    }
}

public interface Dao {}
public class MySqlDao implements Dao {}
public class MongoDao implements Dao {}

配置如下:

<bean name="mongoDao" class="xyz.coolblog.autowire.MongoDao"/>
<bean name="mysqlDao" class="xyz.coolblog.autowire.MySqlDao"/>

<!-- 非自动注入,手动配置依赖 -->
<bean name="service-without-autowire" class="xyz.coolblog.autowire.Service" autowire="no">
    <property name="mysqlDao" ref="mysqlDao"/>
    <property name="mongoDao" ref="mongoDao"/>
</bean>

<!-- 通过设置 autowire 属性,我们就不需要像上面那样显式配置依赖了 -->
<bean name="service-with-autowire" class="xyz.coolblog.autowire.Service" autowire="byName"/>

测试代码如下:

String configLocation = "application-autowire.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
System.out.println("service-without-autowire -> " + applicationContext.getBean("service-without-autowire"));
System.out.println("service-with-autowire -> " + applicationContext.getBean("service-with-autowire"));

测试结果如下:

从测试结果可以看出,两种方式配置方式都能完成解决 bean 之间的依赖问题。只不过使用 autowire 会更加省力一些,配置文件也不会冗长。这里举的例子比较简单,假使一个 bean 依赖了十几二十个 bean,再手动去配置,恐怕就很难受了。

4.3 FactoryBean

FactoryBean?看起来是不是很像 BeanFactory 孪生兄弟。不错,他们看起来很像,但是他们是不一样的。FactoryBean 是一种工厂 bean,与普通的 bean 不一样,FactoryBean 是一种可以产生 bean 的 bean,好吧说起来很绕嘴。FactoryBean 是一个接口,我们可以实现这个接口。下面演示一下:

public class HelloFactoryBean implements FactoryBean<Hello> {

    @Override
    public Hello getObject() throws Exception {
        Hello hello = new Hello();
        hello.setContent("hello");
        return hello;
    }

    @Override
    public Class<?> getObjectType() {
        return Hello.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

配置如下:

<bean id="helloFactory" class="xyz.coolblog.service.HelloFactoryBean"/>

测试代码如下:

public class ApplicationContextTest {

    @Test
    public void testFactoryBean() {
        String configLocation = "application-factory-bean.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
        System.out.println("helloFactory -> " + applicationContext.getBean("helloFactory"));
        System.out.println("&helloFactory -> " + applicationContext.getBean("&helloFactory"));
    }
}

测试结果如下:

由测试结果可以看到,当我们调用 getBean("helloFactory") 时,ApplicationContext 会返回一个 Hello 对象,该对象是 HelloFactoryBean 的 getObject 方法所创建的。如果我们想获取 HelloFactoryBean 本身,则可以在 helloFactory 前加上一个前缀&,即&helloFactory

4.4 factory-method

介绍完 FactoryBean,本节再来看看了一个和工厂相关的特性 -- factory-method。factory-method 可用于标识静态工厂的工厂方法(工厂方法是静态的),直接举例说明吧:

public class StaticHelloFactory {

    public static Hello getHello() {
        Hello hello = new Hello();
        hello.setContent("created by StaticHelloFactory");
        return hello;
    }
}

配置如下:

<bean id="staticHelloFactory" class="xyz.coolblog.service.StaticHelloFactory" factory-method="getHello"/>

测试代码如下:

public class ApplicationContextTest {

    @Test
    public void testFactoryMethod() {
        String configLocation = "application-factory-method.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
        System.out.println("staticHelloFactory -> " + applicationContext.getBean("staticHelloFactory"));
    }
}

测试结果如下:

对于非静态工厂,需要使用 factory-bean 和 factory-method 两个属性配合。关于 factory-bean 这里就不继续说了,留给大家自己去探索吧。

4.5 lookup-method

lookup-method 特性可能大家用的不多(我也没用过),不过它也是个有用的特性。在介绍这个特性前,先介绍一下背景。我们通过 BeanFactory getBean 方法获取 bean 实例时,对于 singleton 类型的 bean,BeanFactory 每次返回的都是同一个 bean。对于 prototype 类型的 bean,BeanFactory 则会返回一个新的 bean。现在考虑这样一种情况,一个 singleton 类型的 bean 中有一个 prototype 类型的成员变量。BeanFactory 在实例化 singleton 类型的 bean 时,会向其注入一个 prototype 类型的实例。但是 singleton 类型的 bean 只会实例化一次,那么它内部的 prototype 类型的成员变量也就不会再被改变。但如果我们每次从 singleton bean 中获取这个 prototype 成员变量时,都想获取一个新的对象。这个时候怎么办?举个例子(该例子源于《Spring 揭秘》一书),我们有一个新闻提供类(NewsProvider),这个类中有一个新闻类(News)成员变量。我们每次调用 getNews 方法都想获取一条新的新闻。这里我们有两种方式实现这个需求,一种方式是让 NewsProvider 类实现 ApplicationContextAware 接口(实现 BeanFactoryAware 接口也是可以的),每次调用 NewsProvider 的 getNews 方法时,都从 ApplicationContext 中获取一个新的 News 实例,返回给调用者。第二种方式就是这里的 lookup-method 了,Spring 会在运行时对 NewsProvider 进行增强,使其 getNews 可以每次都返回一个新的实例。说完了背景和解决方案,接下来就来写点测试代码验证一下。

在演示两种处理方式前,我们先来看看不使用任何处理方式,BeanFactory 所返回的 bean 实例情况。相关类定义如下:

public class News {
    // 仅演示使用,News 类中无成员变量
}

public class NewsProvider {

    private News news;

    public News getNews() {
        return news;
    }

    public void setNews(News news) {
        this.news = news;
    }
}

配置信息如下:

<bean id="news" class="xyz.coolblog.lookupmethod.News" scope="prototype"/>
<bean id="newsProvider" class="xyz.coolblog.lookupmethod.NewsProvider">
    <property name="news" ref="news"/>
</bean>

测试代码如下:

String configLocation = "application-lookup-method.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
NewsProvider newsProvider = (NewsProvider) applicationContext.getBean("newsProvider");
System.out.println(newsProvider.getNews());
System.out.println(newsProvider.getNews());

测试结果如下:

从测试结果中可以看出,newsProvider.getNews() 方法两次返回的结果都是一样的,这个是不满足要求的。

4.5.1 实现 ApplicationContextAware 接口

我们让 NewsProvider 实现 ApplicationContextAware 接口,实现代码如下:

public class NewsProvider implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private News news;

    /** 每次都从 applicationContext 中获取一个新的 bean */
    public News getNews() {
        return applicationContext.getBean("news", News.class);
    }

    public void setNews(News news) {
        this.news = news;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

配置和测试代码同上,测试结果如下:

这里两次获取的 news 并就不是同一个 bean 了,满足了我们的需求。

4.5.2 使用 lookup-method 特性

使用 lookup-method 特性,配置文件需要改一下。如下:

<bean id="news" class="xyz.coolblog.lookupmethod.News" scope="prototype"/>
<bean id="newsProvider" class="xyz.coolblog.lookupmethod.NewsProvider">
    <lookup-method name="getNews" bean="news"/>
</bean>

NewsProvider 的代码沿用 4.5.1 小节之前贴的代码。测试代码稍微变一下,如下:

String configLocation = "application-lookup-method.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
NewsProvider newsProvider = (NewsProvider) applicationContext.getBean("newsProvider");
System.out.println("newsProvider -> " + newsProvider);
System.out.println("news 1 -> " + newsProvider.getNews());
System.out.println("news 2 -> " + newsProvider.getNews());

测试结果如下:

从上面的结果可以看出,new1 和 new2 指向了不同的对象。同时,大家注意看 newsProvider,似乎变的很复杂。由此可看出,NewsProvider 被 CGLIB 增强了。

4.6 depends-on

当一个 bean 直接依赖另一个 bean,可以使用 <ref/> 标签进行配置。不过如某个 bean 并不直接依赖于 其他 bean,但又需要其他 bean 先实例化好,这个时候就需要使用 depends-on 特性了。depends-on 特性比较简单,就不演示了。仅贴一下配置文件的内容,如下:

这里有两个简单的类,其中 Hello 需要 World 在其之前完成实例化。相关配置如下:

<bean id="hello" class="xyz.coolblog.depnedson.Hello" depends-on="world"/>
<bean id="world" class="xyz.coolblog.depnedson.World" />

4.7 BeanPostProcessor

BeanPostProcessor 是 bean 实例化时的后置处理器,包含两个方法,其源码如下:

public interface BeanPostProcessor {
    // bean 初始化前的回调方法
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    // bean 初始化后的回调方法    
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

BeanPostProcessor 是 Spring 框架的一个扩展点,通过实现 BeanPostProcessor 接口,我们就可插手 bean 实例化的过程。比如大家熟悉的 AOP 就是在 bean 实例后期间将切面逻辑织入 bean 实例中的,AOP 也正是通过 BeanPostProcessor 和 IOC 容器建立起了联系。这里我来演示一下 BeanPostProcessor 的使用方式,如下:

/**
 * 日志后置处理器,将会在 bean 创建前、后打印日志
 */
public class LoggerBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before " + beanName + " Initialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After " + beanName + " Initialization");
        return bean;
    }
}

配置如下:

<bean class="xyz.coolblog.beanpostprocessor.LoggerBeanPostProcessor"/>
    
<bean id="hello" class="xyz.coolblog.service.Hello"/>
<bean id="world" class="xyz.coolblog.service.World"/>

测试代码如下:

public class ApplicationContextTest {

    @Test
    public void testBeanPostProcessor() {
        String configLocation = "application-bean-post-processor.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
    }
}

测试结果如下:

与 BeanPostProcessor 类似的还有一个叫 BeanFactoryPostProcessor 拓展点,顾名思义,用户可以通过这个拓展点插手容器启动的过程。不过这个不属于本系列文章范畴,暂时先不细说了。

4.8 BeanFactoryAware

Spring 中定义了一些列的 Aware 接口,比如这里的 BeanFactoryAware,以及 BeanNameAware 和 BeanClassLoaderAware 等等。通过实现这些 Aware 接口,我们可以在运行时获取一些配置信息或者其他一些信息。比如实现 BeanNameAware 接口,我们可以获取 bean 的配置名称(beanName)。通过实现 BeanFactoryAware 接口,我们可以在运行时获取 BeanFactory 实例。关于 Aware 类型接口的使用,可以参考4.5.1 实现 ApplicationContextAware 接口一节中的叙述,这里就不演示了。

5. 阅读源码的一些建议

我在去年八月份的时候,尝试过阅读 Spring 源码。不过 Spring 源码太多了,调用复杂,看着看着就迷失在了代码的海洋里。不过好在当时找到了一个经过简化后的类 Spring 框架,该框架黄亿华前辈在学习 Spring 源码时所写的 tiny-spring。如果大家觉得看 Spring 源码比较困难,可以先学习一下 tiny-spring 的源码,先对 Spring 代码结构有个大概的了解。

另外也建议大家自己动手实现一个简单的 IOC 容器,通过实践,才会有更多的感悟。我在去年八月份的时候,实现过一个简单的 IOC 和 AOP(可以参考我去年发的文章:仿照 Spring 实现简单的 IOC 和 AOP - 上篇),并在最后将两者整合到了一起。正是有了之前的实践,才使得我对 Spring 的原理有了更进一步的认识。当做完一些准备工作后,再次阅读 Spring 源码,就没以前那么痛苦了。当然,Spring 的代码经过十几年的迭代,代码量很大。我在分析的过程中也只是尽量保证搞懂重要的逻辑,无法做到面面俱到。不过,如果大家愿意去读 Spring 源码,我相信会比我理解的更透彻。

除了上面说的动手实践外,在阅读源码的过程中,如果实在看不懂,不妨调试一下。比如某个变量你不知道有什么用,但是它又比较关键,在多个地方都出现了,显然你必须要搞懂它。那么此时写点测试代码调试一下,立马就能知道它有什么用了。

以上是我在阅读源码时所使用过的一些方法,当然仅有上面那些可能还不够。本节的最后再推荐两本书,如下:

第二本书在豆瓣上的评分不太好,不过我觉得还好。这本书我仅看了容器部分的代码分析,总的来说还行。我在看循环依赖那一块的代码时,这本书还是给了我一些帮助的。好了,本节就到这里。

6. 写在最后

在本文的最后一章,我来说说我为什么阅读 Spring 的源码吧。对我个人而言,有两个原因。第一,作为 Java Web 开发人员,我们基本上绕不过 Spring 技术栈。当然除了 Spring,还有很多其他的选择(比如有些公司自己封装框架)。但不可否认,Spring 现在仍是主流。对于这样一个经常打交道的框架,弄懂实现原理,还有很有必要的。起码在它出错输出一大堆异常时,你不会很心慌,可以从容的 debug。第二,我去年的这个时候,工作差不多快满一年。我在写第一年的工作总结时,觉得很心慌。感觉第一年好像只学到了一点开发的皮毛,技术方面,没什么积累。加上后来想换工作,心里想着就自己现在的水平恐怕要失业了。所以为了平复自己焦虑的情绪,于是在去年的八月份的时候,我开始写博客了。到现在写了近30篇博客(比较少),其中有两篇文章被博客平台作为优秀文章推荐过。现在,又快到7月份了,工龄马上又要+1了。所以我现在在抓紧写 Spring 相关的文章,希望在六月份多写几篇。算是对自己工作满两年的一个阶段性技术总结,也是一个纪念吧。

当然,看懂源码并不是什么很了不起的事情,毕竟写这些源码的人才是真正的大神。我在大学的时候,自学 MFC(没错,就是微软早已淘汰的东西)。并读过侯捷老师著的《深入浅出MFC》一书,这本书中的一句话对我影响至深 - “勿在浮沙筑高台”。勿在浮沙筑高台,对于一个程序员来说,基础很重要。所以我现在也在不断的学习,希望能把基础打好,这样以后才能进入更高的层次。

好了,感谢大家耐心看完我的唠叨。本文先到这里,我要去写后续的文章了,后面再见。bye~

附录:Spring 源码分析文章列表

Ⅰ. IOC

更新时间标题
2018-05-30Spring IOC 容器源码分析系列文章导读
2018-06-01Spring IOC 容器源码分析 - 获取单例 bean
2018-06-04Spring IOC 容器源码分析 - 创建单例 bean 的过程
2018-06-06Spring IOC 容器源码分析 - 创建原始 bean 对象
2018-06-08Spring IOC 容器源码分析 - 循环依赖的解决办法
2018-06-11Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
2018-06-11Spring IOC 容器源码分析 - 余下的初始化工作

Ⅱ. AOP

更新时间标题
2018-06-17Spring AOP 源码分析系列文章导读
2018-06-20Spring AOP 源码分析 - 筛选合适的通知器
2018-06-20Spring AOP 源码分析 - 创建代理对象
2018-06-22Spring AOP 源码分析 - 拦截器链的执行过程

Ⅲ. MVC

更新时间标题
2018-06-29Spring MVC 原理探秘 - 一个请求的旅行过程
2018-06-30Spring MVC 原理探秘 - 容器的创建过程
本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处
作者:coolblog.xyz
本文同步发布在我的个人博客:http://www.coolblog.xyz

cc
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

查看原文

赞 14 收藏 10 评论 0

什么都只会一点点 赞了文章 · 2019-08-27

史上最全阿里 Java 面试题总结

以下为大家整理了阿里巴巴史上最全的 Java 面试题,涉及大量 Java 面试知识点和相关试题。


JAVA基础

  1. JAVA中的几种基本数据类型是什么,各自占用多少字节。
  2. String类能被继承吗,为什么。
  3. String,Stringbuffer,StringBuilder的区别。
  4. ArrayList和LinkedList有什么区别。
  5. 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字

段,当new的时候,他们的执行顺序。

  1. 用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们

内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。

  1. JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何

设计。

  1. 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的。
  2. 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口

么。

  1. 继承和聚合的区别在哪。
  2. IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。
  3. 反射的原理,反射创建类实例的三种方式是什么。
  4. 反射中,Class.forName和ClassLoader区别 。
  5. 描述动态代理的几种实现方式,分别说出相应的优缺点。
  6. 动态代理与cglib实现的区别。
  7. 为什么CGlib方式可以对接口实现代理。
  8. final的用途。
  9. 写出三种单例模式实现 。
  10. 如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣。
  11. 请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设

计中的作用。

  1. 深拷贝和浅拷贝区别。
  2. 数组和链表数据结构描述,各自的时间复杂度。
  3. error和exception的区别,CheckedException,RuntimeException的区别。
  4. 请列出5个运行时异常。
  5. 在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加

载?为什么。

  1. 说一说你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需

要重新实现这两个方法。

  1. 在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题。
  2. 这样的a.hashcode() 有什么用,与a.equals(b)有什么关系。
  3. 有没有可能2个不相等的对象有相同的hashcode。
  4. Java中的HashSet内部是如何工作的。
  5. 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
  6. java8的新特性。

JVM知识

  1. 什么情况下会发生栈内存溢出。
  2. JVM的内存结构,Eden和Survivor比例。
  3. JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。
  4. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参

数。

  1. 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。
  2. 垃圾回收算法的实现原理。
  3. 当出现了内存溢出,你怎么排错。
  4. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作

内存等。

  1. 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。
  2. 讲讲JAVA的反射机制。
  3. 你们线上应用的JVM参数有哪些。
  4. g1和cms区别,吞吐量优先和响应优先的垃圾收集器选择。
  5. 怎么打出线程栈信息。
  6. 请解释如下jvm参数的含义:

-server -Xms512m -Xmx512m -Xss1024K
-XX:PermSize=256m -XX:MaxPermSize=512m -
XX:MaxTenuringThreshold=20XX:CMSInitiatingOccupancyFraction=80 -
XX:+UseCMSInitiatingOccupancyOnly。

开源框架知识

  1. 简单讲讲tomcat结构,以及其类加载器流程,线程模型等。
  2. tomcat如何调优,涉及哪些参数 。
  3. 讲讲Spring加载流程。
  4. Spring AOP的实现原理。
  5. 讲讲Spring事务的传播属性。
  6. Spring如何管理事务的。
  7. Spring怎么配置事务(具体说出一些关键的xml 元素)。
  8. 说说你对Spring的理解,非单例注入的原理?它的生命周期?循环注入的原理,aop的实现原

理,说说aop中的几个术语,它们是怎么相互工作的。

  1. Springmvc 中DispatcherServlet初始化过程。
  2. netty的线程模型,netty如何基于reactor模型上实现的。
  3. 为什么选择netty。
  4. 什么是TCP粘包,拆包。解决方式是什么。
  5. netty的fashwheeltimer的用法,实现原理,是否出现过调用不够准时,怎么解决。
  6. netty的心跳处理在弱网下怎么办。
  7. netty的通讯协议是什么样的。
  8. springmvc用到的注解,作用是什么,原理。
  9. springboot启动机制。
  10. 点击这里有一套答案版的Spring试题。

操作系统

  1. Linux系统下你关注过哪些内核参数,说说你知道的。
  2. Linux下IO模型有几种,各自的含义是什么。
  3. epoll和poll有什么区别。
  4. 平时用到哪些Linux命令。
  5. 用一行命令查看文件的最后五行。
  6. 用一行命令输出正在运行的java进程。
  7. 介绍下你理解的操作系统中线程切换过程。
  8. 进程和线程的区别。
  9. top 命令之后有哪些内容,有什么作用。
  10. 线上CPU爆高,请问你如何找到问题所在。

多线程

  1. 多线程的几种实现方式,什么是线程安全。
  2. volatile的原理,作用,能代替锁么。
  3. 画一个线程的生命周期状态图。
  4. sleep和wait的区别。
  5. sleep和sleep(0)的区别。
  6. Lock与Synchronized的区别 。
  7. synchronized的原理是什么,一般用在什么地方(比如加在静态方法和非静态方法的区别,静

态方法和非静态方法同时执行的时候会有影响吗),解释以下名词:重排序,自旋锁,偏向锁,轻
量级锁,可重入锁,公平锁,非公平锁,乐观锁,悲观锁。

  1. 用过哪些原子类,他们的原理是什么。
  2. JUC下研究过哪些并发工具,讲讲原理。
  3. 用过线程池吗,如果用过,请说明原理,并说说newCache和newFixed有什么区别,构造函

数的各个参数的含义是什么,比如coreSize,maxsize等。

  1. 线程池的关闭方式有几种,各自的区别是什么。
  2. 假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有10个线程同

时调用它,如何做到。

  1. spring的controller是单例还是多例,怎么保证并发的安全。
  2. 用三个线程按顺序循环打印abc三个字母,比如abcabcabc。
  3. ThreadLocal用过么,用途是什么,原理是什么,用的时候要注意什么。
  4. 如果让你实现一个并发安全的链表,你会怎么做。
  5. 有哪些无锁数据结构,他们实现的原理是什么。
  6. 讲讲java同步机制的wait和notify。
  7. CAS机制是什么,如何解决ABA问题。
  8. 多线程如果线程挂住了怎么办。
  9. countdowlatch和cyclicbarrier的内部原理和用法,以及相互之间的差别(比如

countdownlatch的await方法和是怎么实现的)。

  1. 对AbstractQueuedSynchronizer了解多少,讲讲加锁和解锁的流程,独占锁和公平所

加锁有什么不同。

  1. 使用synchronized修饰静态方法和非静态方法有什么区别。
  2. 简述ConcurrentLinkedQueue和LinkedBlockingQueue的用处和不同之处。
  3. 导致线程死锁的原因?怎么解除线程死锁。
  4. 非常多个线程(可能是不同机器),相互之间需要等待协调,才能完成某种工作,问怎么设计这种协调方案。
  5. 用过读写锁吗,原理是什么,一般在什么场景下用。
  6. 开启多个线程,如果保证顺序执行,有哪几种实现方式,或者如何保证多个线程都执行完

再拿到结果。

  1. 延迟队列的实现方式,delayQueue和时间轮算法的异同。
  2. 点击这里有一套答案版的多线程试题。

TCP与HTTP

  1. http1.0和http1.1有什么区别。
  2. TCP三次握手和四次挥手的流程,为什么断开连接要4次,如果握手只有两次,会出现什么。
  3. TIME_WAIT和CLOSE_WAIT的区别。
  4. 说说你知道的几种HTTP响应码,比如200, 302, 404。
  5. 当你用浏览器打开一个链接(如:http://www.javastack.cn)的时候,计算机做了哪些工作步骤。
  6. TCP/IP如何保证可靠性,说说TCP头的结构。
  7. 如何避免浏览器缓存。
  8. 如何理解HTTP协议的无状态性。
  9. 简述Http请求get和post的区别以及数据包格式。
  10. HTTP有哪些method
  11. 简述HTTP请求的报文格式。
  12. HTTP的长连接是什么意思。
  13. HTTPS的加密方式是什么,讲讲整个加密解密流程。
  14. Http和https的三次握手有什么区别。
  15. 什么是分块传送。
  16. Session和cookie的区别。
  17. 点击这里有一套答案版的试题。

架构设计与分布式

  1. 用java自己实现一个LRU。
  2. 分布式集群下如何做到唯一序列号。
  3. 设计一个秒杀系统,30分钟没付款就自动关闭交易。
  4. 如何使用redis和zookeeper实现分布式锁?有什么区别优缺点,会有什么问题,分别适用什么

场景。(延伸:如果知道redlock,讲讲他的算法实现,争议在哪里)

  1. 如果有人恶意创建非法连接,怎么解决。
  2. 分布式事务的原理,优缺点,如何使用分布式事务,2pc 3pc 的区别,解决了哪些问题,还有

哪些问题没解决,如何解决,你自己项目里涉及到分布式事务是怎么处理的。

  1. 什么是一致性hash。
  2. 什么是restful,讲讲你理解的restful。
  3. 如何设计一个良好的API。
  4. 如何设计建立和保持100w的长连接。
  5. 解释什么是MESI协议(缓存一致性)。
  6. 说说你知道的几种HASH算法,简单的也可以。
  7. 什么是paxos算法, 什么是zab协议。
  8. 一个在线文档系统,文档可以被编辑,如何防止多人同时对同

一份文档进行编辑更新。

  1. 线上系统突然变得异常缓慢,你如何查找问题。
  2. 说说你平时用到的设计模式。
  3. Dubbo的原理,有看过源码么,数据怎么流转的,怎么实现集群,负载均衡,服务注册

和发现,重试转发,快速失败的策略是怎样的 。

  1. 一次RPC请求的流程是什么。
  2. 自己实现过rpc么,原理可以简单讲讲。Rpc要解决什么问题。
  3. 异步模式的用途和意义。
  4. 编程中自己都怎么考虑一些设计原则的,比如开闭原则,以及在工作中的应用。
  5. 设计一个社交网站中的“私信”功能,要求高并发、可扩展等等。 画一下架构图。
  6. MVC模式,即常见的MVC框架。
  7. 聊下曾经参与设计的服务器架构并画图,谈谈遇到的问题,怎么解决的。
  8. 应用服务器怎么监控性能,各种方式的区别。
  9. 如何设计一套高并发支付方案,架构如何设计。
  10. 如何实现负载均衡,有哪些算法可以实现。
  11. Zookeeper的用途,选举的原理是什么。
  12. Zookeeper watch机制原理。
  13. Mybatis的底层实现原理。
  14. 请思考一个方案,实现分布式环境下的countDownLatch。
  15. 后台系统怎么防止请求重复提交。
  16. 描述一个服务从发布到被消费的详细过程。
  17. 讲讲你理解的服务治理。
  18. 如何做到接口的幂等性。
  19. 如何做限流策略,令牌桶和漏斗算法的使用场景。
  20. 什么叫数据一致性,你怎么理解数据一致性。
  21. 分布式服务调用方,不依赖服务提供方的话,怎么处理服务方挂掉后,大量无效资源请求

的浪费,如果只是服务提供方吞吐不高的时候该怎么做,如果服务挂了,那么一会重启,该怎
么做到最小的资源浪费,流量半开的实现机制是什么。

  1. dubbo的泛化调用怎么实现的,如果是你,你会怎么做。
  2. 远程调用会有超时现象,如果做到优雅的控制,JDK自带的超时机制有哪些,怎么实现的。

算法

  1. 10亿个数字里里面找最小的10个。
  2. 有1亿个数字,其中有2个是重复的,快速找到它,时间和空间要最优。
  3. 2亿个随机生成的无序整数,找出中间大小的值。
  4. 给一个不知道长度的(可能很大)输入字符串,设计一种方案,将重复的字符排重。
  5. 遍历二叉树。
  6. 有3n+1个数字,其中3n个中是重复的,只有1个是不重复的,怎么找出来。
  7. 写一个字符串(如:www.javastack.cn)反转函数。
  8. 常用的排序算法,快排,归并、冒泡。 快排的最优时间复杂度,最差复杂度。冒泡排序的

优化方案。

  1. 二分查找的时间复杂度,优势。
  2. 一个已经构建好的TreeSet,怎么完成倒排序。
  3. 什么是B+树,B-树,列出实际的使用场景。
  4. 一个单向链表,删除倒数第N个数据。
  5. 200个有序的数组,每个数组里面100个元素,找出top20的元素。
  6. 单向链表,查找中间的那个元素。

数据库知识

  1. 数据库隔离级别有哪些,各自的含义是什么,MYSQL默认的隔离级别是是什么。
  2. 什么是幻读。
  3. MYSQL有哪些存储引擎,各自优缺点。
  4. 高并发下,如何做到安全的修改同一行数据。
  5. 乐观锁和悲观锁是什么,INNODB的标准行级锁有哪2种,解释其含义。
  6. SQL优化的一般步骤是什么,怎么看执行计划,如何理解其中各个字段的含义。
  7. 数据库会死锁吗,举一个死锁的例子,mysql怎么解决死锁。
  8. MYsql的索引原理,索引的类型有哪些,如何创建合理的索引,索引如何优化。
  9. 聚集索引和非聚集索引的区别。
  10. select for update 是什么含义,会锁表还是锁行或是其他。
  11. 为什么要用Btree实现,它是怎么分裂的,什么时候分裂,为什么是平衡的。
  12. 数据库的ACID是什么。
  13. 某个表有近千万数据,CRUD比较慢,如何优化。
  14. Mysql怎么优化table scan的。
  15. 如何写sql能够有效的使用到复合索引。
  16. mysql中in 和exists 区别。
  17. 数据库自增主键可能的问题。
  18. MVCC的含义,如何实现的。
  19. 你做过的项目里遇到分库分表了吗,怎么做的,有用到中间件么,比如sharding jdbc等,他

们的原理知道么。

  1. MYSQL的主从延迟怎么解决。

消息队列

  1. 消息队列的使用场景。
  2. 消息的重发,补充策略。
  3. 如何保证消息的有序性。
  4. 用过哪些MQ,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗,你们公司的MQ服务

架构怎样的。

  1. MQ系统的数据如何保证不丢失。
  2. rabbitmq如何实现集群高可用。
  3. kafka吞吐量高的原因。
  4. kafka 和其他消息队列的区别,kafka 主从同步怎么实现。
  5. 利用mq怎么实现最终一致性。
  6. 使用kafka有没有遇到什么问题,怎么解决的。
  7. MQ有可能发生重复消费,如何避免,如何做到幂等。
  8. MQ的消息延迟了怎么处理,消息可以设置过期时间么,过期了你们一般怎么处理。

缓存

  1. 常见的缓存策略有哪些,如何做到缓存(比如redis)与DB里的数据一致性,你们项目中用到了

什么缓存系统,如何设计的。

  1. 如何防止缓存击穿和雪崩。
  2. 缓存数据过期后的更新如何设计。
  3. redis的list结构相关的操作。
  4. Redis的数据结构都有哪些。
  5. Redis的使用要注意什么,讲讲持久化方式,内存设置,集群的应用和优劣势,淘汰策略等。
  6. redis2和redis3的区别,redis3内部通讯机制。
  7. 当前redis集群有哪些玩法,各自优缺点,场景。
  8. Memcache的原理,哪些数据适合放在缓存中。
  9. redis和memcached 的内存管理的区别。
  10. Redis的并发竞争问题如何解决,了解Redis事务的CAS操作吗。
  11. Redis的选举算法和流程是怎样的。
  12. redis的持久化的机制,aof和rdb的区别。
  13. redis的集群怎么同步的数据的。
  14. 知道哪些redis的优化操作。
  15. Reids的主从复制机制原理。
  16. Redis的线程模型是什么。
  17. 请思考一个方案,设计一个可以控制缓存总体大小的自动适应的本地缓存。
  18. 如何看待缓存的使用(本地缓存,集中式缓存),简述本地缓存和集中式缓存和优缺点。

本地缓存在并发使用时的注意事项。

搜索

  1. elasticsearch了解多少,说说你们公司es的集群架构,索引数据大小,分片有多少,以及一些

调优手段 。elasticsearch的倒排索引是什么。

  1. elasticsearch 索引数据多了怎么办,如何调优,部署。
  2. elasticsearch是如何实现master选举的。
  3. 详细描述一下Elasticsearch索引文档的过程。
  4. 详细描述一下Elasticsearch搜索的过程。
  5. Elasticsearch在部署时,对Linux的设置有哪些优化方法?
  6. lucence内部结构是什么。

关注以下公众号回复 "答案" 获取全部面试题整理及参考答案。

image

查看原文

赞 121 收藏 98 评论 2

什么都只会一点点 发布了文章 · 2019-08-24

php的命名空间和自动加载实现

类的自动加载

引子

当我们在php代码中加载类时,我们必须要include或者require 某个类文件。
但遇到类似的情况,例如:

require "Class1.php";
require "Class2.php";
$boy = $_GET['sex'] = 0?true:false;
if($boy)
{
 $class1 = new Class1();
}else{
    $class2 = new Class2();
}

假如我们需要判断一个人的性别,如果是男的就实例化class1这个类,如果是女的就实例化class2这个类。那么问题来了:这段代码,每次我只需要执行一个实例化对象,然而我必须加载这两个类文件。

php对于这种问题提出了解决方案

spl_auto_register()

这个概念在 在php5.1中提出

spl_auto_register($autoload_function = null, $throw = true, $prepend = false)

函数包含3个参数

①autoload_function  这是一个函数【方法】名称,可以是字符串或者数组(调用类方法使用)。这个函数(方法)的功能就是,来把需要new 的类文件包含include(requeire)进来,这样new的时候就不会找不到文件了。其实就是封装整个项目的include和require功能。

② $throw 该参数指定当autoload_function无法注册时,spl_autoload_register()是否应引发异常。 

③ 如果为true,那么spl_autoload_register()将在自动加载到文件前面,而不时在它后面。

用法

那么有了这个函数之后向这样写了

function load($class)
{
    require "./{$class}.php";
}
spl_autoload_register('load');
if($boy)
{
 $class1 = new Class1();
}else{
    $class2 = new Class2();
}

程序执行过程如下:

// 正常的流程
new 一个对象-->找不到对象--> 报错

// 引入spl_autoload_register 后
new 一个对象-->找不到对象--> spl_autoload_register对说交给我试试--> 加载成功

加载之后我们执行了load这个函数,通过class的拼接,我们完成了加载函数的过程

__autoload()

类的自动加载在前面我们讲 spl_autoload_register 的时候已经和大家讲过了。今天我们讲另一种
__autoload() 在php7中已经不建议使用了

php的__autoload函数是一个魔术函数,在这个函数出现之前,如果一个php文件里引用了100个对象,那么这个文件就需要使用include或require引进100个类文件,这将导致该php文件无比庞大。于是就有了这个 __autoload函数。

__autoload函数在什么时候调用呢?当php文件中使用了new关键字实例化一个对象时,如果该类没有在本php文件中被定义,将会触发__autoload函数,此时,就可以引进定义该类的php文件,而后,就能实例化成功了。

(注意:如果需要实例化的对象,在本文件中已经找到该类的定义的话,就不会触发 __autoload 函数)

他和 spl_autoload_registe r的区别就在于当文件中同时出现__autoload和spl_autoload_register时,以spl_autoload_register为准

命名空间

我们先前讲过类的自动加载,然后我就在思索。

我们用框架写代码的时候,每在另一个文件中调用其他类时
我们并没有写spl_autoload_register这个方法啊?那我们时怎么实现的呢?

原理

原来啊,我们php在5.3时引入了命名空间的概念(这也是为什么大多数的框架不支持5.3之前的版本原因之一),命名空间大家多少还是了解的吧:不知道的去墙角面壁思过

命名空间简而言之就是一种标识,它的主要目的是解决命名冲突的问题。就像在日常生活中,有很多姓名相同的人,如何区分这些人呢?那就需要加上一些额外的标识。把工作单位当成标识似乎不错,这样就不用担心 “撞名” 的尴尬了。

命名空间分类

  • 完全限定命名空间
  • 限定命名空间
new 成都\徐大帅(); // 限定类名
new \成都\徐大帅(); // 完全限定类名

在当前命名空间没有声明的情况下,限定类名和完全限定类名是等价的。因为如果不指定空间,则默认为全局()。


namespace 美国;

new 成都\徐大帅(); // 美国\成都\徐大帅(实际结果)
new \成都\徐大帅(); // 成都\徐大帅(实际结果)

这个例子展示了在命名空间下,使用限定类名和完全限定类名的区别。(完全限定类名 = 当前命名空间 + 限定类名)

/* 导入命名空间 */
use 成都\徐大帅;
new 徐大帅(); // 成都\徐大帅(实际结果)

/* 设置别名 */
use 成都\徐大帅 AS CEO;
new CEO(); // 成都\徐大帅(实际结果)

/* 任何情况 */
new \成都\徐大帅();// 成都\徐大帅(实际结果)

使用命名空间只是让类名有了前缀,不容易发生冲突,系统仍然不会进行自动导入。

如果不引入文件,系统会在抛出 "Class Not Found" 错误之前触发 __autoload() 或者spl_autoload_register函数,并将限定类名传入作为参数。

上面的例子都是基于你已经将相关文件手动引入的情况下实现的,否则系统会抛出 " Class '成都徐大帅' not found"。因为她不知道这个文件在哪里。所以在引入命名空间以后又引入了自动加载

接下来,我们就在用命名空间加载我们的 类

一个使用命名空间自动加载类的小实验

首先,我们在一个新文件中定义

//School.php
namespace top;

class School
{
    function __construct()
    {
        echo '这是'.__CLASS__.'类的实现';
    }
}

这当然不是重要的,重要的是我们调用他的函数。我们在同一个目录建立一个index.php文件(不同文件也行,只要你写好映射关系)

//index.php

spl_autoload_register(function ($class){
   //从我们的 class名称中找,有没有对应的路径
   $map = [
       'top\\School'=>'./School.php'
   ];

   $file = $map[$class];
    //查看对应的文件是否存在
   if (file_exists($file))
       include $file;
});
echo "开始<br/>";
new top\School();

结果

开始
这是top\School类的实现

我们使用了 类名和类地址的映射关系,实现了我们的自动加载。然而这也意味着我们每次添加文件,就必须去更新我们的映射文件。在一个大型系统中这样数组维持的映射关系无疑很麻烦。那么有没有好一点的做法呢?

PSR4 自动加载规范

不知道的童鞋,可以看这里
PSR4 中文文档
PSR4 的具体解释
下面摘自上面链接,我觉得上面两篇文章已经讲得很透彻了

 \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

PSR-4 规范中必须要有一个顶级命名空间,它的意义在于表示某一个特殊的目录(文件基目录)。子命名空间代表的是类文件相对于文件基目录的这一段路径(相对路径),类名则与文件名保持一致(注意大小写的区别)。

举个例子:在全限定类名 appviewnewsIndex 中,如果 app 代表 C:Baidu,那么这个类的路径则是 C:BaiduviewnewsIndex.php

我们就以解析 appviewnewsIndex 为例,编写一个简单的 Demo:

$class = 'app\view\news\Index';

/* 顶级命名空间路径映射 */
$vendor_map = array(
    'app' => 'C:\Baidu',
);

/* 解析类名为文件路径 */
$vendor = substr($class, 0, strpos($class, '\\')); // 取出顶级命名空间[app]
$vendor_dir = $vendor_map[$vendor]; // 文件基目录[C:\Baidu]
$rel_path = dirname(substr($class, strlen($vendor))); // 相对路径[/view/news]
$file_name = basename($class) . '.php'; // 文件名[Index.php]

/* 输出文件所在路径 */
echo $vendor_dir . $rel_path . DIRECTORY_SEPARATOR . $file_name;

通过这个 Demo 可以看出限定类名转换为路径的过程。那么现在就让我们用规范的面向对象方式去实现自动加载器吧。

首先我们创建一个文件 Index.php,它处于 appmvcviewhome 目录中:

namespace app\mvc\view\home;

class Index
{
    function __construct()
    {
        echo '<h1> Welcome To Home </h1>';
    }
}

接着我们在创建一个加载类(不需要命名空间),它处于 目录中:

class Loader
{
    /* 路径映射 */
    public static $vendorMap = array(
        'app' => __DIR__ . DIRECTORY_SEPARATOR . 'app',
    );

    /**
     * 自动加载器
     */
    public static function autoload($class)
    {
        $file = self::findFile($class);
        if (file_exists($file)) {
            self::includeFile($file);
        }
    }

    /**
     * 解析文件路径
     */
    private static function findFile($class)
    {
        $vendor = substr($class, 0, strpos($class, '\\')); // 顶级命名空间
        $vendorDir = self::$vendorMap[$vendor]; // 文件基目录
        $filePath = substr($class, strlen($vendor)) . '.php'; // 文件相对路径
        return strtr($vendorDir . $filePath, '\\', DIRECTORY_SEPARATOR); // 文件标准路径
    }

    /**
     * 引入文件
     */
    private static function includeFile($file)
    {
        if (is_file($file)) {
            include $file;
        }
    }
}

最后,将 Loader 类中的 autoload 注册到 spl_autoload_register 函数中:

include 'Loader.php'; // 引入加载器
spl_autoload_register('Loader::autoload'); // 注册自动加载

new \app\mvc\view\home\Index(); // 实例化未引用的类

/**
 * 输出: <h1> Welcome To Home </h1>
 */

示例中的代码其实就是 ThinkPHP 自动加载器源码的精简版,它是 ThinkPHP 5 能实现惰性加载的关键。

查看原文

赞 2 收藏 1 评论 0

认证与成就

  • 获得 88 次点赞
  • 获得 11 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-12-07
个人主页被 867 人浏览