diff --git a/blogs/Algorithm/链表.md b/blogs/Algorithm/链表.md new file mode 100644 index 0000000..8b1f2ac --- /dev/null +++ b/blogs/Algorithm/链表.md @@ -0,0 +1,206 @@ +--- +链表 +--- + +#### 目录 + +1. 链表分类 + - 单链表 + - 循环链表 + - 双向链表 + - 双向循环链表 +2. 常见链表算法题 + - 单链表反转 + - 链表中环的检测 + - 两个有序链表的合并 + - 删除链表倒数第 n 个结点 + - 求链表的中间结点 +3. 总结 + +#### 链表分类 + +##### 单链表 + +![](https://i.loli.net/2019/02/25/5c73a3d48b8f4.jpg) + +##### 循环链表 + +![](https://i.loli.net/2019/02/25/5c73a4202fc70.jpg) + +##### 双向链表 + +![](https://i.loli.net/2019/02/25/5c73a435a1dd8.jpg) + +##### 双向循环链表 + +![](https://i.loli.net/2019/02/25/5c73a44c30c40.jpg) + +#### 常见链表算法题 + +1. 单链表的反转 + + ```java + public class LinkedListDemo { + + private static Node head; + + public static void main(String[] args) { + head = new Node(0); + head.next = new Node(1); + head.next.next = new Node(2); + head.next.next.next = new Node(3); + + reverseLinkedList(); + + while (head != null) { + System.out.println(head.value); + head = head.next; + } + } + + //单链表的反转 + private static void reverseLinkedList() { + if (head == null || head.next == null) { + return; + } + Node p1 = head; + Node p2 = head.next; + Node p3 = null; + while (p2 != null) { + p3 = p2.next; + p2.next = p1; + p1 = p2; + p2 = p3; + } + head.next = null; + head = p1; + } + + static class Node { + int value; + Node next; + + Node(int value) { + this.value = value; + } + } + } + ``` + + 参考:[漫画:如何将一个链表“逆序”?](https://mp.weixin.qq.com/s/MR_qAbonFqGF_ljeWUC26w) + +2. 链表中环的检测 + + 这道题有三种解法: + + ```java + public class LinkedListDemo { + + private static Node head; + + public static void main(String[] args) { + head = new Node(0); + head.next = new Node(1); + head.next.next = new Node(2); + head.next.next.next = new Node(3); + head.next.next.next.next = head.next; + + boolean isHasCycleBySet = hasCycleBySet(); + System.out.println(isHasCycleBySet); + boolean isHasCycleByRun = hasCycleByRun(); + System.out.println(isHasCycleByRun); + boolean isHasCycleByMagic = hasCycleByMagic(); + System.out.println(isHasCycleByMagic); + } + + //用 HashSet 判断 + private static boolean hasCycleBySet() { + if (head == null || head.next == null) { + return false; + } + Set set = new HashSet<>(); + while (head != null) { + if (set.contains(head)) { + return true; + } else { + set.add(head); + } + head = head.next; + } + return false; + } + + //快慢指针 + private static boolean hasCycleByRun() { + if (head == null || head.next == null) { + return false; + } + Node p1 = head; + Node p2 = head.next; + while (p1 != p2) { + if (p2 == null || p2.next == null) { + return false; + } + p1 = p1.next; + p2 = p2.next.next; + } + return true; + } + + //魔法数解法 + private static boolean hasCycleByMagic() { + if (head == null || head.next == null) { + return false; + } + while (head != null) { + if (head.value == 2 << 30) { + return true; + } else { + head.value = 2 << 30; + } + head = head.next; + } + return false; + } + + static class Node { + int value; + Node next; + + Node(int value) { + this.value = value; + } + } + } + ``` + +3. 两个有序链表的合并 + + ```java + + ``` + +4. 删除链表倒数第 n 个结点 + + ```java + + ``` + +5. 求链表的中间结点 + + ```java + + ``` + + + +#### 总结 + +面试中手写算法题最多的就是链表相关的,链表算法到底难不难?难?难得话一个题再写十遍还难不难?(气急败坏中... + +不过,链表中需要注意几个边界条件: + +1. 如果链表为空 +2. 如果链表只有一个结点时 +3. 如果链表只有两个结点时 +4. 代码逻辑在处理头结点和尾结点时 \ No newline at end of file diff --git a/blogs/Java/String.md b/blogs/Java/String.md index 9043ece..b01ad7b 100644 --- a/blogs/Java/String.md +++ b/blogs/Java/String.md @@ -4,18 +4,27 @@ String #### 目录 -1. 思维导图 -2. 源码解析 +1. 前言 +2. 思维导图 +3. 源码解析 - 类继承关系 - 类成员变量 - 类成员方法 - 相关静态方法 -3. 对象内存分配 -4. 参考 +4. StringBuilder 和 StringBuffer +5. 对象内存分配 +6. 常见面试题 +7. 参考 + +#### 前言 + +对于 String,大家可能再常见不过了。我们知道,String 是一个不可变类,也由于它的不可变性,所以在类似拼接、裁剪字符串时,都会产生新的 String 对象。字符串操作不当就可能产生大量临时字符串,这也就引入了 StringBuilder 和 StringBuffer,它们之间的区别就在于是否线程安全。当然,你可能也知道 new String("Omooo") 和 String name = "Omooo" 的区别。 + +那么你是否知道 JDK9 之后对 String 实现的变化呢?StringBuilder 和 StringBuffer 除了线程安全还有哪些知识点呢?以及对 String 的编译器优化、+ 重载符的底层实现、intern() 方法的版本区别? #### 思维导图 -![](https://i.loli.net/2018/12/31/5c29ad8944245.png) +![](https://i.loli.net/2019/03/01/5c7941e96bcb1.png) #### 源码解析 @@ -44,7 +53,7 @@ static { @Native static final byte UTF16 = 1; ``` -在 JDK 9 之前,用的是 char 数组来存储 String 的值,之后就用 byte 数组来存储,char 是两个 byte,比如在存储 ‘A’ 这个字符串时只需一个 byte,就会造成空间浪费。 +在 JDK 9 之前,用的是 char 数组来存储 String 的值,之后就用 byte 数组来存储,一个 char 是两个 byte,在存储单个字符时以及拉丁语系的字符时,根本就不需要太宽的 char,所以在之后用了划分粒度更细的 byte 来存储,这也就带来了更小的内存占用和更快的操作速度。 String 支持多种编码,但是如果不指定编码的话,它可能使用两种编码方式,分别是 LATIN1 和 UTF16,LATIN1 其实就是 ISO 编码,属于单字节编码,而 UTF16 为双字节编码。 @@ -79,7 +88,7 @@ String 在表示因为字符或者数字时,会可能存在浪费空间的情 //... ``` -既然改变了编码方式,计算长度就需要考虑编码方式了,如果是 UTF16,双字节编码,那就是右移一位即长度为之前的 1/2。同时,也能看出来,默认采用的是单字节编码即 ISO 编码。 +既然改变了编码方式,计算长度就需要考虑编码方式了,如果是 UTF16,双字节编码,那就是右移一位即长度为之前的 1/2。同时,也能看出来,默认采用的是单字节编码即 ISO 编码(COMPACT_STRINGS 默认为 true)。 剩下就是 String#intern() 方法: @@ -87,10 +96,119 @@ String 在表示因为字符或者数字时,会可能存在浪费空间的情 public native String intern(); ``` -过于重要,下面解释。 +intern() 是在 Java6 引入的,目的是提示 JVM 把相应字符串放在常量池缓存起来。在我们创建字符串对象并调用 intern() 方法的时候,如果已经有了缓存的字符串,就会返回缓存里面的实例,否则就将其缓存起来。 + +在使用 Java6 中并不推荐大量使用 intern() 方法,这是因为被缓存的字符串是存在所谓的 PermGen 里的,也就是永久代,这个空间是有限的,也基本不会被除了 FullGC 之外的垃圾收集照顾到,所以,如果使用不当,OOM 就会光顾。在后续版本中,这个缓存被放置在堆上,这样就极大避免了永久代被占满的问题,甚至永久代在 JDK8 中被 MetaSpace(元数据区)替代了。而且,默认缓存大小也在不断的扩大中。 + +还有就是在 Java7 中,intern 方法做了些改变,进行拷贝的时候不是拷贝对象,而是拷贝地址值。这里强烈推荐 [String类相关面试题很难?不要方,问题不大](https://www.jianshu.com/p/d416a074409d) 这一篇文章。 + +Intern 是一种显式的重排机制,但是它也有一定的副作用,那就是需要手动调用。我想基本上很少有人用到这个方法,因为我们很难预计字符串的重复情况,反而是一种看视一种冗余的操作。好在,在 JDK8 中,推出了一种新特性,那就是 G1 GC 下的字符串重排,它是通过将相同数据的字符串指向同一份数据来做到的,是 JVM 底层的改变,并不需要 Java 类库做什么修改。 + +#### StringBuilder 和 StringBuffer + +StringBuffer 是 StringBuilder 的线程安全版本,二者都继承了 AbstractStringBuilder,StringBuffer 在修改字符串操作 +(append、replace、substring 等)的时候都加了 synchronized,这里就以 StringBuilder 来分析。 + +```java +StringBuilder sb = new StringBuilder("Om"); +String name = sb.append("o").append("o").append("o").toString(); +``` + +首先需要明确的就是 StringBuilder 和 StringBuffer 针对字符串的修改都是通过 byte[] 数组的。 + +既然是通过内部数组来实现的,那么内部数组应该创建多大呢? + +```java +public StringBuilder(String str) { + //1.创建一个初始容量+16 的 byte[] 数组 + super(str.length() + 16); + //2.把原 byte[] 数组的值拷贝到新数组 + append(str); +} + + //super(str.length() + 16)调用的 AbstractStringBuilder 的构造方法 + AbstractStringBuilder(int capacity) { + if (COMPACT_STRINGS) { + value = new byte[capacity]; + coder = LATIN1; + } else { + value = StringUTF16.newBytesFor(capacity); + coder = UTF16; + } + } +``` + +所以,当如果已知可能拼接的字符串长度过时,可以这样指定: + +```java +StringBuilder sb = new StringBuilder(30); +``` + +这就避免了拼接了长度大于十六之后导致数组拷贝的开销,在循环中拼接字符串要特别注意。 + +还有一种情况,那就是在 for 循环中使用 + 拼接字符串,这也是要极力避免的,因为 + 重载符底层也是通过 StringBuilder 来实现的,实际上这句话也不太准确,因为存在编译器优化的情况。 + +```java +public class StringTest { + + public static void main(String[] args) { + String s = "Name: " + "Omooo"; + System.out.println(s); + System.out.println(add("Omooo")); + } + + private static String add(String name) { + return "Name: " + name; + } +} +``` + +由于字符串的不可变性,所以在编译阶段就能确定 s 的值,所以也就不需要 StringBuilder。对于 add 方法,我们直接看编译后的字节码即可(IDEA -> View -> Show Bytecode): + +```java + L0 + LINENUMBER 11 L0 + NEW java/lang/StringBuilder + DUP + INVOKESPECIAL java/lang/StringBuilder. ()V + LDC "Name: " + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; + ALOAD 0 + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; + INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; + ARETURN + L1 + LOCALVARIABLE name Ljava/lang/String; L0 L1 0 + MAXSTACK = 2 + MAXLOCALS = 1 +``` + +可以明显的看出是通过 StringBuilder 来实现的,以上是在 JDK8 环境,对于 JDK8 之后则是: + +```java + private static add(Ljava/lang/String;)Ljava/lang/String; + L0 + LINENUMBER 12 L0 + ALOAD 0 + INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;)Ljava/lang/String; [ + // handle kind 0x6 : INVOKESTATIC + java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; + // arguments: + "Name: \u0001" + ] + ARETURN + L1 + LOCALVARIABLE name Ljava/lang/String; L0 L1 0 + MAXSTACK = 1 + MAXLOCALS = 1 +``` + +可以看到是通过 StringConcatFactory.makeConcatWithConstants ,查看源码其实内部也是通过 StringBuilder 来实现的。 #### 对象内存分配 +关于内存分配这一块,再次推荐一遍 [String类相关面试题很难?不要方,问题不大](https://www.jianshu.com/p/d416a074409d) 这一篇文章。 + String 对象创建有两种方式: 1. 字面量赋值 @@ -138,10 +256,19 @@ str1 = str1.intern(); System.out.println(str2==str1); ``` +#### 常见面试题 + +1. String、StringBuilder、StingBuffer 的区别? +2. String name = "Omooo" 和 String name = new String("Omoo") 、String name = new String("Omoo") + "o" 的区别以及分别创建了多少个对象? + #### 参考 [String 源码浅析(一)](https://juejin.im/post/5c2588d8f265da6110371d2b) [Java9后String的空间优化](https://blog.csdn.net/wangyangzhizhou/article/details/80371653) -[String类相关面试题很难?不要方,问题不大](https://www.jianshu.com/p/d416a074409d) \ No newline at end of file +[String类相关面试题很难?不要方,问题不大](https://www.jianshu.com/p/d416a074409d) + +[G1垃圾回收器中的字符串去重](http://www.importnew.com/23354.html) + +[第5讲 | String、StringBuffer、StringBuilder有什么区别?](https://time.geekbang.org/column/article/7349) \ No newline at end of file diff --git a/images/Java/String (1).png b/images/Java/String (1).png new file mode 100644 index 0000000..edaafae Binary files /dev/null and b/images/Java/String (1).png differ diff --git a/images/Java/String.png b/images/Java/String.png deleted file mode 100644 index af7e6ef..0000000 Binary files a/images/Java/String.png and /dev/null differ diff --git a/images/data_structure/单链表.jpg b/images/data_structure/单链表.jpg new file mode 100644 index 0000000..dc0b0be Binary files /dev/null and b/images/data_structure/单链表.jpg differ diff --git a/images/data_structure/双向循环链表.jpg b/images/data_structure/双向循环链表.jpg new file mode 100644 index 0000000..c67cb48 Binary files /dev/null and b/images/data_structure/双向循环链表.jpg differ diff --git a/images/data_structure/双向链表.jpg b/images/data_structure/双向链表.jpg new file mode 100644 index 0000000..a5ae6f3 Binary files /dev/null and b/images/data_structure/双向链表.jpg differ diff --git a/images/data_structure/循环链表.jpg b/images/data_structure/循环链表.jpg new file mode 100644 index 0000000..4dff0b9 Binary files /dev/null and b/images/data_structure/循环链表.jpg differ