String
介绍
关于Java JDK中内置的一个类:java.lang.String。
-
String表示字符串类型,属于引用数据类型,不属于基本数据类型。
-
在java中随便使用双引号括起来的都是String对象。例如:“abc”,"hello world"等等。
-
java中规定,双引号括起来的字符串,是不可变的,也就是说"adc"自出生到最终死亡,不可变,不能变成"abcd",也不能变成"ab"。
-
在JDK当中双引号括起来的字符串,例如:“abc”,"def"都是直接存储在方法区的字符串常量池当中的。
-
为什么SUN公司把字符串存储在一个"字符串常量池"当中呢?
因为字符串在实际的开发中使用太频繁,为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
内存分析
-
如:
public class StringTest01 { public static void main(String[] args) { //这两行代码表示底层创建了3个字符串对象,都在字符串常量池当中。 String s1="abcdef"; String s2="abcdef"+"xy"; //分析:这是使用new的方式创建的字符串对象,这个代码中的"xy"是从哪里来的? //凡是双引号括起来的都在字符串常量池中有一份。 //new对象的时候一定在堆内存当中开辟空间 String s3=new String("xy"); } }
内存分析图:
-
面试题:
/* 分析以下程序,一个创建几个对象 */ public class StringTest02 { public static void main(String[] args) { /* * 一共创建3个对象: * 方法区字符串常量池中有1个:"hello"。 * 堆内存当中有两个String对象。 * */ String s1=new String("hello"); String s2=new String("hello"); } }
String类中常见的构造方法
/*
* 关于String类中的构造方法
* 1:String s=new String("");
* 2: String s=""; 最常用的
* 3: String s=new String(char数组);
* 4: String s=new String(char数组,起始下标,长度);
* 5:String s=new String(byte数组);
* 6: String s=new String(byte数组,起始下标,长度);
* */
public class StringTest04 {
public static void main(String[] args) {
//创建字符串对象最常用的一种方式
String s="hello world";
String s1=new String("hello world");
// 这里的常用构造方法
byte[]bytes={97,98,99};//97是a,98是b,99是c
String s2=new String(bytes);
//输出一个引用的时候,会自动调用toString()方法,默认Object的话,会自动输出对象的内存地址
//通过输出结果,我们可知:String类已经重写了toString()方法。
//输出字符串对象的话,输出的不是对象内存地址,而是字符串本身。
System.out.println(s2);//abc
//String(字节数组,数组元素下标的起始位置,长度)
String s3=new String(bytes,1,2); //从下标为1的元素开始,取2个
System.out.println(s3);//bc
//将char数组全部转化成字符串
char[]chars={'我','是','小','马'};
String s4=new String(chars);
System.out.println(chars);//我是小马
String s5=new String(chars,2,2);
System.out.println(s5);//小马
}
}
String类中常用方法
public class StringTest05 {
public static void main(String[] args) {
//String类中常用方法
//1(掌握).char charAt(int index)
char c="小马呀".charAt(1);
System.out.println(c);//马
//2(了解).int compareTo(String anotherString)
//拿着字符串第一个字母和后面字符串的第一个字母比较,能分胜负就不比了。
//字符串之间比较大小不能直接用><,需要使用compareTo
int result="abc".compareTo("abc");
System.out.println(result);//0(等于0) 前后一致
int result1="abcd".compareTo("abce");
System.out.println(result1);//-1(小于0) 前小后大
int result2="abce".compareTo("abcd");
System.out.println(result2);//1(大于0) 前大后小
//3(掌握):boolean contains(CharSequence s)
//判断前面的字符串中是否包含后面的子字符串
System.out.println("小马".contains("马"));//true
System.out.println("http://www.baidu.com".contains("https://"));//false
//4(掌握): boolean endsWith(String suffix)
//判断当前字符串是否以某个子字符串结尾
System.out.println("test.txt".endsWith("txt"));//true
System.out.println("test.txt".endsWith("md"));//false
//5(掌握):boolean equals(Object anObject)
// 比较两个字符串必须使用equals方法,不能使用"=="
System.out.println("abc".equals("abc"));//true
//6(掌握): boolean equalsIgnoreCase(String anotherString)
// 判断两个字符串是否相等,忽略大小写
System.out.println("XiaoMa".equalsIgnoreCase("xiaoma"));//true
//7(掌握): byte[] getBytes()
// 将字符串对象转化成字节数组
byte[]bytes="abc".getBytes();
System.out.println(Arrays.toString(bytes));//[97, 98, 99]
//8(掌握):int indexOf(String str)
//判断某个子字符串在当前字符串中第一次出现处的索引(下标)
System.out.println("matianciailianha".indexOf("ha"));//14
//9(掌握): boolean isEmpty()
//判断某个字符串是否为空。
String s="";
String s1="a";
System.out.println(s.isEmpty());//true
System.out.println(s1.isEmpty());//false
//10(掌握):int length()
//返回字符串长度
//注意:判断数组长度和判断字符串长度不一样
//判断数组长度是length属性,判断字符串长度是length()方法
System.out.println("abc".length());//3
System.out.println("".length());//0
//11(掌握): int lastIndexOf(String str)
//判断某个子字符串在当前字符串中最后一次出现的索引(下标)
System.out.println("1java8ndsdjavanfad".lastIndexOf("java"));//10
//12(掌握): String replace(CharSequence target, CharSequence replacement)
//String的父接口就是:charSequence
//通过新字符替换旧字符,返回一个新字符串
System.out.println("abcb".replace("b","e"));//aece
//13(掌握): String[] split(String regex)
// 拆分字符串
String[]ymd= "1997-09-30".split("-");//将 "1997-09-30" 以"-"分隔符进行拆分
System.out.println(Arrays.toString(ymd));//[1997, 09, 30]
//14(掌握): boolean startsWith(String prefix)
// 判断某个字符串是否以某个子字符串开始
System.out.println("http://www.baidu.com".startsWith("http://"));//true
System.out.println("http://www.baidu.com".startsWith("https://"));//false
//15(掌握): String substring(int beginIndex) 参数是起始下标
//截取字符串
System.out.println("http://www.baidu.com".substring(7));//www.baidu.com
//16(掌握): String substring(int beginIndex, int endIndex)
//左闭右开 beginIndex(起始位置):包含 endIndex(结束位置):不包含
System.out.println("http://www.baidu.com".substring(11,16));//baidu
//17(掌握): char[] toCharArray()
// 将字符串转化为char数组
char[]chars="我是小马呀".toCharArray();
System.out.println(Arrays.toString(chars));//[我, 是, 小, 马, 呀]
//18(掌握): String toLowerCase()
//全部转化为小写
System.out.println("WoshiXIAOMA".toLowerCase());//woshixiaoma
//19(掌握): String toUpperCase()
// 全部转化为大写
System.out.println("WOshixiaoma".toUpperCase());//WOSHIXIAOMA
//20(掌握):String trim()
// 去除字符串前后空白 中间空白不可去
System.out.println(" hello world ".trim());//hello world
//21(掌握):String中只有一个方法是静态的,不需要new
//这个方法是valueof
//作用:将非字符串转化为字符串
System.out.println(String.valueOf(true));//字符串true
System.out.println(String.valueOf(100));//字符串100
//这个静态的valueof()方法,参数是一个对象的时候,会自动调用该对象的toString()方法
String s2=String.valueOf(new User1());
System.out.println(s2);//没有重写toString()方法之前是对象内存地址:com.ma.StringClass.User1@73035e27
//通过源代码可以看出:System.out.println()这个方法,本质上在输出任何数据的时候
//都是先转化成字符串,再输出
System.out.println(s2);//重写后调用toString():用户
}
}
class User1{
//重写toString()
@Override
public String toString() {
return "用户";
}
}
StringBuffer
-
当进行字符串拼接时,若使用String进行拼接,如下:
/* * 我们在实际开发中,如果需要进行字符串的频繁拼接,会有什么问题? * 因为java中的字符串是不可变的,每一次拼接都会产生新字符串。 * 这样会占用大量的方法区内存。造成内存空间的浪费 * String s="abc"; * s+="hello"; * 就以上两行代码,就导致在方法区字符串常量池当中创建了3个对象; * "abc" "hello" "abchello" * */ public class StringBufferTest01 { public static void main(String[] args) { String s=""; //这样做会给java的方法区字符串常量池带来很大的压力 for (int i=0;i<100;i++){ s+=i; System.out.println(s); } } }
-
如果以后需要进行大量字符串的拼接操作,建议使用JDK自带的:
java.lang.StringBuffer
java.lang.StringBuilder- 如何优化StringBuffer的性能:
在创建StringBuffer的时候尽可能给定一个初始化容量,最好减少底层数组的扩容次数,预估计一下,给一个大些的初始化容量。
关键点:给一个合适的初始化容量。
如:
/* * 如果以后需要进行大量字符串的拼接操作,建议使用JDK自带的: * java.lang.StringBuffer * java.lang.StringBuilder * * 如何优化StringBuffer的性能: * 在创建StringBuffer的时候尽可能给定一个初始化容量 * 最好减少底层数组的扩容次数,预估计一下,给一个大些的初始化容量 * 关键点给一个合适的初始化容量 * */ public class StringBufferTest02 { public static void main(String[] args) { //创建一个初始化容量为16的byte[]数组(字符串缓冲区对象) StringBuffer stringBuffer=new StringBuffer(); //拼接字符串,以后拼接字符串统一调用append()方法 //append是追加 //append方法底层在进行追加的时候,如果byte数组满了,会自动扩容 stringBuffer.append("a"); stringBuffer.append("b"); stringBuffer.append("c"); stringBuffer.append(3.14); stringBuffer.append(true); System.out.println(stringBuffer); //指定初始化容量的StringBuffer对象(字符串缓冲区对象) StringBuffer sb=new StringBuffer(10); sb.append("hello"); sb.append("world"); System.out.println(sb); } }
- 如何优化StringBuffer的性能:
StringBuilder
/*
* java.lang.StringBuilder
*
* StringBuffer和StringBuilder的区别
* StringBuffer中的方法都有:synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
* StringBuilder中的方法都没有synchronized关键字修饰。表示StringBuilder在多线程环境下运行是不安全的。
* StringBuffer是线程安全的,StringBuilder是非线程安全的。
* */
public class StringBuilderTest01 {
public static void main(String[] args) {
//使用StringBuilder也是可以完成字符串的拼接
StringBuilder sb=new StringBuilder();
sb.append(100);
sb.append(true);
sb.append("hello");
sb.append("world");
System.out.println(sb);
}
}
面试题
-
String为什么是不可变的?
我看过源代码,String类中有一个byte[]数组,这个byte[]数组用final修饰的,因为数组一旦创建长度是不可变的。并且被final修饰的引用一旦指向某个对象后,不可再指向其他对象,所以String是不可变的。
-
StringBuilder或StringBuffer为什么是可变的?
我看过源代码,StringBuilder或StringBuffer内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuilder或StringBuffer的初始化容量我记得应该是16,当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy()…是这样扩容的。所有StringBuilder或StringBuffer适合于字符串的频繁拼接操作。
-
String字符串不可变是什么意思:
public class StringBufferTest03 { public static void main(String[] args) { //字符串不可变是什么意思? //是说双引号里面的字符串对象一旦创建不可变 String s="abc"; //"abc"放在字符串常量池,"abc"不可变 //s变量是可以指向其他对象的 //字符串不可变不是说以上变量s不可变。说的是"abc" s="xyz"; //"xyz"放在字符串常量池,"xyz"不可变 System.out.println(s);//xzy } }