字符串、日期
字符串(String)字符串常量池(String Constant Pool)字符串的初始化intern 方法字符串的常用方法(截取)可变字符串(StringBuilder)StringBuilder 的 append 原理日期DateSimpleDateFormatCalendar打印格式化(很少用)Java笔记目录可以点这里:Java 强化笔记
字符串(String)
Java 中用java.lang.String
类代表字符串:
Java 9 以前,底层使用char[]
存储字符数据
从 Java 9 开始,底层使用byte[]
存储字符数据所有字符串字面量(比如"abc"
)都是 String 类的实例String 对象一旦创建完毕,它的字符内容不可以修改、
首先来看一段代码:并思考一下这段代码的输出是否与你想的不一样。
public class Main {public static void main(String[] args) {String s = "555";s += "555";s = "666";test(s);System.out.println(s); // 666}static void test(String str) {str += "555";}}
String s = "555";
、s += "555";
、s = "666"
等操作都会在堆空间开辟一块新的内存空间,因为 String 是不可变字符串,每次要修改栈空间的变量(s
、str
)的值,只能改变它的指针指向,令指针指向堆空间新的内存。
我们知道,成员变量、函数形参等都是开辟在栈空间的(因为随时可能销毁),而上面代码中的s
与str
实际上在栈空间不是同一块布局,test
方法修改的是str
的指向的值,所以s
的值不会变。
字符串常量池(String Constant Pool)
Java 中有个字符串常量池(String Constant Pool,简称 SCP)
从 Java 7 开始属于堆空间的一部分(以前放在方法区)
当遇到字符串字面量时,会去查看 SCP
如果 SCP 中存在与字面量内容一样的字符串对象 A 时,就返回 A否则,创建一个新的字符串对象 D,并加入到 SCP 中,返回 D
String s1 = "mj"; // SCP中不存在字符串对象"mj"String s2 = "mj"; // SCP中存在字符串对象"mj", 直接返回SCP中的"mj"// 因此上面两个字符串对象是同一个对象System.out.println(s1 == s2); // true
字符串的初始化
在 C语言里,字符数组就是字符串;在 Java 中,String
底层是由char[]
组成的,但是他们不完全是一个东西。
利用 Java 的断点调试功能来验证上图:右半部分中的表示不是真实的内存地址,但是可以理解为标识不同则内存地址不同,相同则内存地址相同。可以看到s1
、s2
、s3
、s4
、cs
、s5
、s6
的标识都是不同的;但是s1
、s2
、s3
、s4
指向的value的标识都是30,与上图展示的一致;s5
、s6
指向的value的标识都是31。
intern 方法
A.intern
方法的作用:
如果 SCP 中存在与 A 内容一样的字符串对象 C 时,就返回 C否则,将 A 加入到 SCP 中,返回 A
int a = 1, b = 2, c = 3;String s1 = String.format("%d%d%d", a, b, c);String s2 = String.format("%d%d%d", a, b, c);// 去常量池中寻找"123", 发现没有, 就将s1的值"123"放入常量池String s3 = s1.intern(); // s3、s1都指向了常量池中的"123"String s4 = s2.intern(); // 返回常量池的"123"String s5 = "123"; // s5指向常量池中的"123"System.out.println(s1 == s2); // falseSystem.out.println(s1 == s3); // trueSystem.out.println(s1 == s4); // trueSystem.out.println(s1 == s5); // true
字符串的常用方法(截取)
// 去除左右的空格: "123 456"" 123 456 ".trim();// 转为大写字母: "ABC""abc".toUpperCase();// 转为小写字母: "abc""ABC".toLowerCase();// 是否包含某个字符串: true"123456".contains("34");// 是否以某个字符串开头: true"123456".startsWith("123");// 是否以某个字符串结尾: true"123456".endsWith("456");// 将字符串分隔为数组: [1, 2, 3, 4]"1_2_3_4".split("_");// 比较字符串的大小: <"abc".compareTo("adc");// 忽略大小写比较字符串的大小: <"abc".compareTo("ADC");String s1 = "abc";String s2 = new String("abc");// 查看字符串的内容是否相等: trues1.equals(s2);// 忽略大小写查看字符串的内容是否相等: true"abc".equalsIgnoreCase("ABC");// 字符串的替换String str = "hello123";// 由于String是不可变字符串, 必须用变量接收str = str.replace("123", "666"); // hello666
穿插个知识点,Eclipse 中抽取代码块的快捷键:先选中代码块,ALT+SHIFT+M
字符串截取:
String str = "abcdea";// 字符串的截取区间: [2, 4)str.substring(2, 4); // cdstr.substring(2); // cdeastr.indexOf("a"); // 0str.lastIndexOf("a"); // 5str.indexOf("z"); // -1
可变字符串(StringBuilder)
在进行大量字符串的改动操作时(比如拼接、替换)
使用String
会非常消耗内存、降低程序性能使用StringBuilder
可以节省内存、提高程序性能
String s1 = "";s1 += "123";s1 += "456";StringBuilder s2 = new StringBuilder();s2.append("123").append("456");
String
与StringBuilder
性能对比:50000 次字符串拼接操作
public static void main(String[] args) {testString(); // 2511mstestStringBuilder(); // 2ms}static void testString() {long begin = System.currentTimeMillis();String str = "";for (int i = 0; i < 50000; i++) {str += i;}long end = System.currentTimeMillis();System.out.println("String ->" + (end - begin) + "ms");}static void testStringBuilder() {long begin = System.currentTimeMillis();StringBuilder str = new StringBuilder();for (int i = 0; i < 50000; i++) {str.append(i);}long end = System.currentTimeMillis();System.out.println("StringBuilder ->" + (end - begin) + "ms");}
String -> 2511msStringBuilder -> 2ms
StringBuilder
的常用方法有:append
、insert
、delete
、replace
、reverse
等;
注意:
StringBuilder
并不是String
的子类 或者 父类StringBuilder
、String
都实现了CharSequence
接口
StringBuilder 的 append 原理
StringBuilder
的原理类似动态数组ArrayList
:底层会自动扩容;
源码中可以看到扩容的算法:(value.length << 1) + 2
即 新容量是原来容量的 2 倍 + 2
日期
Date
java.util.Date
是开发中经常用到的日期处理类(注意:不是java.sql.Date
)
包含了年、月、日、时、分、秒等信息
// date1 代表当前时间Date date1 = new Date();// System.currentTimeMillis 表示 1970-01-01 00:00:00 GMT 到现在时间的毫秒数// date2 也代表当前时间Date date2 = new Date(System.currentTimeMillis());// date3 代表 1970-01-01 00:00:00 GMT Date date3 = new Date(0);// Mon Apr 20 13:44:49 CST System.out.println(date1);// Mon Apr 20 13:44:49 CST System.out.println(date2);// Thu Jan 01 08:00:00 CST 1970System.out.println(date3);
System.currentTimeMillis
返回的是从 1970-01-01 00:00:00 GMT 开始到现在经历的毫秒数
1970-01-0100:00:00 GMT、1970-01-0108:00:00 CST代表的是同一个时间GMT(Greenwich Mean Time):格林尼治时间CST(China Standard Time UT+8:00):中国标准时间
Date
常用方法:
Date d1 = new Date();Date d2 = new Date();// 设置毫秒数, d1比d2早(d2经历的时间比d1大)d1.setTime(1000);d2.setTime(2000);// 获取毫秒数d1.getTime(); // 1000d2.getTime(); // 2000// 比较时间d2.after(d1); // trued1.before(d2); // pareTo(d2); // -1(d2经历的时间比d1大)
SimpleDateFormat
java.text.SimpleDateFormat
常用来进行日期的格式化处理
SimpleDateFormat fmt = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");// 利用日期对象生成字符串String str = fmt.format(new Date());// 04月20日 13:57:13System.out.println(str); // 解析字符串为日期对象Date date = fmt.parse(str);// Mon Apr 20 13:57:13 CST System.out.println(date);
SimpleDateFormat
的模式字母:
Calendar
java.util.Calendar
也是开发中经常用到的日期处理类
功能比Date
更加丰富,Date
中很多过期的方法都迁移到了Calendar
中
示例:目前是 4月20日 星期一 下午 14:16:33
// 表示当前时间Calendar c = Calendar.getInstance();c.getTime(); // Mon Apr 20 14:16:33 CST c.get(Calendar.YEAR); // 年: // 月(取值范围[0, 11], 0是1月, 11是12月)c.get(Calendar.MONTH); // 3// 一月中的第几天(取值范围[1, 31])c.get(Calendar.DAY_OF_MONTH); // 20// 一周中的第几天(取值范围[1, 7], 1是星期天, 2是星期一...,7是星期六)c.get(Calendar.DAY_OF_WEEK); // 2// 一年中的第几天(取值范围[1, 366])c.get(Calendar.DAY_OF_YEAR); // 111c.get(Calendar.HOUR); // 时: 2c.get(Calendar.MINUTE); // 分: 16c.get(Calendar.SECOND); // 秒: 33c.get(Calendar.MILLISECOND); // 毫秒: 330
Calendar
的常用方法:
Calendar c = Calendar.getInstance();// 设置时间为 7月6日c.set(, 06, 06);// 7月11日c.add(Calendar.DAY_OF_MONTH, 5);// 9月11日c.add(Calendar.MONTH, 2);// 设置Date对象c.setTime(new Date());// 获得Date对象c.getTime();// 设置毫秒数c.setTimeInMillis(System.currentTimeMillis());// 获得毫秒数c.getTimeInMillis();
打印格式化(很少用)
// 获取当前时间Calendar c = Calendar.getInstance();Date date = new Date();System.out.format("%tB %te, %tY%n", date, date, date); // 四月 20, System.out.format("%tl:%tM %tp%n", c, c, c); // 2:24 下午System.out.format("%tD%n", c); // 04/20/20