├── .gitattributes ├── Java相关 ├── ArrayList-Grow.md ├── ArrayList.md ├── HashMap.md ├── J2EE基础知识.md ├── Java IO与NIO.md ├── Java基础知识.md ├── Java虚拟机(jvm).md ├── Java集合框架常见面试题总结.md ├── LinkedList.md ├── Multithread │ ├── AQS.md │ ├── Atomic.md │ └── BATJ都爱问的多线程面试题.md ├── final、static、this、super.md ├── static.md ├── synchronized.md ├── 可能是把Java内存区域讲的最清楚的一篇文章.md ├── 多线程系列.md ├── 搞定JVM垃圾回收就是这么简单.md ├── 设计模式.md └── 这几道Java集合框架面试题几乎必问.md ├── README.md ├── 主流框架 ├── SpringBean.md ├── SpringMVC 工作原理详解.md ├── Spring学习与面试.md └── ZooKeeper.md ├── 其他 ├── 2018 秋招.md ├── 个人阅读书籍清单.md └── 选择技术方向都要考虑哪些因素.md ├── 操作系统 ├── Shell.md └── 后端程序员必备的Linux基础知识.md ├── 数据存储 ├── MySQL Index.md ├── MySQL.md └── Redis │ ├── Redis.md │ ├── Redis持久化.md │ ├── Redlock分布式锁.md │ └── 如何做可靠的分布式锁,Redlock真的可行么.md ├── 数据结构与算法 ├── Leetcode-LinkList1.md ├── source code │ └── securityAlgorithm │ │ ├── .classpath │ │ ├── .gitignore │ │ ├── .project │ │ ├── .settings │ │ ├── org.eclipse.core.resources.prefs │ │ ├── org.eclipse.jdt.core.prefs │ │ └── org.eclipse.m2e.core.prefs │ │ ├── pom.xml │ │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── snailclimb │ │ │ └── ks │ │ │ └── securityAlgorithm │ │ │ ├── Base64Demo.java │ │ │ ├── DesDemo.java │ │ │ ├── IDEADemo.java │ │ │ ├── MD5.java │ │ │ ├── MD5Demo.java │ │ │ ├── RSADemo.java │ │ │ ├── SHA1Demo.java │ │ │ └── readme │ │ └── test │ │ └── java │ │ └── com │ │ └── snailclimb │ │ └── ks │ │ └── securityAlgorithm │ │ └── AppTest.java ├── 常见安全算法(MD5、SHA1、Base64等等)总结.md ├── 搞定BAT面试——几道常见的子符串算法题.md ├── 数据结构.md ├── 算法.md └── 算法题解析 │ ├── 公司真题 │ └── 网易2018校招编程题1-3.md │ └── 剑指offer │ ├── (1)斐波那契数列问题和跳台阶问题.md │ ├── (2)二维数组查找和替换空格问题.md │ ├── (3)数值的整数次方和调整数组元素顺序.md │ ├── (4)链表相关编程题.md │ └── (5)栈变队列和栈的压入、弹出序列.md ├── 架构 └── 分布式.md ├── 计算机网络与数据通信 ├── 干货:计算机网络知识总结.md ├── 数据通信(RESTful、RPC、消息队列).md └── 计算机网络.md └── 面试必备 ├── books.md ├── interviewPrepare.md ├── 手把手教你用Markdown写一份高质量的简历.md ├── 最最最常见的Java面试题总结 ├── 第一周(2018-8-7).md ├── 第二周(2018-8-13).md └── 第四周(2018-8-30).md ├── 程序员的简历之道.md ├── 简历模板.md ├── 美团-基础篇.md ├── 美团-终结篇.md ├── 美团-进阶篇.md └── 面试必备之乐观锁与悲观锁.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.js linguist-language=java 4 | *.css linguist-language=java 5 | *.html linguist-language=java -------------------------------------------------------------------------------- /Java相关/Java IO与NIO.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [IO流学习总结](#io流学习总结) 4 | - [一 Java IO,硬骨头也能变软](#一-java-io,硬骨头也能变软) 5 | - [二 java IO体系的学习总结](#二-java-io体系的学习总结) 6 | - [三 Java IO面试题](#三-java-io面试题) 7 | - [NIO与AIO学习总结](#nio与aio学习总结) 8 | - [一 Java NIO 概览](#一-java-nio-概览) 9 | - [二 Java NIO 之 Buffer\(缓冲区\)](#二-java-nio-之-buffer缓冲区) 10 | - [三 Java NIO 之 Channel(通道)](#三-java-nio-之-channel(通道)) 11 | - [四 Java NIO之Selector(选择器)](#四-java-nio之selector(选择器)) 12 | - [五 Java NIO之拥抱Path和Files](#五-java-nio之拥抱path和files) 13 | - [六 NIO学习总结以及NIO新特性介绍](#六-nio学习总结以及nio新特性介绍) 14 | - [七 Java NIO AsynchronousFileChannel异步文件通](#七-java-nio-asynchronousfilechannel异步文件通) 15 | - [八 高并发Java(8):NIO和AIO](#八-高并发java(8):nio和aio) 16 | - [推荐阅读](#推荐阅读) 17 | - [在 Java 7 中体会 NIO.2 异步执行的快乐](#在-java-7-中体会-nio2-异步执行的快乐) 18 | - [Java AIO总结与示例](#java-aio总结与示例) 19 | 20 | 21 | 22 | 23 | 24 | ## IO流学习总结 25 | 26 | ### [一 Java IO,硬骨头也能变软](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483981&idx=1&sn=6e5c682d76972c8d2cf271a85dcf09e2&chksm=fd98542ccaefdd3a70428e9549bc33e8165836855edaa748928d16c1ebde9648579d3acaac10#rd) 27 | 28 | **(1) 按操作方式分类结构图:** 29 | 30 | ![按操作方式分类结构图:](https://user-gold-cdn.xitu.io/2018/5/16/16367d4fd1ce1b46?w=720&h=1080&f=jpeg&s=69522) 31 | 32 | 33 | **(2)按操作对象分类结构图** 34 | 35 | ![按操作对象分类结构图](https://user-gold-cdn.xitu.io/2018/5/16/16367d673b0e268d?w=720&h=535&f=jpeg&s=46081) 36 | 37 | ### [二 java IO体系的学习总结](https://blog.csdn.net/nightcurtis/article/details/51324105) 38 | 1. **IO流的分类:** 39 | - 按照流的流向分,可以分为输入流和输出流; 40 | - 按照操作单元划分,可以划分为字节流和字符流; 41 | - 按照流的角色划分为节点流和处理流。 42 | 2. **流的原理浅析:** 43 | 44 | java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。 45 | 46 | - **InputStream/Reader**: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 47 | - **OutputStream/Writer**: 所有输出流的基类,前者是字节输出流,后者是字符输出流。 48 | 3. **常用的io流的用法** 49 | 50 | ### [三 Java IO面试题](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483985&idx=1&sn=38531c2cee7b87f125df7aef41637014&chksm=fd985430caefdd26b0506aa84fc26251877eccba24fac73169a4d6bd1eb5e3fbdf3c3b940261#rd) 51 | 52 | ## NIO与AIO学习总结 53 | 54 | 55 | ### [一 Java NIO 概览](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483956&idx=1&sn=57692bc5b7c2c6dfb812489baadc29c9&chksm=fd985455caefdd4331d828d8e89b22f19b304aa87d6da73c5d8c66fcef16e4c0b448b1a6f791#rd) 56 | 57 | 1. **NIO简介**: 58 | 59 | Java NIO 是 java 1.4, 之后新出的一套IO接口NIO中的N可以理解为Non-blocking,不单纯是New。 60 | 61 | 2. **NIO的特性/NIO与IO区别:** 62 | - 1)IO是面向流的,NIO是面向缓冲区的; 63 | - 2)IO流是阻塞的,NIO流是不阻塞的; 64 | - 3)NIO有选择器,而IO没有。 65 | 3. **读数据和写数据方式:** 66 | - 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。 67 | 68 | - 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。 69 | 70 | 4. **NIO核心组件简单介绍** 71 | - **Channels** 72 | - **Buffers** 73 | - **Selectors** 74 | 75 | 76 | ### [二 Java NIO 之 Buffer(缓冲区)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483961&idx=1&sn=f67bef4c279e78043ff649b6b03fdcbc&chksm=fd985458caefdd4e3317ccbdb2d0a5a70a5024d3255eebf38183919ed9c25ade536017c0a6ba#rd) 77 | 78 | 1. **Buffer(缓冲区)介绍:** 79 | - Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels; 80 | - Buffer本质上就是一块内存区; 81 | - 一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit限制。 82 | 2. **Buffer的常见方法** 83 | - Buffer clear() 84 | - Buffer flip() 85 | - Buffer rewind() 86 | - Buffer position(int newPosition) 87 | 3. **Buffer的使用方式/方法介绍:** 88 | - 分配缓冲区(Allocating a Buffer): 89 | ```java 90 | ByteBuffer buf = ByteBuffer.allocate(28);//以ByteBuffer为例子 91 | ``` 92 | - 写入数据到缓冲区(Writing Data to a Buffer) 93 | 94 | **写数据到Buffer有两种方法:** 95 | 96 | 1.从Channel中写数据到Buffer 97 | ```java 98 | int bytesRead = inChannel.read(buf); //read into buffer. 99 | ``` 100 | 2.通过put写数据: 101 | ```java 102 | buf.put(127); 103 | ``` 104 | 105 | 4. **Buffer常用方法测试** 106 | 107 | 说实话,NIO编程真的难,通过后面这个测试例子,你可能才能勉强理解前面说的Buffer方法的作用。 108 | 109 | 110 | ### [三 Java NIO 之 Channel(通道)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483966&idx=1&sn=d5cf18c69f5f9ec2aff149270422731f&chksm=fd98545fcaefdd49296e2c78000ce5da277435b90ba3c03b92b7cf54c6ccc71d61d13efbce63#rd) 111 | 112 | 113 | 1. **Channel(通道)介绍** 114 | - 通常来说NIO中的所有IO都是从 Channel(通道) 开始的。 115 | - NIO Channel通道和流的区别: 116 | 2. **FileChannel的使用** 117 | 3. **SocketChannel和ServerSocketChannel的使用** 118 | 4. **️DatagramChannel的使用** 119 | 5. **Scatter / Gather** 120 | - Scatter: 从一个Channel读取的信息分散到N个缓冲区中(Buufer). 121 | - Gather: 将N个Buffer里面内容按照顺序发送到一个Channel. 122 | 6. **通道之间的数据传输** 123 | - 在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。 124 | - transferFrom() :transferFrom方法把数据从通道源传输到FileChannel 125 | - transferTo() :transferTo方法把FileChannel数据传输到另一个channel 126 | 127 | 128 | ### [四 Java NIO之Selector(选择器)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483970&idx=1&sn=d5e2b133313b1d0f32872d54fbdf0aa7&chksm=fd985423caefdd354b587e57ce6cf5f5a7bec48b9ab7554f39a8d13af47660cae793956e0f46#rd) 129 | 130 | 131 | 1. **Selector(选择器)介绍** 132 | - Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。 133 | - 使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。 134 | 2. **Selector(选择器)的使用方法介绍** 135 | - Selector的创建 136 | ```java 137 | Selector selector = Selector.open(); 138 | ``` 139 | - 注册Channel到Selector(Channel必须是非阻塞的) 140 | ```java 141 | channel.configureBlocking(false); 142 | SelectionKey key = channel.register(selector, Selectionkey.OP_READ); 143 | ``` 144 | - SelectionKey介绍 145 | 146 | 一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。 147 | - 从Selector中选择channel(Selecting Channels via a Selector) 148 | 149 | 选择器维护注册过的通道的集合,并且这种注册关系都被封装在SelectionKey当中. 150 | - 停止选择的方法 151 | 152 | wakeup()方法 和close()方法。 153 | 3. **模板代码** 154 | 155 | 有了模板代码我们在编写程序时,大多数时间都是在模板代码中添加相应的业务代码。 156 | 4. **客户端与服务端简单交互实例** 157 | 158 | 159 | 160 | ### [五 Java NIO之拥抱Path和Files](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483976&idx=1&sn=2296c05fc1b840a64679e2ad7794c96d&chksm=fd985429caefdd3f48e2ee6fdd7b0f6fc419df90b3de46832b484d6d1ca4e74e7837689c8146&token=537240785&lang=zh_CN#rd) 161 | 162 | **一 文件I/O基石:Path:** 163 | - 创建一个Path 164 | - File和Path之间的转换,File和URI之间的转换 165 | - 获取Path的相关信息 166 | - 移除Path中的冗余项 167 | 168 | **二 拥抱Files类:** 169 | - Files.exists() 检测文件路径是否存在 170 | - Files.createFile() 创建文件 171 | - Files.createDirectories()和Files.createDirectory()创建文件夹 172 | - Files.delete()方法 可以删除一个文件或目录 173 | - Files.copy()方法可以吧一个文件从一个地址复制到另一个位置 174 | - 获取文件属性 175 | - 遍历一个文件夹 176 | - Files.walkFileTree()遍历整个目录 177 | 178 | ### [六 NIO学习总结以及NIO新特性介绍](https://blog.csdn.net/a953713428/article/details/64907250) 179 | 180 | - **内存映射:** 181 | 182 | 这个功能主要是为了提高大文件的读写速度而设计的。内存映射文件(memory-mappedfile)能让你创建和修改那些大到无法读入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问了。将文件的一段区域映射到内存中,比传统的文件处理速度要快很多。内存映射文件它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。 183 | 184 | ### [七 Java NIO AsynchronousFileChannel异步文件通](http://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-asynchronousfilechannel.html) 185 | 186 | Java7中新增了AsynchronousFileChannel作为nio的一部分。AsynchronousFileChannel使得数据可以进行异步读写。 187 | 188 | ### [八 高并发Java(8):NIO和AIO](http://www.importnew.com/21341.html) 189 | 190 | 191 | 192 | ## 推荐阅读 193 | 194 | ### [在 Java 7 中体会 NIO.2 异步执行的快乐](https://www.ibm.com/developerworks/cn/java/j-lo-nio2/index.html) 195 | 196 | ### [Java AIO总结与示例](https://blog.csdn.net/x_i_y_u_e/article/details/52223406) 197 | AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。 198 | 199 | 200 | **欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):** 201 | -------------------------------------------------------------------------------- /Java相关/Java虚拟机(jvm).md: -------------------------------------------------------------------------------- 1 | Java面试通关手册(Java学习指南)github地址(欢迎star和pull):[https://github.com/Snailclimb/Java_Guide](https://github.com/Snailclimb/Java_Guide) 2 | 3 | 4 | 5 | 下面是按jvm虚拟机知识点分章节总结的一些jvm学习与面试相关的一些东西。一般作为Java程序员在面试的时候一般会问的大多就是**Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理**这些问题了。这些内容参考周的《深入理解Java虚拟机》中第二章和第三章就足够了对应下面的[深入理解虚拟机之Java内存区域:](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd)和[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd)这两篇文章。 6 | 7 | 8 | > ### 常见面试题 9 | 10 | [深入理解虚拟机之Java内存区域:](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd) 11 | 12 | 1. 介绍下Java内存区域(运行时数据区)。 13 | 14 | 2. 对象的访问定位的两种方式。 15 | 16 | 17 | [深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd) 18 | 19 | 1. 如何判断对象是否死亡(两种方法)。 20 | 21 | 2. 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 22 | 23 | 3. 垃圾收集有哪些算法,各自的特点? 24 | 25 | 4. HotSpot为什么要分为新生代和老年代? 26 | 27 | 5. 常见的垃圾回收器有那些? 28 | 29 | 6. 介绍一下CMS,G1收集器。 30 | 31 | 7. Minor Gc和Full GC 有什么不同呢? 32 | 33 | 34 | 35 | [虚拟机性能监控和故障处理工具](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483922%26idx%3D1%26sn%3D0695ff4c2700ccebb8fbc39011866bd8%26chksm%3Dfd985473caefdd6583eb42dbbc7f01918dc6827c808292bb74a5b6333e3d526c097c9351e694%23rd) 36 | 37 | 1. JVM调优的常见命令行工具有哪些? 38 | 39 | [深入理解虚拟机之类文件结构](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483926%26idx%3D1%26sn%3D224413da998f7e024f7b8d87397934d9%26chksm%3Dfd985477caefdd61a2fe1a3f0be29e057082252e579332f5b6d9072a150b838cefe2c47b6e5a%23rd) 40 | 41 | 1. 简单介绍一下Class类文件结构(常量池主要存放的是那两大常量?Class文件的继承关系是如何确定的?字段表、方法表、属性表主要包含那些信息?) 42 | 43 | [深入理解虚拟机之虚拟机类加载机制](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483934&idx=1&sn=f247f9bee4e240f5e7fac25659da3bff&chksm=fd98547fcaefdd6996e1a7046e03f29df9308bdf82ceeffd111112766ffd3187892700f64b40#rd) 44 | 45 | 1. 简单说说类加载过程,里面执行了哪些操作? 46 | 47 | 2. 对类加载器有了解吗? 48 | 49 | 3. 什么是双亲委派模型? 50 | 51 | 4. 双亲委派模型的工作过程以及使用它的好处。 52 | 53 | 54 | 55 | 56 | 57 | > ### 推荐阅读 58 | 59 | [深入理解虚拟机之虚拟机字节码执行引擎](https://juejin.im/post/5aebcb076fb9a07a9a10b5f3) 60 | 61 | [《深入理解 Java 内存模型》读书笔记](http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/) (非常不错的文章) 62 | 63 | [全面理解Java内存模型(JMM)及volatile关键字 ](https://blog.csdn.net/javazejian/article/details/72772461) 64 | 65 | **欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):** 66 | 67 | ![微信公众号](https://user-gold-cdn.xitu.io/2018/3/19/1623c870135a3609?w=215&h=215&f=jpeg&s=29172) 68 | -------------------------------------------------------------------------------- /Java相关/Multithread/Atomic.md: -------------------------------------------------------------------------------- 1 | > 个人觉得这一节掌握基本的使用即可! 2 | 3 | **本节思维导图:** 4 | 5 | ![](https://user-gold-cdn.xitu.io/2018/10/30/166c58b785368234?w=1200&h=657&f=png&s=49615) 6 | 7 | ### 1 Atomic 原子类介绍 8 | 9 | Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 10 | 11 | 所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 12 | 13 | 并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 14 | 15 | ![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267) 16 | 17 | 根据操作的数据类型,可以将JUC包中的原子类分为4类 18 | 19 | **基本类型** 20 | 21 | 使用原子的方式更新基本类型 22 | 23 | - AtomicInteger:整形原子类 24 | - AtomicLong:长整型原子类 25 | - AtomicBoolean :布尔型原子类 26 | 27 | **数组类型** 28 | 29 | 使用原子的方式更新数组里的某个元素 30 | 31 | 32 | - AtomicIntegerArray:整形数组原子类 33 | - AtomicLongArray:长整形数组原子类 34 | - AtomicReferenceArray :引用类型数组原子类 35 | 36 | **引用类型** 37 | 38 | - AtomicReference:引用类型原子类 39 | - AtomicStampedRerence:原子更新引用类型里的字段原子类 40 | - AtomicMarkableReference :原子更新带有标记位的引用类型 41 | 42 | **对象的属性修改类型** 43 | 44 | - AtomicIntegerFieldUpdater:原子更新整形字段的更新器 45 | - AtomicLongFieldUpdater:原子更新长整形字段的更新器 46 | - AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 47 | 48 | 下面我们来详细介绍一下这些原子类。 49 | 50 | ### 2 基本类型原子类 51 | 52 | #### 2.1 基本类型原子类介绍 53 | 54 | 使用原子的方式更新基本类型 55 | 56 | - AtomicInteger:整形原子类 57 | - AtomicLong:长整型原子类 58 | - AtomicBoolean :布尔型原子类 59 | 60 | 上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。 61 | 62 | **AtomicInteger 类常用方法** 63 | 64 | ```java 65 | public final int get() //获取当前的值 66 | public final int getAndSet(int newValue)//获取当前的值,并设置新的值 67 | public final int getAndIncrement()//获取当前的值,并自增 68 | public final int getAndDecrement() //获取当前的值,并自减 69 | public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 70 | boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) 71 | public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 72 | ``` 73 | 74 | #### 2.2 AtomicInteger 常见方法使用 75 | 76 | ```java 77 | import java.util.concurrent.atomic.AtomicInteger; 78 | 79 | public class AtomicIntegerTest { 80 | 81 | public static void main(String[] args) { 82 | // TODO Auto-generated method stub 83 | int temvalue = 0; 84 | AtomicInteger i = new AtomicInteger(0); 85 | temvalue = i.getAndSet(3); 86 | System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3 87 | temvalue = i.getAndIncrement(); 88 | System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4 89 | temvalue = i.getAndAdd(5); 90 | System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9 91 | } 92 | 93 | } 94 | ``` 95 | 96 | #### 2.3 基本数据类型原子类的优势 97 | 98 | 通过一个简单例子带大家看一下基本数据类型原子类的优势 99 | 100 | **①多线程环境不使用原子类保证线程安全(基本数据类型)** 101 | 102 | ```java 103 | class Test { 104 | private volatile int count = 0; 105 | //若要线程安全执行执行count++,需要加锁 106 | public synchronized void increment() { 107 | count++; 108 | } 109 | 110 | public int getCount() { 111 | return count; 112 | } 113 | } 114 | ``` 115 | **②多线程环境使用原子类保证线程安全(基本数据类型)** 116 | 117 | ```java 118 | class Test2 { 119 | private AtomicInteger count = new AtomicInteger(); 120 | 121 | public void increment() { 122 | count.incrementAndGet(); 123 | } 124 | //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。 125 | public int getCount() { 126 | return count.get(); 127 | } 128 | } 129 | 130 | ``` 131 | #### 2.4 AtomicInteger 线程安全原理简单分析 132 | 133 | AtomicInteger 类的部分源码: 134 | 135 | ```java 136 | // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) 137 | private static final Unsafe unsafe = Unsafe.getUnsafe(); 138 | private static final long valueOffset; 139 | 140 | static { 141 | try { 142 | valueOffset = unsafe.objectFieldOffset 143 | (AtomicInteger.class.getDeclaredField("value")); 144 | } catch (Exception ex) { throw new Error(ex); } 145 | } 146 | 147 | private volatile int value; 148 | ``` 149 | 150 | AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 151 | 152 | CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 153 | 154 | 155 | ### 3 数组类型原子类 156 | 157 | #### 3.1 数组类型原子类介绍 158 | 159 | 使用原子的方式更新数组里的某个元素 160 | 161 | 162 | - AtomicIntegerArray:整形数组原子类 163 | - AtomicLongArray:长整形数组原子类 164 | - AtomicReferenceArray :引用类型数组原子类 165 | 166 | 上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。 167 | 168 | **AtomicIntegerArray 类常用方法** 169 | 170 | ```java 171 | public final int get(int i) //获取 index=i 位置元素的值 172 | public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue 173 | public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增 174 | public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减 175 | public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值 176 | boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update) 177 | public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 178 | ``` 179 | #### 3.2 AtomicIntegerArray 常见方法使用 180 | 181 | ```java 182 | 183 | import java.util.concurrent.atomic.AtomicIntegerArray; 184 | 185 | public class AtomicIntegerArrayTest { 186 | 187 | public static void main(String[] args) { 188 | // TODO Auto-generated method stub 189 | int temvalue = 0; 190 | int[] nums = { 1, 2, 3, 4, 5, 6 }; 191 | AtomicIntegerArray i = new AtomicIntegerArray(nums); 192 | for (int j = 0; j < nums.length; j++) { 193 | System.out.println(i.get(j)); 194 | } 195 | temvalue = i.getAndSet(0, 2); 196 | System.out.println("temvalue:" + temvalue + "; i:" + i); 197 | temvalue = i.getAndIncrement(0); 198 | System.out.println("temvalue:" + temvalue + "; i:" + i); 199 | temvalue = i.getAndAdd(0, 5); 200 | System.out.println("temvalue:" + temvalue + "; i:" + i); 201 | } 202 | 203 | } 204 | ``` 205 | 206 | ### 4 引用类型原子类 207 | 208 | #### 4.1 引用类型原子类介绍 209 | 210 | 基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。 211 | 212 | - AtomicReference:引用类型原子类 213 | - AtomicStampedRerence:原子更新引用类型里的字段原子类 214 | - AtomicMarkableReference :原子更新带有标记位的引用类型 215 | 216 | 上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。 217 | 218 | #### 4.2 AtomicReference 类使用示例 219 | 220 | ```java 221 | import java.util.concurrent.atomic.AtomicReference; 222 | 223 | public class AtomicReferenceTest { 224 | 225 | public static void main(String[] args) { 226 | AtomicReference ar = new AtomicReference(); 227 | Person person = new Person("SnailClimb", 22); 228 | ar.set(person); 229 | Person updatePerson = new Person("Daisy", 20); 230 | ar.compareAndSet(person, updatePerson); 231 | 232 | System.out.println(ar.get().getName()); 233 | System.out.println(ar.get().getAge()); 234 | } 235 | } 236 | 237 | class Person { 238 | private String name; 239 | private int age; 240 | 241 | public Person(String name, int age) { 242 | super(); 243 | this.name = name; 244 | this.age = age; 245 | } 246 | 247 | public String getName() { 248 | return name; 249 | } 250 | 251 | public void setName(String name) { 252 | this.name = name; 253 | } 254 | 255 | public int getAge() { 256 | return age; 257 | } 258 | 259 | public void setAge(int age) { 260 | this.age = age; 261 | } 262 | 263 | } 264 | ``` 265 | 上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下: 266 | 267 | ``` 268 | Daisy 269 | 20 270 | ``` 271 | 272 | 273 | ### 5 对象的属性修改类型原子类 274 | 275 | #### 5.1 对象的属性修改类型原子类介绍 276 | 277 | 如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。 278 | 279 | - AtomicIntegerFieldUpdater:原子更新整形字段的更新器 280 | - AtomicLongFieldUpdater:原子更新长整形字段的更新器 281 | - AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 282 | 283 | 要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。 284 | 285 | 上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。 286 | 287 | #### 5.2 AtomicIntegerFieldUpdater 类使用示例 288 | 289 | ```java 290 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 291 | 292 | public class AtomicIntegerFieldUpdaterTest { 293 | public static void main(String[] args) { 294 | AtomicIntegerFieldUpdater a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); 295 | 296 | User user = new User("Java", 22); 297 | System.out.println(a.getAndIncrement(user));// 22 298 | System.out.println(a.get(user));// 23 299 | } 300 | } 301 | 302 | class User { 303 | private String name; 304 | public volatile int age; 305 | 306 | public User(String name, int age) { 307 | super(); 308 | this.name = name; 309 | this.age = age; 310 | } 311 | 312 | public String getName() { 313 | return name; 314 | } 315 | 316 | public void setName(String name) { 317 | this.name = name; 318 | } 319 | 320 | public int getAge() { 321 | return age; 322 | } 323 | 324 | public void setAge(int age) { 325 | this.age = age; 326 | } 327 | 328 | } 329 | ``` 330 | 331 | 输出结果: 332 | 333 | ``` 334 | 22 335 | 23 336 | ``` 337 | 338 | -------------------------------------------------------------------------------- /Java相关/final、static、this、super.md: -------------------------------------------------------------------------------- 1 | ## final 关键字 2 | 3 | **final关键字主要用在三个地方:变量、方法、类。** 4 | 5 | 1. **对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。** 6 | 7 | 2. **当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。** 8 | 9 | 3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 10 | 11 | ## static 关键字 12 | 13 | **static 关键字主要有以下四种使用场景:** 14 | 15 | 1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` 16 | 2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. 17 | 3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 18 | 4. **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 19 | 20 | ## this 关键字 21 | 22 | this关键字用于引用类的当前实例。 例如: 23 | 24 | ```java 25 | class Manager { 26 | Employees[] employees; 27 | 28 | void manageEmployees() { 29 | int totalEmp = this.employees.length; 30 | System.out.println("Total employees: " + totalEmp); 31 | this.report(); 32 | } 33 | 34 | void report() { } 35 | } 36 | ``` 37 | 38 | 在上面的示例中,this关键字用于两个地方: 39 | 40 | - this.employees.length:访问类Manager的当前实例的变量。 41 | - this.report():调用类Manager的当前实例的方法。 42 | 43 | 此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。 44 | 45 | 46 | 47 | ## super 关键字 48 | 49 | super关键字用于从子类访问父类的变量和方法。 例如: 50 | 51 | ```java 52 | public class Super { 53 | protected int number; 54 | 55 | protected showNumber() { 56 | System.out.println("number = " + number); 57 | } 58 | } 59 | 60 | public class Sub extends Super { 61 | void bar() { 62 | super.number = 10; 63 | super.showNumber(); 64 | } 65 | } 66 | ``` 67 | 68 | 在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber()` 方法。 69 | 70 | **使用 this 和 super 要注意的问题:** 71 | 72 | - super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用本类中的其他构造方法时,也要放在首行。 73 | - this、super不能用在static方法中。 74 | 75 | **简单解释一下:** 76 | 77 | 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。 78 | 79 | 80 | 81 | ## 参考 82 | 83 | - https://www.codejava.net/java-core/the-java-language/java-keywords 84 | - https://blog.csdn.net/u013393958/article/details/79881037 85 | -------------------------------------------------------------------------------- /Java相关/static.md: -------------------------------------------------------------------------------- 1 | 2 | # static 关键字 3 | 4 | ## static 关键字主要有以下四种使用场景 5 | 6 | 1. 修饰成员变量和成员方法 7 | 2. 静态代码块 8 | 3. 修饰类(只能修饰内部类) 9 | 4. 静态导包(用来导入类中的静态资源,1.5之后的新特性) 10 | 11 | ### 修饰成员变量和成员方法(常用) 12 | 13 | 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。 14 | 15 | 方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 16 | 17 | HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 18 | 19 | 20 | 21 | 调用格式: 22 | 23 | - 类名.静态变量名 24 | - 类名.静态方法名() 25 | 26 | 如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被方法。 27 | 28 | 测试方法: 29 | 30 | ```java 31 | public class StaticBean { 32 | 33 | String name; 34 | 静态变量 35 | static int age; 36 | 37 | public StaticBean(String name) { 38 | this.name = name; 39 | } 40 | 静态方法 41 | static void SayHello() { 42 | System.out.println(Hello i am java); 43 | } 44 | @Override 45 | public String toString() { 46 | return StaticBean{ + 47 | name=' + name + ''' + age + age + 48 | '}'; 49 | } 50 | } 51 | ``` 52 | 53 | ```java 54 | public class StaticDemo { 55 | 56 | public static void main(String[] args) { 57 | StaticBean staticBean = new StaticBean(1); 58 | StaticBean staticBean2 = new StaticBean(2); 59 | StaticBean staticBean3 = new StaticBean(3); 60 | StaticBean staticBean4 = new StaticBean(4); 61 | StaticBean.age = 33; 62 | StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33} 63 | System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4); 64 | StaticBean.SayHello();Hello i am java 65 | } 66 | 67 | } 68 | ``` 69 | 70 | 71 | ### 静态代码块 72 | 73 | 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次. 74 | 75 | 静态代码块的格式是 76 | 77 | ``` 78 | static { 79 | 语句体; 80 | } 81 | ``` 82 | 83 | 84 | 一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。 85 | 86 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/88531075.jpg) 87 | 88 | 静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问. 89 | 90 | 91 | ### 静态内部类 92 | 93 | 静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着: 94 | 95 | 1. 它的创建是不需要依赖外围类的创建。 96 | 2. 它不能使用任何外围类的非static成员变量和方法。 97 | 98 | 99 | Example(静态内部类实现单例模式) 100 | 101 | ```java 102 | public class Singleton { 103 | 104 | 声明为 private 避免调用默认构造方法创建对象 105 | private Singleton() { 106 | } 107 | 108 | 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问 109 | private static class SingletonHolder { 110 | private static final Singleton INSTANCE = new Singleton(); 111 | } 112 | 113 | public static Singleton getUniqueInstance() { 114 | return SingletonHolder.INSTANCE; 115 | } 116 | } 117 | ``` 118 | 119 | 当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。 120 | 121 | 这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。 122 | 123 | ### 静态导包 124 | 125 | 格式为:import static 126 | 127 | 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法 128 | 129 | ```java 130 | 131 | 132 | Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用 133 | 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可 134 | 135 | import static java.lang.Math.; 136 | 137 | 换成import static java.lang.Math.max;具有一样的效果 138 | 139 | public class Demo { 140 | public static void main(String[] args) { 141 | 142 | int max = max(1,2); 143 | System.out.println(max); 144 | } 145 | } 146 | 147 | ``` 148 | 149 | 150 | ## 补充内容 151 | 152 | ### 静态方法与非静态方法 153 | 154 | 静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。 155 | 156 | Example 157 | 158 | ```java 159 | class Foo { 160 | int i; 161 | public Foo(int i) { 162 | this.i = i; 163 | } 164 | 165 | public static String method1() { 166 | return An example string that doesn't depend on i (an instance variable); 167 | 168 | } 169 | 170 | public int method2() { 171 | return this.i + 1; Depends on i 172 | } 173 | 174 | } 175 | ``` 176 | 你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:`Foo bar = new Foo(1);bar.method2();` 177 | 178 | 总结: 179 | 180 | - 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 181 | - 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 182 | 183 | ### static{}静态代码块与{}非静态代码块(构造代码块) 184 | 185 | 相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。 186 | 187 | 不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。 188 | 189 | 一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的. 190 | 191 | Example 192 | 193 | ```java 194 | public class Test { 195 | public Test() { 196 | System.out.print(默认构造方法!--); 197 | } 198 | 199 | 非静态代码块 200 | { 201 | System.out.print(非静态代码块!--); 202 | } 203 | 静态代码块 204 | static { 205 | System.out.print(静态代码块!--); 206 | } 207 | 208 | public static void test() { 209 | System.out.print(静态方法中的内容! --); 210 | { 211 | System.out.print(静态方法中的代码块!--); 212 | } 213 | 214 | } 215 | public static void main(String[] args) { 216 | 217 | Test test = new Test(); 218 | Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!-- 219 | } 220 | ``` 221 | 222 | 当执行 `Test.test();` 时输出: 223 | 224 | ``` 225 | 静态代码块!--静态方法中的内容! --静态方法中的代码块!-- 226 | ``` 227 | 228 | 当执行 `Test test = new Test();` 时输出: 229 | 230 | ``` 231 | 静态代码块!--非静态代码块!--默认构造方法!-- 232 | ``` 233 | 234 | 235 | 非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。 236 | 237 | ### 参考 238 | 239 | - httpsblog.csdn.netchen13579867831articledetails78995480 240 | - httpwww.cnblogs.comchenssyp3388487.html 241 | - httpwww.cnblogs.comQian123p5713440.html 242 | -------------------------------------------------------------------------------- /Java相关/synchronized.md: -------------------------------------------------------------------------------- 1 | 以下内容摘自我的 Gitchat :[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb),欢迎订阅! 2 | 3 | Github 地址:[https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md](https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md) 4 | 5 | ![Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/%E4%BA%8C%20%20Synchronized%20%E5%85%B3%E9%94%AE%E5%AD%97%E4%BD%BF%E7%94%A8%E3%80%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E3%80%81JDK1.6%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%BA%95%E5%B1%82%E4%BC%98%E5%8C%96%E4%BB%A5%E5%8F%8A%20%E5%92%8CReenTrantLock%20%E7%9A%84%E5%AF%B9%E6%AF%94.png) 6 | 7 | ### synchronized关键字最主要的三种使用方式的总结 8 | 9 | - **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** 10 | - **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 11 | - **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! 12 | 13 | 下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 14 | 15 | 面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单利模式的原理呗!” 16 | 17 | 18 | 19 | **双重校验锁实现对象单例(线程安全)** 20 | 21 | ```java 22 | public class Singleton { 23 | 24 | private volatile static Singleton uniqueInstance; 25 | 26 | private Singleton() { 27 | } 28 | 29 | public static Singleton getUniqueInstance() { 30 | //先判断对象是否已经实例过,没有实例化过才进入加锁代码 31 | if (uniqueInstance == null) { 32 | //类对象加锁 33 | synchronized (Singleton.class) { 34 | if (uniqueInstance == null) { 35 | uniqueInstance = new Singleton(); 36 | } 37 | } 38 | } 39 | return uniqueInstance; 40 | } 41 | } 42 | ``` 43 | 另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 44 | 45 | uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: 46 | 47 | 1. 为 uniqueInstance 分配内存空间 48 | 2. 初始化 uniqueInstance 49 | 3. 将 uniqueInstance 指向分配的内存地址 50 | 51 | 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 52 | 53 | 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 54 | 55 | 56 | ###synchronized 关键字底层原理总结 57 | 58 | 59 | 60 | **synchronized 关键字底层原理属于 JVM 层面。** 61 | 62 | **① synchronized 同步语句块的情况** 63 | 64 | ```java 65 | public class SynchronizedDemo { 66 | public void method() { 67 | synchronized (this) { 68 | System.out.println("synchronized 代码块"); 69 | } 70 | } 71 | } 72 | 73 | ``` 74 | 75 | 通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 76 | 77 | ![synchronized 关键字原理](https://images.gitbook.cn/abc37c80-d21d-11e8-aab3-09d30029e0d5) 78 | 79 | 从上面我们可以看出: 80 | 81 | **synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 82 | 83 | **② synchronized 修饰方法的的情况** 84 | 85 | ```java 86 | public class SynchronizedDemo2 { 87 | public synchronized void method() { 88 | System.out.println("synchronized 方法"); 89 | } 90 | } 91 | 92 | ``` 93 | 94 | ![synchronized 关键字原理](https://images.gitbook.cn/7d407bf0-d21e-11e8-b2d6-1188c7e0dd7e) 95 | 96 | synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 97 | 98 | 99 | 在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 100 | 101 | 102 | ### JDK1.6 之后的底层优化 103 | 104 | JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 105 | 106 | 锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 107 | 108 | **①偏向锁** 109 | 110 | **引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 111 | 112 | 偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步!关于偏向锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。 113 | 114 | 但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。 115 | 116 | **② 轻量级锁** 117 | 118 | 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。 119 | 120 | **轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!** 121 | 122 | **③ 自旋锁和自适应自旋** 123 | 124 | 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 125 | 126 | 互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。 127 | 128 | **一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。 129 | 130 | 百度百科对自旋锁的解释: 131 | 132 | > 何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。 133 | 134 | 自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的,需要通过`--XX:+UseSpinning`参数来开启。JDK1.6及1.6之后,就改为默认开启的了。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。如果锁被占用的时间短,那么效果当然就很好了!反之,相反!自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁,就应该挂起线程。**自旋次数的默认值是10次,用户可以修改`--XX:PreBlockSpin`来更改**。 135 | 136 | 另外,**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 137 | 138 | **④ 锁消除** 139 | 140 | 锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 141 | 142 | **⑤ 锁粗化** 143 | 144 | 原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 145 | 146 | 大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 147 | 148 | ### Synchronized 和 ReenTrantLock 的对比 149 | 150 | 151 | **① 两者都是可重入锁** 152 | 153 | 两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 154 | 155 | **② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** 156 | 157 | synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 158 | 159 | **③ ReenTrantLock 比 synchronized 增加了一些高级功能** 160 | 161 | 相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** 162 | 163 | - **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 164 | - **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 165 | - synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 166 | 167 | 如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 168 | 169 | **④ 性能已不是选择标准** 170 | 171 | 在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作**。 172 | -------------------------------------------------------------------------------- /Java相关/多线程系列.md: -------------------------------------------------------------------------------- 1 | > ## 多线程系列文章 2 | 下列文章,我都更新在了我的博客专栏:[Java并发编程指南](https://blog.csdn.net/column/details/20860.html)。 3 | 4 | 1. [Java多线程学习(一)Java多线程入门](http://blog.csdn.net/qq_34337272/article/details/79640870) 5 | 2. [Java多线程学习(二)synchronized关键字(1)](http://blog.csdn.net/qq_34337272/article/details/79655194) 6 | 3. [Java多线程学习(二)synchronized关键字(2)](http://blog.csdn.net/qq_34337272/article/details/79670775) 7 | 4. [Java多线程学习(三)volatile关键字](http://blog.csdn.net/qq_34337272/article/details/79680771) 8 | 5. [Java多线程学习(四)等待/通知(wait/notify)机制](http://blog.csdn.net/qq_34337272/article/details/79690279) 9 | 10 | 6. [Java多线程学习(五)线程间通信知识点补充](http://blog.csdn.net/qq_34337272/article/details/79694226) 11 | 7. [Java多线程学习(六)Lock锁的使用](http://blog.csdn.net/qq_34337272/article/details/79714196) 12 | 8. [Java多线程学习(七)并发编程中一些问题](https://blog.csdn.net/qq_34337272/article/details/79844051) 13 | 9. [Java多线程学习(八)线程池与Executor 框架](https://blog.csdn.net/qq_34337272/article/details/79959271) 14 | 15 | 16 | > ## 多线程系列文章重要知识点与思维导图 17 | 18 | ### Java多线程学习(一)Java多线程入门 19 | 20 | ![](https://user-gold-cdn.xitu.io/2018/8/4/16504e0cb6bac32e?w=758&h=772&f=jpeg&s=247210) 21 | 22 | ### Java多线程学习(二)synchronized关键字(1) 23 | ![](https://user-gold-cdn.xitu.io/2018/8/4/16504e245ceb3ea9?w=1028&h=490&f=jpeg&s=203811) 24 | 25 | 注意:**可重入锁的概念**。 26 | 27 | 另外要注意:**synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。** 如果多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定,因为多个对象会产生多个锁。 28 | 29 | ### Java多线程学习(二)synchronized关键字(2) 30 | 31 | ![思维导图](https://user-gold-cdn.xitu.io/2018/8/4/16504e3d98213324?w=1448&h=439&f=jpeg&s=245012) 32 | 33 | **注意:** 34 | 35 | - 其他线程执行对象中**synchronized同步方法**(上一节我们介绍过,需要回顾的可以看上一节的文章)和**synchronized(this)代码块**时呈现同步效果; 36 | - **如果两个线程使用了同一个“对象监视器”(synchronized(object)),运行结果同步,否则不同步**. 37 | 38 | **synchronized关键字加到static静态方法**和**synchronized(class)代码块**上都是是给**Class类**上锁,而**synchronized关键字加到非static静态方法**上是给**对象**上锁。 39 | 40 | 数据类型String的常量池属性:**在Jvm中具有String常量池缓存的功能** 41 | 42 | ### Java多线程学习(三)volatile关键字 43 | 44 | ![volatile关键字](https://user-gold-cdn.xitu.io/2018/8/4/16504e4ab69d8d58) 45 | **注意:** 46 | 47 | **synchronized关键字**和**volatile关键字**比较 48 | 49 | ### Java多线程学习(四)等待/通知(wait/notify)机制 50 | 51 | ![本节思维导图](https://user-gold-cdn.xitu.io/2018/3/25/1625d2a9188ec021?w=1254&h=452&f=jpeg&s=229471) 52 | 53 | ### Java多线程学习(五)线程间通信知识点补充 54 | 55 | ![本节思维导图](https://user-gold-cdn.xitu.io/2018/8/4/16504e618d6886c5?w=1146&h=427&f=jpeg&s=220573) 56 | **注意:** ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。 57 | 58 | ### Java多线程学习(六)Lock锁的使用 59 | 60 | ![本节思维导图](https://user-gold-cdn.xitu.io/2018/3/27/1626755a8e9a8774?w=1197&h=571&f=jpeg&s=258439) 61 | 62 | ### Java多线程学习(七)并发编程中一些问题 63 | 64 | ![思维导图](https://user-gold-cdn.xitu.io/2018/4/7/162a01b71ebc4842?w=1067&h=517&f=png&s=36857) 65 | 66 | ### Java多线程学习(八)线程池与Executor 框架 67 | 68 | ![本节思维导图](https://user-gold-cdn.xitu.io/2018/5/31/163b4379a605fa18?w=1560&h=752&f=png&s=56361) 69 | 70 | -------------------------------------------------------------------------------- /Java相关/设计模式.md: -------------------------------------------------------------------------------- 1 | 下面是自己学习设计模式的时候做的总结,有些是自己的原创文章,有些是网上写的比较好的文章,保存下来细细消化吧! 2 | 3 | ## 创建型模式: 4 | 5 | > ### 创建型模式概述: 6 | 7 | - 创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。 8 | - 创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。 9 | 10 | ![创建型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640641afcb7559b?w=491&h=241&f=png&s=51443) 11 | 12 | > ### 创建型模式系列文章推荐: 13 | 14 | - **单例模式:** 15 | 16 | [深入理解单例模式——只有一个实例](https://blog.csdn.net/qq_34337272/article/details/80455972) 17 | 18 | - **工厂模式:** 19 | 20 | [深入理解工厂模式——由对象工厂生成对象](https://blog.csdn.net/qq_34337272/article/details/80472071) 21 | 22 | - **建造者模式:** 23 | 24 | [深入理解建造者模式 ——组装复杂的实例](http://blog.csdn.net/qq_34337272/article/details/80540059) 25 | 26 | - **原型模式:** 27 | 28 | [深入理解原型模式 ——通过复制生成实例](https://blog.csdn.net/qq_34337272/article/details/80706444) 29 | 30 | 31 | ## 结构型模式: 32 | 33 | > ### 结构型模式概述: 34 | 35 | - **结构型模式(Structural Pattern):** 描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构 36 | ![结构型模式(Structural Pattern)](https://user-gold-cdn.xitu.io/2018/6/16/164064d6b3c205e3?w=719&h=233&f=png&s=270293) 37 | - **结构型模式可以分为类结构型模式和对象结构型模式:** 38 | - 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。 39 | - 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。 40 | 41 | ![结构型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640655459d766d2?w=378&h=266&f=png&s=59652) 42 | 43 | > ### 结构型模式系列文章推荐: 44 | 45 | - **适配器模式:** 46 | 47 | [深入理解适配器模式——加个“适配器”以便于复用](https://segmentfault.com/a/1190000011856448) 48 | 49 | [适配器模式原理及实例介绍-IBM](https://www.ibm.com/developerworks/cn/java/j-lo-adapter-pattern/index.html) 50 | 51 | - **桥接模式:** 52 | 53 | [设计模式笔记16:桥接模式(Bridge Pattern)](https://blog.csdn.net/yangzl2008/article/details/7670996) 54 | 55 | - **组合模式:** 56 | 57 | [大话设计模式—组合模式](https://blog.csdn.net/lmb55/article/details/51039781) 58 | 59 | - **装饰模式:** 60 | 61 | [java模式—装饰者模式](https://www.cnblogs.com/chenxing818/p/4705919.html) 62 | 63 | [Java设计模式-装饰者模式](https://blog.csdn.net/cauchyweierstrass/article/details/48240147) 64 | 65 | - **外观模式:** 66 | 67 | [java设计模式之外观模式(门面模式)](https://www.cnblogs.com/lthIU/p/5860607.html) 68 | 69 | - **享元模式:** 70 | 71 | [享元模式](http://www.jasongj.com/design_pattern/flyweight/) 72 | 73 | - **代理模式:** 74 | 75 | [代理模式原理及实例讲解 (IBM出品,很不错)](https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html) 76 | 77 | [轻松学,Java 中的代理模式及动态代理](https://blog.csdn.net/briblue/article/details/73928350) 78 | 79 | [Java代理模式及其应用](https://blog.csdn.net/justloveyou_/article/details/74203025) 80 | 81 | 82 | ## 行为型模式 83 | 84 | > ### 行为型模式概述: 85 | 86 | - 行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。 87 | - 行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。 88 | - 通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。 89 | 90 | **行为型模式分为类行为型模式和对象行为型模式两种:** 91 | 92 | - **类行为型模式:** 类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。 93 | - **对象行为型模式:** 对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。 94 | 95 | ![行为型模式](https://user-gold-cdn.xitu.io/2018/6/28/164467dd92c6172c?w=453&h=269&f=png&s=63270) 96 | 97 | - **职责链模式:** 98 | 99 | [Java设计模式之责任链模式、职责链模式](https://blog.csdn.net/jason0539/article/details/45091639) 100 | 101 | [责任链模式实现的三种方式](https://www.cnblogs.com/lizo/p/7503862.html) 102 | 103 | - **命令模式:** 104 | 105 | 106 | 107 | - **解释器模式:** 108 | - **迭代器模式:** 109 | - **中介者模式:** 110 | - **备忘录模式:** 111 | - **观察者模式:** 112 | - **状态模式:** 113 | - **策略模式:** 114 | - **模板方法模式:** 115 | - **访问者模式:** 116 | 117 | -------------------------------------------------------------------------------- /主流框架/Spring学习与面试.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Spring相关教程/资料: 4 | 5 | > ## 官网相关 6 | 7 | [Spring官网](https://spring.io/) 8 | 9 | [Spring系列主要项目](https://spring.io/projects) 10 | 11 | 从配置到安全性,Web应用到大数据 - 无论您的应用程序的基础架构需求如何,都有一个Spring Project来帮助您构建它。 从小处着手,根据需要使用 - Spring是通过设计模块化的。 12 | 13 | [Spring官网指南](https://spring.io/guides) 14 | 15 | 无论您在构建什么,这些指南都旨在尽可能快地提高您的工作效率 - 使用Spring团队推荐的最新Spring项目发布和技术。 16 | 17 | [Spring官方文档翻译(1~6章)](https://blog.csdn.net/tangtong1/article/details/51326887) 18 | 19 | > ## 系统学习教程: 20 | 21 | ### 文档: 22 | 23 | [极客学院Spring Wiki](http://wiki.jikexueyuan.com/project/spring/transaction-management.html) 24 | 25 | [Spring W3Cschool教程 ](https://www.w3cschool.cn/wkspring/f6pk1ic8.html) 26 | 27 | ### 视频: 28 | 29 | [网易云课堂——58集精通java教程Spring框架开发](http://study.163.com/course/courseMain.htm?courseId=1004475015#/courseDetail?tab=1&35) 30 | 31 | [慕课网相关视频](https://www.imooc.com/) 32 | 33 | **黑马视频(非常推荐):** 34 | 微信公众号:“**Java面试通关手册**”后台回复“**资源分享第一波**”免费领取。 35 | 36 | > ## 一些常用的东西 37 | 38 | [Spring Framework 4.3.17.RELEASE API](https://docs.spring.io/spring/docs/4.3.17.RELEASE/javadoc-api/) 39 | 40 | 默认浏览器打开,当需要查某个类的作用的时候,可以在浏览器通过ctrl+f搜索。 41 | 42 | 43 | # 面试必备知识点 44 | 45 | 46 | > ## SpringAOP,IOC实现原理 47 | 48 | AOP实现原理、动态代理和静态代理、Spring IOC的初始化过程、IOC原理、自己实现怎么实现一个IOC容器?这些东西都是经常会被问到的。 49 | 50 | [自己动手实现的 Spring IOC 和 AOP - 上篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-上篇/) 51 | 52 | [自己动手实现的 Spring IOC 和 AOP - 下篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-下篇/) 53 | 54 | ### AOP: 55 | 56 | AOP思想的实现一般都是基于 **代理模式** ,在JAVA中一般采用JDK动态代理模式,但是我们都知道,**JDK动态代理模式只能代理接口而不能代理类**。因此,Spring AOP 会这样子来进行切换,因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。 57 | 58 | - 如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类; 59 | - 如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者完全透明、开发者也无需关心。 60 | 61 | 62 | 63 | [※静态代理、JDK动态代理、CGLIB动态代理讲解](http://www.cnblogs.com/puyangsky/p/6218925.html) 64 | 65 | 我们知道AOP思想的实现一般都是基于 **代理模式** ,所以在看下面的文章之前建议先了解一下静态代理以及JDK动态代理、CGLIB动态代理的实现方式。 66 | 67 | [Spring AOP 入门](https://juejin.im/post/5aa7818af265da23844040c6) 68 | 69 | 带你入门的一篇文章。这篇文章主要介绍了AOP中的基本概念:5种类型的通知(Before,After,After-returning,After-throwing,Around);Spring中对AOP的支持:AOP思想的实现一般都是基于代理模式,在JAVA中一般采用JDK动态代理模式,Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理, 70 | 71 | [※Spring AOP 基于AspectJ注解如何实现AOP](https://juejin.im/post/5a55af9e518825734d14813f) 72 | 73 | 74 | **AspectJ是一个AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)**,可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易 75 | 76 | Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。 77 | 78 | 79 | [※探秘Spring AOP(慕课网视频,很不错)](https://www.imooc.com/learn/869) 80 | 81 | 慕课网视频,讲解的很不错,详细且深入 82 | 83 | 84 | [spring源码剖析(六)AOP实现原理剖析](https://blog.csdn.net/fighterandknight/article/details/51209822) 85 | 86 | 通过源码分析Spring AOP的原理 87 | 88 | ### IOC: 89 | 90 | Spring IOC的初始化过程: 91 | ![Spring IOC的初始化过程](https://user-gold-cdn.xitu.io/2018/5/22/16387903ee72c831?w=709&h=56&f=png&s=4673) 92 | 93 | [[Spring框架]Spring IOC的原理及详解。](https://www.cnblogs.com/wang-meng/p/5597490.html) 94 | 95 | [Spring IOC核心源码学习](https://yikun.github.io/2015/05/29/Spring-IOC核心源码学习/) 96 | 97 | 比较简短,推荐阅读。 98 | 99 | [Spring IOC 容器源码分析](https://javadoop.com/post/spring-ioc) 100 | 101 | 强烈推荐,内容详尽,而且便于阅读。 102 | 103 | > ## Spring事务管理 104 | 105 | [可能是最漂亮的Spring事务管理详解](https://juejin.im/post/5b00c52ef265da0b95276091) 106 | 107 | [Spring编程式和声明式事务实例讲解](https://juejin.im/post/5b010f27518825426539ba38) 108 | 109 | > ## 其他 110 | 111 | **Spring单例与线程安全:** 112 | 113 | [Spring框架中的单例模式(源码解读)](http://www.cnblogs.com/chengxuyuanzhilu/p/6404991.html) 114 | 115 | 单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例。spring依赖注入时,使用了 多重判断加锁 的单例模式。 116 | 117 | > ## Spring源码阅读 118 | 119 | 阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己字面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。 120 | 121 | ### [Spring源码阅读](https://github.com/seaswalker/Spring) 122 | - [spring-core](https://github.com/seaswalker/Spring/blob/master/note/Spring.md) 123 | - [spring-aop](https://github.com/seaswalker/Spring/blob/master/note/spring-aop.md) 124 | - [spring-context](https://github.com/seaswalker/Spring/blob/master/note/spring-context.md) 125 | - [spring-task](https://github.com/seaswalker/Spring/blob/master/note/spring-task.md) 126 | - [spring-transaction](https://github.com/seaswalker/Spring/blob/master/note/spring-transaction.md) 127 | - [spring-mvc](https://github.com/seaswalker/Spring/blob/master/note/spring-mvc.md) 128 | - [guava-cache](https://github.com/seaswalker/Spring/blob/master/note/guava-cache.md) 129 | -------------------------------------------------------------------------------- /主流框架/ZooKeeper.md: -------------------------------------------------------------------------------- 1 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/56385654.jpg) 2 | ## 前言 3 | 4 | 相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 是个什么东西吗?如果别人/面试官让你给他讲讲 ZooKeeper 是个什么东西,你能回答到什么地步呢? 5 | 6 | 我本人曾经使用过 ZooKeeper 作为 Dubbo 的注册中心,另外在搭建 solr 集群的时候,我使用到了 ZooKeeper 作为 solr 集群的管理工具。前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?想了半天,脑海中只是简单的能浮现出几句话:“①Zookeeper 可以被用作注册中心。 ②Zookeeper 是 Hadoop 生态系统的一员;③构建 Zookeeper 集群的时候,使用的服务器最好是奇数台。” 可见,我对于 Zookeeper 的理解仅仅是停留在了表面。 7 | 8 | 所以,**通过本文,希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ,那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ,那么本文将带你回顾一下 ZooKeeper 的一些基础概念。** 9 | 10 | 最后,**本文只涉及 ZooKeeper 的一些概念,并不涉及 ZooKeeper 的使用以及 ZooKeeper 集群的搭建。** 网上有介绍 ZooKeeper 的使用以及搭建 ZooKeeper 集群的文章,大家有需要可以自行查阅。 11 | 12 | ## 一 什么是 ZooKeeper 13 | 14 | ### ZooKeeper 的由来 15 | 16 | **下面这段内容摘自《从Paxos到Zookeeper 》第四章第一节的某段内容,推荐大家阅读以下:** 17 | 18 | > Zookeeper最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,**雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。** 19 | > 20 | >关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家RaghuRamakrishnan开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,**雅虎的整个分布式系统看上去就像一个大型的动物园了,而Zookeeper正好要用来进行分布式环境的协调一一于是,Zookeeper的名字也就由此诞生了。** 21 | 22 | 23 | ### 1.1 ZooKeeper 概览 24 | 25 | ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在“Yahoo!"上构建的,用于以简单而稳健的方式访问他们的应用程序。 后来,Apache ZooKeeper成为Hadoop,HBase和其他分布式框架使用的有组织服务的标准。 例如,Apache HBase使用ZooKeeper跟踪分布式数据的状态。**ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。** 26 | 27 | > **原语:** 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。 28 | 29 | **ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。** 30 | 31 | **Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。** 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。 32 | 33 | ![Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/35571782.jpg) 34 | 35 | ### 1.2 结合个人使用情况的讲一下 ZooKeeper 36 | 37 | 在我自己做过的项目中,主要使用到了 ZooKeeper 作为 Dubbo 的注册中心(Dubbo 官方推荐使用 ZooKeeper注册中心)。另外在搭建 solr 集群的时候,我使用 ZooKeeper 作为 solr 集群的管理工具。这时,ZooKeeper 主要提供下面几个功能:1、集群管理:容错、负载均衡。2、配置文件的集中管理3、集群的入口。 38 | 39 | 40 | 我个人觉得在使用 ZooKeeper 的时候,最好是使用 集群版的 ZooKeeper 而不是单机版的。官网给出的架构图就描述的是一个集群版的 ZooKeeper 。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。 41 | 42 | **为什么最好使用奇数台服务器构成 ZooKeeper 集群?** 43 | 44 | 所谓的zookeeper容错是指,当宕掉几个zookeeper服务器之后,剩下的个数必须大于宕掉的个数的话整个zookeeper才依然可用。假如我们的集群中有n台zookeeper服务器,那么也就是剩下的服务数必须大于n/2。先说一下结论,2n和2n-1的容忍度是一样的,都是n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。 45 | 比如假如我们有3台,那么最大允许宕掉1台zookeeper服务器,如果我们有4台的的时候也同样只允许宕掉1台。 46 | 假如我们有5台,那么最大允许宕掉2台zookeeper服务器,如果我们有6台的的时候也同样只允许宕掉2台。 47 | 48 | 综上,何必增加那一个不必要的zookeeper呢? 49 | 50 | 51 | 52 | ## 二 关于 ZooKeeper 的一些重要概念 53 | 54 | ### 2.1 重要概念总结 55 | 56 | - **ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。** 57 | - **为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。** 58 | - **ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟**(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)。 59 | - **ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。**(“读”多于“写”是协调服务的典型场景。) 60 | - **ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。** 61 | - ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提交数据节点监听服务。 62 | 63 | **下面关于会话(Session)、 Znode、版本、Watcher、ACL概念的总结都在《从Paxos到Zookeeper 》第四章第一节以及第七章第八节有提到,感兴趣的可以看看!** 64 | 65 | ### 2.2 会话(Session) 66 | 67 | Session 指的是 ZooKeeper 服务器与客户端会话。**在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接**。客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。**通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的Watch事件通知。** Session的`sessionTimeout`值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,**只要在`sessionTimeout`规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。** 68 | 69 | **在为客户端创建会话之前,服务端首先会为每个客户端都分配一个sessionID。由于 sessionID 是 Zookeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。** 70 | 71 | ### 2.3 Znode 72 | 73 | **在谈到分布式的时候,我们通常说的“节点"是指组成集群的每一台机器。然而,在Zookeeper中,“节点"分为两类,第一类同样是指构成集群的机器,我们称之为机器节点;第二类则是指数据模型中的数据单元,我们称之为数据节点一一ZNode。** 74 | 75 | Zookeeper将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个Znode,例如/foo/path1。每个上都会保存自己的数据内容,同时还会保存一系列属性信息。 76 | 77 | **在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。**另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:**SEQUENTIAL**.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。 78 | 79 | ### 2.4 版本 80 | 81 | 在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 cversion(当前ZNode的ACL版本)。 82 | 83 | 84 | ### 2.5 Watcher 85 | 86 | **Watcher(事件监听器),是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。** 87 | 88 | ### 2.6 ACL 89 | 90 | Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。Zookeeper 定义了如下5种权限。 91 | 92 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/27473480.jpg) 93 | 94 | 其中尤其需要注意的是,CREATE和DELETE这两种权限都是针对子节点的权限控制。 95 | 96 | ## 三 ZooKeeper 特点 97 | 98 | - **顺序一致性:** 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 99 | - **原子性:** 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 100 | - **单一系统映像 :** 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 101 | - **可靠性:** 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 102 | 103 | ## 四 ZooKeeper 设计目标 104 | 105 | ### 4.1 简单的数据模型 106 | 107 | ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相互协调,这与标准文件系统类似。 名称空间由 ZooKeeper 中的数据寄存器组成 - 称为znode,这些类似于文件和目录。 与为存储设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟。 108 | 109 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/94251757.jpg) 110 | 111 | ### 4.2 可构建集群 112 | 113 | **为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么zookeeper本身仍然是可用的。** 客户端在使用 ZooKeeper 时,需要知道集群机器列表,通过与集群中的某一台机器建立 TCP 连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。 114 | 115 | **ZooKeeper 官方提供的架构图:** 116 | 117 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/68900686.jpg) 118 | 119 | 上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。 120 | 121 | ### 4.3 顺序访问 122 | 123 | **对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。** **这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)** 124 | 125 | ### 4.4 高性能 126 | 127 | **ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)** 128 | 129 | ## 五 ZooKeeper 集群角色介绍 130 | 131 | **最典型集群模式: Master/Slave 模式(主备模式)**。在这种模式中,通常 Master服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。 132 | 133 | 但是,**在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三种角色**。如下图所示 134 | 135 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/89602762.jpg) 136 | 137 | **ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。** 138 | 139 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-13/91622395.jpg) 140 | 141 | **当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。这个过程大致是这样的:** 142 | 143 | 1. Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。 144 | 2. Discovery(发现阶段):在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。 145 | 3. Synchronization(同步阶段):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后 146 | 准 leader 才会成为真正的 leader。 147 | 4. Broadcast(广播阶段) 148 | 到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。 149 | 150 | ## 六 ZooKeeper &ZAB 协议&Paxos算法 151 | 152 | ### 6.1 ZAB 协议&Paxos算法 153 | 154 | Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在ZooKeeper的官方文档中也指出,ZAB协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为Zookeeper设计的崩溃可恢复的原子消息广播算法。 155 | 156 | ### 6.2 ZAB 协议介绍 157 | 158 | **ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。** 159 | 160 | ### 6.3 ZAB 协议两种基本的模式:崩溃恢复和消息广播 161 | 162 | ZAB协议包括两种基本的模式,分别是 **崩溃恢复和消息广播**。当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,**所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致**。 163 | 164 | **当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。** 当一台同样遵守ZAB协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加人的服务器就会自觉地进人数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的,ZooKeeper设计成只允许唯一的一个Leader服务器来进行事务请求的处理。Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。 165 | 166 | 关于 **ZAB 协议&Paxos算法** 需要讲和理解的东西太多了,说实话,笔主到现在不太清楚这俩兄弟的具体原理和实现过程。推荐阅读下面两篇文章: 167 | 168 | - [图解 Paxos 一致性协议](http://blog.xiaohansong.com/2016/09/30/Paxos/) 169 | - [Zookeeper ZAB 协议分析](http://blog.xiaohansong.com/2016/08/25/zab/) 170 | 171 | 关于如何使用 zookeeper 实现分布式锁,可以查看下面这篇文章: 172 | 173 | - 174 | [10分钟看懂!基于Zookeeper的分布式锁](https://blog.csdn.net/qiangcuo6087/article/details/79067136) 175 | 176 | ## 六 总结 177 | 178 | 通过阅读本文,想必大家已从 **①ZooKeeper的由来。** -> **②ZooKeeper 到底是什么 。**-> **③ ZooKeeper 的一些重要概念**(会话(Session)、 Znode、版本、Watcher、ACL)-> **④ZooKeeper 的特点。** -> **⑤ZooKeeper 的设计目标。**-> **⑥ ZooKeeper 集群角色介绍** (Leader、Follower 和 Observer 三种角色)-> **⑦ZooKeeper &ZAB 协议&Paxos算法。** 这七点了解了 ZooKeeper 。 179 | 180 | ## 参考 181 | 182 | - 《从Paxos到Zookeeper 》 183 | - https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription 184 | - https://cwiki.apache.org/confluence/display/ZOOKEEPER/Index 185 | - https://www.cnblogs.com/raphael5200/p/5285583.html 186 | - https://zhuanlan.zhihu.com/p/30024403 187 | 188 | -------------------------------------------------------------------------------- /其他/2018 秋招.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 秋招历程流水账总结 4 | 5 | 笔主大四准毕业生,在秋招末流比较幸运地进入了一家自己非常喜欢一家公司——ThoughtWorks. 6 | 7 | ![今天去签约在门外拍的照片](https://images.gitbook.cn/1433af10-d5f7-11e8-841a-4f0b0cc7be7b) 8 | 9 | 从9-6号投递出去第一份简历,到10-18号左右拿到第一份 offer ,中间差不多有 1 个半月的时间了。可能自己比较随缘,而且自己所在的大学所处的位置并不是互联网比较发达的城市的原因。所以,很少会有公司愿意跑到我们学校那边来宣讲,来的公司也大多是一些自己没听过或者不太喜欢的公司。所以,在前期,我仅仅能够通过网上投递简历的方式来找工作。 10 | 11 | 零零总总算了一下,自己在网上投了大概有 10 份左右的简历,都是些自己还算喜欢的公司。简单说一下自己投递的一些公司:网上投递的公司有:ThoughtWorks、网易、小米、携程、爱奇艺、知乎、小红书、搜狐、欢聚时代、京东;直接邮箱投递的有:烽火、中电数据、蚂蚁金服花呗部门、今日头条;线下宣讲会投递的有:玄武科技。 12 | 13 | 网上投递的大部分简历都是在做完笔试之后就没有了下文了,即使有几场笔试自我感觉做的很不错的情况下,还是没有收到后续的面试邀请。还有些邮箱投递的简历,后面也都没了回应。所以,我总共也只参加了3个公司的面试,ThoughtWorks、玄武科技和中电数据,都算是拿到了 offer。拿到 ThoughtWorks 的 offer之后,后面的一些笔试和少部分面试都拒了。决定去 ThoughtWorks 了,春招的大部队会没有我的存在。 14 | 15 | 16 | 我个人对 ThoughtWorks 最有好感,ThoughtWorks 也是我自己之前很想去的一家公司。不光是因为我投递简历的时候可以不用重新填一遍表格可以直接发送我已经编辑好的PDF格式简历的友好,这个公司的文化也让我很喜欢。每次投递一家公司几乎都要重新填写一遍简历真的很让人头疼,即使是用牛客网的简历助手也还是有很多东西需要自己重新填写。 17 | 18 | 说句实话,自己在拿到第一份 offer 之前心里还是比较空的,虽然说对自己还是比较自信。包括自己当时来到武汉的原因,也是因为自己没有 offer ,就感觉心里空空的,我相信很多人在这个时候与我也有一样的感觉。然后,我就想到武汉参加一下别的学校宣讲会。现在看来,这个决定也是不必要的,因为我最后去的公司 ThoughtWorks,虽然就在我租的房子的附近,但之前投递的时候,选择的还是远程面试。来到武汉,简单的修整了一下之后,我就去参加了玄武科技在武理工的宣讲会,顺便做了笔试,然后接着就是技术面、HR面、高管面。总体来说,玄武科技的 HR 真的很热情,为他们点个赞,虽然自己最后没能去玄武科技,然后就是技术面非常简单,HR面和高管面也都还好,不会有压抑的感觉,总体聊得很愉快。需要注意的是 玄武科技和很多公司一样都有笔试中有逻辑题,我之前没有做过类似的题,所以当时第一次做有点懵逼。高管面的时候,高管还专门在我做的逻辑题上聊了一会,让我重新做了一些做错的题,并且给他讲一些题的思路,可以看出高层对于应聘者的这项能力还是比较看重的。 19 | 20 | 21 | 22 | 中电数据的技术面试是电话进行的,花了1个多小时一点,个人感觉问的还是比较深的,感觉自己总体回答的还是比较不错的。 23 | 24 | 这里我着重说一下 ThoughtWorks,也算是给想去 ThoughtWorks 的同学一点小小的提示。我是 9.11 号在官网:https://join.thoughtworks.cn/ 投递的简历,9.20 日邮件通知官网下载作业,作业总体来说不难,9.21 号花了半天多的时间做完,然后就直接在9.21 号下午提交了。然后等了挺长时间的,可能是因为 ThoughtWorks 在管理方面比较扁平化的原因,所以总体来说效率可能不算高。因为我选的是远程面试,所以直接下载好 zoom 之后,等HR打电话过来告诉你一个房间号,你就可以直接进去面试就好,一般技术面试有几个人看着你。技术面试的内容,首先就是在面试官让你在你之前做的作业的基础上新增加一个或者两个功能(20分钟)。所以,你在技术面试之前一定要保证你的程序的扩展性是不错的,另外就是你在技术面试之前最好能重构一下自己写的程序。重构本身就是你自己对你写的程序的理解加强很好的一种方式,另外重构也能让你发现你的程序的一些小问题。然后,这一步完成之后,面试官可能会问你一些基础问题,比较简单,所以我觉得 ThoughtWorks 可能更看重你的代码质量。ThoughtWorks 的 HR 面和其他公司的唯一不同可能在于,他会让你用英语介绍一下自己或者说自己的技术栈啊这些。 25 | 26 | ![思特沃克可爱的招聘官网](https://images.gitbook.cn/83f765e0-d5f6-11e8-9c1a-919e09988420) 27 | 28 | 29 | # 关于面试一些重要的问题总结 30 | 另外,再给大家总结一些我个人想到一些关于面试非常重要的一些问题。 31 | 32 | ### 面试前 33 | 34 | **如何准备** 35 | 36 | 37 | 运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试: 38 | 39 | 1. 自我介绍。(你可千万这样介绍:“我叫某某,性别,来自哪里,学校是那个,自己爱干什么”,记住:多说点简历上没有的,多说点自己哪里比别人强!) 40 | 2. 自己面试中可能涉及哪些知识点、那些知识点是重点。 41 | 3. 面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) 42 | 4. 自己的简历该如何写。 43 | 44 | 45 | 46 | 另外,如果你想去类似阿里巴巴、腾讯这种比较大的互联网公司的话,一定要尽早做打算。像阿里巴巴在7月份左右就开始了提前批招聘,到了9月份差不多就已经招聘完毕了。所以,秋招没有参加到阿里的面试还是很遗憾的,毕竟面试即使失败了,也能从阿里难度Max的面试中学到很多东西。 47 | 48 | **关于着装** 49 | 50 | 穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。 51 | 52 | **关于自我介绍** 53 | 54 | 如果你简历上写的基本信息就不要说了,比如性别、年龄、学校。另外,你也不要一上来就说自己爱好什么这方面内容。因为,面试官根本不关心这些东西。你直接挑和你岗位相关的重要经历和自己最突出的特点讲就好了。 55 | 56 | 57 | 58 | **提前准备** 59 | 60 | 面试之前可以在网上找找有没有你要面试的公司的面经。在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 61 | 62 | 63 | ### 面试中 64 | 65 | 面试的时候一定要自信,千万不要怕自己哪里会答不出来,或者说某个问题自己忘记怎么回答了。面试过程中,很多问题可能是你之前没有碰到过的,这个时候你就要通过自己构建的知识体系来思考这些问题。如果某些问题你回答不上来,你也可以让面试官给你简单的提示一下。总之,你要自信,你自信的前提是自己要做好充分的准备。下面给大家总结一些面试非常常见的问题: 66 | 67 | - SpringMVC 工作原理 68 | - 说一下自己对 IOC 、AOP 的理解 69 | - Spring 中用到了那些设计模式,讲一下自己对于这些设计模式的理解 70 | - Spring Bean 的作用域和生命周期了解吗 71 | - Spring 事务中的隔离级别 72 | - Spring 事务中的事务传播行为 73 | - 手写一个 LRU 算法 74 | - 知道那些排序算法,简单介绍一下快排的原理,能不能手写一下快排 75 | - String 为什么是不可变的?String为啥要设计为不可变的? 76 | - Arraylist 与 LinkedList 异同 77 | - HashMap的底层实现 78 | - HashMap 的长度为什么是2的幂次方 79 | - ConcurrentHashMap 和 Hashtable 的区别 80 | - ConcurrentHashMap线程安全的具体实现方式/底层具体实现 81 | - 如果你的简历写了redis 、dubbo、zookeeper、docker的话,面试官还会问一下这些东西。比如redis可能会问你:为什么要用 redis、为什么要用 redis 而不用 map/guava 做缓存、redis 常见数据结构以及使用场景分析、 redis 设置过期时间、redis 内存淘汰机制、 redis 持久化机制、 缓存雪崩和缓存穿透问题、如何解决 Redis 的并发竞争 Key 问题、如何保证缓存与数据库双写时的数据一致性。 82 | - 一些简单的 Linux 命令。 83 | - 为什么要用 消息队列 84 | - 关于 Java多线程,在面试的时候,问的比较多的就是①悲观锁和乐观锁②synchronized 和 ReenTrantLock 区别以及 volatile 和 synchronized 的区别,③可重入锁与非可重入锁的区别、④多线程是解决什么问题的、⑤线程池解决什么问题,为什么要用线程池 ⑥Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 ReenTrantLock 对比;⑦线程池使用时的注意事项、⑧AQS 原理以及 AQS 同步组件:Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock、⑨ReentranLock源码,设计原理,整体过程 等等问题。 85 | - 关于 Java 虚拟机问的比较多的是:①Java内存区域、②虚拟机垃圾算法、③虚拟机垃圾收集器、④JVM内存管理、⑤JVM调优这些问题。 86 | 87 | 88 | ### 面试后 89 | 90 | 如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /其他/个人阅读书籍清单.md: -------------------------------------------------------------------------------- 1 | 下面是个人阅读书籍的部分清单,我比较建议阅读的书籍前都加上了:thumbsup: 表情。 2 | > ### 核心基础知识 3 | 4 | - :thumbsup: [《图解HTTP》](https://book.douban.com/subject/25863515/) 5 | 6 | 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 7 | 8 | > ### Java相关 9 | 10 | - :thumbsup: [《Head First Java.第二版》](https://book.douban.com/subject/2000732/) 11 | 12 | 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 13 | 14 | - [《Java多线程编程核心技术》](https://book.douban.com/subject/26555197/) 15 | 16 | Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。 17 | 18 | - [《JAVA网络编程 第4版》](https://book.douban.com/subject/26259017/) 19 | 20 | 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。 21 | 22 | - :thumbsup: [《Java核心技术卷1+卷2》](https://book.douban.com/subject/25762168/) 23 | 24 | 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。 25 | 26 | - :thumbsup: [《Java编程思想(第4版)》](https://book.douban.com/subject/2130190/) 27 | 28 | 这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 29 | 30 | - :thumbsup: [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/) 31 | 32 | 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 33 | - :thumbsup: [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/) 34 | 35 | 豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 36 | 37 | - [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/) 38 | 39 | 很杂,我只看了前面几章,不太推荐阅读。 40 | 41 | - :thumbsup: [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/) 42 | 43 | 神书!神书!神书!建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 44 | 45 | > ### JavaWeb相关 46 | 47 | - :thumbsup: [《深入分析Java Web技术内幕》](https://book.douban.com/subject/25953851/) 48 | 49 | 感觉还行,涉及的东西也蛮多,推荐阅读。 50 | 51 | - :thumbsup: [《Spring实战(第4版)》](https://book.douban.com/subject/26767354/) 52 | 53 | 不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。 54 | 55 | - [《Java Web整合开发王者归来》](https://book.douban.com/subject/4189495/) 56 | 57 | 当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。 58 | 59 | - :thumbsup: [《Redis实战》](https://book.douban.com/subject/26612779/) 60 | 61 | 如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 62 | 63 | > ### 架构相关 64 | 65 | - :thumbsup: [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/) 66 | 67 | 这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 68 | 69 | - [《架构解密从分布式到微服务(Leaderus著)》](https://book.douban.com/subject/27081188/) 70 | 71 | 很一般的书籍,我就是当做课后图书来阅读的。 72 | 73 | > ### 代码优化 74 | 75 | - :thumbsup: [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/) 76 | 77 | 豆瓣 9.1 分,重构书籍的开山鼻祖。 78 | 79 | > ### 课外书籍 80 | 81 | 《技术奇点》 :thumbsup:《追风筝的人》 :thumbsup:《穆斯林的葬礼》 :thumbsup:《三体》 《人工智能——李开复》 82 | :thumbsup:《活着——余华》 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /其他/选择技术方向都要考虑哪些因素.md: -------------------------------------------------------------------------------- 1 | 本文主要是作者读安晓辉老师的《程序员程序员职场进阶 32 讲 》中关于“选择技术方向都要考虑哪些因素”这部分做的一些笔记和自己的思考。在这里分享给各位! 2 | 3 | ### 选择一种技术可能会考虑到的决定因素 4 | 5 | 1. 就业机会 6 | 7 | 选择一门就业面广的技术还是比较重要的。我的很多学PHP的同学现在都在培训班学Java,真的!!! 8 | 2. 难易程度 9 | 10 | 我当时是在C/C++语言与Java中选择了Java,因为我感觉Java学起来确实要比C++简单一些。 11 | 3. 个人兴趣 12 | 13 | 兴趣是你能坚持下来的一个很重要的条件。 14 | 4. 薪资水平 15 | 16 | 薪资虽然不是人的唯一追求,但是一定是必备的追求。 17 | 5. 发展前景 18 | 19 | 你肯定不愿意看到这种情况发生:选择了一门技术,结果一年后它就没人用、没市场了。所以我们在选择时就要考虑这一点,做一些预判。 20 | 21 | 选择技术时存在两种考虑:一种是选择稳定的、经典的技术;一种是卡位将来的市场缺口,选择将来可能需要用到的技术。 22 | 6. 他人推荐 23 | 24 | 我们在懵懵懂懂的时候,往往最容易听从别人的推荐,然后选择某种技术。 25 | 7. 相近原则 26 | 27 | 当我们已经掌握了一些技术,要学习新技术时,就可以根据一种新技术是否和自己已经掌握的技术比较接近来判断选择。相近的技术,学起来会更容易上手。 28 | 8. 互补原则 29 | 30 | 和相近性类似,互补性也常用在拓展我们技术能力的情景下。它指的是,有一些技术可以和你已经掌握的技术互相补充,组合在一起,形成更完整、更系统的技术图谱,给你带来更大的竞争力。关于相近原则与互补原则,我们也会在后面的文章里具体解读。 31 | 9. 团队技术图谱 32 | 33 | 我觉得这个可能就是团队开发过程中的需要。比如在做一个项目的时候,这个项目需要你去学习一下某个你没有接触过的新技术。 34 | 35 | ### 入行时如何选择技术方向 36 | 37 | 为了明确自己的求职目标,可以问问自己下面的问题: 38 | - 我想在哪个城市工作? 39 | - 我想在哪些行业、领域发展? 40 | - 我想去什么样的公司? 41 | - 我想做什么样的产品? 42 | 43 | 另外你要知道的是热门技术会有更多机会,相应竞争压力也会更大,并不能保证你找到合适的工作。 44 | 冷门技术,机会相对较少,而且机会相对确定 。 45 | 46 | ### 构建技能树时如何选择技术方向 47 | 48 | 当我们过了专项能力提升的初级阶段之后,就应该开始构建自己的技能体系了。在为搭建技能树而选择技术时,通常考虑下面两个原则: 49 | - 相近原则 50 | - 互补原则 51 | 52 | “学习技术时一定要学对自己以后发展有用的技术”是我经常对自己强调的,另外我觉得很误导人同时也很错误的一个思想是:“只要是技术学了就会有用的”,这句话在我刚学编程时经常听到有人对我说。希望大家不要被误导,很多技术过时了就是过时了,没有必要再去花时间学。 53 | 54 | 我觉得相近原则和互补原则互补原则就是你主精和自己技术方向相同的的东西或者对自己技术领域有提升的东西。比如我目前暂时选择了Java为我的主要发展语言,所以我就要求自己大部分时间还是搞和Java相关的东西比如:Spring、SpingBoot、Dubbo、Mybatis等等。但是千万不要被语言所束缚,在业余时间我学的比较多的就是Python以及JS、C/C++/C#也会偶尔接触。因为我经常会接触前端另外我自己偶尔有爬虫需求或者需要用Python的一些第三库解决一些问题,所以我业余学Pyton以及JS就比较多一点,我觉得这两门技术也是对我现有技术的一个补充了。 55 | 56 | 57 | ### 技术转型时的方向选择 58 | 59 | 我觉得对于技术转型主要有一下几点建议 60 | 61 | - 与自己当前技术栈跨度不太大的领域,比如你做安卓的话转型可以选择做Java后端。 62 | - 真正适合自己去做的,并不是一味看着这个领域火了(比如人工智能),然后自己就不考虑实际的去转型到这个领域里去。 63 | - 技术转型方向尽量对自己以后的发展需要有帮助。 64 | -------------------------------------------------------------------------------- /操作系统/后端程序员必备的Linux基础知识.md: -------------------------------------------------------------------------------- 1 | 2 | > 学习Linux之前,我们先来简单的认识一下操作系统。 3 | 4 | ## 一 从认识操作系统开始 5 | ### 1.1 操作系统简介 6 | 7 | 我通过以下四点介绍什么操作系统: 8 | 9 | - **操作系统(Operation System,简称OS)是管理计算机硬件与软件资源的程序,是计算机系统的内核与基石;** 10 | - **操作系统本质上是运行在计算机上的软件程序 ;** 11 | - **为用户提供一个与系统交互的操作界面 ;** 12 | - **操作系统分内核与外壳(我们可以把外壳理解成围绕着内核的应用程序,而内核就是能操作硬件的程序)。** 13 | 14 | ![操作系统分内核与外壳](https://user-gold-cdn.xitu.io/2018/7/3/1645ee3dc5cf626e?w=862&h=637&f=png&s=23899) 15 | ### 1.2 操作系统简单分类 16 | 17 | 1. **Windows:** 目前最流行的个人桌面操作系统 ,不做多的介绍,大家都清楚。 18 | 2. **Unix:** 最早的多用户、多任务操作系统 .按照操作系统的分类,属于分时操作系统。Unix 大多被用在服务器、工作站,现在也有用在个人计算机上。它在创建互联网、计算机网络或客户端/服务器模型方面发挥着非常重要的作用。 19 | ![Unix](https://user-gold-cdn.xitu.io/2018/7/3/1645ee83f036846d?w=1075&h=475&f=png&s=914462) 20 | 3. **Linux:** Linux是一套免费使用和自由传播的类Unix操作系统.Linux存在着许多不同的Linux版本,但它们都使用了 **Linux内核** 。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、视频游戏控制台、台式计算机、大型机和超级计算机。严格来讲,Linux这个词本身只表示Linux内核,但实际上人们已经习惯了用Linux来形容整个基于Linux内核,并且使用GNU 工程各种工具和数据库的操作系统。 21 | 22 | ![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645eeb8e843f29d?w=426&h=240&f=png&s=32650) 23 | 24 | 25 | ## 二 初探Linux 26 | 27 | ### 2.1 Linux简介 28 | 29 | 我们上面已经介绍到了Linux,我们这里只强调三点。 30 | - **类Unix系统:** Linux是一种自由、开放源码的类似Unix的操作系统 31 | - **Linux内核:** 严格来说,Linux这个词本身只表示Linux内核 32 | - **Linux之父:** 一个编程领域的传奇式人物。他是Linux内核的最早作者,随后发起了这个开源项目,担任Linux内核的首要架构师与项目协调者,是当今世界最著名的电脑程序员、黑客之一。他还发起了Git这个开源项目,并为主要的开发者。 33 | 34 | ![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645ef0a5a4f137f?w=270&h=376&f=png&s=193487) 35 | 36 | ### 2.2 Linux诞生简介 37 | 38 | - 1991年,芬兰的业余计算机爱好者Linus Torvalds编写了一款类似Minix的系统(基于微内核架构的类Unix操作系统)被ftp管理员命名为Linux 加入到自由软件基金的GNU计划中; 39 | - Linux以一只可爱的企鹅作为标志,象征着敢作敢为、热爱生活。 40 | 41 | 42 | ### 2.3 Linux的分类 43 | 44 | **Linux根据原生程度,分为两种:** 45 | 46 | 1. **内核版本:** Linux不是一个操作系统,严格来讲,Linux只是一个操作系统中的内核。内核是什么?内核建立了计算机软件与硬件之间通讯的平台,内核提供系统服务,比如文件管理、虚拟内存、设备I/O等; 47 | 2. **发行版本:** 一些组织或公司在内核版基础上进行二次开发而重新发行的版本。Linux发行版本有很多种(ubuntu和CentOS用的都很多,初学建议选择CentOS),如下图所示: 48 | ![Linux发行版本](https://user-gold-cdn.xitu.io/2018/7/3/1645efa7048fd018?w=548&h=274&f=png&s=99213) 49 | 50 | 51 | ## 三 Linux文件系统概览 52 | 53 | ### 3.1 Linux文件系统简介 54 | 55 | **在Linux操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。** 56 | 57 | 也就是说在LINUX系统中有一个重要的概念:**一切都是文件**。其实这是UNIX哲学的一个体现,而Linux是重写UNIX而来,所以这个概念也就传承了下来。在UNIX系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。 58 | 59 | 60 | ### 3.2 文件类型与目录结构 61 | 62 | **Linux支持5种文件类型 :** 63 | ![文件类型](https://user-gold-cdn.xitu.io/2018/7/3/1645f1a7d64def1a?w=901&h=547&f=png&s=72692) 64 | 65 | **Linux的目录结构如下:** 66 | 67 | Linux文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是其根目录: 68 | ![Linux的目录结构](https://user-gold-cdn.xitu.io/2018/7/3/1645f1c65676caf6?w=823&h=315&f=png&s=15226) 69 | 70 | **常见目录说明:** 71 | 72 | - **/bin:** 存放二进制可执行文件(ls,cat,mkdir等),常用命令一般都在这里; 73 | - **/etc:** 存放系统管理和配置文件; 74 | - **/home:** 存放所有用户文件的根目录,是用户主目录的基点,比如用户user的主目录就是/home/user,可以用~user表示; 75 | - **/usr :** 用于存放系统应用程序; 76 | - **/opt:** 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把tomcat等都安装到这里; 77 | - **/proc:** 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息; 78 | - **/root:** 超级用户(系统管理员)的主目录(特权阶级^o^); 79 | - **/sbin:** 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等; 80 | - **/dev:** 用于存放设备文件; 81 | - **/mnt:** 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统; 82 | - **/boot:** 存放用于系统引导时使用的各种文件; 83 | - **/lib :** 存放着和系统运行相关的库文件 ; 84 | - **/tmp:** 用于存放各种临时文件,是公用的临时文件存储点; 85 | - **/var:** 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等; 86 | - **/lost+found:** 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里。 87 | 88 | 89 | ## 四 Linux基本命令 90 | 91 | 下面只是给出了一些比较常用的命令。推荐一个Linux命令快查网站,非常不错,大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。 92 | 93 | Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/) 94 | ### 4.1 目录切换命令 95 | 96 | - **`cd usr`:** 切换到该目录下usr目录 97 | - **`cd ..(或cd../)`:** 切换到上一层目录 98 | - **`cd /`:** 切换到系统根目录 99 | - **`cd ~`:** 切换到用户主目录 100 | - **`cd -`:** 切换到上一个所在目录 101 | 102 | ### 4.2 目录的操作命令(增删改查) 103 | 104 | 1. **`mkdir 目录名称`:** 增加目录 105 | 2. **`ls或者ll`**(ll是ls -l的缩写,ll命令以看到该目录下的所有目录和文件的详细信息):查看目录信息 106 | 3. **`find 目录 参数`:** 寻找目录(查) 107 | 108 | 示例: 109 | 110 | - 列出当前目录及子目录下所有文件和文件夹: `find .` 111 | - 在`/home`目录下查找以.txt结尾的文件名:`find /home -name "*.txt"` 112 | - 同上,但忽略大小写: `find /home -iname "*.txt"` 113 | - 当前目录及子目录下查找所有以.txt和.pdf结尾的文件:`find . \( -name "*.txt" -o -name "*.pdf" \)`或`find . -name "*.txt" -o -name "*.pdf" ` 114 | 115 | 4. **`mv 目录名称 新目录名称`:** 修改目录的名称(改) 116 | 117 | 注意:mv的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到mv命令的另一个用法。 118 | 5. **`mv 目录名称 目录的新位置`:** 移动目录的位置---剪切(改) 119 | 120 | 注意:mv语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作。另外mv与cp的结果不同,mv好像文件“搬家”,文件个数并未增加。而cp对文件进行复制,文件个数增加了。 121 | 6. **`cp -r 目录名称 目录拷贝的目标位置`:** 拷贝目录(改),-r代表递归拷贝 122 | 123 | 注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归 124 | 7. **`rm [-rf] 目录`:** 删除目录(删) 125 | 126 | 注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包 127 | 128 | 129 | ### 4.3 文件的操作命令(增删改查) 130 | 131 | 1. **`touch 文件名称`:** 文件的创建(增) 132 | 2. **`cat/more/less/tail 文件名称`** 文件的查看(查) 133 | - **`cat`:** 只能显示最后一屏内容 134 | - **`more`:** 可以显示百分比,回车可以向下一行, 空格可以向下一页,q可以退出查看 135 | - **`less`:** 可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看 136 | - **`tail-10` :** 查看文件的后10行,Ctrl+C结束 137 | 138 | 注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化 139 | 3. **`vim 文件`:** 修改文件的内容(改) 140 | 141 | vim编辑器是Linux中的强大组件,是vi编辑器的加强版,vim编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用vim编辑修改文件的方式基本会使用就可以了。 142 | 143 | **在实际开发中,使用vim编辑器主要作用就是修改配置文件,下面是一般步骤:** 144 | 145 | vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。) 146 | 4. **`rm -rf 文件`:** 删除文件(删) 147 | 148 | 同目录删除:熟记 `rm -rf` 文件 即可 149 | 150 | ### 4.4 压缩文件的操作命令 151 | 152 | **1)打包并压缩文件:** 153 | 154 | Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.gz结尾的。 155 | 156 | 而一般情况下打包和压缩是一起进行的,打包并压缩后的文件的后缀名一般.tar.gz。 157 | 命令:**`tar -zcvf 打包压缩后的文件名 要打包压缩的文件`** 158 | 其中: 159 | 160 | z:调用gzip压缩命令进行压缩 161 | 162 | c:打包文件 163 | 164 | v:显示运行过程 165 | 166 | f:指定文件名 167 | 168 | 比如:加入test目录下有三个文件分别是 :aaa.txt bbb.txt ccc.txt,如果我们要打包test目录并指定压缩后的压缩包名称为test.tar.gz可以使用命令:**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt`或:`tar -zcvf test.tar.gz /test/`** 169 | 170 | 171 | **2)解压压缩包:** 172 | 173 | 命令:tar [-xvf] 压缩文件 174 | 175 | 其中:x:代表解压 176 | 177 | 示例: 178 | 179 | 1 将/test下的test.tar.gz解压到当前目录下可以使用命令:**`tar -xvf test.tar.gz`** 180 | 181 | 2 将/test下的test.tar.gz解压到根目录/usr下:**`tar -xvf xxx.tar.gz -C /usr`**(- C代表指定解压的位置) 182 | 183 | 184 | ### 4.5 Linux的权限命令 185 | 186 | 操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在Linux中权限一般分为读(readable)、写(writable)和执行(excutable),分为三组。分别对应文件的属主(owner),属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。通过 **`ls -l`** 命令我们可以 查看某个目录下的文件或目录的权限 187 | 188 | 示例:在随意某个目录下`ls -l` 189 | 190 | ![](https://user-gold-cdn.xitu.io/2018/7/5/1646955be781daaa?w=589&h=228&f=png&s=16360) 191 | 192 | 第一列的内容的信息解释如下: 193 | 194 | ![](https://user-gold-cdn.xitu.io/2018/7/5/16469565b6951791?w=489&h=209&f=png&s=39791) 195 | 196 | > 下面将详细讲解文件的类型、Linux中权限以及文件有所有者、所在组、其它组具体是什么? 197 | 198 | 199 | **文件的类型:** 200 | 201 | - d: 代表目录 202 | - -: 代表文件 203 | - l: 代表链接(可以认为是window中的快捷方式) 204 | 205 | 206 | **Linux中权限分为以下几种:** 207 | 208 | - r:代表权限是可读,r也可以用数字4表示 209 | - w:代表权限是可写,w也可以用数字2表示 210 | - x:代表权限是可执行,x也可以用数字1表示 211 | 212 | **文件和目录权限的区别:** 213 | 214 | 对文件和目录而言,读写执行表示不同的意义。 215 | 216 | 对于文件: 217 | 218 | | 权限名称 | 可执行操作 | 219 | | :-------- | --------:| 220 | | r | 可以使用cat查看文件的内容 | 221 | |w | 可以修改文件的内容 | 222 | | x | 可以将其运行为二进制文件 | 223 | 224 | 对于目录: 225 | 226 | | 权限名称 | 可执行操作 | 227 | | :-------- | --------:| 228 | | r | 可以查看目录下列表 | 229 | |w | 可以创建和删除目录下文件 | 230 | | x | 可以使用cd进入目录 | 231 | 232 | 233 | 234 | **在linux中的每个用户必须属于一个组,不能独立于组外。在linux中每个文件有所有者、所在组、其它组的概念。** 235 | 236 | - **所有者** 237 | 238 | 一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者,用ls ‐ahl命令可以看到文件的所有者 也可以使用chown 用户名 文件名来修改文件的所有者 。 239 | - **文件所在组** 240 | 241 | 当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组 用ls ‐ahl命令可以看到文件的所有组 也可以使用chgrp 组名 文件名来修改文件所在的组。 242 | - **其它组** 243 | 244 | 除开文件的所有者和所在组的用户外,系统的其它用户都是文件的其它组 245 | 246 | > 我们再来看看如何修改文件/目录的权限。 247 | 248 | **修改文件/目录的权限的命令:`chmod`** 249 | 250 | 示例:修改/test下的aaa.txt的权限为属主有全部权限,属主所在的组有读写权限, 251 | 其他用户只有读的权限 252 | 253 | **`chmod u=rwx,g=rw,o=r aaa.txt`** 254 | 255 | ![](https://user-gold-cdn.xitu.io/2018/7/5/164697447dc6ecac?w=525&h=246&f=png&s=12362) 256 | 257 | 上述示例还可以使用数字表示: 258 | 259 | chmod 764 aaa.txt 260 | 261 | 262 | **补充一个比较常用的东西:** 263 | 264 | 假如我们装了一个zookeeper,我们每次开机到要求其自动启动该怎么办? 265 | 266 | 1. 新建一个脚本zookeeper 267 | 2. 为新建的脚本zookeeper添加可执行权限,命令是:`chmod +x zookeeper` 268 | 3. 把zookeeper这个脚本添加到开机启动项里面,命令是:` chkconfig --add zookeeper` 269 | 4. 如果想看看是否添加成功,命令是:`chkconfig --list` 270 | 271 | 272 | ### 4.6 Linux 用户管理 273 | 274 | Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。 275 | 276 | 用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方面也可以帮助用户组织文件,并为用户提供安全性保护。 277 | 278 | **Linux用户管理相关命令:** 279 | - `useradd 选项 用户名`:添加用户账号 280 | - `userdel 选项 用户名`:删除用户帐号 281 | - `usermod 选项 用户名`:修改帐号 282 | - `passwd 用户名`:更改或创建用户的密码 283 | - `passwd -S 用户名` :显示用户账号密码信息 284 | - `passwd -d 用户名`: 清除用户密码 285 | 286 | useradd命令用于Linux中创建的新的系统用户。useradd可用来建立用户帐号。帐号建好之后,再用passwd设定帐号的密码.而可用userdel删除帐号。使用useradd指令所建立的帐号,实际上是保存在/etc/passwd文本文件中。 287 | 288 | passwd命令用于设置用户的认证信息,包括用户密码、密码过期时间等。系统管理者则能用它管理系统用户的密码。只有管理者可以指定用户名称,一般用户只能变更自己的密码。 289 | 290 | 291 | ### 4.7 Linux系统用户组的管理 292 | 293 | 每个用户都有一个用户组,系统可以对一个用户组中的所有用户进行集中管理。不同Linux 系统对用户组的规定有所不同,如Linux下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建。 294 | 295 | 用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修改实际上就是对/etc/group文件的更新。 296 | 297 | **Linux系统用户组的管理相关命令:** 298 | - `groupadd 选项 用户组` :增加一个新的用户组 299 | - `groupdel 用户组`:要删除一个已有的用户组 300 | - `groupmod 选项 用户组` : 修改用户组的属性 301 | 302 | 303 | ### 4.8 其他常用命令 304 | 305 | - **`pwd`:** 显示当前所在位置 306 | - **`grep 要搜索的字符串 要搜索的文件 --color`:** 搜索命令,--color代表高亮显示 307 | - **`ps -ef`/`ps aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括redis字符串的进程) 308 | 309 | 注意:如果直接用ps((Process Status))命令,会显示所有进程的状态,通常结合grep命令查看某进程的状态。 310 | - **`kill -9 进程的pid`:** 杀死进程(-9 表示强制终止。) 311 | 312 | 先用ps查找进程,然后用kill杀掉 313 | - **网络通信命令:** 314 | - 查看当前系统的网卡信息:ifconfig 315 | - 查看与某台机器的连接情况:ping 316 | - 查看当前系统的端口使用:netstat -an 317 | - **`shutdown`:** `shutdown -h now`: 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定5分钟后关机,同时送出警告信息给登入用户。 318 | - **`reboot`:** **`reboot`:** 重开机。**`reboot -w`:** 做个重开机的模拟(只有纪录并不会真的重开机)。 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | -------------------------------------------------------------------------------- /数据存储/MySQL Index.md: -------------------------------------------------------------------------------- 1 | 2 | # 思维导图-索引篇 3 | 4 | > 系列思维导图源文件(数据库+架构)以及思维导图制作软件—XMind8 破解安装,公众号后台回复:**“思维导图”** 免费领取!(下面的图片不是很清楚,原图非常清晰,另外提供给大家源文件也是为了大家根据自己需要进行修改) 5 | 6 | ![【思维导图-索引篇】](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/70973487.jpg) 7 | 8 | > **下面是我补充的一些内容** 9 | 10 | # 为什么索引能提高查询速度 11 | 12 | > 以下内容整理自: 13 | > 地址: https://juejin.im/post/5b55b842f265da0f9e589e79 14 | > 作者 :Java3y 15 | 16 | ### 先从 MySQL 的基本存储结构说起 17 | 18 | MySQL的基本存储结构是页(记录都存在页里边): 19 | 20 | ![MySQL的基本存储结构是页](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/28559421.jpg) 21 | 22 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/82053134.jpg) 23 | 24 | - **各个数据页可以组成一个双向链表** 25 | - **每个数据页中的记录又可以组成一个单向链表** 26 | - 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录 27 | - 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。 28 | 29 | 所以说,如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句,默认会这样做: 30 | 31 | 1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** 32 | 2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** 33 | 34 | 很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。 35 | 36 | 37 | ### 使用索引之后 38 | 39 | 索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对): 40 | 41 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/5373082.jpg) 42 | 43 | 要找到id为8的记录简要步骤: 44 | 45 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/89338047.jpg) 46 | 47 | 很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 **“目录”** 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn)) 48 | 49 | 其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。 50 | 51 | # 关于索引其他重要的内容补充 52 | 53 | > 以下内容整理自:《Java工程师修炼之道》 54 | 55 | 56 | ### 最左前缀原则 57 | 58 | MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city)o而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下: 59 | 60 | ``` 61 | select * from user where name=xx and city=xx ; //可以命中索引 62 | select * from user where name=xx ; // 可以命中索引 63 | select * from user where city=xx; // 无法命中索引 64 | ``` 65 | 这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name =xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的. 66 | 67 | 由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDERBY子句也遵循此规则。 68 | 69 | ### 注意避免冗余索引 70 | 71 | 冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 72 | 73 | MySQLS.7 版本后,可以通过查询 sys 库的 `schemal_r dundant_indexes` 表来查看冗余索引 74 | 75 | ### Mysql如何为表字段添加索引??? 76 | 77 | 1.添加PRIMARY KEY(主键索引) 78 | 79 | ``` 80 | ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) 81 | ``` 82 | 2.添加UNIQUE(唯一索引) 83 | 84 | ``` 85 | ALTER TABLE `table_name` ADD UNIQUE ( `column` ) 86 | ``` 87 | 88 | 3.添加INDEX(普通索引) 89 | 90 | ``` 91 | ALTER TABLE `table_name` ADD INDEX index_name ( `column` ) 92 | ``` 93 | 94 | 4.添加FULLTEXT(全文索引) 95 | 96 | ``` 97 | ALTER TABLE `table_name` ADD FULLTEXT ( `column`) 98 | ``` 99 | 100 | 5.添加多列索引 101 | 102 | ``` 103 | ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` ) 104 | ``` 105 | 106 | 107 | # 参考 108 | 109 | - 《Java工程师修炼之道》 110 | - 《MySQL高性能书籍_第3版》 111 | - https://juejin.im/post/5b55b842f265da0f9e589e79 112 | 113 | -------------------------------------------------------------------------------- /数据存储/MySQL.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):[https://github.com/Snailclimb/Java_Guide](https://github.com/Snailclimb/Java_Guide) 4 | 5 | > ## 书籍推荐 6 | 7 | **《高性能MySQL : 第3版》** 8 | 9 | > ## 文字教程推荐 10 | 11 | [MySQL 教程(菜鸟教程)](http://www.runoob.com/mysql/mysql-tutorial.html) 12 | 13 | [MySQL教程(易百教程)](https://www.yiibai.com/mysql/) 14 | 15 | > ## 视频教程推荐 16 | 17 | 18 | **基础入门:** [与MySQL的零距离接触-慕课网](https://www.imooc.com/learn/122) 19 | 20 | **Mysql开发技巧:** [MySQL开发技巧(一)](https://www.imooc.com/learn/398)  [MySQL开发技巧(二)](https://www.imooc.com/learn/427)  [MySQL开发技巧(三)](https://www.imooc.com/learn/449) 21 | 22 | **Mysql5.7新特性及相关优化技巧:** [MySQL5.7版本新特性](https://www.imooc.com/learn/533)  [性能优化之MySQL优化](https://www.imooc.com/learn/194) 23 | 24 | [MySQL集群(PXC)入门](https://www.imooc.com/learn/993)  [MyCAT入门及应用](https://www.imooc.com/learn/951) 25 | 26 | 27 | 28 | > ## 常见问题总结 29 | 30 | - ### ①存储引擎 31 | 32 | [MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇](https://juejin.im/post/5b1685bef265da6e5c3c1c34) 33 | 34 | - ### ②字符集及校对规则 35 | 36 | 字符集指的是一种从二进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。Mysql中每一种字符集都会对应一系列的校对规则。 37 | 38 | Mysql采用的是类似继承的方式指定字符集的默认值,每个数据库以及每张数据表都有自己的默认值,他们逐层继承。比如:某个库中所有表的默认字符集将是该数据库所指定的字符集(这些表在没有指定字符集的情况下,才会采用默认字符集) PS:整理自《Java工程师修炼之道》 39 | 40 | 详细内容可以参考: [MySQL字符集及校对规则的理解](https://www.cnblogs.com/geaozhang/p/6724393.html#mysqlyuzifuji) 41 | 42 | - ### ③索引相关的内容(数据库使用中非常关键的技术,合理正确的使用索引可以大大提高数据库的查询性能) 43 | 44 |   Mysql索引使用的数据结构主要有**BTree索引** 和 **哈希索引** 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。 45 | 46 |   Mysql的BTree索引使用的是B数中的B+Tree,但对于主要的两种存储引擎的实现方式是不同的。 47 | 48 |   **MyISAM:** B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。 49 | 50 |   **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》 51 | 52 | 详细内容可以参考: 53 | 54 | [干货:mysql索引的数据结构](https://www.jianshu.com/p/1775b4ff123a) 55 | 56 | [MySQL优化系列(三)--索引的使用、原理和设计优化](https://blog.csdn.net/Jack__Frost/article/details/72571540) 57 | 58 | [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79#comment) 59 | 60 | - ### ④查询缓存的使用 61 | 62 | my.cnf加入以下配置,重启Mysql开启查询缓存 63 | ``` 64 | query_cache_type=1 65 | query_cache_size=600000 66 | ``` 67 | 68 | Mysql执行以下命令也可以开启查询缓存 69 | 70 | ``` 71 | set global query_cache_type=1; 72 | set global query_cache_size=600000; 73 | ``` 74 | 如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、Mysql库中的系统表,其查询结果也不会被缓存。 75 | 76 | 缓存建立之后,Mysql的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。 77 | 78 | **缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启缓存查询要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十MB比较合适。此外,**还可以通过sql_cache和sql_no_cache来控制某个查询语句是否需要缓存:** 79 | ``` 80 | select sql_no_cache count(*) from usr; 81 | ``` 82 | 83 | - ### ⑤事务机制 84 | 85 | **关系性数据库需要遵循ACID规则,具体内容如下:** 86 | 87 | ![事务的特性](https://user-gold-cdn.xitu.io/2018/5/20/1637b08b98619455?w=312&h=305&f=png&s=22430) 88 | 89 | 1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; 90 | 2. **一致性:** 执行事务前后,数据保持一致; 91 | 3. **隔离性:** 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的; 92 | 4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。 93 | 94 | **为了达到上述事务特性,数据库定义了几种不同的事务隔离级别:** 95 | 96 | - **READ_UNCOMMITTED(未授权读取):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** 97 | - **READ_COMMITTED(授权读取):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** 98 | - **REPEATABLE_READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** 99 | - **SERIALIZABLE(串行):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 100 | 101 | 这里需要注意的是:**Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.** 102 | 103 | 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。 104 | 105 | 详细内容可以参考: [可能是最漂亮的Spring事务管理详解](https://blog.csdn.net/qq_34337272/article/details/80394121) 106 | 107 | - ### ⑥锁机制与InnoDB锁算法 108 | **MyISAM和InnoDB存储引擎使用的锁:** 109 | 110 | - MyISAM采用表级锁(table-level locking)。 111 | - InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁 112 | 113 | **表级锁和行级锁对比:** 114 | 115 | - **表级锁:** Mysql中锁定 **粒度最大** 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。 116 | - **行级锁:** Mysql中锁定 **粒度最小** 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 117 | 118 | 详细内容可以参考: 119 | [Mysql锁机制简单了解一下](https://blog.csdn.net/qq_34337272/article/details/80611486) 120 | 121 | **InnoDB存储引擎的锁的算法有三种:** 122 | - Record lock:单个行记录上的锁 123 | - Gap lock:间隙锁,锁定一个范围,不包括记录本身 124 | - Next-key lock:record+gap 锁定一个范围,包含记录本身 125 | 126 | **相关知识点:** 127 | 1. innodb对于行的查询使用next-key lock 128 | 2. Next-locking keying为了解决Phantom Problem幻读问题 129 | 3. 当查询的索引含有唯一属性时,将next-key lock降级为record key 130 | 4. Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生 131 | 5. 有两种方式显式关闭gap锁:(除了外键约束和唯一性检查外,其余情况仅使用record lock) A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1 132 | 133 | - ### ⑦大表优化 134 | 135 | 当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下: 136 | 137 | 1. **限定数据的范围:** 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。; 138 | 2. **读/写分离:** 经典的数据库拆分方案,主库负责写,从库负责读; 139 | 3. **缓存:** 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存; 140 | 4. **垂直分区:** 141 | 142 | **根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。 143 | 144 | **简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。** 如下图所示,这样来说大家应该就更容易理解了。 145 | ![](https://user-gold-cdn.xitu.io/2018/6/16/164084354ba2e0fd?w=950&h=279&f=jpeg&s=26015) 146 | 147 | **垂直拆分的优点:** 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。 148 | 149 | **垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; 150 | 151 | 5. **水平分区:** 152 | 153 | 154 | **保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** 155 | 156 | 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。 157 | 158 | ![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119) 159 | 160 | 水品拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水品拆分最好分库** 。 161 | 162 | 水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 163 | 164 | **下面补充一下数据库分片的两种常见方案:** 165 | - **客户端代理:** **分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。 166 | - **中间件代理:** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。 167 | 168 | 169 | 详细内容可以参考: 170 | [MySQL大表优化方案](https://segmentfault.com/a/1190000006158186) 171 | 172 | 173 | -------------------------------------------------------------------------------- /数据存储/Redis/Redis持久化.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 非常感谢《redis实战》真本书,本文大多内容也参考了书中的内容。非常推荐大家看一下《redis实战》这本书,感觉书中的很多理论性东西还是很不错的。 4 | 5 | 为什么本文的名字要加上春夏秋冬又一春,哈哈 ,这是一部韩国的电影,我感觉电影不错,所以就用在文章名字上了,没有什么特别的含义,然后下面的有些配图也是电影相关镜头。 6 | 7 | ![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f97071d71f6de?w=1280&h=720&f=jpeg&s=205252) 8 | 9 | **很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。** 10 | 11 | Redis不同于Memcached的很重一点就是,**Redis支持持久化**,而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照(snapshotting,RDB)**,另一种方式是**只追加文件(append-only file,AOF)**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 12 | 13 | 14 | ## 快照(snapshotting)持久化 15 | 16 | Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。 17 | 18 | 19 | ![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f97568281782a?w=600&h=329&f=jpeg&s=88616) 20 | 21 | **快照持久化是Redis默认采用的持久化方式**,在redis.conf配置文件中默认有此下配置: 22 | ``` 23 | 24 | save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 25 | 26 | save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 27 | 28 | save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 29 | ``` 30 | 31 | 根据配置,快照将被写入dbfilename选项指定的文件里面,并存储在dir选项指定的路径上面。如果在新的快照文件创建完毕之前,Redis、系统或者硬件这三者中的任意一个崩溃了,那么Redis将丢失最近一次创建快照写入的所有数据。 32 | 33 | 举个例子:假设Redis的上一个快照是2:35开始创建的,并且已经创建成功。下午3:06时,Redis又开始创建新的快照,并且在下午3:08快照创建完毕之前,有35个键进行了更新。如果在下午3:06到3:08期间,系统发生了崩溃,导致Redis无法完成新快照的创建工作,那么Redis将丢失下午2:35之后写入的所有数据。另一方面,如果系统恰好在新的快照文件创建完毕之后崩溃,那么Redis将丢失35个键的更新数据。 34 | 35 | **创建快照的办法有如下几种:** 36 | 37 | - **BGSAVE命令:** 客户端向Redis发送 **BGSAVE命令** 来创建一个快照。对于支持BGSAVE命令的平台来说(基本上所有平台支持,除了Windows平台),Redis会调用fork来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。 38 | - **SAVE命令:** 客户端还可以向Redis发送 **SAVE命令** 来创建一个快照,接到SAVE命令的Redis服务器在快照创建完毕之前不会再响应任何其他命令。SAVE命令不常用,我们通常只会在没有足够内存去执行BGSAVE命令的情况下,又或者即使等待持久化操作执行完毕也无所谓的情况下,才会使用这个命令。 39 | - **save选项:** 如果用户设置了save选项(一般会默认设置),比如 **save 60 10000**,那么从Redis最近一次创建快照之后开始算起,当“60秒之内有10000次写入”这个条件被满足时,Redis就会自动触发BGSAVE命令。 40 | - **SHUTDOWN命令:** 当Redis通过SHUTDOWN命令接收到关闭服务器的请求时,或者接收到标准TERM信号时,会执行一个SAVE命令,阻塞所有客户端,不再执行客户端发送的任何命令,并在SAVE命令执行完毕之后关闭服务器。 41 | - **一个Redis服务器连接到另一个Redis服务器:** 当一个Redis服务器连接到另一个Redis服务器,并向对方发送SYNC命令来开始一次复制操作的时候,如果主服务器目前没有执行BGSAVE操作,或者主服务器并非刚刚执行完BGSAVE操作,那么主服务器就会执行BGSAVE命令 42 | 43 | 如果系统真的发生崩溃,用户将丢失最近一次生成快照之后更改的所有数据。因此,快照持久化只适用于即使丢失一部分数据也不会造成一些大问题的应用程序。不能接受这个缺点的话,可以考虑AOF持久化。 44 | 45 | 46 | 47 | ## **AOF(append-only file)持久化** 48 | 与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启: 49 | ``` 50 | appendonly yes 51 | ``` 52 | 53 | 开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。 54 | 55 | ![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f976818876166?w=400&h=219&f=jpeg&s=91022) 56 | 57 | **在Redis的配置文件中存在三种同步方式,它们分别是:** 58 | 59 | ``` 60 | 61 | appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 62 | appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 63 | appendfsync no #让操作系统决定何时进行同步 64 | ``` 65 | 66 | **appendfsync always** 可以实现将数据丢失减到最少,不过这种方式需要对硬盘进行大量的写入而且每次只写入一个命令,十分影响Redis的速度。另外使用固态硬盘的用户谨慎使用appendfsync always选项,因为这会明显降低固态硬盘的使用寿命。 67 | 68 | 为了兼顾数据和写入性能,用户可以考虑 **appendfsync everysec选项** ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 69 | 70 | 71 | **appendfsync no** 选项一般不推荐,这种方案会使Redis丢失不定量的数据而且如果用户的硬盘处理写入操作的速度不够的话,那么当缓冲区被等待写入的数据填满时,Redis的写入操作将被阻塞,这会导致Redis的请求速度变慢。 72 | 73 | **虽然AOF持久化非常灵活地提供了多种不同的选项来满足不同应用程序对数据安全的不同要求,但AOF持久化也有缺陷——AOF文件的体积太大。** 74 | 75 | ## 重写/压缩AOF 76 | 77 | AOF虽然在某个角度可以将数据丢失降低到最小而且对性能影响也很小,但是极端的情况下,体积不断增大的AOF文件很可能会用完硬盘空间。另外,如果AOF体积过大,那么还原操作执行时间就可能会非常长。 78 | 79 | 为了解决AOF体积过大的问题,用户可以向Redis发送 **BGREWRITEAOF命令** ,这个命令会通过移除AOF文件中的冗余命令来重写(rewrite)AOF文件来减小AOF文件的体积。BGREWRITEAOF命令和BGSAVE创建快照原理十分相似,所以AOF文件重写也需要用到子进程,这样会导致性能问题和内存占用问题,和快照持久化一样。更糟糕的是,如果不加以控制的话,AOF文件的体积可能会比快照文件大好几倍。 80 | 81 | **文件重写流程:** 82 | 83 | ![文件重写流程](https://user-gold-cdn.xitu.io/2018/6/13/163f97f9bd0eea50?w=380&h=345&f=jpeg&s=14501) 84 | 和快照持久化可以通过设置save选项来自动执行BGSAVE一样,AOF持久化也可以通过设置 85 | 86 | ``` 87 | auto-aof-rewrite-percentage 88 | ``` 89 | 90 | 选项和 91 | 92 | ``` 93 | auto-aof-rewrite-min-size 94 | ``` 95 | 96 | 选项自动执行BGREWRITEAOF命令。举例:假设用户对Redis设置了如下配置选项并且启用了AOF持久化。那么当AOF文件体积大于64mb,并且AOF的体积比上一次重写之后的体积大了至少一倍(100%)的时候,Redis将执行BGREWRITEAOF命令。 97 | 98 | ``` 99 | auto-aof-rewrite-percentage 100 100 | auto-aof-rewrite-min-size 64mb 101 | ``` 102 | 103 | 无论是AOF持久化还是快照持久化,将数据持久化到硬盘上都是非常有必要的,但除了进行持久化外,用户还必须对持久化得到的文件进行备份(最好是备份到不同的地方),这样才能尽量避免数据丢失事故发生。如果条件允许的话,最好能将快照文件和重新重写的AOF文件备份到不同的服务器上面。 104 | 105 | 随着负载量的上升,或者数据的完整性变得 越来越重要时,用户可能需要使用到复制特性。 106 | 107 | ## Redis 4.0 对于持久化机制的优化 108 | Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 109 | 110 | 如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差。 111 | 112 | 参考: 113 | 114 | 《Redis实战》 115 | 116 | [深入学习Redis(2):持久化](https://www.cnblogs.com/kismetv/p/9137897.html) 117 | 118 | 119 | -------------------------------------------------------------------------------- /数据存储/Redis/Redlock分布式锁.md: -------------------------------------------------------------------------------- 1 | 这篇文章主要是对 Redis 官方网站刊登的 [Distributed locks with Redis](https://redis.io/topics/distlock) 部分内容的总结和翻译。 2 | 3 | ## 什么是 RedLock 4 | 5 | Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 *Redlock*,此种方式比原先的单节点的方法更安全。它可以保证以下特性: 6 | 7 | 1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁 8 | 2. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区 9 | 3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务 10 | 11 | ## 怎么在单节点上实现分布式锁 12 | 13 | > SET resource_name my_random_value NX PX 30000 14 | 15 | 主要依靠上述命令,该命令仅当 Key 不存在时(NX保证)set 值,并且设置过期时间 3000ms (PX保证),值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的,释放锁的逻辑是: 16 | 17 | ```lua 18 | if redis.call("get",KEYS[1]) == ARGV[1] then 19 | return redis.call("del",KEYS[1]) 20 | else 21 | return 0 22 | end 23 | ``` 24 | 25 | 上述实现可以避免释放另一个client创建的锁,如果只有 del 命令的话,那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间,此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2,那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了,但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string,方法很多随意选择一种就行了。 26 | 27 | ## Redlock 算法 28 | 29 | 算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作: 30 | 31 | 1. 得到当前的时间,微妙单位 32 | 2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间 33 | 3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。 34 | 4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间 35 | 5. 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态 36 | 37 | ## 失败重试 38 | 39 | 如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了。 40 | 41 | ## 放锁 42 | 43 | 放锁操作很简单,就是依次释放所有节点上的锁就行了 44 | 45 | ## 性能、崩溃恢复和 fsync 46 | 47 | 如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 **整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁!** 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。 48 | -------------------------------------------------------------------------------- /数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md: -------------------------------------------------------------------------------- 1 | 本文是对 [Martin Kleppmann](https://martin.kleppmann.com/) 的文章 [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html) 部分内容的翻译和总结,上次写 Redlock 的原因就是看到了 Martin 的这篇文章,写得很好,特此翻译和总结。感兴趣的同学可以翻看原文,相信会收获良多。 2 | 3 | 开篇作者认为现在 Redis 逐渐被使用到数据管理领域,这个领域需要更强的数据一致性和耐久性,这使得他感到担心,因为这不是 Redis 最初设计的初衷(事实上这也是很多业界程序员的误区,越来越把 Redis 当成数据库在使用),其中基于 Redis 的分布式锁就是令人担心的其一。 4 | 5 | Martin 指出首先你要明确你为什么使用分布式锁,为了性能还是正确性?为了帮你区分这二者,在这把锁 fail 了的时候你可以询问自己以下问题: 6 | 1. **要性能的:** 拥有这把锁使得你不会重复劳动(例如一个 job 做了两次),如果这把锁 fail 了,两个节点同时做了这个 Job,那么这个 Job 增加了你的成本。 7 | 2. **要正确性的:** 拥有锁可以防止并发操作污染你的系统或者数据,如果这把锁 fail 了两个节点同时操作了一份数据,结果可能是数据不一致、数据丢失、file 冲突等,会导致严重的后果。 8 | 9 | 上述二者都是需求锁的正确场景,但是你必须清楚自己是因为什么原因需要分布式锁。 10 | 11 | 如果你只是为了性能,那没必要用 Redlock,它成本高且复杂,你只用一个 Redis 实例也够了,最多加个从防止主挂了。当然,你使用单节点的 Redis 那么断电或者一些情况下,你会丢失锁,但是你的目的只是加速性能且断电这种事情不会经常发生,这并不是什么大问题。并且如果你使用了单节点 Redis,那么很显然你这个应用需要的锁粒度是很模糊粗糙的,也不会是什么重要的服务。 12 | 13 | 那么是否 Redlock 对于要求正确性的场景就合适呢?Martin 列举了若干场景证明 Redlock 这种算法是不可靠的。 14 | 15 | ## 用锁保护资源 16 | 这节里 Martin 先将 Redlock 放在了一边而是仅讨论总体上一个分布式锁是怎么工作的。在分布式环境下,锁比 mutex 这类复杂,因为涉及到不同节点、网络通信并且他们随时可能无征兆的 fail 。 17 | Martin 假设了一个场景,一个 client 要修改一个文件,它先申请得到锁,然后修改文件写回,放锁。另一个 client 再申请锁 ... 代码流程如下: 18 | 19 | ```java 20 | // THIS CODE IS BROKEN 21 | function writeData(filename, data) { 22 | var lock = lockService.acquireLock(filename); 23 | if (!lock) { 24 | throw 'Failed to acquire lock'; 25 | } 26 | 27 | try { 28 | var file = storage.readFile(filename); 29 | var updated = updateContents(file, data); 30 | storage.writeFile(filename, updated); 31 | } finally { 32 | lock.release(); 33 | } 34 | } 35 | ``` 36 | 37 | 可惜即使你的锁服务非常完美,上述代码还是可能跪,下面的流程图会告诉你为什么: 38 | 39 | ![](https://martin.kleppmann.com/2016/02/unsafe-lock.png) 40 | 41 | 上述图中,得到锁的 client1 在持有锁的期间 pause 了一段时间,例如 GC 停顿。锁有过期时间(一般叫租约,为了防止某个 client 崩溃之后一直占有锁),但是如果 GC 停顿太长超过了锁租约时间,此时锁已经被另一个 client2 所得到,原先的 client1 还没有感知到锁过期,那么奇怪的结果就会发生,曾经 HBase 就发生过这种 Bug。即使你在 client1 写回之前检查一下锁是否过期也无助于解决这个问题,因为 GC 可能在任何时候发生,即使是你非常不便的时候(在最后的检查与写操作期间)。 42 | 如果你认为自己的程序不会有长时间的 GC 停顿,还有其他原因会导致你的进程 pause。例如进程可能读取尚未进入内存的数据,所以它得到一个 page fault 并且等待 page 被加载进缓存;还有可能你依赖于网络服务;或者其他进程占用 CPU;或者其他人意外发生 SIGSTOP 等。 43 | 44 | ... .... 这里 Martin 又增加了一节列举各种进程 pause 的例子,为了证明上面的代码是不安全的,无论你的锁服务多完美。 45 | 46 | ## 使用 Fencing (栅栏)使得锁变安全 47 | 修复问题的方法也很简单:你需要在每次写操作时加入一个 fencing token。这个场景下,fencing token 可以是一个递增的数字(lock service 可以做到),每次有 client 申请锁就递增一次: 48 | 49 | ![](https://martin.kleppmann.com/2016/02/fencing-tokens.png) 50 | 51 | client1 申请锁同时拿到 token33,然后它进入长时间的停顿锁也过期了。client2 得到锁和 token34 写入数据,紧接着 client1 活过来之后尝试写入数据,自身 token33 比 34 小因此写入操作被拒绝。注意这需要存储层来检查 token,但这并不难实现。如果你使用 Zookeeper 作为 lock service 的话那么你可以使用 zxid 作为递增数字。 52 | 但是对于 Redlock 你要知道,没什么生成 fencing token 的方式,并且怎么修改 Redlock 算法使其能产生 fencing token 呢?好像并不那么显而易见。因为产生 token 需要单调递增,除非在单节点 Redis 上完成但是这又没有高可靠性,你好像需要引进一致性协议来让 Redlock 产生可靠的 fencing token。 53 | 54 | ## 使用时间来解决一致性 55 | Redlock 无法产生 fencing token 早该成为在需求正确性的场景下弃用它的理由,但还有一些值得讨论的地方。 56 | 57 | 学术界有个说法,算法对时间不做假设:因为进程可能pause一段时间、数据包可能因为网络延迟延后到达、时钟可能根本就是错的。而可靠的算法依旧要在上述假设下做正确的事情。 58 | 59 | 对于 failure detector 来说,timeout 只能作为猜测某个节点 fail 的依据,因为网络延迟、本地时钟不正确等其他原因的限制。考虑到 Redis 使用 gettimeofday,而不是单调的时钟,会受到系统时间的影响,可能会突然前进或者后退一段时间,这会导致一个 key 更快或更慢地过期。 60 | 61 | 可见,Redlock 依赖于许多时间假设,它假设所有 Redis 节点都能对同一个 Key 在其过期前持有差不多的时间、跟过期时间相比网络延迟很小、跟过期时间相比进程 pause 很短。 62 | 63 | ## 用不可靠的时间打破 Redlock 64 | 这节 Martin 举了个因为时间问题,Redlock 不可靠的例子。 65 | 66 | 1. client1 从 ABC 三个节点处申请到锁,DE由于网络原因请求没有到达 67 | 2. C节点的时钟往前推了,导致 lock 过期 68 | 3. client2 在CDE处获得了锁,AB由于网络原因请求未到达 69 | 4. 此时 client1 和 client2 都获得了锁 70 | 71 | **在 Redlock 官方文档中也提到了这个情况,不过是C崩溃的时候,Redlock 官方本身也是知道 Redlock 算法不是完全可靠的,官方为了解决这种问题建议使用延时启动,相关内容可以看之前的[这篇文章](https://zhuanlan.zhihu.com/p/40915772)。但是 Martin 这里分析得更加全面,指出延时启动不也是依赖于时钟的正确性的么?** 72 | 73 | 接下来 Martin 又列举了进程 Pause 时而不是时钟不可靠时会发生的问题: 74 | 75 | 1. client1 从 ABCDE 处获得了锁 76 | 2. 当获得锁的 response 还没到达 client1 时 client1 进入 GC 停顿 77 | 3. 停顿期间锁已经过期了 78 | 4. client2 在 ABCDE 处获得了锁 79 | 5. client1 GC 完成收到了获得锁的 response,此时两个 client 又拿到了同一把锁 80 | 81 | **同时长时间的网络延迟也有可能导致同样的问题。** 82 | 83 | ## Redlock 的同步性假设 84 | 这些例子说明了,仅有在你假设了一个同步性系统模型的基础上,Redlock 才能正常工作,也就是系统能满足以下属性: 85 | 86 | 1. 网络延时边界,即假设数据包一定能在某个最大延时之内到达 87 | 2. 进程停顿边界,即进程停顿一定在某个最大时间之内 88 | 3. 时钟错误边界,即不会从一个坏的 NTP 服务器处取得时间 89 | 90 | ## 结论 91 | Martin 认为 Redlock 实在不是一个好的选择,对于需求性能的分布式锁应用它太重了且成本高;对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设,如果你的应用只需要高性能的分布式锁不要求多高的正确性,那么单节点 Redis 够了;如果你的应用想要保住正确性,那么不建议 Redlock,建议使用一个合适的一致性协调系统,例如 Zookeeper,且保证存在 fencing token。 92 | -------------------------------------------------------------------------------- /数据结构与算法/Leetcode-LinkList1.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [1. 两数相加](#1-两数相加) 4 | - [题目描述](#题目描述) 5 | - [问题分析](#问题分析) 6 | - [Solution](#solution) 7 | - [2. 翻转链表](#2-翻转链表) 8 | - [题目描述](#题目描述-1) 9 | - [问题分析](#问题分析-1) 10 | - [Solution](#solution-1) 11 | - [3. 链表中倒数第k个节点](#3-链表中倒数第k个节点) 12 | - [题目描述](#题目描述-2) 13 | - [问题分析](#问题分析-2) 14 | - [Solution](#solution-2) 15 | - [4. 删除链表的倒数第N个节点](#4-删除链表的倒数第n个节点) 16 | - [问题分析](#问题分析-3) 17 | - [Solution](#solution-3) 18 | - [5. 合并两个排序的链表](#5-合并两个排序的链表) 19 | - [题目描述](#题目描述-3) 20 | - [问题分析](#问题分析-4) 21 | - [Solution](#solution-4) 22 | 23 | 24 | 25 | 26 | # 1. 两数相加 27 | 28 | ### 题目描述 29 | 30 | > Leetcode:给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。 31 | > 32 | >你可以假设除了数字 0 之外,这两个数字都不会以零开头。 33 | 34 | 示例: 35 | 36 | ``` 37 | 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 38 | 输出:7 -> 0 -> 8 39 | 原因:342 + 465 = 807 40 | ``` 41 | 42 | ### 问题分析 43 | 44 | Leetcode官方详细解答地址: 45 | 46 | https://leetcode-cn.com/problems/add-two-numbers/solution/ 47 | 48 | > 要对头结点进行操作时,考虑创建哑节点dummy,使用dummy->next表示真正的头节点。这样可以避免处理头节点为空的边界问题。 49 | 50 | 我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐 51 | 位相加的过程。 52 | 53 | ![图1,对两数相加方法的可视化: 342 + 465 = 807342+465=807, 每个结点都包含一个数字,并且数字按位逆序存储。](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/34910956.jpg) 54 | 55 | ### Solution 56 | 57 | **我们首先从最低有效位也就是列表 l1和 l2 的表头开始相加。注意需要考虑到进位的情况!** 58 | 59 | ```java 60 | /** 61 | * Definition for singly-linked list. 62 | * public class ListNode { 63 | * int val; 64 | * ListNode next; 65 | * ListNode(int x) { val = x; } 66 | * } 67 | */ 68 | //https://leetcode-cn.com/problems/add-two-numbers/description/ 69 | class Solution { 70 | public ListNode addTwoNumbers(ListNode l1, ListNode l2) { 71 | ListNode dummyHead = new ListNode(0); 72 | ListNode p = l1, q = l2, curr = dummyHead; 73 | //carry 表示进位数 74 | int carry = 0; 75 | while (p != null || q != null) { 76 | int x = (p != null) ? p.val : 0; 77 | int y = (q != null) ? q.val : 0; 78 | int sum = carry + x + y; 79 | //进位数 80 | carry = sum / 10; 81 | //新节点的数值为sum % 10 82 | curr.next = new ListNode(sum % 10); 83 | curr = curr.next; 84 | if (p != null) p = p.next; 85 | if (q != null) q = q.next; 86 | } 87 | if (carry > 0) { 88 | curr.next = new ListNode(carry); 89 | } 90 | return dummyHead.next; 91 | } 92 | } 93 | ``` 94 | 95 | # 2. 翻转链表 96 | 97 | 98 | ### 题目描述 99 | > 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。 100 | 101 | ![翻转链表](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/81431871.jpg) 102 | 103 | ### 问题分析 104 | 105 | 这道算法题,说直白点就是:如何让后一个节点指向前一个节点!在下面的代码中定义了一个 next 节点,该节点主要是保存要反转到头的那个节点,防止链表 “断裂”。 106 | 107 | ### Solution 108 | 109 | 110 | ```java 111 | public class ListNode { 112 | int val; 113 | ListNode next = null; 114 | 115 | ListNode(int val) { 116 | this.val = val; 117 | } 118 | } 119 | ``` 120 | 121 | ```java 122 | /** 123 | * 124 | * @author Snailclimb 125 | * @date 2018年9月19日 126 | * @Description: TODO 127 | */ 128 | public class Solution { 129 | 130 | public ListNode ReverseList(ListNode head) { 131 | 132 | ListNode next = null; 133 | ListNode pre = null; 134 | 135 | while (head != null) { 136 | // 保存要反转到头的那个节点 137 | next = head.next; 138 | // 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null) 139 | head.next = pre; 140 | // 上一个已经反转到头部的节点 141 | pre = head; 142 | // 一直向链表尾走 143 | head = next; 144 | } 145 | return pre; 146 | } 147 | 148 | } 149 | ``` 150 | 151 | 测试方法: 152 | 153 | ```java 154 | public static void main(String[] args) { 155 | 156 | ListNode a = new ListNode(1); 157 | ListNode b = new ListNode(2); 158 | ListNode c = new ListNode(3); 159 | ListNode d = new ListNode(4); 160 | ListNode e = new ListNode(5); 161 | a.next = b; 162 | b.next = c; 163 | c.next = d; 164 | d.next = e; 165 | new Solution().ReverseList(a); 166 | while (e != null) { 167 | System.out.println(e.val); 168 | e = e.next; 169 | } 170 | } 171 | ``` 172 | 173 | 输出: 174 | 175 | ``` 176 | 5 177 | 4 178 | 3 179 | 2 180 | 1 181 | ``` 182 | 183 | # 3. 链表中倒数第k个节点 184 | 185 | ### 题目描述 186 | 187 | > 剑指offer: 输入一个链表,输出该链表中倒数第k个结点。 188 | 189 | ### 问题分析 190 | 191 | > **链表中倒数第k个节点也就是正数第(L-K+1)个节点,知道了只一点,这一题基本就没问题!** 192 | 193 | 首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第k个节点也就是正数第(L-K+1)个节点。 194 | 195 | 196 | ### Solution 197 | 198 | ```java 199 | /* 200 | public class ListNode { 201 | int val; 202 | ListNode next = null; 203 | 204 | ListNode(int val) { 205 | this.val = val; 206 | } 207 | }*/ 208 | 209 | // 时间复杂度O(n),一次遍历即可 210 | // https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking 211 | public class Solution { 212 | public ListNode FindKthToTail(ListNode head, int k) { 213 | // 如果链表为空或者k小于等于0 214 | if (head == null || k <= 0) { 215 | return null; 216 | } 217 | // 声明两个指向头结点的节点 218 | ListNode node1 = head, node2 = head; 219 | // 记录节点的个数 220 | int count = 0; 221 | // 记录k值,后面要使用 222 | int index = k; 223 | // p指针先跑,并且记录节点数,当node1节点跑了k-1个节点后,node2节点开始跑, 224 | // 当node1节点跑到最后时,node2节点所指的节点就是倒数第k个节点 225 | while (node1 != null) { 226 | node1 = node1.next; 227 | count++; 228 | if (k < 1 && node1 != null) { 229 | node2 = node2.next; 230 | } 231 | k--; 232 | } 233 | // 如果节点个数小于所求的倒数第k个节点,则返回空 234 | if (count < index) 235 | return null; 236 | return node2; 237 | 238 | } 239 | } 240 | ``` 241 | 242 | 243 | # 4. 删除链表的倒数第N个节点 244 | 245 | 246 | > Leetcode:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 247 | 248 | **示例:** 249 | 250 | ``` 251 | 给定一个链表: 1->2->3->4->5, 和 n = 2. 252 | 253 | 当删除了倒数第二个节点后,链表变为 1->2->3->5. 254 | 255 | ``` 256 | 257 | **说明:** 258 | 259 | 给定的 n 保证是有效的。 260 | 261 | **进阶:** 262 | 263 | 你能尝试使用一趟扫描实现吗? 264 | 265 | 该题在 leetcode 上有详细解答,具体可参考 Leetcode. 266 | 267 | ### 问题分析 268 | 269 | 270 | 我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。 271 | 272 | ![图 1. 删除列表中的第 L - n + 1 个元素](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/94354387.jpg) 273 | 274 | ### Solution 275 | 276 | **两次遍历法** 277 | 278 | 首先我们将添加一个 **哑结点** 作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n) 个结点那里。**我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点,完成这个算法。** 279 | 280 | ```java 281 | /** 282 | * Definition for singly-linked list. 283 | * public class ListNode { 284 | * int val; 285 | * ListNode next; 286 | * ListNode(int x) { val = x; } 287 | * } 288 | */ 289 | // https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/ 290 | public class Solution { 291 | public ListNode removeNthFromEnd(ListNode head, int n) { 292 | // 哑结点,哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部 293 | ListNode dummy = new ListNode(0); 294 | // 哑结点指向头结点 295 | dummy.next = head; 296 | // 保存链表长度 297 | int length = 0; 298 | ListNode len = head; 299 | while (len != null) { 300 | length++; 301 | len = len.next; 302 | } 303 | length = length - n; 304 | ListNode target = dummy; 305 | // 找到 L-n 位置的节点 306 | while (length > 0) { 307 | target = target.next; 308 | length--; 309 | } 310 | // 把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点 311 | target.next = target.next.next; 312 | return dummy.next; 313 | } 314 | } 315 | ``` 316 | 317 | **复杂度分析:** 318 | 319 | - **时间复杂度 O(L)** :该算法对列表进行了两次遍历,首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步,时间复杂度为 O(L)O(L)。 320 | - **空间复杂度 O(1)** :我们只用了常量级的额外空间。 321 | 322 | 323 | 324 | **进阶——一次遍历法:** 325 | 326 | 327 | > **链表中倒数第N个节点也就是正数第(L-N+1)个节点。 328 | 329 | 其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点) 330 | 331 | ```java 332 | /** 333 | * Definition for singly-linked list. 334 | * public class ListNode { 335 | * int val; 336 | * ListNode next; 337 | * ListNode(int x) { val = x; } 338 | * } 339 | */ 340 | public class Solution { 341 | public ListNode removeNthFromEnd(ListNode head, int n) { 342 | 343 | ListNode dummy = new ListNode(0); 344 | dummy.next = head; 345 | // 声明两个指向头结点的节点 346 | ListNode node1 = dummy, node2 = dummy; 347 | 348 | // node1 节点先跑,node1节点 跑到第 n 个节点的时候,node2 节点开始跑 349 | // 当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点,也就是倒数第 n+1(L代表总链表长度) 350 | while (node1 != null) { 351 | node1 = node1.next; 352 | if (n < 1 && node1 != null) { 353 | node2 = node2.next; 354 | } 355 | n--; 356 | } 357 | 358 | node2.next = node2.next.next; 359 | 360 | return dummy.next; 361 | 362 | } 363 | } 364 | ``` 365 | 366 | 367 | 368 | 369 | 370 | # 5. 合并两个排序的链表 371 | 372 | ### 题目描述 373 | 374 | > 剑指offer:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 375 | 376 | ### 问题分析 377 | 378 | 我们可以这样分析: 379 | 380 | 1. 假设我们有两个链表 A,B; 381 | 2. A的头节点A1的值与B的头结点B1的值比较,假设A1小,则A1为头节点; 382 | 3. A2再和B1比较,假设B1小,则,A1指向B1; 383 | 4. A2再和B2比较 384 | 就这样循环往复就行了,应该还算好理解。 385 | 386 | 考虑通过递归的方式实现! 387 | 388 | ### Solution 389 | 390 | **递归版本:** 391 | 392 | ```java 393 | /* 394 | public class ListNode { 395 | int val; 396 | ListNode next = null; 397 | 398 | ListNode(int val) { 399 | this.val = val; 400 | } 401 | }*/ 402 | //https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking 403 | public class Solution { 404 | public ListNode Merge(ListNode list1,ListNode list2) { 405 | if(list1 == null){ 406 | return list2; 407 | } 408 | if(list2 == null){ 409 | return list1; 410 | } 411 | if(list1.val <= list2.val){ 412 | list1.next = Merge(list1.next, list2); 413 | return list1; 414 | }else{ 415 | list2.next = Merge(list1, list2.next); 416 | return list2; 417 | } 418 | } 419 | } 420 | ``` 421 | 422 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | securityAlgorithm 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 3 | org.eclipse.jdt.core.compiler.compliance=1.5 4 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 5 | org.eclipse.jdt.core.compiler.source=1.5 6 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.snailclimb.ks 6 | securityAlgorithm 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | securityAlgorithm 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | junit 20 | junit 21 | 4.12 22 | test 23 | 24 | 25 | 26 | commons-codec 27 | commons-codec 28 | 1.8 29 | 30 | 31 | org.bouncycastle 32 | bcprov-jdk15on 33 | 1.56 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/Base64Demo.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.util.Base64; 5 | 6 | public class Base64Demo { 7 | 8 | public static void main(String[] args) throws UnsupportedEncodingException { 9 | // TODO Auto-generated method stub 10 | CommonsCodecDemo(); 11 | bouncyCastleDemo(); 12 | jdkDemo(); 13 | } 14 | 15 | static String str = "你若安好,便是晴天"; 16 | 17 | /** 18 | * commons codec实现Base64加密解密 19 | */ 20 | public static void CommonsCodecDemo() { 21 | // 加密: 22 | byte[] encodeBytes = org.apache.commons.codec.binary.Base64.encodeBase64(str.getBytes()); 23 | System.out.println("commons codec实现base64加密: " + new String(encodeBytes)); 24 | // 解密: 25 | byte[] decodeBytes = org.apache.commons.codec.binary.Base64.decodeBase64(encodeBytes); 26 | System.out.println("commons codec实现base64解密: " + new String(decodeBytes)); 27 | } 28 | 29 | /** 30 | * bouncy castle实现Base64加密解密 31 | */ 32 | public static void bouncyCastleDemo() { 33 | // 加密 34 | byte[] encodeBytes = org.bouncycastle.util.encoders.Base64.encode(str.getBytes()); 35 | System.out.println("bouncy castle实现base64加密: " + new String(encodeBytes)); 36 | // 解密 37 | byte[] decodeBytes = org.bouncycastle.util.encoders.Base64.decode(encodeBytes); 38 | System.out.println("bouncy castle实现base64解密:" + new String(decodeBytes)); 39 | } 40 | 41 | public static void jdkDemo() throws UnsupportedEncodingException { 42 | // 加密 43 | String encodeBytes = Base64.getEncoder().encodeToString(str.getBytes("UTF-8")); 44 | System.out.println("JDK实现的base64加密: " + encodeBytes); 45 | //解密 46 | byte[] decodeBytes = Base64.getDecoder().decode(encodeBytes.getBytes("UTF-8")); 47 | System.out.println("JDK实现的base64解密: "+new String(decodeBytes)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/DesDemo.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.security.SecureRandom; 5 | import javax.crypto.spec.DESKeySpec; 6 | import javax.crypto.SecretKeyFactory; 7 | import javax.crypto.SecretKey; 8 | import javax.crypto.Cipher; 9 | 10 | /** 11 | * DES加密介绍 DES是一种对称加密算法,所谓对称加密算法即:加密和解密使用相同密钥的算法。DES加密算法出自IBM的研究, 12 | * 后来被美国政府正式采用,之后开始广泛流传,但是近些年使用越来越少,因为DES使用56位密钥,以现代计算能力, 13 | * 24小时内即可被破解。虽然如此,在某些简单应用中,我们还是可以使用DES加密算法,本文简单讲解DES的JAVA实现 。 14 | * 注意:DES加密和解密过程中,密钥长度都必须是8的倍数 15 | */ 16 | public class DesDemo { 17 | public DesDemo() { 18 | } 19 | 20 | // 测试 21 | public static void main(String args[]) { 22 | // 待加密内容 23 | String str = "cryptology"; 24 | // 密码,长度要是8的倍数 25 | String password = "95880288"; 26 | 27 | byte[] result; 28 | try { 29 | result = DesDemo.encrypt(str.getBytes(), password); 30 | System.out.println("加密后:" + result); 31 | byte[] decryResult = DesDemo.decrypt(result, password); 32 | System.out.println("解密后:" + decryResult); 33 | } catch (UnsupportedEncodingException e2) { 34 | // TODO Auto-generated catch block 35 | e2.printStackTrace(); 36 | } catch (Exception e1) { 37 | e1.printStackTrace(); 38 | } 39 | } 40 | 41 | // 直接将如上内容解密 42 | 43 | /** 44 | * 加密 45 | * 46 | * @param datasource 47 | * byte[] 48 | * @param password 49 | * String 50 | * @return byte[] 51 | */ 52 | public static byte[] encrypt(byte[] datasource, String password) { 53 | try { 54 | SecureRandom random = new SecureRandom(); 55 | DESKeySpec desKey = new DESKeySpec(password.getBytes()); 56 | // 创建一个密匙工厂,然后用它把DESKeySpec转换成 57 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); 58 | SecretKey securekey = keyFactory.generateSecret(desKey); 59 | // Cipher对象实际完成加密操作 60 | Cipher cipher = Cipher.getInstance("DES"); 61 | // 用密匙初始化Cipher对象,ENCRYPT_MODE用于将 Cipher 初始化为加密模式的常量 62 | cipher.init(Cipher.ENCRYPT_MODE, securekey, random); 63 | // 现在,获取数据并加密 64 | // 正式执行加密操作 65 | return cipher.doFinal(datasource); // 按单部分操作加密或解密数据,或者结束一个多部分操作 66 | } catch (Throwable e) { 67 | e.printStackTrace(); 68 | } 69 | return null; 70 | } 71 | 72 | /** 73 | * 解密 74 | * 75 | * @param src 76 | * byte[] 77 | * @param password 78 | * String 79 | * @return byte[] 80 | * @throws Exception 81 | */ 82 | public static byte[] decrypt(byte[] src, String password) throws Exception { 83 | // DES算法要求有一个可信任的随机数源 84 | SecureRandom random = new SecureRandom(); 85 | // 创建一个DESKeySpec对象 86 | DESKeySpec desKey = new DESKeySpec(password.getBytes()); 87 | // 创建一个密匙工厂 88 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");// 返回实现指定转换的 89 | // Cipher 90 | // 对象 91 | // 将DESKeySpec对象转换成SecretKey对象 92 | SecretKey securekey = keyFactory.generateSecret(desKey); 93 | // Cipher对象实际完成解密操作 94 | Cipher cipher = Cipher.getInstance("DES"); 95 | // 用密匙初始化Cipher对象 96 | cipher.init(Cipher.DECRYPT_MODE, securekey, random); 97 | // 真正开始解密操作 98 | return cipher.doFinal(src); 99 | } 100 | } -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/IDEADemo.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | import java.security.Key; 4 | import java.security.Security; 5 | 6 | import javax.crypto.Cipher; 7 | import javax.crypto.KeyGenerator; 8 | import javax.crypto.SecretKey; 9 | import javax.crypto.spec.SecretKeySpec; 10 | 11 | import org.apache.commons.codec.binary.Base64; 12 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 13 | 14 | public class IDEADemo { 15 | public static void main(String args[]) { 16 | bcIDEA(); 17 | } 18 | public static void bcIDEA() { 19 | String src = "www.xttblog.com security idea"; 20 | try { 21 | Security.addProvider(new BouncyCastleProvider()); 22 | 23 | //生成key 24 | KeyGenerator keyGenerator = KeyGenerator.getInstance("IDEA"); 25 | keyGenerator.init(128); 26 | SecretKey secretKey = keyGenerator.generateKey(); 27 | byte[] keyBytes = secretKey.getEncoded(); 28 | 29 | //转换密钥 30 | Key key = new SecretKeySpec(keyBytes, "IDEA"); 31 | 32 | //加密 33 | Cipher cipher = Cipher.getInstance("IDEA/ECB/ISO10126Padding"); 34 | cipher.init(Cipher.ENCRYPT_MODE, key); 35 | byte[] result = cipher.doFinal(src.getBytes()); 36 | System.out.println("bc idea encrypt : " + Base64.encodeBase64String(result)); 37 | 38 | //解密 39 | cipher.init(Cipher.DECRYPT_MODE, key); 40 | result = cipher.doFinal(result); 41 | System.out.println("bc idea decrypt : " + new String(result)); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | public class MD5{ 4 | /* 5 | *四个链接变量 6 | */ 7 | private final int A=0x67452301; 8 | private final int B=0xefcdab89; 9 | private final int C=0x98badcfe; 10 | private final int D=0x10325476; 11 | /* 12 | *ABCD的临时变量 13 | */ 14 | private int Atemp,Btemp,Ctemp,Dtemp; 15 | 16 | /* 17 | *常量ti 18 | *公式:floor(abs(sin(i+1))×(2pow32) 19 | */ 20 | private final int K[]={ 21 | 0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee, 22 | 0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8, 23 | 0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193, 24 | 0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51, 25 | 0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8, 26 | 0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905, 27 | 0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681, 28 | 0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60, 29 | 0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05, 30 | 0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244, 31 | 0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92, 32 | 0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314, 33 | 0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391}; 34 | /* 35 | *向左位移数,计算方法未知 36 | */ 37 | private final int s[]={7,12,17,22,7,12,17,22,7,12,17,22,7, 38 | 12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20, 39 | 4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10, 40 | 15,21,6,10,15,21,6,10,15,21,6,10,15,21}; 41 | 42 | 43 | /* 44 | *初始化函数 45 | */ 46 | private void init(){ 47 | Atemp=A; 48 | Btemp=B; 49 | Ctemp=C; 50 | Dtemp=D; 51 | } 52 | /* 53 | *移动一定位数 54 | */ 55 | private int shift(int a,int s){ 56 | return(a<>>(32-s));//右移的时候,高位一定要补零,而不是补充符号位 57 | } 58 | /* 59 | *主循环 60 | */ 61 | private void MainLoop(int M[]){ 62 | int F,g; 63 | int a=Atemp; 64 | int b=Btemp; 65 | int c=Ctemp; 66 | int d=Dtemp; 67 | for(int i = 0; i < 64; i ++){ 68 | if(i<16){ 69 | F=(b&c)|((~b)&d); 70 | g=i; 71 | }else if(i<32){ 72 | F=(d&b)|((~d)&c); 73 | g=(5*i+1)%16; 74 | }else if(i<48){ 75 | F=b^c^d; 76 | g=(3*i+5)%16; 77 | }else{ 78 | F=c^(b|(~d)); 79 | g=(7*i)%16; 80 | } 81 | int tmp=d; 82 | d=c; 83 | c=b; 84 | b=b+shift(a+F+K[i]+M[g],s[i]); 85 | a=tmp; 86 | } 87 | Atemp=a+Atemp; 88 | Btemp=b+Btemp; 89 | Ctemp=c+Ctemp; 90 | Dtemp=d+Dtemp; 91 | 92 | } 93 | /* 94 | *填充函数 95 | *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64) 96 | *填充方式为先加一个0,其它位补零 97 | *最后加上64位的原来长度 98 | */ 99 | private int[] add(String str){ 100 | int num=((str.length()+8)/64)+1;//以512位,64个字节为一组 101 | int strByte[]=new int[num*16];//64/4=16,所以有16个整数 102 | for(int i=0;i>2]|=str.charAt(i)<<((i%4)*8);//一个整数存储四个字节,小端序 108 | } 109 | strByte[i>>2]|=0x80<<((i%4)*8);//尾部添加1 110 | /* 111 | *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位 112 | */ 113 | strByte[num*16-2]=str.length()*8; 114 | return strByte; 115 | } 116 | /* 117 | *调用函数 118 | */ 119 | public String getMD5(String source){ 120 | init(); 121 | int strByte[]=add(source); 122 | for(int i=0;i>i*8)%(1<<8))&0xff)).replace(' ', '0'); 139 | 140 | } 141 | return str; 142 | } 143 | /* 144 | *单例 145 | */ 146 | private static MD5 instance; 147 | public static MD5 getInstance(){ 148 | if(instance==null){ 149 | instance=new MD5(); 150 | } 151 | return instance; 152 | } 153 | 154 | private MD5(){}; 155 | 156 | public static void main(String[] args){ 157 | String str=MD5.getInstance().getMD5("你若安好,便是晴天"); 158 | System.out.println(str); 159 | } 160 | } -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5Demo.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | import java.security.MessageDigest; 4 | 5 | public class MD5Demo { 6 | 7 | // test 8 | public static void main(String[] args) { 9 | System.out.println(getMD5Code("你若安好,便是晴天")); 10 | } 11 | 12 | private MD5Demo() { 13 | } 14 | 15 | // md5加密 16 | public static String getMD5Code(String message) { 17 | String md5Str = ""; 18 | try { 19 | //创建MD5算法消息摘要 20 | MessageDigest md = MessageDigest.getInstance("MD5"); 21 | //生成的哈希值的字节数组 22 | byte[] md5Bytes = md.digest(message.getBytes()); 23 | md5Str = bytes2Hex(md5Bytes); 24 | }catch(Exception e) { 25 | e.printStackTrace(); 26 | } 27 | return md5Str; 28 | } 29 | 30 | // 2进制转16进制 31 | public static String bytes2Hex(byte[] bytes) { 32 | StringBuffer result = new StringBuffer(); 33 | int temp; 34 | try { 35 | for (int i = 0; i < bytes.length; i++) { 36 | temp = bytes[i]; 37 | if(temp < 0) { 38 | temp += 256; 39 | } 40 | if (temp < 16) { 41 | result.append("0"); 42 | } 43 | result.append(Integer.toHexString(temp)); 44 | } 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | } 48 | return result.toString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/RSADemo.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | 5 | import java.security.*; 6 | import java.security.spec.PKCS8EncodedKeySpec; 7 | import java.security.spec.X509EncodedKeySpec; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import javax.crypto.Cipher; 12 | 13 | /** 14 | * Created by humf.需要依赖 commons-codec 包 15 | */ 16 | public class RSADemo { 17 | 18 | public static void main(String[] args) throws Exception { 19 | Map keyMap = initKey(); 20 | String publicKey = getPublicKey(keyMap); 21 | String privateKey = getPrivateKey(keyMap); 22 | 23 | System.out.println(keyMap); 24 | System.out.println("-----------------------------------"); 25 | System.out.println(publicKey); 26 | System.out.println("-----------------------------------"); 27 | System.out.println(privateKey); 28 | System.out.println("-----------------------------------"); 29 | byte[] encryptByPrivateKey = encryptByPrivateKey("123456".getBytes(), privateKey); 30 | byte[] encryptByPublicKey = encryptByPublicKey("123456", publicKey); 31 | System.out.println(encryptByPrivateKey); 32 | System.out.println("-----------------------------------"); 33 | System.out.println(encryptByPublicKey); 34 | System.out.println("-----------------------------------"); 35 | String sign = sign(encryptByPrivateKey, privateKey); 36 | System.out.println(sign); 37 | System.out.println("-----------------------------------"); 38 | boolean verify = verify(encryptByPrivateKey, publicKey, sign); 39 | System.out.println(verify); 40 | System.out.println("-----------------------------------"); 41 | byte[] decryptByPublicKey = decryptByPublicKey(encryptByPrivateKey, publicKey); 42 | byte[] decryptByPrivateKey = decryptByPrivateKey(encryptByPublicKey, privateKey); 43 | System.out.println(decryptByPublicKey); 44 | System.out.println("-----------------------------------"); 45 | System.out.println(decryptByPrivateKey); 46 | 47 | } 48 | 49 | public static final String KEY_ALGORITHM = "RSA"; 50 | public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; 51 | 52 | private static final String PUBLIC_KEY = "RSAPublicKey"; 53 | private static final String PRIVATE_KEY = "RSAPrivateKey"; 54 | 55 | public static byte[] decryptBASE64(String key) { 56 | return Base64.decodeBase64(key); 57 | } 58 | 59 | public static String encryptBASE64(byte[] bytes) { 60 | return Base64.encodeBase64String(bytes); 61 | } 62 | 63 | /** 64 | * 用私钥对信息生成数字签名 65 | * 66 | * @param data 67 | * 加密数据 68 | * @param privateKey 69 | * 私钥 70 | * @return 71 | * @throws Exception 72 | */ 73 | public static String sign(byte[] data, String privateKey) throws Exception { 74 | // 解密由base64编码的私钥 75 | byte[] keyBytes = decryptBASE64(privateKey); 76 | // 构造PKCS8EncodedKeySpec对象 77 | PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); 78 | // KEY_ALGORITHM 指定的加密算法 79 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); 80 | // 取私钥匙对象 81 | PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec); 82 | // 用私钥对信息生成数字签名 83 | Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); 84 | signature.initSign(priKey); 85 | signature.update(data); 86 | return encryptBASE64(signature.sign()); 87 | } 88 | 89 | /** 90 | * 校验数字签名 91 | * 92 | * @param data 93 | * 加密数据 94 | * @param publicKey 95 | * 公钥 96 | * @param sign 97 | * 数字签名 98 | * @return 校验成功返回true 失败返回false 99 | * @throws Exception 100 | */ 101 | public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { 102 | // 解密由base64编码的公钥 103 | byte[] keyBytes = decryptBASE64(publicKey); 104 | // 构造X509EncodedKeySpec对象 105 | X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); 106 | // KEY_ALGORITHM 指定的加密算法 107 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); 108 | // 取公钥匙对象 109 | PublicKey pubKey = keyFactory.generatePublic(keySpec); 110 | Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); 111 | signature.initVerify(pubKey); 112 | signature.update(data); 113 | // 验证签名是否正常 114 | return signature.verify(decryptBASE64(sign)); 115 | } 116 | 117 | public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception { 118 | // 对密钥解密 119 | byte[] keyBytes = decryptBASE64(key); 120 | // 取得私钥 121 | PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); 122 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); 123 | Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); 124 | // 对数据解密 125 | Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); 126 | cipher.init(Cipher.DECRYPT_MODE, privateKey); 127 | return cipher.doFinal(data); 128 | } 129 | 130 | /** 131 | * 解密
132 | * 用私钥解密 133 | * 134 | * @param data 135 | * @param key 136 | * @return 137 | * @throws Exception 138 | */ 139 | public static byte[] decryptByPrivateKey(String data, String key) throws Exception { 140 | return decryptByPrivateKey(decryptBASE64(data), key); 141 | } 142 | 143 | /** 144 | * 解密
145 | * 用公钥解密 146 | * 147 | * @param data 148 | * @param key 149 | * @return 150 | * @throws Exception 151 | */ 152 | public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception { 153 | // 对密钥解密 154 | byte[] keyBytes = decryptBASE64(key); 155 | // 取得公钥 156 | X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); 157 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); 158 | Key publicKey = keyFactory.generatePublic(x509KeySpec); 159 | // 对数据解密 160 | Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); 161 | cipher.init(Cipher.DECRYPT_MODE, publicKey); 162 | return cipher.doFinal(data); 163 | } 164 | 165 | /** 166 | * 加密
167 | * 用公钥加密 168 | * 169 | * @param data 170 | * @param key 171 | * @return 172 | * @throws Exception 173 | */ 174 | public static byte[] encryptByPublicKey(String data, String key) throws Exception { 175 | // 对公钥解密 176 | byte[] keyBytes = decryptBASE64(key); 177 | // 取得公钥 178 | X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); 179 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); 180 | Key publicKey = keyFactory.generatePublic(x509KeySpec); 181 | // 对数据加密 182 | Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); 183 | cipher.init(Cipher.ENCRYPT_MODE, publicKey); 184 | return cipher.doFinal(data.getBytes()); 185 | } 186 | 187 | /** 188 | * 加密
189 | * 用私钥加密 190 | * 191 | * @param data 192 | * @param key 193 | * @return 194 | * @throws Exception 195 | */ 196 | public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception { 197 | // 对密钥解密 198 | byte[] keyBytes = decryptBASE64(key); 199 | // 取得私钥 200 | PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); 201 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); 202 | Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); 203 | // 对数据加密 204 | Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); 205 | cipher.init(Cipher.ENCRYPT_MODE, privateKey); 206 | return cipher.doFinal(data); 207 | } 208 | 209 | /** 210 | * 取得私钥 211 | * 212 | * @param keyMap 213 | * @return 214 | * @throws Exception 215 | */ 216 | public static String getPrivateKey(Map keyMap) throws Exception { 217 | Key key = (Key) keyMap.get(PRIVATE_KEY); 218 | return encryptBASE64(key.getEncoded()); 219 | } 220 | 221 | /** 222 | * 取得公钥 223 | * 224 | * @param keyMap 225 | * @return 226 | * @throws Exception 227 | */ 228 | public static String getPublicKey(Map keyMap) throws Exception { 229 | Key key = keyMap.get(PUBLIC_KEY); 230 | return encryptBASE64(key.getEncoded()); 231 | } 232 | 233 | /** 234 | * 初始化密钥 235 | * 236 | * @return 237 | * @throws Exception 238 | */ 239 | public static Map initKey() throws Exception { 240 | KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); 241 | keyPairGen.initialize(1024); 242 | KeyPair keyPair = keyPairGen.generateKeyPair(); 243 | Map keyMap = new HashMap(2); 244 | keyMap.put(PUBLIC_KEY, keyPair.getPublic());// 公钥 245 | keyMap.put(PRIVATE_KEY, keyPair.getPrivate());// 私钥 246 | return keyMap; 247 | } 248 | 249 | } -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/SHA1Demo.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | public class SHA1Demo { 8 | 9 | public static void main(String[] args) { 10 | // TODO Auto-generated method stub 11 | System.out.println(getSha1("你若安好,便是晴天")); 12 | 13 | } 14 | 15 | public static String getSha1(String str) { 16 | if (null == str || 0 == str.length()) { 17 | return null; 18 | } 19 | char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 20 | try { 21 | //创建SHA1算法消息摘要对象 22 | MessageDigest mdTemp = MessageDigest.getInstance("SHA1"); 23 | //使用指定的字节数组更新摘要。 24 | mdTemp.update(str.getBytes("UTF-8")); 25 | //生成的哈希值的字节数组 26 | byte[] md = mdTemp.digest(); 27 | //SHA1算法生成信息摘要关键过程 28 | int j = md.length; 29 | char[] buf = new char[j * 2]; 30 | int k = 0; 31 | for (int i = 0; i < j; i++) { 32 | byte byte0 = md[i]; 33 | buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; 34 | buf[k++] = hexDigits[byte0 & 0xf]; 35 | } 36 | return new String(buf); 37 | } catch (NoSuchAlgorithmException e) { 38 | e.printStackTrace(); 39 | } catch (UnsupportedEncodingException e) { 40 | e.printStackTrace(); 41 | } 42 | return "0"; 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/readme: -------------------------------------------------------------------------------- 1 | Des算法参考:http://blog.csdn.net/super_cui/article/details/70820983 2 | IDEA算法参考:https://www.xttblog.com/?p=1121 3 | RSA算法实现参考:https://www.cnblogs.com/xlhan/p/7120488.html -------------------------------------------------------------------------------- /数据结构与算法/source code/securityAlgorithm/src/test/java/com/snailclimb/ks/securityAlgorithm/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.snailclimb.ks.securityAlgorithm; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /数据结构与算法/数据结构.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [Queue](#queue) 4 | - [什么是队列](#什么是队列) 5 | - [队列的种类](#队列的种类) 6 | - [Java 集合框架中的队列 Queue](#java-集合框架中的队列-queue) 7 | - [推荐文章](#推荐文章) 8 | - [Set](#set) 9 | - [什么是 Set](#什么是-set) 10 | - [补充:有序集合与无序集合说明](#补充:有序集合与无序集合说明) 11 | - [HashSet 和 TreeSet 底层数据结构](#hashset-和-treeset-底层数据结构) 12 | - [推荐文章](#推荐文章-1) 13 | - [List](#list) 14 | - [什么是List](#什么是list) 15 | - [List的常见实现类](#list的常见实现类) 16 | - [ArrayList 和 LinkedList 源码学习](#arraylist-和-linkedlist-源码学习) 17 | - [推荐阅读](#推荐阅读) 18 | - [Map](#map) 19 | - [树](#树) 20 | 21 | 22 | 23 | 24 | ## Queue 25 | 26 | ### 什么是队列 27 | 队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。 28 | 29 | ### 队列的种类 30 | 31 | - **单队列**(单队列就是常见的队列, 每次添加元素时,都是添加到队尾,存在“假溢出”的问题也就是明明有位置却不能添加的情况) 32 | - **循环队列**(避免了“假溢出”的问题) 33 | 34 | ### Java 集合框架中的队列 Queue 35 | 36 | Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。 37 | Queue 用来存放 等待处理元素 的集合,这种场景一般用于缓冲、并发访问。 38 | 除了继承 Collection 接口的一些方法,Queue 还添加了额外的 添加、删除、查询操作。 39 | 40 | ### 推荐文章 41 | 42 | - [Java 集合深入理解(9):Queue 队列](https://blog.csdn.net/u011240877/article/details/52860924) 43 | 44 | ## Set 45 | 46 | ### 什么是 Set 47 | Set 继承于 Collection 接口,是一个不允许出现重复元素,并且无序的集合,主要 HashSet 和 TreeSet 两大实现类。 48 | 49 | 在判断重复元素的时候,Set 集合会调用 hashCode()和 equal()方法来实现。 50 | 51 | ### 补充:有序集合与无序集合说明 52 | - 有序集合:集合里的元素可以根据 key 或 index 访问 (List、Map) 53 | - 无序集合:集合里的元素只能遍历。(Set) 54 | 55 | 56 | ### HashSet 和 TreeSet 底层数据结构 57 | 58 | **HashSet** 是哈希表结构,主要利用 HashMap 的 key 来存储元素,计算插入元素的 hashCode 来获取元素在集合中的位置; 59 | 60 | **TreeSet** 是红黑树结构,每一个元素都是树中的一个节点,插入的元素都会进行排序; 61 | 62 | 63 | ### 推荐文章 64 | 65 | - [Java集合--Set(基础)](https://www.jianshu.com/p/b48c47a42916) 66 | 67 | ## List 68 | 69 | ### 什么是List 70 | 71 | 在 List 中,用户可以精确控制列表中每个元素的插入位置,另外用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。 与 Set 不同,List 通常允许重复的元素。 另外 List 是有序集合而 Set 是无序集合。 72 | 73 | ### List的常见实现类 74 | 75 | **ArrayList** 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。 76 | 77 | **LinkedList** 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。 78 | 79 | **Vector** 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。 80 | 81 | **Stack** 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。相关阅读:[java数据结构与算法之栈(Stack)设计与实现](https://blog.csdn.net/javazejian/article/details/53362993) 82 | 83 | ### ArrayList 和 LinkedList 源码学习 84 | - [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) 85 | - [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) 86 | 87 | ### 推荐阅读 88 | 89 | - [java 数据结构与算法之顺序表与链表深入分析](https://blog.csdn.net/javazejian/article/details/52953190) 90 | 91 | 92 | ## Map 93 | 94 | 95 | - [集合框架源码学习之 HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56) 96 | - [ConcurrentHashMap 实现原理及源码分析](https://link.juejin.im/?target=http%3A%2F%2Fwww.cnblogs.com%2Fchengxiao%2Fp%2F6842045.html) 97 | 98 | ## 树 99 | * ### 1 二叉树 100 | 101 | [二叉树](https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) 102 | 103 | (1)[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。 104 | 105 | (2)[满二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。 106 | 107 | (3)[平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91/10421057)——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 108 | 109 | * ### 2 完全二叉树 110 | 111 | [完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) 112 | 113 | 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树 114 | * ### 3 满二叉树 115 | 116 | [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同) 117 | 118 | 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。 119 | * ### 堆 120 | 121 | [数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191) 122 | 123 | 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆 124 | * ### 4 二叉查找树(BST) 125 | 126 | [浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html) 127 | 128 | 二叉查找树的特点: 129 | 130 | 1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值; 131 | 2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 132 | 3. 任意节点的左、右子树也分别为二叉查找树。 133 | 4. 没有键值相等的节点(no duplicate nodes)。 134 | 135 | * ### 5 平衡二叉树(Self-balancing binary search tree) 136 | 137 | [ 平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等) 138 | * ### 6 红黑树 139 | 140 | - 红黑树特点: 141 | 1. 每个节点非红即黑; 142 | 2. 根节点总是黑色的; 143 | 3. 每个叶子节点都是黑色的空节点(NIL节点); 144 | 4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定); 145 | 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度) 146 | 147 | - 红黑树的应用: 148 | 149 | TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。 150 | 151 | - 为什么要用红黑树 152 | 153 | 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) 154 | 155 | - 推荐文章: 156 | - [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) 157 | - [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错) 158 | - [红黑树深入剖析及Java实现](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队) 159 | * ### 7 B-,B+,B*树 160 | 161 | [二叉树学习笔记之B树、B+树、B*树 ](https://yq.aliyun.com/articles/38345) 162 | 163 | [《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186) 164 | 165 | [《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405) 166 | 167 | B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) 168 | 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 169 | 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。 170 | 3. B*树 是B+树的变体,B*树分配新结点的概率比B+树要低,空间使用率更高; 171 | * ### 8 LSM 树 172 | 173 | [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599) 174 | 175 | B+树最大的性能问题是会产生大量的随机IO 176 | 177 | 为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。 178 | 179 | [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html) 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /数据结构与算法/算法.md: -------------------------------------------------------------------------------- 1 | 2 | ## LeetCode 3 | [LeetCode(中国)官网](https://leetcode-cn.com/) 4 | 5 | [如何高效地使用 LeetCode](https://leetcode-cn.com/articles/%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E4%BD%BF%E7%94%A8-leetcode/) 6 | 7 | 8 | ## 牛客网: 9 | 10 | [牛客网首页](https://www.nowcoder.com) 11 | 12 | 13 | > ### **[剑指offer编程题](https://www.nowcoder.com/ta/coding-interviews)** 14 | 15 | **分类解析:** 16 | - [(1)斐波那契数列问题和跳台阶问题](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(1)斐波那契数列问题和跳台阶问题.md) 17 | - [(2)二维数组查找和替换空格问题](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(2)二维数组查找和替换空格问题.md) 18 | - [(3)数值的整数次方和调整数组元素顺序](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(3)数值的整数次方和调整数组元素顺序.md) 19 | - [(4)链表相关编程题](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(4)链表相关编程题.md) 20 | - [(5)栈变队列和栈的压入、弹出序列](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(5)栈变队列和栈的压入、弹出序列.md) 21 | 22 | > ### [2017校招真题](https://www.nowcoder.com/ta/2017test) 23 | 24 | > ### [华为机试题](https://www.nowcoder.com/ta/huawei) 25 | 26 | 27 | ## 公司真题 28 | 29 | > [ 网易2018校园招聘编程题真题集合](https://www.nowcoder.com/test/6910869/summary) 30 | 31 | **解析:** 32 | - [ 网易2018校招编程题1-3](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/公司真题/网易2018校招编程题1-3.md) 33 | 34 | > [ 网易2018校招内推编程题集合](https://www.nowcoder.com/test/6291726/summary) 35 | 36 | > [2017年校招全国统一模拟笔试(第五场)编程题集合](https://www.nowcoder.com/test/5986669/summary) 37 | 38 | > [2017年校招全国统一模拟笔试(第四场)编程题集合](https://www.nowcoder.com/test/5507925/summary) 39 | 40 | > [2017年校招全国统一模拟笔试(第三场)编程题集合](https://www.nowcoder.com/test/5217106/summary) 41 | 42 | > [2017年校招全国统一模拟笔试(第二场)编程题集合](https://www.nowcoder.com/test/4546329/summary) 43 | 44 | > [ 2017年校招全国统一模拟笔试(第一场)编程题集合](https://www.nowcoder.com/test/4236887/summary) 45 | 46 | 47 | > [百度2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4998655/summary) 48 | 49 | > [网易2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4575457/summary) 50 | 51 | > [网易2017秋招编程题集合](https://www.nowcoder.com/test/2811407/summary) 52 | 53 | > [网易有道2017内推编程题](https://www.nowcoder.com/test/2385858/summary) 54 | 55 | > [ 滴滴出行2017秋招笔试真题-编程题汇总](https://www.nowcoder.com/test/3701760/summary) 56 | 57 | > [腾讯2017暑期实习生编程题](https://www.nowcoder.com/test/1725829/summary) 58 | 59 | > [今日头条2017客户端工程师实习生笔试题](https://www.nowcoder.com/test/1649301/summary) 60 | 61 | > [今日头条2017后端工程师实习生笔试题](https://www.nowcoder.com/test/1649268/summary) 62 | 63 | 64 | 65 | ## 排序算法: 66 | [图解排序算法(一)之3种简单排序(选择,冒泡,直接插入)](http://www.cnblogs.com/chengxiao/p/6103002.html) 67 | 68 | [图解排序算法(二)之希尔排序](https://www.cnblogs.com/chengxiao/p/6104371.html) 69 | 70 | [图解排序算法(三)之堆排序](http://www.cnblogs.com/chengxiao/p/6129630.html) 71 | 72 | [图解排序算法(四)之归并排序](http://www.cnblogs.com/chengxiao/p/6194356.html) 73 | 74 | [图解排序算法(五)之快速排序——三数取中法](http://www.cnblogs.com/chengxiao/p/6262208.html) 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /数据结构与算法/算法题解析/公司真题/网易2018校招编程题1-3.md: -------------------------------------------------------------------------------- 1 | 下面三道编程题来自网易2018校招编程题,这三道应该来说是非常简单的编程题了,这些题目大家稍微有点编程和数学基础的话应该没什么问题。看答案之前一定要自己先想一下如果是自己做的话会怎么去做,然后再对照这我的答案看看,和你自己想的有什么区别?那一种方法更好? 2 | 3 | ![问题](https://user-gold-cdn.xitu.io/2018/7/7/1647557d5a1474d7?w=1024&h=1024&f=jpeg&s=638271) 4 | > # 问题 5 | 6 | ## 一 获得特定数量硬币问题 7 | 8 | 小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。 9 | 10 | 魔法机器1:如果投入x个魔法币,魔法机器会将其变为2x+1个魔法币 11 | 12 | 魔法机器2:如果投入x个魔法币,魔法机器会将其变为2x+2个魔法币 13 | 14 | 小易采购魔法神器总共需要n个魔法币,所以小易只能通过两台魔法机器产生恰好n个魔法币,小易需要你帮他设计一个投入方案使他最后恰好拥有n个魔法币。 15 | 16 | **输入描述:** 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。 17 | 18 | 19 | **输出描述:** 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。 20 | 21 | **输入例子1:** 10 22 | 23 | **输出例子1:** 122 24 | 25 | ## 二 求“相反数”问题 26 | 27 | 为了得到一个数的"相反数",我们将这个数的数字顺序颠倒,然后再加上原先的数得到"相反数"。例如,为了得到1325的"相反数",首先我们将该数的数字顺序颠倒,我们得到5231,之后再加上原先的数,我们得到5231+1325=6556.如果颠倒之后的数字有前缀零,前缀零将会被忽略。例如n = 100, 颠倒之后是1. 28 | 29 | **输入描述:** 输入包括一个整数n,(1 ≤ n ≤ 10^5) 30 | 31 | 32 | **输出描述:** 输出一个整数,表示n的相反数 33 | 34 | **输入例子1:** 1325 35 | 36 | **输出例子1:** 6556 37 | 38 | ## 三 字符串碎片的平均长度 39 | 40 | 一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,"aaabbaaac"是由下面碎片组成的:'aaa','bb','c'。牛牛现在给定一个字符串,请你帮助计算这个字符串的所有碎片的平均长度是多少。 41 | 42 | **输入描述:** 输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s只含小写字母('a'-'z') 43 | 44 | 45 | **输出描述:** 输出一个整数,表示所有碎片的平均长度,四舍五入保留两位小数。 46 | 47 | **如样例所示:** s = "aaabbaaac" 48 | 所有碎片的平均长度 = (3 + 2 + 3 + 1) / 4 = 2.25 49 | 50 | **输入例子1:** aaabbaaac 51 | 52 | **输出例子1:** 2.25 53 | 54 | ![答案](https://user-gold-cdn.xitu.io/2018/7/7/16475582faddc9b2?w=1024&h=1024&f=jpeg&s=531663) 55 | 56 | > # 答案 57 | 58 | ## 一 获得特定数量硬币问题 59 | 60 | ### 分析: 61 | 62 | 作为该试卷的第一题,这道题应该只要思路正确就很简单了。 63 | 64 | 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。 65 | 66 | ### 示例代码 67 | 注意:由于用户的输入不确定性,一般是为了程序高可用性使需要将捕获用户输入异常然后友好提示用户输入类型错误并重新输入的。所以下面我给了两个版本,这两个版本都是正确的。这里只是给大家演示如何捕获输入类型异常,后面的题目中我给的代码没有异常处理的部分,参照下面两个示例代码,应该很容易添加。(PS:企业面试中没有明确就不用添加异常处理,当然你有的话也更好) 68 | 69 | **不带输入异常处理判断的版本:** 70 | 71 | ```java 72 | import java.util.Scanner; 73 | 74 | public class Main2 { 75 | // 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。 76 | 77 | public static void main(String[] args) { 78 | System.out.println("请输入要获得的硬币数量:"); 79 | Scanner scanner = new Scanner(System.in); 80 | int coincount = scanner.nextInt(); 81 | StringBuilder sb = new StringBuilder(); 82 | while (coincount >= 1) { 83 | // 偶数的情况 84 | if (coincount % 2 == 0) { 85 | coincount = (coincount - 2) / 2; 86 | sb.append("2"); 87 | // 奇数的情况 88 | } else { 89 | coincount = (coincount - 1) / 2; 90 | sb.append("1"); 91 | } 92 | } 93 | // 输出反转后的字符串 94 | System.out.println(sb.reverse()); 95 | 96 | } 97 | } 98 | ``` 99 | 100 | **带输入异常处理判断的版本(当输入的不是整数的时候会提示重新输入):** 101 | 102 | ```java 103 | import java.util.InputMismatchException; 104 | import java.util.Scanner; 105 | 106 | 107 | public class Main { 108 | // 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。 109 | 110 | public static void main(String[] args) { 111 | System.out.println("请输入要获得的硬币数量:"); 112 | Scanner scanner = new Scanner(System.in); 113 | boolean flag = true; 114 | while (flag) { 115 | try { 116 | int coincount = scanner.nextInt(); 117 | StringBuilder sb = new StringBuilder(); 118 | while (coincount >= 1) { 119 | // 偶数的情况 120 | if (coincount % 2 == 0) { 121 | coincount = (coincount - 2) / 2; 122 | sb.append("2"); 123 | // 奇数的情况 124 | } else { 125 | coincount = (coincount - 1) / 2; 126 | sb.append("1"); 127 | } 128 | } 129 | // 输出反转后的字符串 130 | System.out.println(sb.reverse()); 131 | flag=false;//程序结束 132 | } catch (InputMismatchException e) { 133 | System.out.println("输入数据类型不匹配,请您重新输入:"); 134 | scanner.nextLine(); 135 | continue; 136 | } 137 | } 138 | 139 | } 140 | } 141 | 142 | ``` 143 | 144 | 145 | ## 二 求“相反数”问题 146 | 147 | ### 分析: 148 | 149 | 解决本道题有几种不同的方法,但是最快速的方法就是利用reverse()方法反转字符串然后再将字符串转换成int类型的整数,这个方法是快速解决本题关键。我们先来回顾一下下面两个知识点: 150 | 151 | **1)String转int;** 152 | 153 | 在 Java 中要将 String 类型转化为 int 类型时,需要使用 Integer 类中的 parseInt() 方法或者 valueOf() 方法进行转换. 154 | ```java 155 | String str = "123"; 156 | int a = Integer.parseInt(str); 157 | ``` 158 | 或 159 | ```java 160 | String str = "123"; 161 | int a = Integer.valueOf(str).intValue(); 162 | ``` 163 | 164 | 165 | **2)next()和nextLine()的区别** 166 | 167 | 在Java中输入字符串有两种方法,就是next()和nextLine().两者的区别就是:nextLine()的输入是碰到回车就终止输入,而next()方法是碰到空格,回车,Tab键都会被视为终止符。所以next()不会得到带空格的字符串,而nextLine()可以得到带空格的字符串。 168 | 169 | ### 示例代码: 170 | 171 | ```java 172 | import java.util.Scanner; 173 | 174 | /** 175 | * 本题关键:①String转int;②next()和nextLine()的区别 176 | */ 177 | public class Main { 178 | 179 | public static void main(String[] args) { 180 | 181 | System.out.println("请输入一个整数:"); 182 | Scanner scanner = new Scanner(System.in); 183 | String s=scanner.next(); 184 | //将字符串转换成数字 185 | int number1=Integer.parseInt(s); 186 | //将字符串倒序后转换成数字 187 | //因为Integer.parseInt()的参数类型必须是字符串所以必须加上toString() 188 | int number2=Integer.parseInt(new StringBuilder(s).reverse().toString()); 189 | System.out.println(number1+number2); 190 | 191 | } 192 | } 193 | ``` 194 | 195 | ## 三 字符串碎片的平均长度 196 | 197 | ### 分析: 198 | 199 | 这道题的意思也就是要求:(字符串的总长度)/(相同字母团构成的字符串的个数)。 200 | 201 | 这样就很简单了,就变成了字符串的字符之间的比较。如果需要比较字符串的字符的话,我们可以利用charAt(i)方法:取出特定位置的字符与后一个字符比较,或者利用toCharArray()方法将字符串转换成字符数组采用同样的方法做比较。 202 | 203 | ### 示例代码 204 | 205 | **利用charAt(i)方法:** 206 | 207 | ```java 208 | import java.util.Scanner; 209 | 210 | public class Main { 211 | 212 | public static void main(String[] args) { 213 | 214 | Scanner sc = new Scanner(System.in); 215 | while (sc.hasNext()) { 216 | String s = sc.next(); 217 | //个数至少为一个 218 | float count = 1; 219 | for (int i = 0; i < s.length() - 1; i++) { 220 | if (s.charAt(i) != s.charAt(i + 1)) { 221 | count++; 222 | } 223 | } 224 | System.out.println(s.length() / count); 225 | } 226 | } 227 | 228 | } 229 | ``` 230 | 231 | **利用toCharArray()方法:** 232 | 233 | ```java 234 | import java.util.Scanner; 235 | 236 | public class Main2 { 237 | 238 | public static void main(String[] args) { 239 | 240 | Scanner sc = new Scanner(System.in); 241 | while (sc.hasNext()) { 242 | String s = sc.next(); 243 | //个数至少为一个 244 | float count = 1; 245 | char [] stringArr = s.toCharArray(); 246 | for (int i = 0; i < stringArr.length - 1; i++) { 247 | if (stringArr[i] != stringArr[i + 1]) { 248 | count++; 249 | } 250 | } 251 | System.out.println(s.length() / count); 252 | } 253 | } 254 | 255 | } 256 | ``` -------------------------------------------------------------------------------- /数据结构与算法/算法题解析/剑指offer/(1)斐波那契数列问题和跳台阶问题.md: -------------------------------------------------------------------------------- 1 | ### 一 斐波那契数列 2 | #### **题目描述:** 3 | 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。 4 | n<=39 5 | 6 | #### **问题分析:** 7 | 可以肯定的是这一题通过递归的方式是肯定能做出来,但是这样会有一个很大的问题,那就是递归大量的重复计算会导致内存溢出。另外可以使用迭代法,用fn1和fn2保存计算过程中的结果,并复用起来。下面我会把两个方法示例代码都给出来并给出两个方法的运行时间对比。 8 | 9 | #### **示例代码:** 10 | **采用迭代法:** 11 | 12 | ```java 13 | 14 | int Fibonacci(int number) { 15 | if (number <= 0) { 16 | return 0; 17 | } 18 | if (number == 1 || number == 2) { 19 | return 1; 20 | } 21 | int first = 1, second = 1, third = 0; 22 | for (int i = 3; i <= number; i++) { 23 | third = first + second; 24 | first = second; 25 | second = third; 26 | } 27 | return third; 28 | } 29 | ``` 30 | **采用递归:** 31 | ```java 32 | public int Fibonacci(int n) { 33 | 34 | if (n <= 0) { 35 | return 0; 36 | } 37 | if (n == 1||n==2) { 38 | return 1; 39 | } 40 | 41 | return Fibonacci(n - 2) + Fibonacci(n - 1); 42 | 43 | } 44 | ``` 45 | 46 | #### **运行时间对比:** 47 | 假设n为40我们分别使用迭代法和递归法计算,计算结果如下: 48 | 1. 迭代法 49 | ![迭代法](https://ws1.sinaimg.cn/large/006rNwoDgy1fpydt5as85j308a025dfl.jpg) 50 | 2. 递归法 51 | ![递归法](https://ws1.sinaimg.cn/large/006rNwoDgy1fpydt2d1k3j30ed02kt8i.jpg) 52 | 53 | 54 | ### 二 跳台阶问题 55 | #### **题目描述:** 56 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 57 | #### **问题分析:** 58 | **正常分析法:** 59 | a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1); 60 | b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2) 61 | c.由a,b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2) 62 | d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2 63 | **找规律分析法:** 64 | f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出f(n) = f(n-1) + f(n-2)的规律。 65 | 但是为什么会出现这样的规律呢?假设现在6个台阶,我们可以从第5跳一步到6,这样的话有多少种方案跳到5就有多少种方案跳到6,另外我们也可以从4跳两步跳到6,跳到4有多少种方案的话,就有多少种方案跳到6,其他的不能从3跳到6什么的啦,所以最后就是f(6) = f(5) + f(4);这样子也很好理解变态跳台阶的问题了。 66 | 67 | **所以这道题其实就是斐波那契数列的问题。** 68 | 代码只需要在上一题的代码稍做修改即可。和上一题唯一不同的就是这一题的初始元素变为 1 2 3 5 8.....而上一题为1 1 2 3 5 .......。另外这一题也可以用递归做,但是递归效率太低,所以我这里只给出了迭代方式的代码。 69 | #### **示例代码:** 70 | ```java 71 | 72 | int jumpFloor(int number) { 73 | if (number <= 0) { 74 | return 0; 75 | } 76 | if (number == 1) { 77 | return 1; 78 | } 79 | if (number == 2) { 80 | return 2; 81 | } 82 | int first = 1, second = 2, third = 0; 83 | for (int i = 3; i <= number; i++) { 84 | third = first + second; 85 | first = second; 86 | second = third; 87 | } 88 | return third; 89 | } 90 | ``` 91 | 92 | 93 | ### 三 变态跳台阶问题 94 | #### **题目描述:** 95 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 96 | 97 | #### **问题分析:** 98 | 假设n>=2,第一步有n种跳法:跳1级、跳2级、到跳n级 99 | 跳1级,剩下n-1级,则剩下跳法是f(n-1) 100 | 跳2级,剩下n-2级,则剩下跳法是f(n-2) 101 | ...... 102 | 跳n-1级,剩下1级,则剩下跳法是f(1) 103 | 跳n级,剩下0级,则剩下跳法是f(0) 104 | 所以在n>=2的情况下: 105 | f(n)=f(n-1)+f(n-2)+...+f(1) 106 | 因为f(n-1)=f(n-2)+f(n-3)+...+f(1) 107 | 所以f(n)=2*f(n-1) 又f(1)=1,所以可得**f(n)=2^(number-1)** 108 | 109 | #### **示例代码:** 110 | 111 | ```java 112 | int JumpFloorII(int number) { 113 | return 1 << --number;//2^(number-1)用位移操作进行,更快 114 | } 115 | ``` 116 | #### **补充:** 117 | **java中有三种移位运算符:** 118 | 119 | 1. “<<” : **左移运算符**,等同于乘2的n次方 120 | 2. “>>”: **右移运算符**,等同于除2的n次方 121 | 3. “>>>” **无符号右移运算符**,不管移动前最高位是0还是1,右移后左侧产生的空位部分都以0来填充。与>>类似。 122 | 例: 123 | int a = 16; 124 | int b = a << 2;//左移2,等同于16 * 2的2次方,也就是16 * 4 125 | int c = a >> 2;//右移2,等同于16 / 2的2次方,也就是16 / 4 126 | 127 | 128 | -------------------------------------------------------------------------------- /数据结构与算法/算法题解析/剑指offer/(2)二维数组查找和替换空格问题.md: -------------------------------------------------------------------------------- 1 | ### 一 二维数组查找 2 | #### **题目描述:** 3 | 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 4 | #### **问题解析:** 5 | 这一道题还是比较简单的,我们需要考虑的是如何做,效率最快。这里有一种很好理解的思路: 6 | 7 | > 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增, 8 | 因此从左下角开始查找,当要查找数字比左下角数字大时。右移 9 | 要查找数字比左下角数字小时,上移。这样找的速度最快。 10 | 11 | #### **示例代码:** 12 | ```java 13 | public boolean Find(int target, int [][] array) { 14 | //基本思路从左下角开始找,这样速度最快 15 | int row = array.length-1;//行 16 | int column = 0;//列 17 | //当行数大于0,当前列数小于总列数时循环条件成立 18 | while((row >= 0)&& (column< array[0].length)){ 19 | if(array[row][column] > target){ 20 | row--; 21 | }else if(array[row][column] < target){ 22 | column++; 23 | }else{ 24 | return true; 25 | } 26 | } 27 | return false; 28 | } 29 | ``` 30 | ### 二 替换空格 31 | #### **题目描述:** 32 | 请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 33 | #### **问题分析:** 34 | 这道题不难,我们可以通过循环判断字符串的字符是否为空格,是的话就利用append()方法添加追加“%20”,否则还是追加原字符。 35 | 36 | 或者最简单的方法就是利用: replaceAll(String regex,String replacement)方法了,一行代码就可以解决。 37 | 38 | #### **示例代码:** 39 | **常规做法:** 40 | ```java 41 | public String replaceSpace(StringBuffer str) { 42 | StringBuffer out=new StringBuffer(); 43 | for (int i = 0; i < str.toString().length(); i++) { 44 | char b=str.charAt(i); 45 | if(String.valueOf(b).equals(" ")){ 46 | out.append("%20"); 47 | }else{ 48 | out.append(b); 49 | } 50 | } 51 | return out.toString(); 52 | } 53 | ``` 54 | **一行代码解决:** 55 | ```java 56 | public String replaceSpace(StringBuffer str) { 57 | //return str.toString().replaceAll(" ", "%20"); 58 | //public String replaceAll(String regex,String replacement) 59 | //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。 60 | //\ 转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\". String类型中的空格用“\s”表示,所以我这里猜测"\\s"就是代表空格的意思 61 | return str.toString().replaceAll("\\s", "%20"); 62 | } 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /数据结构与算法/算法题解析/剑指offer/(3)数值的整数次方和调整数组元素顺序.md: -------------------------------------------------------------------------------- 1 | ### 一 数值的整数次方 2 | #### **题目描述:** 3 | 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 4 | #### **问题解析:** 5 | 这道题算是比较麻烦和难一点的一个了。我这里采用的是**二分幂**思想,当然也可以采用**快速幂**。 6 | 更具剑指offer书中细节,该题的解题思路如下: 7 | 1.当底数为0且指数<0时,会出现对0求倒数的情况,需进行错误处理,设置一个全局变量; 8 | 2.判断底数是否等于0,由于base为double型,所以不能直接用==判断 9 | 3.优化求幂函数(二分幂)。 10 | 当n为偶数,a^n =(a^n/2)*(a^n/2); 11 | 当n为奇数,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a。时间复杂度O(logn) 12 | 13 | **时间复杂度**:O(logn) 14 | #### **示例代码:** 15 | ```java 16 | public class Solution { 17 | boolean invalidInput=false; 18 | public double Power(double base, int exponent) { 19 | //如果底数等于0并且指数小于0 20 | //由于base为double型,不能直接用==判断 21 | if(equal(base,0.0)&&exponent<0){ 22 | invalidInput=true; 23 | return 0.0; 24 | } 25 | int absexponent=exponent; 26 | //如果指数小于0,将指数转正 27 | if(exponent<0) 28 | absexponent=-exponent; 29 | //getPower方法求出base的exponent次方。 30 | double res=getPower(base,absexponent); 31 | //如果指数小于0,所得结果为上面求的结果的倒数 32 | if(exponent<0) 33 | res=1.0/res; 34 | return res; 35 | } 36 | //比较两个double型变量是否相等的方法 37 | boolean equal(double num1,double num2){ 38 | if(num1-num2>-0.000001&&num1-num2<0.000001) 39 | return true; 40 | else 41 | return false; 42 | } 43 | //求出b的e次方的方法 44 | double getPower(double b,int e){ 45 | //如果指数为0,返回1 46 | if(e==0) 47 | return 1.0; 48 | //如果指数为1,返回b 49 | if(e==1) 50 | return b; 51 | //e>>1相等于e/2,这里就是求a^n =(a^n/2)*(a^n/2) 52 | double result=getPower(b,e>>1); 53 | result*=result; 54 | //如果指数n为奇数,则要再乘一次底数base 55 | if((e&1)==1) 56 | result*=b; 57 | return result; 58 | } 59 | } 60 | ``` 61 | 62 | 当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为O(n),这样没有前一种方法效率高。 63 | ```java 64 | // 使用累乘 65 | public double powerAnother(double base, int exponent) { 66 | double result = 1.0; 67 | for (int i = 0; i < Math.abs(exponent); i++) { 68 | result *= base; 69 | } 70 | if (exponent >= 0) 71 | return result; 72 | else 73 | return 1 / result; 74 | } 75 | ``` 76 | ### 二 调整数组顺序使奇数位于偶数前面 77 | #### **题目描述:** 78 | 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。 79 | 80 | #### **问题解析:** 81 | 这道题有挺多种解法的,给大家介绍一种我觉得挺好理解的方法: 82 | 我们首先统计奇数的个数假设为n,然后新建一个等长数组,然后通过循环判断原数组中的元素为偶数还是奇数。如果是则从数组下标0的元素开始,把该奇数添加到新数组;如果是偶数则从数组下标为n的元素开始把该偶数添加到新数组中。 83 | 84 | #### **示例代码:** 85 | 时间复杂度为O(n),空间复杂度为O(n)的算法 86 | ```java 87 | public class Solution { 88 | public void reOrderArray(int [] array) { 89 | //如果数组长度等于0或者等于1,什么都不做直接返回 90 | if(array.length==0||array.length==1) 91 | return; 92 | //oddCount:保存奇数个数 93 | //oddBegin:奇数从数组头部开始添加 94 | int oddCount=0,oddBegin=0; 95 | //新建一个数组 96 | int[] newArray=new int[array.length]; 97 | //计算出(数组中的奇数个数)开始添加元素 98 | for(int i=0;i stack1 = new Stack(); 25 | Stack stack2 = new Stack(); 26 | 27 | //当执行push操作时,将元素添加到stack1 28 | public void push(int node) { 29 | stack1.push(node); 30 | } 31 | 32 | public int pop() { 33 | //如果两个队列都为空则抛出异常,说明用户没有push进任何元素 34 | if(stack1.empty()&&stack2.empty()){ 35 | throw new RuntimeException("Queue is empty!"); 36 | } 37 | //如果stack2不为空直接对stack2执行pop操作, 38 | if(stack2.empty()){ 39 | while(!stack1.empty()){ 40 | //将stack1的元素按后进先出push进stack2里面 41 | stack2.push(stack1.pop()); 42 | } 43 | } 44 | return stack2.pop(); 45 | } 46 | } 47 | ``` 48 | 49 | ### 二 栈的压入、弹出序列 50 | #### **题目描述:** 51 | 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) 52 | #### **题目分析:** 53 | 这道题想了半天没有思路,参考了Alias的答案,他的思路写的也很详细应该很容易看懂。 54 | 作者:Alias 55 | https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106 56 | 来源:牛客网 57 | 58 | 【思路】借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。 59 | 60 | 举例: 61 | 62 | 入栈1,2,3,4,5 63 | 64 | 出栈4,5,3,2,1 65 | 66 | 首先1入辅助栈,此时栈顶1≠4,继续入栈2 67 | 68 | 此时栈顶2≠4,继续入栈3 69 | 70 | 此时栈顶3≠4,继续入栈4 71 | 72 | 此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3 73 | 74 | 此时栈顶3≠5,继续入栈5 75 | 76 | 此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3 77 | 78 | …. 79 | 依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。 80 | 81 | 82 | 83 | #### **考察内容:** 84 | 栈 85 | 86 | #### **示例代码:** 87 | ```java 88 | import java.util.ArrayList; 89 | import java.util.Stack; 90 | //这道题没想出来,参考了Alias同学的答案:https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106 91 | public class Solution { 92 | public boolean IsPopOrder(int [] pushA,int [] popA) { 93 | if(pushA.length == 0 || popA.length == 0) 94 | return false; 95 | Stack s = new Stack(); 96 | //用于标识弹出序列的位置 97 | int popIndex = 0; 98 | for(int i = 0; i< pushA.length;i++){ 99 | s.push(pushA[i]); 100 | //如果栈不为空,且栈顶元素等于弹出序列 101 | while(!s.empty() &&s.peek() == popA[popIndex]){ 102 | //出栈 103 | s.pop(); 104 | //弹出序列向后一位 105 | popIndex++; 106 | } 107 | } 108 | return s.empty(); 109 | } 110 | } 111 | ``` -------------------------------------------------------------------------------- /架构/分布式.md: -------------------------------------------------------------------------------- 1 | - ### 一 分布式系统的经典基础理论 2 | 3 | [分布式系统的经典基础理论](https://blog.csdn.net/qq_34337272/article/details/80444032) 4 | 5 | 本文主要是简单的介绍了三个常见的概念: **分布式系统设计理念** 、 **CAP定理** 、 **BASE理论** ,关于分布式系统的还有很多很多东西。 6 | ![分布式系统的经典基础理论总结](https://user-gold-cdn.xitu.io/2018/5/24/1639234237ec9805?w=791&h=466&f=png&s=55908) 7 | 8 | - ### 二 分布式事务 9 | 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。 10 | * [深入理解分布式事务](http://www.codeceo.com/article/distributed-transaction.html) 11 | * [分布式事务?No, 最终一致性](https://zhuanlan.zhihu.com/p/25933039) 12 | * [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html) 13 |    14 | 15 | - ### 三 分布式系统一致性 16 | [分布式服务化系统一致性的“最佳实干”](https://www.jianshu.com/p/1156151e20c8) 17 | 18 | - ### 四 一致性协议/算法 19 | 早在1898年就诞生了著名的 **Paxos经典算法** (**Zookeeper就采用了Paxos算法的近亲兄弟Zab算法**),但由于Paxos算法非常难以理解、实现、排错。所以不断有人尝试简化这一算法,直到2013年才有了重大突破:斯坦福的Diego Ongaro、John Ousterhout以易懂性为目标设计了新的一致性算法—— **Raft算法** ,并发布了对应的论文《In Search of an Understandable Consensus Algorithm》,到现在有十多种语言实现的Raft算法框架,较为出名的有以Go语言实现的Etcd,它的功能类似于Zookeeper,但采用了更为主流的Rest接口。 20 | * [图解 Paxos 一致性协议](http://blog.xiaohansong.com/2016/09/30/Paxos/) 21 | * [图解分布式协议-RAFT](http://ifeve.com/raft/) 22 | * [Zookeeper ZAB 协议分析](http://blog.xiaohansong.com/2016/08/25/zab/) 23 | 24 | - ### 五 分布式存储 25 | 26 | **分布式存储系统将数据分散存储在多台独立的设备上**。传统的网络存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满足大规模存储应用的需要。分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,它不但提高了系统的可靠性、可用性和存取效率,还易于扩展。 27 | 28 | * [分布式存储系统概要](http://witchiman.top/2017/05/05/distributed-system/) 29 | 30 | - ### 六 分布式计算 31 | 32 | **所谓分布式计算是一门计算机科学,它研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分,然后把这些部分分配给许多计算机进行处理,最后把这些计算结果综合起来得到最终的结果。** 33 | 分布式网络存储技术是将数据分散的存储于多台独立的机器设备上。分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,不但解决了传统集中式存储系统中单存储服务器的瓶颈问题,还提高了系统的可靠性、可用性和扩展性。 34 | 35 | * [关于分布式计算的一些概念](https://blog.csdn.net/qq_34337272/article/details/80549020) 36 | 37 | -------------------------------------------------------------------------------- /计算机网络与数据通信/数据通信(RESTful、RPC、消息队列).md: -------------------------------------------------------------------------------- 1 | > ## RPC 2 | 3 | **RPC(Remote Procedure Call)—远程过程调用** ,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发分布式程序就像开发本地程序一样简单。 4 | 5 | **RPC采用客户端(服务调用方)/服务器端(服务提供方)模式,** 都运行在自己的JVM中。客户端只需要引入要使用的接口,接口的实现和运行都在服务器端。RPC主要依赖的技术包括序列化、反序列化和数据传输协议,这是一种定义与实现相分离的设计。 6 | 7 | **目前Java使用比较多的RPC方案主要有RMI(JDK自带)、Hessian、Dubbo以及Thrift等。** 8 | 9 | **注意: RPC主要指内部服务之间的调用,RESTful也可以用于内部服务之间的调用,但其主要用途还在于外部系统提供服务,因此没有将其包含在本知识点内。** 10 | 11 | ### 常见RPC框架: 12 | 13 | - **RMI(JDK自带):** JDK自带的RPC 14 | 15 | 详细内容可以参考:[从懵逼到恍然大悟之Java中RMI的使用](https://blog.csdn.net/lmy86263/article/details/72594760) 16 | 17 | - **Dubbo:** Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。 18 | 19 | 详细内容可以参考: 20 | 21 | - [ 高性能优秀的服务框架-dubbo介绍](https://blog.csdn.net/qq_34337272/article/details/79862899) 22 | 23 | - [Dubbo是什么?能做什么?](https://blog.csdn.net/houshaolin/article/details/76408399) 24 | 25 | 26 | - **Hessian:** Hessian是一个轻量级的remotingonhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。 27 | 28 | 详细内容可以参考: [Hessian的使用以及理解](https://blog.csdn.net/sunwei_pyw/article/details/74002351) 29 | 30 | - **Thrift:** Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于thrift研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。 31 | 32 | 33 | 详细内容可以参考: [【Java】分布式RPC通信框架Apache Thrift 使用总结](https://www.cnblogs.com/zeze/p/8628585.html) 34 | 35 | ### 如何进行选择: 36 | 37 | - **是否允许代码侵入:** 即需要依赖相应的代码生成器生成代码,比如Thrift。 38 | - **是否需要长连接获取高性能:** 如果对于性能需求较高的haul,那么可以果断选择基于TCP的Thrift、Dubbo。 39 | - **是否需要跨越网段、跨越防火墙:** 这种情况一般选择基于HTTP协议的Hessian和Thrift的HTTP Transport。 40 | 41 | 此外,Google推出的基于HTTP2.0的gRPC框架也开始得到应用,其序列化协议基于Protobuf,网络框架使用的是Netty4,但是其需要生成代码,可扩展性也比较差。 42 | 43 | > ## 消息中间件 44 | 45 | **消息中间件,也可以叫做中央消息队列或者是消息队列(区别于本地消息队列,本地消息队列指的是JVM内的队列实现)**,是一种独立的队列系统,消息中间件经常用来解决内部服务之间的 **异步调用问题** 。请求服务方把请求队列放到队列中即可返回,然后等待服务提供方去队列中获取请求进行处理,之后通过回调等机制把结果返回给请求服务方。 46 | 47 | 异步调用只是消息中间件一个非常常见的应用场景。此外,常用的消息队列应用场景还偷如下几个: 48 | - **解耦 :** 一个业务的非核心流程需要依赖其他系统,但结果并不重要,有通知即可。 49 | - **最终一致性 :** 指的是两个系统的状态保持一致,可以有一定的延迟,只要最终达到一致性即可。经常用在解决分布式事务上。 50 | - **广播 :** 消息队列最基本的功能。生产者只负责生产消息,订阅者接收消息。 51 | - **错峰和流控** 52 | 53 | 54 | 具体可以参考: 55 | 56 | [《消息队列深入解析》](https://blog.csdn.net/qq_34337272/article/details/80029918) 57 | 58 | 当前使用较多的消息队列有ActiveMQ(性能差,不推荐使用)、RabbitMQ、RocketMQ、Kafka等等,我们之前提高的redis数据库也可以实现消息队列,不过不推荐,redis本身设计就不是用来做消息队列的。 59 | 60 | - **ActiveMQ:** ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的JMSProvider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。 61 | 62 | 具体可以参考: 63 | 64 | [《消息队列ActiveMQ的使用详解》](https://blog.csdn.net/qq_34337272/article/details/80031702) 65 | 66 | - **RabbitMQ:** RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗 67 | > AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。 68 | 69 | 70 | 具体可以参考: 71 | 72 | [《消息队列之 RabbitMQ》](https://www.jianshu.com/p/79ca08116d57) 73 | 74 | - **RocketMQ:** 75 | 76 | 具体可以参考: 77 | 78 | [《RocketMQ 实战之快速入门》](https://www.jianshu.com/p/824066d70da8) 79 | 80 | [《十分钟入门RocketMQ》](http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/) (阿里中间件团队博客) 81 | 82 | 83 | - **Kafka**:Kafka是一个分布式的、可分区的、可复制的、基于发布/订阅的消息系统,Kafka主要用于大数据领域,当然在分布式系统中也有应用。目前市面上流行的消息队列RocketMQ就是阿里借鉴Kafka的原理、用Java开发而得。 84 | 85 | 具体可以参考: 86 | 87 | [《Kafka应用场景》](http://book.51cto.com/art/201801/565244.htm) 88 | 89 | [《初谈Kafka》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484106&idx=1&sn=aa1999895d009d91eb3692a3e6429d18&chksm=fd9854abcaefddbd1101ca5dc2c7c783d7171320d6300d9b2d8e68b7ef8abd2b02ea03e03600#rd) 90 | 91 | **推荐阅读:** 92 | 93 | [《Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和区别》](https://mp.weixin.qq.com/s?__biz=MzU5OTMyODAyNg==&mid=2247484721&idx=1&sn=11e4e29886e581dd328311d308ccc068&chksm=feb7d144c9c058529465b02a4e26a25ef76b60be8984ace9e4a0f5d3d98ca52e014ecb73b061&scene=21#wechat_redirect) 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /面试必备/books.md: -------------------------------------------------------------------------------- 1 | 2 | ### 核心基础知识 3 | 4 | - [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 5 | - [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 6 | - [《数据结构与算法分析:C语言描述》](https://book.douban.com/subject/1139426/)(推荐,豆瓣评分 8.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。原书曾被评为20世纪顶尖的30部计算机著作之一,作者Mark Allen Weiss在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界500余所大学用作教材。 7 | - [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! 8 | - [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。 9 | 10 | 11 | 12 | 13 | ### Java相关 14 | 15 | - [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。 16 | - [《Head First Java.第二版》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.7,1.0K+人评价): 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 17 | - [《Java多线程编程核心技术》](https://book.douban.com/subject/26555197/): Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。 18 | - [《JAVA网络编程 第4版》](https://book.douban.com/subject/26259017/): 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。 19 | - [《Java核心技术卷1+卷2》](https://book.douban.com/subject/25762168/)(推荐): 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。 20 | - [《Java编程思想(第4版)》](https://book.douban.com/subject/2130190/)(推荐,豆瓣评分 9.1,3.2K+人评价):这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 21 | - [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 22 | - [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 23 | - [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/): 很杂,我只看了前面几章,不太推荐阅读。 24 | - [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 25 | - [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书! 26 | - [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 27 | 28 | ### JavaWeb相关 29 | 30 | - [《深入分析Java Web技术内幕》](https://book.douban.com/subject/25953851/): 感觉还行,涉及的东西也蛮多。 31 | - [《Spring实战(第4版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.3 32 | ,0.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。 33 | - [《Java Web整合开发王者归来》](https://book.douban.com/subject/4189495/)(已过时):当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。 34 | - [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 35 | - [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价) 36 | - [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 37 | - [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 38 | - [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 39 | - [《RabbitMQ实战指南》](https://book.douban.com/subject/27591386/):《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝RabbitMQ的使用,这本书是你最好的选择;如果你想深入RabbitMQ的原理,这本书也是你最好的选择;总之,如果你想玩转RabbitMQ,这本书一定是最值得看的书之一 40 | - [《Spring Cloud微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 41 | - [《第一本Docker书》](https://book.douban.com/subject/26780404/):Docker入门书籍! 42 | 43 | ### 操作系统 44 | 45 | - [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。 46 | 47 | ### 架构相关 48 | 49 | - [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 50 | - [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 51 | - [《架构解密从分布式到微服务(Leaderus著)》](https://book.douban.com/subject/27081188/):很一般的书籍,我就是当做课后图书来阅读的。 52 | 53 | ### 代码优化 54 | 55 | - [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。 56 | 57 | ### 课外书籍 58 | 59 | - 《追风筝的人》(推荐) 60 | - 《穆斯林的葬礼》 (推荐) 61 | - 《三体》 (推荐) 62 | - 《活着——余华》 (推荐) 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /面试必备/interviewPrepare.md: -------------------------------------------------------------------------------- 1 | 这是【备战春招/秋招系列】的第二篇文章,主要是简单地介绍如何去准备面试。 2 | 3 | 不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。 4 | 5 | ### 1 如何获取大厂面试机会? 6 | 7 | **在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。** 8 | 9 | 1. **招聘人数** :秋招多于春招 ; 10 | 2. **招聘时间** : 秋招一般7月左右开始,大概一直持续到10月底。但是大厂(如BAT)都会早开始早结束,所以一定要把握好时间。春招最佳时间为3月,次佳时间为4月,进入5月基本就不会再有春招了(金三银四)。 11 | 3. **应聘难度** :秋招略大于春招; 12 | 4. **招聘公司:** 秋招数量多,而春招数量较少,一般为秋招的补充。 13 | 14 | **综上,一般来说,秋招的含金量明显是高于春招的。** 15 | 16 | **下面我就说一下我自己知道的一些方法,不过应该也涵盖了大部分获取面试机会的方法。** 17 | 18 | 1. **关注大厂官网,随时投递简历(走流程的网申);** 19 | 2. **线下参加宣讲会,直接投递简历;** 20 | 3. **找到师兄师姐/认识的人,帮忙内推(能够让你避开网申简历筛选,笔试筛选,还是挺不错的,不过也还是需要你的简历够棒);** 21 | 4. **博客发文被看中/Github优秀开源项目作者,大厂内部人员邀请你面试;** 22 | 5. **求职类网站投递简历(不是太推荐,适合海投);** 23 | 24 | 25 | 除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。 26 | 27 | ### 2 面试前的准备 28 | 29 | ### 2.1 准备自己的自我介绍 30 | 31 | 从HR面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。 32 | 33 | 我这里简单分享一下我自己的自我介绍的一个简单的模板吧: 34 | 35 | > 面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。 36 | 37 | ### 2.2 关于着装 38 | 39 | 穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。 40 | 41 | ### 2.3 随身带上自己的成绩单和简历 42 | 43 | 有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。 44 | 45 | ### 2.4 如果需要笔试就提前刷一些笔试题 46 | 47 | 平时空闲时间多的可以刷一下笔试题目(牛客网上有很多)。但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。 48 | 49 | ### 2.5 花时间一些逻辑题 50 | 51 | 面试中发现有些公司都有逻辑题测试环节,并且都把逻辑笔试成绩作为很重要的一个参考。 52 | 53 | ### 2.6 准备好自己的项目介绍 54 | 55 | 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑: 56 | 57 | 1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图) 58 | 2. 在这个项目中你负责了什么、做了什么、担任了什么角色 59 | 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 60 | 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 61 | 62 | ### 2.7 提前准备技术面试 63 | 64 | 搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) 65 | 66 | ### 2.7 面试之前做好定向复习 67 | 68 | 所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。 69 | 70 | 举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 71 | 72 | # 3 面试之后复盘 73 | 74 | 如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! -------------------------------------------------------------------------------- /面试必备/手把手教你用Markdown写一份高质量的简历.md: -------------------------------------------------------------------------------- 1 | ## Markdown 简历模板样式一览 2 | ![](https://user-gold-cdn.xitu.io/2018/9/3/1659f91e4843bd67?w=800&h=1737&f=png&s=97357) 3 | **可以看到我把联系方式放在第一位,因为公司一般会与你联系,所以把联系方式放在第一位也是为了方便联系考虑。** 4 | 5 | ## 为什么要用 Markdown 写简历? 6 | 7 | Markdown 语法简单,易于上手。使用正确的 Markdown 语言写出来的简历不论是在排版还是格式上都比较干净,易于阅读。另外,使用 Markdown 写简历也会给面试官一种你比较专业的感觉。 8 | 9 | 除了这些,我觉得使用 Markdown 写简历可以很方便将其与PDF、HTML、PNG格式之间转换。后面我会介绍到转换方法,只需要一条命令你就可以实现 Markdown 到 PDF、HTML 与 PNG之间的无缝切换。 10 | 11 | > 下面的一些内容我在之前的一篇文章中已经提到过,这里再说一遍,最后会分享如何实现Markdown 到 PDF、HTML、PNG格式之间转换。 12 | 13 | ## 为什么说简历很重要? 14 | 15 | 假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。 16 | 17 | 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。 18 | 19 | 另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。 20 | 21 | ## 写简历的两大法则 22 | 23 | 目前写简历的方式有两种普遍被认可,一种是 STAR, 一种是 FAB。 24 | 25 | **STAR法则(Situation Task Action Result):** 26 | 27 | - **Situation:** 事情是在什么情况下发生; 28 | - **Task::** 你是如何明确你的任务的; 29 | - **Action:** 针对这样的情况分析,你采用了什么行动方式; 30 | - **Result:** 结果怎样,在这样的情况下你学习到了什么。 31 | 32 | **FAB 法则(Feature Advantage Benefit):** 33 | 34 | - **Feature:** 是什么; 35 | - **Advantage:** 比别人好在哪些地方; 36 | - **Benefit:** 如果雇佣你,招聘方会得到什么好处。 37 | 38 | ## 项目经历怎么写? 39 | 简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写: 40 | 41 | 1. 对项目整体设计的一个感受 42 | 2. 在这个项目中你负责了什么、做了什么、担任了什么角色 43 | 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 44 | 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。 45 | 46 | ## 专业技能该怎么写? 47 | 先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写: 48 | 49 | - Dubbo:精通 50 | - Spring:精通 51 | - Docker:掌握 52 | - SOA分布式开发 :掌握 53 | - Spring Cloud:了解 54 | 55 | ## 简历模板分享 56 | 57 | **开源程序员简历模板**: [https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample)(包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板) 58 | 59 | **上述简历模板的改进版本:** [https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md) 60 | 61 | ## 其他的一些小tips 62 | 63 | 1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 64 | 2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。 65 | 3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。 66 | 4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。 67 | 5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容 68 | 6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 69 | 7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 70 | 8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。 71 | 72 | 73 | > 我们刚刚讲了很多关于如何写简历的内容并且分享了一份 Markdown 格式的简历文档。下面我们来看看如何实现 Markdown 到 HTML格式、PNG格式之间转换。 74 | ## Markdown 到 HTML格式、PNG格式之间转换 75 | 76 | 网上很难找到一个比较方便并且效果好的转换方法,最后我是通过 Visual Studio Code 的 Markdown PDF 插件完美解决了这个问题! 77 | 78 | ### 安装 Markdown PDF 插件 79 | 80 | **① 打开Visual Studio Code ,按快捷键 F1,选择安装扩展选项** 81 | 82 | ![① 打开Visual Studio Code ,按快捷键 F1,选择安装扩展选项](https://user-gold-cdn.xitu.io/2018/9/3/1659f9a44103e551?w=1366&h=688&f=png&s=104435) 83 | 84 | **② 搜索 “Markdown PDF” 插件并安装 ,然后重启** 85 | 86 | ![② 搜索 “Markdown PDF” 插件并安装 ,然后重启](https://user-gold-cdn.xitu.io/2018/9/3/1659f9dbef0d06fb?w=1280&h=420&f=png&s=70510) 87 | 88 | ### 使用方法 89 | 90 | 随便打开一份 Markdown 文件 点击F1,然后输入export即可! 91 | 92 | ![](https://user-gold-cdn.xitu.io/2018/9/3/1659fa0292906150?w=1289&h=468&f=png&s=72178) 93 | 94 | -------------------------------------------------------------------------------- /面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 一 为什么 Java 中只有值传递? 4 | 5 | 6 | 首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。 7 | 8 | **Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。** 9 | 10 | **下面通过 3 个例子来给大家说明** 11 | 12 | ### example 1 13 | 14 | 15 | ```java 16 | public static void main(String[] args) { 17 | int num1 = 10; 18 | int num2 = 20; 19 | 20 | swap(num1, num2); 21 | 22 | System.out.println("num1 = " + num1); 23 | System.out.println("num2 = " + num2); 24 | } 25 | 26 | public static void swap(int a, int b) { 27 | int temp = a; 28 | a = b; 29 | b = temp; 30 | 31 | System.out.println("a = " + a); 32 | System.out.println("b = " + b); 33 | } 34 | ``` 35 | 36 | **结果:** 37 | 38 | ``` 39 | a = 20 40 | b = 10 41 | num1 = 10 42 | num2 = 20 43 | ``` 44 | 45 | **解析:** 46 | 47 | ![example 1 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/22191348.jpg) 48 | 49 | 在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。 50 | 51 | **通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.** 52 | 53 | 54 | ### example 2 55 | 56 | ```java 57 | public static void main(String[] args) { 58 | int[] arr = { 1, 2, 3, 4, 5 }; 59 | System.out.println(arr[0]); 60 | change(arr); 61 | System.out.println(arr[0]); 62 | } 63 | 64 | public static void change(int[] array) { 65 | // 将数组的第一个元素变为0 66 | array[0] = 0; 67 | } 68 | ``` 69 | 70 | **结果:** 71 | 72 | ``` 73 | 1 74 | 0 75 | ``` 76 | 77 | **解析:** 78 | 79 | ![example 2](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/3825204.jpg) 80 | 81 | array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。 82 | 83 | 84 | **通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。** 85 | 86 | **很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。** 87 | 88 | 89 | ### example 3 90 | 91 | ```java 92 | public class Test { 93 | 94 | public static void main(String[] args) { 95 | // TODO Auto-generated method stub 96 | Student s1 = new Student("小张"); 97 | Student s2 = new Student("小李"); 98 | Test.swap(s1, s2); 99 | System.out.println("s1:" + s1.getName()); 100 | System.out.println("s2:" + s2.getName()); 101 | } 102 | 103 | public static void swap(Student x, Student y) { 104 | Student temp = x; 105 | x = y; 106 | y = temp; 107 | System.out.println("x:" + x.getName()); 108 | System.out.println("y:" + y.getName()); 109 | } 110 | } 111 | ``` 112 | 113 | **结果:** 114 | 115 | ``` 116 | x:小李 117 | y:小张 118 | s1:小张 119 | s2:小李 120 | ``` 121 | 122 | **解析:** 123 | 124 | 交换之前: 125 | 126 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/88729818.jpg) 127 | 128 | 交换之后: 129 | 130 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/34384414.jpg) 131 | 132 | 133 | 通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝** 134 | 135 | ### 总结 136 | 137 | Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按 138 | 值传递的。 139 | 140 | 下面再总结一下Java中方法参数的使用情况: 141 | 142 | - 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》 143 | - 一个方法可以改变一个对象参数的状态。 144 | - 一个方法不能让对象参数引用一个新的对象。 145 | 146 | 147 | ### 参考: 148 | 149 | 《Java核心技术卷Ⅰ》基础知识第十版第四章4.5小节 150 | 151 | ## 二 ==与equals(重要) 152 | 153 | **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) 154 | 155 | **equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: 156 | 157 | - 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。 158 | - 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 159 | 160 | 161 | **举个例子:** 162 | 163 | ```java 164 | public class test1 { 165 | public static void main(String[] args) { 166 | String a = new String("ab"); // a 为一个引用 167 | String b = new String("ab"); // b为另一个引用,对象的内容一样 168 | String aa = "ab"; // 放在常量池中 169 | String bb = "ab"; // 从常量池中查找 170 | if (aa == bb) // true 171 | System.out.println("aa==bb"); 172 | if (a == b) // false,非同一对象 173 | System.out.println("a==b"); 174 | if (a.equals(b)) // true 175 | System.out.println("aEQb"); 176 | if (42 == 42.0) { // true 177 | System.out.println("true"); 178 | } 179 | } 180 | } 181 | ``` 182 | 183 | **说明:** 184 | 185 | - String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 186 | - 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。 187 | 188 | 189 | 190 | ## 三 hashCode与equals(重要) 191 | 192 | 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” 193 | 194 | ### hashCode()介绍 195 | hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。 196 | 197 | ```java 198 | /** 199 | * Returns a hash code value for the object. This method is 200 | * supported for the benefit of hash tables such as those provided by 201 | * {@link java.util.HashMap}. 202 | *

203 | * As much as is reasonably practical, the hashCode method defined by 204 | * class {@code Object} does return distinct integers for distinct 205 | * objects. (This is typically implemented by converting the internal 206 | * address of the object into an integer, but this implementation 207 | * technique is not required by the 208 | * Java™ programming language.) 209 | * 210 | * @return a hash code value for this object. 211 | * @see java.lang.Object#equals(java.lang.Object) 212 | * @see java.lang.System#identityHashCode 213 | */ 214 | public native int hashCode(); 215 | ``` 216 | 217 | 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) 218 | 219 | ### 为什么要有hashCode 220 | 221 | 222 | **我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:** 223 | 224 | 当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。 225 | 226 | 227 | 228 | ### hashCode()与equals()的相关规定 229 | 230 | 1. 如果两个对象相等,则hashcode一定也是相同的 231 | 2. 两个对象相等,对两个对象分别调用equals方法都返回true 232 | 3. 两个对象有相同的hashcode值,它们也不一定是相等的 233 | 4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖** 234 | 5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) 235 | 236 | ### 为什么两个对象有相同的hashcode值,它们也不一定是相等的? 237 | 238 | 在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。 239 | 240 | 因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。 241 | 242 | 我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。 243 | 244 | 参考: 245 | 246 | [https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504) 247 | 248 | [https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html) 249 | 250 | [https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html) 251 | 252 | [https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html) 253 | 254 | -------------------------------------------------------------------------------- /面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md: -------------------------------------------------------------------------------- 1 | 2 | ### String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的? 3 | 4 | #### String和StringBuffer、StringBuilder的区别 5 | 6 | **可变性** 7 |   8 | 9 | 简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 10 | 11 | StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。 12 | 13 | AbstractStringBuilder.java 14 | 15 | ```java 16 | abstract class AbstractStringBuilder implements Appendable, CharSequence { 17 | char[] value; 18 | int count; 19 | AbstractStringBuilder() { 20 | } 21 | AbstractStringBuilder(int capacity) { 22 | value = new char[capacity]; 23 | } 24 | ``` 25 | 26 | 27 | **线程安全性** 28 | 29 | String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。 30 |    31 | 32 | **性能** 33 | 34 | 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 35 | 36 | **对于三者使用的总结:** 37 | 1. 操作少量的数据 = String 38 | 2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder 39 | 3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer 40 | 41 | #### String为什么是不可变的吗? 42 | 简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以: 43 | 44 | ```java 45 | /** The value is used for character storage. */ 46 | private final char value[]; 47 | ``` 48 | 49 | #### String真的是不可变的吗? 50 | 我觉得如果别人问这个问题的话,回答不可变就可以了。 51 | 下面只是给大家看两个有代表性的例子: 52 | 53 | **1) String不可变但不代表引用不可以变** 54 | ```java 55 | String str = "Hello"; 56 | str = str + " World"; 57 | System.out.println("str=" + str); 58 | ``` 59 | 结果: 60 | ``` 61 | str=Hello World 62 | ``` 63 | 解析: 64 | 65 | 实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。 66 | 67 | **2) 通过反射是可以修改所谓的“不可变”对象** 68 | 69 | ```java 70 | // 创建字符串"Hello World", 并赋给引用s 71 | String s = "Hello World"; 72 | 73 | System.out.println("s = " + s); // Hello World 74 | 75 | // 获取String类中的value字段 76 | Field valueFieldOfString = String.class.getDeclaredField("value"); 77 | 78 | // 改变value属性的访问权限 79 | valueFieldOfString.setAccessible(true); 80 | 81 | // 获取s对象上的value属性的值 82 | char[] value = (char[]) valueFieldOfString.get(s); 83 | 84 | // 改变value所引用的数组中的第5个字符 85 | value[5] = '_'; 86 | 87 | System.out.println("s = " + s); // Hello_World 88 | ``` 89 | 90 | 结果: 91 | 92 | ``` 93 | s = Hello World 94 | s = Hello_World 95 | ``` 96 | 97 | 解析: 98 | 99 | 用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。 100 | 101 | ### 什么是反射机制?反射机制的应用场景有哪些? 102 | 103 | #### 反射机制介绍 104 | 105 | JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 106 | 107 | #### 静态编译和动态编译 108 | 109 | - **静态编译:**在编译时确定类型,绑定对象 110 | - **动态编译:**运行时确定类型,绑定对象 111 | 112 | #### 反射机制优缺点 113 | 114 | - **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。 115 | - **缺点:** 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。 116 | 117 | #### 反射的应用场景 118 | 119 | 反射是框架设计的灵魂。 120 | 121 | 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。 122 | 123 | 举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 124 | 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性 125 | 126 | **推荐阅读:** 127 | 128 | - [Reflection:Java反射机制的应用场景](https://segmentfault.com/a/1190000010162647?utm_source=tuicool&utm_medium=referral) 129 | - [Java基础之—反射(非常重要)](https://blog.csdn.net/sinat_38259539/article/details/71799078) 130 | ### 什么是JDK?什么是JRE?什么是JVM?三者之间的联系与区别 131 | 132 | 这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。 133 | 134 | **JDK:** 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。 135 | 136 | **JRE:** 普通用户而只需要安装JRE(Java Runtime Environment)来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。 137 | 138 | **JVM:** 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因。 139 | 140 | **区别与联系:** 141 | 142 | 1. JDK用于开发,JRE用于运行java程序 ; 143 | 2. JDK和JRE中都包含JVM ; 144 | 3. JVM是java编程语言的核心并且具有平台独立性。 145 | 146 | ### 什么是字节码?采用字节码的最大好处是什么? 147 | 148 | **先看下java中的编译器和解释器:**    149 | 150 | Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做`字节码`(即扩展名为`.class`的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。 151 | 152 | Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。 153 | 154 | **采用字节码的好处:**    155 | 156 | Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 157 | 158 | ### Java和C++的区别 159 | 160 | 我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来! 161 | 162 | - 都是面向对象的语言,都支持封装、继承和多态 163 | - Java不提供指针来直接访问内存,程序内存更加安全 164 | - Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。 165 | - Java有自动内存管理机制,不需要程序员手动释放无用内存 166 | 167 | 168 | ### 接口和抽象类的区别是什么? 169 | 170 | 1. 接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法 171 | 2. 接口中的实例变量默认是final类型的,而抽象类中则不一定 172 | 3. 一个类可以实现多个接口,但最多只能实现一个抽象类 173 | 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 174 | 5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 175 | 176 | ### 成员变量与局部变量的区别有那些? 177 | 178 | 1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰; 179 | 2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存 180 | 3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 181 | 4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。 182 | 183 | ### 重载和重写的区别 184 | 185 | **重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。    186 | 187 | **重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。 188 | 189 | ### 字符型常量和字符串常量的区别 190 | 1) 形式上: 191 | 字符常量是单引号引起的一个字符 192 | 字符串常量是双引号引起的若干个字符 193 | 2) 含义上: 194 | 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 195 | 字符串常量代表一个地址值(该字符串在内存中存放位置) 196 | 3) 占内存大小 197 | 字符常量只占一个字节 198 | 字符串常量占若干个字节(至少一个字符结束标志) 199 | -------------------------------------------------------------------------------- /面试必备/最最最常见的Java面试题总结/第四周(2018-8-30).md: -------------------------------------------------------------------------------- 1 | 2 | ## 1. 简述线程,程序、进程的基本概念。以及他们之间关系是什么? 3 | 4 | **线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 5 | 6 | **程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。 7 | 8 | **进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 9 | 10 | **线程** 是 **进程** 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。 11 | 12 | **线程上下文的切换比进程上下文切换要快很多** 13 | 14 | - 进程切换时,涉及到当前进程的CPU环境的保存和新被调度运行进程的CPU环境的设置。 15 | - 线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。 16 | 17 | ## 2. 线程有哪些基本状态?这些状态是如何定义的? 18 | 19 | 1. **新建(new)**:新创建了一个线程对象。 20 | 2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。 21 | 3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。 22 | 4. **阻塞(block)**:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种: 23 | - **(一). 等待阻塞**:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waiting queue)中。 24 | - **(二). 同步阻塞**:运行(running)的线程在获取对象的同步锁时,若该同步 锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 25 | - **(三). 其他阻塞**: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。 26 | 5. **死亡(dead)**:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。 27 | 28 | ![](https://user-gold-cdn.xitu.io/2018/8/9/1651f19d7c4e93a3?w=876&h=492&f=png&s=128092) 29 | 30 | 备注: 可以用早起坐地铁来比喻这个过程(下面参考自牛客网某位同学的回答): 31 | 32 | 1. 还没起床:sleeping 33 | 2. 起床收拾好了,随时可以坐地铁出发:Runnable 34 | 3. 等地铁来:Waiting 35 | 4. 地铁来了,但要排队上地铁:I/O阻塞 36 | 5. 上了地铁,发现暂时没座位:synchronized阻塞 37 | 6. 地铁上找到座位:Running 38 | 7. 到达目的地:Dead 39 | 40 | 41 | ## 3. 何为多线程? 42 | 43 | 多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。 44 | 45 | 46 | ## 4. 为什么多线程是必要的? 47 | 48 | 1. 使用线程可以把占据长时间的程序中的任务放到后台去处理。 49 | 2. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。 50 | 3. 程序的运行速度可能加快。 51 | 52 | ## 5 使用多线程常见的三种方式 53 | 54 | ### ①继承Thread类 55 | 56 | MyThread.java 57 | 58 | ```java 59 | public class MyThread extends Thread { 60 | @Override 61 | public void run() { 62 | super.run(); 63 | System.out.println("MyThread"); 64 | } 65 | } 66 | ``` 67 | Run.java 68 | 69 | ```java 70 | public class Run { 71 | 72 | public static void main(String[] args) { 73 | MyThread mythread = new MyThread(); 74 | mythread.start(); 75 | System.out.println("运行结束"); 76 | } 77 | 78 | } 79 | 80 | ``` 81 | 运行结果: 82 | ![结果](https://user-gold-cdn.xitu.io/2018/3/20/16243e80f22a2d54?w=161&h=54&f=jpeg&s=7380) 83 | 从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。 84 | 85 | ### ②实现Runnable接口 86 | 推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。 87 | 88 | MyRunnable.java 89 | 90 | ```java 91 | public class MyRunnable implements Runnable { 92 | @Override 93 | public void run() { 94 | System.out.println("MyRunnable"); 95 | } 96 | } 97 | ``` 98 | 99 | Run.java 100 | 101 | ```java 102 | public class Run { 103 | 104 | public static void main(String[] args) { 105 | Runnable runnable=new MyRunnable(); 106 | Thread thread=new Thread(runnable); 107 | thread.start(); 108 | System.out.println("运行结束!"); 109 | } 110 | 111 | } 112 | ``` 113 | 运行结果: 114 | ![运行结果](https://user-gold-cdn.xitu.io/2018/3/20/16243f4373c6141a?w=137&h=46&f=jpeg&s=7316) 115 | 116 | ### ③使用线程池 117 | 118 | **在《阿里巴巴Java开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。** 119 | 120 | **为什么呢?** 121 | 122 | > **使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。** 123 | 124 | **另外《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险** 125 | 126 | > Executors 返回线程池对象的弊端如下: 127 | > 128 | > - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 129 | > - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。 130 | 131 | 对于线程池感兴趣的可以查看我的这篇文章:[《Java多线程学习(八)线程池与Executor 框架》](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484042&idx=1&sn=541dbf2cb969a151d79f4a4f837ee1bd&chksm=fd9854ebcaefddfd1876bb96ab218be3ae7b12546695a403075d4ed22e5e17ff30ebdabc8bbf#rd) 点击阅读原文即可查看到该文章的最新版。 132 | 133 | 134 | ## 6 线程的优先级 135 | 136 | 每个线程都具有各自的优先级,**线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态**。但这个并不意味着低 137 | 优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。 138 | 139 | **线程优先级具有继承特性。** 比如A线程启动B线程,则B线程的优先级和A是一样的。 140 | 141 | **线程优先级具有随机性。** 也就是说线程优先级高的不一定每一次都先执行完。 142 | 143 | Thread类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY(常数1)**,**Thread.NORM_PRIORITY(常数5)**, 144 | **Thread.MAX_PRIORITY(常数10)**。其中每个线程的优先级都在**Thread.MIN_PRIORITY(常数1)** 到**Thread.MAX_PRIORITY(常数10)** 之间,在默认情况下优先级都是**Thread.NORM_PRIORITY(常数5)**。 145 | 146 | 学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。 147 | 148 | 149 | ## 7 Java多线程分类 150 | 151 | ### 用户线程 152 | 153 | 运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程 154 | 155 | ### 守护线程 156 | 157 | 运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 **“佣人”**。 158 | 159 | 160 | - **特点:** 一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作 161 | - **应用:** 数据库连接池中的检测线程,JVM虚拟机启动后的检测线程 162 | - **最常见的守护线程:** 垃圾回收线程 163 | 164 | 165 | **如何设置守护线程?** 166 | 167 | 可以通过调用 Thead 类的 `setDaemon(true)` 方法设置当前的线程为守护线程。 168 | 169 | 注意事项: 170 | 171 | 1. setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常 172 | 2. 在守护线程中产生的新线程也是守护线程 173 | 3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑 174 | 175 | 176 | ## 8 sleep()方法和wait()方法简单对比 177 | 178 | - 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁** 。 179 | - 两者都可以暂停线程的执行。 180 | - Wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。 181 | - wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后,线程会自动苏醒。 182 | 183 | 184 | ## 9 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法? 185 | 186 | 这是另一个非常经典的java多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来! 187 | 188 | new一个Thread,线程进入了新建状态;调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 189 | start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。 而直接执行run()方法,会把run方法当成一个mian线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。 190 | 191 | **总结: 调用start方法方可启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法调用,还是在主线程里执行。** 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /面试必备/程序员的简历之道.md: -------------------------------------------------------------------------------- 1 | # 程序员的简历就该这样写 2 | 3 | ### 1 前言 4 | 一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。 5 | 6 | ### 2 为什么说简历很重要? 7 | 8 | #### 2.1 先从面试前来说 9 | 10 | 假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。 11 | 12 | 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。 13 | 14 | 另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。 15 | 16 | 所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。 17 | 18 | #### 2.2 再从面试中来说 19 | 20 | 我发现大家比较喜欢看面经 ,这点无可厚非,但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。 21 | 22 | 所以,首先,你要明确的一点是:**你不会的东西就不要写在简历上**。另外,**你要考虑你该如何才能让你的亮点在简历中凸显出来**,比如:你在某某项目做了什么事情解决了什么问题(只要有项目就一定有要解决的问题)、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。 23 | 24 | 面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。 25 | 26 | ### 3 下面这几点你必须知道 27 | 28 | 1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。 29 | 2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作** 30 | 3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;** 31 | 4. **将自己的项目经历完美的展示出来非常重要。** 32 | 33 | ### 4 必须了解的两大法则 34 | 35 | 36 | **①STAR法则(Situation Task Action Result):** 37 | 38 | - **Situation:** 事情是在什么情况下发生; 39 | - **Task::** 你是如何明确你的任务的; 40 | - **Action:** 针对这样的情况分析,你采用了什么行动方式; 41 | - **Result:** 结果怎样,在这样的情况下你学习到了什么。 42 | 43 | 简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。 44 | 45 | 下面这段内容摘自百度百科,我觉得写的非常不错: 46 | 47 | > STAR法则,500强面试题回答时的技巧法则,备受面试者成功者和500强HR的推崇。 48 | 由于这个法则被广泛应用于面试问题的回答,尽管我们还在写简历阶段,但是,写简历时能把面试的问题就想好,会使自己更加主动和自信,做到简历,面试关联性,逻辑性强,不至于在一个月后去面试,却把简历里的东西都忘掉了(更何况有些朋友会稍微夸大简历内容) 49 | 在我们写简历时,每个人都要写上自己的工作经历,活动经历,想必每一个同学,都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历,争取找出最好的东西写在简历上。 50 | 但是此时,我们要注意了,简历上的任何一个信息点都有可能成为日后面试时的重点提问对象,所以说,不能只管写上让自己感觉最牛的经历就完事了,要想到今后,在面试中,你所写的经历万一被面试官问到,你真的能回答得流利,顺畅,且能通过这段经历,证明自己正是适合这个职位的人吗? 51 | 52 | **②FAB 法则(Feature Advantage Benefit):** 53 | 54 | - **Feature:** 是什么; 55 | - **Advantage:** 比别人好在哪些地方; 56 | - **Benefit:** 如果雇佣你,招聘方会得到什么好处。 57 | 58 | 简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。 59 | 60 | ### 5 项目经历怎么写? 61 | 62 | 简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写: 63 | 64 | 1. 对项目整体设计的一个感受 65 | 2. 在这个项目中你负责了什么、做了什么、担任了什么角色 66 | 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 67 | 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 68 | 69 | ### 6 专业技能该怎么写? 70 | 先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善): 71 | 72 | - 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握 73 | - Java 基础知识:掌握 74 | - JVM 虚拟机(Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理):掌握 75 | - 高并发、高可用、高性能系统开发:掌握 76 | - Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握 77 | - SSH 整合、SSM 整合、 SOA 架构:掌握 78 | - Dubbo: 掌握 79 | - Zookeeper: 掌握 80 | - 常见消息队列: 掌握 81 | - Linux:掌握 82 | - MySQL常见优化手段:掌握 83 | - Spring Boot +Spring Cloud +Docker:了解 84 | - Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解 85 | - Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉 86 | 87 | ### 7 开源程序员Markdown格式简历模板分享 88 | 89 | 分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 90 | Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample) 91 | 92 | 93 | 我的下面这篇文章讲了如何写一份Markdown格式的简历,另外,文中还提到了一种实现 Markdown 格式到PDF、HTML、JPEG这几种格式的转换方法。 94 | 95 | [手把手教你用Markdown写一份高质量的简历](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484347&idx=1&sn=a986ea7e199871999a5257bd3ed78be1&chksm=fd9855dacaefdccc2c5d5f8f79c4aa1b608ad5b42936bccaefb99a850a2e6e8e2e910e1b3153&token=719595858&lang=zh_CN#rd) 96 | 97 | ### 8 其他的一些小tips 98 | 99 | 1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 100 | 2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。 101 | 3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。 102 | 4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。 103 | 5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容 104 | 6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 105 | 7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 106 | 8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。 107 | -------------------------------------------------------------------------------- /面试必备/简历模板.md: -------------------------------------------------------------------------------- 1 | # 联系方式 2 | 3 | - 手机: 4 | - Email: 5 | - 微信: 6 | 7 | # 个人信息 8 | 9 | - 姓名/性别/出生日期 10 | - 本科/xxx计算机系xxx专业/英语六级 11 | - 技术博客:[http://snailclimb.top/](http://snailclimb.top/) 12 | - 荣誉奖励:获得了什么奖(获奖时间) 13 | - Github:[https://github.com/Snailclimb ](https://github.com/Snailclimb) 14 | - Github Resume: [http://resume.github.io/?Snailclimb](http://resume.github.io/?Snailclimb) 15 | - 期望职位:Java 研发程序员/大数据工程师(Java后台开发为首选) 16 | - 期望城市:xxx城市 17 | 18 | 19 | # 项目经历 20 | 21 | ## xxx项目 22 | 23 | ### 项目描述 24 | 25 | 介绍该项目是做什么的、使用到了什么技术以及你对项目整体设计的一个感受 26 | 27 | ### 责任描述 28 | 29 | 主要可以从下面三点来写: 30 | 31 | 1. 在这个项目中你负责了什么、做了什么、担任了什么角色 32 | 2. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 33 | 3. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。 34 | 35 | # 开源项目和技术文章 36 | 37 | ## 开源项目 38 | 39 | - [Java-Guide](https://github.com/Snailclimb/Java-Guide) :一份涵盖大部分Java程序员所需要掌握的核心知识。Star:3.9K; Fork:0.9k。 40 | 41 | 42 | ## 技术文章推荐 43 | 44 | - [可能是把Java内存区域讲的最清楚的一篇文章](https://juejin.im/post/5b7d69e4e51d4538ca5730cb) 45 | - [搞定JVM垃圾回收就是这么简单](https://juejin.im/post/5b85ea54e51d4538dd08f601) 46 | - [前端&后端程序员必备的Linux基础知识](https://juejin.im/post/5b3b19856fb9a04fa42f8c71) 47 | - [可能是把Docker的概念讲的最清楚的一篇文章](https://juejin.im/post/5b260ec26fb9a00e8e4b031a) 48 | 49 | 50 | # 校园经历(可选) 51 | 52 | ## 2016-2017 53 | 54 | 担任学校社团-致深社副会长,主要负责团队每周活动的组建以及每周例会的主持。 55 | 56 | ## 2017-2018 57 | 担任学校传媒组织:“长江大学在线信息传媒”的副站长以及安卓组成员。主要负责每周例会主持、活动策划以及学校校园通APP的研发工作。 58 | 59 | 60 | # 技能清单 61 | 62 | 以下均为我熟练使用的技能 63 | 64 | - Web开发:PHP/Hack/Node 65 | - Web框架:ThinkPHP/Yaf/Yii/Lavarel/LazyPHP 66 | - 前端框架:Bootstrap/AngularJS/EmberJS/HTML5/Cocos2dJS/ionic 67 | - 前端工具:Bower/Gulp/SaSS/LeSS/PhoneGap 68 | - 数据库相关:MySQL/PgSQL/PDO/SQLite 69 | - 版本管理、文档和自动化部署工具:Svn/Git/PHPDoc/Phing/Composer 70 | - 单元测试:PHPUnit/SimpleTest/Qunit 71 | - 云和开放平台:SAE/BAE/AWS/微博开放平台/微信应用开发 72 | 73 | # 自我评价(可选) 74 | 75 | 自我发挥。切记不要过度自夸!!! 76 | 77 | 78 | ### 感谢您花时间阅读我的简历,期待能有机会和您共事。 79 | 80 | -------------------------------------------------------------------------------- /面试必备/面试必备之乐观锁与悲观锁.md: -------------------------------------------------------------------------------- 1 | ### 何谓悲观锁与乐观锁 2 | 3 | > 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 4 | 5 | #### 悲观锁 6 | 7 | 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中`synchronized`和`ReentrantLock`等独占锁就是悲观锁思想的实现。 8 | 9 | 10 | #### 乐观锁 11 | 12 | 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。**乐观锁适用于多读的应用类型,这样可以提高吞吐量**,像数据库提供的类似于**write_condition机制**,其实都是提供的乐观锁。在Java中`java.util.concurrent.atomic`包下面的原子变量类就是使用了乐观锁的一种实现方式**CAS**实现的。 13 | 14 | #### 两种锁的使用场景 15 | 16 | 从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像**乐观锁适用于写比较少的情况下(多读场景)**,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以**一般多写的场景下用悲观锁就比较合适。** 17 | 18 | 19 | ### 乐观锁常见的两种实现方式 20 | 21 | > **乐观锁一般会使用版本号机制或CAS算法实现。** 22 | 23 | #### 1. 版本号机制 24 | 25 | 一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。 26 | 27 | **举一个简单的例子:** 28 | 假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。 29 | 30 | 1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。 31 | 2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。 32 | 3. 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。 33 | 4. 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。 34 | 35 | 这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。 36 | 37 | #### 2. CAS算法 38 | 39 | 即**compare and swap(比较与交换)**,是一种有名的**无锁算法**。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。**CAS算法**涉及到三个操作数 40 | 41 | - 需要读写的内存值 V 42 | - 进行比较的值 A 43 | - 拟写入的新值 B 44 | 45 | 当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个**自旋操作**,即**不断的重试**。 46 | 47 | 关于自旋锁,大家可以看一下这篇文章,非常不错:[《 48 | 面试必备之深入理解自旋锁》](https://blog.csdn.net/qq_34337272/article/details/81252853) 49 | 50 | ### 乐观锁的缺点 51 | 52 | > ABA 问题是乐观锁一个常见的问题 53 | 54 | #### 1 ABA 问题 55 | 56 | 如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 **"ABA"问题。** 57 | 58 | JDK 1.5 以后的 `AtomicStampedReference 类`就提供了此种能力,其中的 `compareAndSet 方法`就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 59 | 60 | #### 2 循环时间长开销大 61 | **自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。** 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。 62 | 63 | #### 3 只能保证一个共享变量的原子操作 64 | 65 | CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了`AtomicReference类`来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。 66 | 67 | 68 | 69 | ### CAS与synchronized的使用情景 70 | 71 | > **简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)** 72 | 73 | 1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。 74 | 2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。 75 | 76 | 77 | 补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 **“重量级锁”** 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 **偏向锁** 和 **轻量级锁** 以及其它**各种优化**之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 **Lock-Free** 的队列,基本思路是 **自旋后阻塞**,**竞争切换后继续竞争锁**,**稍微牺牲了公平性,但获得了高吞吐量**。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | --------------------------------------------------------------------------------