├── README.md ├── SUMMARY.md ├── abstract.md ├── algorithm ├── README.md ├── leetCode_go.md ├── offer_go.md ├── offer_java.md ├── section01.md ├── section02.md ├── string.md └── tu.md ├── assets ├── beans.png ├── bg.jpg ├── collection_compare.png ├── java_collection.png ├── spring.png └── weix_gongzhonghao.jpg ├── author.md ├── book.json ├── cache └── redis.md ├── chapter8 └── ask.md ├── database ├── README.md ├── SQL语句汇总.md ├── all.md ├── mysql.md ├── sql.md └── 数据库设计三大范式.md ├── framework ├── README.md ├── hibernate.md ├── spring.md ├── struts.md └── web.md ├── hr ├── README.md ├── ask.md └── 跳槽HR问题.md ├── images ├── 15262672735241.jpg ├── 15262674498084.jpg ├── 15262725012092.jpg ├── 15262727479347.jpg ├── 15262727709415.jpg ├── 15262741159390.jpg ├── 15262742098959.jpg ├── 15262744168004.jpg ├── 15262744598214.jpg ├── 15262746232094.jpg ├── 15262747755969.jpg ├── 15262804666714.jpg ├── 15262805942603.jpg ├── 15262813568697.jpg ├── 15262858741207.jpg ├── 15263482717424.jpg ├── 15263488660716.jpg ├── 15263490496683.jpg ├── 15263493121606.jpg ├── 15263496139671.jpg ├── 15263505327822.jpg ├── 15263523909429.jpg ├── 15264350580615.jpg ├── 15267019643357.jpg ├── 15267022191672.jpg ├── 15267023963428.jpg ├── 15267059908176.jpg ├── 15267061387896.jpg ├── 15267085161227.jpg ├── 15267089041829.jpg ├── 15267089312034.jpg ├── 15267212678626.jpg ├── 15267213158671.jpg ├── 15267213939865.jpg ├── 15267214238344.jpg ├── 15267214776563.jpg ├── 15267215934973.jpg ├── 15268031792473.jpg ├── 15269538268005.jpg ├── 15276716962414.jpg ├── 15276920363669.jpg ├── 15297611898077.jpg ├── 15297612276666.jpg ├── 15297612660838.jpg ├── 15297613011263.jpg └── 15297621325283.jpg ├── java ├── G1垃圾回收器.md ├── README.md ├── base.md ├── collection.md ├── io.md ├── jvm.md ├── object.md └── thread.md ├── network ├── README.md ├── all.md └── section01.md ├── os ├── README.md ├── chapter1.md ├── linux基础.md ├── product.md ├── section1.md ├── section2.md └── top命令详解.md └── others ├── README.md ├── danli.md ├── hash.md ├── section1.md ├── 大型网站架构.md ├── 机器学习相关.md ├── 设计模式汇总.md └── 项目.md /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 收集了互联网面试过程中常见的面试问题,并对这些问题、知识点做分析和整理,希望对大家有所帮助。如果**有错误地方还希望提出宝贵建议,批评指正。祝大家求职顺利**。 3 | 4 | 如果喜欢可以点下star收藏。 5 | 6 | # 在线阅读 7 | 本文档使用 Gitbook 制作,[在线阅读地址](https://www.gitbook.com/book/zhengjianglong/note-of-interview/details) 8 | 9 | 本文档github地址:https://github.com/zhengjianglong915/note-of-interview 10 | 11 | # 作者简介 12 | 作者: 畅逸风 13 | 14 | 个人主页:[https://zhengjianglong915.github.io/](https://zhengjianglong915.github.io/) 15 | 16 | 简书地址: http://www.jianshu.com/u/9f1f5db3d6b8 17 | 18 | 个人公众号(欢迎关注):主要分享分布式、中间件、云原生相关原理,以及对一些面试题的分解。 19 | 20 | ![](/assets/weix_gongzhonghao.jpg) 21 | 22 | 23 | # 声明 24 | 如果有错之处还望指出和纠正。可以加公众号联系我。 25 | 26 | 27 | ## 内容提纲 28 | * [前言](abstract.md) 29 | * [第一章 操作系统](./os/README.md) 30 | * [1.1 生产者与消费者](os/product.md) 31 | * [1.2 常见面试题](./os/section2.md) 32 | * [第二章 计算机网络](./network/README.md) 33 | * [2.1 常见面试题](./network/all.md) 34 | * [第三章 算法与数据结构](./algorithm/README.md) 35 | * [3.1 排序算法汇总](./algorithm/section01.md) 36 | * [3.2 查找](./algorithm/section02.md) 37 | * [3.3 字符串](./algorithm/string.md) 38 | * [3.4 图算法](./algorithm/tu.md) 39 | * [3.5 剑指offer-java实现版本](./algorithm/offer_java.md) 40 | * [3.6 剑指offer-go实现版本](./algorithm/offer_go.md) 41 | * [第四章 Java](./java/README.md) 42 | * [4.1 基础](./java/base.md) 43 | * [4.2 面向对象](./java/object.md) 44 | * [4.3 集合](./java/collection.md) 45 | * [4.4 多线程](./java/thread.md) 46 | * [4.5 IO](./java/io.md) 47 | * [4.5 JVM](./java/jvm.md) 48 | * [G1垃圾回收器](./java/G1垃圾回收器.md) 49 | * [第五章 数据库](./database/README.md) 50 | * [5.1 常见面试题](./database/all.md) 51 | * [第六章 框架](./framework/README.md) 52 | * [6.1 spring](./framework/spring.md) 53 | * [6.2 mybatis] 54 | * [6.3 hibernate](./framework/hibernate.md) 55 | * [6.4 struts](./framework/struts.md) 56 | * [第七章 其他基础](./others/README.md) 57 | * [7.1 编码历史](./others/section1.md) 58 | * [7.2 单例模式的几种实现方式](./others/danli.md) 59 | * [7.3 一致性哈希算法](./others/hash.md) 60 | * [7.4 设计模式汇总](./others/设计模式汇总.md) 61 | * [7.5 大型网站架构](./others/大型网站架构.md) 62 | * [第八章 分布式相关] 63 | * [8.1 缓存redis](./cache/redis.md) 64 | * [第九章 HR面](./hr/README.md) 65 | * [9.1 项目与个人](./chapter8/ask.md) 66 | 67 | 68 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [前言](abstract.md) 4 | * [第一章 操作系统](./os/README.md) 5 | * [1.1 生产者与消费者](os/product.md) 6 | * [1.2 常见面试题](./os/section2.md) 7 | * [第二章 计算机网络](./network/README.md) 8 | * [2.1 常见面试题](./network/all.md) 9 | * [第三章 算法与数据结构](./algorithm/README.md) 10 | * [3.1 排序算法汇总](./algorithm/section01.md) 11 | * [3.2 查找](./algorithm/section02.md) 12 | * [3.3 字符串](./algorithm/string.md) 13 | * [3.4 图算法](./algorithm/tu.md) 14 | * [3.5 剑指offer-java实现版本](./algorithm/offer_java.md) 15 | * [3.6 剑指offer-go实现版本](./algorithm/offer_go.md) 16 | * [第四章 Java](./java/README.md) 17 | * [4.1 基础](./java/base.md) 18 | * [4.2 面向对象](./java/object.md) 19 | * [4.3 集合](./java/collection.md) 20 | * [4.4 多线程](./java/thread.md) 21 | * [4.5 IO](./java/io.md) 22 | * [4.5 JVM](./java/jvm.md) 23 | * [第五章 数据库](./database/README.md) 24 | * [5.1 常见面试题](./database/all.md) 25 | * [第六章 框架和组件](./framework/README.md) 26 | * [6.1 spring](./framework/spring.md) 27 | * [6.2 hibernate](./framework/hibernate.md) 28 | * [6.3 struts](./framework/struts.md) 29 | * [第七章 其他基础](./others/README.md) 30 | * [7.1 编码历史](./others/section1.md) 31 | * [7.2 单例模式的几种实现方式](./others/danli.md) 32 | * [7.3 一致性哈希算法](./others/hash.md) 33 | * [第八章 HR面](./hr/README.md) 34 | * [8.1 项目与个人](./chapter8/ask.md) 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /abstract.md: -------------------------------------------------------------------------------- 1 | # 序 2 | ## 在被虐中成长,在面试中进步 3 | 指缝很宽,时间太瘦,悄悄从指缝间溜走。转眼之间学生生涯即将结束,即使很是不舍,也终将要走向社会,开始另一段新的旅程。 4 | 5 | 面试过程并不总是一帆风顺,**那些我们经历的终将成为我们人生一笔宝贵的财富**。而最重要的是,我们在面试过程中学会了成长,学会了改变。学习弥补那些我们不会的知识,加深理解那些我们浮于表面的原理,改变我们面试过程中出现的缺点和不足。面试是一个非常好的学习机会,我们应当适当地利用好这次机会完成自己知识的一次飞跃。 6 | 7 | #### 1. 更加集中的学习 8 | 正如考研、期末考试一样,面试也是我们一次知识增长和爆发的时候。因为有了面试和找工作的压力,我们便能更加集中地将大把时间花在学习和复习上。将时间花在哪里,你的成就就在哪里,根据《刻意练习》中的刻意练习理论和《异类》中的10000小时定律,你在某个领域花的时间越多,你在该领域的成就越高。 9 | 10 | 我们在面试过程中加强了知识复习强度,而面试就是对这次知识准备的反馈过程,通过面试来找出自己的不足和了解自己知识的掌握程度。遇到不会的知识,我们便开始上网或翻阅书本,查缺补漏。通过反复的准备、面试、纠正的过程使得我们掌握了大量的面试技巧,同时也强化了对知识掌握程度。 11 | 12 | #### 2. 更加系统的知识体系 13 | 互联网面试不会问单一的知识(如:算法),而是一次综合性考察。因此我们在备战面试过程中各个知识点都需要准备,如计算机操作系统、计算机网络、数据库、算法、数据结构等。而知识之间不可能孤立存在,而是彼此存在联系。《高效学习》中提到,学习过程分为几个阶段:**知识获取,理解,拓展(或者建立联系),纠错,应用,记忆,测试**。我们根据面试要求从相关书籍中**获取知识**;**理解**一些知识和概念,及背后的原理(如什么是面向对象);同时建立起与其他知识之间的**联系**,例如java虚拟机的垃圾回收算法是什么,性能如何?优缺点是什么?---java虚拟机和算法数据结构的联系。java虚拟机如何做到跨平台,与计算机系统之间是怎样的关系等。通过面试过程**纠正**我们对知识的错误认知。通过反复纠正、记忆便掌握更加牢固。 14 | 15 | 面试中的问题都会将一些知识联系起来,比如MySql索引的数据结构是什么?性能如何?不同索引之间的比较如何?这些都不是单纯的考察MySql知识,同时也在考察对数据库中B+树、hash的理解。 16 | 17 | 可以通过复习建立起各个知识之间的联系,并且我们能够花大量时间系统的学习各个方面的知识。通过这样一次复习后,我们拥有了更加系统的互联网知识体系,建立起各知识点之间的联系。 18 | 19 | 20 | #### 3. 弥补不足,提升自我 21 | 面试一个非常好的地方就是能够帮助自己,找出自己的不足,这些不足包括知识和沟通等方面。面试中遇到不会的问题便是我们成长最大、最快的地方。这些问题之前学习的时候是否认真考虑过?是否做项目时,某些问题是否有深入地分析过?用的框架是否知其然而知其所以然?总结面试中回答不足的问题,我们在复习过程中重点攻克,查缺补漏,进一步巩固之前的基础知识。虽然面试过程中有时候会被虐的体无完肤,虐得心力憔悴。但是,这也帮助我们找到自己知识的不足,便于弥补和提高。面试过程中最怕的不是被拒绝,而是不知道为什么被拒绝。被拒绝后还不采取相应的措施去做出改变,那即便面再多也是徒然。 22 | 23 | #### 4. 更加深入地了解知识背后原理 24 | 通常我们掌握的知识,仅仅是这个知识,处于一种**见树而不见森林**的状态,对其别后的原理和联系都不甚了解。于是面试中一旦面试官深入问询时,自己便回答不上了。**面试带来的好处就是让我们有更多的机会去反思和思考,让我们能够更加深入地去挖掘知识背后的原理,而不是简简单单地浮于表面的理解**。面试中你会发现那些习以为常的知识背后隐藏着一个丰富多彩的知识世界,**一花一世界,一叶一森林**,这些知识背后总有那些需要了解的知识原理和知识间的联系。通过面试,我们加强对这些知识的理解和学习,通过深入分析背后的原理和建立起知识点的练习,使自己能够做到**见树即见森林**。 25 | 26 | 面试是一次知识提升的机会,面试中应该学会总结自己的不足,并针对不足之处深入研究、分析才能促进自己的成长。而不应该仅仅将面试准备当做应付考试一样准备,背背网上的一些所谓的”标准答案“。 27 | 28 | ## 缘分与心态 29 | 我们如此看重实习和工作,是因为进入互联网大型公司实习一方面能够给我们一个平台提高我们的实践能力、学习更多知识技能和拓展我们的视野,另一方面是能在未来找工作时为我们简历加分。而工作更不用说了,十几年的学习生涯也是为了毕业时找份好工作,为未来奠定一个好的开端。很多人常说第一份工作至关重要,往往决定了一个人未来的发展道路。是否决定一个人未来因个人而异,但是第一份工作还是非常关键,那是我们走入社会的第一步,会对自己个人的成长和视野的开拓产生重要影响。因为不同的公司的文化环境不同,组织结构不同和给个人提供的机会都有所不同。因此也很多人格外看重这份工作。 30 | 31 | 而不管是找实习还是找工作,我们都会面临一个问题,那就是面试。面试不仅是考验的是技术和知识,同时也是看缘分和心态。 32 | 33 | 找工作之前,有一点你必须清楚,就是找工作是一件看缘分的事情,不是你很牛逼,你就一定能进你想进的公司,都是有一个概率在那。比如相同的公司因为面试官不同问的知识点也可能不同,有时候问的刚好你都准备了,有时候偏偏问些你所不会的。如果你基础好,项目经验足,同时准备充分,那么你拿到offer的概率就会比较高;相反,如果你准备不充分,基础也不好,那么你拿到offer的概率就会比较低,但是你可以多投几家公司,这样拿到offer的几率就要大一点,因为你总有运气好的时候。所以,不要惧怕面试,刚开始失败了没什么的,多投多尝试,面多了你就自然能成面霸了。**得失心也不要太重**,应该放宽心态,应为最后每个人都会有offer的。 34 | 35 | 36 | ## 基础备战 37 | 基础这东西,各个公司都很看重,尤其是大型互联网大公司,他们看中人的潜力,他们舍得花精力去培养,所以基础是重中之重。当你的基础好的发指的时候,你的其他东西都不重要了。 38 | 39 | 基础无外乎几部分:语言(C/C++或java),操作系统,TCP/IP,数据结构与算法,再加上你所熟悉的领域。当面试多了,你便会发现有很多是常见的问题和知识点,因此对那些常见的面试题进行收集和整理,并对一些题目做深入分析,希望能够帮助大家。大家如果觉得有所帮助,也可以关注我微信公众号。 40 | 41 | ## 推荐复习书籍 42 | 复习的时候最好还是以书本为主,毕竟书本内容是成体系的,并且经过编辑和很多读者的验证,内容是准确的。本系列笔记可以作为复习和总结使用,快速复习各个面试要点。前提大家还是得去看相关的书籍和知识,理解相关内容,否则死记硬背一方面效率底下,另一方面当面试官问的比较深入的时候无法解答。 43 | 44 | ### 算法 45 | - **《剑指offer》** 46 | 47 | ### Java 48 | - **《java并发编程的艺术》** | 看完java并发编程方面的面试基本没问题,里面涉及java并发编程的底层实现原理等。 49 | - **《深入理解java虚拟机》**| 对java虚拟机有一个系统的了解,可以应对java虚拟机方面的面试 50 | - **《Netty权威指南》** | 非常经典的java网络编程书籍, 51 | - **《Java NIO》** | JAVA NIO 入门书籍 52 | 53 | 54 | ### 设计模式 55 | - **《设计模式》** 四人班 | 经典之作 56 | - [java_my_life的设计模式博客系列](https://www.cnblogs.com/java-my-life/) | 比较通熟易懂 57 | - **《大话设计模式》**| 可以作为入门读物,看完面试也差不多了 58 | 59 | ### 组件或框架 60 | - **《redis设计与实现》** | 对redis的原理做了详细的讲解,通熟易懂。推荐。 61 | - **《spring源码深度解析》** | 了解spring的实现原理, 对spring实现机制有更深刻的了解。 62 | 63 | ### 分布式相关 64 | - **《大型网站技术架构:核心原理与案例分析》** 65 | - **《大型网站系统与java中间件》** 66 | 67 | 68 | 69 | 待续。。。 70 | 71 | 72 | 个人公众号(欢迎关注) 73 | ![](/assets/weix_gongzhonghao.jpg) 74 | 75 | # 推荐链接 76 | https://github.com/CyC2018/InterviewNotes 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /algorithm/README.md: -------------------------------------------------------------------------------- 1 | ## 知识点汇总 2 | 1. 链表与数组。 3 | 2. 队列和栈,出栈与入栈。 4 | 3. 链表的删除、插入、反向。 5 | 4. 字符串操作。 6 | 5. Hash表的hash函数,冲突解决方法有哪些。 7 | 6. 各种排序:冒泡、选择、插入、希尔、归并、快排、堆排、桶排、基数的原理、平均时间复杂度、最坏时间复杂度、空间复杂度、是否稳定。 8 | 7. 快排的partition函数与归并的Merge函数。 9 | 8. 对冒泡与快排的改进。 10 | 9. 二分查找,与变种二分查找。 11 | 10. 二叉树、B+树、AVL树、红黑树、哈夫曼树。 12 | 11. 二叉树的前中后续遍历:递归与非递归写法,层序遍历算法。 13 | 12. 图的BFS与DFS算法,最小生成树prim算法与最短路径Dijkstra算法。 14 | 13. KMP算法。 15 | 14. 排列组合问题。 16 | 15. 动态规划、贪心算法、分治算法。(一般不会问到) 17 | 16. 大数据处理:类似10亿条数据找出最大的1000个数.........等等 -------------------------------------------------------------------------------- /algorithm/leetCode_go.md: -------------------------------------------------------------------------------- 1 | # 背景 2 | 最近在学习go, 用 leetCode 练练手 3 | 4 | 5 | ## 1480. Running Sum of 1d Array 6 | see more: https://leetcode.com/problems/running-sum-of-1d-array/ 7 | 8 | #### 实现 9 | 10 | ``` 11 | func runningSum(nums []int) []int { 12 | if nil == nums || len(nums) == 0{ 13 | return nums 14 | } 15 | 16 | for i:=1; i < len(nums); i++ { 17 | nums[i] += nums[i-1] 18 | } 19 | return nums 20 | } 21 | ``` 22 | 23 | 24 | ## 1470. Shuffle the Array 25 | see more: https://leetcode.com/problems/shuffle-the-array/ 26 | 27 | #### 实现 28 | 29 | ``` 30 | func shuffle(nums []int, n int) []int { 31 | if nil == nums || len(nums) == 0 { 32 | return nums 33 | } 34 | 35 | result := make ([]int, len(nums)) 36 | 37 | for i:= range nums[:n] { 38 | result[i * 2], result[i * 2 + 1] = nums[i], nums[i+n] 39 | } 40 | 41 | return result 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /algorithm/offer_go.md: -------------------------------------------------------------------------------- 1 | # 剑指offer--go版本 2 | -------------------------------------------------------------------------------- /algorithm/offer_java.md: -------------------------------------------------------------------------------- 1 | # 剑指offer--java版 2 | 代码格式按牛客网在线答题格式编写 3 | 4 | # 01-二维数组中的查找 5 | 6 | > 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 7 | 8 | ## 思路 9 | 坑:不要从中间去,采用二分查找。如果中间查找会有两个区域需要查找,增加复杂度。 10 | 真确思路:**从右上角开始遍历**,有三种情况: 11 | 1. 如果相等,则直接返回。 12 | 2. 如果值大于目标值,列减小。(因为从坐到右增大,因此右边的值不可能存在目标值) 13 | 3. 如果值小于目标值,增大行。(从上往下增大,该值在最右上角为当前行最大,因此当前行没有更大的,可以往下一行找) 14 | 15 | 思考,是否可以从左上角开始、右下角、左下角开始? 16 | 左上角不可以,右下角不可以。因为这这两个移动后还是会形成两个区域需要查找。 17 | 左下角可以。 18 | 19 | 20 | ## 实现 21 | 22 | ``` 23 | public boolean Find(int [][] array,int target) { 24 | if (array == null || array[0].length == 0 ) { 25 | return false; 26 | } 27 | int row = 0; 28 | int col = array.length - 1; 29 | while ( row < array.length && col >=0 ) { 30 | if ( target > array[row][col]) { 31 | row ++; 32 | } else if (target < array[row][col]) { 33 | col --; 34 | } else { 35 | return true; 36 | } 37 | 38 | } 39 | return false; 40 | } 41 | ``` 42 | 43 | # 02-替换空格 44 | 45 | > 请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 46 | 47 | ## 思路 48 | 1. 先计算空格数目 49 | 2. 计算新的字符串长度,并构建新字符数组 50 | 3. 逐个遍历和copy, 遇到空格用户“%20”替换 51 | 52 | 53 | ## 实现 54 | 55 | ``` 56 | public String replaceSpace(StringBuffer str) { 57 | // 第一件重要的事 58 | if( str == null || str.length() == 0) 59 | return str.toString(); 60 | // 长度计算 61 | int newlength = newLength(str); 62 | char[] newChar = new char[newlength]; 63 | int idx = 0; 64 | for (int i = 0; i < str.length(); i++) { 65 | if (str.charAt(i) == ' ') { 66 | newChar[idx++] = '%'; 67 | newChar[idx++] = '2'; 68 | newChar[idx++] = '0'; 69 | } else { 70 | newChar[idx++] = str.charAt(i); 71 | } 72 | } 73 | return new String(newChar); 74 | } 75 | private int newLength(StringBuffer str) { 76 | int spaceNum = 0; 77 | for (int i = 0; i < str.length(); i ++) { 78 | if (str.charAt(i) == ' ') { 79 | spaceNum++; 80 | } 81 | } 82 | 83 | // 或者 str.length() + spaceNum * 2 84 | return (str.length() - spaceNum) + spaceNum * 3; 85 | } 86 | ``` 87 | 88 | # 03-从尾到头打印链表 89 | 90 | > 输入一个链表,从尾到头打印链表每个节点的值。 91 | 92 | ## 思路 93 | 利用栈,先进后出的思路。 94 | 95 | ## 实现 96 | 97 | ``` 98 | public ArrayList printListFromTailToHead(ListNode listNode) { 99 | ArrayList result = new ArrayList(); 100 | if (null == listNode) { 101 | return result; 102 | } 103 | 104 | Stack stack = new Stack(); 105 | ListNode next = listNode; 106 | while(null != next) { 107 | stack.push(next); 108 | next = next.next; 109 | } 110 | while(!stack.isEmpty()) { 111 | result.add(stack.pop().val); 112 | } 113 | return result; 114 | } 115 | ``` 116 | 117 | # 04-旋转数组的最小数字 118 | > 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 119 | 120 | 121 | ## 思路 122 | 二分查找,不断缩短查找范围。 123 | 124 | 1. mid值与左右两端比较,如果小于右边,high=mid. 125 | 2. mid大于左边,low=mid 126 | 3. 如果左值=mid值=右值,顺序遍历 127 | 128 | 129 | ``` 130 | public int minNumberInRotateArray(int [] array) { 131 | if ( array == null || array.length == 0 ) { 132 | return 0; 133 | } 134 | int low = 0, high = array.length - 1; 135 | int mid; 136 | while ( low < high && array[low] >= array[high]) { 137 | // array[low] 需要大于等于 array[high] 在内部进行 遍历 138 | if ( (low +1 )== high) { //这个很关键,说明只有两个元素 139 | return array[high]; 140 | } 141 | if (array[low] == array[high]) { 142 | int min = array[low]; 143 | for ( int i = low +1; i <= high; i++) { 144 | if(array[i] < min){ 145 | min = array[i]; 146 | } 147 | } 148 | return min; 149 | } else { 150 | mid = (low + high) >> 1; 151 | if (array[mid] < array[high]){ 152 | high = mid; 153 | } else { 154 | low = mid; 155 | } 156 | } 157 | } 158 | 159 | // 只有一个元素 或者 array[low] < array[high] 160 | return array[low]; 161 | } 162 | ``` 163 | 164 | ## 05-斐波那契数列 165 | 166 | ``` 167 | public int Fibonacci(int n) { 168 | if(n <= 0) { 169 | return 0; 170 | } 171 | if (n == 1 || n == 2) { 172 | return 1; 173 | } 174 | int preOne = 1; 175 | int preTwo = 1; 176 | int temp; 177 | for ( int i = 3; i <= n; i++ ){ 178 | temp = preOne; 179 | preOne = preOne + preTwo; 180 | preTwo = temp; 181 | } 182 | return preOne; 183 | } 184 | ``` 185 | 186 | ## 06-青蛙跳 187 | > 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 188 | 189 | ``` 190 | public int JumpFloor(int target) { 191 | if(target <= 0) return 0; 192 | if(target == 1) return 1; 193 | if(target == 2) return 2; 194 | int preOne = 2, preTwo = 1; 195 | for(int i = 3; i <= target; i++){ 196 | int temp = preOne + preTwo; 197 | preTwo = preOne; 198 | preOne = temp; 199 | } 200 | return preOne; 201 | } 202 | ``` 203 | 204 | ## 07-变态青蛙跳 205 | > 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 206 | 207 | ``` 208 | public int JumpFloorII(int target) { 209 | if(target <= 0) return 0; 210 | int result = 1; 211 | target --; 212 | while(target !=0){ 213 | result = result << 1; 214 | target --; 215 | } 216 | return result; 217 | } 218 | ``` 219 | ## 08-矩阵覆盖 220 | > 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 221 | 222 | ``` 223 | public int RectCover(int target) { 224 | if (target <= 0) return 0; 225 | if(target == 1) return 1; 226 | if(target == 2) return 2; 227 | int preOne = 2, preTwo =1; 228 | for(int i = 3; i <= target; i++){ 229 | int temp = preOne + preTwo; 230 | preTwo = preOne; 231 | preOne = temp; 232 | } 233 | return preOne; 234 | } 235 | ``` 236 | 237 | ## 09-二进制中1的个数 238 | > 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 239 | 240 | ### 思路 241 | 如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。 242 | 举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。 243 | 244 | ### 实现 245 | ``` 246 | public int NumberOf1(int n) { 247 | int count = 0; 248 | while(n!= 0){ 249 | count++; 250 | n = n & (n - 1); 251 | } 252 | return count; 253 | } 254 | ``` 255 | 256 | ## 10-数值的整数次方 257 | > 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 258 | 259 | ``` 260 | public double Power(double base, int exponent) { 261 | if (base == 0) { 262 | return 0; 263 | } 264 | boolean flag = false; // 是否exponent是负数 265 | if (exponent <= 0) { 266 | exponent = -1 * exponent; 267 | flag = true; 268 | } 269 | 270 | double result = 1.0; 271 | while (exponent > 0) { 272 | result = result * base; 273 | exponent--; 274 | } 275 | 276 | if (flag) { 277 | return 1.0 / result; 278 | } else { 279 | return result; 280 | } 281 | } 282 | ``` 283 | 284 | ## 11-调整数组顺序奇数在偶数前面 285 | > 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。 286 | 287 | ``` 288 | 289 | ``` 290 | 291 | 292 | ## 二叉搜索树的后序遍历序列 293 | > 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 294 | 295 | ### 实现 296 | 297 | ``` 298 | public boolean VerifySquenceOfBST(int [] sequence) { 299 | if (sequence == null || sequence.length ==0) { 300 | return false; 301 | } 302 | return verifySequenceOfBST(sequence, 0, sequence.length-1); 303 | 304 | } 305 | private boolean verifySequenceOfBST(int[] sequence,int begin,int end){ 306 | if (begin == end) { 307 | return true; 308 | } 309 | int rightBegin = begin; 310 | int root = sequence[end]; 311 | for (;rightBegin < end; rightBegin++ ) { 312 | if (sequence[rightBegin] > root ) { 313 | break; 314 | } 315 | } 316 | int i = rightBegin; 317 | for (; i < end; i ++ ) { 318 | if (sequence[i] < root) { 319 | return false; 320 | } 321 | } 322 | boolean left = (rightBegin == begin )? true : verifySequenceOfBST(sequence, begin, rightBegin -1); 323 | boolean right = (rightBegin == end) ? true : verifySequenceOfBST(sequence, rightBegin+1, end); 324 | return left & right; 325 | 326 | } 327 | ``` 328 | 329 | ## 复杂链表的复制 330 | > 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) 331 | 332 | ### 思路 333 | 三步走: 334 | 1. 在原来链中插入克隆节点,是的克隆节点和源节点相间出现。 335 | 2. 复制random指针 336 | 3. 从原链从剥离clone链。 337 | 338 | 339 | ### 340 | ``` 341 | public RandomListNode Clone(RandomListNode pHead) 342 | { 343 | cloneNodes(pHead); 344 | connectRandomNodes(pHead); 345 | return reconnectNodes(pHead); 346 | } 347 | private void cloneNodes(RandomListNode pHead){ 348 | RandomListNode node = pHead; 349 | while(node != null){ 350 | RandomListNode clone = new RandomListNode(node.label); 351 | RandomListNode temp = node.next; 352 | clone.next = temp; 353 | node.next = clone; 354 | node = temp; 355 | } 356 | } 357 | private void connectRandomNodes(RandomListNode pHead){ 358 | RandomListNode node = pHead; 359 | while(node != null){ 360 | RandomListNode clone = node.next; 361 | if(node.random != null)//这里一定要判断 362 | clone.random = node.random.next; 363 | node = clone.next; 364 | } 365 | } 366 | 367 | private RandomListNode reconnectNodes(RandomListNode pHead){ 368 | RandomListNode node = pHead; 369 | RandomListNode cloneHead = null; 370 | RandomListNode cloneNode = null; 371 | if(node != null){ 372 | cloneHead = cloneNode = node.next; 373 | node.next = cloneNode.next; 374 | node = node.next; 375 | } 376 | while(node != null){ 377 | cloneNode.next = node.next; 378 | cloneNode = cloneNode.next; 379 | node.next = cloneNode.next; 380 | node = cloneNode.next; 381 | } 382 | return cloneHead; 383 | } 384 | ``` 385 | 386 | ## 二叉搜索树与双向链表 387 | > 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 388 | 389 | ``` 390 | public class Solution { 391 | public TreeNode Convert(TreeNode root){ 392 | TreeNode lastNode = null; 393 | lastNode = doConvert(root, lastNode); 394 | while(lastNode != null && lastNode.left != null) { 395 | lastNode = lastNode.left; 396 | } 397 | return lastNode; 398 | 399 | } 400 | /* 转换并返回链表最后一个结点 401 | */ 402 | private TreeNode doConvert(TreeNode root, TreeNode lastNode){ 403 | if (root == null) { 404 | return lastNode; 405 | } 406 | if (root.left != null) { 407 | lastNode = doConvert(root.left, lastNode); 408 | } 409 | if(lastNode != null) { 410 | lastNode.right = root; 411 | } 412 | root.left = lastNode; 413 | lastNode = root; 414 | if (root.right != null) { 415 | lastNode = doConvert(root.right, lastNode); 416 | } 417 | return lastNode; 418 | } 419 | } 420 | ``` 421 | 422 | ## 字符串的排列 423 | > 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 424 | 425 | ``` 426 | import java.util.ArrayList; 427 | import java.util.Collections; 428 | public class Solution { 429 | public ArrayList Permutation(String str) { 430 | ArrayList result = new ArrayList(); 431 | if(str == null || str.length() == 0) 432 | return result; 433 | doPermutation(str.toCharArray(),0,result); 434 | // 实际中不需要排序 435 | Collections.sort(result); 436 | return result; 437 | 438 | } 439 | private void doPermutation(char[] array,int idx,ArrayList result){ 440 | if(idx >= array.length){ 441 | String str = new String(array); 442 | result.add(str); 443 | return; 444 | } 445 | for(int i=idx; i < array.length; i++){ //i得从 idx开始 446 | if(i != idx && array[i] == array[idx] ) 447 | continue; 448 | 449 | swap(array,idx,i); 450 | doPermutation(array,idx+1,result); 451 | swap(array,idx,i); 452 | } 453 | } 454 | private void swap(char[] array,int i,int j){ 455 | char temp = array[i]; 456 | array[i] = array[j]; 457 | array[j] = temp; 458 | } 459 | 460 | } 461 | ``` 462 | 463 | 464 | 465 | 466 | -------------------------------------------------------------------------------- /algorithm/section01.md: -------------------------------------------------------------------------------- 1 | # 排序算法汇总 2 | ## 一. 插入排序 3 | ### 1.直接插入排序 4 | - **思想**:每次将一个待排序的数据按照其关键字的大小插入到前面已经排序好的数据中的适当位置,直到全部数据排序完成。 5 | - **时间复杂度**:O(n^2) O(n) O(n^2) (最坏 最好 平均) 6 | - **空间复杂度**:O(1) 7 | - **稳定性**: 稳定 每次都是在前面已排好序的序列中找到适当的位置,只有小的数字会往前插入,所以原来相同的两个数字在排序后相对位置不变。 8 | 9 | ``` 10 | // 插入排序 11 | public static void insertSort(int[] array) { 12 | for (int i = 2; i < array.length; i++ ) { 13 | int val = array[i]; 14 | int j = i -1; 15 | while (j >= 0 && array[j] > val) { // array[j] > val 16 | array[j+1] = array[j]; 17 | j--; 18 | } 19 | array[j+1] = val; // array[j+1] 不是array[j] 20 | } 21 | } 22 | ``` 23 | 24 | ### 2.希尔排序 25 | - **思想**:希尔排序根据增量值对数据按下标进行分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整体采用直接插入排序得到有序数组,算法终止。 26 | - **时间复杂度**:O(n2) O(n) O(n1.5) (最坏,最好,平均) 27 | - **空间复杂度**:O(1) 28 | - **稳定性**:不稳定 因为是分组进行直接插入排序,原来相同的两个数字可能会被分到不同的组去,可能会使得后面的数字会排到前面,使得两个相同的数字排序前后位置发生变化。 29 | - **不稳定举例**: 4 3 3 2 按2为增量分组,则第二个3会跑到前面 30 | 31 | ``` 32 | public static void shellSort(int[] array) { 33 | int len; 34 | len = array.length / 2; // 分成n/2组 35 | while (len >= 1) { 36 | for (int i = len; i < array.length; ++i) { //对每组进行直接插入排序 37 | int temp = array[i]; 38 | int j = i - len; 39 | while (j >= 0 && array[j] > temp) { 40 | array[j + len] = array[j]; 41 | j -= len; 42 | } 43 | array[j + len] = temp; 44 | } 45 | len /= 2; 46 | } 47 | } 48 | ``` 49 | 50 | ## 二. 交换排序 51 | ### 3.冒泡排序 52 | - **思想**:对待排序元素的关键字从后往前进行多遍扫描,遇到相邻两个关键字次序与排序规则不符时,就将这两个元素进行交换。这样关键字较小的那个元素就像一个泡泡一样,从最后面冒到最前面来。 53 | - **时间复杂度**:最坏:O(n2) 最好: O(n) 平均: O(n2) 54 | - **空间复杂度**:O(1) 55 | - **稳定性**:稳定,相邻的关键字两两比较,如果相等则不交换。所以排序前后的相等数字相对位置不变。 56 | 57 | ``` 58 | public static void bubbleSort(int[] array) { 59 | boolean flag; // 用来判断当前这一轮是否有交换数值,若没有则表示已经排好许了 60 | for (int i = 0; i < array.length; i++) { 61 | flag = false; 62 | /** 63 | * 这边要注意 for (int j = array.length -1; j >= i + 1; j--)。 不要写成 64 | * for (int j = i + 1; j < array.length ; j++) 65 | */ 66 | for (int j = array.length -1; j >= i + 1; j--) { 67 | if (array[j -1 ] > array[j]) { 68 | //数据交换 69 | int temp = array[j - 1]; 70 | array[j - 1] = array[j]; 71 | array[j] = temp; 72 | //设置标志位 73 | flag = true; 74 | } 75 | } 76 | if (!flag) { 77 | break; 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | ### 4.快速排序 84 | - **思想**:该算法是分治算法,首先选择一个基准元素,根据基准元素将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。基准元素的选择对快速排序的性能影响很大,所有一般会想打乱排序数组选择第一个元素或则随机地从后面选择一个元素替换第一个元素作为基准元素。 85 | - **时间复杂度**:最坏:O(n2) 最好: O(nlogn) 平均: O(nlogn) 86 | - **空间复杂度**:O(nlogn)用于方法栈 87 | - **稳定性**:不稳定 快排会将大于等于基准元素的关键词放在基准元素右边,加入数组 1 2 2 3 4 5 选择第二个2 作为基准元素,那么排序后 第一个2跑到了后面,相对位置发生变化。 88 | 89 | **代码**: 90 | 91 | ``` 92 | public static void quickSort(int[] array) { 93 | partition(array, 0, array.length - 1); 94 | } 95 | 96 | private static void quickSort(int[] array, int low, int high) { 97 | if (low < high) { 98 | int p = partition(array, low, high); 99 | quickSort(array, low, p - 1); 100 | quickSort(array, p + 1, high); 101 | } 102 | } 103 | 104 | private static int partition(int[] array, int left, int right) { 105 | Random random = new Random(System.currentTimeMillis()); 106 | int idx = random.nextInt(right - left + 1) + left; // 这边需要注意right - left + 1 (要加1) 107 | exch(array, idx, left); 108 | int val = array[left]; 109 | while (left < right) { 110 | while (left < right && array[right] > val) { 111 | right--; 112 | } 113 | if (left < right) { 114 | array[left++] = array[right]; 115 | } 116 | while (left < right && array[left] < val) { 117 | left++; 118 | } 119 | if (left < right) { 120 | array[right--] = array[left]; 121 | } 122 | } 123 | array[left] = val; 124 | return left; 125 | } 126 | ``` 127 | 128 | 三向快速排序算法: 129 | 130 | ``` 131 | // 三向切分快速排序, 适用于存在大量重复元素的数组。 推荐 132 | public static void quick2waySort(int[] array) { 133 | quick2waySort(array, 0, array.length - 1); 134 | } 135 | 136 | private static void quick2waySort(int[] array, int lo, int hi) { 137 | if (hi <= lo) { 138 | return; 139 | } 140 | // lt以左都小于val, gt以右都大于val, i 用于移动遍历 141 | int lt = lo, gt = hi, i = lo + 1; 142 | int val = array[lo]; 143 | while (i <= gt) { // 等于 144 | if (array[i] < val) { 145 | exch(array, i++, lt++); 146 | } else if (array[i] > val) { 147 | exch(array, i, gt--); // i不变 148 | } else { 149 | i++; 150 | } 151 | } 152 | array[i-1] = val; 153 | // lt 到 gt 之间的都是等于val 的. 如果存在大量重复元素的数组使用该算法可以极大提升算法效率, 154 | quick2waySort(array, lo, lt - 1); 155 | quick2waySort(array, gt + 1, hi); 156 | } 157 | 158 | ``` 159 | 160 | ## 三. 选择排序 161 | ### 5.直接选择排序 162 | - **思想**:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后每次从剩余未排序元素中继续寻找最小(大)元素放到已排序序列的末尾。以此类推,直到所有元素均排序完毕. 163 | - **时间复杂度**:最坏:O(n^2) 最好: O(n^2) 平均: O(n^2) 164 | - **空间复杂度**:O(1) 165 | - **稳定性**:不稳定 例如数组 2 2 1 3 第一次选择的时候把第一个2与1交换使得两个2的相对次序发生了改变。 166 | 167 | ``` 168 | public static void selectSort(int[] array) { 169 | for (int i = 0; i < array.length; i++) { 170 | int minIdx = i; 171 | for (int j = i + 1; j < array.length; j++) { 172 | if (array[j] < array[minIdx]) { 173 | minIdx = j; 174 | } 175 | } 176 | exch(array, i, minIdx); 177 | } 178 | } 179 | ``` 180 | 181 | ### 6.堆排序 182 | - **思想**:堆排序是利用堆的性质进行的一种选择排序,先将排序元素构建一个最大堆,每次堆中取出最大的元素并调整堆。将该取出的最大元素放到已排好序的序列前面。这种方法相对选择排序,时间复杂度更低,效率更高。 183 | - **时间复杂度**:最坏:O(nlog2n) 最好: O(nlog2n) 平均: O(nlog2n) 184 | - **空间复杂度**:O(1) 185 | - **稳定性**:不稳定 例如 5 10 15 10。 如果堆顶5先输出,则第三层的10(最后一个10)的跑到堆顶,然后堆稳定,继续输出堆顶,则刚才那个10跑到前面了,所以两个10排序前后的次序发生改变。 186 | 187 | 188 | ``` 189 | // 第一个元素没有利用 190 | public static void heapSort(int[] array) { 191 | int N = array.length -1; 192 | for (int k = N / 2; k >= 1; k--) { // k >= 1 193 | sink(array, k, N); 194 | } 195 | while (N > 1) { 196 | // 最大堆, 选择最大值放在最后 197 | exch(array, 1, N --); 198 | sink(array, 1, N); 199 | } 200 | } 201 | 202 | private static void sink(int[] array, int k, int N){ 203 | while (2 * k <= N) { 204 | int j = 2 * k; 205 | if (j < N && array[j] < array[j+1]) { // < 206 | j++; 207 | } 208 | if (array[j] < array[k]) break; // < 209 | exch(array, k, j); 210 | k = j; 211 | } 212 | } 213 | ``` 214 | 215 | ### 7.归并排序 216 | - **思想**:归并排序采用了分治算法,首先递归将原始数组划分为两个子数组,直到数组元素个数为1,对每个子数组进行排序。然后将排好序的子数组递归合并成一个有序的数组。 217 | - **时间复杂度**:最坏:O(nlog2n) 最好: O(nlog2n) 平均: O(nlog2n) 218 | - **空间复杂度**:O(n) 219 | - **稳定性**:稳定 220 | 221 | **代码**: 222 | 223 | ``` 224 | public static void mergeSort(int[] array) { 225 | sort(array, 0, array.length - 1); 226 | } 227 | private static void sort(int[] array, int left, int right) { 228 | if (left < right) { 229 | int middle = (left + right) >> 1; 230 | //递归处理相关的合并事项 231 | sort(array, left, middle); 232 | sort(array, middle + 1, right); 233 | merge(array, left, middle, right); 234 | } 235 | } 236 | private static void merge(int[] array, int lo, int mid, int hi) { 237 | //创建一个临时数组用来存储合并后的数据 238 | int[] temp = new int[array.length]; 239 | int left = lo; 240 | int right = mid + 1; 241 | int k = lo; 242 | while (left <= mid && right <= hi) { 243 | if (array[left] < array[right]) 244 | temp[k++] = array[left++]; 245 | else 246 | temp[k++] = array[right++]; 247 | } 248 | //处理剩余未合并的部分 249 | while (left <= mid) temp[k++] = array[left++]; 250 | while (right <= hi) temp[k++] = array[right++]; 251 | //将临时数组中的内容存储到原数组中 252 | while (lo <= hi) array[lo] = temp[lo++]; 253 | } 254 | ``` 255 | 256 | ### 8.基数排序算法 257 | - **思想**:基数排序是通过“分配”和“收集”过程来实现排序,首先根据数字的个位的数将数字放入0-9号桶中,然后将所有桶中所盛数据按照桶号由小到大,桶中由顶至底依次重新收集串起来,得到新的元素序列。然后递归对十位、百位这些高位采用同样的方式分配收集,直到没各位都完成分配收集得到一个有序的元素序列。 258 | - **时间复杂度**:最坏:O(d(r+n)) 最好:O(d(r+n)) 平均: O(d(r+n)) 259 | - **空间复杂度**:O(dr+n) n个记录,d个关键码,关键码的取值范围为r 260 | - **稳定性**:稳定 基数排序基于分别排序,分别收集,所以其是稳定的排序算法。 261 | 262 | 263 | 为什么从底部取?因为桶内部是有序的,根据先进先出保证顺序 264 | -------------------------------------------------------------------------------- /algorithm/section02.md: -------------------------------------------------------------------------------- 1 | ## 一.顺序查找 2 | **1.1 思路**:这是最简单的算法,从头开始遍历每个元素,并将每个元素与查找元素比较,如果一致则返回。
3 | **1.2 时间复杂度**: O(N)
4 | **1.3 空间复杂度**: O(1)
5 | **1.4 代码**: 6 | 7 | public int search(int[] array, int num) { 8 | if(array == null || array.length == 0) { 9 | return -1; 10 | } 11 | for(int i = 0; i < array.length; i++) { 12 | if (array[i] == num) { 13 | return i; 14 | } 15 | } 16 | return -1; 17 | } 18 | 19 | 20 | ## 二.二分查找 21 | **2.1 思路**:二分查找前提是查找的数组是有序的,利用数据有序的特性提高查找性能。首先与数组中间位置的值比较,如果查找值大于中间位置值,则对数组右边以相同的思路查找,否则在左边以相同方式查找。这种方式使得每次查找范围变为原来的1/2.
22 | **2.2 时间复杂度**: O(log2n) 23 | **2.3 空间复杂度**: O(1)
24 | 25 | public int halfSearch(int[] array, int num) { 26 | if(array == null || array.length == 0) { 27 | return -1; 28 | } 29 | int lo = 0, hi = array.length-1; 30 | while(lo <= hi) { 31 | int mid = (lo + hi) >> 1; 32 | if (array[mid] == num) { 33 | return mid; 34 | } else if (array[mid] < num) { 35 | hi = mid -1; 36 | } else { 37 | lo = mid + 1; 38 | } 39 | } 40 | return -1; 41 | } 42 | 43 | ## 三. 变种二分查找 44 | 45 | http://www.cr173.com/html/20428_1.html 46 | 47 | ## 四. hash 算法 48 | **4.1 思想**:哈希表是根据设定的**哈希函数H(key)**和**处理冲突方法**将一组关键字映射到一个有限的地址区间上,并将关键字对应的值存储在该地址空间,可以通过关键字快速获取对应的值,这种表称为哈希表或散列,所得存储位置称为哈希地址或散列地址。作为线性数据结构与表格和队列等相比,哈希表无疑是查找速度比较快的一种。 49 | **4.2 查找复杂度:** O(1) 50 | **4.3 哈希函数**: 51 | 1. **直接寻址法**:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a?key + b,其中a和b为常数(这种散列函数叫做自身函数) 52 | 2. **数字分析法**:因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。 53 | 3. **平方取中法**:取关键字平方后的中间几位作为散列地址 54 | 4. **折叠法**:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。 55 | 5. **除留余数法**:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。 56 | 57 | **4.4 hash冲突及解决** 58 | hash冲突在所难免,解决冲突是一个复杂问题。冲突主要取决于: 59 | (1)与散列函数有关,一个好的散列函数的值应尽可能平均分布。 60 | (2)与解决冲突的哈希冲突函数有关。 61 | (3)与负载因子的大小。太大不一定就好,而且浪费空间严重,负载因子和散列函数是联动的。 62 | 解决冲突的办法: 63 | (1)**开放定址法**:线性探查法、平方探查法、伪随机序列法、双哈希函数法。 64 | (2) **拉链法**:把所有同义词,即hash值相同的记录,用单链表连接起来。 65 | 66 | **4.5 应用:** 67 | 1.字符串哈希 68 | 2.加密哈希 69 | 3.几何哈希 70 | 4.布隆过滤器 71 | 72 | **4.6 不足:**获取有序序列复杂度高 73 | 74 | 参考: 75 | http://www.tuicool.com/articles/RnErui 76 | 77 | ## 五.二叉树搜索树 78 | #### 5.1 思想 79 | 二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树: 80 | 1.若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 81 | 2.若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 82 | 3.任意节点的左、右子树也分别为二叉查找树。 83 | 4.没有键值相等的节点(no duplicate nodes)。 84 | 复杂度: 插入和查找的时间复杂度均为O(logN), 最坏为O(N) 85 | 86 | #### 5.2 插入 87 | 1. 如果当前结点是null,则创建新结点返回。 88 | 2. 如果插入结点比当前结点值大,则插入其右孩子结点中。 89 | 3. 如果插入结点比当前结点值小,则插入其左孩子结点中。 90 | **复杂度**: 平均 O(logn) 最坏O(n) 91 | **代码:** 92 | 93 | public Tree insert(Tree root, int val) { 94 | if (root == null) { 95 | return new Tree(val); 96 | } 97 | if (val == root.val) { 98 | return root; 99 | } else if (val > root.val) { 100 | root.right = insert(root.right, val); 101 | } else { 102 | root.left = insert(root.left, val); 103 | } 104 | return root; 105 | } 106 | 107 | 108 | #### 5.3 查找 109 | ##### 5.3.1 某个值 110 | **思想**:查找方式有点类型二分查找方法,知识这里采用的树结构进行存储。首先与根结点进行判断: 111 | 1. 如果当前结点为null,则直接放回Null。 112 | 2. 如果当前结点值与查找值相同则返回当前结点. 113 | 3. 如果当前结点值小余查找值,则递归地到当前结点的右孩子查找 114 | 4. 如果当前结点值大于查找值,则递归地到当前结点的左孩子查找。 115 | **时间复杂度**: 平均O(logn) 最坏O(n) 116 | 117 | public Tree search(Tree root, int val) { 118 | if(root == null) { 119 | return null; 120 | } 121 | if(root.val == val) { 122 | return root; 123 | } else if(root.val > val) { 124 | return search(root.left, val); 125 | } else { 126 | return search(root.right, val); 127 | } 128 | } 129 | 130 | ####5.3.2 查找最小值 131 | **思想**:根据二叉搜索树的特点,最小结点都是在最左结点上或者如果根结点无左孩子便是其本身. 132 | 133 | public Tree min(Tree root) { 134 | if(root == null) { 135 | return null; 136 | } 137 | if (root.left != null) { 138 | return min(root.left); 139 | } 140 | return root; 141 | } 142 | 143 | #### 5.3.2 查找最大结点 144 | **思想**: 同最小结点。 145 | 146 | public Tree max(Tree root) { 147 | if(root == null) { 148 | return null; 149 | } 150 | if (root.right != null) { 151 | return max(root.right); 152 | } 153 | return root; 154 | } 155 | 156 | ### 5.4 删除 157 | #### 5.4.1 删除最小结点 158 | **思想:**找到根结点最左结点,如果其不存在右孩子则直接删除,否则用右孩子替换最左结点。需要注意的是根结点可能为null和不存在做孩子的情况。 159 | 160 | public Tree deleteMin(Tree root) { 161 | if(root == null) { 162 | return null; 163 | } 164 | if(root.left != null) { 165 | root.left = deleteMin(root.left); 166 | } else if (root.right != null) { 167 | return root.right; 168 | } 169 | return null; 170 | } 171 | 172 | #### 5.4.2 删除最大结点 173 | **思想:** 与删除最小结点类型,根据二叉搜索树的特性,最大结点是根结点的最右孩子。所以只要找到最右孩子结点,其存在左结点的话就用左结点替换否则直接删除. 174 | 175 | public Tree deleteMax(Tree root) { 176 | if(root == null) { 177 | return null; 178 | } 179 | if(root.right != null) { 180 | root.right = deleteMax(root.right); 181 | } else if(root.left != null) { 182 | return root.left; 183 | } 184 | return null; 185 | } 186 | 187 | ### 5.4.3 删除某个结点 188 | **思想:**要删除一个结点首先需要找到该结点的位置,采用上面的查找方式进行查找。找到结点后就是删除的问题的,可以按照下面的策略进行删除。 189 | 1. 如果一个结点无做孩子和右孩子,那么就可以直接删除 190 | 2. 如果只存在一个孩子结点,则用孩子结点替换。 191 | 3. 如果存在两个孩子结点,那么可以用其左孩子最大的结点或右孩子最小结点替换,并删除最左孩子结点或最右孩子结点. 192 | 193 | 194 | public Tree delete(Tree root, int val) { 195 | if(root == null) { 196 | return null; 197 | } 198 | if(root.val == val) { 199 | if(root.left == null && root.right == null) { 200 | return null; 201 | } else if(root.left!= null && root.right != null) { 202 | Tree leftBig = max(root.left); 203 | root.val = leftBig.val; 204 | root.left = delete(root.left, leftBig.val); 205 | } else if(root.left != null){ 206 | return root.left; 207 | } else { 208 | return root.right; 209 | } 210 | } else if(root.val < val) { 211 | root.right = delete(root.right, val); 212 | } else { 213 | root.left = delete(root.left, val); 214 | } 215 | return root; 216 | } 217 | 218 | 参考: http://www.cnblogs.com/vamei/archive/2013/03/17/2962290.html 219 | http://blog.jobbole.com/79305/ 220 | 221 | #### 5.5 相关的算法题 222 | **剑指offer**: 223 | 224 | 225 | ## 6. AVL查找树 226 | ### 6.1 思想 227 | 二叉树查找树在插入时没有对二叉树的深度和结构做一个调整,使得叶子结点深度不一,在查找时深度越深的结点时间复杂度越高。为了改进查找的时间时间复杂度,于是出现了平衡二叉树(AVL).**平衡二叉树使得每个结点的左结点和右结点的深度差不超过1.** 228 | ### 6.2 查找 229 | 查找与二叉查找树一样。 230 | 231 | ### 6.3 插入 232 | 当在AVL中插入新的结点时,需要根据实际情况对AVL中的某些结点做单旋转或双旋转操作,单旋转表示做一次顺时针或逆时针的旋转操作,而双旋转则做两次单旋转操作(先顺时针后逆时针,或者先逆时针后顺时针),单旋转发生在LL型插入和RR型插入,而双旋转则发生在LR型插入和RL型插入。以下的失去平衡点都指的是离插入点最近的那个失去平衡的结点。 233 | LL型:插入点位于失去平衡点的左孩子的左子树上; 234 | RR型:插入点位于失去平衡点的右孩子的右子树上; 235 | LR型:插入点位于失去平衡点的左孩子的右子树上; 236 | RR型:插入点位于失去平衡点的右孩子的左子树上。 237 | 238 | **插入思路** 239 | 和二叉搜索树的插入一样,首先在树中找到对应的位置然后插入,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有 1.5 乘 log n 个节点,而每次AVL 旋转都耗费恒定的时间,插入处理在整体上耗费 O(log n) 时间。  240 | 具体插入过程如下: 241 | 1. 如果当前结点为空,创建新结点返回. 242 | 2. 如果当前结点值和插入值相同,不做处理返回。 243 | 3. 如果插入值大于当前结点则插入到右其右孩子结点中。插入完成后比较左右孩子结点进行判断树是否失去平衡。如果是判断属于那种类型(RR, RL),并响应的旋转。最后更新当前结点的深度(孩子结点的最大深度加1,默认null深度为-1)。 244 | 4. 否则插入到其做孩子上。 同样比较左右孩子的深度判断是否平衡,对于失去平衡的情况下做出调整。 245 | 246 | 247 | private AvlNode insert(AnyType x, AvlNode t) { 248 | if (t == null) 249 | return new AvlNode(x, null, null); 250 | int compareResult = myCompare(x, t.element); 251 | if (compareResult < 0) { 252 | t.left = insert(x, t.left); 253 | if (height(t.left) - height(t.right) == 2) { 254 | if (myCompare(x, t.left.element) < 0) //左左情况 255 | t = rotateWithLeftChild(t); 256 | else //左右情况 257 | t = doubleWithLeftChild(t); 258 | } 259 | } else if (compareResult > 0) { 260 | t.right = insert(x, t.right); 261 | if (height(t.right) - height(t.left) == 2) { 262 | if (myCompare(x, t.right.element) < 0) //右左情况 263 | t = doubleWithRightChild(t); 264 | else //右右情况 265 | t = rotateWithRightChild(t); 266 | } 267 | } 268 | //完了之后更新height值 269 | t.height = Math.max(height(t.left), height(t.right)) + 1; 270 | return t; 271 | } 272 | 273 | ### 6.4 删除 274 | 从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有 log n个节点被旋转,而每次 AVL 旋转耗费恒定的时间,删除处理在整体上耗费 O(log n) 时间。 275 | a.当被删除节点n是叶子节点,直接删除 276 | b.当被删除节点n只有一个孩子,删除n,用孩子替代该节点的位置 277 | c.当被删除结点n存在左右孩子时,真正的删除点应该是n的中序遍在前驱,或者说是左子树最大的节点,之后n的值替换为真正删除点的值。这就把c归结为a,b的问题。 278 | 279 | 从删除的结点处自低向上向根结点折回,根据当前结点的左右孩子深度判断是否平衡,如果不平衡则按选择规则进行旋转。最后更新当前结点深度,如此递归折回到根结点。 280 | 281 | http://blog.csdn.net/xiaofan086/article/details/8294382 282 | http://blog.csdn.net/liyong199012/article/details/29219261 283 | http://www.mamicode.com/info-detail-90162.html 284 | ## 七. B-树(B树) 285 | #### 7.1 定义 286 | B-树是一种平衡的多路查找树,它在文件系统中很有用。 287 | 定义:一棵m 阶的B-树,或者为空树,或为满足下列特性的m 叉树: 288 | 289 | - 树中每个结点至多有m 棵子树; 290 | - 若根结点不是叶子结点,则至少有两棵子树; 291 | - 除根结点之外的所有非终端结点至少有⎡m/2⎤ 棵子树; 292 | - 所有的非终端结点中包含以下信息数据:(n,A0,K1,A1,K2,…,Kn,An) 293 | 其中: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 为关键码的个数。 294 | - 所有的叶子结点都出现在同一层次上,并且**不带信息**(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空 295 | 296 | #### 7.2 查找 297 | B-树的查找是由两个基本操作交叉进行的过程,即 298 | - 在B-树上找结点; 299 | - 在结点中找关键码。 300 | 301 | 由于,通常B-树是存储在外存上的,操作⑴就是通过指针在磁盘相对定位,将结点信息读入内存,之后,再对结点中的关键码有序表进行顺序查找或折半查找。因为,在磁盘上读取结点信息比在内存中进行关键码查找耗时多,每次向下搜索一层都需要从内存中加载磁盘信息,B-树的层次树是决定B-树查找效率的首要因素。 302 | 303 | #### 7.2 插入 304 | 在B-树上插入关键码与在二叉排序树上插入结点不同,关键码的插入不是在叶结点上 305 | 进行的,而是在最底层的某个非终端结点中添加一个关键码,若该结点上关键码个数不超过m-1 个,则可直接插入到该结点上;否则,该结点上关键码个数至少达到m 个,因而使该结点的子树超过了m棵,这与B-树定义不符。所以要进行调整,即结点的“分裂”。方法为:关键码加入结点后,将结点中的关键码分成三部分,使得前后两部分关键码个数个结点将其插入到父结点中。若插入父结点而使父结点中关键码个数超过m-1,则父结点继续分裂,直到插入某个父结点,其关键码个数小于m。可见,B-树是从底向上生长的。 306 | 307 | #### 7.3 删除 308 | 分两种情况: 309 | (1)删除最底层结点中关键码 310 | 311 | - 若结点中关键码个数大于⎡m / 2⎤ -1,直接删去。 312 | - 否则除余项与左兄弟(无左兄弟,则找左兄弟)项数之和大于等于2( -1) 就与它 313 | 们父结点中的有关项一起重新分配 314 | 315 | (2)删除为非底层结点中关键码 316 | 若所删除关键码非底层结点中的Ki,则可以指针Ai 所指子树中的最小关键码X 替代 317 | Ki,然后,再删除关键码X,直到这个X 在最底层结点上,即转为(1)的情形 318 | 319 | #### 7.2 B-树的特性: 320 | 1. 关键字集合分布在整颗树中; 321 | 2. 任何一个关键字出现且只出现在一个结点中; 322 | 3. 搜索有可能在非叶子结点结束; 323 | 4. 其搜索性能等价于在关键字全集内做一次二分查找; 324 | 5. 自动层次控制; 325 | 326 | http://c.biancheng.net/cpp/html/1028.html 327 | ## 八. B+树 328 | #### 8.1 定义 329 | - 有n 棵子树的结点中含有n 个关键码; 330 | - 所有的叶子结点中包含了全部关键码的信息,及指向含有这些关键码记录的指针,且 331 | 叶子结点本身依关键码的大小自小而大的顺序链接。 332 | - 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键码。 333 | 334 | #### 8.2 B+的特性 335 | 1. 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的; 336 | 2. 不可能在非叶子结点命中; 337 | 3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层; 338 | 4. 更适合文件索引系统; 339 | 340 | #### 8.3 操作 341 | 1)查找操作 342 | 343 | 对B+树可以进行两种查找运算: 344 | - 从最小关键字起顺序查找; 345 | - 从根结点开始,进行随机查找。 346 | 347 | 在查找时,若非终端结点上的剧组机等于给定值,并不终止,而是继续向下直到叶子结点。因此,在B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子结点的路径。其余同B-树的查找类似。 348 | 349 | 2).插入操作 350 | 351 | B+树的插入与B树的插入过程类似。不同的是B+树在叶结点上进行,如果叶结点中的关键码个数超过m,就必须分裂成关键码数目大致相同的两个结点,并保证上层结点中有这两个结点的最大关键码。 352 | 353 | 3)删除操作 354 | 355 | B+树的删除也仅在叶子结点进行,当叶子结点中的最大关键字被删除时,其在非终端结点中的值可以作为一个“分界关键字”存在。若因删除而使结点中关键字的个数少于m/2 (m/2结果取上界,如5/2结果为3)时,其和兄弟结点的合并过程亦和B-树类似。 356 | 357 | ### 7.2 B+树和B-树最大的不同点: 358 | 1).B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。 359 | 360 | 2).在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。 361 | 362 | 3)B+树支持range-query非常方便,而B树不支持。这是数据库选用B+树的最主要原因 363 | http://www.cnblogs.com/biyeymyhjob/archive/2012/07/25/2608880.html 364 | http://blog.csdn.net/v_JULY_v/article/details/6530142/ 365 | 366 | ## 8. 红黑树 367 | #### 思想 368 | 红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。 369 | 370 | #### 特性 371 | 性质1. 节点是红色或黑色。 372 | 性质2. 根是黑色。 373 | 性质3. 所有叶子都是黑色(叶子是NIL节点)。 374 | 性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) 375 | 性质5. 从任一节点到其每个叶子的所有简单路径 都包含相同数目的黑色节点。 376 | 377 | #### 插入 378 | II、红黑树插入的几种情况: 379 | 情况1,z的叔叔y是红色的。 380 | 情况2:z的叔叔y是黑色的,且z是右孩子 381 | 情况3:z的叔叔y是黑色的,且z是左孩子 382 | 383 | #### 删除 384 | III、红黑树删除的几种情况。 385 | 情况1:x的兄弟w是红色的。 386 | 情况2:x的兄弟w是黑色的,且w的俩个孩子都是黑色的。 387 | 情况3:x的兄弟w是黑色的,且w的左孩子是红色,w的右孩子是黑色。 388 | 情况4:x的兄弟w是黑色的,且w的右孩子是红色的。 389 | 390 | http://www.cnblogs.com/daoluanxiaozi/p/3340382.html 391 | #### 红黑树和AVL树的比较: 392 | 红黑树: 393 | 394 | - (1)并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。 395 | - (2)此外,由于它的设计,**任何不平衡都会在三次旋转之内解决**。红黑树能够给我们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。 396 | 397 | AVL树: 398 | 399 | - 它的左子树和右子树都是AVL树,左子树和右子树的高度差不能超过; 400 | - 查找、插入和删除在平均和最坏情况下都是O(log n),增加和删除可能需要通过一次或**多次**树旋转来重新平衡这个树; 401 | - 一棵n个结点的AVL树的其高度保持在0(log2(n)),不会超过3/2log2(n+1) 402 | 一棵n个结点的AVL树的平均搜索长度保持在0(log2(n)). 403 | 一棵n个结点的AVL树删除一个结点做平衡化旋转所需要的时间为0(log2(n)). 404 | 405 | ## 哈夫曼树。 406 | 407 | 408 | 409 | 个人公众号(欢迎关注):
410 | ![](/assets/weix_gongzhonghao.jpg) 411 | 412 | -------------------------------------------------------------------------------- /algorithm/string.md: -------------------------------------------------------------------------------- 1 | # 排序 2 | 3 | > 键索引计数法: 4 | > - 频率统计:使用int数组count[]计算每个建出现的频率 5 | > - 将频率转化为索引:使用count来计算每个键在排序结果中的起始索引位置 6 | > - 数据分类:将所有元素转移到一个辅助数组aux中以进行排序。 7 | > - 回写:将元素排序后的结果复制回原数组。 8 | 9 | 10 | ## 低优先字符排序 11 | **思路**:从右到左以每个位置的字符为键,用键索引计数法将字符串排序W遍(字符串长度) 12 | **复杂度**: O(NW) W为字符串长度,N为元素选择空间 13 | 实现: 14 | ``` 15 | public static void sort(String[] a, int w) { 16 | // 通过前w个字符将a[] 排序 17 | int N = a.length; 18 | int R = 256; 19 | String[] aux = new String[N]; 20 | for (int d = w - 1; d >= 0; d--) { 21 | int[] count = new int[R + 1]; 22 | // 计算出频率 23 | for (int i = 0; i < N; i++) { 24 | count[a[i].charAt(d) + 1]++; 25 | } 26 | // 将频率转换为索引 27 | for (int r = 0; r < R; r++) { 28 | count[r + 1] += count[r]; 29 | } 30 | // 将元素分类 31 | for (int i = 0; i < N; i++ ) { 32 | aux[count[a[i].charAt(d)] ++] = a[i]; 33 | } 34 | for (int i = 0; i < N; i ++ ) { 35 | a[i] = aux[i]; 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | ## 高位优先字符串排序 42 | **思路**:解决字符串长度不一致的情况,采用从左到右的次序进行排序。 在高位优先排序时需要注意的字符串末尾的情况,因为字符串长度不一,合理的做法是将所有字符都已被检查的字符串所在的子数组排在所有子数组的前面,这样不需要对这些检查完成的字符串再次递归排序,也避免了IndexOutOfBound。 高位优先排序在已排号序的位置,根据字符进行分组,将数组分为更小的数组进行排序,利用了分治算法提高了算法的性能。 43 | **复杂度**:O(N) 到O(Nw)之间 44 | 实现: 45 | 46 | **三向字符串快速排序** 47 | 48 | **思路**:结合了快速排序和高位优先字符串排序算法,采用高位优先字符串排序算法的思路,根据字符串从左到右的字符对字符串进行排序。排序时使用快速排序的思想,使用首字符进行三向切分为首字母小余、等于,大于切分字符的字符串数组,然后递归地将得到的三个字数组排序: 49 | 50 | **优势**:高位优先字符串排序算法会产生大量的子数组,而三向字符串快速排序切分总只有三个。因此该算法能够很好处理等值键、有较长公告前缀的键、取值范围娇小的键和小数组。 相对于高位优先字符串排序需要的空间更少。 51 | 复杂度: 52 | 53 | **实现:** 54 | 55 | public static void sort(String[] strArr) { 56 | sort(strArr, 0, strArr.length - 1, 0); 57 | } 58 | 59 | /** 60 | * 对字符串串数组strArr的 strArr[lo]到strArr[hi]的字符串根据d为的字符进行排序 61 | * @param strArr 62 | * @param lo 63 | * @param hi 64 | * @param d 65 | */ 66 | private static void sort(String[] strArr, int lo, int hi , int d) { 67 | if (hi <= lo) { 68 | return; 69 | } 70 | int lt = lo, gt = hi; 71 | int val = strArr[lo].charAt(d); 72 | int i = lo + 1; 73 | while(i <= gt) { 74 | int tmp = strArr[i].charAt(d); 75 | if ( tmp < val) { 76 | exch(strArr, lt++, i++); 77 | } else if( tmp > val) { 78 | exch(strArr, i, gt--); 79 | } else { 80 | i ++; 81 | } 82 | } 83 | sort(strArr, lo, lt-1, d); 84 | if (val >= 0 ) { 85 | sort(strArr, lt, gt, d+1); 86 | } 87 | sort(strArr, gt+1, hi, d); 88 | } 89 | private static void exch(String[] a, int i ,int j) { 90 | String tmp = a[i]; 91 | a[i] = a[j]; 92 | a[j] = tmp; 93 | } 94 | 95 | > 注意:和快速排序一样,最好在排序之前将数组打乱或将第一个元素和一个随机位置的元素交换以得到一个随机的切分元素,避免快速排序算法接近最坏情况 96 | 97 | 总结: 98 | 99 | | 算法 | 稳定性 | 原地排序|时间复杂度|空间复杂度|优势领域| 100 | | -- | -- | -- | - | -| -| 101 | |字符串的插入排序| 是|是|N到N2之间|1|小数组或已经有序的数组| 102 | |快速排序|否|是|N(log2N)|logN|通用排序算法,特别适合空间不足的情况| 103 | |归并排序|是|否|N(log2N)|N|稳定的通用排序算法| 104 | |三向快速排序|否|是|N到NlogN之间|logN|大量重复建| 105 | |低位优先的字符串排序|是|否|NW|N|较长的定长字符串| 106 | |高位优先的字符串排序|是|否|N到Nw之间|N+WR|随机字符串| 107 | |三向字符串快速排序|否|是|N到Nw之间|W+logN|通用排序算法,特别适合用于含有较长公共前缀的字符串| 108 | 109 | 110 | # 查找树 111 | ## 单词查找树(trie) 112 | 113 | 基本性质: 是由连接结点组成的数据结构,除了根结点,每个结点有且只有一条从父结点指向它的链接。每条链接都对应着一个字符,也可以用链接所对应的字符标记被指向的结点。值为空的结点在符号表中没有对应的键,它们的存在是为了简化单词查找树的查找操作。 114 | 115 | 查找过程:从根结点开始,首先经过的键是首字母所对应的链接。在下一个结点中沿着第二个字符所对应的链接继续前进。依此类推,直到键的最后一个字符所指向的结点或者遇到一条空链接。此时有三种情况: 116 | 117 | - 键的尾字符所对应的结点中的值非空,这是一次命中查找,则返回尾字符所对应的结点中保存的值。 118 | - 键的尾字符所对应的结点中的值为空,表示未命中。 119 | - 查找结束于一条空链接,表示未命中。 120 | 121 | 插入过程:进行插入的时候和二叉查找树一样,需要先进行查找,会遇到以下两个情况: 122 | 123 | - 到达键尾部之前就遇到空链接,则创建对应的结点并将键值保存到最后一个字符的结点中。 124 | - 在遇到空链接之前就到达了键的尾字符,在这种情况下,和关联性数组一样,将该结点的值设置为键所对应的值。 125 | 126 | 删除过程:首先找到对应的结点并将它的值设置为空,如果该结点含有一个非空的链接指向某个子结点那么就不需要进行其他操作。如果它所有链接均为空,那么就需要从数据结构中删去这个结点,如果删去它使得它的父节点的所有链接也均为空,就需要继续删除它的父结点,依此类推。 127 | 128 | 性质: 129 | 130 | - 单词查找树的链表结构和键的插入或删除顺序无关:对于给定任意一组键,其单词查找树都是唯一的。 131 | - 在单词查找树中查找一个键或插入一个键时,访问数组的次数最多的键的长度加1. 132 | - 未命中查找平均所需检查的结点的数量为 logRN 133 | - 一棵单词查找树的链接总数在RN到RNw之间,其中w为建的平均长度。 ??? 134 | 135 | 应用: 136 | 137 | - 数字帐号 138 | - URL 139 | - 文本处理 140 | - 基因组数据中的蛋白质 141 | 142 | 143 | 代码实现: 144 | 145 | - 三向单词查找树 146 | 147 | 思想:为了避免R向单词查找树过度的浪费空间,三向单词查找树每个结点都含有一个字符、三条链接和一个值。这三条链接分别对应着当前字母小于、等于和大于结点字母的所有键。 148 | 149 | 查找:查找时比较键的首字母和根结点的字母,如果键的首字母较小,就选择左链接。如果较大,就选择右链接;如果相等,则选择中链接。然后递归的使用相同的算法,如果遇到一个空链接或则当前键结束时结点的值为空,那么查找未命中;如果键结束时结点的值非空则查找命中。 150 | 151 | 优势:节省大量空间。 152 | 153 | 性质: 154 | 155 | - 每个结点只有三个链接,链接总数在3N到3Nw之间 156 | - 平均查找时间复杂度为lnN 次 157 | 158 | 159 | ## 子字符串查找 160 | 161 | - 暴力字符串查找算法 162 | - KMP算法 163 | 164 | 思想:KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,只要继续把它向后移和移动匹配词就可以,这样就提高了效率。可以针对搜索词,算出一张部分匹配表。通过查表查到最后一个匹配字符对应的部分匹配值,并利用以下公式计算匹配词向后移动的位数: 165 | 166 | 移动位数 = 已匹配的字符数 - 对应的部分匹配值 167 | 168 | "部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例, 169 | 170 | - "A"的前缀和后缀都为空集,共有元素的长度为0; 171 | - "AB"的前缀为[A],后缀为[B],共有元素的长度为0; 172 | - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0; 173 | - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0; 174 | - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1; 175 | - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2; 176 | - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。 177 | 178 | 179 | 实现: 180 | ``` 181 | /** 182 | * 计算部分匹配表 183 | * 184 | * @param pattern 185 | * @param next 186 | */ 187 | public void makeNext(char[] pattern, int next[]) { 188 | int pIdx, maxSuffixLen; // pIdx:模版字符串下标;maxSuffixLen:最大前后缀长度 189 | int m = pattern.length; // 模版字符串长度 190 | next[0] = 0; //模版字符串的第一个字符的最大前后缀长度为0 191 | for (pIdx = 1, maxSuffixLen = 0; pIdx < m; ++pIdx) //for循环,从第二个字符开始,依次计算每一个字符对应的next值 192 | { 193 | /** 194 | * maxSuffixLen 大于0 表示前一个字符已经存在匹配 195 | */ 196 | while (maxSuffixLen > 0 && pattern[pIdx] != pattern[maxSuffixLen]) { //递归的求出P[0]···P[q]的最大的相同的前后缀长度k 197 | maxSuffixLen = next[maxSuffixLen - 1]; //不理解没关系看下面的分析,这个while循环是整段代码的精髓所在,确实不好理解 198 | } 199 | if (pattern[pIdx] == pattern[maxSuffixLen]) //如果相等,那么最大相同前后缀长度加1 200 | { 201 | maxSuffixLen++; 202 | } 203 | next[pIdx] = maxSuffixLen; 204 | } 205 | } 206 | 207 | public int kmp(String str, String pattern) { 208 | int[] next = new int[str.length()]; 209 | int strIdx, pIdx; 210 | makeNext(pattern.toCharArray(), next); 211 | 212 | for (strIdx = 0, pIdx = 0; strIdx < str.length(); ++strIdx) { 213 | while (pIdx > 0 && pattern.charAt(pIdx) != str.charAt(strIdx)) { 214 | /** 215 | * 移动匹配字符串位置 216 | */ 217 | pIdx = next[pIdx - 1]; 218 | } 219 | if (pattern.charAt(pIdx) == str.charAt(strIdx)) { 220 | pIdx++; 221 | } 222 | if (pIdx == pattern.length()) { 223 | return strIdx - pattern.length() + 1; 224 | } 225 | } 226 | return -1; 227 | } 228 | ``` 229 | 230 | 复杂度:时间复杂度最坏(3N) 空间复杂度 O(M) 231 | 232 | 233 | ##参考资料 234 | http://www.cnblogs.com/c-cloud/p/3224788.html 235 | 236 | 字符串S的最长回文子串S1 237 | 238 | 参考资料 239 | https://github.com/JohnZhengHub/blogs/blob/master/algorithm/algorithm-base/Manacher.md 240 | 241 | 242 | 个人公众号(欢迎关注):
243 | ![](/assets/weix_gongzhonghao.jpg) 244 | 245 | 246 | -------------------------------------------------------------------------------- /algorithm/tu.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # 一. 图遍历 4 | ## 1.1 广度优先遍历 (BFS) 5 | 类似树的层次遍历,首先访问起始顶点v,然后选取与v邻接的全部顶点w1,w2,…wn,进行访问。再依次访问与w1,w2,…wn邻接的全部顶点。依次类推,直到所有顶点都被访问过为止。从顶点一层层向外拓展和遍历,实现是需要用到队列。 6 | 7 | ## 1.2 深度优先遍历(DFS) 8 | 首先访问出发节点v,将其标记为已访问过;然后选取与v邻接的未被访问的任意一个顶点w,并访问它;再选取与w邻接的未被访问的任意一个顶点并访问,依次重复进行。当一个顶点的所有邻接顶点都被访问过,则依次退回到最近被被访问过的顶点,如果该顶点还有其他邻接顶点未被访问,则从这些未被访问的顶点中取一个重复上述访问过程。 9 | 10 | # 二. 最小生成树 11 | ## Prim 普里姆算法 12 | 思路: 该算法采用贪心思想,在图中任意选择一结点构建一颗生成树然后从所有与该生成树相邻的结点中取出最近的结点和边加入到生成树中.直到所有的结点都加入到该生成树中. 13 | 14 | 算法复杂度 ElogV 15 | 采用最小优先队列存放剩余的结点,每次从该最小队列中选出与生成树之间最小的结点加入生成树中,同时更新最小队列. 16 | 17 | ## 克鲁斯卡尔 18 | 克鲁斯卡尔算法每次从剩余的边中选择一个最小的边,如果当前边和已选取的边构成回路,则放弃该并选择下一个最小边。如果不构成回路则将该边以及连接的两个结点加入的生成树中.直到完成整棵生成树的构建. 19 | 20 | 时间复杂度, ElogE 21 | 22 | # 三. 距离 23 | ## 迪杰斯特拉 Dijkstra 24 | 思路: 它应用了贪心算法模式,算法解决的是有向图中单个源点到其他顶点的最短路径问题.引进两个集合S和T。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而T则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。 25 | 初始时,S中只有起点v0;T中是除v0之外的顶点,并且T中顶点的路径是"起点s到该顶点的路径"。从T中选择一个到v0最短的顶点vi并加入到S中.如果T集合中的结点以vi作为中转站到达s的距离比原来的小则更新对应的值.依次迭代,直到将T中的所有结点加入S中. 26 | 27 | 算法复杂度 ElogV 28 | ## 弗洛伊德 Floyed 29 | 是采用动态规划的思想计算任意两个结点之间的最短路径. 30 | 1) 初始化距离矩阵,对于所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。 31 | 2,对于每一对顶点 u 和 v,看看其他结点中否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。 32 | 33 | 应用: 34 | 在网络中 35 | 36 | 37 | 38 | 时间复杂度O(V^3)。 39 | ``` 40 | public class Floyed { 41 | private final int INF = Integer.MAX_VALUE; 42 | 43 | /** 44 | * 弗洛伊德算法 45 | * @param matrix 46 | */ 47 | public void Floyd(int[][] matrix) { 48 | int[][] path = new int[matrix.length][matrix[0].length]; 49 | int[][] dist = new int[matrix.length][matrix[0].length]; 50 | int size = matrix.length; 51 | //初始化 52 | for (int i = 0; i < size; i++) { 53 | for (int j = 0; j < size; j++) { 54 | path[i][j] = -1; 55 | dist[i][j] = matrix[i][j]; 56 | } 57 | } 58 | 59 | for (int k = 0; k < size; k++) { 60 | for (int i = 0; i < size; i++) { 61 | for (int j = 0; j < size; j++) { 62 | if (dist[i][k] != INF && dist[k][j] != INF && dist[i][k] + dist[k][j] < dist[i][j]) { 63 | dist[i][j] = dist[i][k] + dist[k][j]; 64 | path[i][j] = k; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /assets/beans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/assets/beans.png -------------------------------------------------------------------------------- /assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/assets/bg.jpg -------------------------------------------------------------------------------- /assets/collection_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/assets/collection_compare.png -------------------------------------------------------------------------------- /assets/java_collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/assets/java_collection.png -------------------------------------------------------------------------------- /assets/spring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/assets/spring.png -------------------------------------------------------------------------------- /assets/weix_gongzhonghao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/assets/weix_gongzhonghao.jpg -------------------------------------------------------------------------------- /author.md: -------------------------------------------------------------------------------- 1 | # 作者 2 | 畅逸风,毕业后加入百度,之后在蚂蚁中间件团队从事多年的中间件、分布式研发。 专注于中间件、分布式、云原生领域。 3 | 4 | 个人微信公众号, 5 | ![](/assets/weix_gongzhonghao.jpg) 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "互联网面试笔记", 3 | "description": "本书主要是对互联网面试中常见的基础知识做总结和分析", 4 | "language": "zh", 5 | "plugins": [ 6 | "duoshuo", 7 | "github", 8 | "editlink", 9 | "prism", 10 | "-highlight", 11 | "baidu", 12 | "splitter", 13 | "sitemap", 14 | "latex-codecogs", 15 | "-fontsettings", 16 | "donate" 17 | ], 18 | "pluginsConfig": { 19 | "duoshuo": { 20 | "short_name": "zhengjl", 21 | "theme": "default" 22 | }, 23 | "github": { 24 | "url": "https://github.com/zhengjianglong915" 25 | }, 26 | "editlink": { 27 | "base": "https://zhengjianglong.gitbooks.io/note-of-interview/content/", 28 | "label": "编辑本页" 29 | }, 30 | "baidu": { 31 | "token": "35d9061f8db12b29ed7072cdf69667f4" 32 | }, 33 | "sitemap": { 34 | "hostname": "http://zhengjianglong.leanote.com" 35 | }, 36 | "fontsettings": { 37 | "theme": "white", 38 | "family": "sans", 39 | "size": 3 40 | }, 41 | "donate": { 42 | "wechat": "http://wx4.sinaimg.cn/mw690/a196fc93ly1fdun4kzymej20i10i8tb6.jpg", 43 | "alipay": "http://wx3.sinaimg.cn/mw690/a196fc93ly1fdun4koc4rj208t08lgme.jpg", 44 | "title": "感谢支持!!", 45 | "button": "赏", 46 | "alipayText": "支付宝打赏", 47 | "wechatText": "微信打赏" 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /cache/redis.md: -------------------------------------------------------------------------------- 1 | # Redis面试常见题 2 | ## 1. 什么是redis? 3 | Redis 是一个基于内存的高性能key-value数据库。 以内存作为数据存储介质,所以读写数据的效率极高,远远超过数据库。 4 | 5 | Redis跟memcache不同的是,储存在Redis中的数据是持久化的,断电或重启后,数据也不会丢失。因为Redis的存储分为内存存储、磁盘存储和log文件三部分,重启后,Redis可以从磁盘重新将数据加载到内存中,这些可以通过配置文件对其进行配置,正因为这样,Redis才能实现持久化。 6 | 7 | Redis支持主从模式,可以配置集群,这样更利于支撑起大型的项目,这也是Redis的一大亮点。 8 | 9 | ## 2. Reids的特点 10 | - **高性能**: Redis是用C语言实现的, 并且数据都存储在内存中,查询数据很快。 11 | - **分布式**: redis通过槽管理方式,将所有的key分布在不同的槽,又将槽指定给不同的master节点。 12 | - **高可用性**: redis采用了主从设计,每个master节点都可以配置若干个从服务器。master向外提供读写服务,而从服务器用于同步master节点的缓存数据。**Redis-Sentinel**监听所有的监听所有的Redis主从服务器,一旦发现主服务器挂掉了,他会从若干服务器中选择一台新的机器作为master,其他从服务器会成为这台新master的从服务器。 13 | - **可伸缩性**: 当集群加入新的master节点,可以通过重新分配槽的方式,均衡各个服务器压力。一般采用reshard和rebalance函数来完成重新分槽和再均衡过程。 14 | - **单线程**: 利用redis队列技术并将访问变为串行访问,消除了传统数据库串行控制的开销。 减少并发控制的复杂度。 15 | - **持久化**: Redis的所有数据存储在内存中,对数据的更新将异步地保存到磁盘上。 16 | 17 | ## 3. redis的优点和缺点 18 | ### 3.1 优点 19 | 20 | - 单线程,利用redis队列技术并将访问变为串行访问,消除了传统数据库串行控制的开销。减少并发控制的复杂度。 21 | - redis具有快速和持久化的特征,速度快,因为数据存在内存中。 22 | - 分布式 读写分离模式 23 | - 支持丰富数据类型 24 | - 支持事务,操作都是原子性,所谓原子性就是对数据的更改要么全部执行,要不全部不执行。 25 | - 可用于缓存,消息,按key设置过期时间,过期后自动删除 26 | - 加上上一条redis特点: 高性能、分布式、高可用、可伸缩性、单线程、持久化等 27 | 28 | ### 3.2 缺点 29 | 30 | ## 4. redis支持的数据类型 31 | 每种基本类型都有两种或以上的编码方式,不同的编码可以在不同场景上优化对象的使用效率。 32 | 33 | - **字符串String**: 34 | - 对象底层数据结构可以是 int(整数值,整数值用long表示然后存到int中)、embstr(小于32字节)和raw 35 | - 命令**SET和GET** 36 | - **列表List** 37 | - 数据结构采用ziplist(每个元素都小于64字节,并且个数少于512)或linklist 38 | - 命令字**LPUSH和LRANGE** 39 | - **字典Hash**: 40 | - 数据结构采用zipList(所有健和value都小于64字节,并且个数少于512)和HashTable 41 | - 命令**HMSET和HGET** 42 | - **集合Set**: 43 | - 数据结构采用inset(都是整数,并且个数小于512)和hashTable 44 | - 命令**sadd**和**smembers** 45 | - **有序集合ZSet** 46 | - 数据结构采用zipList(每个元素小于64字节,并且元素个数小于**128**)或skipList 47 | - 命令**zadd**和**ZRANGEBYSCORE** 48 | - **HyperLogLog**: Redis中hyperloglog是用来做基数统计的,其优点是:在输入元素的数量或者体积非常非常大的时候,计算基数所需的空间总是固定的,并且是很小的。在 Redis 里面,每个HyperLogLog 键只需要花费12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。 49 | 50 | ### 基数和基数计算 51 | 基数(cardinality),是指一个集合中不同元素的个数。例如集合:{1,2,3,4,5,2,3,9,7}, 这个集合有9个元素,但是2和3各出现了两次, 52 | 53 | 因此不重复的元素为1,2,3,4,5,9,7,所以这个集合的基数是7。 54 | 55 | 那什么是**基数统计**呢?基数统计是指在误差允许的情况下估算出一组数据的基数。 56 | 57 | 从上述的概念中,我们可以很容易想到基数统计的用途,假设需要计算出某个网站一天中的独立ip访问量,相同ip访问多次的话值算作一次。这个问题即可转换成求一天内所有访问该网站的ip数组的基数。关键在于如何求这个基数?下面我就以最易懂的方法来给大家讲一下。 58 | 59 | 60 | 更多参考: 61 | 62 | - [Redis HyperLogLog](https://www.cnblogs.com/xiaozong/p/5647981.html) 63 | - [Redis源码剖析--基数统计hyperloglog](https://blog.csdn.net/terence1212/article/details/53543801) 64 | 65 | 如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。 66 | 如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。 67 | 68 | 69 | ## Redis应用场景 70 | 71 | 72 | 73 | 74 | ## 3. AOF和 RDB的区别 75 | ### 二者区别 76 | RDB持久化是指在指定的时间间隔内将内存中的**数据集快照**写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。 77 | 78 | AOF持久化以日志的形式记录服务器所处理的每一个**写、删除操作**,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。 79 | 80 | 81 | ### RDB 82 | RDB相当于redis镜像,用于保存和还原Redis服务器所有数据库中的所有键值对数据。 83 | 84 | RDB文件的载入工作是服务器启动是自动加载,没有专门的命令字,只要服务启动的时候发现RDB文件存在,就自动载入。 85 | 86 | 执行频率,默认: 87 | 88 | - 900 1 (900秒) 89 | - 300 10 90 | - 60 10000 91 | 92 | 实现: 93 | 94 | - dirty: 上次修改次数 95 | - lastsave: 上次保存时间 96 | - serverCron每100毫秒检查一次 97 | 98 | ### AOF 99 | 通过持久化保存redis服务器所执行的命令字来记录数据库状态。 100 | 101 | AOF持久化功能分为: 命令追加append、文件写入、文件同步sync三个步骤。 102 | 103 | - 命令追加append:以协议方式追加到aof_buf缓存中 104 | - 文件写入和同步: serverCron函数在结束循环之前,调用flushAppendOnlyFile函数考虑将aof_buf缓存区内容写入到AOF文件。有三种形式 105 | - always: 写入是总是同步磁盘 106 | - everysec: 写入,并每秒同步 107 | - no: 不进行同步操作,由操作系统决定同步时间 108 | 109 | 载入时会创建一个不带网络连接的伪客户端。执行AOF指令,进行还原。 110 | 111 | 重写: 112 | 113 | - 重写是读数据库数据,生成AOF指令。 114 | - AOF重写在子进程中执行 115 | - 同时将命令写入AOF缓存和AOF重写缓存 116 | - 重写完成,发送信号。 117 | - 父进程停止工作,将AOF重写缓存写入文件。 118 | - 替换文件。---原子性 119 | 120 | 121 | ### 优缺点 122 | #### RDB存在哪些优势 123 | - **灵活设置备份频率和周期**。你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。 124 | 125 | - 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。 126 | 127 | - 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。 128 | 129 | - 相比于AOF机制,如果数据集很大,RDB的**启动效率会更高**。 130 | 131 | #### RDB的缺点 132 | - 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。 133 | 134 | - 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。 135 | 136 | #### AOF的优势 137 | - 该机制可以带来更高的**数据安全性**,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。 138 | 139 | - 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。 140 | 141 | - 如果日志过大,Redis可以自动启用**rewrite**机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。 142 | 143 | - AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。 144 | 145 | #### AOF不足 146 | - 对于相同数量的数据集而言,**AOF文件通常要大于RDB文件**。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。 147 | 148 | - 根据同步策略的不同,**AOF在运行效率上往往会慢于RDB**。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。 149 | 150 | 151 | ## 4. skiplist插入和查询原理(来自360) 152 | 153 | ## 5. redis持久化方式(百度) 154 | ### 1. RDB快照(snapshots) 155 | 缺省情况情况下,Redis把数据快照存放在磁盘上的二进制文件中,文件名为dump.rdb。你可以配置Redis的持久化策略,例如数据集中每N秒钟有超过M次更新,就将数据写入磁盘;或者你可以手工调用命令SAVE或BGSAVE。 156 | 157 | 工作原理: 158 | 159 | - Redis forks. 160 | - 子进程开始将数据写到临时RDB文件中。 161 | - 当子进程完成写RDB文件,用新文件替换老文件。 162 | - 这种方式可以使Redis使用copy-on-write技术。 163 | 164 | ### 2. AOF 165 | 快照模式并不十分健壮,当系统停止,或者无意中Redis被kill掉,最后写入Redis的数据就会丢失。这对某些应用也许不是大问题,但对于要求高可靠性的应用来说Redis就不是一个合适的选择。Append-only文件模式是另一种选择。你可以在配置文件中打开AOF模式 166 | 167 | 168 | ## 6. redis过期时间如何实现(来自58赶集) 169 | 170 | ## 7. 压缩列表的原理(来自360) 171 | 172 | ## 8. redis如何清除过期keys 173 | 174 | - 定期删除 175 | - 懒惰删除 176 | 177 | ## 9. redis底层为什么使用跳跃表而不是红黑树 178 | 跳跃表在范围查找的时候性能比较高 179 | 180 | ## 10. 使用过Redis分布式锁么,它是什么回事? 181 | 先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。 182 | 这时候对方会告诉你说你回答得不错,然后接着问如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样? 183 | 184 | 这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!对方这时会显露笑容,心里开始默念:摁,这小子还不错。 185 | 186 | 187 | ## 11. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来? 188 | 使用keys指令可以扫出指定模式的key列表。 189 | 190 | 对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题? 191 | 192 | 这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。 193 | 194 | ## 12. bgsave的原理是什么? 195 | 你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。 196 | 197 | ## 13. Redis的同步机制了解么? 198 | **Redis可以使用主从同步,从从同步**。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。 199 | 200 | ## 14. 是否使用过Redis集群,集群的原理是什么? 201 | Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 202 | Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。 203 | 204 | ## 15. 为什么redis需要把所有数据放到内存中? 205 | Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。 206 | 207 | 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。 208 | 209 | ## 16. Redis是单进程单线程的 210 | redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销 211 | 212 | ## 17. redis的并发竞争问题如何解决? 213 | Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法: 214 | 215 | 1. 客户端角度,为保证每个客户端间正常有序与Redis进行通信,**对连接进行池化**,同时对客户端读写Redis操作采用内部锁synchronized。 216 | 217 | 2. 服务器角度,利用setnx实现锁。 218 | 219 | 注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。 220 | 221 | ## 使用redis有哪些好处? 222 | - **速度快**,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) 223 | - **支持丰富数据类型**,支持string,list,set,sorted set,hash 224 | - **支持事务**,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 225 | - **丰富的特性**:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 226 | 227 | 228 | ## Memcache与Redis的区别都有哪些? 229 | - **1)、存储方式** 230 | Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis有部份存在硬盘上,这样能保证数据的持久性。 231 | 232 | - **2)、数据支持类型** 233 | Memcached基本只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能。Redis有复杂的数据类型,Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。 234 | 235 | - **3)、使用底层模型不同** 236 | 它们之间底层实现方式以及与客户端之间通信的应用协议不一样。Redis直接自己构建了VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 237 | 238 | - **4)value大小** 239 | redis最大可以达到1GB,而memcache只有1MB 240 | 241 | - **5) Redis支持数据的备份,即master-slave模式的数据备份。** 242 | - **6)、查询速度,redis速度比memchached快** 为什么? 243 | - 7) Memcached是多线程,非阻塞IO复用的网络模型。Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架。 244 | 245 | ## 如何提高redis命中率 246 | ## redis主从是如何实现同步的 247 | ## redis主从中的哨兵是做什么的 248 | Redis的哨兵模式就是对redis系统进行实时的监控,其主要功能有下面两点: 249 | 250 | 1. 监测主数据库和从数据库是否正常运行。 251 | 2. 当我们的主数据库出现故障的时候,可以自动将从数据库转换为主数据库,实现自动的切换 252 | 253 | ## 谈谈redis的LRU算法 254 | 答:LRU即最近最久未使用,当内存达到限制时,Redis 具体的回收策略是通过 maxmemory-policy 配置项配置的。由以下多个选项: 255 | 256 | - **volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰,以供新数据使用 257 | - **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰,以供新数据使用 258 | - **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,以供新数据使用 259 | - **allkeys-lru**:从所有的数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,以供新数据使用 260 | - **allkeys-random**:从所有数据集(server.db[i].dict)中任意选择数据淘汰,以供新数据使用 261 | - **no-eviction**:不清除数据,只是返回错误,这样会导致浪费掉更多的内存,对大多数写命令(DEL 命令和其他的少数命令例外) 262 | 263 | 当 cache 中没有符合清除条件的key时,回收策略 volatile-lru, volatile-random 和volatile-ttl 将会和策略 noeviction 一样返回错误。选择正确的回收策略是很重要的,取决于你的应用程序的访问模式。 264 | 265 | 回收的过程是这么运作的非常的重要: 266 | 267 | - 一个客户端运行一个新命令,添加了新数据。 268 | - Redis 检查内存使用情况,如果大于**maxmemory**限制,根据策略来回收键。 269 | - 一个新的命令被执行,如此等等 270 | 271 | Redis的LRU算法不是一个严格的LRU实现。这意味着Redis不能选择最佳候选键来回收,也就是最久未被访问的那些键。相反,Redis 会尝试执行一个近似的LRU算法,通过采样一小部分键,然后在采样键中回收最适合(拥有最久访问时间)的那个。 272 | 273 | ## mySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据 274 | 275 | 276 | 277 | ## Redis 常见的性能问题都有哪些?如何解决? 278 | 279 | - **Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件**. Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照.Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。 280 | 281 | - 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 282 | - 尽量避免在压力很大的主库上增加从库 283 | - Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。 284 | - Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内 285 | 286 | 287 | # redis 最适合的场景 288 | Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢? 289 | 290 | - **(1)会话缓存(Session Cache)** 291 | 最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。 292 | 293 | - **(2)、全页缓存(FPC)** 294 | 除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。 295 | 再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。 296 | 297 | 此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。 298 | 299 | - **(3)、队列** 300 | Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。 301 | 302 | 如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。 303 | 304 | - **(4),排行榜/计数器** 305 | Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: 306 | 307 | 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: 308 | 309 | ZRANGE user_scores 0 10 WITHSCORES 310 | 311 | Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。 312 | 313 | - **(5)、发布/订阅** 314 | 最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。 315 | 316 | Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。 317 | 318 | ## Redis 客户端连接池 319 | java 客户端使用 JedisPool, JedisPool本质上是使用了 google的 commmons-pool 对象连接池来实现。 320 | 321 | 使用对象连接池可用减少对象的创建和销毁的开销。 322 | 323 | 324 | 325 | # 参考 326 | - [redis详解(三)-- 面试题](http://blog.csdn.net/guchuanyun111/article/details/52064870) 327 | - [Redis的那些最常见面试问题](https://www.cnblogs.com/Survivalist/p/8119891.html) 328 | - [2017年6月Java面试——redis](http://blog.csdn.net/sunqingzhong44/article/details/73866263) 329 | - [坑人无数的Redis面试题](https://baijiahao.baidu.com/s?id=1588454565071211950&wfr=spider&for=pc) 330 | 331 | 332 | -------------------------------------------------------------------------------- /chapter8/ask.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/chapter8/ask.md -------------------------------------------------------------------------------- /database/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/database/README.md -------------------------------------------------------------------------------- /database/SQL语句汇总.md: -------------------------------------------------------------------------------- 1 | # SQL语句汇总 2 | ## 一. 数据库相关 3 | #### 1) 连接数据库 4 | 5 | 格式:mysql -h主机地址 -u用户名 -p用户密码 6 | 7 | 例如: 8 | 9 | ``` 10 | mysql -h localhost -uroot -p 11 | ``` 12 | 13 | #### 2) 创建数据库 14 | 15 | 命令:create database <数据库名> 16 | 例1:建立一个名为test_db的数据库 17 | 18 | ``` 19 | CREATE DATABASE test_db 20 | ``` 21 | 22 | #### 3) 删除数据库 23 | 24 | ``` 25 | DROP DATABASE dbname 26 | ``` 27 | 28 | #### 4) 增加用户 29 | 格式:grant select on 数据库.* to 用户名@登录主机 identified by “密码” 30 | 31 | - 增加一个用户test1密码为abc,让他可以在任何主机上登录,并对所有数据库有查询、插入、修改、删除的权限 32 | 33 | ``` 34 | grant select,insert,update,delete on *.* to [email=test1@"%]test1@"%[/email]" Identified by "abc"; 35 | ``` 36 | 37 | 但增加的用户是十分危险的,你想如某个人知道test1的密码,那么他就可以在internet上的任何一台电脑上登录你的mysql数据库并对你的数据可以为所欲为了,解决办法见2.2。 38 | 39 | 40 | - 增加一个用户test2密码为abc,让他只可以在localhost上登录,并可以对数据库mydb进行查询、插入、修改、删除的操作 41 | 42 | 这样用户即使用知道test2的密码,他也无法从internet上直接访问数据库,只能通过MYSQL主机上的web页来访问。 43 | 44 | ``` 45 | rant select,insert,update,delete on mydb.* to [email=test2@localhost]test2@localhost[/email] identified by "abc"; 46 | ``` 47 | 48 | - 如果你不想test2有密码,可以再打一个命令将密码消掉。 49 | 50 | ``` 51 | grant select,insert,update,delete on mydb.* to [email=test2@localhost]test2@localhost[/email] identified by "" 52 | ``` 53 | 54 | #### 5)显示数据库 55 | 命令:show databases (注意:最后有个s) 56 | 57 | ``` 58 | mysql> show databases 59 | ``` 60 | 61 | #### 6) 使用数据库 62 | 命令: use <数据库名> 63 | 例如:如果test_db数据库存在,尝试存取它: 64 | 65 | ``` 66 | mysql> use test_db; 67 | ``` 68 | 69 | use 语句可以通告MySQL把db_name数据库作为默认(当前)数据库使用,用于后续语句。该数据库保持为默认数据库,直到语段的结尾,或者直到发布一个不同的USE语句: 70 | 71 | ``` 72 | mysql> USE db1; 73 | mysql> SELECT COUNT(*) FROM mytable; # selects from db1.mytable 74 | mysql> USE db2; 75 | mysql> SELECT COUNT(*) FROM mytable; # selects from db2.mytable 76 | ``` 77 | 78 | #### 7) 当前选择数据库 79 | MySQL中SELECT命令类似于其他编程语言里的print或者write,你可以用它来显示一个字符串、数字、数学表达式的结果等等。 80 | 81 | - **显示MYSQL的版本**: select version(); 82 | - **显示当前时间**: select now(); 83 | - **显示年月日**: SELECT DAYOFMONTH(CURRENT_DATE); 84 | - **显示字符串**: SELECT "welecome to my blog!"; 85 | - **当计算器用**: select ((4 * 4) / 10 ) + 25; 86 | - **串接字符串**: select CONCAT(f_name, " ", l_name) AS Name from employee_data; 这里用到CONCAT()函数,用来把字符串串接起来。 87 | 88 | ## 二. 表相关 89 | ### 2.1 创建新表 90 | #### 1) 创建新表 91 | 92 | 命令: 93 | 94 | ``` 95 | create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..) 96 | ``` 97 | 98 | 比较简单的使用方式: 99 | 100 | ``` 101 | create table MyClass( 102 | id int(4) not null primary key auto_increment, 103 | name char(20) not null, 104 | sex int(4) not null default '0', 105 | degree double(16,2)); 106 | ``` 107 | 108 | 较为完整的方式: 109 | 110 | ``` 111 | CREATE TABLE `t_books` ( 112 | `F_id` varchar(32) NOT NULL COMMENT '主键', 113 | `F_book_id` int(10) unsigned NOT NULL COMMENT '书本号', 114 | `F_publish_id` int(20) unsigned NOT NULL COMMENT '厂商ID', 115 | `F_type` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '书本类型', 116 | `F_sell_amount` bigint(20) NOT NULL DEFAULT '0' COMMENT '金额(保留到分)', 117 | `F_desc` varchar(256) DEFAULT NULL DEFAULT '' COMMENT '描述', 118 | `F_create_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间', 119 | `F_modify_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '最近更新时间', 120 | PRIMARY KEY (`F_id`), 121 | KEY `idx_book_id` (`F_book_id`) COMMENT '索引:书本', 122 | KEY `idx_book` (`F_type`, `F_publish_id`) COMMENT '索引' 123 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='书本'; 124 | ``` 125 | 126 | 根据已有的表创建新表: 127 | 128 | ``` 129 | A:create table tab_new like tab_old (使用旧表创建新表) 130 | B:create table tab_new as select col1,col2… from tab_old definition only 131 | ``` 132 | 133 | #### 2)获取表结构 134 | 命令: desc 表名,或者show columns from 表名 135 | 136 | ``` 137 | mysql> desc t_books; 138 | mysql> show columns from t_books; 139 | ``` 140 | 141 | 使用MySQL数据库desc 表名时,我们看到Key那一栏,可能会有4种值,即' ','PRI','UNI','MUL'。 142 | 143 | - **如果Key是空的**, 那么该列值的可以重复, 表示该列没有索引, 或者是一个非唯一的复合索引的非前导列; 144 | - **如果Key是PRI**, 那么该列是**主键**的组成部分; 145 | - **如果Key是UNI**, 那么该列是一个**唯一值索引**的第一列(前导列),并别**不能含有空值(NULL)**; 146 | - **如果Key是MUL**, 那么该列的值**可以重复**, 该列是一个非唯一索引的前导列(第一列)或者是一个 147 | 148 | 唯一性索引的组成部分但是可以含有空值NULL。如果对于一个列的定义,同时满足上述4种情况的多种,比如一个列既是PRI,又是UNI,那么"desc 表名"的时候,显示的Key值按照优先级来显,PRI->UNI->MUL。那么此时,显示PRI。 149 | 150 | - 一个唯一性索引列可以显示为PRI,并且该列不能含有空值,同时该表没有主键。 151 | - 一个唯一性索引列可以显示为MUL, 如果多列构成了一个唯一性复合索引,因为虽然索引的多列组合是唯一的,比如ID+NAME是唯一的,但是没一个单独的列依然可以有重复的值,只要ID+NAME是唯一的即可。 152 | 153 | #### 3) 删除新表 154 | 155 | ``` 156 | drop table tabname 157 | ``` 158 | 159 | #### 4) 向表插入数据 160 | 命令:insert into <表名> [( <字段名1>[,..<字段名n > ])] values ( 值1 )[, ( 值n )] 161 | 162 | 163 | 往表 MyClass中插入二条记录, 这二条记录表示:编号为1的名为Tom的成绩为96.45, 编号为2 的名为Joan 的成绩为82.99, 编号为3 的名为 164 | 165 | Wang 的成绩为96.5。 166 | 167 | ``` 168 | mysql> insert into MyClass values(1,'Tom',96.45), (2,'Joan',82.99), (2,'Wang', 96.59); 169 | ``` 170 | 171 | 注意:insert into每次只能向表中插入一条记录。 172 | 173 | 174 | ### 2.2 修改表 175 | #### 1) 增加一个列 176 | 177 | ``` 178 | Alter table tabname add column col type 179 | ``` 180 | 181 | 注:列增加后将不能删除。DB2中列加上后数据类型也不能改变,唯一能改变的是增加varchar类型的长度。 182 | 183 | #### 2)添加主键 184 | 185 | ``` 186 | Alter table tabname add primary key(col) 187 | ``` 188 | 189 | #### 3)删除主键 190 | 191 | ``` 192 | Alter table tabname drop primary key(col) 193 | ``` 194 | 195 | #### 4)创建索引 196 | 197 | ``` 198 | create [unique] index idxname on tabname(col….) 199 | ``` 200 | 201 | #### 5) 删除索引 202 | 203 | ``` 204 | drop index idxname 205 | ``` 206 | 207 | 索引是不可更改的,想更改必须删除重新建。 208 | 209 | #### 6) 修改表名 210 | 211 | 命令:rename table 原表名 to 新表名; 212 | 例如:在表MyClass名字更改为YouClass 213 | 214 | ``` 215 | mysql> rename table MyClass to YouClass; 216 | ``` 217 | 218 | #### 7) 创建视图 219 | 220 | ## 复杂命令 221 | #### UNION 222 | UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表。当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行。两种情况下,派生表的每一行不是来自 TABLE1 就是来自 TABLE2。 223 | 224 | #### EXCEPT 运算符 225 | EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所有重复行而派生出一个结果表。当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。 226 | 227 | 228 | #### INTERSECT 运算符 229 | INTERSECT 运算符通过只包括 TABLE1 和 TABLE2 中都有的行并消除所有重复行而派生出一个结果表。当 ALL 随 INTERSECT 一起使用时 (INTERSECT ALL),不消除重复行。 230 | 注:使用运算词的几个查询结果行必须是一致的。 231 | 232 | #### 使用外连接 233 | 234 | 235 | ## 简单sql 236 | - **选择**:select * from table1 where 范围 237 | - **插入**:insert into table1(field1,field2) values(value1,value2) 238 | - **删除**:delete from table1 where 范围 239 | - **更新**:update table1 set field1=value1 where 范围 240 | - **查找**:select * from table1 where field1 like ’%value1%’ ---like的语法很精妙,查资料! 241 | - **排序**:select * from table1 order by field1,field2 [desc] 242 | - **总数**:select count as totalcount from table1 243 | - **求和**:select sum(field1) as sumvalue from table1 244 | - **平均**:select avg(field1) as avgvalue from table1 245 | - **最大**:select max(field1) as maxvalue from table1 246 | - **最小**:select min(field1) as minvalue from table1 247 | 248 | ## 其他 249 | ### 备份数据 250 | 251 | 命令在mysql安装目录的bin文件夹下执行。 252 | 253 | #### 1) 导出整个数据库 254 | 导出文件默认是存在mysql\bin目录下 255 | mysqldump -u 用户名 -p 数据库名 > 导出的文件名 256 | 257 | ``` 258 | mysqldump -u user_name -p123456 database_name > outfile_name.sql 259 | ``` 260 | 261 | #### 2) 导出一个表 262 | 263 | 命令: mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名 264 | 265 | ``` 266 | mysqldump -u user_name -p database_name table_name > outfile_name.sql 267 | ``` 268 | 269 | #### 3) 导出一个数据库结构 270 | 271 | 命令: mysqldump -u 用户名 -p -d –add-drop-table 数据库名 > 导出的文件名 272 | 273 | ``` 274 | mysqldump -u user_name -p -d –add-drop-table database_name > outfile_name.sql 275 | ``` 276 | 277 | -d 没有数据 –add-drop-table 在每个create语句之前增加一个drop table 278 | 279 | #### 4)带语言参数导出 280 | 281 | ``` 282 | mysqldump -uroot -p –default-character-set=latin1 –set-charset=gbk –skip-opt database_name > outfile_name.sql 283 | ``` 284 | 285 | 例如,将test_db库备份到文件back_test_db中: 286 | 287 | 代码如下: 288 | 289 | ``` 290 | mysqldump -u root -p --opt test_db > back_test_db 291 | ``` 292 | 293 | ## 提升 294 | #### 复制表 295 | 只复制结构,源表名:a 新表名:b 296 | 297 | ``` 298 | select * into b from a where 1<>1(仅用于SQlServer) // 299 | select top 0 * into b from a; // 状态 300 | ``` 301 | 302 | #### 拷贝表 303 | 拷贝数据,源表名:a 目标表名:b 304 | 305 | ``` 306 | insert into b(a, b, c) select d,e,f from b; 307 | ``` 308 | 309 | #### 跨数据库之间表的拷贝 310 | 具体数据使用绝对路径 311 | 312 | ``` 313 | insert into b(a, b, c) select d,e,f from b in ‘具体数据库’ where 条件 314 | ``` 315 | 316 | #### 拷贝表 317 | 318 | ``` 319 | insert into b(a, b, c) select d,e,f from b; 320 | ``` 321 | 322 | #### 子查询 323 | 324 | ``` 325 | select a,b,c from a where a IN (select d from b ) 326 | ``` 327 | 328 | 或者: 329 | 330 | ``` 331 | select a,b,c from a where a IN (1,2,3) 332 | ``` 333 | 334 | #### 两张关联表,删除主表中已经在副表中没有的信息 335 | 336 | ``` 337 | delete from table1 where not exists ( select * from table2 where table1.field1=table2.field1) 338 | ``` 339 | 340 | #### 随机取出10条数据 341 | 342 | ``` 343 | select top 10 * from tablename order by newid() 344 | ``` 345 | 346 | #### 随机选择记录 347 | 348 | ``` 349 | select newid() 350 | ``` 351 | 352 | #### 执行外部sql 353 | 354 | - 启动时执行 355 | 356 | ``` 357 | mysql -uroot -p密码 < ~/school.sql 358 | ``` 359 | 360 | - 在mysql命令行中执行 361 | 362 | ``` 363 | mysql> source ~/school.sql; 364 | ``` 365 | 366 | 367 | https://www.cnblogs.com/jaechen/p/8028162.html 368 | http://www.jb51.net/article/74564.htm 369 | 370 | 371 | https://www.cnblogs.com/jaechen/p/8028162.html 372 | 373 | 374 | -------------------------------------------------------------------------------- /database/all.md: -------------------------------------------------------------------------------- 1 | 2 | ## 存储引擎的区别 3 | - **InnoDB**: 支持事务,是面向在线事务处理(OLTP)的应用,特点是**行锁设计**,支持外键,并支持一致性非锁定读,即默认情况下读取操作不会产生锁.是默认的**存储引擎**:.还提供了插入缓冲,二次写,自适应哈希索引,预读等高性能和高可用的功能. 4 | 5 | - **MyISAM**: 不支持事务,是表锁设计和支持全文索引,主要面向一些OLAP的数据库应用.它的缓冲池只缓冲索引文件,而不缓冲数据文件.该存储引擎表由MYD和MYI组成,MYD用来存放数据文件,MYI用来存放索引文件. 6 | 7 | - **NDB**:是一个**集群存储引擎**,其特点是数据全部放在内存中,因此主键查找速度极快,并通过添加NDB数据库存储节点可以线性提高数据库性能,是高可用,高性能的集群系统. 8 | 9 | - **Memory**: 将表中的数据存放在内存中,如果数据库重启或发生崩溃,表中的数据库都将消失,它非常适合存储临时数据的临时表.默认采用哈希索引. 10 | 11 | - **Archive**: 只支持INSERT和SELECT操作,使用zlib算法将数据行进行压缩,压缩比可以达到1:10,非常适合存储归档数据.但其本身不是事务安全的存储引擎,其设计目标是提供高速的插入和压缩功能. 12 | 13 | - **Federated**: 并不存放数据,它只是指向一台远程MySQL数据库服务器上的表. 14 | 15 | - **Maria存储引擎**: 设计目标主要是用来取代原有的MyISAM存储引擎. 16 | 17 | ## MyISAM和InnoDB的区别 18 | 19 | 1. MyISAM是非事务安全型的,而InnoDB是事务安全型的。 20 | 2. MyISAM锁的粒度是表级,而InnoDB支持行级锁定。 21 | 3. MyISAM支持全文索引,而Innodb不支持全文索引 22 | 4. MyISAM表是保存成文件形式的,在跨平台的数据转移中使用MyISAM存储会省去不少的麻烦。 23 | 5. InnoDB表比MyISAM表更安全,可以保证数据不丢失的情况下,切换非事务表到事务表 24 | 25 | 应用场景: 26 | 27 | 1. MyISAM 管理非事务表,它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。 28 | 2. InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。 29 | 30 | ### sql注入原理 31 | 就是通过把SQL命令插入到Web 表单 提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令 32 | 33 | 1. 猜表名,列名等 34 | 2. 后台身份验证绕过漏洞 35 | 36 | 验证绕过漏洞就是'or'='or'后台绕过漏洞,利用的就是AND和OR的运算规则,从而造成后台脚本逻辑性错误. 37 | 38 | 防范: 39 | 40 | 1. 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。 41 | 2. 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。 42 | 3. 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。 43 | 4. 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。 44 | 5. 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装,把异常信息存放在独立的表中。 45 | 46 | ## 数据库范式 47 | - **第一范式(1NF):属性不可分**。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。 48 | - **第二范式(2NF):符合1NF,并且,非主属性完全依赖于码(也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中)**。--确保表中的每列都和主键相关 49 | - **第三范式(3NF):符合2NF,并且,消除传递依赖(每一列数据都和主键直接相关,而不能间接相关)**。---确保每列都和主键列直接相关,而不是间接相关 50 | - BCNF:符合3NF,并且,没有任何属性完全函数依赖于非码的任何一组属性. 51 | 52 | 找个例子说. 53 | 54 | 参考: http://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html 55 | http://blog.sina.com.cn/s/blog_46d817650100yj2i.html 56 | 57 | ## 数据库索引 58 | 索引是一个单独存储在磁盘上的数据库结构,它们包含着对数据表里所有记录的引用指针,使用索引可以提高数据库特定数据的查询速度.索引时在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,并且每种存储引擎也不一定支持所有索引类型. 59 | 60 | 索引的存储类型有两种:BTREE和HASH,具体和表的存储引擎有关.MyISAM和InnoDB存储引擎只支持BTREE;MEMORY/HEAD存储索引可以支持HASH和BTREE索引. 61 | 62 | **索引的优点**: 63 | 64 | 1. 通过创建唯一索引,可以保证数据库表中每行数据的唯一性. 65 | 2. 可以加快数据的查询速度. 66 | 3. 在实现数据的参考完整性方面,可以加速表和表之间的连接. 67 | 4. 再使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间 68 | 5. 通过使用索引,可以在查询中使用优化隐藏器,提高系统的性能。 69 | 70 | 71 | **索引的缺点**: 72 | 73 | 1. 创建索引和维护索引要耗费时间,并且随着数据量的增加耗费时间也增加. 74 | 2. 索引需要占空间内存. 75 | 3. 在对表中数据进行增加,删除和修改的时候,索引也需要动态维护,这样降低了数据维护速度. 76 | 77 | ### 索引分类 78 | 1. 普通索引和唯一索引 79 | 2. 直接创建索引和间接创建索引 80 | 3. 普通索引和唯一性索引 81 | 4. 单个索引和符合索引 82 | 5. 聚簇索引和非聚簇索引 83 | 84 | ## 索引失效?? 85 | 1. WHERE字句的查询条件里有不等于号(WHERE column!=...),MYSQL将无法使用索引 86 | 2. 如果WHERE字句的查询条件里使用了函数(如:WHERE DAY(column)=...),MYSQL将无法使用索引 87 | 3. 在JOIN操作中(需要从多个数据表提取数据时),MYSQL只有在主键和外键的数据类型相同时才能使用索引,否则即使建立了索引也不会使用。 88 | 4. 如果WHERE子句的查询条件里使用了比较操作符LIKE和REGEXP,MYSQL只有在搜索模板的第一个字符不是通配符的情况下才能使用索引。比如说,如果查询条件是LIKE 'abc%',MYSQL将使用索引;如果条件是LIKE '%abc',MYSQL将不使用索引。 89 | 5. 在ORDER BY操作中,MYSQL只有在排序条件不是一个查询条件表达式的情况下才使用索引。尽管如此,在涉及多个数据表的查询里,即使有索引可用,那些索引在加快ORDER BY操作方面也没什么作用。 90 | 6. 如果某个数据列里包含着许多重复的值,就算为它建立了索引也不会有很好的效果。比如说,如果某个数据列里包含了净是些诸如“0/1”或“Y/N”等值,就没有必要为它创建一个索引。 91 | 7. 如果条件中有or(并且其中有or的条件是不带索引的),即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。 92 | 8. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。 93 | 9. 如果mysql估计使用全表扫描要比使用索引快,则不使用索引。 94 | 95 | http://www.cnblogs.com/hongfei/archive/2012/10/20/2732589.html 96 | http://my.oschina.net/hebad/blog/370815 97 | 98 | 99 | ## 数据库锁机制 100 | 数据库锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问,访问变得有序所设计的一种规则。MySQL各存储引擎使用了三种类型(级别)的锁定机制:行级锁定,页级锁定和表级锁定。 101 | 102 | - **表级锁定(table-level)**:表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。表级锁分为读锁和写锁。 103 | 104 | - **页级锁定(page-level)**:页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。 105 | 106 | - **行级锁定(row-level)**:行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。InnoDB的行级锁同样分为两种,共享锁和排他锁,同样InnoDB也引入了意向锁(表级锁)的概念,所以也就有了意向共享锁和意向排他锁,所以InnoDB实际上有四种锁,即共享锁(S)、排他锁(X)、意向共享锁(IS)、意向排他锁(IX); 107 | 108 | 在MySQL数据库中,使用表级锁定的主要是MyISAM,Memory,CSV等一些非事务性存储引擎,而使用行级锁定的主要是Innodb存储引擎和NDBCluster存储引擎,页级锁定主要是BerkeleyDB存储引擎的锁定方式。 109 | 110 | 而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。 111 | 112 | 113 | | | 共享锁(S)| 排他锁(X)| 意向共享锁(IS)| 意向排他锁(IX)| 114 | |---|---|---|---|---| 115 | |共享锁(S) | 兼容 | 冲突 | 兼容 |冲突| 116 | |排他锁(X) | 冲突 | 冲突 | 冲突 |冲突| 117 | |意向共享锁(IS) | 兼容 | 冲突 | 兼容 |兼容 | 118 | |意向排他锁(IX) | 冲突 | 冲突 | 兼容 |兼容| 119 | 120 | 121 | 122 | 参考地址:http://www.cnblogs.com/ggjucheng/archive/2012/11/14/2770445.html 123 | 124 | MyISAM 表锁优化建议: 125 | 126 | 1. 缩短锁定时间 127 | 2. 分离能并行的操作 128 | 3. 合理利用读写优先级 129 | 130 | 131 | ## 乐观锁,悲观锁 132 | 133 | - **悲观锁**:它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。 134 | 135 | - **乐观锁( Optimistic Locking )** :相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则则拒绝更新并返回用户错误的信息,让用户决定如何去做。乐观锁由程序实现,不会存在死锁问题。它适用的场景也相对乐观。但乐观锁不能解决脏读的问题 136 | 137 | 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 138 | 139 | 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题。 140 | 141 | ## 事务隔离机制 142 | 事务隔离级别: 143 | 144 | - **未提交读(READ UNCOMMITTED)**:事务中的修改,即使未提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也称为脏读。 145 | - **提交读(READ COMMITTED)**:一个事物从开始到提交之前,所做的任何修改对其他事物都是不可见的,这个级别有时候叫做不可重复读。这个级别上两次执行同样的查询会得到不一样的结果。 146 | - **可重复读(REPEATABLE READ)**:解决了不可重复读问题,该级别保证了在同一个事务中多次读同样记录的结果是一致的,理论上无法解决幻读问题。幻读就是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入新的记录,当之前的事物再次读取该范围的记录时会产生幻行。 147 | - **可串行化(SERIZLIZABLE)**:它通过强制事务串行执行,避免了前面说的幻读的问题。 148 | 149 | || 脏读| 不可重复读| 幻读可能性| 加锁读| 150 | |---|---|---|---|---| 151 | |未提交读| YES| YES| YES | NO| 152 | |提交读 | NO| YES| YES| NO| 153 | |可重复读| NO| NO| YES| NO| 154 | |可串行化| NO| NO| NO| YES| 155 | 156 | 157 | ## 脏读、不可重复读和幻读 158 | 159 | 1. **脏读**: 事务T1更新了一行记录内容,但并没有提交修改。事务T2读取更新后的行,然后T1执行回滚操作。读取了刚才所做的修改。现在T2读取的行就无效了。(一个事务读取了另一个事务未提交的数据) 160 | 2. **不可重复读**:事务T1读取了一行记录,紧接着T2修改了T1刚才读取的那一行记录,然后T1又再次读取这行记录,发现与刚才读取的结果不同。 161 | 3. **幻读**:事务T1读取一个结果集,然后T2事务在T1结果集范围内插入一行记录。然后T1再次对表进行检索,发现多了T2插入的数据。 162 | 163 | 164 | ## 数据库事务属性 165 | 事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性: 166 | 167 | - **原子性(Atomicity)**:事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。 168 | - **一致性(Consistent)**:在事务开始和完成时,数据都必须保持一致状态,即要求事务做完后,要求满足数据库的一些完整性约。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。 169 | - **隔离性(Isolation)**:数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。 170 | - **持久性(Durable)**:事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。 171 | 172 | ## 数据库事务的几种粒度; 173 | 174 | ## 是否了解数据库的索引是如何实现的 175 | ### MyISAM索引实现 176 | 177 | MyISAM索引使用了B+Tree作为索引结构,叶子结点的data域存放的是数据记录的地址。MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。主索引和辅助索引的存储结构没有任何区别。 178 | ### InnoDB索引实现 179 | 虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。 180 | 181 | 第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据。 182 | 183 | 第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。 184 | 185 | ### Memory索引实现 186 | Memory索引适用于需要快速访问数据的场景,显示支持哈希索引。内部基于哈希表数据结构实现,只包含哈希值和行指针,对于每一行数据,存储引擎都会对所有的引擎列计算一个哈希码,在哈希表对应位置存放该行数据的指针或地址。为了解决多个hash冲突问题,哈希索引采用了链地址法来解决冲突问题。所以采用链表数组作为存储结构。这种索引结构十分紧凑,且具有很快的查询速度。但也存在一些问题: 187 | 188 | 1. 哈希表数据不是按照索引顺序存储的,所以无法用于排序。 189 | 2. 只能支持等值比较查询。 190 | 3. 存在冲突情况下查询速度变慢。 191 | 4. 如果宕机,数据丢失 192 | 193 | https://msdn.microsoft.com/zh-cn/library/dn133190.aspx 194 | 195 | ## 数据库连接池原理 196 | ### 背景 197 | 传统的数据库连接方式是,用户每次请求都要向数据库获取连接,而数据库连接的创建和关闭需要一定的开销。频繁的建立、关闭数据库,会极大的降低系统的性能,增大系统的开销,甚至成为系统的瓶颈。另外使用这种传统的模式,还必须管理数据库的每一个连接,以确保他们能正确关闭,如果出现程序异常而导致某些连接未能关闭。同时无节制的创建连接极易导致数据库服务器内存溢出。 198 | 199 | ### 原理 200 | 数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。以及一套连接使用、分配、管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。 201 | 202 | ### 开源java连接池: 203 | 现在很多Web服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。 204 | 205 | 1. C3P0 :是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。参考网站: http://sourceforge.net/projects/c30/ 206 | 207 | 2. Proxool :是一个Java SQL Driver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的JDBC驱动程序增加连接池功能。 参考网站: http://proxool.sourceforge.net 208 | 209 | 3. Jakarta DBCP :是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用。参考网站: http://jakarta.apache.org/commons/dbcp/ 210 | 211 | 原理: http://www.uml.org.cn/sjjm/201004153.asp 212 | 实现: http://www.cnblogs.com/lihuiyy/archive/2012/02/14/2351768.html 213 | 214 | 215 | 连接池使用什么数据结构实现 216 | 链表 217 | 218 | 实现连接池: http://www.cnblogs.com/lihuiyy/archive/2012/02/14/2351768.html 219 | 220 | 四个表 记录成绩,每个大约十万条记录,如何找到成绩最好的同学 221 | servlet的一些相关问题 222 | webservice相关 223 | 224 | 225 | ## mysql有那些存储引擎,分别有什么特点 226 | # 基础知识 227 | 228 | 229 | 230 | 个人公众号(欢迎关注):
231 | ![](/assets/weix_gongzhonghao.jpg) 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /database/mysql.md: -------------------------------------------------------------------------------- 1 | # Mysql 2 | ## 一. 存储引擎的区别 3 | #### 1. InnoDB 4 | InnoDB支持事务,是面向在线事务处理(OLTP)的应用,特点是**行锁设计**,支持外键,并支持一致性非锁定读,即默认情况下读取操作不会产生锁.是默认的**存储引擎**:.还提供了插入缓冲,二次写,自适应哈希索引,预读等高性能和高可用的功能.并支持全文索引。 5 | 6 | - 采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,**默认级别是可重复读(REPEATABLE READ)**,并且通过间隙锁(next-key locking)策略防止幻读的出现。间隙锁使得 InnoDB 不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。 7 | 8 | - 表是基于**聚簇索引**建立的,它对主键的查询性能有很高的提升。 9 | 10 | - 内部做了很多**优化**,包括从磁盘读取数据时采用的可预测性读、能够自动在内存中创建哈希索引以加速读操作的自适应哈希索引、能够加速插入操作的插入缓冲区等。 11 | 12 | - 通过一些机制和工具支持真正的热备份。其它存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 13 | 14 | #### 2. MyISAM 15 | **不支持事务,是表锁设计和支持全文索引**,主要面向一些OLAP的数据库应用. 它的缓冲池只缓冲索引文件,而不缓冲数据文件。该存储引擎表由MYD和MYI组成,MYD用来存放数据文件,MYI用来存放索引文件. MyISAM 提供了大量的特性,包括**全文索引、压缩表、空间数据索引**等 16 | 17 | #### 3. NDB 18 | NDB是一个**集群存储引擎**,其特点是**数据全部放在内存中**,因此主键查找速度极快,并通过添加NDB数据库存储节点可以线性提高数据库性能,是高可用,高性能的集群系统. 19 | 20 | #### 4. Memory 21 | **Memory**: 将表中的数据存放在内存中,如果数据库重启或发生崩溃,表中的数据库都将消失,它非常适合存储临时数据的临时表.默认采用哈希索引. 22 | 23 | #### 5. Archive 24 | **Archive**只支持**INSERT和SELECT**操作,使用**zlib算法将数据行进行压缩**,压缩比可以达到1:10,非常适合存储归档数据.但其本身不是事务安全的存储引擎,其设计目标是提供高速的插入和压缩功能. 25 | 26 | #### 6. Federated 27 | **Federated**并不存放数据,它只是指向一台远程MySQL数据库服务器上的表. 28 | 29 | #### 7. Maria存储引擎 30 | **Maria存储引擎** 设计目标主要是用来取代原有的MyISAM存储引擎. 31 | 32 | ### InnoDB和MyISAM比较 33 | 1. 事务:InnoDB 是事务型的。MyISAM不支持事务。 34 | 2. 备份:InnoDB 支持在线热备份。 35 | 3. 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 36 | 4. 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 37 | 5. 其它特性:MyISAM 支持压缩表和空间数据索引。 38 | 39 | 40 | # 二、数据类型 41 | ### 1) 整数类型 42 | 43 | | 类型 | 大小 | 用途 | 44 | | --- | --- | --- | 45 | | TINYINT | 1 字节 | 小整数值 | 46 | | SMALLINT | 2 字节 | 大整数值 | 47 | | MEDIUMINT | 3 字节 | 大整数值 | 48 | | INT | 4 字节 | 大整数值 | 49 | | BIGINT | 8 字节 | 极大整数值 | 50 | 51 | 52 | 一般情况下越小的列越好。在使用这些类型时,我们一般会在类型后面添加括号指定显示字符格式,如INT(11) 中的数字只是**规定了交互工具显示字符的个数**,对于存储和计算来说是没有意义的。 53 | 54 | ### 2) 浮点数 55 | 56 | | 类型 | 大小 | 用途 | 57 | | --- | --- | --- | 58 | | FLOAT | 4 字节 | 单精度 浮点数值 | 59 | | DOUBLE | 8 字节 | 双精度 浮点数值 | 60 | | DECIMAL | DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 小数值 | 61 | 62 | 63 | **FLOAT** 和 **DOUBLE** 为浮点类型,**DECIMAL** 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。 64 | 65 | FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。 66 | 67 | ### 3) 字符串 68 | 69 | | 类型 | 大小 | 用途 | 70 | | --- | --- | --- | 71 | | CHAR | 0-255字节 | 定长字符串 | 72 | | VARCHAR | 0-65535 字节 | 变长字符串 | 73 | | TINYBLOB | 0-255字节 | 不超过 255 个字符的二进制字符串 | 74 | | TINYTEXT | 0-255字节 | 短文本字符串 | 75 | | BLOB | 0-65 535字节 | 二进制形式的长文本数据 | 76 | | TEXT | 0-65 535字节 | 长文本数据 | 77 | | MEDIUMBLOB | 0-16 777 215字节 | 二进制形式的中等长度文本数据 | 78 | | MEDIUMTEXT | 0-16 777 215字节 | 中等长度文本数据 | 79 | | LONGBLOB | 0-4 294 967 295字节 | 二进制形式的极大文本数据 | 80 | | LONGTEXT | 0-4 294 967 295字节 | 极大文本数据 | 81 | 82 | 83 | VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长,当超出一个页所能容纳的大小时,就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。 84 | 85 | VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。 86 | 87 | ### 4) 时间和日期 88 | 89 | | 类型 | 大小| 格式 | 用途 | 90 | | --- | --- | --- | --- | 91 | | DATE | 3字节 | YYYY-MM-DD | 日期值 | 92 | | TIME | 3字节 | HH:MM:SS | 时间值或持续时间 | 93 | | YEAR | 1字节 | YYYY | 年份值 | 94 | | DATETIME | 8字节 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 | 95 | | TIMESTAMP | 4字节 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 | 96 | 97 | **DATATIME**: 98 | 99 | - 能够保存从 1001 年到 9999 年的日期和时间,**精度为秒**,使用 8 字节的存储空间。 100 | - **它与时区无关**. 101 | - 默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值,例如“2008-01-16 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。 102 | 103 | **TIMESTAMP**: 104 | 105 | - 和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用4个字节,只能表示从1970年到2038年。 106 | - **它和时区有关**。 107 | - 默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。 108 | - 应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。 109 | - MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 UNIX 时间戳。 110 | 111 | # 索引 112 | ## 索引分类 113 | ### 1. B+Tree 索引 114 | ### 2. 哈希索引 115 | ### 3. 空间数据索引(R-Tree) 116 | ### 4. 全文索引 117 | ## 索引的优点 118 | 119 | - 大大减少了服务器需要扫描的数据量; 120 | - 帮助服务器避免进行排序和创建临时表(B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作); 121 | - 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的列值都存储在一起)。 122 | 123 | ## 索引优化 124 | ### 1. 独立的列 125 | 126 | 在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 127 | 128 | 例如下面的查询不能使用 actor_id 列的索引: 129 | 130 | ```sql 131 | SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; 132 | ``` 133 | 134 | ### 2. 前缀索引 135 | 136 | 对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 137 | 138 | 对于前缀长度的选取需要根据**索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1,此时每个记录都有唯一的索引与其对应。 139 | 140 | ### 3. 多列索引 141 | 142 | 在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。 143 | 144 | ```sql 145 | SELECT film_id, actor_ id FROM sakila.film_actor 146 | WhERE actor_id = 1 AND film_id = 1; 147 | ``` 148 | 149 | ### 4. 索引列的顺序 150 | 151 | 让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 152 | 153 | ```sql 154 | SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, 155 | COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, 156 | COUNT(*) 157 | FROM payment; 158 | ``` 159 | 160 | ```html 161 | staff_id_selectivity: 0.0001 162 | customer_id_selectivity: 0.0373 163 | COUNT(*): 16049 164 | ``` 165 | 166 | 167 | ### 5. 聚簇索引 168 | 169 |
170 | 171 | 聚簇索引并不是一种索引类型,而是一种数据存储方式。 172 | 173 | 术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引在同一个结构中保存了 B+Tree 索引和数据行。 174 | 175 | 因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 176 | 177 | **优点** 178 | 179 | 1. 可以把相关数据保存在一起,减少 I/O 操作。例如电子邮件表可以根据用户 ID 来聚集数据,这样只需要从磁盘读取少数的数据也就能获取某个用户的全部邮件,如果没有使用聚聚簇索引,则每封邮件都可能导致一次磁盘 I/O。 180 | 2. 数据访问更快。 181 | 182 | **缺点** 183 | 184 | 1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。 185 | 2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。 186 | 3. 更新操作代价很高,因为每个被更新的行都会移动到新的位置。 187 | 4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。 188 | 5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。 189 | 190 | ### 6. 覆盖索引 191 | 192 | 索引包含所有需要查询的字段的值。 193 | 194 | **优点** 195 | 196 | 1. 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。 197 | 2. 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。 198 | 3. 对于 InnoDB 引擎,若二级索引能够覆盖查询,则无需访问聚簇索引。 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /database/sql.md: -------------------------------------------------------------------------------- 1 | # SQL基础 2 | # 一、基础 3 | 4 | 模式定义了数据如何存储、存储什么样的数据以及数据如何分解等信息,数据库和表都有模式。 5 | 6 | 主键的值不允许修改,也不允许复用(不能使用已经删除的主键值赋给新数据行的主键)。 7 | 8 | SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 9 | 10 | SQL 语句不区分大小写,但是数据库表名、列名和值是否区分依赖于具体的 DBMS 以及配置。 11 | 12 | SQL 支持以下三种注释: 13 | 14 | ```sql 15 | # 注释 16 | SELECT * 17 | FROM mytable; -- 注释 18 | /* 注释1 19 | 注释2 */ 20 | ``` 21 | 22 | 数据库创建与使用: 23 | 24 | ```sql 25 | CREATE DATABASE test; 26 | USE test; 27 | ``` 28 | 29 | # 二、创建表 30 | 31 | ```sql 32 | CREATE TABLE mytable ( 33 | id INT NOT NULL AUTO_INCREMENT, 34 | col1 INT NOT NULL DEFAULT 1, 35 | col2 VARCHAR(45) NULL, 36 | col3 DATE NULL, 37 | PRIMARY KEY (`id`)); 38 | ``` 39 | 40 | # 三、修改表 41 | 42 | 添加列 43 | 44 | ```sql 45 | ALTER TABLE mytable 46 | ADD col CHAR(20); 47 | ``` 48 | 49 | 删除列 50 | 51 | ```sql 52 | ALTER TABLE mytable 53 | DROP COLUMN col; 54 | ``` 55 | 56 | 删除表 57 | 58 | ```sql 59 | DROP TABLE mytable; 60 | ``` 61 | 62 | # 四、插入 63 | 64 | 普通插入 65 | 66 | ```sql 67 | INSERT INTO mytable(col1, col2) 68 | VALUES(val1, val2); 69 | ``` 70 | 71 | 插入检索出来的数据 72 | 73 | ```sql 74 | INSERT INTO mytable1(col1, col2) 75 | SELECT col1, col2 76 | FROM mytable2; 77 | ``` 78 | 79 | 将一个表的内容插入到一个新表 80 | 81 | ```sql 82 | CREATE TABLE newtable AS 83 | SELECT * FROM mytable; 84 | ``` 85 | 86 | # 五、更新 87 | 88 | ```sql 89 | UPDATE mytable 90 | SET col = val 91 | WHERE id = 1; 92 | ``` 93 | 94 | # 六、删除 95 | 96 | ```sql 97 | DELETE FROM mytable 98 | WHERE id = 1; 99 | ``` 100 | 101 | **TRUNCATE TABLE** 可以清空表,也就是删除所有行。 102 | 103 | ```sql 104 | TRUNCATE TABLE mytable; 105 | ``` 106 | 107 | 使用更新和删除操作时一定要用 WHERE 子句,不然会把整张表的数据都破坏。可以先用 SELECT 语句进行测试,防止错误删除。 108 | 109 | # 七、查询 110 | 111 | ## DISTINCT 112 | 113 | 相同值只会出现一次。它作用于所有列,也就是说所有列的值都相同才算相同。 114 | 115 | ```sql 116 | SELECT DISTINCT col1, col2 117 | FROM mytable; 118 | ``` 119 | 120 | ## LIMIT 121 | 122 | 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 123 | 124 | 返回前 5 行: 125 | 126 | ```sql 127 | SELECT * 128 | FROM mytable 129 | LIMIT 5; 130 | ``` 131 | 132 | ```sql 133 | SELECT * 134 | FROM mytable 135 | LIMIT 0, 5; 136 | ``` 137 | 138 | 返回第 3 \~ 5 行: 139 | 140 | ```sql 141 | SELECT * 142 | FROM mytable 143 | LIMIT 2, 3; 144 | ``` 145 | 146 | 147 | # 八、排序 148 | 149 | - **ASC** :升序(默认) 150 | - **DESC** :降序 151 | 152 | 可以按多个列进行排序,并且为每个列指定不同的排序方式: 153 | 154 | ```sql 155 | SELECT * 156 | FROM mytable 157 | ORDER BY col1 DESC, col2 ASC; 158 | ``` 159 | 160 | # 九、过滤 161 | 162 | 不进行过滤的数据非常大,导致通过网络传输了多余的数据,从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据,而不是传输所有的数据到客户端中然后由客户端进行过滤。 163 | 164 | ```sql 165 | SELECT * 166 | FROM mytable 167 | WHERE col IS NULL; 168 | ``` 169 | 170 | 下表显示了 WHERE 子句可用的操作符 171 | 172 | | 操作符 | 说明 | 173 | | ------------ | ------------ | 174 | | `=` `<` `>` | 等于 小于 大于 | 175 | | `<>` `!=` | 不等于 | 176 | | `<=` `!>` | 小于等于 | 177 | | `>=` `!<` | 大于等于 | 178 | | `BETWEEN` | 在两个值之间 | 179 | | `IS NULL` | 为 NULL 值 | 180 | 181 | 应该注意到,NULL 与 0、空字符串都不同。 182 | 183 | **AND 和 OR** 用于连接多个过滤条件。优先处理 AND,当一个过滤表达式涉及到多个 AND 和 OR 时,可以使用 () 来决定优先级,使得优先级关系更清晰。 184 | 185 | **IN** 操作符用于匹配一组值,其后也可以接一个 SELECT 子句,从而匹配子查询得到的一组值。 186 | 187 | **NOT** 操作符用于否定一个条件。 188 | 189 | # 十、通配符 190 | 191 | 通配符也是用在过滤语句中,但它只能用于文本字段。 192 | 193 | - **%** 匹配 >=0 个任意字符; 194 | 195 | - **\_** 匹配 ==1 个任意字符; 196 | 197 | - **[ ]** 可以匹配集合内的字符,例如 [ab] 将匹配字符 a 或者 b。用脱字符 ^ 可以对其进行否定,也就是不匹配集合内的字符。 198 | 199 | 使用 Like 来进行通配符匹配。 200 | 201 | ```sql 202 | SELECT * 203 | FROM mytable 204 | WHERE col LIKE '[^AB]%'; -- 不以 A 和 B 开头的任意文本 205 | ``` 206 | 207 | 不要滥用通配符,通配符位于开头处匹配会非常慢。 208 | 209 | # 十一、计算字段 210 | 211 | 在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多,并且转换和格式化后的数据量更少的话可以减少网络通信量。 212 | 213 | 计算字段通常需要使用 **AS** 来取别名,否则输出的时候字段名为计算表达式。 214 | 215 | ```sql 216 | SELECT col1 * col2 AS alias 217 | FROM mytable; 218 | ``` 219 | 220 | **CONCAT()** 用于连接两个字段。许多数据库会使用空格把一个值填充为列宽,因此连接的结果会出现一些不必要的空格,使用 **TRIM()** 可以去除首尾空格。 221 | 222 | ```sql 223 | SELECT CONCAT(TRIM(col1), '(', TRIM(col2), ')') AS concat_col 224 | FROM mytable; 225 | ``` 226 | 227 | # 十二、函数 228 | 229 | 各个 DBMS 的函数都是不相同的,因此不可移植。 230 | 231 | ## 文本处理 232 | 233 | | 函数 | 说明 | 234 | | :---: | :---: | 235 | | `LEFT()` `RIGHT()` | 左边或者右边的字符 | 236 | | `LOWER()` `UPPER()` | 转换为小写或者大写 | 237 | | `LTRIM()` `RTIM()` | 去除左边或者右边的空格 | 238 | | `LENGTH()` | 长度 | 239 | | `SOUNDEX()` | 转换为语音值 | 240 | 241 | 其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。 242 | 243 | ```sql 244 | SELECT * 245 | FROM mytable 246 | WHERE SOUNDEX(col1) = SOUNDEX('apple') 247 | ``` 248 | 249 | ## 日期和时间处理 250 | 251 | - 日期格式:YYYY-MM-DD 252 | - 时间格式:HH:MM:SS 253 | 254 | |函 数 | 说 明| 255 | | :---: | :---: | 256 | | `AddDate()` | 增加一个日期(天、周等)| 257 | | `AddTime()` | 增加一个时间(时、分等)| 258 | | `CurDate()` | 返回当前日期 | 259 | | `CurTime()` | 返回当前时间 | 260 | | `Date()` |返回日期时间的日期部分| 261 | | `DateDiff()` |计算两个日期之差| 262 | | `Date_Add()` |高度灵活的日期运算函数| 263 | | `Date_Format()` |返回一个格式化的日期或时间串| 264 | | `Day()`| 返回一个日期的天数部分| 265 | | `DayOfWeek()` |对于一个日期,返回对应的星期几| 266 | | `Hour()` |返回一个时间的小时部分| 267 | | `Minute()` |返回一个时间的分钟部分| 268 | | `Month()` |返回一个日期的月份部分| 269 | | `Now()` |返回当前日期和时间| 270 | | `Second()` |返回一个时间的秒部分| 271 | | `Time()` |返回一个日期时间的时间部分| 272 | | `Year()` |返回一个日期的年份部分| 273 | 274 | ```sql 275 | mysql> SELECT NOW(); 276 | ``` 277 | 278 | ``` 279 | 2018-4-14 20:25:11 280 | ``` 281 | 282 | ## 数值处理 283 | 284 | | 函数 | 说明 | 285 | | :---: | :---: | 286 | | `SIN()` | 正弦 | 287 | | `COS()` | 余弦 | 288 | | `TAN()` | 正切 | 289 | | `ABS()` | 绝对值 | 290 | | `SQRT()` | 平方根 | 291 | | `MOD()` | 余数 | 292 | | `EXP()` | 指数 | 293 | | `PI()` | 圆周率 | 294 | | `RAND()` | 随机数 | 295 | 296 | ## 汇总 297 | 298 | |函 数 |说 明| 299 | | :---: | :---: | 300 | | `AVG()` | 返回某列的平均值 | 301 | | `COUNT()` | 返回某列的行数 | 302 | | `MAX()` | 返回某列的最大值 | 303 | | `MIN()` | 返回某列的最小值 | 304 | | `SUM()` |返回某列值之和 | 305 | 306 | AVG() 会忽略 NULL 行。 307 | 308 | 使用 DISTINCT 可以让汇总函数值汇总不同的值。 309 | 310 | ```sql 311 | SELECT AVG(DISTINCT col1) AS avg_col 312 | FROM mytable 313 | ``` 314 | 315 | # 十三、分组 316 | 317 | 分组就是把具有相同的数据值的行放在同一组中。 318 | 319 | 可以对同一分组数据使用汇总函数进行处理,例如求分组数据的平均值等。 320 | 321 | 指定的分组字段除了能按该字段进行分组,也会自动按该字段进行排序。 322 | 323 | ```sql 324 | SELECT col, COUNT(*) AS num 325 | FROM mytable 326 | GROUP BY col; 327 | ``` 328 | 329 | GROUP BY 自动按分组字段进行排序,ORDER BY 也可以按汇总字段来进行排序。 330 | 331 | ```sql 332 | SELECT col, COUNT(*) AS num 333 | FROM mytable 334 | GROUP BY col 335 | ORDER BY num; 336 | ``` 337 | 338 | WHERE 过滤行,HAVING 过滤分组,行过滤应当先于分组过滤。 339 | 340 | ```sql 341 | SELECT col, COUNT(*) AS num 342 | FROM mytable 343 | WHERE col > 2 344 | GROUP BY col 345 | HAVING num >= 2; 346 | ``` 347 | 348 | 分组规定: 349 | 350 | 1. GROUP BY 子句出现在 WHERE 子句之后,ORDER BY 子句之前; 351 | 2. 除了汇总字段外,SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出; 352 | 3. NULL 的行会单独分为一组; 353 | 4. 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。 354 | 355 | # 十四、子查询 356 | 357 | 子查询中只能返回一个字段的数据。 358 | 359 | 可以将子查询的结果作为 WHRER 语句的过滤条件: 360 | 361 | ```sql 362 | SELECT * 363 | FROM mytable1 364 | WHERE col1 IN (SELECT col2 365 | FROM mytable2); 366 | ``` 367 | 368 | 下面的语句可以检索出客户的订单数量,子查询语句会对第一个查询检索出的每个客户执行一次: 369 | 370 | ```sql 371 | SELECT cust_name, (SELECT COUNT(*) 372 | FROM Orders 373 | WHERE Orders.cust_id = Customers.cust_id) 374 | AS orders_num 375 | FROM Customers 376 | ORDER BY cust_name; 377 | ``` 378 | 379 | # 十五、连接 380 | 381 | 连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 WHERE。 382 | 383 | 连接可以替换子查询,并且比子查询的效率一般会更快。 384 | 385 | 可以用 AS 给列名、计算字段和表名取别名,给表名取别名是为了简化 SQL 语句以及连接相同表。 386 | 387 | ## 内连接 388 | 389 | 内连接又称等值连接,使用 INNER JOIN 关键字。 390 | 391 | ```sql 392 | SELECT a, b, c 393 | FROM A INNER JOIN B 394 | ON A.key = B.key; 395 | ``` 396 | 397 | 可以不明确使用 INNER JOIN,而使用普通查询并在 WHERE 中将两个表中要连接的列用等值方法连接起来。 398 | 399 | ```sql 400 | SELECT a, b, c 401 | FROM A, B 402 | WHERE A.key = B.key; 403 | ``` 404 | 405 | 在没有条件语句的情况下返回笛卡尔积。 406 | 407 | ## 自连接 408 | 409 | 自连接可以看成内连接的一种,只是连接的表是自身而已。 410 | 411 | 一张员工表,包含员工姓名和员工所属部门,要找出与 Jim 处在同一部门的所有员工姓名。 412 | 413 | 子查询版本 414 | 415 | ```sql 416 | SELECT name 417 | FROM employee 418 | WHERE department = ( 419 | SELECT department 420 | FROM employee 421 | WHERE name = "Jim"); 422 | ``` 423 | 424 | 自连接版本 425 | 426 | ```sql 427 | SELECT e1.name 428 | FROM employee AS e1, employee AS e2 429 | WHERE e1.department = e2.department 430 | AND e2.name = "Jim"; 431 | ``` 432 | 433 | 连接一般比子查询的效率高。 434 | 435 | ## 自然连接 436 | 437 | 自然连接是把同名列通过等值测试连接起来的,同名列可以有多个。 438 | 439 | 内连接和自然连接的区别:内连接提供连接的列,而自然连接自动连接所有同名列。 440 | 441 | ```sql 442 | SELECT * 443 | FROM employee NATURAL JOIN department; 444 | ``` 445 | 446 | ## 外连接 447 | 448 | 外连接保留了没有关联的那些行。分为左外连接,右外连接以及全外连接,左外连接就是保留左表没有关联的行。 449 | 450 | 检索所有顾客的订单信息,包括还没有订单信息的顾客。 451 | 452 | ```sql 453 | SELECT Customers.cust_id, Orders.order_num 454 | FROM Customers LEFT OUTER JOIN Orders 455 | ON Customers.cust_id = Orders.cust_id; 456 | ``` 457 | 458 | 如果需要统计顾客的订单数,使用聚集函数。 459 | 460 | ```sql 461 | SELECT Customers.cust_id, 462 | COUNT(Orders.order_num) AS num_ord 463 | FROM Customers LEFT OUTER JOIN Orders 464 | ON Customers.cust_id = Orders.cust_id 465 | GROUP BY Customers.cust_id; 466 | ``` 467 | 468 | # 十六、组合查询 469 | 470 | 使用 **UNION** 来组合两个查询,如果第一个查询返回 M 行,第二个查询返回 N 行,那么组合查询的结果一般为 M+N 行。 471 | 472 | 每个查询必须包含相同的列、表达式和聚集函数。 473 | 474 | 默认会去除相同行,如果需要保留相同行,使用 UNION ALL。 475 | 476 | 只能包含一个 ORDER BY 子句,并且必须位于语句的最后。 477 | 478 | ```sql 479 | SELECT col 480 | FROM mytable 481 | WHERE col = 1 482 | UNION 483 | SELECT col 484 | FROM mytable 485 | WHERE col =2; 486 | ``` 487 | 488 | # 十七、视图 489 | 490 | 视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。 491 | 492 | 对视图的操作和对普通表的操作一样。 493 | 494 | 视图具有如下好处: 495 | 496 | 1. 简化复杂的 SQL 操作,比如复杂的连接; 497 | 2. 只使用实际表的一部分数据; 498 | 3. 通过只给用户访问视图的权限,保证数据的安全性; 499 | 4. 更改数据格式和表示。 500 | 501 | ```sql 502 | CREATE VIEW myview AS 503 | SELECT Concat(col1, col2) AS concat_col, col3*col4 AS compute_col 504 | FROM mytable 505 | WHERE col5 = val; 506 | ``` 507 | 508 | # 十八、存储过程 509 | 510 | 存储过程可以看成是对一系列 SQL 操作的批处理; 511 | 512 | 使用存储过程的好处 513 | 514 | 1. 代码封装,保证了一定的安全性; 515 | 2. 代码复用; 516 | 3. 由于是预先编译,因此具有很高的性能。 517 | 518 | 命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 519 | 520 | 包含 in、out 和 inout 三种参数。 521 | 522 | 给变量赋值都需要用 select into 语句。 523 | 524 | 每次只能给一个变量赋值,不支持集合的操作。 525 | 526 | ```sql 527 | delimiter // 528 | 529 | create procedure myprocedure( out ret int ) 530 | begin 531 | declare y int; 532 | select sum(col1) 533 | from mytable 534 | into y; 535 | select y*y into ret; 536 | end // 537 | delimiter ; 538 | ``` 539 | 540 | ```sql 541 | call myprocedure(@ret); 542 | select @ret; 543 | ``` 544 | 545 | # 十九、游标 546 | 547 | 在存储过程中使用游标可以对一个结果集进行移动遍历。 548 | 549 | 游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 550 | 551 | 使用游标的四个步骤: 552 | 553 | 1. 声明游标,这个过程没有实际检索出数据; 554 | 2. 打开游标; 555 | 3. 取出数据; 556 | 4. 关闭游标; 557 | 558 | ```sql 559 | delimiter // 560 | create procedure myprocedure(out ret int) 561 | begin 562 | declare done boolean default 0; 563 | 564 | declare mycursor cursor for 565 | select col1 from mytable; 566 | # 定义了一个 continue handler,当 sqlstate '02000' 这个条件出现时,会执行 set done = 1 567 | declare continue handler for sqlstate '02000' set done = 1; 568 | 569 | open mycursor; 570 | 571 | repeat 572 | fetch mycursor into ret; 573 | select ret; 574 | until done end repeat; 575 | 576 | close mycursor; 577 | end // 578 | delimiter ; 579 | ``` 580 | 581 | # 二十、触发器 582 | 583 | 触发器会在某个表执行以下语句时而自动执行:DELETE、INSERT、UPDATE。 584 | 585 | 触发器必须指定在语句执行之前还是之后自动执行,之前执行使用 BEFORE 关键字,之后执行使用 AFTER 关键字。BEFORE 用于数据验证和净化。 586 | 587 | INSERT 触发器包含一个名为 NEW 的虚拟表。 588 | 589 | ```sql 590 | CREATE TRIGGER mytrigger AFTER INSERT ON mytable 591 | FOR EACH ROW SELECT NEW.col into @result; 592 | 593 | SELECT @result; -- 获取结果 594 | ``` 595 | 596 | DELETE 触发器包含一个名为 OLD 的虚拟表,并且是只读的。 597 | 598 | UPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表,其中 NEW 是可以被修改地,而 OLD 是只读的。 599 | 600 | 可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 601 | 602 | MySQL 不允许在触发器中使用 CALL 语句,也就是不能调用存储过程。 603 | 604 | # 二十一、事务处理 605 | 606 | 基本术语: 607 | 608 | 1. 事务(transaction)指一组 SQL 语句; 609 | 2. 回退(rollback)指撤销指定 SQL 语句的过程; 610 | 3. 提交(commit)指将未存储的 SQL 语句结果写入数据库表; 611 | 4. 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。 612 | 613 | 不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CREATE 和 DROP 语句。 614 | 615 | MySQL 的事务提交默认是隐式提交,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。 616 | 617 | 通过设置 autocommit 为 0 可以取消自动提交,直到 autocommit 被设置为 1 才会提交;autocommit 标记是针对每个连接而不是针对服务器的。 618 | 619 | 如果没有设置保留点,ROLLBACK 会回退到 START TRANSACTION 语句处;如果设置了保留点,并且在 ROLLBACK 中指定该保留点,则会回退到该保留点。 620 | 621 | ```sql 622 | START TRANSACTION 623 | // ... 624 | SAVEPOINT delete1 625 | // ... 626 | ROLLBACK TO delete1 627 | // ... 628 | COMMIT 629 | ``` 630 | 631 | # 二十二、字符集 632 | 633 | 基本术语: 634 | 635 | 1. 字符集为字母和符号的集合; 636 | 2. 编码为某个字符集成员的内部表示; 637 | 3. 校对字符指定如何比较,主要用于排序和分组。 638 | 639 | 除了给表指定字符集和校对外,也可以给列指定: 640 | 641 | ```sql 642 | CREATE TABLE mytable 643 | (col VARCHAR(10) CHARACTER SET latin COLLATE latin1_general_ci ) 644 | DEFAULT CHARACTER SET hebrew COLLATE hebrew_general_ci; 645 | ``` 646 | 647 | 可以在排序、分组时指定校对: 648 | 649 | ```sql 650 | SELECT * 651 | FROM mytable 652 | ORDER BY col COLLATE latin1_general_ci; 653 | ``` 654 | 655 | # 二十三、权限管理 656 | 657 | MySQL 的账户信息保存在 mysql 这个数据库中。 658 | 659 | ```sql 660 | USE mysql; 661 | SELECT user FROM user; 662 | ``` 663 | 664 | **创建账户** 665 | 666 | ```sql 667 | CREATE USER myuser IDENTIFIED BY 'mypassword'; 668 | ``` 669 | 670 | 新创建的账户没有任何权限。 671 | 672 | **修改账户名** 673 | 674 | ```sql 675 | RENAME myuser TO newuser; 676 | ``` 677 | 678 | **删除账户** 679 | 680 | ```sql 681 | DROP USER myuser; 682 | ``` 683 | 684 | **查看权限** 685 | 686 | ```sql 687 | SHOW GRANTS FOR myuser; 688 | ``` 689 | 690 | **授予权限** 691 | 692 | ```sql 693 | GRANT SELECT, INSERT ON mydatabase.* TO myuser; 694 | ``` 695 | 696 | 账户用 username@host 的形式定义,username@% 使用的是默认主机名。 697 | 698 | **删除权限** 699 | 700 | ```sql 701 | REVOKE SELECT, INSERT ON mydatabase.* FROM myuser; 702 | ``` 703 | 704 | GRANT 和 REVOKE 可在几个层次上控制访问权限: 705 | 706 | - 整个服务器,使用 GRANT ALL 和 REVOKE ALL; 707 | - 整个数据库,使用 ON database.\*; 708 | - 特定的表,使用 ON database.table; 709 | - 特定的列; 710 | - 特定的存储过程。 711 | 712 | **更改密码** 713 | 714 | 必须使用 Password() 函数 715 | 716 | ```sql 717 | SET PASSWROD FOR myuser = Password('new_password'); 718 | ``` 719 | 720 | # 参考资料 721 | 722 | - BenForta. SQL 必知必会 [M]. 人民邮电出版社, 2013. 723 | 724 | 725 | -------------------------------------------------------------------------------- /database/数据库设计三大范式.md: -------------------------------------------------------------------------------- 1 | # 数据库设计三大范式 2 | 3 | ## 参考 4 | http://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html 5 | 6 | -------------------------------------------------------------------------------- /framework/README.md: -------------------------------------------------------------------------------- 1 | # 框架部分 2 | java 开发中常用的框架用 Spring、Mybatis、Hibernate、Struts。 目前企业使用SSM比较多 Spring+SpringMVC+ Mybatis. 因此面试中重点掌握spring和Mybatis则可。 3 | 4 | - [Spring](./spring.md) 5 | - [hibernate](./hibernate.md) 6 | - [struts](./struts.md) 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /framework/hibernate.md: -------------------------------------------------------------------------------- 1 | ## MyBatis 与 Hibernate的区别 2 | (1)**hibernate是全自动,而mybatis是半自动** 3 | 4 | hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。 5 | 6 | (2)**hibernate数据库移植性远大于mybatis** 7 | 8 | hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库(Oracle、MySQL等)的耦合性,而mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。 9 | 10 | (3)**hibernate拥有完整的日志系统,mybatis则欠缺一些** 11 | 12 | hibernate日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而mybatis则除了基本记录功能外,功能薄弱很多。 13 | 14 | (4)mybatis相比hibernate需要关心很多细节 15 | 16 | **hibernate配置要比mybatis复杂的多,学习成本也比mybatis高**。但也正因为mybatis使用简单,才导致它要比hibernate关心很多技术细节。mybatis由于不用考虑很多细节,开发模式上与传统jdbc区别很小,因此很容易上手并开发项目,但忽略细节会导致项目前期bug较多,因而开发出相对稳定的软件很慢,而开发出软件却很快。hibernate则正好与之相反。但是如果使用hibernate很熟练的话,实际上开发效率丝毫不差于甚至超越mybatis。 17 | 18 | (5)**sql直接优化上,mybatis要比hibernate方便很多** 19 | 20 | 由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大,见到报表等变态需求时,hql也歇菜,也就是说hql是有局限的;hibernate虽然也支持原生sql,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便。总之写sql的灵活度上hibernate不及mybatis。 21 | 22 | (6)缓存机制上,hibernate要比mybatis更好一些 23 | 24 | MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。 25 | 26 | 而Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。 27 | 28 | ## JDBC 和 Hibernate的区别 29 | 30 | 31 | ## Hibernate的原理体系架构 32 | ![图片标题](http://my.csdn.net/uploads/201205/30/1338346660_4642.gif) 33 | **1) SessionFactory (org.hibernate.SessionFactory)** 34 | 针对单个数据库映射关系经过编译后的内存镜像,是线程安全的(不可变)。它是生成Session的工厂,本身要用到ConnectionProvider。该对象可以在进程或集群的级别上,为那些事务之间可以重用的数据提供可选的二级缓存。 35 | 36 | **2) Session (org.hibernate.Session)** 37 | 表示应用程序与持久储存层之间交互操作的一个单线程对象,此对象生存期很短。 其隐藏了JDBC连接,也是Transaction的工厂。其会持有一个针对持久化对象的必选(第一级)缓存,在遍历对象图或者根据持久化标识查找对象时会用到。 38 | 39 | **3) 事务Transaction (org.hibernate.Transaction)** 40 | (可选的)应用程序用来指定原子操作单元范围的对象,它是单线程的,生命周期很短。 它通过抽象将应用从底层具体的JDBC、JTA以及CORBA事务隔离开。 某些情况下,一个Session之内可能包含多个Transaction对象。 尽管是否使用该对象是可选的,但无论是使用底层的API还是使用Transaction对象,事务边界的开启与关闭是必不可少的。 41 | 42 | **4) ConnectionProvider (org.hibernate.connection.ConnectionProvider)** 43 | (可选的)生成JDBC连接的工厂(同时也起到连接池的作用)。 它通过抽象将应用从底层的Datasource或DriverManager隔离开。 仅供开发者扩展/实现用,并不暴露给应用程序使用。 44 | 45 | **5)TransactionFactory (org.hibernate.TransactionFactory)** 46 | (可选的)生成Transaction对象实例的工厂。 仅供开发者扩展/实现用,并不暴露给应用程序使用。 47 | 48 | **6) 持久的对象及其集合** 49 | 带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。 这些对象可能是普通的JavaBeans/POJO,唯一特殊的是他们正与(仅仅一个)Session相关联。 一旦这个Session被关闭,这些对象就会脱离持久化状态,这样就可被应用程序的任何层自由使用。 (例如,用作跟表示层打交道的数据传输对象。) 50 | 51 | **7) 瞬态(transient)和脱管(detached)的对象及其集合** 52 | 那些目前没有与session关联的持久化类实例。他们可能是在被应用程序实例化后,尚未进行持久化的对象。也可能是因为实例化他们的Session已经被关闭而脱离持久化的对象。 53 | 54 | 参考: http://blog.sina.com.cn/s/blog_667fe4a501016awl.html 55 | 56 | ## 五大核心接口, 57 | Hibernate的核心接口一共有5个,分别为:Session、SessionFactory、Transaction、Query和 Configuration。这5个核心接口在任何开发中都会用到。通过这些接口,不仅可以对持久化对象进行存取,还能够进行事务控制。下面对这五的核心 接口分别加以介绍。 58 | 59 | **1) Session接口**: 60 | Session接口负责执行被持久化对象的CRUD操作(CRUD的任务是完成与数据库的交流,包含了很多常见的 SQL语句。)。但需要注意的是Session对象是非线程安全的。同时,Hibernate的session不同于JSP应用中的HttpSession。这里当使用session这个术语时,其实指的是Hibernate中的session,而以后会将HttpSesion对象称为用户session。 61 | 62 | **2) SessionFactory接口** 63 | SessionFactroy接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建 Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。 64 | 65 | **3) Configuration接口** 66 | Configuration接口负责配置并启动Hibernate,创建SessionFactory对 象。在Hibernate的启动的过程中,Configuration类的实例首先定位映射文档位置、读取配置,然后创建SessionFactory对象。 67 | 68 | **4) Transaction接口** 69 | Transaction接口负责事务相关的操作。它是可选的,开发人员也可以设计编写自己的底层事务处理代码。 70 | 71 | **5) Query和Criteria接口**: 72 | Query和Criteria接口负责执行各种数据库查询。它可以使用HQL语言或SQL语句两种表达方式。 73 | 74 | http://blog.csdn.net/martinmateng/article/details/50879436 75 | 76 | ## Hibernate对象的三种状态转换 77 | ### 1.瞬时状态 (transient) 78 | 特征: 79 | 80 | 1. 不处于Session 缓存中 81 | 2. 数据库中没有对象记录 82 | 83 | Java如何进入临时状态: 84 | 1. 通过new语句刚创建一个对象时 85 | 2. 当调用Session 的delete()方法,从Session 缓存中删除一个对象时。 86 | 87 | ### 2.持久化状态(persisted) 88 | 特征: 89 | 1. 处于Session 缓存中 90 | 2. 持久化对象数据库中设有对象记录 91 | 3. Session 在特定时刻会保持二者同步 92 | 93 | Java如何进入持久化状态 94 | 1. Session 的save()把临时-》持久化状态 95 | 2. Session 的load(),get()方法返回的对象 96 | 3. Session 的find()返回的list集合中存放的对象 97 | 4. session 的update(),saveOrupdate()使游离-》持久化 98 | 99 | ### 3.游离状态(detached) 100 | 特征: 101 | 1. 不再位于Session 缓存中 102 | 2. 游离对象由持久化状态转变而来,数据库中可能还有对应记录。 103 | 104 | Java如何进入持久化状态-》游离状态 105 | 1. Session 的close()方法 106 | 2. Session 的evict()方法,从缓存中删除一个对象。提高性能。少用。 107 | 108 | ## 事务管理 109 | 110 | ## Hibernate对一二级缓存的使用 111 | (1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据; 112 | 113 | (2)二级缓存就是SessionFactory级别的缓存,顾名思义,就是查询的时候会把查询结果缓存到二级缓存中,如果同一个sessionFactory创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿结果,而不会再去连接数据库; 114 | 115 | 3)Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存; 116 | 117 | http://www.open-open.com/lib/view/open1413527015465.html 118 | 119 | ## Lazy-Load的理解 120 | 在Hibernate框架中,当我们要访问的数据量过大时,明显用缓存不太合适, 因为内存容量有限,为了减少并发量,减少系统资源的消耗,这时Hibernate用懒加载机制来弥补这种缺陷,但是这只是弥补而不是用了懒加载总体性能就提高了。我们所说的懒加载也被称为延迟加载,它在查询的时候不会立刻访问数据库,而是返回代理对象,当真正去使用对象的时候才会访问数据库。 121 | 122 | 1、通过Session.load()实现懒加载 123 | 124 | load(Object, Serializable):根据id查询。查询返回的是代理对象,不会立刻访问数据库,是懒加载的。当真正去使用对象的时候才会访问数据库。用load()的时候会发现不会打印出查询语句,而使用get()的时候会打印出查询语句。 125 | 使用load()时如果在session关闭之后再查询此对象,会报异常:could not initialize proxy - no Session。处理办法:在session关闭之前初始化一下查询出来的对象:Hibernate.initialize(user);使用load()可以提高效率,因为刚开始的时候并没有查询数据库。但很少使用。 126 | 127 | 2、one-to-one(元素)实现了懒加载。 128 | 129 | 在一对一的时候,查询主对象时默认不是懒加载。即:查询主对象的时候也会把从对象查询出来。需要把主对象配制成lazy="true" constrained="true" fetch="select"。此时查询主对象的时候就不会查询从对象,从而实现了懒加载。一对一的时候,查询从对象的是默认是懒加载。即:查询从对象的时候不会把主对象查询出来。而是查询出来的是主对象的代理对象。 130 | 131 | 3、many-to-one(元素)实现了懒加载。 132 | 多对一的时候,查询主对象时默认是懒加载。即:查询主对象的时候不会把从对象查询出来。 133 | 134 | 4、one-to-many(元素)懒加载:默认会懒加载,这是必须的,是重常用的。 135 | 一对多的时候,查询主对象时默认是懒加载。即:查询主对象的时候不会把从对象查询出来。 136 | 137 | 参考: http://blog.csdn.net/sanjy523892105/article/details/7071139 138 | http://blog.csdn.net/yaorongwang0521/article/details/7074573 139 | 140 | ## 悲观锁和乐观锁 141 | **悲观锁 (Pessimistic Locking)** 142 | 悲观锁,正如其名,他是对数据库而言的,数据库悲观了,他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据) 143 | 144 | 优点:数据的一致性保持得很好 145 | 缺点:不适合多个用户并发访问。 146 | 147 | **乐观锁** 148 | 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。 149 | 150 | 151 | 个人公众号(欢迎关注): 152 | ![](/assets/weix_gongzhonghao.jpg) 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /framework/struts.md: -------------------------------------------------------------------------------- 1 | ## 描述 Struts 体系结构?对应各个部分的开发工作主要包括哪些? 2 | Struts开源架构很好的实现了MVC模式,MVC即Model-View-Controller的缩写,是一种常用的设计模式。MVC 减弱了业务逻辑接口和数据接口之间的耦合,以及让视图层更富于变化。在Struts2的模型 - 视图 - 控制器模式,实现以下五个核心部件: 3 | 4 | - Actions 5 | - Interceptors 6 | - Value Stack / OGNL 7 | - Results / Result types 8 | - View technologies 9 | 10 | Struts 2 从传统的MVC框架操作需要的模型,而不是在控制器角色略有不同,虽然有一些重叠 11 | 12 | **模型** 13 | 模型以一个或多个java bean的形式存在。这些bean分为三类:Action Form、Action、JavaBean or EJB。Action Form通常称之为FormBean,封装了来自于Client的用户请求信息,如表单信息。Action通常称之为ActionBean,获取从ActionSevlet传来的FormBean,取出FormBean中的相关信息,并做出相关的处理,一般是调用Java Bean或EJB等。 14 | 15 | **视图** 16 | 主要由JSP生成页面完成视图,Struts提供丰富的JSP 标签库: Html,Bean,Logic,Template等 17 | 18 | **控制器** 19 | 该控制器负责响应用户输入和执行数据模型对象的相互作用。控制器接收输入,验证输入,然后进行业务操作,修改数据模型的状态。 20 | 21 | 这个解释比较好: http://www.yiibai.com/struts_2/struts_architecture.html 22 | http://www.cnblogs.com/langtianya/archive/2013/04/09/3011090.html 23 | 24 | ## 什么是Struts2 25 | Apache Struts2的是一个在Java中构建**Web应用程序开源框架**。 Struts2是基于OpenSymphony的WebWork的框架。它是Struts1的提高,它更加灵活,易于使用和扩展。 Struts2的核心组成部分是Action,拦截器和结果页。 26 | 27 | Struts2提供了许多方法来创建Action类,并**通过struts.xml中或通过注释进行配置**。我们可以创建自己的**拦截器**实现常见任务。 Struts2中自带了很多的**标签,并使用OGNL表达式语言**。我们可以创造我们自己的类型转换器来呈现的结果页面。结果页面可以JSP和FreeMarker的模板。 28 | 29 | ## Struts 与webWork的区别 30 | Struts 2是Struts的下一代产品。是在 struts 和WebWork的技术基础上进行了合并,全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构的差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与Servlet API完全脱离开,所以Struts 2可以理解为WebWork的更新产品。 31 | 32 | Struts和Webwork同为服务于Web的一种MVC框架,从某种程度上看,Struts2是从WebWork2上升级得到的。甚至Apache的官方文档也讲:WebWork2到Struts2是平滑的过渡。我们甚至也可以说Struts2就是WebWork2.3而已。在很多方面Struts仅仅是改变了WebWork下的名称。Struts2对应的有自己的标签,并且功能强大。Webwork也有自己的标签。 33 | 1) 在很多方面Struts2仅仅是改变了WebWork下的名称,如DispatcherUtil 改为了Dispatcher. 34 | 2) AroundInterceptor:Struts 2不再支持WebWork中的AroundInterceptor。如果应用程序中需要使用AroundInterceptor,则应该自己手动导入WebWork中的AroundInterceptor类。 35 | 3) IoC容器支持:Struts 2不再支持内建的IoC容器,而改为全面支持Spring的IoC容器,以Spring的IoC容器作为默认的Object工厂。 36 | 4) 富文本编辑器标签:Struts 2不再支持WebWork的富文本编辑器,如果应用中需要使用富文本编辑器,则应该使用**Dojo**的富文本编辑器。 37 | 38 | http://developer.51cto.com/art/201106/271744.htm 39 | 40 | ## struts2 与struts1的区别 41 | Action类,线程安全,测试,标签,验证 42 | 43 | 44 | **Action 类:** 45 | • Struts1要求Action类继承一个**抽象**基类。Struts1的一个普遍问题是使用抽象类编程而不是接口,而struts2的Action是接口。 46 | • Struts 2 Action类可以实现一个Action**接口**,也可实现其他接口,**使可选和定制的服务成为可能**。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。 47 | 48 | **线程模式**: 49 | • Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。(**不是线程安全**) 50 | • Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)(**线程安全**) 51 | 52 | **Servlet 依赖**: 53 | • Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。 54 | • Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。 55 | 56 | **可测性**: 57 | • 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。 58 | • Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。 59 | 60 | **表达式语言**: 61 | • Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。 62 | • Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--"Object Graph Notation Language" (OGNL). 63 | 64 | **绑定值到页面(view)**: 65 | • Struts 1使用标准JSP机制把对象绑定到页面中来访问。 66 | • Struts 2 使用 "ValueStack"技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。 67 | 68 | ## Struts工作流程: 69 | ![图片标题](http://www.evget.com/images/article/08072801.png) 70 | Struts2: 71 | (1)客户端提交一个HttpServletRequest请求(.action或JSP页面) 72 | (2)请求被提交到一系列(主要是三层)的过滤器(Filter),如(ActionContextCleanUp、其他过滤器(SiteMesh等)、 FilterDispatcher)。注意这里是有顺序的,先ActionContextCleanUp,再其他过滤器(SiteMesh等)、最后到FilterDispatcher。 73 | (3)FilterDispatcher是Struts2控制器的核心,它通常是过滤器链中的最后一个过滤器,FilterDispatcher进行初始化并启用核心doFilter().FilterDispatcher询问ActionMapper是否需要调用某个Action来处理这个(request)请求,如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy。 74 | (6)ActionProxy通过**ConfigurationManager**(它会访问struts.xml)询问框架的配置文件,找到需要调用的Action类. 75 | (7)ActionProxy创建一个ActionInvocation实例,而ActionInvocation通过代理模式调用Action,(在调用之前会根据配置文件加载相关的所有Interceptor拦截器) 76 | (8)Action执行完毕后,返回一个result字符串,此时再按相反的顺序通过Interceptor拦截器. 77 | (9)最后ActionInvocation负责根据struts.xml中配置的result元素,找到与返回值对应的result,决定进行下一步输出. 78 | 79 | ## 为什么要使用 Struts2 & Struts2 的优点: 80 | ①. 基于 MVC 架构,框架结构清晰。 81 | ②. 使用 OGNL: OGNL 可以快捷的访问值栈中的数据、调用值栈中对象的方法 82 | ③. 拦截器: Struts2 的拦截器是一个 Action 级别的 AOP, Struts2 中的许多特性都是通过拦截器来实现的, 例如异常处理,文件上传,验证等。拦截器是可配置与重用的 83 | ④. 多种表现层技术. 如:JSP、FreeMarker、Velocity 等 84 | 85 | ## SpringMVC 与 Struts2区别 86 | 1.核心控制器(前端控制器、预处理控制器):对于使用过mvc框架的人来说这个词应该不会陌生,核心控制器的主要用途是处理所有的请求,然后对那些特殊的请求 (控制器)统一的进行处理(字符编码、文件上传、参数接受、异常处理等等),spring mvc核心控制器是Servlet,而Struts2是Filter。 87 | 2.控制器实例:Spring Mvc会比Struts快一些(理论上)。Spring Mvc是基于方法设计,而Sturts是基于对象,每次发一次请求都会实例一个action,每个action都会被注入属性,而Spring更像Servlet一样,只有一个实例,每次请求执行对应的方法即可(注意:由于是单例实例,所以应当避免全局变量的修改,这样会产生线程安全问题)。 88 | 3. 管理方式:大部分的公司的核心架构中,就会使用到spring,而spring mvc又是spring中的一个模块,所以spring对于spring mvc的控制器管理更加简单方便,而且提供了全 注解方式进行管理,各种功能的注解都比较全面,使用简单,而struts2需要采用XML很多的配置参数来管理(虽然也可以采用注解,但是几乎没有公司那 样使用)。 89 | 4.参数传递:Struts2中自身提供多种参数接受,其实都是通过(ValueStack)进行传递和赋值,而SpringMvc是通过方法的参数进行接收。 90 | 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变量是共享的,这不会影响程序运行,却给我们编码,读程序时带来麻烦。 91 | 7.spring mvc处理ajax请求,直接通过返回数据,方法中使用注解@ResponseBody,spring mvc自动帮我们对象转换为JSON数据。 92 | 93 | 94 | ## Filter,Listener,Servlet区别 95 | 1) Filter 实现javax.servlet.Filter接口,在web.xml中配置与标签指定使用哪个Filter实现类过滤哪些URL链接。只在web启动时进行初始化操作。filter流程是**线性**的, url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收等,而servlet 处理之后,不会继续向下传递。filter功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,而servlet的功能主要用来主导流程。 96 | 特点:可以在响应之前修改Request和Response的头部,只能转发请求,不能直接发出响应。filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等 97 | 2) Servlet 流程是短的,url传来之后,就对其进行处理,之后返回或转向到某一自己指定的页面。它主要用来在业务处理之前进行控制。 98 | 3) Listener 99 | servlet,filter都是针对url之类的,而listener是针对**对象操作**的,如session的创建,session.setAttribute的发生,在这样的事件发 100 | 生时做一些事情。 101 | 102 | ## Struts2拦截器和过滤器的区别 103 | ①、过滤器依赖于Servlet容器,而拦截器不依赖于Servlet容器。 104 | ②、Struts2 拦截器只能对 Action 请求起作用,而过滤器则可以对几乎所有请求起作用。 105 | ③、拦截器可以访问 Action 上下文(ActionContext)、值栈里的对象(ValueStack),而过滤器不能. 106 | ④、在 Action 的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。 107 | 108 | ## Struts的Action是不是线程安全的 109 | struts2的action是线程安全的,struts1的action不是线程安全的 。 110 | 对于struts1 ,Action是单例模式,一个实例来处理所有的请求。当第一次**.do的请求过来时,在内存中的actionmapping中找到相对应的action,然后new出这个action放在缓存中,当第二次一样的请求过来时,还是找的这个action,所以对于struts1来说,action是单实例的,只有一个,如果在action中定义变量,就要非常小心了,因为并发问题,可能带来灾难性的后果,也不是不可以,我们可以加锁达到同步,只是在性能上就 要折衷了。 111 | 声明局部变量,或者扩展RequestProcessor,让每次都创建一个Action,或者在spring中用scope=”prototype”来管理,不申明类变量就可以保证线程安全。因为只存在一个Action类实例,所有线程会共享类变量。 112 | struts2 在struts1的基础上做了改进 ,对于struts2 ,每次请求过来都会new一个新的action, 所以说struts2的action没有线程安全问题,是线程安全的,但同时也带来一个问题,每次都new一个action ,这样action的实例太多 , 在性能方面还是存在一定的缺陷的。 113 | 114 | ## struts2.0的mvc模式与struts1.0的区别? 115 | 与struts1最大的不同是:struts2的控制器。struts2的控制器不再像struts1的控制器,需要**继承**一个Action父类,甚至可以无需实现任何接口,struts2的Action就是一个普通的POJO。实际上,Struts2 的Action就是一个包含execute方法的普通Java类该类里包含的多个属性用于封装用户的请求参数。 116 | 117 | ## 你对MVC的理解,MVC有什么优缺点?结合Struts,说明在一个Web应用如何去使用? 118 | MVC设计模式(应用观察者模式的框架模式) 119 | M: Model(Business process layer),模型,是应用程序中用于处理应用程序数据逻辑的部分,通常模型对象负责在数据库中存取数据。,并独立于表现层 (Independent of presentation)。 120 | V: View(Presentation layer),视图,通过客户端数据类型显示数据,并回显模型层的执行结果。 121 | C: Controller(Control layer),控制器,也就是**视图层和模型层桥梁**,控制数据的流向,接受视图层发出的事件,并重绘视图 122 | 优点: 123 | 1)视图控制模型分离,提高代码重用性。 124 | 2)提高开发效率。 125 | 3)便于后期维护,降低维护成本。 126 | 4)方便多开发人员间的分工。 127 | 5)结构清晰 128 | 缺点: 129 | 1)清晰的构架以代码的复杂性为代价, 对小项目优可能反而降低开发效率。 130 | 2)运行效率相对较低 131 | 132 | MVC框架的一种实现模型 133 | 模型二(Servlet-centric): JSP+Servlet+JavaBean,以控制为核心,JSP只负责显示和收集数据,Sevlet,连接视图和模型,将视图层数据,发送给模型层,JavaBean,分为业务类和数据实体,业务类处理业务数据,数据实体,承载数据,基本上大多数的项目都是使用这种MVC的实现模式。 134 | 135 | Struts MVC框架(Web application frameworks) :Struts是使用MVC的实现模式二来实现的,也就是以控制器为核心。 136 | 137 | Struts提供了一些组件使用MVC开发应用程序: 138 | Model:Struts 没有提供model 类。这个商业逻辑必须由Web 应用程序的开发者以JavaBean或EJB的形式提供 139 | View:Struts提供了action form创建form bean, 用于在controller和view间传输数据。此外,Struts提供了自定义JSP标签库,辅助开发者用JSP创建交互式的以表单为基础的应用程序,应用程序资源文件保留了一些文本常量和错误消息,可转变为其它语言, 可用于JSP中。 140 | Controller:Struts提供了一个核心的控制器FilterDispatcher,通过这个核心的控制器来调用其他用户注册了的自定义的控制器Action,自定义Action需要符合Struts的自定义Action规范,还需要在struts-config.xml的特定配置文件中进行配置,接收JSP输入字段形成Action form,然后调用一个Action控制器。Action控制器中提供了model的逻辑接口。 141 | 142 | ## 说出 struts2 中至少 5 个的默认拦截器 143 | exception;fileUpload;i18n;modelDriven;params;prepare;token;tokenSession;validation 等 144 | 拦截器的生命周期与工作过程 ? 145 | 146 | ## 每个拦截器都是需要实现 Interceptor 接口 147 | init():在拦截器被创建后立即被调用, 它在拦截器的生命周期内只被调用一次. 可以在该方法中对相关资源进行必要的初始化; 148 | intercept(ActionInvocation invocation):每拦截一个动作请求,该方法就会被调用一次; 149 | destroy:该方法将在拦截器被销毁之前被调用, 它在拦截器的生命周期内也只被调用一次; 150 | 151 | ## 创建Action类有几种方法? 152 | 实现Action 接口 153 | 使用Struts2 @Action 元注解 154 | 继承ActionSupport类,必须实现 execute() 方法,返回一个可配置的字符串 155 | 156 | ##Struts2的拦截器执行什么模式? 157 | **责任链模式** 158 | 过滤器decorator模式和职责链模式 159 | 160 | 161 | 个人公众号(欢迎关注): 162 | ![](/assets/weix_gongzhonghao.jpg) 163 | 164 | 165 | -------------------------------------------------------------------------------- /framework/web.md: -------------------------------------------------------------------------------- 1 | # web 部分 2 | ## 1. servlet生命周期 3 | servlet的声明周期周期是由servlet的容器来控制,它可以分为3个阶段:初始化,运行和销毁. 4 | 5 | ### 1) 初始化阶段 6 | 初始化阶段主要完成以下任务: 7 | 8 | - servlet容器加载servlet类,把servlet类的.class文件中的数据读到内存中. 9 | - servlet容器创建一个ServletConfig对象,ServletConfig对象包含了servlet的初始化配置信息. 10 | - servlet容器创建一个servlet对象 11 | - servlet容器调用servlet的init方法进行初始化. 12 | 13 | ### 2) 运行阶段 14 | 当servlet容器收到一个请求时,servlet容器会针对这个请求创建servletRequest和servletResponse对象,然后调用service方法.并把这两个参数传递给service方法.service方法通过servletRequest对象获得请求的信息,并处理该请求. 再通过servletResponse对象生成这个请求的相应结果.然后销毁servletRequest和servletResponse对象. 不管这个请求时post还是get提交的,最终这个请求都会由service方法来处理. 15 | 16 | ### 3) 销毁阶段 17 | 当web应用被终止时,servlet容器会调用servlet对象的destroy方法,然后销毁servlet对象. 同时也会销毁servlet对象相关联的servletConfig对象. 我们可以通过destroy方法释放servlet占用的资源. 18 | 19 | -------------------------------------------------------------------------------- /hr/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/hr/README.md -------------------------------------------------------------------------------- /hr/ask.md: -------------------------------------------------------------------------------- 1 | - 你的下属积极性不高你怎么办, 2 | - 你对自己的评价是怎么样的, 3 | - 你凭什么证明你有你所说的这些能力呢? 4 | - 你简历上的自我评价和你自己介绍的有些出入啊,简历是认真写的吗? 5 | - 优缺点?兴趣? 6 | - 为什么想来网易? 7 | - 有投其他公司吗? 8 | - 网易最吸引你的是什么? 9 | - 想来杭州吗? 10 | - 五个词来描述自己 11 | - 怎么理解踏实,你在项目中哪儿体现到了 12 | - 最近最有成就感的事 13 | - 最近在看的技术 14 | - 是否写过其他个人兴趣的小项目 15 | - 写技术博客吗,github呢 16 | - 个人职位目标 17 | 18 | 19 | https://www.nowcoder.com/discuss/84999?type=2&order=0&pos=21&page=0 20 | 21 | -------------------------------------------------------------------------------- /hr/跳槽HR问题.md: -------------------------------------------------------------------------------- 1 | # HR跳槽面试问题 2 | ## 期待薪资 3 | 当hr问期望薪资时,可以先咨询公司的薪资结构。在hr给出类似以上的答案之后,已经能够判断这个公司的薪酬制度是否完善,以及具体的数额框架如何组成。 4 | 。 5 | 6 | 此时,可以按自己期望的年收入,做一番心算,然后告诉hr,按公司框架来说我期望的月薪是税前XXXX元。 7 | 8 | 如果hr说很难,或者好奇这个数字的来源,可以从以下几个维度分析薪资原因: 9 | 10 | 1. 我前一份工作的薪酬水平; 11 | 2. 我预期的涨幅; 12 | 3. 我将入职岗位的价值; 13 | 4. 我与这个岗位的匹配度,以及我在这个岗位上能贡献的额外价值以及获得的额外成长。 14 | 15 | 谈薪资时,如果你手里有其他公司的OFFER做竞争,会刺激当前企业给出超出预期的好结果。 16 | 17 | 18 | 参考:https://www.zhihu.com/question/20890139 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /images/15262672735241.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262672735241.jpg -------------------------------------------------------------------------------- /images/15262674498084.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262674498084.jpg -------------------------------------------------------------------------------- /images/15262725012092.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262725012092.jpg -------------------------------------------------------------------------------- /images/15262727479347.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262727479347.jpg -------------------------------------------------------------------------------- /images/15262727709415.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262727709415.jpg -------------------------------------------------------------------------------- /images/15262741159390.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262741159390.jpg -------------------------------------------------------------------------------- /images/15262742098959.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262742098959.jpg -------------------------------------------------------------------------------- /images/15262744168004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262744168004.jpg -------------------------------------------------------------------------------- /images/15262744598214.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262744598214.jpg -------------------------------------------------------------------------------- /images/15262746232094.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262746232094.jpg -------------------------------------------------------------------------------- /images/15262747755969.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262747755969.jpg -------------------------------------------------------------------------------- /images/15262804666714.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262804666714.jpg -------------------------------------------------------------------------------- /images/15262805942603.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262805942603.jpg -------------------------------------------------------------------------------- /images/15262813568697.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262813568697.jpg -------------------------------------------------------------------------------- /images/15262858741207.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15262858741207.jpg -------------------------------------------------------------------------------- /images/15263482717424.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15263482717424.jpg -------------------------------------------------------------------------------- /images/15263488660716.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15263488660716.jpg -------------------------------------------------------------------------------- /images/15263490496683.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15263490496683.jpg -------------------------------------------------------------------------------- /images/15263493121606.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15263493121606.jpg -------------------------------------------------------------------------------- /images/15263496139671.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15263496139671.jpg -------------------------------------------------------------------------------- /images/15263505327822.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15263505327822.jpg -------------------------------------------------------------------------------- /images/15263523909429.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15263523909429.jpg -------------------------------------------------------------------------------- /images/15264350580615.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15264350580615.jpg -------------------------------------------------------------------------------- /images/15267019643357.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267019643357.jpg -------------------------------------------------------------------------------- /images/15267022191672.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267022191672.jpg -------------------------------------------------------------------------------- /images/15267023963428.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267023963428.jpg -------------------------------------------------------------------------------- /images/15267059908176.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267059908176.jpg -------------------------------------------------------------------------------- /images/15267061387896.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267061387896.jpg -------------------------------------------------------------------------------- /images/15267085161227.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267085161227.jpg -------------------------------------------------------------------------------- /images/15267089041829.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267089041829.jpg -------------------------------------------------------------------------------- /images/15267089312034.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267089312034.jpg -------------------------------------------------------------------------------- /images/15267212678626.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267212678626.jpg -------------------------------------------------------------------------------- /images/15267213158671.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267213158671.jpg -------------------------------------------------------------------------------- /images/15267213939865.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267213939865.jpg -------------------------------------------------------------------------------- /images/15267214238344.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267214238344.jpg -------------------------------------------------------------------------------- /images/15267214776563.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267214776563.jpg -------------------------------------------------------------------------------- /images/15267215934973.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15267215934973.jpg -------------------------------------------------------------------------------- /images/15268031792473.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15268031792473.jpg -------------------------------------------------------------------------------- /images/15269538268005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15269538268005.jpg -------------------------------------------------------------------------------- /images/15276716962414.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15276716962414.jpg -------------------------------------------------------------------------------- /images/15276920363669.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15276920363669.jpg -------------------------------------------------------------------------------- /images/15297611898077.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15297611898077.jpg -------------------------------------------------------------------------------- /images/15297612276666.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15297612276666.jpg -------------------------------------------------------------------------------- /images/15297612660838.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15297612660838.jpg -------------------------------------------------------------------------------- /images/15297613011263.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15297613011263.jpg -------------------------------------------------------------------------------- /images/15297621325283.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/images/15297621325283.jpg -------------------------------------------------------------------------------- /java/G1垃圾回收器.md: -------------------------------------------------------------------------------- 1 | # G1垃圾回收期 2 | 3 | # 一. 介绍 4 | 5 | G1将新生代/老年代的物理空间划分取消了,不再为新生代和老年代分配连续的大空间. 取而代之的是,**G1算法将堆划分为若干个区域(Region)**,每个区域可以是年轻代也可以是老年代,它仍然属于分代收集器。新生代和老年代不再分配连续的物理空间,而是分配块区域。跟踪这些区域的垃圾堆积程度, 在后台维护一个优先列表,每次运行的收集时间,**优先回收垃圾最多的区域**,这种方式可以减少一些内存碎片,也提高内存回收效率。这也是为什么G1命名为**Garbage First的原因:第一时间处理垃圾最多的区块**。 6 | 7 |
8 | 9 | 这些区域的一部分包含新生代,一部分包含老年代。新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。 10 | 11 | 在G1中,还有一种特殊的区域,叫**Humongous [hju'mʌŋɡəs] 区域**。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。 12 | 13 | 14 | 那么G1相对于CMS的区别在: 15 | 16 | - G1在压缩空间方面有优势 17 | - G1通过将内存空间分成区域(Region)的方式避免内存碎片问题 18 | - Eden, Survivor, Old区不再固定、在内存使用效率上来说更灵活 19 | - G1可以通过设置**预期停顿时间**(Pause Time)来控制垃圾收集时间避免应用雪崩现象 20 | - G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做 21 | - G1会在Young GC中使用、而CMS只能在O区使用 22 | 23 | # 二. GC 垃圾收集过程 24 | 25 | G1 垃圾收集过程主要分为4个阶段: 26 | 27 | - YGC(不同于CMS) 28 | - 并发标记周期 29 | - 混合模式 30 | - full GC (一般是G1出现问题时发生) 31 | 32 | ## 2.1 YGC 33 | 34 |
35 | 36 | 37 | Young GC主要是对Eden区进行GC,**它在Eden空间耗尽时会被触发**。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。 38 | 39 | 下面是一个Young GC的例子: 40 | 41 | ``` 42 | 23.430: [GC pause (young), 0.23094400 secs] 43 | ... 44 | [Eden: 1286M(1286M)->0B(1212M) 45 | Survivors: 78M->152M Heap: 1454M(4096M)->242M(4096M)] 46 | [Times: user=0.85 sys=0.05, real=0.23 secs] 47 | ``` 48 | 49 | 上面日志的内容解析: 50 | 51 | - Young GC实际占用230毫秒、其中GC线程占用850毫秒的CPU时间 52 | - E:内存占用从1286MB变成0、都被移出 53 | - S:从78M增长到了152M、说明从Eden移过来74M 54 | - Heap:占用从1454变成242M、说明这次Young GC一共释放了1212M内存空间 55 | 56 | 很多情况下,S区的对象会有部分晋升到Old区,另外如果S区已满、Eden存活的对象会直接晋升到Old区,这种情况下Old的空间就会涨 57 | 58 | 这时,我们需要考虑一个问题,如果仅仅GC 新生代对象,我们如何找到所有的根对象呢? 老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用。 59 | 60 |
61 | 62 | 63 | 如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,**卡表(Card Table)**。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为**卡**。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。 64 | 65 | ## 2.2 并发标记阶段 66 | 67 |
68 | 69 | G1并发标记周期可以分成几个阶段: 70 | 71 | - **初始标记阶段(Initial Marking)**: 标记从根节点直接可达的对象,**这个阶段会伴随一次新生代GC**,eden被清空,并且将存活对象移动到survivor中。 **它会产生全局停顿,应用程序必须在这个阶段停止运行**(Stop of the world)。 72 | - **根区域扫描**: 扫描survivor区,标记直接可达对象。 这个过程和应用程序可以并行执行,但根区域扫描不能和新生代GC同时进行。**如果恰好此刻新生代也在执行,那么需要等待根区域扫描结束以后才能进行,这样YGC的时间就会延长。** 73 | - **并发标记 (Concurrent Marking)**:和CMS一样,是从GC Roots开始对堆中的对象进行可达性分析,找出存活对象。耗时比较长,**是一个并发过程,可以和用户程序并发**。同时运行被新生代GC打断。 74 | - **重新标记/最终标记(Final Marking)**: 和CMS一样,重新标记会产生**应用程序停顿(Stop of th world)**,对并发过程中标记结果进行纠正。这个过程使用的是SATB(Snapshot-At-The-Begining)算法完成, 即G1在标记之初会为存活对象创建一个快照,这个快照有助于加速重新标记速度。 75 | - **筛选回收(Live Data Counting and Evacuation)**: 这个阶段会**引起停顿**,首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC回收时间指定回收计划。这个阶段还会更新记忆集(Remembered Set) 。 该阶段给出了需要被混合回收的区域并进行标记,在混合回收阶段需要这些信息。 76 | - **并发清理阶段**: 这里是识别并清理完全空闲的区域,它是**并发清理**,不会引起停顿。 77 | 78 | 79 | ### 1. 初始标记阶段 80 | 第一个阶段是初始标记阶段。这个阶段会暂停所有应用线程-部分原因是这个过程会执行一次YGC、下面是一个日志示例: 81 | 82 | ``` 83 | 50.541: [GC pause (young) (initial-mark), 0.27767100 secs] 84 | [Eden: 1220M(1220M)->0B(1220M) 85 | Survivors: 144M->144M Heap: 3242M(4096M)->2093M(4096M)] 86 | [Times: user=1.02 sys=0.04, real=0.28 secs] 87 | ``` 88 | 89 | 上面的日志表明发生了YGC、应用线程为此暂停了280毫秒,Eden区被清空(71MB从Young区移到了O区)。 90 | 91 | 日志里面initial-mark的字样表明后台的并发GC阶段开始了。因为初始标记阶段本身也是要暂停应用线程的,G1正好在YGC的过程中把这个事情也一起干了。为此带来的额外开销不是很大、增加了20%的CPU,暂停时间相应的略微变长了些。 92 | 93 | ### 2. 根区域扫描 94 | 接下来,G1开始扫描根区域、日志示例: 95 | 96 | ``` 97 | 50.819: [GC concurrent-root-region-scan-start] 98 | 51.408: [GC concurrent-root-region-scan-end, 0.5890230] 99 | ``` 100 | 101 | 一共花了580毫秒,这个过程没有暂停应用线程;是后台线程并行处理的。这个阶段不能被YGC所打断、因此后台线程有足够的CPU时间很关键。如果Young区空间恰好在Root扫描的时候满了、YGC必须等待root扫描之后才能进行。带来的影响是YGC暂停时间会相应的增加。这时的GC日志是这样的: 102 | 103 | ``` 104 | 350.994: [GC pause (young) 105 | 351.093: [GC concurrent-root-region-scan-end, 0.6100090] 106 | 351.093: [GC concurrent-mark-start],0.37559600 secs] 107 | ``` 108 | 109 | ### 3. 并发标记 110 | 在root扫描完成后,G1进入了一个并发标记阶段。这个阶段也是完全后台进行的;GC日志里面下面的信息代表这个阶段的开始和结束: 111 | 112 | ``` 113 | 2018-02-02T17:31:32.077+0800: 48.625: [GC concurrent-mark-start] 114 | 2018-02-02T17:31:32.180+0800: 48.728: [GC concurrent-mark-end, 0.1030567 secs] 115 | ``` 116 | 117 | 并发标记阶段是可以被打断的,比如这个过程中发生了YGC。 118 | 119 | ### 4. 重新标记 120 | 这两个阶段同样会暂停应用线程,引起全局停顿(Stop of the world),但时间很短。 121 | 122 | ``` 123 | [GC remark 2018-02-02T17:31:32.181+0800: 48.728: [Finalize Marking, 0.0064290 secs] 2018-02-02T17:31:32.187+0800: 48.735: [GC ref-proc, 0.0009395 secs] 2018-02-02T17:31:32.188+0800: 48.736: [Unloading, 0.1899474 secs], 0.1986697 secs] 124 | [Times: user=0.19 sys=0.20, real=0.20 secs] 125 | ``` 126 | 127 | ### 5. 筛选回收 128 | 重新标记后会进行独占清理,独占清理会重新计算各个区域的存活对象,以此得到各个区域的回收价值和成本,并更新优先列表。 129 | 130 | ``` 131 | 2018-02-02T17:31:32.381+0800: 48.928: [GC cleanup 139M->132M(1792M), 0.0035090 secs] 132 | [Times: user=0.02 sys=0.01, real=0.00 secs] 133 | ``` 134 | ### 6. 并发清理 135 | 并发执行,根据筛选回收阶段的得到的各个区域存活对象的数量,直接回收已经不包含存活对象的区域。 日志如下: 136 | 137 | ``` 138 | 2018-02-02T17:31:32.384+0800: 48.932: [GC concurrent-cleanup-start] 139 | 2018-02-02T17:31:32.384+0800: 48.932: [GC concurrent-cleanup-end, 0.0000717 secs] 140 | ``` 141 | 142 | ### 完整日志 143 | 144 | ``` 145 | [Eden: 859.0M(859.0M)->0.0B(828.0M) Survivors: 41.0M->72.0M Heap: 945.5M(1792.0M)->117.0M(1792.0M)] 146 | [Times: user=0.50 sys=0.11, real=0.32 secs] // YGC 147 | 2018-02-02T17:31:31.602+0800: 48.149: [GC pause (Metadata GC Threshold) (young) (initial-mark) // 初始标记 148 | Desired survivor size 59244544 bytes, new threshold 15 (max 15) 149 | - age 1: 32290680 bytes, 32290680 total 150 | - age 2: 7557464 bytes, 39848144 total 151 | - age 3: 54816 bytes, 39902960 total 152 | , 0.3284738 secs] 153 | 2018-02-02T17:31:31.931+0800: 48.478: [GC concurrent-root-region-scan-start] // 根区域扫描 154 | 2018-02-02T17:31:32.077+0800: 48.625: [GC concurrent-root-region-scan-end, 0.1465085 secs] 155 | 2018-02-02T17:31:32.077+0800: 48.625: [GC concurrent-mark-start] // 并发标记 156 | 2018-02-02T17:31:32.180+0800: 48.728: [GC concurrent-mark-end, 0.1030567 secs] 157 | 2018-02-02T17:31:32.181+0800: 48.728: [GC remark 2018-02-02T17:31:32.181+0800: 48.728: [Finalize Marking, 0.0064290 secs] 2018-02-02T17:31:32.187+0800: 48.735: [GC ref-proc, 0.0009395 secs] 2018-02-02T17:31:32.188+0800: 48.736: [Unloading, 0.1899474 secs], 0.1986697 secs] // 重新标记 158 | [Times: user=0.19 sys=0.20, real=0.20 secs] 159 | 2018-02-02T17:31:32.381+0800: 48.928: [GC cleanup 139M->132M(1792M), 0.0035090 secs] // 筛选回收 160 | [Times: user=0.02 sys=0.01, real=0.00 secs] 161 | 2018-02-02T17:31:32.384+0800: 48.932: [GC concurrent-cleanup-start] // 并发清理 162 | 2018-02-02T17:31:32.384+0800: 48.932: [GC concurrent-cleanup-end, 0.0000717 secs] 163 | 164 | ``` 165 | 166 | ## 2.3 混合回收 167 | 并发周期虽然有回收部分对象被回收,但是总体上回收比例相对很低。在并发标记周期以后已经知道哪些区域的垃圾比较多,因此在混合回收阶段主要是针对这些区域进行回收,优先回收垃圾比较高的区域。 这也是G1 垃圾回收器(Garbage First Garbage Collector)名字的由来。 168 | 169 | 这这个阶段会即会执行正常的年轻代GC, 也会选取一些被标记的老年代区域进行回收: 170 | 171 |
172 | 173 | ``` 174 | 2018-02-03T03:27:17.380+0800: 35793.927: [GC pause (G1 Evacuation Pause) (mixed) 175 | Desired survivor size 59244544 bytes, new threshold 15 (max 15) 176 | - age 1: 1996696 bytes, 1996696 total 177 | - age 2: 459960 bytes, 2456656 total 178 | - age 3: 400072 bytes, 2856728 total 179 | - age 4: 701968 bytes, 3558696 total 180 | - age 5: 24731128 bytes, 28289824 total 181 | , 0.4065425 secs] 182 | [Parallel Time: 391.1 ms, GC Workers: 10] 183 | [Eden: 869.0M(869.0M)->0.0B(869.0M) Survivors: 31.0M->31.0M Heap: 1369.4M(1792.0M)->447.2M(1792.0M)] 184 | [Times: user=0.66 sys=0.14, real=0.40 secs] 185 | ``` 186 | 187 | `混合GC会执行多次`,直到回收了足够多的内存空间,然后会触发一次新生代的 YGC。 可能会进入新一轮的处理过程: 188 | 189 |
190 | 191 | ## 2.4 FULL GC 192 | 在必要的时候需要执行一次Full GC, 因为垃圾回收线程和应用线程并发执行,难免会遇到内存清理过程出现内存不足的时候。如果出现这种情况,G1也会转入一个Full GC 进行回收。 193 | 194 | ``` 195 | [Times: user=2.58 sys=0.13, real=1.40 secs] 196 | 2018-02-03T16:22:12.694+0800: 82289.242: [Full GC (Allocation Failure) 1383M->636M(1792M), 3.9350032 secs] 197 | ``` 198 | 199 | 200 | # 参考 201 | 202 | - 《深入理解java虚拟机》 203 | - 《实战java虚拟机》 204 | - http://blog.jobbole.com/109170/ 205 | - http://ifeve.com/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3g1%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8/ 206 | 207 | -------------------------------------------------------------------------------- /java/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/java/README.md -------------------------------------------------------------------------------- /java/io.md: -------------------------------------------------------------------------------- 1 | # Java IO/NIO 2 | 3 | # 一. linux 底层实现 4 | ## 1.1 kernel IO 5 | 用户进程进行I/O操作的时候实际上交给了kernel进行执行,kernel执行的I/O操作可以分为两个阶段: 6 | 7 |
8 | 9 | 10 | - **准备阶段**:在执行I/O操作的时候需要等待I/O是否就绪,因为此刻IO设备在忙状态。以网络IO为例,在读取远程数据时,需要阻塞等待远程将数据发送过来,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。 11 | 12 |
13 | 14 | - **用户进程空间和内核空间的数据拷贝** : 当等到数据准备好了,kernel就会将数据从从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。 15 | 16 | 17 | 准备阶段时,CPU内容发送指令给IO通道,让IO通道负责执行IO的读写操作。CPU不用一直阻塞可以继续执行别的逻辑,但此刻IO线程会挂起,等待IO通道读写操作完成通知。 IO通道将操作交给DMA,DMA直接连接设备控制器负责将设备控制器中的数据直接读入到内核内存中。读完成后通知通道告知IO处理完成,IO通道发送中断给CPU,原内核IO线程醒来继续执行后续工作 18 | 19 | 20 | ## 1.1 I/O 模型 21 | 根据IO对kernel IO操作两个阶段的感知能力可以分为一下四种IO模型: 22 | 23 | - **阻塞(Blocking)**:阻塞等待IO就绪,这期间用户线程不可以做其他事情。 24 | - **非阻塞(Non-blocking)**:轮询感知IO就绪,用户线程可以做其他事情。 25 | - **同步(Synchronous)**:同步等待读IO读写结果。 26 | - **异步(Asynchronous)**:异步等待kernel通知IO读写结果。 27 | 28 | **IO阻塞和非阻塞主要关注IO的就绪状态的感知方式,同步和异步关注IO读写操作的结果获取方式**。同步是指函数完成之前会一直等待;阻塞 是指系统调用的时候进程会被设置为Sleep状态直到等待的事件发生(比如有新的数据)。 29 | 30 | ### 阻塞和非阻塞 31 | 32 | 阻塞和非阻塞是一种**调用机制**,用来描述进程处理调用的方式。在IO中两者的**区别主要体现在I/O未准备好时,用户线程是否可以做其他事情**。比如网络读操作,根据是否需要等待kernel数据准备好。 33 | 34 | 阻塞是等待某个事件的就绪/发生,当前线程会被**挂起**,一直处于等待消息通知,不能执行其他业务。**阻塞通信意味着通信方法在尝试访问套接字或者读写数据时阻塞了对套接字的访问**。以网络读操作为例,用户线程在socket中调用recv函数时,如果缓冲区中没有数据,则需要一直阻塞等待服务端发来的数据,这时候线程会挂起等待。 35 | 36 | 非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。非阻塞IO是用户线程不会一直阻塞待待IO就绪,通过不断轮询的方式来查看就绪状态。。 37 | 38 | 39 | ### 同步和异步 40 | 同步和异步是一种**通信机制**,涉及到调用方和被调用方,**关注的是IO操作结果的获知方式,主要区别在于IO结果未返回时用户线程是否可以做其他事情**: 41 | 42 | - **同步**是调用方需要保持等待直到IO操作完成,进而通过返回获得结果; 43 | - **异步**则调用方在IO操作的执行过程中不需要保持等待,而是在操作完成后被动的接受(通过消息或回调)被调用方推送的结果。 44 | 45 | 46 | 以下是同步和异步定义: 47 | 48 | >**A synchronous I/O** operation causes the requesting process to be blocked until that I/O operation completes; 49 | >**An asynchronous I/O** operation does not cause the requesting process to be blocked; 50 | 51 | 同步和异步的区别也在于在进行整个IO操作的时候会用户进程**是否会阻塞等待结果**,linux中IO模型中blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。 52 | 53 | ## 1.2 linux I/O 模型 54 | 55 | linux根据用户进程对这个两个阶段的感知方式分为5中I/O模型: 56 | 57 | - 阻塞I/O(bloking IO) 58 | - 非阻塞I/O(non-blocking IO) 59 | - 多路复用IO(multiplexing IO) 60 | - 信号驱动式IO(signal-driven IO) 61 | - 异步IO(asynchronous IO) 62 | 63 | 64 | ### 1) 阻塞IO(blocking IO) 65 | 默认情况下所有的socket都是blocking 66 | 67 | 68 |
69 | 70 | 当用户进程调用了recvfrom这个系统调用,就**阻塞等待结果**。kernel负责完成IO操作,完成后返回给用户。用户进程需要阻塞等待kernel完成两个阶段操作:**准备数据(wait for data)**、 **数据拷贝到用户进程空间(copy data from kenel to user)**。 阻塞IO是同步阻塞IO, 准备数据阶段会阻塞并同步等待I/O结果。在准备阶段和数据拷贝阶段中,用户线程都会被阻塞。 71 | 72 | 使用linux中进行网络编程时,一般都从listen()、send()、recv() 等接口开始,这些接口都是阻塞型。使用这些接口可以方便构建服务器/客户机模型。下面是一个简单地“一问一答”服务器。 整体流程如下: 73 | 74 |
75 | 76 | 77 | **服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理**,如果这个连接不做任何事情会造成不必要的线程开销。通常会对它的线程模型进行优化,后端通过一个线程池来处理多个客户端的请求接入。通过“线程池”减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务,提高系统性能。 78 | 79 |
80 | 81 | 82 | java 的BIO就是采用这种模式实现的。 83 | 84 | ### 2)非阻塞IO(non-blocking IO) 85 | 86 |
87 | 88 | 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它**并不会block用户进程,而是立刻返回一个error**。整体流程是: 89 | 90 | 1. 用户线程轮询查看kernel是否准备好数据(datagram ready)。这个过程kernel主要处于wait for data阶段。 91 | 2. 当用户调用read, kernel已准备好数据,则执行数据拷贝操作,**用户线程阻塞直到数据读取完成**。kernel读取数据完成后,返回给用户数据。 92 | 93 | 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。 94 | 95 | **在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有,非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。** 96 | 97 | 98 | **non-blocking IO在执行recvfrom这个系统调用的时候,如果kernel的数据没有准备好,这时候不会block进程。但是当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内进程是被block的**,因此属于同步IO。 99 | 100 | 101 | 优点: 102 | 103 | 1. 用户进程不会被阻塞,可以在期间做一些别的事情。 104 | 105 | 缺点: 106 | 107 | 1. 用户线程不知道什么时候完成,需要不断的轮询查看I/O操作结果。看是否已经读取完成,增加了用户使用的复杂度。 108 | 2. 在数据copy部分用户进程还是需要阻塞。 109 | 110 | ### 3)多路复用IO(IO multiplexing) 111 | IO multiplexing,也称这种IO方式为**事件驱动IO(event driven IO)**。非阻塞IO(non-blocking IO)模式需要用户自己去轮询查看是否数据准备好,如果准备好则阻塞调用kernel进行copy。多路复用IO就是解决这种轮询问题,linux内部提供了select/poll/epoll来完成IO复用。 112 | 113 | select/poll/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/poll/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图: 114 | 115 |
116 | 117 | #### select 118 | 119 | select的调用过程如下所示: 120 | 121 |
122 | 123 | select负责管理多个FD文件描述符,kernel就会**轮询**检查所有select负责的fd,看是否有一个FD的数据已准备好。select会返回kernel数据准备就绪的FD, FD调用read操作让kernel完成数据的拷贝。 select解决了非阻塞状态下用户进程需要自己轮询的问题,同时可以用一个线程管理多个用户进程的读写操作。 124 | 125 | 126 | select的缺点: 127 | 128 | - 单个进程能够监视的文件描述符的**数量存在最大限制**,**通常是1024**,当然可以更改数量。 129 | - 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。 130 | - 内核/用户空间内存拷贝问题。每次调用select,**都需要把fd集合从用户态拷贝到内核态**,这个开销在fd(客户端套接字)很多时会很大。 131 | 132 | #### poll 133 | **poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间**。然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。 134 | 135 | 136 | **它没有最大连接数的限制,原因是它是基于链表来存储的**,但是同样有一个缺点: 137 | 138 | - 1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。 139 | - 2)poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。 140 | 141 | #### epoll 142 | 在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。**epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中**,这样在用户空间和内核空间的copy只需一次。 143 | 144 | 145 | 基本原理:epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,**epoll使用“事件”的就绪通知方式**,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似**callback的回调机制来激活该fd**,epoll_wait便可以收到通知。 146 | 147 | 148 | epoll的优点: 149 | 150 | 1. **没有最大并发连接的限制**,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。 151 | 2. **效率提升,不是轮询的方式,不会随着FD数目的增加效率下降**。只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。 152 | 3. **内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递**;即epoll使用mmap减少复制开销。 153 | 154 | ### 4)信号驱动式IO 155 | 156 | 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它**并不会block用户进程,而是立刻返回一个, 用户进程可以执行自己的程序不用轮询结果。kernel在IO就绪时会发送一个信号给用户进程告知IO准备好,可以执行后续操作**。整体流程是: 157 | 158 | 1. 用户线程调用read, kernel执行IO准备阶段,这个过程不阻塞用户线程。 159 | 2. 数据就绪后内核给用户线程发signal。 160 | 2. 当用户调用read, kernel执行数据拷贝操作,**用户线程阻塞直到数据读取完成**。kernel读取数据完成后,返回给用户数据。 161 | 162 | 应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。 163 | 164 | 相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。这样避免了用户线程进行不断轮询的操作。 165 | 166 |
167 | 168 | 169 | ### 5)异步IO(Asynchronous I/O) 170 | 171 |
172 | 173 | 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以**不会对用户进程产生任何block**。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,**当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了**。 174 | 175 | 和同步方式不同,kernel的数据准备好以后不需要用户进程再次发送拷贝指令并阻塞等待kernel拷贝完成。 176 | 177 | 178 | # Java IO 分类 179 | - **Java BIO**: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 180 | 181 | - **Java NIO** : 同步非阻塞,服务器实现模式为一个请求一个线程,即当一个连接创建后,不需要对应一个线程,这个连接会被注册到**多路复用器**上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。 182 | 183 | - **Java AIO(NIO.2)** : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。 184 | 185 | ## 名词解释 186 | - **同步**:指的是用户进程触发IO操作需要等待或者轮询的去查看IO操作执行完成才能执行其他操作.这种方式性能比较差,只有一些对数据安全性要求比较高的场景中才会使用. 187 | - **异步**:异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知) 188 | - **阻塞**:所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止 189 | - **非阻塞**:非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 190 | 191 | ## Java BIO 192 | 193 | 在JDK 1.4推出Java NIO之前,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),**这种一请求一应答的通信模型简化了上层的应用开发,但是在性能和可靠性方面却存在着巨大的瓶颈**。当并发访问量增大、响应时间延迟增大之后,采用Java BIO开发的服务端软件只有通过硬件的不断扩容来满足高并发和低时延,它极大地增加了企业的成本,并且随着集群规模的不断膨胀,系统的可维护性也面临巨大的挑战,只能通过采购性能更高的硬件服务器来解决问题,这会导致恶性循环,传统采用BIO的Java Web服务器如下所示(典型的如Tomcat的BIO模式): 194 | 195 |
196 | 197 | 198 | 采用该线程模型的服务器调度特点如下: 199 | 200 | 1. 服务端监听线程Acceptor负责客户端连接的接入,每当有新的客户端接入,就会创建一个新的I/O线程负责处理Socket 201 | 2. 客户端请求消息的读取和应答的发送,都有I/O线程负责 202 | 3. 除了I/O读写操作,默认情况下业务的逻辑处理,例如DB操作等,也都在I/O线程处理 203 | 4. I/O操作采用同步阻塞操作,读写没有完成,I/O线程会同步阻塞 204 | 205 | BIO线程模型主要存在如下三个问题: 206 | 207 | 1. **性能问题**:一连接一线程模型导致服务端的并发接入数和系统吞吐量受到极大限制 208 | 2. **可靠性问题**:由于I/O操作采用同步阻塞模式,当网络拥塞或者通信对端处理缓慢会导致I/O线程被挂住,阻塞时间无法预测 209 | 3. **可维护性问题**:I/O线程数无法有效控制、资源无法有效共享(多线程并发问题),系统可维护性差 210 | 211 | ## BIO、NIO、AIO适用场景分析 212 | - BIO方式适用于**连接数目比较小且固定的架构**,这种方式对服务器**资源要求比较高**,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 213 | - NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 214 | - AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。 215 | 216 | ### Java NIO和IO的主要区别 217 | 1. 面向流与面向缓冲. 218 | 219 | Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。 220 | 221 | 2. 阻塞与非阻塞IO 222 | 223 | Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。 224 | 225 | 3. 选择器(Selectors) 226 | 227 | Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。 228 | 229 | 230 | ## 参考资料 231 | 232 | 1. http://bbym010.iteye.com/blog/2100868 233 | 2. http://developer.51cto.com/art/201112/307463.htm 234 | 3. http://ifeve.com/java-nio-vs-io/ 235 | 4. [5种网络IO模型(有图,很清楚)](https://www.cnblogs.com/findumars/p/6361627.html) 236 | 5. [gRPC线程模型分析](http://www.uml.org.cn/zjjs/201711241.asp) | InfoQ 237 | 6. [](https://blog.csdn.net/qq546770908/article/details/53082870) 238 | 7. [IO多路复用之select总结](http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html) 239 | 8. [IO多路复用之select、poll、epoll详解](https://www.cnblogs.com/jeakeven/p/5435916.html) 240 | 9. [透彻 Linux (Unix) 五种 IO 模型](https://blog.csdn.net/a627088424/article/details/54582360) 241 | 10. [设备管理 | I/O软件](https://zhuanlan.zhihu.com/p/32400397) 242 | 11. [磁盘I/O那些事](https://tech.meituan.com/about-desk-io.html) | 美团 243 | 12. [《Linux 设备驱动 Edition 3》](https://www.kancloud.cn/kancloud/ldd3/61083) 244 | 13. [磁盘及网络IO工作方式解析](https://segmentfault.com/a/1190000007692223) 245 | 14. [Socket](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Socket.md) 246 | 247 | 248 | 个人公众号(欢迎关注):
249 | ![](/assets/weix_gongzhonghao.jpg) 250 | 251 | -------------------------------------------------------------------------------- /java/object.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | ## 面向对象 4 | 下面列出了面向对象软件开发的优点: 5 | - (1) 代码开发模块化,更易维护和修改。 6 | - (2) 代码复用。 7 | - (3) 增强代码的可靠性和灵活性。 8 | - (4) 增加代码的可理解性。 9 | 面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象 10 | 11 | ## Java面向对象的三个特征与含义 12 | 一、**继承**: 13 | 14 | 1. 概念:**继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力.** 15 | 16 | 2. 好处:提高代码的复用,缩短开发周期。 17 | 18 | 二、**多态**: 19 | 20 | 1. 概念:多态(Polymorphism)按字面的意思就是“**多种状态,即同一个实体同时具有多种形式**"。一般表现形式是程序在运行的过程中,同一种类型在不同的条件下表现不同的结果。多态也称为动态绑定,一般是在运行时刻才能确定方法的具体执行对象,这个过程也称为动态委派。 21 | 22 | 2. 好处: 23 | 24 | - 将接口和实现分开,改善代码的组织结构和可读性,还能创建可拓展的程序。 25 | - 消除类型之间的耦合关系。允许将多个类型视为同一个类型。 26 | - 一个多态方法的调用允许有多种表现形式 27 | 28 | 三、**封装**: 29 | 30 | 1. 概念:**就是把对象的属性和行为(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节**。 31 | 32 | 2. 好处: 33 | 34 | - 隐藏信息,实现细节。让客户端程序员无法触及他们不应该触及的部分。 35 | - 允许可设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员。 36 | 37 | 38 | ## Overload 和 Override 39 | Overload 是重载的意思,Override 是覆盖的意思,也就是重写。 40 | **重载 Overload** 表示**同一个类**中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。 41 | 42 | **重写 Override** 表示**子类**中的方法可以与**父类**中的某个**方法的名称和参数完全相同**,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。**子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常**,因为子类可以解决父类的一些问题,不能比父类有更多的问题。**子类方法的访问权限只能比父类的更大,不能更小**。如果**父类的方法是private类型,那么,子类则不存在覆盖的限制**,相当于子类中增加了一个全新的方法。 43 | 44 | 至于 Overloaded 的方法是否可以改变返回值的类型这个问题,要看你倒底想问什么呢?这个题目很模糊。如果几个 Overloaded 的方法的参数列表不一样,它们的返回者类型当然也可以不一样。但我估计你想问的问题是:如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载 Overload。这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用 map.remove(key) 方法时,虽然 remove 方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,Java 就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。 45 | 46 | Override 可以翻译为覆盖,从字面就可以知道,它是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。**在覆盖要注意以下的几点**: 47 | 48 | 1. **覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;** 49 | 2. **覆盖的方法的返回值必须和被覆盖的方法的返回一致,或者是其子类(协变返回类型);** 50 | 3. **覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;** 51 | 4. **被覆盖的方法不能为 private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。** 52 | 53 | Overload 对我们来说可能比较熟悉,可以翻译为重载,它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM 就会根据不同的参数样式,来选择合适的方法执行。在使用**重载要注意**以下的几点: 54 | 55 | 1. **在使用重载时只能通过不同的参数样式。**例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是 fun(int,float),但是不能 fun(int,int)); 56 | 2. **不能通过访问权限、返回类型、抛出的异常进行重载**; 57 | 3. **方法的异常类型和数目不会对重载造成影响**; 58 | 59 | 60 | ## 接口和抽象类的区别 61 | Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于: 62 | 63 | 1. 接口中所有的**方法**隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。 64 | 2. 类可以实现很多个接口,但是只能**继承**一个抽象类 65 | 3. 类如果要**实现**一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。 66 | 4. 抽象类可以在不提供接口方法实现的情况下实现接口。 67 | 5. Java 接口中声明的**变量**默认都是 final 的。抽象类可以包含非 final 的变量。 68 | 6. Java 接口中的**成员函数**默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public 。 69 | 7. 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 main 方法的话是可以被调用的 70 | 71 | ## 接口 72 | 接口很重要,为了说明情况,这里稍微啰嗦点: 73 | 74 | - (1) 接口用于描述系统对外提供的所有服务,因此接口中的成员常量和方法**都必须是公开(public)类型**的,确保外部使用者能访问它们; 75 | - (2) 接口仅仅描述系统能做什么,但不指明如何去做,所以接口中的方法**都是抽象(abstract)方法**; 76 | - (3) 接口不涉及和任何具体实例相关的细节,因此接口没有构造方法,不能被实例化,没有实例变量,**只有静态(static)变量**; 77 | - (4) 接口的中的变量是所有实现类**共有的**,既然共有,肯定是不变的东西,因为变化的东西也不能够算共有。所以**变量是不可变(final)类型,也就是常量了**。 78 | - (5) 接口中不可以定义变量.如果接口可以定义变量,但是接口中的方法又都是抽象的,在接口中无法通过行为来修改属性。有的人会说了,没有关系,可以通过 实现接口的对象的行为来修改接口中的属性。这当然没有问题,但是考虑这样的情况。如果接口 A 中有一个public 访问权限的静态变量 a。按照 Java 的语义,我们可以不通过实现接口的对象来访问变量 a,通过 A.a = xxx; 就可以改变接口中的变量 a 的值了。正如抽象类中是可以这样做的,**那么实现接口 A 的所有对象也都会自动拥有这一改变后的 a 的值了,也就是说一个地方改变了 a,所有这些对象中 a 的值也都跟着变了**。这和抽象类有什么区别呢,怎么体现接口更高的抽象级别呢,怎么体现接口提供的统一的协议呢,那还要接口这种抽象来做什么呢?所以接口中 不能出现变量,如果有变量,就和接口提供的统一的抽象这种思想是抵触的。所以接口中的属性必然是常量,只能读不能改,这样才能为实现接口的对象提供一个统 一的属性。 79 | 通俗的讲,你认为是要变化的东西,就放在你自己的实现中,不能放在接口中去,接口只是对一类事物的属性和行为更高层次的抽象。对修改关闭,对扩展(不同的实现 implements)开放,接口是**对开闭原则**的一种体现。 80 | 所以:**接口的方法默认是 public abstract**; 81 | 82 | 接口中不可以定义变量即只能定义常量(加上final修饰就会变成常量)。**所以接口的属性默认是 public static final 常量,且必须赋初值**。 83 | **注意**:final 和 abstract 不能同时出现。 84 | 85 | ## 两个对象值相同(x.equals(y) == true),但却可有不同的 hash code,这句话对不对? 86 | 答:不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hash code)应当相同。 87 | Java 对于 eqauls 方法和 hashCode 方法是这样规定的: 88 | 89 | - **(1)如果两个对象相同(equals 方法返回 true ),那么它们的 hashCode 值一定要相同**; 90 | - **(2)如果两个对象的 hashCode 相同,它们并不一定相同**。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。 91 | - **(3)如果对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数**. 92 | 93 | 补充:关于 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 方法的**诀窍**包括: 94 | 95 | - **使用 == 操作符检查“参数是否为这个对象的引用”**; 96 | - **使用 instanceof 操作符检查“参数是否为正确的类型”**; 97 | - **对于类中的关键属性,检查参数传入对象的属性是否与之相匹配**; 98 | - **编写完 equals 方法后,问自己它是否满足对称性、传递性、一致性**; 99 | - **重写 equals 时总是要重写 hashCode**; 100 | - **不要将 equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉 @Override 注解**。 101 | 102 | ## 什么是 AOP 和 OOP,IOC 和 DI 103 | 1)面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。AOP 是 OOP 的延续,是 Aspect Oriented Programming 的缩写,意思是面向方面编程。 将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。AOP 就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中 104 | 105 | 2)控制反转 IOC(Inversion of Control) 控制指的就是程序相关类之间的依赖关系.传统观念设计中, 106 | 通常由调用者来创建被调用者的实例, **在 Spring 里,创建被调用者的工作不再由调用者来完成,而是由 Spring 容器完成,依赖关系被反转了,称为控制反转,目的是为了获得更好的扩展性和良好的可维护性**。依赖注入(Dependency injection)创建被调用者的工作由 Spring 容器完成,然后注入调用者,因此也称依赖注入。控制反转和依赖注入是**同一个概念**。 107 | 108 | 109 | 个人公众号(欢迎关注):
110 | ![](/assets/weix_gongzhonghao.jpg) 111 | 112 | 113 | -------------------------------------------------------------------------------- /java/thread.md: -------------------------------------------------------------------------------- 1 | ## 什么叫线程安全?举例说明 2 | 多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。 3 | 比如无状态对象一定是线程安全的。 4 | 5 | ## 进程和线程的区别 6 | 调度: 线程是调度的基本单位,进程是拥有资源的基本单位。同一进程的中线程的切换不会引起进程的切换,不同进程中进行线程切换会引起进程的切换。 7 | 8 | 拥有资源:进程是拥有资源的基本单位,线程除了自身的栈外一般不拥有资源。而是和其他线程共享同一进程中的资源。 9 | 10 | 系统开销:由于创建进程或者撤销进程时,系统都要分配和回收资源,如内存空间,I/O设备等,操作系统所付出的开销远大于创建或撤销进程时的开销。 11 | 12 | ## volatile的理解 13 | **Volatile自身特性**: 14 | 1. Volatile 是轻量级的synchronized,它在多处理器开发过程中保证了共享变量的“**可见性**”,可见性是指当一个线程的某个共享变量发生改变时,另一个线程能够读取到这个修改的值。Voaltile变量修饰的变量在进行写操作时在多核处理器下首先将当前处理器缓存行的数据写回到系统内存中。为了保证一致性,其他处理器嗅探到总线上传播的数据,发现数据被修改了使自己缓存地址的数据无效。 15 | 2. Volatile 可以**禁止重排序**, 16 | 3. Volatile 能保持单个简单volatile变量的读/写操作的具有原子性。但不能保证自增自减的**原子性**。 17 | 18 | 从**内存语义**来讲: 19 | 20 | - volatile变量的写-读与锁的释放-获取具有相同语义,volatile的写与锁的释放有相同的内存语义,volatile读与锁的获取具有相同语义。 21 | - 线程A写一个volatile变量,实质上是线程A向接下来要读这个volatile变量的某个线程发出消息 22 | - 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的消息。 23 | - 线程A写volatile变量,随后线程B读这个变量,这个过程实质上线程A通过内存向B发送消息。 24 | 25 | 内存语义的实现,也是禁止重排序特性: 26 | 为了实现volatile内存语义,JMM限制了对volatile重排序做了限制: 27 | 28 | 1. 当第二个操作是volatile写时,不管第一个操作时什么,都不能重排序。 29 | 2. 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。 30 | 3. 当第一个操作是volatile写,第二个操作是volatile读时,不重排序。 31 | 32 | 为了实现volatile的内存语义,编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。JMM采取保守策略: 33 | 34 | 1. 在每个volatile写操作前面插入一个StoreStore屏障 35 | 2. 在每个volatile写操作后面插入一个StoreLoad屏障 36 | 3. 在每个volatile读操作后面插入一个LoadLoad屏障 37 | 4. 在每个volatile读操作后面插入一个LoadStore屏障 38 | 39 | 具体参考《java并发编程的艺术》 40 | 41 | ## 原子性实现机制 42 | 处理器提供总线锁定和缓存锁定两种方式来保证复杂内存操作的原子性。 43 | 44 | - 总线型:就是使用处理器提供一个LOCK信号,当一个处理器在总线传输信号时,其他处理器的请求将被阻塞住,那么该处理独占内存。所以总线锁定开销大。 45 | 46 | - 缓存锁定:内存区域如果被缓存在缓存行中,且在在lock期间被锁定,当它执行锁操作写回内存时,处理器总线不在锁定而是通过修改内部的内存地址并使用缓存一致性制阻止同时修改保证操作的原子性。缓存一致性进制两个以上的处理器同时修改内存区域数据,其他处理器回写被锁定并且使其缓存行无效。 47 | 48 | ## Java原子性操作实现原理 49 | 使用循环CAS实现原子性操作,CAS是在操作期间先比较旧值,如果旧值没有发生改变,才交换成新值,发生了变化则不交换。这种方式会产生以下几种问题: 50 | 1. ABA问题,通过加版本号解决; 51 | 2. 循环时间过长开销大,一般采用自旋方式实现; 52 | 3. 只能保证一个共享变量的原子操作。 53 | 54 | ## Java内存模型 55 | Java内存模型控制线程之间的通信,决定了一个线程对共享变量的写入何时对另一个线程可见。它属于语言级的内存模型,它确保在不同编译器和不同的处理平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。JMM的核心目标是找到一个好的平衡点,一方面是为程序员提供足够强的内存可见性保证(提供happens-before规则),另一方面对编译器和处理器的限制尽可能地放松(只要不改变程序结果,怎么优化都可以) 56 | 57 | 1) **可见性保证** 58 | 59 | 为了提供内存可见性保证,JMM向程序员保证了以下hapens-before规则: 60 | 61 | 1. **程序顺序规则**:一个线程的每个操作happen-before与该线程的任意后续操作。 62 | 2. **监视器锁规则**:一个锁的解锁,happens-before于随后这个锁的加锁。 63 | 3. **Volatile变量规则**:对一个volatile域的写,happens-before于任意后续这个域的读。 64 | 4. **传递性**, 如果A happens-before B, 且B happens-before C 那么A happens-before C 65 | 5. **线程启动规则**:如果线程A执行操作ThreadB.start().那么线程A中的任意操作happens-before与线程B中的任意操作。 66 | 6. **线程结束规则**: 线程中的任何操作都必须在其线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false. 67 | 7. **中断规则**:当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptedException,或者调用isInterrupted和interrupted) 68 | 8. **终结器规则**: 对象的构造函数必须在启动该对象的终结器之前执行完成。 69 | 70 | **2) 禁止重排序** 71 | 72 | 为了保证内存可见性,java编辑器在生成指令序列的适当位置插入内存屏障指令来禁止特定类型的处理器重排序。 73 | 74 | 重排序:编译器和处理器为了优化程序性能对指令进行重新排序的一种手段。 75 | 76 | - 1) 编译器优化的重排序: 编译器在不改变单线程程序语义的前提下可以重新安排语句顺序。 77 | - 2) 指令级并行的重排序.现代处理器采用指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变对应指令的执行顺序。 78 | - 3) 内存系统重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能在乱序执行。 79 | 80 | ## Final域的内存语义 81 | 对于final域编译器和处理器要遵守两个重排序规则: 82 | 83 | 1. 在构造器函数内对final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。(保证了对象引用为任何线程可见之前,对象的final域已经被正确初始化过) 84 | 2. 初次读一个包含final域的对象引用,与随后初次读这个final域这两个操作不能重排序。 85 | 86 | 为何保证其内存语义:可以为java程序员提供安全保证,只要对象是正确构造的,那么不需要使用同步就可以保证线程都能看到这个fianal域在构造函数中被初始化之后的值。 87 | 88 | ## 避免死锁的常见方法: 89 | - 1)避免一个线程同时获取多个锁 90 | - 2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源 91 | - 3)尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。 92 | - 4)对数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。 93 | 94 | ## 死锁的必要条件?怎么克服? 95 | 答:产生死锁的四个必要条件: 96 | 97 | - **互斥条件**:一个资源每次只能被一个进程使用。 98 | - **请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 99 | - **不剥夺条件**:进程已获得的资源,在末使用完之前,不能强行剥夺。 100 | - **循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系。 101 | 102 | 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。 103 | 104 | 死锁的解决方法: 105 | 106 | - 撤消陷于死锁的全部进程; 107 | - 逐个撤消陷于死锁的进程,直到死锁不存在; 108 | - 从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失。 109 | - 从另外一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态 110 | 111 | ## CountDownLatch(闭锁) 与CyclicBarrier(栅栏)的区别 112 | CountDownLatch: **允许一个或多个线程等待其他线程完成操作**. 113 | 114 | CyclicBarrier:**让一组线程到达一个屏障(同步点)被阻塞,直到最后一个线程到达屏障时,所有被屏障拦截的线程才会往下执行**。 115 | 116 | 1. 闭锁用于等待事件、栅栏是等待线程. 117 | 2. 闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数。 118 | 3. CountDownLatch是一次性的,CyclicBarrier可以重用。 119 | 4. CountDownLatch一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行。CyclicBarrier是N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。 120 | 121 | CountDownLatch 是**计数器**, 线程完成一个就记一个,就像报数一样, 只不过是递减的. 122 | 123 | 而CyclicBarrier更像一个**水闸**, 线程执行就像水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流. 124 | 125 | ## execute 和submit的区别 126 | Execute()用于提交不需要返回值得任务,submit()用于提交需要返回值的任务,发挥Future类型的对象。 127 | 128 | ## Shutdown和shutdownNow的区别 129 | 它们的原理都是遍历线程池中的工作线程,然后逐个调用线程的Internet方法来中断线程,所以无法响应中断的任务可能永远无法终止。 130 | 131 | ShutdownNow首先将线程池的状态设置成STOP, 然后尝试停止所有正在执行或暂停的任务,并返回等待执行任务的列表。而shutdown只是将线程池设置成SHUTDOWN状态,然后中断没有正在执行任务的线程。 132 | 133 | ## ThreadLocal的设计理念与作用。 134 | ThreadLocal并不是一个Thread,而是Thread的局部变量, 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本 135 | 136 | http://blog.csdn.net/lufeng20/article/details/24314381 137 | ## 同步 138 | 同步就是协同步调,按预定的先后次序进行运行。 139 | 140 | ## sleep() 和 wait() 区别 141 | 答:sleep()方法是**线程类(Thread)的静态方法**,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用 sleep **不会释放对象锁**。 142 | 143 | wait() 是 **Object 类的方法**,对此对象调用 wait()方法导致本线程放弃对象锁(线程暂停执行),**释放资源并**进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。 144 | 145 | ## sleep() 和 yield() 区别 146 | 147 | - ① sleep() 方法给其他线程运行机会时**不考虑线程的优先级**,因此会给低优先级的线程以运行的机会;yield() 方法**只会给相同优先级或更高优先级**的线程以运行的机会; 148 | - ② 线程执行 sleep() 方法后转入**阻塞**(blocked)状态,而执行 yield() 方法后转入**就绪(ready)**状态; 149 | - ③ sleep() 方法声明抛出InterruptedException,而 yield() 方法没有声明任何异常; 150 | - ④ sleep() 方法比 yield() 方法(跟操作系统相关)具有更好的可移植性。 151 | 152 | ## 线程同步相关的方法。 153 | - **wait()**:使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; 154 | - **sleep()**:使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常; 155 | - **notify()**:唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; 156 | - **notityAll()**:唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争; 157 | 158 | ## 什么是线程池(thread pool) 159 | 在面向对象编程中,**创建和销毁对象是很费时间的**,因为创建一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是**尽可能减少创建和销毁对象的次数**,特别是一些很耗资源的对象创建和销毁,这就是"**池化资源**"技术产生的原因。 160 | 161 | 线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 162 | 163 | http://www.cnblogs.com/dolphin0520/p/3932921.html 164 | ## ConcurrentHashMap实现原理 165 | 166 | ConcurrentHashMap和Hashtable主要区别就是围绕着锁的粒度以及如何锁。 167 | Hashtabl在竞争激烈的环境下表现效率低下的原因是一把锁锁住整张表,导致所有线程同时竞争一个锁。ConcurrentHashMap采用**分段锁**,每把锁锁住容器中的一个Segment。那么多线程访问容器里不同的Segment的数据时线程就不会存在竞争,从而有效提高并发访问效率。首先是将数据分层多个Segment存储,并为每个Segment分配一把锁,当一个线程范围其中一段数据时,其他线程可以访问其他段的数据。 168 | 169 | 数据结构: 170 | 171 | ConcurrentHashMap内部是有**Segment**数组和**HashEntry**数组组成。一个ConcurrentHashMap里包含一个Segment数组,而Segment的结构和HashMap一样,里面是由一个数组和链表结构组成,所以一个Segment内部包含一个HashEntry数组。每个HashEntry是一个链表结构,对于HashEntry数组进行修改时首先需要获取与它对应的Segment锁。默认情况下有16个Segment 172 | 173 | Segment的定位: 174 | 175 | 使用Wang/Jenkins hash变种算法对元素的hashCode进行一次再散列,目的是为了减少散列冲突。 176 | 177 | ConcurrentHashMap的操作: 178 | 179 | - get 180 | 181 | get操作实现非常简单高效。先经过一次**再散列**,然后用这个散列值通过散列运算定位到Segment,**再通过散列算法定位到元素**。get之所以高效是因为整个get过程不需要加锁,除非读到空值才会加锁重读。实现该技术的技术保证是保证**HashEntry是不可变的**。 182 | 183 | 第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count 变量,通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。接下来就是根据hash和key对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。 184 | 185 | 对hash链进行遍历**不需要加锁的原因在于链指针next是final的、entry是不可变类**。但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在 table数组中的值。这使得getFirst(hash)可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。 186 | 187 | - put 188 | 189 | 该方法也是在持有段锁(锁定当前segment)的情况下执行的,这当然是为了并发的安全,修改数据是不能并发进行的,必须得有个判断是否超限的语句以确保容量不足时能够rehash。首先根据计算得到的散列值定位到segment及该segment中的散列桶中。接着判断是否存在同样一个key的结点,如果存在就直接替换这个结点的值。否则创建一个新的结点并添加到hash链的头部,这时一定要修改modCount和count的值,同样修改count的值一定要放在最后一步。 190 | 191 | - remove 192 | HashEntry中除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。 193 | 194 | 首先定位到要删除的节点e。如果不存在这个节点就直接返回null,否则就要将e前面的结点复制一遍,尾结点指向e的下一个结点。e后面的结点不需要复制,它们可以重用。 195 | 196 | - size() 197 | 每个Segment都有一个count变量,是一个volatile变量。当调用size方法时,首先先尝试2次通过不锁住segment的方式统计各个Segment的count值得总和,如果两次值不同则将锁住整个ConcurrentHashMap然后进行计算。 198 | 199 | 200 | 参见《java并发编程的艺术》P156 201 | http://www.cnblogs.com/ITtangtang/p/3948786.html 202 | 203 | ## 线程的几种可用状态 204 | 线程在运行周期里有6中不同的状态: 205 | 206 | 1. New 新建 207 | 2. RUNNABLE 运行状态,操作系统中运行与就绪两种状态统称运行中 208 | 3. BLOCKED 阻塞状态 209 | 4. WAITING 等待状态 210 | 5. TIME_WAITING 超时等待 211 | 6. TERMINATED 终止状态 212 | 213 | ## 同步方法和同步代码块的区别是什么 214 | 1. 同步方法只能锁定当前对象或class对象, 而同步方法块可以使用其他对象、当前对象及当前对象的class作为锁。 215 | 216 | 2. 从反编译后的结果看,对于同步块使用了**monitorenter**和**monitorexit**指令,而同步方法则是依靠方法上的修饰符**ACC_SYNCHRONIZED**来完成,但它们的本质都是对一个对象监视器进行获取,而这个获取过程是排他的。 217 | 218 | ### 显示锁ReentrantLock与内置锁synchronized的相同与区别 219 | 相同:显示锁与内置锁在加锁和内存上提供的语义相同(互斥访问临界区) 220 | 221 | 不同: 222 | 223 | 1. **使用方式**:内置无需指定释放锁,简化锁操作。显示锁拥有锁获取和释放的可操作性。 224 | 2. **功能上**:显示锁提供了其他很多功能如定时锁等待、可中断锁等待、公平性、尝试非阻塞获取锁、以及实现非结构化的加锁。(一个线程获取不到锁而被阻塞在synchronized之外时,对该线程进行中断操作,此时该线程的中断表示为会被修改,但线程依旧会被阻塞在synchronized上,等待获取锁。) 225 | 3. **对死锁的处理**:内置只能重启,显示可以通过设置超时获取锁来避免 226 | 4. **性能上**:java1.5 显示远超内置,java1.6 显示锁稍微比内置好 227 | 5. atomicinteger和Volatile等线程安全操作的关键字的理解和使用 228 | 229 | SOF你遇到过哪些情况。 230 | 231 | ### 实现多线程的3种方法:Thread与Runable。 232 | 233 | - **1)继承Tread类,重写run函数** 234 | - **2)实现Runnable接口** 235 | - **3)实现Callable接口** 236 | 237 | ### 如何选择多线程池 238 | 22. 线程同步的方法:sychronized、lock、reentrantLock等。 239 | 23. 锁的等级:方法锁、对象锁、类锁。 240 | 26. ThreadPool用法与优势。 241 | 26. Callable和Runnable的区别 242 | 27. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。 243 | 29. foreach与正常for循环效率对比。 244 | 31. 反射的作用于原理。 245 | 32. 泛型常用特点,List能否转为List。 246 | 36. 设计模式:单例、工厂、适配器、责任链、观察者等等。 247 | 37. JNI的使用。 248 | 38. java的代理是怎么实现的 249 | 35. Java1.7与1.8新特性。 250 | 1. lmbda表达式 251 | 1. Java8新特性 252 | 1. 连接池使用使用什么数据结构实现 253 | 1. 实现连接池 254 | 1. 结束一条 Thread 有什么方法? interrupt 底层实现有看过吗?线程的状态是怎么样的?如果给你实现会怎么样做? 255 | 1. Java 中有内存泄露吗?是怎么样的情景?为什么不用循环计数? 256 | 1. java都有哪些加锁方式 257 | 1. AIO与BIO的区别 258 | 1. 生产者与消费者,手写代码 259 | 1. Java创建线程之后,直接调用start()方法和run()的区别 260 | 2. 常用的线程池模式以及不同线程池的使用场景 261 | 3. newFixedThreadPool此种线程池如果线程数达到最大值后会怎么办,底层原理。 262 | 4. 多线程之间通信的同步问题,synchronized锁的是对象,衍伸出和synchronized相关很多的具体问题,例如同一个类不同方法都有synchronized锁,一个对象是否可以同时访问。或者一个类的static构造方法加上synchronized之后的锁的影响。 263 | 5. 了解可重入锁的含义,以及ReentrantLock 和synchronized的区别 264 | 6. 同步的数据结构,例如concurrentHashMap的源码理解以及内部实现原理,为什么他是同步的且效率高 265 | 8. 线程间通信,wait和notify 266 | 9. 定时线程的使用 267 | 10. 场景:在一个主线程中,要求有大量(很多很多)子线程执行完之后,主线程才执行完成。多种方式,考虑效率。 268 | 14. 并发、同步的接口或方法 269 | 16. J.U.C下的常见类的使用。 ThreadPool的深入考察; BlockingQueue的使用。(take,poll的区别,put,offer的区别);原子类的实现。 270 | 17. 简单介绍下多线程的情况,从建立一个线程开始。然后怎么控制同步过程,多线程常用的方法和结构 271 | 19. 实现多线程有几种方式,多线程同步怎么做,说说几个线程里常用的方法 272 | 273 | 274 | ## 写出生产者消费者模式。 275 | 276 | public class ProducerConsumerPattern { 277 | public static void main(String args[]){ 278 | BlockingQueue sharedQueue = new LinkedBlockingQueue(); 279 | Thread prodThread = new Thread(new Producer(sharedQueue)); 280 | Thread consThread = new Thread(new Consumer(sharedQueue)); 281 | prodThread.start(); 282 | consThread.start(); 283 | } 284 | } 285 | 286 | //Producer Class in java 287 | class Producer implements Runnable { 288 | private final BlockingQueue sharedQueue; 289 | public Producer(BlockingQueue sharedQueue) { 290 | this.sharedQueue = sharedQueue; 291 | } 292 | @Override 293 | public void run() { 294 | for(int i=0; i<10; i++){ 295 | try { 296 | System.out.println("Produced: " + i); 297 | sharedQueue.put(i); 298 | } catch (InterruptedException ex) { 299 | Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex); 300 | } 301 | } 302 | } 303 | } 304 | 305 | //Consumer Class in Java 306 | class Consumer implements Runnable{ 307 | private final BlockingQueue sharedQueue; 308 | public Consumer (BlockingQueue sharedQueue) { 309 | this.sharedQueue = sharedQueue; 310 | } 311 | @Override 312 | public void run() { 313 | while(true){ 314 | try { 315 | System.out.println("Consumed: "+ sharedQueue.take()); 316 | } catch (InterruptedException ex) { 317 | Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex); 318 | } 319 | } 320 | } 321 | } 322 | 323 | 324 | 个人公众号(欢迎关注):
325 | ![](/assets/weix_gongzhonghao.jpg) 326 | 327 | 328 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /network/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/network/README.md -------------------------------------------------------------------------------- /network/section01.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/network/section01.md -------------------------------------------------------------------------------- /os/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/os/README.md -------------------------------------------------------------------------------- /os/chapter1.md: -------------------------------------------------------------------------------- 1 | # 第一章 操作系统面试基础 2 | 3 | 常见面试题: 4 | 5 | 1. 进程和线程的区别。 6 | 2. 死锁的必要条件,怎么处理死锁。 7 | 3. Window内存管理方式:段存储,页存储,段页存储。 8 | 4. 进程的几种状态。 9 | 5. IPC几种通信方式。 10 | 6. 什么是虚拟内存。 11 | 7. 虚拟地址、逻辑地址、线性地址、物理地址的区别。 12 | 13 | 14 | -------------------------------------------------------------------------------- /os/product.md: -------------------------------------------------------------------------------- 1 | ## 背景 2 | 生产者消费者模式是可以通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。解决生产者/消费者问题的方法可分为两类: 3 | 4 | - (1)采用某种机制保护生产者和消费者之间的同步; 5 | - (2)在生产者和消费者之间建立一个管道。第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。 6 | 7 | ## 实现方式 8 | 在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。 9 | 10 | - 1) wait() / notify()方法 11 | - 2) await() / signal()方法 12 | - 3) BlockingQueue阻塞队列方法 13 | - 4) PipedInputStream / PipedOutputStream 14 | 15 | 16 | ## wait()/notify() 17 | 18 | 19 | 20 | ### blockingQueue方式 21 | 22 | public class ProducerConsumer { 23 | static class MsgQueueManager { 24 | /** 25 | * 消息总队列 26 | */ 27 | public final BlockingQueue messageQueue; 28 | MsgQueueManager(BlockingQueue messageQueue) { 29 | this.messageQueue = messageQueue; 30 | } 31 | MsgQueueManager() { 32 | this.messageQueue = new LinkedBlockingQueue(); 33 | } 34 | 35 | public void put(T msg) { 36 | try { 37 | messageQueue.put(msg); 38 | } catch (InterruptedException e) { 39 | Thread.currentThread().interrupt(); 40 | } 41 | } 42 | 43 | public T take() { 44 | try { 45 | return messageQueue.take(); 46 | } catch (InterruptedException e) { 47 | Thread.currentThread().interrupt(); 48 | } 49 | return null; 50 | } 51 | } 52 | 53 | static class Producer extends Thread { 54 | private MsgQueueManager msgQueueManager; 55 | public Producer(MsgQueueManager msgQueueManager) { 56 | this.msgQueueManager = msgQueueManager; 57 | } 58 | @Override 59 | public void run() { 60 | msgQueueManager.put(new Object()); 61 | } 62 | } 63 | static class Consumer extends Thread{ 64 | private MsgQueueManager msgQueueManager; 65 | public Consumer(MsgQueueManager msgQueueManager) { 66 | this.msgQueueManager = msgQueueManager; 67 | } 68 | @Override 69 | public void run() { 70 | Object o = msgQueueManager.take(); 71 | } 72 | } 73 | 74 | public static void main(String[] args) { 75 | MsgQueueManager msgQueueManager = new MsgQueueManager(); 76 | 77 | for(int i = 0; i < 100; i ++) { 78 | new Producer(msgQueueManager).start(); 79 | 80 | } 81 | for(int i = 0; i < 100; i ++) { 82 | new Consumer(msgQueueManager).start(); 83 | 84 | } 85 | } 86 | } 87 | 88 | 89 | 90 | http://www.infoq.com/cn/articles/producers-and-consumers-mode/ 91 | http://blog.csdn.net/monkey_d_meng/article/details/6251879 92 | 93 | 94 | -------------------------------------------------------------------------------- /os/section1.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/os/section1.md -------------------------------------------------------------------------------- /os/section2.md: -------------------------------------------------------------------------------- 1 | # 常见面试题 2 | ## 1.进程和线程 3 | ### 1.1 线程 4 | #### 1.1.1 概念 5 | 是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。 6 | 7 | #### 1.1.2 好处 8 | - 1) 易于调度。 9 | - 2) 提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。 10 | - 3) 开销少。创建线程比创建进程要快,所需开销很少。。 11 | - 4) 利于充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。 12 | 13 | #### 1.1.3 线程状态 14 | 联系java中线程的几种状态 15 | java thread的运行周期中, 有几种状态, 在 java.lang.Thread.State 中有详细定义和说明: 16 | 17 | - **1)NEW** 状态是指线程刚创建, 尚未启动 18 | 19 | - **2)RUNNABLE** 状态是线程正在正常运行中,当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等,这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等 20 | 21 | - **3)BLOCKED** 这个状态下, 是在多个线程有同步操作的场景,比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法,也就是这里是线程在等待进入临界区 22 | 23 | - **4)WAITING** 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束 24 | 25 | - **5)TIMED_WAITING** 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态 26 | 27 | - **6) TERMINATED** 这个状态下表示该线程的run方法已经执行完毕了,基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收) 28 | 29 | http://www.blogjava.net/santicom/archive/2011/09/01/357765.html 30 | 31 | ### 1.2 进程 32 | #### 1.2.1 概念 33 | 进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。 34 | 35 | #### 1.2.2 进程与程序的区别 36 | 37 | 1. 进程是程序的一次运行活动,属于一种**动态**的概念。程序是一组有序的静态指令,是一种**静态**的概念。 38 | 2. 一个进程可以执行一个或多个程序。 39 | 3. 程序可以作为一种软件资源**长期**保持着,而进程则是一次执行过程,它是**暂时**的,是动态地产生和终止的。 40 | 4. 进程更能真实地描述并发,而程序不能。 41 | 5. 进程由程序和数据两部分组成,进程是竞争计算机系统有限资源的基本单位 42 | 6. 进程具有创建其他进程的功能;而程序没有。 43 | 7. 进程还具有**并发性和交往性**,这也与程序的**封闭性**不同 44 | 45 | #### 1.2.3 进程的几种状态 46 | 进程的基本状态及状态之间的关系 47 | 48 | - **1)创建状态(New)**:进程正在创建过程中,还不能运行。操作系统在创建状态要进行的工作包括分配和建立进程控制块表项、建立资源表格(如打开文件表)并分配资源、加载程序并建立地址空间表等。 49 | 50 | - **2)就绪状态(Ready)**:进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排人低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。 51 | 52 | - **3)执行状态**:进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态; 在多处理机系统中,则有多个进程处于执行状态。 53 | 54 | - **4)阻塞状态**:正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即进程的执行受到阻塞,把这种暂停状态称为阻塞状态,有时也称为等待状态或封锁状态。致使进程阻塞的典型事件有:请求I/O,申请缓冲空间等。通常将这种处于阻塞状态的进程也排成一个队列。有的系统则根据阻塞原因的不同而把处于阻塞状态的进程排成多个队列。 55 | 56 | - **5)退出状态(Exit)**: 进程已结束运行,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中收集有关信息(如记帐和将退出代码传递给父进程)。 57 | 58 | #### 1.2.4 作业(进程)调度算法 59 | **1)先来先服务调度算法(FCFS)** 60 | 每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。 61 | 62 | **2)短作业(进程)优先调度算法(SPF)** 63 | 短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。缺点:长作业的运行得不到保证 64 | 65 | **3)优先权调度算法(HPF)** 66 | 当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程,这时,又可进一步把该算法分成如下两种。 67 | 可以分为: 68 | 1. 非抢占式优先权算法 69 | 2. 抢占式优先权调度算法 70 | 71 | **4)高响应比优先调度算法(HRN)** 72 | 每次选择高响应比最大的作业执行,响应比=(等待时间+要求服务时间)/要求服务时间。该算法同时考虑了短作业优先和先来先服务。 73 | 74 | (1) 如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业。 75 | 76 | (2) 当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务。 77 | 78 | (3) 对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高,从而也可获得处理机。简言之,**该算法既照顾了短作业,又考虑了作业到达的先后次序,不会使长作业长期得不到服务**。因此,该算法实现了一种较好的折衷。当然,在利用该算法时,每要进行调度之前,都须先做响应比的计算,这会**增加系统开销**。 79 | 80 | **5)时间片轮转法(RR)** 81 | 在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片。时间片的大小从几ms到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。换言之,系统能在给定的时间内响应所有用户的请求。 82 | 83 | **6)多级反馈队列调度算法** 84 | 它是目前被公认的一种较好的进程调度算法。 85 | 86 | (1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。 87 | 88 | (2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。 89 | 90 | (3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。 91 | 92 | http://blog.csdn.net/luyafei_89430/article/details/12971171 93 | 94 | #### 1.2.4 作业与进程的区别 95 | 一个进程是一个程序对某个数据集的执行过程,是分配资源的基本单位。作业是用户需要计算机完成的某项任务,是要求计算机所做工作的集合。一个作业的完成要经过作业提交、作业收容、作业执行和作业完成4个阶段。而进程是对已提交完毕的程序所执行过程的描述,是资源分配的基本单位。其主要区别如下。 96 | 97 | 1) 作业是用户向计算机提交任务的任务实体。在用户向计算机提交作业后,系统将它放入外存中的作业等待队列中等待执行。而进程则是完成用户任务的执行实体,是向系统申请分配资源的基本单位。任一进程,只要它被创建,总有相应的部分存在于内存中。 98 | 99 | 2) 一个作业可由多个进程组成,且必须至少由一个进程组成,反过来则不成立。 100 | 101 | 3) 作业的概念主要用在批处理系统中,像UNIX这样的分时系统中就没有作业的概念。而进程的概念则用在几乎所有的多道程序系统中。 102 | 103 | http://blog.csdn.net/qq_32744005/article/details/51817637 104 | 105 | ### 1.3 进程和线程的关系: 106 | - (1) 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。 107 | - (2) 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 108 | - (3) 处理机分给线程,即真正在处理机上运行的是线程。 109 | - (4) 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体. 110 | 111 | ### 1.4 进程与线程的区别 112 | - **1) 基本单位** 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位 113 | 114 | - **2)并发性:** 不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行 115 | 116 | - **3)拥有资源:** 进程是拥有资源的一个独立单位,线程不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源. 117 | 118 | - **4)系统开销:**由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置。而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此,这些线程之间的同步与通信非常容易实现,甚至无需操作系统的干预。 119 | 120 | - **5)通信方面**: 进程间通讯有管道、信号量、信号消息队列、socket来维护,而线程间通过通道、共享内存、信号灯来进行通信。 121 | 122 | 参考: http://blog.csdn.net/dazhong159/article/details/7896070 123 | 124 | ## 2.1 IPC几种通信方式(进程间的通信方式) 125 | 126 | **1)管道( pipe )**:管道是一种**半双工的通信**方式,数据只能单向流动。 127 | 管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。 128 | 129 | 管道有两种种: 130 | 131 | - 匿名管道:有两个限制:一是只支持半双工通信方式,即只能单向传输;二是只能在父子进程之间使用; 132 | - 命名管道:可以在非亲缘进程(父子进程)之间进行通信。 133 | 134 | **2)命名管道 (named pipe)**: 命名管道也是半双工的通信方式,它克服了管道没有名字的限制,并且它允许**无亲缘关系进程间**的通信。命令管道在文件系统中有对应的文件名,命名管道通过命令mkfifo或系统调用mkfifo来创建。 Microsoft SQL Server数据库默认安装后的本地连接使用的就是命名管道。 MySQL在 Window环境下,如果需要两个进程在同一台服务器上通信可以使用命名管道,通过 --enable-named-pipe选项设置。 135 | 136 | **3)信号量( semophore )**: 信号量是一个计数器,可以用来控制多个进程对**共享资源**的访问。它常作为一种**锁机制**,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 137 | 138 | **4)消息队列( message queue )**: 消息队列是由**消息的链表**结构实现,存放在内核中并由消息队列标识符标识。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 139 | 140 | **5)信号 ( sinal )**:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。除了用于进程通信外,进程还可以发送信号给进程本身。 141 | 142 | **6)共享内存( shared memory )**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。 143 | MySQL内部通信也使用了共享内存的方式,可以通过配置文件添加 --shared-memory实现 144 | 145 | **7)套接字( socket )**: 也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。 146 | 147 | 148 | ## 3.死锁的必要条件,怎么处理死锁。 149 | ### 3.1 死锁概念 150 | 是指两个或两个以上的进程在执行过程中,**由于竞争资源或者由于彼此通信而造成的一种阻塞的现象**,若无外力作用,它们都将无法推进下去。 151 | ### 3.2 活锁 152 | 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。 153 | 154 | ### 3.3 死锁条件 155 | 1. **互斥条件**:一个资源每次只能被一个进程使用 156 | 2. **不可剥夺条件**:进程已获得的资源,在末使用完之前,不能强行剥夺 157 | 3. **请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放 158 | 4. **循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系. 159 | 160 | ### 3.4 死锁预防 161 | 1. **破坏互斥条件**。允许某些进程(线程)同时访问某些资源,但有的资源不允许同时被访问如打印机等。 162 | 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。 163 | 2. **破坏不可抢占条件**:即允许进程强行从占有者那里夺取某些资源。这种预防方法实现起来困难,会降低系统性能。 164 | 3. **破坏占有且申请条件**。可以实行预先分配策略,即进程在运行前一次性地向系统申请它所需要的全部资源。如果当前进程所需的全部资源得不到满足,则不分配任何资源。只有当系统能够满足当前的全部资源得到满足时,才一次性将所有申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又重新申请资源的现象,因此不会发生死锁。但是有以下缺点: 165 | - 在许多情况下,一个进程在执行之前不可能知道它所需的全部资源。这是由于进程在执行时是动态的,不可预测的。 166 | - 资源利用率低。无论所分配资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间一直占有它们,造成长期占有。 167 | - 降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数必然少了。 168 | 4. **破坏循环等待条件**。实行资源有序分配策略。采用这种策略即把资源事先分类编号,按号分配。所有进程对资源的请求必须严格按资源需要递增的顺序提出。进程占用小好资源,才能申请大号资源,就不会产生环路。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高,但是也存在以下缺点: 169 | - 限制了进程对资源的请求,同时系统给所有资源合理编号也是件困难事,并增加了系统开销。 170 | 171 | ### 3.4 死锁的避免 172 | 1. **银行家算法**:该算法需要检查申请者对资源的最大需求量,如果系统现存的各类资源可以满足申请者的请求,就满足申请者的请求。这样申请者就可很快完成其计算,然后释放它占用的资源,从而保证了系统中的所有进程都能完成,所以可避免死锁的发生。 173 | 174 | 175 | ### 3.5 死锁的解除 176 | 一旦检测出死锁,就应立即釆取相应的措施,以解除死锁。 177 | 死锁解除的主要方法有: 178 | 179 | - **资源剥夺法**。挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。 180 | - **撤销进程法**。强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。 181 | - **进程回退法**。让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。 182 | 183 | ### 3.6 死锁检测与死锁恢复 184 | **死锁检测算法**: 死锁检测的基本思想是,如果一个进程所请求的资源能够被满足,那么就让它执行,否则释放它拥有的所有资源,然后让其它能满足条件的进程执行。 185 | 186 | 187 | 188 | ## 4.内存管理方式:页存储、段存储、段页存储。 189 | 参考: 190 | https://www.cnblogs.com/onepeace/p/5066736.html 191 | http://blog.sina.com.cn/s/blog_a46817ff0101hjzp.html 192 | http://blog.csdn.net/bupt_tinyfisher/article/details/8939689 193 | 194 | ### 4.1 分页存储管理 195 | #### 4.1.1 基本思想 196 | 将程序的逻辑地址空间划分为固定大小的**页(page)**,而物理内存划分为同样大小的**页框(page frame)或物理块**,每个物理块的大小一般取2的整数幂。程序加载时,可将任意一页放人内存中任意一个页框,这些页框不必连续,从而实现了离散分配。该方法需要CPU的硬件支持,来实现逻辑地址和物理地址之间的映射。在页式存储管理方式中地址结构由两部构成,前一部分是页号,后一部分为页内地址w(位移量)。 197 | 198 | 逻辑地址到物理地址变化原理:CPU中的内存管理单元(MMU)按逻辑页号通过查进程页表得到物理页框号,将物理页框号与页内地址相加形成物理地址(见图4-4)。 199 | #### 4.1.2 页式管理方式的优点 200 | - 没有外碎片,每个内碎片不超过页大小,提高内存的利用率。 201 | - 一个程序不必连续存放。 202 | - 便于改变程序占用空间的大小(主要指随着程序运行,动态生成的数据增多,所要求的地址空间相应增长)。 203 | #### 4.1.3 缺点 204 | 1. 无论数据有多少,都只能按照页面大小分配,容易产生**内部碎片**(一个页可能填充不满,造成浪费。 205 | 2. 不能体现程序逻辑 206 | 3. 分页方式的缺点是页长与程序的逻辑大小不相关 207 | 4. 不利于编程时的独立性,并给换入换出处理、存储保护和存储共享等操作造成麻烦。 208 | 209 | ### 4.2 分段存储 210 | #### 4.2.1 思想 211 | 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。通常,程序员把子程序、操作数和常数等不同类型的数据划分到不同的段中(写c程序时会用到),并且每个程序可以有多个相同类型的段。段表本身也是一个段,可以存在辅存中,但一般是驻留在主存中。 212 | 213 | 段表本身也是一个段,可以存在辅存中,但一般是驻留在主存中。 214 | 在段式虚拟存储系统中,虚拟地址由段号和段内地址组成,虚拟地址到实存地址的变换通过段表来实现。 215 | 216 | 在为某个段分配物理内存时,可以采用**首先适配法、下次适配法、最佳适配法等**方法。在回收某个段所占用的空间时,要注意将收回的空间与其相邻的空间合并。 217 | 218 | 在段式虚拟存储系统中,虚拟地址由段号和段内地址组成,虚拟地址到实存地址的变换通过段表来实现。 219 | #### 4.2.2 地址映射 220 | 在段式 管理系统中,整个进程的地址空间是**二维**的,即其逻辑地址由段号和段内地址两部分组成。为了完成进程逻辑地址到物理地址的映射,处理器会查找内存中的段表,由段号得到段的首地址,加上段内地址,得到实际的物理地址。这个过程也是由处理器的硬件直接完成的,操作系统只需在进程切换时,将进程段表的首地址装入处理器的特定寄存器当中。这个寄存器一般被称作段表地址寄存器。 221 | 222 | #### 4.2.3 分段存储方式的优缺点 223 | 分页对程序员而言是不可见的,而分段通常对程序员而言是可见的,因而分段为组织程序和数据提供了方便。与页式虚拟存储器相比,段式虚拟存储器有许多优点: 224 | 225 | - 段的逻辑独立性使其易于**编译、管理、修改和保护**,也便于多道程序共享。 226 | - 段长可以根据需要动态改变,允许自由调度,以便有效利用主存空间。 227 | - 方便编程,分段共享,分段保护,动态链接,动态增长 228 | 229 | 因为段的长度不固定,段式虚拟存储器也有一些缺点: 230 | 231 | - 主存空间分配比较麻烦。 232 | - 容易在段间留下许多碎片(外部碎片),造成存储空间利用率降低。 233 | - 由于段长不一定是2的整数次幂,因而不能简单地像分页方式那样用虚拟地址和实存地址的最低若干二进制位作为段内地址,并与段号进行直接拼接,必须用加法操作通过段起址与段内地址的求和运算得到物理地址。因此,**段式存储管理比页式存储管理方式需要更多的硬件支持**。 234 | 235 | ### 4.3 分页和分段的主要区别 236 | - 页是信息的**物理单位**,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率;段则是信息的**逻辑单位**,它含有一组其意义相对完整的信息,分段的目的是为了能更好地满足用户的需要。 237 | 238 | - 页的**大小固定且由系统决定**,由系统把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而在系统中只能有一种大小的页面;而**段的长度却不固定**,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时,根据信息的性质来划分。 239 | 240 | - **分页的作业地址空间是一维的,即单一的线性地址空间**,程序员只需利用一个记忆符,即可表示一个地址;而分段的作业地址空间则是二维的,程序员在标识一个地址是,即需给出段名,又需给出段内地址。 241 | 242 | - 分页信息很难保护和共享、分段存储按逻辑存储所以容易实现对段的保存和共享。 243 | 244 | ### 4.4 段页存储 245 | 程序员按照分段系统的地址结构将地址分为段号与段内位移量,地址变换机构将段内位移量分解为页号和页内位移量。 246 | 247 | 为实现段页式存储管理,系统应为每个进程设置一个段表,包括每段的段号,该段的页表始址和页表长度。每个段有自己的页表,记录段中的每一页的页号和存放在主存中的物理块 248 | 249 | 它首先将程序按其逻辑结构划分为若干个大小不等的逻辑段,然后再将每个逻辑段划分为若干个大小相等的逻辑页。主存空间也划分为若干个同样大小的物理页。辅存和主存之间的信息调度以页为基本传送单位,每个程序段对应一个段表,每页对应一个页表。 250 | 251 | 段页式系统中,作业的**地址结构包含三部分的内容:段号,页号,页内位移量** 252 | 253 | CPU访问时,段表指示每段对应的页表地址,每一段的页表确定页所在的主存空间的位置,最后与页表内地址拼接,确定CPU要访问单元的物理地址。 254 | 255 | **段页存储管理方式综合了段式管理和页式管理的优点,但需要经过两级查表才能完成地址转换,消耗时间多**。 256 | 257 | #### 4.1.1 地址变换的过程: 258 | - 进行地址变换时,首先利用段号S,将它与段表长TL进行比较。若S 321 | ![](../assets/weix_gongzhonghao.jpg) 322 | 323 | -------------------------------------------------------------------------------- /os/top命令详解.md: -------------------------------------------------------------------------------- 1 | # top命令 2 | top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定. 3 | 4 | ![](../images/15262805942603.jpg) 5 | 6 | ## 1. 显示内容 7 | 8 | - 第一行:**任务队列信息** 9 | - 10:01:23 当前系统时间 10 | - **days**:系统运行时间,格式为时:分(在这期间没有重启过) 11 | - **users**:当前登录用户数 12 | - **load average**: 1.15, 1.42, 1.44 load average后面的三个数分别是**1分钟、5分钟、15分钟**的负载情况。 13 | 14 | - 第二行:**进程信息** 15 | - Tasks 任务(进程), 16 | - **totoal**: 系统现在共有进程数, 17 | - **running**:其中处于运行中进程个数。 18 | - **sleeping**:休眠中的线程数。 19 | - **stoped**:停止的进程数 20 | - **zombie**:僵尸进程数 21 | 22 | - 第三行:**cpu状态** 23 | - 6.7% **us**:用户空间占用CPU的百分比。 24 | - 0.4% **sy**:内核空间占用CPU的百分比。 25 | - 0.0% **ni**:改变过优先级的进程占用CPU的百分比 26 | - 92.9% **id**:空闲CPU百分比 27 | - 0.0% **wa**:IO等待占用CPU的百分比 28 | - 0.0% **hi**: 硬中断(Hardware IRQ)占用CPU的百分比 29 | - 0.0% **si** 软中断(Software Interrupts)占用CPU的百分比 30 | 31 | - 第四行:**内存状态** 32 | - **total**: 物理内存总量 33 | - **used**: 使用中的内存总量 34 | - **free**: 空闲内存总量 35 | - **buffers**: 缓存的内存量 36 | 37 | - 第五行:**swap交换分区** 38 | - **total**: 交换区总量 39 | - **used**: 使用的交换区总量 40 | - **free**: 空闲交换区总量 41 | - **cached**: 缓冲的交换区总量 42 | 43 | - 第六行:进程信息 44 | 45 | ![](../images/15262813568697.jpg) 46 | 47 | 48 | ## 2. load average 49 | 可参考: 50 | 51 | - [理解Linux系统负荷](http://www.ruanyifeng.com/blog/2011/07/linux_load_average_explained.html) 52 | - [Understanding Linux CPU Load - when should you be worried?](http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages) 53 | 54 | 系统平均负载被定义为在**特定时间间隔内运行队列中(在CPU上运行或者等待运行多少进程)的平均进程数**。如果一个进程满足以下条件则其就会位于运行队列中: 55 | 56 | - 它没有在等待I/O操作的结果 57 | - 它没有主动进入等待状态(也就是没有调用’wait’) 58 | - 没有被停止(例如:等待终止) 59 | 60 | 是指系统的运行队列的平均利用率,也可以认为是可运行进程的平均数 61 | 62 | ### Load误解 63 | - 系统load高一定是性能有问题。 64 | - 真相:Load高也许是因为在进行cpu密集型的计算 65 | - 系统Load高一定是CPU能力问题或数量不够? 66 | - 真相:Load高只是代表需要**运行的队列累计过多了**。但队列中的任务实际可能是耗Cpu的,也可能是耗I/O 67 | - 在Load average 高的情况下如何鉴别系统瓶颈。 68 | - 是CPU不足,还是io不够快造成或是内存不足? 69 | 70 | -------------------------------------------------------------------------------- /others/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjianglong915/note-of-interview/105ebbb9964ea4cbdf42cd7dbc30d2c31bea450e/others/README.md -------------------------------------------------------------------------------- /others/danli.md: -------------------------------------------------------------------------------- 1 | > 《设计模式》提出近二十年里,随着面向对象语言的发展,单例模式也随之演化,如今其实现形式变得多种多样。常见的单例模式有懒汉、饿汉、双重校验锁、枚举和静态内部类五种形式。 2 | 双重校验锁DCL(double checked locking) 3 | 4 | ## 1.双重校验 5 | 双重校验锁式(也有人把双重校验锁式和懒汉式归为一类)分别在代码锁前后进行判空校验,避免了多个有机会进入临界区的线程都创建对象,同时也避免了代码段1-4后来线程在先来线程创建对象后但仍未退出临界区的情况下等待。双重校验锁代码如下: 6 | 7 | ``` 8 | public class Singleton{ 9 | private volatile static Singleton singleton = null; //注意此处加上了volatile关键字 10 | 11 | private Singleton(){ // 这边不能省略 12 | } 13 | 14 | public static Singleton getInstance(){ 15 | if(singleton == null){ 16 | synchronized(Singleton.class){ 17 | if(singleton == null){ 18 | singleton = new Singleton(); 19 | } 20 | } 21 | } 22 | return singleton; 23 | } 24 | } 25 | ``` 26 | 27 | 在JDK1.5以前,DCL是不稳定的,有时也可能创建多个实例,在1.5以后开始提供volatile关键字修饰变量来达到稳定效果。 28 | 29 | ## 2.饿汉式单例 30 | 单例模式的饿汉式,在定义自身类型的成员变量时就将其实例化,使得在Singleton单例类被系统(姑且这么说)加载时就已经被实例化出一个单例对象,从而一劳永逸地避免了线程安全的问题。代码如下: 31 | 32 | ``` 33 | public class Singleton { 34 | private final static Singleton INSTANCE = new Singleton(); 35 | private Singleton(){ } 36 | public static Singleton getInstance() { 37 | return INSTANCE; 38 | } 39 | } 40 | ``` 41 | 42 | ClassLoader加载Singleton类时,饿汉式单例就被创建,虽然饿汉式单例是线程安全的,但也有其不足之处。饿汉式单例在类被加载时就创建单例对象并且**长驻内存**,不管你需不需要它;如果单例类占用的资源比较多,就会降低资源利用率以及程序的运行效率。有一种更高级的单例模式则很好地解决了这个问题——静态内部类。 43 | 44 | ##3. 静态内部类 45 | 静态内部类式和饿汉式一样,同样利用了ClassLoader的机制保证了线程安全;不同的是,饿汉式在Singleton类被加载时(从代码段3-2的Class.forName可见)就创建了一个实例对象,而**静态内部类即使Singleton类被加载也不会创建单例对象**,除非调用里面的getInstance()方法。因为当Singleton类被加载时,其静态内部类SingletonHolder没有被主动使用。只有当调用getInstance方法时,才会装载SingletonHolder类,从而实例化单例对象。 46 | 47 | ``` 48 | public class Singleton { 49 | public static Singleton getInstance(){ 50 | return SingletonHolder.instance; 51 | } 52 | private Singleton(){} 53 | private static class SingletonHolder{ 54 | private static Singleton instance = new Singleton(); 55 | } 56 | } 57 | ``` 58 | 59 | 通过静态内部类的方法就实现了lazy loading,很好地将懒汉式和饿汉式结合起来,既实现延迟加载,保证系统性能,也能保证线程安全。 60 | 61 | ##4.枚举单例 62 | 上面说到的静态内部类方式不失为一个高级的单例模式实现。但如果开发要求更严格一些,比如你的Singleton类实现了序列化,又或者想避免通过反射来破解单例模式的话,单例模式还可以有另一种形式。那就是枚举单例。枚举类型在JDK1.5被引进。这种方式也是《Effective Java》作者Josh Bloch 提倡的方式,它不仅能避免多线程的问题,而且还能防止反序列化重新创建新的对象、防止被反射攻击。代码如下: 63 | 64 | public enum EnumSingleton { 65 | INSTANCE{ 66 | @Override 67 | protected void work() { 68 | System.out.println("你好,是我!"); 69 | } 70 | 71 | }; 72 | 73 | protected abstract void work(); //单例需要进行操作(也可以不写成抽象方法) 74 | } 75 | 76 | 77 | 在外部,可以通过EnumSingleton.INSTANCE.work()来调用work方法。默认的枚举实例的创建是线程安全的,但是实例内的各种方法则需要程序员来保证线程安全。总的来说,使用枚举单例模式,有三个好处: 78 | 79 | 1. 实例的创建线程安全,确保单例。 80 | 2. 防止被反射创建多个实例。 81 | 3. 没有序列化的问题 82 | 83 | ##5.懒汉式 84 | 在需要的时候才创建单例对象,而不是随着软件系统的运行或者当类被加载器加载的时候就创建。当单例类的创建或者单例对象的存在会消耗比较多的资源,常常采用lazy loading策略。这样做的一个明显好处是提高了软件系统的效率,节约内存资源。 85 | 86 | ``` 87 | public class Singleton { 88 | private static Singleton singleton = null; 89 | 90 | private Singleton(){ 91 | System.out.println("构造函数被调用"); 92 | } 93 | 94 | public static Singleton getInstance(){ 95 | synchronized(Singleton.class){ 96 | if(singleton == null){ 97 | singleton = new Singleton(); 98 | } 99 | 100 | return singleton; 101 | } 102 | } 103 | ``` 104 | 105 | 然而这样做类似于在方法签名上加上synchronized关键字,会影响程序效率。因为当有多个线程几乎同时访问getInstance方法时,多个线程必须有次序地进入方法内,这样导致了若干个线程需要耗费等待进入临界区(被锁住的代码块)的时间。基于此,有人提出了双重校验锁式。 106 | 107 | ## 参考资料 108 | http://blog.chenzuhuang.com/archive/13.html 109 | 110 | 个人公众号(欢迎关注):
111 | ![](/assets/weix_gongzhonghao.jpg) 112 | 113 | 114 | -------------------------------------------------------------------------------- /others/hash.md: -------------------------------------------------------------------------------- 1 | ## 参考 2 | http://blog.csdn.net/cywosp/article/details/23397179/ -------------------------------------------------------------------------------- /others/section1.md: -------------------------------------------------------------------------------- 1 | ## 第一个编码表 ASCII 2 | 在最初的时候,美国人制定了第一张编码表 《美国标准信息交换码》,简称 ASCII,它总共规定了 128 个符号所对应的数字代号,使用了7位二进制的位来表示这些数字。其中包含了英文的大小写字母、数字、标点符号等常用的字符,数字代号从 0 至 127。 3 | 4 | ## 扩展 ASCII 编码 ISO8859 5 | 美国人顺利解决了字符的问题,可是欧洲的各个国家还没有,比如法语中就有许多英语中没有的字符,因此 ASCII 不能帮助欧洲人解决编码问题。为了解决这个问题,人们借鉴 ASCII 的设计思想,创造了许多使用 8 位二进制数来表示字符的扩充字符集,这样我们就可以使用256种数字代号了,表示更多的字符了。在这些字符集中,从 0 - 127 的代码与 ASCII 保持兼容,从128到255用于其它的字符和符号,由于有很多的语言,有着各自不同的字符,于是人们为不同的语言制定了大量不同的编码表,在这些码表中,从128-255表示各自不同的字符,其中,国际标准化组织的 ISO8859 标准得到了广泛的使用。 6 | 7 | ## GB2312-80 8 | 我们就使用两个字节来表示一个中文,在每个字符的 256 种可能中,低于 128 的为了与 ASCII 保持兼容,我们不使用,借鉴 ISO8859的设计方案,只使用从 160 以后的 96 个数字,**两个字节**分成高位和低位,高位的取值范围从 176-247 共72个,低位从 161 – 254共94这样,两个字节就有 72 * 94 = 6768种可能,也就是可以表示 6768 种汉字 9 | 10 | ## GBK 11 | GB2312-80 仅收汉字6763个,这大大少于现有汉字,随着时间推移及汉字文化的不断延伸推广,有些原来很少用的字,现在变成了常用字,例如:朱镕基的“镕”字,未收入GB2312-80,现在大陆的报业出刊只得使用(金+容)、(金容)、(左金右容)等来表示,形式不一而同,这使得表示、存储、输入、处理都非常不方便,而且这种表示没有统一标准。 12 | 为了解决这些问题,全国信息技术化技术委员会于1995年12月1日《汉字内码扩展规范》。**GBK向下与GB2312完全兼容,向上支持ISO 10646国际标准**,在前者向后者过渡过程中起到的承上启下的作用。G**BK亦采用双字节表示**,总体编码范围为8140-FEFE之间,高字节在81-FE之间,低字节在40-FE之间,不包括7F。在 GBK 1.0 中共收录了 21886个符号,汉字有21003个。 13 | 14 | ## GB18030 15 | GB18030 是最新的汉字编码字符集国家标准, **向下兼容 GBK 和 GB2312 标准**。 GB18030 编码是**一二四字节变长编码**。 一字节部分从 0x0~0x7F与 ASCII 编码兼容。二字节部分, 首字节从 0x81~0xFE, 尾字节从 0x40~0x7E 以及 0x80~0xFE, 与 GBK标准基本兼容。 四字节部分, 第一字节从 0x81~0xFE, 第二字节从 0x30~0x39,第三和第四字节的范围和前两个字节分别相同。 16 | 17 | ## UNICODE 18 | 在80年代就有了一个称为 UNICODE 的组织,这个组织制定了一个能够覆盖几乎任何语言的编码表,在 Unicode3.0.1中就包含了 49194 个字符,将来,Unicode 中还会增加更多的字符。Unicode 的全称是 Universal Multiple-Octet Coded Character Set ,简称为 UCS。 19 | unicode中,**一个字符就是两个字节, 对于只需要1字节表示的字符来说很浪费内存**。 20 | 21 | ## UTF-16 22 | UTF-16比较好理解, **就是任何字符对应的数字都用两个字节来保存**.我们通常对Unicode的误解就是把Unicode与UTF-16等同了.但是很显然如果都是英文字母这做有点浪费.明明用一个字节能表示一个字符为啥整两个啊. 23 | 24 | ## UTF-8 25 | 于是又有个UTF-8,这里的8非常容易误导人,8不是指一个字节,难道一个字节表示一个字符?实际上不是.当用UTF-8时表示**一个字符是可变的,有可能是用一个字节表示一个字符**,也可能是两个,三个.当然最多不能超过3个字节了.反正是根据字符对应的数字大小来确定. 26 | 27 | 参考 28 | http://blog.sina.com.cn/s/blog_69c189bf0100mt93.html 29 | http://www.zhihu.com/question/23374078 30 | 31 | 32 | 个人公众号(欢迎关注):
33 | ![](/assets/weix_gongzhonghao.jpg) 34 | 35 | -------------------------------------------------------------------------------- /others/机器学习相关.md: -------------------------------------------------------------------------------- 1 | # 机器学习相关 2 | 3 | https://www.nowcoder.com/discuss/71482?type=2&order=4&pos=15&page=3 4 | 5 | -------------------------------------------------------------------------------- /others/设计模式汇总.md: -------------------------------------------------------------------------------- 1 | # 设计模式汇总 2 | [TOC] 3 | 4 | # 一. 面向对象开发的六原则一法则 5 | (单一职责、开放封闭、里氏替换、依赖倒置、合成聚合复合、接口隔离、迪米特法则) 6 | 7 | - **单一原则**:就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因,实现高内聚。 8 | - **开放封闭**:软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。 9 | - **依赖倒置原则**: 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。即面向接口编程。该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代。 10 | 11 | - 任何变量都不应该持有一个指向具体类的指针或者引用; 12 | - 任何类都不应该从具体类派生; 13 | - 任何方法都不应该覆写它的任何基类中的已经实现的方法。 14 | 15 | - **里氏替换原则**:子类对象必须能够替换掉所有父类对象。简单的说就是能用父类型的地方就一定能使用子类型,子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。 16 | - **接口隔离原则**:接口要小而专,绝不能大而全.臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。 17 | - **合成聚合复用原则**:优先使用聚合或合成关系复用代码。 18 | - **迪米特法则**:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。迪米特法则简单的说就是如何做到"低耦合",门面模式和调停者模式就是对迪米特法则的践行。 19 | 20 | 21 | # 二. 设计模式 22 | ## 2.1 创建型 23 | 创建型抽象了实例化过程,它们帮助一个系统独立于如何创建、组合和表达它的那些对象。 24 | 25 | ### `1)抽象工厂(Abstract Factory)` 26 | **定义**:**提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类**。 27 | **优点(效果)**: 28 | 29 | - **它分离了具体的类**。 它将客户和类的实现分离,客户通过它们的抽象接口操作实例。 30 | - **它使得易于交换产品系列**。 它只要改变具体的工厂,就可以使用不同的产品配置。 网关那边就可以采用这种方式实现。 31 | - **它有利于产品的一致性**。 当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用一个系列中的对象。 32 | 33 | **缺点**: 34 | 35 | - **难以支持新种类的产品**。 接口确定了可以被创建的产品集合,如果需要增加产品,则破坏了开放封闭原则。 36 | 37 | **适用场景**: 38 | 39 | - 一个系统要独立于它的产品 40 | - 一个系统要由多个产品系列中的一个来配置时。 41 | - 当你强调一系列相关产品对象的设计以便进行联合使用时。 42 | - 当你提供一个产品类库,而只想显示它们的接口而不是实现时。 43 | 44 | ### `2)生成器(Builder)` 45 | **定义**:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 46 | 47 | **适用场景**: 48 | 49 | - 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。 50 | - 当构造过程必须允许被构造的对象有不同的表示时。 51 | 52 | **效果**: 53 | 54 | - **它使你可以改变一个产品的内部表示**。Builder 对象提供给导向器一个构造产品的抽象接口,隐藏了内部实现细节。当你需要改变该产品的内部表示时,只需要创建一个新的生成器。 55 | - **它将构造代码和表示代码分开(解耦)** 56 | - **它使你可以对构造过程进行更精细的控制** 57 | 58 | **现实应用**: 59 | 60 | - spring的beanDefination的构造过程 61 | 62 | ### `3)工厂方法(Factory Method)` 63 | **定义**:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂模式使一个类的实例化延迟到其子类。 64 | 65 | **适用场景**: 66 | 67 | - 当一个类不知道它所必须创建的对象的类的时候 68 | - 当一个类希望由它的子类来指定它所创建的对象的时候 69 | - 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。 70 | 71 | **效果**: 72 | 73 | - 灵活性。用工厂方法在一个类的内部创建对象通常比直接创建对象更加灵活。 74 | - 连接平行的类层次。将一个类的职责委托给一个独立的类的时候,就产生了平行类层次。 75 | 76 | 缺点: 77 | 78 | - 客户端可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator子类 79 | 80 | **现实应用**: 81 | 82 | ### 4)原型(Prototype) 83 | **定义**:用原型实例指定创建对象的种类,并通过拷贝这个原型来创建新的对象。 84 | 85 | **适用性**: 86 | 87 | - 当要实例化的类是在运行时刻指定时,例如,通过动态装载 88 | - 为了避免创建一个与产品层次平行的工厂类层次 89 | - 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们,可能比每次用合适的状态手动实例化该类更方便些。 90 | 91 | **优点**: 92 | 93 | - **运行时刻增加和删除产品**。 允许只通过客户注册原型实例,就可以将一个新的具体产品类并入系统。它比创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型。 94 | - **改变值以指定新对象**。 95 | - **改变结构以指定新对象** 96 | - **减少子类的构造** 97 | - **用类动态配置应用** 98 | 99 | ### `5)单例模式(Singleton)` 100 | **定义**:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 101 | 102 | **适用性**: 103 | 104 | - 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它 105 | - 当这个唯一实例是通过子类实例化扩展的,并且客户应该无需修改代码就能使用一个扩展的实例时。 106 | 107 | **优点**: 108 | 109 | - 对唯一实例的受控访问 110 | - 允许可变数目的实例 111 | - 允许对操作和表示的精化 112 | 113 | **实际应用**: 114 | 115 | - 数据源 116 | 117 | ## 2.2 结构型 118 | 结构型类模式采用继承机制来组合接口或实现,这一模式有助于多个独立开发的类库协同工作。 119 | 结构型对象模式不是对接口或实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法。 120 | 121 | ### `6)适配器(Adapter)` 122 | **定义**:将一个接口转换为客户希望的另一个接口,适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以在一起工作。 123 | 别名包装器(wrapper) 124 | 125 | **适用性**: 126 | 127 | - 你想使用一个已经存在的类,而它的接口不符合你的需求。 128 | - 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作 129 | - 你想使用一些已经存在的子类,但不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父接口。 130 | 131 | **效果**: 132 | 133 | - 用一个具体的Adapter对Adaptee和Target进行匹配。 134 | - 使用Adapter可以重新定义Adaptee的部分行为,因为Adapter是Adaptee的子类。 135 | - 允许一个Adapter与多个Adaptee同时工作 136 | 137 | ### 7)桥接(Bridge) 138 | **定义**:将抽象部分和它的实现部分分离,使它们都可以独立地变化。 139 | 140 | **适用性**: 141 | 142 | - 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。 143 | - 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充 144 | - 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。 145 | - 你想在多个对象间共享实现,但同时要求客户并不知道。 146 | 147 | **效果**: 148 | 149 | - 实现接口以及其实现部分的分离 150 | - 提高可扩充性,你可以独立地对Abstraction和Implementor层次结构进行扩充。 151 | - 实现细节对客户透明 152 | 153 | ### 8)组合(Composite) 154 | **定义**:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得客户对单个对象和复合对象的使用具有一致性。 155 | 156 | **适用性**: 157 | 158 | - 你想表示对象的部分-整体层次结构 159 | - 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。 160 | 161 | **效果**: 162 | 163 | - 包含了基本对象和组合对象的层次结构,基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合。 164 | - 使得更容易加新类型的组件 165 | - 使你的设计更加一般化。容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。 166 | 167 | ### `9)装饰模式(Decorator)` 168 | **定义**:动态地给一个对象添加一些额外的职责。就扩展功能而言,装饰模式比生成子类的方式更加灵活。 169 | **适用性**: 170 | 171 | - 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责 172 | - 处理那些可能撤销的职责 173 | - 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的拓展,为了支持每种组合而产生大量的子类,使得子类数目爆炸性增长。 174 | 175 | **效果**: 176 | 177 | - 比静态继承更灵活。 178 | - 避免在层次结构高层的类有太多的特征。 179 | 180 | **缺点**: 181 | 182 | - 产生许多小对象: 这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。 183 | 184 | 185 | **应用**: 186 | 187 | - Dubbo的过滤器 188 | 189 | ### 10)外观模式(Facade) 190 | **定义**:为系统中的一组接口提供一个一致的界面,Façade模式定义一个高层接口,这个接口使得这一子系统更加容易使用。 191 | 192 | **适用性**: 193 | 194 | - 为一个复杂的子系统提供一个简单接口时,子系统往往因为不断演化变得越来越复杂,给用户带来使用上的困难。 195 | - 客户程序和抽象类的实现部分之间存在很大的依赖。引入Facade将这个子系统与客户以及其他子系统分离,可以提高子系统独立性和可移植性。 196 | - 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。 197 | 198 | **优点**: 199 | 200 | - 对客户屏蔽了系统组件,因而减少了客户处理对象的数目并使得子系统使用起来更加方便。 201 | - 它实现子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。 松耦合关系使得子系统组件的变化不影响它的客户。Fcade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。 202 | 203 | **应用**: 204 | 205 | - 控制器 206 | 207 | ### 11) 享元模式(Flyweight) 208 | **定义**:**运用共享技术有效支持大量细粒度的对象**。 209 | 210 | **适用性**: 211 | 212 | - 一个应用程序使用了大量的对象 213 | - 完全由于使用大量的对象,造成很大的存储开销 214 | - 对象的大多状态都可变为外部状态 215 | - 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。 216 | - 应用程序不依赖对象标识 217 | 218 | **效果**: 219 | 220 | - 节省空间开销 221 | 222 | ### `12) 代理模式(Proxy)` 223 | **定义**:为其他对象提供一个代理以控制这个对象的访问。 224 | 225 | **适用性**: 226 | 227 | - **远程代理**:为一个对象在不同地址空间提供局部代表。 228 | - **虚代理**:根据需要创建开销很大的对象。 229 | - **保护代理**:控制对原始对象的访问,保护代理用于对象应该有不同的访问权限的时候。 230 | - **智能代理**: 取代了简单的指针,它在访问对象时执行一些附加操作如:引入计数、检查等。 231 | 232 | **效果**: 233 | 234 | - 远程代理隐藏一个对象存在不同地址空间的事实 235 | - 虚拟代理可以进行优化,例如根据要求创建对象 236 | - 保护代理和智能代理都允许方位一个对象时有一些附加的内务处理 237 | 238 | **应用**: 239 | 240 | - Spring aop 241 | - mocktio , powermock 242 | - Mybatis 243 | 244 | ## 2.3 行为模式 245 | ### 13)`职责链(Chain of Responsibility)` 246 | **定义**:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链上传递该请求,直到一个对象处理它为止。 247 | 248 | **适用性**: 249 | 250 | - 有多个的对象可以处理一个请求,那个对象处理该请求运行时刻自动确定 251 | - 你想在不明确指定接受者的情况下,向多个对象中的一个提交请求。 252 | - 可以处理一个请求的对象集合应被动态指定。 253 | 254 | **效果**: 255 | 256 | - 降低耦合度:使得一个无需知道是其他哪个对象处理其请求。 257 | - 增强了给对象指派职责的灵活性:当对象中分派职责时,职责链给你更多的灵活性。你可以通过在运行时刻对该链进行动态的增加或修改来增加或改变厨艺一个请求的那些职责。 258 | - 不保证被接受:有可能该请求得不到处理 259 | 260 | ### 14)命令(Command) 261 | **定义**:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可取消的操作。 262 | 263 | **适用性**: 264 | 265 | - 抽象出待执行的动作以参数化某对象 266 | - 在不同的时刻指定、排列和执行请求。 267 | - 支持取消操作 268 | - 支持修改日志,这样当系统奔溃时,这些修改可以被重做一遍 269 | - 用构建在原语操作上的高层操作构造一个系统。 270 | 271 | **效果**: 272 | 273 | - 将调用操作的对象与知道如何实现该操作的对象解耦 274 | - Command是头等对象,它们可像其他的对象一样被操纵和扩展 275 | - 你可以将多个命令转配成一个复合命令 276 | - 增加新的Command很容易,因为这无需改变已有的类 277 | ### 15)解释器(Interpreter) 278 | **定义**:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。 279 | 280 | **适用性**: 281 | 282 | - 当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式 283 | - 该文法简单对于复杂的文法,文法的类层次变得庞大而无法管理 284 | - 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换为成另一种形式。 285 | 286 | **优点**: 287 | 288 | - 易于改变和拓展文法。因为该模式使用类来表示文法规则,你可使用继承来改变和扩展该文法 289 | - 易于实现文法。定义抽象语法树中各个节点的类的实现大体类似。 290 | 291 | **缺点**: 292 | 293 | - 复杂的文法难以维护 294 | 295 | ### 16)迭代器(Iterator) 296 | **定义**:提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。 297 | **适用性**: 298 | 299 | - 访问一个聚合对象的内容,而无需暴露它的内部表示。 300 | - 支持对聚合对象的多种遍历 301 | - 为遍历不同的聚合结构提供一个统一的接口 302 | 303 | **效果**: 304 | 305 | - 它支持以不同的形式遍历一个聚合 306 | - 迭代器简化了聚合的接口。 聚合本身不需要类似的遍历接口了 307 | - 在同一个聚合上可以有多个遍历。 308 | 309 | **应用**: 310 | 311 | - java list的遍历 312 | 313 | ### 17)中介者(Mediator) 314 | **定义**: 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 315 | 316 | **适用性**: 317 | 318 | - 一组对象以定义良好但复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解 319 | - 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象 320 | - 想定制一个分布在多个类中的行为,而又不想生成太多的子类 321 | 322 | **优点**: 323 | 324 | - 减少了子类生成。 中介者将原本分布于多个对象间的行为集中在一起,改变这些行为只需要生成Meditator的子类即可。 325 | - 它简化了对象协议。使用Mediator和各个Colleague间的一对多的交互来替换多对多交互,一对多的关系更易于理解、维护和拓展。 326 | - 它对对象如何协作进行了抽象 327 | - 它使控制集中化。这可能使得中介者自身成为一个难以维护的庞然大物。 328 | 329 | ### 18)备忘录(Memento) 330 | **定义**:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到保持状态。 331 | 332 | **适用性**: 333 | 334 | - 必须保持一个对象在某一时刻的状态,这样以后需要时它才能恢复到先前的状态 335 | - 如果一个用接口来让其他对象直接得到这些对象,将会暴露对象的实现细节并破坏对象的封装性 336 | 337 | **效果**: 338 | 339 | - **保护封装边界**:使用备忘录可以避免暴露一些只应由原发器管理却又必须存储在原发器之外的信息。 340 | - **它简化了原发器**:这就把所有存储管理的重任交给Originator,让客户管理它们请求的状态将会简化Originator, 并且使得客户工作结束时无需通知原发器。 341 | - **使用备忘录可能代价很高**:如果原发器在生成备忘录时必须拷贝大量的存储信息,或者客户非常频繁地创建备忘录和恢复原发器状态,可能会导致非常大的开销。 342 | - 定义窄接口和宽接口 343 | - 维护备忘录的潜在代价:可能会产生大量的存储开销 344 | 345 | ### 19) `观察者(Observer)` 346 | **定义**:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时所有依赖于它的对象都得到通知并自动刷新。 347 | 348 | **适用性**: 349 | 350 | - 当一个抽象模型有两个方面,其中一个方面依赖与另一个方面。将这二者封装在独立的对象中以使他们可以各自独立地改变和复用。 351 | - 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。 352 | 353 | **优点**: 354 | 355 | - 目标和观察者间的抽象耦合 356 | - 支持广播通信 357 | 358 | **缺点**: 359 | 360 | - 意外的更新。 因为一个观察者不知道其他观察者的存在,它可能对改变目标的最终代价一无所知。如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。 361 | 362 | **应用**: 363 | 364 | - 消息队列 365 | 366 | ### 20)状态模式(State) 367 | **定义**:**允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类**。 368 | 369 | **适用性**: 370 | 371 | - 一个对象的行为取决于它的状态,并且必须在运行时刻根据状态改变它的行为 372 | - 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态 373 | 374 | **效果**: 375 | 376 | - 它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。State模式将所有与一个特定状态相关的行为都放在一个对象中,因为所有与状态相关的代码都存在于某个子类中,所以通过定义新的子类可以很容易的增加新的状态和转换。 377 | - 它使得状态转换明显化 378 | - state对象可以被共享。 如果state对象没有实例变量,那么context对象可以共享一个state对象。 379 | 380 | ### `21)策略模式(Strategy)`: 381 | **定义**: **定义一系列的算法,把它们一个个封装起来,并且使它们可相互替代。本模式使得算法的变化可以独立于使用它的客户**。 382 | 383 | **适用性**: 384 | 385 | - 许多相关的类仅仅是行为有异。策略提供了一种用多个行为中的一个行为来配置一个类的方法。 386 | - 需要使用一个算法的不同变体。 387 | - 算法使用客户不应该知道的数据。可使用策略模式避免暴露复杂的、与算法相关的数据结构。 388 | - 一个类定义多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。 389 | 390 | **效果**: 391 | 392 | - 相关算法系列:定义了一系列可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。 393 | - 一个替代继承的方法。将算法封装在独立的Strategy类中,使得你可要独立于其Context改变它,使它易于切换、易于理解、易于扩展。 394 | - 消除了一些条件语句。当不同的行为堆砌在一个类中,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Stragety类中消除了这些条件语句。 395 | 396 | ### `22) 模板方法(Template Method)` 397 | **定义**: **定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。** 398 | 399 | **适用性**: 400 | 401 | - 一次性实现一个算法的不变部分,并将可变行为部分留给子类实现 402 | - 各个子类中公共行为应该被提取出来集中到一个公共父类中以避免代码重复。 403 | - 控制子类扩展。模板方法只在特定点调用“hook”操作,这样就只允许在这些点进行扩展。 404 | 405 | **效果**: 406 | 407 | - 提高代码复用性 408 | 409 | ### 23) 访问者(Visitor) 410 | **定义**: **表示一个作用于某个对象结构中的各个元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。** 411 | 412 | **适用性**: 413 | 414 | - 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。 415 | - 需要对一个对象结构中的对象进行很多不同的并且不相干的操作,而你想避免让这些操作“污染”这些对象的类。 416 | - 定义对象结构的类很少改变,当经常需要在此结构上定义新的操作。 417 | 418 | **效果**: 419 | 420 | - 访问者模式使得易于增加新的操作。访问者使得增加依赖于复杂对象结构的构建的操作变得容易。 421 | - 访问者几种相关的操作而分离无关的操作。相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中。 422 | - 增加新的ConcreteElement类很困难。 423 | 424 | -------------------------------------------------------------------------------- /others/项目.md: -------------------------------------------------------------------------------- 1 | # 项目问题 2 | - 工作中遇到困难怎么解决, 3 | - 项目中遇到什么问题 4 | - 有没有遇到设计层面的问题 5 | - 项目中怎么保存用户登录信息的 6 | - 做的最深的项目?解决了什么问题? 7 | 8 | --------------------------------------------------------------------------------