面试复习(二) Java篇

  • 三大框架

    • MVC(Model、View、Controller)参考

      面试复习(二) Java篇_第1张图片
      image

      • 原理:view层发送指令到controller层,由controller去通知model层更新数据,model层更新完数据以后直接显示在view层上。

      • 对于安卓:View-》各类layout文件;Model-》数据逻辑操作,包括java bean及其他repository、API等;Controller-》activity等

      • 缺点:

        xml对view层控制力差,不能直接进行动态修改进行显示或隐藏,只能交给controller(activity等)进行

        view与model层可以相互感知,存在耦合关系
        * MVP(Model、View、Presenter)


        面试复习(二) Java篇_第2张图片
        image

        * 原理:用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,完全由presenter作为桥梁,通过接口与view连接
        * MVVM(Model、View、ViewModel)

      面试复习(二) Java篇_第3张图片
      image
      • 与MVP相似,应具体分析优劣,使用了谷歌data Binding框架
  • Collection框架

    • 存储、复用对象。特点:
      • 集合大小动态可变
      • 存储引用类型数据(可以不同类型)
      • 提供迭代器对元素进行高效访问
    • 六大接口:
      • Collection
      • Set、List、SortedSet(继承Collection接口)
      • Map、SortedMap
    • List:有序可重复,包括
      • ArrayList线程不安全,底层数组实现,容量不足长度扩大一倍。插入数据效率低,查找效率高
      • LinkedList线程不安全,底层链表实现。插入数据位置越靠前效率越高,查找效率较低
      • Vector线程安全,底层数组实现,容量不足长度扩大一倍。java1.5后不推荐
    • Set:元素不能重复,相当于只存储了map的key值
      • HashSet底层HashMap实现,无序
      • TreeSet底层TreeMap实现
    • Map:
      • HashMap
      • TreeMap,排序,红黑树
      • HashTable
    • 遍历:
  • hashmap 参考

    • Entry数组+链表组成,每个数组元素保存一个链表头结点


      面试复习(二) Java篇_第4张图片
      image

      HashMap中每个元素的HashCode%数组长度的值即为其所在链表对应的数组编号

    • 链表中结点保存 key、value、Entry next、hashCode
    • 实现Collection的Map接口,不允许重复键,可以空键空值
    • 存入元素
      • 获取该元素hashCode对应的链表数组编号,再调用equals()方法在链中遍历查找正确结点。

      • 该元素next指针指向该链表中栈顶键值对对象,然后将该对象赋给数组值

          int hashCode = key.hasCode();
          int index = hashCode % Entry[].length;
          value.next = Entry[index];
          Entry[index] = element;
        
    • 优化
      • 随着map数据量增大,某个链表可能会很长,超过其负载因子所代表的容积时则创建一个两倍大小的数组,将原存储的对象重新计算位置后放入到新的数组中。
      • 减少碰撞:采用不可变对象做键值
      • 多线程下条件竞争,多个线程写入新的头结点,造成操作覆盖
      • equals()比hashCode()方法更全面复杂,hashCode只用生成hashCode进行比较,效率更高。因此全部使用equals效率稍低,可以先使用hashCode对比,若hashCode相同再采用equals
    • 碰撞检测
      • hashCode相同但不equal时,两个对象存储在统一链表中,
      • 键的hashCode相同时,首先找到对应的链表位置,在调用key.equals()找到键在链表中的正确结点,从而找到对应的值对象
    • 不可变对象的好处:
      • 适合采用String、Interger、Double这样的封装类做键,其值是不可变的,并且重写了hashCode()与equals()方法
      • 保证存入与获取时通过键值计算的HashCode是不变的,从而保证可以在HashMap中找到正确的对象
      • 线程安全
      • 采用自定义对象作为键时,需要保证遵守hashCode()与equals()方法定义规则以减少碰撞,且对象插入后不会再改变(声明为final)。
    • ConcurrentHashMap,相对HashTable在多线程时同步性能更好,效率更高,采用锁分段技术,将数据分段存储,每段配一把锁,可以代替HashTable
    • synchronized Map:使用synchronized关键字保证map的操作是线程安全的
    • LinkedHashMap:HashMap子类,采用了双向链表,时间空间开销增大,但是能保证插入与访问顺序
      • 节点中保存:before、(hashCode、key、value、next)、after。
        • before、after用于维护双向链表,指明了插入(或访问)的前后顺序
      • 构造函数中accessOrder置为true,使双向链表按访问先后顺序排列,每次put或get时将当前entry放在链表尾部。
    • hashCode()与equals()方法重写
      • hashCode:
        • Object类中:根据内存地址计算hashCode,因此同一个类的不同对象会不一样
        • String重写方法:根据字符串内容计算,字符串所在堆空间相同,则hashCode相同
        • Integer重写方法:数值相同返回hashCode相同
      • equals:
        • Object类中:通过对象内存地址判断

            public boolean equals(Object obj){
                return (this == obj);
            }
          
        • String重写方法:对地址与内容进行比较

            public boolean equals(Object anObj){
                if (this == anObj){return true;}
                if (anObj instanceof String){
                    String anotherStr = (String)anObj;
                    int n = count;
                    if (n == anotherStr.count){
                        char v1[] = this.value;
                        char v2[] = anotherStr.value;
                        int i = 0;//offset
                        int j = 0;//anotherStr.offset
                        while(n- != 0){
                            if(v1[i++] != v2[j++]){return false;}
                        }
                        return true;
                    }
                }
                return false;
            }
          
  • HashSet

    • 实现Collection的Set接口,保存“值”。
    • 不允许重复元素(通过hashCode()与equals()确定)
  • HashTable

    • 继承Dictionary,实现Map、Cloneable、Serializable接口
    • 多线程下synchronized同步,键值不能为空
    • 多线程下线程竞争锁导致效率低
  • ArrayList

    • 内部结构为Object[]数组
    • 非线程安全
  • 线程、四种线程池区别;run()与start()区别 1 2

    • 进程:一个独立的运行环境,一个程序、应用。不同进程使用不同内存空间

    • 线程:进程中负责程序执行的单元,是进程的子集。同一进程的不同线程使用同一块内存

      • 时间片轮转抢占式调度:每个线程执行一段时间后强行暂停执行下一个,使每个任务轮流执行。由于CPU执行频率高,任务切换非常快,因此感觉是多个任务同时进行。

      • 线程任务按实现的接口划分,可以继承Runnable接口(重写run())与Callable接口(重写call()),将任务提交给其它线程(Thread类或线程池)执行

        1.new Thread(task).start();

        2.FutureTask result = ThreadPoolExecutor.submit(task);

        3.new Thread(new FutureTask(V){callable}).start();

      • 创建线程的方法:

        • 继承Thread并重写run(),通过start()开启

            Thread thread = new Thread(){run(){...}};
            thread.start();
          
        • 实现Runnable并重写run(),通过start()开启

            Runnable r = new Runnable(){run(){...}};
            Thread thread = new Thread(r);
            thread.start;
          
        • Callable与FutureTask

          • Callable接口与Runnable接口类似,但是可以返回值与异常,重写方法call(),任务返回FutureTask对象,其中携带了操作结果
          • FutureTask实现了RunnableFuture接口,RunnableFuture接口实现了Future接口(包括取消操作、判断是非取消或完成、获取结果抽象函数)与Runnable接口,可以在Thread及ExecutorService中执行。
        • 线程池

          • 新建线程池提交任务

            1.ThreadPoolExecutor.execute(task);

            2.线程池构造函数重要参数:线程池核心线程数最大值、线程最大总数、非核心线程最大闲置时长、任务队列、创建线程Factory等

            3.当有任务被提交时,若线程数小于核心线程数,则新建一个线程执行任务

            若线程数大于核心线程数,则将任务加入任务队列,队列装满后新建非核心线程执行队列任务

            若队列也满,则抛出异常
            * 选取四种现成的线程池直接提交任务

            ExecutorService threadPool = Executors.new .....ThreadPool(params);

            ExecutorService.newCachedThreadPool(2).submit(task);
            * 优点:提高程序执行效率、提高资源(CPU、内存)利用率
            * 缺点:占用内存空间、线程越多CPU调度开销越大、程序复杂
            * wait()与sleep()
            * wait:线程暂停执行,释放同步锁,直到被notify()通知唤醒
            * sleep:线程休眠,不释放同步锁,直到线程休眠时间结束或被interupt()打断
            * thread.join()该thread内run方法执行完成后再进行下一步代码或线程,保证了多线程间的执行顺序
            * thread.yield()线程放弃执行,将CPU让出,等待下次被系统调度运行。
            * thread.stop()已过时,强制性,破坏线程原子逻辑,不提倡使用
            * 保证安全停止线程的方法:

          stop = false
          while(!stop){
          synchronized{
          ....//需要保证运行逻辑完整的原子代码
          }
          }

          public void terminate(){stop = true;}

    • 线程池:一块存放多个为死亡线程的内存空间,由线程池管理器管理调度,当有开辟线程的任务时即从池中获取一个线程执行任务,完成后再返回给线程池,从而

      1.节省了反复创建线程对象的性能开销。

      2.控制最大并发数,避免大量线程相互抢占系统资源导致阻塞

      3.能够对线程进行简单的管理、定时执行、间隔执行等功能

      • Executor框架:调度、执行、控制异任务对框架。利用Executor框架可以方便的创建线程池,可以控制线程数量并对空闲线程进行回收利用,避免了无限制创建线程导致程序内存溢出。

      • Executor接口——》ThreadPoolExecutor实现类

      • newCachedThreadPool:无限大小线程池,有任务添加且无空闲线程时可创建新线程,之前构造的空闲线程可用时能过进行重用

        对于较多的短期异步任务可以提高程序性能

        现有线程都不可用时将创建添加一个新的线程,并移除超过60s未被使用的线程。
        * newSingleThreadExecutor:池内只有一个线程工作,所有任务串形执行。若该线程因异常而结束,则会构造一个新的线程代替。

        保证所有任务按提交顺序执行
        * newFixedThreadPool:固定大小的线程池。每接受一个任务即创建一个线程直到线程数量达到上限。若其中有线程因异常结束则创建新的代替
        * newScheduledThreadPool:无限大小的线程池,支持定时、周期性任务
        * start()与run()
        * 继承Thread类或重写Runnable接口时在run中重写线程中的操作,对线程实例调用start,从而开辟新的线程并执行run内操作
        * 若直接调用run,并不能开辟出新的线程,还是在原线程中执行

  • synchronized同步块、lock

    • 针对多条线程同时操作共享数据的安全问题,采用互斥锁保证被锁上的线程在运行时让其他试图访问的线程处于等待状态,直到当前线程结束并释放锁。

    • synchronized保证同一时刻只有一个线程可以操作被其修饰的代码块或方法,同时保证当前线程上数据的变化可以被其他线程看到。

    • synchronized锁定代码块:该对象在synchronized块内的内容在运行时不允许其他线程访问,其所在线程结束后才能执行另一个线程;若不同线程执行该类的不同对象,则锁定失效

        class SynThread implements Runnable{
            public void run(){
                synchronized(this){....}
            }
        }   
      
    • synchronized锁定对象:其他线程试图访问该对象时会被阻塞,直到对象所在的当前线程结束

        public void run(){
            synchronized(obj){...}
        }
      
    • synchronized锁定实例方法

        public synchronized void run(){}
      
    • synchronized修饰的对象、方法不能被继承或实现

    • 每个对象有一个锁,线程拿到锁则可以对对象进行操作

  • 死锁可以发生在不同线程、进程,甚至不同机器之间

    • 形成原因:
      • 竞争不能抢占到的资源 T1、T2分别占据a、b资源,并等待对方释放资源,形成了无限的等待从而造成死锁
      • 竞争可消耗的资源 T1、T2分别需要先接收对方消息才能发送消息
      • 进程推进顺序不当 T1、T2都需要a、b资源,正确顺序应当是T1运行a、b完成后再进行T2线程,但是若运行顺序不当易造成第一种死锁
    • 必要条件:
      • 资源只能被一个线程使用
      • 资源未被使用完,不能被抢占,只能由当前获取资源的线程自己释放(主动释放)
      • 线程占用了资源但是又提出了新的资源请求。而该资源已被其他线程占用,导致请求的线程被阻塞,同时不释放自己已有的资源
      • 循环等待条件,存在一条占用资源与请求资源的循环链
    • 处理方法
      • 线程开始前一次性申请所有资源
      • 加锁顺序,使线程按照一定顺序加锁
      • 加上锁时限,线程尝试获取锁时,超过时限则放弃对该锁的请求,并释放自己占用的锁
      • 死锁检测,针对不能实现按序加锁或锁时限的情况。记录线程获取锁的情况以及请求记录,每当存在请求失败的情况时,则遍历该记录(锁的关系图)查看是否有死锁发生。若发现死锁,则释放所有锁,回退,并在一段时间后重试;或给各线程随机设置优先级,较低级的线程回退,其他较高的线程继续保持锁
  • 内存泄漏、溢出、ANR、

    • JVM内存环境:
      • 堆heap:存放new创建的对象和数组(对象本身),由JVM垃圾回收器管理,被所有线程共享
      • 栈stack:后进先出,只存放基本类型与对象的引用(不是对象)。每个线程拥有一个栈,其中内存单元为帧frame,存放线程内使用到的方法(参数、局部变量、返回地址等)
      • 方法区method:静态区,存放所有class、static变量。被所有线程共享
    • 泄漏:程序向系统申请分配内存,使用完毕后未释放,(当程序不再需要这个对象时仍保留了这个对象的引用)一直占据该内存单元直到程序结束,使得该内存无法再被使用。大量的泄漏会导致内存溢出,如未注销的receiver、eventbus等
      • 形成原因:
        • 栈:方法运行结束时,对应的frame会从栈中删除,空间被释放,线程回到原方法继续执行,而所有栈都被清空则程序结束运行,很少导致泄漏。
        • :存放普通变量,不会随着方法结束而清空。随着对象的不停创建而耗尽空间以及长期持有,若一直不被GC回收(存在于引用链上)则造成泄漏。
      • 影响:
        • 浪费内存
        • 内存泄漏导致可用内存越来越少,最终导致oom
        • 随着内存减少,GC被频繁触发影响性能造成程序卡顿
      • 常见情况:
        • context:activity被强引用后,随着其生命周期短结束,本该被GC回收的activity对象因为该引用一直不被回收,存活在生命周期之外。因此最好采用application.context代替activity.context或及时清除对activity的引用
        • 内部类:内部类持有一个静态(static)变量的引用,就需要在销毁时将静态变量置空
        • handler内部类存在于activity中,activity结束后,handler对象中有message还在循环,handler生命周期没有结束,因此activity无法被回收
          • handler声明为static静态内部类,不再持有外部类的引用从而可以释放activity
          • 内部类引用外部对象时采用弱应用
      • 解决方法:在对象生命周期结束的时候解除绑定,将引用置为空或使用弱引用。
    • 溢出oom:程序向系统申请的内存空间超过系统所能分配的
      • 导致原因:
        • 大量长期保存资源不释放的内存泄漏问题
        • 对象过大超出内存限制,如bitmap、xml等
        • static变量过多
      • 加载图片导致oom解决方法:
        • 只加载缩略图
        • 读取时适当压缩
        • 使用软引用对bitmap进行引用,及时recycle,并对经常需要加载的图片进行缓存避免反复加载
        • 及时销毁不需要的bitmap对象
    • ANR(application not responding)
      • 导致原因:
        • 主线程太多耗时操作
        • CPU满负荷,导致IO阻塞
      • 解决方法:
        • 开辟子线程进行耗时操作或IO操作
        • 排查内存泄漏、增大VM内存等
        • activity onCreate和onResume中尽量避免耗时操作
        • BroadCastReceiver onReceive尽量避免耗时操作,交给IntentService处理
  • Java类加载机制

  • Java内存模型

    • 可见性:线程间,一个线程修改的状态对另一个线程数可见的,可以使用volatile、synchronized、final修饰变量使其具有可见性,直接在内存中被修改,不允许其在线程内缓存与重排序
    • 原子性:操作不可分割,如a=0。非原子操作,如a++会存在线程安全问题。使用同步技术如synchronized、lock将其封为一个原子操作。
    • 有序性:持有同一对象锁的两个同步块只能串形执行。通过volatile、synchronized保证
  • Java同步机制:

    • JVM中临界区内资源可被共享,同一时间内只能被一个线程访问
    • 同步机制保证了每次一个线程需要访问临界区时首先检查区内是否正有线程访问,有则将线程挂起等待区内线程执行完
  • Volatile

    • 一种稍弱的同步机制,将变量的更新操作通知到其它线程,保证线程间操作有序性的关键字。声明的变量被共享,其他线程读取时获取其被操作后最新的值
    • 访问volatile变量时不会进行加锁操作,不会使执行线程阻塞,比sychronized更加轻型
    • 非volatile对象被操作时,由操作线程拷贝从内存将其拷贝到cpu上,可能随着处理线程的cpu不同而拷贝到不同cpu cache上,造成操作的不可见
    • volatile对象每次直接从内存中读取,不经过CPU cache,修改后立即同步到内存,因此下一个进行操作的线程即对此操作可见
    • 读取性能与普通变量几乎相同,写入稍慢
  • lock

  • final

  • 垃圾回收

  • Java反射:java程序对在运行时才知道名称的类进行加载,获取到它的完整定义与成员

    • 运行时判断一个对象所属类
    • 运行时构造任意类的对象
    • 运行时判断一个类的成员变量和方法
    • 运行时调用对象的方法
    • 动态代理
    • java.lang.Class类,类被加载后,jvm产生一个Class对象,并通过这个class对象获取到其对应的方法、成员等信息
  • Java装箱拆箱

    • 装箱:基本数据类型转为封装类,如Integer a = 1(而不用Integer a = new Integer(1))、(Array) list.add(3);
      • 调用Integer.valueOf(int)
    • 拆箱:封装类转换为基本数据类型,如int n = Integer i
      • 调用Integer.intValue
    • Integer在[-128,127]内数值相同则保存在同一对象中
    • Boolean值保存为类静态常量中,值相同则对象相同
  • Java封装、继承、多态

    • 封装:面向对象特征之一,类封装了数据及操作的逻辑实体,私有不能被外界访问。为内部数据提供不同级别的保护,防止无意改变对象私有部分
    • 继承:某个类型对象获取另一个类型对象属性的方法,使用现有类的所有功能,并且在不重新编写原类的前提下进行扩展,实现从一般到特殊的过程。包括继承和组合
    • 多态:一个类对象的相同方法在不同情形下有不同的表现形式,使不同内部结构的对象共享相同的外部接口,让父对象根据赋值的子对象特性进行运作。必要条件:
      • 继承
      • 重写
      • 父类引用指向子类对象
  • Java内部类

    • 特点:
      • 提供了更好的封装,只有外部类可以访问内部类
      • 内部类可以独立于外部类实现接口
      • 外部类不能直接访问非静态内部类,非静态内部类可以访问外部类属性与方法:非静态内部类依赖于外部类对象,因此保存有外部类对象的引用,从而可以访问外部类的成员。外部类需要访问非静态内部类必须显示创建内部类对象
      • 非静态内部类中不能定义静态方法变量等
    • 编译时内部类编译成一个单独的class文件
    • 成员内部类
    • 静态内部类:用static修饰内部类,则这个内部类属于外部类类本身
    • 局部内部类
    • 匿名内部类:只使用一次的内部类,如setOnClickListener(new OnClickListener(){onClick(){}})
  • for,foreach

    • for: for(int i=0 ; i
    • foreach: for(Integer i : integers)对集合采用了迭代器效率更高,对数组采用for循环
    • 遍历过程中若有其它线程对对象进行修改,for会导致不可知的错误,而foreach可以抛出错误信息
  • 范性、类擦除机制

你可能感兴趣的:(面试复习(二) Java篇)