创建String对象的方法
java中的String是一个引用类型,它被用来创建一个字符串。
我们最常使用的创建一个字符串的方法即类似这样:
String s = "java";
但其实也可以这样:
String s = new String("java");
这说明String也是一个类,事实上,String是一个特殊的包装类,在String的早期api官方描述文档中,String被这样定义:
public final class String implements ...
{
private final char[];
......
}
可以看到,String类是一个final类,它无法被继承,String类的所有方法也都默认为final方法。
另外可以看到,在String类的内部定义了一个char数组,它被用来存储String数据,即我们在new一个String时,其实是这样的:
String s = new String(new char[]{});
而较新的JDK版本的String则以byte[]存储:如果String仅包含ASCII字符,则每个byte存储一个字符,否则,每两个byte存储一个字符。这种优化主要是为了节省内存。但我们不必关心String的内部优化,它是不会影响任何已有代码的。
字符串常量池
字符串的分配和其它对象分配一样是很耗时间和空间的,况且字符串又是如此的常用,JVM为了提高性能和减少内存的开销,为String维护了一个字符串常量池,每当有字符串将被创建时,JVM会首先检查字符串常量池中是否有该字符串对象,如果有直接返回常量池中的实例引用,如果字符串不在常量池中,就实例化该字符串并将它放进常量池中。
另外,字符串还有一个非常显著的特性,即不可变性,任何字符串对象的操作方法都会返回一个新的字符串对象,而不会对原字符串进行操作,这点需要牢记。
因此可以确定,字符串常量池中不存在两个一样的字符串。
接下来看一些例子:
public static void main(String[] args)
{
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //true s1与s2均指向字符串常量池中的hello对象
}
public static void main(String[] args)
{
String s1 = new String("java");
String s2 = new String("java");
System.out.println(s1 == s2); //false s1与s2指向不同的堆中的String对象
}
public static void main(String[] args)
{
String s1 = "java";
String s2 = "ja" + "va";
System.out.println(s1 == s2); //true s1与s2都能在编译期被确定,都指向字符串常量池中的“java”对象
}
public static void main(String[] args)
{
String s1 = "java";
String s2 = "ja" + new String("va");
System.out.println(s1 == s2); //false s2在编译器无法确定,不是常量,需要在堆中存储
}
public static void main(String[] args)
{
String s1 = "java";
String s2 = "ja" + new String("va");
System.out.println(s1 == s2);
}
public static void main(String[] args)
{
String s1 = "java";
String s2 = "ja";
String s3 = "va";
String s4 = s2 + s3;
System.out.println(s1 == s4);//false s4在编译器无法确定,因为s4是两个引用变量相加,而不是确切的字符串常量相加
//s4 = s2 + s3会在堆中创建一个“ja”对象和一个“va”对象,并再在堆中创建一个"java"对象
//”java“对象引用自字符串池中的”java“对象,并使s4指向堆中该对象
}
.
public static void main(String[] args)
{
String s1 = "java" + 8;
String s2 = "java8";
System.out.println(s1 == s2); //true "java"+8在编译器就会被优化为"java8"
}
例子怎么都说不完,给出以上这些例子好好体会差不多就够了。
String对象的一些常用方法
public static void main(String[] args){
String s = "hello,java!";
String[] ss = {"j","a","v","a"};
System.out.println(s.substring(4)); //拆分字符串,获得索引4之后的全部数据
System.out.println(s.substring(2,8)); //拆分字符串,获得索引2到8之间的数据
System.out.println(s.trim()); //去除首尾空白字符,包括空格、\t、\r、\n。
System.out.println(s.strip()); //去除首尾空白字符,包括空格、\t、\r、\n及\u3000。
System.out.println(s.isEmpty()); //判断字符串是否为空
System.out.println(s.isBlank()); //判断字符串是否是空白字符串
System.out.println(s.replace("ll", "gg")); //替换子串
System.out.println(s.split("\\,")); //分割字符串
System.out.println(String.join("**",ss)); //连接字符串
int a = 100;
boolean f = true;
System.out.println(String.valueOf(a)); //把其它类型强制转换为String
System.out.println(String.valueOf(f));
System.out.println(Integer.parseInt("123456")); //把String转换为Integer
System.out.println(Integer.parseInt("ff",16));
System.out.println(Boolean.parseBoolean("true")); //把String转换为Boolean
System.out.println("hello".toCharArray()); //把字符串转换为字符数组
}
output:
o,java!
llo,ja
hello,java!
hello,java!
false
false
heggo,java!
[Ljava.lang.String;@5b6f7412
j**a**v**a
100
true
123456
255
true
hello
另外,还用一个较为特殊的方法,即String.intern(),它可被用来动态扩充字符串常量池。调用该方法,如果字符串常量池中存在该String对象则返回它的引用,如果不存在,则创建了之后再返回它的引用。
字符编码
ASCII编码:适用于英文字母、数字和常用符号的编码,一个字符一个字节。
GB2313编码:汉字编码,两个字符表示一个汉字。
Unicode编码:全球统一编码。需要两个或更多字节来表示一个字符。英文字符被表示为高位00字节加低位ASCII字节,即两个字节。
UTF-8编码:Unicode编码对英文较多的文本不友好,因为它会浪费很多空间。UTF-8是一种变长编码,长度不固定,为1~4个字节,节省空间,容错能力强。常被用于作为传输编码。
java内部编码:java内部的char类型与String类型都采用了Unicode编码,如果要把它们转换为其它编码,方法如下:
byte[] b1 = "Hello".getBytes(); // 按ISO8859-1编码转换,不推荐
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换
如果要把byte[]转换回来,采用如下方法:
String s1 = new String(b, "GBK"); // 按GBK转换
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8转换
理解String的+操作
String可以直接用+进行拼接,这个过程具体是如何实现的呢?
如果让s1与s2直接用+拼接,从最左边的s1开始,java内部会创建一个StringBuilder对象,然后依次对右边进行append操作,最后通过StringBuilder的toString()返回拼接完成后的字符串。
对于多个字符串连加,每加一个就会创建一次StringBuilder对象,这会大幅度拖慢程序的允许效率,因此对于需要连加很多次的时候,合适的做法是先创建一个StringBuilder对象,连接完了之后手动调用toString()方法得到我们的结果。
StringBuffer是与StringBuilder功能相同的线程安全版本。
StringJoiner
如果需要用连接符连接字符串,可以这么做:
public static void main(String[] args)
{
String[] ssr = {"java","Python","C++"};
var sb = new StringBuilder();
sb.append("<");
for(String s:ssr)
sb.append(s).append(",");
System.out.println(sb.toString().replace("+,","+>")); //<java,Python,C++>
}
也可以使用StringJoiner,使用StringJoiner的一个好处就是可以指定开头和结尾,如下:
public static void main(String[] args)
{
String ssr = {"java","Python","C++"};
var sj = new StringJoiner(",","<",">");
for(String s:ssr)
sj.add(s);
System.out.println(sj.toString()); //<java,Python,C++>
}
参考链接:
https://www.liaoxuefeng.com/wiki/1252599548343744/1260469698963456
https://www.cnblogs.com/zyy1688/p/9269493.html