diff --git a/.gitattributes b/.gitattributes index 05d154096837744c9f19dc70b562028796a902a0..2ab2d3c5962cf93b258ecf49095caa1b8a713574 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,4 @@ -# Auto detect text files and perform LF normalization * text=auto *.js linguist-language=java *.css linguist-language=java -*.html linguist-language=java \ No newline at end of file +*.html linguist-language=java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b2cb8d3d80312c823d64aa9ccf95489f7dabdacb --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +.gradle +/build/ +/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +/out/ +/**/out/ +.shelf/ +.ideaDataSources/ +dataSources/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/node_modules/ + +### OS ### +.DS_Store +.Ds_Store´ +/node_modules diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" "b/Java\347\233\270\345\205\263/ArrayList-Grow.md" deleted file mode 100644 index d763cb83492b4319280298756de303800b732bf3..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" +++ /dev/null @@ -1,347 +0,0 @@ - -## 一 先从 ArrayList 的构造函数说起 - -**ArrayList有三种方式来初始化,构造方法源码如下:** - -```java - /** - * 默认初始容量大小 - */ - private static final int DEFAULT_CAPACITY = 10; - - - private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; - - /** - *默认构造函数,使用初始容量10构造一个空列表(无参数构造) - */ - public ArrayList() { - this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; - } - - /** - * 带初始容量参数的构造函数。(用户自己指定容量) - */ - public ArrayList(int initialCapacity) { - if (initialCapacity > 0) {//初始容量大于0 - //创建initialCapacity大小的数组 - this.elementData = new Object[initialCapacity]; - } else if (initialCapacity == 0) {//初始容量等于0 - //创建空数组 - this.elementData = EMPTY_ELEMENTDATA; - } else {//初始容量小于0,抛出异常 - throw new IllegalArgumentException("Illegal Capacity: "+ - initialCapacity); - } - } - - - /** - *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回 - *如果指定的集合为null,throws NullPointerException。 - */ - public ArrayList(Collection c) { - elementData = c.toArray(); - if ((size = elementData.length) != 0) { - // c.toArray might (incorrectly) not return Object[] (see 6260652) - if (elementData.getClass() != Object[].class) - elementData = Arrays.copyOf(elementData, size, Object[].class); - } else { - // replace with empty array. - this.elementData = EMPTY_ELEMENTDATA; - } - } - -``` - -细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容! - -## 二 一步一步分析 ArrayList 扩容机制 - -这里以无参构造函数创建的 ArrayList 为例分析 - -### 1. 先来看 `add` 方法 - -```java - /** - * 将指定的元素追加到此列表的末尾。 - */ - public boolean add(E e) { - //添加元素之前,先调用ensureCapacityInternal方法 - ensureCapacityInternal(size + 1); // Increments modCount!! - //这里看到ArrayList添加元素的实质就相当于为数组赋值 - elementData[size++] = e; - return true; - } -``` -### 2. 再来看看 `ensureCapacityInternal()` 方法 - -可以看到 `add` 方法 首先调用了`ensureCapacityInternal(size + 1)` - -```java - //得到最小扩容量 - private void ensureCapacityInternal(int minCapacity) { - if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - // 获取默认的容量和传入参数的较大值 - minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); - } - - ensureExplicitCapacity(minCapacity); - } -``` -**当 要 add 进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。** - -### 3. `ensureExplicitCapacity()` 方法 - -如果调用 `ensureCapacityInternal()` 方法就一定会进过(执行)这个方法,下面我们来研究一下这个方法的源码! - -```java - //判断是否需要扩容 - private void ensureExplicitCapacity(int minCapacity) { - modCount++; - - // overflow-conscious code - if (minCapacity - elementData.length > 0) - //调用grow方法进行扩容,调用此方法代表已经开始扩容了 - grow(minCapacity); - } - -``` - -我们来仔细分析一下: - -- 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 `ensureCapacityInternal()` 方法 ,所以 minCapacity 此时为10。此时,`minCapacity - elementData.length > 0 `成立,所以会进入 `grow(minCapacity)` 方法。 -- 当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,`minCapacity - elementData.length > 0 ` 不成立,所以不会进入 (执行)`grow(minCapacity)` 方法。 -- 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。 - -直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进入grow方法进行扩容。 - -### 4. `grow()` 方法 - -```java - /** - * 要分配的最大数组大小 - */ - private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - - /** - * ArrayList扩容的核心方法。 - */ - private void grow(int minCapacity) { - // oldCapacity为旧容量,newCapacity为新容量 - int oldCapacity = elementData.length; - //将oldCapacity 右移一位,其效果相当于oldCapacity /2, - //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, - int newCapacity = oldCapacity + (oldCapacity >> 1); - //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE, - //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 - if (newCapacity - MAX_ARRAY_SIZE > 0) - newCapacity = hugeCapacity(minCapacity); - // minCapacity is usually close to size, so this is a win: - elementData = Arrays.copyOf(elementData, newCapacity); - } -``` - -**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!** 记清楚了!不是网上很多人说的 1.5 倍+1! - -> ">>"(移位运算符):>>1 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源   - -**我们再来通过例子探究一下`grow()` 方法 :** - -- 当add第1个元素时,oldCapacity 为0,经比较后第一个if判断成立,newCapacity = minCapacity(为10)。但是第二个if判断不会成立,即newCapacity 不比 MAX_ARRAY_SIZE大,则不会进入 `hugeCapacity` 方法。数组容量为10,add方法中 return true,size增为1。 -- 当add第11个元素进入grow方法时,newCapacity为15,比minCapacity(为11)大,第一个if判断不成立。新容量没有大于数组最大size,不会进入hugeCapacity方法。数组容量扩为15,add方法中return true,size增为11。 -- 以此类推······ - -**这里补充一点比较重要,但是容易被忽视掉的知识点:** - -- java 中的 `length `属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性. -- java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法. -- java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! - -### 5. `hugeCapacity()` 方法。 - -从上面 `grow()` 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 - - -```java - private static int hugeCapacity(int minCapacity) { - if (minCapacity < 0) // overflow - throw new OutOfMemoryError(); - //对minCapacity和MAX_ARRAY_SIZE进行比较 - //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小 - //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小 - //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - return (minCapacity > MAX_ARRAY_SIZE) ? - Integer.MAX_VALUE : - MAX_ARRAY_SIZE; - } -``` - - - -## 三 `System.arraycopy()` 和 `Arrays.copyOf()`方法 - - -阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法! - - -### 3.1 `System.arraycopy()` 方法 - -```java - /** - * 在此列表中的指定位置插入指定的元素。 - *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; - *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 - */ - public void add(int index, E element) { - rangeCheckForAdd(index); - - ensureCapacityInternal(size + 1); // Increments modCount!! - //arraycopy()方法实现数组自己复制自己 - //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; - System.arraycopy(elementData, index, elementData, index + 1, size - index); - elementData[index] = element; - size++; - } -``` - -我们写一个简单的方法测试以下: - -```java -public class ArraycopyTest { - - public static void main(String[] args) { - // TODO Auto-generated method stub - int[] a = new int[10]; - a[0] = 0; - a[1] = 1; - a[2] = 2; - a[3] = 3; - System.arraycopy(a, 2, a, 3, 3); - a[2]=99; - for (int i = 0; i < a.length; i++) { - System.out.println(a[i]); - } - } - -} -``` - -结果: - -``` -0 1 99 2 3 0 0 0 0 0 -``` - -### 3.2 `Arrays.copyOf()`方法 - -```java - /** - 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 - */ - public Object[] toArray() { - //elementData:要复制的数组;size:要复制的长度 - return Arrays.copyOf(elementData, size); - } -``` - -个人觉得使用 `Arrays.copyOf()`方法主要是为了给原有数组扩容,测试代码如下: - -```java -public class ArrayscopyOfTest { - - public static void main(String[] args) { - int[] a = new int[3]; - a[0] = 0; - a[1] = 1; - a[2] = 2; - int[] b = Arrays.copyOf(a, 10); - System.out.println("b.length"+b.length); - } -} -``` - -结果: - -``` -10 -``` - - -### 3.3 两者联系和区别 - -**联系:** - -看两者源代码可以发现 copyOf() 内部实际调用了 `System.arraycopy()` 方法 - -**区别:** - -`arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。 - - - -## 四 `ensureCapacity`方法 - -ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢? - -```java - /** - 如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。 - * - * @param minCapacity 所需的最小容量 - */ - public void ensureCapacity(int minCapacity) { - int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) - // any size if not default element table - ? 0 - // larger than default for default empty table. It's already - // supposed to be at default size. - : DEFAULT_CAPACITY; - - if (minCapacity > minExpand) { - ensureExplicitCapacity(minCapacity); - } - } - -``` - -**最好在 add 大量元素之前用 `ensureCapacity` 方法,以减少增量重新分配的次数** - -我们通过下面的代码实际测试以下这个方法的效果: - -```java -public class EnsureCapacityTest { - public static void main(String[] args) { - ArrayList list = new ArrayList(); - final int N = 10000000; - long startTime = System.currentTimeMillis(); - for (int i = 0; i < N; i++) { - list.add(i); - } - long endTime = System.currentTimeMillis(); - System.out.println("使用ensureCapacity方法前:"+(endTime - startTime)); - - list = new ArrayList(); - long startTime1 = System.currentTimeMillis(); - list.ensureCapacity(N); - for (int i = 0; i < N; i++) { - list.add(i); - } - long endTime1 = System.currentTimeMillis(); - System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1)); - } -} -``` - -运行结果: - -``` -使用ensureCapacity方法前:4637 -使用ensureCapacity方法后:241 - -``` - -通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数 diff --git "a/Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" "b/Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" deleted file mode 100644 index 905df527c40e26cd22ed74857de5e8faccdf283b..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" +++ /dev/null @@ -1,200 +0,0 @@ - - -- [IO流学习总结](#io流学习总结) - - [一 Java IO,硬骨头也能变软](#一-java-io,硬骨头也能变软) - - [二 java IO体系的学习总结](#二-java-io体系的学习总结) - - [三 Java IO面试题](#三-java-io面试题) -- [NIO与AIO学习总结](#nio与aio学习总结) - - [一 Java NIO 概览](#一-java-nio-概览) - - [二 Java NIO 之 Buffer\(缓冲区\)](#二-java-nio-之-buffer缓冲区) - - [三 Java NIO 之 Channel(通道)](#三-java-nio-之-channel(通道)) - - [四 Java NIO之Selector(选择器)](#四-java-nio之selector(选择器)) - - [五 Java NIO之拥抱Path和Files](#五-java-nio之拥抱path和files) - - [六 NIO学习总结以及NIO新特性介绍](#六-nio学习总结以及nio新特性介绍) - - [七 Java NIO AsynchronousFileChannel异步文件通](#七-java-nio-asynchronousfilechannel异步文件通) - - [八 高并发Java(8):NIO和AIO](#八-高并发java(8):nio和aio) -- [推荐阅读](#推荐阅读) - - [在 Java 7 中体会 NIO.2 异步执行的快乐](#在-java-7-中体会-nio2-异步执行的快乐) - - [Java AIO总结与示例](#java-aio总结与示例) - - - - - -## IO流学习总结 - -### [一 Java IO,硬骨头也能变软](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483981&idx=1&sn=6e5c682d76972c8d2cf271a85dcf09e2&chksm=fd98542ccaefdd3a70428e9549bc33e8165836855edaa748928d16c1ebde9648579d3acaac10#rd) - -**(1) 按操作方式分类结构图:** - -![按操作方式分类结构图:](https://user-gold-cdn.xitu.io/2018/5/16/16367d4fd1ce1b46?w=720&h=1080&f=jpeg&s=69522) - - -**(2)按操作对象分类结构图** - -![按操作对象分类结构图](https://user-gold-cdn.xitu.io/2018/5/16/16367d673b0e268d?w=720&h=535&f=jpeg&s=46081) - -### [二 java IO体系的学习总结](https://blog.csdn.net/nightcurtis/article/details/51324105) -1. **IO流的分类:** - - 按照流的流向分,可以分为输入流和输出流; - - 按照操作单元划分,可以划分为字节流和字符流; - - 按照流的角色划分为节点流和处理流。 -2. **流的原理浅析:** - - java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。 - - - **InputStream/Reader**: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 - - **OutputStream/Writer**: 所有输出流的基类,前者是字节输出流,后者是字符输出流。 -3. **常用的io流的用法** - -### [三 Java IO面试题](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483985&idx=1&sn=38531c2cee7b87f125df7aef41637014&chksm=fd985430caefdd26b0506aa84fc26251877eccba24fac73169a4d6bd1eb5e3fbdf3c3b940261#rd) - -## NIO与AIO学习总结 - - -### [一 Java NIO 概览](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483956&idx=1&sn=57692bc5b7c2c6dfb812489baadc29c9&chksm=fd985455caefdd4331d828d8e89b22f19b304aa87d6da73c5d8c66fcef16e4c0b448b1a6f791#rd) - -1. **NIO简介**: - - Java NIO 是 java 1.4, 之后新出的一套IO接口NIO中的N可以理解为Non-blocking,不单纯是New。 - -2. **NIO的特性/NIO与IO区别:** - - 1)IO是面向流的,NIO是面向缓冲区的; - - 2)IO流是阻塞的,NIO流是不阻塞的; - - 3)NIO有选择器,而IO没有。 -3. **读数据和写数据方式:** - - 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。 - - - 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。 - -4. **NIO核心组件简单介绍** - - **Channels** - - **Buffers** - - **Selectors** - - -### [二 Java NIO 之 Buffer(缓冲区)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483961&idx=1&sn=f67bef4c279e78043ff649b6b03fdcbc&chksm=fd985458caefdd4e3317ccbdb2d0a5a70a5024d3255eebf38183919ed9c25ade536017c0a6ba#rd) - -1. **Buffer(缓冲区)介绍:** - - Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels; - - Buffer本质上就是一块内存区; - - 一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit限制。 -2. **Buffer的常见方法** - - Buffer clear() - - Buffer flip() - - Buffer rewind() - - Buffer position(int newPosition) -3. **Buffer的使用方式/方法介绍:** - - 分配缓冲区(Allocating a Buffer): - ```java - ByteBuffer buf = ByteBuffer.allocate(28);//以ByteBuffer为例子 - ``` - - 写入数据到缓冲区(Writing Data to a Buffer) - - **写数据到Buffer有两种方法:** - - 1.从Channel中写数据到Buffer - ```java - int bytesRead = inChannel.read(buf); //read into buffer. - ``` - 2.通过put写数据: - ```java - buf.put(127); - ``` - -4. **Buffer常用方法测试** - - 说实话,NIO编程真的难,通过后面这个测试例子,你可能才能勉强理解前面说的Buffer方法的作用。 - - -### [三 Java NIO 之 Channel(通道)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483966&idx=1&sn=d5cf18c69f5f9ec2aff149270422731f&chksm=fd98545fcaefdd49296e2c78000ce5da277435b90ba3c03b92b7cf54c6ccc71d61d13efbce63#rd) - - -1. **Channel(通道)介绍** - - 通常来说NIO中的所有IO都是从 Channel(通道) 开始的。 - - NIO Channel通道和流的区别: -2. **FileChannel的使用** -3. **SocketChannel和ServerSocketChannel的使用** -4. **️DatagramChannel的使用** -5. **Scatter / Gather** - - Scatter: 从一个Channel读取的信息分散到N个缓冲区中(Buufer). - - Gather: 将N个Buffer里面内容按照顺序发送到一个Channel. -6. **通道之间的数据传输** - - 在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。 - - transferFrom() :transferFrom方法把数据从通道源传输到FileChannel - - transferTo() :transferTo方法把FileChannel数据传输到另一个channel - - -### [四 Java NIO之Selector(选择器)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483970&idx=1&sn=d5e2b133313b1d0f32872d54fbdf0aa7&chksm=fd985423caefdd354b587e57ce6cf5f5a7bec48b9ab7554f39a8d13af47660cae793956e0f46#rd) - - -1. **Selector(选择器)介绍** - - Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。 - - 使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。 -2. **Selector(选择器)的使用方法介绍** - - Selector的创建 - ```java - Selector selector = Selector.open(); - ``` - - 注册Channel到Selector(Channel必须是非阻塞的) - ```java - channel.configureBlocking(false); - SelectionKey key = channel.register(selector, Selectionkey.OP_READ); - ``` - - SelectionKey介绍 - - 一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。 - - 从Selector中选择channel(Selecting Channels via a Selector) - - 选择器维护注册过的通道的集合,并且这种注册关系都被封装在SelectionKey当中. - - 停止选择的方法 - - wakeup()方法 和close()方法。 -3. **模板代码** - - 有了模板代码我们在编写程序时,大多数时间都是在模板代码中添加相应的业务代码。 -4. **客户端与服务端简单交互实例** - - - -### [五 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) - -**一 文件I/O基石:Path:** -- 创建一个Path -- File和Path之间的转换,File和URI之间的转换 -- 获取Path的相关信息 -- 移除Path中的冗余项 - -**二 拥抱Files类:** -- Files.exists() 检测文件路径是否存在 -- Files.createFile() 创建文件 -- Files.createDirectories()和Files.createDirectory()创建文件夹 -- Files.delete()方法 可以删除一个文件或目录 -- Files.copy()方法可以吧一个文件从一个地址复制到另一个位置 -- 获取文件属性 -- 遍历一个文件夹 -- Files.walkFileTree()遍历整个目录 - -### [六 NIO学习总结以及NIO新特性介绍](https://blog.csdn.net/a953713428/article/details/64907250) - -- **内存映射:** - -这个功能主要是为了提高大文件的读写速度而设计的。内存映射文件(memory-mappedfile)能让你创建和修改那些大到无法读入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问了。将文件的一段区域映射到内存中,比传统的文件处理速度要快很多。内存映射文件它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。 - -### [七 Java NIO AsynchronousFileChannel异步文件通](http://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-asynchronousfilechannel.html) - -Java7中新增了AsynchronousFileChannel作为nio的一部分。AsynchronousFileChannel使得数据可以进行异步读写。 - -### [八 高并发Java(8):NIO和AIO](http://www.importnew.com/21341.html) - - - -## 推荐阅读 - -### [在 Java 7 中体会 NIO.2 异步执行的快乐](https://www.ibm.com/developerworks/cn/java/j-lo-nio2/index.html) - -### [Java AIO总结与示例](https://blog.csdn.net/x_i_y_u_e/article/details/52223406) -AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。 - - -**欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):** diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" deleted file mode 100644 index 8b438eb18f8b37a13a85ddaa384cdb2938612c84..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ /dev/null @@ -1,475 +0,0 @@ - - - - -- [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) - - [面向过程](#面向过程) - - [面向对象](#面向对象) -- [2. Java 语言有哪些特点](#2-java-语言有哪些特点) -- [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答) - - [JVM](#jvm) - - [JDK 和 JRE](#jdk-和-jre) -- [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比) -- [5. Java和C++的区别](#5-java和c的区别) -- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同) -- [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别) -- [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别) -- [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override) -- [10. 重载和重写的区别](#10-重载和重写的区别) -- [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态) - - [封装](#封装) - - [继承](#继承) - - [多态](#多态) -- [12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的) -- [13. 自动装箱与拆箱](#13-自动装箱与拆箱) -- [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的) -- [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用) -- [16. import java和javax有什么区别](#16-import-java和javax有什么区别) -- [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么) -- [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些) -- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同) -- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么) -- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么) -- [22. 构造方法有哪些特性](#22-构造方法有哪些特性) -- [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同) -- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同) -- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是) -- [26. == 与 equals\(重要\)](#26--与-equals重要) -- [27. hashCode 与 equals \(重要\)](#27-hashcode-与-equals-重要) - - [hashCode()介绍](#hashcode()介绍) - - [为什么要有 hashCode](#为什么要有-hashcode) - - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) -- [28. 为什么Java中只有值传递](#28-为什么java中只有值传递) -- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程程序进程的基本概念以及他们之间关系是什么) -- [30. 线程有哪些基本状态?](#30-线程有哪些基本状态) -- [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) -- [32 Java 中的异常处理](#32-java-中的异常处理) - - [Java异常类层次结构图](#java异常类层次结构图) - - [Throwable类常用方法](#throwable类常用方法) - - [异常处理总结](#异常处理总结) -- [33 Java序列化中如果有些字段不想进行序列化 怎么办](#33-java序列化中如果有些字段不想进行序列化-怎么办) -- [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法) -- [参考](#参考) - - - - - -## 1. 面向对象和面向过程的区别 - -### 面向过程 - -**优点:** 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 - -**缺点:** 没有面向对象易维护、易复用、易扩展 - -### 面向对象 - -**优点:** 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 - -**缺点:** 性能比面向过程低 - -## 2. Java 语言有哪些特点 - -1. 简单易学; -2. 面向对象(封装,继承,多态); -3. 平台无关性( Java 虚拟机实现平台无关性); -4. 可靠性; -5. 安全性; -6. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持); -7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); -8. 编译与解释并存; - -## 3. 关于 JVM JDK 和 JRE 最详细通俗的解答 - -### JVM - -Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。 - -**什么是字节码?采用字节码的好处是什么?** - -> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 - -**Java 程序从源代码到运行一般有下面3步:** - -![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png) - -我们需要格外注意的是 .class->机器码 这一步。在这一步 jvm 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的,也就是所谓的热点代码,所以后面引进了 JIT 编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。 - -> HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。 - -总结:Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 - -### JDK 和 JRE - -JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。 - -JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。 - -如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。 - -## 4. Oracle JDK 和 OpenJDK 的对比 - -可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面通过我通过我收集到一些资料对你解答这个被很多人忽视的问题。 - -对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案: - -> 问:OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别? -> -> 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源Oracle JDK的所有部分,除了我们考虑商业功能的部分。 - -总结: - -1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次; -2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的; -3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题; -4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能; -5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本; -6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。 - -## 5. Java和C++的区别 - -我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来! - -- 都是面向对象的语言,都支持封装、继承和多态 -- Java 不提供指针来直接访问内存,程序内存更加安全 -- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。 -- Java 有自动内存管理机制,不需要程序员手动释放无用内存 - - -## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同 - -一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。 - -## 7. Java 应用程序与小程序之间有那些差别 - -简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟 flash 的小游戏类似。 - -## 8. 字符型常量和字符串常量的区别 - -1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符 -2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置) -3. 占内存大小 字符常量只占2个字节 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**) - -> java编程思想第四版:2.2.2节 -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) - -## 9. 构造器 Constructor 是否可被 override - -在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。 - -## 10. 重载和重写的区别 - -**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。    - -**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。 - -## 11. Java 面向对象编程三大特性: 封装 继承 多态 - -### 封装 - -封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 - - -### 继承 -继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。 - -**关于继承如下 3 点请记住:** - -1. 子类拥有父类非 private 的属性和方法。 -2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 -3. 子类可以用自己的方式实现父类的方法。(以后介绍)。 - -### 多态 - -所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 - -在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 - -## 12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的 - -**可变性** -  - -简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 - -StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。 - -AbstractStringBuilder.java - -```java -abstract class AbstractStringBuilder implements Appendable, CharSequence { - char[] value; - int count; - AbstractStringBuilder() { - } - AbstractStringBuilder(int capacity) { - value = new char[capacity]; - } -``` - - -**线程安全性** - -String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。 -   - -**性能** - -每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 - -**对于三者使用的总结:** -1. 操作少量的数据 = String -2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder -3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer - -## 13. 自动装箱与拆箱 -**装箱**:将基本类型用它们对应的引用类型包装起来; - -**拆箱**:将包装类型转换为基本数据类型; - -## 14. 在一个静态方法内调用一个非静态成员为什么是非法的 - -由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。 - -## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用 - Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 -  -## 16. import java和javax有什么区别 - -刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。 - -所以,实际上java和javax没有区别。这都是一个名字。 - -## 17. 接口和抽象类的区别是什么 - -1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法 -2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 -3. 一个类可以实现多个接口,但最多只能实现一个抽象类 -4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 -5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 - -备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,必须重写,不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146)) - -## 18. 成员变量与局部变量的区别有那些 - -1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰; -2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存 -3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 -4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。 - -## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同? - -new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。 - -## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么? - -方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! - -## 21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么? - -主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 - -## 22. 构造方法有哪些特性 - -1. 名字与类名相同; -2. 没有返回值,但不能用void声明构造函数; -3. 生成类的对象时自动执行,无需调用。 - -## 23. 静态方法和实例方法有何不同 - -1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 - -2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制. - -## 24. 对象的相等与指向他们的引用相等,两者有什么不同? - -对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。 - -## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是? - -帮助子类做初始化工作。 - -## 26. == 与 equals(重要) - -**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) - -**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: -- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。 -- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 - - -**举个例子:** - -```java -public class test1 { - public static void main(String[] args) { - String a = new String("ab"); // a 为一个引用 - String b = new String("ab"); // b为另一个引用,对象的内容一样 - String aa = "ab"; // 放在常量池中 - String bb = "ab"; // 从常量池中查找 - if (aa == bb) // true - System.out.println("aa==bb"); - if (a == b) // false,非同一对象 - System.out.println("a==b"); - if (a.equals(b)) // true - System.out.println("aEQb"); - if (42 == 42.0) { // true - System.out.println("true"); - } - } -} -``` - -**说明:** -- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。 -- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。 - - - -## 27. hashCode 与 equals (重要) - -面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” - -### hashCode()介绍 -hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 - -散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) - -### 为什么要有 hashCode - - -**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** - -当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - - - -### hashCode()与equals()的相关规定 - -1. 如果两个对象相等,则hashcode一定也是相同的 -2. 两个对象相等,对两个对象分别调用equals方法都返回true -3. 两个对象有相同的hashcode值,它们也不一定是相等的 -4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖** -5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) - - -## 28. 为什么Java中只有值传递 - - [为什么Java中只有值传递?](https://github.com/Snailclimb/Java-Guide/blob/master/%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87/%E6%9C%80%E6%9C%80%E6%9C%80%E5%B8%B8%E8%A7%81%E7%9A%84Java%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md) - - -## 29. 简述线程,程序,进程的基本概念.以及他们之间关系是什么? - -**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 - -**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。 - -**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 -线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。 - -## 30. 线程有哪些基本状态? - -Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4节)。 - -![Java线程的状态](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png) - -线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4节): - -![Java线程状态变迁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png) - - - -由上图可以看出: - -线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。 - -> 操作系统隐藏 Java虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。 - -![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png) - -当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。 - -## 31 关于 final 关键字的一些总结 - -final关键字主要用在三个地方:变量、方法、类。 - -1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 -2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 -3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 - -## 32 Java 中的异常处理 - -### Java异常类层次结构图 - -![Java异常类层次结构图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Exception.png) - 在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。 - -**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 - -这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。 - -**Exception(异常):是程序本身可以处理的异常**。Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由Java虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以0时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。 - -**注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。** - -### Throwable类常用方法 - -- **public string getMessage()**:返回异常发生时的详细信息 -- **public string toString()**:返回异常发生时的简要描述 -- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同 -- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 - -### 异常处理总结 - -- **try 块:**用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。 -- **catch 块:**用于处理try捕获到的异常。 -- **finally 块:**无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。 - -**在以下4种特殊情况下,finally块不会被执行:** - -1. 在finally语句块第一行发生了异常。 因为在其他行,finally块还是会得到执行 -2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行 -3. 程序所在的线程死亡。 -4. 关闭CPU。 - -下面这部分内容来自issue:。 - -**关于返回值:** - -如果try语句里有return,返回的是try语句块中变量值。 -详细执行过程如下: - -1. 如果有返回值,就把返回值保存到局部变量中; -2. 执行jsr指令跳到finally语句里执行; -3. 执行完finally语句后,返回之前保存在局部变量表里的值。 -4. 如果try,finally语句里均有return,忽略try的return,而使用finally的return. - -## 33 Java序列化中如果有些字段不想进行序列化 怎么办 - -对于不想进行序列化的变量,使用transient关键字修饰。 - -transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。 - -## 34 获取用键盘输入常用的的两种方法 - -方法1:通过 Scanner - -```java -Scanner input = new Scanner(System.in); -String s = input.nextLine(); -input.close(); -``` - -方法2:通过 BufferedReader - -```java -BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); -String s = input.readLine(); -``` - -## 参考 - -- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre -- https://www.educba.com/oracle-vs-openjdk/ -- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top diff --git "a/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" "b/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" deleted file mode 100644 index 238e7c8b5df9e4014b5fb62fcea53ad9f4371a0c..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" +++ /dev/null @@ -1,67 +0,0 @@ -Java面试通关手册(Java学习指南)github地址(欢迎star和pull):[https://github.com/Snailclimb/Java_Guide](https://github.com/Snailclimb/Java_Guide) - - - -下面是按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)这两篇文章。 - - -> ### 常见面试题 - -[深入理解虚拟机之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) - -1. 介绍下Java内存区域(运行时数据区)。 - -2. 对象的访问定位的两种方式。 - - -[深入理解虚拟机之垃圾回收](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) - -1. 如何判断对象是否死亡(两种方法)。 - -2. 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 - -3. 垃圾收集有哪些算法,各自的特点? - -4. HotSpot为什么要分为新生代和老年代? - -5. 常见的垃圾回收器有那些? - -6. 介绍一下CMS,G1收集器。 - -7. Minor Gc和Full GC 有什么不同呢? - - - -[虚拟机性能监控和故障处理工具](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) - -1. JVM调优的常见命令行工具有哪些? - -[深入理解虚拟机之类文件结构](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) - -1. 简单介绍一下Class类文件结构(常量池主要存放的是那两大常量?Class文件的继承关系是如何确定的?字段表、方法表、属性表主要包含那些信息?) - -[深入理解虚拟机之虚拟机类加载机制](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483934&idx=1&sn=f247f9bee4e240f5e7fac25659da3bff&chksm=fd98547fcaefdd6996e1a7046e03f29df9308bdf82ceeffd111112766ffd3187892700f64b40#rd) - -1. 简单说说类加载过程,里面执行了哪些操作? - -2. 对类加载器有了解吗? - -3. 什么是双亲委派模型? - -4. 双亲委派模型的工作过程以及使用它的好处。 - - - - - -> ### 推荐阅读 - -[深入理解虚拟机之虚拟机字节码执行引擎](https://juejin.im/post/5aebcb076fb9a07a9a10b5f3) - -[《深入理解 Java 内存模型》读书笔记](http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/) (非常不错的文章) - -[全面理解Java内存模型(JMM)及volatile关键字 ](https://blog.csdn.net/javazejian/article/details/72772461) - -**欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):** - -![微信公众号](https://user-gold-cdn.xitu.io/2018/3/19/1623c870135a3609?w=215&h=215&f=jpeg&s=29172) diff --git "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" deleted file mode 100644 index cb0bd1fe0e3dba790dbdec610c25d2654f79ffd3..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ /dev/null @@ -1,353 +0,0 @@ - - -1. [List,Set,Map三者的区别及总结](#list,setmap三者的区别及总结) -1. [Arraylist 与 LinkedList 区别](#arraylist-与-linkedlist-区别) -1. [ArrayList 与 Vector 区别(为什么要用Arraylist取代Vector呢?)](#arraylist-与-vector-区别) -1. [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别) -1. [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别) -1. [HashMap 和 ConcurrentHashMap 的区别](#hashmap-和-concurrenthashmap-的区别) -1. [HashSet如何检查重复](#hashset如何检查重复) -1. [comparable 和 comparator的区别](#comparable-和-comparator的区别) - 1. [Comparator定制排序](#comparator定制排序) - 1. [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序) -1. [如何对Object的list排序?](#如何对object的list排序) -1. [如何实现数组与List的相互转换?](#如何实现数组与list的相互转换) -1. [如何求ArrayList集合的交集 并集 差集 去重复并集](#如何求arraylist集合的交集-并集-差集-去重复并集) -1. [HashMap 的工作原理及代码实现](#hashmap-的工作原理及代码实现) -1. [ConcurrentHashMap 的工作原理及代码实现](#concurrenthashmap-的工作原理及代码实现) -1. [集合框架底层数据结构总结](#集合框架底层数据结构总结) - 1. [- Collection](#--collection) - 1. [1. List](#1-list) - 1. [2. Set](#2-set) - 1. [- Map](#--map) -1. [集合的选用](#集合的选用) -1. [集合的常用方法](#集合的常用方法) - - - - -## List,Set,Map三者的区别及总结 -- **List:对付顺序的好帮手** - - List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象 -- **Set:注重独一无二的性质** - - 不允许重复的集合。不会有多个元素引用相同的对象。 - -- **Map:用Key来搜索的专家** - - 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。 - - -## Arraylist 与 LinkedList 区别 -Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList 底层使用的是双向链表数据结构(插入,删除效率特别高)(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读JDK1.7-LinkedList循环链表优化。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。 - -## ArrayList 与 Vector 区别 -Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector -,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist。 - -## HashMap 和 Hashtable 的区别 -1. HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。 - -2. 因为线程安全的问题,HashMap要比HashTable效率高一点,HashTable基本被淘汰。 -3. HashMap允许有null值的存在,而在HashTable中put进的键值只要有一个null,直接抛出NullPointerException。 - -Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java5或以上的话,请使用ConcurrentHashMap吧 - -## HashSet 和 HashMap 区别 -![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536) - -## HashMap 和 ConcurrentHashMap 的区别 -[HashMap与ConcurrentHashMap的区别](https://blog.csdn.net/xuefeng0707/article/details/40834595) - -1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。) -2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。 - -## HashSet如何检查重复 -当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。(摘自我的Java启蒙书《Head fist java》第二版) - -**hashCode()与equals()的相关规定:** -1. 如果两个对象相等,则hashcode一定也是相同的 -2. 两个对象相等,对两个equals方法返回true -3. 两个对象有相同的hashcode值,它们也不一定是相等的 -4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖 -5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。 - -**==与equals的区别** - -1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同 -2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较 -3. ==指引用是否相同 equals()指的是值是否相同 - -## comparable 和 comparator的区别 -- comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序 -- comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序 - -一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort(). - -### Comparator定制排序 -```java -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * TODO Collections类方法测试之排序 - * @author 寇爽 - * @date 2017年11月20日 - * @version 1.8 - */ -public class CollectionsSort { - - public static void main(String[] args) { - - ArrayList arrayList = new ArrayList(); - arrayList.add(-1); - arrayList.add(3); - arrayList.add(3); - arrayList.add(-5); - arrayList.add(7); - arrayList.add(4); - arrayList.add(-9); - arrayList.add(-7); - System.out.println("原始数组:"); - System.out.println(arrayList); - // void reverse(List list):反转 - Collections.reverse(arrayList); - System.out.println("Collections.reverse(arrayList):"); - System.out.println(arrayList); -/* - * void rotate(List list, int distance),旋转。 - * 当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 - * list的前distance个元素整体移到后面。 - - Collections.rotate(arrayList, 4); - System.out.println("Collections.rotate(arrayList, 4):"); - System.out.println(arrayList);*/ - - // void sort(List list),按自然排序的升序排序 - Collections.sort(arrayList); - System.out.println("Collections.sort(arrayList):"); - System.out.println(arrayList); - - // void shuffle(List list),随机排序 - Collections.shuffle(arrayList); - System.out.println("Collections.shuffle(arrayList):"); - System.out.println(arrayList); - - // 定制排序的用法 - Collections.sort(arrayList, new Comparator() { - - @Override - public int compare(Integer o1, Integer o2) { - return o2.compareTo(o1); - } - }); - System.out.println("定制排序后:"); - System.out.println(arrayList); - } - -} - -``` -### 重写compareTo方法实现按年龄来排序 -```java -package map; - -import java.util.Set; -import java.util.TreeMap; - -public class TreeMap2 { - - public static void main(String[] args) { - // TODO Auto-generated method stub - TreeMap pdata = new TreeMap(); - pdata.put(new Person("张三", 30), "zhangsan"); - pdata.put(new Person("李四", 20), "lisi"); - pdata.put(new Person("王五", 10), "wangwu"); - pdata.put(new Person("小红", 5), "xiaohong"); - // 得到key的值的同时得到key所对应的值 - Set keys = pdata.keySet(); - for (Person key : keys) { - System.out.println(key.getAge() + "-" + key.getName()); - - } - } -} - -// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列 -// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他 -// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了 - -class Person implements Comparable { - private String name; - private int age; - - public Person(String name, int age) { - super(); - this.name = name; - this.age = age; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - /** - * TODO重写compareTo方法实现按年龄来排序 - */ - @Override - public int compareTo(Person o) { - // TODO Auto-generated method stub - if (this.age > o.getAge()) { - return 1; - } else if (this.age < o.getAge()) { - return -1; - } - return age; - } -} -``` - -## 如何对Object的list排序 -- 对objects数组进行排序,我们可以用Arrays.sort()方法 -- 对objects的集合进行排序,需要使用Collections.sort()方法 - - -## 如何实现数组与List的相互转换 -List转数组:toArray(arraylist.size()方法;数组转List:Arrays的asList(a)方法 -```java -List arrayList = new ArrayList(); - arrayList.add("s"); - arrayList.add("e"); - arrayList.add("n"); - /** - * ArrayList转数组 - */ - int size=arrayList.size(); - String[] a = arrayList.toArray(new String[size]); - //输出第二个元素 - System.out.println(a[1]);//结果:e - //输出整个数组 - System.out.println(Arrays.toString(a));//结果:[s, e, n] - /** - * 数组转list - */ - List list=Arrays.asList(a); - /** - * list转Arraylist - */ - List arrayList2 = new ArrayList(); - arrayList2.addAll(list); - System.out.println(list); -``` -## 如何求ArrayList集合的交集 并集 差集 去重复并集 -需要用到List接口中定义的几个方法: - -- addAll(Collection c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾 -实例代码: -- retainAll(Collection c): 仅保留此列表中包含在指定集合中的元素。 -- removeAll(Collection c) :从此列表中删除指定集合中包含的所有元素。 -```java -package list; - -import java.util.ArrayList; -import java.util.List; - -/** - *TODO 两个集合之间求交集 并集 差集 去重复并集 - * @author 寇爽 - * @date 2017年11月21日 - * @version 1.8 - */ -public class MethodDemo { - - public static void main(String[] args) { - // TODO Auto-generated method stub - List list1 = new ArrayList(); - list1.add(1); - list1.add(2); - list1.add(3); - list1.add(4); - - List list2 = new ArrayList(); - list2.add(2); - list2.add(3); - list2.add(4); - list2.add(5); - // 并集 - // list1.addAll(list2); - // 交集 - //list1.retainAll(list2); - // 差集 - // list1.removeAll(list2); - // 无重复并集 - list2.removeAll(list1); - list1.addAll(list2); - for (Integer i : list1) { - System.out.println(i); - } - } - -} - -``` - -## HashMap 的工作原理及代码实现 - -[集合框架源码学习之HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56) - -## ConcurrentHashMap 的工作原理及代码实现 - -[ConcurrentHashMap实现原理及源码分析](http://www.cnblogs.com/chengxiao/p/6842045.html) - - -## 集合框架底层数据结构总结 -### - Collection - -#### 1. List - - Arraylist:数组(查询快,增删慢 线程不安全,效率高 ) - - Vector:数组(查询快,增删慢 线程安全,效率低 ) - - LinkedList:链表(查询慢,增删快 线程不安全,效率高 ) - -#### 2. Set - - HashSet(无序,唯一):哈希表或者叫散列集(hash table) - - LinkedHashSet:链表和哈希表组成 。 由链表保证元素的排序 , 由哈希表证元素的唯一性 - - TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。) - -### - Map - - HashMap:基于哈希表的Map接口实现(哈希表对键进行散列,Map结构即映射表存放键值对) - - LinkedHashMap:HashMap 的基础上加上了链表数据结构 - - HashTable:哈希表 - - TreeMap:红黑树(自平衡的排序二叉树) - - -## 集合的选用 -主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。 - -2018/3/11更新 -## 集合的常用方法 -今天下午无意看见一道某大厂的面试题,面试题的内容就是问你某一个集合常见的方法有哪些。虽然平时也经常见到这些集合,但是猛一下让我想某一个集合的常用的方法难免会有遗漏或者与其他集合搞混,所以建议大家还是照着API文档把常见的那几个集合的常用方法看一看。 - -会持续更新。。。 - -**参考书籍:** - -《Head first java 》第二版 推荐阅读真心不错 (适合基础较差的) - - 《Java核心技术卷1》推荐阅读真心不错 (适合基础较好的) - - 《算法》第四版 (适合想对数据结构的Java实现感兴趣的) - diff --git "a/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" "b/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" deleted file mode 100644 index 10d6177b040b9415a8d5e86017c24f553e7543f8..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" +++ /dev/null @@ -1,429 +0,0 @@ - - - -# 一 面试中关于 synchronized 关键字的 5 连击 - -### 1.1 说一说自己对于 synchronized 关键字的了解 - -synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 - -另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 - - -### 1.2 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 - -**synchronized关键字最主要的三种使用方式:** - -- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 -- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! - -下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 - -面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” - - - -**双重校验锁实现对象单例(线程安全)** - -```java -public class Singleton { - - private volatile static Singleton uniqueInstance; - - private Singleton() { - } - - public static Singleton getUniqueInstance() { - //先判断对象是否已经实例过,没有实例化过才进入加锁代码 - if (uniqueInstance == null) { - //类对象加锁 - synchronized (Singleton.class) { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - } - } - return uniqueInstance; - } -} -``` -另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 - -uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: - -1. 为 uniqueInstance 分配内存空间 -2. 初始化 uniqueInstance -3. 将 uniqueInstance 指向分配的内存地址 - -但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 - -使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 - -### 1.3 讲一下 synchronized 关键字的底层原理 - -**synchronized 关键字底层原理属于 JVM 层面。** - -**① synchronized 同步语句块的情况** - -```java -public class SynchronizedDemo { - public void method() { - synchronized (this) { - System.out.println("synchronized 代码块"); - } - } -} - -``` - -通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 - -![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add616a292bcf?w=917&h=633&f=png&s=21863) - -从上面我们可以看出: - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 - -**② synchronized 修饰方法的的情况** - -```java -public class SynchronizedDemo2 { - public synchronized void method() { - System.out.println("synchronized 方法"); - } -} - -``` - -![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add6169fc206d?w=875&h=421&f=png&s=16114) - -synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 - - -### 1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗 - -JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 - -锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -关于这几种优化的详细信息可以查看:[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd) - -### 1.5 谈谈 synchronized和ReenTrantLock 的区别 - - -**① 两者都是可重入锁** - -两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 - -**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** - -synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 - -**③ ReenTrantLock 比 synchronized 增加了一些高级功能** - -相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** - -- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 -- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 -- 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实例中的所有等待线程。 - -如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 - -**④ 性能已不是选择标准** - -# 二 面试中关于线程池的 4 连击 - -### 2.1 讲一下Java内存模型 - - -在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。 - -![数据的不一致](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4423ba2?w=273&h=166&f=jpeg&s=7268) - -要解决这个问题,就需要把变量声明为 **volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。 - -说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。 - -![volatile关键字的可见性](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4b9f501?w=474&h=238&f=jpeg&s=9942) - - -### 2.2 说说 synchronized 关键字和 volatile 关键字的区别 - - synchronized关键字和volatile关键字比较 - -- **volatile关键字**是线程同步的**轻量级实现**,所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,**实际开发中使用 synchronized 关键字的场景还是更多一些**。 -- **多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞** -- **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。** -- **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。** - - -# 三 面试中关于 线程池的 2 连击 - - -### 3.1 为什么要用线程池? - -线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 - -这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处: - -- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - - -### 3.2 实现Runnable接口和Callable接口的区别 - -如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。 - - **备注:** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。(`Executors.callable(Runnable task)`或`Executors.callable(Runnable task,Object resule)`)。 - -### 3.3 执行execute()方法和submit()方法的区别是什么呢? - - 1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** - - 2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功**,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 - - -### 3.4 如何创建线程池 - -《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险 - -> Executors 返回线程池对象的弊端如下: -> -> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 -> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。 - -**方式一:通过构造方法实现** -![通过构造方法实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baac923e9?w=925&h=158&f=jpeg&s=29190) -**方式二:通过Executor 框架的工具类Executors来实现** -我们可以创建三种类型的ThreadPoolExecutor: - -- **FixedThreadPool** : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 -- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 -- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 - -对应Executors工具类中的方法如图所示: -![通过Executor 框架的工具类Executors来实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baa9ca5e9?w=645&h=222&f=jpeg&s=31710) - - -# 四 面试中关于 Atomic 原子类的 4 连击 - -### 4.1 介绍一下Atomic 原子类 - -Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 - -所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 - - -并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 - -![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267) - -### 4.2 JUC 包中的原子类是哪4类? - -**基本类型** - -使用原子的方式更新基本类型 - -- AtomicInteger:整形原子类 -- AtomicLong:长整型原子类 -- AtomicBoolean :布尔型原子类 - -**数组类型** - -使用原子的方式更新数组里的某个元素 - - -- AtomicIntegerArray:整形数组原子类 -- AtomicLongArray:长整形数组原子类 -- AtomicReferenceArray :引用类型数组原子类 - -**引用类型** - -- AtomicReference:引用类型原子类 -- AtomicStampedRerence:原子更新引用类型里的字段原子类 -- AtomicMarkableReference :原子更新带有标记位的引用类型 - -**对象的属性修改类型** - -- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 -- AtomicLongFieldUpdater:原子更新长整形字段的更新器 -- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 - - -### 4.3 讲讲 AtomicInteger 的使用 - - **AtomicInteger 类常用方法** - -```java -public final int get() //获取当前的值 -public final int getAndSet(int newValue)//获取当前的值,并设置新的值 -public final int getAndIncrement()//获取当前的值,并自增 -public final int getAndDecrement() //获取当前的值,并自减 -public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 -boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) -public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 -``` - - **AtomicInteger 类的使用示例** - -使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。 -```java -class AtomicIntegerTest { - private AtomicInteger count = new AtomicInteger(); - //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 - public void increment() { - count.incrementAndGet(); - } - - public int getCount() { - return count.get(); - } -} - -``` - -### 4.4 能不能给我简单介绍一下 AtomicInteger 类的原理 - -AtomicInteger 线程安全原理简单分析 - -AtomicInteger 类的部分源码: - -```java - // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long valueOffset; - - static { - try { - valueOffset = unsafe.objectFieldOffset - (AtomicInteger.class.getDeclaredField("value")); - } catch (Exception ex) { throw new Error(ex); } - } - - private volatile int value; -``` - -AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 - -CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 - -关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) - -# 五 AQS - -### 5.1 AQS 介绍 - -AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 - -![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bb575d4a690?w=317&h=338&f=png&s=14122) - -AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 - -### 5.2 AQS 原理分析 - -AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。 - -> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。 - -下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。 - -#### 5.2.1 AQS 原理概览 - - - -**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** - -> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 - -看个AQS(AbstractQueuedSynchronizer)原理图: - - -![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bbe4a9c5ae7?w=852&h=401&f=png&s=21797) - -AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 - -```java -private volatile int state;//共享变量,使用volatile修饰保证线程可见性 -``` - -状态信息通过procted类型的getState,setState,compareAndSetState进行操作 - -```java - -//返回同步状态的当前值 -protected final int getState() { - return state; -} - // 设置同步状态的值 -protected final void setState(int newState) { - state = newState; -} -//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) -protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); -} -``` - -#### 5.2.2 AQS 对资源的共享方式 - -**AQS定义两种资源共享方式** - -- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: - - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 - - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 -- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 - -ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 - -不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。 - -#### 5.2.3 AQS底层使用了模板方法模式 - -同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): - -1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) -2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。 - -这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。 - -**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:** - -```java -isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 -tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 -tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 -tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 -tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 - -``` - -默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 - -以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。 - -再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。 - -一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 - -推荐两篇 AQS 原理和相关源码分析的文章: - -- http://www.cnblogs.com/waterystone/p/4920797.html -- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html - -### 5.3 AQS 组件总结 - -- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。 -- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。 -- **CyclicBarrier(循环栅栏):** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - -关于AQS这部分的更多内容可以查看我的这篇文章:[并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) - -# Reference - -- 《深入理解 Java 虚拟机》 -- 《实战 Java 高并发程序设计》 -- 《Java并发编程的艺术》 -- http://www.cnblogs.com/waterystone/p/4920797.html -- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html diff --git "a/Java\347\233\270\345\205\263/synchronized.md" "b/Java\347\233\270\345\205\263/synchronized.md" deleted file mode 100644 index b0b0700ad8edcd429b3e8c631566670beeb85331..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/synchronized.md" +++ /dev/null @@ -1,171 +0,0 @@ -以下内容摘自我的 Gitchat :[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb),欢迎订阅! - -Github 地址:[https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md](https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md) - -![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) - -### synchronized关键字最主要的三种使用方式的总结 - -- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 -- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! - -下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 - -面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单利模式的原理呗!” - - - -**双重校验锁实现对象单例(线程安全)** - -```java -public class Singleton { - - private volatile static Singleton uniqueInstance; - - private Singleton() { - } - - public static Singleton getUniqueInstance() { - //先判断对象是否已经实例过,没有实例化过才进入加锁代码 - if (uniqueInstance == null) { - //类对象加锁 - synchronized (Singleton.class) { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - } - } - return uniqueInstance; - } -} -``` -另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 - -uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: - -1. 为 uniqueInstance 分配内存空间 -2. 初始化 uniqueInstance -3. 将 uniqueInstance 指向分配的内存地址 - -但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 - -使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 - - -###synchronized 关键字底层原理总结 - - - -**synchronized 关键字底层原理属于 JVM 层面。** - -**① synchronized 同步语句块的情况** - -```java -public class SynchronizedDemo { - public void method() { - synchronized (this) { - System.out.println("synchronized 代码块"); - } - } -} - -``` - -通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 - -![synchronized 关键字原理](https://images.gitbook.cn/abc37c80-d21d-11e8-aab3-09d30029e0d5) - -从上面我们可以看出: - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 - -**② synchronized 修饰方法的的情况** - -```java -public class SynchronizedDemo2 { - public synchronized void method() { - System.out.println("synchronized 方法"); - } -} - -``` - -![synchronized 关键字原理](https://images.gitbook.cn/7d407bf0-d21e-11e8-b2d6-1188c7e0dd7e) - -synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 - - -在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 - - -### JDK1.6 之后的底层优化 - -JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 - -锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -**①偏向锁** - -**引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 - -偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步!关于偏向锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。 - -但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。 - -**② 轻量级锁** - -倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。 - -**轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!** - -**③ 自旋锁和自适应自旋** - -轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 - -互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。 - -**一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。 - -百度百科对自旋锁的解释: - -> 何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。 - -自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的,需要通过`--XX:+UseSpinning`参数来开启。JDK1.6及1.6之后,就改为默认开启的了。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。如果锁被占用的时间短,那么效果当然就很好了!反之,相反!自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁,就应该挂起线程。**自旋次数的默认值是10次,用户可以修改`--XX:PreBlockSpin`来更改**。 - -另外,**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 - -**④ 锁消除** - -锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 - -**⑤ 锁粗化** - -原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 - -大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 - -### Synchronized 和 ReenTrantLock 的对比 - - -**① 两者都是可重入锁** - -两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 - -**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** - -synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 - -**③ ReenTrantLock 比 synchronized 增加了一些高级功能** - -相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** - -- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 -- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 -- 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实例中的所有等待线程。 - -如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 - -**④ 性能已不是选择标准** - -在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操作**。 diff --git "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" deleted file mode 100644 index 61d5e7221740c5b0b22b70331cf88714c2df359e..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" +++ /dev/null @@ -1,342 +0,0 @@ - - -## 写在前面(常见面试题) - -### 基本问题: - -- **介绍下 Java 内存区域(运行时数据区)** -- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)** -- **对象的访问定位的两种方式(句柄和直接指针两种方式)** - -### 拓展问题: - -- **String类和常量池** -- **8种基本类型的包装类和常量池** - - -## 1 概述 - -对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。 - - -## 2 运行时数据区域 -Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。 -![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png) - -这些组成部分一些是线程私有的,其他的则是线程共享的。 - -**线程私有的:** - -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -**线程共享的:** - -- 堆 -- 方法区 -- 直接内存 - - -### 2.1 程序计数器 -程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。**字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。** - -另外,**为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。** - -**从上面的介绍中我们知道程序计数器主要有两个作用:** - -1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 - -**注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。** - -### 2.2 Java 虚拟机栈 - -**与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型。** - -**Java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** (实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。) - -**局部变量表主要存放了编译器可知的各种数据类型**(boolean、byte、char、short、int、float、long、double)、**对象引用**(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。 - -**Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。** - -- **StackOverFlowError:** 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。 -- **OutOfMemoryError:** 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。 - -Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。 - -### 2.3 本地方法栈 - -和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 - -本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。 - -方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。 - -### 2.4 堆 -Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。** - -Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** - -![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946) - -**在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。** - -推荐阅读: - -- 《Java8内存模型—永久代(PermGen)和元空间(Metaspace)》:[http://www.cnblogs.com/paddix/p/5309550.html](http://www.cnblogs.com/paddix/p/5309550.html) - -### 2.5 方法区 - -**方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。** - -HotSpot 虚拟机中方法区也常被称为 **“永久代”**,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 - - - -**相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。** - -### 2.6 运行时常量池 - -运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用) - -既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。 - -**JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。** - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg) -——图片来源:https://blog.csdn.net/wangbiao007/article/details/78545189 - - - -推荐阅读: - -- 《Java 中几种常量池的区分》: [https://blog.csdn.net/qq_26222859/article/details/73135660](https://blog.csdn.net/qq_26222859/article/details/73135660) - - -### 2.7 直接内存 - -直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。 - -JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道(Channel)** 与**缓存区(Buffer)** 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。 - -本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 - - -## 3 HotSpot 虚拟机对象探秘 -通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。 - -### 3.1 对象的创建 -下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。 -![Java对象的创建过程](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a4135869?w=950&h=279&f=png&s=28529) - -**①类加载检查:** 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 - -**②分配内存:** 在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。 - - -**内存分配的两种方式:(补充内容,需要掌握)** - -选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的 - -![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a40a2c3d?w=1426&h=333&f=png&s=26346) - -**内存分配并发问题(补充内容,需要掌握)** - -在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全: - -- **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。** -- **TLAB:** 为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配 - - - -**③初始化零值:** 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 - -**④设置对象头:** 初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 - - -**⑤执行 init 方法:** 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,`` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 - - -### 3.2 对象的内存布局 - -在 Hotspot 虚拟机中,对象在内存中的布局可以分为3块区域:**对象头**、**实例数据**和**对齐填充**。 - -**Hotspot虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希码、GC分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 - -**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。 - -**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 - -### 3.3 对象的访问定位 -建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有**①使用句柄**和**②直接指针**两种: - -1. **句柄:** 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; -![使用句柄](https://user-gold-cdn.xitu.io/2018/4/27/16306b9573968946?w=786&h=362&f=png&s=109201) - -2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。 - -![使用直接指针](https://user-gold-cdn.xitu.io/2018/4/27/16306ba3a41b6b65?w=766&h=353&f=png&s=99172) - -**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。** - - - - -## 四 重点补充内容 - -### String 类和常量池 - -**1 String 对象的两种创建方式:** - -```java - String str1 = "abcd"; - String str2 = new String("abcd"); - System.out.println(str1==str2);//false -``` - -这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。 -![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a59c0873?w=698&h=355&f=png&s=10449) -记住:只要使用new方法,便需要创建新的对象。 - - - -**2 String 类型的常量池比较特殊。它的主要使用方法有两种:** - -- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。 -- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。 - -```java - String s1 = new String("计算机"); - String s2 = s1.intern(); - String s3 = "计算机"; - System.out.println(s2);//计算机 - System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象, - System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象 -``` -**3 String 字符串拼接** -```java - String str1 = "str"; - String str2 = "ing"; - - String str3 = "str" + "ing";//常量池中的对象 - String str4 = str1 + str2; //在堆上创建的新的对象 - String str5 = "string";//常量池中的对象 - System.out.println(str3 == str4);//false - System.out.println(str3 == str5);//true - System.out.println(str4 == str5);//false -``` -![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a4d13f92?w=593&h=603&f=png&s=22265) - -尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。 -### String s1 = new String("abc");这句话创建了几个对象? - -**创建了两个对象。** - -**验证:** - -```java - String s1 = new String("abc");// 堆内存的地址值 - String s2 = "abc"; - System.out.println(s1 == s2);// 输出false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。 - System.out.println(s1.equals(s2));// 输出true -``` - -**结果:** - -``` -false -true -``` - -**解释:** - -先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定),然后 Java 栈的 str1 指向Java堆上的"abc"。 - -### 8种基本类型的包装类和常量池 - -- **Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。** -- **两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。** - -```java - Integer i1 = 33; - Integer i2 = 33; - System.out.println(i1 == i2);// 输出true - Integer i11 = 333; - Integer i22 = 333; - System.out.println(i11 == i22);// 输出false - Double i3 = 1.2; - Double i4 = 1.2; - System.out.println(i3 == i4);// 输出false -``` - -**Integer 缓存源代码:** - -```java -/** -*此方法将始终缓存-128到127(包括端点)范围内的值,并可以缓存此范围之外的其他值。 -*/ - public static Integer valueOf(int i) { - if (i >= IntegerCache.low && i <= IntegerCache.high) - return IntegerCache.cache[i + (-IntegerCache.low)]; - return new Integer(i); - } - -``` - -**应用场景:** -1. Integer i1=40;Java 在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。 -2. Integer i1 = new Integer(40);这种情况下会创建新的对象。 - -```java - Integer i1 = 40; - Integer i2 = new Integer(40); - System.out.println(i1==i2);//输出false -``` -**Integer比较更丰富的一个例子:** - -```java - Integer i1 = 40; - Integer i2 = 40; - Integer i3 = 0; - Integer i4 = new Integer(40); - Integer i5 = new Integer(40); - Integer i6 = new Integer(0); - - System.out.println("i1=i2 " + (i1 == i2)); - System.out.println("i1=i2+i3 " + (i1 == i2 + i3)); - System.out.println("i1=i4 " + (i1 == i4)); - System.out.println("i4=i5 " + (i4 == i5)); - System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); - System.out.println("40=i5+i6 " + (40 == i5 + i6)); -``` - -结果: - -``` -i1=i2 true -i1=i2+i3 true -i1=i4 false -i4=i5 false -i4=i5+i6 true -40=i5+i6 true -``` - -解释: - -语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。 - - -**参考:** - -- 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》 -- 《实战java虚拟机》 -- https://www.cnblogs.com/CZDblog/p/5589379.html -- https://www.cnblogs.com/java-zhao/p/5180492.html -- https://blog.csdn.net/qq_26222859/article/details/73135660 -- https://blog.csdn.net/cugwuhan2014/article/details/78038254 - - - - - diff --git "a/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" "b/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" deleted file mode 100644 index 6ed7bdafb211a19fba73f256597d350ac60099b0..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" +++ /dev/null @@ -1,69 +0,0 @@ -> ## 多线程系列文章 -下列文章,我都更新在了我的博客专栏:[Java并发编程指南](https://blog.csdn.net/column/details/20860.html)。 - -1. [Java多线程学习(一)Java多线程入门](http://blog.csdn.net/qq_34337272/article/details/79640870) -2. [Java多线程学习(二)synchronized关键字(1)](http://blog.csdn.net/qq_34337272/article/details/79655194) -3. [Java多线程学习(二)synchronized关键字(2)](http://blog.csdn.net/qq_34337272/article/details/79670775) -4. [Java多线程学习(三)volatile关键字](http://blog.csdn.net/qq_34337272/article/details/79680771) -5. [Java多线程学习(四)等待/通知(wait/notify)机制](http://blog.csdn.net/qq_34337272/article/details/79690279) - -6. [Java多线程学习(五)线程间通信知识点补充](http://blog.csdn.net/qq_34337272/article/details/79694226) -7. [Java多线程学习(六)Lock锁的使用](http://blog.csdn.net/qq_34337272/article/details/79714196) -8. [Java多线程学习(七)并发编程中一些问题](https://blog.csdn.net/qq_34337272/article/details/79844051) -9. [Java多线程学习(八)线程池与Executor 框架](https://blog.csdn.net/qq_34337272/article/details/79959271) - - -> ## 多线程系列文章重要知识点与思维导图 - -### Java多线程学习(一)Java多线程入门 - -![](https://user-gold-cdn.xitu.io/2018/8/4/16504e0cb6bac32e?w=758&h=772&f=jpeg&s=247210) - -### Java多线程学习(二)synchronized关键字(1) -![](https://user-gold-cdn.xitu.io/2018/8/4/16504e245ceb3ea9?w=1028&h=490&f=jpeg&s=203811) - -注意:**可重入锁的概念**。 - - 另外要注意:**synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。** 如果多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定,因为多个对象会产生多个锁。 - -### Java多线程学习(二)synchronized关键字(2) - -![思维导图](https://user-gold-cdn.xitu.io/2018/8/4/16504e3d98213324?w=1448&h=439&f=jpeg&s=245012) - - **注意:** - - - 其他线程执行对象中**synchronized同步方法**(上一节我们介绍过,需要回顾的可以看上一节的文章)和**synchronized(this)代码块**时呈现同步效果; - - **如果两个线程使用了同一个“对象监视器”(synchronized(object)),运行结果同步,否则不同步**. - - **synchronized关键字加到static静态方法**和**synchronized(class)代码块**上都是是给**Class类**上锁,而**synchronized关键字加到非static静态方法**上是给**对象**上锁。 - - 数据类型String的常量池属性:**在Jvm中具有String常量池缓存的功能** - -### Java多线程学习(三)volatile关键字 - -![volatile关键字](https://user-gold-cdn.xitu.io/2018/8/4/16504e4ab69d8d58) - **注意:** - - **synchronized关键字**和**volatile关键字**比较 - -### Java多线程学习(四)等待/通知(wait/notify)机制 - -![本节思维导图](https://user-gold-cdn.xitu.io/2018/3/25/1625d2a9188ec021?w=1254&h=452&f=jpeg&s=229471) - -### Java多线程学习(五)线程间通信知识点补充 - -![本节思维导图](https://user-gold-cdn.xitu.io/2018/8/4/16504e618d6886c5?w=1146&h=427&f=jpeg&s=220573) - **注意:** ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。 - -### Java多线程学习(六)Lock锁的使用 - - ![本节思维导图](https://user-gold-cdn.xitu.io/2018/3/27/1626755a8e9a8774?w=1197&h=571&f=jpeg&s=258439) - -### Java多线程学习(七)并发编程中一些问题 - -![思维导图](https://user-gold-cdn.xitu.io/2018/4/7/162a01b71ebc4842?w=1067&h=517&f=png&s=36857) - -### Java多线程学习(八)线程池与Executor 框架 - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-14/86510659.jpg) - diff --git "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" "b/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" deleted file mode 100644 index 819d37faca2c878148d011d4dcb3055eef03ccc0..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" +++ /dev/null @@ -1,374 +0,0 @@ - -上文回顾:[《可能是把Java内存区域讲的最清楚的一篇文章》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd) -## 写在前面 - -### 本节常见面试题: - -问题答案在文中都有提到 - -- 如何判断对象是否死亡(两种方法)。 -- 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 -- 如何判断一个常量是废弃常量 -- 如何判断一个类是无用的类 -- 垃圾收集有哪些算法,各自的特点? -- HotSpot为什么要分为新生代和老年代? -- 常见的垃圾回收器有那些? -- 介绍一下CMS,G1收集器。 -- Minor Gc和Full GC 有什么不同呢? - -### 本文导火索 - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg) - -当需要排查各种 内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。 - - - -## 1 揭开JVM内存分配与回收的神秘面纱 - -Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。 - -**JDK1.8之前的堆内存示意图:** - -![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946) - -从上图可以看出堆内存分为新生代、老年代和永久代。新生代又被进一步分为:Eden 区+Survivor1 区+Survivor2 区。值得注意的是,在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。 - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/89294547.jpg) - -### 1.1 对象优先在eden区分配 - -目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 - -大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC.下面我们来进行实际测试以下。 - -在测试之前我们先来看看 **Minor GC和Full GC 有什么不同呢?** - -- **新生代GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。 -- **老年代GC(Major GC/Full GC)**:指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。 - -**测试:** - -```java -public class GCTest { - - public static void main(String[] args) { - byte[] allocation1, allocation2; - allocation1 = new byte[30900*1024]; - //allocation2 = new byte[900*1024]; - } -} -``` -通过以下方式运行: -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/25178350.jpg) - -添加的参数:`-XX:+PrintGCDetails` -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg) - -运行结果(红色字体描述有误,应该是对应于JDK1.7的永久代): - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28954286.jpg) - -从上图我们可以看出eden区内存几乎已经被分配完全(即使程序什么也不做,新生代也会使用2000多k内存)。假如我们再为allocation2分配内存会出现什么情况呢? - -```java -allocation2 = new byte[900*1024]; -``` -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg) - -**简单解释一下为什么会出现这种情况:** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了,我们刚刚讲了当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survivor空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。执行Minor GC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。可以执行如下代码验证: - -```java -public class GCTest { - - public static void main(String[] args) { - byte[] allocation1, allocation2,allocation3,allocation4,allocation5; - allocation1 = new byte[32000*1024]; - allocation2 = new byte[1000*1024]; - allocation3 = new byte[1000*1024]; - allocation4 = new byte[1000*1024]; - allocation5 = new byte[1000*1024]; - } -} - -``` - - -### 1.2 大对象直接进入老年代 -大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。 - -**为什么要这样呢?** - -为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。 - -### 1.3 长期存活的对象将进入老年代 -既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 - -如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 - -### 1.4 动态对象年龄判定 - -为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。 - - -## 2 对象已经死亡? - -堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。 - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/11034259.jpg) - -### 2.1 引用计数法 - -给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。 - -**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** 所谓对象之间的相互引用问题,如下面代码所示:除了对象objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。 - -```java -public class ReferenceCountingGc { - Object instance = null; - public static void main(String[] args) { - ReferenceCountingGc objA = new ReferenceCountingGc(); - ReferenceCountingGc objB = new ReferenceCountingGc(); - objA.instance = objB; - objB.instance = objA; - objA = null; - objB = null; - - } -} -``` - - - -### 2.2 可达性分析算法 - -这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。 - -![可达性分析算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/72762049.jpg) - - -### 2.3 再谈引用 - -无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。 - -JDK1.2之前,Java中引用的定义很传统:如果reference类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。 - -JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱) - - - -**1.强引用** - -以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于**必不可少的生活用品**,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 - -**2.软引用(SoftReference)** - -如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 - -软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。 - -**3.弱引用(WeakReference)** - -如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 - -弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 - -**4.虚引用(PhantomReference)** - -"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 - -**虚引用主要用来跟踪对象被垃圾回收的活动**。 - -**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 - -特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生**。 - -### 2.4 不可达的对象并非“非死不可” - -即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。 - -被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。 - -### 2.5 如何判断一个常量是废弃常量 - -运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢? - -假如在常量池中存在字符串 "abc",如果当前没有任何String对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。 - -注意:我们在 [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd) 也讲了JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。 - -### 2.6 如何判断一个类是无用的类 - -方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢? - -判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是 **“无用的类”** : - -- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 -- 加载该类的 ClassLoader 已经被回收。 -- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 - -虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。 - - -## 3 垃圾收集算法 - -![垃圾收集算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/1142723.jpg) - -### 3.1 标记-清除算法 - -算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,效率也很高,但是会带来两个明显的问题: - -1. **效率问题** -2. **空间问题(标记清除后会产生大量不连续的碎片)** - -![标记-清除算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/63707281.jpg) - -### 3.2 复制算法 - -为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 - -![复制算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg) - -### 3.3 标记-整理算法 -根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 - -![标记-整理算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg) - -### 3.4 分代收集算法 - -当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 - -**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** - -**延伸面试问题:** HotSpot为什么要分为新生代和老年代? - -根据上面的对分代收集算法的介绍回答。 - -## 4 垃圾收集器 - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/41460955.jpg) - -**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。** - -虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为知道现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。 - - -### 4.1 Serial收集器 -Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。 - - **新生代采用复制算法,老年代采用标记-整理算法。** -![ Serial收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/46873026.jpg) - -虚拟机的设计者们当然知道Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 - -但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它**简单而高效(与其他收集器的单线程相比)**。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial收集器对于运行在Client模式下的虚拟机来说是个不错的选择。 - - - -### 4.2 ParNew收集器 -**ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。** - - **新生代采用复制算法,老年代采用标记-整理算法。** -![ParNew收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg) - -它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收集器,后面会介绍到)配合工作。 - -**并行和并发概念补充:** - -- **并行(Parallel)** :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 - -- **并发(Concurrent)**:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个CPU上。 - - -### 4.3 Parallel Scavenge收集器 - -Parallel Scavenge 收集器类似于ParNew 收集器。 **那么它有什么特别之处呢?** - -``` --XX:+UseParallelGC - - 使用Parallel收集器+ 老年代串行 - --XX:+UseParallelOldGC - - 使用Parallel收集器+ 老年代并行 - -``` - -**Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。** Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。 - - **新生代采用复制算法,老年代采用标记-整理算法。** -![ParNew收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg) - - -### 4.4.Serial Old收集器 -**Serial收集器的老年代版本**,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。 - -### 4.5 Parallel Old收集器 - **Parallel Scavenge收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。 - -### 4.6 CMS收集器 - -**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。** - -**CMS(Concurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。** - -从名字中的**Mark Sweep**这两个词可以看出,CMS收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤: - -- **初始标记:** 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ; -- **并发标记:** 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 -- **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 -- **并发清除:** 开启用户线程,同时GC线程开始对为标记的区域做清扫。 - -![CMS垃圾收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/82825079.jpg) - -从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点: - -- **对CPU资源敏感;** -- **无法处理浮动垃圾;** -- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。** - -### 4.7 G1收集器 - - -**G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.** - -被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备一下特点: - -- **并行与并发**:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。 -- **分代收集**:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。 -- **空间整合**:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。 -- **可预测的停顿**:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。 - - -G1收集器的运作大致分为以下几个步骤: - -- **初始标记** -- **并发标记** -- **最终标记** -- **筛选回收** - - -**G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)**。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。 - - - - - -参考: - -- 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》 -- https://my.oschina.net/hosee/blog/644618 - - - - - - - - - - - - - diff --git "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" deleted file mode 100644 index e3e9586052910cebcd161aa4922ab6a643946d09..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ /dev/null @@ -1,84 +0,0 @@ -# Java 设计模式 - -下面是自己学习设计模式的时候做的总结,有些是自己的原创文章,有些是网上写的比较好的文章,保存下来细细消化吧! - -**系列文章推荐:** - -## 创建型模式 - -### 创建型模式概述 - -- 创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。 -- 创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。 - -![创建型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640641afcb7559b?w=491&h=241&f=png&s=51443) - -### 常见创建型模式详解 - -- **单例模式:** [深入理解单例模式——只有一个实例](https://blog.csdn.net/qq_34337272/article/details/80455972) -- **工厂模式:** [深入理解工厂模式——由对象工厂生成对象](https://blog.csdn.net/qq_34337272/article/details/80472071) -- **建造者模式:** [深入理解建造者模式 ——组装复杂的实例](http://blog.csdn.net/qq_34337272/article/details/80540059) -- **原型模式:** [深入理解原型模式 ——通过复制生成实例](https://blog.csdn.net/qq_34337272/article/details/80706444) - -## 结构型模式 - -### 结构型模式概述 - -- **结构型模式(Structural Pattern):** 描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构 -![结构型模式(Structural Pattern)](https://user-gold-cdn.xitu.io/2018/6/16/164064d6b3c205e3?w=719&h=233&f=png&s=270293) -- **结构型模式可以分为类结构型模式和对象结构型模式:** - - 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。 - - 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。 - -![结构型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640655459d766d2?w=378&h=266&f=png&s=59652) - -### 常见结构型模式详解 - -- **适配器模式:** - - [深入理解适配器模式——加个“适配器”以便于复用](https://segmentfault.com/a/1190000011856448) - - [适配器模式原理及实例介绍-IBM](https://www.ibm.com/developerworks/cn/java/j-lo-adapter-pattern/index.html) -- **桥接模式:** [设计模式笔记16:桥接模式(Bridge Pattern)](https://blog.csdn.net/yangzl2008/article/details/7670996) -- **组合模式:** [大话设计模式—组合模式](https://blog.csdn.net/lmb55/article/details/51039781) -- **装饰模式:** [java模式—装饰者模式](https://www.cnblogs.com/chenxing818/p/4705919.html)、[Java设计模式-装饰者模式](https://blog.csdn.net/cauchyweierstrass/article/details/48240147) -- **外观模式:** [java设计模式之外观模式(门面模式)](https://www.cnblogs.com/lthIU/p/5860607.html) -- **享元模式:** [享元模式](http://www.jasongj.com/design_pattern/flyweight/) -- **代理模式:** - - [代理模式原理及实例讲解 (IBM出品,很不错)](https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html) - - [轻松学,Java 中的代理模式及动态代理](https://blog.csdn.net/briblue/article/details/73928350) - - [Java代理模式及其应用](https://blog.csdn.net/justloveyou_/article/details/74203025) - - -## 行为型模式 - -### 行为型模式概述 - -- 行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。 -- 行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。 -- 通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。 - -**行为型模式分为类行为型模式和对象行为型模式两种:** - -- **类行为型模式:** 类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。 -- **对象行为型模式:** 对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。 - -![行为型模式](https://user-gold-cdn.xitu.io/2018/6/28/164467dd92c6172c?w=453&h=269&f=png&s=63270) - -- **职责链模式:** -- [Java设计模式之责任链模式、职责链模式](https://blog.csdn.net/jason0539/article/details/45091639) -- [责任链模式实现的三种方式](https://www.cnblogs.com/lizo/p/7503862.html) -- **命令模式:** 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。 -- **解释器模式:** -- **迭代器模式:** -- **中介者模式:** -- **备忘录模式:** -- **观察者模式:** -- **状态模式:** -- **策略模式:** - -策略模式作为设计原则中开闭原则最典型的体现,也是经常使用的。下面这篇博客介绍了策略模式一般的组成部分和概念,并用了一个小demo去说明了策略模式的应用。 - -[java设计模式之策略模式](https://blog.csdn.net/zlj1217/article/details/81230077) - -- **模板方法模式:** -- **访问者模式:** - diff --git "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" deleted file mode 100644 index 18d276c4e22a416c86515aec7d522f206f95c9ee..0000000000000000000000000000000000000000 --- "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" +++ /dev/null @@ -1,288 +0,0 @@ - - - - -- [Arraylist 与 LinkedList 异同](#arraylist-与-linkedlist-异同) - - [补充:数据结构基础之双向链表](#补充:数据结构基础之双向链表) -- [ArrayList 与 Vector 区别](#arraylist-与-vector-区别) -- [HashMap的底层实现](#hashmap的底层实现) - - [JDK1.8之前](#jdk18之前) - - [JDK1.8之后](#jdk18之后) -- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别) -- [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方) -- [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题) -- [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别) -- [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别) -- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现) - - [JDK1.7(上面有示意图)](#jdk17(上面有示意图)) - - [JDK1.8 (上面有示意图)](#jdk18-(上面有示意图)) -- [集合框架底层数据结构总结](#集合框架底层数据结构总结) - - [Collection](#collection) - - [1. List](#1-list) - - [2. Set](#2-set) - - [Map](#map) - - [推荐阅读:](#推荐阅读:) - - - -## Arraylist 与 LinkedList 异同 - -- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; -- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html) -- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。** -- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 -- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 -- **6.补充内容:RandomAccess接口** - -```java -public interface RandomAccess { -} -``` - -查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。 - -在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法 - -```java - public static - int binarySearch(List> list, T key) { - if (list instanceof RandomAccess || list.size()>>:无符号右移,忽略符号位,空位都以0补齐 - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); - } - ``` -对比一下 JDK1.7的 HashMap 的 hash 方法源码. - -```java -static int hash(int h) { - // This function ensures that hashCodes that differ only by - // constant multiples at each bit position have a bounded - // number of collisions (approximately 8 at default load factor). - - h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); -} -``` - -相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。 - -所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 - - - -![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991) - - -### JDK1.8之后 -相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 - -![JDK1.8之后的HashMap底层数据结构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/67233764.jpg) - ->TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。 - -**推荐阅读:** - -- 《Java 8系列之重新认识HashMap》 :[https://zhuanlan.zhihu.com/p/21673805](https://zhuanlan.zhihu.com/p/21673805) - -## HashMap 和 Hashtable 的区别 - -1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); -2. **效率:** 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; -3. **对Null key 和Null value的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。 -4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 -5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 - -**HasMap 中带有初始容量的构造函数:** - -```java - public HashMap(int initialCapacity, float loadFactor) { - if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal initial capacity: " + - initialCapacity); - if (initialCapacity > MAXIMUM_CAPACITY) - initialCapacity = MAXIMUM_CAPACITY; - if (loadFactor <= 0 || Float.isNaN(loadFactor)) - throw new IllegalArgumentException("Illegal load factor: " + - loadFactor); - this.loadFactor = loadFactor; - this.threshold = tableSizeFor(initialCapacity); - } - public HashMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } -``` - -下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。 - -```java - /** - * Returns a power of two size for the given target capacity. - */ - static final int tableSizeFor(int cap) { - int n = cap - 1; - n |= n >>> 1; - n |= n >>> 2; - n |= n >>> 4; - n |= n >>> 8; - n |= n >>> 16; - return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; - } -``` - -## HashMap 的长度为什么是2的幂次方 - -为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 - -**这个算法应该如何设计呢?** - -我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。** - -## HashMap 多线程操作导致死循环问题 - -在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。复制链表过程如下: -以下模拟2个线程同时扩容。假设,当前 HashMap 的空间为2(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 经过 hash 运算,得到散列地址为 1,这时候由于超过了临界值,空间不够,需要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争,模拟过程如下: - - 线程一:读取到当前的 HashMap 情况,在准备扩容时,线程二介入 - -![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/41aed567e3419e1314bfbf689e3255a2/192) - -线程二:读取 HashMap,进行扩容 - -![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/f44624419c0a49686fb12aa37527ee65/191) - -线程一:继续执行 - -![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/79424b2bf4a89902a9e85c64600268e4/193) - -这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头(A 的前边:B.next=A),本来 B.next=null,到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将 B.next=A,所以,这里继续复制A,让 A.next=B,由此,环形链表出现:B.next=A; A.next=B - -**注意:jdk1.8已经解决了死循环的问题。**详细信息请阅读[jdk1.8 hashmap多线程put不会造成死循环](https://blog.csdn.net/qq_27007251/article/details/71403647) - - -## HashSet 和 HashMap 区别 - -如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。) - -![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536) - -## ConcurrentHashMap 和 Hashtable 的区别 - -ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 - -- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; -- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 - -**两者的对比图:** - -图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html - -HashTable: -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg) - -JDK1.7的ConcurrentHashMap: -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg) -JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 -Node: 链表节点): -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg) - -## ConcurrentHashMap线程安全的具体实现方式/底层具体实现 - -### JDK1.7(上面有示意图) - -首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 - -**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。 - -Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 - -```java -static class Segment extends ReentrantLock implements Serializable { -} -``` - -一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 - -### JDK1.8 (上面有示意图) - -ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。 - -synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 - - - -## 集合框架底层数据结构总结 -### Collection - -#### 1. List - - **Arraylist:** Object数组 - - **Vector:** Object数组 - - **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) - 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html) - -#### 2. Set - - **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素 - - **LinkedHashSet:** LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。 - - **TreeSet(有序,唯一):** 红黑树(自平衡的排序二叉树。) - -### Map - - **HashMap:** JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间 - - **LinkedHashMap:** LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931) - - **HashTable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 - - **TreeMap:** 红黑树(自平衡的排序二叉树) - - - - -### 推荐阅读: - -- [jdk1.8中ConcurrentHashMap的实现原理](https://blog.csdn.net/fjse51/article/details/55260493) -- [HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/) -- [HASHMAP、HASHTABLE、CONCURRENTHASHMAP的原理与区别](http://www.yuanrengu.com/index.php/2017-01-17.html) -- [ConcurrentHashMap实现原理及源码分析](https://www.cnblogs.com/chengxiao/p/6842045.html) -- [java-并发-ConcurrentHashMap高并发机制-jdk1.8](https://blog.csdn.net/jianghuxiaojin/article/details/52006118#commentBox) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 8aef7455edfd511a06eb5bc2816ea8b79af29273..25d5258089c9843b2739eb34a1f73bd5091c5292 100644 --- a/README.md +++ b/README.md @@ -1,268 +1,393 @@ -# Java 学习/面试指南 - -
- -
-
- -## 阅读之前必看 - -1. **加群:** 微信交流群添加 [我的微信](#联系我) 后回复关键字“加群”即可入群。 -2. **Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。 -3. **《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! -4. **关于贡献者:** 对本仓库提过有价值的 issue 或 pr 的小伙伴将出现在 [Contributor](#Contributor) 这里。 -5. **欢迎投稿:** 由于我个人能力有限,很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。**对于不错的原创文章我根据你的选择给予现金(50-300)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。 -4. **【限时福利】** 极客时间[《Java 并发编程面试必备》](#Java并发编程专栏)专栏限时特惠,购买之后的小伙伴加 [我的微信](#联系我) 报上自己的极客时间大名可以找我会把24元返现退给大家,减轻各位学习成本。 - -## 目录 - -* [:coffee: Java](#coffee-java) - * [Java/J2EE 基础](#javaj2ee-基础) - * [Java 集合框架](#java-集合框架) - * [Java 多线程](#java-多线程) - * [Java BIO,NIO,AIO](#java-bionioaio) - * [Java 虚拟机 jvm](#java-虚拟机-jvm) - * [设计模式](#设计模式) -* [:open_file_folder: 数据结构与算法](#open_file_folder-数据结构与算法) - * [数据结构](#数据结构) - * [算法](#算法) -* [:computer: 计算机网络与数据通信](#computer-计算机网络与数据通信) - * [网络相关](#网络相关) - * [数据通信\(RESTful,RPC,消息队列\)总结](#数据通信restfulrpc消息队列总结) -* [:iphone: 操作系统](#iphone-操作系统) - * [Linux相关](#linux相关) -* [:pencil2: 主流框架/软件](#pencil2-主流框架软件) - * [Spring](#spring) - * [ZooKeeper](#zookeeper) -* [:floppy_disk: 数据存储](#floppy_disk-数据存储) - * [MySQL](#mysql) - * [Redis](#redis) -* [:punch: 架构](#punch-架构) -* [:musical_note: 面试必备](#musical_note-面试必备) - * [备战面试](#备战面试) - * [最最最常见的Java面试题总结](#最最最常见的java面试题总结) - * [Java学习/面试开源仓库推荐](#java学习面试开源仓库推荐) -* [:art: 闲谈](#art-闲谈) -* [:envelope: 说明](#envelope-说明) - -## 待办 - -* [ ] Java 8 新特性总结 -* [ ] Java 多线程类别知识重构 -* [x] BIO,NIO,AIO 总结 -* [ ] Netty 总结 -* [ ] 数据结构总结重构 - -## :coffee: Java - -### Java/J2EE 基础 - -* [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) -* [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) -* [Java常见关键字总结:static、final、this、super](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) - - -### Java 集合框架 - -* [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) -* [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md) -* [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) -* [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](https://github.com/Snailclimb/JavaGuide/blob/master/Java相关/ArrayList-Grow.md) -* [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) -* [HashMap(JDK1.8)源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/HashMap.md) - -### Java 多线程 - -* [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) -* [并发编程面试必备:乐观锁与悲观锁](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/面试必备之乐观锁与悲观锁.md) -* [并发编程面试必备:JUC 中的 Atomic 原子类总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/Atomic.md) -* [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/AQS.md) -* [BATJ都爱问的多线程面试题](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/BATJ都爱问的多线程面试题.md) -* [并发容器总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/并发容器总结.md) - -### Java 虚拟机 jvm - -* [可能是把Java内存区域讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md) -* [搞定JVM垃圾回收就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/搞定JVM垃圾回收就是这么简单.md) -* [《深入理解Java虚拟机》第2版学习笔记](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java虚拟机(jvm).md) - -### Java BIO,NIO,AIO - -* [BIO,NIO,AIO 总结 ](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/BIO%2CNIO%2CAIO%20summary.md) -* [Java IO 与 NIO系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java%20IO与NIO.md) - -### 设计模式 - -* [设计模式系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/设计模式.md) - -## :open_file_folder: 数据结构与算法 +👍推荐 [在线阅读](https://snailclimb.gitee.io/javaguide) (Github 访问速度比较慢可能会导致部分图片无法刷新出来) -### 数据结构 +👍推荐[2021最新实战项目源码下载](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100018862&idx=1&sn=858e00b60c6097e3ba061e79be472280&chksm=4ea1856579d60c73224e4d852af6b0188c3ab905069fc28f4b293963fd1ee55d2069fb229848#rd) -* [数据结构知识学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/数据结构.md) +书单已经被移动到[awesome-cs](https://github.com/CodingDocs/awesome-cs) 这个仓库。 -### 算法 +> 1. **介绍**:关于 JavaGuide 的相关介绍请看:[关于 JavaGuide 的一些说明](https://www.yuque.com/snailclimb/dr6cvl/mr44yt) 。 +> 2. **PDF版本** : [《JavaGuide 面试突击版》PDF 版本](#公众号) 。[图解计算机基础 PDF 版](#优质原创PDF资源)。 +> 3. **知识星球** : 简历指导/Java学习/面试指导/面试小册。欢迎加入[我的知识星球](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100015911&idx=1&sn=2e8a0f5acb749ecbcbb417aa8a4e18cc&chksm=4ea1b0ec79d639fae37df1b86f196e8ce397accfd1dd2004bcadb66b4df5f582d90ae0d62448#rd) 。星球内部更新的[《Java面试进阶指北 打造个人的技术竞争力》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)这个小册的质量很高,专为面试打造。 +> 4. **面试专版** :准备面试的小伙伴可以考虑面试专版:[《Java 面试进阶指南》](https://xiaozhuanlan.com/javainterview?rel=javaguide) +> 6. **转载须知** :以下所有文章如非文首说明皆为我(Guide哥)的原创,转载在文首注明出处,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!⛽️ + +

+ + + +

+

+ 阅读 + 公众号 + 公众号 + 投稿 + 投稿 + 投稿 +

+ + +

Sponsor

+ + + + + + + +
+ + +
+ +## Java + +### 基础 + +**知识点/面试题:**(必看:+1: ) + +1. **[Java 基础知识](docs/java/basis/Java基础知识.md)** +2. **[Java 基础知识疑难点/易错点](docs/java/basis/Java基础知识疑难点.md)** + +**重要知识点详解:** + +1. [枚举](docs/java/basis/用好Java中的枚举真的没有那么简单.md) (很重要的一个数据结构,用好枚举真的没有那么简单!) +2. [Java 常见关键字总结:final、static、this、super!](docs/java/basis/Java常见关键字总结.md) +3. [什么是反射机制?反射机制的应用场景有哪些?](docs/java/basis/反射机制.md) +4. [代理模式详解:静态代理+JDK/CGLIB 动态代理实战](docs/java/basis/代理模式详解.md) +5. [常见的 IO 模型有哪些?Java 中的 BIO、NIO、AIO 有啥区别?](docs/java/basis/IO模型.md) + +### 容器 -* [算法学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/算法.md) -* [常见安全算法(MD5、SHA1、Base64等等)总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/常见安全算法(MD5、SHA1、Base64等等)总结.md) -* [算法总结——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) -* [算法总结——几道常见的链表算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/Leetcode-LinkList1.md) +1. **[Java 容器常见问题总结](docs/java/collection/Java集合框架常见面试题.md)** (必看 :+1:) +2. **源码分析** :[ArrayList 源码+扩容机制分析](docs/java/collection/ArrayList源码+扩容机制分析.md) 、[LinkedList 源码](docs/java/collection/LinkedList源码分析.md) 、[HashMap(JDK1.8)源码+底层数据结构分析]() 、[ConcurrentHashMap 源码+底层数据结构分析](docs/java/collection/ConcurrentHashMap源码+底层数据结构分析.md) -## :computer: 计算机网络与数据通信 +### 并发 -### 网络相关 +**知识点/面试题:** (必看 :+1:) -* [计算机网络常见面试题](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/计算机网络.md) -* [计算机网络基础知识总结](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/干货:计算机网络知识总结.md) -* [HTTPS中的TLS](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/HTTPS中的TLS.md) +1. **[Java 并发基础常见面试题总结](docs/java/multi-thread/2020最新Java并发基础常见面试题总结.md)** +2. **[Java 并发进阶常见面试题总结](docs/java/multi-thread/2020最新Java并发进阶常见面试题总结.md)** -### 数据通信(RESTful,RPC,消息队列)总结 +**重要知识点详解:** -* [数据通信(RESTful、RPC、消息队列)相关知识点总结](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/数据通信(RESTful、RPC、消息队列).md) -* [Dubbo 总结:关于 Dubbo 的重要知识点](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/dubbo.md) -* [消息队列总结:新手也能看懂,消息队列其实很简单](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/message-queue.md) -* [一文搞懂 RabbitMQ 的重要概念以及安装](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/rabbitmq.md) +2. **线程池**:[Java 线程池学习总结](./docs/java/multi-thread/java线程池学习总结.md)、[拿来即用的线程池最佳实践](./docs/java/multi-thread/拿来即用的线程池最佳实践.md) +4. [ ThreadLocal 关键字解析](docs/java/multi-thread/万字详解ThreadLocal关键字.md) +5. [并发容器总结](docs/java/multi-thread/并发容器总结.md) +6. [JUC 中的 Atomic 原子类总结](docs/java/multi-thread/Atomic原子类总结.md) +7. [AQS 原理以及 AQS 同步组件总结](docs/java/multi-thread/AQS原理以及AQS同步组件总结.md) -## :iphone: 操作系统 +### JVM (必看 :+1:) -### Linux相关 +1. **[Java 内存区域](docs/java/jvm/Java内存区域.md)** +2. **[JVM 垃圾回收](docs/java/jvm/JVM垃圾回收.md)** +3. [JDK 监控和故障处理工具](docs/java/jvm/JDK监控和故障处理工具总结.md) +4. [类文件结构](docs/java/jvm/类文件结构.md) +5. **[类加载过程](docs/java/jvm/类加载过程.md)** +6. [类加载器](docs/java/jvm/类加载器.md) +7. **[【待完成】最重要的 JVM 参数指南(翻译完善了一半)](docs/java/jvm/最重要的JVM参数指南.md)** +9. **[【加餐】大白话带你认识 JVM](docs/java/jvm/[加餐]大白话带你认识JVM.md)** -* [后端程序员必备的 Linux 基础知识](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/后端程序员必备的Linux基础知识.md) -* [Shell 编程入门](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/Shell.md) -## :pencil2: 主流框架/软件 +### 新特性 -### Spring +1. **Java 8** :[Java 8 新特性总结](docs/java/new-features/Java8新特性总结.md)、[Java8常用新特性总结](docs/java/new-features/java8-common-new-features.md) 、[Java 8 学习资源推荐](docs/java/new-features/Java8教程推荐.md)、[Java8 forEach 指南](docs/java/new-features/Java8foreach指南.md) +2. **Java9~Java14** : [一文带你看遍 JDK9~14 的重要新特性!](./docs/java/new-features/一文带你看遍JDK9到14的重要新特性.md) -* [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) -* [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringBean.md) -* [SpringMVC 工作原理详解](https://github.com/Snailclimb/JavaGuide/blob/master/主流框架/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md) +## 网络 -### ZooKeeper +1. [计算机网络常见面试题](docs/network/计算机网络.md) +2. [计算机网络基础知识总结](docs/network/计算机网络知识总结.md) -* [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) -* [ZooKeeper 数据模型和常见命令了解一下,速度收藏!](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper数据模型和常见命令.md) - -## :floppy_disk: 数据存储 +## 操作系统 + +1. [操作系统常见问题总结!](docs/operating-system/basis.md) +2. [后端程序员必备的 Linux 基础知识](docs/operating-system/linux.md) +3. [Shell 编程入门](docs/operating-system/Shell.md) + +## 数据结构与算法 + +### 数据结构 + +- **图解数据结构:** + 1. [线性数据结构 :数组、链表、栈、队列](docs/dataStructures-algorithms/data-structure/线性数据结构.md) + 2. [图](docs/dataStructures-algorithms/data-structure/图.md) +- [不了解布隆过滤器?一文给你整的明明白白!](docs/dataStructures-algorithms/data-structure/bloom-filter.md) + +### 算法 + +算法这部分内容非常重要,如果你不知道如何学习算法的话,可以看下我写的: + +- [算法学习书籍+资源推荐](https://www.zhihu.com/question/323359308/answer/1545320858) 。 +- [如何刷Leetcode?](https://www.zhihu.com/question/31092580/answer/1534887374) + +**常见算法问题总结:** + +- [几道常见的字符串算法题总结 ](docs/dataStructures-algorithms/几道常见的子符串算法题.md) +- [几道常见的链表算法题总结 ](docs/dataStructures-algorithms/几道常见的链表算法题.md) +- [剑指 offer 部分编程题](docs/dataStructures-algorithms/剑指offer部分编程题.md) + +## 数据库 ### MySQL -* [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) -* [【思维导图-索引篇】搞定数据库索引就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL%20Index.md) +**总结:** + +1. **[MySQL知识点总结](docs/database/MySQL.md)** (必看 :+1:) +2. [阿里巴巴开发手册数据库部分的一些最佳实践](docs/database/阿里巴巴开发手册数据库部分的一些最佳实践.md) +3. [一千行 MySQL 学习笔记](docs/database/一千行MySQL命令.md) +4. [MySQL 高性能优化规范建议](docs/database/MySQL高性能优化规范建议.md) + +**重要知识点:** + +1. [MySQL数据库索引总结](docs/database/MySQL数据库索引.md) +2. [事务隔离级别(图文详解)]() +3. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/一条sql语句在mysql中如何执行的.md) +4. [关于数据库中如何存储时间的一点思考](docs/database/关于数据库存储时间的一点思考.md) ### Redis -* [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) -* [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) -* [如何做可靠的分布式锁,Redlock真的可行么](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md) +2. [Redis 常见问题总结](docs/database/Redis/redis-all.md) +3. [面试/工作必备!3种常用的缓存读写策略!](docs/database/Redis/3种常用的缓存读写策略.md) -## :punch: 架构 +## 系统设计 -* [一文读懂分布式应该学什么](https://github.com/Snailclimb/Java_Guide/blob/master/架构/分布式.md) -* [8 张图读懂大型网站技术架构](https://github.com/Snailclimb/JavaGuide/blob/master/架构/8%20张图读懂大型网站技术架构.md) -* [【面试精选】关于大型网站系统架构你不得不懂的10个问题](https://github.com/Snailclimb/JavaGuide/blob/master/架构/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md) +### 编码之道(必看 :+1:) -## :musical_note: 面试必备 +1. [RestFul API 简明教程](docs/system-design/coding-way/RESTfulAPI简明教程.md) +2. [Java 编程规范以及优雅 Java 代码实践总结](docs/java/Java编程规范.md) +3. [Java 命名之道](docs/system-design/naming.md) -### 备战面试 +### 常用框架 -* [【备战面试1】程序员的简历就该这样写](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/程序员的简历之道.md) -* [【备战面试2】初出茅庐的程序员该如何准备面试?](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/interviewPrepare.md) -* [【备战面试3】7个大部分程序员在面试前很关心的问题](https://github.com/Snailclimb/JavaGuide/blob/master/面试必备/JavaProgrammerNeedKnow.md) -* [【备战面试4】Java程序员必备书单](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/books.md) -* [【备战面试5】美团面试常见问题总结(附详解答案)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/美团面试常见问题总结.md) +如果你没有接触过 Java Web 开发的话,可以先看一下我总结的 [《J2EE 基础知识》](docs/java/J2EE基础知识.md) 。虽然,这篇文章中的很多内容已经淘汰,但是可以让你对 Java 后台技术发展有更深的认识。 +#### Spring/SpringBoot (必看 :+1:) -### 最最最常见的Java面试题总结 +**知识点/面试题:** -这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。 +1. **[Spring 常见问题总结](docs/system-design/framework/spring/Spring常见问题总结.md)** +2. **[SpringBoot 入门指南](https://github.com/Snailclimb/springboot-guide)** +3. **[面试常问:“讲述一下 SpringBoot 自动装配原理?”](https://www.cnblogs.com/javaguide/p/springboot-auto-config.html)** -* [第一周(2018-8-7)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) -* [第二周(2018-8-13)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) -* [第三周(2018-08-22)](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) (Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结) -* [第四周(2018-8-30).md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。) +**重要知识点详解:** -### Java学习/面试开源仓库推荐 +1. **[Spring/Spring Boot 常用注解总结!安排!](./docs/system-design/framework/spring/SpringBoot+Spring常用注解总结.md)** +2. **[Spring 事务总结](docs/system-design/framework/spring/Spring事务总结.md)** +3. [Spring 中都用到了那些设计模式?](docs/system-design/framework/spring/Spring-Design-Patterns.md) -* [盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/JavaInterviewGithub.md) +#### MyBatis -## :art: 闲谈 +- [MyBatis 常见面试题总结](docs/system-design/framework/mybatis/mybatis-interview.md) -* [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/闲谈/选择技术方向都要考虑哪些因素.md) -* [结束了我短暂的秋招,说点自己的感受](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/2018%20%E7%A7%8B%E6%8B%9B.md) -* [【2018总结】即使平凡,也要热爱自己的生活](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/2018%20summary.md) -* [Java项目 Github Trending 月榜](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/JavaGithubTrending.md) +#### Netty (必看 :+1:) -*** +1. [剖析面试最常见问题之 Netty(上)](https://xiaozhuanlan.com/topic/4028536971) +2. [剖析面试最常见问题之 Netty(下)](https://xiaozhuanlan.com/topic/3985146207) -## :envelope: 说明 +#### ZooKeeper -### 介绍 +> 前两篇文章可能有内容重合部分,推荐都看一遍。 -* **对于 Java 初学者来说:** 本文档倾向于给你提供一个比较详细的学习路径,让你对于Java整体的知识体系有一个初步认识。另外,本文的一些文章 -也是你学习和复习 Java 知识不错的实践; -* **对于非 Java 初学者来说:** 本文档更适合回顾知识,准备面试,搞清面试应该把重心放在那些问题上。要搞清楚这个道理:提前知道那些面试常见,不是为了背下来应付面试,而是为了让你可以更有针对的学习重点。 +1. [【入门】ZooKeeper 相关概念总结](docs/system-design/distributed-system/zookeeper/zookeeper-intro.md) +2. [【进阶】ZooKeeper 相关概念总结](docs/system-design/distributed-system/zookeeper/zookeeper-plus.md) +3. [【实战】ZooKeeper 实战](docs/system-design/distributed-system/zookeeper/zookeeper-in-action.md) -本文档 Markdown 格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。 +### 安全 -### 关于转载 +#### 认证授权 -如果你需要转载本仓库的一些文章到自己的博客的话,记得注明原文地址就可以了。 +**[《认证授权基础》](docs/system-design/authority-certification/basis-of-authority-certification.md)** 这篇文章中我会介绍认证授权常见概念: **Authentication**,**Authorization** 以及 **Cookie**、**Session**、Token、**OAuth 2**、**SSO** 。如果你不清楚这些概念的话,建议好好阅读一下这篇文章。 -### 如何对该开源文档进行贡献 +- **JWT** :JWT(JSON Web Token)是一种身份认证的方式,JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。相关阅读: + - [JWT 优缺点分析以及常见问题解决方案](docs/system-design/authority-certification/JWT优缺点分析以及常见问题解决方案.md) + - [适合初学者入门 Spring Security With JWT 的 Demo](https://github.com/Snailclimb/spring-security-jwt-guide) -1. 笔记内容大多是手敲,所以难免会有笔误,你可以帮我找错别字。 -2. 很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。(**对于不错的原创文章我根据你的选择给予现金奖励、付费专栏或者书籍进行奖励!快提 pr 给我投稿吧!**) -3. 现有的知识点难免存在不完善或者错误,所以你可以对已有知识点的修改/补充。 +- **SSO(单点登录)** :**SSO(Single Sign On)** 即单点登录说的是用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。举个例子我们在登陆了京东金融之后,我们同时也成功登陆京东的京东超市、京东家电等子系统。相关阅读:[**SSO 单点登录看这篇就够了!**](docs/system-design/authority-certification/SSO单点登录看这一篇就够了.md) -### 为什么要做这个开源文档? +#### 数据脱敏 -初始想法源于自己的个人那一段比较迷茫的学习经历。主要目的是为了通过这个开源平台来帮助一些在学习 Java 或者面试过程中遇到问题的小伙伴。 +数据脱敏说的就是我们根据特定的规则对敏感信息数据进行变形,比如我们把手机号、身份证号某些位数使用 * 来代替。相关阅读: -### 联系我 +- [大厂也在用的 6种 数据脱敏方案,严防泄露数据的 “内鬼”](https://www.cnblogs.com/chengxy-nds/p/14107671.html) +- [【进阶之路】基于ShardingSphere的线上业务数据脱敏解决方案](https://juejin.cn/post/6906074730437836813) -如果大家需要与我交流,可以扫描下方二维码添加我的微信获取关注[我的公众号](#公众号): +### 分布式 -![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/JavaGuide.jpg) +#### CAP 理论 -### Java并发编程专栏 +CAP 也就是 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性) 这三个单词首字母组合。 -微信扫描下方二维码,购买之后我会将自己得到的24元返现都还给你,减轻各位的学习成本! +关于 CAP 的详细解读请看:[《CAP理论解读》](docs/system-design/distributed-system/CAP理论.md)。 -![ Java并发编程专栏](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Java并发编程实战.jpg) +#### BASE 理论 -### Contributor +**BASE** 是 **Basically Available(基本可用)** 、**Soft-state(软状态)** 和 **Eventually Consistent(最终一致性)** 三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。 -下面是笔主收集的一些对本仓库提过有价值的pr或者issue的朋友,人数较多,如果你也对本仓库提过不错的pr或者issue的话,你可以加我的微信与我联系。下面的排名不分先后! +关于 CAP 的详细解读请看:[《BASE理论解读》](docs/system-design/distributed-system/BASE理论.md)。 - - - - - - - - - - - - - - - - -" - - - - - - - - - +#### Paxos 算法和 Raft 算法 + +**Paxos 算法**诞生于 1990 年,这是一种解决分布式系统一致性的经典算法 。但是,由于 Paxos 算法非常难以理解和实现,不断有人尝试简化这一算法。到了2013 年才诞生了一个比 Paxos 算法更易理解和实现的分布式一致性算法—**Raft 算法**。 + +#### 搜索引擎 + +用于提高搜索效率,功能和浏览器搜索引擎类似。比较常见的搜索引擎是 Elasticsearch(推荐) 和 Solr。 + +#### RPC + +RPC 让调用远程服务调用像调用本地方法那样简单。 + +Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读: + +- [Dubbo 常见问题总结](docs/system-design/distributed-system/rpc/Dubbo.md) +- [服务之间的调用为啥不直接用 HTTP 而用 RPC?](docs/system-design/distributed-system/rpc/服务之间的调用为啥不直接用HTTP而用RPC.md) + +#### API 网关 + +网关主要用于请求转发、安全认证、协议转换、容灾。 + +1. [为什么要网关?你知道有哪些常见的网关系统?](docs/system-design/distributed-system/api-gateway/为什么要网站有哪些常见的网站系统.md) +2. [如何设计一个亿级网关(API Gateway)?](docs/system-design/distributed-system/api-gateway/如何设计一个亿级网关.md) + +#### 分布式 id + +在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。比如数据量太大之后,往往需要对进行对数据进行分库分表,分库分表后需要有一个唯一 ID 来标识一条数据或消息,数据库的自增 ID 显然不能满足需求。相关阅读:[为什么要分布式 id ?分布式 id 生成方案有哪些?](docs/system-design/micro-service/分布式id生成方案总结.md) + +#### 分布式事务 + +**分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。** + +简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。 + +### 微服务 + +1. [ 大白话入门 Spring Cloud](docs/system-design/micro-service/spring-cloud.md) +2. [微服务/分布式大厂真实面试问题解答](https://xiaozhuanlan.com/topic/2895047136) + +### 高并发 + +#### 消息队列 + +消息队列在分布式系统中主要是为了解耦和削峰。相关阅读: [消息队列常见问题总结](docs/system-design/distributed-system/message-queue/message-queue.md)。 + +1. **RabbitMQ** : [RabbitMQ 入门](docs/system-design/distributed-system/message-queue/RabbitMQ入门看这一篇就够了.md) +2. **RocketMQ** : [RocketMQ 入门](docs/system-design/distributed-system/message-queue/RocketMQ.md)、[RocketMQ 的几个简单问题与答案](docs/system-design/distributed-system/message-queue/RocketMQ-Questions.md) +3. **Kafka** :[Kafka 常见问题总结](docs/system-design/distributed-system/message-queue/Kafka常见面试题总结.md) + +#### 读写分离&分库分表 + +读写分离主要是为了将数据库的读和写操作分不到不同的数据库节点上。主服务器负责写,从服务器负责读。另外,一主一从或者一主多从都可以。 + +读写分离可以大幅提高读性能,小幅提高写的性能。因此,读写分离更适合单机并发读请求比较多的场景。 + +分库分表是为了解决由于库、表数据量过大,而导致数据库性能持续下降的问题。 + +常见的分库分表工具有:`sharding-jdbc`(当当)、`TSharding`(蘑菇街)、`MyCAT`(基于 Cobar)、`Cobar`(阿里巴巴)...。 推荐使用 `sharding-jdbc`。 因为,`sharding-jdbc` 是一款轻量级 `Java` 框架,以 `jar` 包形式提供服务,不要我们做额外的运维工作,并且兼容性也很好。 + +相关阅读: [读写分离&分库分表常见问题总结](docs/system-design/读写分离&分库分表.md) + +#### 负载均衡 + +负载均衡系统通常用于将任务比如用户请求处理分配到多个服务器处理以提高网站、应用或者数据库的性能和可靠性。 + +常见的负载均衡系统包括 3 种: + +1. **DNS 负载均衡** :一般用来实现地理级别的均衡。 +2. **硬件负载均衡** : 通过单独的硬件设备比如 F5 来实现负载均衡功能(硬件的价格一般很贵)。 +3. **软件负载均衡** :通过负载均衡软件比如 Nginx 来实现负载均衡功能。 + +### 高可用 + +高可用描述的是一个系统在大部分时间都是可用的,可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的 。 + +相关阅读: **《[如何设计一个高可用系统?要考虑哪些地方?](docs/system-design/high-availability/如何设计一个高可用系统要考虑哪些地方.md)》** 。 + +#### 限流 + +限流是从用户访问压力的角度来考虑如何应对系统故障。 + +限流为了对服务端的接口接受请求的频率进行限制,防止服务挂掉。比如某一接口的请求限制为 100 个每秒, 对超过限制的请求放弃处理或者放到队列中等待处理。限流可以有效应对突发请求过多。相关阅读:[限流算法有哪些?](docs/system-design/high-availability/limit-request.md) + +#### 降级 + +降级是从系统功能优先级的角度考虑如何应对系统故障。 + +服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。 + +#### 熔断 + +熔断和降级是两个比较容易混淆的概念,两者的含义并不相同。 + +降级的目的在于应对系统自身的故障,而熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障。 + +#### 排队 + +另类的一种限流,类比于现实世界的排队。玩过英雄联盟的小伙伴应该有体会,每次一有活动,就要经历一波排队才能进入游戏。 + +#### 集群 + +相同的服务部署多份,避免单点故障。 + +#### 超时和重试机制 + +**一旦用户的请求超过某个时间得不到响应就结束此次请求并抛出异常。** 如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法在处理请求。 + +另外,重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。 + +### 大型网站架构 + +- [8 张图读懂大型网站技术架构](docs/system-design/website-architecture/8%20张图读懂大型网站技术架构.md) +- [关于大型网站系统架构你不得不懂的 10 个问题](docs/system-design/website-architecture/关于大型网站系统架构你不得不懂的10个问题.md) + +## 工具 + +1. **Java** :[JAD 反编译](docs/java/JAD反编译tricks.md)、[手把手教你定位常见 Java 性能问题](./docs/java/手把手教你定位常见Java性能问题.md) +2. **Git** :[Git 入门](docs/tools/Git.md) +3. **Github** : [Github小技巧](docs/tools/Github技巧.md) +4. **Docker** : [Docker 基本概念解读](docs/tools/Docker.md) 、[Docker从入门到上手干事](docs/tools/Docker从入门到实战.md) + +## Java 学习常见问题汇总 + +1. [Java 学习路线和方法推荐](docs/questions/java-learning-path-and-methods.md) +2. [Java 培训四个月能学会吗?](docs/questions/java-training-4-month.md) +3. [新手学习 Java,有哪些 Java 相关的博客,专栏,和技术学习网站推荐?](docs/questions/java-learning-website-blog.md) +4. [Java 还是大数据,你需要了解这些东西!](docs/questions/java-big-data.md) + +--- + +## 其他 + +### 贡献者 + +[你可以点此链接查看JavaGuide的所有贡献者。](https://github.com/Snailclimb/JavaGuide/graphs/contributors) 感谢你们让 JavaGuide 变得更好!如果你们来到武汉一定要找我,我请你们吃饭玩耍。 + +*悄悄话:JavaGuide 会不定时为贡献者们送福利。* + +### 待办 + +- [ ] 数据结构总结重构 + +### 优质原创PDF资源 + +![](https://cdn.jsdelivr.net/gh/javaguide-tech/blog-images-2@main/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%93%E4%B8%9A/image-20201027160348395.png) + +为了避免恶意传播,微信搜“**Github掘金计划**”后台回复 **“006”** 即可获取。 + + + +### 捐赠支持 + +项目的发展离不开你的支持,如果 JavaGuide 帮助到了你找到自己满意的 offer,请作者喝杯咖啡吧 ☕ 后续会继续完善更新!加油! + +[点击捐赠支持作者](https://www.yuque.com/snailclimb/dr6cvl/mr44yt#vu3ok) + +### 联系我 + + ### 公众号 -如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号“**JavaGuide**”。 + +**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V4.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可领取! + +![我的公众号](https://cdn.jsdelivr.net/gh/javaguide-tech/blog-images/2020-08/167598cd2e17b8ec.png) -![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334) diff --git a/_coverpage.md b/_coverpage.md new file mode 100644 index 0000000000000000000000000000000000000000..7310181ac883d353dc84275b971e40a2c3caffd8 --- /dev/null +++ b/_coverpage.md @@ -0,0 +1,12 @@ +

+ +

+ + +

Java 学习/面试指南

+ +[常用资源](https://shimo.im/docs/MuiACIg1HlYfVxrj/) +[GitHub]() +[开始阅读](#java) + + diff --git a/docs/dataStructures-algorithms/data-structure/bloom-filter.md b/docs/dataStructures-algorithms/data-structure/bloom-filter.md new file mode 100644 index 0000000000000000000000000000000000000000..b9d129d28cdf58db3a617d8b1b6b4f3c00a677ca --- /dev/null +++ b/docs/dataStructures-algorithms/data-structure/bloom-filter.md @@ -0,0 +1,297 @@ +海量数据处理以及缓存穿透这两个场景让我认识了 布隆过滤器 ,我查阅了一些资料来了解它,但是很多现成资料并不满足我的需求,所以就决定自己总结一篇关于布隆过滤器的文章。希望通过这篇文章让更多人了解布隆过滤器,并且会实际去使用它! + +下面我们将分为几个方面来介绍布隆过滤器: + +1. 什么是布隆过滤器? +2. 布隆过滤器的原理介绍。 +3. 布隆过滤器使用场景。 +4. 通过 Java 编程手动实现布隆过滤器。 +5. 利用Google开源的Guava中自带的布隆过滤器。 +6. Redis 中的布隆过滤器。 + +### 1.什么是布隆过滤器? + +首先,我们需要了解布隆过滤器的概念。 + +布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于1970年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。 + +![布隆过滤器示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/布隆过滤器-bit数组.png) + +位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb 的空间。 + +总结:**一个名叫 Bloom 的人提出了一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大。** + +### 2.布隆过滤器的原理介绍 + +**当一个元素加入布隆过滤器中的时候,会进行如下操作:** + +1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。 +2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。 + +**当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:** + +1. 对给定元素再次进行相同的哈希计算; +2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。 + +举个简单的例子: + + + +![布隆过滤器hash计算](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/布隆过滤器-hash运算.png) + +如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为 1,所以很容易知道此值已经存在(去重非常方便)。 + +如果我们需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。 + +**不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。** + +综上,我们可以得出:**布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。** + +### 3.布隆过滤器使用场景 + +1. 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。 +2. 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。 + +### 4.通过 Java 编程手动实现布隆过滤器 + +我们上面已经说了布隆过滤器的原理,知道了布隆过滤器的原理之后就可以自己手动实现一个了。 + +如果你想要手动实现一个的话,你需要: + +1. 一个合适大小的位数组保存数据 +2. 几个不同的哈希函数 +3. 添加元素到位数组(布隆过滤器)的方法实现 +4. 判断给定元素是否存在于位数组(布隆过滤器)的方法实现。 + +下面给出一个我觉得写的还算不错的代码(参考网上已有代码改进得到,对于所有类型对象皆适用): + +```java +import java.util.BitSet; + +public class MyBloomFilter { + + /** + * 位数组的大小 + */ + private static final int DEFAULT_SIZE = 2 << 24; + /** + * 通过这个数组可以创建 6 个不同的哈希函数 + */ + private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134}; + + /** + * 位数组。数组中的元素只能是 0 或者 1 + */ + private BitSet bits = new BitSet(DEFAULT_SIZE); + + /** + * 存放包含 hash 函数的类的数组 + */ + private SimpleHash[] func = new SimpleHash[SEEDS.length]; + + /** + * 初始化多个包含 hash 函数的类的数组,每个类中的 hash 函数都不一样 + */ + public MyBloomFilter() { + // 初始化多个不同的 Hash 函数 + for (int i = 0; i < SEEDS.length; i++) { + func[i] = new SimpleHash(DEFAULT_SIZE, SEEDS[i]); + } + } + + /** + * 添加元素到位数组 + */ + public void add(Object value) { + for (SimpleHash f : func) { + bits.set(f.hash(value), true); + } + } + + /** + * 判断指定元素是否存在于位数组 + */ + public boolean contains(Object value) { + boolean ret = true; + for (SimpleHash f : func) { + ret = ret && bits.get(f.hash(value)); + } + return ret; + } + + /** + * 静态内部类。用于 hash 操作! + */ + public static class SimpleHash { + + private int cap; + private int seed; + + public SimpleHash(int cap, int seed) { + this.cap = cap; + this.seed = seed; + } + + /** + * 计算 hash 值 + */ + public int hash(Object value) { + int h; + return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16))); + } + + } +} +``` + +测试: + +```java + String value1 = "https://javaguide.cn/"; + String value2 = "https://github.com/Snailclimb"; + MyBloomFilter filter = new MyBloomFilter(); + System.out.println(filter.contains(value1)); + System.out.println(filter.contains(value2)); + filter.add(value1); + filter.add(value2); + System.out.println(filter.contains(value1)); + System.out.println(filter.contains(value2)); +``` + +Output: + +``` +false +false +true +true +``` + +测试: + +```java + Integer value1 = 13423; + Integer value2 = 22131; + MyBloomFilter filter = new MyBloomFilter(); + System.out.println(filter.contains(value1)); + System.out.println(filter.contains(value2)); + filter.add(value1); + filter.add(value2); + System.out.println(filter.contains(value1)); + System.out.println(filter.contains(value2)); +``` + +Output: + +```java +false +false +true +true +``` + +### 5.利用Google开源的 Guava中自带的布隆过滤器 + +自己实现的目的主要是为了让自己搞懂布隆过滤器的原理,Guava 中布隆过滤器的实现算是比较权威的,所以实际项目中我们不需要手动实现一个布隆过滤器。 + +首先我们需要在项目中引入 Guava 的依赖: + +```java + + com.google.guava + guava + 28.0-jre + +``` + +实际使用如下: + +我们创建了一个最多存放 最多 1500个整数的布隆过滤器,并且我们可以容忍误判的概率为百分之(0.01) + +```java + // 创建布隆过滤器对象 + BloomFilter filter = BloomFilter.create( + Funnels.integerFunnel(), + 1500, + 0.01); + // 判断指定元素是否存在 + System.out.println(filter.mightContain(1)); + System.out.println(filter.mightContain(2)); + // 将元素添加进布隆过滤器 + filter.put(1); + filter.put(2); + System.out.println(filter.mightContain(1)); + System.out.println(filter.mightContain(2)); +``` + +在我们的示例中,当`mightContain()` 方法返回*true*时,我们可以99%确定该元素在过滤器中,当过滤器返回*false*时,我们可以100%确定该元素不存在于过滤器中。 + +**Guava 提供的布隆过滤器的实现还是很不错的(想要详细了解的可以看一下它的源码实现),但是它有一个重大的缺陷就是只能单机使用(另外,容量扩展也不容易),而现在互联网一般都是分布式的场景。为了解决这个问题,我们就需要用到 Redis 中的布隆过滤器了。** + +### 6.Redis 中的布隆过滤器 + +#### 6.1介绍 + +Redis v4.0 之后有了 Module(模块/插件) 功能,Redis Modules 让 Redis 可以使用外部模块扩展其功能 。布隆过滤器就是其中的 Module。详情可以查看 Redis 官方对 Redis Modules 的介绍 :https://redis.io/modules + +另外,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module,地址:https://github.com/RedisBloom/RedisBloom. 其他还有: + +- redis-lua-scaling-bloom-filter (lua 脚本实现):https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter +- pyreBloom(Python中的快速Redis 布隆过滤器) :https://github.com/seomoz/pyreBloom +- ...... + +RedisBloom 提供了多种语言的客户端支持,包括:Python、Java、JavaScript 和 PHP。 + +#### 6.2使用Docker安装 + +如果我们需要体验 Redis 中的布隆过滤器非常简单,通过 Docker 就可以了!我们直接在 Google 搜索**docker redis bloomfilter** 然后在排除广告的第一条搜素结果就找到了我们想要的答案(这是我平常解决问题的一种方式,分享一下),具体地址:https://hub.docker.com/r/redislabs/rebloom/ (介绍的很详细 )。 + +**具体操作如下:** + +``` +➜ ~ docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest +➜ ~ docker exec -it redis-redisbloom bash +root@21396d02c252:/data# redis-cli +127.0.0.1:6379> +``` + +#### 6.3常用命令一览 + +> 注意: key:布隆过滤器的名称,item : 添加的元素。 + +1. **`BF.ADD `**:将元素添加到布隆过滤器中,如果该过滤器尚不存在,则创建该过滤器。格式:`BF.ADD {key} {item}`。 +2. **`BF.MADD `** : 将一个或多个元素添加到“布隆过滤器”中,并创建一个尚不存在的过滤器。该命令的操作方式`BF.ADD`与之相同,只不过它允许多个输入并返回多个值。格式:`BF.MADD {key} {item} [item ...]` 。 +3. **`BF.EXISTS` ** : 确定元素是否在布隆过滤器中存在。格式:`BF.EXISTS {key} {item}`。 +4. **`BF.MEXISTS`** : 确定一个或者多个元素是否在布隆过滤器中存在格式:`BF.MEXISTS {key} {item} [item ...]`。 + +另外,`BF.RESERVE` 命令需要单独介绍一下: + +这个命令的格式如下: + +`BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion] `。 + +下面简单介绍一下每个参数的具体含义: + +1. key:布隆过滤器的名称 +2. error_rate :误报的期望概率。这应该是介于0到1之间的十进制值。例如,对于期望的误报率0.1%(1000中为1),error_rate应该设置为0.001。该数字越接近零,则每个项目的内存消耗越大,并且每个操作的CPU使用率越高。 +3. capacity: 过滤器的容量。当实际存储的元素个数超过这个值之后,性能将开始下降。实际的降级将取决于超出限制的程度。随着过滤器元素数量呈指数增长,性能将线性下降。 + +可选参数: + +- expansion:如果创建了一个新的子过滤器,则其大小将是当前过滤器的大小乘以`expansion`。默认扩展值为2。这意味着每个后续子过滤器将是前一个子过滤器的两倍。 + +#### 6.4实际使用 + +```shell +127.0.0.1:6379> BF.ADD myFilter java +(integer) 1 +127.0.0.1:6379> BF.ADD myFilter javaguide +(integer) 1 +127.0.0.1:6379> BF.EXISTS myFilter java +(integer) 1 +127.0.0.1:6379> BF.EXISTS myFilter javaguide +(integer) 1 +127.0.0.1:6379> BF.EXISTS myFilter github +(integer) 0 +``` + diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\233\276.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..10547ded08d0ee2a364214c2c1b647ae298f4249 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\233\276.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..72381b7ae284083c0df4e776769c7d51da8a6bad --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" new file mode 100644 index 0000000000000000000000000000000000000000..46b67b62e85ddf0ce2afcd6c0fe3cd5099f63489 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..f4ad6803c4f7c9cfa1d5a4d1d3449e13dfd66986 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" new file mode 100644 index 0000000000000000000000000000000000000000..758f84e4a101d3b3c859ddeb60e98ddb061f82ff Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..bbe5df1038248c25440aa87e35d94ecdaf0ee093 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" new file mode 100644 index 0000000000000000000000000000000000000000..4b254eb807742eae3aac13ab2a424627753f9d1f Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..620445a760678ae030887913c5a710bc5e7450ca --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" new file mode 100644 index 0000000000000000000000000000000000000000..072bda258e056664d34c3ff4f379caf848350256 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..8dc681fa499b670e25f88cb9840461a2506609b7 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" new file mode 100644 index 0000000000000000000000000000000000000000..37255d9bae65561c9585078e067d067b0aceba85 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..d1072768e6736af6282ffb42d71f346d112f66d3 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" new file mode 100644 index 0000000000000000000000000000000000000000..8b0b528bde5d20cef3a64c4be93e24b6eb4b44a8 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..4e40d1522e7916f4a804f0ec47e0c551a9dc5304 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" @@ -0,0 +1 @@ +3Vrfc9o4EP5r/FiP9dPyI1C4PrQznWbuLu2bwAp4zljUVgL0rz8JS8YWJqEJhDQ8gLRarWR9n1baNQEaLTd/lXy1+CJTkQcwSjcB+hhACEBE9Y+RbGtJEkW1YF5mqVXaC26yX8IKndp9loqqo6ikzFW26gpnsijETHVkvCzluqt2J/PuqCs+FweCmxnPD6X/Zqla1FIG4738k8jmCzcyoEndsuRO2T5JteCpXLdEaBygUSmlqkvLzUjkZvHcutT9Jkdam4mVolCndPjnVrHB3+vP6MvP9bf1YvTrB5p8sFYeeH5vHziANNf2hlMzZbW160B/3pt5Du9koT5UO5QGWgGS1WbfqEtz8xs5G3oyUye0i9BYhHp2GkRdGa4XmRI3Kz4zLWvNIy1bqGWua0AXebWqkb3LNiI1k8jyfCRzWe4Mobs7QWczLa9UKf8TrZY0Tqaab27wB1EqsTm6fKABRbNZyKVQ5Var2A6QkLqLJTLBFtd1ixZWtGgxwsm4JeK8sbzHShcsXL8BHeyBzl/iIh2YPaBrhSxEd1n1c5fbW12JXOV7u/Jx06ltXW2TqVtnQZd3fUJia/tOpuL61PMS6cFO89Zez13elzPxyENbb6J4ORfqET3Uj2ULK9KDlZOVIucqe+hOtw9AO8JXmekHaahCEA6ThMSQxURXGOgSB0ZhFGEYA4hjTEHSNV8vgbXY3s7+IACEhLbMII+eKMToYA5ulHoBD0bZEbFZr+dzE13CrcD36FYI7roVCq7sVvAloEPvETocedAlV4aOXAI6/B6h8w/zq0NHLwEdeI/Q+bvu6vew+IX3sDPejlyA9NTtiF7zdoQZCiFrLi4QdC8uFLBu8zOvRzENY9Dcf+KkOwgGfa2vdDtib4cx5ETGHNmTr8MY5N2RSOLt5VNJgcEThi4MfPJC4F3w5SKsU4KvJsxrYr5Wr0cCvTOSDJ9IMnJVknnnShw9k2S+IYpel2QugHg5yxpmfW+1PMGyFrFOSSeckWWnpgbwVQ8/zwPF+Lksw08YOsIyDTvfttRWRqF6ZMJ+fEo7WUZdqC2el8J9acmXOsoTCXw9N4n+CAL7kZPv3U4+i8kT/vbSbhK+FY69PRd51YP4/TCsLwvqMcyEwtmM55/5VORfZZWpTBa6aSqVkkuNvlMY5NncNCjpReXVgq+MseVmbt7AhVNeZbOQl3Ugzks1KOa70aIQxCyOccIQ00FXjA0CQ0PxRsE4u5RXCxPd74x7Eb4gAO/ieJN+6ET+5tNE/u5FGezLBUwmSH/OkwsAiIaMdUDGFDSiFl8RoCEkh5yFaC8/e2YA9OVSr0kACDBkKGKQERjrb+oTAMcoYSABNGY00lxBfxghnKuwZEDsMDdESA8N0MWyQ+B4UrZa8SI4JbkX9SX3gjENdPiajIJxHAxxMIDBOAkGIBgOgjEx32wSjCfBcBQkg8iW2Ag0JdyUYFNq5+nr6R1JGmqIlEfDDq72uGzTxYq45fFMQyrKHoIvszTNj6UjS3lfpDs2Rj41z0AfSLsnBut5GdObXIS/Tx9d3b/7r4+c/T8o0Ph/ \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" new file mode 100644 index 0000000000000000000000000000000000000000..784add44411bc3d6dc609b8792e2c5c832379030 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..055cf29683baa54a25dcff8972d41489a48c4fb6 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..5d2a50290dac828eadcf6aab77f196d4fbfc985e Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..053d6656491066fcb1a42f8a920e0e7c685bf4e9 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..2a669ee2066b253674055629781bc22879851e95 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..d090804e9127436d10c65aaf9759b0506dcc0ae9 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..4ea176bdc2760758484337648dd6338a233eed50 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250\347\232\204\345\211\257\346\234\254.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250\347\232\204\345\211\257\346\234\254.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..f4a1af667631c071eb2229e2de119bcd092d5f61 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250\347\232\204\345\211\257\346\234\254.drawio" @@ -0,0 +1 @@ +3VlJl9soEP41HDNP+3KUbLnnvUnykvRhOrlhCUtKY+HGqC3n1wcksBbHiqfHjpfng6migKKWrwABc7KsHihcZR9IgjAwtKQC5hQYhq5rDv8TnG3D8TWtYaQ0T6RQy3jMfyDJVGJlnqB1T5ARglm+6jNjUhQoZj0epJRs+mILgvurrmCK9hiPMcT73H/zhGUN1zPclv83ytNMraw7ftOzhEpY7mSdwYRsOiwzAuaEEsKa1rKaICyMp+zSjJsd6N0pRlHBjhngf3yqXrzvwQspzRhZ0xn7uHgnZ1mzrdowSvj+JUkoy0hKCoijlhtSUhYJErNqnGpl3hOy4kydM78jxrbSmbBkhLMytsSyF1U5e+q0v4qp/rIlNa3kzDWxVUTB6PapS3RGCbIdVlNq3IIUTCpi1HSO8YRgQuvdmosFcuKY89eMkmfU6dED8eM9+3ZWRiMljdGIcU0Zr5CmiI05wWgEhek7K0g3PiCyRHxLXIAiDFn+2g9NKCM83cm1QcAbMg7+Q0wYl4yJNg6+dnp+HRO34FvvqlwrtX6FuJQrAcPBXP9w3vO481IKWKot/G5dmzjgAoa9qtpO3krFv6bm4CrNFXM/hjDmmC1iZZPlDD2uYG3fDS8b/UiA61UD5Iu8EhF1vFsT159r2phbXxFlqBp1hOw1HAnbsm5ZnqQ3nSogWVmnACjeyV1njWZlQYrTpuFboFm/HWi2j0xf56rS1z5H+hr3mL623U/fHX2p9HXO4TrzHl1n6X3XOeaFXef+UeS990Oxd5PI650jfa17TN/hweni6evvWfQsp6Txq8qBM9IV56m6lN7YDUcVjz91Tr6R6+rR3rwu2NXH36cuWXuH3nxzLb7GKLBPHQVy6CeSc53bU7p6HVa1whgUgUZROWoQSzs1/kd4Geco6/o9lvXhqfzi7yHG+IPIVT5dt2Dwluofl/S11l2/0seS86BGQCncdgRWAg3Wh0HFGV79/cF3kd/Ja+Pyar4D8rzRaPxWpNp+e/+cfaY/4uD5kfzzZZHPlrH6TtMFqsgGYQSCCEQWCGcg0EHkAK6bPwGRAby6IYFIRBIQ38hUVLSQxSPH901zH8UecpaVczGnFwHfA5EPPBf4fBVPLCqWs4HnAb/7ftQsJBFPajHdQSo9BlN161eYKhbzQ+BHohFYwJueZJOfprPfas/3yy0QuvV+Z8B3pC7c+MIULgjMjk1c0fDd2hcTEAa18KxuuELA2z+fcgRmfZTpJ7E85XQzXrIgztOCkzFPacT5ocDzPIY4kB3LPEnwoRrTB0MM5wiHMH5Oa/5w8RaLdEvSHZAJ69+JLpDeoNJY+5Vm91G1W2qGh4cjSg0n2w+wTa62n7HN6Cc= \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..a2ee5e47f227a9ee299f22c7d18431ef0f74eadd --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..6fd510403971121c7a63c1dc7455400e851ad1d3 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..bceea69589d052d806e4e8974b265755c99f81fc --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" new file mode 100644 index 0000000000000000000000000000000000000000..ef97be64bb5a085aae57dcd9a9c7ace8fc0a7330 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..a25027440d05bca518d52f3ef7548151c35d0169 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" new file mode 100644 index 0000000000000000000000000000000000000000..a7074d37b2808a9bd5e469913dfb21b01583ab97 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..0c727214ab0c1a0597d91a62c6d7b81b198a40fc --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" new file mode 100644 index 0000000000000000000000000000000000000000..063d9ca8a6c90bcd76bfb0abc08e9ec653f82ca1 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..b115c61ee2da92a3c96b08f441ef158ec6f354be --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" new file mode 100644 index 0000000000000000000000000000000000000000..72456379dc57f4638e8f404755d13d6f470ce6c6 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..a5a6d89bd82a139554e4b8389d9559cee4fe29d6 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" new file mode 100644 index 0000000000000000000000000000000000000000..0a9d4084789a045c3d02ad9e4f1cc1f57d83cd85 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..dcf6c68ef1813da5e32f4f82551722d56356dfdb --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" new file mode 100644 index 0000000000000000000000000000000000000000..5393b0c06feb75f7858254bd493fba0e42258bad Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..ae77016546086d9780c0aaa63139630b11262ab4 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" @@ -0,0 +1 @@ +3VpLk6M2EP41HMclifcRv5JDtmoqU6nNHhkjYzYYsUKewfn1kYzEy2Dj8TOew1hqdUsgfV833aDpk3X+G/XT1TcS4FhDIMg1faohBCGw+I+QbAuJC0AhCGkUSKVK8Bb9i6VQqW2iAGcNRUZIzKK0KVyQJMEL1pD5lJLPptqSxM1VUz/Ee4K3hR/vS79HAVsVUgfZlfx3HIUrtTK03GJk7StleSfZyg/IZ02kzzR9QglhRWudT3AsNk/tS2E37xktL4zihA0xCN0cvE1oDl+nKZn+lX3/Mwxf5CwZ26obxgG/f9kllK1ISBI/nlXS8WJDP7CYFPIOJZsk2PUA71UGfxCSSpWfmLGtPFl/wwgXrdg6lqM4j9jf0ly0f4j2yJS9aV4bmm5VJ2F0K4xewAhAJSgsLegoQWW86zWsXzGN1phhKoUZo+QfPCExobt90Odznf/xkWKHxLb0brzaRbKhC3xgt5EEsE9DzA7ouSU8OK8w4ddJt9yO4thn0UfzOnwJ8LDUk6Yepf62ppCSKGFZbeZXIeAKiqu2BKpkKnRaeDqir+tuXZ83iitQvdqtVKIdRk/Aq9zBDz/eyG3QkBXzvRy/N2Bs/doIXo2XJGEv2Q57HldAZppXg7wVil+g5uCX9K6E+8SIY+50BAE+VxHDb6m/O+tP7veaiPaztPBEyygXzBgvoziuIWu5xNZi0YW5wHbfASgX/8CU4fww6vZRIg1Kv6OO05D9z5obk6JVzYMpWReuamd7+tHpHUfX3uIk8ITP5r2EJLjlKBTpQYPwx8gu3Qs8wb1ckPEqjB2lvNl9mLXDMjsOS8lO8wx7VDZcd+S6po0c20Sm7gC7CR3E3SwwkA2RYRsWdPXmAsUuyDkPeAzDtkamVZunBVB9ZOjVRcDWKsUW7q1yKc9iPE4kLOH5o47Ow5FQGg2lxU3CnTkQ+xB0g38wqs/yS+Y1Qgp6xpBiwmZIKR/S7xVSrIfhbD28wC8xFj4CY+3zotVtCGtfg7D6MxJWbz2i8+z7voR1HoawzSD7P6asO5Cy9pmU/VJOiawmAA1wOKc04EH96+SU7jX8ifGM/qSdU97dnyi4XPbs4DOeXTsW3L0eAGHH2Z1SELigEx1amOvLVG6UphvOCDll/oxUhlw+jncPn5qmmwYc2bBMxO1mmo5ao47dXOXKaTrsqgDeCTVDQy+6J2j0VkiFbovQQ2GhO0cmuvbJn11APOdNQ+stQ/kc2PPod0GUDc3Jel4a3AZlqP0yAHwRZe2J+Ey3RZlxKZRdIME4VsW7IMoGF6p78ogbObOWD9KNrzozeGSiHpidmgC1ExqVEF01oYFdJc1zPeVABN/PTw6tNt8VwEY7g2q7t8HvUtARh3ttP2k9CsYe0EfeNRQ/D8T6C71Z6ieD8nvQld9rM1sbe5qHZGM812aO5kHNc4TEM4RwZmn8Aded7HSMnbKjjfkoVza18UxzyhLPO61m3rdyxcxiQlP8d7j5XBtPNNcDsuVMjLKlly1UturliOLGeyoSDOesSbJmRUGSsF5+kCI/jsKEdxecE+KTmLEoNUQLP/bkwDoKgriv1tEsygZ+tirrtdr5hYsSv6omqPo1OnVWLhDop05P5YJ3qw+yChxXn7Xps/8A \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" new file mode 100644 index 0000000000000000000000000000000000000000..95655ea4138e7aa2dfdf6a0dde8cd48a9dacc0a9 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\351\223\276\350\241\2502.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\351\223\276\350\241\2502.png" new file mode 100644 index 0000000000000000000000000000000000000000..9fe82753c809e2d06b5049394b5b7998959185a1 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\351\223\276\350\241\2502.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\345\276\252\347\216\257\351\223\276\350\241\250.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\345\276\252\347\216\257\351\223\276\350\241\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..9d134bcbea0f675788238856fa64c71e412f1232 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\345\276\252\347\216\257\351\223\276\350\241\250.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\351\223\276\350\241\250.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\351\223\276\350\241\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..ffb3b3efa0e3324f8ad556b0b1ed2a8d3a2f20ab Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\351\223\276\350\241\250.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\276\252\347\216\257\351\230\237\345\210\227-\345\240\206\346\273\241.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\276\252\347\216\257\351\230\237\345\210\227-\345\240\206\346\273\241.png" new file mode 100644 index 0000000000000000000000000000000000000000..225c46d779712475fbe27efe68c2be02097597cd Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\276\252\347\216\257\351\230\237\345\210\227-\345\240\206\346\273\241.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..923e8c042d8041cd0086178e354a2301d0425255 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.png" new file mode 100644 index 0000000000000000000000000000000000000000..2d704d564c63f450ffa4bb2ddeb37c5e51f31d75 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.drawio" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..25a0512ed99ec34122e1dadc14445f70a4f0b82d --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.drawio" @@ -0,0 +1 @@ +7Vpbc6M2FP41PCbDHfNobCedTne60+xMm77JIGN1BaJCvu2vrwTiToLt4Mt0sw8bdJCOpO87+o4koxizaP9MQbL+QgKIFV0N9ooxV3Rd01Sb/xGWQ25xVTU3hBQFslJleEE/oDQW1TYogGmjIiMEM5Q0jT6JY+izhg1QSnbNaiuCm70mIIQdw4sPcNf6JwrYOrdOdKey/wJRuC561mw3fxOBorKcSboGAdnVTMZCMWaUEJY/RfsZxAK8Ape83dMbb8uBURizYxq8vk42//y9/DZPbY8eovTLD8d6kF62AG/khOVg2aFAYLdGDL4kwBflHWdZMbw1izAvafwRpEmO+wrtIe/KWyGMZwQTmjU3VquV7vvcnjJKvsPam8Be2pbN38gxQMrg/s3JaSVkPNYgiSCjB15FNtAdibIMM82W5V1FWhlT6xphpRHIQAlL3xWW/EHCeQK0+jC0MA6mIkZ5KSYxbAJ7Lo4w6ET0IIo1lKwekAobhRgwtG267wNO9vCVIN7xMEmFi5RsqA9lq3ooDzhyW34YoCFkHT8ZjeWsz2fW+GS2RYjpuI/WONyak5tya94tt3wMGYRDan6rGDBai1JXR1rdHUdvhAAnBRxq1RJRIX0naCf9/VQRlXscNb6su42vm2lHmwZzrLhpO7pU3JhXiBv7M24GaDCMkeKm4+hCcVP0c9G4cT7jZmj5tncYZ+vNkVuVD+uNe4W4mQzHDSWbOBBHvrnKaR84Jt7kVGg2gStwHDoTtvPGaEdC9/8AqqU1UXVvjWrR2ZEa52OQpshvItlBTM3+8TdCyF6kI0LZmoQkBnhRWT1/Q7cZY8IRB5Ee/hL0PRpuUX7l5Qf1UXWswjLfS4bz0qFe+gop4shAKo3HCqqu5oLzHlTmLZW39FOcEc89IRhtRxfK2O0BW+rAsPR3ql9Gp7XuDV6ySdedBcDXO3sv5GXmryuKNAGMwlisGx5tIiY9oR7IB3gqX0QoCLKF0KdWTT1bkZjJO17dGunmz25lx64cOT0h3Y6Y8dRI7xJCkp+HD1Mz7ouP027rTswObQDPzhb1VKE+DuWJEVOCe8uMYKoDQn70raHVv9UbeyuunpQQ2lulqySEI24w73+T2fmpoEdGrrzL7Lm5W9jKVFUmE2XhKlNN8Wyx4M4QewGe4y5zRWnDDe0M7juXffuNhVzjSzOvqvs9N2YVX5biTRWXU6ouCWMk+qSsuva6HWWnXVZ9MFUfm0Nvdlpq5RrDNIvf2D58w2m2HF34RzXtiNukn5dYrc3GubR2HF2a1r77LBsL3QzQtkGv/e9GfOfiCVl9kLo45TUwXLHqLX8K5d/MS5qAuNeNoPwhzTgXXjQr2fd4WZiKN1fcmbKYKHxDOLGUxZPizRR3+hIDhGcYRUteGURCbuNlmtS65oDkvTdHxM3Z1JpWMZwPjPMCmBUIWGLW01kGBZ+4k1k4CE457/z/Z8TWm2WJz69gC57Fl1dnwcGtOSKFeTC3jpkpy++shONQSIl89kkkRCXf1NY0QzQi3CNiYh25oojBEmIP+N/DzHf7UM4b9wlR3orQANKeFk8gQlj08AwoiEgcFMOQwKiS6t/LoThqzqeP4vAb31Yac6My/CaioGHx8p1Mw/aHVC9jpLvf1l3LBXflvFh9G5crVvWFobH4Dw== \ No newline at end of file diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.png" new file mode 100644 index 0000000000000000000000000000000000000000..07bdb36a2c6b876727f99bb0588e746e98903ccf Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227.png" new file mode 100644 index 0000000000000000000000000000000000000000..2fa16dcdb3a374038ba9029d112bfee06eaada26 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\241\272\345\272\217\351\230\237\345\210\227\345\201\207\346\272\242\345\207\272.png" "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\241\272\345\272\217\351\230\237\345\210\227\345\201\207\346\272\242\345\207\272.png" new file mode 100644 index 0000000000000000000000000000000000000000..f3b01def26620f5d7cbc86c1f414cdfbf60b9ab8 Binary files /dev/null and "b/docs/dataStructures-algorithms/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\241\272\345\272\217\351\230\237\345\210\227\345\201\207\346\272\242\345\207\272.png" differ diff --git "a/docs/dataStructures-algorithms/data-structure/\345\233\276.md" "b/docs/dataStructures-algorithms/data-structure/\345\233\276.md" new file mode 100644 index 0000000000000000000000000000000000000000..71a00d8f4f229cbbf688fba47a4f1f2d79b01390 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/\345\233\276.md" @@ -0,0 +1,153 @@ +# 图 + +> 开头还是求点赞,求转发!原创优质公众号,希望大家能让更多人看到我们的文章。 +> +> 图片都是我们手绘的,可以说非常用心了! + +图是一种较为复杂的非线性结构。 **为啥说其较为复杂呢?** + +根据前面的内容,我们知道: + +- 线性数据结构的元素满足唯一的线性关系,每个元素(除第一个和最后一个外)只有一个直接前趋和一个直接后继。 +- 树形数据结构的元素之间有着明显的层次关系。 + +但是,树形结构的元素之间的关系是任意的。 + +**何为图呢?** 简单来说,图就是由顶点的有穷非空集合和顶点之间的边组成的集合。通常表示为:**G(V,E)**,其中,G表示一个图,V表示顶点的集合,E表示边的集合。 + +下图所展示的就是图这种数据结构,并且还是一张有向图。 + +![图](pictures/图/图.png) + +图在我们日常生活中的例子很多!比如我们在社交软件上好友关系就可以用图来表示。 + +## 图的基本概念 + +### 顶点 +图中的数据元素,我们称之为顶点,图至少有一个顶点(非空有穷集合) + +对应到好友关系图,每一个用户就代表一个顶点。 + +### 边 +顶点之间的关系用边表示。 + +对应到好友关系图,两个用户是好友的话,那两者之间就存在一条边。 + +### 度 +度表示一个顶点包含多少条边,在有向图中,还分为出度和入度,出度表示从该顶点出去的边的条数,入度表示进入该顶点的边的条数。 + +对应到好友关系图,度就代表了某个人的好友数量。 + +### 无向图和有向图 +边表示的是顶点之间的关系,有的关系是双向的,比如同学关系,A是B的同学,那么B也肯定是A的同学,那么在表示A和B的关系时,就不用关注方向,用不带箭头的边表示,这样的图就是无向图。 + +有的关系是有方向的,比如父子关系,师生关系,微博的关注关系,A是B的爸爸,但B肯定不是A的爸爸,A关注B,B不一定关注A。在这种情况下,我们就用带箭头的边表示二者的关系,这样的图就是有向图。 + +### 无权图和带权图 + +对于一个关系,如果我们只关心关系的有无,而不关心关系有多强,那么就可以用无权图表示二者的关系。 + +对于一个关系,如果我们既关心关系的有无,也关心关系的强度,比如描述地图上两个城市的关系,需要用到距离,那么就用带权图来表示,带权图中的每一条边一个数值表示权值,代表关系的强度。 + +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1*FvCzzcpYVwyB759QKoDCOQ.png) + +## 图的存储 +### 邻接矩阵存储 +邻接矩阵将图用二维矩阵存储,是一种较为直观的表示方式。 + +如果第i个顶点和第j个顶点之间有关系,且关系权值为n,则 `A[i][j]=n` 。 + +在无向图中,我们只关心关系的有无,所以当顶点i和顶点j有关系时,`A[i][j]`=1,当顶点i和顶点j没有关系时,`A[i][j]`=0。如下图所示: + +![无向图的邻接矩阵存储](pictures/图/无向图的邻接矩阵存储.png) + +值得注意的是:**无向图的邻接矩阵是一个对称矩阵,因为在无向图中,顶点i和顶点j有关系,则顶点j和顶点i必有关系。** + +![有向图的邻接矩阵存储](pictures/图/有向图的邻接矩阵存储.png) + +邻接矩阵存储的方式优点是简单直接(直接使用一个二维数组即可),并且,在获取两个定点之间的关系的时候也非常高效(直接获取指定位置的数组元素的值即可)。但是,这种存储方式的缺点也比较明显,那就是比较浪费空间, + +### 邻接表存储 + +针对上面邻接矩阵比较浪费内存空间的问题,诞生了图的另外一种存储方法—**邻接表** 。 + +邻接链表使用一个链表来存储某个顶点的所有后继相邻顶点。对于图中每个顶点Vi,把所有邻接于Vi的顶点Vj链成一个单链表,这个单链表称为顶点Vi的 **邻接表**。如下图所示: + + + +![无向图的邻接表存储](pictures/图/无向图的邻接表存储.png) + + + +![有向图的邻接表存储](pictures/图/有向图的邻接表存储.png) + +大家可以数一数邻接表中所存储的元素的个数以及图中边的条数,你会发现: + +- 在无向图中,邻接表元素个数等于边的条数的两倍,如左图所示的无向图中,边的条数为7,邻接表存储的元素个数为14。 +- 在有向图中,邻接表元素个数等于边的条数,如右图所示的有向图中,边的条数为8,邻接表存储的元素个数为8。 + +## 图的搜索 +### 广度优先搜索 +广度优先搜索就像水面上的波纹一样一层一层向外扩展,如下图所示: + +![广度优先搜索图示](pictures/图/广度优先搜索图示.png) + +**广度优先搜索的具体实现方式用到了之前所学过的线性数据结构——队列** 。具体过程如下图所示: + +**第1步:** + +![广度优先搜索1](pictures/图/广度优先搜索1.png) + +**第2步:** + +![广度优先搜索2](pictures/图/广度优先搜索2.png) + +**第3步:** + +![广度优先搜索3](pictures/图/广度优先搜索3.png) + +**第4步:** + +![广度优先搜索4](pictures/图/广度优先搜索4.png) + +**第5步:** + +![广度优先搜索5](pictures/图/广度优先搜索5.png) + +**第6步:** + +![广度优先搜索6](pictures/图/广度优先搜索6.png) + +### 深度优先搜索 + +深度优先搜索就是“一条路走到黑”,从源顶点开始,一直走到没有后继节点,才回溯到上一顶点,然后继续“一条路走到黑”,如下图所示: + +![深度优先搜索图示](pictures/图/深度优先搜索图示.png) + + +**和广度优先搜索类似,深度优先搜索的具体实现用到了另一种线性数据结构——栈** 。具体过程如下图所示: + +**第1步:** + +![深度优先搜索1](pictures/图/深度优先搜索1.png) + +**第2步:** + +![深度优先搜索1](pictures/图/深度优先搜索2.png) + +**第3步:** + +![深度优先搜索1](pictures/图/深度优先搜索3.png) + +**第4步:** + +![深度优先搜索1](pictures/图/深度优先搜索4.png) + +**第5步:** + +![深度优先搜索1](pictures/图/深度优先搜索5.png) + +**第6步:** + +![深度优先搜索1](pictures/图/深度优先搜索6.png) + diff --git "a/docs/dataStructures-algorithms/data-structure/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/docs/dataStructures-algorithms/data-structure/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 0000000000000000000000000000000000000000..c592a9ea1acd8d6d2594d1e299616e7e64e18199 --- /dev/null +++ "b/docs/dataStructures-algorithms/data-structure/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,310 @@ +# 线性数据结构 + +> 开头还是求点赞,求转发!原创优质公众号,希望大家能让更多人看到我们的文章。 +> +> 图片都是我们手绘的,可以说非常用心了! + +## 1. 数组 + +**数组(Array)** 是一种很常见的数据结构。它由相同类型的元素(element)组成,并且是使用一块连续的内存来存储。 + +我们直接可以利用元素的索引(index)可以计算出该元素对应的存储地址。 + +数组的特点是:**提供随机访问** 并且容量有限。 + +```java +假如数组的长度为 n。 +访问:O(1)//访问特定位置的元素 +插入:O(n )//最坏的情况发生在插入发生在数组的首部并需要移动所有元素时 +删除:O(n)//最坏的情况发生在删除数组的开头发生并需要移动第一元素后面所有的元素时 +``` + +![数组](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/数组.png) + +## 2. 链表 + +### 2.1. 链表简介 + +**链表(LinkedList)** 虽然是一种线性表,但是并不会按线性的顺序存储数据,使用的不是连续的内存空间来存储数据。 + +链表的插入和删除操作的复杂度为 O(1) ,只需要知道目标位置元素的上一个元素即可。但是,在查找一个节点或者访问特定位置的节点的时候复杂度为 O(n) 。 + +使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但链表不会节省空间,相比于数组会占用更多的空间,因为链表中每个节点存放的还有指向其他节点的指针。除此之外,链表不具有数组随机读取的优点。 + +### 2.2. 链表分类 + +**常见链表分类:** + +1. 单链表 +2. 双向链表 +3. 循环链表 +4. 双向循环链表 + +```java +假如链表中有n个元素。 +访问:O(n)//访问特定位置的元素 +插入删除:O(1)//必须要要知道插入元素的位置 +``` + +#### 2.2.1. 单链表 + +**单链表** 单向链表只有一个方向,结点只有一个后继指针 next 指向后面的节点。因此,链表这种数据结构通常在物理内存上是不连续的。我们习惯性地把第一个结点叫作头结点,链表通常有一个不保存任何值的 head 节点(头结点),通过头结点我们可以遍历整个链表。尾结点通常指向 null。 + +![单链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/单链表2.png) + +#### 2.2.2. 循环链表 + +**循环链表** 其实是一种特殊的单链表,和单链表不同的是循环链表的尾结点不是指向 null,而是指向链表的头结点。 + +![循环链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/循环链表2.png) + +#### 2.2.3. 双向链表 + +**双向链表** 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。 + +![双向链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/双向链表.png) + +#### 2.2.4. 双向循环链表 + +**双向循环链表** 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。 + +![双向循环链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/双向循环链表.png) + +### 2.3. 应用场景 + +- 如果需要支持随机访问的话,链表没办法做到。 +- 如果需要存储的数据元素的个数不确定,并且需要经常添加和删除数据的话,使用链表比较合适。 +- 如果需要存储的数据元素的个数确定,并且不需要经常添加和删除数据的话,使用数组比较合适。 + +### 2.4. 数组 vs 链表 + +- 数据支持随机访问,而链表不支持。 +- 数组使用的是连续内存空间对 CPU 的缓存机制友好,链表则相反。 +- 数据的大小固定,而链表则天然支持动态扩容。如果声明的数组过小,需要另外申请一个更大的内存空间存放数组元素,然后将原数组拷贝进去,这个操作是比较耗时的! + +## 3. 栈 + +### 3.1. 栈简介 + +**栈** (stack)只允许在有序的线性数据集合的一端(称为栈顶 top)进行加入数据(push)和移除数据(pop)。因而按照 **后进先出(LIFO, Last In First Out)** 的原理运作。**在栈中,push 和 pop 的操作都发生在栈顶。** + +栈常用一维数组或链表来实现,用数组实现的栈叫作 **顺序栈** ,用链表实现的栈叫作 **链式栈** 。 + +```java +假设堆栈中有n个元素。 +访问:O(n)//最坏情况 +插入删除:O(1)//顶端插入和删除元素 +``` + +![栈](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/栈.png) + +### 3.2. 栈的常见应用常见应用场景 + +当我们我们要处理的数据只涉及在一端插入和删除数据,并且满足 **后进先出(LIFO, Last In First Out)** 的特性时,我们就可以使用栈这个数据结构。 + +#### 3.2.1. 实现浏览器的回退和前进功能 + +我们只需要使用两个栈(Stack1 和 Stack2)和就能实现这个功能。比如你按顺序查看了 1,2,3,4 这四个页面,我们依次把 1,2,3,4 这四个页面压入 Stack1 中。当你想回头看 2 这个页面的时候,你点击回退按钮,我们依次把 4,3 这两个页面从 Stack1 弹出,然后压入 Stack2 中。假如你又想回到页面 3,你点击前进按钮,我们将 3 页面从 Stack2 弹出,然后压入到 Stack1 中。示例图如下: + +![栈实现浏览器倒退和前进](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/栈实现浏览器倒退和前进.png) + +#### 3.2.2. 检查符号是否成对出现 + +> 给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串,判断该字符串是否有效。 +> +> 有效字符串需满足: +> +> 1. 左括号必须用相同类型的右括号闭合。 +> 2. 左括号必须以正确的顺序闭合。 +> +> 比如 "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]" 、"([)]" 则不是。 + +这个问题实际是 Leetcode 的一道题目,我们可以利用栈 `Stack` 来解决这个问题。 + +1. 首先我们将括号间的对应规则存放在 `Map` 中,这一点应该毋容置疑; +2. 创建一个栈。遍历字符串,如果字符是左括号就直接加入`stack`中,否则将`stack` 的栈顶元素与这个括号做比较,如果不相等就直接返回 false。遍历结束,如果`stack`为空,返回 `true`。 + +```java +public boolean isValid(String s){ + // 括号之间的对应规则 + HashMap mappings = new HashMap(); + mappings.put(')', '('); + mappings.put('}', '{'); + mappings.put(']', '['); + Stack stack = new Stack(); + char[] chars = s.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (mappings.containsKey(chars[i])) { + char topElement = stack.empty() ? '#' : stack.pop(); + if (topElement != mappings.get(chars[i])) { + return false; + } + } else { + stack.push(chars[i]); + } + } + return stack.isEmpty(); +} +``` + +#### 3.2.3. 反转字符串 + +将字符串中的每个字符先入栈再出栈就可以了。 + +#### 3.2.4. 维护函数调用 + +最后一个被调用的函数必须先完成执行,符合栈的 **后进先出(LIFO, Last In First Out)** 特性。 + +### 3.3. 栈的实现 + +栈既可以通过数组实现,也可以通过链表来实现。不管基于数组还是链表,入栈、出栈的时间复杂度都为 O(1)。 + +下面我们使用数组来实现一个栈,并且这个栈具有`push()`、`pop()`(返回栈顶元素并出栈)、`peek()` (返回栈顶元素不出栈)、`isEmpty()`、`size()`这些基本的方法。 + +> 提示:每次入栈之前先判断栈的容量是否够用,如果不够用就用`Arrays.copyOf()`进行扩容; + +```java +public class MyStack { + private int[] storage;//存放栈中元素的数组 + private int capacity;//栈的容量 + private int count;//栈中元素数量 + private static final int GROW_FACTOR = 2; + + //不带初始容量的构造方法。默认容量为8 + public MyStack() { + this.capacity = 8; + this.storage=new int[8]; + this.count = 0; + } + + //带初始容量的构造方法 + public MyStack(int initialCapacity) { + if (initialCapacity < 1) + throw new IllegalArgumentException("Capacity too small."); + + this.capacity = initialCapacity; + this.storage = new int[initialCapacity]; + this.count = 0; + } + + //入栈 + public void push(int value) { + if (count == capacity) { + ensureCapacity(); + } + storage[count++] = value; + } + + //确保容量大小 + private void ensureCapacity() { + int newCapacity = capacity * GROW_FACTOR; + storage = Arrays.copyOf(storage, newCapacity); + capacity = newCapacity; + } + + //返回栈顶元素并出栈 + private int pop() { + if (count == 0) + throw new IllegalArgumentException("Stack is empty."); + count--; + return storage[count]; + } + + //返回栈顶元素不出栈 + private int peek() { + if (count == 0){ + throw new IllegalArgumentException("Stack is empty."); + }else { + return storage[count-1]; + } + } + + //判断栈是否为空 + private boolean isEmpty() { + return count == 0; + } + + //返回栈中元素的个数 + private int size() { + return count; + } + +} +``` + +验证 + +```java +MyStack myStack = new MyStack(3); +myStack.push(1); +myStack.push(2); +myStack.push(3); +myStack.push(4); +myStack.push(5); +myStack.push(6); +myStack.push(7); +myStack.push(8); +System.out.println(myStack.peek());//8 +System.out.println(myStack.size());//8 +for (int i = 0; i < 8; i++) { + System.out.println(myStack.pop()); +} +System.out.println(myStack.isEmpty());//true +myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty. +``` + +## 4. 队列 + +### 4.1. 队列简介 + +**队列** 是 **先进先出( FIFO,First In, First Out)** 的线性表。在具体应用中通常用链表或者数组来实现,用数组实现的队列叫作 **顺序队列** ,用链表实现的队列叫作 **链式队列** 。**队列只允许在后端(rear)进行插入操作也就是 入队 enqueue,在前端(front)进行删除操作也就是出队 dequeue** + +队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。 + +```java +假设队列中有n个元素。 +访问:O(n)//最坏情况 +插入删除:O(1)//后端插入前端删除元素 +``` + +![队列](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/队列.png) + +### 4.2. 队列分类 + +#### 4.2.1. 单队列 + +单队列就是常见的队列, 每次添加元素时,都是添加到队尾。单队列又分为 **顺序队列(数组实现)** 和 **链式队列(链表实现)**。 + +**顺序队列存在“假溢出”的问题也就是明明有位置却不能添加的情况。** + +假设下图是一个顺序队列,我们将前两个元素 1,2 出队,并入队两个元素 7,8。当进行入队、出队操作的时候,front 和 rear 都会持续往后移动,当 rear 移动到最后的时候,我们无法再往队列中添加数据,即使数组中还有空余空间,这种现象就是 **”假溢出“** 。除了假溢出问题之外,如下图所示,当添加元素 8 的时候,rear 指针移动到数组之外(越界)。 + +> 为了避免当只有一个元素的时候,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向对头元素,rear 指针指向队列最后一个元素的下一个位置,这样当 front 等于 rear 时,此队列不是还剩一个元素,而是空队列。——From 《大话数据结构》 + +![顺序队列假溢出](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/顺序队列假溢出1.png) + +#### 4.2.2. 循环队列 + +循环队列可以解决顺序队列的假溢出和越界问题。解决办法就是:从头开始,这样也就会形成头尾相接的循环,这也就是循环队列名字的由来。 + +还是用上面的图,我们将 rear 指针指向数组下标为 0 的位置就不会有越界问题了。当我们再向队列中添加元素的时候, rear 向后移动。 + +![循环队列](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/循环队列.png) + +顺序队列中,我们说 `front==rear` 的时候队列为空,循环队列中则不一样,也可能为满,如上图所示。解决办法有两种: + +1. 可以设置一个标志变量 `flag`,当 `front==rear` 并且 `flag=0` 的时候队列为空,当`front==rear` 并且 `flag=1` 的时候队列为满。 +2. 队列为空的时候就是 `front==rear` ,队列满的时候,我们保证数组还有一个空闲的位置,rear 就指向这个空闲位置,如下图所示,那么现在判断队列是否为满的条件就是: `(rear+1) % QueueSize= front` 。 + +![循环队列-队满](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/循环队列-堆满.png) + +### 4.3. 常见应用场景 + +当我们需要按照一定顺序来处理数据的时候可以考虑使用队列这个数据结构。 + +- **阻塞队列:** 阻塞队列可以看成在队列基础上加了阻塞操作的队列。当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现“生产者 - 消费者“模型。 +- **线程池中的请求/任务队列:** 线程池中没有空闲线程时,新的任务请求线程资源时,线程池该如何处理呢?答案是将这些请求放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如 :`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`。但是有界队列就不一样了,当队列满的话后面再有任务/请求就会拒绝,在 Java 中的体现就是会抛出`java.util.concurrent.RejectedExecutionException` 异常。 +- Linux 内核进程队列(按优先级排队) +- 现实生活中的派对,播放器上的播放列表; +- 消息队列 +- 等等...... diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" similarity index 97% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" rename to "docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" index 938f4a48f1a526dc59a0315555ac259353abe292..af63c584c2fb61dd2303b98e2ba04f20937224ab 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" +++ "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" @@ -15,11 +15,13 @@ -## 说明 -- 本文作者:wwwxmu -- 原文地址:https://www.weiweiblog.cn/13string/ -- 作者的博客站点:https://www.weiweiblog.cn/ (推荐哦!) +> 授权转载! +> +> - 本文作者:wwwxmu +> - 原文地址:https://www.weiweiblog.cn/13string/ + + 考虑到篇幅问题,我会分两次更新这个内容。本篇文章只是原文的一部分,我在原文的基础上增加了部分内容以及修改了部分代码和注释。另外,我增加了爱奇艺 2018 秋招 Java:`求给定合法括号序列的深度` 这道题。所有代码均编译成功,并带有注释,欢迎各位享用! @@ -112,7 +114,7 @@ public class Main { public static String replaceSpace(String[] strs) { // 如果检查值不合法及就返回空串 - if (!chechStrs(strs)) { + if (!checkStrs(strs)) { return ""; } // 数组长度 @@ -137,7 +139,6 @@ public class Main { private static boolean chechStrs(String[] strs) { boolean flag = false; - // 注意:=是赋值,==是判断 if (strs != null) { // 遍历strs检查元素值 for (int i = 0; i < strs.length; i++) { @@ -145,6 +146,7 @@ public class Main { flag = true; } else { flag = false; + break; } } } @@ -190,7 +192,7 @@ public class Main { 我们上面已经知道了什么是回文串?现在我们考虑一下可以构成回文串的两种情况: - 字符出现次数为双数的组合 -- 字符出现次数为双数的组合+一个只出现一次的字符 +- **字符出现次数为偶数的组合+单个字符中出现次数最多且为奇数次的字符** (参见 **[issue665](https://github.com/Snailclimb/JavaGuide/issues/665)** ) 统计字符出现的次数即可,双数才能构成回文。因为允许中间一个数单独出现,比如“abcba”,所以如果最后有字母落单,总长度可以加 1。首先将字符串转变为字符数组。然后遍历该数组,判断对应字符是否在hashset中,如果不在就加进去,如果在就让count++,然后移除该字符!这样就能找到出现次数为双数的字符个数。 @@ -463,7 +465,7 @@ public class Main { return 0; } } - return flag == 1 ? res : -res; + return flag != 2 ? res : -res; } diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" similarity index 99% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" rename to "docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" index 79b74441debfec34a332b09807e85fc88310a324..9daa0fc159c1ef76ec7790c9fb36bd4773358198 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" +++ "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" @@ -225,7 +225,7 @@ public class Solution { while (node1 != null) { node1 = node1.next; count++; - if (k < 1 && node1 != null) { + if (k < 1) { node2 = node2.next; } k--; @@ -324,7 +324,7 @@ public class Solution { **进阶——一次遍历法:** -> **链表中倒数第N个节点也就是正数第(L-N+1)个节点。 +> 链表中倒数第N个节点也就是正数第(L-N+1)个节点。 其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点) diff --git "a/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" new file mode 100644 index 0000000000000000000000000000000000000000..b7b077fb041f0fd188965b0c5aab15b5f5d5130e --- /dev/null +++ "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" @@ -0,0 +1,677 @@ +### 一 斐波那契数列 + +#### **题目描述:** + +大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。 +n<=39 + +#### **问题分析:** + +可以肯定的是这一题通过递归的方式是肯定能做出来,但是这样会有一个很大的问题,那就是递归大量的重复计算会导致内存溢出。另外可以使用迭代法,用fn1和fn2保存计算过程中的结果,并复用起来。下面我会把两个方法示例代码都给出来并给出两个方法的运行时间对比。 + +#### **示例代码:** + +**采用迭代法:** + +```java + int Fibonacci(int number) { + if (number <= 0) { + return 0; + } + if (number == 1 || number == 2) { + return 1; + } + int first = 1, second = 1, third = 0; + for (int i = 3; i <= number; i++) { + third = first + second; + first = second; + second = third; + } + return third; + } +``` + +**采用递归:** + +```java + public int Fibonacci(int n) { + + if (n <= 0) { + return 0; + } + if (n == 1||n==2) { + return 1; + } + + return Fibonacci(n - 2) + Fibonacci(n - 1); + + } +``` + +### 二 跳台阶问题 + +#### **题目描述:** + +一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 + +#### **问题分析:** + +**正常分析法:** +a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1); +b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2) +c.由a,b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2) +d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2 +**找规律分析法:** +f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出f(n) = f(n-1) + f(n-2)的规律。 +但是为什么会出现这样的规律呢?假设现在6个台阶,我们可以从第5跳一步到6,这样的话有多少种方案跳到5就有多少种方案跳到6,另外我们也可以从4跳两步跳到6,跳到4有多少种方案的话,就有多少种方案跳到6,其他的不能从3跳到6什么的啦,所以最后就是f(6) = f(5) + f(4);这样子也很好理解变态跳台阶的问题了。 + +**所以这道题其实就是斐波那契数列的问题。** +代码只需要在上一题的代码稍做修改即可。和上一题唯一不同的就是这一题的初始元素变为 1 2 3 5 8.....而上一题为1 1 2 3 5 .......。另外这一题也可以用递归做,但是递归效率太低,所以我这里只给出了迭代方式的代码。 + +#### **示例代码:** + +```java + int jumpFloor(int number) { + if (number <= 0) { + return 0; + } + if (number == 1) { + return 1; + } + if (number == 2) { + return 2; + } + int first = 1, second = 2, third = 0; + for (int i = 3; i <= number; i++) { + third = first + second; + first = second; + second = third; + } + return third; + } +``` + +### 三 变态跳台阶问题 + +#### **题目描述:** + +一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 + +#### **问题分析:** + +假设n>=2,第一步有n种跳法:跳1级、跳2级、到跳n级 +跳1级,剩下n-1级,则剩下跳法是f(n-1) +跳2级,剩下n-2级,则剩下跳法是f(n-2) +...... +跳n-1级,剩下1级,则剩下跳法是f(1) +跳n级,剩下0级,则剩下跳法是f(0) +所以在n>=2的情况下: +f(n)=f(n-1)+f(n-2)+...+f(1) +因为f(n-1)=f(n-2)+f(n-3)+...+f(1) +所以f(n)=2*f(n-1) 又f(1)=1,所以可得**f(n)=2^(number-1)** + +#### **示例代码:** + +```java + int JumpFloorII(int number) { + return 1 << --number;//2^(number-1)用位移操作进行,更快 + } +``` + +#### **补充:** + +**java中有三种移位运算符:** + +1. “<<” : **左移运算符**,等同于乘2的n次方 +2. “>>”: **右移运算符**,等同于除2的n次方 +3. “>>>” **无符号右移运算符**,不管移动前最高位是0还是1,右移后左侧产生的空位部分都以0来填充。与>>类似。 + 例: + int a = 16; + int b = a << 2;//左移2,等同于16 * 2的2次方,也就是16 * 4 + int c = a >> 2;//右移2,等同于16 / 2的2次方,也就是16 / 4 + +### 四 二维数组查找 + +#### **题目描述:** + +在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 + +#### **问题解析:** + +这一道题还是比较简单的,我们需要考虑的是如何做,效率最快。这里有一种很好理解的思路: + +> 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增, +> 因此从左下角开始查找,当要查找数字比左下角数字大时。右移 +> 要查找数字比左下角数字小时,上移。这样找的速度最快。 + +#### **示例代码:** + +```java + public boolean Find(int target, int [][] array) { + //基本思路从左下角开始找,这样速度最快 + int row = array.length-1;//行 + int column = 0;//列 + //当行数大于0,当前列数小于总列数时循环条件成立 + while((row >= 0)&& (column< array[0].length)){ + if(array[row][column] > target){ + row--; + }else if(array[row][column] < target){ + column++; + }else{ + return true; + } + } + return false; + } +``` + +### 五 替换空格 + +#### **题目描述:** + +请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 + +#### **问题分析:** + +这道题不难,我们可以通过循环判断字符串的字符是否为空格,是的话就利用append()方法添加追加“%20”,否则还是追加原字符。 + +或者最简单的方法就是利用: replaceAll(String regex,String replacement)方法了,一行代码就可以解决。 + +#### **示例代码:** + +**常规做法:** + +```java + public String replaceSpace(StringBuffer str) { + StringBuffer out=new StringBuffer(); + for (int i = 0; i < str.toString().length(); i++) { + char b=str.charAt(i); + if(String.valueOf(b).equals(" ")){ + out.append("%20"); + }else{ + out.append(b); + } + } + return out.toString(); + } +``` + +**一行代码解决:** + +```java + public String replaceSpace(StringBuffer str) { + //return str.toString().replaceAll(" ", "%20"); + //public String replaceAll(String regex,String replacement) + //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。 + //\ 转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\". String类型中的空格用“\s”表示,所以我这里猜测"\\s"就是代表空格的意思 + return str.toString().replaceAll("\\s", "%20"); + } + +``` + +### 六 数值的整数次方 + +#### **题目描述:** + +给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 + +#### **问题解析:** + +这道题算是比较麻烦和难一点的一个了。我这里采用的是**二分幂**思想,当然也可以采用**快速幂**。 +更具剑指offer书中细节,该题的解题思路如下: +1.当底数为0且指数<0时,会出现对0求倒数的情况,需进行错误处理,设置一个全局变量; +2.判断底数是否等于0,由于base为double型,所以不能直接用==判断 +3.优化求幂函数(二分幂)。 +当n为偶数,a^n =(a^n/2)*(a^n/2); +当n为奇数,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a。时间复杂度O(logn) + +**时间复杂度**:O(logn) + +#### **示例代码:** + +```java +public class Solution { + boolean invalidInput=false; + public double Power(double base, int exponent) { + //如果底数等于0并且指数小于0 + //由于base为double型,不能直接用==判断 + if(equal(base,0.0)&&exponent<0){ + invalidInput=true; + return 0.0; + } + int absexponent=exponent; + //如果指数小于0,将指数转正 + if(exponent<0) + absexponent=-exponent; + //getPower方法求出base的exponent次方。 + double res=getPower(base,absexponent); + //如果指数小于0,所得结果为上面求的结果的倒数 + if(exponent<0) + res=1.0/res; + return res; + } + //比较两个double型变量是否相等的方法 + boolean equal(double num1,double num2){ + if(num1-num2>-0.000001&&num1-num2<0.000001) + return true; + else + return false; + } + //求出b的e次方的方法 + double getPower(double b,int e){ + //如果指数为0,返回1 + if(e==0) + return 1.0; + //如果指数为1,返回b + if(e==1) + return b; + //e>>1相等于e/2,这里就是求a^n =(a^n/2)*(a^n/2) + double result=getPower(b,e>>1); + result*=result; + //如果指数n为奇数,则要再乘一次底数base + if((e&1)==1) + result*=b; + return result; + } +} +``` + +当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为O(n),这样没有前一种方法效率高。 + +```java + // 使用累乘 + public double powerAnother(double base, int exponent) { + double result = 1.0; + for (int i = 0; i < Math.abs(exponent); i++) { + result *= base; + } + if (exponent >= 0) + return result; + else + return 1 / result; + } +``` + +### 七 调整数组顺序使奇数位于偶数前面 + +#### **题目描述:** + +输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。 + +#### **问题解析:** + +这道题有挺多种解法的,给大家介绍一种我觉得挺好理解的方法: +我们首先统计奇数的个数假设为n,然后新建一个等长数组,然后通过循环判断原数组中的元素为偶数还是奇数。如果是则从数组下标0的元素开始,把该奇数添加到新数组;如果是偶数则从数组下标为n的元素开始把该偶数添加到新数组中。 + +#### **示例代码:** + +时间复杂度为O(n),空间复杂度为O(n)的算法 + +```java +public class Solution { + public void reOrderArray(int [] array) { + //如果数组长度等于0或者等于1,什么都不做直接返回 + if(array.length==0||array.length==1) + return; + //oddCount:保存奇数个数 + //oddBegin:奇数从数组头部开始添加 + int oddCount=0,oddBegin=0; + //新建一个数组 + int[] newArray=new int[array.length]; + //计算出(数组中的奇数个数)开始添加元素 + for(int i=0;i stack1 = new Stack(); + Stack stack2 = new Stack(); + + //当执行push操作时,将元素添加到stack1 + public void push(int node) { + stack1.push(node); + } + + public int pop() { + //如果两个队列都为空则抛出异常,说明用户没有push进任何元素 + if(stack1.empty()&&stack2.empty()){ + throw new RuntimeException("Queue is empty!"); + } + //如果stack2不为空直接对stack2执行pop操作, + if(stack2.empty()){ + while(!stack1.empty()){ + //将stack1的元素按后进先出push进stack2里面 + stack2.push(stack1.pop()); + } + } + return stack2.pop(); + } +} +``` + +### 十二 栈的压入,弹出序列 + +#### **题目描述:** + +输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) + +#### **题目分析:** + +这道题想了半天没有思路,参考了Alias的答案,他的思路写的也很详细应该很容易看懂。 +作者:Alias +https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106 +来源:牛客网 + +【思路】借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。 + +举例: + +入栈1,2,3,4,5 + +出栈4,5,3,2,1 + +首先1入辅助栈,此时栈顶1≠4,继续入栈2 + +此时栈顶2≠4,继续入栈3 + +此时栈顶3≠4,继续入栈4 + +此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3 + +此时栈顶3≠5,继续入栈5 + +此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3 + +…. +依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。 + + + +#### **考察内容:** + +栈 + +#### **示例代码:** + +```java +import java.util.ArrayList; +import java.util.Stack; +//这道题没想出来,参考了Alias同学的答案:https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106 +public class Solution { + public boolean IsPopOrder(int [] pushA,int [] popA) { + if(pushA.length == 0 || popA.length == 0) + return false; + Stack s = new Stack(); + //用于标识弹出序列的位置 + int popIndex = 0; + for(int i = 0; i< pushA.length;i++){ + s.push(pushA[i]); + //如果栈不为空,且栈顶元素等于弹出序列 + while(!s.empty() &&s.peek() == popA[popIndex]){ + //出栈 + s.pop(); + //弹出序列向后一位 + popIndex++; + } + } + return s.empty(); + } +} +``` \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" similarity index 35% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" rename to "docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" index 9889644f39c7a73eeeb57c61f2a5e54dc4ad8420..314b2a855ab96b9dc33fb858823636d8095968b1 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ "b/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -1,4 +1,5 @@ - +> 注意!!!这部分内容会进行重构,以下内容仅作为参考。 +> - [Queue](#queue) - [什么是队列](#什么是队列) @@ -37,16 +38,12 @@ Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, Priori Queue 用来存放 等待处理元素 的集合,这种场景一般用于缓冲、并发访问。 除了继承 Collection 接口的一些方法,Queue 还添加了额外的 添加、删除、查询操作。 -### 推荐文章 - -- [Java 集合深入理解(9):Queue 队列](https://blog.csdn.net/u011240877/article/details/52860924) - ## Set ### 什么是 Set Set 继承于 Collection 接口,是一个不允许出现重复元素,并且无序的集合,主要 HashSet 和 TreeSet 两大实现类。 -在判断重复元素的时候,Set 集合会调用 hashCode()和 equal()方法来实现。 +在判断重复元素的时候,HashSet 集合会调用 hashCode()和 equal()方法来实现;TreeSet 集合会调用compareTo方法来实现。 ### 补充:有序集合与无序集合说明 - 有序集合:集合里的元素可以根据 key 或 index 访问 (List、Map) @@ -59,11 +56,6 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 **TreeSet** 是红黑树结构,每一个元素都是树中的一个节点,插入的元素都会进行排序; - -### 推荐文章 - -- [Java集合--Set(基础)](https://www.jianshu.com/p/b48c47a42916) - ## List ### 什么是List @@ -78,105 +70,102 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 **Vector** 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。 -**Stack** 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。相关阅读:[java数据结构与算法之栈(Stack)设计与实现](https://blog.csdn.net/javazejian/article/details/53362993) +**Stack** 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。 + +## 树 + +### 1 二叉树 + +[二叉树](https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) -### ArrayList 和 LinkedList 源码学习 -- [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) -- [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) +(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层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。 + +(2)[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。 + +(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,并且左右两个子树都是一棵平衡二叉树。 -### 推荐阅读 +### 2 完全二叉树 -- [java 数据结构与算法之顺序表与链表深入分析](https://blog.csdn.net/javazejian/article/details/52953190) +[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) + +完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。 +### 3 满二叉树 -## Map +[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同) +国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。 -- [集合框架源码学习之 HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56) -- [ConcurrentHashMap 实现原理及源码分析](https://link.juejin.im/?target=http%3A%2F%2Fwww.cnblogs.com%2Fchengxiao%2Fp%2F6842045.html) +### 堆 -## 树 - * ### 1 二叉树 - - [二叉树](https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) +[数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191) - (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层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。 - - (2)[满二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。 - - (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,并且左右两个子树都是一棵平衡二叉树。 +堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。 - * ### 2 完全二叉树 +### 4 二叉查找树(BST) - [完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) - - 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树 - * ### 3 满二叉树 - - [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同) +[浅谈算法和数据结构: 七 二叉查找树](https://www.yycoding.xyz/post/2014/3/24/introduce-binary-search-tree) - 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。 - * ### 堆 - - [数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191) +二叉查找树的特点: - 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆 - * ### 4 二叉查找树(BST) - - [浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html) +1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值; +2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; +3. 任意节点的左、右子树也分别为二叉查找树; +4. 没有键值相等的节点(no duplicate nodes)。 - 二叉查找树的特点: +### 5 平衡二叉树(Self-balancing binary search tree) - 1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值; - 2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; - 3. 任意节点的左、右子树也分别为二叉查找树。 - 4. 没有键值相等的节点(no duplicate nodes)。 +[ 平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等) - * ### 5 平衡二叉树(Self-balancing binary search tree) - - [ 平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等) - * ### 6 红黑树 +### 6 红黑树 + +红黑树特点: + +1. 每个节点非红即黑; +2. 根节点总是黑色的; +3. 每个叶子节点都是黑色的空节点(NIL节点); +4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定); +5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。 + + +红黑树的应用: - - 红黑树特点: - 1. 每个节点非红即黑; - 2. 根节点总是黑色的; - 3. 每个叶子节点都是黑色的空节点(NIL节点); - 4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定); - 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度) +TreeMap、TreeSet以及JDK1.8的HashMap底层都用到了红黑树。 - - 红黑树的应用: - - TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。 +**为什么要用红黑树?** - - 为什么要用红黑树 - - 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) - - - 推荐文章: - - [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) - - [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错) - - [红黑树深入剖析及Java实现](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队) - * ### 7 B-,B+,B*树 - - [二叉树学习笔记之B树、B+树、B*树 ](https://yq.aliyun.com/articles/38345) - - [《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186) - [《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405) +简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) - B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) - 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 - 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。 - 3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高; - * ### 8 LSM 树 - - [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599) - - B+树最大的性能问题是会产生大量的随机IO +推荐文章: + +- [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) +- [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错) +- [红黑树深入剖析及Java实现](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队) - 为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。 +### 7 B-,B+,B*树 + +[二叉树学习笔记之B树、B+树、B*树 ](https://yq.aliyun.com/articles/38345) + +[《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186) + +[《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405) + +B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) - [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html) +1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 +2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。 +3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高; + +### 8 LSM 树 + +[[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599) + +B+树最大的性能问题是会产生大量的随机IO + +为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。 + +[LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html) ## 图 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" b/docs/database/MySQL Index.md similarity index 46% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" rename to docs/database/MySQL Index.md index 27b82c8bc70dac1d0c3c3edb0c5e2a4d4f1335df..69d606f106e1baea088292d38eb4178e348532fb 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" +++ b/docs/database/MySQL Index.md @@ -1,13 +1,40 @@ -# 思维导图-索引篇 -> 系列思维导图源文件(数据库+架构)以及思维导图制作软件—XMind8 破解安装,公众号后台回复:**“思维导图”** 免费领取!(下面的图片不是很清楚,原图非常清晰,另外提供给大家源文件也是为了大家根据自己需要进行修改) +## Mysql索引主要使用的两种数据结构 -![【思维导图-索引篇】](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/70973487.jpg) +### 哈希索引 -> **下面是我补充的一些内容** +对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。 -# 为什么索引能提高查询速度 +### BTree索引 + + + +## 覆盖索引介绍 + +### 什么是覆盖索引 + +如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢覆盖索引就是把要查询出的列和索引是对应的,不做回表操作! + +### 覆盖索引使用实例 + +现在我创建了索引(username,age),我们执行下面的 sql 语句 + +```sql +select username , age from user where username = 'Java' and age = 22 +``` + +在查询数据的时候:要查询出的列在叶子节点都存在!所以,就不用回表。 + +## 选择索引和编写利用这些索引的查询的3个原则 + +1. 单行访问是很慢的。特别是在机械硬盘存储中(SSD的随机I/O要快很多,不过这一点仍然成立)。如果服务器从存储中读取一个数据块只是为了获取其中一行,那么就浪费了很多工作。最好读取的块中能包含尽可能多所需要的行。使用索引可以创建位置引,用以提升效率。 +2. 按顺序访问范围数据是很快的,这有两个原因。第一,顺序 I/O 不需要多次磁盘寻道,所以比随机I/O要快很多(特别是对机械硬盘)。第二,如果服务器能够按需要顺序读取数据,那么就不再需要额外的排序操作,并且GROUPBY查询也无须再做排序和将行按组进行聚合计算了。 +3. 索引覆盖查询是很快的。如果一个索引包含了查询需要的所有列,那么存储引擎就 + 不需要再回表查找行。这避免了大量的单行访问,而上面的第1点已经写明单行访 + 问是很慢的。 + +## 为什么索引能提高查询速度 > 以下内容整理自: > 地址: https://juejin.im/post/5b55b842f265da0f9e589e79 @@ -28,8 +55,8 @@ MySQL的基本存储结构是页(记录都存在页里边): 所以说,如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句,默认会这样做: -1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** -2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** +1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** +2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** 很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。 @@ -48,65 +75,27 @@ MySQL的基本存储结构是页(记录都存在页里边): 其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。 -# 关于索引其他重要的内容补充 +## 关于索引其他重要的内容补充 > 以下内容整理自:《Java工程师修炼之道》 ### 最左前缀原则 -MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city)o而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下: +MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下: ``` select * from user where name=xx and city=xx ; //可以命中索引 select * from user where name=xx ; // 可以命中索引 -select * from user where city=xx; // 无法命中索引 -``` -这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name =xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的. - -由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDERBY子句也遵循此规则。 - -### 注意避免冗余索引 - -冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 - -MySQLS.7 版本后,可以通过查询 sys 库的 `schemal_r dundant_indexes` 表来查看冗余索引 - -### Mysql如何为表字段添加索引??? - -1.添加PRIMARY KEY(主键索引) - -``` -ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) -``` -2.添加UNIQUE(唯一索引) - -``` -ALTER TABLE `table_name` ADD UNIQUE ( `column` ) +select * from user where city=xx ; // 无法命中索引 ``` - -3.添加INDEX(普通索引) +这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name =xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。 -``` -ALTER TABLE `table_name` ADD INDEX index_name ( `column` ) -``` - -4.添加FULLTEXT(全文索引) - -``` -ALTER TABLE `table_name` ADD FULLTEXT ( `column`) -``` - -5.添加多列索引 +由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDER BY子句也遵循此规则。 -``` -ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` ) -``` +### 注意避免冗余索引 +冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 -# 参考 +MySQL 5.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引 -- 《Java工程师修炼之道》 -- 《MySQL高性能书籍_第3版》 -- https://juejin.im/post/5b55b842f265da0f9e589e79 - diff --git a/docs/database/MySQL.md b/docs/database/MySQL.md new file mode 100644 index 0000000000000000000000000000000000000000..0bf562e8e3d528ce8dd8c830b97b7012f21793fc --- /dev/null +++ b/docs/database/MySQL.md @@ -0,0 +1,284 @@ +## MySQL 基础 + +### 关系型数据库介绍 + +顾名思义,关系型数据库就是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多)。 + +关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一列就存放着一条数据(比如一个用户的信息)。 + +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/5e3c1a71724a38245aa43b02_99bf70d46cc247be878de9d3a88f0c44.png) + +大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID)。 + +**有哪些常见的关系型数据库呢?** + +MySQL、PostgreSQL、Oracle、SQL Server、SQLite(微信本地的聊天记录的存储就是用的 SQLite) ......。 + +### MySQL 介绍 + +![](https://img-blog.csdnimg.cn/20210327143351823.png) + +**MySQL 是一种关系型数据库,主要用于持久化存储我们的系统中的一些数据比如用户信息。** + +由于 MySQL 是开源免费并且比较成熟的数据库,因此,MySQL 被大量使用在各种系统中。任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL 的默认端口号是**3306**。 + +## 存储引擎 + +### 存储引擎相关的命令 + +**查看 MySQL 提供的所有存储引擎** + +```sql +mysql> show engines; +``` + +![查看MySQL提供的所有存储引擎](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/mysql-engines.png) + +从上图我们可以查看出 MySQL 当前默认的存储引擎是 InnoDB,并且在 5.7 版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。 + +**查看 MySQL 当前默认的存储引擎** + +我们也可以通过下面的命令查看默认的存储引擎。 + +```sql +mysql> show variables like '%storage_engine%'; +``` + +**查看表的存储引擎** + +```sql +show table status like "table_name" ; +``` + +![查看表的存储引擎](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/查看表的存储引擎.png) + +### MyISAM 和 InnoDB 的区别 + +![](https://img-blog.csdnimg.cn/20210327145248960.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0MzM3Mjcy,size_16,color_FFFFFF,t_70) + +MySQL 5.5 之前,MyISAM 引擎是 MySQL 的默认存储引擎,可谓是风光一时。 + +虽然,MyISAM 的性能还行,各种特性也还不错(比如全文索引、压缩、空间函数等)。但是,MyISAM 不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。 + +5.5 版本之后,MySQL 引入了 InnoDB(事务性数据库引擎),MySQL 5.5 版本后默认的存储引擎为 InnoDB。小伙子,一定要记好这个 InnoDB ,你每次使用 MySQL 数据库都是用的这个存储引擎吧? + +言归正传!咱们下面还是来简单对比一下两者: + +**1.是否支持行级锁** + +MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。 + +也就说,MyISAM 一锁就是锁住了整张表,这在并发写的情况下是多么滴憨憨啊!这也是为什么 InnoDB 在并发写的时候,性能更牛皮了! + +**2.是否支持事务** + +MyISAM 不提供事务支持。 + +InnoDB 提供事务支持,具有提交(commit)和回滚(rollback)事务的能力。 + +**3.是否支持外键** + +MyISAM 不支持,而 InnoDB 支持。 + +🌈 拓展一下: + +一般我们也是不建议在数据库层面使用外键的,应用层面可以解决。不过,这样会对数据的一致性造成威胁。具体要不要使用外键还是要根据你的项目来决定。 + +**4.是否支持数据库异常崩溃后的安全恢复** + +MyISAM 不支持,而 InnoDB 支持。 + +使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 `redo log` 。 + +🌈 拓展一下: + +- MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。 +- MySQL InnoDB 引擎通过 **锁机制**、**MVCC** 等手段来保证事务的隔离性( 默认支持的隔离级别是 **`REPEATABLE-READ`** )。 +- 保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。 + +**5.是否支持 MVCC** + +MyISAM 不支持,而 InnoDB 支持。 + +讲真,这个对比有点废话,毕竟 MyISAM 连行级锁都不支持。 + +MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提供性能。 + +### 关于 MyISAM 和 InnoDB 的选择问题 + +大多数时候我们使用的都是 InnoDB 存储引擎,在某些读密集的情况下,使用 MyISAM 也是合适的。不过,前提是你的项目不介意 MyISAM 不支持事务、崩溃恢复等缺点(可是~我们一般都会介意啊!)。 + +《MySQL 高性能》上面有一句话这样写到: + +> 不要轻易相信“MyISAM 比 InnoDB 快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB 的速度都可以让 MyISAM 望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。 + +一般情况下我们选择 InnoDB 都是没有问题的,但是某些情况下你并不在乎可扩展能力和并发能力,也不需要事务支持,也不在乎崩溃后的安全恢复问题的话,选择 MyISAM 也是一个不错的选择。但是一般情况下,我们都是需要考虑到这些问题的。 + +因此,对于咱们日常开发的业务系统来说,你几乎找不到什么理由再使用 MyISAM 作为自己的 MySQL 数据库的存储引擎。 + +## 锁机制与 InnoDB 锁算法 + +**MyISAM 和 InnoDB 存储引擎使用的锁:** + +- MyISAM 采用表级锁(table-level locking)。 +- InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁 + +**表级锁和行级锁对比:** + +- **表级锁:** MySQL 中锁定 **粒度最大** 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。 +- **行级锁:** MySQL 中锁定 **粒度最小** 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 + +**InnoDB 存储引擎的锁的算法有三种:** + +- Record lock:单个行记录上的锁 +- Gap lock:间隙锁,锁定一个范围,不包括记录本身 +- Next-key lock:record+gap 锁定一个范围,包含记录本身 + +## 查询缓存 + +执行查询语句的时候,会先查询缓存。不过,MySQL 8.0 版本后移除,因为这个功能不太实用 + +`my.cnf` 加入以下配置,重启 MySQL 开启查询缓存 + +```properties +query_cache_type=1 +query_cache_size=600000 +``` + +MySQL 执行以下命令也可以开启查询缓存 + +```properties +set global query_cache_type=1; +set global query_cache_size=600000; +``` + +如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。 + +缓存建立之后,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。 + +**缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启查询缓存要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,**还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存:** + +```sql +select sql_no_cache count(*) from usr; +``` + +## 事务 + +### 何为事务? + +一言蔽之,**事务是逻辑上的一组操作,要么都执行,要么都不执行。** + +**可以简单举一个例子不?** + +事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是: + +1. 将小明的余额减少 1000 元 +2. 将小红的余额增加 1000 元。 + +事务会把这两个操作就可以看成逻辑上的一个整体,这个整体包含的操作要么都成功,要么都要失败。 + +这样就不会出现小明余额减少而小红的余额却并没有增加的情况。 + +### 何为数据库事务? + +数据库事务在我们日常开发中接触的最多了。如果你的项目属于单体架构的话,你接触到的往往就是数据库事务了。 + +平时,我们在谈论事务的时候,如果没有特指**分布式事务**,往往指的就是**数据库事务**。 + +**那数据库事务有什么作用呢?** + +简单来说:数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:**要么全部执行成功,要么全部不执行** 。 + +```sql +# 开启一个事务 +START TRANSACTION; +# 多条 SQL 语句 +SQL1,SQL2... +## 提交事务 +COMMIT; +``` + +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/640-20201207160554677.png) + +另外,关系型数据库(例如:`MySQL`、`SQL Server`、`Oracle` 等)事务都有 **ACID** 特性: + +![事务的特性](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E4%BA%8B%E5%8A%A1%E7%89%B9%E6%80%A7.png) + +### 何为 ACID 特性呢? + +1. **原子性**(`Atomicity`) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; +2. **一致性**(`Consistency`): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的; +3. **隔离性**(`Isolation`): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; +4. **持久性**(`Durabilily`): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。 + +**数据事务的实现原理呢?** + +我们这里以 MySQL 的 InnoDB 引擎为例来简单说一下。 + +MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。 + +MySQL InnoDB 引擎通过 **锁机制**、**MVCC** 等手段来保证事务的隔离性( 默认支持的隔离级别是 **`REPEATABLE-READ`** )。 + +保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。 + +### 并发事务带来哪些问题? + +在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。 + +- **脏读(Dirty read):** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。 +- **丢失修改(Lost to modify):** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。 +- **不可重复读(Unrepeatableread):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。 +- **幻读(Phantom read):** 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。 + +**不可重复读和幻读区别:** + +不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。 + +### 事务隔离级别有哪些? + +SQL 标准定义了四个隔离级别: + +- **READ-UNCOMMITTED(读取未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。 +- **READ-COMMITTED(读取已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。 +- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。 +- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。 + +--- + +| 隔离级别 | 脏读 | 不可重复读 | 幻读 | +| :--------------: | :--: | :--------: | :--: | +| READ-UNCOMMITTED | √ | √ | √ | +| READ-COMMITTED | × | √ | √ | +| REPEATABLE-READ | × | × | √ | +| SERIALIZABLE | × | × | × | + +### MySQL 的默认隔离级别是什么? + +MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;` + +```sql +mysql> SELECT @@tx_isolation; ++-----------------+ +| @@tx_isolation | ++-----------------+ +| REPEATABLE-READ | ++-----------------+ +``` + +~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~ + +🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。** + +因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEAaTABLE-READ(可重读)** 并不会有任何性能损失。 + +InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。 + +🌈 拓展一下(以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章): + +> InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。 + +## 参考 + +- 《高性能 MySQL》 + +- https://www.omnisci.com/technical-glossary/relational-database \ No newline at end of file diff --git "a/docs/database/MySQL\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" "b/docs/database/MySQL\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" new file mode 100644 index 0000000000000000000000000000000000000000..4e8af540b5b008af50c1d9394301ab4e8ab69510 --- /dev/null +++ "b/docs/database/MySQL\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" @@ -0,0 +1,240 @@ +## 何为索引?有什么作用? + +**索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。** + +索引的作用就相当于目录的作用。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。 + +## 索引的优缺点 + +**优点** : + +- 使用索引可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。 +- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 + +**缺点** : + +- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。 +- 索引需要使用物理文件存储,也会耗费一定空间。 + +但是,**使用索引一定能提高查询性能吗?** + +大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。 + +## 索引的底层数据结构 + +### Hash & B+树 + +Hash 索引指的就是 Hash 表,最大的优点就是能够在很短的时间内,根据 Hash 函数定位到数据所在的位置,也就是说 Hash 索引检索指定数据的时间复杂度可以接近 0(1)。 + +但是,MySQL 并没有使用 Hash 索引而是使用 B+树作为索引的数据结构。**为什么呢?** + +**1.Hash 冲突问题** :知道 HashMap 或 HashTable 的同学,相信都知道它们最大的缺点就是 Hash 冲突了。不过对于数据库来说这还不算最大的缺点。 + +**2.Hash 索引不支持顺序和范围查询(Hash 索引不支持顺序和范围查询是它最大的缺点:** 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。 + +试想一种情况: + +```java +SELECT * FROM tb1 WHERE id < 500; +``` + +在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗?这就是 Hash 最大的缺点了。 + +### B 树& B+树 + +B 树也称 B-树,全称为 **多路平衡查找树** ,B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 `Balanced` (平衡)的意思。 + +目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。 + +**B 树& B+树两者有何异同呢?** + +- B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。 +- B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。 +- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。 + +![](https://img-blog.csdnimg.cn/20210420165409106.png) + +在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是,两者的实现方式不太一样。(下面的内容整理自《Java 工程师修炼之道》) + +MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。 + +InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。 + +## 索引类型 + +### 主键索引(Primary Key) + +数据表的主键列使用的就是主键索引。 + +一张数据表有只能有一个主键,并且主键不能为 null,不能重复。 + +在 MySQL 的 InnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键。 + +### 二级索引(辅助索引) + +**二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。** + +唯一索引,普通索引,前缀索引等索引属于二级索引。 + +**PS:不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。** + +1. **唯一索引(Unique Key)** :唯一索引也是一种约束。**唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。** 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。 +2. **普通索引(Index)** :**普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。** +3. **前缀索引(Prefix)** :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, + 因为只取前几个字符。 +4. **全文索引(Full Text)** :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。 + +二级索引: +![](https://img-blog.csdnimg.cn/20210420165254215.png) + +## 聚集索引与非聚集索引 + +### 聚集索引 + +**聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。** + +在 Mysql 中,InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。 + +#### 聚集索引的优点 + +聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。 + +#### 聚集索引的缺点 + +1. **依赖于有序的数据** :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。 +2. **更新代价大** : 如果对索引列的数据被修改时,那么对应的索引也将会被修改, + 而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的, + 所以对于主键索引来说,主键一般都是不可被修改的。 + +### 非聚集索引 + +**非聚集索引即索引结构和数据分开存放的索引。** + +**二级索引属于非聚集索引。** + +> MYISAM 引擎的表的.MYI 文件包含了表的索引, +> 该表的索引(B+树)的每个叶子非叶子节点存储索引, +> 叶子节点存储索引和索引对应数据的指针,指向.MYD 文件的数据。 +> +> **非聚集索引的叶子节点并不一定存放数据的指针, +> 因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。** + +#### 非聚集索引的优点 + +**更新代价比聚集索引要小** 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的 + +#### 非聚集索引的缺点 + +1. 跟聚集索引一样,非聚集索引也依赖于有序的数据 +2. **可能会二次查询(回表)** :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。 + +这是 MySQL 的表的文件截图: + +![](https://img-blog.csdnimg.cn/20210420165311654.png) + +聚集索引和非聚集索引: + +![](https://img-blog.csdnimg.cn/20210420165326946.png) + +### 非聚集索引一定回表查询吗(覆盖索引)? + +**非聚集索引不一定回表查询。** + +> 试想一种情况,用户准备使用 SQL 查询用户名,而用户名字段正好建立了索引。 + +```text + SELECT name FROM table WHERE name='guang19'; +``` + +> 那么这个索引的 key 本身就是 name,查到对应的 name 直接返回就行了,无需回表查询。 + +**即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表, +因为它的主键索引的叶子节点存放的是指针。但是如果 SQL 查的就是主键呢?** + +```text +SELECT id FROM table WHERE id=1; +``` + +主键索引本身的 key 就是主键,查到返回就行了。这种情况就称之为覆盖索引了。 + +## 覆盖索引 + +如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢覆盖索引就是把要查询出的列和索引是对应的,不做回表操作! + +**覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了, +而无需回表查询。** + +> 如主键索引,如果一条 SQL 需要查询主键,那么正好根据主键索引就可以查到主键。 +> +> 再如普通索引,如果一条 SQL 需要查询 name,name 字段正好有索引, +> 那么直接根据这个索引就可以查到数据,也无需回表。 + +覆盖索引: +![](https://img-blog.csdnimg.cn/20210420165341868.png) + +## 创建索引的注意事项 + +**1.选择合适的字段创建索引:** + +- **不为 NULL 的字段** :索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。 +- **被频繁查询的字段** :我们创建索引的字段应该是查询操作非常频繁的字段。 +- **被作为条件查询的字段** :被作为 WHERE 条件查询的字段,应该被考虑建立索引。 +- **频繁需要排序的字段** :索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。 +- **被经常频繁用于连接的字段** :经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。 + +**2.被频繁更新的字段应该慎重建立索引。** + +虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 +如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。 + +**3.尽可能的考虑建立联合索引而不是单列索引。** + +因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。 + +**4.注意避免冗余索引** 。 + +冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 + +**5.考虑在字符串类型的字段上使用前缀索引代替普通索引。** + +前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。 + +## 使用索引的一些建议 + +- 对于中到大型表索引都是非常有效的,但是特大型表的话维护开销会很大,不适合建索引 +- 避免 where 子句中对字段施加函数,这会造成无法命中索引。 +- 在使用 InnoDB 时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 +- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用 +- 在使用 limit offset 查询缓慢时,可以借助索引来提高性能 + +## MySQL 如何为表字段添加索引? + +1.添加 PRIMARY KEY(主键索引) + +```sql +ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) +``` + +2.添加 UNIQUE(唯一索引) + +```sqlite +ALTER TABLE `table_name` ADD UNIQUE ( `column` ) +``` + +3.添加 INDEX(普通索引) + +```sql +ALTER TABLE `table_name` ADD INDEX index_name ( `column` ) +``` + +4.添加 FULLTEXT(全文索引) + +```sql +ALTER TABLE `table_name` ADD FULLTEXT ( `column`) +``` + +5.添加多列索引 + +```sql +ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` ) +``` \ No newline at end of file diff --git "a/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" "b/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" new file mode 100644 index 0000000000000000000000000000000000000000..60f175838ce2acb8fb590a8e5d9d8b7a7f101121 --- /dev/null +++ "b/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" @@ -0,0 +1,443 @@ +> 作者: 听风,原文地址: 。JavaGuide 已获得作者授权。 + + + +- [数据库命令规范](#数据库命令规范) +- [数据库基本设计规范](#数据库基本设计规范) + - [1. 所有表必须使用 Innodb 存储引擎](#1-所有表必须使用-innodb-存储引擎) + - [2. 数据库和表的字符集统一使用 UTF8](#2-数据库和表的字符集统一使用-utf8) + - [3. 所有表和字段都需要添加注释](#3-所有表和字段都需要添加注释) + - [4. 尽量控制单表数据量的大小,建议控制在 500 万以内。](#4-尽量控制单表数据量的大小建议控制在-500-万以内) + - [5. 谨慎使用 MySQL 分区表](#5-谨慎使用-mysql-分区表) + - [6.尽量做到冷热数据分离,减小表的宽度](#6尽量做到冷热数据分离减小表的宽度) + - [7. 禁止在表中建立预留字段](#7-禁止在表中建立预留字段) + - [8. 禁止在数据库中存储图片,文件等大的二进制数据](#8-禁止在数据库中存储图片文件等大的二进制数据) + - [9. 禁止在线上做数据库压力测试](#9-禁止在线上做数据库压力测试) + - [10. 禁止从开发环境,测试环境直接连接生成环境数据库](#10-禁止从开发环境测试环境直接连接生成环境数据库) +- [数据库字段设计规范](#数据库字段设计规范) + - [1. 优先选择符合存储需要的最小的数据类型](#1-优先选择符合存储需要的最小的数据类型) + - [2. 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据](#2-避免使用-textblob-数据类型最常见的-text-类型可以存储-64k-的数据) + - [3. 避免使用 ENUM 类型](#3-避免使用-enum-类型) + - [4. 尽可能把所有列定义为 NOT NULL](#4-尽可能把所有列定义为-not-null) + - [5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间](#5-使用-timestamp4-个字节-或-datetime-类型-8-个字节-存储时间) + - [6. 同财务相关的金额类数据必须使用 decimal 类型](#6-同财务相关的金额类数据必须使用-decimal-类型) +- [索引设计规范](#索引设计规范) + - [1. 限制每张表上的索引数量,建议单张表索引不超过 5 个](#1-限制每张表上的索引数量建议单张表索引不超过-5-个) + - [2. 禁止给表中的每一列都建立单独的索引](#2-禁止给表中的每一列都建立单独的索引) + - [3. 每个 Innodb 表必须有个主键](#3-每个-innodb-表必须有个主键) + - [4. 常见索引列建议](#4-常见索引列建议) + - [5.如何选择索引列的顺序](#5如何选择索引列的顺序) + - [6. 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间)](#6-避免建立冗余索引和重复索引增加了查询优化器生成执行计划的时间) + - [7. 对于频繁的查询优先考虑使用覆盖索引](#7-对于频繁的查询优先考虑使用覆盖索引) + - [8.索引 SET 规范](#8索引-set-规范) +- [数据库 SQL 开发规范](#数据库-sql-开发规范) + - [1. 建议使用预编译语句进行数据库操作](#1-建议使用预编译语句进行数据库操作) + - [2. 避免数据类型的隐式转换](#2-避免数据类型的隐式转换) + - [3. 充分利用表上已经存在的索引](#3-充分利用表上已经存在的索引) + - [4. 数据库设计时,应该要对以后扩展进行考虑](#4-数据库设计时应该要对以后扩展进行考虑) + - [5. 程序连接不同的数据库使用不同的账号,禁止跨库查询](#5-程序连接不同的数据库使用不同的账号禁止跨库查询) + - [6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询](#6-禁止使用-select--必须使用-select-字段列表-查询) + - [7. 禁止使用不含字段列表的 INSERT 语句](#7-禁止使用不含字段列表的-insert-语句) + - [8. 避免使用子查询,可以把子查询优化为 join 操作](#8-避免使用子查询可以把子查询优化为-join-操作) + - [9. 避免使用 JOIN 关联太多的表](#9-避免使用-join-关联太多的表) + - [10. 减少同数据库的交互次数](#10-减少同数据库的交互次数) + - [11. 对应同一列进行 or 判断时,使用 in 代替 or](#11-对应同一列进行-or-判断时使用-in-代替-or) + - [12. 禁止使用 order by rand() 进行随机排序](#12-禁止使用-order-by-rand-进行随机排序) + - [13. WHERE 从句中禁止对列进行函数转换和计算](#13-where-从句中禁止对列进行函数转换和计算) + - [14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION](#14-在明显不会有重复值时使用-union-all-而不是-union) + - [15. 拆分复杂的大 SQL 为多个小 SQL](#15-拆分复杂的大-sql-为多个小-sql) +- [数据库操作行为规范](#数据库操作行为规范) + - [1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作](#1-超-100-万行的批量写-updatedeleteinsert-操作要分批多次进行操作) + - [2. 对于大表使用 pt-online-schema-change 修改表结构](#2-对于大表使用-pt-online-schema-change-修改表结构) + - [3. 禁止为程序使用的账号赋予 super 权限](#3-禁止为程序使用的账号赋予-super-权限) + - [4. 对于程序连接数据库账号,遵循权限最小原则](#4-对于程序连接数据库账号遵循权限最小原则) + + + +## 数据库命令规范 + +- 所有数据库对象名称必须使用小写字母并用下划线分割 +- 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) +- 数据库对象的命名要能做到见名识意,并且最后不要超过 32 个字符 +- 临时库表必须以 tmp_为前缀并以日期为后缀,备份表必须以 bak_为前缀并以日期 (时间戳) 为后缀 +- 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低) + +------ + +## 数据库基本设计规范 + +### 1. 所有表必须使用 Innodb 存储引擎 + +没有特殊要求(即 Innodb 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 Innodb 存储引擎(MySQL5.5 之前默认使用 Myisam,5.6 以后默认的为 Innodb)。 + +Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。 + +### 2. 数据库和表的字符集统一使用 UTF8 + +兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储 emoji 表情的需要,字符集需要采用 utf8mb4 字符集。 + +参考文章:[MySQL 字符集不一致导致索引失效的一个真实案例](https://blog.csdn.net/horses/article/details/107243447) + +### 3. 所有表和字段都需要添加注释 + +使用 comment 从句添加表和列的备注,从一开始就进行数据字典的维护 + +### 4. 尽量控制单表数据量的大小,建议控制在 500 万以内。 + +500 万并不是 MySQL 数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。 + +可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小 + +### 5. 谨慎使用 MySQL 分区表 + +分区表在物理上表现为多个文件,在逻辑上表现为一个表; + +谨慎选择分区键,跨分区查询效率可能更低; + +建议采用物理分表的方式管理大数据。 + +### 6.尽量做到冷热数据分离,减小表的宽度 + +> MySQL 限制每个表最多存储 4096 列,并且每一行数据的大小不能超过 65535 字节。 + +减少磁盘 IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的 IO); + +更有效的利用缓存,避免读入无用的冷数据; + +经常一起使用的列放到一个表中(避免更多的关联操作)。 + +### 7. 禁止在表中建立预留字段 + +预留字段的命名很难做到见名识义。 + +预留字段无法确认存储的数据类型,所以无法选择合适的类型。 + +对预留字段类型的修改,会对表进行锁定。 + +### 8. 禁止在数据库中存储图片,文件等大的二进制数据 + +通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机 IO 操作,文件很大时,IO 操作很耗时。 + +通常存储于文件服务器,数据库只存储文件地址信息 + +### 9. 禁止在线上做数据库压力测试 + +### 10. 禁止从开发环境,测试环境直接连接生产环境数据库 + +------ + +## 数据库字段设计规范 + +### 1. 优先选择符合存储需要的最小的数据类型 + +**原因:** + +列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少也越少,在遍历时所需要的 IO 次数也就越多,索引的性能也就越差。 + +**方法:** + +**a.将字符串转换成数字类型存储,如:将 IP 地址转换成整形数据** + +MySQL 提供了两个方法来处理 ip 地址 + +- inet_aton 把 ip 转为无符号整型 (4-8 位) +- inet_ntoa 把整型的 ip 转为地址 + +插入数据前,先用 inet_aton 把 ip 地址转为整型,可以节省空间,显示数据时,使用 inet_ntoa 把整型的 ip 地址转为地址显示即可。 + +**b.对于非负型的数据 (如自增 ID,整型 IP) 来说,要优先使用无符号整型来存储** + +**原因:** + +无符号相对于有符号可以多出一倍的存储空间 + +``` +SIGNED INT -2147483648~2147483647 +UNSIGNED INT 0~4294967295 +``` + +VARCHAR(N) 中的 N 代表的是字符数,而不是字节数,使用 UTF8 存储 255 个汉字 Varchar(255)=765 个字节。**过大的长度会消耗更多的内存。** + +### 2. 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据 + +**a. 建议把 BLOB 或是 TEXT 列分离到单独的扩展表中** + +MySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型,如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。而且对于这种数据,MySQL 还是要进行二次查询,会使 sql 性能变得很差,但是不是说一定不能使用这样的数据类型。 + +如果一定要使用,建议把 BLOB 或是 TEXT 列分离到单独的扩展表中,查询时一定不要使用 select * 而只需要取出必要的列,不需要 TEXT 列的数据时不要对该列进行查询。 + +**2、TEXT 或 BLOB 类型只能使用前缀索引** + +因为[MySQL](http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的 + +### 3. 避免使用 ENUM 类型 + +修改 ENUM 值需要使用 ALTER 语句 + +ENUM 类型的 ORDER BY 操作效率低,需要额外操作 + +禁止使用数值作为 ENUM 的枚举值 + +### 4. 尽可能把所有列定义为 NOT NULL + +**原因:** + +索引 NULL 列需要额外的空间来保存,所以要占用更多的空间 + +进行比较和计算时要对 NULL 值做特别的处理 + +### 5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间 + +TIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07 + +TIMESTAMP 占用 4 字节和 INT 相同,但比 INT 可读性高 + +超出 TIMESTAMP 取值范围的使用 DATETIME 类型存储 + +**经常会有人用字符串存储日期型的数据(不正确的做法)** + +- 缺点 1:无法用日期函数进行计算和比较 +- 缺点 2:用字符串存储日期要占用更多的空间 + +### 6. 同财务相关的金额类数据必须使用 decimal 类型 + +- 非精准浮点:float,double +- 精准浮点:decimal + +Decimal 类型为精准浮点数,在计算时不会丢失精度 + +占用空间由定义的宽度决定,每 4 个字节可以存储 9 位数字,并且小数点要占用一个字节 + +可用于存储比 bigint 更大的整型数据 + +------ + +## 索引设计规范 + +### 1. 限制每张表上的索引数量,建议单张表索引不超过 5 个 + +索引并不是越多越好!索引可以提高效率同样可以降低效率。 + +索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。 + +因为 MySQL 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。 + +### 2. 禁止给表中的每一列都建立单独的索引 + +5.6 版本之前,一个 sql 只能使用到一个表中的一个索引,5.6 以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询方式好。 + +### 3. 每个 Innodb 表必须有个主键 + +Innodb 是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引,但是表的存储顺序只能有一种。 + +Innodb 是按照主键索引的顺序来组织表的 + +- 不要使用更新频繁的列作为主键,不适用多列主键(相当于联合索引) +- 不要使用 UUID,MD5,HASH,字符串列作为主键(无法保证数据的顺序增长) +- 主键建议使用自增 ID 值 + +------ + +### 4. 常见索引列建议 + +- 出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列 +- 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段 +- 并不要将符合 1 和 2 中的字段的列都建立一个索引, 通常将 1、2 中的字段建立联合索引效果更好 +- 多表 join 的关联列 + +------ + +### 5.如何选择索引列的顺序 + +建立索引的目的是:希望通过索引进行数据查找,减少随机 IO,增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。 + +- 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数) +- 尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO 性能也就越好) +- 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引) + +------ + +### 6. 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间) + +- 重复索引示例:primary key(id)、index(id)、unique index(id) +- 冗余索引示例:index(a,b,c)、index(a,b)、index(a) + +------ + +### 7. 对于频繁的查询优先考虑使用覆盖索引 + +> 覆盖索引:就是包含了所有查询字段 (where,select,ordery by,group by 包含的字段) 的索引 + +**覆盖索引的好处:** + +- **避免 Innodb 表进行索引的二次查询:** Innodb 是以聚集索引的顺序来存储的,对于 Innodb 来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了 IO 操作,提升了查询效率。 +- **可以把随机 IO 变成顺序 IO 加快查询效率:** 由于覆盖索引是按键值的顺序存储的,对于 IO 密集型的范围查找来说,对比随机从磁盘读取每一行的数据 IO 要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的 IO 转变成索引查找的顺序 IO。 + +------ + +### 8.索引 SET 规范 + +**尽量避免使用外键约束** + +- 不建议使用外键约束(foreign key),但一定要在表与表之间的关联键上建立索引 +- 外键可用于保证数据的参照完整性,但建议在业务端实现 +- 外键会影响父表和子表的写操作从而降低性能 + +------ + +## 数据库 SQL 开发规范 + +### 1. 建议使用预编译语句进行数据库操作 + +预编译语句可以重复使用这些计划,减少 SQL 编译所需要的时间,还可以解决动态 SQL 所带来的 SQL 注入的问题。 + +只传参数,比传递 SQL 语句更高效。 + +相同语句可以一次解析,多次使用,提高处理效率。 + +### 2. 避免数据类型的隐式转换 + +隐式转换会导致索引失效如: + +``` +select name,phone from customer where id = '111'; +``` + +### 3. 充分利用表上已经存在的索引 + +避免使用双%号的查询条件。如:`a like '%123%'`,(如果无前置%,只有后置%,是可以用到列上的索引的) + +一个 SQL 只能利用到复合索引中的一列进行范围查询。如:有 a,b,c 列的联合索引,在查询条件中有 a 列的范围查询,则在 b,c 列上的索引将不会被用到。 + +在定义联合索引时,如果 a 列要用到范围查找的话,就要把 a 列放到联合索引的右侧,使用 left join 或 not exists 来优化 not in 操作,因为 not in 也通常会使用索引失效。 + +### 4. 数据库设计时,应该要对以后扩展进行考虑 + +### 5. 程序连接不同的数据库使用不同的账号,禁止跨库查询 + +- 为数据库迁移和分库分表留出余地 +- 降低业务耦合度 +- 避免权限过大而产生的安全风险 + +### 6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询 + +**原因:** + +- 消耗更多的 CPU 和 IO 以网络带宽资源 +- 无法使用覆盖索引 +- 可减少表结构变更带来的影响 + +### 7. 禁止使用不含字段列表的 INSERT 语句 + +如: + +``` +insert into values ('a','b','c'); +``` + +应使用: + +``` +insert into t(c1,c2,c3) values ('a','b','c'); +``` + +### 8. 避免使用子查询,可以把子查询优化为 join 操作 + +通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时,才可以把子查询转化为关联查询进行优化。 + +**子查询性能差的原因:** + +子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大。 + +由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。 + +### 9. 避免使用 JOIN 关联太多的表 + +对于 MySQL 来说,是存在关联缓存的,缓存的大小可以由 join_buffer_size 参数进行设置。 + +在 MySQL 中,对于同一个 SQL 多关联(join)一个表,就会多分配一个关联缓存,如果在一个 SQL 中关联的表越多,所占用的内存也就越大。 + +如果程序中大量的使用了多表关联的操作,同时 join_buffer_size 设置的也不合理的情况下,就容易造成服务器内存溢出的情况,就会影响到服务器数据库性能的稳定性。 + +同时对于关联操作来说,会产生临时表操作,影响查询效率,MySQL 最多允许关联 61 个表,建议不超过 5 个。 + +### 10. 减少同数据库的交互次数 + +数据库更适合处理批量操作,合并多个相同的操作到一起,可以提高处理效率。 + +### 11. 对应同一列进行 or 判断时,使用 in 代替 or + +in 的值不要超过 500 个,in 操作可以更有效的利用索引,or 大多数情况下很少能利用到索引。 + +### 12. 禁止使用 order by rand() 进行随机排序 + +order by rand() 会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值,如果满足条件的数据集非常大,就会消耗大量的 CPU 和 IO 及内存资源。 + +推荐在程序中获取一个随机值,然后从数据库中获取数据的方式。 + +### 13. WHERE 从句中禁止对列进行函数转换和计算 + +对列进行函数转换或计算时会导致无法使用索引 + +**不推荐:** + +``` +where date(create_time)='20190101' +``` + +**推荐:** + +``` +where create_time >= '20190101' and create_time < '20190102' +``` + +### 14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION + +- UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作 +- UNION ALL 不会再对结果集进行去重操作 + +### 15. 拆分复杂的大 SQL 为多个小 SQL + +- 大 SQL 逻辑上比较复杂,需要占用大量 CPU 进行计算的 SQL +- MySQL 中,一个 SQL 只能使用一个 CPU 进行计算 +- SQL 拆分后可以通过并行执行来提高处理效率 + +------ + +## 数据库操作行为规范 + +### 1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作 + +**大批量操作可能会造成严重的主从延迟** + +主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间, +而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况 + +**binlog 日志为 row 格式时会产生大量的日志** + +大批量写操作会产生大量日志,特别是对于 row 格式二进制数据而言,由于在 row 格式中会记录每一行数据的修改,我们一次修改的数据越多,产生的日志量也就会越多,日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因 + +**避免产生大事务操作** + +大批量修改数据,一定是在一个事务中进行的,这就会造成表中大批量数据进行锁定,从而导致大量的阻塞,阻塞会对 MySQL 的性能产生非常大的影响。 + +特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批 + +### 2. 对于大表使用 pt-online-schema-change 修改表结构 + +- 避免大表修改产生的主从延迟 +- 避免在对表字段进行修改时进行锁表 + +对大表数据结构的修改一定要谨慎,会造成严重的锁表操作,尤其是生产环境,是不能容忍的。 + +pt-online-schema-change 它会首先建立一个与原表结构相同的新表,并且在新表上进行表结构的修改,然后再把原表中的数据复制到新表中,并在原表中增加一些触发器。把原表中新增的数据也复制到新表中,在行所有数据复制完成之后,把新表命名成原表,并把原来的表删除掉。把原来一个 DDL 操作,分解成多个小的批次进行。 + +### 3. 禁止为程序使用的账号赋予 super 权限 + +- 当达到最大连接数限制时,还运行 1 个有 super 权限的用户连接 +- super 权限只能留给 DBA 处理问题的账号使用 + +### 4. 对于程序连接数据库账号,遵循权限最小原则 + +- 程序使用数据库账号只能在一个 DB 下使用,不准跨库 +- 程序使用的账号原则上不准有 drop 权限 diff --git "a/docs/database/Redis/3\347\247\215\345\270\270\347\224\250\347\232\204\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245.md" "b/docs/database/Redis/3\347\247\215\345\270\270\347\224\250\347\232\204\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245.md" new file mode 100644 index 0000000000000000000000000000000000000000..21332e5dd411159dc72d31fa80b5858dd4f00435 --- /dev/null +++ "b/docs/database/Redis/3\347\247\215\345\270\270\347\224\250\347\232\204\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245.md" @@ -0,0 +1,108 @@ +看到很多小伙伴简历上写了“**熟练使用缓存**”,但是被我问到“**缓存常用的3种读写策略**”的时候却一脸懵逼。 + +在我看来,造成这个问题的原因是我们在学习 Redis 的时候,可能只是简单了写一些 Demo,并没有去关注缓存的读写策略,或者说压根不知道这回事。 + +但是,搞懂3种常见的缓存读写策略对于实际工作中使用缓存以及面试中被问到缓存都是非常有帮助的! + +下面我会简单介绍一下自己对于这 3 种缓存读写策略的理解。 + +另外,**这3 种缓存读写策略各有优劣,不存在最佳,需要我们根据具体的业务场景选择更适合的。** + +*个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!——爱你们的 Guide 哥* + +### Cache Aside Pattern(旁路缓存模式) + +**Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。** + +Cache Aside Pattern 中服务端需要同时维系 DB 和 cache,并且是以 DB 的结果为准。 + +下面我们来看一下这个策略模式下的缓存读写步骤。 + +**写** : + +- 先更新 DB +- 然后直接删除 cache 。 + +简单画了一张图帮助大家理解写的步骤。 + +![](https://img-blog.csdnimg.cn/img_convert/5687fe759a1dac9ed9554d27e3a23b6d.png) + +**读** : + +- 从 cache 中读取数据,读取到就直接返回 +- cache中读取不到的话,就从 DB 中读取数据返回 +- 再把数据放到 cache 中。 + +简单画了一张图帮助大家理解读的步骤。 + +![](https://img-blog.csdnimg.cn/img_convert/a8c18b5f5b1aed03234bcbbd8c173a87.png) + + +你仅仅了解了上面这些内容的话是远远不够的,我们还要搞懂其中的原理。 + +比如说面试官很可能会追问:“**在写数据的过程中,可以先删除 cache ,后更新 DB 么?**” + +**答案:** 那肯定是不行的!因为这样可能会造成**数据库(DB)和缓存(Cache)数据不一致**的问题。为什么呢?比如说请求1 先写数据A,请求2随后读数据A的话就很有可能产生数据不一致性的问题。这个过程可以简单描述为: + +> 请求1先把cache中的A数据删除 -> 请求2从DB中读取数据->请求1再把DB中的A数据更新。 + +当你这样回答之后,面试官可能会紧接着就追问:“**在写数据的过程中,先更新DB,后删除cache就没有问题了么?**” + +**答案:** 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多! + +比如请求1先读数据 A,请求2随后写数据A,并且数据A不在缓存中的话也有可能产生数据不一致性的问题。这个过程可以简单描述为: + +> 请求1从DB读数据A->请求2写更新数据 A 到数据库并把删除cache中的A数据->请求1将数据A写入cache。 + +现在我们再来分析一下 **Cache Aside Pattern 的缺陷**。 + +**缺陷1:首次请求数据一定不在 cache 的问题** + +解决办法:可以将热点数据可以提前放入cache 中。 + +**缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除,这样会影响缓存命中率 。** + +解决办法: + +- 数据库和缓存数据强一致场景 :更新DB的时候同样更新cache,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。 +- 可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。 + +### Read/Write Through Pattern(读写穿透) + +Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。 + +这种缓存读写策略小伙伴们应该也发现了在平时在开发过程中非常少见。抛去性能方面的影响,大概率是因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能。 + +**写(Write Through):** + +- 先查 cache,cache 中不存在,直接更新 DB。 +- cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(**同步更新 cache 和 DB**)。 + +简单画了一张图帮助大家理解写的步骤。 + +![](https://img-blog.csdnimg.cn/20210201100340808.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0MzM3Mjcy,size_16,color_FFFFFF,t_70) + +**读(Read Through):** + +- 从 cache 中读取数据,读取到就直接返回 。 +- 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。 + +简单画了一张图帮助大家理解读的步骤。 + +![](https://img-blog.csdnimg.cn/img_convert/9ada757c78614934aca11306f334638d.png) + +Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装。在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。 + +和 Cache Aside Pattern 一样, Read-Through Pattern 也有首次请求数据一定不再 cache 的问题,对于热点数据可以提前放入缓存中。 + +### Write Behind Pattern(异步缓存写入) + +Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。 + +但是,两个又有很大的不同:**Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。** + +很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就就挂掉了。 + +这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。 + +Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" "b/docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md" similarity index 99% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" rename to "docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md" index fbad95556a0a10affabf21e662152c9a7c694730..0408c2764d10b98eeebf9299b68458d1f41ac960 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" +++ "b/docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md" @@ -1,5 +1,4 @@ - 非常感谢《redis实战》真本书,本文大多内容也参考了书中的内容。非常推荐大家看一下《redis实战》这本书,感觉书中的很多理论性东西还是很不错的。 为什么本文的名字要加上春夏秋冬又一春,哈哈 ,这是一部韩国的电影,我感觉电影不错,所以就用在文章名字上了,没有什么特别的含义,然后下面的有些配图也是电影相关镜头。 @@ -10,12 +9,10 @@ Redis不同于Memcached的很重一点就是,**Redis支持持久化**,而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照(snapshotting,RDB)**,另一种方式是**只追加文件(append-only file,AOF)**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 - ## 快照(snapshotting)持久化 Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。 - ![春夏秋冬又一春](https://user-gold-cdn.xitu.io/2018/6/13/163f97568281782a?w=600&h=329&f=jpeg&s=88616) **快照持久化是Redis默认采用的持久化方式**,在redis.conf配置文件中默认有此下配置: @@ -46,6 +43,7 @@ save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生 ## **AOF(append-only file)持久化** 与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启: + ``` appendonly yes ``` diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" similarity index 99% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" rename to "docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" index b1742f2fbf9ef086b489d62e58f1bbf47e379bb0..86a15ff6fafba8b800aae1588ae50d41143bab98 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" +++ "b/docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -28,7 +28,7 @@ end 算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作: -1. 得到当前的时间,微妙单位 +1. 得到当前的时间,微秒单位 2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间 3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。 4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间 diff --git a/docs/database/Redis/images/redis-all/redis-list.drawio b/docs/database/Redis/images/redis-all/redis-list.drawio new file mode 100644 index 0000000000000000000000000000000000000000..afa767154b78e800813cbfe4c672ac94cbdcff60 --- /dev/null +++ b/docs/database/Redis/images/redis-all/redis-list.drawio @@ -0,0 +1 @@ +7VlNc5swFPw1PiaDENjmmNjpx6Gdjt1J2lNHAzKoEYgRcmzn11cYyRgJT1w3DnScU3grIaHd5b2HM4CTdP2Rozz5wiJMB64TrQdwOnBdEEAo/5TIpkICx6mAmJNITaqBOXnGCtTTliTCRWOiYIwKkjfBkGUZDkUDQ5yzVXPagtHmrjmKsQXMQ0Rt9IFEIqnQsTuq8U+YxIneGQyDaiRFerI6SZGgiK32IHg3gBPOmKiu0vUE05I8zUt134cDo7sH4zgTx9wwu/+V/H4g/myezsD35/vZ56/ulVrlCdGlOrB6WLHRDHC2zCJcLgIG8HaVEIHnOQrL0ZXUXGKJSKkaLh6xCBMVLFgmlKJgJGO1F+YCrw8eAuyokZ7CLMWCb+QUdcOVp9hUdnKhile1OL62WLInzEhhSPkh3i1dUyYvFGt/waBrMbi9BD3lUfPm2bwFLbT556INttPWc/sB035d0+i10wj7TaM77hmNw5fTIM6im7KeyCikqChI2ORMHp1vfsjA0cHPMrh2fR1P1/uj042KjiAbR1aRMqiWVRHxGIuXUr0tSSNxHqacY4oEeWo+RpsOaodvjMgHrPM2gKbk4+YaBVvyEKvb9suYuZLpHdcwRUWEtdDWF7tzn26V0f9ula4s4A0N4cbOtX+aB3zfWupNPaCb01cxAXjPF0cofGq68AJjIT8wXXdusxzRY/fbLJ0VDbPbOj1j2PXnrVOG/Z3A82WRWFaQXZUwujHB2SOeMMq4RDKW4VJLQqkBIUrirHSQVA9L/Lbs0Yj8lL1RAymJonKb1vavbhCP9M0/dYA+OFDF95zltTjLLPav1gECu5PmOcvf5aloD7qWx7fkoRf8+siiYggEuhbI/oSil/v+2Pp0/gLZ3y18u20/BTqDJp5O8VoSp2tJxvYrgxcXpIjV351PERnWv7RXPV39/wp49wc= \ No newline at end of file diff --git a/docs/database/Redis/images/redis-all/redis-list.png b/docs/database/Redis/images/redis-all/redis-list.png new file mode 100644 index 0000000000000000000000000000000000000000..4fb4e36cb494ee9554a156f2bd295d6f2f983864 Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis-list.png differ diff --git a/docs/database/Redis/images/redis-all/redis-rollBack.png b/docs/database/Redis/images/redis-all/redis-rollBack.png new file mode 100644 index 0000000000000000000000000000000000000000..91f7f46d66dba0c55398d5b7aad42254dd65adde Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis-rollBack.png differ diff --git a/docs/database/Redis/images/redis-all/redis-vs-memcached.png b/docs/database/Redis/images/redis-all/redis-vs-memcached.png new file mode 100644 index 0000000000000000000000000000000000000000..23844d67e6fc949920643ec78628e2e37a909c45 Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis-vs-memcached.png differ diff --git a/docs/database/Redis/images/redis-all/redis4.0-more-thread.png b/docs/database/Redis/images/redis-all/redis4.0-more-thread.png new file mode 100644 index 0000000000000000000000000000000000000000..e7e19e52e17e12050cfa110bf2c9fa973d7dc299 Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis4.0-more-thread.png differ diff --git "a/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" "b/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" new file mode 100644 index 0000000000000000000000000000000000000000..fc280fffabaeaf8d984449b62e61ef21c84f1d06 Binary files /dev/null and "b/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" differ diff --git "a/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png" "b/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png" new file mode 100644 index 0000000000000000000000000000000000000000..eb0c404cafd1197235fb37563611f5c5b13eaa6a Binary files /dev/null and "b/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png" differ diff --git "a/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" "b/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" new file mode 100644 index 0000000000000000000000000000000000000000..27df6ead8e4c8a576b53d777d0d9fa8e29586f17 Binary files /dev/null and "b/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" differ diff --git a/docs/database/Redis/images/redis-all/try-redis.png b/docs/database/Redis/images/redis-all/try-redis.png new file mode 100644 index 0000000000000000000000000000000000000000..cd21a6518e4de4a25fd740d029e3222c1dabd41c Binary files /dev/null and b/docs/database/Redis/images/redis-all/try-redis.png differ diff --git a/docs/database/Redis/images/redis-all/what-is-redis.png b/docs/database/Redis/images/redis-all/what-is-redis.png new file mode 100644 index 0000000000000000000000000000000000000000..913881ac6cf59e9c3c56a58db545048d4f7c47ca Binary files /dev/null and b/docs/database/Redis/images/redis-all/what-is-redis.png differ diff --git "a/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" "b/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" new file mode 100644 index 0000000000000000000000000000000000000000..2c73bd90276521694b6a7e7c2fe78fbbd0517b31 Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" differ diff --git "a/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..a2c2ed6906fa5e1e558301dc1c36d7bbeee181fd Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" differ diff --git "a/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" "b/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..648a404af8c93ae61d9e9797d2cbae6b4f3776da Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" differ diff --git "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..11860ae1f024368ecd8173d94d5235a9c0c2fc08 Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" differ diff --git "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" new file mode 100644 index 0000000000000000000000000000000000000000..e7298c15ed6a483fa565fd6a6ba7841eabd9ff3b Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" differ diff --git "a/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" "b/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..5aff414baa44e30e307d103ba1eea8eb56aa1b54 Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" differ diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..bc4c6d0cca713d2e0db2aaac480477ec795407d3 --- /dev/null +++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio" @@ -0,0 +1 @@ +7Vpbc+o2EP41ekzHl/j2iAmk02nmdA6dpn3qCFsYTWSLY0Qg/fWVbMnYluE4B4jTQh4Se6WVVt+32pU2BvY43T3mcLV8ojEiwDLiHbAfgGWZluPzP0LyVkoCwygFSY5j2WkvmOF/kBSqbhsco3WjI6OUMLxqCiOaZShiDRnMc7ptdltQ0px1BROkCWYRJLr0GcdsWUp9y9vLf0Y4WaqZTTcoW1KoOsuVrJcwptuayJ4Ae5xTysqndDdGRICncCn1pgdaK8NylLE+Ck/Pf/xthHPraf7NePliLeLZ1+c7OcorJBu5YGkse1MI5HSTxUgMYgI73C4xQ7MVjETrlnPOZUuWEtm8wISMKaF5oWsvFgsrirh8zXL6gmotsTt3HVe0KFiEekRTzLs/GPw5IXC9ls/SSpQztDu4fLMClXsjoili+RvvIhVs5VHSEX3fKd+3e1pNxdWyRqkrZVB6UlINvQebP0i834G9db3YW0Njb2vYx5BBDX++ZNYEuQlmRjN0BHlIcJJxWcRxQrwxFChiHlxGsiHFcSzm6qR2T75RTaxiUME3zZgMmFbQIq1O5jkIvG8RqN5rBLod/NmX4u9e4y8TVN3468dflYWH4s+5mtjnGJ8t77jXi/3gece75Z13EWi3845OYFfcUjyfnT//lndO4W/wvBNcTexzg8+Wd9Ql4BrBHzzxmPpN/5Z5jjDoWZ/sxmPq5YJb6nkHgYOnHlOvOfxfw5/vf7rcoxcMrgb84XOPftu/5Z4jDAbtcunguUcvGdxyzzsIHD736HUHjTyUxSPxDzNBgIBGANKIeC38mgjbXIJ2mP0pe4vnvwSiPzny7WEnAS5e3hpoo1j7L1wLa24q3eQROrbIbk5qmDsdmCtZjghk+LVpRhcRcobfKOYGHiw1BSrjqCFK86XWns4eA7VqGQzmCWLaQIVfVMs+wVX0Esd/zFWGcgG/vevbzPV1gXYG/3AX6FElublAF3Nu+9Lxo1GgXUDQBrqwC6h1NFzAJUV2t/bK7reN+HwhBJN7EPrAD8DEAf4U+GPxwNcQmGASgMAG4QRMfDAywcivqdVdSgmF59ytC9cZ8Q62vdrVNdyknJDP8wACp3N4UnadClNlfymM8esJk8pB5vnpY3DrCluU9PSTlBSd9RBVv7IcOi/Vdrptn+f8ZPt+w/s9Vz8/OeZHHqAsvXz2PZcSFN5JNoQ/ELRgh/1hvYJZH68yne7twPcf3w5jsQt49uD37ckUhGMQjGYZxGRMcDrnnWEqSM7m61XTFcvZj3toKRXmnGDnBTCrBQQHjMYFFHzhngxFoVetu/z9iNlyM6/w+QW+wkfxkdkPwcGlJSK99/HF92e9vNDOxEKJ8hExEzsrEK8EzhEJYfSSFGO3AwxXrl3VjOKn0qJ5jPIOjSlMMREzPMIcpjSLlRkSGENS/aUyxTNKPiOcJb/TlTwiSMGvwgsakpAyRtOm7KsMA2eKQZ6qWakM7OhlFKsrBt1fLAbpFeDbsazXscw0vnep6nsuMw1v2LO5pVeRs41Q+9jAM2R1RefgcuUV/rr/QrfkcP+dsz35Fw== \ No newline at end of file diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" new file mode 100644 index 0000000000000000000000000000000000000000..f8b9589d6d7814b49aef01013b7ce61d38b304db Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" differ diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..6fddf10f064eb3ecd2446ba3c5663e373533521d --- /dev/null +++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" new file mode 100644 index 0000000000000000000000000000000000000000..c976cc99b912f4ce1049482ff6bf27d6817e60e6 Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" differ diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..7f7bfd71641b9b8e6822b9368fdca5827fa92ddf --- /dev/null +++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" new file mode 100644 index 0000000000000000000000000000000000000000..f8f457c7490d10907baa89f5786aa57faf2de3f4 Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" differ diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio" new file mode 100644 index 0000000000000000000000000000000000000000..7626c8d1f5004745fcbd532b7a6a90c6f3b2a9e1 --- /dev/null +++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio" @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" new file mode 100644 index 0000000000000000000000000000000000000000..ecdbd6d2c2e2a773b0013f527e180497b63277e2 Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" differ diff --git a/docs/database/Redis/redis-all.md b/docs/database/Redis/redis-all.md new file mode 100644 index 0000000000000000000000000000000000000000..50fa4ebd1cb8d792efe92e760dbabee299fd9124 --- /dev/null +++ b/docs/database/Redis/redis-all.md @@ -0,0 +1,796 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java 面试突击》以及 Java 工程师必备学习资源。 + + + + + + +- [1. 简单介绍一下 Redis 呗!](#1-简单介绍一下-redis-呗) +- [2. 分布式缓存常见的技术选型方案有哪些?](#2-分布式缓存常见的技术选型方案有哪些) +- [3. 说一下 Redis 和 Memcached 的区别和共同点](#3-说一下-redis-和-memcached-的区别和共同点) +- [4. 缓存数据的处理流程是怎样的?](#4-缓存数据的处理流程是怎样的) +- [5. 为什么要用 Redis/为什么要用缓存?](#5-为什么要用-redis为什么要用缓存) +- [6. Redis 常见数据结构以及使用场景分析](#6-redis-常见数据结构以及使用场景分析) + - [6.1. string](#61-string) + - [6.2. list](#62-list) + - [6.3. hash](#63-hash) + - [6.4. set](#64-set) + - [6.5. sorted set](#65-sorted-set) + - [6.6 bitmap](#66-bitmap) +- [7. Redis 单线程模型详解](#7-redis-单线程模型详解) +- [8. Redis 没有使用多线程?为什么不使用多线程?](#8-redis-没有使用多线程为什么不使用多线程) +- [9. Redis6.0 之后为何引入了多线程?](#9-redis60-之后为何引入了多线程) +- [10. Redis 给缓存数据设置过期时间有啥用?](#10-redis-给缓存数据设置过期时间有啥用) +- [11. Redis 是如何判断数据是否过期的呢?](#11-redis-是如何判断数据是否过期的呢) +- [12. 过期的数据的删除策略了解么?](#12-过期的数据的删除策略了解么) +- [13. Redis 内存淘汰机制了解么?](#13-redis-内存淘汰机制了解么) +- [14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)](#14-redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复) +- [15. Redis 事务](#15-redis-事务) +- [16. 缓存穿透](#16-缓存穿透) + - [16.1. 什么是缓存穿透?](#161-什么是缓存穿透) + - [16.2. 缓存穿透情况的处理流程是怎样的?](#162-缓存穿透情况的处理流程是怎样的) + - [16.3. 有哪些解决办法?](#163-有哪些解决办法) +- [17. 缓存雪崩](#17-缓存雪崩) + - [17.1. 什么是缓存雪崩?](#171-什么是缓存雪崩) + - [17.2. 有哪些解决办法?](#172-有哪些解决办法) +- [18. 如何保证缓存和数据库数据的一致性?](#18-如何保证缓存和数据库数据的一致性) +- [19. 参考](#19-参考) +- [20. 公众号](#20-公众号) + + + + + +### 1. 简单介绍一下 Redis 呗! + +简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。 + +另外,**Redis 除了做缓存之外,Redis 也经常用来做分布式锁,甚至是消息队列。** + +**Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。** + +### 2. 分布式缓存常见的技术选型方案有哪些? + +分布式缓存的话,使用的比较多的主要是 **Memcached** 和 **Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**。 + +Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。 + +分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。 + +### 3. 说一下 Redis 和 Memcached 的区别和共同点 + +现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!不过,了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据! + +**共同点** : + +1. 都是基于内存的数据库,一般都用来当做缓存使用。 +2. 都有过期策略。 +3. 两者的性能都非常高。 + +**区别** : + +1. **Redis 支持更丰富的数据类型(支持更复杂的应用场景)**。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。 +2. **Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。** +3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。 +4. **Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。** +5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.** +6. **Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。** (Redis 6.0 引入了多线程 IO ) +7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。** +8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。** + +相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。 + +### 4. 缓存数据的处理流程是怎样的? + +作为暖男一号,我给大家画了一个草图。 + +![正常缓存处理流程](images/redis-all/缓存的处理流程.png) + +简单来说就是: + +1. 如果用户请求的数据在缓存中就直接返回。 +2. 缓存中不存在的话就看数据库中是否存在。 +3. 数据库中存在的话就更新缓存中的数据。 +4. 数据库中不存在的话就返回空数据。 + +### 5. 为什么要用 Redis/为什么要用缓存? + +_简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。_ + +下面我们主要从“高性能”和“高并发”这两点来看待这个问题。 + +![](./images/redis-all/使用缓存之后.png) + +**高性能** : + +对照上面 👆 我画的图。我们设想这样的场景: + +假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。 + +**这样有什么好处呢?** 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。 + +不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可! + +**高并发:** + +一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。 + +> QPS(Query Per Second):服务器每秒可以执行的查询次数; + +所以,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高的系统整体的并发。 + +### 6. Redis 常见数据结构以及使用场景分析 + +你可以自己本机安装 redis 或者通过 redis 官网提供的[在线 redis 环境](https://try.redis.io/)。 + +![try-redis](./images/redis-all/try-redis.png) + +#### 6.1. string + +1. **介绍** :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 **简单动态字符串**(simple dynamic string,**SDS**)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。 +2. **常用命令:** `set,get,strlen,exists,dect,incr,setex` 等等。 +3. **应用场景** :一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。 + +下面我们简单看看它的使用! + +**普通字符串的基本操作:** + +```bash +127.0.0.1:6379> set key value #设置 key-value 类型的值 +OK +127.0.0.1:6379> get key # 根据 key 获得对应的 value +"value" +127.0.0.1:6379> exists key # 判断某个 key 是否存在 +(integer) 1 +127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。 +(integer) 5 +127.0.0.1:6379> del key # 删除某个 key 对应的值 +(integer) 1 +127.0.0.1:6379> get key +(nil) +``` + +**批量设置** : + +```bash +127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值 +OK +127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value +1) "value1" +2) "value2" +``` + +**计数器(字符串的内容为整数的时候可以使用):** + +```bash + +127.0.0.1:6379> set number 1 +OK +127.0.0.1:6379> incr number # 将 key 中储存的数字值增一 +(integer) 2 +127.0.0.1:6379> get number +"2" +127.0.0.1:6379> decr number # 将 key 中储存的数字值减一 +(integer) 1 +127.0.0.1:6379> get number +"1" +``` + +**过期**: + +```bash +127.0.0.1:6379> expire key 60 # 数据在 60s 后过期 +(integer) 1 +127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire) +OK +127.0.0.1:6379> ttl key # 查看数据还有多久过期 +(integer) 56 +``` + +#### 6.2. list + +1. **介绍** :**list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 +2. **常用命令:** `rpush,lpop,lpush,rpop,lrange、llen` 等。 +3. **应用场景:** 发布与订阅或者说消息队列、慢查询。 + +下面我们简单看看它的使用! + +**通过 `rpush/lpop` 实现队列:** + +```bash +127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素 +(integer) 1 +127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素 +(integer) 3 +127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出 +"value1" +127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end +1) "value2" +2) "value3" +127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一 +1) "value2" +2) "value3" +``` + +**通过 `rpush/rpop` 实现栈:** + +```bash +127.0.0.1:6379> rpush myList2 value1 value2 value3 +(integer) 3 +127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出 +"value3" +``` + +我专门花了一个图方便小伙伴们来理解: + +![redis list](./images/redis-all/redis-list.png) + +**通过 `lrange` 查看对应下标范围的列表元素:** + +```bash +127.0.0.1:6379> rpush myList value1 value2 value3 +(integer) 3 +127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end +1) "value1" +2) "value2" +127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一 +1) "value1" +2) "value2" +3) "value3" +``` + +通过 `lrange` 命令,你可以基于 list 实现分页查询,性能非常高! + +**通过 `llen` 查看链表长度:** + +```bash +127.0.0.1:6379> llen myList +(integer) 3 +``` + +#### 6.3. hash + +1. **介绍** :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,**特别适合用于存储对象**,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。 +2. **常用命令:** `hset,hmset,hexists,hget,hgetall,hkeys,hvals` 等。 +3. **应用场景:** 系统中对象数据的存储。 + +下面我们简单看看它的使用! + +```bash +127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24" +OK +127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。 +(integer) 1 +127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。 +"guide" +127.0.0.1:6379> hget userInfoKey age +"24" +127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值 +1) "name" +2) "guide" +3) "description" +4) "dev" +5) "age" +6) "24" +127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表 +1) "name" +2) "description" +3) "age" +127.0.0.1:6379> hvals userInfoKey # 获取 value 列表 +1) "guide" +2) "dev" +3) "24" +127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值 +127.0.0.1:6379> hget userInfoKey name +"GuideGeGe" +``` + +#### 6.4. set + +1. **介绍 :** set 类似于 Java 中的 `HashSet` 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。 +2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。 +3. **应用场景:** 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景 + +下面我们简单看看它的使用! + +```bash +127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去 +(integer) 2 +127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素 +(integer) 0 +127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素 +1) "value1" +2) "value2" +127.0.0.1:6379> scard mySet # 查看 set 的长度 +(integer) 2 +127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素 +(integer) 1 +127.0.0.1:6379> sadd mySet2 value2 value3 +(integer) 2 +127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中 +(integer) 1 +127.0.0.1:6379> smembers mySet3 +1) "value2" +``` + +#### 6.5. sorted set + +1. **介绍:** 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。 +2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。 +3. **应用场景:** 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。 + +```bash +127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重 +(integer) 1 +127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素 +(integer) 2 +127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量 +(integer) 3 +127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重 +"3" +127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素 +1) "value3" +2) "value2" +3) "value1" +127.0.0.1:6379> zrange myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start 1 为 stop +1) "value3" +2) "value2" +127.0.0.1:6379> zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1 为 stop +1) "value1" +2) "value2" +``` + +#### 6.6 bitmap + +1. **介绍 :** bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。 +2. **常用命令:** `setbit` 、`getbit` 、`bitcount`、`bitop` +3. **应用场景:** 适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。 + +```bash +# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位 +127.0.0.1:6379> setbit mykey 7 1 +(integer) 0 +127.0.0.1:6379> setbit mykey 7 0 +(integer) 1 +127.0.0.1:6379> getbit mykey 7 +(integer) 0 +127.0.0.1:6379> setbit mykey 6 1 +(integer) 0 +127.0.0.1:6379> setbit mykey 8 1 +(integer) 0 +# 通过 bitcount 统计被被设置为 1 的位的数量。 +127.0.0.1:6379> bitcount mykey +(integer) 2 +``` + +针对上面提到的一些场景,这里进行进一步说明。 + +**使用场景一:用户行为分析** +很多网站为了分析你的喜好,需要研究你点赞过的内容。 + +```bash +# 记录你喜欢过 001 号小姐姐 +127.0.0.1:6379> setbit beauty_girl_001 uid 1 +``` + +**使用场景二:统计活跃用户** + +使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1 + +那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个 redis 的命令 + +```bash +# 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 +# BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数 +BITOP operation destkey key [key ...] +``` + +初始化数据: + +```bash +127.0.0.1:6379> setbit 20210308 1 1 +(integer) 0 +127.0.0.1:6379> setbit 20210308 2 1 +(integer) 0 +127.0.0.1:6379> setbit 20210309 1 1 +(integer) 0 +``` + +统计 20210308~20210309 总活跃用户数: 1 + +```bash +127.0.0.1:6379> bitop and desk1 20210308 20210309 +(integer) 1 +127.0.0.1:6379> bitcount desk1 +(integer) 1 +``` + +统计 20210308~20210309 在线活跃用户数: 2 + +```bash +127.0.0.1:6379> bitop or desk2 20210308 20210309 +(integer) 1 +127.0.0.1:6379> bitcount desk2 +(integer) 2 +``` + +**使用场景三:用户在线状态** + +对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间效率又高的一种方法。 + +只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。 + +### 7. Redis 单线程模型详解 + +**Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型** (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。 + +**既然是单线程,那怎么监听大量的客户端连接呢?** + +Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。 + +这样的好处非常明显: **I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。 + +另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: 1. 文件事件; 2. 时间事件。 + +时间事件不需要多花时间了解,我们接触最多的还是 **文件事件**(客户端进行读取写入等操作,涉及一系列网络通信)。 + +《Redis 设计与实现》有一段话是如是介绍文件事件的,我觉得写得挺不错。 + +> Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据 套接字目前执行的任务来为套接字关联不同的事件处理器。 +> +> 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。 +> +> **虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字**,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。 + +可以看出,文件事件处理器(file event handler)主要是包含 4 个部分: + +- 多个 socket(客户端连接) +- IO 多路复用程序(支持多个客户端连接的关键) +- 文件事件分派器(将 socket 关联到相应的事件处理器) +- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) + +![](images/redis-all/redis事件处理器.png) + +

《Redis设计与实现:12章》

+ +### 8. Redis 没有使用多线程?为什么不使用多线程? + +虽然说 Redis 是单线程模型,但是, 实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。** + +![redis4.0 more thread](images/redis-all/redis4.0-more-thread.png) + +不过,Redis 4.0 增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主处理之外的其他线程来“异步处理”。 + +大体上来说,**Redis 6.0 之前主要还是单线程处理。** + +**那,Redis6.0 之前 为什么不使用多线程?** + +我觉得主要原因有下面 3 个: + +1. 单线程编程容易并且更容易维护; +2. Redis 的性能瓶颈不再 CPU ,主要在内存和网络; +3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。 + +### 9. Redis6.0 之后为何引入了多线程? + +**Redis6.0 引入多线程主要是为了提高网络 IO 读写性能**,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。 + +虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了, 执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。 + +Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件 `redis.conf` : + +```bash +io-threads-do-reads yes +``` + +开启多线程后,还需要设置线程数,否则是不生效的。同样需要修改 redis 配置文件 `redis.conf` : + +```bash +io-threads 4 #官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程 +``` + +推荐阅读: + +1. [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw) +2. [为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/) + +### 10. Redis 给缓存数据设置过期时间有啥用? + +一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢? + +因为内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory。 + +Redis 自带了给缓存数据设置过期时间的功能,比如: + +```bash +127.0.0.1:6379> exp key 60 # 数据在 60s 后过期 +(integer) 1 +127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire) +OK +127.0.0.1:6379> ttl key # 查看数据还有多久过期 +(integer) 56 +``` + +注意:**Redis 中除了字符串类型有自己独有设置过期时间的命令 `setex` 外,其他方法都需要依靠 `expire` 命令来设置过期时间 。另外, `persist` 命令可以移除一个键的过期时间: ** + +**过期时间除了有助于缓解内存的消耗,还有什么其他用么?** + +很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。 + +如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。 + +### 11. Redis 是如何判断数据是否过期的呢? + +Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。 + +![redis过期字典](images/redis-all/redis过期时间.png) + +过期字典是存储在 redisDb 这个结构里的: + +```c +typedef struct redisDb { + ... + + dict *dict; //数据库键空间,保存着数据库中所有键值对 + dict *expires // 过期字典,保存着键的过期时间 + ... +} redisDb; +``` + +### 12. 过期的数据的删除策略了解么? + +如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢? + +常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西): + +1. **惰性删除** :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。 +2. **定期删除** : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。 + +定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 **定期删除+惰性/懒汉式删除** 。 + +但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。 + +怎么解决这个问题呢?答案就是: **Redis 内存淘汰机制。** + +### 13. Redis 内存淘汰机制了解么? + +> 相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据? + +Redis 提供 6 种数据淘汰策略: + +1. **volatile-lru(least recently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 +2. **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 +3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 +4. **allkeys-lru(least recently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的) +5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰 +6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! + +4.0 版本后增加以下两种: + +7. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 +8. **allkeys-lfu(least frequently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key + +### 14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复) + +很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 + +Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。**Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 + +**快照(snapshotting)持久化(RDB)** + +Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。 + +快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置: + +```conf +save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 + +save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 + +save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 +``` + +**AOF(append-only file)持久化** + +与快照持久化相比,AOF 持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启: + +```conf +appendonly yes +``` + +开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。 + +在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: + +```conf +appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 +appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 +appendfsync no #让操作系统决定何时进行同步 +``` + +为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 + +**相关 issue** :[783:Redis 的 AOF 方式](https://github.com/Snailclimb/JavaGuide/issues/783) + +**拓展:Redis 4.0 对于持久化机制的优化** + +Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 + +如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 + +**补充内容:AOF 重写** + +AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。 + +AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。 + +在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作 + +### 15. Redis 事务 + +Redis 可以通过 **MULTI,EXEC,DISCARD 和 WATCH** 等命令来实现事务(transaction)功能。 + +```bash +> MULTI +OK +> INCR foo +QUEUED +> INCR bar +QUEUED +> EXEC +1) (integer) 1 +2) (integer) 1 +``` + +使用 [MULTI](https://redis.io/commands/multi)命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了[EXEC](https://redis.io/commands/exec)命令将执行所有命令。 + +Redis 官网相关介绍 [https://redis.io/topics/transactions](https://redis.io/topics/transactions) 如下: + +![redis事务](images/redis-all/redis事务.png) + +但是,Redis 的事务和我们平时理解的关系型数据库的事务不同。我们知道事务具有四大特性: **1. 原子性**,**2. 隔离性**,**3. 持久性**,**4. 一致性**。 + +1. **原子性(Atomicity):** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; +2. **隔离性(Isolation):** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; +3. **持久性(Durability):** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。 +4. **一致性(Consistency):** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的; + +**Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。** + +Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚,这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。 + +![redis roll back](images/redis-all/redis-rollBack.png) + +你可以将 Redis 中的事务就理解为 :**Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。** + +**相关 issue** :[issue452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) ,推荐阅读:[https://zhuanlan.zhihu.com/p/43897838](https://zhuanlan.zhihu.com/p/43897838) 。 + +### 16. 缓存穿透 + +#### 16.1. 什么是缓存穿透? + +缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。 + +#### 16.2. 缓存穿透情况的处理流程是怎样的? + +如下图所示,用户的请求最终都要跑到数据库中查询一遍。 + +![缓存穿透情况](./images/redis-all/缓存穿透情况.png) + +#### 16.3. 有哪些解决办法? + +最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。 + +**1)缓存无效 key** + +如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下: `SET key value EX 10086` 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。 + +另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值` 。 + +如果用 Java 代码展示的话,差不多是下面这样的: + +```java +public Object getObjectInclNullById(Integer id) { + // 从缓存中获取数据 + Object cacheValue = cache.get(id); + // 缓存为空 + if (cacheValue == null) { + // 从数据库中获取 + Object storageValue = storage.get(key); + // 缓存空对象 + cache.set(key, storageValue); + // 如果存储数据为空,需要设置一个过期时间(300秒) + if (storageValue == null) { + // 必须设置过期时间,否则有被攻击的风险 + cache.expire(key, 60 * 5); + } + return storageValue; + } + return cacheValue; +} +``` + +**2)布隆过滤器** + +布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。 + +具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。 + +加入布隆过滤器之后的缓存处理流程图如下。 + +![image](images/redis-all/加入布隆过滤器后的缓存处理流程.png) + +但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: **布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。** + +_为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理来说!_ + +我们先来看一下,**当一个元素加入布隆过滤器中的时候,会进行哪些操作:** + +1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。 +2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。 + +我们再来看一下,**当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行哪些操作:** + +1. 对给定元素再次进行相同的哈希计算; +2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。 + +然后,一定会出现这样一种情况:**不同的字符串可能哈希出来的位置相同。** (可以适当增加位数组大小或者调整我们的哈希函数来降低概率) + +更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。 + +### 17. 缓存雪崩 + +#### 17.1. 什么是缓存雪崩? + +我发现缓存雪崩这名字起的有点意思,哈哈。 + +实际上,缓存雪崩描述的就是这样一个简单的场景:**缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。** 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。 + +举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。 + +还有一种缓存雪崩的场景是:**有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。** 这样的情况,有下面几种解决办法: + +举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。 + +#### 17.2. 有哪些解决办法? + +**针对 Redis 服务不可用的情况:** + +1. 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。 +2. 限流,避免同时处理大量的请求。 + +**针对热点缓存失效的情况:** + +1. 设置不同的失效时间比如随机设置缓存的失效时间。 +2. 缓存永不失效。 + +### 18. 如何保证缓存和数据库数据的一致性? + +细说的话可以扯很多,但是我觉得其实没太大必要(小声 BB:很多解决方案我也没太弄明白)。我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。 + +下面单独对 **Cache Aside Pattern(旁路缓存模式)** 来聊聊。 + +Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删除 cache 。 + +如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案: + +1. **缓存失效时间变短(不推荐,治标不治本)** :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。 +2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将 缓存中对应的 key 删除即可。 + +### 19. 参考 + +- 《Redis 开发与运维》 +- 《Redis 设计与实现》 +- Redis 命令总结:http://Redisdoc.com/string/set.html +- 通俗易懂的 Redis 数据结构基础教程:[https://juejin.im/post/5b53ee7e5188251aaa2d2e16](https://juejin.im/post/5b53ee7e5188251aaa2d2e16) +- WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153) + +### 20. 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java 面试突击"** 即可免费领取! + +**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 + +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) diff --git "a/docs/database/Redis/redis\351\233\206\347\276\244\344\273\245\345\217\212\345\272\224\347\224\250\345\234\272\346\231\257.md" "b/docs/database/Redis/redis\351\233\206\347\276\244\344\273\245\345\217\212\345\272\224\347\224\250\345\234\272\346\231\257.md" new file mode 100644 index 0000000000000000000000000000000000000000..dfa0d40e834f951d1a4aa3d4157010b324896679 --- /dev/null +++ "b/docs/database/Redis/redis\351\233\206\347\276\244\344\273\245\345\217\212\345\272\224\347\224\250\345\234\272\346\231\257.md" @@ -0,0 +1,269 @@ +相关阅读: + +- [史上最全Redis高可用技术解决方案大全](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484850&idx=1&sn=3238360bfa8105cf758dcf7354af2814&chksm=cea24a79f9d5c36fb2399aafa91d7fb2699b5006d8d037fe8aaf2e5577ff20ae322868b04a87&token=1082669959&lang=zh_CN&scene=21#wechat_redirect) +- [Raft协议实战之Redis Sentinel的选举Leader源码解析](http://weizijun.cn/2015/04/30/Raft%E5%8D%8F%E8%AE%AE%E5%AE%9E%E6%88%98%E4%B9%8BRedis%20Sentinel%E7%9A%84%E9%80%89%E4%B8%BELeader%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/) + +目录: + + + +- [Redis 集群以及应用](#redis-集群以及应用) + - [集群](#集群) + - [主从复制](#主从复制) + - [主从链(拓扑结构)](#主从链拓扑结构) + - [复制模式](#复制模式) + - [问题点](#问题点) + - [哨兵机制](#哨兵机制) + - [拓扑图](#拓扑图) + - [节点下线](#节点下线) + - [Leader选举](#Leader选举) + - [故障转移](#故障转移) + - [读写分离](#读写分离) + - [定时任务](#定时任务) + - [分布式集群(Cluster)](#分布式集群cluster) + - [拓扑图](#拓扑图) + - [通讯](#通讯) + - [集中式](#集中式) + - [Gossip](#gossip) + - [寻址分片](#寻址分片) + - [hash取模](#hash取模) + - [一致性hash](#一致性hash) + - [hash槽](#hash槽) + - [使用场景](#使用场景) + - [热点数据](#热点数据) + - [会话维持 Session](#会话维持-session) + - [分布式锁 SETNX](#分布式锁-setnx) + - [表缓存](#表缓存) + - [消息队列 list](#消息队列-list) + - [计数器 string](#计数器-string) + - [缓存设计](#缓存设计) + - [更新策略](#更新策略) + - [更新一致性](#更新一致性) + - [缓存粒度](#缓存粒度) + - [缓存穿透](#缓存穿透) + - [解决方案](#解决方案) + - [缓存雪崩](#缓存雪崩) + - [出现后应对](#出现后应对) + - [请求过程](#请求过程) + + + +# Redis 集群以及应用 + +## 集群 + +### 主从复制 + +#### 主从链(拓扑结构) + + + +![主从](https://user-images.githubusercontent.com/26766909/67539461-d1a26c00-f714-11e9-81ae-61fa89faf156.png) + +![主从](https://user-images.githubusercontent.com/26766909/67539485-e0891e80-f714-11e9-8980-d253239fcd8b.png) + +#### 复制模式 +- 全量复制:Master 全部同步到 Slave +- 部分复制:Slave 数据丢失进行备份 + +#### 问题点 +- 同步故障 + - 复制数据延迟(不一致) + - 读取过期数据(Slave 不能删除数据) + - 从节点故障 + - 主节点故障 +- 配置不一致 + - maxmemory 不一致:丢失数据 + - 优化参数不一致:内存不一致. +- 避免全量复制 + - 选择小主节点(分片)、低峰期间操作. + - 如果节点运行 id 不匹配(如主节点重启、运行 id 发送变化),此时要执行全量复制,应该配合哨兵和集群解决. + - 主从复制挤压缓冲区不足产生的问题(网络中断,部分复制无法满足),可增大复制缓冲区( rel_backlog_size 参数). +- 复制风暴 + +### 哨兵机制 + +#### 拓扑图 + +![哨兵机制-拓扑图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/哨兵机制-拓扑图.png) + +#### 节点下线 + +- 主观下线 + - 即 Sentinel 节点对 Redis 节点失败的偏见,超出超时时间认为 Master 已经宕机。 + - Sentinel 集群的每一个 Sentinel 节点会定时对 Redis 集群的所有节点发心跳包检测节点是否正常。如果一个节点在 `down-after-milliseconds` 时间内没有回复 Sentinel 节点的心跳包,则该 Redis 节点被该 Sentinel 节点主观下线。 +- 客观下线 + - 所有 Sentinel 节点对 Redis 节点失败要达成共识,即超过 quorum 个统一。 + - 当节点被一个 Sentinel 节点记为主观下线时,并不意味着该节点肯定故障了,还需要 Sentinel 集群的其他 Sentinel 节点共同判断为主观下线才行。 + - 该 Sentinel 节点会询问其它 Sentinel 节点,如果 Sentinel 集群中超过 quorum 数量的 Sentinel 节点认为该 Redis 节点主观下线,则该 Redis 客观下线。 + +#### Leader选举 + +- 选举出一个 Sentinel 作为 Leader:集群中至少有三个 Sentinel 节点,但只有其中一个节点可完成故障转移.通过以下命令可以进行失败判定或领导者选举。 +- 选举流程 + 1. 每个主观下线的 Sentinel 节点向其他 Sentinel 节点发送命令,要求设置它为领导者. + 2. 收到命令的 Sentinel 节点如果没有同意通过其他 Sentinel 节点发送的命令,则同意该请求,否则拒绝。 + 3. 如果该 Sentinel 节点发现自己的票数已经超过 Sentinel 集合半数且超过 quorum,则它成为领导者。 + 4. 如果此过程有多个 Sentinel 节点成为领导者,则等待一段时间再重新进行选举。 + +#### 故障转移 + +- 转移流程 + 1. Sentinel 选出一个合适的 Slave 作为新的 Master(slaveof no one 命令)。 + 2. 向其余 Slave 发出通知,让它们成为新 Master 的 Slave( parallel-syncs 参数)。 + 3. 等待旧 Master 复活,并使之称为新 Master 的 Slave。 + 4. 向客户端通知 Master 变化。 +- 从 Slave 中选择新 Master 节点的规则(slave 升级成 master 之后) + 1. 选择 slave-priority 最高的节点。 + 2. 选择复制偏移量最大的节点(同步数据最多)。 + 3. 选择 runId 最小的节点。 + +>Sentinel 集群运行过程中故障转移完成,所有 Sentinel 又会恢复平等。Leader 仅仅是故障转移操作出现的角色。 + +#### 读写分离 + +#### 定时任务 + +- 每 1s 每个 Sentinel 对其他 Sentinel 和 Redis 执行 ping,进行心跳检测。 +- 每 2s 每个 Sentinel 通过 Master 的 Channel 交换信息(pub - sub)。 +- 每 10s 每个 Sentinel 对 Master 和 Slave 执行 info,目的是发现 Slave 节点、确定主从关系。 + +### 分布式集群(Cluster) + +#### 拓扑图 + +![image](https://user-images.githubusercontent.com/26766909/67539510-f8f93900-f714-11e9-9d8d-08afdecff95a.png) + +#### 通讯 + +##### 集中式 + +> 将集群元数据(节点信息、故障等等)几种存储在某个节点上。 +- 优势 + 1. 元数据的更新读取具有很强的时效性,元数据修改立即更新 +- 劣势 + 1. 数据集中存储 + +##### Gossip + +![image](https://user-images.githubusercontent.com/26766909/67539546-16c69e00-f715-11e9-9891-1e81b6af624c.png) + +- [Gossip 协议](https://www.jianshu.com/p/8279d6fd65bb) + +#### 寻址分片 + +##### hash取模 + +- hash(key)%机器数量 +- 问题 + 1. 机器宕机,造成数据丢失,数据读取失败 + 1. 伸缩性 + +##### 一致性hash + +- ![image](https://user-images.githubusercontent.com/26766909/67539595-352c9980-f715-11e9-8e4a-9d9c04027785.png) + +- 问题 + 1. 一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。 + - 解决方案 + - 可以通过引入虚拟节点机制解决:即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。 + +##### hash槽 + +- CRC16(key)%16384 +- +![image](https://user-images.githubusercontent.com/26766909/67539610-3fe72e80-f715-11e9-8e0d-ea58bc965795.png) + +## 使用场景 + +### 热点数据 + +存取数据优先从 Redis 操作,如果不存在再从文件(例如 MySQL)中操作,从文件操作完后将数据存储到 Redis 中并返回。同时有个定时任务后台定时扫描 Redis 的 key,根据业务规则进行淘汰,防止某些只访问一两次的数据一直存在 Redis 中。 +>例如使用 Zset 数据结构,存储 Key 的访问次数/最后访问时间作为 Score,最后做排序,来淘汰那些最少访问的 Key。 + +如果企业级应用,可以参考:[阿里云的 Redis 混合存储版][1] + +### 会话维持 Session + +会话维持 Session 场景,即使用 Redis 作为分布式场景下的登录中心存储应用。每次不同的服务在登录的时候,都会去统一的 Redis 去验证 Session 是否正确。但是在微服务场景,一般会考虑 Redis + JWT 做 Oauth2 模块。 +>其中 Redis 存储 JWT 的相关信息主要是留出口子,方便以后做统一的防刷接口,或者做登录设备限制等。 + +### 分布式锁 SETNX + +命令格式:`SETNX key value`:当且仅当 key 不存在,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX 不做任何动作。 + +1. 超时时间设置:获取锁的同时,启动守护线程,使用 expire 进行定时更新超时时间。如果该业务机器宕机,守护线程也挂掉,这样也会自动过期。如果该业务不是宕机,而是真的需要这么久的操作时间,那么增加超时时间在业务上也是可以接受的,但是肯定有个最大的阈值。 +2. 但是为了增加高可用,需要使用多台 Redis,就增加了复杂性,就可以参考 Redlock:[Redlock分布式锁](Redlock分布式锁.md#怎么在单节点上实现分布式锁) + +### 表缓存 + +Redis 缓存表的场景有黑名单、禁言表等。访问频率较高,即读高。根据业务需求,可以使用后台定时任务定时刷新 Redis 的缓存表数据。 + +### 消息队列 list + +主要使用了 List 数据结构。 +List 支持在头部和尾部操作,因此可以实现简单的消息队列。 +1. 发消息:在 List 尾部塞入数据。 +2. 消费消息:在 List 头部拿出数据。 + +同时可以使用多个 List,来实现多个队列,根据不同的业务消息,塞入不同的 List,来增加吞吐量。 + +### 计数器 string + +主要使用了 INCR、DECR、INCRBY、DECRBY 方法。 + +INCR key:给 key 的 value 值增加一 +DECR key:给 key 的 value 值减去一 + +## 缓存设计 + +### 更新策略 + +- LRU、LFU、FIFO 算法自动清除:一致性最差,维护成本低。 +- 超时自动清除(key expire):一致性较差,维护成本低。 +- 主动更新:代码层面控制生命周期,一致性最好,维护成本高。 + +在 Redis 根据在 redis.conf 的参数 `maxmemory` 来做更新淘汰策略: +1. noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL 命令)。 +2. allkeys-lru: 所有 key 通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。 +3. volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。 +4. allkeys-random: 所有key通用; 随机删除一部分 key。 +5. volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。 +6. volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。 + +### 更新一致性 + +- 读请求:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 +- 写请求:先删除缓存,然后再更新数据库(避免大量地写、却又不经常读的数据导致缓存频繁更新)。 + +### 缓存粒度 + +- 通用性:全量属性更好。 +- 占用空间:部分属性更好。 +- 代码维护成本。 + +### 缓存穿透 + +> 当大量的请求无命中缓存、直接请求到后端数据库(业务代码的 bug、或恶意攻击),同时后端数据库也没有查询到相应的记录、无法添加缓存。 +> 这种状态会一直维持,流量一直打到存储层上,无法利用缓存、还会给存储层带来巨大压力。 + +#### 解决方案 + +1. 请求无法命中缓存、同时数据库记录为空时在缓存添加该 key 的空对象(设置过期时间),缺点是可能会在缓存中添加大量的空值键(比如遭到恶意攻击或爬虫),而且缓存层和存储层数据短期内不一致; +2. 使用布隆过滤器在缓存层前拦截非法请求、自动为空值添加黑名单(同时可能要为误判的记录添加白名单).但需要考虑布隆过滤器的维护(离线生成/ 实时生成)。 + +### 缓存雪崩 + +> 缓存崩溃时请求会直接落到数据库上,很可能由于无法承受大量的并发请求而崩溃,此时如果只重启数据库,或因为缓存重启后没有数据,新的流量进来很快又会把数据库击倒。 + +#### 出现后应对 + +- 事前:Redis 高可用,主从 + 哨兵,Redis Cluster,避免全盘崩溃。 +- 事中:本地 ehcache 缓存 + hystrix 限流 & 降级,避免数据库承受太多压力。 +- 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 + +#### 请求过程 + +1. 用户请求先访问本地缓存,无命中后再访问 Redis,如果本地缓存和 Redis 都没有再查数据库,并把数据添加到本地缓存和 Redis; +2. 由于设置了限流,一段时间范围内超出的请求走降级处理(返回默认值,或给出友情提示)。 + diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" "b/docs/database/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" similarity index 100% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" rename to "docs/database/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" diff --git "a/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" "b/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" new file mode 100644 index 0000000000000000000000000000000000000000..385aa37dc7ad69f19fc53ad2a5420e81dceb1635 --- /dev/null +++ "b/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" @@ -0,0 +1,973 @@ +> 原文地址:https://shockerli.net/post/1000-line-mysql-note/ ,JavaGuide 对本文进行了简答排版,新增了目录。 +> 作者:格物 + +非常不错的总结,强烈建议保存下来,需要的时候看一看。 + + +- [基本操作](#基本操作) +- [数据库操作](#数据库操作) +- [表的操作](#表的操作) +- [数据操作](#数据操作) +- [字符集编码](#字符集编码) +- [数据类型(列类型)](#数据类型列类型) +- [列属性(列约束)](#列属性列约束) +- [建表规范](#建表规范) +- [SELECT](#select) +- [UNION](#union) +- [子查询](#子查询) +- [连接查询(join)](#连接查询join) +- [TRUNCATE](#truncate) +- [备份与还原](#备份与还原) +- [视图](#视图) +- [事务(transaction)](#事务transaction) +- [锁表](#锁表) +- [触发器](#触发器) +- [SQL编程](#sql编程) +- [存储过程](#存储过程) +- [用户和权限管理](#用户和权限管理) +- [表维护](#表维护) +- [杂项](#杂项) + + + +### 基本操作 + +```mysql +/* Windows服务 */ +-- 启动MySQL + net start mysql +-- 创建Windows服务 + sc create mysql binPath= mysqld_bin_path(注意:等号与值之间有空格) +/* 连接与断开服务器 */ +mysql -h 地址 -P 端口 -u 用户名 -p 密码 +SHOW PROCESSLIST -- 显示哪些线程正在运行 +SHOW VARIABLES -- 显示系统变量信息 +``` + +### 数据库操作 + +```mysql +/* 数据库操作 */ ------------------ +-- 查看当前数据库 + SELECT DATABASE(); +-- 显示当前时间、用户名、数据库版本 + SELECT now(), user(), version(); +-- 创建库 + CREATE DATABASE[ IF NOT EXISTS] 数据库名 数据库选项 + 数据库选项: + CHARACTER SET charset_name + COLLATE collation_name +-- 查看已有库 + SHOW DATABASES[ LIKE 'PATTERN'] +-- 查看当前库信息 + SHOW CREATE DATABASE 数据库名 +-- 修改库的选项信息 + ALTER DATABASE 库名 选项信息 +-- 删除库 + DROP DATABASE[ IF EXISTS] 数据库名 + 同时删除该数据库相关的目录及其目录内容 +``` + +### 表的操作 + +```mysql +-- 创建表 + CREATE [TEMPORARY] TABLE[ IF NOT EXISTS] [库名.]表名 ( 表的结构定义 )[ 表选项] + 每个字段必须有数据类型 + 最后一个字段后不能有逗号 + TEMPORARY 临时表,会话结束时表自动消失 + 对于字段的定义: + 字段名 数据类型 [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] [COMMENT 'string'] +-- 表选项 + -- 字符集 + CHARSET = charset_name + 如果表没有设定,则使用数据库字符集 + -- 存储引擎 + ENGINE = engine_name + 表在管理数据时采用的不同的数据结构,结构不同会导致处理方式、提供的特性操作等不同 + 常见的引擎:InnoDB MyISAM Memory/Heap BDB Merge Example CSV MaxDB Archive + 不同的引擎在保存表的结构和数据时采用不同的方式 + MyISAM表文件含义:.frm表定义,.MYD表数据,.MYI表索引 + InnoDB表文件含义:.frm表定义,表空间数据和日志文件 + SHOW ENGINES -- 显示存储引擎的状态信息 + SHOW ENGINE 引擎名 {LOGS|STATUS} -- 显示存储引擎的日志或状态信息 + -- 自增起始数 + AUTO_INCREMENT = 行数 + -- 数据文件目录 + DATA DIRECTORY = '目录' + -- 索引文件目录 + INDEX DIRECTORY = '目录' + -- 表注释 + COMMENT = 'string' + -- 分区选项 + PARTITION BY ... (详细见手册) +-- 查看所有表 + SHOW TABLES[ LIKE 'pattern'] + SHOW TABLES FROM 库名 +-- 查看表结构 + SHOW CREATE TABLE 表名 (信息更详细) + DESC 表名 / DESCRIBE 表名 / EXPLAIN 表名 / SHOW COLUMNS FROM 表名 [LIKE 'PATTERN'] + SHOW TABLE STATUS [FROM db_name] [LIKE 'pattern'] +-- 修改表 + -- 修改表本身的选项 + ALTER TABLE 表名 表的选项 + eg: ALTER TABLE 表名 ENGINE=MYISAM; + -- 对表进行重命名 + RENAME TABLE 原表名 TO 新表名 + RENAME TABLE 原表名 TO 库名.表名 (可将表移动到另一个数据库) + -- RENAME可以交换两个表名 + -- 修改表的字段机构(13.1.2. ALTER TABLE语法) + ALTER TABLE 表名 操作名 + -- 操作名 + ADD[ COLUMN] 字段定义 -- 增加字段 + AFTER 字段名 -- 表示增加在该字段名后面 + FIRST -- 表示增加在第一个 + ADD PRIMARY KEY(字段名) -- 创建主键 + ADD UNIQUE [索引名] (字段名)-- 创建唯一索引 + ADD INDEX [索引名] (字段名) -- 创建普通索引 + DROP[ COLUMN] 字段名 -- 删除字段 + MODIFY[ COLUMN] 字段名 字段属性 -- 支持对字段属性进行修改,不能修改字段名(所有原有属性也需写上) + CHANGE[ COLUMN] 原字段名 新字段名 字段属性 -- 支持对字段名修改 + DROP PRIMARY KEY -- 删除主键(删除主键前需删除其AUTO_INCREMENT属性) + DROP INDEX 索引名 -- 删除索引 + DROP FOREIGN KEY 外键 -- 删除外键 +-- 删除表 + DROP TABLE[ IF EXISTS] 表名 ... +-- 清空表数据 + TRUNCATE [TABLE] 表名 +-- 复制表结构 + CREATE TABLE 表名 LIKE 要复制的表名 +-- 复制表结构和数据 + CREATE TABLE 表名 [AS] SELECT * FROM 要复制的表名 +-- 检查表是否有错误 + CHECK TABLE tbl_name [, tbl_name] ... [option] ... +-- 优化表 + OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... +-- 修复表 + REPAIR [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... [QUICK] [EXTENDED] [USE_FRM] +-- 分析表 + ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... +``` + +### 数据操作 + +```mysql +/* 数据操作 */ ------------------ +-- 增 + INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), ...] + -- 如果要插入的值列表包含所有字段并且顺序一致,则可以省略字段列表。 + -- 可同时插入多条数据记录! + REPLACE 与 INSERT 完全一样,可互换。 + INSERT [INTO] 表名 SET 字段名=值[, 字段名=值, ...] +-- 查 + SELECT 字段列表 FROM 表名[ 其他子句] + -- 可来自多个表的多个字段 + -- 其他子句可以不使用 + -- 字段列表可以用*代替,表示所有字段 +-- 删 + DELETE FROM 表名[ 删除条件子句] + 没有条件子句,则会删除全部 +-- 改 + UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新条件] +``` + +### 字符集编码 + +```mysql +/* 字符集编码 */ ------------------ +-- MySQL、数据库、表、字段均可设置编码 +-- 数据编码与客户端编码不需一致 +SHOW VARIABLES LIKE 'character_set_%' -- 查看所有字符集编码项 + character_set_client 客户端向服务器发送数据时使用的编码 + character_set_results 服务器端将结果返回给客户端所使用的编码 + character_set_connection 连接层编码 +SET 变量名 = 变量值 + SET character_set_client = gbk; + SET character_set_results = gbk; + SET character_set_connection = gbk; +SET NAMES GBK; -- 相当于完成以上三个设置 +-- 校对集 + 校对集用以排序 + SHOW CHARACTER SET [LIKE 'pattern']/SHOW CHARSET [LIKE 'pattern'] 查看所有字符集 + SHOW COLLATION [LIKE 'pattern'] 查看所有校对集 + CHARSET 字符集编码 设置字符集编码 + COLLATE 校对集编码 设置校对集编码 +``` + +### 数据类型(列类型) + +```mysql +/* 数据类型(列类型) */ ------------------ +1. 数值类型 +-- a. 整型 ---------- + 类型 字节 范围(有符号位) + tinyint 1字节 -128 ~ 127 无符号位:0 ~ 255 + smallint 2字节 -32768 ~ 32767 + mediumint 3字节 -8388608 ~ 8388607 + int 4字节 + bigint 8字节 + int(M) M表示总位数 + - 默认存在符号位,unsigned 属性修改 + - 显示宽度,如果某个数不够定义字段时设置的位数,则前面以0补填,zerofill 属性修改 + 例:int(5) 插入一个数'123',补填后为'00123' + - 在满足要求的情况下,越小越好。 + - 1表示bool值真,0表示bool值假。MySQL没有布尔类型,通过整型0和1表示。常用tinyint(1)表示布尔型。 +-- b. 浮点型 ---------- + 类型 字节 范围 + float(单精度) 4字节 + double(双精度) 8字节 + 浮点型既支持符号位 unsigned 属性,也支持显示宽度 zerofill 属性。 + 不同于整型,前后均会补填0. + 定义浮点型时,需指定总位数和小数位数。 + float(M, D) double(M, D) + M表示总位数,D表示小数位数。 + M和D的大小会决定浮点数的范围。不同于整型的固定范围。 + M既表示总位数(不包括小数点和正负号),也表示显示宽度(所有显示符号均包括)。 + 支持科学计数法表示。 + 浮点数表示近似值。 +-- c. 定点数 ---------- + decimal -- 可变长度 + decimal(M, D) M也表示总位数,D表示小数位数。 + 保存一个精确的数值,不会发生数据的改变,不同于浮点数的四舍五入。 + 将浮点数转换为字符串来保存,每9位数字保存为4个字节。 +2. 字符串类型 +-- a. char, varchar ---------- + char 定长字符串,速度快,但浪费空间 + varchar 变长字符串,速度慢,但节省空间 + M表示能存储的最大长度,此长度是字符数,非字节数。 + 不同的编码,所占用的空间不同。 + char,最多255个字符,与编码无关。 + varchar,最多65535字符,与编码有关。 + 一条有效记录最大不能超过65535个字节。 + utf8 最大为21844个字符,gbk 最大为32766个字符,latin1 最大为65532个字符 + varchar 是变长的,需要利用存储空间保存 varchar 的长度,如果数据小于255个字节,则采用一个字节来保存长度,反之需要两个字节来保存。 + varchar 的最大有效长度由最大行大小和使用的字符集确定。 + 最大有效长度是65532字节,因为在varchar存字符串时,第一个字节是空的,不存在任何数据,然后还需两个字节来存放字符串的长度,所以有效长度是65535-1-2=65532字节。 + 例:若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少? 答:(65535-1-2-4-30*3)/3 +-- b. blob, text ---------- + blob 二进制字符串(字节字符串) + tinyblob, blob, mediumblob, longblob + text 非二进制字符串(字符字符串) + tinytext, text, mediumtext, longtext + text 在定义时,不需要定义长度,也不会计算总长度。 + text 类型在定义时,不可给default值 +-- c. binary, varbinary ---------- + 类似于char和varchar,用于保存二进制字符串,也就是保存字节字符串而非字符字符串。 + char, varchar, text 对应 binary, varbinary, blob. +3. 日期时间类型 + 一般用整型保存时间戳,因为PHP可以很方便的将时间戳进行格式化。 + datetime 8字节 日期及时间 1000-01-01 00:00:00 到 9999-12-31 23:59:59 + date 3字节 日期 1000-01-01 到 9999-12-31 + timestamp 4字节 时间戳 19700101000000 到 2038-01-19 03:14:07 + time 3字节 时间 -838:59:59 到 838:59:59 + year 1字节 年份 1901 - 2155 +datetime YYYY-MM-DD hh:mm:ss +timestamp YY-MM-DD hh:mm:ss + YYYYMMDDhhmmss + YYMMDDhhmmss + YYYYMMDDhhmmss + YYMMDDhhmmss +date YYYY-MM-DD + YY-MM-DD + YYYYMMDD + YYMMDD + YYYYMMDD + YYMMDD +time hh:mm:ss + hhmmss + hhmmss +year YYYY + YY + YYYY + YY +4. 枚举和集合 +-- 枚举(enum) ---------- +enum(val1, val2, val3...) + 在已知的值中进行单选。最大数量为65535. + 枚举值在保存时,以2个字节的整型(smallint)保存。每个枚举值,按保存的位置顺序,从1开始逐一递增。 + 表现为字符串类型,存储却是整型。 + NULL值的索引是NULL。 + 空字符串错误值的索引值是0。 +-- 集合(set) ---------- +set(val1, val2, val3...) + create table tab ( gender set('男', '女', '无') ); + insert into tab values ('男, 女'); + 最多可以有64个不同的成员。以bigint存储,共8个字节。采取位运算的形式。 + 当创建表时,SET成员值的尾部空格将自动被删除。 +``` + +### 列属性(列约束) + +```mysql +/* 列属性(列约束) */ ------------------ +1. PRIMARY 主键 + - 能唯一标识记录的字段,可以作为主键。 + - 一个表只能有一个主键。 + - 主键具有唯一性。 + - 声明字段时,用 primary key 标识。 + 也可以在字段列表之后声明 + 例:create table tab ( id int, stu varchar(10), primary key (id)); + - 主键字段的值不能为null。 + - 主键可以由多个字段共同组成。此时需要在字段列表后声明的方法。 + 例:create table tab ( id int, stu varchar(10), age int, primary key (stu, age)); +2. UNIQUE 唯一索引(唯一约束) + 使得某字段的值也不能重复。 +3. NULL 约束 + null不是数据类型,是列的一个属性。 + 表示当前列是否可以为null,表示什么都没有。 + null, 允许为空。默认。 + not null, 不允许为空。 + insert into tab values (null, 'val'); + -- 此时表示将第一个字段的值设为null, 取决于该字段是否允许为null +4. DEFAULT 默认值属性 + 当前字段的默认值。 + insert into tab values (default, 'val'); -- 此时表示强制使用默认值。 + create table tab ( add_time timestamp default current_timestamp ); + -- 表示将当前时间的时间戳设为默认值。 + current_date, current_time +5. AUTO_INCREMENT 自动增长约束 + 自动增长必须为索引(主键或unique) + 只能存在一个字段为自动增长。 + 默认为1开始自动增长。可以通过表属性 auto_increment = x进行设置,或 alter table tbl auto_increment = x; +6. COMMENT 注释 + 例:create table tab ( id int ) comment '注释内容'; +7. FOREIGN KEY 外键约束 + 用于限制主表与从表数据完整性。 + alter table t1 add constraint `t1_t2_fk` foreign key (t1_id) references t2(id); + -- 将表t1的t1_id外键关联到表t2的id字段。 + -- 每个外键都有一个名字,可以通过 constraint 指定 + 存在外键的表,称之为从表(子表),外键指向的表,称之为主表(父表)。 + 作用:保持数据一致性,完整性,主要目的是控制存储在外键表(从表)中的数据。 + MySQL中,可以对InnoDB引擎使用外键约束: + 语法: + foreign key (外键字段) references 主表名 (关联字段) [主表记录删除时的动作] [主表记录更新时的动作] + 此时需要检测一个从表的外键需要约束为主表的已存在的值。外键在没有关联的情况下,可以设置为null.前提是该外键列,没有not null。 + 可以不指定主表记录更改或更新时的动作,那么此时主表的操作被拒绝。 + 如果指定了 on update 或 on delete:在删除或更新时,有如下几个操作可以选择: + 1. cascade,级联操作。主表数据被更新(主键值更新),从表也被更新(外键值更新)。主表记录被删除,从表相关记录也被删除。 + 2. set null,设置为null。主表数据被更新(主键值更新),从表的外键被设置为null。主表记录被删除,从表相关记录外键被设置成null。但注意,要求该外键列,没有not null属性约束。 + 3. restrict,拒绝父表删除和更新。 + 注意,外键只被InnoDB存储引擎所支持。其他引擎是不支持的。 + +``` + +### 建表规范 + +```mysql +/* 建表规范 */ ------------------ + -- Normal Format, NF + - 每个表保存一个实体信息 + - 每个具有一个ID字段作为主键 + - ID主键 + 原子表 + -- 1NF, 第一范式 + 字段不能再分,就满足第一范式。 + -- 2NF, 第二范式 + 满足第一范式的前提下,不能出现部分依赖。 + 消除复合主键就可以避免部分依赖。增加单列关键字。 + -- 3NF, 第三范式 + 满足第二范式的前提下,不能出现传递依赖。 + 某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。 + 将一个实体信息的数据放在一个表内实现。 +``` + +### SELECT + +```mysql +/* SELECT */ ------------------ +SELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING -> ORDER BY -> LIMIT +a. select_expr + -- 可以用 * 表示所有字段。 + select * from tb; + -- 可以使用表达式(计算公式、函数调用、字段也是个表达式) + select stu, 29+25, now() from tb; + -- 可以为每个列使用别名。适用于简化列标识,避免多个列标识符重复。 + - 使用 as 关键字,也可省略 as. + select stu+10 as add10 from tb; +b. FROM 子句 + 用于标识查询来源。 + -- 可以为表起别名。使用as关键字。 + SELECT * FROM tb1 AS tt, tb2 AS bb; + -- from子句后,可以同时出现多个表。 + -- 多个表会横向叠加到一起,而数据会形成一个笛卡尔积。 + SELECT * FROM tb1, tb2; + -- 向优化符提示如何选择索引 + USE INDEX、IGNORE INDEX、FORCE INDEX + SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3; + SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3; +c. WHERE 子句 + -- 从from获得的数据源中进行筛选。 + -- 整型1表示真,0表示假。 + -- 表达式由运算符和运算数组成。 + -- 运算数:变量(字段)、值、函数返回值 + -- 运算符: + =, <=>, <>, !=, <=, <, >=, >, !, &&, ||, + in (not) null, (not) like, (not) in, (not) between and, is (not), and, or, not, xor + is/is not 加上ture/false/unknown,检验某个值的真假 + <=>与<>功能相同,<=>可用于null比较 +d. GROUP BY 子句, 分组子句 + GROUP BY 字段/别名 [排序方式] + 分组后会进行排序。升序:ASC,降序:DESC + 以下[合计函数]需配合 GROUP BY 使用: + count 返回不同的非NULL值数目 count(*)、count(字段) + sum 求和 + max 求最大值 + min 求最小值 + avg 求平均值 + group_concat 返回带有来自一个组的连接的非NULL值的字符串结果。组内字符串连接。 +e. HAVING 子句,条件子句 + 与 where 功能、用法相同,执行时机不同。 + where 在开始时执行检测数据,对原数据进行过滤。 + having 对筛选出的结果再次进行过滤。 + having 字段必须是查询出来的,where 字段必须是数据表存在的。 + where 不可以使用字段的别名,having 可以。因为执行WHERE代码时,可能尚未确定列值。 + where 不可以使用合计函数。一般需用合计函数才会用 having + SQL标准要求HAVING必须引用GROUP BY子句中的列或用于合计函数中的列。 +f. ORDER BY 子句,排序子句 + order by 排序字段/别名 排序方式 [,排序字段/别名 排序方式]... + 升序:ASC,降序:DESC + 支持多个字段的排序。 +g. LIMIT 子句,限制结果数量子句 + 仅对处理好的结果进行数量限制。将处理好的结果的看作是一个集合,按照记录出现的顺序,索引从0开始。 + limit 起始位置, 获取条数 + 省略第一个参数,表示从索引0开始。limit 获取条数 +h. DISTINCT, ALL 选项 + distinct 去除重复记录 + 默认为 all, 全部记录 +``` + +### UNION + +```mysql +/* UNION */ ------------------ + 将多个select查询的结果组合成一个结果集合。 + SELECT ... UNION [ALL|DISTINCT] SELECT ... + 默认 DISTINCT 方式,即所有返回的行都是唯一的 + 建议,对每个SELECT查询加上小括号包裹。 + ORDER BY 排序时,需加上 LIMIT 进行结合。 + 需要各select查询的字段数量一样。 + 每个select查询的字段列表(数量、类型)应一致,因为结果中的字段名以第一条select语句为准。 +``` + +### 子查询 + +```mysql +/* 子查询 */ ------------------ + - 子查询需用括号包裹。 +-- from型 + from后要求是一个表,必须给子查询结果取个别名。 + - 简化每个查询内的条件。 + - from型需将结果生成一个临时表格,可用以原表的锁定的释放。 + - 子查询返回一个表,表型子查询。 + select * from (select * from tb where id>0) as subfrom where id>1; +-- where型 + - 子查询返回一个值,标量子查询。 + - 不需要给子查询取别名。 + - where子查询内的表,不能直接用以更新。 + select * from tb where money = (select max(money) from tb); + -- 列子查询 + 如果子查询结果返回的是一列。 + 使用 in 或 not in 完成查询 + exists 和 not exists 条件 + 如果子查询返回数据,则返回1或0。常用于判断条件。 + select column1 from t1 where exists (select * from t2); + -- 行子查询 + 查询条件是一个行。 + select * from t1 where (id, gender) in (select id, gender from t2); + 行构造符:(col1, col2, ...) 或 ROW(col1, col2, ...) + 行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。 + -- 特殊运算符 + != all() 相当于 not in + = some() 相当于 in。any 是 some 的别名 + != some() 不等同于 not in,不等于其中某一个。 + all, some 可以配合其他运算符一起使用。 +``` + +### 连接查询(join) + +```mysql +/* 连接查询(join) */ ------------------ + 将多个表的字段进行连接,可以指定连接条件。 +-- 内连接(inner join) + - 默认就是内连接,可省略inner。 + - 只有数据存在时才能发送连接。即连接结果不能出现空行。 + on 表示连接条件。其条件表达式与where类似。也可以省略条件(表示条件永远为真) + 也可用where表示连接条件。 + 还有 using, 但需字段名相同。 using(字段名) + -- 交叉连接 cross join + 即,没有条件的内连接。 + select * from tb1 cross join tb2; +-- 外连接(outer join) + - 如果数据不存在,也会出现在连接结果中。 + -- 左外连接 left join + 如果数据不存在,左表记录会出现,而右表为null填充 + -- 右外连接 right join + 如果数据不存在,右表记录会出现,而左表为null填充 +-- 自然连接(natural join) + 自动判断连接条件完成连接。 + 相当于省略了using,会自动查找相同字段名。 + natural join + natural left join + natural right join +select info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id; +``` + +### TRUNCATE + +```mysql +/* TRUNCATE */ ------------------ +TRUNCATE [TABLE] tbl_name +清空数据 +删除重建表 +区别: +1,truncate 是删除表再创建,delete 是逐条删除 +2,truncate 重置auto_increment的值。而delete不会 +3,truncate 不知道删除了几条,而delete知道。 +4,当被用于带分区的表时,truncate 会保留分区 +``` + +### 备份与还原 + +```mysql +/* 备份与还原 */ ------------------ +备份,将数据的结构与表内数据保存起来。 +利用 mysqldump 指令完成。 +-- 导出 +mysqldump [options] db_name [tables] +mysqldump [options] ---database DB1 [DB2 DB3...] +mysqldump [options] --all--database +1. 导出一张表 +  mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql) +2. 导出多张表 +  mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql) +3. 导出所有表 +  mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql) +4. 导出一个库 +  mysqldump -u用户名 -p密码 --lock-all-tables --database 库名 > 文件名(D:/a.sql) +可以-w携带WHERE条件 +-- 导入 +1. 在登录mysql的情况下: +  source 备份文件 +2. 在不登录的情况下 +  mysql -u用户名 -p密码 库名 < 备份文件 +``` + +### 视图 + +```mysql +什么是视图: + 视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。 + 视图具有表结构文件,但不存在数据文件。 + 对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个或多个表,或者其它视图。通过视图进行查询没有任何限制,通过它们进行数据修改时的限制也很少。 + 视图是存储在数据库中的查询的sql语句,它主要出于两种原因:安全原因,视图可以隐藏一些数据,如:社会保险基金表,可以用视图只显示姓名,地址,而不显示社会保险号和工资数等,另一原因是可使复杂的查询易于理解和使用。 +-- 创建视图 +CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS select_statement + - 视图名必须唯一,同时不能与表重名。 + - 视图可以使用select语句查询到的列名,也可以自己指定相应的列名。 + - 可以指定视图执行的算法,通过ALGORITHM指定。 + - column_list如果存在,则数目必须等于SELECT语句检索的列数 +-- 查看结构 + SHOW CREATE VIEW view_name +-- 删除视图 + - 删除视图后,数据依然存在。 + - 可同时删除多个视图。 + DROP VIEW [IF EXISTS] view_name ... +-- 修改视图结构 + - 一般不修改视图,因为不是所有的更新视图都会映射到表上。 + ALTER VIEW view_name [(column_list)] AS select_statement +-- 视图作用 + 1. 简化业务逻辑 + 2. 对客户端隐藏真实的表结构 +-- 视图算法(ALGORITHM) + MERGE 合并 + 将视图的查询语句,与外部查询需要先合并再执行! + TEMPTABLE 临时表 + 将视图执行完毕后,形成临时表,再做外层查询! + UNDEFINED 未定义(默认),指的是MySQL自主去选择相应的算法。 +``` + +### 事务(transaction) + +```mysql +事务是指逻辑上的一组操作,组成这组操作的各个单元,要不全成功要不全失败。 + - 支持连续SQL的集体成功或集体撤销。 + - 事务是数据库在数据完整性方面的一个功能。 + - 需要利用 InnoDB 或 BDB 存储引擎,对自动提交的特性支持完成。 + - InnoDB被称为事务安全型引擎。 +-- 事务开启 + START TRANSACTION; 或者 BEGIN; + 开启事务后,所有被执行的SQL语句均被认作当前事务内的SQL语句。 +-- 事务提交 + COMMIT; +-- 事务回滚 + ROLLBACK; + 如果部分操作发生问题,映射到事务开启前。 +-- 事务的特性 + 1. 原子性(Atomicity) + 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 + 2. 一致性(Consistency) + 事务前后数据的完整性必须保持一致。 + - 事务开始和结束时,外部数据一致 + - 在整个事务过程中,操作是连续的 + 3. 隔离性(Isolation) + 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事物所干扰,多个并发事务之间的数据要相互隔离。 + 4. 持久性(Durability) + 一个事务一旦被提交,它对数据库中的数据改变就是永久性的。 +-- 事务的实现 + 1. 要求是事务支持的表类型 + 2. 执行一组相关的操作前开启事务 + 3. 整组操作完成后,都成功,则提交;如果存在失败,选择回滚,则会回到事务开始的备份点。 +-- 事务的原理 + 利用InnoDB的自动提交(autocommit)特性完成。 + 普通的MySQL执行语句后,当前的数据提交操作均可被其他客户端可见。 + 而事务是暂时关闭“自动提交”机制,需要commit提交持久化数据操作。 +-- 注意 + 1. 数据定义语言(DDL)语句不能被回滚,比如创建或取消数据库的语句,和创建、取消或更改表或存储的子程序的语句。 + 2. 事务不能被嵌套 +-- 保存点 + SAVEPOINT 保存点名称 -- 设置一个事务保存点 + ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点 + RELEASE SAVEPOINT 保存点名称 -- 删除保存点 +-- InnoDB自动提交特性设置 + SET autocommit = 0|1; 0表示关闭自动提交,1表示开启自动提交。 + - 如果关闭了,那普通操作的结果对其他客户端也不可见,需要commit提交后才能持久化数据操作。 + - 也可以关闭自动提交来开启事务。但与START TRANSACTION不同的是, + SET autocommit是永久改变服务器的设置,直到下次再次修改该设置。(针对当前连接) + 而START TRANSACTION记录开启前的状态,而一旦事务提交或回滚后就需要再次开启事务。(针对当前事务) + +``` + +### 锁表 + +```mysql +/* 锁表 */ +表锁定只用于防止其它客户端进行不正当地读取和写入 +MyISAM 支持表锁,InnoDB 支持行锁 +-- 锁定 + LOCK TABLES tbl_name [AS alias] +-- 解锁 + UNLOCK TABLES +``` + +### 触发器 + +```mysql +/* 触发器 */ ------------------ + 触发程序是与表有关的命名数据库对象,当该表出现特定事件时,将激活该对象 + 监听:记录的增加、修改、删除。 +-- 创建触发器 +CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt + 参数: + trigger_time是触发程序的动作时间。它可以是 before 或 after,以指明触发程序是在激活它的语句之前或之后触发。 + trigger_event指明了激活触发程序的语句的类型 + INSERT:将新行插入表时激活触发程序 + UPDATE:更改某一行时激活触发程序 + DELETE:从表中删除某一行时激活触发程序 + tbl_name:监听的表,必须是永久性的表,不能将触发程序与TEMPORARY表或视图关联起来。 + trigger_stmt:当触发程序激活时执行的语句。执行多个语句,可使用BEGIN...END复合语句结构 +-- 删除 +DROP TRIGGER [schema_name.]trigger_name +可以使用old和new代替旧的和新的数据 + 更新操作,更新前是old,更新后是new. + 删除操作,只有old. + 增加操作,只有new. +-- 注意 + 1. 对于具有相同触发程序动作时间和事件的给定表,不能有两个触发程序。 +-- 字符连接函数 +concat(str1,str2,...]) +concat_ws(separator,str1,str2,...) +-- 分支语句 +if 条件 then + 执行语句 +elseif 条件 then + 执行语句 +else + 执行语句 +end if; +-- 修改最外层语句结束符 +delimiter 自定义结束符号 + SQL语句 +自定义结束符号 +delimiter ; -- 修改回原来的分号 +-- 语句块包裹 +begin + 语句块 +end +-- 特殊的执行 +1. 只要添加记录,就会触发程序。 +2. Insert into on duplicate key update 语法会触发: + 如果没有重复记录,会触发 before insert, after insert; + 如果有重复记录并更新,会触发 before insert, before update, after update; + 如果有重复记录但是没有发生更新,则触发 before insert, before update +3. Replace 语法 如果有记录,则执行 before insert, before delete, after delete, after insert +``` + +### SQL编程 + +```mysql +/* SQL编程 */ ------------------ +--// 局部变量 ---------- +-- 变量声明 + declare var_name[,...] type [default value] + 这个语句被用来声明局部变量。要给变量提供一个默认值,请包含一个default子句。值可以被指定为一个表达式,不需要为一个常数。如果没有default子句,初始值为null。 +-- 赋值 + 使用 set 和 select into 语句为变量赋值。 + - 注意:在函数内是可以使用全局变量(用户自定义的变量) +--// 全局变量 ---------- +-- 定义、赋值 +set 语句可以定义并为变量赋值。 +set @var = value; +也可以使用select into语句为变量初始化并赋值。这样要求select语句只能返回一行,但是可以是多个字段,就意味着同时为多个变量进行赋值,变量的数量需要与查询的列数一致。 +还可以把赋值语句看作一个表达式,通过select执行完成。此时为了避免=被当作关系运算符看待,使用:=代替。(set语句可以使用= 和 :=)。 +select @var:=20; +select @v1:=id, @v2=name from t1 limit 1; +select * from tbl_name where @var:=30; +select into 可以将表中查询获得的数据赋给变量。 + -| select max(height) into @max_height from tb; +-- 自定义变量名 +为了避免select语句中,用户自定义的变量与系统标识符(通常是字段名)冲突,用户自定义变量在变量名前使用@作为开始符号。 +@var=10; + - 变量被定义后,在整个会话周期都有效(登录到退出) +--// 控制结构 ---------- +-- if语句 +if search_condition then + statement_list +[elseif search_condition then + statement_list] +... +[else + statement_list] +end if; +-- case语句 +CASE value WHEN [compare-value] THEN result +[WHEN [compare-value] THEN result ...] +[ELSE result] +END +-- while循环 +[begin_label:] while search_condition do + statement_list +end while [end_label]; +- 如果需要在循环内提前终止 while循环,则需要使用标签;标签需要成对出现。 + -- 退出循环 + 退出整个循环 leave + 退出当前循环 iterate + 通过退出的标签决定退出哪个循环 +--// 内置函数 ---------- +-- 数值函数 +abs(x) -- 绝对值 abs(-10.9) = 10 +format(x, d) -- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46 +ceil(x) -- 向上取整 ceil(10.1) = 11 +floor(x) -- 向下取整 floor (10.1) = 10 +round(x) -- 四舍五入去整 +mod(m, n) -- m%n m mod n 求余 10%3=1 +pi() -- 获得圆周率 +pow(m, n) -- m^n +sqrt(x) -- 算术平方根 +rand() -- 随机数 +truncate(x, d) -- 截取d位小数 +-- 时间日期函数 +now(), current_timestamp(); -- 当前日期时间 +current_date(); -- 当前日期 +current_time(); -- 当前时间 +date('yyyy-mm-dd hh:ii:ss'); -- 获取日期部分 +time('yyyy-mm-dd hh:ii:ss'); -- 获取时间部分 +date_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间 +unix_timestamp(); -- 获得unix时间戳 +from_unixtime(); -- 从时间戳获得时间 +-- 字符串函数 +length(string) -- string长度,字节 +char_length(string) -- string的字符个数 +substring(str, position [,length]) -- 从str的position开始,取length个字符 +replace(str ,search_str ,replace_str) -- 在str中用replace_str替换search_str +instr(string ,substring) -- 返回substring首次在string中出现的位置 +concat(string [,...]) -- 连接字串 +charset(str) -- 返回字串字符集 +lcase(string) -- 转换成小写 +left(string, length) -- 从string2中的左边起取length个字符 +load_file(file_name) -- 从文件读取内容 +locate(substring, string [,start_position]) -- 同instr,但可指定开始位置 +lpad(string, length, pad) -- 重复用pad加在string开头,直到字串长度为length +ltrim(string) -- 去除前端空格 +repeat(string, count) -- 重复count次 +rpad(string, length, pad) --在str后用pad补充,直到长度为length +rtrim(string) -- 去除后端空格 +strcmp(string1 ,string2) -- 逐字符比较两字串大小 +-- 流程函数 +case when [condition] then result [when [condition] then result ...] [else result] end 多分支 +if(expr1,expr2,expr3) 双分支。 +-- 聚合函数 +count() +sum(); +max(); +min(); +avg(); +group_concat() +-- 其他常用函数 +md5(); +default(); +--// 存储函数,自定义函数 ---------- +-- 新建 + CREATE FUNCTION function_name (参数列表) RETURNS 返回值类型 + 函数体 + - 函数名,应该合法的标识符,并且不应该与已有的关键字冲突。 + - 一个函数应该属于某个数据库,可以使用db_name.funciton_name的形式执行当前函数所属数据库,否则为当前数据库。 + - 参数部分,由"参数名"和"参数类型"组成。多个参数用逗号隔开。 + - 函数体由多条可用的mysql语句,流程控制,变量声明等语句构成。 + - 多条语句应该使用 begin...end 语句块包含。 + - 一定要有 return 返回值语句。 +-- 删除 + DROP FUNCTION [IF EXISTS] function_name; +-- 查看 + SHOW FUNCTION STATUS LIKE 'partten' + SHOW CREATE FUNCTION function_name; +-- 修改 + ALTER FUNCTION function_name 函数选项 +--// 存储过程,自定义功能 ---------- +-- 定义 +存储存储过程 是一段代码(过程),存储在数据库中的sql组成。 +一个存储过程通常用于完成一段业务逻辑,例如报名,交班费,订单入库等。 +而一个函数通常专注与某个功能,视为其他程序服务的,需要在其他语句中调用函数才可以,而存储过程不能被其他调用,是自己执行 通过call执行。 +-- 创建 +CREATE PROCEDURE sp_name (参数列表) + 过程体 +参数列表:不同于函数的参数列表,需要指明参数类型 +IN,表示输入型 +OUT,表示输出型 +INOUT,表示混合型 +注意,没有返回值。 +``` + +### 存储过程 + +```mysql +/* 存储过程 */ ------------------ +存储过程是一段可执行性代码的集合。相比函数,更偏向于业务逻辑。 +调用:CALL 过程名 +-- 注意 +- 没有返回值。 +- 只能单独调用,不可夹杂在其他语句中 +-- 参数 +IN|OUT|INOUT 参数名 数据类型 +IN 输入:在调用过程中,将数据输入到过程体内部的参数 +OUT 输出:在调用过程中,将过程体处理完的结果返回到客户端 +INOUT 输入输出:既可输入,也可输出 +-- 语法 +CREATE PROCEDURE 过程名 (参数列表) +BEGIN + 过程体 +END +``` + +### 用户和权限管理 + +```mysql +/* 用户和权限管理 */ ------------------ +-- root密码重置 +1. 停止MySQL服务 +2. [Linux] /usr/local/mysql/bin/safe_mysqld --skip-grant-tables & + [Windows] mysqld --skip-grant-tables +3. use mysql; +4. UPDATE `user` SET PASSWORD=PASSWORD("密码") WHERE `user` = "root"; +5. FLUSH PRIVILEGES; +用户信息表:mysql.user +-- 刷新权限 +FLUSH PRIVILEGES; +-- 增加用户 +CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串) + - 必须拥有mysql数据库的全局CREATE USER权限,或拥有INSERT权限。 + - 只能创建用户,不能赋予权限。 + - 用户名,注意引号:如 'user_name'@'192.168.1.1' + - 密码也需引号,纯数字密码也要加引号 + - 要在纯文本中指定密码,需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值,需包含关键字PASSWORD +-- 重命名用户 +RENAME USER old_user TO new_user +-- 设置密码 +SET PASSWORD = PASSWORD('密码') -- 为当前用户设置密码 +SET PASSWORD FOR 用户名 = PASSWORD('密码') -- 为指定用户设置密码 +-- 删除用户 +DROP USER 用户名 +-- 分配权限/添加用户 +GRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password'] + - all privileges 表示所有权限 + - *.* 表示所有库的所有表 + - 库名.表名 表示某库下面的某表 + GRANT ALL PRIVILEGES ON `pms`.* TO 'pms'@'%' IDENTIFIED BY 'pms0817'; +-- 查看权限 +SHOW GRANTS FOR 用户名 + -- 查看当前用户权限 + SHOW GRANTS; 或 SHOW GRANTS FOR CURRENT_USER; 或 SHOW GRANTS FOR CURRENT_USER(); +-- 撤消权限 +REVOKE 权限列表 ON 表名 FROM 用户名 +REVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名 -- 撤销所有权限 +-- 权限层级 +-- 要使用GRANT或REVOKE,您必须拥有GRANT OPTION权限,并且您必须用于您正在授予或撤销的权限。 +全局层级:全局权限适用于一个给定服务器中的所有数据库,mysql.user + GRANT ALL ON *.*和 REVOKE ALL ON *.*只授予和撤销全局权限。 +数据库层级:数据库权限适用于一个给定数据库中的所有目标,mysql.db, mysql.host + GRANT ALL ON db_name.*和REVOKE ALL ON db_name.*只授予和撤销数据库权限。 +表层级:表权限适用于一个给定表中的所有列,mysql.talbes_priv + GRANT ALL ON db_name.tbl_name和REVOKE ALL ON db_name.tbl_name只授予和撤销表权限。 +列层级:列权限适用于一个给定表中的单一列,mysql.columns_priv + 当使用REVOKE时,您必须指定与被授权列相同的列。 +-- 权限列表 +ALL [PRIVILEGES] -- 设置除GRANT OPTION之外的所有简单权限 +ALTER -- 允许使用ALTER TABLE +ALTER ROUTINE -- 更改或取消已存储的子程序 +CREATE -- 允许使用CREATE TABLE +CREATE ROUTINE -- 创建已存储的子程序 +CREATE TEMPORARY TABLES -- 允许使用CREATE TEMPORARY TABLE +CREATE USER -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALL PRIVILEGES。 +CREATE VIEW -- 允许使用CREATE VIEW +DELETE -- 允许使用DELETE +DROP -- 允许使用DROP TABLE +EXECUTE -- 允许用户运行已存储的子程序 +FILE -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILE +INDEX -- 允许使用CREATE INDEX和DROP INDEX +INSERT -- 允许使用INSERT +LOCK TABLES -- 允许对您拥有SELECT权限的表使用LOCK TABLES +PROCESS -- 允许使用SHOW FULL PROCESSLIST +REFERENCES -- 未被实施 +RELOAD -- 允许使用FLUSH +REPLICATION CLIENT -- 允许用户询问从属服务器或主服务器的地址 +REPLICATION SLAVE -- 用于复制型从属服务器(从主服务器中读取二进制日志事件) +SELECT -- 允许使用SELECT +SHOW DATABASES -- 显示所有数据库 +SHOW VIEW -- 允许使用SHOW CREATE VIEW +SHUTDOWN -- 允许使用mysqladmin shutdown +SUPER -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句,mysqladmin debug命令;允许您连接(一次),即使已达到max_connections。 +UPDATE -- 允许使用UPDATE +USAGE -- “无权限”的同义词 +GRANT OPTION -- 允许授予权限 +``` + +### 表维护 + +```mysql +/* 表维护 */ +-- 分析和存储表的关键字分布 +ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ... +-- 检查一个或多个表是否有错误 +CHECK TABLE tbl_name [, tbl_name] ... [option] ... +option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED} +-- 整理数据文件的碎片 +OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... +``` + +### 杂项 + +```mysql +/* 杂项 */ ------------------ +1. 可用反引号(`)为标识符(库名、表名、字段名、索引、别名)包裹,以避免与关键字重名!中文也可以作为标识符! +2. 每个库目录存在一个保存当前数据库的选项文件db.opt。 +3. 注释: + 单行注释 # 注释内容 + 多行注释 /* 注释内容 */ + 单行注释 -- 注释内容 (标准SQL注释风格,要求双破折号后加一空格符(空格、TAB、换行等)) +4. 模式通配符: + _ 任意单个字符 + % 任意多个字符,甚至包括零字符 + 单引号需要进行转义 \' +5. CMD命令行内的语句结束符可以为 ";", "\G", "\g",仅影响显示结果。其他地方还是用分号结束。delimiter 可修改当前对话的语句结束符。 +6. SQL对大小写不敏感 +7. 清除已有语句:\c +``` + diff --git "a/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" "b/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" new file mode 100644 index 0000000000000000000000000000000000000000..261a0c0b975e7bd9c719820bb3019c2c291c7d26 --- /dev/null +++ "b/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" @@ -0,0 +1,149 @@ +本文来自[木木匠](https://github.com/kinglaw1204)投稿。 + + + +- [一 MySQL 基础架构分析](#一-mysql-基础架构分析) + - [1.1 MySQL 基本架构概览](#11-mysql-基本架构概览) + - [1.2 Server 层基本组件介绍](#12-server-层基本组件介绍) + - [1) 连接器](#1-连接器) + - [2) 查询缓存(MySQL 8.0 版本后移除)](#2-查询缓存mysql-80-版本后移除) + - [3) 分析器](#3-分析器) + - [4) 优化器](#4-优化器) + - [5) 执行器](#5-执行器) +- [二 语句分析](#二-语句分析) + - [2.1 查询语句](#21-查询语句) + - [2.2 更新语句](#22-更新语句) +- [三 总结](#三-总结) +- [四 参考](#四-参考) + + + +本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的。 + +在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成已经这些组件的作用是什么,可以帮助我们理解和解决这些问题。 + +## 一 MySQL 基础架构分析 + +### 1.1 MySQL 基本架构概览 + +下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。 + +先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。 + +- **连接器:** 身份认证和权限相关(登录 MySQL 的时候)。 +- **查询缓存:** 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。 +- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。 +- **优化器:** 按照 MySQL 认为最优的方案去执行。 +- **执行器:** 执行语句,然后从存储引擎返回数据。 + +![](https://user-gold-cdn.xitu.io/2019/3/23/169a8bc60a083849?w=950&h=1062&f=jpeg&s=38189) + +简单来说 MySQL 主要分为 Server 层和存储引擎层: + +- **Server 层**:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。 +- **存储引擎**: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。**现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。** + +### 1.2 Server 层基本组件介绍 + +#### 1) 连接器 + +连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。 + +主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。 + +#### 2) 查询缓存(MySQL 8.0 版本后移除) + +查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。 + +连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询预计,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。 + +MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。 + +所以,一般在大多数情况下我们都是不推荐去使用查询缓存的。 + +MySQL 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。 + +#### 3) 分析器 + +MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用来分析 SQL 语句是来干嘛的,分析器也会分为几步: + +**第一步,词法分析**,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。 + +**第二步,语法分析**,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。 + +完成这 2 步之后,MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。 + +#### 4) 优化器 + +优化器的作用就是它认为的最优的执行方案去执行(有时候可能也不是最优,这篇文章涉及对这部分知识的深入讲解),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。 + +可以说,经过了优化器之后可以说这个语句具体该如何执行就已经定下来。 + +#### 5) 执行器 + +当选择了执行方案后,MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。 + +## 二 语句分析 + +### 2.1 查询语句 + +说了以上这么多,那么究竟一条 sql 语句是如何执行的呢?其实我们的 sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。我们先分析下查询语句,语句如下: + +```sql +select * from tb_student A where A.age='18' and A.name=' 张三 '; +``` + +结合上面的说明,我们分析下这个语句的执行流程: + +* 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。 +* 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。 +* 接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案: + + a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。 + b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生。 + 那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。 + +* 进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。 + +### 2.2 更新语句 + +以上就是一条查询 sql 的执行流程,那么接下来我们看看一条更新语句如何执行的呢?sql 语句如下: + +``` +update tb_student A set A.age='19' where A.name=' 张三 '; +``` +我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 **binlog(归档日志)** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log(重做日志)**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下: + +* 先查询到张三这一条数据,如果有缓存,也是会用到缓存。 +* 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。 +* 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。 +* 更新完成。 + +**这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?** + +这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。 + +并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做? + +* **先写 redo log 直接提交,然后写 binlog**,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。 +* **先写 binlog,然后写 redo log**,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。 + +如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢? +这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下: + +* 判断 redo log 是否完整,如果判断是完整的,就立即提交。 +* 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。 + +这样就解决了数据一致性的问题。 + +## 三 总结 + +* MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。 +* 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。 +* 查询语句的执行流程如下:权限校验(如果命中缓存)---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎 +* 更新语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log(prepare 状态---》binlog---》redo log(commit状态) + +## 四 参考 + +* 《MySQL 实战45讲》 +* MySQL 5.6参考手册: diff --git "a/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" "b/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" new file mode 100644 index 0000000000000000000000000000000000000000..95da8be960d681b270a45d5fec53717f5abc6de7 --- /dev/null +++ "b/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" @@ -0,0 +1,148 @@ +> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [guang19](https://github.com/guang19) 共同完成。 + + +- [事务隔离级别(图文详解)](#事务隔离级别图文详解) + - [什么是事务?](#什么是事务) + - [事务的特性(ACID)](#事务的特性acid) + - [并发事务带来的问题](#并发事务带来的问题) + - [事务隔离级别](#事务隔离级别) + - [实际情况演示](#实际情况演示) + - [脏读(读未提交)](#脏读读未提交) + - [避免脏读(读已提交)](#避免脏读读已提交) + - [不可重复读](#不可重复读) + - [可重复读](#可重复读) + - [防止幻读(可重复读)](#防止幻读可重复读) + - [参考](#参考) + + + +## 事务隔离级别(图文详解) + +### 什么是事务? + +事务是逻辑上的一组操作,要么都执行,要么都不执行。 + +事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。 + +### 事务的特性(ACID) + +![事务的特性](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/事务特性.png) + + +1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; +2. **一致性:** 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的; +3. **隔离性:** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; +4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。 + +### 并发事务带来的问题 + +在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。 + +- **脏读(Dirty read):** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。 +- **丢失修改(Lost to modify):** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。 +- **不可重复读(Unrepeatableread):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。 +- **幻读(Phantom read):** 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。 + +**不可重复度和幻读区别:** + +不可重复读的重点是修改,幻读的重点在于新增或者删除。 + +例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。 + + 例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。 + +### 事务隔离级别 + +**SQL 标准定义了四个隔离级别:** + +- **READ-UNCOMMITTED(读取未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。 +- **READ-COMMITTED(读取已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。 +- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。 +- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。 + +---- + +| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | +| :---: | :---: | :---:| :---: | +| READ-UNCOMMITTED | √ | √ | √ | +| READ-COMMITTED | × | √ | √ | +| REPEATABLE-READ | × | × | √ | +| SERIALIZABLE | × | × | × | + +MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;` + +```sql +mysql> SELECT @@tx_isolation; ++-----------------+ +| @@tx_isolation | ++-----------------+ +| REPEATABLE-READ | ++-----------------+ +``` + +这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下,允许应用使用 Next-Key Lock 锁算法来避免幻读的产生。这与其他数据库系统(如 SQL Server)是不同的。所以说虽然 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** ,但是可以通过应用加锁读(例如 `select * from table for update` 语句)来保证不会产生幻读,而这个加锁度使用到的机制就是 Next-Key Lock 锁算法。从而达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。 + +因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是**READ-COMMITTED(读取提交内容):**,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。 + +InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到**SERIALIZABLE(可串行化)** 隔离级别。 + +### 实际情况演示 + +在下面我会使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。 + +MySQL 命令行的默认配置中事务都是自动提交的,即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:`START TARNSACTION`。 + +我们可以通过下面的命令来设置隔离级别。 + +```sql +SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE] +``` + +我们再来看一下我们在下面实际操作中使用到的一些并发控制语句: + +- `START TARNSACTION` |`BEGIN`:显式地开启一个事务。 +- `COMMIT`:提交事务,使得对数据库做的所有修改成为永久性。 +- `ROLLBACK`:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。 + +#### 脏读(读未提交) + +
+ +
+ +#### 避免脏读(读已提交) + +
+ +
+ +#### 不可重复读 + +还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。 + +
+ +
+ +#### 可重复读 + +
+ +
+ +#### 防止幻读(可重复读) + +
+ +
+ +一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢? + +幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除。 + +### 参考 + +- 《MySQL技术内幕:InnoDB存储引擎》 +- +- [Mysql 锁:灵魂七拷问](https://tech.youzan.com/seven-questions-about-the-lock-of-mysql/) +- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html) diff --git "a/docs/database/\345\205\263\344\272\216\346\225\260\346\215\256\345\272\223\345\255\230\345\202\250\346\227\266\351\227\264\347\232\204\344\270\200\347\202\271\346\200\235\350\200\203.md" "b/docs/database/\345\205\263\344\272\216\346\225\260\346\215\256\345\272\223\345\255\230\345\202\250\346\227\266\351\227\264\347\232\204\344\270\200\347\202\271\346\200\235\350\200\203.md" new file mode 100644 index 0000000000000000000000000000000000000000..ab4e62c6a9f99cc555f5e181e78f17a9567a7ae7 --- /dev/null +++ "b/docs/database/\345\205\263\344\272\216\346\225\260\346\215\256\345\272\223\345\255\230\345\202\250\346\227\266\351\227\264\347\232\204\344\270\200\347\202\271\346\200\235\350\200\203.md" @@ -0,0 +1,160 @@ +我们平时开发中不可避免的就是要存储时间,比如我们要记录操作表中这条记录的时间、记录转账的交易时间、记录出发时间等等。你会发现这个时间这个东西与我们开发的联系还是非常紧密的,用的好与不好会给我们的业务甚至功能带来很大的影响。所以,我们有必要重新出发,好好认识一下这个东西。 + +这是一篇短小精悍的文章,仔细阅读一定能学到不少东西! + +### 1.切记不要用字符串存储日期 + +我记得我在大学的时候就这样干过,而且现在很多对数据库不太了解的新手也会这样干,可见,这种存储日期的方式的优点还是有的,就是简单直白,容易上手。 + +但是,这是不正确的做法,主要会有下面两个问题: + +1. 字符串占用的空间更大! +2. 字符串存储的日期比较效率比较低(逐个字符进行比对),无法用日期相关的 API 进行计算和比较。 + +### 2.Datetime 和 Timestamp 之间抉择 + +Datetime 和 Timestamp 是 MySQL 提供的两种比较相似的保存时间的数据类型。他们两者究竟该如何选择呢? + +**通常我们都会首选 Timestamp。** 下面说一下为什么这样做! + +#### 2.1 DateTime 类型没有时区信息的 + +**DateTime 类型是没有时区信息的(时区无关)** ,DateTime 类型保存的时间都是当前会话所设置的时区对应的时间。这样就会有什么问题呢?当你的时区更换之后,比如你的服务器更换地址或者更换客户端连接时区设置的话,就会导致你从数据库中读出的时间错误。不要小看这个问题,很多系统就是因为这个问题闹出了很多笑话。 + +**Timestamp 和时区有关**。Timestamp 类型字段的值会随着服务器时区的变化而变化,自动换算成相应的时间,说简单点就是在不同时区,查询到同一个条记录此字段的值会不一样。 + +下面实际演示一下! + +建表 SQL 语句: + +```sql +CREATE TABLE `time_zone_test` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `date_time` datetime DEFAULT NULL, + `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +插入数据: + +```sql +INSERT INTO time_zone_test(date_time,time_stamp) VALUES(NOW(),NOW()); +``` + +查看数据: + +```sql +select date_time,time_stamp from time_zone_test; +``` + +结果: + +``` ++---------------------+---------------------+ +| date_time | time_stamp | ++---------------------+---------------------+ +| 2020-01-11 09:53:32 | 2020-01-11 09:53:32 | ++---------------------+---------------------+ +``` + +现在我们运行 + +修改当前会话的时区: + +```sql +set time_zone='+8:00'; +``` + +再次查看数据: + +``` ++---------------------+---------------------+ +| date_time | time_stamp | ++---------------------+---------------------+ +| 2020-01-11 09:53:32 | 2020-01-11 17:53:32 | ++---------------------+---------------------+ +``` + +**扩展:一些关于 MySQL 时区设置的一个常用 sql 命令** + +```sql +# 查看当前会话时区 +SELECT @@session.time_zone; +# 设置当前会话时区 +SET time_zone = 'Europe/Helsinki'; +SET time_zone = "+00:00"; +# 数据库全局时区设置 +SELECT @@global.time_zone; +# 设置全局时区 +SET GLOBAL time_zone = '+8:00'; +SET GLOBAL time_zone = 'Europe/Helsinki'; +``` + +#### 2.2 DateTime 类型耗费空间更大 + +Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。但是,这样同样造成了一个问题,Timestamp 表示的时间范围更小。 + +- DateTime :1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 +- Timestamp: 1970-01-01 00:00:01 ~ 2037-12-31 23:59:59 + +> Timestamp 在不同版本的 MySQL 中有细微差别。 + +### 3 再看 MySQL 日期类型存储空间 + +下图是 MySQL 5.6 版本中日期类型所占的存储空间: + +![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/FhRGUVHFK0ujRPNA75f6CuOXQHTE.jpeg) + +可以看出 5.6.4 之后的 MySQL 多出了一个需要 0 ~ 3 字节的小数位。Datatime 和 Timestamp 会有几种不同的存储空间占用。 + +为了方便,本文我们还是默认 Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。 + +### 4.数值型时间戳是更好的选择吗? + +很多时候,我们也会使用 int 或者 bigint 类型的数值也就是时间戳来表示时间。 + +这种存储方式的具有 Timestamp 类型的所具有一些优点,并且使用它的进行日期排序以及对比等操作的效率会更高,跨系统也很方便,毕竟只是存放的数值。缺点也很明显,就是数据的可读性太差了,你无法直观的看到具体时间。 + +时间戳的定义如下: + +> 时间戳的定义是从一个基准时间开始算起,这个基准时间是「1970-1-1 00:00:00 +0:00」,从这个时间开始,用整数表示,以秒计时,随着时间的流逝这个时间整数不断增加。这样一来,我只需要一个数值,就可以完美地表示时间了,而且这个数值是一个绝对数值,即无论的身处地球的任何角落,这个表示时间的时间戳,都是一样的,生成的数值都是一样的,并且没有时区的概念,所以在系统的中时间的传输中,都不需要进行额外的转换了,只有在显示给用户的时候,才转换为字符串格式的本地时间。 + +数据库中实际操作: + +```sql +mysql> select UNIX_TIMESTAMP('2020-01-11 09:53:32'); ++---------------------------------------+ +| UNIX_TIMESTAMP('2020-01-11 09:53:32') | ++---------------------------------------+ +| 1578707612 | ++---------------------------------------+ +1 row in set (0.00 sec) + +mysql> select FROM_UNIXTIME(1578707612); ++---------------------------+ +| FROM_UNIXTIME(1578707612) | ++---------------------------+ +| 2020-01-11 09:53:32 | ++---------------------------+ +1 row in set (0.01 sec) +``` + +### 5.总结 + +MySQL 中时间到底怎么存储才好?Datetime?Timestamp? 数值保存的时间戳? + +好像并没有一个银弹,很多程序员会觉得数值型时间戳是真的好,效率又高还各种兼容,但是很多人又觉得它表现的不够直观。这里插一嘴,《高性能 MySQL 》这本神书的作者就是推荐 Timestamp,原因是数值表示时间不够直观。下面是原文: + + + +每种方式都有各自的优势,根据实际场景才是王道。下面再对这三种方式做一个简单的对比,以供大家实际开发中选择正确的存放时间的数据类型: + + + +如果还有什么问题欢迎给我留言!如果文章有什么问题的话,也劳烦指出,Guide 哥感激不尽! + +后面的文章我会介绍: + +- [ ] Java8 对日期的支持以及为啥不能用 SimpleDateFormat。 +- [ ] SpringBoot 中如何实际使用(JPA 为例) \ No newline at end of file diff --git "a/docs/database/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" "b/docs/database/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" new file mode 100644 index 0000000000000000000000000000000000000000..3e84dfc8efd30b593879524ce47eb823829676c2 --- /dev/null +++ "b/docs/database/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" @@ -0,0 +1,21 @@ +- 公众号和Github待发文章:[数据库:数据库连接池原理详解与自定义连接池实现](https://www.fangzhipeng.com/javainterview/2019/07/15/mysql-connector-pool.html) +- [基于JDBC的数据库连接池技术研究与应用](http://blog.itpub.net/9403012/viewspace-111794/) +- [数据库连接池技术详解](https://juejin.im/post/5b7944c6e51d4538c86cf195) + +数据库连接本质就是一个 socket 的连接。数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存 + +连接池是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。**在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。**连接池还减少了用户必须等待建立与数据库的连接的时间。 + +操作过数据库的朋友应该都知道数据库连接池这个概念,它几乎每天都在和我们打交道,但是你真的了解 **数据库连接池** 吗? + +### 没有数据库连接池之前 + +我相信你一定听过这样一句话:**Java语言中,JDBC(Java DataBase Connection)是应用程序与数据库沟通的桥梁**。 + + + + + + + + diff --git "a/docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 0000000000000000000000000000000000000000..abd9331d6f7628cf425132bc99189d721af1160f --- /dev/null +++ "b/docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,41 @@ +# 阿里巴巴Java开发手册数据库部分的一些最佳实践总结 + +## 模糊查询 + +对于模糊查询阿里巴巴开发手册这样说到: + +> 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。 +> +> 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。 + +## 外键和级联 + +对于外键和级联,阿里巴巴开发手册这样说到: + +>【强制】不得使用外键与级联,一切外键概念必须在应用层解决。 +> +>说明:以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风 险;外键影响数据库的插入速度 + +为什么不要用外键呢?大部分人可能会这样回答: + +> 1. **增加了复杂性:** a.每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦,测试数据极为不方便;b.外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。 +> 2. **增加了额外工作**: 数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源;(个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。所以,我觉得这个影响可以忽略不计。) +> 3. 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况; +> 4. **对分库分表不友好** :因为分库分表下外键是无法生效的。 +> 5. ...... + +我个人觉得上面这种回答不是特别的全面,只是说了外键存在的一个常见的问题。实际上,我们知道外键也是有很多好处的,比如: + +1. 保证了数据库数据的一致性和完整性; +2. 级联操作方便,减轻了程序代码量; +3. ...... + +所以说,不要一股脑的就抛弃了外键这个概念,既然它存在就有它存在的道理,如果系统不涉及分不分表,并发量不是很高的情况还是可以考虑使用外键的。 + +我个人是不太喜欢外键约束,比较喜欢在应用层去进行相关操作。 + +## 关于@Transactional注解 + +对于`@Transactional`事务注解,阿里巴巴开发手册这样说到: + +>【参考】@Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。 diff --git "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" similarity index 72% rename from "Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" rename to "docs/java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" index ced017ab47bf396f33802869d14ac41e9b038ccc..22ce6911669129ad52062f81c0976dbce1b678b0 100644 --- "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/docs/java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,3 +1,5 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + - [Servlet总结](#servlet总结) @@ -26,7 +28,7 @@ ## Servlet总结 -在Java Web程序中,**Servlet**主要负责接收用户请求**HttpServletRequest**,在**doGet()**,**doPost()**中做相应的处理,并将回应**HttpServletResponse**反馈给用户。Servlet可以设置初始化参数,供Servlet内部使用。一个Servlet类只会有一个实例,在它初始化时调用**init()方法**,销毁时调用**destroy()方法**。**Servlet需要在web.xml中配置**(MyEclipse中创建Servlet会自动配置),**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**,因此要谨慎使用类变量。 +在Java Web程序中,**Servlet**主要负责接收用户请求 `HttpServletRequest`,在`doGet()`,`doPost()`中做相应的处理,并将回应`HttpServletResponse`反馈给用户。**Servlet** 可以设置初始化参数,供Servlet内部使用。一个Servlet类只会有一个实例,在它初始化时调用`init()`方法,销毁时调用`destroy()`方法**。**Servlet需要在web.xml中配置(MyEclipse中创建Servlet会自动配置),**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**,因此要谨慎使用类变量。 ## 阐述Servlet和CGI的区别? @@ -55,11 +57,11 @@ ## Servlet接口中有哪些方法及Servlet生命周期探秘 Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期相关**: -- **void init(ServletConfig config) throws ServletException** -- **void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException** -- **void destory()** -- java.lang.String getServletInfo() -- ServletConfig getServletConfig() +- `void init(ServletConfig config) throws ServletException` +- `void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException` +- `void destroy()` +- `java.lang.String getServletInfo()` +- `ServletConfig getServletConfig()` **生命周期:** **Web容器加载Servlet并将其实例化后,Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化;请求到达时调用Servlet的**service()方法**,service()方法会根据需要调用与请求对应的**doGet或doPost**等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的**destroy()方法**。**init方法和destroy方法只会执行一次,service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。 @@ -67,21 +69,11 @@ Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期 ## get和post请求的区别 -> 网上也有文章说:get和post请求实际上是没有区别,大家可以自行查询相关文章(参考文章:[https://www.cnblogs.com/logsharing/p/8448446.html](https://www.cnblogs.com/logsharing/p/8448446.html),知乎对应的问题链接:[get和post区别?](https://www.zhihu.com/question/28586791))!我下面给出的只是一种常见的答案。 - -①get请求用来从服务器上获得资源,而post是用来向服务器提交数据; - -②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL; - -③get传输的数据要受到URL长度限制(最大长度是 2048 个字符);而post可以传输大量的数据,上传文件通常要使用post方式; +get和post请求实际上是没有区别,大家可以自行查询相关文章(参考文章:[https://www.cnblogs.com/logsharing/p/8448446.html](https://www.cnblogs.com/logsharing/p/8448446.html),知乎对应的问题链接:[get和post区别?](https://www.zhihu.com/question/28586791))! -④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post; +可以把 get 和 post 当作两个不同的行为,两者并没有什么本质区别,底层都是 TCP 连接。 get请求用来从服务器上获得资源,而post是用来向服务器提交数据。比如你要获取人员列表可以用 get 请求,你需要创建一个人员可以用 post 。这也是 Restful API 最基本的一个要求。 -⑤get使用MIME类型application/x-www-form-urlencoded的URL编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20"。 - -补充:GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。 - -还有另外一种回答。推荐大家看一下: +推荐阅读: - https://www.zhihu.com/question/28586791 - https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd @@ -93,7 +85,7 @@ Form标签里的method的属性为get时调用doGet(),为post时调用doPost() **转发是服务器行为,重定向是客户端行为。** -**转发(Forword)** +**转发(Forward)** 通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。 ```java request.getRequestDispatcher("login_success.jsp").forward(request, response); @@ -143,13 +135,11 @@ Response.setHeader("Refresh","5;URL=http://localhost:8080/servlet/example.htm"); JSP是一种Servlet,但是与HttpServlet的工作方式不太一样。HttpServlet是先由源代码编译为class文件后部署到服务器下,为先编译后部署。而JSP则是先部署后编译。JSP会在客户端第一次请求JSP文件时被编译为HttpJspPage类(接口Servlet的一个子类)。该类会被服务器临时存放在服务器工作目录里面。下面通过实例给大家介绍。 工程JspLoginDemo下有一个名为login.jsp的Jsp文件,把工程第一次部署到服务器上后访问这个Jsp文件,我们发现这个目录下多了下图这两个东东。 .class文件便是JSP对应的Servlet。编译完毕后再运行class文件来响应客户端请求。以后客户端访问login.jsp的时候,Tomcat将不再重新编译JSP文件,而是直接调用class文件来响应客户端请求。 -![JSP工作原理](https://user-gold-cdn.xitu.io/2018/3/31/1627bee073079a28?w=675&h=292&f=jpeg&s=133553) +![JSP工作原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/1.png) 由于JSP只会在客户端第一次请求的时候被编译 ,因此第一次请求JSP时会感觉比较慢,之后就会感觉快很多。如果把服务器保存的class文件删除,服务器也会重新编译JSP。 开发Web程序时经常需要修改JSP。Tomcat能够自动检测到JSP程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP,而不需要重启Tomcat。这种自动检测功能是默认开启的,检测改动会消耗少量的时间,在部署Web应用的时候可以在web.xml中将它关掉。 - - 参考:《javaweb整合开发王者归来》P97 ## JSP有哪些内置对象、作用分别是什么 @@ -195,31 +185,31 @@ JSP有9个内置对象: ## request.getAttribute()和 request.getParameter()有何区别 **从获取方向来看:** -getParameter()是获取 POST/GET 传递的参数值; +`getParameter()`是获取 POST/GET 传递的参数值; -getAttribute()是获取对象容器中的数据值; +`getAttribute()`是获取对象容器中的数据值; **从用途来看:** -getParameter用于客户端重定向时,即点击了链接或提交按扭时传值用,即用于在用表单或url重定向传值时接收数据用。 +`getParameter()`用于客户端重定向时,即点击了链接或提交按扭时传值用,即用于在用表单或url重定向传值时接收数据用。 -getAttribute用于服务器端重定向时,即在 sevlet 中使用了 forward 函数,或 struts 中使用了 +`getAttribute()` 用于服务器端重定向时,即在 sevlet 中使用了 forward 函数,或 struts 中使用了 mapping.findForward。 getAttribute 只能收到程序用 setAttribute 传过来的值。 -另外,可以用 setAttribute,getAttribute 发送接收对象.而 getParameter 显然只能传字符串。 -setAttribute 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样getAttribute就能取得你所设下的值,当然这种方法可以传对象。session也一样,只是对象在内存中的生命周期不一样而已。getParameter只是应用服务器在分析你送上来的 request页面的文本时,取得你设在表单或 url 重定向时的值。 +另外,可以用 `setAttribute()`,`getAttribute()` 发送接收对象.而 `getParameter()` 显然只能传字符串。 +`setAttribute()` 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样`getAttribute()`就能取得你所设下的值,当然这种方法可以传对象。session也一样,只是对象在内存中的生命周期不一样而已。`getParameter()`只是应用服务器在分析你送上来的 request页面的文本时,取得你设在表单或 url 重定向时的值。 **总结:** -getParameter 返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等) +`getParameter()`返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等) -getAttribute 返回的是Object,需进行转换,可用setAttribute 设置成任意对象,使用很灵活,可随时用 +`getAttribute()`返回的是Object,需进行转换,可用`setAttribute()`设置成任意对象,使用很灵活,可随时用 ## include指令include的行为的区别 **include指令:** JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分,会被同时编译执行。 语法格式如下: <%@ include file="文件相对 url 地址" %> -i**nclude动作:** 动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下: +i**nclude动作:** ``动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下: ## JSP九大内置对象,七大动作,三大指令 @@ -232,11 +222,9 @@ JSP中的四种作用域包括page、request、session和application,具体来 - **session**代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。 - **application**代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。 - - ## 如何实现JSP或Servlet的单线程模式 对于JSP页面,可以通过page指令进行设置。 -<%@page isThreadSafe=”false”%> +`<%@page isThreadSafe="false"%>` 对于Servlet,可以让自定义的Servlet实现SingleThreadModel标识接口。 @@ -294,12 +282,20 @@ if(cookies !=null){ 在所有会话跟踪技术中,HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession,每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方 法获得HttpSession,通过HttpSession的setAttribute方法可以将一个值放在HttpSession中,通过调用 HttpSession对象的getAttribute方法,同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的 是,HttpSession放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的Servlet容器可以在内存将满时将HttpSession 中的对象移到其他存储设备中,但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象,这个对象最好实现了 Serializable接口,这样Servlet容器在必要的时候可以将其序列化到文件中,否则在序列化时就会出现异常。 ## Cookie和Session的的区别 -1. 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。 -2. 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。 -3. Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。 +Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。 + + **Cookie 一般用来保存用户信息** 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。 + +Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。 + +Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。 + +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 -参考: +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! -https://www.zhihu.com/question/19786827/answer/28752144 +**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 -《javaweb整合开发王者归来》P158 Cookie和Session的比较 +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) diff --git "a/docs/java/JAD\345\217\215\347\274\226\350\257\221tricks.md" "b/docs/java/JAD\345\217\215\347\274\226\350\257\221tricks.md" new file mode 100644 index 0000000000000000000000000000000000000000..f9d11bec9a9afceb2d86a5a952de276e98396f26 --- /dev/null +++ "b/docs/java/JAD\345\217\215\347\274\226\350\257\221tricks.md" @@ -0,0 +1,375 @@ +[jad](https://varaneckas.com/jad/)反编译工具,已经不再更新,且只支持JDK1.4,但并不影响其强大的功能。 + +基本用法:`jad xxx.class`,会生成直接可读的xxx.jad文件。 + +## 自动拆装箱 + +对于基本类型和包装类型之间的转换,通过xxxValue()和valueOf()两个方法完成自动拆装箱,使用jad进行反编译可以看到该过程: + +```java +public class Demo { + public static void main(String[] args) { + int x = new Integer(10); // 自动拆箱 + Integer y = x; // 自动装箱 + } +} +``` +反编译后结果: + +```java +public class Demo +{ + public Demo(){} + + public static void main(String args[]) + { + int i = (new Integer(10)).intValue(); // intValue()拆箱 + Integer integer = Integer.valueOf(i); // valueOf()装箱 + } +} +``` + + + +## foreach语法糖 + +在遍历迭代时可以foreach语法糖,对于数组类型直接转换成for循环: + +```java +// 原始代码 +int[] arr = {1, 2, 3, 4, 5}; + for(int item: arr) { + System.out.println(item); + } +} + +// 反编译后代码 +int ai[] = { + 1, 2, 3, 4, 5 +}; +int ai1[] = ai; +int i = ai1.length; +// 转换成for循环 +for(int j = 0; j < i; j++) +{ + int k = ai1[j]; + System.out.println(k); +} +``` + + + +对于容器类的遍历会使用iterator进行迭代: + +```java +import java.io.PrintStream; +import java.util.*; + +public class Demo +{ + public Demo() {} + public static void main(String args[]) + { + ArrayList arraylist = new ArrayList(); + arraylist.add(Integer.valueOf(1)); + arraylist.add(Integer.valueOf(2)); + arraylist.add(Integer.valueOf(3)); + Integer integer; + // 使用的for循环+Iterator,类似于链表迭代: + // for (ListNode cur = head; cur != null; System.out.println(cur.val)){ + // cur = cur.next; + // } + for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(integer)) + integer = (Integer)iterator.next(); + } +} +``` + + + +## Arrays.asList(T...) + +熟悉Arrays.asList(T...)用法的小伙伴都应该知道,asList()方法传入的参数不能是基本类型的数组,必须包装成包装类型再使用,否则对应生成的列表的大小永远是1: + +```java +import java.util.*; +public class Demo { + public static void main(String[] args) { + int[] arr1 = {1, 2, 3}; + Integer[] arr2 = {1, 2, 3}; + List lists1 = Arrays.asList(arr1); + List lists2 = Arrays.asList(arr2); + System.out.println(lists1.size()); // 1 + System.out.println(lists2.size()); // 3 + } +} +``` + +从反编译结果来解释,为什么传入基本类型的数组后,返回的List大小是1: + +```java +// 反编译后文件 +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; + +public class Demo +{ + public Demo() {} + + public static void main(String args[]) + { + int ai[] = { + 1, 2, 3 + }; + // 使用包装类型,全部元素由int包装为Integer + Integer ainteger[] = { + Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) + }; + + // 注意这里被反编译成二维数组,而且是一个1行三列的二维数组 + // list.size()当然返回1 + List list = Arrays.asList(new int[][] { ai }); + List list1 = Arrays.asList(ainteger); + System.out.println(list.size()); + System.out.println(list1.size()); + } +} +``` + +从上面结果可以看到,传入基本类型的数组后,会被转换成一个二维数组,而且是**new int\[1]\[arr.length]**这样的数组,调用list.size()当然返回1。 + + + +## 注解 + +Java中的类、接口、枚举、注解都可以看做是类类型。使用jad来看一下@interface被转换成什么: + +```java +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Foo{ + String[] value(); + boolean bar(); +} +``` +查看反编译代码可以看出: + +- 自定义的注解类Foo被转换成接口Foo,并且继承Annotation接口 +- 原来自定义接口中的value()和bar()被转换成抽象方法 + +```java +import java.lang.annotation.Annotation; + +public interface Foo + extends Annotation +{ + public abstract String[] value(); + + public abstract boolean bar(); +} +``` +注解通常和反射配合使用,而且既然自定义的注解最终被转换成接口,注解中的属性被转换成接口中的抽象方法,那么通过反射之后拿到接口实例,在通过接口实例自然能够调用对应的抽象方法: +```java +import java.util.Arrays; + +@Foo(value={"sherman", "decompiler"}, bar=true) +public class Demo{ + public static void main(String[] args) { + Foo foo = Demo.class.getAnnotation(Foo.class); + System.out.println(Arrays.toString(foo.value())); // [sherman, decompiler] + System.out.println(foo.bar()); // true + } +} +``` + + +## 枚举 + +通过jad反编译可以很好地理解枚举类。 + + + +### 空枚举 + +先定义一个空的枚举类: + +```java +public enum DummyEnum { +} +``` +使用jad反编译查看结果: + +- 自定义枚举类被转换成final类,并且继承Enum +- 提供了两个参数(name,odinal)的私有构造器,并且调用了父类的构造器。注意即使没有提供任何参数,也会有该该构造器,其中name就是枚举实例的名称,odinal是枚举实例的索引号 +- 初始化了一个private static final自定义类型的空数组 **$VALUES** +- 提供了两个public static方法: + - values()方法通过clone()方法返回内部$VALUES的浅拷贝。这个方法结合私有构造器可以完美实现单例模式,想一想values()方法是不是和单例模式中getInstance()方法功能类似 + - valueOf(String s):调用父类Enum的valueOf方法并强转返回 + +```java +public final class DummyEnum extends Enum +{ + // 功能和单例模式的getInstance()方法相同 + public static DummyEnum[] values() + { + return (DummyEnum[])$VALUES.clone(); + } + // 调用父类的valueOf方法,并强转返回 + public static DummyEnum valueOf(String s) + { + return (DummyEnum)Enum.valueOf(DummyEnum, s); + } + // 默认提供一个私有的两个参数的构造器,并调用父类Enum的构造器 + private DummyEnum(String s, int i) + { + super(s, i); + } + // 初始化一个private static final的本类空数组 + private static final DummyEnum $VALUES[] = new DummyEnum[0]; + +} + +``` +### 包含抽象方法的枚举 + +枚举类中也可以包含抽象方法,但是必须定义枚举实例并且立即重写抽象方法,就像下面这样: + +```java +public enum DummyEnum { + DUMMY1 { + public void dummyMethod() { + System.out.println("[1]: implements abstract method in enum class"); + } + }, + + DUMMY2 { + public void dummyMethod() { + System.out.println("[2]: implements abstract method in enum class"); + } + }; + + abstract void dummyMethod(); + +} +``` +再来反编译看看有哪些变化: + +- 原来final class变成了abstract class:这很好理解,有抽象方法的类自然是抽象类 +- 多了两个public static final的成员DUMMY1、DUMMY2,这两个实例的初始化过程被放到了static代码块中,并且实例过程中直接重写了抽象方法,类似于匿名内部类的形式。 +- 数组**$VALUES[]**初始化时放入枚举实例 + +还有其它变化么? + +在反编译后的DummyEnum类中,是存在抽象方法的,而枚举实例在静态代码块中初始化过程中重写了抽象方法。在Java中,抽象方法和抽象方法重写同时放在一个类中,只能通过内部类形式完成。因此上面第二点应该说成就是以内部类形式初始化。 + +可以看一下DummyEnum.class存放的位置,应该多了两个文件: + +- DummyEnum$1.class +- DummyEnum$2.class + +Java中.class文件出现$符号表示有内部类存在,就像OutClass$InnerClass,这两个文件出现也应证了上面的匿名内部类初始化的说法。 + +```java +import java.io.PrintStream; + +public abstract class DummyEnum extends Enum +{ + public static DummyEnum[] values() + { + return (DummyEnum[])$VALUES.clone(); + } + + public static DummyEnum valueOf(String s) + { + return (DummyEnum)Enum.valueOf(DummyEnum, s); + } + + private DummyEnum(String s, int i) + { + super(s, i); + } + + // 抽象方法 + abstract void dummyMethod(); + + // 两个pubic static final实例 + public static final DummyEnum DUMMY1; + public static final DummyEnum DUMMY2; + private static final DummyEnum $VALUES[]; + + // static代码块进行初始化 + static + { + DUMMY1 = new DummyEnum("DUMMY1", 0) { + public void dummyMethod() + { + System.out.println("[1]: implements abstract method in enum class"); + } + } +; + DUMMY2 = new DummyEnum("DUMMY2", 1) { + public void dummyMethod() + { + System.out.println("[2]: implements abstract method in enum class"); + } + } +; + // 对本类数组进行初始化 + $VALUES = (new DummyEnum[] { + DUMMY1, DUMMY2 + }); + } +} +``` + + + +### 正常的枚举类 + +实际开发中,枚举类通常的形式是有两个参数(int code,Sring msg)的构造器,可以作为状态码进行返回。Enum类实际上也是提供了包含两个参数且是protected的构造器,这里为了避免歧义,将枚举类的构造器设置为三个,使用jad反编译: + +最大的变化是:现在的private构造器从2个参数变成5个,而且在内部仍然将前两个参数通过super传递给父类,剩余的三个参数才是真正自己提供的参数。可以想象,如果自定义的枚举类只提供了一个参数,最终生成底层代码中private构造器应该有三个参数,前两个依然通过super传递给父类。 + +```java +public final class CustomEnum extends Enum +{ + public static CustomEnum[] values() + { + return (CustomEnum[])$VALUES.clone(); + } + + public static CustomEnum valueOf(String s) + { + return (CustomEnum)Enum.valueOf(CustomEnum, s); + } + + private CustomEnum(String s, int i, int j, String s1, Object obj) + { + super(s, i); + code = j; + msg = s1; + data = obj; + } + + public static final CustomEnum FIRST; + public static final CustomEnum SECOND; + public static final CustomEnum THIRD; + private int code; + private String msg; + private Object data; + private static final CustomEnum $VALUES[]; + + static + { + FIRST = new CustomEnum("FIRST", 0, 10010, "first", Long.valueOf(100L)); + SECOND = new CustomEnum("SECOND", 1, 10020, "second", "Foo"); + THIRD = new CustomEnum("THIRD", 2, 10030, "third", new Object()); + $VALUES = (new CustomEnum[] { + FIRST, SECOND, THIRD + }); + } +} +``` diff --git "a/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md" "b/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md" new file mode 100644 index 0000000000000000000000000000000000000000..6b4731ef3c9d72d9853e65995b61e323df61fa49 --- /dev/null +++ "b/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md" @@ -0,0 +1,30 @@ +讲真的,下面推荐的文章或者资源建议阅读 3 遍以上。 + +### 团队 + +- **阿里巴巴Java开发手册(详尽版)** +- **Google Java编程风格指南:** + +### 个人 + +- **程序员你为什么这么累:** + +### 如何写出优雅的 Java 代码 + +1. 使用 IntelliJ IDEA 作为您的集成开发环境 (IDE) +1. 使用 JDK 8 或更高版本 +1. 使用 Maven/Gradle +1. 使用 Lombok +1. 编写单元测试 +1. 重构:常见,但也很慢 +1. 注意代码规范 +1. 定期联络客户,以获取他们的反馈 + +上述建议的详细内容:[八点建议助您写出优雅的Java代码](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485140&idx=1&sn=ecaeace613474f1859aaeed0282ae680&chksm=cea2491ff9d5c00982ffaece847ce1aead89fdb3fe190752d9837c075c79fc95db5940992c56&token=1328169465&lang=zh_CN&scene=21#wechat_redirect)。 + +更多代码优化相关内容推荐: + +- [业务复杂=if else?刚来的大神竟然用策略+工厂彻底干掉了他们!](https://juejin.im/post/5dad23685188251d2c4ea2b6) +- [一些不错的 Java 实践!推荐阅读3遍以上!](http://lrwinx.github.io/2017/03/04/%E7%BB%86%E6%80%9D%E6%9E%81%E6%81%90-%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BC%9A%E5%86%99java%E5%90%97/) +- [[解锁新姿势] 兄dei,你代码需要优化了](https://juejin.im/post/5dafbc02e51d4524a0060bdd) +- [消灭 Java 代码的“坏味道”](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485599&idx=1&sn=d83ff4e6b1ee951a0a33508a10980ea3&chksm=cea24754f9d5ce426d18b435a8c373ddc580c06c7d6a45cc51377361729c31c7301f1bbc3b78&token=1328169465&lang=zh_CN#rd) \ No newline at end of file diff --git "a/Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" similarity index 89% rename from "Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" rename to "docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" index c5ec6dddd0440fd1775aef418fcad97a9b60df0b..50f6b7fec83a62dfe67b647b0678f9369c7a3aa8 100644 --- "a/Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" +++ "b/docs/java/basis/BIO,NIO,AIO\346\200\273\347\273\223.md" @@ -11,10 +11,10 @@ - [2. NIO \(New I/O\)](#2-nio-new-io) - [2.1 NIO 简介](#21-nio-简介) - [2.2 NIO的特性/NIO与IO区别](#22-nio的特性nio与io区别) - - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io(非阻塞io)) + - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io非阻塞io) - [2)Buffer\(缓冲区\)](#2buffer缓冲区) - [3)Channel \(通道\)](#3channel-通道) - - [4)Selectors\(选择器\)](#4selectors选择器) + - [4)Selectors\(选择器\)](#4selector-选择器) - [2.3 NIO 读数据和写数据方式](#23-nio-读数据和写数据方式) - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍) - [2.5 代码示例](#25-代码示例) @@ -30,20 +30,23 @@ 在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。 -**同步与异步** +关于同步和异步的概念解读困扰着很多程序员,大部分的解读都会带有自己的一点偏见。参考了 [Stackoverflow](https://stackoverflow.com/questions/748175/asynchronous-vs-synchronous-execution-what-does-it-really-mean)相关问题后对原有答案进行了进一步完善: -- **同步:** 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。 -- **异步:** 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。 +> When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes. +> +> 当你同步执行某项任务时,你需要等待其完成才能继续执行其他任务。当你异步执行某些操作时,你可以在完成另一个任务之前继续进行。 -同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。 +- **同步** :两个同步任务相互依赖,并且一个任务必须以依赖于另一任务的某种方式执行。 比如在`A->B`事件模型中,你需要先完成 A 才能执行B。 再换句话说,同步调用中被调用者未处理完请求之前,调用不返回,调用者会一直等待结果的返回。 +- **异步**: 两个异步的任务是完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用中一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情, **阻塞和非阻塞** - **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。 - **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。 -举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。 +**如何区分 “同步/异步 ”和 “阻塞/非阻塞” 呢?** +同步/异步是从行为角度描述事物的,而阻塞和非阻塞描述的当前事物的状态(等待调用结果时的状态)。 ## 1. BIO (Blocking I/O) @@ -73,7 +76,7 @@ BIO通信(一请求一应答)模型图如下(图源网络,原出处不明) 采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 -伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。 +伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。 ### 1.3 代码示例 @@ -164,14 +167,12 @@ public class IOServer { 在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 - - ## 2. NIO (New I/O) ### 2.1 NIO 简介 - NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。 - + NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。 + NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。 ### 2.2 NIO的特性/NIO与IO区别 @@ -202,13 +203,13 @@ NIO 通过Channel(通道) 进行读写。 通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。 -#### 4)Selectors(选择器) +#### 4)Selector (选择器) NIO有选择器,而IO没有。 选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。 -![一个单线程中Slector维护3个Channel的示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Slector.png) +![一个单线程中Selector维护3个Channel的示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Slector.png) ### 2.3 NIO 读数据和写数据方式 通常来说NIO中的所有IO都是从 Channel(通道) 开始的。 @@ -273,8 +274,7 @@ public class NIOServer { if (key.isAcceptable()) { try { - // (1) - // 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector + // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false); clientChannel.register(clientSelector, SelectionKey.OP_READ); diff --git "a/docs/java/basis/IO\346\250\241\345\236\213.md" "b/docs/java/basis/IO\346\250\241\345\236\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..70a0d8078f3e246669c285c6a359b1538e9b5a7f --- /dev/null +++ "b/docs/java/basis/IO\346\250\241\345\236\213.md" @@ -0,0 +1,122 @@ +IO 模型这块确实挺难理解的,需要太多计算机底层知识。写这篇文章用了挺久,就非常希望能把我所知道的讲出来吧!希望朋友们能有收货!为了写这篇文章,还翻看了一下《UNIX 网络编程》这本书,太难了,我滴乖乖!心痛~ + +_个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!_ + +## 前言 + +I/O 一直是很多小伙伴难以理解的一个知识点,这篇文章我会将我所理解的 I/O 讲给你听,希望可以对你有所帮助。 + +## I/O + +### 何为 I/O? + +I/O(**I**nput/**O**utpu) 即**输入/输出** 。 + +**我们先从计算机结构的角度来解读一下 I/O。** + +根据冯.诺依曼结构,计算机结构分为 5 大部分:运算器、控制器、存储器、输入设备、输出设备。 + +![冯诺依曼体系结构](https://img-blog.csdnimg.cn/20190624122126398.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9pcy1jbG91ZC5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70) + +输入设备(比如键盘)和输出设备(比如鼠标)都属于外部设备。网卡、硬盘这种既可以属于输入设备,也可以属于输出设备。 + +输入设备向计算机输入数据,输出设备接收计算机输出的数据。 + +**从计算机结构的视角来看的话, I/O 描述了计算机系统与外部设备之间通信的过程。** + +**我们再先从应用程序的角度来解读一下 I/O。** + +根据大学里学到的操作系统相关的知识:为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 **用户空间(User space)** 和 **内核空间(Kernel space )** 。 + +像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如如文件管理、进程通信、内存管理等等。也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。 + +并且,用户空间的程序不能直接访问内核空间。 + +当想要执行 IO 操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。 + +因此,用户进程想要执行 IO 操作的话,必须通过 **系统调用** 来间接访问内核空间 + +我们在平常开发过程中接触最多的就是 **磁盘 IO(读写文件)** 和 **网络 IO(网络请求和相应)**。 + +**从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。** + +当应用程序发起 I/O 调用后,会经历两个步骤: + +1. 内核等待 I/O 设备准备好数据 +2. 内核将数据从内核空间拷贝到用户空间。 + +### 有哪些常见的 IO 模型? + +UNIX 系统下, IO 模型一共有 5 种: **同步阻塞 I/O**、**同步非阻塞 I/O**、**I/O 多路复用**、**信号驱动 I/O** 和**异步 I/O**。 + +这也是我们经常提到的 5 种 IO 模型。 + +## Java 中 3 种常见 IO 模型 + +### BIO (Blocking I/O) + +**BIO 属于同步阻塞 IO 模型** 。 + +同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到在内核把数据拷贝到用户空间。 + +![图源:《深入拆解Tomcat & Jetty》](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6a9e704af49b4380bb686f0c96d33b81~tplv-k3u1fbpfcp-watermark.image) + +在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 + +### NIO (Non-blocking/New I/O) + +Java 中的 NIO 于 Java 1.4 中引入,对应 `java.nio` 包,提供了 `Channel` , `Selector`,`Buffer` 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。 + +Java 中的 NIO 可以看作是 **I/O 多路复用模型**。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。 + +跟着我的思路往下看看,相信你会得到答案! + +我们先来看看 **同步非阻塞 IO 模型**。 + +![图源:《深入拆解Tomcat & Jetty》](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb174e22dbe04bb79fe3fc126aed0c61~tplv-k3u1fbpfcp-watermark.image) + +同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。 + +相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。 + +但是,这种 IO 模型同样存在问题:**应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。** + +这个时候,**I/O 多路复用模型** 就上场了。 + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/88ff862764024c3b8567367df11df6ab~tplv-k3u1fbpfcp-watermark.image) + +IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。 + +> 目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持 +> +> - **select 调用** :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。 +> - **epoll 调用** :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。 + +**IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。** + +Java 中的 NIO ,有一个非常重要的**选择器 ( Selector )** 的概念,也可以被称为 **多路复用器**。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f483f2437ce4ecdb180134270a00144~tplv-k3u1fbpfcp-watermark.image) + +### AIO (Asynchronous I/O) + +AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。 + +异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。 + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3077e72a1af049559e81d18205b56fd7~tplv-k3u1fbpfcp-watermark.image) + +目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。 + +最后,来一张图,简单总结一下 Java 中的 BIO、NIO、AIO。 + +![](https://images.xiaozhuanlan.com/photo/2020/33b193457c928ae02217480f994814b6.png) + +## 参考 + +- 《深入拆解 Tomcat & Jetty》 +- 如何完成一次 IO:[https://llc687.top/post/如何完成一次-io/](https://llc687.top/post/如何完成一次-io/) +- 程序员应该这样理解 IO:[https://www.jianshu.com/p/fa7bdc4f3de7](https://www.jianshu.com/p/fa7bdc4f3de7) +- 10 分钟看懂, Java NIO 底层原理:https://www.cnblogs.com/crazymakercircle/p/10225159.html +- IO 模型知多少 | 理论篇:https://www.cnblogs.com/sheng-jie/p/how-much-you-know-about-io-models.html +- 《UNIX 网络编程 卷 1;套接字联网 API 》6.2 节 IO 模型 \ No newline at end of file diff --git "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" new file mode 100644 index 0000000000000000000000000000000000000000..14cc3d83da701ff529efc3c8d624a5353f579c53 --- /dev/null +++ "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -0,0 +1,1394 @@ + + + + +- [基础概念与常识](#基础概念与常识) + - [Java 语言有哪些特点?](#java-语言有哪些特点) + - [JVM vs JDK vs JRE](#jvm-vs-jdk-vs-jre) + - [JVM](#jvm) + - [JDK 和 JRE](#jdk-和-jre) + - [为什么说 Java 语言“编译与解释并存”?](#为什么说-java-语言编译与解释并存) + - [Oracle JDK 和 OpenJDK 的对比](#oracle-jdk-和-openjdk-的对比) + - [Java 和 C++的区别?](#java-和-c的区别) + - [import java 和 javax 有什么区别?](#import-java-和-javax-有什么区别) +- [基本语法](#基本语法) + - [字符型常量和字符串常量的区别?](#字符型常量和字符串常量的区别) + - [注释](#注释) + - [标识符和关键字的区别是什么?](#标识符和关键字的区别是什么) + - [Java 中有哪些常见的关键字?](#java-中有哪些常见的关键字) + - [自增自减运算符](#自增自减运算符) + - [continue、break、和 return 的区别是什么?](#continue-break-和-return-的区别是什么) + - [Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?](#java-泛型了解么什么是类型擦除介绍一下常用的通配符) + - [==和 equals 的区别](#和-equals-的区别) + - [hashCode()与 equals()](#hashcode与-equals) +- [基本数据类型](#基本数据类型) + - [Java 中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?](#java-中的几种基本数据类型是什么对应的包装类型是什么各自占用多少字节呢) + - [自动装箱与拆箱](#自动装箱与拆箱) + - [8 种基本类型的包装类和常量池](#8-种基本类型的包装类和常量池) +- [方法(函数)](#方法函数) + - [什么是方法的返回值?](#什么是方法的返回值) + - [方法有哪几种类型?](#方法有哪几种类型) + - [在一个静态方法内调用一个非静态成员为什么是非法的?](#在一个静态方法内调用一个非静态成员为什么是非法的) + - [静态方法和实例方法有何不同?](#静态方法和实例方法有何不同) + - [为什么 Java 中只有值传递?](#为什么-java-中只有值传递) + - [重载和重写的区别](#重载和重写的区别) + - [重载](#重载) + - [重写](#重写) + - [深拷贝 vs 浅拷贝](#深拷贝-vs-浅拷贝) +- [Java 面向对象](#java-面向对象) + - [面向对象和面向过程的区别](#面向对象和面向过程的区别) + - [成员变量与局部变量的区别有哪些?](#成员变量与局部变量的区别有哪些) + - [创建一个对象用什么运算符?对象实体与对象引用有何不同?](#创建一个对象用什么运算符对象实体与对象引用有何不同) + - [对象的相等与指向他们的引用相等,两者有什么不同?](#对象的相等与指向他们的引用相等两者有什么不同) + - [一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?](#一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么) + - [构造方法有哪些特点?是否可被 override?](#构造方法有哪些特点是否可被-override) + - [面向对象三大特征](#面向对象三大特征) + - [封装](#封装) + - [继承](#继承) + - [多态](#多态) + - [String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?](#string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的) + - [Object 类的常见方法总结](#object-类的常见方法总结) +- [反射](#反射) + - [何为反射?](#何为反射) + - [反射机制优缺点](#反射机制优缺点) + - [反射的应用场景](#反射的应用场景) +- [异常](#异常) + - [Java 异常类层次结构图](#java-异常类层次结构图) + - [Throwable 类常用方法](#throwable-类常用方法) + - [try-catch-finally](#try-catch-finally) + - [使用 `try-with-resources` 来代替`try-catch-finally`](#使用-try-with-resources-来代替try-catch-finally) +- [I\O 流](#io-流) + - [什么是序列化?什么是反序列化?](#什么是序列化什么是反序列化) + - [Java 序列化中如果有些字段不想进行序列化,怎么办?](#java-序列化中如果有些字段不想进行序列化怎么办) + - [获取用键盘输入常用的两种方法](#获取用键盘输入常用的两种方法) + - [Java 中 IO 流分为几种?](#java-中-io-流分为几种) + - [既然有了字节流,为什么还要有字符流?](#既然有了字节流为什么还要有字符流) +- [4. 参考](#4-参考) + + + +## 基础概念与常识 + +### Java 语言有哪些特点? + +1. 简单易学; +2. 面向对象(封装,继承,多态); +3. 平台无关性( Java 虚拟机实现平台无关性); +4. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持); +5. 可靠性; +6. 安全性; +7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); +8. 编译与解释并存; + +> 修正(参见: [issue#544](https://github.com/Snailclimb/JavaGuide/issues/544)):C++11 开始(2011 年的时候),C++就引入了多线程库,在 windows、linux、macos 都可以使用`std::thread`和`std::async`来创建线程。参考链接:http://www.cplusplus.com/reference/thread/thread/?kw=thread + +### JVM vs JDK vs JRE + +#### JVM + +Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。 + +**什么是字节码?采用字节码的好处是什么?** + +> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。 + +**Java 程序从源代码到运行一般有下面 3 步:** + +![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png) + +我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。 + +> HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。 + +**总结:** + +Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 + +#### JDK 和 JRE + +JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。 + +JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。 + +如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。 + +### 为什么说 Java 语言“编译与解释并存”? + +高级编程语言按照程序的执行方式分为编译型和解释型两种。简单来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。比如,你想阅读一本英文名著,你可以找一个英文翻译人员帮助你阅读, +有两种选择方式,你可以先等翻译人员将全本的英文名著(也就是源码)都翻译成汉语,再去阅读,也可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。 + +Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(`\*.class` 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。 + +### Oracle JDK 和 OpenJDK 的对比 + +可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle 和 OpenJDK 之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。 + +对于 Java 7,没什么关键的地方。OpenJDK 项目主要基于 Sun 捐赠的 HotSpot 源代码。此外,OpenJDK 被选为 Java 7 的参考实现,由 Oracle 工程师维护。关于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更详细的答案: + +> 问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别? +> +> 答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。 + +**总结:** + +1. Oracle JDK 大概每 6 个月发一次主要版本,而 OpenJDK 版本大概每三个月发布一次。但这不是固定的,我觉得了解这个没啥用处。详情参见:[https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence](https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence) 。 +2. OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是 OpenJDK 的一个实现,并不是完全开源的; +3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择 Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用 OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题; +4. 在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能; +5. Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本; +6. Oracle JDK 根据二进制代码许可协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。 + +### Java 和 C++的区别? + +我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过 C++,也要记下来! + +- 都是面向对象的语言,都支持封装、继承和多态 +- Java 不提供指针来直接访问内存,程序内存更加安全 +- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。 +- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。 +- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。 +- ...... + +### import java 和 javax 有什么区别? + +刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。 + +所以,实际上 java 和 javax 没有区别。这都是一个名字。 + +## 基本语法 + +### 字符型常量和字符串常量的区别? + +1. **形式** : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符 +2. **含义** : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置) +3. **占内存大小** : 字符常量只占 2 个字节; 字符串常量占若干个字节 (**注意: char 在 Java 中占两个字节**), + + > 字符封装类 `Character` 有一个成员常量 `Character.SIZE` 值为 16,单位是`bits`,该值除以 8(`1byte=8bits`)后就可以得到 2 个字节 + +> java 编程思想第四版:2.2.2 节 +> ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) + +### 注释 + +Java 中的注释有三种: + +1. 单行注释 + +2. 多行注释 + +3. 文档注释。 + +在我们编写代码的时候,如果代码量比较少,我们自己或者团队其他成员还可以很轻易地看懂代码,但是当项目结构一旦复杂起来,我们就需要用到注释了。注释并不会执行(编译器在编译代码之前会把代码中的所有注释抹掉,字节码中不保留注释),是我们程序员写给自己看的,注释是你的代码说明书,能够帮助看代码的人快速地理清代码之间的逻辑关系。因此,在写程序的时候随手加上注释是一个非常好的习惯。 + +《Clean Code》这本书明确指出: + +> **代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。** +> +> **若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。** +> +> 举个例子: +> +> 去掉下面复杂的注释,只需要创建一个与注释所言同一事物的函数即可 +> +> ```java +> // check to see if the employee is eligible for full benefits +> if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) +> ``` +> +> 应替换为 +> +> ```java +> if (employee.isEligibleForFullBenefits()) +> ``` + +### 标识符和关键字的区别是什么? + +在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了标识符,简单来说,标识符就是一个名字。但是有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这种特殊的标识符就是关键字。因此,关键字是被赋予特殊含义的标识符。比如,在我们的日常生活中 ,“警察局”这个名字已经被赋予了特殊的含义,所以如果你开一家店,店的名字不能叫“警察局”,“警察局”就是我们日常生活中的关键字。 + +### Java 中有哪些常见的关键字? + +| 访问控制 | private | protected | public | | | | | +| -------------------- | -------- | ---------- | -------- | ------------ | ---------- | --------- | ------ | +| 类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native | +| | new | static | strictfp | synchronized | transient | volatile | | +| 程序控制 | break | continue | return | do | while | if | else | +| | for | instanceof | switch | case | default | | | +| 错误处理 | try | catch | throw | throws | finally | | | +| 包相关 | import | package | | | | | | +| 基本类型 | boolean | byte | char | double | float | int | long | +| | short | null | true | false | | | | +| 变量引用 | super | this | void | | | | | +| 保留字 | goto | const | | | | | | + +### 自增自减运算符 + +在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(--)。 + +++和--运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。例如,当 `b = ++a` 时,先自增(自己增加 1),再赋值(赋值给 b);当 `b = a++` 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。 + +### continue、break、和 return 的区别是什么? + +在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词: + +1. continue :指跳出当前的这一次循环,继续下一次循环。 +2. break :指跳出整个循环体,继续执行循环下面的语句。 + +return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法: + +1. `return;` :直接使用 return 结束方法执行,用于没有返回值函数的方法 +2. `return value;` :return 一个特定值,用于有返回值函数的方法 + +### Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符? + +Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 + +Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。 + +```java +List list = new ArrayList<>(); + +list.add(12); +//这里直接添加会报错 +list.add("a"); +Class clazz = list.getClass(); +Method add = clazz.getDeclaredMethod("add", Object.class); +//但是通过反射添加,是可以的 +add.invoke(list, "kl"); + +System.out.println(list); +``` + +泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。 + +**1.泛型类**: + +```java +//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 +//在实例化泛型类时,必须指定T的具体类型 +public class Generic{ + + private T key; + + public Generic(T key) { + this.key = key; + } + + public T getKey(){ + return key; + } +} +``` + +如何实例化泛型类: + +```java +Generic genericInteger = new Generic(123456); +``` + +**2.泛型接口** : + +```java +public interface Generator { + public T method(); +} +``` + +实现泛型接口,不指定类型: + +```java +class GeneratorImpl implements Generator{ + @Override + public T method() { + return null; + } +} +``` + +实现泛型接口,指定类型: + +```java +class GeneratorImpl implements Generator{ + @Override + public String method() { + return "hello"; + } +} +``` + +**3.泛型方法** : + +```java + public static < E > void printArray( E[] inputArray ) + { + for ( E element : inputArray ){ + System.out.printf( "%s ", element ); + } + System.out.println(); + } +``` + +使用: + +```java +// 创建不同类型数组: Integer, Double 和 Character +Integer[] intArray = { 1, 2, 3 }; +String[] stringArray = { "Hello", "World" }; +printArray( intArray ); +printArray( stringArray ); +``` + +**常用的通配符为: T,E,K,V,?** + +- ? 表示不确定的 java 类型 +- T (type) 表示具体的一个 java 类型 +- K V (key value) 分别代表 java 键值中的 Key Value +- E (element) 代表 Element + +### ==和 equals 的区别 + +对于基本数据类型来说,==比较的是值。对于引用数据类型来说,==比较的是对象的内存地址。 + +> 因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。 + +**`equals()`** 作用不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。`equals()`方法存在于`Object`类中,而`Object`类是所有类的直接或间接父类。 + +`Object` 类 `equals()` 方法: + +```java +public boolean equals(Object obj) { + return (this == obj); +} +``` + +`equals()` 方法存在两种使用情况: + +- **类没有覆盖 `equals()`方法** :通过`equals()`比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 `Object`类`equals()`方法。 +- **类覆盖了 `equals()`方法** :一般我们都覆盖 `equals()`方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。 + +**举个例子:** + +```java +public class test1 { + public static void main(String[] args) { + String a = new String("ab"); // a 为一个引用 + String b = new String("ab"); // b为另一个引用,对象的内容一样 + String aa = "ab"; // 放在常量池中 + String bb = "ab"; // 从常量池中查找 + if (aa == bb) // true + System.out.println("aa==bb"); + if (a == b) // false,非同一对象 + System.out.println("a==b"); + if (a.equals(b)) // true + System.out.println("aEQb"); + if (42 == 42.0) { // true + System.out.println("true"); + } + } +} +``` + +**说明:** + +- `String` 中的 `equals` 方法是被重写过的,因为 `Object` 的 `equals` 方法是比较的对象的内存地址,而 `String` 的 `equals` 方法比较的是对象的值。 +- 当创建 `String` 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 `String` 对象。 + +`String`类`equals()`方法: + +```java +public boolean equals(Object anObject) { + if (this == anObject) { + return true; + } + if (anObject instanceof String) { + String anotherString = (String)anObject; + int n = value.length; + if (n == anotherString.value.length) { + char v1[] = value; + char v2[] = anotherString.value; + int i = 0; + while (n-- != 0) { + if (v1[i] != v2[i]) + return false; + i++; + } + return true; + } + } + return false; +} +``` + +### hashCode()与 equals() + +面试官可能会问你:“你重写过 `hashcode` 和 `equals`么,为什么重写 `equals` 时必须重写 `hashCode` 方法?” + +**1)hashCode()介绍:** + +`hashCode()` 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。`hashCode()`定义在 JDK 的 `Object` 类中,这就意味着 Java 中的任何类都包含有 `hashCode()` 函数。另外需要注意的是: `Object` 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。 + +```java +public native int hashCode(); +``` + +散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) + +**2)为什么要有 hashCode?** + +我们以“`HashSet` 如何检查重复”为例子来说明为什么要有 hashCode? + +当你把对象加入 `HashSet` 时,`HashSet` 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals()` 方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head First Java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 + +**3)为什么重写 `equals` 时必须重写 `hashCode` 方法?** + +如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。**因此,equals 方法被覆盖过,则 `hashCode` 方法也必须被覆盖。** + +> `hashCode()`的默认行为是对堆上的对象产生独特值。如果没有重写 `hashCode()`,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) + +**4)为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?** + +在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。 + +因为 `hashCode()` 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 `hashCode`。 + +我们刚刚也提到了 `HashSet`,如果 `HashSet` 在对比的时候,同样的 hashcode 有多个对象,它会使用 `equals()` 来判断是否真的相同。也就是说 `hashcode` 只是用来缩小查找成本。 + +更多关于 `hashcode()` 和 `equals()` 的内容可以查看:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html) + +## 基本数据类型 + +### Java 中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢? + +Java 中有 8 种基本数据类型,分别为: + +1. 6 种数字类型 :`byte`、`short`、`int`、`long`、`float`、`double` +2. 1 种字符类型:`char` +3. 1 种布尔型:`boolean`。 + +这 8 种基本数据类型的默认值以及所占空间的大小如下: + +| 基本类型 | 位数 | 字节 | 默认值 | +| :-------- | :--- | :--- | :------ | +| `int` | 32 | 4 | 0 | +| `short` | 16 | 2 | 0 | +| `long` | 64 | 8 | 0L | +| `byte` | 8 | 1 | 0 | +| `char` | 16 | 2 | 'u0000' | +| `float` | 32 | 4 | 0f | +| `double` | 64 | 8 | 0d | +| `boolean` | 1 | | false | + +另外,对于 `boolean`,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。 + +**注意:** + +1. Java 里使用 `long` 类型的数据一定要在数值后面加上 **L**,否则将作为整型解析。 +2. `char a = 'h'`char :单引号,`String a = "hello"` :双引号。 + +这八种基本类型都有对应的包装类分别为:`Byte`、`Short`、`Integer`、`Long`、`Float`、`Double`、`Character`、`Boolean` 。 + +包装类型不赋值就是 `Null` ,而基本类型有默认值且不是 `Null`。 + +另外,这个问题建议还可以先从 JVM 层面来分析。 + +基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。 + +> 《深入理解 Java 虚拟机》 :局部变量表主要存放了编译期可知的基本数据类型**(boolean、byte、char、short、int、float、long、double)**、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。 + +### 自动装箱与拆箱 + +- **装箱**:将基本类型用它们对应的引用类型包装起来; +- **拆箱**:将包装类型转换为基本数据类型; + +举例: + +```java +Integer i = 10; //装箱 +int n = i; //拆箱 +``` + +上面这两行代码对应的字节码为: + +```java + L1 + + LINENUMBER 8 L1 + + ALOAD 0 + + BIPUSH 10 + + INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; + + PUTFIELD AutoBoxTest.i : Ljava/lang/Integer; + + L2 + + LINENUMBER 9 L2 + + ALOAD 0 + + ALOAD 0 + + GETFIELD AutoBoxTest.i : Ljava/lang/Integer; + + INVOKEVIRTUAL java/lang/Integer.intValue ()I + + PUTFIELD AutoBoxTest.n : I + + RETURN +``` + +从字节码中,我们发现装箱其实就是调用了 包装类的`valueOf()`方法,拆箱其实就是调用了 `xxxValue()`方法。 + +因此, + +- `Integer i = 10` 等价于 `Integer i = Integer.valueOf(10)` +- `int n = i` 等价于 `int n = i.intValue()`; + +### 8 种基本类型的包装类和常量池 + +Java 基本类型的包装类的大部分都实现了常量池技术。`Byte`,`Short`,`Integer`,`Long` 这 4 种包装类默认创建了数值 **[-128,127]** 的相应类型的缓存数据,`Character` 创建了数值在[0,127]范围的缓存数据,`Boolean` 直接返回 `True` Or `False`。 + +**Integer 缓存源码:** + +```java +/** + +*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。 + +*/ + +public static Integer valueOf(int i) { + + if (i >= IntegerCache.low && i <= IntegerCache.high) + + return IntegerCache.cache[i + (-IntegerCache.low)]; + + return new Integer(i); + +} + +private static class IntegerCache { + + static final int low = -128; + + static final int high; + + static final Integer cache[]; + +} +``` + +**`Character` 缓存源码:** + +```java +public static Character valueOf(char c) { + + if (c <= 127) { // must cache + + return CharacterCache.cache[(int)c]; + + } + + return new Character(c); + +} + + + +private static class CharacterCache { + + private CharacterCache(){} + + + + static final Character cache[] = new Character[127 + 1]; + + static { + + for (int i = 0; i < cache.length; i++) + + cache[i] = new Character((char)i); + + } + +} +``` + +**`Boolean` 缓存源码:** + +```java +public static Boolean valueOf(boolean b) { + + return (b ? TRUE : FALSE); + +} +``` + +如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。 + +两种浮点数类型的包装类 `Float`,`Double` 并没有实现常量池技术。 + +```java +Integer i1 = 33; + +Integer i2 = 33; + +System.out.println(i1 == i2);// 输出 true + +Float i11 = 333f; + +Float i22 = 333f; + +System.out.println(i11 == i22);// 输出 false + +Double i3 = 1.2; + +Double i4 = 1.2; + +System.out.println(i3 == i4);// 输出 false +``` + +下面我们来看一下问题。下面的代码的输出结果是 `true` 还是 `flase` 呢? + +```java +Integer i1 = 40; + +Integer i2 = new Integer(40); + +System.out.println(i1==i2); +``` + +`Integer i1=40` 这一行代码会发生拆箱,也就是说这行代码等价于 `Integer i1=Integer.valueOf(40)` 。因此,`i1` 直接使用的是常量池中的对象。而`Integer i1 = new Integer(40)` 会直接创建新的对象。 + +因此,答案是 `false` 。你答对了吗? + +记住:**所有整型包装类对象之间值的比较,全部使用 equals 方法比较**。 + +![](https://img-blog.csdnimg.cn/20210422164544846.png) + +## 方法(函数) + +### 什么是方法的返回值? + +方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用是接收出结果,使得它可以用于其他的操作! + +### 方法有哪几种类型? + +**1.无参数无返回值的方法** + +```java +// 无参数无返回值的方法(如果方法没有返回值,不能不写,必须写void,表示没有返回值) +public void f1() { + System.out.println("无参数无返回值的方法"); +} +``` + +**2.有参数无返回值的方法** + +```java +/** +* 有参数无返回值的方法 +* 参数列表由零组到多组“参数类型+形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间以英文空格隔开 +*/ +public void f2(int a, String b, int c) { + System.out.println(a + "-->" + b + "-->" + c); +} +``` + +**3.有返回值无参数的方法** + +```java +// 有返回值无参数的方法(返回值可以是任意的类型,在函数里面必须有return关键字返回对应的类型) +public int f3() { + System.out.println("有返回值无参数的方法"); + return 2; +} +``` + +**4.有返回值有参数的方法** + +```java +// 有返回值有参数的方法 +public int f4(int a, int b) { + return a * b; +} +``` + +**5.return 在无返回值方法的特殊使用** + +```java +// return在无返回值方法的特殊使用 +public void f5(int a) { + if (a > 10) { + return;//表示结束所在方法 (f5方法)的执行,下方的输出语句不会执行 + } + System.out.println(a); +} +``` + +### 在一个静态方法内调用一个非静态成员为什么是非法的? + +这个需要结合 JVM 的相关知识,静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,然后通过类的实例对象去访问。在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。 + +### 静态方法和实例方法有何不同? + +1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象。** + +2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。 + +### 为什么 Java 中只有值传递? + +首先,我们回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。 + +**按值调用(call by value)** 表示方法接收的是调用者提供的值,**按引用调用(call by reference)** 表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。 + +**Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。** + +**下面通过 3 个例子来给大家说明** + +> **example 1** + +```java +public static void main(String[] args) { + int num1 = 10; + int num2 = 20; + + swap(num1, num2); + + System.out.println("num1 = " + num1); + System.out.println("num2 = " + num2); +} + +public static void swap(int a, int b) { + int temp = a; + a = b; + b = temp; + + System.out.println("a = " + a); + System.out.println("b = " + b); +} +``` + +**结果:** + +``` +a = 20 +b = 10 +num1 = 10 +num2 = 20 +``` + +**解析:** + +![example 1 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/22191348.jpg) + +在 swap 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。 + +**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.** + +> **example 2** + +```java + public static void main(String[] args) { + int[] arr = { 1, 2, 3, 4, 5 }; + System.out.println(arr[0]); + change(arr); + System.out.println(arr[0]); + } + + public static void change(int[] array) { + // 将数组的第一个元素变为0 + array[0] = 0; + } +``` + +**结果:** + +``` +1 +0 +``` + +**解析:** + +![example 2](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/3825204.jpg) + +array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。 + +**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。** + +**很多程序设计语言(特别是,C++和 Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。** + +> **example 3** + +```java +public class Test { + + public static void main(String[] args) { + // TODO Auto-generated method stub + Student s1 = new Student("小张"); + Student s2 = new Student("小李"); + Test.swap(s1, s2); + System.out.println("s1:" + s1.getName()); + System.out.println("s2:" + s2.getName()); + } + + public static void swap(Student x, Student y) { + Student temp = x; + x = y; + y = temp; + System.out.println("x:" + x.getName()); + System.out.println("y:" + y.getName()); + } +} +``` + +**结果:** + +``` +x:小李 +y:小张 +s1:小张 +s2:小李 +``` + +**解析:** + +交换之前: + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/88729818.jpg) + +交换之后: + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/34384414.jpg) + +通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝** + +> **总结** + +Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按 +值传递的。 + +下面再总结一下 Java 中方法参数的使用情况: + +- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。 +- 一个方法可以改变一个对象参数的状态。 +- 一个方法不能让对象参数引用一个新的对象。 + +**参考:** + +《Java 核心技术卷 Ⅰ》基础知识第十版第四章 4.5 小节 + +### 重载和重写的区别 + +> 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理 +> +> 重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法 + +#### 重载 + +发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。 + +下面是《Java 核心技术》对重载这个概念的介绍: + +![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/bg/desktopjava核心技术-重载.jpg) + +综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。 + +#### 重写 + +重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。 + +1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。 +2. 如果父类方法访问修饰符为 `private/final/static` 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。 +3. 构造方法无法被重写 + +综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变 + +暖心的 Guide 哥最后再来个图表总结一下! + +| 区别点 | 重载方法 | 重写方法 | +| :--------- | :------- | :----------------------------------------------------------- | +| 发生范围 | 同一个类 | 子类 | +| 参数列表 | 必须修改 | 一定不能修改 | +| 返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 | +| 异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; | +| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) | +| 发生阶段 | 编译期 | 运行期 | + +**方法的重写要遵循“两同两小一大”**(以下内容摘录自《疯狂 Java 讲义》,[issue#892](https://github.com/Snailclimb/JavaGuide/issues/892) ): + +- “两同”即方法名相同、形参列表相同; +- “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; +- “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。 + +⭐️ 关于 **重写的返回值类**型 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。 + +```java +public class Hero { + public String name() { + return "超级英雄"; + } +} +public class SuperMan extends Hero{ + @Override + public String name() { + return "超人"; + } + public Hero hero() { + return new Hero(); + } +} + +public class SuperSuperMan extends SuperMan { + public String name() { + return "超级超级英雄"; + } + + @Override + public SuperMan hero() { + return new SuperMan(); + } +} +``` + +### 深拷贝 vs 浅拷贝 + +1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。 +2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。 + +![deep and shallow copy](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/java-deep-and-shallow-copy.jpg) + +## Java 面向对象 + +### 面向对象和面向过程的区别 + +- **面向过程** :**面向过程性能比面向对象高。** 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,**面向过程没有面向对象易维护、易复用、易扩展。** +- **面向对象** :**面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。 + +参见 issue : [面向过程 :面向过程性能比面向对象高??](https://github.com/Snailclimb/JavaGuide/issues/431) + +> 这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java 性能差的主要原因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是可以直接被 CPU 执行的二进制机械码。 +> +> 而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比 Java 好。 + +### 成员变量与局部变量的区别有哪些? + +1. 从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 `public`,`private`,`static` 等修饰符所修饰,而局部变量不能被访问控制修饰符及 `static` 所修饰;但是,成员变量和局部变量都能被 `final` 所修饰。 +2. 从变量在内存中的存储方式来看,如果成员变量是使用 `static` 修饰的,那么这个成员变量是属于类的,如果没有使用 `static` 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 +3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 +4. 从变量是否有默认值来看,成员变量如果没有被赋初,则会自动以类型的默认值而赋值(一种情况例外:被 `final` 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。 + +### 创建一个对象用什么运算符?对象实体与对象引用有何不同? + +new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。 + +一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。 + +### 对象的相等与指向他们的引用相等,两者有什么不同? + +对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。 + +### 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么? + +构造方法主要作用是完成对类对象的初始化工作。 + +如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,这时候,就不能直接 new 一个对象而不传递参数了,所以我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。 + +### 构造方法有哪些特点?是否可被 override? + +特点: + +1. 名字与类名相同。 +2. 没有返回值,但不能用 void 声明构造函数。 +3. 生成类的对象时自动执行,无需调用。 + +构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。 + +### 面向对象三大特征 + +#### 封装 + +封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。 + +```java +public class Student { + private int id;//id属性私有化 + private String name;//name属性私有化 + + //获取id的方法 + public int getId() { + return id; + } + + //设置id的方法 + public void setId(int id) { + this.id = id; + } + + //获取name的方法 + public String getName() { + return name; + } + + //设置name的方法 + public void setName(String name) { + this.name = name; + } +} +``` + +#### 继承 + +不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。 + +**关于继承如下 3 点请记住:** + +1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。 +2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 +3. 子类可以用自己的方式实现父类的方法。(以后介绍)。 + +#### 多态 + +多态,顾名思义,表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例。 + +**多态的特点:** + +- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系; +- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定; +- 多态不能调用“只在子类存在但在父类不存在”的方法; +- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。 + +### String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的? + +**可变性** + +简单的来说:`String` 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以`String` 对象是不可变的。 + +> 补充(来自[issue 675](https://github.com/Snailclimb/JavaGuide/issues/675)):在 Java 9 之后,String 、`StringBuilder` 与 `StringBuffer` 的实现改用 byte 数组存储字符串 `private final byte[] value` + +而 `StringBuilder` 与 `StringBuffer` 都继承自 `AbstractStringBuilder` 类,在 `AbstractStringBuilder` 中也是使用字符数组保存字符串`char[]value` 但是没有用 `final` 关键字修饰,所以这两种对象都是可变的。 + +`StringBuilder` 与 `StringBuffer` 的构造方法都是调用父类构造方法也就是`AbstractStringBuilder` 实现的,大家可以自行查阅源码。 + +`AbstractStringBuilder.java` + +```java +abstract class AbstractStringBuilder implements Appendable, CharSequence { + /** + * The value is used for character storage. + */ + char[] value; + + /** + * The count is the number of characters used. + */ + int count; + + AbstractStringBuilder(int capacity) { + value = new char[capacity]; + }} +``` + +**线程安全性** + +`String` 中的对象是不可变的,也就可以理解为常量,线程安全。`AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。 + +**性能** + +每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。`StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 + +**对于三者使用的总结:** + +1. 操作少量的数据: 适用 `String` +2. 单线程操作字符串缓冲区下操作大量数据: 适用 `StringBuilder` +3. 多线程操作字符串缓冲区下操作大量数据: 适用 `StringBuffer` + +### Object 类的常见方法总结 + +Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法: + +```java + +public final native Class getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。 + +public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。 +public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。 + +protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。 + +public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。 + +public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。 + +public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。 + +public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。 + +public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。 + +public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念 + +protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作 + +``` + +## 反射 + +### 何为反射? + +如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。 + +反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。 + +通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。 + +### 反射机制优缺点 + +- **优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利 +- **缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow) + +### 反射的应用场景 + +像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。 + +但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。 + +**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。** + +比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。 + +```java +public class DebugInvocationHandler implements InvocationHandler { + /** + * 代理类中的真实对象 + */ + private final Object target; + + public DebugInvocationHandler(Object target) { + this.target = target; + } + + + public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { + System.out.println("before method " + method.getName()); + Object result = method.invoke(target, args); + System.out.println("after method " + method.getName()); + return result; + } +} + +``` + +另外,像 Java 中的一大利器 **注解** 的实现也用到了反射。 + +为什么你使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢? + +这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。 + +## 异常 + +### Java 异常类层次结构图 + +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/Java%E5%BC%82%E5%B8%B8%E7%B1%BB%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E5%9B%BE.png) + +

图片来自:https://simplesnippets.tech/exception-handling-in-java-part-1/

+ +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/Java%E5%BC%82%E5%B8%B8%E7%B1%BB%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E5%9B%BE2.png) + +

图片来自:https://chercher.tech/java-programming/exceptions-java

+ +在 Java 中,所有的异常都有一个共同的祖先 `java.lang` 包中的 `Throwable` 类。`Throwable` 类有两个重要的子类 `Exception`(异常)和 `Error`(错误)。`Exception` 能被程序本身处理(`try-catch`), `Error` 是无法处理的(只能尽量避免)。 + +`Exception` 和 `Error` 二者都是 Java 异常处理的重要子类,各自都包含大量子类。 + +- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。 +- **`Error`** :`Error` 属于程序无法处理的错误 ,我们没办法通过 `catch` 来进行捕获 。例如,Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。 + +**受检查异常** + +Java 代码在编译过程中,如果受检查异常没有被 `catch`/`throw` 处理的话,就没办法通过编译 。比如下面这段 IO 操作的代码。 + +![check-exception](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/check-exception.png) + +除了`RuntimeException`及其子类以外,其他的`Exception`类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、`ClassNotFoundException` 、`SQLException`...。 + +**不受检查异常** + +Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。 + +`RuntimeException` 及其子类都统称为非受检查异常,例如:`NullPointerException`、`NumberFormatException`(字符串转换为数字)、`ArrayIndexOutOfBoundsException`(数组越界)、`ClassCastException`(类型转换错误)、`ArithmeticException`(算术错误)等。 + +### Throwable 类常用方法 + +- **`public string getMessage()`**:返回异常发生时的简要描述 +- **`public string toString()`**:返回异常发生时的详细信息 +- **`public string getLocalizedMessage()`**:返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同 +- **`public void printStackTrace()`**:在控制台上打印 `Throwable` 对象封装的异常信息 + +### try-catch-finally + +- **`try`块:** 用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。 +- **`catch`块:** 用于处理 try 捕获到的异常。 +- **`finally` 块:** 无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。 + +**在以下 3 种特殊情况下,`finally` 块不会被执行:** + +2. 在 `try` 或 `finally`块中用了 `System.exit(int)`退出程序。但是,如果 `System.exit(int)` 在异常语句之后,`finally` 还是会被执行 +3. 程序所在的线程死亡。 +4. 关闭 CPU。 + +下面这部分内容来自 issue:。 + +**注意:** 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下: + +```java +public class Test { + public static int f(int value) { + try { + return value * value; + } finally { + if (value == 2) { + return 0; + } + } + } +} +``` + +如果调用 `f(2)`,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。 + +### 使用 `try-with-resources` 来代替`try-catch-finally` + +1. **适用范围(资源的定义):** 任何实现 `java.lang.AutoCloseable`或者 `java.io.Closeable` 的对象 +2. **关闭资源和 finally 块的执行顺序:** 在 `try-with-resources` 语句中,任何 catch 或 finally 块在声明的资源关闭后运行 + +《Effecitve Java》中明确指出: + +> 面对必须要关闭的资源,我们总是应该优先使用 `try-with-resources` 而不是`try-finally`。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。`try-with-resources`语句让我们更容易编写必须要关闭的资源的代码,若采用`try-finally`则几乎做不到这点。 + +Java 中类似于`InputStream`、`OutputStream` 、`Scanner` 、`PrintWriter`等的资源都需要我们调用`close()`方法来手动关闭,一般情况下我们都是通过`try-catch-finally`语句来实现这个需求,如下: + +```java + //读取文本文件的内容 + Scanner scanner = null; + try { + scanner = new Scanner(new File("D://read.txt")); + while (scanner.hasNext()) { + System.out.println(scanner.nextLine()); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + if (scanner != null) { + scanner.close(); + } + } +``` + +使用 Java 7 之后的 `try-with-resources` 语句改造上面的代码: + +```java +try (Scanner scanner = new Scanner(new File("test.txt"))) { + while (scanner.hasNext()) { + System.out.println(scanner.nextLine()); + } +} catch (FileNotFoundException fnfe) { + fnfe.printStackTrace(); +} +``` + +当然多个资源需要关闭的时候,使用 `try-with-resources` 实现起来也非常简单,如果你还是用`try-catch-finally`可能会带来很多问题。 + +通过使用分号分隔,可以在`try-with-resources`块中声明多个资源。 + +```java +try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); + BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) { + int b; + while ((b = bin.read()) != -1) { + bout.write(b); + } + } + catch (IOException e) { + e.printStackTrace(); + } +``` + +## I\O 流 + +### 什么是序列化?什么是反序列化? + +如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。 + +简单来说: + +- **序列化**: 将数据结构或对象转换成二进制字节流的过程 +- **反序列化**:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程 + +对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。 + +维基百科是如是介绍序列化的: + +> **序列化**(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 + +综上:**序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。** + +![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2020-8/a478c74d-2c48-40ae-9374-87aacf05188c.png) + +

https://www.corejavaguru.com/java/serialization/interview-questions-1

+ +### Java 序列化中如果有些字段不想进行序列化,怎么办? + +`对于不想进行序列化的变量,使用`transient`关键字修饰。` + +`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。`transient` 只能修饰变量,不能修饰类和方法。 + +### 获取用键盘输入常用的两种方法 + +方法 1:通过 `Scanner` + +```java +Scanner input = new Scanner(System.in); +String s = input.nextLine(); +input.close(); +``` + +方法 2:通过 `BufferedReader` + +```java +BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); +String s = input.readLine(); +``` + +### Java 中 IO 流分为几种? + +- 按照流的流向分,可以分为输入流和输出流; +- 按照操作单元划分,可以划分为字节流和字符流; +- 按照流的角色划分为节点流和处理流。 + +Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。 + +- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 +- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。 + +按操作方式分类结构图: + +![IO-操作方式分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-操作方式分类.png) + +按操作对象分类结构图: + +![IO-操作对象分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-操作对象分类.png) + +### 既然有了字节流,为什么还要有字符流? + +问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?** + +回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。 + +## 4. 参考 + +- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre +- https://www.educba.com/oracle-vs-openjdk/ +- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top## 基础概念与常识 \ No newline at end of file diff --git "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206\347\226\221\351\232\276\347\202\271.md" "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206\347\226\221\351\232\276\347\202\271.md" new file mode 100644 index 0000000000000000000000000000000000000000..fc669f19b1dedf19fd4d10c02b4a431bd3e1fea4 --- /dev/null +++ "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206\347\226\221\351\232\276\347\202\271.md" @@ -0,0 +1,396 @@ + + +- [1. 基础](#1-基础) + - [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法) + - [1.2. 整型包装类值的比较](#12-整型包装类值的比较) + - [1.3. BigDecimal](#13-bigdecimal) + - [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处) + - [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较) + - [1.3.3. BigDecimal 保留几位小数](#133-bigdecimal-保留几位小数) + - [1.3.4. BigDecimal 的使用注意事项](#134-bigdecimal-的使用注意事项) + - [1.3.5. 总结](#135-总结) + - [1.4. 基本数据类型与包装数据类型的使用标准](#14-基本数据类型与包装数据类型的使用标准) +- [2. 集合](#_2-集合) + - [2.1. Arrays.asList()使用指南](#21-arraysaslist使用指南) + - [2.1.1. 简介](#211-简介) + - [2.1.2. 《阿里巴巴Java 开发手册》对其的描述](#212-阿里巴巴java-开发手册对其的描述) + - [2.1.3. 使用时的注意事项总结](#213-使用时的注意事项总结) + - [2.1.4. 如何正确的将数组转换为ArrayList?](#214-如何正确的将数组转换为arraylist) + - [2.2. Collection.toArray()方法使用的坑&如何反转数组](#22-collectiontoarray方法使用的坑如何反转数组) + - [2.3. 不要在 foreach 循环里进行元素的 remove/add 操作](#23-不要在-foreach-循环里进行元素的-removeadd-操作) + + + +# 1. 基础 + +## 1.1. 正确使用 equals 方法 + +Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 + +举个例子: + +```java +// 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常 +String str = null; +if (str.equals("SnailClimb")) { + ... +} else { + .. +} +``` + +运行上面的程序会抛出空指针异常,但是我们把第二行的条件判断语句改为下面这样的话,就不会抛出空指针异常,else 语句块得到执行。: + +```java +"SnailClimb".equals(str);// false +``` +不过更推荐使用 `java.util.Objects#equals`(JDK7 引入的工具类)。 + +```java +Objects.equals(null,"SnailClimb");// false +``` +我们看一下`java.util.Objects#equals`的源码就知道原因了。 +```java +public static boolean equals(Object a, Object b) { + // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。 + return (a == b) || (a != null && a.equals(b)); +} +``` + +**注意:** + +Reference:[Java中equals方法造成空指针异常的原因及解决方案](https://blog.csdn.net/tick_tock97/article/details/72824894) + +- 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。 +- 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null == null`将返回true。 +- 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常 + +## 1.2. 整型包装类值的比较 + +所有整型包装类对象值的比较必须使用equals方法。 + +先看下面这个例子: + +```java +Integer x = 3; +Integer y = 3; +System.out.println(x == y);// true +Integer a = new Integer(3); +Integer b = new Integer(3); +System.out.println(a == b);//false +System.out.println(a.equals(b));//true +``` + +当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。 + +**注意:** 如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件,这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。 + +## 1.3. BigDecimal + +### 1.3.1. BigDecimal 的用处 + +《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例: + +```java +float a = 1.0f - 0.9f; +float b = 0.9f - 0.8f; +System.out.println(a);// 0.100000024 +System.out.println(b);// 0.099999964 +System.out.println(a == b);// false +``` +具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(**精度丢失**),我们如何解决这个问题呢?一种很常用的方法是:**使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。** + +```java +BigDecimal a = new BigDecimal("1.0"); +BigDecimal b = new BigDecimal("0.9"); +BigDecimal c = new BigDecimal("0.8"); + +BigDecimal x = a.subtract(b); +BigDecimal y = b.subtract(c); + +System.out.println(x); /* 0.1 */ +System.out.println(y); /* 0.1 */ +System.out.println(Objects.equals(x, y)); /* true */ +``` + +### 1.3.2. BigDecimal 的大小比较 + +`a.compareTo(b)` : 返回 -1 表示 `a` 小于 `b`,0 表示 `a` 等于 `b` , 1表示 `a` 大于 `b`。 + +```java +BigDecimal a = new BigDecimal("1.0"); +BigDecimal b = new BigDecimal("0.9"); +System.out.println(a.compareTo(b));// 1 +``` +### 1.3.3. BigDecimal 保留几位小数 + +通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。 + +```java +BigDecimal m = new BigDecimal("1.255433"); +BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN); +System.out.println(n);// 1.255 +``` + +### 1.3.4. BigDecimal 的使用注意事项 + +注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。 + +![《阿里巴巴Java开发手册》对这部分BigDecimal的描述](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/BigDecimal.png) + +### 1.3.5. 总结 + +BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。 + +BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念 + +## 1.4. 基本数据类型与包装数据类型的使用标准 + +Reference:《阿里巴巴Java开发手册》 + +- 【强制】所有的 POJO 类属性必须使用包装数据类型。 +- 【强制】RPC 方法的返回值和参数必须使用包装数据类型。 +- 【推荐】所有的局部变量使用基本数据类型。 + +比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样. + +**说明** :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。 + +**正例** : 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。 + +**反例** : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。 + +# 2. 集合 + +## 2.1. Arrays.asList()使用指南 + +最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。 + +### 2.1.1. 简介 + +`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。 + +```java +String[] myArray = {"Apple", "Banana", "Orange"}; +List myList = Arrays.asList(myArray); +//上面两个语句等价于下面一条语句 +List myList = Arrays.asList("Apple","Banana", "Orange"); +``` + +JDK 源码对于这个方法的说明: + +```java +/** + *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁, + * 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 + */ +public static List asList(T... a) { + return new ArrayList<>(a); +} +``` + +### 2.1.2. 《阿里巴巴Java 开发手册》对其的描述 + +`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴Java 开发手册》对于这个方法有如下描述: + +![阿里巴巴Java开发手-Arrays.asList()方法](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/阿里巴巴Java开发手-Arrays.asList()方法.png) + +### 2.1.3. 使用时的注意事项总结 + +**传递的数组必须是对象数组,而不是基本类型。** + +`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。 + +```java +int[] myArray = {1, 2, 3}; +List myList = Arrays.asList(myArray); +System.out.println(myList.size());//1 +System.out.println(myList.get(0));//数组地址值 +System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException +int[] array = (int[]) myList.get(0); +System.out.println(array[0]);//1 +``` +当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。 + +我们使用包装类型数组就可以解决这个问题。 + +```java +Integer[] myArray = {1, 2, 3}; +``` + +**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。** + +```java +List myList = Arrays.asList(1, 2, 3); +myList.add(4);//运行时报错:UnsupportedOperationException +myList.remove(1);//运行时报错:UnsupportedOperationException +myList.clear();//运行时报错:UnsupportedOperationException +``` + +`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。 + +```java +List myList = Arrays.asList(1, 2, 3); +System.out.println(myList.getClass());//class java.util.Arrays$ArrayList +``` + +下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。 + +```java + private static class ArrayList extends AbstractList + implements RandomAccess, java.io.Serializable + { + ... + + @Override + public E get(int index) { + ... + } + + @Override + public E set(int index, E element) { + ... + } + + @Override + public int indexOf(Object o) { + ... + } + + @Override + public boolean contains(Object o) { + ... + } + + @Override + public void forEach(Consumer action) { + ... + } + + @Override + public void replaceAll(UnaryOperator operator) { + ... + } + + @Override + public void sort(Comparator c) { + ... + } + } +``` + +我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。 + +```java +public E remove(int index) { + throw new UnsupportedOperationException(); +} +``` + +### 2.1.4. 如何正确的将数组转换为ArrayList? + +stackoverflow:https://dwz.cn/vcBkTiTW + +**1. 自己动手实现(教育目的)** + +```java +//JDK1.5+ +static List arrayToList(final T[] array) { + final List l = new ArrayList(array.length); + + for (final T s : array) { + l.add(s); + } + return l; +} +``` + +```java +Integer [] myArray = { 1, 2, 3 }; +System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList +``` + +**2. 最简便的方法(推荐)** + +```java +List list = new ArrayList<>(Arrays.asList("a", "b", "c")) +``` + +**3. 使用 Java8 的Stream(推荐)** + +```java +Integer [] myArray = { 1, 2, 3 }; +List myList = Arrays.stream(myArray).collect(Collectors.toList()); +//基本类型也可以实现转换(依赖boxed的装箱操作) +int [] myArray2 = { 1, 2, 3 }; +List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList()); +``` + +**4. 使用 Guava(推荐)** + +对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空) + +```java +List il = ImmutableList.of("string", "elements"); // from varargs +List il = ImmutableList.copyOf(aStringArray); // from array +``` +对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法: + +```java +List l1 = Lists.newArrayList(anotherListOrCollection); // from collection +List l2 = Lists.newArrayList(aStringArray); // from array +List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs +``` + +**5. 使用 Apache Commons Collections** + +```java +List list = new ArrayList(); +CollectionUtils.addAll(list, str); +``` + +**6. 使用 Java9 的 `List.of()`方法** +``` java +Integer[] array = {1, 2, 3}; +List list = List.of(array); +System.out.println(list); /* [1, 2, 3] */ +/* 不支持基本数据类型 */ +``` + +## 2.2. Collection.toArray()方法使用的坑&如何反转数组 + +该方法是一个泛型方法:` T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。 + +```java +String [] s= new String[]{ + "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A" +}; +List list = Arrays.asList(s); +Collections.reverse(list); +s=list.toArray(new String[0]);//没有指定类型的话会报错 +``` + +由于JVM优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见: + +## 2.3. 不要在 foreach 循环里进行元素的 remove/add 操作 + +如果要进行`remove`操作,可以调用迭代器的 `remove `方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身`remove/add`方法,迭代器都将抛出一个`ConcurrentModificationException`,这就是单线程状态下产生的 **fail-fast 机制**。 + +> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。 + +Java8开始,可以使用`Collection#removeIf()`方法删除满足特定条件的元素,如 +``` java +List list = new ArrayList<>(); +for (int i = 1; i <= 10; ++i) { + list.add(i); +} +list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */ +System.out.println(list); /* [1, 3, 5, 7, 9] */ +``` + +`java.util`包下面的所有的集合类都是fail-fast的,而`java.util.concurrent`包下面的所有的类都是fail-safe的。 + +![不要在 foreach 循环里进行元素的 remove/add 操作](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/foreach-remove:add.png) + + + diff --git "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" "b/docs/java/basis/Java\345\270\270\350\247\201\345\205\263\351\224\256\345\255\227\346\200\273\347\273\223.md" similarity index 70% rename from "Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" rename to "docs/java/basis/Java\345\270\270\350\247\201\345\205\263\351\224\256\345\255\227\346\200\273\347\273\223.md" index 2c8a917f710241d5a97ed04c0937c884d45dea4b..4b70cc8fb873e992ccdf2a0fb0fe2298fe853f9a 100644 --- "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" +++ "b/docs/java/basis/Java\345\270\270\350\247\201\345\205\263\351\224\256\345\255\227\346\200\273\347\273\223.md" @@ -23,13 +23,15 @@ ## final 关键字 -**final关键字主要用在三个地方:变量、方法、类。** +**final关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:** -1. **对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。** +1. **final修饰的类不能被继承,final类中的所有成员方法都会被隐式的指定为final方法;** -2. **当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。** +2. **final修饰的方法不能被重写;** -3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 +3. **final修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。** + +说明:使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 ## static 关键字 @@ -65,8 +67,6 @@ class Manager { 此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。 - - ## super 关键字 super关键字用于从子类访问父类的变量和方法。 例如: @@ -92,15 +92,13 @@ public class Sub extends Super { **使用 this 和 super 要注意的问题:** -- super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用本类中的其他构造方法时,也要放在首行。 +- 在构造器中使用 `super()` 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。 - this、super不能用在static方法中。 **简单解释一下:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。 - - ## 参考 - https://www.codejava.net/java-core/the-java-language/java-keywords @@ -123,12 +121,10 @@ public class Sub extends Super { HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 - - 调用格式: -- 类名.静态变量名 -- 类名.静态方法名() +- `类名.静态变量名` +- `类名.静态方法名()` 如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。 @@ -138,21 +134,21 @@ public class Sub extends Super { public class StaticBean { String name; - 静态变量 + //静态变量 static int age; public StaticBean(String name) { this.name = name; } - 静态方法 - static void SayHello() { - System.out.println(Hello i am java); + //静态方法 + static void sayHello() { + System.out.println("Hello i am java"); } @Override public String toString() { - return StaticBean{ + - name=' + name + ''' + age + age + - '}'; + return "StaticBean{"+ + "name=" + name + ",age=" + age + + "}"; } } ``` @@ -161,14 +157,14 @@ public class StaticBean { public class StaticDemo { public static void main(String[] args) { - StaticBean staticBean = new StaticBean(1); - StaticBean staticBean2 = new StaticBean(2); - StaticBean staticBean3 = new StaticBean(3); - StaticBean staticBean4 = new StaticBean(4); + StaticBean staticBean = new StaticBean("1"); + StaticBean staticBean2 = new StaticBean("2"); + StaticBean staticBean3 = new StaticBean("3"); + StaticBean staticBean4 = new StaticBean("4"); StaticBean.age = 33; - StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33} - System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4); - StaticBean.SayHello();Hello i am java + System.out.println(staticBean + " " + staticBean2 + " " + staticBean3 + " " + staticBean4); + //StaticBean{name=1,age=33} StaticBean{name=2,age=33} StaticBean{name=3,age=33} StaticBean{name=4,age=33} + StaticBean.sayHello();//Hello i am java } } @@ -177,7 +173,7 @@ public class StaticDemo { ### 静态代码块 -静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次. +静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块 —> 非静态代码块 —> 构造方法)。 该类不管创建多少对象,静态代码块只执行一次. 静态代码块的格式是 @@ -208,11 +204,11 @@ Example(静态内部类实现单例模式) ```java public class Singleton { - 声明为 private 避免调用默认构造方法创建对象 + //声明为 private 避免调用默认构造方法创建对象 private Singleton() { } - 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问 + // 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问 private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } @@ -236,12 +232,10 @@ public class Singleton { ```java - Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用 - 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可 + //将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用 + //如果只想导入单一某个静态方法,只需要将*换成对应的方法名即可 -import static java.lang.Math.; - - 换成import static java.lang.Math.max;具有一样的效果 +import static java.lang.Math.*;//换成import static java.lang.Math.max;具有一样的效果 public class Demo { public static void main(String[] args) { @@ -270,69 +264,83 @@ class Foo { } public static String method1() { - return An example string that doesn't depend on i (an instance variable); + return "An example string that doesn't depend on i (an instance variable)"; } public int method2() { - return this.i + 1; Depends on i + return this.i + 1; //Depends on i } } ``` -你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:`Foo bar = new Foo(1);bar.method2();` +你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行 +``` java +Foo bar = new Foo(1); +bar.method2(); +``` 总结: - 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 - 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 -### static{}静态代码块与{}非静态代码块(构造代码块) +### `static{}`静态代码块与`{}`非静态代码块(构造代码块) 相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。 -不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。 +不同点: 静态代码块在非静态代码块之前执行(静态代码块 -> 非静态代码块 -> 构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。 + +> 修正 [issue #677](https://github.com/Snailclimb/JavaGuide/issues/677):静态代码块可能在第一次new的时候执行,但不一定只在第一次new的时候执行。比如通过 `Class.forName("ClassDemo")`创建 Class 对象的时候也会执行。 一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的. -Example +Example: ```java public class Test { public Test() { - System.out.print(默认构造方法!--); + System.out.print("默认构造方法!--"); } - 非静态代码块 + //非静态代码块 { - System.out.print(非静态代码块!--); + System.out.print("非静态代码块!--"); } - 静态代码块 + + //静态代码块 static { - System.out.print(静态代码块!--); + System.out.print("静态代码块!--"); } - public static void test() { - System.out.print(静态方法中的内容! --); + private static void test() { + System.out.print("静态方法中的内容! --"); { - System.out.print(静态方法中的代码块!--); + System.out.print("静态方法中的代码块!--"); } } - public static void main(String[] args) { - Test test = new Test(); - Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!-- + public static void main(String[] args) { + Test test = new Test(); + Test.test();//静态代码块!--静态方法中的内容! --静态方法中的代码块!-- } +} +``` + +上述代码输出: + +``` +静态代码块!--非静态代码块!--默认构造方法!--静态方法中的内容! --静态方法中的代码块!-- ``` -当执行 `Test.test();` 时输出: +当只执行 `Test.test();` 时输出: ``` 静态代码块!--静态方法中的内容! --静态方法中的代码块!-- ``` -当执行 `Test test = new Test();` 时输出: +当只执行 `Test test = new Test();` 时输出: ``` 静态代码块!--非静态代码块!--默认构造方法!-- @@ -343,6 +351,6 @@ public class Test { ### 参考 -- httpsblog.csdn.netchen13579867831articledetails78995480 -- httpwww.cnblogs.comchenssyp3388487.html -- httpwww.cnblogs.comQian123p5713440.html +- https://blog.csdn.net/chen13579867831/article/details/78995480 +- https://www.cnblogs.com/chenssy/p/3388487.html +- https://www.cnblogs.com/Qian123/p/5713440.html diff --git "a/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" "b/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" new file mode 100644 index 0000000000000000000000000000000000000000..8323cedc168dbb999fb8a716713834d2e397469f --- /dev/null +++ "b/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" @@ -0,0 +1,420 @@ +> 本文首更于[《从零开始手把手教你实现一个简单的RPC框架》](https://t.zsxq.com/iIUv7Mn) 。 + + + + + +- [1. 代理模式](#1-代理模式) +- [2. 静态代理](#2-静态代理) +- [3. 动态代理](#3-动态代理) + - [3.1. JDK 动态代理机制](#31-jdk-动态代理机制) + - [3.1.1. 介绍](#311-介绍) + - [3.1.2. JDK 动态代理类使用步骤](#312-jdk-动态代理类使用步骤) + - [3.1.3. 代码示例](#313-代码示例) + - [3.2. CGLIB 动态代理机制](#32-cglib-动态代理机制) + - [3.2.1. 介绍](#321-介绍) + - [3.2.2. CGLIB 动态代理类使用步骤](#322-cglib-动态代理类使用步骤) + - [3.2.3. 代码示例](#323-代码示例) + - [3.3. JDK 动态代理和 CGLIB 动态代理对比](#33-jdk-动态代理和-cglib-动态代理对比) +- [4. 静态代理和动态代理的对比](#4-静态代理和动态代理的对比) +- [5. 总结](#5-总结) + + + + +## 1. 代理模式 + +代理模式是一种比较好理解的设计模式。简单来说就是 **我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。** + +**代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。** + +举个例子:你找了小红来帮你问话,小红就可以看作是代理你的代理对象,代理的行为(方法)是问话。 + +![Understanding the Proxy Design Pattern | by Mithun Sasidharan | Medium](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-8/1*DjWCgTFm-xqbhbNQVsaWQw.png) + +

https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a

+ +代理模式有静态代理和动态代理两种实现方式,我们 先来看一下静态代理模式的实现。 + +## 2. 静态代理 + +**静态代理中,我们对目标对象的每个方法的增强都是手动完成的(_后面会具体演示代码_),非常不灵活(_比如接口一旦新增加方法,目标对象和代理对象都要进行修改_)且麻烦(_需要对每个目标类都单独写一个代理类_)。** 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。 + +上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, **静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。** + +静态代理实现步骤: + +1. 定义一个接口及其实现类; +2. 创建一个代理类同样实现这个接口 +3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。 + +下面通过代码展示! + +**1.定义发送短信的接口** + +```java +public interface SmsService { + String send(String message); +} +``` + +**2.实现发送短信的接口** + +```java +public class SmsServiceImpl implements SmsService { + public String send(String message) { + System.out.println("send message:" + message); + return message; + } +} +``` + +**3.创建代理类并同样实现发送短信的接口** + +```java +public class SmsProxy implements SmsService { + + private final SmsService smsService; + + public SmsProxy(SmsService smsService) { + this.smsService = smsService; + } + + @Override + public String send(String message) { + //调用方法之前,我们可以添加自己的操作 + System.out.println("before method send()"); + smsService.send(message); + //调用方法之后,我们同样可以添加自己的操作 + System.out.println("after method send()"); + return null; + } +} +``` + +**4.实际使用** + +```java +public class Main { + public static void main(String[] args) { + SmsService smsService = new SmsServiceImpl(); + SmsProxy smsProxy = new SmsProxy(smsService); + smsProxy.send("java"); + } +} +``` + +运行上述代码之后,控制台打印出: + +```bash +before method send() +send message:java +after method send() +``` + +可以输出结果看出,我们已经增加了 `SmsServiceImpl` 的`send()`方法。 + +## 3. 动态代理 + +相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( _CGLIB 动态代理机制_)。 + +**从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。** + +说到动态代理,Spring AOP、RPC 框架应该是两个不得不的提的,它们的实现都依赖了动态代理。 + +**动态代理在我们日常开发中使用的相对较小,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。** + +就 Java 来说,动态代理的实现方式有很多种,比如 **JDK 动态代理**、**CGLIB 动态代理**等等。 + +[guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 使用的是 JDK 动态代理,我们先来看看 JDK 动态代理的使用。 + +另外,虽然 [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 没有用到 **CGLIB 动态代理 ,我们这里还是简单介绍一下其使用以及和**JDK 动态代理的对比。 + +### 3.1. JDK 动态代理机制 + +#### 3.1.1. 介绍 + +**在 Java 动态代理机制中 `InvocationHandler` 接口和 `Proxy` 类是核心。** + +`Proxy` 类中使用频率最高的方法是:`newProxyInstance()` ,这个方法主要用来生成一个代理对象。 + +```java + public static Object newProxyInstance(ClassLoader loader, + Class[] interfaces, + InvocationHandler h) + throws IllegalArgumentException + { + ...... + } +``` + +这个方法一共有 3 个参数: + +1. **loader** :类加载器,用于加载代理对象。 +2. **interfaces** : 被代理类实现的一些接口; +3. **h** : 实现了 `InvocationHandler` 接口的对象; + +要实现动态代理的话,还必须需要实现`InvocationHandler` 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现`InvocationHandler` 接口类的 `invoke` 方法来调用。 + +```java +public interface InvocationHandler { + + /** + * 当你使用代理对象调用方法的时候实际会调用到这个方法 + */ + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable; +} +``` + +`invoke()` 方法有下面三个参数: + +1. **proxy** :动态生成的代理类 +2. **method** : 与代理类对象调用的方法相对应 +3. **args** : 当前 method 方法的参数 + +也就是说:**你通过`Proxy` 类的 `newProxyInstance()` 创建的代理对象在调用方法的时候,实际会调用到实现`InvocationHandler` 接口的类的 `invoke()`方法。** 你可以在 `invoke()` 方法中自定义处理逻辑,比如在方法执行前后做什么事情。 + +#### 3.1.2. JDK 动态代理类使用步骤 + +1. 定义一个接口及其实现类; +2. 自定义 `InvocationHandler` 并重写`invoke`方法,在 `invoke` 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; +3. 通过 `Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)` 方法创建代理对象; + +#### 3.1.3. 代码示例 + +这样说可能会有点空洞和难以理解,我上个例子,大家感受一下吧! + +**1.定义发送短信的接口** + +```java +public interface SmsService { + String send(String message); +} +``` + +**2.实现发送短信的接口** + +```java +public class SmsServiceImpl implements SmsService { + public String send(String message) { + System.out.println("send message:" + message); + return message; + } +} +``` + +**3.定义一个 JDK 动态代理类** + +```java +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author shuang.kou + * @createTime 2020年05月11日 11:23:00 + */ +public class DebugInvocationHandler implements InvocationHandler { + /** + * 代理类中的真实对象 + */ + private final Object target; + + public DebugInvocationHandler(Object target) { + this.target = target; + } + + + public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { + //调用方法之前,我们可以添加自己的操作 + System.out.println("before method " + method.getName()); + Object result = method.invoke(target, args); + //调用方法之后,我们同样可以添加自己的操作 + System.out.println("after method " + method.getName()); + return result; + } +} + +``` + +`invoke()` 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 `invoke()` 方法,然后 `invoke()` 方法代替我们去调用了被代理对象的原生方法。 + +**4.获取代理对象的工厂类** + +```java +public class JdkProxyFactory { + public static Object getProxy(Object target) { + return Proxy.newProxyInstance( + target.getClass().getClassLoader(), // 目标类的类加载 + target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个 + new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler + ); + } +} +``` + +`getProxy()` :主要通过`Proxy.newProxyInstance()`方法获取某个类的代理对象 + +**5.实际使用** + +```java +SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl()); +smsService.send("java"); +``` + +运行上述代码之后,控制台打印出: + +``` +before method send +send message:java +after method send +``` + +### 3.2. CGLIB 动态代理机制 + +#### 3.2.1. 介绍 + +**JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。** + +**为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。** + +[CGLIB](https://github.com/cglib/cglib)(_Code Generation Library_)是一个基于[ASM](http://www.baeldung.com/java-asm)的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了[CGLIB](https://github.com/cglib/cglib), 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。 + +**在 CGLIB 动态代理机制中 `MethodInterceptor` 接口和 `Enhancer` 类是核心。** + +你需要自定义 `MethodInterceptor` 并重写 `intercept` 方法,`intercept` 用于拦截增强被代理类的方法。 + +```java +public interface MethodInterceptor +extends Callback{ + // 拦截被代理类中的方法 + public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, + MethodProxy proxy) throws Throwable; +} + +``` + +1. **obj** :被代理的对象(需要增强的对象) +2. **method** :被拦截的方法(需要增强的方法) +3. **args** :方法入参 +4. **methodProxy** :用于调用原始方法 + +你可以通过 `Enhancer`类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 `MethodInterceptor` 中的 `intercept` 方法。 + +#### 3.2.2. CGLIB 动态代理类使用步骤 + +1. 定义一个类; +2. 自定义 `MethodInterceptor` 并重写 `intercept` 方法,`intercept` 用于拦截增强被代理类的方法,和 JDK 动态代理中的 `invoke` 方法类似; +3. 通过 `Enhancer` 类的 `create()`创建代理类; + +#### 3.2.3. 代码示例 + +不同于 JDK 动态代理不需要额外的依赖。[CGLIB](https://github.com/cglib/cglib)(_Code Generation Library_) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。 + +```xml + + cglib + cglib + 3.3.0 + +``` + +**1.实现一个使用阿里云发送短信的类** + +```java +package github.javaguide.dynamicProxy.cglibDynamicProxy; + +public class AliSmsService { + public String send(String message) { + System.out.println("send message:" + message); + return message; + } +} +``` + +**2.自定义 `MethodInterceptor`(方法拦截器)** + +```java +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import java.lang.reflect.Method; + +/** + * 自定义MethodInterceptor + */ +public class DebugMethodInterceptor implements MethodInterceptor { + + + /** + * @param o 被代理的对象(需要增强的对象) + * @param method 被拦截的方法(需要增强的方法) + * @param args 方法入参 + * @param methodProxy 用于调用原始方法 + */ + @Override + public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + //调用方法之前,我们可以添加自己的操作 + System.out.println("before method " + method.getName()); + Object object = methodProxy.invokeSuper(o, args); + //调用方法之后,我们同样可以添加自己的操作 + System.out.println("after method " + method.getName()); + return object; + } + +} +``` + +**3.获取代理类** + +```java +import net.sf.cglib.proxy.Enhancer; + +public class CglibProxyFactory { + + public static Object getProxy(Class clazz) { + // 创建动态代理增强类 + Enhancer enhancer = new Enhancer(); + // 设置类加载器 + enhancer.setClassLoader(clazz.getClassLoader()); + // 设置被代理类 + enhancer.setSuperclass(clazz); + // 设置方法拦截器 + enhancer.setCallback(new DebugMethodInterceptor()); + // 创建代理类 + return enhancer.create(); + } +} +``` + +**4.实际使用** + +```java +AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class); +aliSmsService.send("java"); +``` + +运行上述代码之后,控制台打印出: + +```bash +before method send +send message:java +after method send +``` + +### 3.3. JDK 动态代理和 CGLIB 动态代理对比 + +1. **JDK 动态代理只能只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。** 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。 +2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。 + +## 4. 静态代理和动态代理的对比 + +1. **灵活性** :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的! +2. **JVM 层面** :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。 + +## 5. 总结 + +这篇文章中主要介绍了代理模式的两种实现:静态代理以及动态代理。涵盖了静态代理和动态代理实战、静态代理和动态代理的区别、JDK 动态代理和 Cglib 动态代理区别等内容。 + +文中涉及到的所有源码,你可以在这里找到:[https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy](https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy) 。 diff --git "a/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266.md" "b/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..0e2389b59294d6b8704b85c6537166e83b7cf770 --- /dev/null +++ "b/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266.md" @@ -0,0 +1,176 @@ +## 何为反射? + +如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。 + +反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。 + +通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。 + +## 反射的应用场景了解么? + +像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。 + +但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。 + +**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。** + +比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。 + +```java +public class DebugInvocationHandler implements InvocationHandler { + /** + * 代理类中的真实对象 + */ + private final Object target; + + public DebugInvocationHandler(Object target) { + this.target = target; + } + + + public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { + System.out.println("before method " + method.getName()); + Object result = method.invoke(target, args); + System.out.println("after method " + method.getName()); + return result; + } +} + +``` + +另外,像 Java 中的一大利器 **注解** 的实现也用到了反射。 + +为什么你使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢? + +这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。 + +## 谈谈反射机制的优缺点 + +**优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利 + +**缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。相关阅读:[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow) + +## 反射实战 + +### 获取 Class 对象的四种方式 + +如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象: + +**1.知道具体类的情况下可以使用:** + +```java +Class alunbarClass = TargetObject.class; +``` + +但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化 + +**2.通过 `Class.forName()`传入类的路径获取:** + +```java +Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject"); +``` + +**3.通过对象实例`instance.getClass()`获取:** + +```java +TargetObject o = new TargetObject(); +Class alunbarClass2 = o.getClass(); +``` + +**4.通过类加载器`xxxClassLoader.loadClass()`传入类路径获取:** + +```java +class clazz = ClassLoader.LoadClass("cn.javaguide.TargetObject"); +``` + +通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行 + +### 反射的一些基本操作 + +**简单用代码演示一下反射的一些操作!** + +1.创建一个我们要使用反射操作的类 `TargetObject`。 + +```java +package cn.javaguide; + +public class TargetObject { + private String value; + + public TargetObject() { + value = "JavaGuide"; + } + + public void publicMethod(String s) { + System.out.println("I love " + s); + } + + private void privateMethod() { + System.out.println("value is " + value); + } +} +``` + +2.使用反射操作这个类的方法以及参数 + +```java +package cn.javaguide; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class Main { + public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException { + /** + * 获取TargetObject类的Class对象并且创建TargetObject类实例 + */ + Class tagetClass = Class.forName("cn.javaguide.TargetObject"); + TargetObject targetObject = (TargetObject) tagetClass.newInstance(); + /** + * 获取所有类中所有定义的方法 + */ + Method[] methods = tagetClass.getDeclaredMethods(); + for (Method method : methods) { + System.out.println(method.getName()); + } + /** + * 获取指定方法并调用 + */ + Method publicMethod = tagetClass.getDeclaredMethod("publicMethod", + String.class); + + publicMethod.invoke(targetObject, "JavaGuide"); + /** + * 获取指定参数并对参数进行修改 + */ + Field field = tagetClass.getDeclaredField("value"); + //为了对类中的参数进行修改我们取消安全检查 + field.setAccessible(true); + field.set(targetObject, "JavaGuide"); + /** + * 调用 private 方法 + */ + Method privateMethod = tagetClass.getDeclaredMethod("privateMethod"); + //为了调用private方法我们取消安全检查 + privateMethod.setAccessible(true); + privateMethod.invoke(targetObject); + } +} + +``` + +输出内容: + +``` +publicMethod +privateMethod +I love JavaGuide +value is JavaGuide +``` + +**注意** : 有读者提到上面代码运行会抛出 `ClassNotFoundException` 异常,具体原因是你没有下面把这段代码的包名替换成自己创建的 `TargetObject` 所在的包 。 + +```java +Class tagetClass = Class.forName("cn.javaguide.TargetObject"); +``` \ No newline at end of file diff --git "a/docs/java/basis/\347\224\250\345\245\275Java\344\270\255\347\232\204\346\236\232\344\270\276\347\234\237\347\232\204\346\262\241\346\234\211\351\202\243\344\271\210\347\256\200\345\215\225.md" "b/docs/java/basis/\347\224\250\345\245\275Java\344\270\255\347\232\204\346\236\232\344\270\276\347\234\237\347\232\204\346\262\241\346\234\211\351\202\243\344\271\210\347\256\200\345\215\225.md" new file mode 100644 index 0000000000000000000000000000000000000000..23e47ef6b8a4fe0247ebc81db6452e6ff7ee4de7 --- /dev/null +++ "b/docs/java/basis/\347\224\250\345\245\275Java\344\270\255\347\232\204\346\236\232\344\270\276\347\234\237\347\232\204\346\262\241\346\234\211\351\202\243\344\271\210\347\256\200\345\215\225.md" @@ -0,0 +1,557 @@ +> 最近重看 Java 枚举,看到这篇觉得还不错的文章,于是简单翻译和完善了一些内容,分享给大家,希望你们也能有所收获。另外,不要忘了文末还有补充哦! +> +> ps: 这里发一篇枚举的文章,也是因为后面要发一篇非常实用的关于 SpringBoot 全局异常处理的比较好的实践,里面就用到了枚举。 +> +> 这篇文章由 JavaGuide 翻译,公众号: JavaGuide,原文地址:https://www.baeldung.com/a-guide-to-java-enums 。 +> +> 转载请注明上面这段文字。 + +## 1.概览 + +在本文中,我们将看到什么是 Java 枚举,它们解决了哪些问题以及如何在实践中使用 Java 枚举实现一些设计模式。 + +enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承java.lang.Enum类,更多内容可以自行查看其[官方文档](https://docs.oracle.com/javase/6/docs/api/java/lang/Enum.html)。 + +枚举在很多时候会和常量拿来对比,可能因为本身我们大量实际使用枚举的地方就是为了替代常量。那么这种方式由什么优势呢? + +**以这种方式定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为。** + +下面示例定义一个简单的枚举类型 pizza 订单的状态,共有三种 ORDERED, READY, DELIVERED状态: + +```java +package shuang.kou.enumdemo.enumtest; + +public enum PizzaStatus { + ORDERED, + READY, + DELIVERED; +} +``` + +**简单来说,我们通过上面的代码避免了定义常量,我们将所有和 pizza 订单的状态的常量都统一放到了一个枚举类型里面。** + +```java +System.out.println(PizzaStatus.ORDERED.name());//ORDERED +System.out.println(PizzaStatus.ORDERED);//ORDERED +System.out.println(PizzaStatus.ORDERED.name().getClass());//class java.lang.String +System.out.println(PizzaStatus.ORDERED.getClass());//class shuang.kou.enumdemo.enumtest.PizzaStatus +``` + +## 2.自定义枚举方法 + +现在我们对枚举是什么以及如何使用它们有了基本的了解,让我们通过在枚举上定义一些额外的API方法,将上一个示例提升到一个新的水平: + +```java +public class Pizza { + private PizzaStatus status; + public enum PizzaStatus { + ORDERED, + READY, + DELIVERED; + } + + public boolean isDeliverable() { + return getStatus() == PizzaStatus.READY; + } + + // Methods that set and get the status variable. +} +``` + +## 3.使用 == 比较枚举类型 + +由于枚举类型确保JVM中仅存在一个常量实例,因此我们可以安全地使用 `==` 运算符比较两个变量,如上例所示;此外,`==` 运算符可提供编译时和运行时的安全性。 + +首先,让我们看一下以下代码段中的运行时安全性,其中 `==` 运算符用于比较状态,并且如果两个值均为null 都不会引发 NullPointerException。相反,如果使用equals方法,将抛出 NullPointerException: + +```java +Pizza.PizzaStatus pizza = null; +System.out.println(pizza.equals(Pizza.PizzaStatus.DELIVERED));//空指针异常 +System.out.println(pizza == Pizza.PizzaStatus.DELIVERED);//正常运行 +``` + +对于编译时安全性,我们看另一个示例,两个不同枚举类型进行比较: + +```java +if (Pizza.PizzaStatus.DELIVERED.equals(TestColor.GREEN)); // 编译正常 +if (Pizza.PizzaStatus.DELIVERED == TestColor.GREEN); // 编译失败,类型不匹配 +``` + +## 4.在 switch 语句中使用枚举类型 + +```java +public int getDeliveryTimeInDays() { + switch (status) { + case ORDERED: + return 5; + case READY: + return 2; + case DELIVERED: + return 0; + } + return 0; +} +``` + +## 5.枚举类型的属性,方法和构造函数 + +> 文末有我(JavaGuide)的补充。 + +你可以通过在枚举类型中定义属性,方法和构造函数让它变得更加强大。 + +下面,让我们扩展上面的示例,实现从比萨的一个阶段到另一个阶段的过渡,并了解如何摆脱之前使用的if语句和switch语句: + +```java +public class Pizza { + + private PizzaStatus status; + public enum PizzaStatus { + ORDERED (5){ + @Override + public boolean isOrdered() { + return true; + } + }, + READY (2){ + @Override + public boolean isReady() { + return true; + } + }, + DELIVERED (0){ + @Override + public boolean isDelivered() { + return true; + } + }; + + private int timeToDelivery; + + public boolean isOrdered() {return false;} + + public boolean isReady() {return false;} + + public boolean isDelivered(){return false;} + + public int getTimeToDelivery() { + return timeToDelivery; + } + + PizzaStatus (int timeToDelivery) { + this.timeToDelivery = timeToDelivery; + } + } + + public boolean isDeliverable() { + return this.status.isReady(); + } + + public void printTimeToDeliver() { + System.out.println("Time to delivery is " + + this.getStatus().getTimeToDelivery()); + } + + // Methods that set and get the status variable. +} +``` + +下面这段代码展示它是如何 work 的: + +```java +@Test +public void givenPizaOrder_whenReady_thenDeliverable() { + Pizza testPz = new Pizza(); + testPz.setStatus(Pizza.PizzaStatus.READY); + assertTrue(testPz.isDeliverable()); +} +``` + +## 6.EnumSet and EnumMap + +### 6.1. EnumSet + +`EnumSet` 是一种专门为枚举类型所设计的 `Set` 类型。 + +与`HashSet`相比,由于使用了内部位向量表示,因此它是特定 `Enum` 常量集的非常有效且紧凑的表示形式。 + +它提供了类型安全的替代方法,以替代传统的基于int的“位标志”,使我们能够编写更易读和易于维护的简洁代码。 + +`EnumSet` 是抽象类,其有两个实现:`RegularEnumSet` 、`JumboEnumSet`,选择哪一个取决于实例化时枚举中常量的数量。 + +在很多场景中的枚举常量集合操作(如:取子集、增加、删除、`containsAll`和`removeAll`批操作)使用`EnumSet`非常合适;如果需要迭代所有可能的常量则使用`Enum.values()`。 + +```java +public class Pizza { + + private static EnumSet undeliveredPizzaStatuses = + EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY); + + private PizzaStatus status; + + public enum PizzaStatus { + ... + } + + public boolean isDeliverable() { + return this.status.isReady(); + } + + public void printTimeToDeliver() { + System.out.println("Time to delivery is " + + this.getStatus().getTimeToDelivery() + " days"); + } + + public static List getAllUndeliveredPizzas(List input) { + return input.stream().filter( + (s) -> undeliveredPizzaStatuses.contains(s.getStatus())) + .collect(Collectors.toList()); + } + + public void deliver() { + if (isDeliverable()) { + PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy() + .deliver(this); + this.setStatus(PizzaStatus.DELIVERED); + } + } + + // Methods that set and get the status variable. +} +``` + + 下面的测试演示了展示了 `EnumSet` 在某些场景下的强大功能: + +```java +@Test +public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() { + List pzList = new ArrayList<>(); + Pizza pz1 = new Pizza(); + pz1.setStatus(Pizza.PizzaStatus.DELIVERED); + + Pizza pz2 = new Pizza(); + pz2.setStatus(Pizza.PizzaStatus.ORDERED); + + Pizza pz3 = new Pizza(); + pz3.setStatus(Pizza.PizzaStatus.ORDERED); + + Pizza pz4 = new Pizza(); + pz4.setStatus(Pizza.PizzaStatus.READY); + + pzList.add(pz1); + pzList.add(pz2); + pzList.add(pz3); + pzList.add(pz4); + + List undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList); + assertTrue(undeliveredPzs.size() == 3); +} +``` + +### 6.2. EnumMap + +`EnumMap`是一个专门化的映射实现,用于将枚举常量用作键。与对应的 `HashMap` 相比,它是一个高效紧凑的实现,并且在内部表示为一个数组: + +```java +EnumMap map; +``` + +让我们快速看一个真实的示例,该示例演示如何在实践中使用它: + +```java +Iterator iterator = pizzaList.iterator(); +while (iterator.hasNext()) { + Pizza pz = iterator.next(); + PizzaStatus status = pz.getStatus(); + if (pzByStatus.containsKey(status)) { + pzByStatus.get(status).add(pz); + } else { + List newPzList = new ArrayList<>(); + newPzList.add(pz); + pzByStatus.put(status, newPzList); + } +} +``` + + 下面的测试演示了展示了 `EnumMap` 在某些场景下的强大功能: + +```java +@Test +public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() { + List pzList = new ArrayList<>(); + Pizza pz1 = new Pizza(); + pz1.setStatus(Pizza.PizzaStatus.DELIVERED); + + Pizza pz2 = new Pizza(); + pz2.setStatus(Pizza.PizzaStatus.ORDERED); + + Pizza pz3 = new Pizza(); + pz3.setStatus(Pizza.PizzaStatus.ORDERED); + + Pizza pz4 = new Pizza(); + pz4.setStatus(Pizza.PizzaStatus.READY); + + pzList.add(pz1); + pzList.add(pz2); + pzList.add(pz3); + pzList.add(pz4); + + EnumMap> map = Pizza.groupPizzaByStatus(pzList); + assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1); + assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2); + assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1); +} +``` + +## 7. 通过枚举实现一些设计模式 + +### 7.1 单例模式 + +通常,使用类实现 Singleton 模式并非易事,枚举提供了一种实现单例的简便方法。 + +《Effective Java 》和《Java与模式》都非常推荐这种方式,使用这种方式方式实现枚举可以有什么好处呢? + +《Effective Java》 + +> 这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现 Singleton的最佳方法。 —-《Effective Java 中文版 第二版》 + +《Java与模式》 + +> 《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。 + +下面的代码段显示了如何使用枚举实现单例模式: + +```java +public enum PizzaDeliverySystemConfiguration { + INSTANCE; + PizzaDeliverySystemConfiguration() { + // Initialization configuration which involves + // overriding defaults like delivery strategy + } + + private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL; + + public static PizzaDeliverySystemConfiguration getInstance() { + return INSTANCE; + } + + public PizzaDeliveryStrategy getDeliveryStrategy() { + return deliveryStrategy; + } +} +``` + +如何使用呢?请看下面的代码: + +```java +PizzaDeliveryStrategy deliveryStrategy = PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy(); +``` + +通过 `PizzaDeliverySystemConfiguration.getInstance()` 获取的就是单例的 `PizzaDeliverySystemConfiguration` + +### 7.2 策略模式 + +通常,策略模式由不同类实现同一个接口来实现的。 + + 这也就意味着添加新策略意味着添加新的实现类。使用枚举,可以轻松完成此任务,添加新的实现意味着只定义具有某个实现的另一个实例。 + +下面的代码段显示了如何使用枚举实现策略模式: + +```java +public enum PizzaDeliveryStrategy { + EXPRESS { + @Override + public void deliver(Pizza pz) { + System.out.println("Pizza will be delivered in express mode"); + } + }, + NORMAL { + @Override + public void deliver(Pizza pz) { + System.out.println("Pizza will be delivered in normal mode"); + } + }; + + public abstract void deliver(Pizza pz); +} +``` + +给 `Pizza `增加下面的方法: + +```java +public void deliver() { + if (isDeliverable()) { + PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy() + .deliver(this); + this.setStatus(PizzaStatus.DELIVERED); + } +} +``` + +如何使用呢?请看下面的代码: + +```java +@Test +public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() { + Pizza pz = new Pizza(); + pz.setStatus(Pizza.PizzaStatus.READY); + pz.deliver(); + assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED); +} +``` + +## 8. Java 8 与枚举 + +Pizza 类可以用Java 8重写,您可以看到方法 lambda 和Stream API如何使 `getAllUndeliveredPizzas()`和`groupPizzaByStatus()`方法变得如此简洁: + +`getAllUndeliveredPizzas()`: + +```java +public static List getAllUndeliveredPizzas(List input) { + return input.stream().filter( + (s) -> !deliveredPizzaStatuses.contains(s.getStatus())) + .collect(Collectors.toList()); +} +``` + +`groupPizzaByStatus()` : + +```java +public static EnumMap> + groupPizzaByStatus(List pzList) { + EnumMap> map = pzList.stream().collect( + Collectors.groupingBy(Pizza::getStatus, + () -> new EnumMap<>(PizzaStatus.class), Collectors.toList())); + return map; +} +``` + +## 9. Enum 类型的 JSON 表现形式 + +使用Jackson库,可以将枚举类型的JSON表示为POJO。下面的代码段显示了可以用于同一目的的Jackson批注: + +```java +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum PizzaStatus { + ORDERED (5){ + @Override + public boolean isOrdered() { + return true; + } + }, + READY (2){ + @Override + public boolean isReady() { + return true; + } + }, + DELIVERED (0){ + @Override + public boolean isDelivered() { + return true; + } + }; + + private int timeToDelivery; + + public boolean isOrdered() {return false;} + + public boolean isReady() {return false;} + + public boolean isDelivered(){return false;} + + @JsonProperty("timeToDelivery") + public int getTimeToDelivery() { + return timeToDelivery; + } + + private PizzaStatus (int timeToDelivery) { + this.timeToDelivery = timeToDelivery; + } +} +``` + +我们可以按如下方式使用 `Pizza` 和 `PizzaStatus`: + +```java +Pizza pz = new Pizza(); +pz.setStatus(Pizza.PizzaStatus.READY); +System.out.println(Pizza.getJsonString(pz)); +``` + +生成 Pizza 状态以以下JSON展示: + +```json +{ + "status" : { + "timeToDelivery" : 2, + "ready" : true, + "ordered" : false, + "delivered" : false + }, + "deliverable" : true +} +``` + +有关枚举类型的JSON序列化/反序列化(包括自定义)的更多信息,请参阅[Jackson-将枚举序列化为JSON对象。](https://www.baeldung.com/jackson-serialize-enums) + +## 10.总结 + +本文我们讨论了Java枚举类型,从基础知识到高级应用以及实际应用场景,让我们感受到枚举的强大功能。 + +## 11. 补充 + +我们在上面讲到了,我们可以通过在枚举类型中定义属性,方法和构造函数让它变得更加强大。 + +下面我通过一个实际的例子展示一下,当我们调用短信验证码的时候可能有几种不同的用途,我们在下面这样定义: + +```java + +public enum PinType { + + REGISTER(100000, "注册使用"), + FORGET_PASSWORD(100001, "忘记密码使用"), + UPDATE_PHONE_NUMBER(100002, "更新手机号码使用"); + + private final int code; + private final String message; + + PinType(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + + @Override + public String toString() { + return "PinType{" + + "code=" + code + + ", message='" + message + '\'' + + '}'; + } +} +``` + +实际使用: + + ```java +System.out.println(PinType.FORGET_PASSWORD.getCode()); +System.out.println(PinType.FORGET_PASSWORD.getMessage()); +System.out.println(PinType.FORGET_PASSWORD.toString()); + ``` + +Output: + +```java +100001 +忘记密码使用 +PinType{code=100001, message='忘记密码使用'} +``` + +这样的话,在实际使用起来就会非常灵活方便! \ No newline at end of file diff --git "a/Java\347\233\270\345\205\263/ArrayList.md" "b/docs/java/collection/ArrayList\346\272\220\347\240\201+\346\211\251\345\256\271\346\234\272\345\210\266\345\210\206\346\236\220.md" similarity index 54% rename from "Java\347\233\270\345\205\263/ArrayList.md" rename to "docs/java/collection/ArrayList\346\272\220\347\240\201+\346\211\251\345\256\271\346\234\272\345\210\266\345\210\206\346\236\220.md" index c3e8dd47896f8ec31b3606fa67c7a52a0818895e..1a04914fcda9831798a20e7e5023fd1e03144e1a 100644 --- "a/Java\347\233\270\345\205\263/ArrayList.md" +++ "b/docs/java/collection/ArrayList\346\272\220\347\240\201+\346\211\251\345\256\271\346\234\272\345\210\266\345\210\206\346\236\220.md" @@ -1,34 +1,35 @@ - +## 1. ArrayList 简介 -- [ArrayList简介](#arraylist简介) -- [ArrayList核心源码](#arraylist核心源码) -- [ArrayList源码分析](#arraylist源码分析) - - [System.arraycopy\(\)和Arrays.copyOf\(\)方法](#systemarraycopy和arrayscopyof方法) - - [两者联系与区别](#两者联系与区别) - - [ArrayList核心扩容技术](#arraylist核心扩容技术) - - [内部类](#内部类) -- [ArrayList经典Demo](#arraylist经典demo) +`ArrayList` 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用`ensureCapacity`操作来增加 `ArrayList` 实例的容量。这可以减少递增式再分配的数量。 - +`ArrayList`继承于 **`AbstractList`** ,实现了 **`List`**, **`RandomAccess`**, **`Cloneable`**, **`java.io.Serializable`** 这些接口。 +```java + +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable{ -### ArrayList简介 -  ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用`ensureCapacity`操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。 - - 它继承于 **AbstractList**,实现了 **List**, **RandomAccess**, **Cloneable**, **java.io.Serializable** 这些接口。 - - 在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为**O(n)**,求表长以及增加元素,取第 i 元素的时间复杂度为**O(1)** + } +``` -  ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。 +- `RandomAccess` 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 `ArrayList` 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 +- `ArrayList` 实现了 **`Cloneable` 接口** ,即覆盖了函数`clone()`,能被克隆。 +- `ArrayList` 实现了 `java.io.Serializable `接口,这意味着`ArrayList`支持序列化,能通过序列化去传输。 -  ArrayList 实现了**RandomAccess 接口**, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 +### 1.1. Arraylist 和 Vector 的区别? -  ArrayList 实现了**Cloneable 接口**,即覆盖了函数 clone(),**能被克隆**。 +1. `ArrayList` 是 `List` 的主要实现类,底层使用 `Object[ ]`存储,适用于频繁的查找工作,线程不安全 ; +2. `Vector` 是 `List` 的古老实现类,底层使用 `Object[ ]`存储,线程安全的。 -  ArrayList 实现**java.io.Serializable 接口**,这意味着ArrayList**支持序列化**,**能通过序列化去传输**。 +### 1.2. Arraylist 与 LinkedList 区别? -  和 Vector 不同,**ArrayList 中的操作不是线程安全的**!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。 -### ArrayList核心源码 +1. **是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全; +2. **底层数据结构:** `Arraylist` 底层使用的是 **`Object` 数组**;`LinkedList` 底层使用的是 **双向链表** 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!) +3. **插入和删除是否受元素位置的影响:** ① **`ArrayList` 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e)`方法的时候, `ArrayList` 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element)`)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **`LinkedList` 采用链表存储,所以对于`add(E e)`方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置`i`插入和删除元素的话(`(add(int index, E element)`) 时间复杂度近似为`o(n))`因为需要先移动到指定位置再插入。** +4. **是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index)`方法)。 +5. **内存空间占用:** `ArrayList` 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 `LinkedList` 的空间花费则体现在它的每一个元素都需要消耗比 `ArrayList` 更多的空间(因为要存放直接后继和直接前驱以及数据)。 + +## 2. ArrayList 核心源码解读 ```java package java.util; @@ -68,23 +69,25 @@ public class ArrayList extends AbstractList private int size; /** - * 带初始容量参数的构造函数。(用户自己指定容量) + * 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小) */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { - //创建initialCapacity大小的数组 + //如果传入的参数大于0,创建initialCapacity大小的数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { - //创建空数组 + //如果传入的参数等于0,创建空数组 this.elementData = EMPTY_ELEMENTDATA; } else { + //其他情况,抛出异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** - *默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 + *默认无参构造函数 + *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; @@ -94,22 +97,22 @@ public class ArrayList extends AbstractList * 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。 */ public ArrayList(Collection c) { - // + //将指定集合转换为数组 elementData = c.toArray(); - //如果指定集合元素个数不为0 + //如果elementData数组的长度不为0 if ((size = elementData.length) != 0) { - // c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断, - //这里用到了反射里面的getClass()方法 + // 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断) if (elementData.getClass() != Object[].class) + //将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { - // 用空数组代替 + // 其他情况,用空数组代替 this.elementData = EMPTY_ELEMENTDATA; } } /** - * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。 + * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。 */ public void trimToSize() { modCount++; @@ -127,13 +130,14 @@ public class ArrayList extends AbstractList * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { + //如果是true,minExpand的值为0,如果是false,minExpand的值为10 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; - + //如果最小容量大于已有的最大容量 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } @@ -141,7 +145,7 @@ public class ArrayList extends AbstractList //得到最小扩容量 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - // 获取默认的容量和传入参数的较大值 + // 获取“默认的容量”和“传入参数”两者之间的最大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } @@ -192,7 +196,7 @@ public class ArrayList extends AbstractList } /** - *返回此列表中的元素数。 + *返回此列表中的元素数。 */ public int size() { return size; @@ -210,12 +214,12 @@ public class ArrayList extends AbstractList * 如果此列表包含指定的元素,则返回true 。 */ public boolean contains(Object o) { - //indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 + //indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 return indexOf(o) >= 0; } /** - *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 + *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 */ public int indexOf(Object o) { if (o == null) { @@ -248,7 +252,7 @@ public class ArrayList extends AbstractList } /** - * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。) + * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。) */ public Object clone() { try { @@ -264,7 +268,7 @@ public class ArrayList extends AbstractList } /** - *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 + *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 */ @@ -273,11 +277,11 @@ public class ArrayList extends AbstractList } /** - * 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); - *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 - *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 + * 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); + *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 + *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 *如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。 - *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) + *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) */ @SuppressWarnings("unchecked") public T[] toArray(T[] a) { @@ -308,7 +312,7 @@ public class ArrayList extends AbstractList } /** - * 用指定的元素替换此列表中指定位置的元素。 + * 用指定的元素替换此列表中指定位置的元素。 */ public E set(int index, E element) { //对index进行界限检查 @@ -321,7 +325,7 @@ public class ArrayList extends AbstractList } /** - * 将指定的元素追加到此列表的末尾。 + * 将指定的元素追加到此列表的末尾。 */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! @@ -331,7 +335,7 @@ public class ArrayList extends AbstractList } /** - * 在此列表中的指定位置插入指定的元素。 + * 在此列表中的指定位置插入指定的元素。 *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */ @@ -347,7 +351,7 @@ public class ArrayList extends AbstractList } /** - * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。 + * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。 */ public E remove(int index) { rangeCheck(index); @@ -360,7 +364,7 @@ public class ArrayList extends AbstractList System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work - //从列表中删除的元素 + //从列表中删除的元素 return oldValue; } @@ -399,7 +403,7 @@ public class ArrayList extends AbstractList } /** - * 从列表中删除所有元素。 + * 从列表中删除所有元素。 */ public void clear() { modCount++; @@ -485,7 +489,7 @@ public class ArrayList extends AbstractList } /** - * 从此列表中删除指定集合中包含的所有元素。 + * 从此列表中删除指定集合中包含的所有元素。 */ public boolean removeAll(Collection c) { Objects.requireNonNull(c); @@ -495,7 +499,7 @@ public class ArrayList extends AbstractList /** * 仅保留此列表中包含在指定集合中的元素。 - *换句话说,从此列表中删除其中不包含在指定集合中的所有元素。 + *换句话说,从此列表中删除其中不包含在指定集合中的所有元素。 */ public boolean retainAll(Collection c) { Objects.requireNonNull(c); @@ -505,8 +509,8 @@ public class ArrayList extends AbstractList /** * 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。 - *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 - *返回的列表迭代器是fail-fast 。 + *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 + *返回的列表迭代器是fail-fast 。 */ public ListIterator listIterator(int index) { if (index < 0 || index > size) @@ -515,7 +519,7 @@ public class ArrayList extends AbstractList } /** - *返回列表中的列表迭代器(按适当的顺序)。 + *返回列表中的列表迭代器(按适当的顺序)。 *返回的列表迭代器是fail-fast 。 */ public ListIterator listIterator() { @@ -523,75 +527,103 @@ public class ArrayList extends AbstractList } /** - *以正确的顺序返回该列表中的元素的迭代器。 - *返回的迭代器是fail-fast 。 + *以正确的顺序返回该列表中的元素的迭代器。 + *返回的迭代器是fail-fast 。 */ public Iterator iterator() { return new Itr(); } - + ``` -### ArrayList源码分析 -#### System.arraycopy()和Arrays.copyOf()方法 -  通过上面源码我们发现这两个实现数组复制的方法被广泛使用而且很多地方都特别巧妙。比如下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法让数组自己复制自己实现让index开始之后的所有成员后移一个位置: -```java - /** - * 在此列表中的指定位置插入指定的元素。 - *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; - *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 + +## 3. ArrayList 扩容机制分析 + +### 3.1. 先从 ArrayList 的构造函数说起 + +**(JDK8)ArrayList 有三种方式来初始化,构造方法源码如下:** + +```java + /** + * 默认初始容量大小 */ - public void add(int index, E element) { - rangeCheckForAdd(index); + private static final int DEFAULT_CAPACITY = 10; - ensureCapacityInternal(size + 1); // Increments modCount!! - //arraycopy()方法实现数组自己复制自己 - //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; - System.arraycopy(elementData, index, elementData, index + 1, size - index); - elementData[index] = element; - size++; + + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + *默认构造函数,使用初始容量10构造一个空列表(无参数构造) + */ + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } -``` -又如toArray()方法中用到了copyOf()方法 -```java /** - *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 - *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 - *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 + * 带初始容量参数的构造函数。(用户自己指定容量) */ - public Object[] toArray() { - //elementData:要复制的数组;size:要复制的长度 - return Arrays.copyOf(elementData, size); + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) {//初始容量大于0 + //创建initialCapacity大小的数组 + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) {//初始容量等于0 + //创建空数组 + this.elementData = EMPTY_ELEMENTDATA; + } else {//初始容量小于0,抛出异常 + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } } + + + /** + *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回 + *如果指定的集合为null,throws NullPointerException。 + */ + public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } + ``` -##### 两者联系与区别 -**联系:** -看两者源代码可以发现`copyOf()`内部调用了`System.arraycopy()`方法 -**区别:** -1. arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置 -2. copyOf()是系统自动在内部新建一个数组,并返回该数组。 -#### ArrayList 核心扩容技术 + +细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容! + +> 补充:JDK7 new无参构造的ArrayList对象时,直接创建了长度是10的Object[]数组elementData 。jdk7中的ArrayList的对象的创建**类似于单例的饿汉式**,而jdk8中的ArrayList的对象的创建**类似于单例的懒汉式**。JDK8的内存优化也值得我们在平时开发中学习。 + +### 3.2. 一步一步分析 ArrayList 扩容机制 + +这里以无参构造函数创建的 ArrayList 为例分析 + +#### 3.2.1. 先来看 `add` 方法 + ```java -//下面是ArrayList的扩容机制 -//ArrayList的扩容机制提高了性能,如果每次只扩充一个, -//那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。 /** - * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量 - * @param minCapacity 所需的最小容量 + * 将指定的元素追加到此列表的末尾。 */ - public void ensureCapacity(int minCapacity) { - int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) - // any size if not default element table - ? 0 - // larger than default for default empty table. It's already - // supposed to be at default size. - : DEFAULT_CAPACITY; - - if (minCapacity > minExpand) { - ensureExplicitCapacity(minCapacity); - } + public boolean add(E e) { + //添加元素之前,先调用ensureCapacityInternal方法 + ensureCapacityInternal(size + 1); // Increments modCount!! + //这里看到ArrayList添加元素的实质就相当于为数组赋值 + elementData[size++] = e; + return true; } +``` + +> **注意** :JDK11 移除了 `ensureCapacityInternal()` 和 `ensureExplicitCapacity()` 方法 + +#### 3.2.2. 再来看看 `ensureCapacityInternal()` 方法 + +(JDK7)可以看到 `add` 方法 首先调用了`ensureCapacityInternal(size + 1)` + +```java //得到最小扩容量 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { @@ -601,25 +633,49 @@ public class ArrayList extends AbstractList ensureExplicitCapacity(minCapacity); } - //判断是否需要扩容,上面两个方法都要调用 +``` + +**当 要 add 进第 1 个元素时,minCapacity 为 1,在 Math.max()方法比较后,minCapacity 为 10。** + +> 此处和后续 JDK8 代码格式化略有不同,核心代码基本一样。 + +#### 3.2.3. `ensureExplicitCapacity()` 方法 + +如果调用 `ensureCapacityInternal()` 方法就一定会进入(执行)这个方法,下面我们来研究一下这个方法的源码! + +```java + //判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; - // 如果说minCapacity也就是所需的最小容量大于保存ArrayList数据的数组的长度的话,就需要调用grow(minCapacity)方法扩容。 - //这个minCapacity到底为多少呢?举个例子在添加元素(add)方法中这个minCapacity的大小就为现在数组的长度加1 + // overflow-conscious code if (minCapacity - elementData.length > 0) //调用grow方法进行扩容,调用此方法代表已经开始扩容了 grow(minCapacity); } ``` + +我们来仔细分析一下: + +- 当我们要 add 进第 1 个元素到 ArrayList 时,elementData.length 为 0 (因为还是一个空的 list),因为执行了 `ensureCapacityInternal()` 方法 ,所以 minCapacity 此时为 10。此时,`minCapacity - elementData.length > 0`成立,所以会进入 `grow(minCapacity)` 方法。 +- 当 add 第 2 个元素时,minCapacity 为 2,此时 e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,`minCapacity - elementData.length > 0` 不成立,所以不会进入 (执行)`grow(minCapacity)` 方法。 +- 添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10。 + +直到添加第 11 个元素,minCapacity(为 11)比 elementData.length(为 10)要大。进入 grow 方法进行扩容。 + +#### 3.2.4. `grow()` 方法 + ```java + /** + * 要分配的最大数组大小 + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + /** * ArrayList扩容的核心方法。 */ private void grow(int minCapacity) { - //elementData为保存ArrayList数据的数组 - ///elementData.length求数组长度elementData.size是求数组中的元素个数 // oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; //将oldCapacity 右移一位,其效果相当于oldCapacity /2, @@ -628,110 +684,217 @@ public class ArrayList extends AbstractList //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; - //再检查新容量是否超出了ArrayList所定义的最大容量, - //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, - //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 + // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE, + //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } - ``` -  扩容机制代码已经做了详细的解释。另外值得注意的是大家很容易忽略的一个运算符:**移位运算符** -  **简介**:移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)>>(带符号右移)>>>(无符号右移)。 -  **作用**:**对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源** -  比如这里:int newCapacity = oldCapacity + (oldCapacity >> 1); -右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。 -**另外需要注意的是:** +**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)!** 奇偶不同,比如 :10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数. + +> ">>"(移位运算符):>>1 右移一位相当于除 2,右移 n 位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了 1 位所以相当于 oldCapacity /2。对于大数据的 2 进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源 -1. java 中的**length 属性**是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性. +**我们再来通过例子探究一下`grow()` 方法 :** -2. java 中的**length()方法**是针对字 符串String说的,如果想看这个字符串的长度则用到 length()这个方法. +- 当 add 第 1 个元素时,oldCapacity 为 0,经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 `hugeCapacity` 方法。数组容量为 10,add 方法中 return true,size 增为 1。 +- 当 add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。 +- 以此类推······ -3. .java 中的**size()方法**是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! +**这里补充一点比较重要,但是容易被忽视掉的知识点:** +- java 中的 `length`属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性. +- java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法. +- java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! + +#### 3.2.5. `hugeCapacity()` 方法。 + +从上面 `grow()` 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 -#### 内部类 ```java - (1)private class Itr implements Iterator - (2)private class ListItr extends Itr implements ListIterator - (3)private class SubList extends AbstractList implements RandomAccess - (4)static final class ArrayListSpliterator implements Spliterator + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + //对minCapacity和MAX_ARRAY_SIZE进行比较 + //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小 + //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小 + //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } ``` -  ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**,**next()**,**remove()**等方法;其中的**ListItr**继承**Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**,**nextIndex()**,**previousIndex()**,**previous()**,**set(E e)**,**add(E e)**等方法,所以这也可以看出了 **Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。 -### ArrayList经典Demo + +### 3.3. `System.arraycopy()` 和 `Arrays.copyOf()`方法 + +阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法! + +#### 3.3.1. `System.arraycopy()` 方法 ```java -package list; -import java.util.ArrayList; -import java.util.Iterator; - -public class ArrayListDemo { - - public static void main(String[] srgs){ - ArrayList arrayList = new ArrayList(); - - System.out.printf("Before add:arrayList.size() = %d\n",arrayList.size()); - - arrayList.add(1); - arrayList.add(3); - arrayList.add(5); - arrayList.add(7); - arrayList.add(9); - System.out.printf("After add:arrayList.size() = %d\n",arrayList.size()); - - System.out.println("Printing elements of arrayList"); - // 三种遍历方式打印元素 - // 第一种:通过迭代器遍历 - System.out.print("通过迭代器遍历:"); - Iterator it = arrayList.iterator(); - while(it.hasNext()){ - System.out.print(it.next() + " "); - } - System.out.println(); - - // 第二种:通过索引值遍历 - System.out.print("通过索引值遍历:"); - for(int i = 0; i < arrayList.size(); i++){ - System.out.print(arrayList.get(i) + " "); - } - System.out.println(); - - // 第三种:for循环遍历 - System.out.print("for循环遍历:"); - for(Integer number : arrayList){ - System.out.print(number + " "); - } - - // toArray用法 - // 第一种方式(最常用) - Integer[] integer = arrayList.toArray(new Integer[0]); - - // 第二种方式(容易理解) - Integer[] integer1 = new Integer[arrayList.size()]; - arrayList.toArray(integer1); - - // 抛出异常,java不支持向下转型 - //Integer[] integer2 = new Integer[arrayList.size()]; - //integer2 = arrayList.toArray(); - System.out.println(); - - // 在指定位置添加元素 - arrayList.add(2,2); - // 删除指定位置上的元素 - arrayList.remove(2); - // 删除指定元素 - arrayList.remove((Object)3); - // 判断arrayList是否包含5 - System.out.println("ArrayList contains 5 is: " + arrayList.contains(5)); - - // 清空ArrayList - arrayList.clear(); - // 判断ArrayList是否为空 - System.out.println("ArrayList is empty: " + arrayList.isEmpty()); + /** + * 在此列表中的指定位置插入指定的元素。 + *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; + *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 + */ + public void add(int index, E element) { + rangeCheckForAdd(index); + + ensureCapacityInternal(size + 1); // Increments modCount!! + //arraycopy()方法实现数组自己复制自己 + //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = element; + size++; } +``` + +我们写一个简单的方法测试以下: + +```java +public class ArraycopyTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + int[] a = new int[10]; + a[0] = 0; + a[1] = 1; + a[2] = 2; + a[3] = 3; + System.arraycopy(a, 2, a, 3, 3); + a[2]=99; + for (int i = 0; i < a.length; i++) { + System.out.print(a[i] + " "); + } + } + } ``` +结果: + +``` +0 1 99 2 3 0 0 0 0 0 +``` + +#### 3.3.2. `Arrays.copyOf()`方法 + +```java + /** + 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 + */ + public Object[] toArray() { + //elementData:要复制的数组;size:要复制的长度 + return Arrays.copyOf(elementData, size); + } +``` + +个人觉得使用 `Arrays.copyOf()`方法主要是为了给原有数组扩容,测试代码如下: + +```java +public class ArrayscopyOfTest { + + public static void main(String[] args) { + int[] a = new int[3]; + a[0] = 0; + a[1] = 1; + a[2] = 2; + int[] b = Arrays.copyOf(a, 10); + System.out.println("b.length"+b.length); + } +} +``` + +结果: + +``` +10 +``` + +#### 3.3.3. 两者联系和区别 + +**联系:** + +看两者源代码可以发现 `copyOf()`内部实际调用了 `System.arraycopy()` 方法 + +**区别:** + +`arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。 + +### 3.4. `ensureCapacity`方法 + +ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢? + +```java + /** + 如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。 + * + * @param minCapacity 所需的最小容量 + */ + public void ensureCapacity(int minCapacity) { + int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) + // any size if not default element table + ? 0 + // larger than default for default empty table. It's already + // supposed to be at default size. + : DEFAULT_CAPACITY; + + if (minCapacity > minExpand) { + ensureExplicitCapacity(minCapacity); + } + } + +``` + +**最好在 add 大量元素之前用 `ensureCapacity` 方法,以减少增量重新分配的次数** + +我们通过下面的代码实际测试以下这个方法的效果: + +```java +public class EnsureCapacityTest { + public static void main(String[] args) { + ArrayList list = new ArrayList(); + final int N = 10000000; + long startTime = System.currentTimeMillis(); + for (int i = 0; i < N; i++) { + list.add(i); + } + long endTime = System.currentTimeMillis(); + System.out.println("使用ensureCapacity方法前:"+(endTime - startTime)); + + } +} +``` + +运行结果: + +``` +使用ensureCapacity方法前:2158 +``` + +```java +public class EnsureCapacityTest { + public static void main(String[] args) { + ArrayList list = new ArrayList(); + final int N = 10000000; + list = new ArrayList(); + long startTime1 = System.currentTimeMillis(); + list.ensureCapacity(N); + for (int i = 0; i < N; i++) { + list.add(i); + } + long endTime1 = System.currentTimeMillis(); + System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1)); + } +} +``` + +运行结果: + +``` +使用ensureCapacity方法前:1773 +``` + +通过运行结果,我们可以看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数。 diff --git "a/docs/java/collection/ConcurrentHashMap\346\272\220\347\240\201+\345\272\225\345\261\202\346\225\260\346\215\256\347\273\223\346\236\204\345\210\206\346\236\220.md" "b/docs/java/collection/ConcurrentHashMap\346\272\220\347\240\201+\345\272\225\345\261\202\346\225\260\346\215\256\347\273\223\346\236\204\345\210\206\346\236\220.md" new file mode 100644 index 0000000000000000000000000000000000000000..ebabf4a4b2f97a3496d1fa5c3ff676f83ab0d59e --- /dev/null +++ "b/docs/java/collection/ConcurrentHashMap\346\272\220\347\240\201+\345\272\225\345\261\202\346\225\260\346\215\256\347\273\223\346\236\204\345\210\206\346\236\220.md" @@ -0,0 +1,584 @@ +> 本文来自公众号:末读代码的投稿,原文地址:https://mp.weixin.qq.com/s/AHWzboztt53ZfFZmsSnMSw 。 + +上一篇文章介绍了 HashMap 源码,反响不错,也有很多同学发表了自己的观点,这次又来了,这次是 `ConcurrentHashMap ` 了,作为线程安全的HashMap ,它的使用频率也是很高。那么它的存储结构和实现原理是怎么样的呢? + +## 1. ConcurrentHashMap 1.7 + +### 1. 存储结构 + +![Java 7 ConcurrentHashMap 存储结构](./images/image-20200405151029416.png) + +Java 7 中 ConcurrentHashMap 的存储结构如上图,ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦**初始化就不能改变**,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。 + +### 2. 初始化 + +通过 ConcurrentHashMap 的无参构造探寻 ConcurrentHashMap 的初始化流程。 + +```java + /** + * Creates a new, empty map with a default initial capacity (16), + * load factor (0.75) and concurrencyLevel (16). + */ + public ConcurrentHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } +``` + +无参构造中调用了有参构造,传入了三个参数的默认值,他们的值是。 + +```java + /** + * 默认初始化容量 + */ + static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * 默认负载因子 + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * 默认并发级别 + */ + static final int DEFAULT_CONCURRENCY_LEVEL = 16; +``` + +接着看下这个有参构造函数的内部实现逻辑。 + +```java +@SuppressWarnings("unchecked") +public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) { + // 参数校验 + if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) + throw new IllegalArgumentException(); + // 校验并发级别大小,大于 1<<16,重置为 65536 + if (concurrencyLevel > MAX_SEGMENTS) + concurrencyLevel = MAX_SEGMENTS; + // Find power-of-two sizes best matching arguments + // 2的多少次方 + int sshift = 0; + int ssize = 1; + // 这个循环可以找到 concurrencyLevel 之上最近的 2的次方值 + while (ssize < concurrencyLevel) { + ++sshift; + ssize <<= 1; + } + // 记录段偏移量 + this.segmentShift = 32 - sshift; + // 记录段掩码 + this.segmentMask = ssize - 1; + // 设置容量 + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + // c = 容量 / ssize ,默认 16 / 16 = 1,这里是计算每个 Segment 中的类似于 HashMap 的容量 + int c = initialCapacity / ssize; + if (c * ssize < initialCapacity) + ++c; + int cap = MIN_SEGMENT_TABLE_CAPACITY; + //Segment 中的类似于 HashMap 的容量至少是2或者2的倍数 + while (cap < c) + cap <<= 1; + // create segments and segments[0] + // 创建 Segment 数组,设置 segments[0] + Segment s0 = new Segment(loadFactor, (int)(cap * loadFactor), + (HashEntry[])new HashEntry[cap]); + Segment[] ss = (Segment[])new Segment[ssize]; + UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] + this.segments = ss; +} +``` + +总结一下在 Java 7 中 ConcurrnetHashMap 的初始化逻辑。 + +1. 必要参数校验。 +2. 校验并发级别 concurrencyLevel 大小,如果大于最大值,重置为最大值。无惨构造**默认值是 16.** +3. 寻找并发级别 concurrencyLevel 之上最近的 **2 的幂次方**值,作为初始化容量大小,**默认是 16**。 +4. 记录 segmentShift 偏移量,这个值为【容量 = 2 的N次方】中的 N,在后面 Put 时计算位置时会用到。**默认是 32 - sshift = 28**. +5. 记录 segmentMask,默认是 ssize - 1 = 16 -1 = 15. +6. **初始化 segments[0]**,**默认大小为 2**,**负载因子 0.75**,**扩容阀值是 2*0.75=1.5**,插入第二个值时才会进行扩容。 + +### 3. put + +接着上面的初始化参数继续查看 put 方法源码。 + +```java +/** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * + *

The value can be retrieved by calling the get method + * with a key that is equal to the original key. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with key, or + * null if there was no mapping for key + * @throws NullPointerException if the specified key or value is null + */ +public V put(K key, V value) { + Segment s; + if (value == null) + throw new NullPointerException(); + int hash = hash(key); + // hash 值无符号右移 28位(初始化时获得),然后与 segmentMask=15 做与运算 + // 其实也就是把高4位与segmentMask(1111)做与运算 + int j = (hash >>> segmentShift) & segmentMask; + if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck + (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment + // 如果查找到的 Segment 为空,初始化 + s = ensureSegment(j); + return s.put(key, hash, value, false); +} + +/** + * Returns the segment for the given index, creating it and + * recording in segment table (via CAS) if not already present. + * + * @param k the index + * @return the segment + */ +@SuppressWarnings("unchecked") +private Segment ensureSegment(int k) { + final Segment[] ss = this.segments; + long u = (k << SSHIFT) + SBASE; // raw offset + Segment seg; + // 判断 u 位置的 Segment 是否为null + if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) == null) { + Segment proto = ss[0]; // use segment 0 as prototype + // 获取0号 segment 里的 HashEntry 初始化长度 + int cap = proto.table.length; + // 获取0号 segment 里的 hash 表里的扩容负载因子,所有的 segment 的 loadFactor 是相同的 + float lf = proto.loadFactor; + // 计算扩容阀值 + int threshold = (int)(cap * lf); + // 创建一个 cap 容量的 HashEntry 数组 + HashEntry[] tab = (HashEntry[])new HashEntry[cap]; + if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck + // 再次检查 u 位置的 Segment 是否为null,因为这时可能有其他线程进行了操作 + Segment s = new Segment(lf, threshold, tab); + // 自旋检查 u 位置的 Segment 是否为null + while ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) + == null) { + // 使用CAS 赋值,只会成功一次 + if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) + break; + } + } + } + return seg; +} +``` + +上面的源码分析了 ConcurrentHashMap 在 put 一个数据时的处理流程,下面梳理下具体流程。 + +1. 计算要 put 的 key 的位置,获取指定位置的 Segment。 + +2. 如果指定位置的 Segment 为空,则初始化这个 Segment. + + **初始化 Segment 流程:** + + 1. 检查计算得到的位置的 Segment 是否为null. + 2. 为 null 继续初始化,使用 Segment[0] 的容量和负载因子创建一个 HashEntry 数组。 + 3. 再次检查计算得到的指定位置的 Segment 是否为null. + 4. 使用创建的 HashEntry 数组初始化这个 Segment. + 5. 自旋判断计算得到的指定位置的 Segment 是否为null,使用 CAS 在这个位置赋值为 Segment. + +3. Segment.put 插入 key,value 值。 + +上面探究了获取 Segment 段和初始化 Segment 段的操作。最后一行的 Segment 的 put 方法还没有查看,继续分析。 + +```java +final V put(K key, int hash, V value, boolean onlyIfAbsent) { + // 获取 ReentrantLock 独占锁,获取不到,scanAndLockForPut 获取。 + HashEntry node = tryLock() ? null : scanAndLockForPut(key, hash, value); + V oldValue; + try { + HashEntry[] tab = table; + // 计算要put的数据位置 + int index = (tab.length - 1) & hash; + // CAS 获取 index 坐标的值 + HashEntry first = entryAt(tab, index); + for (HashEntry e = first;;) { + if (e != null) { + // 检查是否 key 已经存在,如果存在,则遍历链表寻找位置,找到后替换 value + K k; + if ((k = e.key) == key || + (e.hash == hash && key.equals(k))) { + oldValue = e.value; + if (!onlyIfAbsent) { + e.value = value; + ++modCount; + } + break; + } + e = e.next; + } + else { + // first 有值没说明 index 位置已经有值了,有冲突,链表头插法。 + if (node != null) + node.setNext(first); + else + node = new HashEntry(hash, key, value, first); + int c = count + 1; + // 容量大于扩容阀值,小于最大容量,进行扩容 + if (c > threshold && tab.length < MAXIMUM_CAPACITY) + rehash(node); + else + // index 位置赋值 node,node 可能是一个元素,也可能是一个链表的表头 + setEntryAt(tab, index, node); + ++modCount; + count = c; + oldValue = null; + break; + } + } + } finally { + unlock(); + } + return oldValue; +} +``` + +由于 Segment 继承了 ReentrantLock,所以 Segment 内部可以很方便的获取锁,put 流程就用到了这个功能。 + +1. tryLock() 获取锁,获取不到使用 **`scanAndLockForPut`** 方法继续获取。 + +2. 计算 put 的数据要放入的 index 位置,然后获取这个位置上的 HashEntry 。 + +3. 遍历 put 新元素,为什么要遍历?因为这里获取的 HashEntry 可能是一个空元素,也可能是链表已存在,所以要区别对待。 + + 如果这个位置上的 **HashEntry 不存在**: + + 1. 如果当前容量大于扩容阀值,小于最大容量,**进行扩容**。 + 2. 直接头插法插入。 + + 如果这个位置上的 **HashEntry 存在**: + + 1. 判断链表当前元素 Key 和 hash 值是否和要 put 的 key 和 hash 值一致。一致则替换值 + 2. 不一致,获取链表下一个节点,直到发现相同进行值替换,或者链表表里完毕没有相同的。 + 1. 如果当前容量大于扩容阀值,小于最大容量,**进行扩容**。 + 2. 直接链表头插法插入。 + +4. 如果要插入的位置之前已经存在,替换后返回旧值,否则返回 null. + +这里面的第一步中的 scanAndLockForPut 操作这里没有介绍,这个方法做的操作就是不断的自旋 `tryLock()` 获取锁。当自旋次数大于指定次数时,使用 `lock()` 阻塞获取锁。在自旋时顺表获取下 hash 位置的 HashEntry。 + +```java +private HashEntry scanAndLockForPut(K key, int hash, V value) { + HashEntry first = entryForHash(this, hash); + HashEntry e = first; + HashEntry node = null; + int retries = -1; // negative while locating node + // 自旋获取锁 + while (!tryLock()) { + HashEntry f; // to recheck first below + if (retries < 0) { + if (e == null) { + if (node == null) // speculatively create node + node = new HashEntry(hash, key, value, null); + retries = 0; + } + else if (key.equals(e.key)) + retries = 0; + else + e = e.next; + } + else if (++retries > MAX_SCAN_RETRIES) { + // 自旋达到指定次数后,阻塞等到只到获取到锁 + lock(); + break; + } + else if ((retries & 1) == 0 && + (f = entryForHash(this, hash)) != first) { + e = first = f; // re-traverse if entry changed + retries = -1; + } + } + return node; +} + +``` + +### 4. 扩容 rehash + +ConcurrentHashMap 的扩容只会扩容到原来的两倍。老数组里的数据移动到新的数组时,位置要么不变,要么变为 index+ oldSize,参数里的 node 会在扩容之后使用链表**头插法**插入到指定位置。 + +```java +private void rehash(HashEntry node) { + HashEntry[] oldTable = table; + // 老容量 + int oldCapacity = oldTable.length; + // 新容量,扩大两倍 + int newCapacity = oldCapacity << 1; + // 新的扩容阀值 + threshold = (int)(newCapacity * loadFactor); + // 创建新的数组 + HashEntry[] newTable = (HashEntry[]) new HashEntry[newCapacity]; + // 新的掩码,默认2扩容后是4,-1是3,二进制就是11。 + int sizeMask = newCapacity - 1; + for (int i = 0; i < oldCapacity ; i++) { + // 遍历老数组 + HashEntry e = oldTable[i]; + if (e != null) { + HashEntry next = e.next; + // 计算新的位置,新的位置只可能是不便或者是老的位置+老的容量。 + int idx = e.hash & sizeMask; + if (next == null) // Single node on list + // 如果当前位置还不是链表,只是一个元素,直接赋值 + newTable[idx] = e; + else { // Reuse consecutive sequence at same slot + // 如果是链表了 + HashEntry lastRun = e; + int lastIdx = idx; + // 新的位置只可能是不便或者是老的位置+老的容量。 + // 遍历结束后,lastRun 后面的元素位置都是相同的 + for (HashEntry last = next; last != null; last = last.next) { + int k = last.hash & sizeMask; + if (k != lastIdx) { + lastIdx = k; + lastRun = last; + } + } + // ,lastRun 后面的元素位置都是相同的,直接作为链表赋值到新位置。 + newTable[lastIdx] = lastRun; + // Clone remaining nodes + for (HashEntry p = e; p != lastRun; p = p.next) { + // 遍历剩余元素,头插法到指定 k 位置。 + V v = p.value; + int h = p.hash; + int k = h & sizeMask; + HashEntry n = newTable[k]; + newTable[k] = new HashEntry(h, p.key, v, n); + } + } + } + } + // 头插法插入新的节点 + int nodeIndex = node.hash & sizeMask; // add the new node + node.setNext(newTable[nodeIndex]); + newTable[nodeIndex] = node; + table = newTable; +} +``` + +有些同学可能会对最后的两个 for 循环有疑惑,这里第一个 for 是为了寻找这样一个节点,这个节点后面的所有 next 节点的新位置都是相同的。然后把这个作为一个链表赋值到新位置。第二个 for 循环是为了把剩余的元素通过头插法插入到指定位置链表。这样实现的原因可能是基于概率统计,有深入研究的同学可以发表下意见。 + +### 5. get + +到这里就很简单了,get 方法只需要两步即可。 + +1. 计算得到 key 的存放位置。 +2. 遍历指定位置查找相同 key 的 value 值。 + +```java +public V get(Object key) { + Segment s; // manually integrate access methods to reduce overhead + HashEntry[] tab; + int h = hash(key); + long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; + // 计算得到 key 的存放位置 + if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && + (tab = s.table) != null) { + for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile + (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); + e != null; e = e.next) { + // 如果是链表,遍历查找到相同 key 的 value。 + K k; + if ((k = e.key) == key || (e.hash == h && key.equals(k))) + return e.value; + } + } + return null; +} +``` + +## 2. ConcurrentHashMap 1.8 + +### 1. 存储结构 + +![Java8 ConcurrentHashMap 存储结构(图片来自 javadoop)](./images/java8_concurrenthashmap.png) + +可以发现 Java8 的 ConcurrentHashMap 相对于 Java7 来说变化比较大,不再是之前的 **Segment 数组 + HashEntry 数组 + 链表**,而是 **Node 数组 + 链表 / 红黑树**。当冲突链表达到一定长度时,链表会转换成红黑树。 + +### 2. 初始化 initTable + +```java +/** + * Initializes table, using the size recorded in sizeCtl. + */ +private final Node[] initTable() { + Node[] tab; int sc; + while ((tab = table) == null || tab.length == 0) { + // 如果 sizeCtl < 0 ,说明另外的线程执行CAS 成功,正在进行初始化。 + if ((sc = sizeCtl) < 0) + // 让出 CPU 使用权 + Thread.yield(); // lost initialization race; just spin + else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if ((tab = table) == null || tab.length == 0) { + int n = (sc > 0) ? sc : DEFAULT_CAPACITY; + @SuppressWarnings("unchecked") + Node[] nt = (Node[])new Node[n]; + table = tab = nt; + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + break; + } + } + return tab; +} +``` + +从源码中可以发现 ConcurrentHashMap 的初始化是通过**自旋和 CAS** 操作完成的。里面需要注意的是变量 `sizeCtl` ,它的值决定着当前的初始化状态。 + +1. -1 说明正在初始化 +2. -N 说明有N-1个线程正在进行扩容 +3. 表示 table 初始化大小,如果 table 没有初始化 +4. 表示 table 容量,如果 table 已经初始化。 + +### 3. put + +直接过一遍 put 源码。 + +```java +public V put(K key, V value) { + return putVal(key, value, false); +} + +/** Implementation for put and putIfAbsent */ +final V putVal(K key, V value, boolean onlyIfAbsent) { + // key 和 value 不能为空 + if (key == null || value == null) throw new NullPointerException(); + int hash = spread(key.hashCode()); + int binCount = 0; + for (Node[] tab = table;;) { + // f = 目标位置元素 + Node f; int n, i, fh;// fh 后面存放目标位置的元素 hash 值 + if (tab == null || (n = tab.length) == 0) + // 数组桶为空,初始化数组桶(自旋+CAS) + tab = initTable(); + else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { + // 桶内为空,CAS 放入,不加锁,成功了就直接 break 跳出 + if (casTabAt(tab, i, null,new Node(hash, key, value, null))) + break; // no lock when adding to empty bin + } + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); + else { + V oldVal = null; + // 使用 synchronized 加锁加入节点 + synchronized (f) { + if (tabAt(tab, i) == f) { + // 说明是链表 + if (fh >= 0) { + binCount = 1; + // 循环加入新的或者覆盖节点 + for (Node e = f;; ++binCount) { + K ek; + if (e.hash == hash && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + oldVal = e.val; + if (!onlyIfAbsent) + e.val = value; + break; + } + Node pred = e; + if ((e = e.next) == null) { + pred.next = new Node(hash, key, + value, null); + break; + } + } + } + else if (f instanceof TreeBin) { + // 红黑树 + Node p; + binCount = 2; + if ((p = ((TreeBin)f).putTreeVal(hash, key, + value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) + p.val = value; + } + } + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) + treeifyBin(tab, i); + if (oldVal != null) + return oldVal; + break; + } + } + } + addCount(1L, binCount); + return null; +} +``` + +1. 根据 key 计算出 hashcode 。 + +2. 判断是否需要进行初始化。 + +3. 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。 + +4. 如果当前位置的 `hashcode == MOVED == -1`,则需要进行扩容。 + +5. 如果都不满足,则利用 synchronized 锁写入数据。 + +6. 如果数量大于 `TREEIFY_THRESHOLD` 则要转换为红黑树。 + +### 4. get + +get 流程比较简单,直接过一遍源码。 + +```java +public V get(Object key) { + Node[] tab; Node e, p; int n, eh; K ek; + // key 所在的 hash 位置 + int h = spread(key.hashCode()); + if ((tab = table) != null && (n = tab.length) > 0 && + (e = tabAt(tab, (n - 1) & h)) != null) { + // 如果指定位置元素存在,头结点hash值相同 + if ((eh = e.hash) == h) { + if ((ek = e.key) == key || (ek != null && key.equals(ek))) + // key hash 值相等,key值相同,直接返回元素 value + return e.val; + } + else if (eh < 0) + // 头结点hash值小于0,说明正在扩容或者是红黑树,find查找 + return (p = e.find(h, key)) != null ? p.val : null; + while ((e = e.next) != null) { + // 是链表,遍历查找 + if (e.hash == h && + ((ek = e.key) == key || (ek != null && key.equals(ek)))) + return e.val; + } + } + return null; +} +``` + +总结一下 get 过程: + +1. 根据 hash 值计算位置。 +2. 查找到指定位置,如果头节点就是要找的,直接返回它的 value. +3. 如果头节点 hash 值小于 0 ,说明正在扩容或者是红黑树,查找之。 +4. 如果是链表,遍历查找之。 + +总结: + +总的来说 ConcurrentHashMap 在 Java8 中相对于 Java7 来说变化还是挺大的, + +## 3. 总结 + +Java7 中 ConcurrentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。 + +Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 **Segment 数组 + HashEntry 数组 + 链表** 进化成了 **Node 数组 + 链表 / 红黑树**,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。 + +有些同学可能对 Synchronized 的性能存在疑问,其实 Synchronized 锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 Synchronized 的**锁升级**。 diff --git "a/Java\347\233\270\345\205\263/HashMap.md" "b/docs/java/collection/HashMap(JDK1.8)\346\272\220\347\240\201+\345\272\225\345\261\202\346\225\260\346\215\256\347\273\223\346\236\204\345\210\206\346\236\220.md" similarity index 72% rename from "Java\347\233\270\345\205\263/HashMap.md" rename to "docs/java/collection/HashMap(JDK1.8)\346\272\220\347\240\201+\345\272\225\345\261\202\346\225\260\346\215\256\347\273\223\346\236\204\345\210\206\346\236\220.md" index 45fad50cdb3617b94f0b0a08a230538bd43cc63f..977fdf78bb144b6a463cb2cd86b6698da1f037e4 100644 --- "a/Java\347\233\270\345\205\263/HashMap.md" +++ "b/docs/java/collection/HashMap(JDK1.8)\346\272\220\347\240\201+\345\272\225\345\261\202\346\225\260\346\215\256\347\273\223\346\236\204\345\210\206\346\236\220.md" @@ -1,45 +1,60 @@ - + + + + - [HashMap 简介](#hashmap-简介) - [底层数据结构分析](#底层数据结构分析) - - [JDK1.8之前](#jdk18之前) - - [JDK1.8之后](#jdk18之后) -- [HashMap源码分析](#hashmap源码分析) + - [JDK1.8 之前](#jdk18-之前) + - [JDK1.8 之后](#jdk18-之后) +- [HashMap 源码分析](#hashmap-源码分析) - [构造方法](#构造方法) - - [put方法](#put方法) - - [get方法](#get方法) - - [resize方法](#resize方法) -- [HashMap常用方法测试](#hashmap常用方法测试) + - [put 方法](#put-方法) + - [get 方法](#get-方法) + - [resize 方法](#resize-方法) +- [HashMap 常用方法测试](#hashmap-常用方法测试) + + - > 感谢 [changfubai](https://github.com/changfubai) 对本文的改进做出的贡献! ## HashMap 简介 -HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。 -JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。 +HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一。 + +JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 + +JDK1.8 之后 HashMap 的组成多了红黑树,在满足下面两个条件之后,会执行链表转红黑树操作,以此来加快搜索速度。 + +- 链表长度大于阈值(默认为 8) +- HashMap 数组长度超过 64 ## 底层数据结构分析 -### JDK1.8之前 -JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** -**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** +### JDK1.8 之前 + +JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。 + +HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。 + +所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。 **JDK 1.8 HashMap 的 hash 方法源码:** -JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 +JDK 1.8 的 hash 方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 - ```java - static final int hash(Object key) { - int h; - // key.hashCode():返回散列值也就是hashcode - // ^ :按位异或 - // >>>:无符号右移,忽略符号位,空位都以0补齐 - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); - } - ``` -对比一下 JDK1.7的 HashMap 的 hash 方法源码. +```java + static final int hash(Object key) { + int h; + // key.hashCode():返回散列值也就是hashcode + // ^ :按位异或 + // >>>:无符号右移,忽略符号位,空位都以0补齐 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } +``` + +对比一下 JDK1.7 的 HashMap 的 hash 方法源码. ```java static int hash(int h) { @@ -56,57 +71,62 @@ static int hash(int h) { 所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 -![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991) +![jdk1.8之前的内部结构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/jdk1.8之前的内部结构.png) + +### JDK1.8 之后 -### JDK1.8之后 -相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 +相比于之前的版本,JDK1.8 以后在解决哈希冲突时有了较大的变化。 -![JDK1.8之后的HashMap底层数据结构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/67233764.jpg) +当链表长度大于阈值(默认为 8)时,会首先调用 `treeifyBin()`方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 `resize()` 方法对数组扩容。相关源码这里就不贴了,重点关注 `treeifyBin()`方法即可! + +![](https://oscimg.oschina.net/oscnet/up-bba283228693dae74e78da1ef7a9a04c684.png) **类的属性:** + ```java public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { // 序列号 - private static final long serialVersionUID = 362498820763181265L; + private static final long serialVersionUID = 362498820763181265L; // 默认的初始容量是16 - static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 - static final int MAXIMUM_CAPACITY = 1 << 30; + static final int MAXIMUM_CAPACITY = 1 << 30; // 默认的填充因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 当桶(bucket)上的结点数大于这个值时会转成红黑树 - static final int TREEIFY_THRESHOLD = 8; + static final int TREEIFY_THRESHOLD = 8; // 当桶(bucket)上的结点数小于这个值时树转链表 static final int UNTREEIFY_THRESHOLD = 6; // 桶中结构转化为红黑树对应的table的最小大小 static final int MIN_TREEIFY_CAPACITY = 64; // 存储元素的数组,总是2的幂次倍 - transient Node[] table; + transient Node[] table; // 存放具体元素的集 transient Set> entrySet; // 存放元素的个数,注意这个不等于数组的长度。 transient int size; // 每次扩容和更改map结构的计数器 - transient int modCount; + transient int modCount; // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 int threshold; // 加载因子 final float loadFactor; } ``` -- **loadFactor加载因子** - loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,数组中存放的数据(entry)也就越少,也就越稀疏。 +- **loadFactor 加载因子** - **loadFactor太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值**。 - - 给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 + loadFactor 加载因子是控制数组存放数据的疏密程度,loadFactor 越趋近于 1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor 越小,也就是趋近于 0,数组中存放的数据(entry)也就越少,也就越稀疏。 + + **loadFactor 太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor 的默认值为 0.75f 是官方给出的一个比较好的临界值**。 + + 给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 \* 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 - **threshold** - **threshold = capacity * loadFactor**,**当Size>=threshold**的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是 **衡量数组是否需要扩增的一个标准**。 + **threshold = capacity \* loadFactor**,**当 Size>=threshold**的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是 **衡量数组是否需要扩增的一个标准**。 -**Node节点类源码:** +**Node 节点类源码:** ```java // 继承自 Map.Entry @@ -149,7 +169,9 @@ static class Node implements Map.Entry { } } ``` + **树节点类源码:** + ```java static final class TreeNode extends LinkedHashMap.Entry { TreeNode parent; // 父 @@ -168,26 +190,30 @@ static final class TreeNode extends LinkedHashMap.Entry { r = p; } ``` -## HashMap源码分析 + +## HashMap 源码分析 + ### 构造方法 -![四个构造方法](https://user-gold-cdn.xitu.io/2018/3/20/162410d912a2e0e1?w=336&h=90&f=jpeg&s=26744) + +HashMap 中有四个构造方法,它们分别如下: + ```java // 默认构造函数。 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } - + // 包含另一个“Map”的构造函数 public HashMap(Map m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false);//下面会分析到这个方法 } - + // 指定“容量大小”的构造函数 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } - + // 指定“容量大小”和“加载因子”的构造函数 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) @@ -201,7 +227,7 @@ static final class TreeNode extends LinkedHashMap.Entry { } ``` -**putMapEntries方法:** +**putMapEntries 方法:** ```java final void putMapEntries(Map m, boolean evict) { @@ -229,17 +255,22 @@ final void putMapEntries(Map m, boolean evict) { } } ``` -### put方法 -HashMap只提供了put用于添加元素,putVal方法只是给put方法调用的一个方法,并没有提供给用户使用。 -**对putVal方法添加元素的分析如下:** +### put 方法 + +HashMap 只提供了 put 用于添加元素,putVal 方法只是给 put 方法调用的一个方法,并没有提供给用户使用。 + +**对 putVal 方法添加元素的分析如下:** -- ①如果定位到的数组位置没有元素 就直接插入。 -- ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用`e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入。 +1. 如果定位到的数组位置没有元素 就直接插入。 +2. 如果定位到的数组位置有元素就和要插入的 key 比较,如果 key 相同就直接覆盖,如果 key 不相同,就判断 p 是否是一个树节点,如果是就调用`e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。 +![ ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/put方法.png) +说明:上图有两个小问题: -![put方法](https://user-gold-cdn.xitu.io/2018/9/2/16598bf758c747e6?w=999&h=679&f=png&s=54486) +- 直接覆盖之后应该就会 return,不会有后续操作。参考 JDK8 HashMap.java 658 行([issue#608](https://github.com/Snailclimb/JavaGuide/issues/608))。 +- 当链表长度大于阈值(默认为 8)并且 HashMap 数组长度超过 64 的时候才会执行链表转红黑树的操作,否则就只是对数组扩容。参考 HashMap 的 `treeifyBin()` 方法([issue#1087](https://github.com/Snailclimb/JavaGuide/issues/1087))。 ```java public V put(K key, V value) { @@ -275,7 +306,9 @@ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, if ((e = p.next) == null) { // 在尾部插入新结点 p.next = newNode(hash, key, value, null); - // 结点数量达到阈值,转化为红黑树 + // 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法 + // 这个方法会根据 HashMap 数组来决定是否转换为红黑树。 + // 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是对数组扩容。 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); // 跳出循环 @@ -291,7 +324,7 @@ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, } } // 表示在桶中找到key值、hash值与插入元素相等的结点 - if (e != null) { + if (e != null) { // 记录e的value V oldValue = e.value; // onlyIfAbsent为false或者旧值为null @@ -312,21 +345,21 @@ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, // 插入后回调 afterNodeInsertion(evict); return null; -} +} ``` -**我们再来对比一下 JDK1.7 put方法的代码** +**我们再来对比一下 JDK1.7 put 方法的代码** -**对于put方法的分析如下:** +**对于 put 方法的分析如下:** -- ①如果定位到的数组位置没有元素 就直接插入。 -- ②如果定位到的数组位置有元素,遍历以这个元素为头结点的链表,依次和插入的key比较,如果key相同就直接覆盖,不同就采用头插法插入元素。 +- ① 如果定位到的数组位置没有元素 就直接插入。 +- ② 如果定位到的数组位置有元素,遍历以这个元素为头结点的链表,依次和插入的 key 比较,如果 key 相同就直接覆盖,不同就采用头插法插入元素。 ```java public V put(K key, V value) - if (table == EMPTY_TABLE) { - inflateTable(threshold); -} + if (table == EMPTY_TABLE) { + inflateTable(threshold); +} if (key == null) return putForNullKey(value); int hash = hash(key); @@ -337,7 +370,7 @@ public V put(K key, V value) V oldValue = e.value; e.value = value; e.recordAccess(this); - return oldValue; + return oldValue; } } @@ -347,9 +380,8 @@ public V put(K key, V value) } ``` +### get 方法 - -### get方法 ```java public V get(Object key) { Node e; @@ -380,8 +412,11 @@ final Node getNode(int hash, Object key) { return null; } ``` -### resize方法 -进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,要尽量避免resize。 + +### resize 方法 + +进行扩容,会伴随着一次重新 hash 分配,并且会遍历 hash 表中所有的元素,是非常耗时的。在编写程序中,要尽量避免 resize。 + ```java final Node[] resize() { Node[] oldTab = table; @@ -400,7 +435,7 @@ final Node[] resize() { } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; - else { + else { // signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); @@ -424,7 +459,7 @@ final Node[] resize() { newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode)e).split(this, newTab, j, oldCap); - else { + else { Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; @@ -464,7 +499,9 @@ final Node[] resize() { return newTab; } ``` -## HashMap常用方法测试 + +## HashMap 常用方法测试 + ```java package map; @@ -510,7 +547,8 @@ public class HashMapDemo { } /** - * 另外一种不常用的遍历方式 + * 如果既要遍历key又要value,那么建议这种方式,因为如果先获取keySet然后再执行map.get(key),map内部会执行两次遍历。 + * 一次是在获取keySet的时候,一次是在遍历所有key的时候。 */ // 当我调用put(key,value)方法的时候,首先会把key和value封装到 // Entry这个静态内部类对象中,把Entry对象再添加到数组中,所以我们想获取 @@ -520,7 +558,7 @@ public class HashMapDemo { for (java.util.Map.Entry entry : entrys) { System.out.println(entry.getKey() + "--" + entry.getValue()); } - + /** * HashMap其他常用方法 */ @@ -537,4 +575,4 @@ public class HashMapDemo { } -``` +``` \ No newline at end of file diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" new file mode 100644 index 0000000000000000000000000000000000000000..78f86a5bcb3800ca2cb954d96bd83d6068b5b238 --- /dev/null +++ "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" @@ -0,0 +1,623 @@ + + +- [1. 剖析面试最常见问题之 Java 集合框架](#1-剖析面试最常见问题之-java-集合框架) + - [1.1. 集合概述](#11-集合概述) + - [1.1.1. Java 集合概览](#111-java-集合概览) + - [1.1.2. 说说 List,Set,Map 三者的区别?](#112-说说-listsetmap-三者的区别) + - [1.1.3. 集合框架底层数据结构总结](#113-集合框架底层数据结构总结) + - [1.1.3.1. List](#1131-list) + - [1.1.3.2. Set](#1132-set) + - [1.1.3.3. Map](#1133-map) + - [1.1.4. 如何选用集合?](#114-如何选用集合) + - [1.1.5. 为什么要使用集合?](#115-为什么要使用集合) + - [1.2. Collection 子接口之 List](#12-collection-子接口之-list) + - [1.2.1. Arraylist 和 Vector 的区别?](#121-arraylist-和-vector-的区别) + - [1.2.2. Arraylist 与 LinkedList 区别?](#122-arraylist-与-linkedlist-区别) + - [1.2.2.1. 补充内容:双向链表和双向循环链表](#1221-补充内容双向链表和双向循环链表) + - [1.2.2.2. 补充内容:RandomAccess 接口](#1222-补充内容randomaccess-接口) + - [1.2.3. 说一说 ArrayList 的扩容机制吧](#123-说一说-arraylist-的扩容机制吧) + - [1.3. Collection 子接口之 Set](#13-collection-子接口之-set) + - [1.3.1. comparable 和 Comparator 的区别](#131-comparable-和-comparator-的区别) + - [1.3.1.1. Comparator 定制排序](#1311-comparator-定制排序) + - [1.3.1.2. 重写 compareTo 方法实现按年龄来排序](#1312-重写-compareto-方法实现按年龄来排序) + - [1.3.2. 无序性和不可重复性的含义是什么](#132-无序性和不可重复性的含义是什么) + - [1.3.3. 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同](#133-比较-hashsetlinkedhashset-和-treeset-三者的异同) + - [1.4. Map 接口](#14-map-接口) + - [1.4.1. HashMap 和 Hashtable 的区别](#141-hashmap-和-hashtable-的区别) + - [1.4.2. HashMap 和 HashSet 区别](#142-hashmap-和-hashset-区别) + - [1.4.3. HashMap 和 TreeMap 区别](#143-hashmap-和-treemap-区别) + - [1.4.4. HashSet 如何检查重复](#144-hashset-如何检查重复) + - [1.4.5. HashMap 的底层实现](#145-hashmap-的底层实现) + - [1.4.5.1. JDK1.8 之前](#1451-jdk18-之前) + - [1.4.5.2. JDK1.8 之后](#1452-jdk18-之后) + - [1.4.6. HashMap 的长度为什么是 2 的幂次方](#146-hashmap-的长度为什么是-2-的幂次方) + - [1.4.7. HashMap 多线程操作导致死循环问题](#147-hashmap-多线程操作导致死循环问题) + - [1.4.8. HashMap 有哪几种常见的遍历方式?](#148-hashmap-有哪几种常见的遍历方式) + - [1.4.9. ConcurrentHashMap 和 Hashtable 的区别](#149-concurrenthashmap-和-hashtable-的区别) + - [1.4.10. ConcurrentHashMap 线程安全的具体实现方式/底层具体实现](#1410-concurrenthashmap-线程安全的具体实现方式底层具体实现) + - [1.4.10.1. JDK1.7(上面有示意图)](#14101-jdk17上面有示意图) + - [1.4.10.2. JDK1.8 (上面有示意图)](#14102-jdk18-上面有示意图) + - [1.5. Collections 工具类](#15-collections-工具类) + - [1.5.1. 排序操作](#151-排序操作) + - [1.5.2. 查找,替换操作](#152-查找替换操作) + - [1.5.3. 同步控制](#153-同步控制) + + + + +# 1. 剖析面试最常见问题之 Java 集合框架 + +## 1.1. 集合概述 + +### 1.1.1. Java 集合概览 + +从下图可以看出,在 Java 中除了以 `Map` 结尾的类之外, 其他类都实现了 `Collection` 接口。 + +并且,以 `Map` 结尾的类都实现了 `Map` 接口。 + +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/source-code/dubbo/java-collection-hierarchy.png) + +

https://www.javatpoint.com/collections-in-java

+ +### 1.1.2. 说说 List,Set,Map 三者的区别? + +- `List`(对付顺序的好帮手): 存储的元素是有序的、可重复的。 +- `Set`(注重独一无二的性质): 存储的元素是无序的、不可重复的。 +- `Map`(用 Key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x”代表 key,"y"代表 value,Key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。 + +### 1.1.3. 集合框架底层数据结构总结 + +先来看一下 `Collection` 接口下面的集合。 + +#### 1.1.3.1. List + +- `Arraylist`: `Object[]`数组 +- `Vector`:`Object[]`数组 +- `LinkedList`: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环) + +#### 1.1.3.2. Set + +- `HashSet`(无序,唯一): 基于 `HashMap` 实现的,底层采用 `HashMap` 来保存元素 +- `LinkedHashSet`:`LinkedHashSet` 是 `HashSet` 的子类,并且其内部是通过 `LinkedHashMap` 来实现的。有点类似于我们之前说的 `LinkedHashMap` 其内部是基于 `HashMap` 实现一样,不过还是有一点点区别的 +- `TreeSet`(有序,唯一): 红黑树(自平衡的排序二叉树) + +再来看看 `Map` 接口下面的集合。 + +#### 1.1.3.3. Map + +- `HashMap`: JDK1.8 之前 `HashMap` 由数组+链表组成的,数组是 `HashMap` 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间 +- `LinkedHashMap`: `LinkedHashMap` 继承自 `HashMap`,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,`LinkedHashMap` 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931) +- `Hashtable`: 数组+链表组成的,数组是 `HashMap` 的主体,链表则是主要为了解决哈希冲突而存在的 +- `TreeMap`: 红黑树(自平衡的排序二叉树) + +### 1.1.4. 如何选用集合? + +主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用 `Map` 接口下的集合,需要排序时选择 `TreeMap`,不需要排序时就选择 `HashMap`,需要保证线程安全就选用 `ConcurrentHashMap`。 + +当我们只需要存放元素值时,就选择实现`Collection` 接口的集合,需要保证元素唯一时选择实现 `Set` 接口的集合比如 `TreeSet` 或 `HashSet`,不需要就选择实现 `List` 接口的比如 `ArrayList` 或 `LinkedList`,然后再根据实现这些接口的集合的特点来选用。 + +### 1.1.5. 为什么要使用集合? + +当我们需要保存一组类型相同的数据的时候,我们应该是用一个容器来保存,这个容器就是数组,但是,使用数组存储对象具有一定的弊端, +因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。 + +数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 +但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。 + +## 1.2. Collection 子接口之 List + +### 1.2.1. Arraylist 和 Vector 的区别? + +- `ArrayList` 是 `List` 的主要实现类,底层使用 `Object[ ]`存储,适用于频繁的查找工作,线程不安全 ; +- `Vector` 是 `List` 的古老实现类,底层使用` Object[ ]` 存储,线程安全的。 + +### 1.2.2. Arraylist 与 LinkedList 区别? + +1. **是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全; +2. **底层数据结构:** `Arraylist` 底层使用的是 **`Object` 数组**;`LinkedList` 底层使用的是 **双向链表** 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!) +3. **插入和删除是否受元素位置的影响:** ① **`ArrayList` 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e)`方法的时候, `ArrayList` 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element)`)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **`LinkedList` 采用链表存储,所以对于`add(E e)`方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置`i`插入和删除元素的话(`(add(int index, E element)`) 时间复杂度近似为`o(n))`因为需要先移动到指定位置再插入。** +4. **是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index)`方法)。 +5. **内存空间占用:** ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。 + +#### 1.2.2.1. 补充内容:双向链表和双向循环链表 + +**双向链表:** 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。 + +> 另外推荐一篇把双向链表讲清楚的文章:[https://juejin.im/post/5b5d1a9af265da0f47352f14](https://juejin.im/post/5b5d1a9af265da0f47352f14) + +![双向链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/双向链表.png) + +**双向循环链表:** 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。 + +![双向循环链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/双向循环链表.png) + +#### 1.2.2.2. 补充内容:RandomAccess 接口 + +```java +public interface RandomAccess { +} +``` + +查看源码我们发现实际上 `RandomAccess` 接口中什么都没有定义。所以,在我看来 `RandomAccess` 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。 + +在 `binarySearch()` 方法中,它要判断传入的 list 是否 `RamdomAccess` 的实例,如果是,调用`indexedBinarySearch()`方法,如果不是,那么调用`iteratorBinarySearch()`方法 + +```java + public static + int binarySearch(List> list, T key) { + if (list instanceof RandomAccess || list.size() arrayList = new ArrayList(); + arrayList.add(-1); + arrayList.add(3); + arrayList.add(3); + arrayList.add(-5); + arrayList.add(7); + arrayList.add(4); + arrayList.add(-9); + arrayList.add(-7); + System.out.println("原始数组:"); + System.out.println(arrayList); + // void reverse(List list):反转 + Collections.reverse(arrayList); + System.out.println("Collections.reverse(arrayList):"); + System.out.println(arrayList); + + // void sort(List list),按自然排序的升序排序 + Collections.sort(arrayList); + System.out.println("Collections.sort(arrayList):"); + System.out.println(arrayList); + // 定制排序的用法 + Collections.sort(arrayList, new Comparator() { + + @Override + public int compare(Integer o1, Integer o2) { + return o2.compareTo(o1); + } + }); + System.out.println("定制排序后:"); + System.out.println(arrayList); +``` + +Output: + +``` +原始数组: +[-1, 3, 3, -5, 7, 4, -9, -7] +Collections.reverse(arrayList): +[-7, -9, 4, 7, -5, 3, 3, -1] +Collections.sort(arrayList): +[-9, -7, -5, -1, 3, 3, 4, 7] +定制排序后: +[7, 4, 3, 3, -1, -5, -7, -9] +``` + +#### 1.3.1.2. 重写 compareTo 方法实现按年龄来排序 + +```java +// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列 +// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他 +// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了 +public class Person implements Comparable { + private String name; + private int age; + + public Person(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + /** + * T重写compareTo方法实现按年龄来排序 + */ + @Override + public int compareTo(Person o) { + if (this.age > o.getAge()) { + return 1; + } + if (this.age < o.getAge()) { + return -1; + } + return 0; + } +} + +``` + +```java + public static void main(String[] args) { + TreeMap pdata = new TreeMap(); + pdata.put(new Person("张三", 30), "zhangsan"); + pdata.put(new Person("李四", 20), "lisi"); + pdata.put(new Person("王五", 10), "wangwu"); + pdata.put(new Person("小红", 5), "xiaohong"); + // 得到key的值的同时得到key所对应的值 + Set keys = pdata.keySet(); + for (Person key : keys) { + System.out.println(key.getAge() + "-" + key.getName()); + + } + } +``` + +Output: + +``` +5-小红 +10-王五 +20-李四 +30-张三 +``` + +### 1.3.2. 无序性和不可重复性的含义是什么 + +1、什么是无序性?无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。 + +2、什么是不可重复性?不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()方法和 HashCode()方法。 + +### 1.3.3. 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同 + +`HashSet` 是 `Set` 接口的主要实现类 ,`HashSet` 的底层是 `HashMap`,线程不安全的,可以存储 null 值; + +`LinkedHashSet` 是 `HashSet` 的子类,能够按照添加的顺序遍历; + +`TreeSet` 底层使用红黑树,能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。 + +## 1.4. Map 接口 + +### 1.4.1. HashMap 和 Hashtable 的区别 + +1. **线程是否安全:** `HashMap` 是非线程安全的,`HashTable` 是线程安全的,因为 `HashTable` 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 `ConcurrentHashMap` 吧!); +2. **效率:** 因为线程安全的问题,`HashMap` 要比 `HashTable` 效率高一点。另外,`HashTable` 基本被淘汰,不要在代码中使用它; +3. **对 Null key 和 Null value 的支持:** `HashMap` 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出 `NullPointerException`。 +4. **初始容量大小和每次扩充容量大小的不同 :** ① 创建时如果不指定容量初始值,`Hashtable` 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。`HashMap` 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 `HashMap` 会将其扩充为 2 的幂次方大小(`HashMap` 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 `HashMap` 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。 +5. **底层数据结构:** JDK1.8 以后的 `HashMap` 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 + +**`HashMap` 中带有初始容量的构造函数:** + +```java + public HashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); + } + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } +``` + +下面这个方法保证了 `HashMap` 总是使用 2 的幂作为哈希表的大小。 + +```java + /** + * Returns a power of two size for the given target capacity. + */ + static final int tableSizeFor(int cap) { + int n = cap - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } +``` + +### 1.4.2. HashMap 和 HashSet 区别 + +如果你看过 `HashSet` 源码的话就应该知道:`HashSet` 底层就是基于 `HashMap` 实现的。(`HashSet` 的源码非常非常少,因为除了 `clone()`、`writeObject()`、`readObject()`是 `HashSet` 自己不得不实现之外,其他方法都是直接调用 `HashMap` 中的方法。 + +| `HashMap` | `HashSet` | +| :------------------------------------: | :----------------------------------------------------------: | +| 实现了 `Map` 接口 | 实现 `Set` 接口 | +| 存储键值对 | 仅存储对象 | +| 调用 `put()`向 map 中添加元素 | 调用 `add()`方法向 `Set` 中添加元素 | +| `HashMap` 使用键(Key)计算 `hashcode` | `HashSet` 使用成员对象来计算 `hashcode` 值,对于两个对象来说 `hashcode` 可能相同,所以` equals()`方法用来判断对象的相等性 | + +### 1.4.3. HashMap 和 TreeMap 区别 + +`TreeMap` 和`HashMap` 都继承自`AbstractMap` ,但是需要注意的是`TreeMap`它还实现了`NavigableMap`接口和`SortedMap` 接口。 + +![](./images/TreeMap继承结构.png) + +实现 `NavigableMap` 接口让 `TreeMap` 有了对集合内元素的搜索的能力。 + +实现`SortMap`接口让 `TreeMap` 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。示例代码如下: + +```java +/** + * @author shuang.kou + * @createTime 2020年06月15日 17:02:00 + */ +public class Person { + private Integer age; + + public Person(Integer age) { + this.age = age; + } + + public Integer getAge() { + return age; + } + + + public static void main(String[] args) { + TreeMap treeMap = new TreeMap<>(new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int num = person1.getAge() - person2.getAge(); + return Integer.compare(num, 0); + } + }); + treeMap.put(new Person(3), "person1"); + treeMap.put(new Person(18), "person2"); + treeMap.put(new Person(35), "person3"); + treeMap.put(new Person(16), "person4"); + treeMap.entrySet().stream().forEach(personStringEntry -> { + System.out.println(personStringEntry.getValue()); + }); + } +} +``` + +输出: + +``` +person1 +person4 +person2 +person3 +``` + +可以看出,`TreeMap` 中的元素已经是按照 `Person` 的 age 字段的升序来排列了。 + +上面,我们是通过传入匿名内部类的方式实现的,你可以将代码替换成 Lambda 表达式实现的方式: + +```java +TreeMap treeMap = new TreeMap<>((person1, person2) -> { + int num = person1.getAge() - person2.getAge(); + return Integer.compare(num, 0); +}); +``` + +**综上,相比于`HashMap`来说 `TreeMap` 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。** + +### 1.4.4. HashSet 如何检查重复 + +以下内容摘自我的 Java 启蒙书《Head fist java》第二版: + +当你把对象加入`HashSet`时,`HashSet` 会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的 `hashcode` 值作比较,如果没有相符的 `hashcode`,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 `hashcode` 值的对象,这时会调用`equals()`方法来检查 `hashcode` 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让加入操作成功。 + +**`hashCode()`与 `equals()` 的相关规定:** + +1. 如果两个对象相等,则 `hashcode` 一定也是相同的 +2. 两个对象相等,对两个 `equals()` 方法返回 true +3. 两个对象有相同的 `hashcode` 值,它们也不一定是相等的 +4. 综上,`equals()` 方法被覆盖过,则 `hashCode()` 方法也必须被覆盖 +5. `hashCode() `的默认行为是对堆上的对象产生独特值。如果没有重写 `hashCode()`,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。 + +**==与 equals 的区别** + +对于基本类型来说,== 比较的是值是否相等; + +对于引用类型来说,== 比较的是两个引用是否指向同一个对象地址(两者在内存中存放的地址(堆内存地址)是否指向同一个地方); + +对于引用类型(包括包装类型)来说,equals 如果没有被重写,对比它们的地址是否相等;如果 equals()方法被重写(例如 String),则比较的是地址里的内容。 + +### 1.4.5. HashMap 的底层实现 + +#### 1.4.5.1. JDK1.8 之前 + +JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** + +**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** + +**JDK 1.8 HashMap 的 hash 方法源码:** + +JDK 1.8 的 hash 方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 + +```java + static final int hash(Object key) { + int h; + // key.hashCode():返回散列值也就是hashcode + // ^ :按位异或 + // >>>:无符号右移,忽略符号位,空位都以0补齐 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } +``` + +对比一下 JDK1.7 的 HashMap 的 hash 方法源码. + +```java +static int hash(int h) { + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); +} +``` + +相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。 + +所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 + +![jdk1.8之前的内部结构-HashMap](images/jdk1.8之前的内部结构-HashMap.png) + +#### 1.4.5.2. JDK1.8 之后 + +相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。 + +![jdk1.8之后的内部结构-HashMap](images/jdk1.8之后的内部结构-HashMap.png) + +> TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。 + +### 1.4.6. HashMap 的长度为什么是 2 的幂次方 + +为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash`”。(n 代表数组长度)。这也就解释了 HashMap 的长度为什么是 2 的幂次方。 + +**这个算法应该如何设计呢?** + +我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。** + +### 1.4.7. HashMap 多线程操作导致死循环问题 + +主要原因在于并发下的 Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。 + +详情请查看: + +### 1.4.8. HashMap 有哪几种常见的遍历方式? + +[HashMap 的 7 种遍历方式与性能分析!](https://mp.weixin.qq.com/s/zQBN3UvJDhRTKP6SzcZFKw) + +### 1.4.9. ConcurrentHashMap 和 Hashtable 的区别 + +`ConcurrentHashMap` 和 `Hashtable` 的区别主要体现在实现线程安全的方式上不同。 + +- **底层数据结构:** JDK1.7 的 `ConcurrentHashMap` 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟 `HashMap1.8` 的结构一样,数组+链表/红黑二叉树。`Hashtable` 和 JDK1.8 之前的 `HashMap` 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; +- **实现线程安全的方式(重要):** ① **在 JDK1.7 的时候,`ConcurrentHashMap`(分段锁)** 对整个桶数组进行了分割分段(`Segment`),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了 `Segment` 的概念,而是直接用 `Node` 数组+链表+红黑树的数据结构来实现,并发控制使用 `synchronized` 和 CAS 来操作。(JDK1.6 以后 对 `synchronized` 锁做了很多优化)** 整个看起来就像是优化过且线程安全的 `HashMap`,虽然在 JDK1.8 中还能看到 `Segment` 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **`Hashtable`(同一把锁)** :使用 `synchronized` 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 + +**两者的对比图:** + +**HashTable:** + +![HashTable全表锁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/HashTable全表锁.png) + +

http://www.cnblogs.com/chengxiao/p/6842045.html>

+ +**JDK1.7 的 ConcurrentHashMap:** + +![JDK1.7的ConcurrentHashMap](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ConcurrentHashMap分段锁.jpg) + +

http://www.cnblogs.com/chengxiao/p/6842045.html>

+ +**JDK1.8 的 ConcurrentHashMap:** + +![Java8 ConcurrentHashMap 存储结构(图片来自 javadoop)](./images/java8_concurrenthashmap.png) + +JDK1.8 的 `ConcurrentHashMap` 不在是 **Segment 数组 + HashEntry 数组 + 链表**,而是 **Node 数组 + 链表 / 红黑树**。不过,Node 只能用于链表的情况,红黑树的情况需要使用 **`TreeNode`**。当冲突链表达到一定长度时,链表会转换成红黑树。 + +### 1.4.10. ConcurrentHashMap 线程安全的具体实现方式/底层具体实现 + +#### 1.4.10.1. JDK1.7(上面有示意图) + +首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 + +**`ConcurrentHashMap` 是由 `Segment` 数组结构和 `HashEntry` 数组结构组成**。 + +Segment 实现了 `ReentrantLock`,所以 `Segment` 是一种可重入锁,扮演锁的角色。`HashEntry` 用于存储键值对数据。 + +```java +static class Segment extends ReentrantLock implements Serializable { +} +``` + +一个 `ConcurrentHashMap` 里包含一个 `Segment` 数组。`Segment` 的结构和 `HashMap` 类似,是一种数组和链表结构,一个 `Segment` 包含一个 `HashEntry` 数组,每个 `HashEntry` 是一个链表结构的元素,每个 `Segment` 守护着一个 `HashEntry` 数组里的元素,当对 `HashEntry` 数组的数据进行修改时,必须首先获得对应的 `Segment` 的锁。 + +#### 1.4.10.2. JDK1.8 (上面有示意图) + +`ConcurrentHashMap` 取消了 `Segment` 分段锁,采用 CAS 和 `synchronized` 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N))) + +`synchronized` 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。 + +## 1.5. Collections 工具类 + +Collections 工具类常用方法: + +1. 排序 +2. 查找,替换操作 +3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合) + +### 1.5.1. 排序操作 + +```java +void reverse(List list)//反转 +void shuffle(List list)//随机排序 +void sort(List list)//按自然排序的升序排序 +void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑 +void swap(List list, int i , int j)//交换两个索引位置的元素 +void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面 +``` + +### 1.5.2. 查找,替换操作 + +```java +int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的 +int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll) +int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c) +void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。 +int frequency(Collection c, Object o)//统计元素出现次数 +int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target). +boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素 +``` + +### 1.5.3. 同步控制 + +`Collections` 提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。 + +我们知道 `HashSet`,`TreeSet`,`ArrayList`,`LinkedList`,`HashMap`,`TreeMap` 都是线程不安全的。`Collections` 提供了多个静态方法可以把他们包装成线程同步的集合。 + +**最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。** + +方法如下: + +```java +synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)collection。 +synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。 +synchronizedMap(Map m) //返回由指定映射支持的同步(线程安全的)Map。 +synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。 +``` + + + +**《Java面试突击》:** Java 程序员面试必备的《Java面试突击》V3.0 PDF 版本扫码关注下面的公众号,在后台回复 **"面试突击"** 即可免费领取! + +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/format,png.jpeg) diff --git "a/Java\347\233\270\345\205\263/LinkedList.md" "b/docs/java/collection/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220.md" similarity index 98% rename from "Java\347\233\270\345\205\263/LinkedList.md" rename to "docs/java/collection/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220.md" index 31f5f4838e9858890c8e3babee49882c84fa900b..a8159a34367e4d2dc67e827592fbab531311be2d 100644 --- "a/Java\347\233\270\345\205\263/LinkedList.md" +++ "b/docs/java/collection/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -23,8 +23,10 @@ List list=Collections.synchronizedList(new LinkedList(...)); ``` ## 内部结构分析 **如下图所示:** -![LinkedList内部结构](https://user-gold-cdn.xitu.io/2018/3/19/1623e363fe0450b0?w=600&h=481&f=jpeg&s=18502) + +![LinkedList内部结构](images/linkedlist/LinkedList内部结构.png) 看完了图之后,我们再看LinkedList类中的一个**内部私有类Node**就很好理解了: + ```java private static class Node { E item;//节点值 @@ -186,7 +188,7 @@ public void addLast(E e) { } ``` ### 根据位置取数据的方法 -**get(int index):**:根据指定索引返回数据 +**get(int index):** 根据指定索引返回数据 ```java public E get(int index) { //检查index范围是否在size之内 @@ -220,7 +222,7 @@ public E peekFirst() { getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,其中**getFirst()** 和**element()** 方法将会在链表为空时,抛出异常 -element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException +element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException **获取尾节点(index=-1)数据方法:** ```java public E getLast() { @@ -359,7 +361,7 @@ E unlink(Node x) { //删除前驱指针 if (prev == null) { - first = next;如果删除的节点是头节点,令头节点指向该节点的后继节点 + first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点 } else { prev.next = next;//将前驱节点的后继节点指向后继节点 x.prev = null; @@ -458,7 +460,7 @@ public class LinkedListDemo { linkedList.add(3); linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表) System.out.println("After removeFirstOccurrence(3):" + linkedList); - linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表) + linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从尾部到头部遍历列表) System.out.println("After removeFirstOccurrence(3):" + linkedList); /************************** 遍历操作 ************************/ diff --git a/docs/java/collection/images/77c95eb733284dbd8ce4e85c9cb6b042.png b/docs/java/collection/images/77c95eb733284dbd8ce4e85c9cb6b042.png new file mode 100644 index 0000000000000000000000000000000000000000..54180092b12364b9f613641554d61f7f3b36d61f Binary files /dev/null and b/docs/java/collection/images/77c95eb733284dbd8ce4e85c9cb6b042.png differ diff --git a/docs/java/collection/images/Java-Collections.jpeg b/docs/java/collection/images/Java-Collections.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..cf9071ff6a26db773f72703f21c3703b88c3e6bb Binary files /dev/null and b/docs/java/collection/images/Java-Collections.jpeg differ diff --git "a/docs/java/collection/images/TreeMap\347\273\247\346\211\277\347\273\223\346\236\204.png" "b/docs/java/collection/images/TreeMap\347\273\247\346\211\277\347\273\223\346\236\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..553e41b84acf32fb0bad3af15c22db55c148a17b Binary files /dev/null and "b/docs/java/collection/images/TreeMap\347\273\247\346\211\277\347\273\223\346\236\204.png" differ diff --git a/docs/java/collection/images/ad28e3ba-e419-4724-869c-73879e604da1.png b/docs/java/collection/images/ad28e3ba-e419-4724-869c-73879e604da1.png new file mode 100644 index 0000000000000000000000000000000000000000..1c05ebaad67d5c563211c26a1a55561d2626dd39 Binary files /dev/null and b/docs/java/collection/images/ad28e3ba-e419-4724-869c-73879e604da1.png differ diff --git a/docs/java/collection/images/image-20200405151029416.png b/docs/java/collection/images/image-20200405151029416.png new file mode 100644 index 0000000000000000000000000000000000000000..26ea14ca479fc22b0279e92d08a4b0cdbb6364e3 Binary files /dev/null and b/docs/java/collection/images/image-20200405151029416.png differ diff --git a/docs/java/collection/images/java8_concurrenthashmap.png b/docs/java/collection/images/java8_concurrenthashmap.png new file mode 100644 index 0000000000000000000000000000000000000000..a090c7cc25eb1a93058eddf1cd8f5799aafeb4ad Binary files /dev/null and b/docs/java/collection/images/java8_concurrenthashmap.png differ diff --git "a/docs/java/collection/images/jdk1.8\344\271\213\345\211\215\347\232\204\345\206\205\351\203\250\347\273\223\346\236\204-HashMap.png" "b/docs/java/collection/images/jdk1.8\344\271\213\345\211\215\347\232\204\345\206\205\351\203\250\347\273\223\346\236\204-HashMap.png" new file mode 100644 index 0000000000000000000000000000000000000000..54180092b12364b9f613641554d61f7f3b36d61f Binary files /dev/null and "b/docs/java/collection/images/jdk1.8\344\271\213\345\211\215\347\232\204\345\206\205\351\203\250\347\273\223\346\236\204-HashMap.png" differ diff --git "a/docs/java/collection/images/jdk1.8\344\271\213\345\220\216\347\232\204\345\206\205\351\203\250\347\273\223\346\236\204-HashMap.png" "b/docs/java/collection/images/jdk1.8\344\271\213\345\220\216\347\232\204\345\206\205\351\203\250\347\273\223\346\236\204-HashMap.png" new file mode 100644 index 0000000000000000000000000000000000000000..7c95e73847e980169b2dd1fe846bfe45e2592ae6 Binary files /dev/null and "b/docs/java/collection/images/jdk1.8\344\271\213\345\220\216\347\232\204\345\206\205\351\203\250\347\273\223\346\236\204-HashMap.png" differ diff --git "a/docs/java/collection/images/linkedlist/LinkedList\345\206\205\351\203\250\347\273\223\346\236\204.png" "b/docs/java/collection/images/linkedlist/LinkedList\345\206\205\351\203\250\347\273\223\346\236\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..b70a93721e171ab4de9e455e2c409a4eb4bc0ee7 Binary files /dev/null and "b/docs/java/collection/images/linkedlist/LinkedList\345\206\205\351\203\250\347\273\223\346\236\204.png" differ diff --git a/docs/java/images/image-20200405151029416.png b/docs/java/images/image-20200405151029416.png new file mode 100644 index 0000000000000000000000000000000000000000..26ea14ca479fc22b0279e92d08a4b0cdbb6364e3 Binary files /dev/null and b/docs/java/images/image-20200405151029416.png differ diff --git a/docs/java/images/performance-tuning/java-performance1.png b/docs/java/images/performance-tuning/java-performance1.png new file mode 100644 index 0000000000000000000000000000000000000000..0975d26519efa05868b682ba2760fe83b2f8735e Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance1.png differ diff --git a/docs/java/images/performance-tuning/java-performance2.png b/docs/java/images/performance-tuning/java-performance2.png new file mode 100644 index 0000000000000000000000000000000000000000..76bbc0a82f13c34e0ae91d8eff10713e85c6a859 Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance2.png differ diff --git a/docs/java/images/performance-tuning/java-performance3.png b/docs/java/images/performance-tuning/java-performance3.png new file mode 100644 index 0000000000000000000000000000000000000000..150e5ce5d5a9ed051e911d95f9ec2a5bf6fee348 Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance3.png differ diff --git a/docs/java/images/performance-tuning/java-performance4.png b/docs/java/images/performance-tuning/java-performance4.png new file mode 100644 index 0000000000000000000000000000000000000000..e3bf74505922a16076dc2c0364fc06314b16e96a Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance4.png differ diff --git a/docs/java/images/performance-tuning/java-performance5.png b/docs/java/images/performance-tuning/java-performance5.png new file mode 100644 index 0000000000000000000000000000000000000000..c6b44840ff40cc5fa6d03517f9564a1cf4ab226d Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance5.png differ diff --git a/docs/java/images/performance-tuning/java-performance6.png b/docs/java/images/performance-tuning/java-performance6.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e182ed89c17ab57254c2e37329643786d64ab4 Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance6.png differ diff --git a/docs/java/images/performance-tuning/java-performance7.png b/docs/java/images/performance-tuning/java-performance7.png new file mode 100644 index 0000000000000000000000000000000000000000..0796e30d2399bc7405be867607b9c2f91d2c31ba Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance7.png differ diff --git a/docs/java/images/performance-tuning/java-performance8.png b/docs/java/images/performance-tuning/java-performance8.png new file mode 100644 index 0000000000000000000000000000000000000000..75172dfad5f088b5e5a7f30348c50eca3a4c4855 Binary files /dev/null and b/docs/java/images/performance-tuning/java-performance8.png differ diff --git "a/docs/java/jvm/JDK\347\233\221\346\216\247\345\222\214\346\225\205\351\232\234\345\244\204\347\220\206\345\267\245\345\205\267\346\200\273\347\273\223.md" "b/docs/java/jvm/JDK\347\233\221\346\216\247\345\222\214\346\225\205\351\232\234\345\244\204\347\220\206\345\267\245\345\205\267\346\200\273\347\273\223.md" new file mode 100644 index 0000000000000000000000000000000000000000..c8263de02e2c6a59f9db21ec746d977b188abc7a --- /dev/null +++ "b/docs/java/jvm/JDK\347\233\221\346\216\247\345\222\214\346\225\205\351\232\234\345\244\204\347\220\206\345\267\245\345\205\267\346\200\273\347\273\223.md" @@ -0,0 +1,325 @@ + + +- [JDK 监控和故障处理工具总结](#jdk-监控和故障处理工具总结) + - [JDK 命令行工具](#jdk-命令行工具) + - [`jps`:查看所有 Java 进程](#jps查看所有-java-进程) + - [`jstat`: 监视虚拟机各种运行状态信息](#jstat-监视虚拟机各种运行状态信息) + - [` jinfo`: 实时地查看和调整虚拟机各项参数](#-jinfo-实时地查看和调整虚拟机各项参数) + - [`jmap`:生成堆转储快照](#jmap生成堆转储快照) + - [**`jhat`**: 分析 heapdump 文件](#jhat-分析-heapdump-文件) + - [**`jstack`** :生成虚拟机当前时刻的线程快照](#jstack-生成虚拟机当前时刻的线程快照) + - [JDK 可视化分析工具](#jdk-可视化分析工具) + - [JConsole:Java 监视与管理控制台](#jconsolejava-监视与管理控制台) + - [连接 Jconsole](#连接-jconsole) + - [查看 Java 程序概况](#查看-java-程序概况) + - [内存监控](#内存监控) + - [线程监控](#线程监控) + - [Visual VM:多合一故障处理工具](#visual-vm多合一故障处理工具) + + + +# JDK 监控和故障处理工具总结 + +## JDK 命令行工具 + +这些命令在 JDK 安装目录下的 bin 目录下: + +- **`jps`** (JVM Process Status): 类似 UNIX 的 `ps` 命令。用户查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息; +- **`jstat`**( JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据; +- **`jinfo`** (Configuration Info for Java) : Configuration Info forJava,显示虚拟机配置信息; +- **`jmap`** (Memory Map for Java) :生成堆转储快照; +- **`jhat`** (JVM Heap Dump Browser ) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果; +- **`jstack`** (Stack Trace for Java):生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。 + +### `jps`:查看所有 Java 进程 + +`jps`(JVM Process Status) 命令类似 UNIX 的 `ps` 命令。 + +`jps`:显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID(Local Virtual Machine Identifier,LVMID)。`jps -q` :只输出进程的本地虚拟机唯一 ID。 + +```powershell +C:\Users\SnailClimb>jps +7360 NettyClient2 +17396 +7972 Launcher +16504 Jps +17340 NettyServer +``` + +`jps -l`:输出主类的全名,如果进程执行的是 Jar 包,输出 Jar 路径。 + +```powershell +C:\Users\SnailClimb>jps -l +7360 firstNettyDemo.NettyClient2 +17396 +7972 org.jetbrains.jps.cmdline.Launcher +16492 sun.tools.jps.Jps +17340 firstNettyDemo.NettyServer +``` + +`jps -v`:输出虚拟机进程启动时 JVM 参数。 + +`jps -m`:输出传递给 Java 进程 main() 函数的参数。 + +### `jstat`: 监视虚拟机各种运行状态信息 + +jstat(JVM Statistics Monitoring Tool) 使用于监视虚拟机各种运行状态信息的命令行工具。 它可以显示本地或者远程(需要远程主机提供 RMI 支持)虚拟机进程中的类信息、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI,只提供了纯文本控制台环境的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。 + +**`jstat` 命令使用格式:** + +```powershell +jstat -