Android面试题总结
Posted lxn_李小牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android面试题总结相关的知识,希望对你有一定的参考价值。
1.String转化为Integer的Integer.valueOf方法
public static Integer valueOf(String s) throws NumberFormatException
return Integer.valueOf(parseInt(s, 10));
先通过parseInt方法把String转化为int类型,有个格式检查
public static int parseInt(String s, int radix)
throws NumberFormatException
如果格式正确,调用下面的方法
public static Integer valueOf(int i)
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
如果i的值在[-128,127之间],会从缓存池里取,否则创建一个新的Integer对象。
2.Volatile关键字
3.HashMap相关
数据结构的分类:集合,线性结构,树形结构,图形结构
集合结构:除了同属于一种类型外,别无其它关系
线性结构:元素之间存在一对一关系常见类型有: 数组,链表,队列,栈,它们之间在操作上有所区别.例如:链表可在任意位置插入或删除元素,而队列在队尾插入元素,队头删除元素,栈只能在栈顶进行插
入,删除操作.
树形结构:元素之间存在一对多关系,常见类型有:树(有许多特例:二叉树、平衡二叉树、查找树等)
图形结构:元素之间存在多对多关系,图形结构中每个结点的前驱结点数和后续结点多个数可以任意
数组和链表的特点:
数组:查询快,增删效率低,增删会涉及到后面元素的移动
链表:查询效率低,因为要做全链表扫描,删除效率高,直接把前面的指向后面的
HashMap如何计算元素存放位置:(1.8)
public V put(K key, V value)
return putVal(hash(key), key, value, false, true);
我们先看hash方法
static final int hash(Object key)
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//尽可能的分散,减少碰撞概率
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict)
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//对数组进行初始化
if ((p = tab[i = (n - 1) & hash]) == null)//判断数组当前下标位置是否有元素
tab[i] = newNode(hash, key, value, null);
else
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else
for (int binCount = 0; ; ++binCount)
if ((e = p.next) == null)
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
if (e != null) // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
获取当前位置的时候采用了
(n - 1) & hash
这种计算方式,为什么要采用这种方式呢?
首先可以保证计算出的位置不大于数组长度,其次可以减少碰撞概率,n代表数组的长度,源码中要求n必须是2的次幂,因为2的次幂减去1以后,得到的是类似0111这样的数,如果不是2的次幂,那么就会得到00001110这样的数,0与1和0都为0,计算出的位置可能是同一个点,这样就增加了碰撞的概率,
final Node<K,V>[] resize()
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0)
if (oldCap >= MAXIMUM_CAPACITY)
threshold = Integer.MAX_VALUE;
return oldTab;
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else // zero initial threshold signifies using defaults,第一次走这里
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
if (newThr == 0)
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
threshold = newThr;
@SuppressWarnings("rawtypes","unchecked")
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
resize方法用于对数组进行初始化或者扩容
HashMap的非线程安全解决:
1.HashTable:锁住整张hash表,让线程独占,hashtabl允许为空,synchronized是针对整张hash表的
2.ConcurentHashMap:一个更快的hashmap,提供了更好的并发性,多个读操作可以并发的执行,采用锁分段,默认把hash表分为16个段,在get,put,remove操作中只锁定当前需要的段,只有在求size时才锁定整个表。
2.HashSet总结
不保证元素的顺序,元素不可重复,HashSet底层是以HashMap来存储的,值是一个Object对象
private static final Object PRESENT = new Object();
我们重点看add方法
public boolean add(E e)
return map.put(e, PRESENT)==null;
add方法其实是调用了HashMap的put方法,返回true,代表存入成功,如果元素已经存在(存在的判定是元素的hashCode和equals方法相同),则set集合不变,返回false(因为map集合存放元素时,如果键相同,值会覆盖,因为对HashSet来说,值是Object对象,所以即使覆盖了,HashSet中的元素还是没有变)
泛型
类型擦除与多态的冲突和解决方法
public class Pair<T> //父类
private T t;
public void setValue(T t)
this.t = t;
public T getValue()
return t;
public class Info extends Pair<Date> //子类
@Override
public void setValue(Date date)
super.setValue(date);
@Override
public Date getValue()
return super.getValue();
public class GenericDemo
public static void main(String[] args)
Pair<Date> pair = new Info();
pair.setValue(new Date());
/*
* 1.类型擦除和多态的冲突
* 类型擦除以后,我们如果调用pair.setValue这句,应该调用Info的setValue方法,可是Info此时
* 有两个setValue方法,
* setValue(Date date)//自身的
* setValue(Object obj)//继承自Pair<Date>
* 那么应该调用哪个方法?
*
* 此时就要引出桥方法。
* JVM工作原理如下:
* 1.变量pair声明为Pair<Date>类型,该类型只有一个setValue(Object obj)方法,所以用pair指向的实际对象
* 去调用setValue(Object obj)这个方法
* 2.pair引用的对象是Info,所以会调用Info的setValue(Object obj)方法,这个方法是桥方法
*3.这个桥方法会调用Info的setValue(Date date)方法
* */
异常中不能使用泛型,因为类型擦除后会出现两个同样的异常
ArrayList总结
//将数组转化为集合,该集合是只读的
List<String> list = Arrays.asList("2", "3");
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
//将ArrayList转化为数组,new String[0]代表默认0个元素
String[] strings = arrayList.toArray(new String[0]);
for (String string : strings)
System.out.println(string);
在初始化时,数组长度是空的,当通过add添加元素时,才会对数组长度进行初始化为默认长度10,具体代码如下
public boolean add(E e)
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;//元素添加到集合中,索引增加
return true;
private void ensureCapacityInternal(int minCapacity)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) //默认true
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//minCapacity此处为10
ensureExplicitCapacity(minCapacity);
private void ensureExplicitCapacity(int minCapacity)
modCount++;
// overflow-conscious code,当需要的容量大于数组长度的时候,就扩容,第一次扩容为默认的长度10
if (minCapacity - elementData.length > 0)
grow(minCapacity);//增长容量
private void grow(int minCapacity)
// overflow-conscious code
int oldCapacity = elementData.length;//oldCapacity为0
int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity为0
if (newCapacity - minCapacity < 0)//默认为true
newCapacity = minCapacity;//newCapacity设置为10
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//返回指定长度的复制后的数组
ArrayList不是同步的,同步要使用Vector或者CopyOnWriteArrayList,Collections.synchronizedList
public static <T> List<T> synchronizedList(List<T> list)
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E>
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list)
super(list);
this.list = list;
SynchronizedList(List<E> list, Object mutex)
super(list, mutex);
this.list = list;
public boolean equals(Object o)
if (this == o)
return true;
synchronized (mutex) return list.equals(o);
public int hashCode()
synchronized (mutex) return list.hashCode();
public E get(int index) //对list的每个方法进行了同步代码块的包装
synchronized (mutex) return list.get(index);
还有一点必须注意,当使用iterator迭代Collections.synchronizedList返回的List时,需要手动的进行一下同步
List list = Collections.synchronizedList(new ArrayList());
synchronized (list)
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
CopyOnWriteArrayList原理
Collections.synchronizedMap()
同样,在迭代的时候需要手动保持同步,如下
Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
Set<Object> keySet = map.keySet();
synchronized (map) //同步map,而不是keySet
for (Object next : keySet)
System.out.println(next);
ConcurrentHashMap
ConcurrentHashMap<String,String> concurrentHashMap = new ConcurrentHashMap<>();
concurrentHashMap.put("A","北京");
concurrentHashMap.put("B","西安");
Set<Map.Entry<String, String>> entrySet = concurrentHashMap.entrySet();
for(Map.Entry<String,String> entry : entrySet)
System.out.println(entry.getKey());
System.out.println(entry.getValue());
ConcurrentHashMap是线程安全的,
信号量Semaphore(控制资源被并发访问的次数,同时可以实现监控多个线程是否全部执行完毕)
public class ThreadDemo
static Semaphore semaphore = new Semaphore(3);
public static void main(String[] args)
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
Thread3 t3 = new Thread3();
t1.start();
t2.start();
t3.start();
static class Thread1 extends Thread
@Override
public void run()
try
semaphore.acquire();
catch (InterruptedException e)
e.printStackTrace();
System.out.println("线程一执行完毕");
semaphore.release();
checkFinish(semaphore.availablePermits());
static class Thread2 extends Thread
@Override
public void run()
try
semaphore.acquire();
Thread.sleep(3000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("线程二执行完毕");
semaphore.release();
checkFinish(semaphore.availablePermits());
static class Thread3 extends Thread
@Override
public void run()
try
semaphore.acquire();
Thread.sleep(4000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("线程3执行完毕");
semaphore.release();
checkFinish(semaphore.availablePermits());
static void checkFinish(int count)
if (count == 3)
System.out.println("所有线程执行完毕");
LinkedList
保证了元素的插入顺序,底层是双向链表,每个节点保存了自己上一个和下一个节点
private static class Node<E>
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next)
this.item = element;
this.next = next;
this.prev = prev;
void linkLast(E e)
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);//创建一个新节点,last是上一个节点
last = newNode;
if (l == null)//如果上一个节点没有,说明是头结点
first = newNode;
else
l.next = newNode;//上一个有节点,新节点做为下一个节点
size++;
modCount++;
TreeSet
保证了元素的顺序,如果元素没有实现Comparable接口,则会抛出类型转化异常
public static void main(String[] args)
//第一种,元素必须实现Comparable接口,并且实现compareTo方法
/* TreeSet<Person> treeSet = new TreeSet<>();
treeSet.add(new Person("a",2));
treeSet.add(new Person("b",1));
treeSet.add(new Person("c",3));
treeSet.add(new Person("d",4));
for(Person i : treeSet)
System.out.println(i.getAge());
*/
//第二种,TreeSet的构造方法中传入Comparator实现类,实现compare方法排序
TreeSet<Person> treeSet2 = new TreeSet<>(new Comparator<Person>()
@Override
public int compare(Person o1, Person o2)
return o1.getAge() - o2.getAge();
);
以上是关于Android面试题总结的主要内容,如果未能解决你的问题,请参考以下文章