commit 74d27c8ca4a614d66633733b6a0ad4319c84bb9a Author: Alawn Date: Mon Jul 5 11:27:03 2021 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc90d3b --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# 介绍 +收集了互联网面试过程中常见的面试问题,并对这些问题、知识点做分析和整理,希望对大家有所帮助。如果**有错误地方还希望提出宝贵建议,批评指正。祝大家求职顺利**。 + +# 在线阅读 +本文档使用 Gitbook 制作,[在线阅读地址](https://www.gitbook.com/book/zhengjianglong/note-of-interview/details) + +本文档github地址:https://github.com/zhengjianglong915/note-of-interview + +# 作者简介 +作者: 入境繁华 + +微博:[@入境繁华](http://weibo.com/2711026835/profile?rightmod=1&wvr=6&mod=personinfo) + +个人主页:http://zhengjianglong.leanote.com/ + +简书地址: http://www.jianshu.com/u/9f1f5db3d6b8 + +个人公众号(欢迎关注): + +![](/assets/weix_gongzhonghao.jpg) + + +# 声明 +如果有错之处还望指出和纠正。联系方式:zhengjianglong915@gmail.com + +**所有引用内容版权归原作者所有。** + +知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。 + + +## 内容提纲 +* [前言](abstract.md) +* [第一章 操作系统](./os/README.md) + * [1.1 生产者与消费者](os/product.md) + * [1.2 常见面试题](./os/section2.md) +* [第二章 计算机网络](./network/README.md) + * [2.1 常见面试题](./network/all.md) +* [第三章 算法与数据结构](./algorithm/README.md) + * [3.1 排序算法汇总](./algorithm/section01.md) + * [3.2 查找](./algorithm/section02.md) + * [3.3 字符串](./algorithm/string.md) + * [3.4 图算法](./algorithm/tu.md) +* [第四章 Java](./java/README.md) + * [4.1 基础](./java/base.md) + * [4.2 面向对象](./java/object.md) + * [4.3 集合](./java/collection.md) + * [4.4 多线程](./java/thread.md) + * [4.5 IO](./java/io.md) + * [4.5 JVM](./java/jvm.md) +* [第五章 数据库](./database/README.md) + * [5.1 常见面试题](./database/all.md) +* [第六章 框架](./framework/README.md) + * [6.1 spring](./framework/spring.md) + * [6.2 hibernate](./framework/hibernate.md) + * [6.3 struts](./framework/struts.md) +* [第七章 其他基础](./others/README.md) + * [7.1 编码历史](./others/section1.md) + * [7.2 单例模式的几种实现方式](./others/danli.md) + * [7.3 一致性哈希算法](./others/hash.md) +* [第八章 HR面](./hr/README.md) + * [8.1 项目与个人](./chapter8/ask.md) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..c754a26 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,52 @@ +# Summary + +* [前言](abstract.md) +* [第一章 操作系统](./os/README.md) + * [1.1 生产者与消费者](os/product.md) + * [1.2 常见面试题](./os/section2.md) +* [第二章 计算机网络](./network/README.md) + * [2.1 常见面试题](./network/all.md) +* [第三章 算法与数据结构](./algorithm/README.md) + * [3.1 排序算法汇总](./algorithm/section01.md) + * [3.2 查找](./algorithm/section02.md) + * [3.3 字符串](./algorithm/string.md) + * [3.4 图算法](./algorithm/tu.md) +* [第四章 Java](./java/README.md) + * [4.1 基础](./java/base.md) + * [4.2 面向对象](./java/object.md) + * [4.3 集合](./java/collection.md) + * [4.4 多线程](./java/thread.md) + * [4.5 IO](./java/io.md) + * [4.5 JVM](./java/jvm.md) +* [第五章 数据库](./database/README.md) + * [5.1 常见面试题](./database/all.md) +* [第六章 框架](./framework/README.md) + * [6.1 spring](./framework/spring.md) + * [6.2 hibernate](./framework/hibernate.md) + * [6.3 struts](./framework/struts.md) +* [第七章 其他基础](./others/README.md) + * [7.1 编码历史](./others/section1.md) + * [7.2 单例模式的几种实现方式](./others/danli.md) + * [7.3 一致性哈希算法](./others/hash.md) +* [第八章 HR面](./hr/README.md) + * [8.1 项目与个人](./chapter8/ask.md) + + + + + + + + + + + + + + + + + + + + diff --git a/abstract.md b/abstract.md new file mode 100644 index 0000000..2405cbe --- /dev/null +++ b/abstract.md @@ -0,0 +1,49 @@ +# 序 + +## 在被虐中成长,在面试中进步 +指缝很宽,时间太瘦,悄悄从指缝间溜走。转眼之间三年的研究生生活即将结束,即使很是不舍,也终将要走向社会,开始另一段新的旅程。此刻,看着下一届师弟师妹开始忙碌地准备找实习,便开始追忆起过去一年找实习和工作的历程,那是一段艰辛而又成长颇多的经历。 + +过去一年找实习和工作的经历还是那么清晰,每每想起,都彷如发生在昨日。面试官问的问题以及那些踩过的坑却都一一浮现眼前,久久不能忘怀。那无数日夜挑灯备战的艰辛,那焦虑不安等着面试电话的心情,那一次次被被拒后的失落和那收到一个个offer喜悦之情,**那些我们经历的终将成为我们人生一笔宝贵的财富**。而最重要的是,我们在面试过程中学会了成长,学会了改变。学习弥补那些我们不会的知识,加深理解那些我们浮于表面的原理,改变我们面试过程中出现的缺点和不足。那段时间,我们快速吸收了大量知识,又好像重新回到了当年的考研状态,但又感觉比考研还要艰辛,因为要准备的知识面太多、太广。亦如考研,朝看夕忘,每天沉浸在复习和备战中。书本和笔记一遍遍地翻看,算法一遍遍地刷,又一遍遍地准备那些自己不擅长的知识。闲暇时便又去收集各种招聘信息,看看招聘网站是否有新的找人消息,问问学长学姐还有没有内推的机会。就这样一步一步,**我们在被虐中成长,在面试中进步**。面试是一个非常好的学习机会,我们应当适当地利用好这次机会完成自己知识的一次飞跃。 + +#### 1. 更加集中的学习 +正如考研、期末考试一样,面试也是我们一次知识增长和爆发的时候。因为有了面试和找工作的压力,我们便能更加集中地将大把时间花在学习和复习上。将时间花在哪里,你的成就就在哪里,根据《刻意练习》中的刻意练习理论和《异类》中的10000小时定律,你在某个领域花的时间越多,你在该领域的成就越高。 + +我们在面试过程中加强了知识复习强度,而面试就是对这次知识准备的反馈过程,通过面试来找出自己的不足和了解自己知识的掌握程度。遇到不会的知识,我们便开始上网或翻阅书本,查缺补漏。通过反复的准备、面试、纠正的过程使得我们掌握了大量的面试技巧,同时也强化了对知识掌握程度。 + +#### 2. 更加系统的知识体系 +互联网面试不会问单一的知识(如:算法),而是一次综合性考察。因此我们在备战面试过程中各个知识点都需要准备,如计算机操作系统、计算机网络、数据库、算法、数据结构等。而知识之间不可能孤立存在,而是彼此存在联系。《高效学习》中提到,学习过程分为几个阶段:**知识获取,理解,拓展(或者建立联系),纠错,应用,记忆,测试**。我们根据面试要求从相关书籍中**获取知识**;**理解**一些知识和概念,及背后的原理(如什么是面向对象);同时建立起与其他知识之间的**联系**,例如java虚拟机的垃圾回收算法是什么,性能如何?优缺点是什么?---java虚拟机和算法数据结构的联系。java虚拟机如何做到跨平台,与计算机系统之间是怎样的关系等。通过面试过程**纠正**我们对知识的错误认知。通过反复纠正、记忆便掌握更加牢固。 + +面试中的问题都会将一些知识联系起来,比如MySql索引的数据结构是什么?性能如何?不同索引之间的比较如何?这些都不是单纯的考察MySql知识,同时也在考察对数据库中B+树、hash的理解。 + +可以通过复习建立起各个知识之间的联系,并且我们能够花大量时间系统的学习各个方面的知识。通过这样一次复习后,我们拥有了更加系统的互联网知识体系,建立起各知识点之间的联系。 + + +#### 3. 弥补不足,提升自我 +面试一个非常好的地方就是能够帮助自己,找出自己的不足,这些不足包括知识和沟通等方面。面试中遇到不会的问题便是我们成长最大、最快的地方。这些问题之前学习的时候是否认真考虑过?是否做项目时,某些问题是否有深入地分析过?用的框架是否知其然而知其所以然?总结面试中回答不足的问题,我们在复习过程中重点攻克,查缺补漏,进一步巩固之前的基础知识。虽然面试过程中有时候会被虐的体无完肤,虐得心力憔悴。但是,这也帮助我们找到自己知识的不足,便于弥补和提高。面试过程中最怕的不是被拒绝,而是不知道为什么被拒绝。被拒绝后还不采取相应的措施去做出改变,那即便面再多也是徒然。 + +#### 4. 更加深入地了解知识背后原理 +通常我们掌握的知识,仅仅是这个知识,处于一种**见树而不见森林**的状态,对其别后的原理和联系都不甚了解。于是面试中一旦面试官深入问询时,自己便回答不上了。**面试带来的好处就是让我们有更多的机会去反思和思考,让我们能够更加深入地去挖掘知识背后的原理,而不是简简单单地浮于表面的理解**。面试中你会发现那些习以为常的知识背后隐藏着一个丰富多彩的知识世界,**一花一世界,一叶一森林**,这些知识背后总有那些需要了解的知识原理和知识间的联系。通过面试,我们加强对这些知识的理解和学习,通过深入分析背后的原理和建立起知识点的练习,使自己能够做到**见树即见森林**。 + +面试是一次知识提升的机会,面试中应该学会总结自己的不足,并针对不足之处深入研究、分析才能促进自己的成长。而不应该仅仅将面试准备当做应付考试一样准备,背背网上的一些所谓的”标准答案“。 + +## 缘分与心态 +我们如此看重实习和工作,是因为进入BAT等互联网大型公司实习一方面能够给我们一个平台提高我们的实践能力、学习更多知识技能和拓展我们的视野,另一方面是能在未来找工作时为我们简历加分。而工作更不用说了,十几年的学习生涯也是为了毕业时找份好工作,为未来奠定一个好的开端。很多人常说第一份工作至关重要,往往决定了一个人未来的发展道路。是否决定一个人未来因个人而异,但是第一份工作还是非常关键,那是我们走入社会的第一步,会对自己个人的成长和视野的开拓产生重要影响。因为不同的公司的文化环境不同,组织结构不同和给个人提供的机会都有所不同。因此也很多人格外看重这份工作。 + +而不管是找实习还是找工作,我们都会面临一个问题,那就是面试。面试不仅是考验的是技术和知识,同时也是看缘分和心态。 + +找工作之前,有一点你必须清楚,就是找工作是一件看缘分的事情,不是你很牛逼,你就一定能进你想进的公司,都是有一个概率在那。比如相同的公司因为面试官不同问的知识点也可能不同,有时候问的刚好你都准备了,有时候偏偏问些你所不会的。如果你基础好,项目经验足,同时准备充分,那么你拿到offer的概率就会比较高;相反,如果你准备不充分,基础也不好,那么你拿到offer的概率就会比较低,但是你可以多投几家公司,这样拿到offer的几率就要大一点,因为你总有运气好的时候。所以,不要惧怕面试,刚开始失败了没什么的,多投多尝试,面多了你就自然能成面霸了。**得失心也不要太重**,应该放宽心态,应为最后每个人都会有offer的。 + + +## 基础备战 +基础这东西,各个公司都很看重,尤其是BAT这种大公司,他们看中人的潜力,他们舍得花精力去培养,所以基础是重中之重。当你的基础好的发指的时候,你的其他东西都不重要了。 + +基础无外乎几部分:语言(C/C++或java),操作系统,TCP/IP,数据结构与算法,再加上你所熟悉的领域。当面试多了,你便会发现有很多是常见的问题和知识点,因此本书对那些常见的面试题进行收集和整理,并对一些题目做深入分析,希望能够帮助大家。大家如果觉得有所帮助,也可以给我打赏。 + + + +个人公众号(欢迎关注) +![](/assets/weix_gongzhonghao.jpg) + + + + diff --git a/algorithm/README.md b/algorithm/README.md new file mode 100644 index 0000000..d12dff9 --- /dev/null +++ b/algorithm/README.md @@ -0,0 +1,17 @@ +## 知识点汇总 +1. 链表与数组。 +2. 队列和栈,出栈与入栈。 +3. 链表的删除、插入、反向。 +4. 字符串操作。 +5. Hash表的hash函数,冲突解决方法有哪些。 +6. 各种排序:冒泡、选择、插入、希尔、归并、快排、堆排、桶排、基数的原理、平均时间复杂度、最坏时间复杂度、空间复杂度、是否稳定。 +7. 快排的partition函数与归并的Merge函数。 +8. 对冒泡与快排的改进。 +9. 二分查找,与变种二分查找。 +10. 二叉树、B+树、AVL树、红黑树、哈夫曼树。 +11. 二叉树的前中后续遍历:递归与非递归写法,层序遍历算法。 +12. 图的BFS与DFS算法,最小生成树prim算法与最短路径Dijkstra算法。 +13. KMP算法。 +14. 排列组合问题。 +15. 动态规划、贪心算法、分治算法。(一般不会问到) +16. 大数据处理:类似10亿条数据找出最大的1000个数.........等等 \ No newline at end of file diff --git a/algorithm/section01.md b/algorithm/section01.md new file mode 100644 index 0000000..ea44ff2 --- /dev/null +++ b/algorithm/section01.md @@ -0,0 +1,267 @@ +## 插入排序 +### 1.直接插入排序 +**思想**:每次将一个待排序的数据按照其关键字的大小插入到前面已经排序好的数据中的适当位置,直到全部数据排序完成。 +**时间复杂度**:O(n2) O(n) O(n2) (最坏 最好 平均) +**空间复杂度**:O(1) +**稳定性**: 稳定 每次都是在前面已排好序的序列中找到适当的位置,只有小的数字会往前插入,所以原来相同的两个数字在排序后相对位置不变。 + 代码: + + + /** + * 插入排序 + * @param array + */ + public static void insertSort(int[] array) { + for (int i = 2; i < array.length; i++ ) { + int val = array[i]; + int j = i -1; + while (j >= 0 && array[j] > val) { // array[j] > val + array[j+1] = array[j]; + j--; + } + array[j+1] = val; // array[j+1] 不是array[j] + } + } + +### 2.希尔排序 +**思想**:希尔排序根据增量值对数据按下表进行分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整体采用直接插入排序得到有序数组,算法终止。 +**时间复杂度**:O(n2) O(n) O(n1.5) (最坏,最好,平均) +**空间复杂度**:O(1) +**稳定性**:不稳定 因为是分组进行直接插入排序,原来相同的两个数字可能会被分到不同的组去,可能会使得后面的数字会排到前面,使得两个相同的数字排序前后位置发生变化。 +**不稳定举例**: 4 3 3 2 按2为增量分组,则第二个3会跑到前面 +**代码**: + + + public static void shellSort(int[] array) { + int len; + len = array.length / 2; // 分成n/2组 + while (len >= 1) { + for (int i = len; i < array.length; ++i) { //对每组进行直接插入排序 + int temp = array[i]; + int j = i - len; + while (j >= 0 && array[j] > temp) { + array[j + len] = array[j]; + j -= len; + } + array[j + len] = temp; + } + len /= 2; + } + + } + +## 交换排序 +### 3.冒泡排序 +**思想**:对待排序元素的关键字从后往前进行多遍扫描,遇到相邻两个关键字次序与排序规则不符时,就将这两个元素进行交换。这样关键字较小的那个元素就像一个泡泡一样,从最后面冒到最前面来。 +**时间复杂度**:最坏:O(n2) 最好: O(n) 平均: O(n2) +**空间复杂度**:O(1) +**稳定性**:稳定,相邻的关键字两两比较,如果相等则不交换。所以排序前后的相等数字相对位置不变。 +**代码**: + + public static void bubbleSort(int[] array) { + boolean flag; // 用来判断当前这一轮是否有交换数值,若没有则表示已经排好许了 + for (int i = 0; i < array.length; i++) { + flag = false; + /** + * 这边要注意 for (int j = array.length -1; j >= i + 1; j--)。 不要写成 + * for (int j = i + 1; j < array.length ; j++) + */ + for (int j = array.length -1; j >= i + 1; j--) { + if (array[j -1 ] > array[j]) { + //数据交换 + int temp = array[j - 1]; + array[j - 1] = array[j]; + array[j] = temp; + //设置标志位 + flag = true; + } + } + if (!flag) { + break; + } + } + } + +### 4.快速排序 +**思想**:该算法是分治算法,首先选择一个基准元素,根据基准元素将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。基准元素的选择对快速排序的性能影响很大,所有一般会想打乱排序数组选择第一个元素或则随机地从后面选择一个元素替换第一个元素作为基准元素。 +**时间复杂度**:最坏:O(n2) 最好: O(nlogn) 平均: O(nlogn) +**空间复杂度**:O(nlogn)用于方法栈 +**稳定性**:不稳定 快排会将大于等于基准元素的关键词放在基准元素右边,加入数组 1 2 2 3 4 5 选择第二个2 作为基准元素,那么排序后 第一个2跑到了后面,相对位置发生变化。 +**代码**: + + public static void quickSort(int[] array) { + partition(array, 0, array.length - 1); + } + + private static void partition(int[] array, int low, int high) { + if (low < high) { + int p = quickSort(array, low, high); + partition(array, low, p - 1); + partition(array, p + 1, high); + } + } + + private static int quickSort(int[] array, int left, int right) { + Random random = new Random(System.currentTimeMillis()); + int idx = random.nextInt(right - left + 1) + left; // 这边需要注意right - left + 1 (要加1) + exch(array, idx, left); + int val = array[left]; + while (left < right) { + while (left < right && array[right] > val) { + right--; + } + if (left < right) { + array[left++] = array[right]; + } + while (left < right && array[left] < val) { + left++; + } + if (left < right) { + array[right--] = array[left]; + } + } + array[left] = val; + return left; + } + +三向快速排序算法 + + /** + * 三向切分快速排序, 适用于存在大量重复元素的数组 + * + * @param array + */ + public static void quick2waySort(int[] array) { + quick2waySort(array, 0, array.length - 1); + } + + private static void quick2waySort(int[] array, int lo, int hi) { + if (hi <= lo) { + return; + } + int lt = lo, gt = hi, i = lo + 1; + int val = array[lo]; + while (i <= gt) { + if (array[i] < val) { + exch(array, i++, lt++); + } else if (array[i] > val) { + exch(array, i, gt--); + } else { + i++; + } + } + // lt 到 gt 之间的都是等于val 的. 如果存在大量重复元素的数组使用该算法可以极大提升算法效率, + quick2waySort(array, lo, lt - 1); + quick2waySort(array, gt + 1, hi); + } + +## 选择排序 +### 5.直接选择排序 +**思想**:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后每次从剩余未排序元素中继续寻找最小(大)元素放到已排序序列的末尾。以此类推,直到所有元素均排序完毕 +**时间复杂度**:最坏:O(n2) 最好: O(n2) 平均: O(n2) +**空间复杂度**:O(1) +**稳定性**:不稳定 例如数组 2 2 1 3 第一次选择的时候把第一个2与1交换使得两个2的相对次序发生了改变。 +**代码**: + + public static void selectSort(int[] array) { + for (int i = 0; i < array.length; i++) { + int minIdx = i; + for (int j = i + 1; j < array.length; j++) { + if (array[j] < array[minIdx]) { + minIdx = j; + } + } + exch(array, i, minIdx); + } + } + +### 6.堆排序 +**思想**:堆排序是利用堆的性质进行的一种选择排序,先将排序元素构建一个最大堆,每次堆中取出最大的元素并调整堆。将该取出的最大元素放到已排好序的序列前面。这种方法相对选择排序,时间复杂度更低,效率更高。 +**时间复杂度**:最坏:O(nlog2n) 最好: O(nlog2n) 平均: O(nlog2n) +**空间复杂度**:O(1) +**稳定性**:不稳定 例如 5 10 15 10。 如果堆顶5先输出,则第三层的10(最后一个10)的跑到堆顶,然后堆稳定,继续输出堆顶,则刚才那个10跑到前面了,所以两个10排序前后的次序发生改变。 +**代码**: + + public static void heapSort(int[] array) { + int N = array.length -1; + for (int k = N / 2; k >= 1; k--) { + sink(array, k, N); + } + while (N > 1) { + // 最大堆, 选择最大值放在最后 + exch(array, 1, N --); + sink(array, 1, N); + } + } + + private static void sink(int[] array, int k, int N) { + while (2 * k <= N) { + int j = 2 * k; + if (j < N && array[j] < array[j+1]) { // < + j++; + } + if (array[j] < array[k]) break; // < + exch(array, k, j); + k = j; + } + } + +## 7.归并排序 +**思想**:归并排序采用了分治算法,首先递归将原始数组划分为若干子数组,对每个子数组进行排序。然后将排好序的子数组递归合并成一个有序的数组。 +**时间复杂度**:最坏:O(nlog2n) 最好: O(nlog2n) 平均: O(nlog2n) +**空间复杂度**:O(n) +**稳定性**:稳定 +**代码**: + + public static void mergeSort(int[] array) { + sort(array, 0, array.length - 1); + } + + private static void sort(int[] array, int left, int right) { + if (left < right) { + int middle = (left + right) >> 1; + //递归处理相关的合并事项 + sort(array, left, middle); + sort(array, middle + 1, right); + merge(array, left, middle, right); + } + } + + private static void merge(int[] array, int lo, int mid, int hi) { + //创建一个临时数组用来存储合并后的数据 + int[] temp = new int[array.length]; + int left = lo; + int right = mid + 1; + int k = lo; + while (left <= mid && right <= hi) { + if (array[left] < array[right]) + temp[k++] = array[left++]; + else + temp[k++] = array[right++]; + } + //处理剩余未合并的部分 + while (left <= mid) temp[k++] = array[left++]; + while (right <= hi) temp[k++] = array[right++]; + //将临时数组中的内容存储到原数组中 + while (lo <= hi) array[lo] = temp[lo++]; + + } + +## 8.基数排序算法 +**思想**:基数排序是通过“分配”和“收集”过程来实现排序,首先根据数字的个位的数将数字放入0-9号桶中,然后将所有桶中所盛数据按照桶号由小到大,桶中由顶至底依次重新收集串起来,得到新的元素序列。然后递归对十位、百位这些高位采用同样的方式分配收集,直到没各位都完成分配收集得到一个有序的元素序列。 +**时间复杂度**:最坏:O(d(r+n)) 最好:O(d(r+n)) 平均: O(d(r+n)) +**空间复杂度**:O(dr+n) n个记录,d个关键码,关键码的取值范围为r +**稳定性**:稳定 基数排序基于分别排序,分别收集,所以其是稳定的排序算法。 +**代码**: + + +## 参考 +http://blog.jobbole.com/90256/ + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + + + diff --git a/algorithm/section02.md b/algorithm/section02.md new file mode 100644 index 0000000..abdf037 --- /dev/null +++ b/algorithm/section02.md @@ -0,0 +1,393 @@ +## 一.顺序查找 +**1.1 思路**:这是最简单的算法,从头开始遍历每个元素,并将每个元素与查找元素比较,如果一致则返回。 +**1.2 时间复杂度**: O(N) +**1.3 空间复杂度**: O(1) +**1.4 代码**: + + public int search(int[] array, int num) { + if(array == null || array.length == 0) { + return -1; + } + for(int i = 0; i < array.length; i++) { + if (array[i] == num) { + return i; + } + } + return -1; + } + + +## 二.二分查找 +**2.1 思路**:二分查找前提是查找的数组是有序的,利用数据有序的特性提高查找性能。首先与数组中间位置的值比较,如果查找值大于中间位置值,则对数组右边以相同的思路查找,否则在左边以相同方式查找。这种方式使得每次查找范围变为原来的1/2. +**2.2 时间复杂度**:O(log2n) +**2.3 空间复杂度**: O(1) + + public int halfSearch(int[] array, int num) { + if(array == null || array.length == 0) { + return -1; + } + int lo = 0, hi = array.length-1; + while(lo <= hi) { + int mid = (lo + hi) >> 2; + if (array[mid] == num) { + return mid; + } else if (array[mid] < num) { + hi = mid -1; + } else { + lo = mid + 1; + } + } + return -1; + } + +## 三. 变种二分查找 + +http://www.cr173.com/html/20428_1.html + +## 四. hash 算法 +**4.1 思想**:哈希表是根据设定的**哈希函数H(key)**和**处理冲突方法**将一组关键字映射到一个有限的地址区间上,并将关键字对应的值存储在该地址空间,可以通过关键字快速获取对应的值,这种表称为哈希表或散列,所得存储位置称为哈希地址或散列地址。作为线性数据结构与表格和队列等相比,哈希表无疑是查找速度比较快的一种。 +**4.2 查找复杂度:** O(1) +**4.3 哈希函数**: +1. **直接寻址法**:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a?key + b,其中a和b为常数(这种散列函数叫做自身函数) +2. **数字分析法**:因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。 +3. **平方取中法**:取关键字平方后的中间几位作为散列地址 +4. **折叠法**:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。 +5. **除留余数法**:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。 + +**4.4 hash冲突及解决** +hash冲突在所难免,解决冲突是一个复杂问题。冲突主要取决于: +(1)与散列函数有关,一个好的散列函数的值应尽可能平均分布。 +(2)与解决冲突的哈希冲突函数有关。 +(3)与负载因子的大小。太大不一定就好,而且浪费空间严重,负载因子和散列函数是联动的。 +解决冲突的办法: + (1)**开放定址法**:线性探查法、平方探查法、伪随机序列法、双哈希函数法。 + (2) **拉链法**:把所有同义词,即hash值相同的记录,用单链表连接起来。 + +**4.5 应用:** +1.字符串哈希 +2.加密哈希 +3.几何哈希 +4.布隆过滤器 + +**4.6 不足:**获取有序序列复杂度高 + +参考: +http://www.tuicool.com/articles/RnErui + +## 五.二叉树搜索树 +#### 5.1 思想 +二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树: +1.若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; +2.若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; +3.任意节点的左、右子树也分别为二叉查找树。 +4.没有键值相等的节点(no duplicate nodes)。 +复杂度: 插入和查找的时间复杂度均为O(logN), 最坏为O(N) + +#### 5.2 插入 +1. 如果当前结点是null,则创建新结点返回。 +2. 如果插入结点比当前结点值大,则插入其右孩子结点中。 +3. 如果插入结点比当前结点值小,则插入其左孩子结点中。 +**复杂度**: 平均 O(logn) 最坏O(n) +**代码:** + + public Tree insert(Tree root, int val) { + if (root == null) { + return new Tree(val); + } + if (val == root.val) { + return root; + } else if (val > root.val) { + root.right = insert(root.right, val); + } else { + root.left = insert(root.left, val); + } + return root; + } + + +### 5.3 查找 +#### 5.3.1 某个值 +**思想**:查找方式有点类型二分查找方法,知识这里采用的树结构进行存储。首先与根结点进行判断: +1. 如果当前结点为null,则直接放回Null。 +2. 如果当前结点值与查找值相同则返回当前结点. +3. 如果当前结点值小余查找值,则递归地到当前结点的右孩子查找 +4. 如果当前结点值大于查找值,则递归地到当前结点的左孩子查找。 +**时间复杂度**: 平均O(logn) 最坏O(n) + + public Tree search(Tree root, int val) { + if(root == null) { + return null; + } + if(root.val == val) { + return root; + } else if(root.val > val) { + return search(root.left, val); + } else { + return search(root.right, val); + } + } + +####5.3.2 查找最小值 +**思想**:根据二叉搜索树的特点,最小结点都是在最左结点上或者如果根结点无左孩子便是其本身. + + public Tree min(Tree root) { + if(root == null) { + return null; + } + if (root.left != null) { + return min(root.left); + } + return root; + } + +#### 5.3.2 查找最大结点 +**思想**: 同最小结点。 + + public Tree max(Tree root) { + if(root == null) { + return null; + } + if (root.right != null) { + return max(root.right); + } + return root; + } + +### 5.4 删除 +#### 5.4.1 删除最小结点 +**思想:**找到根结点最左结点,如果其不存在右孩子则直接删除,否则用右孩子替换最左结点。需要注意的是根结点可能为null和不存在做孩子的情况。 + + public Tree deleteMin(Tree root) { + if(root == null) { + return null; + } + if(root.left != null) { + root.left = deleteMin(root.left); + } else if (root.right != null) { + return root.right; + } + return null; + } + +### 5.4.2 删除最大结点 +**思想:** 与删除最小结点类型,根据二叉搜索树的特性,最大结点是根结点的最右孩子。所以只要找到最右孩子结点,其存在左结点的话就用左结点替换否则直接删除. + + public Tree deleteMax(Tree root) { + if(root == null) { + return null; + } + if(root.right != null) { + root.right = deleteMax(root.right); + } else if(root.left != null) { + return root.left; + } + return null; + } + +### 5.4.3 删除某个结点 +**思想:**要删除一个结点首先需要找到该结点的位置,采用上面的查找方式进行查找。找到结点后就是删除的问题的,可以按照下面的策略进行删除。 +1. 如果一个结点无做孩子和右孩子,那么就可以直接删除 +2. 如果只存在一个孩子结点,则用孩子结点替换。 +3. 如果存在两个孩子结点,那么可以用其左孩子最大的结点或右孩子最小结点替换,并删除最左孩子结点或最右孩子结点. + + + public Tree delete(Tree root, int val) { + if(root == null) { + return null; + } + if(root.val == val) { + if(root.left == null && root.right == null) { + return null; + } else if(root.left!= null && root.right != null) { + Tree leftBig = max(root.left); + root.val = leftBig.val; + root.left = delete(root.left, leftBig.val); + } else if(root.left != null){ + return root.left; + } else { + return root.right; + } + } else if(root.val < val) { + root.right = delete(root.right, val); + } else { + root.left = delete(root.left, val); + } + return root; + } + +参考: http://www.cnblogs.com/vamei/archive/2013/03/17/2962290.html +http://blog.jobbole.com/79305/ + +#### 5.5 相关的算法题 +**剑指offer**: + + +## 6. AVL查找树 +### 6.1 思想 +二叉树查找树在插入时没有对二叉树的深度和结构做一个调整,使得叶子结点深度不一,在查找时深度越深的结点时间复杂度越高。为了改进查找的时间时间复杂度,于是出现了平衡二叉树(AVL).**平衡二叉树使得每个结点的左结点和右结点的深度差不超过1.** +### 6.2 查找 +查找与二叉查找树一样。 + +### 6.3 插入 + 当在AVL中插入新的结点时,需要根据实际情况对AVL中的某些结点做单旋转或双旋转操作,单旋转表示做一次顺时针或逆时针的旋转操作,而双旋转则做两次单旋转操作(先顺时针后逆时针,或者先逆时针后顺时针),单旋转发生在LL型插入和RR型插入,而双旋转则发生在LR型插入和RL型插入。以下的失去平衡点都指的是离插入点最近的那个失去平衡的结点。 +LL型:插入点位于失去平衡点的左孩子的左子树上; +RR型:插入点位于失去平衡点的右孩子的右子树上; +LR型:插入点位于失去平衡点的左孩子的右子树上; +RR型:插入点位于失去平衡点的右孩子的左子树上。 + +**插入思路** +和二叉搜索树的插入一样,首先在树中找到对应的位置然后插入,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有 1.5 乘 log n 个节点,而每次AVL 旋转都耗费恒定的时间,插入处理在整体上耗费 O(log n) 时间。  +具体插入过程如下: +1. 如果当前结点为空,创建新结点返回. +2. 如果当前结点值和插入值相同,不做处理返回。 +3. 如果插入值大于当前结点则插入到右其右孩子结点中。插入完成后比较左右孩子结点进行判断树是否失去平衡。如果是判断属于那种类型(RR, RL),并响应的旋转。最后更新当前结点的深度(孩子结点的最大深度加1,默认null深度为-1)。 +4. 否则插入到其做孩子上。 同样比较左右孩子的深度判断是否平衡,对于失去平衡的情况下做出调整。 + + + private AvlNode insert(AnyType x, AvlNode t) { + if (t == null) + return new AvlNode(x, null, null); + int compareResult = myCompare(x, t.element); + if (compareResult < 0) { + t.left = insert(x, t.left); + if (height(t.left) - height(t.right) == 2) { + if (myCompare(x, t.left.element) < 0) //左左情况 + t = rotateWithLeftChild(t); + else //左右情况 + t = doubleWithLeftChild(t); + } + } else if (compareResult > 0) { + t.right = insert(x, t.right); + if (height(t.right) - height(t.left) == 2) { + if (myCompare(x, t.right.element) < 0) //右左情况 + t = doubleWithRightChild(t); + else //右右情况 + t = rotateWithRightChild(t); + } + } + //完了之后更新height值 + t.height = Math.max(height(t.left), height(t.right)) + 1; + return t; + } + +### 6.4 删除 +从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有 log n个节点被旋转,而每次 AVL 旋转耗费恒定的时间,删除处理在整体上耗费 O(log n) 时间。 +a.当被删除节点n是叶子节点,直接删除 +b.当被删除节点n只有一个孩子,删除n,用孩子替代该节点的位置 +c.当被删除结点n存在左右孩子时,真正的删除点应该是n的中序遍在前驱,或者说是左子树最大的节点,之后n的值替换为真正删除点的值。这就把c归结为a,b的问题。 + +从删除的结点处自低向上向根结点折回,根据当前结点的左右孩子深度判断是否平衡,如果不平衡则按选择规则进行旋转。最后更新当前结点深度,如此递归折回到根结点。 + +http://blog.csdn.net/xiaofan086/article/details/8294382 +http://blog.csdn.net/liyong199012/article/details/29219261 +http://www.mamicode.com/info-detail-90162.html +## 七. B-树(B树) +#### 7.1 定义 +B-树是一种平衡的多路查找树,它在文件系统中很有用。 +定义:一棵m 阶的B-树,或者为空树,或为满足下列特性的m 叉树: +⑴树中每个结点至多有m 棵子树; +⑵若根结点不是叶子结点,则至少有两棵子树; +⑶除根结点之外的所有非终端结点至少有⎡m/2⎤ 棵子树; +⑷所有的非终端结点中包含以下信息数据:(n,A0,K1,A1,K2,…,Kn,An) +其中:Ki(i=1,2,…,n)为关键码,且Ki< Ki+1,Ai 为指向子树根结点的指针(i=0,1,…,n),且指针Ai-1 所指子树中所有结点的关键码均小于Ki (i=1,2,…,n),An 所指子树中所有结点的关键码均大于Kn, ⎡m/2⎤ −1 ≤ n ≤m −1 ,n 为关键码的个数。 +⑸所有的叶子结点都出现在同一层次上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空 +#### 7.2 查找 +B-树的查找是由两个基本操作交叉进行的过程,即 +⑴在B-树上找结点; ⑵在结点中找关键码。 +由于,通常B-树是存储在外存上的,操作⑴就是通过指针在磁盘相对定位,将结点信息读入内存,之后,再对结点中的关键码有序表进行顺序查找或折半查找。因为,在磁盘上读取结点信息比在内存中进行关键码查找耗时多,每次向下搜索一层都需要从内存中加载磁盘信息,B-树的层次树是决定B-树查找效率的首要因素。 + +#### 7.2 插入 +1.插入 +在B-树上插入关键码与在二叉排序树上插入结点不同,关键码的插入不是在叶结点上 +进行的,而是在最底层的某个非终端结点中添加一个关键码,若该结点上关键码个数不超过m-1 个,则可直接插入到该结点上;否则,该结点上关键码个数至少达到m 个,因而使该结点的子树超过了m棵,这与B-树定义不符。所以要进行调整,即结点的“分裂”。方法为:关键码加入结点后,将结点中的关键码分成三部分,使得前后两部分关键码个数个结点将其插入到父结点中。若插入父结点而使父结点中关键码个数超过m-1,则父结点继续分裂,直到插入某个父结点,其关键码个数小于m。可见,B-树是从底向上生长的。 +#### 7.3 删除 +分两种情况: +(1)删除最底层结点中关键码 +a)若结点中关键码个数大于⎡m / 2⎤ -1,直接删去。 +b)否则除余项与左兄弟(无左兄弟,则找左兄弟)项数之和大于等于2( -1) 就与它 +们父结点中的有关项一起重新分配 +(2)删除为非底层结点中关键码 +若所删除关键码非底层结点中的Ki,则可以指针Ai 所指子树中的最小关键码X 替代 +Ki,然后,再删除关键码X,直到这个X 在最底层结点上,即转为(1)的情形 + +#### 7.2 B-树的特性: +1.关键字集合分布在整颗树中; +2.任何一个关键字出现且只出现在一个结点中; +3.搜索有可能在非叶子结点结束; +4.其搜索性能等价于在关键字全集内做一次二分查找; +5.自动层次控制; + +http://c.biancheng.net/cpp/html/1028.html +## 八. B+树 +#### 8.1 定义 +⑴有n 棵子树的结点中含有n 个关键码; +⑵所有的叶子结点中包含了全部关键码的信息,及指向含有这些关键码记录的指针,且 +叶子结点本身依关键码的大小自小而大的顺序链接。 +⑶所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键码。 +#### 8.2 B+的特性 +1.所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的; +2.不可能在非叶子结点命中; +3.非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层; +4.更适合文件索引系统; + +#### 8.3 操作 +1)查找操作 +对B+树可以进行两种查找运算: +a.从最小关键字起顺序查找; +b.从根结点开始,进行随机查找。 +在查找时,若非终端结点上的剧组机等于给定值,并不终止,而是继续向下直到叶子结点。因此,在B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子结点的路径。其余同B-树的查找类似。 +2).插入操作 +B+树的插入与B树的插入过程类似。不同的是B+树在叶结点上进行,如果叶结点中的关键码个数超过m,就必须分裂成关键码数目大致相同的两个结点,并保证上层结点中有这两个结点的最大关键码。(算法见百度百科) +3)删除操作 +B+树的删除也仅在叶子结点进行,当叶子结点中的最大关键字被删除时,其在非终端结点中的值可以作为一个“分界关键字”存在。若因删除而使结点中关键字的个数少于m/2 (m/2结果取上界,如5/2结果为3)时,其和兄弟结点的合并过程亦和B-树类似。 + +### 7.2 B+树和B-树最大的不同点: +1).B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。 + +2).在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。 +3)B+树支持range-query非常方便,而B树不支持。这是数据库选用B+树的最主要原因 +http://www.cnblogs.com/biyeymyhjob/archive/2012/07/25/2608880.html +http://blog.csdn.net/v_JULY_v/article/details/6530142/ + +## 8. 红黑树 +#### 思想 +红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。 + +#### 特性 +性质1. 节点是红色或黑色。 +性质2. 根是黑色。 +性质3. 所有叶子都是黑色(叶子是NIL节点)。 +性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) +性质5. 从任一节点到其每个叶子的所有简单路径 都包含相同数目的黑色节点。 + +#### 插入 +II、红黑树插入的几种情况: +情况1,z的叔叔y是红色的。 +情况2:z的叔叔y是黑色的,且z是右孩子 +情况3:z的叔叔y是黑色的,且z是左孩子 + +#### 删除 +III、红黑树删除的几种情况。 +情况1:x的兄弟w是红色的。 +情况2:x的兄弟w是黑色的,且w的俩个孩子都是黑色的。 +情况3:x的兄弟w是黑色的,且w的左孩子是红色,w的右孩子是黑色。 +情况4:x的兄弟w是黑色的,且w的右孩子是红色的。 + +http://www.cnblogs.com/daoluanxiaozi/p/3340382.html +#### 红黑树和AVL树的比较: +红黑树: +(1)并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。 +(2)此外,由于它的设计,任何不平衡都会在三次旋转之内解决。红黑树能够给我们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。 +AVL树: +(1)它的左子树和右子树都是AVL树,左子树和右子树的高度差不能超过; +(2) 查找、插入和删除在平均和最坏情况下都是O(log n),增加和删除可能需要通过一次或多次树旋转来重新平衡这个树; +(3)一棵n个结点的AVL树的其高度保持在0(log2(n)),不会超过3/2log2(n+1) +一棵n个结点的AVL树的平均搜索长度保持在0(log2(n)). +一棵n个结点的AVL树删除一个结点做平衡化旋转所需要的时间为0(log2(n)). +## 哈夫曼树。 + + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + diff --git a/algorithm/string.md b/algorithm/string.md new file mode 100644 index 0000000..ba5a9b2 --- /dev/null +++ b/algorithm/string.md @@ -0,0 +1,235 @@ +##排序 + +> 键索引计数法: +> - 频率统计:使用int数组count[]计算每个建出现的频率 +> - 将频率转化为索引:使用count来计算每个键在排序结果中的起始索引位置 +> - 数据分类:将所有元素转移到一个辅助数组aux中以进行排序。 +> - 回写:将元素排序后的结果复制回原数组。 + + +### 低优先字符排序 +**思路**:从右到左以每个位置的字符为键,用键索引计数法将字符串排序W遍(字符串长度) + **复杂度**: O(NW) W为字符串长度,N为元素选择空间 + 实现: + + public static void sort(String[] a, int w) { + // 通过前w个字符将a[] 排序 + int N = a.length; + int R = 256; + String[] aux = new String[N]; + for (int d = w - 1; d >= 0; d--) { + int[] count = new int[R + 1]; + // 计算出频率 + for (int i = 0; i < N; i++) { + count[a[i].charAt(d) + 1]++; + } + // 将频率转换为索引 + for (int r = 0; r < R; r++) { + count[r + 1] += count[r]; + } + // 将元素分类 + for (int i = 0; i < N; i++ ) { + aux[count[a[i].charAt(d)] ++] = a[i]; + } + for (int i = 0; i < N; i ++ ) { + a[i] = aux[i]; + } + } + } + +### 高位优先字符串排序 +**思路**:解决字符串长度不一致的情况,采用从左到右的次序进行排序。 在高位优先排序时需要注意的字符串末尾的情况,因为字符串长度不一,合理的做法是将所有字符都已被检查的字符串所在的子数组排在所有子数组的前面,这样不需要对这些检查完成的字符串再次递归排序,也避免了IndexOutOfBound。 高位优先排序在已排号序的位置,根据字符进行分组,将数组分为更小的数组进行排序,利用了分治算法提高了算法的性能。 +**复杂度**:O(N) 到O(Nw)之间 +实现: + +**三向字符串快速排序** +**思路**:结合了快速排序和高位优先字符串排序算法,采用高位优先字符串排序算法的思路,根据字符串从左到右的字符对字符串进行排序。排序时使用快速排序的思想,使用首字符进行三向切分为首字母小余、等于,大于切分字符的字符串数组,然后递归地将得到的三个字数组排序: +**优势**:高位优先字符串排序算法会产生大量的子数组,而三向字符串快速排序切分总只有三个。因此该算法能够很好处理等值键、有较长公告前缀的键、取值范围娇小的键和小数组。 相对于高位优先字符串排序需要的空间更少。 +复杂度: +**实现:** + + public static void sort(String[] strArr) { + sort(strArr, 0, strArr.length - 1, 0); + } + + /** + * 对字符串串数组strArr的 strArr[lo]到strArr[hi]的字符串根据d为的字符进行排序 + * @param strArr + * @param lo + * @param hi + * @param d + */ + private static void sort(String[] strArr, int lo, int hi , int d) { + if (hi <= lo) { + return; + } + int lt = lo, gt = hi; + int val = strArr[lo].charAt(d); + int i = lo + 1; + while(i <= gt) { + int tmp = strArr[i].charAt(d); + if ( tmp < val) { + exch(strArr, lt++, i++); + } else if( tmp > val) { + exch(strArr, i, gt--); + } else { + i ++; + } + } + sort(strArr, lo, lt-1, d); + if (val >= 0 ) { + sort(strArr, lt, gt, d+1); + } + sort(strArr, gt+1, hi, d); + } + private static void exch(String[] a, int i ,int j) { + String tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + +> 注意:和快速排序一样,最好在排序之前将数组打乱或将第一个元素和一个随机位置的元素交换以得到一个随机的切分元素,避免快速排序算法接近最坏情况 + +总结: + +| 算法 | 稳定性 | 原地排序|时间复杂度|空间复杂度|优势领域| +| -- | -- | -- | - | -| -| +|字符串的插入排序| 是|是|N到N2之间|1|小数组或已经有序的数组| +|快速排序|否|是|N(log2N)|logN|通用排序算法,特别适合空间不足的情况| +|归并排序|是|否|N(log2N)|N|稳定的通用排序算法| +|三向快速排序|否|是|N到NlogN之间|logN|大量重复建| +|低位优先的字符串排序|是|否|NW|N|较长的定长字符串| +|高位优先的字符串排序|是|否|N到Nw之间|N+WR|随机字符串| +|三向字符串快速排序|否|是|N到Nw之间|W+logN|通用排序算法,特别适合用于含有较长公共前缀的字符串| + + +查找树 + +- 单词查找树(trie) + + 基本性质: 是由连接结点组成的数据结构,除了根结点,每个结点有且只有一条从父结点指向它的链接。每条链接都对应着一个字符,也可以用链接所对应的字符标记被指向的结点。值为空的结点在符号表中没有对应的键,它们的存在是为了简化单词查找树的查找操作。 + 查找过程:从根结点开始,首先经过的键是首字母所对应的链接。在下一个结点中沿着第二个字符所对应的链接继续前进。依此类推,直到键的最后一个字符所指向的结点或者遇到一条空链接。此时有三种情况: + + - 键的尾字符所对应的结点中的值非空,这是一次命中查找,则返回尾字符所对应的结点中保存的值。 + - 键的尾字符所对应的结点中的值为空,表示未命中。 + - 查找结束于一条空链接,表示未命中。 + + 插入过程:进行插入的时候和二叉查找树一样,需要先进行查找,会遇到以下两个情况: + + - 到达键尾部之前就遇到空链接,则创建对应的结点并将键值保存到最后一个字符的结点中。 + - 在遇到空链接之前就到达了键的尾字符,在这种情况下,和关联性数组一样,将该结点的值设置为键所对应的值。 + + 删除过程:首先找到对应的结点并将它的值设置为空,如果该结点含有一个非空的链接指向某个子结点那么就不需要进行其他操作。如果它所有链接均为空,那么就需要从数据结构中删去这个结点,如果删去它使得它的父节点的所有链接也均为空,就需要继续删除它的父结点,依此类推。 + 性质: + + - 单词查找树的链表结构和键的插入或删除顺序无关:对于给定任意一组键,其单词查找树都是唯一的。 + - 在单词查找树中查找一个键或插入一个键时,访问数组的次数最多的键的长度加1. + - 未命中查找平均所需检查的结点的数量为 logRN + - 一棵单词查找树的链接总数在RN到RNw之间,其中w为建的平均长度。 ??? + + 应用: + + - 数字帐号 + - URL + - 文本处理 + - 基因组数据中的蛋白质 + + 代码实现: + +- 三向单词查找树 + + 思想:为了避免R向单词查找树过度的浪费空间,三向单词查找树每个结点都含有一个字符、三条链接和一个值。这三条链接分别对应着当前字母小于、等于和大于结点字母的所有键。 + 查找:查找时比较键的首字母和根结点的字母,如果键的首字母较小,就选择左链接。如果较大,就选择右链接;如果相等,则选择中链接。然后递归的使用相同的算法,如果遇到一个空链接或则当前键结束时结点的值为空,那么查找未命中;如果键结束时结点的值非空则查找命中。 + 优势:节省大量空间。 + 性质: + + - 每个结点只有三个链接,链接总数在3N到3Nw之间 + - 平均查找时间复杂度为lnN 次 + + 代码实现: + +子字符串查找 + +- 暴力字符串查找算法 +- KMP算法 + + 思想:KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,只要继续把它向后移和移动匹配词就可以,这样就提高了效率。可以针对搜索词,算出一张部分匹配表。通过查表查到最后一个匹配字符对应的部分匹配值,并利用以下公式计算匹配词向后移动的位数: + 移动位数 = 已匹配的字符数 - 对应的部分匹配值 + + "部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例, + + - "A"的前缀和后缀都为空集,共有元素的长度为0; + - "AB"的前缀为[A],后缀为[B],共有元素的长度为0; + - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0; + - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0; + - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1; + - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2; + - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。 + + +实现: + + /** + * 计算部分匹配表 + * + * @param pattern + * @param next + */ + public void makeNext(char[] pattern, int next[]) { + int pIdx, maxSuffixLen; // pIdx:模版字符串下标;maxSuffixLen:最大前后缀长度 + int m = pattern.length; // 模版字符串长度 + next[0] = 0; //模版字符串的第一个字符的最大前后缀长度为0 + for (pIdx = 1, maxSuffixLen = 0; pIdx < m; ++pIdx) //for循环,从第二个字符开始,依次计算每一个字符对应的next值 + { + /** + * maxSuffixLen 大于0 表示前一个字符已经存在匹配 + */ + while (maxSuffixLen > 0 && pattern[pIdx] != pattern[maxSuffixLen]) { //递归的求出P[0]···P[q]的最大的相同的前后缀长度k + maxSuffixLen = next[maxSuffixLen - 1]; //不理解没关系看下面的分析,这个while循环是整段代码的精髓所在,确实不好理解 + } + if (pattern[pIdx] == pattern[maxSuffixLen]) //如果相等,那么最大相同前后缀长度加1 + { + maxSuffixLen++; + } + next[pIdx] = maxSuffixLen; + } + } + + public int kmp(String str, String pattern) { + int[] next = new int[str.length()]; + int strIdx, pIdx; + makeNext(pattern.toCharArray(), next); + + for (strIdx = 0, pIdx = 0; strIdx < str.length(); ++strIdx) { + while (pIdx > 0 && pattern.charAt(pIdx) != str.charAt(strIdx)) { + /** + * 移动匹配字符串位置 + */ + pIdx = next[pIdx - 1]; + } + if (pattern.charAt(pIdx) == str.charAt(strIdx)) { + pIdx++; + } + if (pIdx == pattern.length()) { + return strIdx - pattern.length() + 1; + } + } + return -1; + } + +复杂度:时间复杂度最坏(3N) 空间复杂度 O(M) + + +##参考资料 +http://www.cnblogs.com/c-cloud/p/3224788.html + +字符串S的最长回文子串S1 + +参考资料 +https://github.com/JohnZhengHub/blogs/blob/master/algorithm/algorithm-base/Manacher.md + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + diff --git a/algorithm/tu.md b/algorithm/tu.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/bg.jpg b/assets/bg.jpg new file mode 100644 index 0000000..5e5b03a Binary files /dev/null and b/assets/bg.jpg differ diff --git a/assets/weix_gongzhonghao.jpg b/assets/weix_gongzhonghao.jpg new file mode 100644 index 0000000..6a9f13f Binary files /dev/null and b/assets/weix_gongzhonghao.jpg differ diff --git a/author.md b/author.md new file mode 100644 index 0000000..2ac115d --- /dev/null +++ b/author.md @@ -0,0 +1,6 @@ +# 作者 +郑江龙,厦门大学研究生,研究生毕业签了百度金融offer。 网上个人IP:入境繁华。 + +个人博客地址:http://zhengjianglong.leanote.com/ + + diff --git a/book.json b/book.json new file mode 100644 index 0000000..4e19bf2 --- /dev/null +++ b/book.json @@ -0,0 +1,50 @@ +{ + "title": "互联网面试笔记", + "description": "本书主要是对互联网面试中常见的基础知识做总结和分析", + "language": "zh", + "plugins": [ + "duoshuo", + "github", + "editlink", + "prism", + "-highlight", + "baidu", + "splitter", + "sitemap", + "latex-codecogs", + "-fontsettings", + "donate" + ], + "pluginsConfig": { + "duoshuo": { + "short_name": "zhengjl", + "theme": "default" + }, + "github": { + "url": "https://github.com/zhengjianglong915" + }, + "editlink": { + "base": "https://zhengjianglong.gitbooks.io/note-of-interview/content/", + "label": "编辑本页" + }, + "baidu": { + "token": "35d9061f8db12b29ed7072cdf69667f4" + }, + "sitemap": { + "hostname": "http://zhengjianglong.leanote.com" + }, + "fontsettings": { + "theme": "white", + "family": "sans", + "size": 3 + }, + "donate": { + "wechat": "http://wx4.sinaimg.cn/mw690/a196fc93ly1fdun4kzymej20i10i8tb6.jpg", + "alipay": "http://wx3.sinaimg.cn/mw690/a196fc93ly1fdun4koc4rj208t08lgme.jpg", + "title": "感谢支持!!", + "button": "赏", + "alipayText": "支付宝打赏", + "wechatText": "微信打赏" + } + } +} \ No newline at end of file diff --git a/chapter8/ask.md b/chapter8/ask.md new file mode 100644 index 0000000..e69de29 diff --git a/database/README.md b/database/README.md new file mode 100644 index 0000000..e69de29 diff --git a/database/all.md b/database/all.md new file mode 100644 index 0000000..5a668da --- /dev/null +++ b/database/all.md @@ -0,0 +1,200 @@ + +## 手写一段sql语句,具体内容忘了,好像和limit有关 + +## 存储引擎的区别 +**InnoDB**: 支持事务,是面向在线事务处理(OLTP)的应用,特点是行锁设计,支持外键,并支持一致性非锁定读,即默认情况下读取操作不会产生锁.是默认的**存储引擎**:.还提供了插入缓冲,二次写,自适应哈希索引,预读等高性能和高可用的功能. +MyISAM: 不支持事务,是表锁设计和支持全文索引,主要面向一些OLAP的数据库应用.它的缓冲池只缓冲索引文件,而不缓冲数据文件.该存储引擎表由MYD和MYI组成,MYD用来存放数据文件,MYI用来存放索引文件. +**NDB**:是一个集群存储引擎,其特点是数据全部放在内存中,因此主键查找速度极快,并通过添加NDB数据库存储节点可以线性提高数据库性能,是高可用,高性能的集群系统. +**Memory**: 将表中的数据存放在内存中,如果数据库重启或发生崩溃,表中的数据库都将消失,它非常适合存储临时数据的临时表.默认采用哈希索引. +**Archive**: 只支持INSERT和SELECT操作,使用zlib算法将数据行进行压缩,压缩比可以达到1:10,非常适合存储归档数据.但其本身不是事务安全的存储引擎,其设计目标是提供高速的插入和压缩功能. +**Federated**: 并不存放数据,它只是指向一台远程MySQL数据库服务器上的表. +**Maria存储引擎**: 设计目标主要是用来取代原有的MyISAM存储引擎. + +## MyISAM和InnoDB的区别 +1. MyISAM是非事务安全型的,而InnoDB是事务安全型的。 +2. MyISAM锁的粒度是表级,而InnoDB支持行级锁定。 +3. MyISAM支持全文索引,而Innodb不支持全文索引 +4. MyISAM表是保存成文件形式的,在跨平台的数据转移中使用MyISAM存储会省去不少的麻烦。 +5. InnoDB表比MyISAM表更安全,可以保证数据不丢失的情况下,切换非事务表到事务表 + +应用场景 +1. MyISAM 管理非事务表,它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。 +2. InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。 + +### sql注入原理 +就是通过把SQL命令插入到Web 表单 提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令 +1.猜表名,列名等 +2.后台身份验证绕过漏洞 +验证绕过漏洞就是'or'='or'后台绕过漏洞,利用的就是AND和OR的运算规则,从而造成后台脚本逻辑性错误. + +防范: +1.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。 +2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。 +3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。 +4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。 +5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装,把异常信息存放在独立的表中。 + +## 数据库范式 +**第一范式(1NF):属性不可分**。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。 +**第二范式(2NF):符合1NF,并且,非主属性完全依赖于码(也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中)**。 +**第三范式(3NF):符合2NF,并且,消除传递依赖(每一列数据都和主键直接相关,而不能间接相关)**。 +BCNF:符合3NF,并且,没有任何属性完全函数依赖于非码的任何一组属性. +找个例子说. + +参考: http://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html +http://blog.sina.com.cn/s/blog_46d817650100yj2i.html + +## 数据库索引 +索引是一个单独存储在磁盘上的数据库结构,它们包含着对数据表里所有记录的引用指针,使用索引可以提高数据库特定数据的查询速度.索引时在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,并且每种存储引擎也不一定支持所有索引类型. + +索引的存储类型有两种:BTREE和HASH,具体和表的存储引擎有关.MyISAM和InnoDB存储引擎只支持BTREE;MEMORY/HEAD存储索引可以支持HASH和BTREE索引. +**索引的优点**: +1. 通过创建唯一索引,可以保证数据库表中每行数据的唯一性. +2. 可以加快数据的查询速度. +3. 在实现数据的参考完整性方面,可以加速表和表之间的连接. +4. 再使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间 +5. 通过使用索引,可以在查询中使用优化隐藏器,提高系统的性能。 + + +**索引的缺点**: +1. 创建索引和维护索引要耗费时间,并且随着数据量的增加耗费时间也增加. +2. 索引需要占空间内存. +3. 在对表中数据进行增加,删除和修改的时候,索引也需要动态维护,这样降低了数据维护速度. + +### 索引分类 +1. 普通索引和唯一索引 +2. 直接创建索引和间接创建索引 +3. 普通索引和唯一性索引 +4. 单个索引和符合索引 +5. 聚簇索引和非聚簇索引 + +## 索引失效?? +1. WHERE字句的查询条件里有不等于号(WHERE column!=...),MYSQL将无法使用索引 +2. 如果WHERE字句的查询条件里使用了函数(如:WHERE DAY(column)=...),MYSQL将无法使用索引 +3. 在JOIN操作中(需要从多个数据表提取数据时),MYSQL只有在主键和外键的数据类型相同时才能使用索引,否则即使建立了索引也不会使用。 +4. 如果WHERE子句的查询条件里使用了比较操作符LIKE和REGEXP,MYSQL只有在搜索模板的第一个字符不是通配符的情况下才能使用索引。比如说,如果查询条件是LIKE 'abc%',MYSQL将使用索引;如果条件是LIKE '%abc',MYSQL将不使用索引。 +5. 在ORDER BY操作中,MYSQL只有在排序条件不是一个查询条件表达式的情况下才使用索引。尽管如此,在涉及多个数据表的查询里,即使有索引可用,那些索引在加快ORDER BY操作方面也没什么作用。 +6. 如果某个数据列里包含着许多重复的值,就算为它建立了索引也不会有很好的效果。比如说,如果某个数据列里包含了净是些诸如“0/1”或“Y/N”等值,就没有必要为它创建一个索引。 +7. 如果条件中有or(并且其中有or的条件是不带索引的),即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。 +8. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。 +9. 如果mysql估计使用全表扫描要比使用索引快,则不使用索引。 + +http://www.cnblogs.com/hongfei/archive/2012/10/20/2732589.html +http://my.oschina.net/hebad/blog/370815 + + +## 数据库锁机制 +数据库锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问,访问变得有序所设计的一种规则。MySQL各存储引擎使用了三种类型(级别)的锁定机制:行级锁定,页级锁定和表级锁定。 +- 表级锁定(table-level):表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。表级锁分为读锁和写锁。 + +- 页级锁定(page-level):页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。 + +- 行级锁定(row-level):行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。InnoDB的行级锁同样分为两种,共享锁和排他锁,同样InnoDB也引入了意向锁(表级锁)的概念,所以也就有了意向共享锁和意向排他锁,所以InnoDB实际上有四种锁,即共享锁(S)、排他锁(X)、意向共享锁(IS)、意向排他锁(IX); +在MySQL数据库中,使用表级锁定的主要是MyISAM,Memory,CSV等一些非事务性存储引擎,而使用行级锁定的主要是Innodb存储引擎和NDBCluster存储引擎,页级锁定主要是BerkeleyDB存储引擎的锁定方式。 + +而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。 + + +| | 共享锁(S)| 排他锁(X)| 意向共享锁(IS)| 意向排他锁(IX)| +共享锁(S) | 兼容 | 冲突 | 兼容 |冲突 +排他锁(X) | 冲突 | 冲突 | 冲突 |冲突 +意向共享锁(IS) | 兼容 | 冲突 | 兼容 |兼容 +意向排他锁(IX) | 冲突 | 冲突 | 兼容 |兼容 + + + +参考地址:http://www.cnblogs.com/ggjucheng/archive/2012/11/14/2770445.html + +MyISAM 表锁优化建议: +1、缩短锁定时间 +2、分离能并行的操作 +3、合理利用读写优先级 + + +## 乐观锁,悲观锁 + +悲观锁:它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。 +乐观锁( Optimistic Locking ) :相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则则拒绝更新并返回用户错误的信息,让用户决定如何去做。乐观锁由程序实现,不会存在死锁问题。它适用的场景也相对乐观。但乐观锁不能解决脏读的问题 + +悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1] +乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。 + +## 事务隔离机制 +事务隔离级别: + - 未提交读(READ UNCOMMITTED):事务中的修改,即使未提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也称为脏读。 + - 提交读(READ COMMITTED):一个事物从开始到提交之前,所做的任何修改对其他事物都是不可见的,这个级别有时候叫做不可重复读。这个级别上两次执行同样的查询会得到不一样的结果。 + - 可重复读(REPEATABLE READ):解决了脏读问题,该级别保证了在同一个事务中多次读同样记录的结果是一致的,理论上无法解决幻读问题。幻读就是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入新的记录,当之前的事物再次读取该范围的记录时会产生幻行。 + - 可串行化(SERIZLIZABLE):它通过强制事务串行执行,避免了前面说的幻读的问题。 + + 脏读 不可重复读 幻读可能性 加锁读 +未提交读 YES YES YES NO +提交读 NO YES YES NO +可重复读 NO NO YES NO +可串行化 NO NO NO YES + + +## 脏读、不可重复读和幻读 +1. 脏读: 事务T1更新了一行记录内容,但并没有提交修改。事务T2读取更新后的行,然后T1执行回滚操作。读取了刚才所做的修改。现在T2读取的行就无效了。(一个事务读取了另一个事务未提交的数据) +2. 不可重复读:事务T1读取了一行记录,紧接着T2修改了T1刚才读取的那一行记录,然后T1又再次读取这行记录,发现与刚才读取的结果不同。 +3. 幻读:事务T1读取一个结果集,然后T2事务在T1结果集范围内插入一行记录。然后T1再次对表进行检索,发现多了T2插入的数据。 + + + +## 数据库事务属性 +事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。 +原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。 +一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。 +隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。 +持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。 + +## 数据库事务的几种粒度; + +## 是否了解数据库的索引是如何实现的 +### MyISAM索引实现 +MyISAM索引使用了B+Tree作为索引结构,叶子结点的data域存放的是数据记录的地址。MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。主索引和辅助索引的存储结构没有任何区别。 +### InnoDB索引实现 +虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。 +第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据。 +第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。 + +### Memory索引实现 +Memory索引适用于需要快速访问数据的场景,显示支持哈希索引。内部基于哈希表数据结构实现,只包含哈希值和行指针,对于每一行数据,存储引擎都会对所有的引擎列计算一个哈希码,在哈希表对应位置存放该行数据的指针或地址。为了解决多个hash冲突问题,哈希索引采用了链地址法来解决冲突问题。所以采用链表数组作为存储结构。这种索引结构十分紧凑,且具有很快的查询速度。但也存在一些问题, +1. 哈希表数据不是按照索引顺序存储的,所以无法用于排序。 +2. 只能支持等值比较查询。 +3. 存在冲突情况下查询速度变慢。 +https://msdn.microsoft.com/zh-cn/library/dn133190.aspx + +## 数据库连接池原理 +### 背景 +传统的数据库连接方式是,用户每次请求都要向数据库获取连接,而数据库连接的创建和关闭需要一定的开销。频繁的建立、关闭数据库,会极大的降低系统的性能,增大系统的开销,甚至成为系统的瓶颈。另外使用这种传统的模式,还必须管理数据库的每一个连接,以确保他们能正确关闭,如果出现程序异常而导致某些连接未能关闭。同时无节制的创建连接极易导致数据库服务器内存溢出。 + +### 原理 +数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。以及一套连接使用、分配、管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。 + +### 开源java连接池: +现在很多Web服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。 +1.C3P0 :是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。参考网站: http://sourceforge.net/projects/c30/ +2.Proxool :是一个Java SQL Driver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的JDBC驱动程序增加连接池功能。 参考网站: http://proxool.sourceforge.net +3.Jakarta DBCP :是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用。参考网站: http://jakarta.apache.org/commons/dbcp/ + +原理: http://www.uml.org.cn/sjjm/201004153.asp +实现: http://www.cnblogs.com/lihuiyy/archive/2012/02/14/2351768.html + + +连接池使用什么数据结构实现 +链表 + +实现连接池: http://www.cnblogs.com/lihuiyy/archive/2012/02/14/2351768.html +四个表 记录成绩,每个大约十万条记录,如何找到成绩最好的同学 +servlet的一些相关问题 +webservice相关 + +## mysql有那些存储引擎,分别有什么特点 + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + + + diff --git a/framework/README.md b/framework/README.md new file mode 100644 index 0000000..e69de29 diff --git a/framework/hibernate.md b/framework/hibernate.md new file mode 100644 index 0000000..5c34ae1 --- /dev/null +++ b/framework/hibernate.md @@ -0,0 +1,111 @@ +## MyBatis 与 Hibernate的区别 + + +## JDBC 和 Hibernate的区别 + + +## Hibernate的原理体系架构 +![图片标题](http://my.csdn.net/uploads/201205/30/1338346660_4642.gif) +**1) SessionFactory (org.hibernate.SessionFactory)** +针对单个数据库映射关系经过编译后的内存镜像,是线程安全的(不可变)。它是生成Session的工厂,本身要用到ConnectionProvider。该对象可以在进程或集群的级别上,为那些事务之间可以重用的数据提供可选的二级缓存。 +**2) Session (org.hibernate.Session)** +表示应用程序与持久储存层之间交互操作的一个单线程对象,此对象生存期很短。 其隐藏了JDBC连接,也是Transaction的工厂。其会持有一个针对持久化对象的必选(第一级)缓存,在遍历对象图或者根据持久化标识查找对象时会用到。 +**3)事务Transaction (org.hibernate.Transaction)** +(可选的)应用程序用来指定原子操作单元范围的对象,它是单线程的,生命周期很短。 它通过抽象将应用从底层具体的JDBC、JTA以及CORBA事务隔离开。 某些情况下,一个Session之内可能包含多个Transaction对象。 尽管是否使用该对象是可选的,但无论是使用底层的API还是使用Transaction对象,事务边界的开启与关闭是必不可少的。 +**4) ConnectionProvider (org.hibernate.connection.ConnectionProvider)** +(可选的)生成JDBC连接的工厂(同时也起到连接池的作用)。 它通过抽象将应用从底层的Datasource或DriverManager隔离开。 仅供开发者扩展/实现用,并不暴露给应用程序使用。 +**5)TransactionFactory (org.hibernate.TransactionFactory)** +(可选的)生成Transaction对象实例的工厂。 仅供开发者扩展/实现用,并不暴露给应用程序使用。 +**6) 持久的对象及其集合** +带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。 这些对象可能是普通的JavaBeans/POJO,唯一特殊的是他们正与(仅仅一个)Session相关联。 一旦这个Session被关闭,这些对象就会脱离持久化状态,这样就可被应用程序的任何层自由使用。 (例如,用作跟表示层打交道的数据传输对象。) +**7) 瞬态(transient)和脱管(detached)的对象及其集合** +那些目前没有与session关联的持久化类实例。他们可能是在被应用程序实例化后,尚未进行持久化的对象。也可能是因为实例化他们的Session已经被关闭而脱离持久化的对象。 + +参考: http://blog.sina.com.cn/s/blog_667fe4a501016awl.html + +## 五大核心接口, +Hibernate的核心接口一共有5个,分别为:Session、SessionFactory、Transaction、Query和 Configuration。这5个核心接口在任何开发中都会用到。通过这些接口,不仅可以对持久化对象进行存取,还能够进行事务控制。下面对这五的核心 接口分别加以介绍。 +**1) Session接口**: +Session接口负责执行被持久化对象的CRUD操作(CRUD的任务是完成与数据库的交流,包含了很多常见的 SQL语句。)。但需要注意的是Session对象是非线程安全的。同时,Hibernate的session不同于JSP应用中的HttpSession。这里当使用session这个术语时,其实指的是Hibernate中的session,而以后会将HttpSesion对象称为用户session。 +**2) SessionFactory接口** +SessionFactroy接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建 Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。 +**3) Configuration接口** +Configuration接口负责配置并启动Hibernate,创建SessionFactory对 象。在Hibernate的启动的过程中,Configuration类的实例首先定位映射文档位置、读取配置,然后创建SessionFactory对象。 +**4) Transaction接口** +Transaction接口负责事务相关的操作。它是可选的,开发人员也可以设计编写自己的底层事务处理代码。 +**5) Query和Criteria接口**: +Query和Criteria接口负责执行各种数据库查询。它可以使用HQL语言或SQL语句两种表达方式。 + +http://blog.csdn.net/martinmateng/article/details/50879436 + +## Hibernate对象的三种状态转换 +### 1.瞬时状态 (transient) +特征: +1.不处于Session 缓存中 +2.数据库中没有对象记录 +Java如何进入临时状态 +1.通过new语句刚创建一个对象时 +2.当调用Session 的delete()方法,从Session 缓存中删除一个对象时。 + +### 2.持久化状态(persisted) +特征: +1.处于Session 缓存中 +2.持久化对象数据库中设有对象记录 +3.Session 在特定时刻会保持二者同步 +Java如何进入持久化状态 +1.Session 的save()把临时-》持久化状态 +2.Session 的load(),get()方法返回的对象 +3.Session 的find()返回的list集合中存放的对象 +4.ession 的update(),saveOrupdate()使游离-》持久化 + +### 3.游离状态(detached) +特征: +1.不再位于Session 缓存中 +2.游离对象由持久化状态转变而来,数据库中可能还有对应记录。 +Java如何进入持久化状态-》游离状态 +1.Session 的close()方法 +2.Session 的evict()方法,从缓存中删除一个对象。提高性能。少用。 + +## 事务管理 + +## Hibernate对一二级缓存的使用 +(1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据; +(2)二级缓存就是SessionFactory级别的缓存,顾名思义,就是查询的时候会把查询结果缓存到二级缓存中,如果同一个sessionFactory创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿结果,而不会再去连接数据库; +3)Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存; + +http://www.open-open.com/lib/view/open1413527015465.html + +## Lazy-Load的理解 +在Hibernate框架中,当我们要访问的数据量过大时,明显用缓存不太合适, 因为内存容量有限,为了减少并发量,减少系统资源的消耗,这时Hibernate用懒加载机制来弥补这种缺陷,但是这只是弥补而不是用了懒加载总体性能就提高了。我们所说的懒加载也被称为延迟加载,它在查询的时候不会立刻访问数据库,而是返回代理对象,当真正去使用对象的时候才会访问数据库。 +1、通过Session.load()实现懒加载 +load(Object, Serializable):根据id查询。查询返回的是代理对象,不会立刻访问数据库,是懒加载的。当真正去使用对象的时候才会访问数据库。用load()的时候会发现不会打印出查询语句,而使用get()的时候会打印出查询语句。 +使用load()时如果在session关闭之后再查询此对象,会报异常:could not initialize proxy - no Session。处理办法:在session关闭之前初始化一下查询出来的对象:Hibernate.initialize(user);使用load()可以提高效率,因为刚开始的时候并没有查询数据库。但很少使用。 + +2、one-to-one(元素)实现了懒加载。 +在一对一的时候,查询主对象时默认不是懒加载。即:查询主对象的时候也会把从对象查询出来。需要把主对象配制成lazy="true" constrained="true" fetch="select"。此时查询主对象的时候就不会查询从对象,从而实现了懒加载。一对一的时候,查询从对象的是默认是懒加载。即:查询从对象的时候不会把主对象查询出来。而是查询出来的是主对象的代理对象。 + +3、many-to-one(元素)实现了懒加载。 +多对一的时候,查询主对象时默认是懒加载。即:查询主对象的时候不会把从对象查询出来。 + +4、one-to-many(元素)懒加载:默认会懒加载,这是必须的,是重常用的。 +一对多的时候,查询主对象时默认是懒加载。即:查询主对象的时候不会把从对象查询出来。 + +参考: http://blog.csdn.net/sanjy523892105/article/details/7071139 +http://blog.csdn.net/yaorongwang0521/article/details/7074573 + +## 悲观锁和乐观锁 +**悲观锁 (Pessimistic Locking)** +悲观锁,正如其名,他是对数据库而言的,数据库悲观了,他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据) + +优点:数据的一致性保持得很好 +缺点:不适合多个用户并发访问。 + +**乐观锁** + 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。 + + + 个人公众号(欢迎关注): +![](/assets/weix_gongzhonghao.jpg) + + + diff --git a/framework/spring.md b/framework/spring.md new file mode 100644 index 0000000..d013f84 --- /dev/null +++ b/framework/spring.md @@ -0,0 +1,417 @@ +## 什么是Spring +Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Java应用程序中,但对Java EE平台上的Web应用程序有更好的扩展性。Spring框架的目标是使得Java EE应用程序的开发更加简捷,通过使用POJO为基础的编程模型促进良好的编程风格。 + +## Spring有哪些优点 +**轻量级**:Spring在大小和透明性方面绝对属于轻量级的,基础版本的Spring框架大约只有2MB。 +**控制反转(IOC)**:Spring使用控制反转技术实现了松耦合。依赖被注入到对象,而不是创建或寻找依赖对象。 +**面向切面编程(AOP)**: Spring支持面向切面编程,同时把应用的**业务逻辑与系统的服务分离开来**。 +**容器**:Spring包含并管理应用程序对象的配置及生命周期。 +**MVC框架**:Spring的web框架是一个设计优良的web MVC框架,很好的取代了一些web框架。 +**事务管理**:Spring对下至本地业务上至全局业务(JAT)提供了统一的事务管理接口。 +**异常处理**:Spring提供一个方便的API将特定技术的异常(由JDBC, Hibernate, 或JDO抛出)转化为一致的、Unchecked异常。 + +## Spring框架有哪些模块 +Spring框架至今已集成了20多个模块。这些模块主要被分如下图所示的核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。 +**核心容器模块**:是spring中最核心的模块。负责Bean的创建,配置和管理。主要包括:beans,core,context,expression等模块。 +**Spring的AOP模块**:主要负责对面向切面编程的支持,帮助应用对象解耦。 +**数据访问和集成模块**:包括JDBC,ORM,OXM,JMS和事务处理模块,其细节如下: +JDBC模块提供了不再需要冗长的JDBC编码相关了JDBC的抽象层。 +ORM模块提供的集成层。流行的对象关系映射API,包括JPA,JDO,Hibernate和iBatis。 +OXM模块提供了一个支持对象/ XML映射实现对JAXB,Castor,使用XMLBeans,JiBX和XStream 的抽象层。 +Java消息服务JMS模块包含的功能为生产和消费的信息。 +事务模块支持编程和声明式事务管理实现特殊接口类,并为所有的POJO。 +**Web和远程调用**:包括web,servlet,struts,portlet模块。 +**测试模块**:test +**工具模块** +**消息模块** + +## 什么是控制反转(IOC)?什么是依赖注入? +传统模式中对象的调用者需要创建被调用对象,两个对象过于耦合,不利于变化和拓展.在spring中,直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理,从而实现对象之间的松耦合。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。 +**依赖注入**:对象无需自行创建或管理它们的依赖关系,IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。依赖注入能让相互协作的软件组件保持松散耦合。 + +## BeanFactory和ApplicationContext有什么区别? +Bean工厂(BeanFactory)是Spring框架最核心的接口,提供了高级Ioc的配置机制.应用上下文(ApplicationContext)建立在BeanFacotry基础之上,提供了更多面向应用的功能,如果国际化,属性编辑器,事件等等.beanFactory是spring框架的基础设施,是面向spring本身,ApplicationContext是面向使用Spring框架的开发者,几乎所有场合都会用到ApplicationContext. + +## Spring有几种配置方式? +将Spring配置到应用开发中有以下三种方式: +**基于XML的配置**: +**基于注解的配置**: Spring在2.5版本以后开始支持用注解的方式来配置依赖注入。可以用注解的方式来替代XML方式的bean描述,可以将bean描述转移到组件类的内部,只需要在相关类上、方法上或者字段声明上使用注解即可。注解注入将会被容器在XML注入之前被处理,所以后者会覆盖掉前者对于同一个属性的处理结果 +**基于Java的配置**: Spring对Java配置的支持是由@Configuration注解和@Bean注解来实现的。由@Bean注解的方法将会实例化、配置和初始化一个新对象,这个对象将由Spring的IoC容器来管理。@Bean声明所起到的作用与 元素类似。被@Configuration所注解的类则表示这个类的主要目的是作为bean定义的资源。被@Configuration声明的类可以通过在同一个类的内部调用@bean方法来设置嵌入bean的依赖关系。 + +## Spring Bean的生命周期 + +Bean在Spring中的生命周期如下: +**实例化**。Spring通过new关键字将一个Bean进行实例化,JavaBean都有默认的构造函数,因此不需要提供构造参数。 +**填入属性**。Spring根据xml文件中的配置通过调用Bean中的setXXX方法填入对应的属性。 +事件通知。Spring依次检查Bean是否实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware、BeanPostProcessor、InitializingBean接口,如果有的话,依次调用这些接口。 +**使用**。应用程序可以正常使用这个Bean了。 +**销毁**。如果Bean实现了DisposableBean接口,就调用其destroy方法。 + + +---加载过程--- +1.容器寻找Bean的定义信息并且将其实例化。 +2.如果允许提前暴露工厂,则提前暴露这个bean的工厂,这个工厂主要是返回该未完全处理的bean.主要是用于避免单例属性循环依赖问题. +3.受用**依赖注入**,Spring按照Bean定义信息配置Bean的所有属性。 +4.如果Bean实现了**BeanNameAware**接口,工厂调用Bean的**setBeanName()**方法传递Bean的ID。 +5.如果Bean实现了**BeanFactoryAware**接口,工厂调用**setBeanFactory()**方法传入工厂自身。 +6.如果**BeanPostProcessor**和Bean关联,那么它们的**postProcessBeforeInitialzation()**方法将被调用。 +7.如果Bean指定了init-method方法,它将被调用。 +8.如果有**BeanPostProcessor**和Bean关联,那么它们的postProcessAfterInitialization()方法将被调用 +9.最后如果配置了destroy-method方法则注册**DisposableBean**. + +到这个时候,Bean已经可以被应用系统使用了,并且将被保留在Bean Factory中知道它不再需要。 +有两种方法可以把它从Bean Factory中删除掉。 +1.如果Bean实现了DisposableBean接口,destory()方法被调用。 +2.如果指定了订制的销毁方法,就调用这个方法。 + +## Spring Bean的作用域之间有什么区别 +singleton:这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean的实例,单例的模式由bean factory自身来维护。 +prototype:原形范围与单例范围相反,为每一个bean请求提供一个实例。 +request:在请求bean范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。 +Session:与请求范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。 +global-session:global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。 + +## 请解释自动装配模式的区别 +**no**:这是Spring框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在bean定义中用标签明确的设置依赖关系。 +**byName****:该选项可以根据bean名称设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。 +**byType**:该选项可以根据bean类型设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的类型自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。 +**constructor**:造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。 +**autodetect**:该模式自动探测**使用构造器自动装配或者byType自动装配**。首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到相应的构造器或者是无参构造器,容器就会自动选择byTpe的自动装配方式。 + +## Spring 框架中都用到了哪些设计模式 +**代理模式**—在AOP和remoting中被用的比较多。 +**单例模式**—在spring配置文件中定义的bean默认为单例模式。 +**模板方法**—用来解决代码重复的问题 +比如. RestTemplate, JmsTemplate, JpaTemplate。 +前端控制器—Srping提供了DispatcherServlet来对请求进行分发。 +视图帮助(View Helper )—Spring提供了一系列的JSP标签,高效宏来辅助将分散的代码整合在视图里。 +依赖注入—贯穿于BeanFactory / ApplicationContext接口的核心理念。 +**工厂模式**—BeanFactory用来创建对象的实例。 +**Builder模式** - 自定义配置文件的解析bean是时采用builder模式,一步一步地构建一个beanDefinition +**策略模式** :Spring 中策略模式使用有多个地方,如 Bean 定义对象的创建以及代理对象的创建等。这里主要看一下代理对象创建的策略模式的实现。 +前面已经了解 Spring 的代理方式有两个 Jdk 动态代理和 CGLIB 代理。这两个代理方式的使用正是使用了策略模式。 + +## AOP是怎么实现的 +实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。Spring AOP 的实现原理其实很简单:AOP 框架负责动态地生成 AOP 代理类,这个代理类的方法则由 Advice 和回调目标对象的方法所组成,并将该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。 +Spring AOP使用动态代理技术在运行期织入增强代码。使用两种代理机制:基于JDK的动态代理(JDK本身只提供接口的代理);基于CGlib的动态代理。 + +1)JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler只是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑与业务逻辑织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。 +其代理对象**必须是某个接口的实现**,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理.只能实现接口的类生成代理,而**不能针对类** + +2)CGLib采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的调用方法,并顺势织入横切逻辑.它运行期间生成的代理对象是目标类的扩展子类.所以无法通知final的方法,因为它们不能被覆写.是针对类实现代理,主要是为指定的类生成一个子类,覆盖其中方法. + +在spring中默认情况下使用JDK动态代理实现AOP,如果proxy-target-class设置为true或者使用了优化策略那么会使用CGLIB来创建动态代理.Spring AOP在这两种方式的实现上基本一样.以JDK代理为例,会使用JdkDynamicAopProxy来创建代理,在invoke()方法首先需要织入到当前类的增强器封装到拦截器链中,然后递归的调用这些拦截器完成功能的织入.最终返回代理对象. + +http://zhengjianglong.cn/2015/12/12/Spring/spring-source-aop/ + +## 介绍spring的IOC实现 +Spring IOC主要负责创建和管理bean及bean之间的依赖关系.Spring IOC的可分为:IOC容器的初始化和bean的加载. +在IOC容器阶段主要是完成资源的加载(如定义bean的xml文件),bean的解析及对解析后得到的beanDefinition的进行注册.以xmlBeanFactory为例,XmlBeanFactory继承了DefaultListableBeanFactory,XmlBeanFactory将读取xml配置文件,解析bean和注册解析后的beanDefinition工作交给XmlBeanDefinitionReader(是BeanDefinitionReader接口的一个个性化实现)来执行.spring中定义了一套资源类,将文件,class等都看做资源. +1)所以首先是将xml文件转化为资源然后用EncodeResouce来封装,该功能主要时考虑Resource可能存在编码要求的情况,如UTF-8等. +2)然后根据xml文件判断xml的约束模式,是DTD还是Schema,以及寻找模式文档(验证文件)的方法(EntityResolver,这部分采用了代理模式和策略模式). +完成了前面所有的准备工作以后就可以正式的加载配置文件,获取Docoment和解析注册BeanDefinition.Docoment的获取以及BeanDefinition的解析注册并不是由XmlBeanDefinitionReader完成,XmlBeanDefinitionReader只是将前面的工作完成以后文档加载交给DefaultDocumentLoader类来完成.而解析交给了DefaultBeanDefinitionDocumentReader来处理.bean标签可以分为两种,一种是spring自带的默认标签,另一种就是用户自定义的标签.所以spring针对这两种情况,提供了不同的解析方式. 每种bean的解析完成后都会先注册到容器中然后最后发出响应事件,通知相关的监听器这个bean已经注册完成了. + +**bean的加载** +http://zhengjianglong.cn/2015/12/06/Spring/spring-source-ioc-bean-parse/ + +## spring中bean加载机制,bean生成的具体步骤 +1.容器寻找Bean的定义信息并且将其实例化。 +2.如果允许提前暴露工厂,则提前暴露这个bean的工厂,这个工厂主要是返回该未完全处理的bean.主要是用于避免单例属性循环依赖问题. +3.受用**依赖注入**,Spring按照Bean定义信息配置Bean的所有属性。 +4.如果Bean实现了**BeanNameAware**接口,工厂调用Bean的**setBeanName()**方法传递Bean的ID。 +5.如果Bean实现了**BeanFactoryAware**接口,工厂调用**setBeanFactory()**方法传入工厂自身。 +6.如果**BeanPostProcessor**和Bean关联,那么它们的**postProcessBeforeInitialzation()**方法将被调用。 +7.如果Bean指定了init-method方法,它将被调用。 +8.如果有**BeanPsotProcessor**和Bean关联,那么它们的postProcessAfterInitialization()方法将被调用 +9.最后如果配置了destroy-method方法则注册**DisposableBean**. +http://www.cnblogs.com/ITtangtang/p/3978349.html + + +## springMVC流程具体叙述下 +当应用启动时,容器会加载servlet类并调用init方法. 在这个阶段,DispatcherServlet在init()完成初始化参数init-param的解析和封装,相关配置,spring的WebApplicationContext的初始化即完成xml文件的加载,bean的解析和注册等工作,另外为servlet功能所用的变量进行初始化,如:handlerMapping,viewResolvers等. + +当用户发送一个请求时,首先根据请求的类型调用DispatcherServlet不同的方法,这些方法都会转发到doService()中执行.在该方法内部完成以下工作: +1)spring首先考虑multipart的处理,如果是MultipartContent类型的request,则将该请求转换成MultipartHttpServletRequest类型的request. +2)根据request信息获取对应的Handler. 首先根据request获取访问路径,然后根据该路径可以选择直接匹配或通用匹配的方式寻找Handler,即用户定义的controller. Handler在init()方法时已经完成加载且保存到Map中了,只要根据路径就可以得到对应的Handler. 如果不存在则尝试使用默认的Handler. 如果还是没有找到那么就通过response向用户返回错误信息.找到handler后会将其包装在一个执行链中,然后将所有的拦截器也加入到该链中. +4)如果存在handler则根据当前的handler寻找对应的HandlerAdapter. 通过遍历所有适配器来选择合适的适配器. +5)SpringMVC允许你通过处理拦截器Web请求,进行前置处理和后置处理.所以在正式调用 Handler的逻辑方法时,先执行所有拦截器的preHandle()方法. +6)正式执行handle的业务逻辑方法handle(),返回ModelAndView.逻辑处理是通过适配器调用handle并返回视图.这过程其实是调用用户controller的业务逻辑. +8)调用拦截器的postHandle()方法,完成后置处理. +9)根据视图进行页面跳转.该过程首先会根据视图名字解析得到视图,该过程支持缓存,如果缓存中存在则直接获取,否则创建新的视图并在支持缓存的情况下保存到缓冲中.该10)过程完成了像添加前缀后缀,设置必须的属性等工作.最后就是进行页面跳转处理. +11)调用拦截器的afterComplection() + +## spring各个版本的区别 + + +## ioc注入的方式 +1)setter方法注入 +2)构造器注入 + + + + + + + + + + +3)静态工厂注入 factory-method参数 +4)实例工厂 +http://blessht.iteye.com/blog/1162131 + +## AOP相关概念 +**方面(Aspect)**:一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。 +**连接点(Joinpoint)**: 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。 +**通知(Advice):** 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了4个advice.Interception Around(MethodInterceptor)、Before(MethodBeforeAdvice)、After Returning(AfterReturningAdvice)、After(AfterAdvice)。 +**切入点(Pointcut)**: 一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上 +**引入(Introduction)**: 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口 +**目标对象(Target Object)**: 包含连接点的对象。也被称作被通知或被代理对象。POJO +**AOP代理(AOP Proxy)**: AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 +**织入(Weaving)**: 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。 + +## spring何时创建applicationContext(web.xml中使用listener 或dispatcherServlet) + +## listener是监听哪个事件(ServletContext创建事件) +ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件。 + (1) 当 ServletContext 对象被创建时,激发contextInitialized (ServletContextEvent sce)方法 + (2) 当 ServletContext 对象被销毁时,激发contextDestroyed(ServletContextEvent sce)方法。 + +## 过滤器与监听器的区别 +Filter可认为是Servlet的一种“变种”,它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理,是个典型的处理链。它与Servlet的区别在于:它不能直接向用户生成响应。完整的流程是:Filter对用户请求进行预处理,接着将请求交给 Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。 + Java中的Filter 并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。 主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链。优点:过滤链的好处是,执行过程中任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容。而在实际使用时,就要特别注意过滤链的执行顺序问题 + http://blog.csdn.net/sd0902/article/details/8395641 + +Servlet,Filter都是针对url之类的,而Listener是针对对象的操作的,如session的创建,session.setAttribute的发生,或者在启动服务器的时候将你需要的数据加载到缓存等,在这样的事件发生时做一些事情。 +http://www.tuicool.com/articles/bmqMjm + +## 请描述一下java事件监听机制。 + (1) Java的事件监听机制涉及到三个组件:事件源、事件监听器、事件对象 + (2) 当事件源上发生操作时,它将会调用事件监听器的一个方法,并在调用这个方法时,会传递事件对象过来 + (3) 事件监听器由开发人员编写,开发人员在事件监听器中,通过事件对象可以拿到事件源,从而对事件源上的操作进行处理。 + +## 解释核心容器(应用上下文)模块 +这是Spring的基本模块,它提供了Spring框架的基本功能。BeanFactory 是所有Spring应用的核心。Spring框架是建立在这个模块之上的,这也使得Spring成为一个容器。 + +## BeanFactory – BeanFactory 实例 +BeanFactory是工厂模式的一种实现,它使用控制反转将应用的配置和依赖与实际的应用代码分离开来。最常用的BeanFactory实现是XmlBeanFactory类。 + +## XmlBeanFactory +最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory,它根据XML文件中定义的内容加载beans。该容器从XML文件中读取配置元数据,并用它来创建一个完备的系统或应用。 + +## 解释AOP模块 +AOP模块用来开发Spring应用程序中具有切面性质的部分。该模块的大部分服务由AOP Aliance提供,这就保证了Spring框架和其他AOP框架之间的互操作性。另外,该模块将元数据编程引入到了Spring。 + +## 解释抽象JDBC和DAO模块 +通过使用抽象JDBC和DAO模块保证了与数据库连接代码的整洁与简单,同时避免了由于未能关闭数据库资源引起的问题。它在多种数据库服务器的错误信息之上提供了一个很重要的异常层。它还利用Spring的AOP模块为Spring应用程序中的对象提供事务管理服务。 + +## 解释对象/关系映射集成模块 +Spring通过提供ORM模块在JDBC的基础上支持对象关系映射工具。这样的支持使得Spring可以集成主流的ORM框架,包括Hibernate, JDO, 及iBATIS SQL Maps。Spring的事务管理可以同时支持以上某种框架和JDBC。 + +## 解释web模块 +Spring的web模块建立在应用上下文(application context)模块之上,提供了一个适合基于web应用程序的上下文环境。该模块还支持了几个面向web的任务,如透明的处理多文件上传请求及将请求参数同业务对象绑定起来。 + +## 解释Spring MVC模块 +Spring提供MVC框架构建web应用程序。Spring可以很轻松的同其他MVC框架结合,但Spring的MVC是个更好的选择,因为它通过控制反转将控制逻辑和业务对象完全分离开来。 + +## ContextLoaderListener是监听什么事件 +ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。 + +## Spring IoC容器 +Spring IOC负责创建对象、管理对象(通过依赖注入)、整合对象、配置对象以及管理这些对象的生命周期。 + +## IOC有什么优点? +IOC或依赖注入减少了应用程序的代码量。它使得应用程序的**测试很简单**,因为在单元测试中不再需要单例或JNDI查找机制。简单的实现以及较少的干扰机制使得**松耦合**得以实现。IOC容器支持勤性单例及延迟加载服务。 + +## 应用上下文是如何实现的? +ClassPathXmlApplicationContext 容器加载XML文件中beans的定义。XML Bean配置文件的完整路径必须传递给构造器。 +FileSystemXmlApplicationContext 容器也加载XML文件中beans的定义。注意,你需要正确的设置CLASSPATH,因为该容器会在CLASSPATH中查看bean的XML配置文件。 +WebXmlApplicationContext:该容器加载xml文件,这些文件定义了web应用中所有的beans。 + +## 有哪些不同类型的IOC(依赖注入) +**接口注入**:接口注入的意思是通过接口来实现信息的注入,而其它的类要实现该接口时,就可以实现了注入 +构造器依赖注入:构造器依赖注入在容器触发构造器的时候完成,该构造器有一系列的参数,每个参数代表注入的对象。 +Setter方法依赖注入:首先容器会触发一个无参构造函数或无参静态工厂方法实例化对象,之后容器调用bean中的setter方法完成Setter方法依赖注入。 + +## 你推荐哪种依赖注入?构造器依赖注入还是Setter方法依赖注入? +你可以同时使用两种方式的依赖注入,最好的选择是使用构造器参数实现强制依赖注入,使用setter方法实现可选的依赖关系。 + +## 什么是Spring Beans +Spring Beans是构成Spring应用核心的Java对象。这些对象由Spring IOC容器实例化、组装、管理。这些对象通过容器中配置的元数据创建,例如,使用XML文件中定义的创建。 +在Spring中创建的beans都是单例的beans。在bean标签中有一个属性为”singleton”,如果设为true,该bean是单例的,如果设为false,该bean是原型bean。Singleton属性默认设置为true。因此,spring框架中所有的bean都默认为单例bean。 + +## Spring Bean中定义了什么内容? +Spring Bean中定义了所有的配置元数据,这些配置信息告知容器如何创建它,它的生命周期是什么以及它的依赖关系。 + +## 如何向Spring 容器提供配置元数据 +有三种方式向Spring 容器提供元数据: +XML配置文件 +基于注解配置 +基于Java的配置 + +## 你如何定义bean的作用域 +在Spring中创建一个bean的时候,我们可以声明它的作用域。只需要在bean定义的时候通过’scope’属性定义即可。例如,当Spring需要产生每次一个新的bean实例时,应该声明bean的scope属性为prototype。如果每次你希望Spring返回一个实例,应该声明bean的scope属性为singleton。 + +## Spring框架中单例beans是线程安全的吗? +不是,Spring框架中的单例beans不是线程安全的。 + +## 哪些是最重要的bean生命周期方法?能重写它们吗? +有两个重要的bean生命周期方法。第一个是setup方法,该方法在容器加载bean的时候被调用。第二个是teardown方法,该方法在bean从容器中移除的时候调用。 +bean标签有两个重要的属性(init-method 和 destroy-method),你可以通过这两个属性定义自己的初始化方法和析构方法。Spring也有相应的注解:@PostConstruct 和 @PreDestroy。 + +## 什么是Spring的内部bean +当一个bean被用作另一个bean的属性时,这个bean可以被声明为内部bean。在基于XML的配置元数据中,可以通过把元素定义在 或元素内部实现定义内部bean。内部bean总是匿名的并且它们的scope总是prototype。 + +## 如何在Spring中注入Java集合类 +Spring提供如下几种类型的集合配置元素: +list元素用来注入一系列的值,允许有相同的值。 +set元素用来注入一些列的值,不允许有相同的值。 +map用来注入一组”键-值”对,键、值可以是任何类型的。 +props也可以用来注入一组”键-值”对,这里的键、值都字符串类型。 + +## 什么是bean wiring? +Wiring,或者说bean Wiring是指beans在Spring容器中结合在一起的情况。当装配bean的时候,Spring容器需要知道需要哪些beans以及如何使用依赖注入将它们结合起来。 + +## 什么是bean自动装配? +Spring容器可以自动配置相互协作beans之间的关联关系。这意味着Spring可以自动配置一个bean和其他协作bean之间的关系,通过检查BeanFactory 的内容里没有使用和< property>元素。 + +## 解释自动装配的各种模式 +自动装配提供五种不同的模式供Spring容器用来自动装配beans之间的依赖注入: +no:默认的方式是不进行自动装配,通过手工设置ref 属性来进行装配bean。 +byName:通过参数名自动装配,Spring容器查找beans的属性,这些beans在XML配置文件中被设置为byName。之后容器试图匹配、装配和该bean的属性具有相同名字的bean。 +byType:通过参数的数据类型自动自动装配,Spring容器查找beans的属性,这些beans在XML配置文件中被设置为byType。之后容器试图匹配和装配和该bean的属性类型一样的bean。如果有多个bean符合条件,则抛出错误。 +constructor:这个同byType类似,不过是应用于构造函数的参数。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则抛出一个严重的错误。 +autodetect:如果有默认的构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。 + +## 自动装配有哪些局限性? +自动装配有如下局限性: +重写:你仍然需要使用 和< property>设置指明依赖,这意味着总要重写自动装配。 +原生数据类型:你不能自动装配简单的属性,如原生类型、字符串和类。 +模糊特性:自动装配总是没有自定义装配精确,因此,如果可能尽量使用自定义装配。 + +## 你可以在Spring中注入null或空字符串吗 +完全可以。 + +## 什么是Spring基于Java的配置?给出一些注解的例子 +基于Java的配置允许你使用Java的注解进行Spring的大部分配置而非通过传统的XML文件配置。以注解@Configuration为例,它用来标记类,说明作为beans的定义,可以被Spring IOC容器使用。另一个例子是@Bean注解,它表示该方法定义的Bean要被注册进Spring应用上下文中。 + +## 什么是基于注解的容器配置 +另外一种替代XML配置的方式为基于注解的配置,这种方式通过字节元数据装配组件而非使用尖括号声明。开发人员将直接在类中进行配置,通过注解标记相关的类、方法或字段声明,而不再使用XML描述bean之间的连线关系。 + +## 如何开启注解装配? +注解装配默认情况下在Spring容器中是不开启的。如果想要开启基于注解的装配只需在Spring配置文件中配置元素即可。 + +## @Required 注解 +@Required表明bean的属性必须在配置时设置,可以在bean的定义中明确指定也可通过自动装配设置。如果bean的属性未设置,则抛出BeanInitializationException异常。 + +## @Autowired 注解 +@Autowired 注解提供更加精细的控制,包括自动装配在何处完成以及如何完成。它可以像@Required一样自动装配setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。 + +## @Qualifier 注解 +当有多个相同类型的bean而只有其中的一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用消除这种混淆,指明需要装配的bean。 +Spring数据访问 + +## 在Spring框架中如何更有效的使用JDBC? +使用Spring JDBC框架,资源管理以及错误处理的代价都会减轻。开发人员只需通过statements和queries语句从数据库中存取数据。Spring框架中通过使用模板类能更有效的使用JDBC,也就是所谓的JdbcTemplate。 + +## JdbcTemplate +JdbcTemplate类提供了许多方法,为我们与数据库的交互提供了便利。例如,它可以将数据库的数据转化为原生类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据库错误处理功能。 + +## Spring对DAO的支持 +Spring对数据访问对象(DAO)的支持旨在使它可以与数据访问技术(如 JDBC, Hibernate 及JDO)方便的结合起来工作。这使得我们可以很容易在的不同的持久层技术间切换,编码时也无需担心会抛出特定技术的异常。 + +## 使用Spring可以通过什么方式访问Hibernate? +使用Spring有两种方式访问Hibernate: +使用Hibernate Template的反转控制以及回调方法 +继承HibernateDAOSupport,并申请一个AOP拦截器节点 + +## Spring支持的ORM +Spring支持一下ORM: +Hibernate +iBatis +JPA (Java -Persistence API) +TopLink +JDO (Java Data Objects) +OJB + +## 如何通过HibernateDaoSupport将Spring和Hibernate结合起来? +使用Spring的SessionFactory 调用LocalSessionFactory。结合过程分为以下三步: +配置Hibernate SessionFactory +继承HibernateDaoSupport实现一个DAO +使用AOP装载事务支持 + +## Spring支持的事务管理类型 +Spring支持如下两种方式的事务管理: +编码式事务管理:sping对编码式事务的支持与EJB有很大区别,不像EJB与java事务API耦合在一起.spring通过回调机制将实际的事务实现从事务性代码中抽象出来.你能够精确控制事务的边界,它们的开始和结束完全取决于你. +声明式事务管理:这种方式意味着你可以将事务管理和业务代码分离。你只需要通过注解或者XML配置管理事务。通过传播行为,隔离级别,回滚规则,事务超时,只读提示来定义. + +## Spring框架的事务管理有哪些优点 +它为不同的事务API(如JTA, JDBC, Hibernate, JPA, 和JDO)提供了统一的编程模型。 +它为编程式事务管理提供了一个简单的API而非一系列复杂的事务API(如JTA). +它支持声明式事务管理。 +它可以和Spring 的多种数据访问技术很好的融合。 + +## ACID +**原子性(Atomic)**:一个操作要么成功,要么全部不执行. +**一致性(Consistent)**: 一旦事务完成,系统必须确保它所建模业务处于一致的状态 +**隔离性(Isolated)**: 事务允许多个用户对相同的数据进行操作,每个用户用户的操作相互隔离互补影响. +**持久性(Durable)**: 一旦事务完成,事务的结果应该持久化. + +## spring事务定义的传播规则 +PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 +PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。 +PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。 +PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。 +PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 +PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。 +PROPAGATION_NESTED–如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 + +## spring事务支持的隔离级别 +并发会导致以下问题: +藏读:发生在一个事务读取了另一个事务改写但尚未提交的数据. +不可重复读:在一个事务执行相同的查询两次或两次以上,每次得到的数据不同. +幻读:与不可重复读类似,发生在一个事务读取多行数据,接着另一个并发事务插入一些数据,随后查询中,第一个事务发现多了一些原本不存在的数据. +spring 事务上提供以下的隔离级别: +ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别 +ISOLATION_READ_UNCOMMITTED : 允许读取未提交的数据变更,可能会导致脏读,幻读或不可重复读 +ISOLATION_READ_COMMITTD : 允许读取为提交数据,可以阻止脏读,当时幻读或不可重复读仍可能发生 +ISOLATION_REPEATABLE_READ: 对统一字段多次读取结果是一致的,除非数据是被本事务自己修改.可以阻止脏读,不可重复读,但幻读可能发生 +ISOLATION_SERIALIZABLE : 完全服从ACID + +## 你更推荐那种类型的事务管理? +许多Spring框架的用户选择声明式事务管理,因为这种方式和应用程序的关联较少,因此更加符合轻量级容器的概念。声明式事务管理要优于编程式事务管理,尽管在灵活性方面它弱于编程式事务管理(这种方式允许你通过代码控制业务)。 + +## 有几种不同类型的自动代理? +BeanNameAutoProxyCreator:bean名称自动代理创建器 +DefaultAdvisorAutoProxyCreator:默认通知者自动代理创建器 +Metadata autoproxying:元数据自动代理 + +## 什么是织入?什么是织入应用的不同点? +织入是将切面和其他应用类型或对象连接起来创建一个通知对象的过程。织入可以在编译、加载或运行时完成。 + +## 什么是Spring的MVC框架? +Spring提供了一个功能齐全的MVC框架用于构建Web应用程序。Spring框架可以很容易的和其他的MVC框架融合(如Struts),该框架使用控制反转(IOC)将控制器逻辑和业务对象分离开来。它也允许以声明的方式绑定请求参数到业务对象上。 + +## DispatcherServlet +Spring的MVC框架围绕DispatcherServlet来设计的,它用来处理所有的HTTP请求和响应。 + +## WebApplicationContext +WebApplicationContext继承了ApplicationContext,并添加了一些web应用程序需要的功能。和普通的ApplicationContext 不同,WebApplicationContext可以用来处理主题样式,它也知道如何找到相应的servlet。 + +## 什么是Spring MVC框架的控制器? +控制器提供对应用程序行为的访问,通常通过服务接口实现。控制器解析用户的输入,并将其转换为一个由视图呈现给用户的模型。Spring 通过一种极其抽象的方式实现控制器,它允许用户创建多种类型的控制器。 + +## @Controller annotation +@Controller注解表示该类扮演控制器的角色。Spring不需要继承任何控制器基类或应用Servlet API。 + +## @RequestMapping annotation +@RequestMapping注解用于将URL映射到任何一个类或者一个特定的处理方法上。 + + +http://www.codeceo.com/article/spring-top-25-interview.html + +个人公众号(欢迎关注): +![](/assets/weix_gongzhonghao.jpg) + + + diff --git a/framework/struts.md b/framework/struts.md new file mode 100644 index 0000000..b74a4d2 --- /dev/null +++ b/framework/struts.md @@ -0,0 +1,162 @@ +## 描述 Struts 体系结构?对应各个部分的开发工作主要包括哪些? +Struts开源架构很好的实现了MVC模式,MVC即Model-View-Controller的缩写,是一种常用的设计模式。MVC 减弱了业务逻辑接口和数据接口之间的耦合,以及让视图层更富于变化。在Struts2的模型 - 视图 - 控制器模式,实现以下五个核心部件: + Actions + Interceptors + Value Stack / OGNL + Results / Result types + View technologies + +Struts 2 从传统的MVC框架操作需要的模型,而不是在控制器角色略有不同,虽然有一些重叠 +**模型** +模型以一个或多个java bean的形式存在。这些bean分为三类:Action Form、Action、JavaBean or EJB。Action Form通常称之为FormBean,封装了来自于Client的用户请求信息,如表单信息。Action通常称之为ActionBean,获取从ActionSevlet传来的FormBean,取出FormBean中的相关信息,并做出相关的处理,一般是调用Java Bean或EJB等。 + +**视图** +主要由JSP生成页面完成视图,Struts提供丰富的JSP 标签库: Html,Bean,Logic,Template等 + +**控制器** +该控制器负责响应用户输入和执行数据模型对象的相互作用。控制器接收输入,验证输入,然后进行业务操作,修改数据模型的状态。 + +这个解释比较好: http://www.yiibai.com/struts_2/struts_architecture.html +http://www.cnblogs.com/langtianya/archive/2013/04/09/3011090.html + +## 什么是Struts2 +Apache Struts2的是一个在Java中构建**Web应用程序开源框架**。 Struts2是基于OpenSymphony的WebWork的框架。它是Struts1的提高,它更加灵活,易于使用和扩展。 Struts2的核心组成部分是Action,拦截器和结果页。 + +Struts2提供了许多方法来创建Action类,并**通过struts.xml中或通过注释进行配置**。我们可以创建自己的**拦截器**实现常见任务。 Struts2中自带了很多的**标签,并使用OGNL表达式语言**。我们可以创造我们自己的类型转换器来呈现的结果页面。结果页面可以JSP和FreeMarker的模板。 + +## Struts 与webWork的区别 +Struts 2是Struts的下一代产品。是在 struts 和WebWork的技术基础上进行了合并,全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构的差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与Servlet API完全脱离开,所以Struts 2可以理解为WebWork的更新产品。 + +Struts和Webwork同为服务于Web的一种MVC框架,从某种程度上看,Struts2是从WebWork2上升级得到的。甚至Apache的官方文档也讲:WebWork2到Struts2是平滑的过渡。我们甚至也可以说Struts2就是WebWork2.3而已。在很多方面Struts仅仅是改变了WebWork下的名称。Struts2对应的有自己的标签,并且功能强大。Webwork也有自己的标签。 +1) 在很多方面Struts2仅仅是改变了WebWork下的名称,如DispatcherUtil 改为了Dispatcher. +2) AroundInterceptor:Struts 2不再支持WebWork中的AroundInterceptor。如果应用程序中需要使用AroundInterceptor,则应该自己手动导入WebWork中的AroundInterceptor类。 +3) IoC容器支持:Struts 2不再支持内建的IoC容器,而改为全面支持Spring的IoC容器,以Spring的IoC容器作为默认的Object工厂。 +4) 富文本编辑器标签:Struts 2不再支持WebWork的富文本编辑器,如果应用中需要使用富文本编辑器,则应该使用**Dojo**的富文本编辑器。 + +http://developer.51cto.com/art/201106/271744.htm + +## struts2 与struts1的区别 +Action类,线程安全,测试,标签,验证 + + +**Action 类:** +• Struts1要求Action类继承一个**抽象**基类。Struts1的一个普遍问题是使用抽象类编程而不是接口,而struts2的Action是接口。 +• Struts 2 Action类可以实现一个Action**接口**,也可实现其他接口,**使可选和定制的服务成为可能**。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。 + +**线程模式**: +• Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。(**不是线程安全**) +• Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)(**线程安全**) + +**Servlet 依赖**: +• Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。 +• Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。 + +**可测性**: +• 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。 +• Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。 + +**表达式语言**: +• Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。 +• Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--"Object Graph Notation Language" (OGNL). + +**绑定值到页面(view)**: +• Struts 1使用标准JSP机制把对象绑定到页面中来访问。 +• Struts 2 使用 "ValueStack"技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。 + +## Struts工作流程: +![图片标题](http://www.evget.com/images/article/08072801.png) +Struts2: +(1)客户端提交一个HttpServletRequest请求(.action或JSP页面) +(2)请求被提交到一系列(主要是三层)的过滤器(Filter),如(ActionContextCleanUp、其他过滤器(SiteMesh等)、 FilterDispatcher)。注意这里是有顺序的,先ActionContextCleanUp,再其他过滤器(SiteMesh等)、最后到FilterDispatcher。 +(3)FilterDispatcher是Struts2控制器的核心,它通常是过滤器链中的最后一个过滤器,FilterDispatcher进行初始化并启用核心doFilter().FilterDispatcher询问ActionMapper是否需要调用某个Action来处理这个(request)请求,如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy。 +(6)ActionProxy通过**ConfigurationManager**(它会访问struts.xml)询问框架的配置文件,找到需要调用的Action类. +(7)ActionProxy创建一个ActionInvocation实例,而ActionInvocation通过代理模式调用Action,(在调用之前会根据配置文件加载相关的所有Interceptor拦截器) +(8)Action执行完毕后,返回一个result字符串,此时再按相反的顺序通过Interceptor拦截器. +(9)最后ActionInvocation负责根据struts.xml中配置的result元素,找到与返回值对应的result,决定进行下一步输出. + +## 为什么要使用 Struts2 & Struts2 的优点: +①. 基于 MVC 架构,框架结构清晰。 +②. 使用 OGNL: OGNL 可以快捷的访问值栈中的数据、调用值栈中对象的方法 +③. 拦截器: Struts2 的拦截器是一个 Action 级别的 AOP, Struts2 中的许多特性都是通过拦截器来实现的, 例如异常处理,文件上传,验证等。拦截器是可配置与重用的 +④. 多种表现层技术. 如:JSP、FreeMarker、Velocity 等 + +## SpringMVC 与 Struts2区别 +1.核心控制器(前端控制器、预处理控制器):对于使用过mvc框架的人来说这个词应该不会陌生,核心控制器的主要用途是处理所有的请求,然后对那些特殊的请求 (控制器)统一的进行处理(字符编码、文件上传、参数接受、异常处理等等),spring mvc核心控制器是Servlet,而Struts2是Filter。 +2.控制器实例:Spring Mvc会比Struts快一些(理论上)。Spring Mvc是基于方法设计,而Sturts是基于对象,每次发一次请求都会实例一个action,每个action都会被注入属性,而Spring更像Servlet一样,只有一个实例,每次请求执行对应的方法即可(注意:由于是单例实例,所以应当避免全局变量的修改,这样会产生线程安全问题)。 +3. 管理方式:大部分的公司的核心架构中,就会使用到spring,而spring mvc又是spring中的一个模块,所以spring对于spring mvc的控制器管理更加简单方便,而且提供了全 注解方式进行管理,各种功能的注解都比较全面,使用简单,而struts2需要采用XML很多的配置参数来管理(虽然也可以采用注解,但是几乎没有公司那 样使用)。 +4.参数传递:Struts2中自身提供多种参数接受,其实都是通过(ValueStack)进行传递和赋值,而SpringMvc是通过方法的参数进行接收。 +5.intercepter 的实现机制:struts有以自己的interceptor机制,spring mvc用的是独立的AOP方式。这样导致struts的配置文件量还是比spring mvc大,虽然struts的配置能继承,所以我觉得论使用上来讲,spring mvc使用更加简洁,开发效率Spring MVC确实比struts2高。spring mvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上spring3 mvc就容易实现restful url。struts2是类级别的拦截,一个类对应一个request上下文;实现restful url要费劲,因为struts2 action的一个方法可以对应一个url;而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。spring3 mvc的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架方法之间不共享变量,而struts2搞的就比较乱,虽然方法之间 也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码,读程序时带来麻烦。 +7.spring mvc处理ajax请求,直接通过返回数据,方法中使用注解@ResponseBody,spring mvc自动帮我们对象转换为JSON数据。 + + +## Filter,Listener,Servlet区别 +1) Filter 实现javax.servlet.Filter接口,在web.xml中配置与标签指定使用哪个Filter实现类过滤哪些URL链接。只在web启动时进行初始化操作。filter流程是**线性**的, url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收等,而servlet 处理之后,不会继续向下传递。filter功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,而servlet的功能主要用来主导流程。 +特点:可以在响应之前修改Request和Response的头部,只能转发请求,不能直接发出响应。filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等 +2) Servlet 流程是短的,url传来之后,就对其进行处理,之后返回或转向到某一自己指定的页面。它主要用来在业务处理之前进行控制。 +3) Listener +servlet,filter都是针对url之类的,而listener是针对**对象操作**的,如session的创建,session.setAttribute的发生,在这样的事件发 +生时做一些事情。 + +## Struts2拦截器和过滤器的区别 +①、过滤器依赖于Servlet容器,而拦截器不依赖于Servlet容器。 +②、Struts2 拦截器只能对 Action 请求起作用,而过滤器则可以对几乎所有请求起作用。 +③、拦截器可以访问 Action 上下文(ActionContext)、值栈里的对象(ValueStack),而过滤器不能. +④、在 Action 的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。 + +## Struts的Action是不是线程安全的 + struts2的action是线程安全的,struts1的action不是线程安全的 。 +对于struts1 ,Action是单例模式,一个实例来处理所有的请求。当第一次**.do的请求过来时,在内存中的actionmapping中找到相对应的action,然后new出这个action放在缓存中,当第二次一样的请求过来时,还是找的这个action,所以对于struts1来说,action是单实例的,只有一个,如果在action中定义变量,就要非常小心了,因为并发问题,可能带来灾难性的后果,也不是不可以,我们可以加锁达到同步,只是在性能上就 要折衷了。 +声明局部变量,或者扩展RequestProcessor,让每次都创建一个Action,或者在spring中用scope=”prototype”来管理,不申明类变量就可以保证线程安全。因为只存在一个Action类实例,所有线程会共享类变量。 +struts2 在struts1的基础上做了改进 ,对于struts2 ,每次请求过来都会new一个新的action, 所以说struts2的action没有线程安全问题,是线程安全的,但同时也带来一个问题,每次都new一个action ,这样action的实例太多 , 在性能方面还是存在一定的缺陷的。 + +## struts2.0的mvc模式与struts1.0的区别? +与struts1最大的不同是:struts2的控制器。struts2的控制器不再像struts1的控制器,需要**继承**一个Action父类,甚至可以无需实现任何接口,struts2的Action就是一个普通的POJO。实际上,Struts2 的Action就是一个包含execute方法的普通Java类该类里包含的多个属性用于封装用户的请求参数。 + +## 你对MVC的理解,MVC有什么优缺点?结合Struts,说明在一个Web应用如何去使用? +MVC设计模式(应用观察者模式的框架模式) +M: Model(Business process layer),模型,是应用程序中用于处理应用程序数据逻辑的部分,通常模型对象负责在数据库中存取数据。,并独立于表现层 (Independent of presentation)。 +V: View(Presentation layer),视图,通过客户端数据类型显示数据,并回显模型层的执行结果。 +C: Controller(Control layer),控制器,也就是**视图层和模型层桥梁**,控制数据的流向,接受视图层发出的事件,并重绘视图 +优点: +1)视图控制模型分离,提高代码重用性。 +2)提高开发效率。 +3)便于后期维护,降低维护成本。 +4)方便多开发人员间的分工。 +5)结构清晰 +缺点: +1)清晰的构架以代码的复杂性为代价, 对小项目优可能反而降低开发效率。 +2)运行效率相对较低 + +MVC框架的一种实现模型 +模型二(Servlet-centric): JSP+Servlet+JavaBean,以控制为核心,JSP只负责显示和收集数据,Sevlet,连接视图和模型,将视图层数据,发送给模型层,JavaBean,分为业务类和数据实体,业务类处理业务数据,数据实体,承载数据,基本上大多数的项目都是使用这种MVC的实现模式。 + +Struts MVC框架(Web application frameworks) :Struts是使用MVC的实现模式二来实现的,也就是以控制器为核心。 + +Struts提供了一些组件使用MVC开发应用程序: +Model:Struts 没有提供model 类。这个商业逻辑必须由Web 应用程序的开发者以JavaBean或EJB的形式提供 +View:Struts提供了action form创建form bean, 用于在controller和view间传输数据。此外,Struts提供了自定义JSP标签库,辅助开发者用JSP创建交互式的以表单为基础的应用程序,应用程序资源文件保留了一些文本常量和错误消息,可转变为其它语言, 可用于JSP中。 +Controller:Struts提供了一个核心的控制器FilterDispatcher,通过这个核心的控制器来调用其他用户注册了的自定义的控制器Action,自定义Action需要符合Struts的自定义Action规范,还需要在struts-config.xml的特定配置文件中进行配置,接收JSP输入字段形成Action form,然后调用一个Action控制器。Action控制器中提供了model的逻辑接口。 + +## 说出 struts2 中至少 5 个的默认拦截器 +exception;fileUpload;i18n;modelDriven;params;prepare;token;tokenSession;validation 等 +拦截器的生命周期与工作过程 ? + +## 每个拦截器都是需要实现 Interceptor 接口 +init():在拦截器被创建后立即被调用, 它在拦截器的生命周期内只被调用一次. 可以在该方法中对相关资源进行必要的初始化; +intercept(ActionInvocation invocation):每拦截一个动作请求,该方法就会被调用一次; +destroy:该方法将在拦截器被销毁之前被调用, 它在拦截器的生命周期内也只被调用一次; + +## 创建Action类有几种方法? +实现Action 接口 +使用Struts2 @Action 元注解 +继承ActionSupport类,必须实现 execute() 方法,返回一个可配置的字符串 + +##Struts2的拦截器执行什么模式? +**责任链模式** +过滤器decorator模式和职责链模式 + + +个人公众号(欢迎关注): +![](/assets/weix_gongzhonghao.jpg) + + diff --git a/hr/README.md b/hr/README.md new file mode 100644 index 0000000..e69de29 diff --git a/hr/ask.md b/hr/ask.md new file mode 100644 index 0000000..d6a862c --- /dev/null +++ b/hr/ask.md @@ -0,0 +1,22 @@ +工作中遇到困难怎么解决, +项目中遇到什么问题 +有没有遇到设计层面的问题 +项目中怎么保存用户登录信息的 +做的最深的项目?解决了什么问题? + +你的下属积极性不高你怎么办, +你对自己的评价是怎么样的, +你凭什么证明你有你所说的这些能力呢? +你简历上的自我评价和你自己介绍的有些出入啊,简历是认真写的吗? + 优缺点?兴趣? + 为什么想来网易? +有投其他公司吗? +网易最吸引你的是什么? +想来杭州吗? +五个词来描述自己 +怎么理解踏实,你在项目中哪儿体现到了 +最近最有成就感的事 +最近在看的技术 +是否写过其他个人兴趣的小项目 +写技术博客吗,github呢 +个人职位目标 diff --git a/java/README.md b/java/README.md new file mode 100644 index 0000000..e69de29 diff --git a/java/base.md b/java/base.md new file mode 100644 index 0000000..e9481db --- /dev/null +++ b/java/base.md @@ -0,0 +1,258 @@ +## 1.Java 语言的优点 +面向对象,平台无关,内存管理,安全性,多线程,Java 是解释型的 + +### 2.Java 和 C++的区别 +1. **多重继承**(java接口多重,类不支持,C++支持) +2. **自动内存管理** +3. **预处理功能** +4. **goto语句**(java不支持) +5. **引用与指针**。在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行**加减运算**。 + +## 3.值传递和引用传递 +变量被值传递,意味着传递了**变量的一个副本**。因此,就算是改变了变量副本,也不会影响源对象的值。 +对象被引用传递,意味着传递的并不是实际的对象,而是**对象的引用**。因此,外部对引用对象所做的改变会反映到所有的对象上。 +java本质上还是值传递,如方法调用的时候传入一个对象引用进去,在方法栈中会构建一个副本和该引用变量值相同指向同一个地址。如果改变引用的值不会对改变传入的引用的值。 + +## 4.静态变量和实例变量的区别 +**在语法定义上的区别**:静态变量前要加 static 关键字,而实例变量前则不加。 +**在程序运行时的区别**:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。 + +## 5.JDK 包. +JDK 常用的 package +**java.lang**:这个是系统的基础类,比如 String 等都是这里面的,这个 package 是唯一一个可以不用 import 就可以使用的 Package +**java.io**: 这里面是所有输入输出有关的类,比如文件操作等 +**java.net**: 这里面是与网络有关的类,比如 URL,URLConnection 等。 +**java.util**: 这个是系统辅助类,特别是集合类 Collection,List,Map 等。 +**java.sql**: 这个是数据库操作的类,Connection, Statememt,ResultSet 等 + +## 6.JDK, JRE 和 JVM 的区别 +JDK,JRE和JVM 是 Java 编程语言的核心概念。尽管它们看起来差不多,作为程序员我们也不怎么关心这些概念,但是它们是不同的针对特定目的的产品。这是一道常见的 Java 面试题,而本文则会一一解释这些概念并给出它们之间的区别。 +**1)Java 开发工具包 (JDK)** +Java 开发工具包是 Java 环境的**核心组件,并提供编译、调试和运行一个 Java 程序所需的所有工具,可执行文件和二进制文件**。包括java基础jar包、虚拟机、javac等可执行文件等。JDK 是一个平台特定的软件,有针对 Windows,Mac 和 Unix 系统的不同的安装包。可以说 JDK 是 JRE 的超集,它包含了 JRE 的 Java 编译器,调试器和核心类。 +**2)Java 虚拟机(JVM)** +JVM 是 Java 编程语言的核心。当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码。JVM 也是平台特定的,并提供核心的 Java 方法,例如内存管理、垃圾回收和安全机制等。JVM 是可定制化的,我们可以通过 Java 选项(java options)定制它,比如配置 JVM 内存的上下界。JVM 之所以被称为虚拟的是因为它提供了一个不依赖于底层操作系统和机器硬件的接口。这种独立于硬件和操作系统的特性正是 Java 程序可以一次编写多处执行的原因。 +**3)Java 运行时环境(JRE)** +JRE 是 JVM 的实施实现,它提供了运行 Java 程序的平台。**JRE 包含了 JVM、Java 二进制文件和其它成功执行程序的类文件**。JRE 不包含任何像 Java 编译器、调试器之类的开发工具。如果你只是想要执行 Java 程序,你只需安装 JRE 即可,没有安装 JDK 的必要。 + +**JDK, JRE 和 JVM 的区别** +JDK 是用于开发的而 JRE 是用于运行 Java 程序的。 +JDK 和 JRE 都包含了 JVM,从而使得我们可以运行 Java 程序。 +JVM 是 Java 编程语言的核心并且具有平台独立性。 +**即时编译器(JIT)** +有时我们会听到 JIT 这个概念,并说它是 JVM 的一部分,这让我们很困惑。JIT 是 JVM 的一部分,它可以在同一时间编译类似的字节码来优化将字节码转换为机器特定语言的过程相似的字节码,从而将优化字节码转换为机器特定语言的过程,这样减少转换过程所需要花费的时间。 + +## 7.是否可以在 static 环境中访问非 static 变量 +static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。 + +## Java 中的 final关键字用法 +(1)修饰类:表示该类不能被继承; +(2)修饰方法:表示方法不能被覆盖; +(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。 + +## assert +assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,assertion **用于保证程序最基本、关键的正确性**。assertion 检查通常在开发和测试时开启。为了提高性能,在软件发布后, assertion 检查通常是关闭的。在实现中,断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true;如果表达式计算为 false,那么系统会报告一个 AssertionError。 + +断言用于调试目的: + + assert(a > 0); // throws an AssertionError if a <= 0 + +断言可以有两种形式: + + assert Expression1; + assert Expression1 : Expression2 ; + +Expression1 应该总是产生一个布尔值。 +Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的**字符串消息** +断言在默认情况下是禁用的,要在编译时启用断言,需使用 source 1.4 标记: + + javac -source 1.4 Test.java + +要在运行时启用断言,可使用 -enableassertions 或者 -ea 标记。 +要在运行时选择禁用断言,可使用 -da 或者 -disableassertions 标记。 + +## final, finally, finalize 的区别? +**final**:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和 abstract 是反义词。将变量声明为 final,可以保证它们在使用中不被改变,被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为 final 的方法也同样只能使用,不能在子类中被重写。 +**finally**:通常放在 try…catch 的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中。 +**finalize**:Object 类中定义的方法,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。 + +## & 和 && +& 和 && 都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为 true 时,整个运算结果才为 true,否则,只要有一方为 false,则结果为 false。 +**&& 还具有短路的功能**,即如果第一个表达式为 false,则不再计算第二个表达式.& 还可以用作位运算符,当 & 操作符两边的表达式不是 boolean 类型时,& 表示按位与操作. + +## 用最有效率的方法算出 2 乘以 8 等於几 +2 << 3,因为将一个数左移 n 位,就相当于乘以了 2 的 n 次方,那么,一个数乘以 8 只要将其左移 3 位即可,而位运算 cpu 直接支持的,效率最高,所以,2 乘以 8 等於几的最效率的方法是 2 << 3 。 + +## char 型变量 +char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个 char 类型占 2 个字节(16bit),所以放一个中文是没问题的。 +补充:使用 Unicode 意味着字符在 JVM 内部和外部有不同的表现形式,在 JVM 内部都是 Unicode,当这个字符被从 JVM 内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以 Java 中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如 InputStreamReader 和 OutputStreamReader. + +## String 和StringBuilder、StringBuffer 的区别 +答:Java 平台提供了两种类型的字符串:String 和StringBuffer / StringBuilder,它们可以储存和操作字符串。其中 String 是**只读**字符串,也就意味着 String 引用的字符串内容是不能被改变的。而 StringBuffer 和 StringBuilder 类表示的字符串对象可以直接进行修改。StringBuilder 是 JDK 1.5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 略高。 +有一个面试题问:有没有哪种情况用 + 做字符串连接比调用 StringBuffer / StringBuilder 对象的 append 方法性能更好?如果连接后得到的字符串在静态存储区中是早已存在的,那么用+做字符串连接是优于 StringBuffer / StringBuilder 的 append 方法的。 + +## String不可变性 +至于为什么要把 String 类设计成不可变类,是它的用途决定的。其实不只 String,很多 Java 标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以 Java 标准类库还提供了一个可变版本,即 StringBuffer。 + Javac 编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期去进行加法运算处理,而是在**编译时去掉其中的加号**,直接将其编译成一个这些常量相连的结果。所以 + String s=“a”+”b”+”c”+”d”;只生成一个对象. + +## 不可变对象 +如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。 +**如何创建不可变类** +1. 将类声明为final,所以它不能被继承 +2. 将所有的成员声明为私有的,这样就不允许直接访问这些成员 +3. 对变量不要提供setter方法 +4. 将所有可变的成员声明为final,这样只能对它们赋值一次 +5. 通过构造器初始化所有成员,进行深拷贝(deep copy):如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化(in)或者get方法(out)时通过深度clone方法,来确保类的不可变。 +6. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝 + +http://www.cnblogs.com/yg_zhang/p/4355354.html +http://www.importnew.com/7535.html +## 为什么String要设计成不可变的** +在Java中将String设计成不可变的是综合考虑到各种因素的结果,如内存,同步,数据结构以及安全等方面的考虑. +1. 字符串常量池的需要. + 字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(译者注:String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。 +2. 线程安全考虑。 +同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。 +3. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。 +4. 支持hash映射和缓存。 +因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。 + +http://blog.csdn.net/renfufei/article/details/16808775 +http://www.codeceo.com/article/why-java-string-immutable.html +http://www.importnew.com/7440.html +http://www.importnew.com/16817.html + +## 什么是 Java 序列化,如何实现 Java 序列化 +序列化就是一种用来处理对象流的机制,**所谓对象流也就是将对象的内容进行流化**。**可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间**。序列化是为了解决在对对象流进行读写操作时所引发的问题。 + +序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法, implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。 + + + + +## 错误和异常的区别(Error vs Exception) +1) java.lang.Error: Throwable 的子类,**用于标记严重错误,表示系统级的错误和程序不必处理的异常**。合理的应用程序不应该去 try/catch 这种错误。是恢复不是不可能但很困难的情况下的一种严重问题;**比如内存溢出**,不可能指望程序能处理这样的情况; +java.lang.Exception: Throwable 的子类,**表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题**;也就是说,它表示如果程序运行正常,从不会发生的情况。并且鼓励用户程序去 catch 它。 + +2) Error 和 RuntimeException 及其子类都是**未检查的异常(unchecked exceptions)**,而所有其他的 Exception 类都是检查了的异常(checked exceptions) + **checked exceptions**: **上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发.通常是从一个可以恢复的程序中抛出来的,并且最好能够从这种异常中使用程序恢复**。比如 **FileNotFoundException**, ParseException 等。检查了的异常发生在编译阶段,必须要使用 try…catch(或者 throws )否则编译不通过。 + **unchecked exceptions**:通常是如果一切正常的话本不该发生的异常,但是的确发生了。 **发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的**。比如 ArrayIndexOutOfBoundException, ClassCastException 等。从语言本身的角度讲,程序不该去 catch 这类异常,虽然能够从诸如 RuntimeException 这样的异常中 catch 并恢复,但是并不鼓励终端程序员这么做,因为完全没要必要。因为这类错误本身就是 bug,应该被修复,出现此类错误时程序就应该立即停止执行。 因此,面对 Errors 和 unchecked exceptions 应该让程序自动终止执行,程序员不该做诸如 try/catch 这样的事情,而是应该查明原因,修改代码逻辑。 + RuntimeException:RuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等。处理 RuntimeException 的原则是:如果出现 RuntimeException,那么一定是程序员的错误。例如,可以通过检查数组下标和数组边界来避免数组越界访问异常。其他(IOException等等)checked 异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误。 + +## try{} 里的 return 语句 +Java 允许在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,这会对程序造成很大的困扰 + +## 运行时异常与受检异常有何异同? +答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的**上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发**。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,神作《Effective Java》中对异常的使用给出了以下指导原则: + **不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)** + **对可以恢复的情况使用受检异常,对编程错误使用运行时异常** + **避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)** + **优先使用标准的异常** + **每个方法抛出的异常都要有文档** + **保持异常的原子性** + **不要在 catch 中忽略掉捕获到的异常** + +## throws、throw、try、catch、finally +一般情况下是用 try 来执行一段程序,如果出现异常,系统会抛出(throw)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理;try 用来指定一块预防所有“异常”的程序;catch 子句紧跟在 try 块后面,用来指定你想要捕捉的“异常”的类型;throw 语句用来明确地抛出一个“异常”;throws用来标明一个成员函数可能抛出的各种“异常”;finally 为确保一段代码不管发生什么“异常”都被执行一段代码; + +## 常见的runtime exception +1、NullPointerException 空指针引用异常 +2、ClassCastException - 类型强制转换异常。 +3、IllegalArgumentException - 传递非法参数异常。 +4、IndexOutOfBoundsException - 下标越界异常 +5、UnsupportedOperationException - 不支持的操作异常 +6、ArithmeticException - 算术运算异常 +7、ArrayStoreException - 向数组中存放与声明类型不兼容对象异常 +8、NegativeArraySizeException - 创建一个大小为负数的数组错误异常 +9、NumberFormatException - 数字格式异常 +10、SecurityException - 安全异常 + +## throw和throws有什么区别 +throw关键字用来在程序中明确的抛出异常,相反,throws语句用来表明方法不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。 + +## Switch能否用string做参数? +Java 1.7之前不可以,java 1.7后String可以作为参数。 +整型(byte,short int,int,long int),枚举类型,boolean,字符型(char),字符串都可以,唯独浮点型不可以 + +## equals与==的区别 +1、 == 是一个运算符。 +2、Equals则是string对象的方法,可以.(点)出来。 +我们比较无非就是这两种 1、基本数据类型比较 2、引用对象比较 +1、基本数据类型比较 + ==比较两个值是否相等。相等为true 否则为false; + equals不能直接用于基本类型的比较。需要将基本类型转换为包装器进行比较。 +2、引用对象比较 +==和equals都是比较栈内存中的地址是否相等 。相等为true 否则为false;   +需注意几点: +1、string是一个特殊的引用类型。对于两个字符串的比较,不管是 == 和 equals 这两者比较的都是字符串是否相同; +2、当你创建两个string对象时,内存中的地址是不相同的,你可以赋相同的值。 +  所以字符串的内容相同。引用地址不一定相同,(相同内容的对象地址不一定相同),但反过来却是肯定的; +3、基本数据类型比较(string 除外) == 和 Equals 两者都是比较值; + +## Object有哪些公用方法 +protected Object clone()创建并返回此对象的一个副本。 +public boolean equals(Object obj)指示其他某个对象是否与此对象“相等”。 +protected void finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 +public final native Class< ? > getClass() 返回此 Object 的运行时类。 +public int hashCode()返回该对象的哈希码值。 +public String toString()返回该对象的字符串表示。 +public void notify()唤醒在此对象监视器上等待的单个线程。 +public void notifyAll()唤醒在此对象监视器上等待的所有线程。 +public void wait()在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 +public void wait(long timeout)在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 +public void wait(long timeout, int nanos)在其他线程调用此对象的 notify() 方法或 + +注意: +1.如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportException异常。java.lang.Cloneable 是一个标示性接口,不包含任何方法(详见《effective java》 p46)。 +2. 覆盖equals()时总要覆盖hashCode()(详见《effective java》p39) +在每个覆盖equals方法的类中也必须覆盖hashCode方法,如果不这样做就会导致Object.hashCode的通用约定---**相等的对象必须具有相等的散列码的约定**,从而导致该类无法集合所有基于散列集合一起工作,这样的集合有HashMap,HashSet和HashTable。HashMap等使用Key对象的hashCode()和equals()方法去决定key-value对的索引。如果将equals相等的对象认为是同一个对象的话,那么put方法将对象放在一个散列桶,而get方法可能从另一个散列桶获取该对象,因为这两个方法传入的对象虽然equals相同,但hashCode可能不同,而hashMap根据hashCode去定位散列桶位置导致出现在不同的散列桶中。 + Hashcode的作用。 +1. hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的; +2. 比较对象是否相同。 +一下是关于hashCode的约定: +1、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同; +2、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点; +3、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。 + +## String、StringBuffer与StringBuilder的区别 +1. String是字符串常量,是不可变类。如果要操作少量的数据用 +2. StringBuffer是字符串变量,是线程安全的。多线程操作字符串缓冲区 下操作大量数据 +3. StringBuilder是字符串变量,非线程安全。单线程操作字符串缓冲区 下操作大量数据 +速度:StringBuilder > StringBuffer > String +String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的: +String S1 = “This is only a” + “ simple” + “ test”; +StringBuffer Sb = new StringBuilder(“This is only a”).append(“simple”).append(“ test”); +参见:http://www.findspace.name/easycoding/1090 + +## try catch finally,try里有return,finally还执行么? +1)不管有木有出现异常,finally块中代码都会执行 +2)当try和catch中有return时,finally仍然会执行 +3)finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,**不管finally中的代码怎么样,返回的值都不会改变**,任然是之前保存的值),所以函数返回值是在finally执行前确定的 +4)finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值 + +## UnsupportedOperationException是什么 +UnsupportedOperationException是用于表明操作不支持的异常。在JDK类中已被大量运用,在集合框架java.util.Collections.UnmodifiableCollection将会在所有add和remove操作中抛出这个异常。 + + +## Excption与Error包结构 + + +## java值传递问题 +1.对象就是传引用 +2.原始类型就是传值 +3.String,Integer, Double等immutable类型因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待。可以认为是传值。 +Integer 和 String 一样。保存value的类变量是Final属性,无法被修改,只能被重新赋值/生成新的对象。 当Integer 做为方法参数传递进方法内时,对其的赋值都会导致 原Integer 的引用被 指向了方法内的栈地址,失去了对原类变量地址的指向。对赋值后的Integer对象做得任何操作,都不会影响原来对象。 + + +## Integer +http://www.cnblogs.com/dolphin0520/p/3780005.html + +## 修饰符顺序 +public protected private abstract static final transient volatile synchronized native strictfp + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + + diff --git a/java/collection.md b/java/collection.md new file mode 100644 index 0000000..b687d5e --- /dev/null +++ b/java/collection.md @@ -0,0 +1,343 @@ +## 集合框架 +Collection:List列表,Set集 +Map:Hashtable,HashMap,TreeMap + +Collection 是单列集合 +List 元素是有序的(元素存取是有序)、可重复 +有序的 collection,可以对列表中每个元素的插入位置进行精确地控制。可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。可存放重复元素,元素存取是有序的。 +List接口中常用类: +Vector:线程安全,但速度慢,已被ArrayList替代。底层数据结构是**数组结构** +ArrayList:线程不安全,查询速度快。底层数据结构是数组结构 +LinkedList:线程不安全。增删速度快。底层数据结构是链表结构 + +Set(集) 元素不可重复。 +取出元素的方法只有迭代器。**不可以存放重复元素,元素存取是无序的**。 +Set接口中常用的类 +HashSet:线程不安全,内部是基于散列函数实现,存取速度快。它是如何保证元素唯一性的呢?依赖的是元素的hashCode方法和euqals方法。. + LinkedHashSet:具有HashSet的查询速度,且内部采用双向链表**维护元素的顺序**(默认保持插入顺序排序, 如果初始化时将accessOrder设置为true将使用最近最少顺序,即最近使用的元素插在链表尾部)。元素必须实现hashCode()方法。内部是由链表实现。 +TreeSet:线程不安全,可以对Set集合中的元素进行排序,底层采用了红黑树。它的排序是如何进行的呢?通过compareTo或者compare方法中的来保证元素的唯一性。元素是以二叉树的形式存放的。意味着TreeSet中的元素要实现Comparable接口。或者有一个自定义的比较器。 + +Map 是一个双列集合 +|--Hashtable:线程安全,速度快。底层是**哈希表数据**结构。是同步的。不允许null作为键,null作为值。 + |--Properties:用于配置文件的定义和操作,使用频率非常高,同时键和值都是字符串。是集合中可以和IO技术相结合的对象。(到了IO在学习它的特有和io相关的功能。) +|--HashMap:线程不安全,速度慢。底层也是哈希表数据结构。是不同步的。 +允许null作为键,null作为值。替代了Hashtable. + |--LinkedHashMap: 可以保证HashMap集合有序。存入的顺序和取出的顺序一致。 +|--TreeMap:可以用来对Map集合中的键进行排序.底层是采用红黑树. +Collection 和 Collections的区别 +Collection是集合类的上级接口,子接口主要有Set 和List、Map。 +Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。 + +## 接口:Collection +Collection是最基本的集合接口,**一个Collection代表一组Object,即Collection的元素(Elements)**。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。 +所有实现Collection接口的类都必须提供**两个标准的构造函数**:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。 +主要的一个接口方法:boolean add(Ojbect c)虽然返回的是boolean,但不是表示添加成功与否,这个返回值表示的意义是add()执行后,集合的内容是否改变了(就是元素的数量、位置等有无变化)。类似的addAll,remove,removeAll,remainAll也是一样的。 +用Iterator模式实现遍历集合,Collection**有一个重要的方法:iterator(),返回一个Iterator(迭代器),用于遍历集合的所有元素**。Iterator模式可以把访问逻辑从不同的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。典型的用法如下: + +``` + Iterator it = collection.iterator(); // 获得一个迭代器 + while(it.hasNext()) { + Object obj = it.next(); // 得到下一个元素 + } +``` + +不需要维护遍历集合的“指针”,所有的内部状态都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成。每一种集合类返回的Iterator具体类型可能不同,但它们都实现了Iterator接口,因此,我们不需要关心到底是哪种Iterator,它只需要获得这个Iterator接口即可,这就是接口的好处,面向对象的威力。 +要确保遍历过程顺利完成,必须保证遍历过程中不更改集合的内容(Iterator的remove()方法除外),所以,确保遍历可靠的原则是:只在一个线程中使用这个集合,或者在多线程中对遍历代码进行同步。由Collection接口派生的两个接口是List和Set。 + +## Collection包结构,与Collections的区别 +Collection是集类,包含List有序列表,Set无序集合以及Map双列集合 Collection是集合类的上级接口,子接口主要有Set 和List、Map。 +Collections是针对集合类的一个**帮助类**,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作 + +## List、Map、Set 三个接口存储元素时各有什么特点 +1)**List**是有序的Collection,使用此接口**能够精确的控制每个元素插入的位置**。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。和下面要提到的Set不同,List**允许有相同**的元素。 +除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。 +2)**Set** 是一种不包含重复的元素的 Collection,即任意的两个元素 e1 和 e2 都有e1.equals(e2)=false,**Set 最多有一个 null 元素**。 +3)**Map** 接口 :请注意,Map 没有继承 Collection 接口,Map 提供 key 到 value 的映射.使用底层散列函数 + +## Map、Set、List、Queue、Stack的特点与用法 +Set集合类似于一个罐子,"丢进"Set集合里的多个对象之间没有明显的顺序。 +List集合代表元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。 +Stack是Vector提供的一个子类,用于模拟"栈"这种数据结构(LIFO后进先出) +Queue用于模拟"队列"这种数据结构(先进先出 FIFO)。 +Map用于保存具有**"映射关系"**的数据,因此Map集合里保存着两组值 + +## Array和ArrayList有何区别?什么时候更适合用Array +Array可以容纳**基本类型和对象**,而**ArrayList只能容纳对象**。 +Array是**指定大小的**,而ArrayList大小是固定的。 +**Array没有提供ArrayList那么多功能**,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。 +(1)如果列表的大小已经指定,大部分情况下是存储和遍历它们。 +(2)对于遍历**基本数据类型**,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。 +(3)如果你要使用**多维数组**,使用[][]比List更容易。 + +## ArrayList 和 LinkedList +ArrayList 和 LinkedList 都实现了 List 接口,他们有以下的不同点: +ArrayList 是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行**随机访问**。与此对应,LinkedList 是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。 +相对于 ArrayList,LinkedList的**插入,添加,删除操作速度更快**,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。 +LinkedList 比 ArrayList **更占内存**,因为 LinkedList 为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。 + +## ArrayList和Vector异同点 + ArrayList和Vector在很多时候都很类似。 +(1)两者都是基于索引的,内部由一个**数组支持**。 +(2)两者维护插入的顺序,我们可以**根据插入顺序来获取元素**。 +(3)ArrayList和Vector的迭代器实现都是**fail-fast**的。 +(4)ArrayList和Vector两者**允许null值**,也可以使用索引值对元素进行随机访问。 + +以下是ArrayList和Vector的不同点。 +(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。 +(2)ArrayList比Vector快,它因为有同步,不会过载。 +(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。 + +## 遍历一个List有哪些不同的方式 + + List strList = new ArrayList<>(); + //使用for-each循环 + for(String obj : strList){ + System.out.println(obj); + } + //using iterator + Iterator it = strList.iterator(); + while(it.hasNext()){ + String obj = it.next(); + System.out.println(obj); + } + +使用**迭代器更加线程安全,因为它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException**。 + +## LinkedList 和PriorityQueue 的区别 +**Queue中只有两个实现类LinkedList 和PriorityQueue**。他们都是属于队列,即拥有先进先出(FIFO)的特点。而这两个类的区别在于他们的排序行为并非性能。 +LinkedList 支持双向列表操作, +PriorityQueue 按优先权控制队列元素的出队次序。(列表中的排序顺序是通过实现Comparable来控制的) + +## HashMap的工作原理 +HashMap在Map.Entry静态内部类实现中存储key-value对。HashMap使用哈希算法,在put和get方法中,它使用hashCode()和equals()方法。当我们通过传递key-value对调用put方法的时候,HashMap使用Key hashCode()和哈希算法来找出存储key-value对的索引。Entry存储在LinkedList中,所以如果存在entry,它使用equals()方法来检查传递的key是否已经存在,如果存在,它会覆盖value,如果不存在,它会创建一个新的entry然后保存。当我们通过传递key调用get方法时,它再次使用hashCode()来找到数组中的索引,然后使用equals()方法找出正确的Entry,然后返回它的值。 + +其它关于HashMap比较重要的问题**是容量、加载因子和阀值调整**。HashMap默认的初始容量是16,**负荷系数是0.75**。阀值是为负荷系数乘以容量,无论何时我们尝试添加一个entry,如果map的大小比阀值大的时候,HashMap会对map的内容进行重新哈希,且使用更大的容量。**容量总是2的幂**,所以如果你知道你需要存储大量的key-value对,比如缓存从数据库里面拉取的数据,使用正确的容量和负荷系数对HashMap进行初始化是个不错的做法。 + +## HashMap实现原理 +**数据结构**: HashMap基于哈希算法,采用链地址法来避免hash冲突,所以其内部采用链表数组数据结构。我们通过put()和get()方法储存和获取对象。HashMap里面实现一个静态内部类Entry, Entry其重要的属性有key , value, next.用该Entry定义了一个链表的数组用来存放数据,这种方法叫做连地址法,是为了避免hash冲突。 +HashMap的存取实现: +1) put +首先会根据对象的hashCode计算得到**散列桶坐标**(数组下表),因为数组中存放的是**Entry链表结构**,所以还需要对该链表进行遍历,通过调用key.equals方法判断是否存在该key,如果存在则修改内容。如果不存在则**采用头插入**的方式在链表头部插入一个新的节点存储这个新的映射关系。 +**null key总是存放在Entry[]数组的第一个元素,而且仅保留一个null key**。 +2) get +首先也会根据hashCode的值定位到散列桶,并遍历存放在改桶中的链表。如果存在的该key则返回对应的value,**如果不存在则返回null**。(比较时调用key.equals) 具体比较: +(e.hash == hash && ((k = e.key) == key || key.equals(k)))) +再散列rehash过程: +Entry[]的长度一定后,随着map里面数据的越来越长,超过了加载因子,必须调整table的大小。每次将数组拓展到原来数据长度的两倍。这时,需要创建一张新表,将原表的映射到新表中。 +注:**在存储当前值后再判断是否进行扩展容器**,这样的扩容方式有可能导致拓展后容器没有被使用造成浪费。 +解决hash冲突的办法 +1.开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列) +2.再哈希法 +3.链地址法 +4.建立一个公共溢出区 +Java中hashmap的解决办法就是采用的链地址法。 +HashMap实现原理:http://blog.csdn.net/vking_wang/article/details/14166593 + +## 我们能否使用任何类作为Map的key + 我们可以使用任何类作为Map的key,然而在使用它们之前,需要考虑以下几点: +(1)**如果类重写了equals()方法,它也应该重写hashCode()方法**。 +(2)**类的所有实例需要遵循与equals()和hashCode()相关的规则**。请参考之前提到的这些规则。 +(3)如果一个类没有使用equals(),你不应该在hashCode()中使用它。 +(4)**用户自定义key类的最佳实践是使之为不可变的**,这样,hashCode()值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashCode()和equals()在未来不会改变,这样就会解决与可变相关的问题了。 + 比如,我有一个类MyKey,在HashMap中使用它 + + //传递给MyKey的name参数被用于equals()和hashCode()中 + MyKey key = new MyKey('Pankaj'); //assume hashCode=1234 + myHashMap.put(key, 'Value'); + // 以下的代码会改变key的hashCode()和equals()值 + key.setName('Amit'); //assume new hashCode=7890 + //下面会返回null,因为HashMap会尝试查找存储同样索引的key,而key已被改变了,匹配失败,返回null + myHashMap.get(new MyKey('Pankaj')); + +那就是为何String和Integer被作为HashMap的key大量使用 + +## 如何决定选用HashMap还是TreeMap +对于在**Map中插入、删除和定位元素这类操作,HashMap是最好的选择**。然而,**假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择**。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。 + +## HashMap和HashTable的区别 +HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点: +1)HashMap允许键和值是null,而Hashtable不允许键或者值是null。 +2)Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。 +3)HashMap的**迭代器(Iterator)是fail-fast迭代器**,而Hashtable的**enumerator(列举)迭代器不是fail-fast的**。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。一般认为Hashtable是一个遗留的类。如果你寻求在迭代的时候修改Map,你应该使用CocurrentHashMap +http://www.importnew.com/7010.html + +## HashMap 和 Hashtable 有什么区别 +HashMap 和 Hashtable 都实现了 Map 接口,因此很多特性非常相似。但是,他们有以下不同点: +HashMap 允许键和值是 null,而 Hashtable 不允许键或者值是 null。 +Hashtable 是同步的,而 HashMap 不是。因此, HashMap 更适合于单线程环境,而 Hashtable 适合于多线程环境。 +HashMap 提供了可供应用迭代的键的集合,因此,HashMap 是快速失败的。另一方面,Hashtable 提供了对键的列举(Enumeration)。 + +## Enumeration 接口和 Iterator 接口的区别 +Enumeration 速度是 Iterator 的2倍,**同时占用更少的内存**。 +但是,Iterator远远比 Enumeration 安全,因为其他线程不能够修改正在被 iterator 遍历的集合里面的对象。 +同时,Iterator 允许调用者删除底层集合里面的元素,这对 Enumeration 来说是不可能的。 + +## 通过迭代器fail-fast属性 +每次我们尝试获取下一个元素的时候,Iterator fail-fast属性检查当前集合结构里的任何改动。如果发现任何改动,它抛出ConcurrentModificationException。Collection中所有Iterator的实现都是按fail-fast来设计。在遍历一个集合的时候,我们可以使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList + +## 快速失败(fail-fast)和安全失败(fail-safe)的区别 +Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。 + +## HashSet,TreeSet,LinkedHashSet 之间的区别 +首先这三个类都是实现了Set接口,并且LinkedHashSet继承HashSet ,TreeSet实现了SortedSet。所以它们都拥有Set的基本特性,如:集合中的元素不能重复(这个需要和容器转载的类中的**equals()方法**有关). 这三个容器所接受的类必须实现equals() 方法。 +**HashSet**: 为查询速度所设计的,存入HashSet的元素必须定义hashCode()方法。内部是基于散列函数实现。 +**TreeSet**: 实现了SortedSet(SortedSet的元素可以保证处于排序状态),所以内部的元素是保持一定次序的(次序与元素实现的compareTo()方法有关),且底层是树状结构。使用它可以从Set中提取有序的序列。要求存入的元素必须实现Comparable接口,或则需要传入一个比较器Comparator。否则报ClassCastException。内部采用了**红黑树**实现。 +**LinkedHashSet**: 具有HashSet的查询速度,且内部链表维护元素的顺序(按插入顺序排序)。元素必须实现hashCode()方法。内部是由链表实现。 + +## HashMap ,LinkedHashMap ,TreeMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap的区别 +**Map** 中任何键的比较都是通过equals进行比较的,所以如果键用于映射的话需要由恰当的hashCode() 方法。如果键值用于TreeMap,它必须实现Comparable。 +**HashMap** 基于散列表来的实现,即使用hashCode()进行快速查询元素的位置,显著提高性能。插入和查询“键值对”的开销是固定的。可以通过设置容量和负载因子,以调整容器的性能。 +**LinkedHashMap**, 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入的次序,只比HashMap慢一点。而在迭代访问时反而更快,**因为它使用链表维护内部次序**。此外可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用(LRU)算法。于是没有被访问过的元素就会出现在队列前面。对于需要定期清理元素以节省空间的程序员来说,此功能使得程序员很容易得以实现。被访问过的键值会放在链表尾部。 +**TreeMap**, 是基于**红黑树**的实现。实现了SortedMap,SortedMap 可以确保键处于排序状态。所以查看“键”和“键值对”时,所有得到的结果都是经过排序的,次序由Comparable或Comparator决定。SortedMap拥有其他额外的功能,如:Comparator comparator()返回当前Map使用的Comparator或者null. T firstKey() 返回Map的第一个键,T lastKey() 返回最后一个键。SortedMap headMap(toKey),生成一个键小于toKey的Map子集。SortedMap tailMap(fromKey) 也是生成一个子集。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。 +**WeakHashMap** 表示**弱键映射**,允许释放映射所指向的对象。这是为了解决某类特殊问题而设计的,如果映射之外没有引用指向某个“键”,则“键”可以被垃圾收集器回收。 +**ConcurrentHashMap** 一种线程安全的Map,它不涉及同步加锁。 +**IdentityHashMap** 使用==代替equals() 对“键”进行比较的散列映射。专为解决特殊问题而设计。 +**HashTable** (已经摒弃了) + +## Map接口提供了哪些不同的集合视图 +Map接口提供三个集合视图: +(1)**Set keyset()**:**返回map中包含的所有key的一个Set视图**。**集合是受map支持的,map的变化会在集合中反映出来,反之亦然**。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过**Iterator**的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。 +(2)**Collection values()**:**返回一个map中包含的所有value的一个Collection视图**。这个collection受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个collection时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为**未定义**。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。 +(3)Set< Map.Entry < K,V > > entrySet():**返回一个map钟包含的所有映射的一个集合视图**。这个集合受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作,以及对迭代器返回的entry进行setValue外),**迭代器的结果会变为未定义**。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。 + + +## hashCode()和equals()方法有何重要性 +HashMap使用Key对象的hashCode()和equals()方法去决定key-value对的索引。当我们试着从HashMap中获取值的时候,这些方法也会被用到。如果这些方法没有被正确地实现,在这种情况下,两个不同Key也许会产生相同的hashCode()和equals()输出,HashMap将会认为它们是相同的,然后覆盖它们,而非把它们存储到不同的地方。同样的,所有不允许存储重复数据的集合类都使用hashCode()和equals()去查找重复,所以正确实现它们非常重要。equals()和hashCode()的实现应该遵循以下规则: +(1)如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()总是为true的。 +(2)如果o1.hashCode() == o2.hashCode(),并不意味着o1.equals(o2)会为true。 + +## Iterator是什么 +Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。 + +## Iterator和ListIterator的区别是什么 +下面列出了他们的区别: +Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List 。 +Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。 +ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。 + +## 为何Iterator接口没有具体的实现 +Iterator接口定义了遍历集合的方法,但它的实现则是集合实现类的责任。每个能够返回用于遍历的Iterator的集合类都有它自己的Iterator实现内部类。 +这就允许集合类去选择迭代器是fail-fast还是fail-safe的。比如,ArrayList迭代器是fail-fast的,而CopyOnWriteArrayList迭代器是fail-safe的 + +## EnumSet是什么 +java.util.EnumSet是使用**枚举类型**的集合实现。当集合创建时,枚举集合中的所有元素必须来自单个指定的枚举类型,可以是显示的或隐示的。EnumSet是不同步的,不允许值为null的元素。它也提供了一些有用的方法,比如copyOf(Collection c)、of(E first,E…rest)和complementOf(EnumSet s)。 + +## 哪些集合类是线程安全的 +Vector、HashTable、Properties和Stack是同步类,所以它们是线程安全的,可以在多线程环境下使用。Java1.5并发API包括一些集合类,允许迭代时修改,因为它们都工作在集合的克隆上,所以它们在多线程环境中是安全的。 + +## BockingQueue是什么 +Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。 + +## 我们如何对一组对象进行排序 + 如果我们需要对一个对象数组进行排序,我们可以使用Arrays.sort()方法。如果我们需要排序一个对象列表,我们可以使用Collections.sort()方法。两个类都有用于自然排序(使用Comparable)或基于标准的排序(使用Comparator)的重载方法sort()。Collections内部使用数组排序方法,所有它们两者都有相同的性能,只是Collections需要花时间将列表转换为数组。 + +## Comparable和Comparator接口 +Comparable & Comparator 都是用来实现集合中元素的比较、排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法。 +1) Comparator位于包java.util下,而Comparable位于包java.lang下 +2) **Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序**.Comparable 是一个对象本身就已经支持自比较所需要实现的接口,自定义的类要在加入list容器中后能够排序,可以实现Comparable接口.在用Collections类的sort方法排序时,如果不指定Comparator,那么就以自然顺序排序.而 Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。**用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为**。 +3) Comparator定义了俩个方法,分别是int compare(T o1,T o2)和boolean equals(Object obj).Comparable接口只提供了int compareTo(T o)方法. +也就是说假如我定义了一个Person类,这个类实现了Comparable接口,那么当我实例化Person类的person1后,我想比较person1和一个现有的Person对象person2的大小时,我就可以这样来调用:person1.comparTo(person2),通过返回值就可以判断了;而此时如果你定义了一个PersonComparator(实现了Comparator接口)的话,那你就可以这样:PersonComparator comparator= new PersonComparator(); +comparator.compare(person1,person2);。 + +## Java集合框架是什么?说出一些集合框架的优点? +每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。集合框架的部分优点如下: +(1)使用核心集合类降低开发成本,而非实现我们自己的集合类。 +(2)随着使用经过严格测试的集合框架类,代码质量会得到提高。 +(3)通过使用JDK附带的集合类,可以降低代码维护成本。 +(4)复用性和可操作性。 + +## 集合框架中的泛型有什么优点 +Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。 + +## 为何Map接口不继承Collection接口 +尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。 +如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。 + +## 为什么集合类没有实现Cloneable和Serializable接口 +Collection接口指定一组对象,对象即为它的元素。**如何维护这些元素由Collection的具体实现决定**。例如,一些如List的Collection实现允许重复的元素,而其它的如Set就不允许。**很多Collection实现有一个公有的clone方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为Collection是一个抽象表现。重要的是实现**。 +**当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用**。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。 +**在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制**。特定的实现应该决定它是否可以被克隆和序列化。 + +## 与Java集合框架相关的有哪些最好的实践 +(1)根据需要选择正确的集合类型。比如,如果指定了大小,我们会选用Array而非ArrayList。如果我们想根据插入顺序遍历一个Map,我们需要使用TreeMap。如果我们不想重复,我们应该使用Set。如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。 +(2)如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。 +(3)要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。使用JDK提供的不可变类作为Map的key,可以避免自己实现hashCode()和equals()。 +性。 +(4)尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。 +(5)尽可能使用Collections工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提供代码重用性,它有着更好的稳定性和可维护 +(6)一些集合类允许指定**初始容量**,所以如果我们能够估计到存储元素的数量,我们可以使用它,就避免了重新哈希或大小调整。 +(7)基于接口编程,而非基于实现编程,它允许我们后来轻易地改变实现。 +(8)总是使用类型安全的泛型,避免在运行时出现ClassCastException。 + +## ArrayList 是否支持序列化 +支持, +实现了 serlializable接口,并实现了以下两个方法: +``` +    private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException{ + // Write out element count, and any hidden stuff + int expectedModCount = modCount; + s.defaultWriteObject(); + + // Write out size as capacity for behavioural compatibility with clone() + s.writeInt(size); + + // Write out all elements in the proper order. + for (int i=0; iArrayList instance from a stream (that is, + * deserialize it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + elementData = EMPTY_ELEMENTDATA; + + // Read in size, and any hidden stuff + s.defaultReadObject(); + + // Read in capacity + s.readInt(); // ignored + + if (size > 0) { + // be like clone(), allocate array based upon size not capacity + ensureCapacityInternal(size); + + Object[] a = elementData; + // Read in all elements in the proper order. + for (int i=0; i v = (ArrayList) super.clone(); + v.elementData = Arrays.copyOf(elementData, size); + v.modCount = 0; + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + } +``` +## 为什么集合类没有实现 Cloneable 和 Serializable 接口 +## Comparable 和Comparator 接口 + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + diff --git a/java/io.md b/java/io.md new file mode 100644 index 0000000..ca14530 --- /dev/null +++ b/java/io.md @@ -0,0 +1,37 @@ +# Java IO +## Java IO 分类 + - **Java BIO**: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 + - **Java NIO** : 同步非阻塞,服务器实现模式为一个请求一个线程,即当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。 + - **Java AIO(NIO.2)** : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。 + +## 名词解释 + - **同步**:指的是用户进程触发IO操作需要等待或者轮询的去查看IO操作执行完成才能执行其他操作.这种方式性能比较差,只有一些对数据安全性要求比较高的场景中才会使用. + - **异步**:异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知) + - **阻塞**:所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止 + - **非阻塞**:非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 + +## BIO、NIO、AIO适用场景分析 + - BIO方式适用于**连接数目比较小且固定的架构**,这种方式对服务器**资源要求比较高**,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 + - NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 + - AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。 + +### Java NIO和IO的主要区别 +1. 面向流与面向缓冲. +Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。 + +2. 阻塞与非阻塞IO +Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。 + +3. 选择器(Selectors) +Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。 + + +## 参考资料 + 1. http://bbym010.iteye.com/blog/2100868 + 2. http://developer.51cto.com/art/201112/307463.htm + 3. http://ifeve.com/java-nio-vs-io/ + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + diff --git a/java/jvm.md b/java/jvm.md new file mode 100644 index 0000000..127db34 --- /dev/null +++ b/java/jvm.md @@ -0,0 +1,242 @@ +## 介绍JVM中7个区域,然后把每个区域可能造成内存的溢出的情况说明 +**程序计数器**:看做当前线程所执行的**字节码行号指示器**。是线程**私有**的内存,且唯一一块不报OutOfMemoryError异常。 +**Java虚拟机栈**:用于描述java方法的**内存模型**:每个方法被执行时都会同时创建一个**栈帧**用于存储**局部变量表,操作数栈,动态链接,方法出口**等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机中从入栈到出栈的过程。如果线程请求的**栈深度**大于虚拟机所允许的深度就报StackOverflowError, 如果虚拟机栈可以动态**扩展**,当拓展时无法申请到足够的内存会抛出OutOfMemoryError. 是线程**私有**的。 +**本地方法栈**:与虚拟机栈相似,不同的在于它是为虚拟机使用到的**Native**方法服务的。会抛出StackOverflowError和OutOfMemoryError。是线程**私有**的。 +**Java堆**:是所有线程共享的一块内存,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。如果堆上没有内存完成实例的分配就会报OutOfMemoryError. +**方法区(永久代)**:用于存储已被虚拟机加载的**类信息、常量、静态变量、即时编译器编译后的代码**等数据。当方法区无法满足内存分配需求时,会抛出OutOfMemoryError。是共享内存。 +**运行时常量池**:用于存放编译器生成的各种字面量和符号引用,是方法区的一部分。无法申请内存时抛出OutOfMemoryError。 +**直接内存**:不是虚拟机运行时数据的一部分,也不是java虚拟机规范中定义的区域,是计算机直接的内存空间。这部分也被频繁使用,如JAVA NIO的引入基于通道和缓存区的I/O使用native函数直接分配堆外内存。如果内存不足会报OutOfMemoryError。 + +2. GC的两种判定方法:引用计数与根搜索算法。 +**引用计数**: +给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器值加1,当引用失效时,计数器值减1,。任何时候计数器都为0的对象就是不可能再被使用的。它很难解决对象之间相互**循环引用**问题。 +**根搜索算法(GC Roots Traceing):** +通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径成为**引用链**,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。 +GC Roots对象一般是:虚拟机栈中的引用对象,方法区中类静态属性引用的对象,方法区常量引用的对象等。 + +## Java中的四种引用 +**强引用**:程序代码中的普通引用。如Object obj = new Object(),只要强引用存在,垃圾回收器就不会回收。 +**软引用**:描述一些有用但并非必须的对象。对于软引用关联的对象在系统将要**发生内存溢出异常之前**,将会把这些对象列进回收范围之中进行第二次回收。**SoftRefence** +**弱引用**:描述非必须对象,比软引用弱一些。被弱引用关联的对象只能**生存到下一次垃圾收集发生之前**。无论当前内存是否足够,都会回收掉只被弱引用关联的对象。**WeakRefence** +**虚引用**:最弱的引用,不管是否有虚引用存在,完全不会对对象生存时间构成影响,也无法通过虚引用来取得一个对象实例。唯一目的是希望能够在这个对象被垃圾回收器之前收到系统通知。**PhantomReference** + +## 对象创建方法,对象的内存分配,对象的访问定位。 +Object obj = new Object(); +obj 保存在java栈中的局部变量表里,作为一个引用数据出现。 +New Object()会在java堆上分配一块存储Object类型实例的所有数值的结构化内存,根据类型以及虚拟机实现的对象内存布局不同。这块内存是不固定的。 +对象访问方式有两种:**句柄和直接指针**: +**句柄**:在java堆中会划分出一块内存作为句柄池,reference中存储的对象是句柄地址。而句柄中包含对象实例数据和类型数据各自的具体地址信息。最大的好处是如果对象地址发生变化不需要改变reference的值,只需要改变句柄中实例数据指针。 +**直接指针访问**:reference直接存储对象的地址,最大的好处是**速度更快**。 + +## 内存溢出和内存泄漏 +**内存溢出**:通俗理解就是**内存不够**,程序所需要的内存远远超出了你虚拟机分配的内存大小,就叫内存溢出 +**内存泄露**:内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在**使用完毕后未释放**,结果导致**一直占据该内存单元**。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏 + +## 内存溢出了怎么办 +通过内存映像工具如jhat,jconsole等对dump出来的堆转存储快照进行分析,重点是确认内存是出现内存泄露还是内存溢出。 +如果是**内存泄露**进一步使用工具查看泄露的对象到GC Roots的引用链。于是就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们。掌握泄露对象的信息,以及GC Roots引用链的信息,就可以比较准确定位泄露代码的位置。 +如果不存在**内存泄露,那就需要通过jinfo,Jconsole等工具分析java堆参数与机器物理内存对比是否还可以调大,从代码上检查是否存在某些对象生命周期过长,持有状态过长的情况,尝试减少程序的运行消耗。 + +## Java 中有内存泄露吗? +有,Java中,造成内存泄露的原因有很多种。典型的例子是**长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露**,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。 +检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。(采用什么工具?) +**如果一个外部类的实例对象的方法返回了一个内部类的实例对象**,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。 +http://www.mamicode.com/info-detail-504269.html + +## 什么时候会发生jvm堆(持久区)内存溢出 +简单的来说 java的堆内存分为两块:permantspace(持久代) 和 heap space。 +持久带中主要存放用于存放静态类型数据,如 Java Class, Method 等, 与垃圾收集器要收集的Java对象关系不大。 +而heapspace分为年轻代和年老代 +年轻代的垃圾回收叫 Young GC, 年老代的垃圾回收叫 Full GC。 +在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,就会被复制到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象 +年老代溢出原因有 循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存 +持久代溢出原因动态加载了大量Java类而导致溢出,以及生产大量的常量。 +**永久代内存泄露**: +以一个部署到应用程序服务器的Java web程序来说,当该应用程序被卸载的时候,你的EAR/WAR包中的所有类都将变得无用。只要应用程序服务器还活着,JVM将继续运行,但是一大堆的类定义将不再使用,理应将它们从永久代(PermGen)中移除。如果不移除的话,我们在永久代(PermGen)区域就会有内存泄漏。 + +## 堆里面的分区:Eden,survival from to,老年代,各自的特点。 +新生代:朝生夕死 +老年代一般是放对象和长期存活对象。当一个对象分配的内存空间大于某个阈值时或则年龄增加到一定程度(默认15岁)就进入老年代。 + +## OOM你遇到过哪些情况 +java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。 +java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,**一般出现于大量Class或者jsp页面**,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。 +java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在**死循环或者深度递归调用**造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数**-Xss**来设置栈的大小。 + +## GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路? +**标记清理**:首先标记所有需要回收的对象,在标记完成后**统一回收掉**所有被标记的对象,它的标记的对象。缺点是**效率低**,且存在**内存碎片**。主要用于老生代垃圾回收。 +**复制算法**:将内存按容量划分为大小相等的一块,每次只用其中一块。当内存用完了,将还存活的对象复制到另一块内存,然后把已使用过的内存空间一次清理掉。实现简单,高效。一般用于新生代。一般是将内存分为一块较大的Eden空间和两块较小的Survivor空间。HotSpot虚拟机默认比例是8:1,。每次使用Eden和一块Survivor,当回收时将这两块内存中还存活的对象复制到Survivor然后清理掉刚才Eden和Survivor的空间。如果复制过程内存不够使用则向老年代分配担保。 +**标记整理**:首先标记所有需要回收的对象,在标记完成后让所有存活的对象都向一端移动,然后直接清理掉端边界意外的内存。用于老年代。 +**分代收集算法**:根据对象的生存周期将内存划分为新生代和老年代,根据年代的特点采用最适当的收集算法。 + +## GC收集器有哪些?CMS收集器与G1收集器的特点。 +**Serial**: 单线程收集器,只会使用一个CPU或一条收集器线程去完成,垃圾回收工作,更重要的是在进行垃圾回收时,必须暂停其他所有的工作线程。(Stop the world)。简单高效,用于新生代。 +**ParNew**: 是Serial收集器的多线程版本,垃圾回收时采用多线程方式进行回收。默认情况下使用的线程数是cpu数量。除了serial收集器,目前只有它能和CMS收集器配合工作。 +**Parallel Scavenge**: 使用复制算法收集器,也是一个并行的多线程收集器。Parallel Scavenge收集器与其他收集器关注点不同,其它收集器主要关注缩短垃圾回收时用户线程的停顿时间。而它关心**吞吐量**,即**运行用户代码时间/(运行用户代码时间+垃圾收集时间)**。停顿时间越短越适合需要与用户交互的程序,高吞吐量则可以最高效率的利用CPU时间。 +**Serial Old**: 老年代,单线程收集器,使用**标记整理算法**。主要有两个用途,一是和Parallel Scavenge 收集器配合使用,二是作为CMS的后备方案在并发收集器发生**Concurrent Mode Failure**时候使用。 +**Parallel Old**:并行的老年代版本收集器,使用标记整理算法。主要与Parallel Scavenge配合使用。 +**CMS**:是以获得最短回收停顿时间为目标的收集器,使用标记清除算法。整个过程包括4个: + **初始标记**: 标记Gc ROOTS能直接关联到的对象 + **并发标记**:进行Roots Traceing的过程 + **重新标记**:修正并发标记期间因用户继续工作导致标记产生变动 + **并发清除**: +初始标记和重新标记需要stop the world. 并发标记和并发清除过程用户线程和收集器线程可以并行执行。 +**G1(Garbage First):**基于标记-整理算法的收集器,不会产生空间碎片.它可以精确控制停顿,能够让使用者明确指定一个长度为M毫秒的时间片段内,消耗集上的时间不超过N秒.是不牺牲吞吐量的前提下完成低停顿的.G1将整个java堆(新生和老生)划分为大小相同的区,并跟踪这些区上发生的变化.在后台维护一个优先列表,每次根据允许的收集时间优先回收垃圾最多的区域. + +## Minor GC与Full GC分别在什么时候发生? +FullGC 一般是发生在老年代的GC,出现一个FullGC经常会伴随至少一次的Minor GC。速度比MinorGC慢10倍以上。FULL GC发生的情况: +1) 老年代空间不足 +老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:java.lang.OutOfMemoryError: Java heap space . +措施:为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组 +2) Permanet Generation(方法区或永久代)空间满 +PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息: +java.lang.OutOfMemoryError: PermGen space +措施:为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。 +3) CMS GC时出现promotion failed和concurrent mode failure +对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。 +promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的; +concurrent mode failure: CMS在执行垃圾回收时需要一部分的内存空间并且此刻用户程序也在运行需要预留一部分内存给用户程序,如果预留的内存无法满足程序需求就出现一次"Concurrent mod failure",并触发一次Full GC。 +应对措施为:增大survivor space、老年代空间或调低触发并发GC的比率, +4) 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间 +Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。如果小于并且不允许担保失败也会发生一次Full GC + +MinorGC +MinorGC 指发生在新生代的垃圾收集动作,非常频繁,回收速度也快。一般发生在新生代空间不足时,另外一个FullGC经常会伴随至少一次的Minor GC. 当虚拟检测晋升到到老年代的平均大小是否小于老年代剩余空间大小,如果小于并且允许担保失败,则执行Minor GC. +http://zhidao.baidu.com/link?url=hxwfrDGb87nYAAaKh6kQerv45RzwFkGWOcvl9wbaKVwb8rjdiQNgxLy19ga_A0W8ozisKwCkrSlMjISwB1PeDcoXN9Z7qf12CdEm19VxiuO + +## 几种常用的内存调试工具:jmap、jstack、jconsole。 +(如何用工具分析jvm状态) +**jps**: 列出正在虚拟机运行的虚拟机进程,并显示虚拟机执行主类的名称,以及这些进程的本地虚拟机的唯一ID。 +**jstat** : 监视虚拟机各种运行状态信息的命令。可以显示本地或远程虚拟机进程中**类装载,垃圾收集,JIT编译,内存**等数据。 +**jinof**: 实时查看和调整虚拟机的各项参数。 +**jmap**: 生成堆转存储快照,查询fianlize执行队列、java堆和永生代详细信息,如空间使用率,当前用的是那种收集器。 +**Jhat**: 和jmap搭配使用,来分析jmap生成的堆转存储快照。内置一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以通过浏览器查看。 +**jstack**:用于生成当前时刻线程快照.线程快照是当前虚拟机内每一条线程正在执行的方法堆栈的集合.生成线程快照的主要目的是为了定位线程长时间停顿的原因.如死锁,死循环,请求外部资源导致的长时间等待. +**JConsole**:可视化监视和管理工具,几乎包括以上工具的所有功能 +**VisualVM** + +## GC 是什么?为什么要有 GC +答:GC 是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。Java 程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但 JVM 可以屏蔽掉显示的垃圾回收调用。 + +垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java 最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今 Java 的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在于 Android 系统中垃圾回收的不可预知性。 + + +## JVM 加载 class 文件的原理机制 +JVM 中类的装载是由类加载器(ClassLoader) 和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。 + 由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的 .class 文件中的数据读入到内存中,通常是创建一个字节数组读入 .class 文件,然后产生与所加载类对应的 Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:1. 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2. 如果类中存在初始化语句,就依次执行这些初始化语句。 + 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从JDK 1.2开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类加载器的说明: +a)**Bootstrap**:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar); +b)**Extension**:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap; +c)**System**:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。 + +## 类加载的五个过程:加载、验证、准备、解析、初始化。 +**加载**: 根据全限定名来获取定义类的二进制字节流,然后将该字节流所代表的静态结构转化为方法区的运行时数据结构,最后在java堆上生成一个代表该类的Class对象,作为方法区这些数据的访问入口. +**验证**:主要时为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全.包含四个阶段的验证过程: +1)**文件格式验证**:保证输入的字节流能够正确地解析并存储在方法区之内,格式上符合描述一个java类型信息的要求 +2)**元数据验证**:字节码语义信息的验证,以保证描述的信息符合java语言规范.验证点有:这个类是否有父类等. +3)**字节码验证**:主要是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为. +4)**符号引用验证**:对符号引用转化为直接引用过程的验证. +**准备**:为类变量分配内存并设置变量的初始值,这些内存在方法区进行分配. +**解析**:将虚拟机常量池中的符号引用转化为直接引用的过程.解析主要是针对类或接口,字段,类方法,类几口方法四类. +**初始化**:执行静态变量的赋值操作以及静态代码块,完成初识化.初始化过程保证了父类中定义的初始化优先于子类的初始化.但接口不需要执行父类的初始化. + + +## 双亲委派模型: +除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器.顺序依次是: +Bootstrap ClassLoader: 启动类加载器,加载java_home/lib中的类 +Extension ClassLoader: 扩展类加载器,加载java_home/lib/ext目录下的类库 +Application ClassLoader: 应用程序类加载器,加载用户类路径上指定类库. +双亲委派模型的工作原理是:如果一个类加载器受到了类加载请求,它首先不会自己去尝试加载这个类,而把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求时,加载器才尝试自己加载.这种方式保证了Oject类在各个加载器加载环境中都是同一个类. + +## 分派:静态分派与动态分派。 +多态性特征的一些最基本的体现. +静态类型是编译器可知的,动态类型是在运行时可知.Human h =new Man(); Human是静态类型,Man时动态类型. +所有依赖于静态类型定义方法执行版本的分配动作称作**静态分派**,最典型的应用是方法重载. +**动态分派**是根据动态类型来确定执行的版本,所以只有到运行时才能确定具体的执行方法版本.典型的代表时重写.其过程如下: +1)首先找到操作数栈栈顶的第一个元素所执向对象的实际类型,记做C. +2) 如果在类型C中找到和常量中的描述符和简单名称都相符的方法,则进行范围权限校验.如果通过则返回该方法的直接引用,否则抛出IllegalAccessError异常. +3) 否则按照继承关系从下往上一次对C的各个父类进行第2步的搜索和验证过程. +4) 如果始终没有找到旧抛出AbstractMethodError异常. +方法的接受者和方法的参数统称方法宗量,根据分配基于多少中宗量可以分为单分派和多分派.java是静态多分派,动态分派属于单分派. + +**动态分派的实现:** +动态分派时非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法,因此出于性能的考虑,在方法区中建立一个虚方法表,用来保存各个方法的实际入口地址.如果某个方法的子类中没有被重新,那么子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的.都是指向父类的实现入口,如果子类中重写了这个方法,子类方法表中的地址将会被替换为指向子类实现版本的入口地址.虚方法表在类加载的连接阶段进行初始化. + +## Jvm 自动内存管理(什么时候触发 gc ) +http://jeromecen1021.blog.163.com/blog/static/18851527120117274624888/ +FULL GC 和 Minor GC 的触发时间 +程序员不能具体控制时间,系统在不可预测的时间调用System.gc()函数的时候;当然可以通过调优,用NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制进入oldObject的次数,使得oldObject 存储空间延迟达到full gc,从而使得计时器引发gc时间延迟OOM的时间延迟,以延长对象生存期。 + +## GC停顿原因,如何降低停顿 +## JVM如何调优、参数怎么调 + +## jvm的体系结构及各个部分的职责 +JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),包含类的装载 连接 初始化的过程叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为: + + +JVM的每个实例都有一个它自己的方法域和一个堆,运行于JVM内的所有的线程都共享这些区域;当虚拟机装载类文件的时候,它解析其中的二进制数据所包含的类信息,并把它们放到方法域中;当程序运行的时候,JVM把程序初始化的所有对象置于堆上;而每个线程创建的时候,都会拥有自己的程序计数器和Java栈,其中程序计数器中的值指向下一条即将被执行的指令,线程的Java栈则存储为该线程调用Java方法的状态;本地方法调用的状态被存储在本地方法栈,该方法栈依赖于具体的实现。 + +http://blog.csdn.net/dongdong_java/article/details/24797307 +http://blog.csdn.net/longyulu/article/details/8350622 + +## 如果想不被 GC 怎么办 +可以先说那些对象可以被GC,然后说java对象会不会回收,决定于是否还被引用,不被引用了就有可能被GC回收,一直被引用着就不会被回收. + +2. jvm性能调优都做了什么 +4. 介绍GC 和GC Root不正常引用。 +5. 自己从classload 加载方式,加载机制说开去,从程序运行时数据区,讲到内存分配,讲到String常量池,讲到JVM垃圾回收机制,算法,hotspot。反正就是各种扩展 +7. 数组多大放在 JVM 老年代(不只是设置 PretenureSizeThreshold ,问通常多大,没做过一问便知) +8. 老年代中数组的访问方式 +9. GC 算法,永久代对象如何 GC , GC 有环怎么处理 +9. jvm 如何分配直接内存?? +10. new 对象如何不分配在堆而是栈上? +11. 常量池解析 + +## 运行期优化 +## 最佳实践 + + +## Student s= new Student(),在内存中做了那些事情 +1. 加载Student.class 文件进内存 +2. 在栈内存为s开辟空间 +3. 在堆内存为学术对象开辟空间 +4. 学生对象的成员变量进行显示初始化 +5. 通过构造方法对学生对象变量赋值 +6. 学生对象初始完毕,把对象地址赋值给s变量 + +## 常用参数配置 +参数 含义 默认值 备注 +-Xms:表示初始堆大小,默认为物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. +-Xmx 最大堆大小 物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 +-Xmn 年轻代大小(1.4or lator) 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 +整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. +增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 +-XX:NewSize 设置年轻代大小(for 1.3/1.4) +-XX:MaxNewSize 年轻代最大值(for 1.3/1.4) +-XX:PermSize 设置持久代(perm gen)初始值 物理内存的1/64 +-XX:MaxPermSize 设置持久代最大值 物理内存的1/4 +-Xss 每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 +一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长) +和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话: +-Xss is translated in a VM flag named ThreadStackSize +一般设置这个值就可以了。 +-XX:NewRatio 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) +-XX:NewRatio = 4表示年轻代与年老代所占比值为1:4, 年轻代占整个堆栈的1/5 +Xms = Xmx并且设置了Xmn的情况下,该参数不需要进行设置。 +-XX:SurvivorRatio Eden区与Survivor区的大小比值 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 +-XX:MaxTenuringThreshold 垃圾最大年龄 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 +该参数只有在串行GC时才有效. +-XX:PretenureSizeThreshold 对象超过多大是直接在旧生代分配 0 单位字节 新生代采用Parallel Scavenge GC时无效 +另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象. + + + +相关资料: +http://blog.csdn.net/wisgood/article/details/16818243 + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + + diff --git a/java/object.md b/java/object.md new file mode 100644 index 0000000..785b803 --- /dev/null +++ b/java/object.md @@ -0,0 +1,92 @@ +## 面向对象 +下面列出了面向对象软件开发的优点: +(1) 代码开发模块化,更易维护和修改。 +(2) 代码复用。 +(3) 增强代码的可靠性和灵活性。 +(4) 增加代码的可理解性。 +面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象 + +## Java面向对象的三个特征与含义 +一、继承: +1.概念:继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力 +2.好处:提高代码的复用,缩短开发周期。 +二、多态: +1.概念:多态(Polymorphism)按字面的意思就是“多种状态,即同一个实体同时具有多种形式。一般表现形式是程序在运行的过程中,同一种类型在不同的条件下表现不同的结果。多态也称为动态绑定,一般是在运行时刻才能确定方法的具体执行对象,这个过程也称为动态委派。 +2.好处: +1)将接口和实现分开,改善代码的组织结构和可读性,还能创建可拓展的程序。 +2)消除类型之间的耦合关系。允许将多个类型视为同一个类型。 +3)一个多态方法的调用允许有多种表现形式 +三、封装: +1.概念:就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。 + 2.好处: +(1)隐藏信息,实现细节。让客户端程序员无法触及他们不应该触及的部分。 +(2)允许可设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员。 + + +## Overload 和 Override +Overload 是重载的意思,Override 是覆盖的意思,也就是重写。 +**重载 Overload** 表示**同一个类**中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。 +**重写 Override** 表示**子类**中的方法可以与**父类**中的某个**方法的名称和参数完全相同**,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。**子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常**,因为子类可以解决父类的一些问题,不能比父类有更多的问题。**子类方法的访问权限只能比父类的更大,不能更小**。如果**父类的方法是 private 类型,那么,子类则不存在覆盖的限制**,相当于子类中增加了一个全新的方法。 + 至于 Overloaded 的方法是否可以改变返回值的类型这个问题,要看你倒底想问什么呢?这个题目很模糊。如果几个 Overloaded 的方法的参数列表不一样,它们的返回者类型当然也可以不一样。但我估计你想问的问题是:如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载 Overload。这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用 map.remove(key) 方法时,虽然 remove 方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,Java 就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。 + +Override 可以翻译为覆盖,从字面就可以知道,它是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。**在覆盖要注意以下的几点**: +**1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;** +**2、覆盖的方法的返回值必须和被覆盖的方法的返回一致,或者是其子类(协变返回类型);** +**3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;** +**4、被覆盖的方法不能为 private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。** + +Overload 对我们来说可能比较熟悉,可以翻译为重载,它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM 就会根据不同的参数样式,来选择合适的方法执行。在使用**重载要注意**以下的几点: +**1、在使用重载时只能通过不同的参数样式。**例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是 fun(int,float),但是不能 fun(int,int)); + **2、不能通过访问权限、返回类型、抛出的异常进行重载**; +**3、方法的异常类型和数目不会对重载造成影响**; + + +## 接口和抽象类的区别 +Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于: +1,接口中所有的**方法**隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。 +2,类可以实现很多个接口,但是只能**继承**一个抽象类 +3,类如果要**实现**一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。 +4,抽象类可以在不提供接口方法实现的情况下实现接口。 +5,Java 接口中声明的**变量**默认都是 final 的。抽象类可以包含非 final 的变量。 +6,Java 接口中的**成员函数**默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public 。 +7,接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 main 方法的话是可以被调用的 + +## 接口 +接口很重要,为了说明情况,这里稍微啰嗦点: +(1)接口用于描述系统对外提供的所有服务,因此接口中的成员常量和方法**都必须是公开(public)类型**的,确保外部使用者能访问它们; +(2)接口仅仅描述系统能做什么,但不指明如何去做,所以接口中的方法**都是抽象(abstract)方法**; +(3)接口不涉及和任何具体实例相关的细节,因此接口没有构造方法,不能被实例化,没有实例变量,**只有静态(static)变量**; +(4)接口的中的变量是所有实现类**共有的**,既然共有,肯定是不变的东西,因为变化的东西也不能够算共有。所以**变量是不可变(final)类型,也就是常量了**。 +(5) 接口中不可以定义变量.如果接口可以定义变量,但是接口中的方法又都是抽象的,在接口中无法通过行为来修改属性。有的人会说了,没有关系,可以通过 实现接口的对象的行为来修改接口中的属性。这当然没有问题,但是考虑这样的情况。如果接口 A 中有一个public 访问权限的静态变量 a。按照 Java 的语义,我们可以不通过实现接口的对象来访问变量 a,通过 A.a = xxx; 就可以改变接口中的变量 a 的值了。正如抽象类中是可以这样做的,**那么实现接口 A 的所有对象也都会自动拥有这一改变后的 a 的值了,也就是说一个地方改变了 a,所有这些对象中 a 的值也都跟着变了**。这和抽象类有什么区别呢,怎么体现接口更高的抽象级别呢,怎么体现接口提供的统一的协议呢,那还要接口这种抽象来做什么呢?所以接口中 不能出现变量,如果有变量,就和接口提供的统一的抽象这种思想是抵触的。所以接口中的属性必然是常量,只能读不能改,这样才能为实现接口的对象提供一个统 一的属性。 +通俗的讲,你认为是要变化的东西,就放在你自己的实现中,不能放在接口中去,接口只是对一类事物的属性和行为更高层次的抽象。对修改关闭,对扩展(不同的实现 implements)开放,接口是**对开闭原则**的一种体现。 +所以:**接口的方法默认是 public abstract**; +接口中不可以定义变量即只能定义常量(加上final修饰就会变成常量)。**所以接口的属性默认是 public static final 常量,且必须赋初值**。 +**注意**:final 和 abstract 不能同时出现。 + +## 两个对象值相同(x.equals(y) == true),但却可有不同的 hash code,这句话对不对? +答:不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hash code)应当相同。 +Java 对于 eqauls 方法和 hashCode 方法是这样规定的: +**(1)如果两个对象相同(equals 方法返回 true ),那么它们的 hashCode 值一定要相同**; +**(2)如果两个对象的 hashCode 相同,它们并不一定相同**。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。 +**(3)如果对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数**. + +补充:关于 equals 和 hashCode 方法,很多 Java 程序都知道,但很多人也就是仅仅知道而已,在 Joshua Bloch 的大作《Effective Java》(很多软件公司,《Effective Java》、《Java 编程思想》以及《重构:改善既有代码质量》是 Java 程序员必看书籍,如果你还没看过,那就赶紧去亚马逊买一本吧)中是这样介绍 equals 方法的:首先 equals 方法必须满足自反性(x.equals(x) 必须返回true)、对称性(x.equals(y) 返回true时,y.equals(x) 也必须返回 true)、传递性(x.equals(y)和y.equals(z)都返回 true 时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非 null 值的引用 x,x.equals(null) 必须返回 false。实现高质量的 equals 方法的**诀窍**包括: + + **使用 == 操作符检查“参数是否为这个对象的引用”**; + **使用 instanceof 操作符检查“参数是否为正确的类型”**; + **对于类中的关键属性,检查参数传入对象的属性是否与之相匹配**; + **编写完 equals 方法后,问自己它是否满足对称性、传递性、一致性**; + **重写 equals 时总是要重写 hashCode**; + **不要将 equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉 @Override 注解**。 + +## 是 AOP 和 OOP,IOC 和 DI +1)面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。AOP 是 OOP 的延续,是 Aspect Oriented Programming 的缩写,意思是面向方面编程。 将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。AOP 就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中 + +2)控制反转 IOC(Inversion of Control) 控制指的就是程序相关类之间的依赖关系.传统观念设计中, +通常由调用者来创建被调用者的实例, **在 Spring 里,创建被调用者的工作不再由调用者来完成,而是由 Spring 容器完成,依赖关系被反转了,称为控制反转,目的是为了获得更好的扩展性和良好的可维护性**。依赖注入(Dependency injection)创建被调用者的工作由 Spring 容器完成,然后注入调用者,因此也称依赖注入。控制反转和依赖注入是**同一个概念**。 + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + diff --git a/java/thread.md b/java/thread.md new file mode 100644 index 0000000..7b43382 --- /dev/null +++ b/java/thread.md @@ -0,0 +1,283 @@ +## 什么叫线程安全?举例说明 +多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。 +比如无状态对象一定是线程安全的。 + +## 进程和线程的区别 +调度: 线程是调度的基本单位,进程是拥有资源的基本单位。同一进程的中线程的切换不会引起进程的切换,不同进程中进行线程切换会引起进程的切换。 +拥有资源:进程是拥有资源的基本单位,线程除了自身的栈外一般不拥有资源。而是和其他线程共享同一进程中的资源。 +系统开销:由于创建进程或者撤销进程时,系统都要分配和回收资源,如内存空间,I/O设备等,操作系统所付出的开销远大于创建或撤销进程时的开销。 + +## volatile的理解 +**Volatile自身特性**: +Volatile 是轻量级的synchronized,它在多处理器开发过程中保证了共享变量的“可见性”,可见性是指当一个线程的某个共享变量发生改变时,另一个线程能够读取到这个修改的值。Voaltile变量修饰的变量在进行写操作时在多核处理器下首先将当前处理器缓存行的数据写回到系统内存中。为了保证一致性,其他处理器嗅探到总线上传播的数据,发现数据被修改了自己缓存地址的数据无效。 +Volatile 可以禁止重排序, +Volatile 能保持单个简单volatile变量的读/写操作的具有原子性。但不能保证自增自减的原子性。 + +从**内存语义**来讲: +volatile变量的写-读与锁的释放-获取具有相同语义,volatile的写与锁的释放有相同的内存语义,volatile读与锁的获取具有相同语义。 +线程A写一个volatile变量,实质上是线程A向接下来要读这个volatile变量的某个线程发出消息 +线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的消息。 +线程A写volatile变量,随后线程B读这个变量,这个过程实质上线程A通过内存向B发送消息。 +内存语义的实现,也是禁止重排序特性: +为了实现volatile内存语义,JMM限制了对volatile重排序做了限制: +1.当第二个操作是volatile写时,不管第一个操作时什么,都不能重排序。 +2.当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。 +3.当第一个操作是volatile写,第二个操作是volatile读时,不重排序。 +为了实现volatile的内存语义,编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。JMM采取保守策略。 +1. 在每个volatile写操作前面插入一个StoreStore屏障 +2. 在每个volatile写操作后面插入一个StoreLoad屏障 +3. 在每个volatile读操作后面插入一个LoadLoad屏障 +4. 在每个volatile读操作后面插入一个LoadStore屏障 + +## 原子性实现机制 +处理器提供总线锁定和缓存锁定两种方式来保证复杂内存操作的原子性。 +总线型:就是使用处理器提供一个LOCK信号,当一个处理器在总线传输信号时,其他处理器的请求将被阻塞住,那么该处理独占内存。所以总线锁定开销大。 +缓存锁定:内存区域如果被缓存在缓存行中,且在在lock期间被锁定,当它执行锁操作写回内存时,处理器总线不在锁定而是通过修改内部的内存地址并使用缓存一致性制阻止同时修改保证操作的原子性。缓存一致性进制两个以上的处理器同时修改内存区域数据,其他处理器回写被锁定并且使其缓存行无效。 + +## Java原子性操作实现原理 +使用循环CAS实现原子性操作,CAS是在操作期间先比较旧值,如果旧值没有发生改变,才交换成新值,发生了变化则不交换。这种方式会产生以下几种问题:1.ABA问题,通过加版本号解决;2.循环时间过长开销大,一般采用自旋方式实现;3. 只能保证一个共享变量的原子操作。 + +## Java内存模型 +Java内存模型控制线程之间的通信,决定了一个线程对共享变量的写入何时对另一个线程可见。它属于语言及的内存模型,它确保在不同编译器和不同的处理平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。JMM的核心目标是找到一个好的平衡点,一方面是为程序员提供足够强的内存可见性保证(提供happens-before规则),另一方面对编译器和处理器的限制尽可能地放松(只要不改变程序结果,怎么优化都可以) + +**可见性保证** + 为了提供内存可见性保证,JMM向程序员保证了以下hapens-before规则: +1.程序顺序规则:一个线程的每个操作happen-before与该线程的任意后续操作。 +2.监视器锁规则:一个锁的解锁,happens-before于随后这个锁的加锁。 +3.Volatile变量规则:对一个volatile域的写,happens-before于任意后续这个域的读。 +4.传递性, 如果A happens-before B, 且B happens-before C 那么A happens-before C +5.线程启动规则:如果线程A执行操作ThreadB.start().那么线程A中的任意操作happens-before与线程B中的任意操作。 +6.线程结束规则: 线程中的任何操作都必须在其线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false. +7.中断规则:当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptedException,或者调用isInterrupted和interrupted) +8.终结器规则: 对象的构造函数必须在启动该对象的终结器之前执行完成。 + +**禁止重排序** +为了保证内存可见性,java编辑器在生成指令序列的适当位置插入内存屏障指令来禁止特定类型的处理器重排序。 +重排序:编译器和处理器为了优化程序性能对指令进行重新排序的一种手段。 +1)编译器优化的重排序: 编译器在不改变单线程程序语义的前提下可以重新安排语句顺序。 +2)指令级并行的重排序.现代处理器采用指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变对应指令的执行顺序。 +3)内存系统重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能在乱序执行。 + +## Final域的内存语义 +对于final域编译器和处理器要遵守两个重排序规则: +1.在构造器函数内对final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。(保证了对象引用为任何线程可见之前,对象的final域已经被正确初始化过) +2.初次读一个包含final域的对象引用,与随后初次读这个final域这两个操作不能重排序。 +为何保证其内存语义:可以为java程序员提供安全保证,只要对象是正确构造的,那么不需要使用同步就可以保证线程都能看到这个fianal域在构造函数中被初始化之后的值。 + +## 避免死锁的常见方法: +1)不免一个线程同时获取多个锁 +2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源 +3)尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。 +4)对数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。 + +## 死锁的必要条件?怎么克服? +答:产生死锁的四个必要条件: +**互斥条件**:一个资源每次只能被一个进程使用。 +**请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 +**不剥夺条件**:进程已获得的资源,在末使用完之前,不能强行剥夺。 +**循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系。 +这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。 + +死锁的解决方法: +a 撤消陷于死锁的全部进程; +b 逐个撤消陷于死锁的进程,直到死锁不存在; +c 从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失。 +d 从另外一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态 + +## CountDownLatch(闭锁) 与CyclicBarrier(栅栏)的区别 +CountDownLatch: 允许一个或多个线程等待其他线程完成操作. CyclicBarrier:阻塞一组线程直到某个事件发生。 +1. 闭锁用于等待事件、栅栏是等待线程. +2. 闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数。 +3. CountDownLatch是一次性的,CyclicBarrier可以重用。 +4. CountDownLatch一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行。CyclicBarrier是N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。 +CountDownLatch 是计数器, 线程完成一个就记一个,就像报数一样, 只不过是递减的. +而CyclicBarrier更像一个水闸, 线程执行就像水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流. + +## execute 和submit的区别 +Execute()用于提交不需要返回值得任务,submit()用于提交需要返回值的任务,发挥Future类型的对象。 + +## Shutdown和shutdownNow的区别 +它们的原理都是遍历线程池中的工作线程,然后逐个调用线程的Internet方法来中断线程,所以无法响应中断的任务可能永远无法终止。 +ShutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有正在执行或暂停的任务,并返回等待执行任务的列表。而shutdown只是将线程池设置成SHUTDOWN状态,然后中断没有正在执行任务的线程。 + +## ThreadLocal的设计理念与作用。 +ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本 + +http://blog.csdn.net/lufeng20/article/details/24314381 +## 同步 +同步就是协同步调,按预定的先后次序进行运行。 + +## sleep() 和 wait() 区别 +答:sleep()方法是线程类(Thread)的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用 sleep 不会释放对象锁。wait() 是 Object 类的方法,对此对象调用 wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。 + +## sleep() 和 yield() 区别 +① sleep() 方法给其他线程运行机会时**不考虑线程的优先级**,因此会给低优先级的线程以运行的机会;yield() 方法**只会给相同优先级或更高优先级**的线程以运行的机会; +② 线程执行 sleep() 方法后转入**阻塞**(blocked)状态,而执行 yield() 方法后转入**就绪(ready)**状态; +③ sleep() 方法声明抛出InterruptedException,而 yield() 方法没有声明任何异常; +④ sleep() 方法比 yield() 方法(跟操作系统相关)具有更好的可移植性。 + +## 线程同步相关的方法。 +**wait()**:使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; +**sleep()**:使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常; +**notify()**:唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; +**notityAll()**:唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争; + +## 什么是线程池(thread pool) +答:在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是"池化资源"技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 + +http://www.cnblogs.com/dolphin0520/p/3932921.html +## ConcurrentHashMap实现原理 +ConcurrentHashMap和Hashtable主要区别就是围绕着锁的粒度以及如何锁。 +Hashtabl在竞争激烈的环境下表现效率低下的原因是一把锁锁住整张表,导致所有线程同时竞争一个锁。ConcurrentHashMap采用分段锁,每把锁锁住容器中的一个Segment。那么多线程访问容器里不同的Segment的数据时线程就不会存在竞争,从而有效提高并发访问效率。首先是将数据分层多个Segment存储,并为每个Segment分配一把锁,当一个线程范围其中一段数据时,其他线程可以访问其他段的数据。 + +数据结构: +ConcurrentHashMap内部是有Segment数组和HashEntry数组组成。一个ConcurrentHashMapMap里包含一个Segment数组,而Segment的结构和HashMap一样,里面是由一个数组和链表结构组成,所以一个Segment内部包含一个HashEntry数组。每个HashEntry是一个链表结构,对于HashEntry数组进行修改时首先需要获取与它对应的Segment锁。默认情况下有16个Segment + +Segment的定位: +使用Wang/Jenkins hash变种算法对元素的hashCode进行一次再散列,目的是为了减少散列冲突。 + +ConcurrentHashMap的操作: +1.) get +get操作实现非常简单高效。先经过一次在散列,然后用这个散列值通过散列运算定位到Segment,再通过散列算法定位到元素。get之所以高效是因为整个get过程不需要加锁,除非读到空值才会加锁重读。实现该技术的技术保证是保证HashEntry是不可变的。 +第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count 变量,通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。接下来就是根据hash和key对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。 +对hash链进行遍历不需要加锁的原因在于链指针next是final的。但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在 table数组中的值。这使得getFirst(hash)可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。 +2.) put +该方法也是在持有段锁(锁定当前segment)的情况下执行的,这当然是为了并发的安全,修改数据是不能并发进行的,必须得有个判断是否超限的语句以确保容量不足时能够rehash。首先根据计算得到的散列值定位到segment及该segment中的散列桶中。接着判断是否存在同样一个key的结点,如果存在就直接替换这个结点的值。否则创建一个新的结点并添加到hash链的头部,这时一定要修改modCount和count的值,同样修改count的值一定要放在最后一步。 +3)remove +HashEntry中除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。 +首先定位到要删除的节点e。如果不存在这个节点就直接返回null,否则就要将e前面的结点复制一遍,尾结点指向e的下一个结点。e后面的结点不需要复制,它们可以重用。 +4) size() +每个Segment都有一个count变量,是一个volatile变量。当调用size方法时,首先先尝试2次通过不锁住segment的方式统计各个Segment的count值得总和,如果两次值不同则将锁住整个ConcurrentHashMap然后进行计算。 +参见《java并发编程的艺术》P156 +http://www.cnblogs.com/ITtangtang/p/3948786.html + +## 线程的几种可用状态 +线程在运行周期里有6中不同的状态。 +1. New 新建 +2. RUNNABLE 运行状态,操作系统中运行与就绪两种状态统称运行中 +3. BLOCKED 阻塞状态 +4. WAITING 等待状态 +5. TIME_WAITING 超时等待 +6. TERMINATED 终止状态 + +## 同步方法和同步代码块的区别是什么 +1. 同步方法只能锁定当前对象或class对象, 而同步方法块可以使用其他对象、当前对象及当前对象的class作为锁。 +2. 从反编译后的结果看,对于同步块使用了monitorenter和monitorexit指令,而同步方法则是依靠方法上的修饰符ACC_SYNCHRONIZED来完成,但它们的本质都是对一个对象监视器进行获取,而这个获取过程是排他的。 + +### 显示锁ReentrantLock与内置锁synchronized的相同与区别 +相同:显示锁与内置锁在加锁和内存上提供的语义相同(互斥访问临界区) +不同: +1.使用方式,内置无需指定释放锁,简化锁操作。显示锁拥有锁获取和释放的可操作性。 +2.功能上:显示锁提供了其他很多功能如定时锁等待、可中断锁等待、公平性、尝试非阻塞获取锁、以及实现非结构化的加锁。(一个线程获取不到锁而被阻塞在synchronized之外时,对该线程进行中断操作,此时该线程的中断表示为会被修改,但线程依旧会被阻塞在synchronized上,等待获取锁。) +3.对死锁的处理:内置只能重启,显示可以通过设置超时获取锁来避免 +4.性能上:java1.5 显示远超内置,java1.6 显示锁稍微比内置好 + +7. atomicinteger和Volatile等线程安全操作的关键字的理解和使用 + +SOF你遇到过哪些情况。 + +21. 实现多线程的3种方法:Thread与Runable。 +**1)继承Tread类,重写run函数** +**2)实现Runnable接口** +**3)实现Callable接口** + + +22. 线程同步的方法:sychronized、lock、reentrantLock等。 + +23. 锁的等级:方法锁、对象锁、类锁。 +26. ThreadPool用法与优势。 + +26 Callable和Runnable的区别 +27. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。 + +29. foreach与正常for循环效率对比。 +31. 反射的作用于原理。 + +32. 泛型常用特点,List能否转为List。 + +36. 设计模式:单例、工厂、适配器、责任链、观察者等等。 +37. JNI的使用。 +java的代理是怎么实现的 +35. Java1.7与1.8新特性。 +lmbda表达式 +Java8新特性 +连接池使用使用什么数据结构实现 +实现连接池 +结束一条 Thread 有什么方法? interrupt 底层实现有看过吗?线程的状态是怎么样的?如果给你实现会怎么样做? +Java 中有内存泄露吗?是怎么样的情景?为什么不用循环计数? +java都有哪些加锁方式 +AIO与BIO的区别 +生产者与消费者,手写代码 +1. Java创建线程之后,直接调用start()方法和run()的区别 +2. 常用的线程池模式以及不同线程池的使用场景 +3. newFixedThreadPool此种线程池如果线程数达到最大值后会怎么办,底层原理。 +4. 多线程之间通信的同步问题,synchronized锁的是对象,衍伸出和synchronized相关很多的具体问题,例如同一个类不同方法都有synchronized锁,一个对象是否可以同时访问。或者一个类的static构造方法加上synchronized之后的锁的影响。 +5. 了解可重入锁的含义,以及ReentrantLock 和synchronized的区别 +6. 同步的数据结构,例如concurrentHashMap的源码理解以及内部实现原理,为什么他是同步的且效率高 +8. 线程间通信,wait和notify + +9. 定时线程的使用 +10. 场景:在一个主线程中,要求有大量(很多很多)子线程执行完之后,主线程才执行完成。多种方式,考虑效率。 +14. 并发、同步的接口或方法 +16. J.U.C下的常见类的使用。 ThreadPool的深入考察; BlockingQueue的使用。(take,poll的区别,put,offer的区别);原子类的实现。 +17. 简单介绍下多线程的情况,从建立一个线程开始。然后怎么控制同步过程,多线程常用的方法和结构 +19. 实现多线程有几种方式,多线程同步怎么做,说说几个线程里常用的方法 + + +## 写出生产者消费者模式。 + + public class ProducerConsumerPattern { + public static void main(String args[]){ + BlockingQueue sharedQueue = new LinkedBlockingQueue(); + Thread prodThread = new Thread(new Producer(sharedQueue)); + Thread consThread = new Thread(new Consumer(sharedQueue)); + prodThread.start(); + consThread.start(); + } + } + + //Producer Class in java + class Producer implements Runnable { + private final BlockingQueue sharedQueue; + public Producer(BlockingQueue sharedQueue) { + this.sharedQueue = sharedQueue; + } + @Override + public void run() { + for(int i=0; i<10; i++){ + try { + System.out.println("Produced: " + i); + sharedQueue.put(i); + } catch (InterruptedException ex) { + Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + + //Consumer Class in Java + class Consumer implements Runnable{ + private final BlockingQueue sharedQueue; + public Consumer (BlockingQueue sharedQueue) { + this.sharedQueue = sharedQueue; + } + @Override + public void run() { + while(true){ + try { + System.out.println("Consumed: "+ sharedQueue.take()); + } catch (InterruptedException ex) { + Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + + + + diff --git a/network/README.md b/network/README.md new file mode 100644 index 0000000..e69de29 diff --git a/network/all.md b/network/all.md new file mode 100644 index 0000000..bc957c8 --- /dev/null +++ b/network/all.md @@ -0,0 +1,451 @@ + +## OSI(开放系统互联参考模型)标准模型 +**物理层** +负责为数据端设备透明地传输原始比特流,并且定义了数据终端设备和数据通信设备的物理和逻辑链接方法。传输单位是比特。 +协议:RJ45、CLOCK、IEEE802.3 +设备:(中继器,集线器) +**数据链路层** +将网络层传下来的IP数据报组装成帧,并检测和矫正物理层产生的传输差错,使得链路对网络层显示一条无差错、可靠的数据传输线路。功能可以概括为成帧,差错控制、流量控制和传输管理 +协议有:HDLC(高级数据链路控制协议),PPP,STP,SDLC,CSMA(载波监听多路访问) +设备:(网桥,交换机) +**网络层** +负载在网络层上将数据封装成数据报,将数据报从源端传到目的端,同时进行路由选择,为分组交换网上的不同主机提供通信服务。关键问题是对分组进行选择,并实现流量控制、拥塞控制、差错控制和网际互联等功能。传输单位数据报。 +协议: IP,ICMP(因特网控制报文协议),IGMP(因特网组管理协议),ARP,RARP,OSPF(开放最短路径优先),IPX +设备:路由器 +**传输层** +负责主机中两个进程之间的通信,为端到端连接提供可靠的传输服务。为端到端连接提供流量控制、差错控制、服务质量、数据传输管理等服务。 +协议:TCP,UDP +**会话层** +会话层允许不同主机上各个进程之间的**会话**,会话层利用传输层提供的端到端的服务,向表示层提供它的增值服务。这种服务主要是为表示层实体或用户进程**建立连接**并在连接上提供**有序**地传输数据。 +协议:SQL、RPC(远程调用协议) +**表示层** +用于处理两个通信系统中交换信息的表示方式。如数据压缩,加密和解密等。 +协议:JPEG、MPEG、ASII +**应用层** +是TCP/IP的最高层,它是直接为应用进程服务的一层。当不同的应用进程数据通信或数据交换时,就去调用应用层的**不同协议实体**,让这些实体去调用TCP或者UDP层服务来进行网络传输。 +协议:FTP(21) TELNET(23) SMTP(25) DNS(53) TFTP(69) HTTP(80) SNMP(161),DHCP(动态主机配置协议) + +## TCP/IP 分层 +1)**网络接口层(接收和发送数据报)** +负责将数据报发送到**网络介质**上,以及从网络上接收TCP/IP数据报,相当于OSI的物理层和数据层。 +2)**网际层(数据报封装和路由寻址功能)** +主要负责寻址和对数据报的封装以及重要的路由选择功能。 +3)**传输层** +**负责在应用进程之间的“端到端”的通信**,即从某个应用进程传输到另一个应用进程。 +4)应用层 +是TCP/IP的最高层,它是直接为应用进程服务的一层。当不同的应用进程数据通信或数据交换时,就去调用应用层的**不同协议实体**,让这些实体去调用TCP或者UDP层服务来进行网络传输。 + +## 五层体系结构模型 +物理层,数据链路层,网络层,传输层,应用层 + +## IP地址分类 +A类地址:以0开头, 第一个字节范围:1~126(1.0.0.0 - 126.255.255.255); +B类地址:以10开头, 第一个字节范围:128~191(128.0.0.0 - 191.255.255.255); +C类地址:以110开头, 第一个字节范围:192~223(192.0.0.0 - 223.255.255.255); +D类地址:1110开头, 224~239 +E类地址:11110开头, 240~255 +主机号全为0表示本网络本身 +主机号全为1表示本网络广播地址 +127.0.0.0 环路自检地址,表示任意主机本身 +32个0 表示本网络的本主机 +32个1表示整个TCP/IP网络的广播地址,等效本网络的广播地址。 +专用地址: +10.0.0.0—10.255.255.255, 172.16.0.0—172.31.255.255, 192.168.0.0—192.168.255.255。(Internet上保留地址用于内部) +IP地址与子网掩码相与得到网络号 + +## ARP是地址解析协议,简单语言解释一下工作原理 +每个主机都存有一个ARP高速缓存,存放本局域网上各主机和路由器的IP地址到MAC地址的映射表,称为ARP表。使用ARP协议动态维护此表。 +ARP工作在网络层中,其工作原理是:当主机A欲向本局域网上的某个主机B发送IP数据报时,就先在其ARP高速缓存中查看有无主机B的IP地址。如果有可以查出其对应的硬件地址,再将此硬件地址写入**MAC帧**,然后通过局域网将该MAC帧发往此硬件地址。如果没有,就通过使用目的MAC地址为本网络的广播地址即32个1的帧来封装并广播ARP请求分组,可以使同一个局域网里的所有主机收到ARP请求。当主机B收到该ARP请求后,就会向主机A发出响应ARP分组,分组中包含主机B的IP与MAC地址的映射关系,主机A在收到后将此映射写入ARP缓存中,然后按查询到的硬件地址发送MAC帧。 +**ARP是解决同一个局域网上主机与路由器的IP地址和硬件地址的映射问题**。如果所要找的主机和源主机不在同一个局域网上,那么通过ARP协议找到一个位于本局域网上的某个路由器硬件地址,然后把分组发送给这个路由器,让这个路由器把分组转发给下一个网络。 + +## 描述RARP协议 +RARP是逆地址解析协议,作用是完成硬件地址到IP地址的映射.(请求是广播,应答是单播) +1)网络上的每台设备都会有一个独一无二的硬件地址,通常是由设备厂商分配的MAC地址。主机从网卡上读取MAC地址,然后在网络上发送一个RARP请求的广播数据包,请求的RARP服务器分配一个IP地址。 +2)本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址。 +3)如果存在,RARP服务器就给源主机发送一个响应数据包并将此IP地址提供给对方主机使用。 +4)如果不存在,RARP服务器对此不做任何的响应。 +5)源主机收到从RARP服务器的响应信息,就利用得到的IP地址进行通讯;如果一直没有收到RARP服务器的响应信息,表示初始化失败 + + +## 了解交换机、路由器、网关的概念,并知道各自的用途 +两者简介: +**路由器**是具有多个输入输出端口的专用计算机,**其任务是连接不同的网络并完成路由转发**。当源主机向目标主机发送数据报时,路由器先检查源主机与目标主机是否连接在一个网络上。如果源主机和目标主机在一个网络上则直接交付而无需通过路由器。但如果源主机和目标主机不在同一个网络上,则路由器转发表指出路由将数据报转发给下一个路由器,即间接交付。路由器隔离了广播域。 + +**交换机**:是多个端口的网桥,工作在数据链路上,**将两个或多个以太网连接起来成为更大的以太网。它能将网络分成小的冲突域,为每个工作站提供更高的带宽**。其原理是,检测从以太端口来的数据帧的源和目的地的MAC地址,然后与系统内部的动态查找表进行比较。若数据帧的MAC地址不在查找表中,则将该地址加入查找表中,并将数据帧发送给相应的端口。 + +区别: +**路由器**:工作在网络层,是能够连接不同的广域网形成更大的广域网。连接的是异构网络。根据IP地址转发。 +**交换机**:工作在数据链路层,是将以太网连接形成更大的以太网,同一个网络。根据MAC地址进行转发。 + +**网关(Gateway)**又称网间连接器、协议转换器。网关在网络层以上实现网络互连,是最复杂的网络互连设备,**仅用于两个高层协议不同的网络互连**。网关既可以用于广域网互连,也可以用于局域网互连。 网关是一种充当转换重任的计算机系统或设备。使用在不同的通信协议、数据格式或语言,甚至体系结构完全不同的两种系统之间,网关是一个翻译器。 + +## OSI 和TCP/IP的区别 +1. OSI精确定义了服务、协议和接口,符合面向对象程序设计思想。而TCP/IP在这些概念上没有明确区分。 +2. OSI在产生协议之前没有偏向任何特定的协议,通用性良好,但设计没有太多经验不知道将哪些功能放到哪一层。TCP/IP,首先出现协议,模型实际上是对已有协议的描述。因此不会出现协议不能匹配的模式。 +3. TCP/IP考虑了异构网的互联问题,而OSI只考虑用一种标准的公用数据网将各种不同系统互连。 +4. OSI在网络层支持无连接和面向连接的通信,但在传输层仅有面向连接通信。而TCP/IP则相反,在网际层仅有无连接服务,在传输层支持无连接和面向连接两种模式。 + +## TCP与UDP的区别 +1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接 +2、TCP提供可靠全双功的通信服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP则是不可靠信道,尽最大努力交付,即不保证可靠交付 +3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的. +4.TCP具有拥塞控制,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) +5、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 +6、TCP首部开销20字节;UDP的首部开销小,只有8个字节. + +### TCP应用场景 +:效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。 + +### UDP应用场景 +:效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。 + +## TCP对应的协议和UDP对应的协议 +**TCP对应的协议**: +(1) FTP(21):定义了文件传输协议,使用21端口。常说某某计算机开了FTP服务便是启动了文件传输服务。下载文件,上传主页,都要用到FTP服务。 +(2) Telnet(23):(远程登陆协议)它是一种用于远程登陆的端口,用户可以以自己的身份远程连接到计算机上,通过这种端口可以提供一种基于DOS模式下的通信服务。如以前的BBS是-纯字符界面的,支持BBS的服务器将23端口打开,对外提供服务。 +(3) SMTP(25):定义了简单邮件传送协议,现在很多邮件服务器都用的是这个协议,用于发送邮件。如常见的免费邮件服务中用的就是这个邮件服务端口,所以在电子邮件设置-中常看到有这么SMTP端口设置这个栏,服务器开放的是25号端口。 +(4) POP3(110):它是和SMTP对应,POP3用于接收邮件。通常情况下,POP3协议所用的是110端口。也是说,只要你有相应的使用POP3协议的程序(例如Fo-xmail或Outlook),就可以不以Web方式登陆进邮箱界面,直接用邮件程序就可以收到邮件(如是163邮箱就没有必要先进入网易网站,再进入自己的邮-箱来收信)。 +(5)HTTP(80)协议:是从Web服务器传输超文本到本地浏览器的传送协议。 +**UDP对应的协议**: +(1) DNS(53):用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。 +(2) RIP:路由信息协议,端口520 +(2) SNMP(161):简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。 +(3) TFTP(69)(Trival File Transfer Protocal),简单文件传输协议,该协议在熟知端口69上使用UDP服务。 + +http://www.jianshu.com/p/8be9b3204864 +http://www.jianshu.com/p/eab86c0d1612 +## 常见的路由选择协议,以及它们的区别 +常见的路由选择协议有:RIP协议、OSPF协议。 +RIP协议(路由信息协议):底层是贝尔曼福特算法,**基于距离向量的路由选择协议**,它选择路由的度量标准(metric)是**跳数**,最大跳数是15跳,如果大于15跳,它就会丢弃数据包。仅和相邻路由器交换当前路由器所知道的全部信息。是应用层协议,使用UDP传输数据,端口520 +OSPF协议(开放最短路由优先):底层是迪杰斯特拉算法,**是链路状态路由选择协议**,它选择路由的度量标准是**带宽**,延迟。向本自治系统中所有路由器发送与本路由器相邻的所有路由器的链路状态,但这只是路由器知道的部分信息。网络层协议,直接IP数据报传输. + +## TCP的三次握手与四次挥手过程,各个状态名称与含义,TIMEWAIT的作用 +**三次握手** +第一次握手:客户机首先向服务器的TCP发送一个连接请求报文段,这个特殊的报文段不含应用层数据,其首部中同步位SYN被设置为1。另外,客户机会随机选择一个起始序号seq=x(连接请求报文不携带数据,但要消耗一个序号); +第二次握手:服务器的TCP收到连接请求报文段后,如果同意建立连接,就向客户机发回确认,并为该TCP连接分配TCP缓存和变量。在确认报文段中,SYN和ACK位都被设置为1,确认号字段值为ack=x+1,并且服务器随机产生起始序号seq=y. 确认包同样不包含应用层数据。 +第三次握手:当客户机收到确认报文段后,还要向服务器给出确认,并且也要给该连接**分配缓存和变量**。这个报文段的确认为ACK被设置为1,序号段被设置为seq=x+1,确认号字段ack=y+1. 该报文段可以携带数据,如果不携带数据则不消耗序号。 +理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。因为TCP提供全双工通信,因此双方任何时候都可以发送数据。 + +四次挥手 +第一次挥手:客户机打算关闭连接,就向其TCP发送一个连接释放报文,并停止再发送数据,主动关闭TCP连接。该报文段的FIN标志位被设置为1,seq=u,它等于前面已经发送过的数据的最后一个字节的序号加1。 +第二次挥手:服务器收到连接释放报文段后即发出确认,确认号是ack=u+1,序号为v,等于它前面已经发送过的数据的最后一个字节序号加1.此时客户机到服务器这个方向的连接就释放了,TCP处于半关闭状态。ACK=1,seq=v,ack=u+1 +第三次挥手:若服务器已经没有要向客户机发送的数据,就通知TCP释放连接,此时发出FIN=1,确认号ack= u+1,序号seq =w,已经发送过的数据最后一个字节加1。确认为ACK=1. +(FIN = 1, ACK=1,seq = w, ack =u+1) +第四次挥手:客户机收到连接释放报文段后,必须发出确认。在确认报文段中,确认位ACK=1,序号seq=u+1,确认号ack=w+1. 此时连接还没有释放掉,必须经过实践等待计时器设置的时间2MSL(Max Segment Lifetime),后,客户机才进入连接关闭状态。 +(ACK=1,seq=u+1,ack=w+1) + +http://www.cnblogs.com/Jessy/p/3535612.html + +## 为什么会采用三次握手,若采用二次握手可以吗? +采用三次握手是为了防止失效的连接请求报文段再次传到服务器,因而产生错误。如果由于网络不稳定,虽然客户端以前发送的连接请求以到达服务方,但服务方的同意连接的应答未能到达客户端。则客户方要重新发送连接请求,若采用二次握手,服务方收到客服端重传的请求连接后,会以为是新的请求,就会发送同意连接报文,并新开进程提供服务,这样会造成服务方资源的无谓浪费。 +如果只采用一次的话,客户端不知道服务端是否已经收到自己发送的数据,则会不断地发送数据。为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手,为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手 + + +## 为什么四次挥手,主动方要等待2MSL后才关闭连接. +一、保证TCP协议的全双工连接能够可靠关闭. +主要为了确保对方能受到ACK信息. 如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。所以,Client不是直接进入CLOSED,而是要保持2MSL,如果在这个时间内又收到了server的关闭请求时可以进行重传,否则说明server已经受到确认包则可以关闭. + + +https://www.zhihu.com/question/36930631 +## TCP拥塞控制 +为了更好对TCP进行拥塞控制,因特网建议标准定义了以下四种算法:慢开始,拥塞避免,快重传,快恢复。 +首先在TCP要求发送端维护两个窗口: +1) 接收窗口rwnd,接收方根据当前缓存大小锁许诺的最新窗口值。 +2) 拥塞窗口 cwnd ,发送方根据自己估算的网络拥塞程度而设置的窗口值。 +发送窗口的上限是取这两者的最小值。 +慢开始: TCP刚连接好时,先令拥塞窗口cwnd =1 ,在每次收到一个对新报文段的确认时将cwnd加1. Cwnd的大小呈指数增长。 +拥塞避免算法: 当cwnd大于等于慢开始门限ssthresh时,cwnd窗口每次加1而不是加倍。当发送方检测到**超时事件**的发生时,就将慢开始门限设置为当前cwnd的一半,同时将cwnd设置为1. 这样的目的是迅速减少主机发送到网络的分组数,使得发生拥塞的路由器有足够的时间吧队列中积压的分组处理完毕。 +快重传:当发送方连续收到三个重复的ACK报文时,直接重传对方尚未收到的报文段,而不必等待那个报文段设置的重传计时器超时。 +快恢复:当发送端收到连续三个冗余的ACK时,就执行“乘法减小”算法,把慢开始门限ssthresh减半,cwnd设置为慢开始门限减半后的数值(与慢开始不同)。 + +## TCP滑动窗口与回退N针协议 +滑动窗口: +发送方都维持一组连续的允许发送的帧的序号称为发送窗口。同时接收方也维持一组连续的允许接收的帧序号,称为接收窗口。发送窗口是用来对发送方进行流量控制,接收窗口是用来控制接收那些数据帧不可以接收那些帧。 +在发送端,收到一个确认帧,发送窗口就向前滑动一个帧位置,当发送窗口没有可以发送的帧时,发送方就停止发送。直到接收方发送的确认帧使发送窗口向前移动。 +在接收端,只有收到数据帧的序号落在接收窗口内才将该帧收下,否则一律丢弃。每收到一个帧后就发送回确认帧。 + +后退N帧协议 +发送窗口大于1,接收窗口等于1.在后退N帧中,发送方不需要收到上一帧的ACK后才能发送下一帧,而是可以连续发送帧。当接收方检测出失序信息帧后,要求发送方重发最后一个正确接收的帧之后的所有未被确认的帧。源站每发完一帧就要为该帧设置超时计时器,如果在超时时间内没有收到确认帧则进行重发。服务端会采用累积确认的方式,不是每个帧都发确认,可以连续收到好几个正确帧后发回一个确认信息。接收方因为窗口为1,所以必须按序接收数据帧,如果某个序大于当前所期望的序号时就会连续发送3个ACK确认帧,要求客户端重传失序帧。 + +## TCP的可靠性如何保证 +在TCP的连接中,数据流必须以正确的顺序送达对方。TCP的可靠性是通过顺序编号和确认(ACK)来实现的。TCP在开始传送一个段时,为准备重传而首先将该段插入到发送队列之中,同时启动时钟。其后,如果收到了接受端对该段的ACK信息,就将该段从队列中删去。如果在时钟规定的时间内,ACK未返回,那么就从发送队列中再次送出这个段。TCP在协议中就对数据可靠传输做了保障,握手与断开都需要通讯双方确认,数据传输也需要双方确认成功,在协议中还规定了:分包、重组、重传等规则;而UDP主要是面向不可靠连接的,不能保证数据正确到达目的地。 + +## Http的报文结构 +http请求由三部分组成,分别是:请求行、消息报头、请求正文 +方法 [空格] URL [空格] 版本 +首部字段名: [空格] 值 +… +首部字段名: [空格] 值 +[空一行] +实体主体 + +HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文 +版本[空格] 状态码 [空格] 短语 +首部字段名: [空格] 值 +… +首部字段名: [空格] 值 +[空一行] +实体主体 +更多http内容参考:http://www.cnblogs.com/maanshancss/p/4503385.html + +## Http request的几种类型 +HTTP协议中共定义了八种方法或者叫“动作”来表明对Request-URI指定的资源的不同操作方式,具体介绍如下: +GET:向特定的资源发出请求。 +POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。 +PUT:向指定资源位置上传其最新内容。 +DELETE:请求服务器删除Request-URI所标识的资源。 +HEAD:请求读取由URL所标志的信息的首部。 +OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。 +TRACE:回显服务器收到的请求,主要用于测试或诊断。 +CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 + +## Http1.1和Http1.0的区别 +1)HTTP1.0 是短连接,HTTP1.1是长连接。 HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。如果一个html包含多个图片或资源时需要多次与服务器建立连接。 HTTP 1.1支持持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应答仍然需要使用各自的连接.HTTP 1.1采用了流水线的持久连接,即客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间。 +2)HTTP 1.0不支持Host请求头字段,WEB浏览器无法使用主机头名来明确表示要访问服务器上的哪个WEB站点,这样就无法使用WEB服务器在同一个IP地址和端口号上配置多个虚拟WEB站点。在HTTP 1.1中增加Host请求头字段后,WEB浏览器可以使用主机头名来明确表示要访问服务器上的哪个WEB站点,这才实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点。 +3)HTTP 1.1还提供了与身份认证、状态管理和Cache缓存等机制相关的请求头和响应头 +4)带宽优化。HTTP/1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了。例如,客户端只需要显示一个文档的部分内容,又比如下载大文件时需要支持断点续传功能,而不是在发生断连后不得不重新下载完整的包。 +HTTP/1.1中在请求消息中引入了range头域,它允许只请求资源的某个部分。 +5) HTTP/1.1增加了OPTIONS方法,它允许客户端获取一个服务器支持的方法列表 + +http://blog.csdn.net/forgotaboutgirl/article/details/6936982 + +## Http怎么处理长连接 +HTTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持。TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了带宽。 +使用长连接的HTTP协议,会在响应头有加入这行代码:Connection:keep-alive 。在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。 + +http://www.codeceo.com/article/http-long-connect.html + +## Cookie与Session的作用于原理 +会话跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。 +Cookie +Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就产生一个用户身份标识,然后在响应消息中将该标识号以Cookie的形式传递给浏览器.客户端浏览器会把Cookie保存起来。浏览器在以后每次访问该web服务器时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容. Cookie不能被浏览器共享 +Cookie具有不可跨域名性,例如浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。Cookie的maxAge决定着Cookie的有效期,单位为秒(Second) +**默认情况下,cookie是一个会话级别的,用户退出浏览器后被删除** + +Session +Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。 +如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。 +Session保存在服务器端。为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简。 + +session工作原理 +(1)web不会在客户端开始访问它时创建session,在访问特殊的程序并且该程序(servlet)决定与客户端开启会话时,服务器生成一个唯一值,称为Session ID(好像是通过取进程ID的方式取得的)。服务器开辟一块内存,对应于该Session ID。 +(2)服务器再将该Session ID写入浏览器的cookie。 +(3)服务器内有一进程,监视所有Session的活动状况,如果有Session超时或是主动关闭,服务器就释放改内存块。 +(4)当浏览器连入服务器时并请求Session时,服务器就读浏览器Cookie中的Session ID。 +(5)然后,服务检查该Session ID所对应的内存是否有效。 +(6)如果有效,就读出内存中的值。 +(7)如果无效,就建立新的Session。 + +**tomcat默认设置是30分钟** +具体设置很简单,方法有三种: +(1)在主页面或者公共页面中加入:session.setMaxInactiveInterval(900); +参数900单位是秒,即在没有活动15分钟后,session将失效。设置为-1将永不关闭。 +这里要注意这个session设置的时间是根据服务器来计算的,而不是客户端。所以如果是在调试程序,应该是修改服务器端时间来测试,而不是客户端。 +(2)也是比较通用的设置session失效时间的方法,就是在项目的web.xml中设置 + +15 + +这里的15也就是15分钟失效. +(3)直接在应用服务器中设置,如果是tomcat,可以在tomcat目录下conf/web.xml中 +找到元素,tomcat默认设置是30分钟,只要修改这个值就可以了。 +需要注意的是如果上述三个地方如果都设置了,有个优先级的问题,从高到低: +(1)-->(2)--->(3) + +生命周期: +1)tomcat默认是30分钟 +2)因为session和cookie有关系,客户端浏览器通过cookie维持该会话,cookie不支持跨浏览器共享,意味着如果把浏览器关该会闭的话该会话就会失效(但服务端还保存着)。因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。 + +http://qiusuoge.com/10576.html +http://blog.csdn.net/colzer/article/details/8686966 +http://www.cnblogs.com/binger/archive/2013/03/19/2970171.html + +## 电脑上访问一个网页,整个过程是怎么样的:DNS、HTTP、TCP、OSPF、IP、ARP +1) 浏览器分析连接指向的页面URL(http://www.baidu.com) +2) 浏览器向DNS请求www.baidu.com.的IP地址 +3) 域名系统DNS解析出百度官网的服务器IP地址 +4) 浏览器与该服务器建立TCP连接(默认端口80) +5) 浏览器发出HTTP请求获取指定页面。 +6) 服务器通过HTTP响应把文件对应页面发送给浏览器。 +7) TCP连接释放。 +8) 浏览器将文件进行解析,并将web网页显示给用户。 + +## get提交和post提交的区别 +1.根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的。 +  (1).所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。 +  (2).幂等的意味着对同一URL的多个请求应该返回同样的结果。 +2.根据HTTP规范,POST表示可能修改变服务器上的资源的请求。还是新闻以网站为例,读者对新闻发表自己的评论应该通过POST实现,因为在评论提交后站点的资源已经不同了,或者说资源被修改了。 +3.GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连.POST把提交的数据则放置在是HTTP包的包体中。 +4.GET安全性较低,POST安全性较高。因为GET在传输过程,数据被放在请求的URL中,而如今现有的很多服务器、代理服务器或者用户代理都会将请求URL记录到日志文件中,然后放在某个地方,这样就可能会有一些隐私的信息被第三方看到。另外,用户也可以在浏览器上直接看到提交的数据,一些系统内部消息将会一同显示在用户面前。 +5.get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。 +6.在FORM(表单)中,Method默认为"GET" + + +幂等(idempotent、idempotence)是一个数学或计算机学概念,常见于抽象代数中。 +  幂等有一下几种定义: +  对于单目运算,如果一个运算对于在范围内的所有的一个数多次进行该运算所得的结果和进行一次该运算所得的结果是一样的,那么我们就称该运算是幂等的。比如绝对值运算就是一个例子,在实数集中,有abs(a)=abs(abs(a))。 +  对于双目运算,则要求当参与运算的两个值是等值的情况下,如果满足运算结果与参与运算的两个值相等,则称该运算幂等,如求两个数的最大值的函数,有在在实数集中幂等,即max(x,x) = x。 +详见: http://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html +http://www.cnblogs.com/skynet/archive/2010/05/18/1738301.html + +## https如何加密的 +HTTPS介绍: +HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议 它是一个安全通信通道,它基于HTTP开发,用于在客户计算机和服务器之间交换信息。**它使用安全套接字层(SSL)进行信息交换**,简单来说它是HTTP的安全版。 它是由Netscape开发并内置于其浏览器中,使用SSL在发送方把原始数据进行加密,然后在接受方进行解密保证数据的安全性.然而,加密和解密过程需要耗费系统大量的开销,严重降低机器的性能. +HTTPS实际上就是SSL over HTTP,它使用默认端口**443**,而不是像HTTP那样使用端口80来和TCP/IP进行通信。HTTPS协议使用SSL在发送方把原始数据进行加密,然 后在接受方进行解密,加密和解密需要发送方和接受方通过交换共知的密钥来实现,因此,所传送的数据不容易被网络黑客截获和解密.然而,加密和解密过程需要耗费系统大量的开销,严重降低机器的性能,相关测试数据表明使用HTTPS协议传输数据的工作效率只有使用HTTP协议传输的十分之一。 + +如何实现加密:(其实就是sssl的交互过程) +HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。具体是如何进行加密,解密,验证的 + +http://blog.csdn.net/binyao02123202/article/details/8049446 +http://www.codeceo.com/article/why-http-better-than-https.html + +## http和https的区别 +1. https协议需要到ca申请证书,一般免费证书很少,需要交费。 +2. http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议 http和https使用的是完全不同的连接方式用的端口也不一样:前者是80,后者是443。 +3. http的连接很简单,是无状态的 HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全 HTTPS解决的问题. +http://www.admin5.com/article/20150708/608526.shtml + +## SSL协议及完整交互过程 +SSL是Netscape公司所提出的安全保密协议,在浏览器(如Internet Explorer、Netscape Navigator)和Web服务器(如Netscape的Netscape Enterprise Server、ColdFusion Server等等)之间构造安全通道来进行数据传输,SSL运行在TCP/IP层之上、应用层之下,为应用程序提供加密数据通道,它采用了RC4、MD5 以及RSA等加密算法,使用40 位的密钥,适用于商业信息的加密。 + +开始加密通信之前,客户端和服务器首先必须建立连接和交换参数,这个过程叫做握手(handshake)。 +**1 客户端发出请求(ClientHello)** +首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。在这一步,客户端主要向服务器提供以下信息。 +(1) 支持的协议版本,比如TLS 1.0版。 +(2) 一个客户端生成的随机数,稍后用于生成"对话密钥"。 +(3) 支持的加密方法,比如RSA公钥加密。 +(4) 支持的压缩方法。 + +**4.2 服务器回应(SeverHello)** +服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容: +(1) 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。 +(2) 一个服务器生成的随机数,稍后用于生成"对话密钥"。 +(3) 确认使用的加密方法,比如RSA公钥加密。 +(4) 服务器证书。(公钥) +**4.3 客户端回应** +客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。 +如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。 +(1) 一个随机数。该随机数用服务器公钥加密,防止被窃听。 +(2) 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。 +(3) 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。 +上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称"pre-master key"。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把"会话密钥"。(会话秘钥是采用对称加密方式,而这里的公钥是采用非对称加密) +**4 服务器的最后回应** +服务器通过私钥解密收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的"会话密钥"。然后,向客户端最后发送下面信息。 +(1)编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。 +(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。 +至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用"会话密钥"加密内容。 + +至于为什么一定要用三个随机数,来生成"会话密钥",dog250解释得很好: +"不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。 +对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,三个随机数通过一个密钥导出器最终导出一个对称密钥。 +pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,那么pre master secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,因此必须引入新的随机因素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,一个伪随机可能完全不随机,可是是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。" + +http://limboy.me/tech/2011/02/19/https-workflow.html +http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html +http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html +http://kb.cnblogs.com/page/162080/ + +## https处理的一个过程,对称加密和非对称加密(参考上一条,这一条就是通俗的表示) +1. SSL原理很简单。当你的浏览器向服务器请求一个安全的网页(通常是 https://) +2. 服务器就把它的证书和公匙发回来 +3. 浏览器检查证书是不是由可以信赖的机构颁发的,确认证书有效和此证书是此网站的。 +4. 浏览器使用公钥加密了一个随机对称密钥,包括加密的URL一起发送到服务器。 +5. 服务器用自己的私匙解密了你发送的钥匙。然后用这把对称加密的钥匙给你请求的URL链接解密。 +6. 服务器用你发的对称钥匙给你请求的网页加密。你也有相同的钥匙就可以解密发回来的网页了 + +加密算法绝大部分都属于以下两种加密类型之一: +对称加密:加密解密用的是同样的“钥匙” +非对称加密:加密解密用的是不同的“钥匙” + +对称加密 +Alice 在盒子里放有信息,盒子上有挂锁,她有钥匙。通过邮局她把这个盒子寄给Bob。Bob收到盒子后,用相同的钥匙打开盒子(钥匙之前就得到了,可能是Alice面对面给他的)。然后Bob可以用同样的方法回复。 + +非对称加密 +Bob和Alice各有自己的盒子。Alice要跟Bob秘密通信,她先让Bob把开着的盒子通过邮局发给她。Alice拿到盒子后放入信息锁上,然后发给Bob。Bob就可以用他自己的钥匙打开了。回复的话就用同样的方法。 +非对称算法在加密和解密时用的是不同的钥匙。信息接受者有两把钥匙:一把“公匙”,一把“私匙”。公匙是给信息发送者用来加密的,私匙是自己用来解密的。这样最大的好处是:不必通过不安全的渠道发送私密的东西。公匙本来就是给别人用的,不用藏好。你的私匙在你产生私匙的电脑里保存着。 + +http://article.yeeyan.org/view/90729/174903/ + +## Http和https的区别 + - http是HTTP协议运行在TCP之上。所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份 + - https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上。所有传输的内容都经过加密,加密采用对 + - 加密,但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份 + - 如果配置了客户端验证,服务器方也可以验证客户端的身份。 + - https协议需要到ca申请证书,一般免费证书很少,需要交费。 + - http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议 + - http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。 + - http的连接很简单,是无状态的 + - HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全 + +https://github.com/Wl201314/MartinBlog/tree/master/Blog/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80 +## DNS域名系统,简单描述其工作原理。 +域名系统DNS是因特网使用的命名系统,用于把便于人们记忆的含有特定含义的主机名转换为便于机器处理的IP地址。DNS系统采用C/S模式,其协议运行在UDP上,使用53号端口。从概念上讲DNS可以分为3个部分:层次域名空间,域名服务器和解析器。 + +域名解析是把域名映射成IP地址或者把IP地址映射成域名的过程。域名解析有两种方式:递归查询和迭代查询。 +递归查询:如果本地主机询问的本地域名服务器不知道被查询的域名IP地址,那么**本地域名服务器**就以DNS客户的身份,向**根域名服务器**继续发出查询请求报文,而不是自己进行下一步非查询。根域名服务器会继续向**顶级域名服务器**查询请求,如果存在则直接返回给根服务器,否则顶级域名服务器向对应的**权限域名服务器**查询。是一种递归的查询方式。 + +迭代查询:如果本地主机询问的本地域名服务器不知道被查询的域名IP地址,那么本地域名服务器就以DNS客户的身份,向根域名服务器继续发出查询请求报文。跟服务器收到请求报文时,要么给出所有查询的IP地址,要么告诉本地域名服务器下一步需要查询的顶级域名服务器。然后本地服务器向这个顶级域名服务器进行后续的查询。同样的顶级域名服务器收到查询报文后,要么给出所要查询的IP地址,要么告诉本地域名服务器下一步应该查找那个权限域名服务器。 + +## 面向连接和非面向连接的服务的特点是什么? +面向连接的服务:通信双方进行通信之前,必须先建立连接,在通信过程中,整个连接情况一直都是被实时地监控和管理。当通信结束后,则应释放这个连接。 +无连接服务:两个实体之间的通信不需要先建立好连接,需要通信的时候,直接将信息发送到”网络”中,让该信息的传递在网上尽力而为地往目的地传送。 + +## 端口及对应的服务? +FTP 21 SSH 22 telnet 23 SMTP 25 Domain(域名服务器) 53 HTTP 80 +POP3 110 NTP(网络时间协议)123 MySQL数据库服务3306 https 443 SNMP(161) TFTP(69) +Shell或 cmd 514 POP-2 109 SQL Server 1433 + +## 各种协议 +ICMP协议: 因特网控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。 +TFTP协议: 是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。 +HTTP协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。 +DHCP协议: 动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段。 +NAT协议:网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术, +DHCP协议:一个局域网的网络协议,使用UDP协议工作,用途:给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。 + +## Ping的整个过程 +同一个局域网中: +1、Pc1在应用层发起个目标IP位IP2的Ping请求。 +2、传输层接到上层请求的数据,将数据分段并加上UDP报头。下传到Internet层。 +3、 网际层接收来处上层的数据后,根据ICMP协议进行封装,添加PC1的IP为源IP为和PC2IP为目标IP后封装成数据包。下传到网络接口层。 +4、网络接口层接收数据包后,进行封装,源MAC地址为PC1的MAC地址,目标MAC地址则查询自己的ARP缓存表获取。如果PC1 arp缓存表中没有目标IP对应的MAC地址,则PC1发出一个ARP广播报文。ARP报文中源MAC地址为Pc1mac地址,源IP地址为pc1 IP,所要请求的是PC2的IP对应的mac地址。 +5、PC2收到ARP广播后,进行解封装,发现所请求的MAC地址是自己的。则PC2将PC1的mac地址写入arp缓存表中。然后向PC1发送一个 ARP应答单播。该单播消息包括目标IP为PC1ip,目标Mac为pc1mac地址,源IP为PC2的IP,源Mac为pc2的Mac。 +6、Pc1接收到PC2的arp应答报文后,将Pc2的MAC地址存入arp缓存中,并将Pc2的Mac地址作为目标地址封装到数据帧中。发给下层进行网络传输。 +7、PC2接收这个帧后,在网络接口层查看目标mac地址是否指向自己。是,PC2则将帧头去掉,向上层传输。 +8、Pc2网际层接收到这个信息包,查看包头,发现目标IP和自己匹配,则解封装,将数据向上层传输。 +9、传输层接收来自下层的Ping请求的UDP报文,则去掉UDP报头,向应用层传送。 +10、应用层收到ping请求后,发送一个PIng回应报文给PC1 +http://blog.chinaunix.net/uid-26758209-id-3146224.html + +## ICMP协议: +1) ICMP允许主机或路由报告差错情况和提供有关异常情况。ICMP是因特网的标准协议,但ICMP不是高层协议,而是IP层的协议。通常ICMP报文被IP层或更高层协议(TCP或UDP)使用。一些ICMP报文把差错报文返回给用户进程。 +2) ICMP报文作为IP层数据报的数据,加上数据报的首部,组成数据报发送出去。 +3) ICMP报文的种类有两种,即ICMP差错报告报文和ICMP询问报文。 + +差错报告 +ICMP差错报告报文共有5种: + 终点不可达:终点不可达分为:网络不可达,主机不可达,协议不可达,端口不可达,需要分片但DF比特已置为1,以及源路由失败等六种情况,其代码字段分别置为0至5。当出现以上六种情况时就向源站发送终点不可达报文。 +说明: +端口不可达:UDP的规则之一是:如果收到UDP数据报而且目的端口与某个正在使用的进程不相符,那么UDP返回一个ICMP不可达报文。 +源站抑制:当路由器或主机由于拥塞而丢弃数据报时,就向源站发送源站抑制报文,使源站知道应当将数据报的发送速率放慢。 +时间超过:当路由器收到生存时间为零的数据报时,除丢弃该数据报外,还要向源站发送时间超过报文。当目的站在预先规定的时间内不能收到一个数据报的全部数据报片时,就将已收到的数据报片都丢弃,并向源站发送时间超过报文。 +参数问题:当路由器或目的主机收到的数据报的首部中的字段的值不正确时,就丢弃该数据报,并向源站发送参数问题报文。 +改变路由(重定向)路由器将改变路由报文发送给主机,让主机知道下次应将数据报发送给另外的路由器。 + +询问报文 +ICMP询问报文有四种 +**回送请求和回答**,ICMP回送请求报文是由主机或路由器向一个特定的目的主机发出的询问。收到此报文的机器必须给源主机发送ICMP回送应答报文。这种询问报文用来测试目的站是否可达以及了解其有关状态 +**时间戳请求和回答**:ICMP时间戳请求允许系统向另一个系统查询当前的时间。该ICMP报文的好处是它提供了毫秒级的分辨率,而利用其他方法从别的主机获取的时间只能提供秒级的分辨率。请求端填写发起时间,然后发送报文。应答系统收到请求报文时填写接收时间戳,在发送应答时填写发送时间戳。大多数的实现是把后面两个字段都设成相同的值。 +**掩码地址请求和回答**:主机使用ICMP地址掩码请求报文可向子网掩码服务器得到某个接口的地址掩码。 +**路由器询问和通过**:主机使用ICMP路由器询问和通过报文可了解连接在本网络上的路由器是否正常工作。主机将路由器询问报文进行广播(或多播)。收到询问报文的一个或几个路由器就使用路由器通过报文广播其路由选择信息 + +## C/S模式下使用socket通信,几个关键函数 + +## 知识点: +1) SSP通过七号信令网实现与SAU的连接,而SCP与SAU之间通过TCP/IP协议连接。 +2) 重组发生在目的主机,分片发生在路由器上 + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + diff --git a/network/section01.md b/network/section01.md new file mode 100644 index 0000000..e69de29 diff --git a/os/README.md b/os/README.md new file mode 100644 index 0000000..e69de29 diff --git a/os/chapter1.md b/os/chapter1.md new file mode 100644 index 0000000..e4d9eea --- /dev/null +++ b/os/chapter1.md @@ -0,0 +1,2 @@ +# 第一章 操作系统面试基础 + diff --git a/os/product.md b/os/product.md new file mode 100644 index 0000000..83fa5d9 --- /dev/null +++ b/os/product.md @@ -0,0 +1,89 @@ +## 背景 +生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。解决生产者/消费者问题的方法可分为两类: +(1)采用某种机制保护生产者和消费者之间的同步; +(2)在生产者和消费者之间建立一个管道。第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。 + +## 实现方式 +在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。 +1) wait() / notify()方法 +2) await() / signal()方法 +3) BlockingQueue阻塞队列方法 +4) PipedInputStream / PipedOutputStream + + +## wait()/notify() + + + +### blockingQueue方式 + + public class ProducerConsumer { + static class MsgQueueManager { + /** + * 消息总队列 + */ + public final BlockingQueue messageQueue; + MsgQueueManager(BlockingQueue messageQueue) { + this.messageQueue = messageQueue; + } + MsgQueueManager() { + this.messageQueue = new LinkedBlockingQueue(); + } + + public void put(T msg) { + try { + messageQueue.put(msg); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public T take() { + try { + return messageQueue.take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return null; + } + } + + static class Producer extends Thread { + private MsgQueueManager msgQueueManager; + public Producer(MsgQueueManager msgQueueManager) { + this.msgQueueManager = msgQueueManager; + } + @Override + public void run() { + msgQueueManager.put(new Object()); + } + } + static class Consumer extends Thread{ + private MsgQueueManager msgQueueManager; + public Consumer(MsgQueueManager msgQueueManager) { + this.msgQueueManager = msgQueueManager; + } + @Override + public void run() { + Object o = msgQueueManager.take(); + } + } + + public static void main(String[] args) { + MsgQueueManager msgQueueManager = new MsgQueueManager(); + + for(int i = 0; i < 100; i ++) { + new Producer(msgQueueManager).start(); + + } + for(int i = 0; i < 100; i ++) { + new Consumer(msgQueueManager).start(); + + } + } + } + + + +http://www.infoq.com/cn/articles/producers-and-consumers-mode/ +http://blog.csdn.net/monkey_d_meng/article/details/6251879 diff --git a/os/section1.md b/os/section1.md new file mode 100644 index 0000000..e69de29 diff --git a/os/section2.md b/os/section2.md new file mode 100644 index 0000000..60e9372 --- /dev/null +++ b/os/section2.md @@ -0,0 +1,206 @@ +# 常见面试题 +1. 进程和线程的区别。 +2. 死锁的必要条件,怎么处理死锁。 +3. Window内存管理方式:段存储,页存储,段页存储。 +4. 进程的几种状态。 +5. IPC几种通信方式。 +6. 什么是虚拟内存。 +7. 虚拟地址、逻辑地址、线性地址、物理地址的区别。 + + + + + + +## 1.进程和线程 +### 1.1 线程 +#### 1.1.1 概念 +是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。 +#### 1.1.2 好处 +1)易于调度。 +2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。 +3)开销少。创建线程比创建进程要快,所需开销很少。。 +4)利于充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。 + +#### 1.1.3 线程状态 +联系java中线程的几种状态 +java thread的运行周期中, 有几种状态, 在 java.lang.Thread.State 中有详细定义和说明: +**1)NEW** 状态是指线程刚创建, 尚未启动 +**2)RUNNABLE** 状态是线程正在正常运行中,当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等,这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等 +**3)BLOCKED** 这个状态下, 是在多个线程有同步操作的场景,比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法,也就是这里是线程在等待进入临界区 +**4)WAITING** 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束 +**5)TIMED_WAITING** 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态 +**6) TERMINATED** 这个状态下表示该线程的run方法已经执行完毕了,基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收) +http://www.blogjava.net/santicom/archive/2011/09/01/357765.html + +### 1.2 进程 +#### 1.2.1 概念 +进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。 +#### 1.2.2 进程与程序的区别 +1.进程是程序的一次运行活动,属于一种动态的概念。程序是一组有序的静态指令,是一种静态的概念。 +2.一个进程可以执行一个或多个程序。 +3.程序可以作为一种软件资源长期保持着,而进程则是一次执行过程,它是暂时的,是动态地产生和终止的。 +4.进程更能真实地描述并发,而程序不能。 +5.进程由程序和数据两部分组成,进程是竞争计算机系统有限资源的基本单位 +6.进程具有创建其他进程的功能;而程序没有。 +7.进程还具有并发性和交往性,这也与程序的封闭性不同 + +#### 1.2.3 进程的几种状态 +进程的基本状态及状态之间的关系 +**1)创建状态(New)**:进程正在创建过程中,还不能运行。操作系统在创建状态要进行的工作包括分配和建立进程控制块表项、建立资源表格(如打开文件表)并分配资源、加载程序并建立地址空间表等。 +**2)就绪状态(Ready)**:进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排人低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。 +**3)执行状态**:进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态; 在多处理机系统中,则有多个进程处于执行状态。 +**4)阻塞状态**:正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即进程的执行受到阻塞,把这种暂停状态称为阻塞状态,有时也称为等待状态或封锁状态。致使进程阻塞的典型事件有:请求I/O,申请缓冲空间等。通常将这种处于阻塞状态的进程也排成一个队列。有的系统则根据阻塞原因的不同而把处于阻塞状态的进程排成多个队列。 +**5)退出状态(Exit)**:进程已结束运行,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中收集有关信息(如记帐和将退出代码传递给父进程)。 + +#### 1.2.4 作业(进程)调度算法 +**1)先来先服务调度算法(FCFS)** +每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。 +**2)短作业(进程)优先调度算法(SPF)** +短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。缺点:长作业的运行得不到保证 +**3)优先权调度算法(HPF)** +当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程,这时,又可进一步把该算法分成如下两种。 +可以分为:1.非抢占式优先权算法 2.抢占式优先权调度算法 +**4)高响应比优先调度算法(HRN)** +每次选择高响应比最大的作业执行,响应比=(等待时间+要求服务时间)/要求服务时间。 +(1) 如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业。 +(2) 当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务。 +(3) 对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高,从而也可获得处理机。简言之,该算法既照顾了短作业,又考虑了作业到达的先后次序,不会使长作业长期得不到服务。因此,该算法实现了一种较好的折衷。当然,在利用该算法时,每要进行调度之前,都须先做响应比的计算,这会**增加系统开销**。 +**5)时间片轮转法(RR)** +在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片。时间片的大小从几ms到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。换言之,系统能在给定的时间内响应所有用户的请求。 +**6)多级反馈队列调度算法** +它是目前被公认的一种较好的进程调度算法。 +(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。 +(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。 +(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。 +http://blog.csdn.net/luyafei_89430/article/details/12971171 + +#### 1.2.4 作业与进程的区别 +一个进程是一个程序对某个数据集的执行过程,是分配资源的基本单位。作业是用户需要计算机完成的某项任务,是要求计算机所做工作的集合。一个作业的完成要经过作业提交、作业收容、作业执行和作业完成4个阶段。而进程是对已提交完毕的程序所执行过程的描述,是资源分配的基本单位。其主要区别如下。 +1) 作业是用户向计算机提交任务的任务实体。在用户向计算机提交作业后,系统将它放入外存中的作业等待队列中等待执行。而进程则是完成用户任务的执行实体,是向系统申请分配资源的基本单位。任一进程,只要它被创建,总有相应的部分存在于内存中。 +2) 一个作业可由多个进程组成,且必须至少由一个进程组成,反过来则不成立。 +3) 作业的概念主要用在批处理系统中,像UNIX这样的分时系统中就没有作业的概念。而进程的概念则用在几乎所有的多道程序系统中。 + +http://blog.csdn.net/qq_32744005/article/details/51817637 +### 1.3 进程和线程的关系: +(1) 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。 +(2) 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 +(3) 处理机分给线程,即真正在处理机上运行的是线程。 +(4) 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体. + +### 1.4 进程与线程的区别 +**1)调度**:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位 +**2)并发性:**不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行 +**3)拥有资源:**进程是拥有资源的一个独立单位,线程不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源. +**4)系统开销:**在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。 +参考: http://blog.csdn.net/dazhong159/article/details/7896070 + + +## 2.1 IPC几种通信方式(进程间的通信方式) +**管道( pipe )**:管道是一种半双工的通信方式,数据只能单向流动,而且只能在**具有亲缘关系**的进程间使用。进程的亲缘关系通常是指父子进程关系。 +**命名管道 (named pipe)**: 命名管道也是半双工的通信方式,它克服了管道没有名字的限制,并且它允许**无亲缘关系进程间**的通信。命令管道在文件系统中有对应的文件名,命名管道通过命令mkfifo或系统调用mkfifo来创建。 +**信号量( semophore )**: 信号量是一个计数器,可以用来控制多个进程对**共享资源**的访问。它常作为一种**锁机制**,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 +**消息队列( message queue )**: 消息队列是由**消息的链表**,存放在内核中并由消息队列标识符标识。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 +**信号 ( sinal )**:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。除了用于进程通信外,进程还可以发送信号给进程本身。 +**共享内存( shared memory )**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。 +**套接字( socket )**: 也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。 + + +## 3.死锁的必要条件,怎么处理死锁。 +### 3.1 死锁概念 + 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 +### 3.2 活锁 +活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。 +### 3.3 死锁条件 +1. **互斥条件**:一个资源每次只能被一个进程使用 +2. **不可剥夺条件**:进程已获得的资源,在末使用完之前,不能强行剥夺 +3. **请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放 +4. **循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系. + +### 3.4 死锁预防 +1. **破坏互斥条件**。允许某些进程(线程)同时访问某些资源,但有的资源不允许同时被访问如打印机等。 +2. **破坏不可抢占条件**:即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已经占有某些资源,它又重新占有全部资源,以后再重新申请,它所释放的资源可以分配给其他进程。这种预防方法实现起来困难,会降低系统性能。 +3. **破坏占有且申请条件**。可以实行预先分配策略,即进程在运行前一次性地向系统申请它所需要的全部资源。如果当前进程所需的全部资源得不到满足,则不分配任何资源。只有当系统能够满足当前的全部资源得到满足时,才一次性将所有申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又重新申请资源的现象,因此不会发生死锁。但是有以下缺点: +1) 在许多情况下,一个进程在执行之前不可能知道它所需的全部资源。这是由于进程在执行时是动态的,不可预测的。 +2)资源利用率低。无论所分配资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间一直占有它们,造成长期占有。 +3) 降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数必然少了。 +4. **破坏循环等待条件**。实行资源有序分配策略。采用这种策略即把资源事先分类编号,按号分配。所有进程对资源的请求必须严格按资源需要递增的顺序提出。进程占用小好资源,才能申请大号资源,就不会产生环路。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高,但是也存在以下缺点: +1)限制了进程对资源的请求,同时系统给所有资源合理编号也是件困难事,并增加了系统开销。 + +### 3.4 死锁的避免 +1. **银行家算法**:该算法需要检查申请者对资源的最大需求量,如果系统现存的各类资源可以满足申请者的请求,就满足申请者的请求。这样申请者就可很快完成其计算,然后释放它占用的资源,从而保证了系统中的所有进程都能完成,所以可避免死锁的发生 + +### 3.5 死锁的解除 +一旦检测出死锁,就应立即釆取相应的措施,以解除死锁。 +死锁解除的主要方法有: +1) **资源剥夺法**。挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。 +2) **撤销进程法**。强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。 +3) **进程回退法**。让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。 + +## 4. Window内存管理方式:页存储、段存储、段页存储。 +### 4.1 分页存储管理 +#### 4.1.1 基本思想 +将程序的逻辑地址空间划分为固定大小的**页(page)**,而物理内存划分为同样大小的**页框(page frame)**。程序加载时,可将任意一页放人内存中任意一个页框,这些页框不必连续,从而实现了离散分配。该方法需要CPU的硬件支持,来实现逻辑地址和物理地址之间的映射。在页式存储管理方式中地址结构由两部构成,前一部分是页号,后一部分为页内地址w(位移量)。 + +逻辑地址道物理地址变化原理:CPU中的内存管理单元(MMU)按逻辑页号通过查进程页表得到物理页框号,将物理页框号与页内地址相加形成物理地址(见图4-4)。 +#### 4.1.2 页式管理方式的优点 +1)没有外碎片,每个内碎片不超过页大比前面所讨论的几种管理方式的最大进步是, +2)一个程序不必连续存放。 +3)便于改变程序占用空间的大小(主要指随着程序运行,动态生成的数据增多,所要求的地址空间相应增长)。 +#### 4.1.3 缺点 +要求程序全部装入内存,没有足够的内存,程序就不能执行。 + +### 4.2 分段存储 +#### 4.2.1 思想 +将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。 +在为某个段分配物理内存时,可以采用首先适配法、下次适配法、最佳适配法等方法。 +在回收某个段所占用的空间时,要注意将收回的空间与其相邻的空间合并。 +#### 4.2.2 地址映射 +在段式 管理系统中,整个进程的地址空间是二维的,即其逻辑地址由段号和段内地址两部分组成。为了完成进程逻辑地址到物理地址的映射,处理器会查找内存中的段表,由段号得到段的首地址,加上段内地址,得到实际的物理地址(见图4—5)。这个过程也是由处理器的硬件直接完成的,操作系统只需在进程切换时,将进程段表的首地址装入处理器的特定寄存器当中。这个寄存器一般被称作段表地址寄存器。 + +### 4.3 分页和分段的主要区别 +a)、页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率;段则是信息的逻辑单位,它含有一组其意义相对完整的信息,分段的目的是为了能更好地满足用户的需要。 +b)、页的大小固定且由系统决定,由系统把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而在系统中只能有一种大小的页面;而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时,根据信息的性质来划分。 +c)、分页的作业地址空间是一维的,即单一的线性地址空间,程序员只需利用一个记忆符,即可表示一个地址;而分段的作业地址空间则是二维的,程序员在标识一个地址是,即需给出段名,又需给出段内地址。 +d)、分页信息很难保护和共享、分段存储按逻辑存储所以容易实现对段的保存和共享。 + +http://blog.sina.com.cn/s/blog_a46817ff0101hjzp.html +### 4.4 段页存储 +它首先将程序按其逻辑结构划分为若干个大小不等的逻辑段,然后再将每个逻辑段划分为若干个大小相等的逻辑页。主存空间也划分为若干个同样大小的物理页。辅存和主存之间的信息调度以页为基本传送单位,每个程序段对应一个段表,每页对应一个页表。CPU访问时,段表指示每段对应的页表地址,每一段的页表确定页所在的主存空间的位置,最后与页表内地址拼接,确定CPU要访问单元的物理地址。 + +段页存储管理方式综合了段式管理和页式管理的优点,但需要经过两级查表才能完成地址转换,消耗时间多。 + +## 5. 什么是虚拟内存。 +**物理内存**:在应用中,自然是顾名思义,物理上,真实的插在板子上的内存是多大就是多大了。而在CPU中的概念,物理内存就是CPU的地址线可以直接进行寻址的内存空间大小。 +**虚拟内存**:它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。 + +## 6. 虚拟地址、逻辑地址、线性地址、物理地址的区别。 +**虚拟地址**:指的是由程序产生的由段选择符和段内偏移地址两个部分组成的地址。为什么叫它是虚拟的地址呢?因为这两部分组成的地址并没有直接访问物理内存,而是要通过分段地址的变换机构处理或映射后才会对应到相应的物理内存地址。 +**逻辑地址**:指由程序产生的与段相关的偏移地址部分。不过有些资料是直接把逻辑地址当成虚拟地址,两者并没有明确的界限。 +**线性地址**:指的是虚拟地址到物理地址变换之间的中间层,是处理器可寻指的内存空间(称为线性地址空间)中的地址。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址。 +**物理地址**:指的是现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果! + +## 7. 操作系统如何进行分页调度 +1. **先进先出算法(FIFO)**:这种算法的实质是,总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。理由是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大。 +2.**最优置换算法(OPT)**:当调入新的一页而必须预先置换某个老页时,所选择的老页应是将来不再被使用,或者是在最远的将来才被访问。采用这种页面置换算法,保证有最少的缺页率。 +但是最优页面置换算法的实现是困难的,因为它需要人们预先就知道一个进程整个运行过程中页面走向的全部情况。 +3.**最久未使用算法(LRU)** +当需要置换一页时,选择在最近一段时间里最久没有使用过的页面予以置换.LRU算法是经常采用的页面置换算法,并被认为是相当好的,但是存在如何实现它的问题。LRU算法需要实际硬件的支持。 +4.**LRU(最近最少使用)算法** +如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小 +5.**时钟算法()**:将页面排成一个时钟的形状,该时钟有一个针臂,每次需要更换页面时,我们从针臂所指的页面开始检查。如果当前页面的访问位为0,即从上次检查到这次,该页面没有被访问过,将该页面替换。反之,就将其访问位清零,并顺时针移动指针到下一个页面。重复这些步骤,直到找到一个访问位为0的页面。 +(1)计数器。最简单的情况是使每个页表项对应一个使用时间字段,并给CPU增加一个逻辑时钟或计数器。每次存储访问,该时钟都加1。在置换页面时,选择该时间值最小的页面。这样做,不仅要查页表,而且当页表改变时(因CPU调度)要维护这个页表中的时间,还要考虑到时钟值溢出的问题。 + +## 8. 磁盘调度算法: +1.**先来先服务(FCFS)**,按访问请求到达的先后顺序服务。简单,公平,但是效率不高,相临两次请求可能会造成最内到最外柱面寻道,使磁头反复移动,增加了服务时间,对机器不利。 +2.**最短寻道时间优先(SSTF)**,优先选择距当前磁头最近的访问请求进行服务,主要考虑寻道优先。改善了磁盘平均服务时间,但是造成某些访问请求长期等待得不到服务。 +3.**扫描算法(SCAN)**,当设备无访问请求时,磁头不动;当有访问请求时,磁头按一个方向移动,在移动过程中对遇到的访问请求进行服务,然后判断该方向上是否还有访问请求,如果有则继续 +4.**循环扫描算法(CSCAN)**:循环扫描调度算法是在扫描算法的基础上改进的。磁臂改为单项移动,由外向里。当前位置开始沿磁臂的移动方向去选择离当前磁臂最近的哪个柱面的访问者。如果沿磁臂的方向无请求访问时,再回到最外,访问柱面号最小的作业请求。 + +参考 http://www.cnblogs.com/edisonchou/p/5094066.html +http://blog.csdn.net/shinehoo/article/details/5881939 +http://blog.chinaunix.net/uid-25132162-id-361291.html + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + diff --git a/others/README.md b/others/README.md new file mode 100644 index 0000000..e69de29 diff --git a/others/danli.md b/others/danli.md new file mode 100644 index 0000000..d9d43f5 --- /dev/null +++ b/others/danli.md @@ -0,0 +1,103 @@ +> 《设计模式》提出近二十年里,随着面向对象语言的发展,单例模式也随之演化,如今其实现形式变得多种多样。常见的单例模式有懒汉、饿汉、双重校验锁、枚举和静态内部类五种形式。 +双重校验锁DCL(double checked locking) + +## 1.双重校验 +双重校验锁式(也有人把双重校验锁式和懒汉式归为一类)分别在代码锁前后进行判空校验,避免了多个有机会进入临界区的线程都创建对象,同时也避免了代码段1-4后来线程在先来线程创建对象后但仍未退出临界区的情况下等待。双重校验锁代码如下: + + public class Singleton{ + private volatile static Singleton singleton = null; //注意此处加上了volatile关键字 + + private Singleton(){//这边不能省略 + } + + public static Singleton getInstance(){ + if(singleton == null){ + synchronized(Singleton.class){ + if(singleton == null){ + singleton = new Singleton(); + //return singleton; //有人提议在此处进行一次返回 + } + //return singleton; //也有人提议在此处进行一次返回 + } + } + return singleton; + } + } + + +经多次试验说明,双重校验锁式是线程安全的。然而,在JDK1.5以前,DCL是不稳定的,有时也可能创建多个实例,在1.5以后开始提供volatile关键字修饰变量来达到稳定效果。 + +## 2.饿汉式单例 +单例模式的饿汉式,在定义自身类型的成员变量时就将其实例化,使得在Singleton单例类被系统(姑且这么说)加载时就已经被实例化出一个单例对象,从而一劳永逸地避免了线程安全的问题。代码如下: + + public class Singleton { + private final static Singleton INSTANCE = new Singleton(); + private Singleton(){ } + public static Singleton getInstance() { + return INSTANCE; + } + } + +ClassLoader加载Singleton类时,饿汉式单例就被创建,虽然饿汉式单例是线程安全的,但也有其不足之处。饿汉式单例在类被加载时就创建单例对象并且长驻内存,不管你需不需要它;如果单例类占用的资源比较多,就会降低资源利用率以及程序的运行效率。有一种更高级的单例模式则很好地解决了这个问题——静态内部类。 + +##3. 静态内部类 +静态内部类式和饿汉式一样,同样利用了ClassLoader的机制保证了线程安全;不同的是,饿汉式在Singleton类被加载时(从代码段3-2的Class.forName可见)就创建了一个实例对象,而静态内部类即使Singleton类被加载也不会创建单例对象,除非调用里面的getInstance()方法。因为当Singleton类被加载时,其静态内部类SingletonHolder没有被主动使用。只有当调用getInstance方法时,才会装载SingletonHolder类,从而实例化单例对象。 + + public class Singleton { + public static Singleton getInstance(){ + return SingletonHolder.instance; + } + private Singleton(){} + private static class SingletonHolder{ + private static Singleton instance = new Singleton(); + } + } + +通过静态内部类的方法就实现了lazy loading,很好地将懒汉式和饿汉式结合起来,既实现延迟加载,保证系统性能,也能保证线程安全。 + +##4.枚举单例 +上面说到的静态内部类方式不失为一个高级的单例模式实现。但如果开发要求更严格一些,比如你的Singleton类实现了序列化,又或者想避免通过反射来破解单例模式的话,单例模式还可以有另一种形式。那就是枚举单例。枚举类型在JDK1.5被引进。这种方式也是《Effective Java》作者Josh Bloch 提倡的方式,它不仅能避免多线程的问题,而且还能防止反序列化重新创建新的对象、防止被反射攻击。代码如下: + + public enum EnumSingleton { + INSTANCE{ + @Override + protected void work() { + System.out.println("你好,是我!"); + } + + }; + + protected abstract void work(); //单例需要进行操作(也可以不写成抽象方法) + } + + +在外部,可以通过EnumSingleton.INSTANCE.work()来调用work方法。默认的枚举实例的创建是线程安全的,但是实例内的各种方法则需要程序员来保证线程安全。总的来说,使用枚举单例模式,有三个好处:1.实例的创建线程安全,确保单例。2.防止被反射创建多个实例。3.没有序列化的问题 + +##5.懒汉式 +在需要的时候才创建单例对象,而不是随着软件系统的运行或者当类被加载器加载的时候就创建。当单例类的创建或者单例对象的存在会消耗比较多的资源,常常采用lazy loading策略。这样做的一个明显好处是提高了软件系统的效率,节约内存资源。 + + public class Singleton { + private static Singleton singleton = null; + + private Singleton(){ + System.out.println("构造函数被调用"); + } + + public static Singleton getInstance(){ + synchronized(Singleton.class){ + if(singleton == null){ + singleton = new Singleton(); + } + } + return singleton; + } + } +然而这样做类似于在方法签名上加上synchronized关键字,会影响程序效率。因为当有多个线程几乎同时访问getInstance方法时,多个线程必须有次序地进入方法内,这样导致了若干个线程需要耗费等待进入临界区(被锁住的代码块)的时间。基于此,有人提出了双重校验锁式。 + +## 参考资料 +http://blog.chenzuhuang.com/archive/13.html + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) + + diff --git a/others/hash.md b/others/hash.md new file mode 100644 index 0000000..d04ce0f --- /dev/null +++ b/others/hash.md @@ -0,0 +1,2 @@ +## 参考 +http://blog.csdn.net/cywosp/article/details/23397179/ \ No newline at end of file diff --git a/others/section1.md b/others/section1.md new file mode 100644 index 0000000..9c2a99d --- /dev/null +++ b/others/section1.md @@ -0,0 +1,34 @@ +### 第一个编码表 ASCII + 在最初的时候,美国人制定了第一张编码表 《美国标准信息交换码》,简称 ASCII,它总共规定了 128 个符号所对应的数字代号,使用了7位二进制的位来表示这些数字。其中包含了英文的大小写字母、数字、标点符号等常用的字符,数字代号从 0 至 127。 + +## 扩展 ASCII 编码 ISO8859 +美国人顺利解决了字符的问题,可是欧洲的各个国家还没有,比如法语中就有许多英语中没有的字符,因此 ASCII 不能帮助欧洲人解决编码问题。为了解决这个问题,人们借鉴 ASCII 的设计思想,创造了许多使用 8 位二进制数来表示字符的扩充字符集,这样我们就可以使用256种数字代号了,表示更多的字符了。在这些字符集中,从 0 - 127 的代码与 ASCII 保持兼容,从128到255用于其它的字符和符号,由于有很多的语言,有着各自不同的字符,于是人们为不同的语言制定了大量不同的编码表,在这些码表中,从128-255表示各自不同的字符,其中,国际标准化组织的 ISO8859 标准得到了广泛的使用。 + +## GB2312-80 +我们就使用两个字节来表示一个中文,在每个字符的 256 种可能中,低于 128 的为了与 ASCII 保持兼容,我们不使用,借鉴 ISO8859的设计方案,只使用从 160 以后的 96 个数字,两个字节分成高位和低位,高位的取值范围从 176-247 共72个,低位从 161 – 254共94这样,两个字节就有 72 * 94 = 6768种可能,也就是可以表示 6768 种汉字 + +## GBK +GB2312-80 仅收汉字6763个,这大大少于现有汉字,随着时间推移及汉字文化的不断延伸推广,有些原来很少用的字,现在变成了常用字,例如:朱镕基的“镕”字,未收入GB2312-80,现在大陆的报业出刊只得使用(金+容)、(金容)、(左金右容)等来表示,形式不一而同,这使得表示、存储、输入、处理都非常不方便,而且这种表示没有统一标准。 +为了解决这些问题,全国信息技术化技术委员会于1995年12月1日《汉字内码扩展规范》。GBK向下与GB2312完全兼容,向上支持ISO 10646国际标准,在前者向后者过渡过程中起到的承上启下的作用。GBK亦采用双字节表示,总体编码范围为8140-FEFE之间,高字节在81-FE之间,低字节在40-FE之间,不包括7F。在 GBK 1.0 中共收录了 21886个符号,汉字有21003个。 + +## GB18030 +GB18030 是最新的汉字编码字符集国家标准, 向下兼容 GBK 和 GB2312 标准。 GB18030 编码是一二四字节变长编码。 一字节部分从 0x0~0x7F与 ASCII 编码兼容。二字节部分, 首字节从 0x81~0xFE, 尾字节从 0x40~0x7E 以及 0x80~0xFE, 与 GBK标准基本兼容。 四字节部分, 第一字节从 0x81~0xFE, 第二字节从 0x30~0x39,第三和第四字节的范围和前两个字节分别相同。 + +## UNICODE +在80年代就有了一个称为 UNICODE 的组织,这个组织制定了一个能够覆盖几乎任何语言的编码表,在 Unicode3.0.1中就包含了 49194 个字符,将来,Unicode 中还会增加更多的字符。Unicode 的全称是 Universal Multiple-Octet Coded Character Set ,简称为 UCS。 +unicode中,一个字符就是两个字节, 对于只需要1字节表示的字符来说很浪费内存。 + +## UTF-16 +UTF-16比较好理解,就是任何字符对应的数字都用两个字节来保存.我们通常对Unicode的误解就是把Unicode与UTF-16等同了.但是很显然如果都是英文字母这做有点浪费.明明用一个字节能表示一个字符为啥整两个啊. + +## UTF-8 +于是又有个UTF-8,这里的8非常容易误导人,8不是指一个字节,难道一个字节表示一个字符?实际上不是.当用UTF-8时表示一个字符是可变的,有可能是用一个字节表示一个字符,也可能是两个,三个.当然最多不能超过3个字节了.反正是根据字符对应的数字大小来确定. + +参考 +http://blog.sina.com.cn/s/blog_69c189bf0100mt93.html +http://www.zhihu.com/question/23374078 + + +个人公众号(欢迎关注):
+![](/assets/weix_gongzhonghao.jpg) +