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关键字

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原理

线程安全的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是线程安全的,

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面试题总结的主要内容,如果未能解决你的问题,请参考以下文章

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础

Android面试题基础集锦总结《二》

Android 面试题总结之Android 基础