欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android面试题(一)Java基础篇

程序员文章站 2024-03-23 17:59:04
...

前言

“Android工程师面试为什么要讲Java基础?
答:Android基于Java语言开发,所以Java知识也是Android开发人员必不可少的知识。Java基础不过关的话,Android开发也走不远。Java的基础以及一些常用的底层原理,肯定是要懂的。所以,面试时很多公司都会问到Java基础及原理,一般来说基础的准备是必要并且通用的,所以这部分真的很重要!”

1、Java基本数据类型

  1. 整形:byte(1个字节), short(2个字节) , int (4个字节), long(8个字节) 。

  2. 浮点型:float(4个字节),double(8个字节)。

  3. 浮点型:char(2个字节) 。

  4. 布尔型:boolean (1个字节)

2、String ,Stringbuffer ,Stringbulider的区别?

  1. String底层是一个final类型的字符数组,所以String的值是不可变的,每次对String的操作都会生成新的String对象,造成内存浪费。

  2. StringBuffer和StringBuilder都继承AbstractStringBuilder抽象类,底层是可变的字符数组。

  3. StringBuffer是在jdk1.0出现的,是线程安全的,查看源码有synchronized关键字修饰,但是执行速度慢 ,StringBuilder是jdk1.5出现的,是非线程安全的,执行速度快。

执行效率:在串行的的情况下,String 的效率最低(因为底层是不可变数组,每次操作都是新建一个Sting对象),Stringbuffer因为是同步,有syncchronzed稀释,效率相对低,StringBulider效率最高。

3、final ,finally ,fianlize的区别?

  1. final是一个关键字,用来修饰类,变量,方法.修饰的类不能被继承,但是可以继承其他的类,修饰的方法不能被子类冲重写,修饰的变量是常量,只能被赋值一次。

  2. finally是try-catch-finally语句的一个模块,正常情况下里面的代码永远会被执行,一般用来释放资源。

  3. finalize是Object类中的方法,当对象变成垃圾的时候,由GC来调用finalize()方法回收。

4、静态变量和成员变量的不同?

  1. 所属范围不同。静态变量是属于类范围的;成员变量是属于对象范围的。

  2. 存活时间不同。类的一生有着静态变量的伴随;而成员变量只能陪类走一程,对象产生的时候它就产生,而且它会随着对象的消亡而消亡。

  3. 存储位置不同。静态变量时存储在方法区里的静态区;成员变量存储在堆栈内存区。

  4. 调用方式不同。静态变量可以通过类名调用,也可以通过对象来调用;成员变量只能通过对象名调用。

5、集合框架,list,map,set都有哪些具体的实现类,区别都是什么?

  1. List:有序、可重复;索引查询速度快;插入、删除伴随数据移动,速度慢;

  2. Set:无序,不可重复;

  3. Map:键值对,键唯一,值多个;

  4. List、Set都是继承自Collection接口,Map则不是;

6、ArrayList和LinkedList有什么区别?

  1. ArrayList和LinkedList都实现了List接口

  2. ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)

  3. 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快

  4. LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素

7、HashMap和Hashtable有什么区别?

  1. HashMap和Hashtable都实现了Map接口,因此很多特性非常相似

  2. HashMap允许键和值是null,而Hashtable不允许键或者值是null

  3. Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境

  4. HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)

8、Java四种应用类型

  1. 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收;

  2. 软引用:只有在内存不足时,软引用才会被垃圾回收器回收;

  3. 弱引用:具有弱引用的对象拥有的生命周期更短暂;

  4. 虚引用:顾名思义,就是形同虚设。

9、线程安全集合类与非线程安全集合类

  1. LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;

  2. HashMap是非线程安全的,HashTable是线程安全的;

  3. StringBuilder是非线程安全的,StringBuffer是线程安的。

