Java里的字符串, String类简单介绍.

String类在java面试中也是1个常见的问题点. 所以也是写在这里方便以后查阅了.


大家都知道c语言里是没有String 字符串这个数据类型的.

只能用字符数组的1个特殊形式来表示一个字符串, 就是这个字符数组的最后1元素必须是以'\0'(空) 来结尾的.

例如:

char c[] = "abcd" 是1个字符串, 但是它的长度是5, char[4] = '\0'

char d[6] = "abcde" 也是1个字符串.

但是, char e[5] = "abcde" 只是1个字符数组, 因为它最后1个元素不是 = "\0" ,如果对字符数组e执行string.h里面的函数的话, 就肯定会出错了.

因为检测不到'\0'字符啊.


当然, 用c语言写1个字符串容器不难.  而java是由c语言发展而来, sun公司已经帮我们写好了1个字符串容器, 这个容器就是String类了.


一, Java里的字符串.

首先声明:

1.1 字符串跟String类是不同的概念


本文涉及两个重点,  1个是字符串, 1个是String类. 它们虽然有联系, 但是却是完全不同的两个概念!


我们可以参考jdk api中文里对String类的解释:

public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence


String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。
字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。


由上面的解析我们见到几个不易理解的地方:

例如:

字符串是常量?

它们的值不能改?

因为..所以..  这什么逻辑?


在实际编程当中, 我们觉得字符串变量的值可以更改的呀?


本人认为, 大家没必要担心自己的理解能力, 中文jdk api的翻译实在是很没有节操的.

上面解释的最后一句话的原文是这样的:

Strings are constant; their values cannot be changed after they are created.String buffers support mutablestrings. Because String objects are immutable they can be shared


本人渣翻:

字符串是常量; 它们的值一旦创建不能更改. 然而String类的引用(变量 or 内存)却可以指向不同的字符串. 是因为字符串对象虽然是不能修改的, 但是它们的地址可以共享.


原文和本人翻译都有两种颜色的单词,  红色就是指上面第一个概念字符串.  而蓝色指的另1个概念String类.

相信即使本人修改了翻译, 仍然会有人觉得还是不能理解, 请往下看:



1.2 java里字符串的定义

注意这个不是String类的定义哦, 定义如下:

Java里的字符串就是存放于数据区(静态区)以Unicode编码的字符集合.

可见java里的字符串跟c语言的字符串如下两个本质上的区别:


1.2.1 Java里字符串用Unicode编码

c语言中的字符串里面的字符串是用ASCII编码的, ASCII只用1个字节(byte)的内存表示1个字符. 但是1个字节的内存数量不足以表示全世界那么多种字符.

例如1个汉子就需要2个字节来表示.


所以c语言里的某些字符处理函数, 如果参数传入1个汉字可能就会出错, 因为毕竟字符的占用内存长度不同.


而Unicdoe也叫万国码, 它用两个字节去表示任何1个字节, 无论是字母还是汉字. 所以利用java来做内存处理更加方便, 跨平台性非常好.

缺点就是比c语言字符处理更加耗内存.



1.2.2 Java里字符串存放在数据区(静态区).

我之前的博文见过, java程序类似于c语言, 运行时会把程序占用的内存大致分割成几个部分.

分别是

stuck(栈区), Heap(堆区), Data(数据区)和代码区 

其中数据区用于存放静态变量和字符串常量.

见下图


一, Java里的字符串.



1.3 为什么说java里的字符串是常量, 不可修改的.

1.3.1 一般的类指向的是变量

关于这点, 需要对比才能讲得清楚.

这里我们利用1个自定的类来举个例子:

package String_kng;

class Human_1{
    int id;
    int age;
    public Human_1(int id, int age){
        this.id = id;
        this.age = age;
    }
    public String toString(){
        return "id is " + id + ","  + " age is " + age;
    }
}

public class String_2{
    public static void f(){
        Human_1 h = new Human_1(1,30);
        Human_1 h2 = h; //
        System.out.printf("h: %s\n", h.toString()); 
        System.out.printf("h2: %s\n\n", h.toString()); 

        h.id = 3;
        h.age = 32;
        System.out.printf("h: %s\n", h.toString()); 
        System.out.printf("h2: %s\n\n", h.toString()); 

        System.out.println( h == h2 );
    }
}

