JAVA学习笔记 05 - 字符串

本文是Java基础课程的第五课。主要介绍Java中的字符串,包括字符串基本的声明、使用,字符串在JVM中的内存分配,字符串常用的API,字符串与基本数据类型的转换等内容

文章目录

  • 一、认识字符串
    • 1、字符串的声明和初始化
    • 2、字符串的连接
    • 3、字符串的比较
    • 4、内存中的字符串
  • 二、字符串常用API
    • 1、什么是API
    • 2、字符串常用API
    • 3、案例
      • 3.1 案例1
      • 3.2 案例2
      • 3.3 案例3
      • 3.4 案例4
      • 3.5 案例5
      • 3.6 案例6
  • 三、字符串与基本数据类型的转换
    • 1、基本数据类型转换为String类型
    • 2、String类型转换为基本数据类型

一、认识字符串

字符(char)类型是Java的基本数据类型之一,用来存储单个字符。在开发过程中,往往多个字符一起才能表达一个有意义的数据。Java提供了字符串String类型,用来处理一连串的字符。字符串便是由若干字符组成的序列

1、字符串的声明和初始化

Java中,声明和初始化一个字符串的语法格式如下:

String 变量名 = "初始值";
// 或
String 变量名 = new String("初始值");

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		String str1 = "Hello world";
		String str2 = new String("Hello World");
	}
}

说明:

  • 一旦创建了String对象,那它的无法改变了。
  • 事实上,String类型用一个char类型数组来存储,处理字符串的。

2、字符串的连接

多个字符串连接在一起,也可以将字符串与基本类型变量连接。最常用连接字符串的方法是使用+操作符
下面是一个示例:

public class Test {
	public static void main(String[] args) {
		String str1 = "Hello";
		String str2 = "World";
		String str3 = str1 + str2;
		String str4 = "Hello" + "World";
		int num1 = 123;
		String str5 = num1 + str1;
		String str6 = str1 + num1;
		System.out.println("str1 = " + str1);
		System.out.println("str2 = " + str2);
		System.out.println("str3 = " + str3);
		System.out.println("str4 = " + str4);
		System.out.println("str5 = " + str5);
		System.out.println("str6 = " + str6);
	}
}

说明:

  • 字符串基本数据类型变量使用+操作符进行连接时,会基本类型转换字符串类型,然进行连接操作。

3、字符串的比较

在程序中,比较两个数据是否相等是一种很常见的操作,基本数据类型的变量之间使用==操作符便可以判断变量中保存的数据是否相等。而字符串之间的比较有所不同。

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		String str1 = "Hello";
		String str2 = "Hello";
		String str3 = new String("Hello");
		System.out.println("(str1 == str2) = " + (str1 == str2));
		System.out.println("(str1 == str3) = " + (str1 == str3));
		System.out.println("str1.equals(str3) = " + str1.equals(str3));
	}
}

说明:

  • ==操作符比较str1str2,结果为true,事实上,==操作符比较的是变量中存储的内容数据内存地址编号)。字符串是引用类型,str1 == str2的结果为true,意味着str1str2两个变量中存储着同样的内存地址编号,即引用了同一块内存(这一点在下面会详细介绍)。
  • ==操作符比较str1str2,结果为false,意味着str1str3两个变量分别存储了不同的内存地址编号。
  • String类型提供了equals(Object anObject)方法,可以比较字符串字面值是否相同,str1.equals(str3)的返回结果为true。因此,如果要比较字符串的字面值是否相同,需要使用equals(Object anObject)方法

4、内存中的字符串

观察下面的示例:

public class Test {
	public static void main(String[] args) {
		// 基本数据类型
		int num1 = 1;
		int num2 = 1;
		System.out.println("(num1 == num2) = " + (num1 == num2));

		// 数组,引用数据类型
		char[] char1 = {'H'};
		char[] char2 = {'H'};
		System.out.println("(char1 == char2) = " + (char1 == char2));

		// String,引用数据类型
		String str1 = "Hello";
		String str2 = "Hello";
		System.out.println("(str1 == str2) = " + (str1 == str2));
	}
}

num1num2两个基本数据类型的变量进行==运算,这两个变脸中存储的都是基本数据类型的值,即1,因此返回结果是truechar1char2两个数组进行==运算,因为它们是引用类型,分别在堆内存中申请了内存空间,所以char1char2两个变量中存储的是不同的内存地址编号,故而返回结果为false。同为引用类型的String型的变量,str1str2使用==操作符比较时,返回的结果是true,即str1str2引用的内存地址相同,这是什么原因呢?