10、HashMap在jdk1.7和1.8有什么变化?

  1. jdk1.7,底层采用了数组+链表,头插法;

    ● 负载因子:默认容量为16,负载因子为0.75,当超过16*0.75=12时,就要仅从hashmap的扩容,扩容涉及到rehash和复制数据等,会非常消耗性能。

    ● 真正存储数据的是entry<key,value>[] table,entry是hashmap的一个静态内部类,有key,value,next,hash(key的hashcode)成员变量。

  2. jdk1.8,采用是的底层+链表+红黑树;

    ● 增加一个阈值进行判断是否将链表转红黑树,HashEntry 修改为 Node。解决hash冲突,造成链表越来越长,查询慢的问题。

11、Java反射获取字节码有几种方式?

  1. Class.forName(className)

  2. 类名.class

  3. this.getClass()

12、Java内存区域

运行时内存区域如图所示:

  1. 程序计数器

    ① 当线程切换时,用来标记当前线程所执行的字节码的行号指示器;

    ② 每一个线程都有一个独立的程序计数器,用来线程切换时能恢复到正确的位置;

  2. JAVA虚拟机栈

    ① 线程私有,生命周期和线程一样,用来存储:局部变量表,操作数栈,动态链接,方法出口等;

    ② 局部变量表所占用的内存在编译器就完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小;

  3. 本地方法栈

    ① 为虚拟机执行native方法;

  4. JAVA堆

    ① 存放对象实例;

    ② java堆是垃圾收集器管理的主要区域;

    ③被所有线程共享的一块内存区域;

  5. 方法区

    ① 各个线程所共享的内存区域;

    ② 用于存储class二进制文件,包含了虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;

    ③ 线程安全的;

    ④ 运行时常量池存在方法区中;

常量池

① 常量是指被final修饰的变量,值一旦确定就无法改变;

② 常量池在编译期间就将一部分数据存放于该区域,包含基本数据类型如int、long等以final声明的常量值,和String字符串、特别注意的是对于方法运行期位于栈中的局部变量String常量的值可以通过 String.intern()方法将该值置入到常量池中。

③ 分类:

● Class文件常量池;
● 运行时常量池;
● 全局字符串常量池;
● 基本类型包装类对象常量池;

13、Java-GC的原理和回收策略?

Java-GC机制内容较多,建议深入理解。

1.GC核心思想:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象
,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能。

  1. GC roots 来源:

    ① 虚拟机栈中引用的对象。

    ② 方法区中类静态属性引用的对象。

    ③ 方法区中常量引用的对象。

    ④ 本地方法栈中JNI引用的对象

  2. 四种引用对象

    ① 强引用:不进行垃圾回收

    ② 软引用:在内存溢出前进行垃圾回收

    ③ 弱引用:只要有垃圾回收就会回收掉

    ④ 虚引用:无用对象,垃圾收集时会接收到通知

  3. 方法区的垃圾回收

    ① 废弃常量

    ② 无用的类

    ● 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。

    ● 加载该类的ClassLoader已经被回收。

    ● 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

  4. 回收算法

① 复制算法:
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。

② 标记-清除:
标记-清除算法采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

③ 标记-整理:

标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。该垃圾回收算法适用于对象存活率高的场景(老年代)。

④ 分代收集算法:

● 不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。当代商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。

● 新生代:

● 老年代:

  1. 垃圾收集器

    ① Serial收集器(复制算法):
    

    新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

    ② **ParNew收集器 (复制算法): **
    

    新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

    ③ Parallel Scavenge收集器 (复制算法):
    

    新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景

    ④ **Serial Old收集器 (标记-整理算法): **
    

    老年代单线程收集器,Serial收集器的老年代版本

    ⑤ CMS(Concurrent Mark Sweep)收集器(标记-清除算法):

    老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

    ⑥ Parallel Old收集器 (标记-整理算法):
    

    老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

    ⑦ G1(Garbage First)收集器 (标记-整理算法):
    

    Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

14、Java虚拟机和Dalvik虚拟机区别?

  1. Java虚拟机:

① java虚拟机基于栈。基于栈的机器必须使用指令来载入和操作栈上数据,所需指令更多更多。

