update String

master
Omooo 6 years ago
parent 72f66183fe
commit e8f43870ae
  1. 206
      blogs/Algorithm/链表.md
  2. 145
      blogs/Java/String.md
  3. BIN
      images/Java/String (1).png
  4. BIN
      images/Java/String.png
  5. BIN
      images/data_structure/单链表.jpg
  6. BIN
      images/data_structure/双向循环链表.jpg
  7. BIN
      images/data_structure/双向链表.jpg
  8. BIN
      images/data_structure/循环链表.jpg

@ -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<Node> 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. 代码逻辑在处理头结点和尾结点时

@ -4,18 +4,27 @@ String
#### 目录 #### 目录
1. 思维导图 1. 前言
2. 源码解析 2. 思维导图
3. 源码解析
- 类继承关系 - 类继承关系
- 类成员变量 - 类成员变量
- 类成员方法 - 类成员方法
- 相关静态方法 - 相关静态方法
3. 对象内存分配 4. StringBuilder 和 StringBuffer
4. 参考 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; @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 为双字节编码。 String 支持多种编码,但是如果不指定编码的话,它可能使用两种编码方式,分别是 LATIN1 和 UTF16,LATIN1 其实就是 ISO 编码,属于单字节编码,而 UTF16 为双字节编码。
@ -79,7 +88,7 @@ String 在表示因为字符或者数字时,会可能存在浪费空间的情
//... //...
``` ```
既然改变了编码方式,计算长度就需要考虑编码方式了,如果是 UTF16,双字节编码,那就是右移一位即长度为之前的 1/2。同时,也能看出来,默认采用的是单字节编码即 ISO 编码。 既然改变了编码方式,计算长度就需要考虑编码方式了,如果是 UTF16,双字节编码,那就是右移一位即长度为之前的 1/2。同时,也能看出来,默认采用的是单字节编码即 ISO 编码(COMPACT_STRINGS 默认为 true)
剩下就是 String#intern() 方法: 剩下就是 String#intern() 方法:
@ -87,10 +96,119 @@ String 在表示因为字符或者数字时,会可能存在浪费空间的情
public native String intern(); 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.<init> ()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 对象创建有两种方式: String 对象创建有两种方式:
1. 字面量赋值 1. 字面量赋值
@ -138,10 +256,19 @@ str1 = str1.intern();
System.out.println(str2==str1); 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) [String 源码浅析(一)](https://juejin.im/post/5c2588d8f265da6110371d2b)
[Java9后String的空间优化](https://blog.csdn.net/wangyangzhizhou/article/details/80371653) [Java9后String的空间优化](https://blog.csdn.net/wangyangzhizhou/article/details/80371653)
[String类相关面试题很难?不要方,问题不大](https://www.jianshu.com/p/d416a074409d) [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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Loading…
Cancel
Save