在C语言中要表示字符串只能使用字符数组或者字符指针,可以使用标准库提供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想,而字符串应用又非常广泛,因此Java语言专门提供了String类。
在开发和校招笔试中,字符串也是常客,如:
字符串转整形数字
字符串相加
而且在面试中也频繁被问到
String类提供的构造方式非常多,常用的就以下三种:
String str1 = "hello";
System.out.println(str1);
String str2 = new String("hello");
System.out.println(str2);
char[] chars = {'h','e','l','l','o'};
String str3 = new String(chars);
System.out.println(str3);
注意:
1) 在Java中和c语言不同,c语言中的字符串以\0结尾,而Java中是通过长度判断字符串是不是结尾
2) String是引用类型,内部并不存储字符串本身,在String类的实现源码中,String类实例变量如下:
当我们进行如下操作时:
在栈和堆内存上引用所指向的对象一目了然:
我们可以看到s3指向的是s1这个引用所指向的对象,两者指向同一个对象,但是不可以修改任意一个引用里的数值来改变另外一个引用的数值,在Java当中字符串是字符串常量,不能被修改,而且此处并没有发生浅拷贝,因为这里根本没有进行拷贝。
3)在Java中" "引起来的也是String类型对象。
打印"hello"字符串(String对象)的长度
String s1 = new String("hello");
System.out.println(s1.length());
字符串的比较是常见操作之一,比如:字符串排序。Java中总共提供了4中方式:
注意:
对于基本类型,==比较的是变量中的值:
int a = 10;
int b = 20;
int c = 10;
System.out.println(a == b); // false
System.out.println(a == c); // true
对于引用类型==比较的是引用中的地址:
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);//false
只要你new就会创建新的对象,开辟新的空间,所以地址自然不一样
字典序:字符大小的顺序
String类重写了父类Object中equals方法,Object中equals默认按照==比较,String重写equals方法后,按照如下规则进行比较,它的源码实现中:
我们前面讲到在Object类当中的equals是比较地址的,在String类里面,它重写了equals方法,所以这里的equals比较的是对象内容是否一样(字符串注意区分大小写,大小写不一样对象的内容就不一样)
在此处也拓展一下,String也重写了ToString方法
String s1 = new String("hello");
System.out.println(s1);//hello
所以直接打印s1的话不是打印地址而是和ToString方法是一样的,输出对象的内容
与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。
具体比较方式:
1). 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
输出结果:(h比a大7)
2). 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("ABc");
String s4 = new String("abcdef");
System.out.println(s1.compareToIgnoreCase(s2)); // 不同输出字符差值-1
System.out.println(s1.compareToIgnoreCase(s3)); // 相同输出 0
System.out.println(s1.compareToIgnoreCase(s4)); // 前k个字符完全相同,输出长度差值 -3
在这里面和3里面描述的compareTo方法只有一点不同,那就是这方法忽略大小写,无论是大写还是小写在内容上是视为一样的
字符串查找也是字符串中非常常见的操作,String类提供的常用查找的方法:
详细举例:
String s = "aaabbbcccaaabbbccc";
System.out.println(s.charAt(3)); //拿到下标为3的字符--> 'b' (参数必须在合法范围内)
System.out.println(s.indexOf('c')); //从头到尾第一次遇到字符c的下标--> 6
System.out.println(s.indexOf('c', 10)); //从下标为10的元素开始往后面找字符,返回找到的第一个c的下标--> 15
System.out.println(s.indexOf("bbb")); //在s里找bbb的字符串,并且返回第一个b的下标--> 3
System.out.println(s.indexOf("bbb", 10)); //从下标为10的元素开始往后面找字符串,并且返回第一个b的下标--> 12
System.out.println(s.lastIndexOf('c')); //从后往前找返回第一次出现的c的位置--> 17
System.out.println(s.lastIndexOf('c', 10)); //从下标为10的位置开始从后往前找第一次出现字符c的位置--> 8
System.out.println(s.lastIndexOf("bbb")); //从后往前找字符串bbb的位置,打印最后一个字符的下标--> 12
System.out.println(s.lastIndexOf("bbb", 10)); //从下标为10的位置开始从后往前找,打印最后一个字符的下标--> 3
看着复杂其实万变不离其宗,都是一个模板刻出来的,所以不用刻意去记,多上手写写代码就熟悉了
1) 数字转字符串
只需要借助valueOf()方法即可
int a = 10;
String str = String.valueOf(a);
System.out.println(str);
2)字符串转数字
String str2 = "1234";
int ret = Integer.valueOf(str2);
int ret2 = Integer.parseInt(str2);
double ret3 = Double.parseDouble(str2);
System.out.println(ret);//1234
System.out.println(ret2);//1234
System.out.println(ret3);//1234.0
小写转换为大写:借助toUpperCase()方法
String s1 = "hello";
String s2 = s1.toUpperCase();
System.out.println(s2);//HELLO
在这里值得注意的是s2并不是在s1的基础上进行转换的,而是new了一个新的对象,在新的对象上进行修改的
大写转换为小写:借助toLowerCase()方法
String s3 = "HeLlo";
String s4 = s3.toLowerCase();
System.out.println(s4);//hello
原有的小写就不会继续转换了,大写转换为小写,小写的保留
String s1 = "hello";
char[] chars = s.toCharArray();
System.out.println(Arrays.toString(chars));//[h, e, l, l, o]
在这里我们也可以不借助Array.toString方法,而是遍历打印这个字符串
当然也可以把数组转回成字符串
String s2 = new String(s1);
System.out.println(s2);//hello
//以格式化的形式 转变为字符串
String s = String.format("%d-%d-%d", 2019, 5,20);
System.out.println(s);//2019-5-20
使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:
字符串的替换处理:
String str = "abababababccc";
String str2 = str.replace('a','h');//将字符a替换成字符h
System.out.println(str);//abababababccc
System.out.println(str2);//hbhbhbhbhbccc
在Java当中,所有对字符串本身的操作,都不是在原来的字符串对象上进行操作,因为字符串是不可改变的 (由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串)
在字符串的替换里,不仅可以替换单个的字符,也可以替换一串字符串,得用双引号引起来
前面三个方法是全都替换成你要替换的元素,而最后一个方法只替换第一个你要替换的元素
在以后的代码中,是不是能随意用这些库函数呢?
在此提几点建议:
String str = "abc def gh";
String[] strings = str.split(" ");
for (String s:strings) {
System.out.println(s);
}
String str = "192.168.11";
String[] strings = str.split("\\.");
for (String s:strings) {
System.out.println(s);
}
为了按照点号来拆分ip地址,我们首先得转义字符点,就必须加个\,但是\也需要转义,又需要一个\来转义一下!!
这个方法里还可以指定切割组数,以此来达到我们的需求(组数不受限制,大了依旧按照显示分割组数)
String[] strings = str.split("\\.",3);
技巧:
1). 字符"|“,”*“,”+“都得加上转义字符,前面加上 “\”
2). 而如果是 “” ,那么就得写成 “\\”
3). 如果一个字符串中有多个分隔符,可以用”|"作为连字符
解释一下第三点:
String str = "hahaha bbb&999";
String[] strings = str.split(" |&");
for (String s:strings) {
System.out.println(s);
}
当我们看到字符串时,我们想先分割空格,然后再在&处分割,这时候就要用|符来把两个分割标准符分开来
String str = "name=zhangsan&age=18";
String[] strings = str.split("&");//第一次拆分
for (String s:strings) {
String[] s1 = s.split("=");//第二次拆分
for (String x:s1) {
System.out.println(x);
}
}
就是在原来的分割基础上进行嵌套分割
String str = "abcdefg";
String ret = str.substring(2);//从2下标开始截取,取后半部分 -->cdefg
String ret1 = str.substring(2,5);//从2下标开始截取,到5下标,区间左闭右开 -->cde
System.out.println(ret);
System.out.println(ret1);
注意:
String str = " abc def ";
System.out.println(str.trim());
打印结果就只保留字符串中间的空格,两边空格去掉
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
System.out.println(str1 == str2);//true
System.out.println(str1 == str3);//false
在这里插入图片描述
通过上述代码我们可以看到str1和str2引用的对象相同,而str1和str3引用的对象却不是,在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池(StringTable)。
为了节省存储空间以及程序的运行效率,Java中引入了:
字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来进行查找的数据结构,后序给大家详细介绍),不同JDK版本下字符串常量池的位置以及默认大小是不同的:
注意:
在我们前面讲到过,创建对象有三种方法
String str1 = "hello";
String str2 = "he"+"llo";//常量在编译的时候,就直接视为hello了
System.out.println(str1 == str2);//true
String str3 = "he";
String str4 = "llo";
String str5 = str3 +str4;//str3和str4是变量,在编译的时候,还不知道里面的内容
System.out.println(str1 == str5);//false
在我们用被拆分的字符串拼接的时候和一个完整的字符串相比,注意是常量还是变量,常量则相同,变量则不同
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
String类在设计时就是不可改变的,String类实现描述中已经说明了,并且在源码底下,我们可以看到它是由private和final修饰的,所以String类不能被继承,value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。在String类里没有提供get和set方法,所以修改不了数组指向的内容
所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
有些人说:字符串不可变是因为其内部保存字符的数组被final修饰了,因此不能改变。这种说法是错误的,不是因为String类自身,或者其内部value被final修饰而不能被修改。
final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。
为什么 String 要涉及成不可变的?(不可变对象的好处是什么?)
1.方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
2.不可变对象是线程安全的.
3.不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
此处不了解的我后续详细介绍
注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。
String str = "hello";
str = str + "world";
System.out.println(str);
如图我们拼接一串字符串,打印出来的就是helloworld,我们为了更仔细查找它的内部结构,我们进入字节码文件查看
在我们的字节码文件里可以看到它底下生成了许多的对象,这种方式不推荐使用,因为其效率非常低,中间创建了好多临时对象。
可以看出在对String类进行修改时,效率是非常慢的,因此:尽量避免对String的直接需要,如果非要修改我们该如何修改呢?下篇文章将带你详细了解,如何修改字符串并且不用创建许多临时对象以此来满足我们的需求