事实上,这是Java有意为之,由于字符串这一类型的数据是程序中出现频率的数据类型,并且字符串内容重复概率也非常的,如果全部分开申请内存的话,将会非常耗费内存,Java为了节省内存考虑,设计了字符串池这一内存区域(字符串池在JDK 7以前设计在方法区中,而在JDK 8以后设计在堆内存中),下面用图示说明上例中两个变量str1str2中存储的内存地址编号为何相同:
JAVA学习笔记 05 - 字符串_第1张图片
说明:

  1. 执行代码String str1 = "Hello"时,首先在栈中main方法对应栈帧的局部变量表中,为变量str1分配内存查找字符串池是否有一块内存中已经存储Hello这一字符串字面量没有的话,字符串池分配内存并存储字符串字面量"Hello"赋值操作会将字符串池中已经分配好的内存空间的首地址存储到变量str1局部变量表对应位置
  2. 执行代码String str2 = "Hello"时,栈中main方法对应栈帧的局部变量表中,为变量str2分配内存查找字符串池是否有一块内存中已经存储Hello这一字符串字面量的话,引用该内存地址
  3. 执行代码System.out.println("(str1 == str2) = " + (str1 == str2)),由于变量str1str2引用的内存地址相同,故返回结果为true

进一步观察下面的另一个例子:

public class Test {
	public static void main(String[] args) {
		String str1 = "Hello";
		String str2 = "Hello";
		String str3 = new String("Hello");
		System.out.println("(str1 == str2) = " + (str1 == str2));
		System.out.println("(str1 == str3) = " + (str1 == str3));
	}
}

前面的例子已经进行了分析,变量str1str2引用了字符串池中相同的内存地址,str1 == str2的结果为true。运行str1 == str3的结果为false,这又是什么原因呢?

原因就在于使用了new运算符,下面用图示来说明:

说明:

  • 执行代码new String("Hello")时,会判断字符串池是否已经存储了Hello这一字符串字面量没有的话,字符串池中会分配内存存储Hello这一字符串字面量,同时,堆内存分配内存存储Hello这一字符串字面量,最终,返回堆内存分配好的内存空间首地址。因此,变量str1str3中存储的是不同的内存地址编号,str1 == str3的结果为false
  • 使用new运算符时,总会堆内存分配内存空间

再来观察一个例子:

public class Test {
	public static void main(String[] args) {
		String str1 = "Hello";
		String str2 = "HelloWorld";
		String str3 = "Hello" + "World";
		String str4 = str1 + "World";
		System.out.println("(str2 == str3) = " + (str2 == str3));
		System.out.println("(str2 == str4) = " + (str2 == str4));
	}
}

str2 == str3的结果为true,即变量str2str3引用的内存地址相同;而str2 == str4的结果为false,即变量str2str4引用的内存地址不同,这又是怎么回事呢?

str2 == str3的结果为true,原因在于字符串字面量连接,在编译阶段就已经完成了,String str3 = "Hello" + "World"String str3 = "HelloWorld",对于JVM来说并没有什么区别;而str2 == str4的结果为false,是由于字符串变量参与字符串连接,这一过程发生在程序运行时,会在堆内存申请内存空间。下面用图示来说明:

说明:

  1. 执行代码String str2 = "HelloWorld"时,字符串池分配内存存储HelloWorld这一字符串字面量,变量str2引用该内存地址。
  2. 执行代码String str3 = "Hello" + "World"时,会在字符串池查找HelloWorld这一字符串字面量直接返回内存地址,故变量str3str2引用相同的内存地址。
  3. 执行代码String str4 = str1 + "World"时,会将字符串变量str1中的字符串Hello和字符串字面量World进行连接,并在堆内存分配内存空间进行存储,最终变量str4引用该堆内存中的内存地址。

最后来观察一个例子:

public class Test {
	public static void main(String[] args) {
		// 引用数据类型的变量相互赋值
		int[] nums1, nums2;
		nums1 = new int[1];
		nums1[0] = 3;
		nums2 = nums1;
		nums2[0] = 4;
		System.out.println("nums1[0] = " + nums1[0]);
		// 字符串类型的变量相互赋值
		String str1, str2;
		str1 = "Hello";
		str2 = str1;
		str2 = "World";
		System.out.println("str1 = " + str1);
	}
}