上面例子中定义了1个Human_1的类, 只有2个成员id和age.

下面f()中首先实例化了1个Human_1的对象h.

然后定义另外1个引用h2, 然后把h的地址赋予给h2 ( Human_1 h2 = h)

然后输出h, h2的值, 它们是一样的.


然后修改h的值,

再次输出h h2的值, 发现h2的值也被修改.

最后用 " == " 来比较h 和 h2所指向的地址.

明显它们两者所指向的地址是相同.


输出:

     [java] h: id is 1, age is 30
     [java] h2: id is 1, age is 30
     [java] 
     [java] h: id is 3, age is 32
     [java] h2: id is 3, age is 32
     [java] 
     [java] true

其实上面例子可以理解成:

首先用1个容器h2 来保存 h1所指向的地址.

然后修改h1的值, 最后h1的地址没变化.


也就是说:

h这个对象虽然值被修改了, 但是指向的内存地址没有变化, 变的是该内存的内容(值)


画张图便于理解:



如上图可见:

1  对象名h 和 h2 本身是都是局部变量, 位于栈区中, 里面存放的是1个地址.

2. 该地址指向的是堆区的一块内存, 这块内存是用new Human()划分出来的. 而且把头部地址赋予对象名h

3. 该内存包括两个部分, 1个用于存放成员id的值, 另1个存放成员age的值.


可见, 无论对象h成员的值如何改变, 变的只是堆区内存的存放内容, 而堆区内存地址是没变化的. 对象引用h的指向也没有变化.

我们一般把这种内存地址不变, 值可以改变的东西成为变量.

意思就是内存地址不变的前提下内存的内容是可变的.


注意, 上面的例子不说是对象引用h是1个变量,  而是说h指向的内存是1个变量

1.3.2 java里的字符串是常量

将上面的例子改一下, 把Human_1类改成String类:

package String_kng;

public class String_3{
    public static void f(){
        String s = "cat";
        String s2 = s;

        System.out.printf("s: %s\n", s); 
        System.out.printf("s2: %s\n", s2); 
        System.out.println(s == s2); 

        s = "dog";
        System.out.printf("\ns: %s\n", s); 
        System.out.printf("s2: %s\n", s2); 
        System.out.println(s == s2); 
    }
}

逻辑跟上面的例子基本没区别, 也是首先实例化1个String对象s, 它的值是s;

然后将s所指向的地址保存在另个引用s2.


这时输出s 和 s2的值, 它们当然是相等的.

这时"修改"s的值为"dog"

再输出s 和 s2的值, 却发现s的值变成dog了, 但是s2的值还是cat..  而且它们的所指向的地址也不再相等.

输出结果:

     [java] s: cat
     [java] s2: cat
     [java] true
     [java] 
     [java] s: dog
     [java] s2: cat
     [java] false

为什么s 和 s2所指向的地址一开始是相等的, 一旦s的修改为dog后, s 和 s2所指向的地址就不等呢.

原因就是这句代码:

s = "dog"; 

并不是修改s所指向的内存地址, 而是改变了s的指向, 也就是修改了s的所指向的地址啊.


画两张图:

s的值"修改"前:



由上图可见:

1. String类也是java的类, 所以它的实例化对象也需要在堆区划分内存。

2. 两个对象引用s 和 s2这时都指向了堆区同1块对象内存。所以它们的所指向地址是相等的。

3. 字符串真正的地址不是再堆区中, 是在数据区中的。 而堆区对象内存中有其中1个对象成员保存了该字符串在数据区的真正地址


s的值"修改"为dog后:



由上图可见:

1. s = "dog" 并不是修改s所指向的内容. 而是在堆区和数据区各划分了1个新的内存. 其中数据区划分1个新的字符串"dog" , 堆区划分1个新的String对象内存, 保存了dog的字符串地址.

2. 当然之前那个堆区对象内存和数据"cat"的内存是由 String s = "cat" 这条语句创建,关于String类语法机制后面会再讲。

