Java基础语法——String类
Posted rain67
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础语法——String类相关的知识,希望对你有一定的参考价值。
文章目录
Java基础语法(九)——String类
本次内容介绍大纲
字符串是我们以后工作中非常常用到的类型. 使用起来都非常简单方便, 我们一定要使用熟练。
那么C语言中是否有字符串类型? 答案是 “ 没有 ” !!
char *p = " hello";
那么p 的类型是一个字符串类型么? 不是,p是一个指针!!
而在 Java当中 是有 字符串类型的——String
一、定义方式
创建字符串的方式有很多种,常见的构造 String 的方式如以下:
方式一:直接赋值法
String str1 = "hello";
方式二: new String()
String str2 = new String("hello");
方式三:创建一个字符数组ch,new String ( ch )
char chs[] = {'h','e','l','l','l','o'};
String str3 = new String(chs);
二、内存
在此之前我们要先引入一个概念 字符串常量池
Sting constant pool 字符串常量池 的特性
1.在JDK.7 开始,字符串常量池 被挪到堆里了
2.池内的数据不存在重复
下面我们通过一系列的练习来熟悉 字符串常量池以及 字符串类型数据在内存中的存放。
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);
String str3 = "hello";
System.out.println(str1 == str3);
}
我们来看这样的代码,str 代表的是引用\\地址,请判断 两次打印分别是什么?
我们来看结果
这个结果说明 str1 和 str2存放的地址是不一样的, str1 和 str3 存放的地址是一样的。
好的,为什么是这样的结果呢?我们来看一下这几个字符串类型变量的内存。
"hello"如果存放在常量池当中,就会占用内存,假如这块空间的地址为111,那么str1中存放的就是111.
str2 new一个String对象,那么肯定在堆上开辟内存,假设内存地址是888,在这个String 对象中,存在一个value[] 保存着 orginal传入的字符串,这个val ==“hello”,因为在字符串常量池中已经有了"hello",所以val 直接指向 常量池中的"hello".但是str2 指向的依然是 888在堆中的空间。
所以 str1 不等于 str2。
之后呢,str3 也等于"hello",他也准备把hello放在常量池当中.此时常量池中已经存在"hello",那么之后str3 在存放"hello"地址的时候,就指向的是常量池中原来hello的地址。
所以 str1 等于 str3
再看一组练习
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hel"+"lo";
System.out.println(str1==str2);
String str3 = new String("hel")+"lo";
System.out.println(str1==str3);
}
请判断两次打印的结果…
结果如下:
下面我们来分析,这组代码中str变量的内存存放
str1 指向字符串常量池中的 “hello”
str2 是"hel"与"lo" 组合而成的,常量在编译的时候就已经确定了,所以在编译时,已经被处理为"hello",所以也指向 常量池中的"hello"。
所以 str1 等于 str2
str3 首先new 了一个String(“hel”)对象,在堆中开辟一块空间,这个对象中的"hel"同时存放在常量池中,之后又在常量池中开辟一块空间存放 “lo”。两块部分之间的"+",将 String 的对象 与常量池中的 "lo"结合在堆中再次开辟一块新的空间,这块内存中的val ==“hello”,str3指向的是合并之后的对象 ,地址为999.
所以 str1 不等于 str3.
再看一组练习
public static void func(String str,char[] array){
str = "abcdef";
array[0] = 'g';
}
public static void main(String[] args) {
String str1 = "hello";
char[] val = {'a'};
System.out.println(str1);
System.out.println(Arrays.toString(val));
func(str1,val);
System.out.println("=================");
System.out.println(str1);
System.out.println(Arrays.toString(val));
}
请看一下,我们将String str 作为参数,改变str 的内容,以及传入 数组 val 改变 数组元素,其打印结果是什么?
我们看到 String str 的内容并未改变,但是数组 val 的元素却改变了。
我们从内存的角度来分析。
str1 指向字符串常量区的"hello",地址为888
val 作为数组引用,指向堆中开辟的数组空间,地址为777
str 作为函数的形参,接收str1实参的值,也就是888,此时str指向常量区的”hello“,但是在方法的内部,str = “abcde”,在字符串常量区中有开辟一块"abcde"的内存,地址为000,最后 str 存放的地址为000.
array 作为函数的形参,接收val 实参的值,也就是777,此时array 指向堆中 开辟的数组空间,此时通过array 来改变数组元素的内容,最终 改变的也同样是val 实参的内容.
三、字符串比较相等
如果现在有两个int型变量,判断其相等可以使用 == 完成。
str1 = "world";
System.out.println(str2);
// 执行结果
//Hello
int x = 10 ;
int y = 10 ;
System.out.println(x == y);
// 执行结果
//true
如果说现在在String类对象上使用 == ?
代码1
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
// 执行结果
//true
看起来貌似没啥问题, 再换个代码试试, 发现情况不太妙.
代码2
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
// 执行结果
//false
在上面的几个练习中,我们 用 str1 == str2 比较的是两个字符串的引用/地址,如果比较字符串里面的内容,我们需要用到 equals 方法。
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1==str2); //比较的是引用
System.out.println(str1.equals(str2)); //比较 str1 和 str2 字符串的内容
String str3 = "hello";
System.out.println(str1.equals(str3)); //比较 str1 和 str3 字符串的内容
}
最后的打印结果
打印的结果符合字符串的内容比较。
常用的比较方式:
我们再来看一种情况,
public static void main(String[] args) {
String str1 = null;
String str2 = "hello";
System.out.println(str1.equals(str2));
}
这时候运行程序,就会出现以下情况:
空指针异常,因为 null. 任何方法都会出现异常。
所以一定要保证 str1 不能为null。
那么如果我们改一下,
public static void main(String[] args) {
String str1 = null;
String str2 = "hello";
System.out.println(str2.equals(str1));
}
所以我们知道 equals(),括号里可以是null,但是 点之前一定不能是 null.
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.equals("hello")); // 方式1
System.out.println("hello".equals(str1)); // 方式2
}
当我们写代码遇到以上的情况时,我们应该尽量选方式2,这样保证 equals之前一定不为null,以防出现异常.
四、字符串常量池
在上面的例子中, String类的两种实例化操作, 直接赋值和 new 一个新的 String.
(1) 直接赋值
System.out.println("Hello".equals(str)); // 执行结果 false
String str1 = "hello" ;
String str2 = "hello" ;
String str3 = "hello" ;
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true
String类的设计使用了共享设计模式
在JVM底层实际上会自动维护一个对象池(字符串常量池)
如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用
理解 “池” (pool)
“池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据库连接池” …然而池这样的概念不是计算机独有, 也是来自于生活中.
举个栗子:现实生活中有一种女神, 称为 “绿茶”, 在和高富帅谈着对象的同时, 还可能和别的屌丝搞暧昧. 这时候这个屌丝被称为 “备胎”. 那么为啥要有备胎? 因为一旦和高富帅分手了, 就可以立刻找备胎接盘, 这样 效率比较高.如果这个女神, 同时在和很多个屌丝搞暧昧, 那么这些备胎就称为 备胎池.
(2)采用构造方法
类对象使用构造方法实例化是标准做法。分析如下程序:
String str = new String("hello");
这样的做法有两个缺点:
1. 如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
2. 字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间.
(3)intern 的使用
String str1 = "hello";
String str2 = new String("hello").intren();
从上面的由 构造方法定义字符串,我们会浪费内存空间,而这里有一个方法 ,叫做intern(),手动入池。
那这是什么意思呢?
这是先看一下传入构造方法的字符串在字符串常量池中是否存在,如果有的话,就把常量池中的引用传给当前的引用类型变量。
综上所述,我们一般使用 直接赋值法来 创建 String 对象。
我们再来看这样一组代码,来画一下他的内存结构
在第一步的代码中,new 了两个字符串"1",在堆中创建了两个对象,指向常量池中的"1",拼接在一起,s3.interb(),s3手动入池,“11"在池中没有,所以就把 堆中的"11"的引用 555 传入常量池中。s4 指向池中的"11”,而这时池中已经有了"11"的引用,所以s4 指向的就是 s3在入池的引用。
所以结果为 true。
所以呢,我们解决了一个疑问
在常量池当中,可以放 字符串的字面值常量,也可以放引用。什么时候放引用,就是类似于上面的那种情况之下,s3.intern(),s3所指向的这个对象在字符串常量池中是不存在的,那么入池的时候就把堆中s3的引用放入。
五、理解字符串不可变
字符串是一种不可变对象. 它的内容不可改变.这是什么意思呢?
public static void main(String[] args) {
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
}
对于这种代码,乍一看我们以为成功的将str 每次与其他的字符串拼接,但是这样是不可以的, str 原来指向的是"hello",但是 在与" world"拼接之后,又会产生一个新的对象"helll world",再次拼接一个"!!!",那么又会产生一个新的对象"hello world!!!",在内存中就会产生多个对象。
我们最后需要的是"hello world!!!",但是却开辟了5块内存空间。
如果在一个循环中拼接,那么会开辟更多的内存空间!!
所以这样的代码是极为不可取的!!!
那么如何拼接呢,具体在之后的StringBuff、StringBuilder中介绍。
六、字符、字节、字符串
(1)字符与字符串
字符串内部包含一个字符数组,String 可以和 char[] 相互转换
1.字符数组转字符串
public static void main(String[] args) {
char[] val = {'h','e','l','l','o'};
String str = new String(val);
System.out.println(val);
}
此时我们 的 str 结果就是 “hello”,同时他也可以再给两个参数.
2.将部分字符数组中的内容转换为字符串
offset–偏移量
count-- 转换几个
public static void main(String[] args) {
char[] val = {'h','e','l','l','o'};
String str = new String(val,1,2);
System.out.println(str);
}
此时我们将val 中偏移1个,转换之后的两个数组元素为字符串
打印结果应该为 el
运行结果如下:
3.将字符串中对应索引转换为字符
public static void main(String[] args) {
String str = "hello";
char ch = str.charAt(1);
System.out.println(ch);
}
索引从0开始,我们输入1,所以转换的为字符串中的e
运行结果如下:
4.将字符串转换为字符数组
public static void main(String[] args) {
String str = "hello";
char[] val = str.toCharArray();
System.out.println(Arrays.toString(val));
}
我们用字符数组接收 str转换后的字符。
运行结果如下:
好了,了解了这几种字符与字符串的方法,我们通过几个练习来继续熟悉。
练习一
给定字符串一个字符串, 判断其是否全部由数字所组成.
思路: 将字符串变为字符数组而后判断每一位字符是否是" 0 “~”‘9’"之间的内容,如果是则为数字.
public static boolean func1(String str){
for (int i = 0; i <str.length() ; i++) {
if(str.charAt(i)>'9' || str.charAt(i)<'0'){
return false;
}
}
return true;
}
(2)字节与字符串
字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[] 相互转换
常用方法:
1.字节数组转换为字符串
public static void main(String[] args) {
byte[] bytes = {97,98,99,100};
String str = new String(bytes);
System.out.println(str);
}
运行结果:
字符串中的内容是字节数组与Ascii 码表中对应的字符。
2.部分字节数组的内容转换为字符串
public static void main(String[] args) {
byte[] bytes = {97,98,99,100};
String str = new String(bytes,2,1);
System.out.println(str);
}
运行结果:
3.字符串转换为字节数组
public static void main(String[] args) {
String str = "abcd";
byte[] bytes = str.getBytes();
System.out.println(Arrays.toString(bytes));
}
运行结果:
(3) 小结
那么何时使用 byte[], 何时使用 char[] 呢?
byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候.
七、字符串的常见操作
(1)字符串比较
上面使用过String类提供的equals()方法,该方法本身是可以进行区分大小写的相等判断。除了这个方法之外,String类还提供有如下的比较操作.
1.区分大小写比较
public static void main(String[] args) {
String str1 = "abcd";
String str2 = "Abcd";
System.out.println(str1.equals(str2));
}
运行结果:
我们常用的equals 方法 是区分大小写的,这点要注意。
2.不区分大小写的比较
public static void main(String[] args) {
String str1 = "abcd";
String str2 = "Abcd";
System.以上是关于Java基础语法——String类的主要内容,如果未能解决你的问题,请参考以下文章