打印nums1[0]的结果为4,作为引用类型变量,将数组nums2赋值给数组nums1时,实际拷贝的是内存地址编号,变量nums1nums2引用同一内存地址改变数组nums2元素,其实就是改变数组nums1元素。打印str1的结果为Hello,同样作为引用类型,在执行str2 = str1后,字符串变量str1str2也引用同一内存地址,可是改变变量str2的值,变量str1却没有发生变化,这又是为什么呢?

事实上,在程序运行str2 = "World"之后,字符串变量str1str2也并不再引用同一内存地址了,仍然用图示来说明:
JAVA学习笔记 05 - 字符串_第2张图片

说明:

  1. 执行代码str1 = "Hello"时,首先查找字符串池是否有一块内存中已经存储Hello这一字符串字面量没有的话,字符串池分配内存并存储字符串字面量"Hello"赋值操作会将字符串池中已经分配好的内存空间的首地址存储到变量str1局部变量表对应位置
  2. 执行代码str2 = str1时,变量str2str1引用字符串池中同一内存地址。
  3. 执行代码str2 = "World"时,首先查找字符串池是否有一块内存中已经存储World这一字符串字面量没有的话,字符串池分配内存并存储字符串字面量"World"赋值操作会将字符串池中已经分配好的内存空间的首地址存储到变量str2局部变量表对应位置。注意,这一过程中变量str1并没有受到影响。

二、字符串常用API

在开发工程中,经常需要对字符串进行各种操作,熟练掌握字符串的各种操作,对编码过程很有帮助。

1、什么是API

API(Application Programming Interface),即应用程序编程接口,对这一概念的定义大多数都晦涩难懂。这里通俗的理解这一概念:

  • A(Application),即应用,按不同的场合,可以是一个提供特定功能的软件一个提供某些数据的网络服务一个作为程序组件的类库、甚至是一个硬件设备,等等。
  • P(Programming),即编程
  • I(Interface),即接口,是不同系统实体交接并通过它彼此作用媒介(或者理解为方式、渠道、手段)。

所以,API就是在编程过程中实体或系统之间进行信息交换的媒介

更直白的理解,当某一应用需要与其他应用进行信息交换时,需要通过某一媒介告知对方应用一些信息,同时获取本应用所需要信息,同时,该媒介应当隐藏这些应用的其他细节,这一媒介便是API,与这一媒介具有同样性质事物都可以称之为API

开发人员在使用API时,无需关心API后面隐藏的实现细节仅需要关心API的名称输入什么数据获得什么数据这三点就足够了。

2、字符串常用API

Java中的字符串提供给开发人员许多非常有用的API,通过它们,可以很轻松的获得字符串的一些常用信息,或者对字符串进行一些加工处理。
Java中字符串常用API有:

方法 返回值类型 方法说明
equals(Object anObject) boolean 将此字符串与指定的对象比较
equalsIgnoreCase(String anotherString) boolean 忽略大小写比较两个字符串的字面值是否相等
length() int 获取字符串的长度
charAt(int index) char 获取指定索引处的 char
indexOf(String str) int 获取指定子字符串在原字符串中第一次出现处的索引
lastIndexOf(String str) int 获取指定子字符串在原字符串中最右边出现处的索引
startsWith(String prefix) boolean 测试原字符串是否以指定的前缀开始
endsWith(String suffix) boolean 测试原字符串是否以指定的后缀结束
toLowerCase() String 获取原字符串对应的小写字符串
toUpperCase() String 获取原字符串对应的大写字符串
substring(int beginIndex) String 截取原字符串,从传入参数beginIndex为下标的位置开始截取到末尾
substring(int beginIndex, int endIndex) String 截取原字符串,从参数beginIndex为下标的位置开始截取到参数endIndex为下标的位置
trim() String 去掉原字符串首尾的空格
split(String regex) String[] 将原字符串按照传入参数regex分割为字符串数组
replace(String regex, String replacement) String 将原字符串中指定的内容替换成另外的内容

3、案例

3.1 案例1

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		// 验证用户名长度,长度在6至20之间为合法,否则为不合法
		Scanner input = new Scanner(System.in);
		System.out.println("请输入用户名");
		String name = input.next();
		if (name.length() >= 6 && name.length() <= 20) {
			System.out.println("用户名长度合法");
		} else {
			System.out.println("用户名长度不合法");
		}
	}
}

