目录
String、StringBuffer和StringBuilder的区别
StringBuffer/StringBuilder常用方法
String、StringBuffer、StringBuilder效率字符拼接效率对比
LocalDate、LocalTime、LocalDateTime的使用
自然排序Comparable与Comparator排序的对比
System类、Math类、BigInteger与BigDecimal
字符串相关类
String类
- String类的声明与初始化
- string s;该语句表示只是声明了一个引用变量,但是并没有初始化引用,所以对变量s的任何操作(除了初始化赋值外) 都将引发异常。
- string s=null;该语句表示声明了一个引用变量并初始化引用,但是该引用没有指向任何对象.但可以把它作为参数传递或其它使用,但是不能调用它作为对象的方法,如toString,getHashCode等。
- string s="";该语句表示声明并引用到一个对象,只不过这个对象为0个字节.所以既然有了对象,就可以调用对象的方法。
- String字符串,用""引起字符串来表示
- String类声明为final的,不可被继承
- String实现了Serializable接口:表示字符串是支持序列化的。
- String实现了Comparable接口:表示String可以比较大小
- String内部定义了final char[] value用于存储字符串数据.
- 被final修饰,也意味着String:代表不可变的字符序列,简称:"不可变性"
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值(这个地址不是常量池,后面"String拼接"会说),不能使用原有的value进行赋值
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中。而new String(String s)这种构造方式创建的对象是在堆内存中,像每个对象一样拥有自己独立的内存空间
- 字符串常量池中是不会存储相同内容的字符串的。因为他底层是HahsTable(到时候将集合会提到)
String不可变性代码演示
- 注意"replace()"与"变量拼接"的字符不是存放在常量池!!!!!!!!!!!!!!!!!!!!
package javase11;
import org.junit.Test;
public class CommonClass1 {
@Test
public void Test1() {
//字面量的定义方式
String s1 = "abc";//在字符串常量池中创建了一个字面量为"abc"的字符串。赋予给s1
String s2 = "abc";//从常量池中获取abc字符
System.out.println(s1 == s2);//true
s1 = "hello";//在字符串常量池中创建了一个字面量为"hello"的字符串。当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
System.out.println(s1 == s2);//false,因为指向常量池的地址不同
System.out.println("*********************");
String s3 = "abc";
String s4 = "abc";
s3 += "def";//当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值(这个地址不是常量池,后面会说),不能使用原有的value进行赋值
System.out.println(s3);//abcdef
String s5 = "abcdef";
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println("*********************");
String s6 = "abc";
String s7 = s6.replace('a', 'm');//把a字符全部改成m字符,也需要重新指定内存区域赋值(这个地址不是常量池),不能使用原有的value进行赋值。
System.out.println(s7);//mbc
String s8 = "mbc";
System.out.println(s6 == s7);//false
System.out.println(s7 == s8);//false
String s9 = "abc" + "def";//当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
System.out.println(s5 == s9);//true
}
}
String不同实例化方式的对比
- 字符串常量存储在字符串常量池,目的是共享
- 字符串非常量对象存储在堆中。
- 面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
- 两个,一个是String类型的对象,存放在堆内存中,这个对象里面有个value值,是一个char数组类型变量,因此需要new一个新的char数组让value值存放该char数组的内存地址.
package javase11;
import org.junit.Test;
import java.io.ObjectStreamField;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class CommonClass2 {
@Test
public void Test1() {
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中,s1、s2存的都是指向常量池的地址值。
String s1 = "javaEE";
String s2 = "javaEE";
//new String()内存地址执行的是堆内存
/*本质上this.value = new char[0];
这个String构造器会给String类型对象的private final char value[]变量赋予一个长度为0的char[]数组,没有任何意义*/
String s3 = new String();
// s3.charAt(0);//下标越界异常
/*底层是this.value = s.value;
这个String构造器会把String类型对象s的value值赋予String类型对象s2的private final char value[]变量,
或者把String类型对象s存的常量池地址值赋予String类型对象s2的private final char value[]变量*/
// String s4 = new String(String s);
/*底层是this.value = Arrays.copyOf(value, value.length);
* 这个String构造器会把传入的char[]数组拷贝成一份新的数组赋予给String类型对象s3的private final char value[]变量*/
// String s5 = new String(char[] a);
/*这个String构造器会把传入的char[]数组指定下标,从下标开始挑多少个字符创建一个数组,
赋予给String类型对象s4的private final char value[]变量*/
// String s6 = new String(char[] a,int startIndex,int count);
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s7 = new String("javaEE");
String s8 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s7);//false
System.out.println(s1 == s7);//false
System.out.println(s7 == s8);//false
System.out.println("***********************");
Person p1 = new Person("Tom", 12);
Person p2 = new Person("Tom", 12);
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true,证明他们引用的都是常量池的"Tom"字符串
}
}
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.age = age;
this.name = name;
}
}
String不同拼接操作的对比(String不可变性补充)
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中
- 如果拼接的结果调用intern()方法,返回值就在常量池中
package javase11;
import org.junit.Test;
public class CommonClass3 {
@Test
public void Test1() {
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false,只要其中有一个是变量,结果就在堆中
final String s4 = "javaEE";//被final修饰后的s4是常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true,常量与常量的拼接结果在常量池。
}
@Test
public void test2(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";//常量与常量的拼接结果在常量池。
String s5 = s1 + "hadoop";//s1是变量,因此结果一定在堆
String s6 = "javaEE" + s2;//s2是变量.因此结果一定在堆
String s7 = s1 + s2;//s1,s2都是变量,因此结果一定在堆
System.out.println(s3 == s4);//true,
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s5 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
String s8 = s5.intern();//.intern可以把里面的字符串存到常量池中返回一个常量池地址给s8
System.out.println(s3 == s8);//true
String s9 = (s2+s1).intern();//与上同理
System.out.println(s9 == "hadoopjavaEE");//true
}
}
String使用陷阱及String类的关于浅复制的面试题
package javase11;
import org.junit.Test;
public class CommonClass4 {
@Test
public void Test1() {
/*在常量池声明一个字面量为"0"的字符串赋予给s*/
String s = "0";
/*实际上原来的“0”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s+“1”(也就是"01")。
如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。
如果这样的操作放到循环中,会极大影响程序的性能。*/
s = s + "1";
for (int i = 2; i <= 5; i++) {
s = s + i;
System.out.println(s);
}
/*因此可以直接在常量池中创建一个字面量为01的字符串*/
String s1 = "01";
/*也可以常量池两个常量拼接,后面自动创建一个"01"的字符串*/
String s2 = "0" + "1";
/*又或者堆空间的s3对象在调用intern()之后,会将常量池中已经存在的"012"字符串赋值给s4。*/
String s3 = new String("3");
String s4 = s1 + "2" + s3;
String s5 = s4.intern();
System.out.println(s5 == "0123");//true
/*创建了一个String类型对象,里面的value存的是指向常量池的中的字面量"0",后续操作会在堆空间产生一个字符串s5+"1",也就是("01").
* 如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。
* 如果这样的操作放到循环中,会极大影响程序的性能。*/
String s6 = new String("0");
for (int i = 1; i <= 5; i++) {
s6 += i;
System.out.println(s6);
}
}
}
/*关于String类的值传递之浅复制的面试题*/
class StringTest {
String str = new String("good");//1.1创建一个String类型对象,由变量str保存指向堆内存的地址
char[] ch = { 't', 'e', 's', 't' };//1.2创建一个char[]数组,由变量ch保存指向堆内存的地址
public void change(String str, char ch[]) {//2.1此时方法的形参列表的变量str和变量ch就会接受到传入指向堆内存地址值
str = "test ok";//2.2这里只是改变形参str存储的堆内存地址值
ch[0] = 'b';//这里是通过指向堆内存地址的下标改变其元素字符为"b"
// ch = new char[5];//这才是改变形参ch存储的堆内存地址值,这里就不再做演示
}
public static void main(String[] args) {
StringTest ex = new StringTest();//1.创建一个对象
ex.change(ex.str, ex.ch);//2.将对象里的属性传入到change()方法种
System.out.println(ex.str);//3.1,good
System.out.println(ex.ch);//3.2,best
}
}
JVM中涉及字符串的内存结构
JVM运行时各数据区
- 方法区(类资源,常量,静态资源)和堆内存都是线程共享的
- 虚拟机栈、本地方法栈‘程序计数器都是线程私有的
三种JVM虚拟机介绍
- 随着jdk版本的更替Java虚拟机规范也会发生变化,会落实到具体的JVM虚拟机,虚拟机不止一个,还有不同公司的虚拟机
- 下图三种是使用比较多的JVM虚拟机,通常都是HotSpot
- 不同的虚拟机会对具体的问题有不同的优化处理,例如用Java写的代码在Android上运行,Dalvik是Google公司自己设计用于Android平台的Java虚拟机
Heap堆
- 一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为三部分:
- Young Generation Space 新生区
- Tenure Generation Space 养老区
- Permanent Space 永久存储区 #这里灰色的原因:
String常用方法
-
普通方法
- int length():返回字符串的字节:return value.length
- char charAt(int index):返回某索引处的字符return value[index]
- boolean isEmpty():判断是否是空字符串,其源码就是return value.length == 0;
但是不能判断null,如果String s=null,那么s.isEmpty()会出现空指针异常.原因:String s = null只是声明了s引用变量并初始化引用变量,但是却没有指向任何对象,因此不能调用他作为一个对象的任何方法 - String toLowerCase():使用默认语言环境,将String中的所有字符转换为小写
- String toUpperCase():使用默认语言环境,将String中的所有字符转换为大写
- String trim():返回字符串的副本,去除字符串两端的空格。
- boolean equals(Object obj):比较字符串的内容是否相同,String类重写了Object类的equal方法
- boolean equals(Object obj):比较字符串的内容和类型是否相同
- boolean equalsIgnoreCase(String anotherString):忽略大小写来比较字符串的内容是否相同
- String concat(String str):将指定字符串连接到此字符串的结尾。等价于用“+”
- int compareTo(String anotherString):比较两个字符串的大小,从左到右截取两个字符串中最小的长度,然后将截取的字符串的每个字符解析成Unicode码从左到右一一对照比较,如果完全相等则返回0;如果String从左往右有一个字符比对应anotherString的一个字符大,则返回值一定是一个正数,返回值=String字符-anthoerString字符,后面就不再做对比了;如果String从左往右有一个字符比对应anotherString的一个字符小,则返回值一定是一个负数,后面就不再做对比了,返回值=String字符-anthoerString字符;
- int compareToIgnoreCase(String anotherString):在忽略大小写的情况下比较两个字符串的大小
- String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
- String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。java特性,含头不含尾
- boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束,一般用作检查文件类型
- boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
- boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
- boolean contains(CharSequence s):此字符串包含指定的 char 值序列时,返回 true
- int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
- int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
- int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
- int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
- 提示:indexOf和lastIndexOf方法如果未找到都是返回-1
-
替换:
- String replace(char oldChar, char newChar):返回一个新的字符串,新字符串是用新的字符替换掉旧字符串里的就字符得来的
- String replace(CharSequence target, CharSequence replacement):使用指定的字符串replace