文章目录
- 1. String
- 1.1 String的特性
- 1.2 String对象创建
- 1.2.1 字符串的特性
- 1.2.2 intern()
- 1.2.3 String使用细节
- 1.3 String常用方法
- 1.4 String类型转换
- 1.4.1 String与包装类
- 1.4.2 String与char[]数组
- 1.4.3 String与Byte[]数组
- 2. StringBuffer与StringBuilder
- 2.1 字符串构造器源码
- 2.2 构造器
- 2.3 常用方法
- 2.4 StringBuffer Stringbuilder String 之间转化
- 2.4 面试题
- 2.6 算法题
- 3. JDK8之前日期时间API
- 3.1 java.lang.System类
- 3.2 java.util.Date类
- 3.3 java.text.SimpleDateFormat类
- 3.4 java.util.Calendar(日历)类
- 4. JDK8之后日期时间API
- 4.1 LocalDate、LocalTime、LocalDateTime
- 4.2 Instant
- 4.3 DateTimeFormatter
- 4.4 其他API
- 4.5 与传统API日期处理的转换
- 5. Java比较器
- 5.1 java.lang.Comparable
- 5.2 java.util.Comparator
- 6. System类
- 7. Math类
- 7.1 求最大值最小值和绝对值
- 7.2 求整运算
- 7.3 三角函数运算
- 7.4 指数运算
- 8. BigInteger
- 9. BigDecimal
1. String
1.1 String的特性
-
String是final类型对象,不可被继承。且代表不可变的字符序列。当字符串变量值发生改变,会在方法区的字符串常量池中重新开辟空间进行赋值
字符串是常量,存储在方法区的字符串常量池。同一字符串在常量池中独一份。且常量池中已经存在的字符串不容许任何修改(不可变性)。
所以常常出现String类型变量由于内容相同,指向常量池的同一空间。
-
String实现了Serializable接口:表示字符串是支持序列化的。
-
String实现了Comparable接口:表示String可以比较大小
-
String底层使用value数组存储字符串,并且是final类型数组(不可变性)
1.2 String对象创建
String str = "hello";
//本质上this.value = new char[0];
String s1 = new String();
//this.value = original.value;
String s2 = new String(String original);
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);//以数组a下标为startIndex字符开始,长度为count的串构造新的String
String对象的创建有两种方式,第一种是直接赋值,第二种是通过构造器的方式,这两种创建的String对象指向的空间是不同的。第一种是指向(方法区的)字符串常量池,第二种是指向堆空间。但字符串最终实际都是存储在字符串常量池中的。(不同的版本字符串常量池位置不同)
下面来看一段代码说说区别在哪?
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
直接赋值的方式,s1和s2指向字符串常量池。
而构造器创建的方式,s3和s4指向堆空间,堆空间开辟String对象,String对象的value属性存储地址值指向字符串常量池的字符串。也就是说用构造器的方式创建String类在内存中一共创建两个对象图形化来说就是:
总结:字符串实际存储位置是方法区的字符串常量池,但不同方式创建的String对象,可能导致对象值不同(类类型对象村的是地址)
加强理解:
@Test
public void test4(){
String s1="woaibiancheng";
String s2="woai";
String s3=s2+"biancheng";
System.out.println(s1==s3);//false
final String s4="woai";
String s5=s4+"biancheng";
System.out.println(s1==s5);//true
}
为什么?final 修饰的String 变量与未修饰的String变量的区别
在class文件中:
直接看下一节,也有总结!
下面思考一个问题
//Person类有name和age两个属性
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);
System.out.println(p1.name == p2.name);//false还是true?
关键得看Person类构造器是怎么对name构造的,
第一种name通过String构造器创建的方式—false
public Person(String name,int age){
this.name=new String(name);
this.age=age;
}
第二种name通过直接赋值—true
public Person(String name, int age) {
this.name = name;
this.age = age;
}
总的一句话就是new就是要在String中重新开辟空间。
1.2.1 字符串的特性
常量和常量拼接的返回的是拼接后字符串在方法区的字符串常量池中的地址。如果字符串拼接涉及String类型的变量,那么就相当于new,在堆区开辟String空间,指向常量区
所以看下面的例子
@Test
public void test3(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == 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 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
}
@Test
public void test4(){
String s1="woaibiancheng";
String s2="woai";
String s3=s2+"biancheng";
System.out.println(s1==s3);//false
final String s4="woai";
String s5=s4+"biancheng";
System.out.println(s1==s5);//true
}
1.2.2 intern()
如果常量池中有对应字符串,则直接返回该字符串的引用。如果
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
String s = new String("1") + new String("1");
String s2 = s.intern();
String s1 = "11";
System.out.println(s == s1);
System.out.println(s2 == s1);
System.out.println(s2 == s);
// true,true,true
1.2.3 String使用细节
String s1 = “a”;
说明:在字符串常量池中创建了一个字面量为"a"的字符串。
s1 = s1 + “b”;
说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+“b”(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。
String s2 = “ab”;
说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。
String s3 = “a” + “b”;
说明:s3指向字符串常量池中已经创建的"ab"的字符串。
String s4 = s1.intern();
说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。
1.3 String常用方法
-
int length()
:返回字符串的长度 -
char charAt(int index)
: 返回某索引处的字符 -
boolean isEmpty()
:判断是否是空字符串 -
String toLowerCase()
:使用默认语言环境,将 String 中的所有字符转换为小写 -
String toUpperCase()
:使用默认语言环境,将 String 中的所有字符转换为大写 -
String trim()
:返回字符串的副本,去除前导空白和尾部空白 -
boolean equals(Object obj)
:比较字符串的内容是否相同 -
boolean equalsIgnoreCase(String anotherString)
:与equals方法类似,忽略大小写 -
String concat(String str)
:将指定字符串连接到此字符串的结尾。 -
int compareTo(String anotherString)
:比较两个字符串的大小A.compareTo(anotherString)
:定义下标k,从前向后逐个比较字符大小- 如果有字母差异,返回
this.charAt(k)-anotherString.charAt(k)
。 - 当k到达较短字符串末尾,字母均相同,返回
this.length()-anotherString.length()
总结:逐个按照字母比较,字母小的放前面。如果无法出结果,字符串短的放前面
如果返回负数,则当前字符串小。如果返回正数,则当前字符串大。(用于字符串排序)
- 如果有字母差异,返回
-
String substring(int beginIndex)
:返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个字符的子字符串。 -
String substring(int beginIndex, int endIndex)
:返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。(左闭右开) -
boolean endsWith(String suffix)
:测试此字符串是否以指定的后缀结束(指定的后缀可以是多个字符) -
boolean startsWith(String prefix)
:测试此字符串是否以指定的前缀开始(指定的后缀可以是多个字符) -
boolean startsWith(String prefix, int toffset)
:测试此字符串从指定索引开始的子字符串是否以指定前缀开始 -
boolean contains(CharSequence s)
:当且仅当此字符串包含指定的 char 值序列时,返回 true -
int indexOf(String str)
:返回指定子字符串在此字符串中第一次出现处的索引,没有则返回-1 -
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)
:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 -
String replace(CharSequence target, CharSequence replacement)
:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。 -
String replaceAll(String regex, String replacement)
: 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。String str = "12hello34world5java7891mysql456"; //把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉 String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", ""); System.out.println(string);
-
String replaceFirst(String regex, String replacement)
: 使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 -
boolean matches(String regex)
:告知此字符串是否匹配给定的正则表达式。常用于匹配输入是否符合预定格式
String str = "12345"; //判断str字符串中是否全部有数字组成,即有1-n个数字组成 boolean matches = str.matches("\\d+"); System.out.println(matches); String tel = "0571-4534289"; //判断这是否是一个杭州的固定电话 boolean result = tel.matches("0571-\\d{7,8}"); System.out.println(result);
-
String[] split(String regex)
:根据给定正则表达式的匹配拆分此字符串。String str = "hello|world|java"; String[] strs = str.split("\\|"); for (int i = 0; i < strs.length; i++) { System.out.println(strs[i]); } System.out.println(); String str2 = "hello.world.java"; String[] strs2 = str2.split("\\."); for (int i = 0; i < strs2.length; i++) { System.out.println(strs2[i]); }
-
String[] split(String regex, int limit)
:根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
1.4 String类型转换
1.4.1 String与包装类
我建议基本数据类型直接当包装类处理,这样只需要记住包装类和String之间的转换了,也就是toString和包装类的构造器,就能完成三者之间的转换了
1.4.2 String与char[]数组
-
字符数组–>字符串
调用String 类的构造器:
String(char[])和String(char[],int offset,int length)
分别用字符数组中的全部字符和部分字符创建字符串对象。 -
字符串–>字符数组
public char[] toCharArray()
:将字符串每个字符转化为字符数组中的元素public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
:提供了将指定索引范围内的字符串存放到数组中的方法。
1.4.3 String与Byte[]数组
涉及字符集----
-
Byte[]数组–>字符串
String(byte[])
:通过使用平台的默认字符集解码指定的 byte 数组,构造 String。String(byte[],int offset,int length)
:从字节数组起始位置offset开始取length个字节构造一个字符串对象。 -
字符串–>Byte[]数组
public byte[] getBytes()
:使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中。public byte[] getBytes(String charsetName)
:使用指定的字符集将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码,将字符串转化为Bytes数组。
System.out.println("Arrays.toString(bytes) = " + Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码,将字符串转化为Bytes数组。
System.out.println("Arrays.toString(gbks) = " + Arrays.toString(gbks));
String str2 = new String(bytes);//使用默认的字符集,进行解码。
System.out.println("str2 = " + str2);
String str3 = new String(gbks);
System.out.println("str3 = " + str3);//出现乱码。原因:编码集和解码集不一致!
String str4 = new String(gbks, "gbk");
System.out.println("str4 = " + str4);//没有出现乱码。原因:编码集和解码集一致!
}
输出结果:
Arrays.toString(bytes) = [97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
Arrays.toString(gbks) = [97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
str2 = abc123中国
str3 = abc123�й�
str4 = abc123中国
2. StringBuffer与StringBuilder
String表示的是不可变字符序列,而StringBuffer和StringBuilder代表可变的字符序列。
StringBuffer和StringBuilder几乎一致,唯一区别就是线程是否安全
先总体了解String、StringBuffer、StringBuilder三者的导同(字符序列是否可变+线程是否安全)
- String:不可变的字符序列。效率最差
- StringBuffer: 可变的字符序列。线程安全教率低(内部方法有synchronized修饰)
- StringBuilder:可变的字符疗列。线程不安全效率高(JDK5.0新增)
三者都使用底层使用value字符数组存储。三者的value数组是否有final修饰决定对应字符串是否可变。String的value字符数组为不可变数组,有final修饰。StringBuffer和StringBuilder的value字符数组没有final修饰。不可变字符序列就是每次堆字符串修改都需要重新开辟空间,反之就是可变字符序列(有时候要重新开辟,有时候不用,下面源码分析有讲哦)
下面用代码来比较效率–
/*
对比String、StringBuffer、StringBuilder三者的效率:
从高到低排列:StringBuilder > StringBuffer > String
*/
@Test
public void test3(){
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
}
2.1 字符串构造器源码
创建不同的字符序列的底层–
//String按需开辟
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
//StringBuffer和StringBuilder后面具体分析
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//0 相当于输出有效长度
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
字符串的构造,String是按需开辟空间。StringBuffer和StringBuilder构造器创建字符串会预留16个字符空间。(因为他们可以指定开辟多少空间)
下面我们分析StringBuffer源码
对于无参构造:
以String为参构造:
StringBuffer和StringBuilder构造器首次创建会开辟当前所需空间+16的空间
当空间不够了咋整–看append源码
append功能是尾插字符串,每次插入检查空间是否足够:
ensurecapacityInternal
, 空间不够则开辟更大的空间复制数组,然后将尾插的字符串尾插上去。
这里看看新开辟的空间的大小的讲究:newCapacity 一般来说扩容一倍再加2,如果还不够就直接按照新的字符串长度来开辟(第一个if)接着return语句,newCapacity可能通过移位运算超出范围,成为负数等等在对新容量进行考虑
总结扩容问题:如果要添加的数据底层数组容量不足,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
由于append每次扩容开销大所以:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
回答StringBuffer和Stringbuilder内存解析主要从构造器开辟空间和append函数两个方面来谈
2.2 构造器
二者构造器类似
-
StringBuffer() StringBuilder()
:初始容量为16的字符串缓冲区 -
StringBuffer(int size) StringBuilder(int size)
:构造指定容量的字符串缓冲区 -
StringBuffer(String str) StringBuilder(String str)
:将内容初始化为指定字符串内容
为了避免频繁扩容,我们常使用第二个指定空间创造可变字符序列
2.3 常用方法
这里只列举比String类多出来的方法(当然个别string特有的方法StringBuffer和StringBuilder没有)
-
StringBuffer append(xxx)
:提供了很多重载的append()方法,用于进行字符串拼接,任何类型都按照字符面值尾插,甚至Boolean类型append中返回值是return this,所以支持方法链的使用,
-
StringBuffer delete(int start,int end)
:删除指定位置的内容,左闭右开 -
StringBuffer replace(int start, int end, String str)
:把[start,end)位置替换为str -
StringBuffer insert(int offset, xxx)
:在指定位置插入xxx -
StringBuffer reverse()
:把当前字符序列逆转
如果当修改中空间出现不足,都会扩容如append和insert方法
2.4 StringBuffer Stringbuilder String 之间转化
-
StringBuffer,StringBuilder转化为String:调用String的构造器或调用StringBuffer,Stringbuilder的toString()函数返回String值
-
String转化为Stringbuffer,StringBuilder:调用StringBuffer,Stringbuilder的构造器
-
可见StringBuffer和StringBuilder之间的转化完全可以以String之间为桥梁
2.4 面试题
append源码分析
程序输出
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length());
System.out.println(sb);
StringBuffer sb1 = new StringBuffer(str);
System.out.println(sb1);
结果: