那些年安卓面试我遇到的坑
回想起几次安卓面试的场景,小编真是一脸懵逼,随后掩面痛哭。。。特地分享一下,互相伤害吧,哈哈哈哈。
Java中会存在内存泄露吗?(腾讯面试题)
所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象编程了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。
但是,java也会存在内存泄露的情况,长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况。例如:
-
缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
-
一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
代码案例:
public class Stack {
private Object[] elements = new Object[10];
private int size = 0;
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
public Object pop(){
if( size == 0) throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity(){
if(elements.length == size){
Object[] oldElements = elements;
elements = new Object[2 * elements.length+1];
System.arraycopy(oldElements,0, elements, 0, size);
}
}
}
public class Bad{
public static Stack s=Stack();
static{
s.push(new Object());
s.pop(); //这里有一个对象发生内存泄露
s.push(new Object()); //上面的对象可以被回收了,等于是自愈了
}
}
上面的原理应该很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,没有我们要的东西,但是这是个对象是无法回收的,这个才符合了内存泄露的两个条件:无用,无法回收。
因为是static,就一直存在到程序退出,但是我们也可以看到它有自愈功能,就是说如果你的Stack最多有100个对象,那么最多也就只有100个对象无法被回收其实这个应该很容易理解,Stack内部持有100个引用,最坏的情况就是他们都是无用的,因为我们一旦放新的进取,以前的引用自然消失!
如果让你在Java拼接数以万计的字符串,你会选择哪种方式?(ModChat面试题)
这个问题当时问得小编就真的是一脸懵逼,然后我就轻声问道:不是直接用+号吗,然后接下来的事情你们就懂了。。。(不可描述)
字符串拼接的方式有:
- Concatenation Operator (+)
- String concat method – concat(String str)
- StringBuffer append method – append(String str)
- StringBuilder append method – append(String str)
有人曾经做过这样测试,执行20个拼接 ' * ' 50,000次的测试,经过20次迭代后,得到如下的数据
method | Avg | Min | Max |
---|---|---|---|
Operator (+) | 6583.8 | 6392 | 6819 |
String concat | 2.3 | 1 | 9 |
String Buffer | 1.8 | 1 | 2 |
String Builder | 3.3 | 3 | 5 |
然后。。。然后也不用小编说什么了(感觉好丢人。。。)
对于三者使用的总结:
- 如果要操作少量的数据用 = String
- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
谈谈对Android四大组件的理解吧
- Activity
- BroadcastReceiver
- **Service **
-
Content Provider
这四大组件小编就不啰嗦了,自己看书去。。。(给你们两个图体会一下)
有兴趣的同学可以看看这篇博文
(http://blog.csdn.net/ican87/article/details/21874321)
说说List和Set的区别
- List,Set都是继承自Collection接口
- List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)
- List接口有三个实现类:LinkedList,ArrayList,Vector ,Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
当然也可以对Set定义排序规则,需要定义一个排序规则类实现Comparator接口,如:
public class TreeSetTest2 {
public static void main(String[] args) {
Set<Person> set = new TreeSet<Person>(new PersonComparator());
Person p1 = new Person(10);
Person p2 = new Person(20);
Person p3 = new Person(30);
Person p4 = new Person(40);
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
for(Iterator<Person> iterator = set.iterator();iterator.hasNext();){
System.out.print(iterator.next().score+" ");
}
}
}
class Person{
int score;
public Person(int score){
this.score = score;
}
public String toString(){
return String.valueOf(this.score);
}
}
class PersonComparator implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
return o1.score - o2.score;
}
}
你能说出java的根类Object的哪些常用方法?
当时主编听到这问题是想到的就只是eclipse的自动补全功能。。。
-
clone()
clone方法主要用于克隆当前对象,制作本地对象,这肯定需要在所有对象中所拥有。 -
equals()、toString()和hashCode()
这两个方法主要用于比较两个对象是否相等,查看Object源代码(要多查看源代码便于自己理解)知道,默认的equals()是
boolean equals(Object obj){
return this == obj;
}
==只有当两个对象地址相同时才返回true,所以默认的equals()方法根本没什么用,因为对象在内存中的地址(基本类型不同)肯定不同的;所以我们编写类时最好覆盖默认的equals()、hashCode()和toString()方法(查看JDK中的类也大部分覆盖了这些方法),默认的hashCode()返回的值就是对象在内存中的地址,而默认的toString()方法就是打印出对象的地址,toString()和equals()方法内部是通过hashCode()的返回值来实现的,hashCode()是本地(native)方法,所谓本地方法就是使用其他语言(C或C++)编写的,我们可以通过本地接口(JNI)编写本地方法。
-
finalize()
这是GC清理对象之前所调用的清理方法,是回调方法,我们可以覆盖这个方法写一些清理的代码,GC会自动扫描没有引用的对象,即对象赋值为null;可以通过调用System.runFinalization()或System.runFinalizersOnExit()强制GC清理该对象前调用finalize()方法,GC有时不会调用对象的finalize()方法(由JVM决定) -
getClass()
返回当前对象的Class类的对象引用,用于取得类名等(方法查看API)。 -
notify()、notifyAll()和wait()
这三个方法主要用于多线程中,wait方法会导致当前的线程等待,直到其他线程调用此对象的 notify方法或 notifyAll 方法。
Activity的启动模式有哪四种
- standard:标准模式,系统的默认模式
- singleTop:栈顶复用模式,Activity如果在栈顶就不会被重新创建
- singleTask:栈内复用模式,Activity如果在某个栈中存在就不会被重新创建
- singleInstance:单实例模式,是加强版的singleTask,当实例创建后,系统会建一个新的任务栈(独立存在)