② java虚拟机运行的是java字节码。(java类会被编译成一个或多个字节码.class文件)

  1. Dalvik虚拟机:

① dalvik虚拟机是基于寄存器的

② Dalvik运行的是自定义的.dex字节码格式。(java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据

③ 常量池已被修改为只使用32位的索引,以 简化解释器。

④ 一个应用,一个虚拟机实例,一个进程(所有android应用的线程都是对应一个linux线程,都运行在自己的沙盒中,不同的应用在不同的进程中运行。每个android dalvik应用程序都被赋予了一个独立的linux PID(app_*))

15、Java类加载过程

  1. 通过一个类的全限定名来获取定义此类的二进制字节流;

  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;

  3. 将类的class文件读入内存,并为之创建一个java.lang.Class对象;

  4. 当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象;

16、Synchronized三种应用方式?

  1. 修饰普通方法

一个对象中的加锁方法只允许一个线程访问。但要注意这种情况下锁的是访问该方法的实例对象, 如果多个线程不同对象访问该方法,则无法保证同步。

  1. 修饰静态方法

由于静态方法是类方法, 所以这种情况下锁的是包含这个方法的类,也就是类对象;这样如果多个线程不同对象访问该静态方法,也是可以保证同步的。

  1. 修饰代码块

其中普通代码块 如Synchronized(obj) 这里的obj 可以为类中的一个属性、也可以是当前的对象,它的同步效果和修饰普通方法一样;Synchronized方法 (obj.class)静态代码块它的同步效果和修饰静态方法类似。

17、Java线程的创建方式有几种?

  1. 继承Thread类

创建方法

    ① 定义threade的子类,重写run方法

    ② 创建Thread子类的实例,即创建了线程对象

    ③ 调用线程对象的start()方法来启动该线程

优缺点

    优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。

    缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
  1. 实现Runnable接口

创建方法

    ① 定义runnable接口的实现类,并重写该接口的run()方法

    ② 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

    ③ 调用线程对象的start()方法来启动该线程

优缺点

    优点:线程类只是实现了Runable接口,还可以继承其他的类。

    缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
  1. 实现Callable接口

创建方式

    ① 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值

    ② 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

    ③ 使用FutureTask对象作为Thread对象的target创建并启动新线程

    ④ 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

Runnable和Callable的区别:

    ① Runnable实现是run方法,callable 重写的是call(),并有返回值。

    ② call方法可以抛异常,run方法不行。

    ③ 运行Callable任务可以拿到一个Future对象,表示异步计算的结果.它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

18、Java线程生命周期

新建:

就是刚使用new方法,new出来的线程;

就绪:

就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行

运行:

当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

阻塞:

在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

死亡:

如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

19、sleep()、wait()的区别?

  1. sleep()

线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep()方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。

  1. wait()

Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。

sleep() 和 wait() 的区别:

调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁 线程池

20、线程池

  1. 常见的线程池

① CacheThreadPool(),可缓存的线程池。

    ● 线程数无限制。

    ● 有空闲线程则复用空闲线程,若无空闲线程则新建线程。

    ● 一定程序减少频繁创建/销毁线程,减少系统开销。

② FixedThreadPool(n)定长线程池

    ● 可控制线程最大并发数(同时执行的线程数)

    ● 超出的线程会在队列中等待

③ SingleThreadExecutor()单线程化的线程池

    ● 有且仅有一个工作线程执行任务。

    ● 所有任务按照指定顺序执行,即遵循队列的入队出队规则。串行

④ 4.ScheduledThreadPool(int n)

    ● 创建一个定长线程池,支持定时及周期性任务执行
  1. 使用线程池的好处

    ① 重用,减少创建和销毁所产生的性能开销
    ② 避免过多的线程抢占CPU而产生阻塞现象
    ③ 统一管理

附:Java思维导图

Android面试题(一)Java基础篇

更多精彩文章,请关注:

Android面试题(一)Java基础篇