### String
在JAVA语言中有八种基本类型包装类和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。因为String不可变的性质,因此Java内部实现了String常量池。当一个String被创建时,会先去常量池查看有没有值相同的示例,有的话直接返回。节省了内存,加快了字符串的加载速度。不可变的对象也可以保证在并发中保持线程安全
其中String类型的常量池比较特殊。它的主要使用方法有两种:
* 直接使用双引号声明出来的String对象会直接存储在常量池中。
* 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
### 构造器
![](https://img.kancloud.cn/6f/1a/6f1a725fa8e4b35231c6957501dfcb30_809x130.png)
![](https://img.kancloud.cn/37/bf/37bfbdc1d623d9e4b49b955369e2d1d5_807x278.png)
### 特性
* 字符串常量,实际上也是String对象
* 所有不是通过new创建的String都是放在常量池中
* String类型的对象是不可变的
* String实现了CharSequence接口
### String对象创建方式
```
String str1 = "abcd";
String str2 = new String("abcd");
```
这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。
只要使用new方法,便需要创建新的对象
### 连接表达式+\(加号\)
1. 只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
2. 对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中
```
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
System.out.println(str3 == str4);//false
String str5 = "string";
System.out.println(str3 == str5);//true
```
```
1、 Sting s; //定义了一个变量s,没有创建对象;
2、 = // 赋值,将某个对象的引用(句柄)赋给s ,没有创建对象;
3、 “abc” //创建一个对象;
4、 new String(); // 创建一个对象。
```
### 常用方法
![](https://img.kancloud.cn/5a/b8/5ab8d6a8f55ca52b9770a83d29d2e910_803x51.png)
![](https://img.kancloud.cn/4b/37/4b37298a0b771efcb49ff55e3d8761fc_806x82.png)
![](https://img.kancloud.cn/a0/05/a00569e48e560aec9f0ade81babc77e4_815x230.png)
![](https://img.kancloud.cn/f7/2a/f72a63d0e5f7a4ef4bd605273054180e_801x134.png)
![](https://img.kancloud.cn/07/19/07192e2aed8468e62dce058f9a1ad07c_847x618.png)![](https://img.kancloud.cn/e8/c0/e8c094e181420e87ccd4d5f8315a91dc_837x562.png)![](https://img.kancloud.cn/48/f6/48f6654f1f4bde0ef7dff769a029cf5f_818x145.png)
* length 返回字符串长度
* isEmpty 判断字符串是否为空
* charAt 根据索引位置获取char
* getChars 复制对应位置范围的char到数组中
* equals, equalsIgnoreCase 对比顺序依次为引用地址,char数组长度,char数组内容
* compareTo 对比字符串大小
* startsWith, endsWith 判断前后缀
* hashCode 计算hash值, 公式为s\[0\]\*31^\(n-1\) + s\[1\]\*31^\(n-2\) + ... + s\[n-1\]
* indexOf 查找首次出现的位置
* lastIndexOf 查找最后出现的位置
* substring 返回子串(旧版本是返回一个引用在父串的一个新串,节省重新分配内存。但实际如果子串引用了一个占用极大的父串,会因为子串一直被使用导致父串没法被垃圾回收,新版本substring每次重新复制char数组)
* concat 拼接字符串(拼接char数组,重新创建字符串)
* replace 用新字符替换所有的旧字符(会先遍历一次char数组,寻找时候存在,再去替换,避免每次都要分配char数组)
* matches 判断是否符合正则 (复用Pattern.matches\(\)方法)
* contains 判断是否包含子串(复用indexOf\(\)方法)
* replaceFirst 只替换一次
* replaceAll 替换所有正则符合的地方
* split 按照正则分割字符串
* toLowerCase 返回小写
* toUpperCase 返回大写
* trim 去除前后空格
* toCharArray 重新复制char数组返回
* join\(CharSequence delimiter, CharSequence... elements\)
```
String.join(",", "you", "bao", "luo");
//out: you,bao,luo
```
* equals\(Object anObject\)
String.equals\(\)代码逻辑:
1. 判断传入的对象与当前对象是否为同一个对象,如果是就直接返回true;
2. 判断传入的对象是否为String,若不是则返回false\(如果为null也不成立\);
3. 判断传入的String与当前String长度是否一致,若不一致则返回false;
4. 循环对比两个字符串的char\[\]数组,逐个对比字符是否一致,若不一致则直接返回false;
5. 循环结束没有找到不匹配的则返回true;
```
JDK8源码:
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;
}
```
* intern\(\):naive方法,直接返回常量池中的引用
当调用intern\(\)方法时,JVM会在常量池中通过equals\(\)方法查找是否存在等值的String,如果存在则直接返回常量池中这个String对象的地址;如果不存在则会创建等值的字符串放入常量池\(即等值的char\[\]数组字符串,但是char\[\]是新开辟的一份拷贝空间\),然后再返回这个新创建空间的地址;
注意:String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009
在常量池查找等值String时,通常不止一个字符串而是多个字符串因此效率会比较低,另外为保证唯一性,需要有锁的介入;
```
String str1 = "ab";
String str2 = new String("ab");
System.out.println(str1== str2);//false
System.out.println(str2.intern() == str1);//true
System.out.println(str1== str2);//false
str2 = str2.intern();
System.out.println(str1== str2);//true
```
### 知识点
* 在调用x.toString\(\)的地方可以用""+x替代;
* 字符串的+拼接操作
```
public static void main(String[] args) throws InterruptedException {
String s = "a";
String st = s + "b" + "c";
}
javap out====>
Code:
stack=3, locals=3, args_size=1
0: ldc #19 // String a
2: astore_1
3: new #21 // class java/lang/StringBuilder
6: dup
7: aload_1
8: invokestatic #23 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
11: invokespecial #29 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
14: ldc #32 // String b
16: invokevirtual #34 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #38 // String c
21: invokevirtual #34 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #40 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore_2
28: return
```
* StringBuffer是线程安全操作
```
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
```
* StringBuilder非线程安全
```
public StringBuilder append(String str) {
super.append(str);
return this;
}
```
```
System.err.println("hello,world"); ##hello,world实际是String对象
```
### printf格式化输出
![](https://img.kancloud.cn/2d/27/2d2767f1acc42cc4544c1ace11ec4e7c_761x471.png)
**FAQ**
1. String str1 = "abc"; System.out.println\(str1 == "abc"\);
步骤:
a> 栈中开辟一块空间存放引用str1;
b> String池中开辟一块空间,存放String常量"abc";
c> 引用str1指向池中String常量"abc";
d> str1所指代的地址即常量"abc"所在地址,输出为true;
1. String str2 = new String\("abc"\); System.out.println\(str2 == "abc"\);
步骤:
a> 栈中开辟一块空间存放引用str2;
b> 堆中开辟一块空间存放一个新建的String对象"abc";
c> 引用str2指向堆中的新建的String对象"abc";
d> str2所指代的对象地址为堆中地址,而常量"abc"地址在池中,输出为false;
注意:对于通过new产生的对象,会先去常量池检查有没有 “abc”,如果没有,先在常量池创建一个 “abc” 对象,然后在堆中创建一个常量池中此 “abc” 对象的拷贝对象;
2. String s2 = new String\(“Hello”\); 产生几个对象?
首先,在jvm的工作过程中,会创建一片的内存空间专门存入string对象。我们把这片内存空间叫做string池;
String s2 = new String\(“Hello”\);jvm首先在string池内里面看找不找到字符串"Hello",如果找到不做任何事情;否则创建新的string对象,放到string池里面。由于遇到了new,还会在内存Heap上(不是string池里面)创建string对象存储"Hello",并将内存上的(不是string池内的)string对象返回给s2。
Re: 如果常量池中原来没有“Hello”, 则创建两个对象。如果原来的常量池中存在“Hello”时,就是一个对象;
3. 其它
\`\`\`
String str1 = "a";
String str2 = "b";
String str3 = str1 + "b";
//str1 和 str2 是字符串常量,所以在编译期就确定了。
//str3 中有个 str1 是引用,所以不会在编译期确定。
//又因为String是 final 类型的,所以在 str1 + "b" 的时候实际上是创建了一个新的对象,在把新对象的引用传给str3
final String str1 = "a";
String str2 = "b";
String str3 = str1 + "b";
//这里和\(3\)的不同就是给 str1 加上了一个final,这样str1就变成了一个常量。
//这样 str3 就可以在编译期中就确定了
\`\`\`
【参考资料】
[https://tech.meituan.com/in\_depth\_understanding\_string\_intern.html](https://tech.meituan.com/in_depth_understanding_string_intern.html) \#\#【深入解析String\#intern -- 美团】
[http://rednaxelafx.iteye.com/blog/774673](http://rednaxelafx.iteye.com/blog/774673)
- java演变
- JDK各个版本的新特性
- JDK1.5新特性
- JDK1.6新特性
- JDK1.7新特性
- JDK1.8新特性
- JAVA基础
- 面向对象特性
- 多态
- 方法重载
- 方法重写
- class
- 常量
- 访问修饰符
- 类加载路径
- java-equals
- 局部类
- java-hashCode
- Java类初始化顺序
- java-clone方法
- JAVA对象实例化的方法
- 基础部分
- JAVA基础特性
- JAVA关键字
- javabean
- static
- 日期相关
- final
- interface
- 函数式接口
- JAVA异常
- 异常屏蔽
- try-with-resource资源泄露
- JAVA引用
- WeakReference
- SoftReference
- PhantomReference
- 位运算符
- try-with-resource语法糖
- JDK冷知识
- JAVA包装类
- JAVA基本类型与包装类
- java.lang.Boolean
- java.lang.Integer
- java.lang.Byte
- java.lang.Short
- java.lang.Long
- java.lang.Float
- java.lang.Double
- java.lang.Character
- 日期相关
- TemporalAdjusters
- String
- 字符串常量池
- String拼接
- String编译期优化
- StringBuilder&StringBuffer
- intern
- 注解
- java标准注解
- 内置注解
- 元注解
- 自定义注解
- 注解处理器
- JVM注解
- Java8 Annotation新特性
- 反射-Reflective
- Reflection
- Class
- Constructor
- Method
- javabean-property
- MethodHandles
- 泛型
- 类型擦除
- bridge-method
- Accessor&Mutator方法
- enum
- JAVA数组
- finalize方法
- JAR文件
- JAVA高级编程
- CORBA
- JMX
- SPI
- Java SPI使用约定
- ServiceLoader
- 实际应用
- IO
- 工具类
- JDK常用工具类
- Objects
- System
- Optional
- Throwable
- Collections
- Array
- Arrays
- System
- Unsafe
- Number
- ClassLoader
- Runtime
- Object
- Comparator
- VarHandle
- 数据结构
- 栈-Stack
- 队列(Queue)
- Deque
- PriorityQueue
- BlockingQueue
- SynchronousQueue
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- ConcurrentLinkedQueue
- 列表
- 迭代器
- KV键值对数据类型
- HashMap
- TreeMap
- Hash冲突
- ConcurrentHashMap
- JDK1.7 ConcurrentHashMap结构
- jdk7&jdk8区别
- 集合
- Vector
- Stack
- HashSet
- TreeSet
- ArrayList
- LinkedList
- ArrayList && LinkedList相互转换
- 线程安全的集合类
- 集合类遍历性能
- 并发容器
- CopyOnWriteArrayList
- ConcurrentHashMap
- 同步容器
- BitMap
- BloomFilter
- SkipList
- 设计模式
- 设计模式六大原则
- 单例模式
- 代理模式
- 静态代理
- 动态代理
- JDK动态代理
- cglib动态代理
- spring aop
- 策略模式
- SpringAOP策略模式的运用
- 生产者消费者模式
- 迭代器模式
- 函数式编程
- 方法引用
- 性能问题
- Lambda
- Lambda类型检查
- Stream
- findFirst和findAny
- reduce
- 原始类型流特化
- 无限流
- 收集器
- 并行流
- AOP
- 静态织入
- aspect
- aspect的定义
- AspectJ与SpringAOP
- 动态织入
- 静态代理
- 动态代理
- JDK动态代理
- CGLib动态代理
- Spring AOP
- SpringAOP五种通知类型
- @Before
- @AfterReturning
- @AfterThrowing
- @After
- @Around
- Aspect优先级
- SpringAOP切点表达式
- within
- execution
- 嵌套调用
- 系统优化与重构
- 重叠构造器模式
- 工具类构造器优化
- 常见面试题
- new Object()到底占用几个字节
- 访问修饰符
- cloneable接口实现原理
- 异常分类以及处理机制
- wait和sleep的区别
- 数组在内存中如何分配
- 类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式
- 类的实例化顺序
- 附录
- JAVA术语
- FAQ
- 墨菲定律
- 康威定律
- 软件设计原则
- 阿姆达尔定律
- 字节码工具
- OSGI