3. s = "dog" 不但在数据区和堆区都各自创建1个新内存, 而且还改变了自己所指向的地址, 所以这时s 和 s2 不再相等.

4. 关键是原来数据的字符"cat" 并没有被修改! 也不可能被修改.


我们一般把这种内存值不能改变, 只能通过引用去指向另1块的东西叫做常量.


1.3.3 java里字符串不能修改的一些小结.

其实从另外一些方面一也能体现出java字符串不能修改的.

例如一些java的内部类, 如Calendar(日期)一般都会提供一些setXXXX的方法让程序猿去修改对应的值. 例如 setYear(), setDate().等等

而String类是没有这类setXXXX方法.


虽然字符串在数据区中的内存不能修改, 但是我们可以为String类的对象指向另一块内存. 所以这个特性在编程造成的影响不大.

那么原来的内存怎么办呢? 放心, java的内存回收机制会收拾它们的..



二, Java里的String类.

2.1 java里 String 类的 本质

String类的书面解释在本文的1.1 章里提高过了, 是1个用于字符串的类.

但是这个解释并没有指明String类的本质.


我们知道, Java类的本质大致上可以理解为 成员(属性) 和 方法的集合体.

String类也一样, 只不过String类有1个关键的成员, 这个成员保存着数据区的某个字符串的内存地址. 可以理解为1个指针.

而String类的方法是一些对对应字符串的查询方法(例如indexOf(), charAt()等). 注意, 并没有对这个字符串进行修改的方法哦, 字符串是常量, 不能修改.

虽然String类不能修改字符串, 但是上面保存字符串地址的成员却是可以被改变的, 也就是说String类的对象可以指向另1个字符串.


画个图:




见上图, java的String类实例化1个对象后, 会在堆区划分一块对象的内存, 其中1个关键成员存放的是数据区字符串的地址.

而下面若干个方法内存, 存放的是该函数(方法)在代码区的2进制代码的地址.



2.2 String类实例化对象的第一个方法. new String("abc")

当然, String类的构造函数有很多个(参数不同), 但是在coding中,常用的实例化对象方法无非是两种.

第一种就是与其他类一样, 利用构造方法.

String s = new String("abc");

上面的代码做了下面若干个事情.

1. 在数据区中划分一块内存存放字符串, 值是"abc", 这块内存一旦创建, 值"abc" 不能被修改.

2. 在堆区划分1块对象内存, 其中小块用于存放上面字符串的地址, 另一些用于存放函数指针.

3. 在栈区划分一块内存, 存放上面堆区的头部地址.


下面是1个例子:

package String_kng;

public class String_4{
    public static void f(){
        String s = new String("cat");
        String s2 = new String("cat");

        System.out.printf("s: %s\n", s); 
        System.out.printf("s2: %s\n", s2); 
        System.out.println(s == s2); 
        System.out.println(s.equals(s2));
    }
}

上面利用new 实例化了两个对象s和s2 , 它们所指向的字符串值都是"cat"

然后用 "==" 和 equals来比较两者

输出:

     [java] s: cat
     [java] s2: cat
     [java] false
     [java] true
  

可见用equals 来比较s 和 s2, 它们是相等的, 因为它们的内容相同. 而且equals方法在String类里重写过了.

而用 "==" 比较的是两个对象s 和 s2所指向的地址, 它们所指向的地址是不同的.

如下图:


亦即系讲, 两个new语句分别在数据区和堆区各自都划分2个内存.

数据区中有两个字符串内存, 它们的值是一样的都是"cat".

堆区有两个对象内存, 它们分别保存了各自对应的字符串地址.

而stuck区中两个s1 s2 保存了各自的堆区内存地址.  这两个地址明显是不同的. 也就是 s == s2 返回false的原因.



2.3 String类实例化对象的另一个方法.  = "abc"

事实上, 我们在编程中新建1个字符串更多情况下会用如下的方式:

String s = "abc";

这种方式更上面那种有什么区别呢?

package String_kng;

public class String_5{
    public static String g(){
        String s4 = "cat";
        return s4;
    }

