String 类声明为 final 的,不可被继承。
String 类实现了 Serializable 接口:表示字符串是支持序列化的。实现了 Comparable 接口:表示 String 可以比较大小的。
String 内部定义了 final char[] value 用于存储字符串数据。( JDK8 与 JDK8 之前版本 )
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 ... }
String:代表不可变的字符序列。简称:不可变性。
体现:
(1)当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行赋值。
(2)当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
(3)String 的 replace() 方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
字符串常量池中是不会存储相同内容的字符串的。
public class test1{ public static void main(String[] args){ // 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。 String s1 = "abc"; //字面量的定义方式 String s2 = "abc"; System.out.println(s1 == s2); //比较s1和s2的地址值:true s1 = "hello"; System.out.println(s1 == s2); //比较s1和s2的地址值:false System.out.println(s1); //hello System.out.println(s2); //abc // 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。 String s3 = "abc"; s3 += "def"; System.out.println(s3); //abcdef System.out.println(s2); //abc // String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。 String s4 = "abc"; String s5 = s4.replace('a', 'm'); System.out.println(s4); //abc System.out.println(s5); //mbc } }
方式一:通过字面量定义的方式
方式二:通过 new + 构造器的方式
public class test2{ public static void main(String[] args){ //通过字面量定义的方式:此时的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 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 p1.name = "Jerry"; System.out.println(p2.name);//Tom } } public class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } }
面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中 new 结构,另一个是 char[] 对应的常量池中的数据:"abc"。
public class test3{ public static void main(String[] args){ 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 final String s9 = "xiaozhao"; String s10 = s9 + "java"; String s11 = "xiaozhaojava"; System.out.println(s10 == s11); //true(原因:s9用final修饰,类似于常量,存储在常量池中) } } /* s1 = s1 + "b"; 说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+"b"(也就是"ab")。如果多次执行这些改变串内容的 操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。 */
特殊例子:
public class StringTest { String str = new String("good"); char[] ch = { 't', 'e', 's', 't' }; public void change(String str, char ch[]) { // 将实参str和ch的地址值传递给形参str和ch // this.str = "test ok"; 如果使用该语句,则ex.str输出为 test ok str = "test ok"; System.out.println(str); // test ok ch[0] = 'b'; } public static void main(String[] args) { StringTest ex = new StringTest(); ex.change(ex.str, ex.ch); System.out.println(ex.str); // good System.out.println(ex.ch); // best } }
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 substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。 String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
public class test1 { public static void main(String[] args){} String s1 = "HelloWorld"; System.out.println(s1.length()); // 10 System.out.println(s1.charAt(0)); // H System.out.println(s1.isEmpty()); // false String s2 = s1.toLowerCase(); System.out.println(s1); // HelloWorld(s1不可变的,仍然为原来的字符串) 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 String s5 = "helloworld"; System.out.println(s1.equals(s5)); // false System.out.println(s1.equalsIgnoreCase(s5)); // true String s6 = s1.concat("ByJava"); System.out.println(s6); // HelloWorldByJava String s7 = "abc"; String s8 = new String("abe"); System.out.println(s7.compareTo(s8));// -2 String s9 = "小钊学Java"; String s10 = s9.substring(2); System.out.println(s9); // 小钊学Java System.out.println(s10); // 学Java String s11 = s9.substring(1, 4); System.out.println(s11); // 钊学J } }
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
public class test2{ public static void main(String[] args){ String s1 = "zhaohelloworldzhao"; boolean b1 = s1.endsWith("zhao"); System.out.println(b1); // true boolean b2 = s1.startsWith("Zhao"); System.out.println(b2); // false boolean b3 = s1.startsWith("hello",4); boolean b4 = s1.startsWith("hello",5); System.out.println(b3); // true System.out.println(b4); // false String s2 = "zhao"; System.out.println(s1.contains(s2)); // true System.out.println(s1.indexOf("zhao")); // 0 System.out.println(s1.indexOf("zhao",1)); // 14 System.out.println(s1.lastIndexOf("zhao")); // 14 System.out.println(s1.lastIndexOf("zhao",6)); // 0 // 什么情况下,indexOf(str)和lastIndexOf(str)返回值相同? // 情况一:存在唯一的一个str。情况二:不存在str,均等于-1。 } }
// 替换 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 test3{ public static void main(String[] args){ String s1 = "小钊学Java小钊"; String s2 = s1.replace('钊', '明'); System.out.println(s1); // 小钊学Java小钊 System.out.println(s2); // 小明学Java小明 String s = "12hello34world5java7891xiaozhao456"; //把字符串中的数字替换成!,如果结果中开头和结尾有!的话去掉 String s3 = s.replaceAll("\\d+", "!"); String s4 = s.replaceAll("\\d+", "!").replaceAll("^,|,$", ""); System.out.println(s3); // !hello!world!java!xiaozhao! System.out.println(s4); // hello!world!java!xiaozhao String s5 = "12345"; //判断s5字符串中是否全部有数字组成,即有1-n个数字组成 boolean matches = s5.matches("\\d+"); System.out.println(matches); // true String tel = "020-4534289"; //判断这是否是一个广州的固定电话,且-后的号码为7-8位 boolean result = tel.matches("020-\\d{7,8}"); System.out.println(result); // true String s6 = "hello|world|java"; String[] strs = s6.split("\\|"); for (int i = 0; i < strs.length; i++) { System.out.print(strs[i] + " "); // hello world java } System.out.println(); String s7 = "hello.world.java"; String[] strs2 = s7.split("\\."); for (int i = 0; i < strs2.length; i++) { System.out.print(strs2[i] + " "); // hello world java } } }
字符串 >> 基本数据类型、包装类
Integer 包装类的 public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
类似地,使用 java.lang 包中的 Byte、Short、Long、Float、Double 类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。
基本数据类型、包装类 >> 字符串
调用 String 类的 public String valueOf(int n) 可将 int 型转换为字符串
相应的 valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b) 可由参数的相应类型到字符串的转换
public class test1{ public static void main(String[] args){ 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(str3为堆地址值,str1为"123"在字符常量池的地址值) } }
public class test2{ public static void main(String[] args){ String str1 = "abc123"; char[] charArray = str1.toCharArray(); for (int i = 0; i < charArray.length; i++) { System.out.print(charArray[i] + " "); // a b c 1 2 3 } char[] arr = new char[]{'h','e','l','l','o'}; String str2 = new String(arr); System.out.println(str2); // hello } }
@Test public void test3() throws UnsupportedEncodingException { String str1 = "abc123中国"; byte[] bytes = str1.getBytes(); // 使用默认的字符集,进行编码。 System.out.println(Arrays.toString(bytes)); byte[] gbks = str1.getBytes("gbk"); // 使用gbk字符集进行编码。 System.out.println(Arrays.toString(gbks)); String str2 = new String(bytes); // 使用默认的字符集,进行解码。 System.out.println(str2); String str3 = new String(gbks); System.out.println(str3); // 出现乱码。原因:编码集和解码集不一致! String str4 = new String(gbks, "gbk"); System.out.println(str4); // 没有出现乱码。原因:编码集和解码集一致! }
java.lang.StringBuffer 代表可变的字符序列,JDK1.0 中声明,可以对字符串内容进行增删,此时不会产生新的对象。
很多方法与String相同。
作为参数传递时,方法内部可以改变值。
StringBuffer 类不同于 String,其对象必须使用构造器生成。有三个构造器:
StringBuffer():初始容量为16的字符串缓冲区。char[] value = new char[16];
StringBuffer(int size):构造指定容量的字符串缓冲区。
StringBuffer(String str):将内容初始化为指定字符串内容。
StringBuffer 类的常用方法:
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):修改n索引处的字符为ch
public class test1{ public static void main(String[] args){ StringBuffer s1 = new StringBuffer("abc"); s1.setCharAt(0,'m'); System.out.println(s1); // mbc StringBuffer s2 = new StringBuffer(); System.out.println(s2.length()); // 0 s1.append(1); s1.append('1'); System.out.println(s1); // mbc11 // s1.delete(2,4); // mb1 // s1.replace(2,4,"hello"); // mbhello1 // s1.insert(2,false); // mbfalsec11 // s1.reverse(); // 11cbm String s2 = s1.substring(1, 3); System.out.println(s1); // mbc11 System.out.println(s1.length()); // 5 System.out.println(s2); // bc } }
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样。
面试题:对比 String、StringBuffer、StringBuilder
String:不可变的字符序列;底层使用 char[] 存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用 char[] 存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用 char[] 存储
注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder 会改变其值。
StringBuffer 类和 StringBuilder 类初始容量均为 16 的字符串缓冲区,如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的 2 倍加 2,同时将原有数组中的元素复制到新的数组中。
因此开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity) 。
对比 String、StringBuffer、StringBuilder 三者的效率:
从高到低排列:StringBuilder > StringBuffer > String