String 类型是不可变类,StringBuffer 和 StringBuilder 是可变类。
速度:StringBuilder > StringBuffer > String。
StringBuffer 和 StringBuilder 底层是 char[] 数组实现的。
StringBuffer是线程安全的,而 StringBuilder 是线程不安全的。
如果要操作少量的数据用 String,单线程操作大量数据用 StringBuilder,多线程操作大量数据用 StringBuffer。
垃圾回收由 java 虚拟机自动执行,不能人为的干预,系统在空闲的时候会自动执行垃圾回收机制,可以通过 System.gc() 方法建议执行垃圾回收,但不能确定什么时候回执行回收。
在 JVM 垃圾回收器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java 提供了默认机制来终止该对象并释放资源,这个方法就是 finalize()。
垃圾回收指的是对内存的回收,而这里的内存主要指 JVM 的堆区和方法区的内存。
sleep() 方法是线程类 Thread 的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu 的执行时间。sleep() 是 static 静态的方法,它不能改变对象的锁,当一个 synchronized 块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
wait() 是 Object 类的方法,当一个线程执行到 wait 方法时,它就进入到一个和该对象相关的等待池,同时释放对象的锁,使得其他线程能够访问,可以通过 notify 或 notifyAll 方法来唤醒等待的线程。
/**
* @author Renda Zhang
* @create 2020-06-24 16:40
*/
public class FrogJumpStairs {
// 总台阶数
private static final int TOTAL_STAIRS = ;
/**
* 数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
* 使用 Fibonacci sequence(斐波那契数列)来解答
* 时间复杂度:O(n) 单循环到 n
* 空间复杂度:O(1)
*
* @param total 总的台阶数量
* @return 所有跳法的总数量
*/
private static int jumpStairsFibonacci(int total) {
if (total == ) {
return ;
}
int firstNum = ;
int secondNum = ;
for (int i = ; i <= total; i++) {
int third = firstNum + secondNum;
firstNum = secondNum;
secondNum = third;
}
return secondNum;
}
/**
* Dynamic Programming (动态规划)
* 时间复杂度:O(n) 单循环到 n
* 空间复杂度:O(n) dp 数组用了 n 空间
*
* @param total 总的台阶数量
* @return 所有跳法的总数量
*/
private static int jumpStairsDp(int total) {
if (total == ) {
return ;
}
int[] dp = new int[total + ];
dp[] = ;
dp[] = ;
for (int i = ; i <= total; i++) {
dp[i] = dp[i - ] + dp[i -];
}
return dp[total];
}
// 记忆每一层递归对应的数值
private static int[] memo;
/**
* 递归解法的优化
* 时间复杂度:O(n)
* 空间复杂度:O(n)
*
* @param current 青蛙已经跳过的台阶数量
* @param total 总的台阶数量
* @return 所有跳法的总数量
*/
private static int jumpStairsMemo(int current, int total) {
// 如果目前已经跳过的台阶数大于总台阶数,说明传入的参数不合理,返回 0 代表跳法为 0
if (current > total) {
return ;
}
// 如果相等,说明青蛙已经跳完一次。
if (current == total) {
return ;
}
// 说明已有记录,直接返回
if (memo[current] > ) {
return memo[current];
}
// 通过递归把所有次数相加即得到总次数。
memo[current] = jumpStairsMemo(current + , total) + jumpStairsMemo(current + , total);
return memo[current];
}
/**
* 递归暴力破解法
* 时间复杂度:O(2ⁿ) - 递归树的所有节点数
* 空间复杂度:O(n) - 递归树可达深度
*
* @param current 青蛙已经跳过的台阶数量
* @param total 总的台阶数量
* @return 所有跳法的总数量
*/
private static int jumpStairs(int current, int total) {
// 如果目前已经跳过的台阶数大于总台阶数,说明传入的参数不合理,返回 0 代表跳法为 0
if (current > total) {
return ;
}
// 如果相等,说明青蛙已经跳完一次。
if (current == total) {
return ;
}
// 通过递归把所有次数相加即得到总次数。
return jumpStairs(current + , total) + jumpStairs(current + , total);
}
public static void main(String[] args) {
System.out.println(jumpStairs(, TOTAL_STAIRS));
memo = new int[TOTAL_STAIRS + ];
System.out.println(jumpStairsMemo(, TOTAL_STAIRS));
System.out.println(jumpStairsDp(TOTAL_STAIRS));
System.out.println(jumpStairsFibonacci(TOTAL_STAIRS));
}
}
实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用。
两者的主要区别如下
hash & 0x7FFFFFFF
后,再对 length 取模,& 0x7FFFFFFF
的目的是为了将负的 hash 值转化为正值,因为 hash 值有可能为负数,而 & 0x7FFFFFFF
后,只有符号位改变。字符流 Character Stream 和字节流 Byte Stream 是 IO 流的划分,按处理照流的数据单位进行的划分,两类都分为输入和输出操作。
在字节流中输出数据主要是使用 OutputStream 类完成,输入使的是 InputStream 类;在字符流中输出主要是使用 Writer 类完成,输入流主要使用 Reader 类完成。这四个都是抽象类。
字符流处理的单元为 2 个字节的 Unicode 字符,操作“字符、字符数组或字符串”;而字节流处理的单元为 1 个字节,操作“字节和字节数组”。
字节流是最基本的,所有的 InputStrem 和 OutputStream 的子类都是主要用在处理二进制数据,是按字节来处理的;但是实际中很多的数据是文本,所以又提出了字符流的概念,它是按虚拟机的字符编码来处理的,也就是要进行字符集的转化。
字节流和字符流之间通过 InputStreamReader 和 OutputStreamWriter 来关联,底层是通过 byte[] 和 String 来关联的。
1、ArrayList 支持以角标位置索引来获取对应的元素(随机访问);但是 LinkedList 则需要遍历整个链表来获取对应的元素。因此一般 ArrayList 的访问速度要快于 LinkedList。 2、ArrayList 由于是数组结构,对于删除和修改而言消耗是比较大(需要复制和移动数组);而 LinkedList 是双向链表,删除和修改只需要修改对应的指针即可,其消耗是很小的。因此一般 LinkedList 的增删速度比 ArrayList 快。
3、它们都是线程不安全的,但动态数组 Vector 类集合依靠同步访问的方式达到线程安全的目的。
4、ArrayList 调用无参构造后初始长度为 0,当第一次调用 add 后,长度变为 10;而 LinkedList 是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在头或尾进行增删。
考察重载函数调用时精确性的问题。
思考一下下面程序的输出结果
public class TestNull {
public void show(String a){
System.out.println("String");
}
public void show(Object o){
System.out.println("Object");
}
public static void main(String args[]){
TestNull t = new TestNull();
t.show(null);
}
}
Java 的重载 Overload 解析过程是以两阶段运行的:第一阶段,选取所有可获得并且可应用的方法或构造器;第二阶段,在第一阶段选取的方法或构造器中选取最精确的一个。如果一个方法或构造器可以接受传递给另一个方法或构造器的任何参数,那么就认为第一个方法比第二个方法缺乏精确性。
这个程序的 show(Object o)
可以接受任何传递给 show(String a)
的参数,因此 show(Object o)
相对缺乏精确性。
所以,运行的结果为:"String"。