    public static void f(){
        String s = new String("cat");
        String s2 = "cat";
        String s3 = "cat";
        System.out.printf("s: %s\n", s); 
        System.out.printf("s2: %s\n", s2); 
        System.out.printf("s3: %s\n", s3); 
        
        System.out.println(s == s2); 
        System.out.println(s2 == s3); 
        System.out.println(s2 == g()); 
    }
}



这个例子步骤也不复杂:


首先f()方法里 利用第一种方法实例化了1个值为"cat"的对象s

然后里利用第二种方法 又 创建了两个String 对象s2 和 s3, 它们的值都是"cat".

然后用"==" 来比较它们.

然后f()方法调用g()方法, g()方法利用第二方式实例化了1个值为"cat"的String 对象s4
最后用 "==" 比较s2 和 s4 的地址.



输出:

     [java] s: cat
     [java] s2: cat
     [java] s3: cat
     [java] false
     [java] true
     [java] true

由结果得知, s4 和 s2 和 s3的地址是相同的! 而由第一种方法创建的s 跟前面三者地址不同.


所以结论如下:

利用 = "cat" 方式创建1个String对象时, java 首先会检测当前进程的数据区是否有1个以相同方式创建的值是一样的字符串存在.

如果无, 则类似 new Sring("cat")方式, 在数据区和堆区都各自划分一块新内存, 用于该创建的对象.

如果有, 则直接把该对象的地址指向 已存在的堆区内存地址.


也就是讲, 在f() 里的String s2 = "cat" 相当于执行了 String s2 = new String("cat");

而在f()里的 String   s3 = "cat" 相当执行了String s3 = s2;

而在g()里, 理论上g()是不能访问f()里的 局部变量的, 但是g()还是检测到数据区存在用相同方式创建而且值1个样的字符串.

所以s4 也指向了堆区的那一块内存.


如下图:


这个例子说明了, 在同1个java程序中, 所有用 " = "abc" " 方式创建的而且具有相同值的多个String对象其实都是同1个对象. 因为它们指向同一块堆区的内存.

由于这种特性, 所以这种用" = "abc"" 方式创建的对象十分适合做 synchronized对象锁 要锁的对象.   不用担心锁的是两个不同的对象导致 多线程同步失败.



三, String类的常用方法.

下面是应付面试的, 大家看看就好.

1.

public char charAt(int index) //返回字符串中第index个字符


2.

public int length()  //返回字符串的长度


3.

public int indexOf(String str)

返回字符串中出现str的第1个位置


4.

public int indexOf(String str, int fromIndex)

返回字符串中, 从第fromIndex个字符数起, 出现str的第1个位置, 这个方法是上面方法的重载


5.

public boolean equalsIgnoreCase(String str)

忽略大小写, 比较两个字符是否相等.


6.

public String replace(char oldChar, char newChar)

返回1个新字符串, 该新字符串内的oldChar被newChar替换掉, 注意旧字符串没有被修改.


7.

public boolean startsWith(String prefix)

判断字符串是否以 prefix 开头


8.

public boolean endsWith(String suffix)

判断字符产是否以suffix 结尾


9.

public String subString(int beginIndex)

截取从第beginIndex个字符开始到最后1个字符, 返回1个新字符串


10.

public String subString(int beginIndex, int endIndex)

截取从第beginIndex个字符开始, 第endIndex个字符, 返回1个新字符串, 是上面方法的重载


11.

public static String valueOf(...)

注意这个是静态方法. 可以把其他基本数据类型转换成String对象


12.

Integer.parseInt(String s)

