-
常用类概述
-
String类概述
-
理解String类的不可变性
-
String不同实例化方式的对比
-
String不同拼接方式的对比
-
String的一道面试题
-
JVM中涉及字符串的内存结构
-
String的常用方法1
-
String的常用方法2
-
String的常用方法3
-
回顾String与基本数据类型包装类的转换
-
String与char[]的转换
-
String与byte[]的转换
-
解决一个拼接问题
-
StringBuffer与StringBuilder的介绍
-
StringBuffer的源码分析
-
StringBuffer的常用方法
-
String、StringBuffer、StringBuilder的效率对比
-
System类中获取时间戳的方法
-
Java中两个Date类的使用
1,常用类概述
字符串相关的类:String及常用方法、StringBuffer、StringBuilder JDK8之前的日期时间API:System静态方法、Date类、Calendar类、SimpleDateFormat类 JDK8中新的日期时间API:LocalDate、LocalTime、LocalDateTime、Instant、DateTimeFormatter Java比较器:Comparable接口、Comparator接口 System类、Math类、BigInteger与BigDecimal2,String类概述
定义在java.lang下: public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; } String代表字符串,用一对""表示 String类声明为final,表示不可被继承 实现了Serializable接口,表示字符串可以被序列化;实现了Comparable接口,表示字符串之间可以比较大小 存储字符串的实际是String内部的char类型数组,该数组被声明为final,表示这是一个常量,既不能被重新赋值,数组的元素也不能被修改3,理解String类的不可变性
package com.atguigu.java;
import org.junit.Test;
/**
* 通过字面量的方式(区别于new)给一个字符串引用变量赋值,此时该字符串声明在字符串常量池中(方法区)
* 字符串常量池不会存储相同内容的字符串
*/
public class StringTest {
@Test
public void test1() {
String s1 = "abc"; // 字面量的方式,其实是一个对象
String s2 = "abc";
System.out.println(s1);
System.out.println(s1 == s2); // 比较两个对象的地址值,true,说明是同一对象
// 如果s1是一个基本数据类型,重新赋值就是将s1所在内存原来的值变为新的值
s1 = "hello"; // 这个重新赋值的过程,不是将字符串abc的值改变了,而是在字符串常量池中新创建了hello的字符串
String s3 = "abc";
s3 += "def"; // 同上,在字符串常量池中新建了字符串abcdef
String s4 = "abc";
String s5 = s4.replace("a", "m"); // 替换字符
System.out.println(s4); // abc,字符串内部的char类型数组的元素也不能改变
System.out.println(s5); // mbc
}
}
4,String不同实例化方式的对比
通过字面量方式创建的字符串实际也是一个对象,存放在方法区的字符串常量池中,s1和s2是引用数据类型,存储的是字符串对象的地址值,而在字符串常量池中相同的字符串只有一份,所以s1和s2是相等的 通过new+构造器的方式创建的字符串对象存放在堆空间中,这里有两个对象,它们都有一个char数组类型的属性(引用数据类型)来存储字符串对象“javaEE”,而这个字符串就是常量池中的那个字符串package com.atguigu.java;
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
}
Person p1 = new Person("Tom", 12); // 以字面量的方式赋值
Person p2 = new Person("Tom", 12);
System.out.println(p1.name.equals(p2.name)); // String重写过equals(),true
System.out.println(p1.name == p2.name); // true,字符串“Tom”只有一份,存在于字符串常量池
p1.name = "Jerry";
System.out.println(p2.name); // Tom,字符串不可被修改
面试题:String s = new String("abc");在内存中创建了几个对象?
两个:一个是在堆空间中new的对象,另一个是char[]对应的在字符串常量池中的数据:"abc"。如果之前常量池中已有"abc",那就只会在堆空间中创建一个对象
5,String不同拼接方式的对比
@Test
public void test3() {
String s1 = "javaEE"; // 字面量/常量
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop"; // 以字面量/常量方式的拼接,其结果“javaEEhadoop”在字符串常量池中,所以s3和s4是同一个字符串对象
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2; // 只要出现了变量方式的拼接,其结果就在堆空间中,当然s5、s6、s7指向的是不同的对象,它们中的
// char类型数组存储的字符串对象又放在常量池中
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 = s5.intern(); // 不管是s3还是s5,返回的结果(字符串)声明在字符串常量池中
System.out.println(s3 == s8); // true,二者指向的是同一个“javaEEhadoop”
}
6,String的一道面试题
package com.atguigu.exer;
public class StringTest {
String str = new String("good");
char[] ch = { 't', 'e', 's', 't' };
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and "); // 将属性str存储的地址值传递给了局部变量str,另一个字
// 符串的地址值赋值给了局部变量str,属性str并为受到影响,这和通过
// str修改对象属性有区别
System.out.println(ex.ch); // good and best。修改了char类型数组中的元素,String中char类型数组的
// 元素为什么不能被修改?因为该数组被定义为final
}
}
7,JVM中涉及字符串的内存结构
字符串常量池在内存中的位置随JVM不同版本、JDK版本变化,具体到讲解JVM再谈8,String的常用方法1
package com.atguigu.java;
import org.junit.Test;
/**
* int length():返回字符串的长度:return value.length
* char charAt(int index):返回某索引处的字符return value[index]
* boolean isEmpty():判断是否是空字符串:return value.length == 0
* 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):比较两个字符串的大小,是String实现的Comparable接口中的方法
* String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串
* String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串
*/
public class StringMethodTest1 {
@Test
public void test1() {
String s1 = "HelloWorld";
System.out.println(s1.length()); // 字符串长度,就是内部char类型数组的长度
System.out.println(s1.charAt(2)); // 0-9,返回内部数组指定索引的元素
System.out.println(s1.isEmpty()); // 判断字符串是否为空,实际是判断内部数组长度是否为0
String s2 = s1.toLowerCase(); // 将字符串转换为小写字母,不是直接在s1上操作的,因为字符串不可变
System.out.println(s1); // HelloWorld
System.out.println(s2); // helloworld
String s3 = " he llo world ";
String s4 = s3.trim();
System.out.println("-----" + s3 + "-----"); // ----- he llo world -----
System.out.println("-----" + s4 + "-----"); // -----he llo world-----
}
@Test
public void test2() {
String s1 = "HelloWorld";
String s2 = "helloworld";
System.out.println(s1.equals(s2)); // false
System.out.println(s1.equalsIgnoreCase(s2)); // true
String s3 = "abc";
String s4 = s3.concat("def");
System.out.println(s4); // abcdef
String s5 = "abc";
String s6 = new String("abe"); // 都是字符串,与创建方式无关
System.out.println(s5.compareTo(s6)); // 从前往后比较两个字符串中的每个字符,它们
// 之间做减法运算,c-e=-2,所以返回-2。返回值小于0表示s5小于s6,
// 返回0表示内容相同
String s7 = "北京尚硅谷教育";
String s8 = s7.substring(2);
String s9 = s7.substring(2, 5);
System.out.println(s7); // 北京尚硅谷教育
System.out.println(s8); // 尚硅谷教育
System.out.println(s9); // 尚硅谷
}
}
9,String的常用方法2
package com.atguigu.java;
import org.junit.Test;
/**
* boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
* boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
* boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
* boolean contains(CharSequence s):判断一个字符串中是否包含一个子串
* int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
* int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
* int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
* int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
* 注:indexOf和lastIndexOf方法如果未找到都是返回-1
*/
public class StringMethodTest2 {
@Test
public void test1() {
String str1 = "helloworld";
boolean b1 = str1.endsWith("rld");
System.out.println(b1); // true
boolean b2 = str1.startsWith("He");
System.out.println(b2); // false
boolean b3 = str1.startsWith("ll", 2);
System.out.println(b3); //true
String str2 = "wo";
System.out.println(str1.contains(str2)); // true
System.out.println(str1.indexOf("lo")); // 3
System.out.println(str1.indexOf("lo", 5)); // -1
String str3 = "hellorworld";
System.out.println(str1.lastIndexOf("or")); // 7
System.out.println(str1.lastIndexOf("or", 6)); // 4
}
}
10,String的常用方法3
package com.atguigu.java;
import org.junit.Test;
/**
* String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的
* String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
* String replaceAll(String regex, String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串
* String replaceFirst(String regex, String replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串
* 匹配:
* boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
* 切片:
* String[] split(String regex):根据给定正则表达式的匹配拆分此字符串
* String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
*/
public class StringMethodTest3 {
@Test
public void test1() {
String str1 = "北京北京尚硅谷教育";
String str2 = str1.replace('北', '东');
String str3 = str1.replace("北京", "上海");
System.out.println(str1); // 北京北京尚硅谷教育
System.out.println(str2); // 东京东京尚硅谷教育
System.out.println(str3); // 上海上海尚硅谷教育
String str4 = "12hello34world5java67mysql89";
String str5 = str4.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
System.out.println(str5); // hello,world,java,mysql
String str6 = "123456";
boolean matches = str6.matches("\\d+");
System.out.println(matches); // true,全由数字组成
String tel = "0571-4534786";
boolean result = tel.matches("0571-\\d{7,8}");
System.out.println(result); // true,由0571-开头,接着7-8个数字
String str7 = "hello|world|java";
String[] strs = str7.split("\\|");
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]); // hello world java
}
}
}
11,回顾String与基本数据类型包装类的转换
@Test
public void test1(){
String str1 = "123";
// int num = (int)str1;//错误的
int num = Integer.parseInt(str1);
String str2 = String.valueOf(num);//"123"
String str3 = num + "";
System.out.println(str1 == str3); // false,一个在方法去,一个在堆
}
12,String与char[]的转换
@Test
public void test2() {
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for(int i = 0 ; i < charArray.length ; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{'h', 'e', 'l', 'l', 'o'};
String str2 = new String(arr);
System.out.println(str2);
}
13,String与byte[]的转换
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes(); // 编码。使用当前默认的utf8字符集进行转换
System.out.println(Arrays.toString(bytes)); // [97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
byte[] gbks = str1.getBytes("gbk"); // 可能出现异常
System.out.println(Arrays.toString(gbks)); // [97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
String str2 = new String(bytes); // 使用当前默认字符集utf8解码
System.out.println(str2); // abc123中国
String str3 = new String(gbks);
System.out.println(str3); // 出现乱码,编码解码字符集不一致
String str4 = new String(gbks, "gbk");
System.out.println(str4); // 使用对应的字符集解码就不会出现乱码
}
14,解决一个拼接问题
@Test
public void test4() {
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3); // false,s2是一个变量
final String s4 = "javaEE"; // s4是一个常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5); // true,即使是用变量的方式使用s4,但它是一个字面量/常量
}
15,StringBuffer与StringBuilder的介绍
String:不可变的字符序列,JDK1.0 StringBuffer:可变的字符序列,JDK1.0,线程安全,效率较低 StringBuilder:可变的字符序列,JDK5.0,线程不安全,效率较高,与StringBuffer的唯一区别就是方法没加synchronized 都是使用char类型数组存储字符串,String的声明为final,后两者则没有final16,StringBuffer的源码分析
对于String,创建一个空字符串时,对应就创建一个长度为0的char类型数组;创建一个非空字符串,对应就创建相应长度的char类型数组,并将字符存在数组中 String str = new String(); // char[] value = new char[0]; String str1 = new String("abc"); // char[] value = new char[]{'a', 'b', 'c'}; StringBuffer和StringBuilder的内存结构是相同的 以StringBuffer为例 StringBuffer sb1 = new StringBuffer(); // char[] value = new char[16]; 创建空字符串时,调用StringBuffer的空参构造器,默认创建的是一个长度为16的char类型数组 上面调用的父类构造器,创建长度为16的char类型数组并将其赋值给value 调用append方法给StringBuffer类型字符串添加字符,这点正说明了StringBuffer和StringBuilder是可变的 sb1.append('a'); // value[0] = 'a'; sb1.append('b'); // value[1] = 'b'; 创建非空字符串时,调用另一个构造器,创建的char类型的数组长度是字符串的长度+16,再调用append方法将字符串添加到该数组中 StringBuffer sb2 = new StringBuffer("abc"); // char[] value = new char["abc".length() + 16];,创建了一个长度为19的char类型数组 问题一:System.out.println(sb2.length()); // 3 字符串的length()返回字符串的长度,而不是内部char类型数组的长度;当刚创建空字符串sb1是,调用length()返回0,sb2中存了abc,所以返回3 问题二:当字符串内部数组满了时,再添加字符串就要先扩容数组。默认情况下创建一个新的数组,长度为原来数组长度的两倍(左移一位)+2,然后将原来数组的值复制到新数组中 开发中建议使用可以指定初始字符串长度的更灵活的StringBuffer(int capacity)和StringBuilder(int capacity),根据效率和是否需要线程安全选择其中一种
17,StringBuffer的常用方法
StringBuilder与其方法一样,只是未同步package com.atguigu.java;
import org.junit.Test;
/**
* 可变的字符串,所有操作都会改变原来的字符串
* StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
* 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() :把当前字符序列逆转
* public int indexOf(String str)
* public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
* public int length()
* public char charAt(int n )
* public void setCharAt(int n ,char ch):将指定索引位置的元素改为ch
*/
public class StringBufferBuilderTest {
@Test
public void test1() {
StringBuffer s1 = new StringBuffer("abc");
s1.append(1); // 相当于使用+连接的方式
s1.append('2');
System.out.println(s1); // abc12
// s1.delete(2, 4);
// System.out.println(s1); // ab2
// s1.replace(2, 4, "hello"); // abhello2
// System.out.println(s1);
// s1.insert(2, false);
// System.out.println(s1); // abfalsec12
// System.out.println(s1.length()); // 10
// s1.reverse();
// System.out.println(s1); // 21cba
String s2 = s1.substring(1, 3);
System.out.println(s1); // abc12
System.out.println(s2); // bc
s1.setCharAt(1, 'c');
System.out.println(s1); // acc12
}
}
以上的方法支持方法链的操作。比如s1.append('a').append('b').append('c');因为append方法在字符串尾部加完字符后返回调用该方法的对象
总结:
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length()
遍历:for() + charAt() / toString()
18,String、StringBuffer、StringBuilder的效率对比
@Test
public void test2(){
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));
}
执行结果,不考虑线程安全的情况下,尽量使用StringBuilder
StringBuffer的执行时间:13
StringBuilder的执行时间:2
String的执行时间:2238
19,System类中获取时间戳的方法
@Test
public void test1() {
// 时间戳:返回1970年1月1日0时0分0秒到当前时间的时间差,以毫秒表示
long time = System.currentTimeMillis();
System.out.println(time);
}
20,Java中两个Date类的使用
package com.atguigu.java;
import org.junit.Test;
import java.util.Date;
/**
* java.sql.Date类继承java.util.Date类,前者对应数据库下日期类型的变量,与数据库交互时才会使用
*/
public class DateTimeTest {
@Test
public void test2() {
Date date1 = new Date(); // java.util.Date,调用空参构造器创建当前时间的Date对象
System.out.println(date1.toString()); // Wed Jul 07 22:31:50 CST 2021,重写过toString()
System.out.println(date1.getTime()); // 获取该对象的时间戳,1625668310092
Date date2 = new Date(1625668310092L); // 创建指定时间戳的Date对象
System.out.println(date2.toString()); // Wed Jul 07 22:31:50 CST 2021
java.sql.Date date3 = new java.sql.Date(1625668310092L); // 创建java.sql.Date对象
System.out.println(date3); // 2021-07-07
// 任何将java.util.Date对象转换为java.sql.Date对象
//情况一:
// Date date4 = new java.sql.Date(2343243242323L);
// java.sql.Date date5 = (java.sql.Date)date4;
//情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}
}