目录
- 1.String 为什么是不可变的?
- 2.字符串拼接用“+” 和 StringBuilder 有什么区别?
- 3.String、StringBuffer 和 StringBuilder 的区别是什么?
- 4.String 中的 equals() 和 Object 中的 equals() 有何区别?
- 5.Object 类有哪些常用的方法?
- 6.如何获取当前系统的剩余内存、总内存及最大堆内存?
- 7.LocalDateTime & Calendar
- 7.1.如何取当前的年、月、日、时、分、秒、毫秒?
- 7.2.如何获取从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数?
- 7.3.如何获取某年某月的最后一天?
- 7.4.如何打印昨天的当前时刻?
- 7.5.如何格式化日期?
1.String 为什么是不可变的?
(1)String 类中使用 final 关键字修饰的字符数组来保存字符串,如下面的代码所示:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
(2)我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。
(3)String 真正不可变的原因如下:
① 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
② String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 的不可变性。
2.字符串拼接用“+” 和 StringBuilder 有什么区别?
(1)Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
public static void main(String[] args) {
String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;
}
上述代码对应的字节码如下:
可以看出,字符串对象通过 “+” 的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象。不过在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象,从而比较占用内存空间,并且效率较低。
public static void main(String[] args) {
String[] arr = {"he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
s += arr[i];
}
System.out.println(s);
}
上述代码对应的字节码如下所示:
(2)如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。
public static void main(String[] args) {
String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
s.append(value);
}
System.out.println(s);
}
上述代码对应的字节码如下所示:
如果在 IDEA 中使用 “+” 来拼接字符串的话,会出现建议使用 StringBuilder 的提示,如下图所示:
查看字节码的方式之一:
在控制台使用命令 javap -v xxx.class 即可,不过在此之前需要先将 xxx.java 进行编译得到 xxx.class 文件才行。
3.String、StringBuffer 和 StringBuilder 的区别是什么?
(1)底层数据结构
- String 是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个 final 类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对 String 的操作都会生成新的 String 对象。例如每次拼接操作(即两个字符串相加): 隐式地在堆上 new 了一个跟原字符串相同的 StringBuilder 对象,再调用 append() 拼接后面的字符。
private final char value[];
- StringBuffer 和 StringBuilder 都继承了 AbstractStringBuilder 抽象类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰(如下面的代码所示),最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法,例如 append 方法。所以在进行频繁的字符串操作时,建议使用 StringBuffer 和 StringBuilder 来进行操作。
/**
* The value is used for character storage.
*/
char[] value;
(2)线程安全性
- String 中的对象是不可变的,也就可以理解为常量,所以是线程安全的、
- StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
- StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
(3)性能
- 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
- StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
- 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
- 性能:StringBuilder > StringBuffer > String
(4)使用场景
- 操作少量的数据:建议使用 String;
- 单线程操作字符串缓冲区下操作大量数据: 建议使用 StringBuilder;
- 多线程操作字符串缓冲区下操作大量数据: 建议使用 StringBuffer;
4.String 中的 equals() 和 Object 中的 equals() 有何区别?
String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。Object 的 equals 方法是比较的对象的内存地址。
5.Object 类有哪些常用的方法?
(1)Object 是所有类的根,是所有类的父类,所有对象包括数组都实现了 Object 的方法。Object 类结构如下图所示:
(2)各种方法介绍如下:
① clone()
保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone()。
② finalize()
该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。
③ equals()
该方法使用频率非常高。equals 和 == 的区别见上面的4.1题,但是在 Object 中两者是一样的。子类一般都要重写这个方法。
④ hashCode()
该方法用于哈希查找,重写了 equals() 一般都要重写 hashCode(),这个方法在一些具有哈希功能的 Collection 中用到。
⑤ notify()
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)。
⑥ notifyAll()
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。
⑦ wait()
配合 synchronized 使用,wait() 就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 一直等待,直到获得锁或者被中断。wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:
- 其他线程调用了该对象的 notify 方法;
- 其他线程调用了该对象的 notifyAll 方法;
- 其他线程调用了 interrupt 中断该线程;
- 时间间隔到了。此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。
6.如何获取当前系统的剩余内存、总内存及最大堆内存?
(1)可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存、总内存及最大堆内存。通过下面方法可以获取到堆使用的百分比及堆内存的剩余空间。
freeMemory() 方法返回剩余空间的字节数
totalMemory() 方法总内存的字节数
maxMemory() 返回最大内存的字节数
(2)示例如下:
class Solution {
public static void main(String[] args) {
System.out.println("JVM 从操纵系统那里挖到的最大的内存 maxMemory : " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "M");
System.out.println("JVM 已经从操作系统那里挖过来的内存 totalMemory : " + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "M");
System.out.println("JVM 从操纵系统挖过来还没用上的内存 freeMemory : " + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
byte[] b1 = new byte[3 * 1024 * 1024];
System.out.println("JVM 从操纵系统那里挖到的最大的内存 maxMemory " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "M");
System.out.println("JVM 已经从操作系统那里挖过来的内存 totalMemory : " + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "M");
System.out.println("JVM 从操纵系统挖过来还没用上的内存 freeMemory : " + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
}
}
输出结果如下:
JVM 从操纵系统那里挖到的最大的内存 maxMemory : 3154M
JVM 已经从操作系统那里挖过来的内存 totalMemory : 213M
JVM 从操纵系统挖过来还没用上的内存 freeMemory : 208M
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
JVM 从操纵系统那里挖到的最大的内存 maxMemory 3154M
JVM 已经从操作系统那里挖过来的内存 totalMemory : 213M
JVM 从操纵系统挖过来还没用上的内存 freeMemory : 205M
7.LocalDateTime & Calendar
7.1.如何取当前的年、月、日、时、分、秒、毫秒?
创建 java.util.Calendar 实例,调用其 get() 传入不同的参数即可获得参数所对应的值。
import java.util.Calendar;
public class TestDateAndTime {
public static void main(String[] args) {
//获取当前的年、月、日、时、分、秒、毫秒
Calendar calendar = Calendar.getInstance();
//年
System.out.println(calendar.get(Calendar.YEAR));
//月,需要注意的是 Calendar.MONTH 是从 0 开始的
System.out.println(calendar.get(Calendar.MONTH) + 1);
//天
System.out.println(calendar.get(Calendar.DATE));
//时
System.out.println(calendar.get(Calendar.HOUR));
//分
System.out.println(calendar.get(Calendar.MINUTE));
//秒
System.out.println(calendar.get(Calendar.SECOND));
//毫秒
System.out.println(calendar.get(Calendar.MILLISECOND));
}
}
7.2.如何获取从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数?
import java.util.Calendar;
public class TestDateAndTime {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
//以下 2 种方法均可以获取从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数
System.out.println(System.currentTimeMillis());
System.out.println(Calendar.getInstance().getTimeInMillis());
}
}
7.3.如何获取某年某月的最后一天?
import java.time.LocalDate;
import java.util.Calendar;
public class TestDateAndTime {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
//某月最后一天
//2018-05月最后一天,6月1号往前一天
calendar.set(Calendar.YEAR, 2018);
calendar.set(Calendar.MONTH, 5);
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.add(Calendar.DAY_OF_MONTH, -1);
System.out.println(calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) + 1) + "-" + calendar.get(Calendar.DAY_OF_MONTH));
//JDK 1.8 java.time 包
LocalDate date = LocalDate.of(2019, 6, 1).minusDays(1);
System.out.println(date.getYear() + "-" + date.getMonthValue() + "-" + date.getDayOfMonth());
}
}
7.4.如何打印昨天的当前时刻?
import java.time.LocalDateTime;
public class YesterdayCurrent {
public static void main(String[] args) {
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minusDays(1);
System.out.println(yesterday);
}
}
或者使用以下方式:
import java.util.Calendar;
public class YesterdayCurrent {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());
}
}
7.5.如何格式化日期?
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class TestDateAndTime {
public static void main(String[] args) {
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化日期
System.out.println(simpleDateFormat.format(date));
//JDK 1.8 java.time 包
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}