这个是另1个类Integer 的敬爱函数, 可以把字符串转换成int类型.  会抛出异常..





































  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Java中的String是用于表示字符串,它包含许多常用的方法来处理字符串,以下是一些常用的字符串方法及其返回值: 1. length()方法:返回字符串的长度,即包含的字符数。 2. charAt(int index)方法:返回字符串中指定位置的字符。 3. substring(int beginIndex, int endIndex)方法:返回从指定位置开始到指定位置结束的子字符串,其中beginIndex表示开始位置(包含),endIndex表示结束位置(不包含)。 4. indexOf(char ch)方法:返回指定字符在字符串中第一次出现的位置,如果不存在则返回-1。 5. indexOf(String str)方法:返回指定字符串字符串中第一次出现的位置,如果不存在则返回-1。 6. replace(char oldChar, char newChar)方法:用指定的新字符替换字符串中所有的旧字符,并返回替换后的新字符串。 7. replaceAll(String regex, String replacement)方法:用指定的新字符串替换字符串中符合正则表达式的所有子串,并返回替换后的新字符串。 8. toUpperCase()方法:将字符串中的所有字母转换为大写字母,并返回新字符串。 9. toLowerCase()方法:将字符串中的所有字母转换为小写字母,并返回新字符串。 10. trim()方法:去除字符串两端的空格,并返回新字符串。 以上这些方法是String中常用的一些字符串方法,能够满足大多数字符串操作的需求。 ### 回答2: Java中的String是非常重要的一个,常用于存储和操作字符串,其中字符串长度是一个基本概念。String中提供了一些常用方法来获取字符串长度。 1. length()方法:该方法返回一个字符串的长度,即字符串中字符的个数。例如: ```java String str = "Hello World!"; int len = str.length(); ``` 上面的代码中,len的值为12,因为字符串中一共有12个字符。 2. isEmpty()方法:该方法返回一个布尔值,判断一个字符串是否为空。如果一个字符串的长度为0,则认为它是空的。例如: ```java String str = ""; boolean isEmpty = str.isEmpty(); ``` 上面的代码中,isEmpty的值为true,因为str是一个空字符串。 3. trim()方法:该方法去除字符串两端的空格,并返回去除空格后的字符串。例如: ```java String str = " Hello World! "; str = str.trim(); ``` 上面的代码中,在执行str.trim()方法之后,str的值变为了"Hello World!",去除了两端的空格。 4. getBytes()方法:该方法返回一个字节数组,表示该字符串中每个字符的字节编码。例如: ```java String str = "Hello World!"; byte[] bytes = str.getBytes(); ``` 上面的代码中,bytes的长度为12,因为字符串中一共有12个字符,每个字符占用1个字节。 5. length vs length():需要注意的是,length和length()虽然都可以用来获取字符串长度,但是它们的返回值型不同。length是一个数组的属性,返回该数组的长度;而length()是String的方法,返回字符串的长度。因此,我们需要根据具体的情况选择使用哪个方法。 在实际开发中,我们会频繁地使用到这些方法,进行字符串长度的计算和判断,从而达到我们想要的程序效果。 ### 回答3: Java中的字符串是一种非常常见的数据型,可以使用String来创建和处理字符串。其中,字符串长度是String中的一个非常基本的方法,对于字符串的处理和操作都有着非常重要的意义。 字符串长度可以通过调用String中的length()方法来获取。该方法返回的是一个整数值,表示该字符串中字符的数量,包括空格和特殊字符。例如,对于下面的字符串String str = "Hello World!"; 调用str.length()方法将返回整数值12,它是该字符串中字符的数量。如果该字符串为空字符串,即“”,那么调用该方法将返回整数值0。 在实际的开发中,字符串长度的获取非常常见,比如在字符串的截取、比较、连接等操作中都需要用到字符串长度。另外,在一些具有字符个数限制的场景中(如数据库中的字段),该方法也可以用来判断字符串的长度是否符合规定。 除了length()方法,还有一个似的属性可以获取字符串的长度,就是String中的length属性。该属性是一个final型的整数,通过直接访问该属性可以获取字符串的长度。例如: String str = "Hello World!"; int len = str.length; 调用str.length将返回整数值12,它等价于调用str.length()方法。不过,需要注意的是,该属性是final型的,即一旦获取了长度值,就不能再修改了。 因此,对于字符串的操作和处理中,获取字符串长度是一项非常基本的操作。Java中提供了String中的length()方法和length属性来获取字符串的长度,能够满足我们大部分的需求。对于需要频繁使用的场景,也可以将获取到的长度值存储在变量中,以便下次使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值