说明:

  • 该案例演示验证用户名的长度,需要从键盘上输入用户名,对用户名进行验证,合法的用户名长度在6到20之间。如果在这区间,输出用户名长度合法,否则输出用户名长度不合法。
  • 使用字符串length()方法可以获取字符串长度

3.2 案例2

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		/*
		  从键盘上输入email,对email进行验证,合法的email的条件是
		  1. 必须包含“@”和“.”
		  2. “@”必须在“.”的前面且不能连着
		  3. “@”不在开头和结尾,并且只能出现一次
		 */
		Scanner input = new Scanner(System.in);
		System.out.println("请输入Email");
		String email = input.next();
		int atIndex = email.indexOf("@");
		int dotIndex = email.indexOf(".");
		if (atIndex == -1 || dotIndex == -1) { //必须包含“@”和“.”
			System.out.println("Email非法,不存在@或.");
		} else if (atIndex >= dotIndex - 1) {  //@必须在.的前面且不能连着
			System.out.println("Email非法,@必须在.的前面且不能连着");
		} else if (email.startsWith("@") || email.endsWith("@")) { //“@”不在开头和结尾
			System.out.println("Email非法,“@”不能出现在开头和结尾");
		} else if(email.lastIndexOf("@") != atIndex){ // “@”只能出现一次
			System.out.println("Email非法,要求@只能出现一次");
		}else {
			System.out.println("Email合法");
		}
	}
}

说明:

  • 该案例演示电子邮箱地址校验,校验规则为必须包含“@”和“.”,“@”必须在“.”的前面且不能连着,“@”不在开头和结尾,并且只能出现一次。
  • 使用字符串indexOf(String str)方法可以获取指定子字符串原字符串第一次出现处的索引没有找到返回-1
  • 使用字符串startsWith(String str)方法可以判断原字符串是否以指定前缀开始
  • 使用字符串endsWith(String str)方法可以判断原字符串是否以指定后缀结束
  • 使用字符串lastIndexOf(String str)方法可以获取指定子字符串原字符串最后一次出现处的索引没有找到返回-1

3.3 案例3

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		// 将新闻标题中的“爪洼”换成“Java”
		String title = "爪洼技术发展这些年";
		System.out.println("替换前的标题:" + title);
		title = title.replace("爪洼", "Java");
		System.out.println("替换后的标题:" + title);
	}
}

说明:

  • 该案例演示将新闻标题中的“爪洼”换成“Java”。
  • 使用字符串replace(String regex, String replacement)方法可以将原字符串指定内容替换成另外内容

3.4 案例4

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		// 截取路径中的文件名称
		String path = "C:\\HTML\\front\\assets\\img\\pc\\logo.png";
		int startIndex = path.lastIndexOf("\\");
		int endIndex = path.lastIndexOf(".");
		String fileName = path.substring(startIndex + 1, endIndex);
		System.out.println(path + "路径中的文件是:" + fileName);
	}
}

说明:

  • 该案例演示截取路径中的文件名称。
  • 使用字符串substring(int beginIndex, int endIndex)方法可以获取指定索引处char

3.5 案例5

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		/*
		  检测输入的单词是否是回文单词
		  回文单词即单词倒过来与原单词一样,比如“level”、“pop”、“noon”等
		 */
		Scanner input = new Scanner(System.in);
		System.out.println("请输入需要检测的单词:");
		String word = input.next();
		// 检测回文单词的逻辑
		boolean isPalindromeWord = true;
		for (int i = 0; i < word.length() / 2; i++) {
			if (word.charAt(i) != word.charAt(word.length() - 1 - i)) {
				isPalindromeWord = false;
			}
		}
		System.out.println(word + (isPalindromeWord ? "是" : "不是") + "回文单词");
	}
}

说明:

  • 该案例演示回文单词检测。
  • 使用字符串charAt(int index)方法可以从指定开始位置结束位置截取字符串

3.6 案例6

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		// 字符串格式化
		String str1 = String.format("见过,%s及%s", "晁天王", "众位头领");
		System.out.println(str1);
		String str2 = String.format("字母a的大写是:%c", 'A');
		System.out.println(str2);
		String str3 = String.format("3 > 7的结果是:%b", 3 > 7);
		System.out.println(str3);
		String str4 = String.format("100的一半是:%d", 100 / 2);
		System.out.println(str4);
		//使用printf代替format方法来格式化字符串
		System.out.printf("50元的书打8.5折扣是:%f 元", 50 * 0.85);
	}
}

