错题一
- 下面程序的运行结果()
Object obj=new Object();
List aList=new ArrayList();
List bList=new LinkedList();
long t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
aList.add(0,obj);
}
long t2=System.currentTimeMillis()-t1;
t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
bList.add(0,obj);
}
long t3=System.currentTimeMillis()-t1;
原文中最后得到的结论如下:
-
在尾部插入数据,数据量较小时LinkedList比较快,因为ArrayList要频繁扩容,当数据量大时ArrayList比较快,因为ArrayList扩容是当前容量*1.5,大容量扩容一次就能提供很多空间,当ArrayList不需扩容时效率明显比LinkedList高,因为直接数组元素赋值不需new Node
-
在首部插入数据,LinkedList较快,因为LinkedList遍历插入位置花费时间很小,而ArrayList需要将原数组所有元素进行一次System.arraycopy;
-
插入位置越往中间,LinkedList效率越低,因为它遍历获取插入位置是从两头往中间搜,index越往中间遍历越久,因此ArrayList的插入效率可能会比LinkedList高;
-
插入位置越往后,ArrayList效率越高,因为数组需要复制后移的数据少了,那么System.arraycopy就快了,因此在首部插入数据LinkedList效率比ArrayList高,尾部插入数据ArrayList效率比LinkedList高
-
LinkedList可以实现队列,栈等数据结构,这是它的优势;
错题二
- 下面有关java threadlocal说法正确的有?
【ThreadLocal介绍】
1、ThreadLocal叫做线程变量,指的是属于当前线程的一个变量,该变量首先对其他线程来说是隔离的,也就是说ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量;
2、作为一个面试常问的点,理解起来就没有那么容易了;
3、它的核心作用就是:实现线程范围的局部变量;
4、在数据库连接的时候,ThreadLocal使用的比较多:
假如这时候有一个客户端频繁的连接数据库资源,服务端需要建立多次的连接和关闭,如果客户量增多,服务器压力太大;这时候可以使用ThreadLocal,它在每个线程中对连接会创建一个副本,且在线程内部任意地方可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会影响程序执行性能;
5、ThreadLocal中的set(),是将当前线程对象作为key,线程局部变量作为value存储在ThreadLocalMap中;ThreadLocalMap是ThreadLocal中的一个静态内部类,里面定义了一个Entry来保存数据;
6、也就是说,ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个变量,因此保存在map中;
7、内存泄漏
- 上图详解了了ThreadLocal和Thread以及ThreadLocalMap三者的关系;
1、Thread中有一个Map,就是ThreadLocalMap;
2、ThreadLocalMap中的key是ThreadLocal,值是我们自己设定的;
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收;
4、如果我们的ThreadLocal是null,也就是要被垃圾回收器回收了,但是ThreadLocalMap生命周期和Thread一样,它不会被回收,这就造成了内存泄漏的问题;
5、解决办法就是:使用完ThreadLocal后,执行remove操作,避免出现内存泄漏的情况;
错题三
- URL u =new URL(“http://www.123.com”);。如果www.123.com不存在,则返回______。
- 我们在执行
URL u =new URL("http://www.123.com");
这句话的时候确实要抛出异常,但是这个异常属于IOException
,是由于字符串格式和URL不符导致的,不管网址是否存在,最后都会返回该网址的一个连接,打印出来就是该网址;
错题四
- 关键字super的作用是?
1、super关键字用于对父类的访问;
2、可以使用super关键字访问被子类隐藏的变量或者覆盖的方法;
3、每个子类的构造方法第一句都会隐式调用super()
,如果父类没有这种形式的构造,编译期间会报错;
4、构造不能被继承;
错题五
- 以下哪个事件会导致线程销毁?()
1、调用sleep()让线程进入睡眠状态,睡眠指定的时间后再次执行;
2、调用wait()让线程进入等待状态,等待别的线程执行notify()或notifyAll()唤醒后继续执行;
3、调用start()让线程进入就绪状态,得到CPU时间就执行线程;
4、run()方法是线程的具体逻辑方法,执行完,线程就结束。
错题六
- 下面程序段的时间复杂度是()
i = k = 0;
while( k < n ){
i ++ ;
k += i ;
}
在评论区看到一个比较好理解的思路:
i= 1, 2, 3, 4, 5, 6, 7, 8
k= 1, 3, 6, 10, 15, 21, 28, 36
把k的数字两个两个一起看的话, 也就是(1,3), (6,10), (15, 21), (7,8), 求和后可以发现规律:
(1+3=4), (6+10=16), (15+21=36), (28+36=64)
也就是2^2, 4^2, 6^2, 8^2...偶数的平方
循环在x^2>=n时终止, 可得x等于根号n, 也就是n^(1/2)
循环的次数是x/2, 时间复杂度为O((1/2)n^(1/2)), 一般而言时间复杂度认为常系数为1, 所以答案就是O(n^(1/2))
错题七
- 为什么一个负责任的Java程序员想使用嵌套类?
1、为了让非常专门的类的代码与和它一起工作的类联系起来
2、为了支持产生特定事件的新的用户界面
3、为了用Java知识给打动老板,到处使用嵌套类
【使用嵌套类的原因为】:
1、如果B类嵌套在A类里面,即使A类的成员声明为private,B类也可以使用;
2、更可读性:代码在定顶级类里面嵌套小类,让代码更靠近使用的地方;
错题八
- final、finally和finalize的区别中,下述说法正确的有?
- finalize方法一个对象只能执行一次,只能在第一次进入被回收的队列,而且对象所属于的类重写了finalize方法才会被执行。第二次进入回收队列的时候,不会再执行其finalize方法,而是直接被二次标记,在下一次GC的时候被GC。
错题九
- 以下JAVA程序的运行结果是什么( )
public static void main(String[] args) {
Object o1 = true ? new Integer(1) : new Double(2.0);
Object o2;
if (true) {
o2 = new Integer(1);
} else {
o2 = new Double(2.0);
}
System.out.print(o1);
System.out.print(" ");
System.out.print(o2);
}
【三元运算符的计算规则】:
1、若两个操作数不可转换,则不做转换,返回值为Object类型;
2、若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字来转换,int类型转换为long类型,long类型转换为float类型等。
3、若两个操作数中有一个是数字S,另外一个是表达式,且其类型标示为T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T类型的范围,则T转换为S类型。
4、若两个操作数都是直接量数字,则返回值类型为范围较大者;
错题十
yield()
让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。但实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
错题十一
- 给定以下JAVA代码,这段代码运行后输出的结果是()
public class Test
{
public static int aMethod(int i)throws Exception
{
try{
return i/10;
}
catch (Exception ex)
{
throw new Exception("exception in a aMethod");
}finally{
System.out.printf("finally");
}
}
public static void main(String[] args){
try
{
aMethod(0);
}
catch (Exception ex)
{
System.out.printf("exception in main");
}
System.out.printf("finished");
}
}
首先该方法执行正常,没有发生异常,因此依次执行两个try中对应的finally块代码;
算法题一:长键按入
-
你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。
-
你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回 True。
示例 1:
输入:name = "alex", typed = "aaleex"
输出:true
解释:'alex' 中的 'a' 和 'e' 被长按。
示例 2:
输入:name = "saeed", typed = "ssaaedd"
输出:false
解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。
示例 3:
输入:name = "leelee", typed = "lleeelee"
输出:true
示例 4:
输入:name = "laiden", typed = "laiden"
输出:true
解释:长按名字中的字符并不是必要的。
提示:
name.length <= 1000
typed.length <= 1000
name 和 typed 的字符都是小写字母。
自己的做法:
在做这道题目的时候,我用了一个看起来容易理解的做法解决了,但是时间和空间复杂度都比较高,性能不好:
public boolean isLongPressedName(String name, String typed) {
if (typed.length() < name.length()) {
return false;
}
if (typed.length() == name.length() && typed.equals(name)) {
return true;
} else if (typed.length() == name.length() && !typed.equals(name)) {
return false;
}
StringBuilder regx = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
regx.append(name.charAt(i)).append("+");
}
return typed.matches(regx.toString());
}
使用正则表达式的特点,将第一个字符串的所有字符拿出来,与
+
一起拼接成为一个regx正则规则,然后让第二个字符串去匹配他;
为了节省时间,在最开始的时候做出一些判断,如果后一个字符串长度小于前一个字符串的长度,直接返回false;
如果后一个字符串的长度等于前一个字符串的长度,返回是否相等;
官方解法:
双指针
class Solution {
public:
bool isLongPressedName(string name, string typed) {
int i = 0, j = 0;
while (j < typed.length()) {
if (i < name.length() && name[i] == typed[j]) {
i++;
j++;
} else if (j > 0 && typed[j] == typed[j - 1]) {
j++;
} else {
return false;
}
}
return i == name.length();
}
};
【理解】:
1、type中的字符有两个作用:
- 作为name的一部分,此时会匹配name的一个字符;(此时
指针i和j
都向前挪动一位) - 作为长键按入的一部分,此时会和前一个字符相同;(此时
指针j
向前挪动一位)
2、如果不满足上面的作用,则返回false;
3、在执行完上面的判断之后,再去检查name的没个元素是否都被匹配上了;
算法题二:删除排序数组中的重复项
-
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
-
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
解法:
class Solution {
public int removeDuplicates(int[] nums) {
int length = nums.length;
int i = 0;
for (int j = 1; j < length; j++) {
if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
//System.out.println(Arrays.toString(nums));
return i + 1;
}
}
这是在学习官方解答之后的代码,这个解法的核心思路是:
1、使用双指针,i指针指向数组中最后一个有意义的元素,j指针指向当前遍历到的元素;
2、如果当前元素不等于数组中最后一个有意义的元素,i 指针挪动一位,i 指针指向的元素的值等于当前遍历到的这个元素;
3、最终返回i指针+1;
算法三:比较含退格的字符串
-
给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。
-
注意:如果对空文本输入退格字符,文本继续为空。
示例 1:
输入:S = "ab#c", T = "ad#c"
输出:true
解释:S 和 T 都会变成 “ac”。
示例 2:
输入:S = "ab##", T = "c#d#"
输出:true
解释:S 和 T 都会变成 “”。
示例 3:
输入:S = "a##c", T = "#a#c"
输出:true
解释:S 和 T 都会变成 “c”。
示例 4:
输入:S = "a#c", T = "b"
输出:false
解释:S 会变成 “c”,但 T 仍然是 “b”。
提示:
1 <= S.length <= 200
1 <= T.length <= 200
S 和 T 只含有小写字母以及字符 '#'。
自己的做法:
class Solution {
public boolean backspaceCompare(String S, String T) {
if (S.length() == 0 || T.length() == 0) {
return false;
}
String sNew = dealString(S);
String tNew = dealString(T);
return sNew.equals(tNew);
}
private String dealString(String str) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '#') {
if (i > 0 && sb.length() > 0) {
sb.deleteCharAt(sb.length()-1);
}
} else {
sb.append(str.charAt(i));
}
}
//System.out.println(sb.toString());
return sb.toString();
}
}
【思路】:
1、定义方法,传入当前的字符串,返回值为去掉#之后的字符串;
2、该方法的内部是:遍历字符串,如果当前字符是#,并且StringBuffer长度不为0,则删除最后一个元素;如果当前字符不是#,则拼接进StringBuffer;
3、最终比较两个字符串是否相等;