说明:

  • 输出结果为:
    见过,晁天王及众位头领
    字母a的大写是:A
    3 > 7的结果是:false
    100的一半是:50
    50元的书打8.5折扣是:42.500000
  • String类的format(String format, Object... args)方法用于创建格式化字符串。该方法无需String类型的变量直接使用类名String即可调用。该方法第一个参数被格式化字符串第二个参数替换格式符的字符串,第二个参数中的表示方法可变参数,即参数个数根据格式符个数来确定字符串格式化就是使用第二个可变参数中的值按照顺序替换第一个参数中的格式符format(String format, Object... args)方法的格式符定义如下:
    格式符 说明 示例
    %s 字符串类型 "李逵"
    %c 字符类型 'm'
    %b 布尔类型 true
    %d 整数类型(十进制) 100
    %x 整数类型(十六进制) FF
    %o 整数类型(八进制) 77
    %f 浮点类型 99.99
  • 字符串的格式化避免了使用+来连接字符串,使得字符串的构建更方便。
  • System.out.printf(String format, Object ... args)具有相同功能,但是能用于一次输出

三、字符串与基本数据类型的转换

在实际开发中,经常遇到字符串类型基本类型转换操作

1、基本数据类型转换为String类型

基本类型的数据转换字符串类型有通常有两种方法:

  1. 通过字符串连接将基本类型转换成String类型。
  2. 通过String类型提供的valueOf(基本类型数据 变量名)方法将基本类型转换成字符串类型。

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		// 通过连接字符串将基本类型转换为String类型
		int num1 = 10;
		double num2 = 3.14;
		boolean boolean1 = true;
		String sNum1 = "" + num1;
		String sNum2 = "" + num2;
		String sBoolean1 = "" + boolean1;
		// 通过valueOf(基本类型数据 变量名)方法将基本类型转换为String类型
		int num3 = 10;
		double num4 = 3.14;
		boolean boolean2 = true;
		String sNum3 = String.valueOf(num3);
		String sNum4 = String.valueOf(num4);
		String sBoolean2 = String.valueOf(boolean2);
	}
}

说明:

  • 使用+运算符基本类型与字符串类型连接时,Java首先自动将基本类型转换成字符串类型,然后连接在一起.。
  • 使用String类的valueOf(基本类型数据 变量名)方法可以将基本类型转换成字符串类型,该方法无需String类型的变量直接使用类名String即可调用

2、String类型转换为基本数据类型

字符串类型转换为基本类型,需要使用基本类型包装类。Java为每一种基本类型都提供了对应包装类,包装类提供了一些常用的操作,其中就包括将字符串类型转换成基本类型。基本类型的包装类及其转换方法如下表:

基本类型 包装类 方法 方法说明
byte Byte parseByte(String s) 将字符串转换为byte类型
short Short parseShort(String s) 将字符串转换为short类型
int Integer parseInt(String s) 将字符串转换为int类型
long Long parseLong(String s) 将字符串转换为long类型
float Float parseFloat(String s) 将字符串转换为float类型
double Double parseDouble(String s) 将字符串转换为double类型
boolean Boolean parseBoolean(String s) 将字符串转换为boolean类型

下面是一个示例:

public class Test {
	public static void main(String[] args) {
		String str1 = "1", str2 = "true", str3 = "3.14";
		byte num1 = Byte.parseByte(str1);
		short num2 = Short.parseShort(str1);
		int num3 = Integer.parseInt(str1);
		long num4 = Long.parseLong(str1);
		float num5 = Float.parseFloat(str3);
		double num6 = Double.parseDouble(str3);
		boolean boolean1 = Boolean.parseBoolean(str2);
	}
}

说明:

  • 本例中的转换方法均无需对应类型的变量,直接使用类名即可调用
  • 字符串与char类型的转换可以通过字符串的charAt(int index)方法完成。如下例:
    public class Test {
    	public static void main(String[] args) {
    		String str = "Hello World";
    		char char1 = str.charAt(6);
    		System.out.println("Hello World中第6位下标是字符" + char1);
    		// “Hello World中第6位下标是字符W”
    	}
    }
    

你可能感兴趣的:(JAVA学习笔记)