一、Java基础部分

1.HashMap和HashTable以及CurrentHashMap的区别。

一般来说,这三个东西基本在面试中70%会被问到,而问的方向也不太一样。比如初级的问法是讲讲它们之前的区别,这个我想没什么难度,大多数人还是知道主要核心区别是并发上的处理。此外,内部数据结构的实现、扩容、存取操作这些问题应该是很老生常谈了,这并没有什么好说的,大多数人也都知道。稍微问的深一点的可能会在下面这些点上出问题。哈希碰撞,哈希计算,哈希映射,为什么是头插法,扩容为什么是2的幂次等这样的问题。

2.synchronized和volatile、ReentrantLock、CAS的区别。

这个问题被问频率不在HashMap之下,因为并发编程,真的很重要。能问到这几个点的方式真的是太多了,我们能发挥的空间也同样很大。CAS的ABA问题?上面几个东西的特性?使用场景?大概我不用再例举了吧?对了,我多次被问到的一个问题是:synchronized修饰实例方法和修饰静态方法有啥不一样。

3.JVM类加载机制、垃圾回收算法对比、Java虚拟机结构等。

这三个问题大概出现概率40%,基本只需要看我每日一问系列的推文就差不多了吧,希望更清楚明白的可以直接看《深入理解Java虚拟机》。当你讲到分代回收算法的时候,不免会被追问到新生对象是怎么从年轻代到老年代的,以及可以作为root结点的对象有哪些两个问题。

4.Java的四大引用

四大引用面试出现概率比我想象中要高,我原本以为就强引用、软引用、弱引用、虚引用这四个玩意儿没啥可讲的。实际上也确实没啥好讲的,稍微问的深一些的面试官会和内存泄漏检测原理以及垃圾回收糅杂在一起。

5.Java的泛型,<?superT>和<?extendsT>的区别。

Java泛型还是会在面试中出现的,不过几率不是很高,大概是因为我简历中有提到泛型擦除相关的东西。所以会被问到泛型、泛型擦除、通配符相关的东西。不过这个东西,不应该是为了应付面试,实际开发中真的很重要。

6.final、finally、finalize区别。

老生常谈的问题,没啥好说的,实际上这次社招面试也只遇到了两次。比较喜欢追根溯源的面试官可能会对这个finalize有点执念,一定希望搞清楚,这玩意儿我们是不是可以真的搞点黑科技骚操作。

7.sleep、wait、yield的区别,wait的线程如何唤醒它?

大多数Android应用开发并接触不到很多并发相关的东西,不过这玩意儿还是在面试中挺容易出现的。

10.java中==和equals和hashCode的区别

  • 1)==若是基本数据类型比较,是比较值,若是引用类型,则比较的是他们在内存中的存放地址。对象是存放在堆中,栈中存放的对象的引用,所以==是对栈中的值进行比较,若返回true代表变量的内存地址相等;

  • 2)equals是Object类中的方法,Object类的equals方法用于判断对象的内存地址引用是不是同一个地址(是不是同一个对象)。若是类中覆盖了equals方法,就要根据具体代码来确定,一般覆盖后都是通过对象的内容是否相等来判断对象是否相等。

  • 3)hashCode()计算出对象实例的哈希码,在对象进行散列时作为key存入。之所以有hashCode方法,因为在批量的对象比较中,hashCode比较要比equals快。在添加新元素时,先调用这个元素的hashCode方法,一下子能定位到它应该旋转的物理位置,若该位置没有元素,可直接存储;若该位置有元素,就调用它的equals方法与新元素进行比较,若相同则不存,不相同,就放到该位置的链表末端。

  • 4)equals与hashCode方法关系:hashCode()是一个本地方法,实现是根据本地机器上关的。equals()相等的对象,hashCode()也一定相等;hashCode()不等,equals()一定也不等;hashCode()相等,equals()可能相等,也可能不等。所以在重写equals(Objectobj)方法,有必要重写hashCode()方法,确保通过equals(Objectobj)方法判断结果为true的两个对象具备相等的hashCode()返回值。

  • 5)equals与==的关系:Integerb1=127;在java编译时被编译成Integerb1=Integer.valueOf(127);对于-128到127之间的Integer值,用的是原生数据类型int,会在内存里供重用,也就是这之间的Integer值进行==比较时,只是进行int原生数据类型的数值进行比较。而超出-128〜127的范围,进行==比较时是进行地址及数值比较。

8.String、StringBuffer、StringBuilder区别

都是字符串类,String类中使用字符数组保存字符串,因有final修饰符,String对象是不可变的,每次对String操作都会生成新的String对象,这样效率低,且浪费内存空间。但线程安全。
StringBuilder和StringBuffer也是使用字符数组保存字符,但这两种对象都是可变的,即对字符串进行append操作,不会产生新的对象。它们的区别是:StringBuffer对方法加了同步锁,是线程安全的,StringBuilder非线程安全。

9.接口的意义

  • 1)有利于代码的规范,对于大型项目,对一些接口进行定义,可以给开发人员一个清晰的指示,防止开发人员随意命名和代码混乱,影响开发效率。
  • 2)有利于代码维护和扩展,当前类不能满足要求时,不需要重新设计类,只需要重新写了个类实现对应的方法。
  • 3)解耦作用,全局变量的定义,当发生需求变化时,只需改变接口中的值即可。
  • 4)直接看接口,就可以清楚知道具体实现类间的关系,代码交给别人看,别人也能立马明白。

10.进程和线程的区别

  • 进程:具有一定独立功能的程序,是系统进行资源分配和调度运行的基本单位。
  • 线程:进程的一个实体,是CPU调度的苯单位,也是进程中执行运算的最小单位,即执行处理机调度的基本单位,如果把进程理解为逻辑上操作系统所完成的任务,线程则表示完成该任务的许多可能的子任务之一。
  • 关系:一个进程可有多个线程,至少一个;一个线程只能属于一个进程。同一进程的所有线程共享该进程的所有资源。不同进程的线程间要利用消息通信方式实现同步。
  • 区别:进程有独立的地址空间,而多个线程共享内存;进程具有一个独立功能的程序,线程不能独立运行,必须依存于应用程序中

11.讲一下常见编码方式?

编码的意义:计算机中存储的最小单元是一个字节即8bit,所能表示的字符范围是255个,而人类要表示的符号太多,无法用一个字节来完全表示,固需要将符号编码,将各种语言翻译成计算机能懂的语言。

  • 1)ASCII码:总共128个,用一个字节的低7位表示,0〜31控制字符如换回车删除等;32~126是打印字符,可通过键盘输入并显示出来;
  • 2)ISO-8859-1,用来扩展ASCII编码,256个字符,涵盖了大多数西欧语言字符。
  • 3)GB2312:双字节编码,总编码范围是A1-A7,A1-A9是符号区,包含682个字符,B0-B7是汉字区,包含6763个汉字;
  • 4)GBK为了扩展GB2312,加入了更多的汉字,编码范围是8140~FEFE,有23940个码位,能表示21003个汉字。
  • 5)UTF-16:ISO试图想创建一个全新的超语言字典,世界上所有语言都可通过这本字典Unicode来相互翻译,而UTF-16定义了Unicode字符在计算机中存取方法,用两个字节来表示Unicode转化格式。不论什么字符都可用两字节表示,即16bit,固叫UTF-16。
  • 6)UTF-8:UTF-16统一采用两字节表示一个字符,但有些字符只用一个字节就可表示,浪费存储空间,而UTF-8采用一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以由1~6个字节组成。

12序列化Serializable和Parcelable的区别

  • 序列化:将一个对象转换成可存储或可传输的状态,序列化后的对象可以在网络上传输,也可以存储到本地,或实现跨进程传输;
  • 为什么要进行序列化:开发过程中,我们需要将对象的引用传给其他activity或fragment使用时,需要将这些对象放到一个Intent或Bundle中,再进行传递,而Intent或Bundle只能识别基本数据类型和被序列化的类型。Serializable:表示将一个对象转换成可存储或可传输的状态。
  • Parcelable:与Serializable实现的效果相同,也是将一个对象转换成可传输的状态,但它的实现原理是将一个完整的对象进行分解,分解后的每一部分都是Intent所支持的数据类型,这样实现传递对象的功能。
  • Parcelable实现序列化的重要方法:序列化功能是由writeToParcel完成,通过Parcel中的write方法来完成;反序列化由CREATOR完成,内部标明了如何创建序列化对象及数级,通过Parcel的read方法完成;内容描述功能由describeContents方法完成,一般直接返回0。
  • 区别:Serializable在序列化时会产生大量临时变量,引起频繁GC。Serializable本质上使用了反射,序列化过程慢。Parcelable不能将数据存储在磁盘上,在外界变化时,它不能很好的保证数据的持续性。
  • 选择原则:若仅在内存中使用,如activity\service间传递对象,优先使用Parcelable,它性能高。若是持久化操作,优先使用Serializable
  • 注意:静态成员变量属于类,不属于对象,固不会参与序列化的过程;用transient关键字编辑的成员变量不会参与序列化过程;可以通过重写writeObject()和readObject()方法来重写系统默认的序列化和反序列化。

13.string转换成integer的方式及原理

1.parseInt(Strings)内部调用parseInt(s,10)默认为10进制。
2.正常判断null\进制范围,length等。
3.判断第一个字符是否是符号位。
4.循环遍历确定每个字符的十进制值。
5.通过*=和-=进行计算拼接。
6.判断是否为负值返回结果。

14.静态代理和动态代理的区别,什么场景使用?

代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。
区别:
1)静态代理:由程序员创建或是由特定工具生成,在代码编译时就确定了被代理的类是哪一个是静态代理。静态代理通常只代理一个类;
2)动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类;
实现步骤:a.实现InvocationHandler接口创建自己的调用处理器;b.给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类;c.利用反射机制得到动态代理类的构造函数;d.利用动态代理类的构造函数创建动态代理类对象;
使用场景:Retrofit中直接调用接口的方法;Spring的AOP机制

15.说说你对Java反射的理解

在运行状态中,对任意一个类,都能知道这个类的所有属性和方法,对任意一个对象,都能调用它的任意一个方法和属性。这种能动态获取信息及动态调用对象方法的功能称为java语言的反射机制。
反射的作用:开发过程中,经常会遇到某个类的某个成员变量、方法或属性是私有的,或只对系统应用开放,这里就可以利用java的反射机制通过反射来获取所需的私有成员或是方法。
1)获取类的Class对象实例Classclz=Class.forName("com.zhenai.api.Apple");
2)根据Class对象实例获取Constructor对象ConstructorappConstructor=clz.getConstructor();
3)使用Constructor对象的newInstance方法获取反射类对象ObjectappleObj=appConstructor.newInstance();
4)获取方法的Method对象MethodsetPriceMethod=clz.getMethod("setPrice",int.class);
5)利用invoke方法调用方法setPriceMethod.invoke(appleObj,14);
6)通过getFields()可以获取Class类的属性,但无法获取私有属性,而getDeclaredFields()可以获取到包括私有属性在内的所有属性。带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法,其他如Annotation\Field\Constructor也是如此。

二、Jvm

1.Java内存模型

1.1 Jvm内存区域是如何划分的?

内存区域划分:

  • 程序计数器:当前线程的字节码执行位置的指示器,线程私有。
  • Java虚拟机栈:描述的Java方法执行的内存模型,每个方法在执行的同时会创建一个栈帧,存储着局部变量、操作数栈、动态链接和方法出口等,线程私有。
  • 本地方法栈:本地方法执行的内存模型,线程私有。
  • Java堆:所有对象实例分配的区域。
  • 方法区:所有已经被虚拟机加载的类的信息、常量、静态变量和即时编辑器编译后的代码数据。

1.2 Jvm内存模型是怎么样的?

1.Java规定所有变量的内存都需要存储在主内存。
2.每个线程都有自己的工作内存,线程中使用的所有变量以及对变量的操作都基于工作内存,工作内存中的所有变量都从主内存读取过来的。
3.不同线程间的工作内存无法进行直接交流,必须通过主内存完成。

主内存和工作内存之间的交互协议,即变量如何从主内存传递到工作内存、工作内存如何将变量传递到主内存,Java内存模型定义了8种操作来完成,并且每一种操作都是原子的,不可再分的。

1.3 Strings1="abc"和Strings2=newString("abc")的区别,生成对象的情况

1.指向方法区:"abc"是常量,所以它会在方法区中分配内存,如果方法区已经给"abc"分配过内存,则s1会直接指向这块内存区域。
2.指向Java堆:newString("abc")是重新生成了一个Java实例,它会在Java堆中分配一块内存。
所以s1和s2的内存地址肯定不一样,但是内容一样。

2. GC机制

2.1如何判断对象可回收?

判断一个对象可以回收通常采用的算法是引用几算法和可达性算法。由于互相引用导致的计数不好判断,Java采用的可达性算法。
可达性算法的思路是:通过一些列被成为GCRoots的对象作为起始点,自上往下从这些起点往下搜索,搜索所有走过的路径称为引用链,如果一个对象没有跟任何引用链相关联的时候,则证明该对象不可用,所以这些对象就会被判定为可以回收。
可以被当作GCRoots的对象包括:

  • Java虚拟机栈中的引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法中JNI引用的对象

2.2 GC的常用算法?

  • 标记-清除:首先标记出需要回收的对象,标记完成后统一回收所有被标记的对象。容易产生碎片空间。
  • 复制算法:它将可用的内存分为两块,每次只用其中的一块,当需要内存回收的时候,将存活的对象复制到另一块内存,然后将当前已经使用的内存一次性回收掉。需要浪费一半的内存。
  • 标记-整理:让存活的对象向一端移动,之后清除边界外的内存。
  • 分代搜集:根据对象存活的周期,Java堆会被分为新生代和老年代,根据不同年代的特性,选择合适的GC收集算法。

2.3MinarGC和FullGC的区别?

  • MinarGC:频率高、针对新生代。
  • FullGC:频率低、发生在老年代、通常会伴随一次MinarGC和速度慢。

2.4说一下四种引用以及他们的区别?

  • 强引用:强引用还在,垃圾搜集器就不会回收被引用的对象。
  • 软引用:对于软引用关联的对象,在系统发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  • 弱引用:被若引用关联的对象只能存活到下一次GC之前。
  • 虚引用:为对象设置虚引用的目的仅仅是为了GC之前收到一个系统通知。

3.类加载

3.1 类加载的过程?

类加载的过程可以分为:
1.加载:将类的全限定名转化为二进制流,再将二进制流转化为方法区中的类型信息,从而生成一个Class对象。
2.验证:对类的验证,包括格式、字节码、属性等。
3.准备:为类变量分配内存并设置初始值。
4.解析:将常量池的符号引用转化为直接引用。
5.初始化:执行类中定义的Java程序代码,包括类变量的赋值动作和构造函数的赋值。
6.使用
7.卸载

只有加载、验证、准备、初始化和卸载的这个五个阶段的顺序是确定的。

3.2类加载的机制,以及为什么要这样设计?

类加载的机制是双亲委派模型。大部分Java程序需要使用的类加载器包括:

  • 启动类加载器:由C++语言实现,负责加载Java中的核心类。
  • 扩展类加载器:负责加载Java扩展的核心类之外的类。
  • 应用程序类加载器:负责加载用户类路径上指定的类库。

双亲委派模型如下:

双亲委派模型要求出了顶层的启动类加载器之外,其他的类加载器都有自己的父加载器,通过组合实现。

双亲委派模型的工作流程:
当一个类加载的任务来临的时候,先交给父类加载器完成,父类加载器交给父父类加载器完成,知道传递给启动类加载器,如果完成不了的情况下,再依次往下传递类加载的任务。

这样设计的原因:
双亲委派模型能够保证Java程序的稳定运行,不同层次的类加载器具有不同优先级,所有的对象的父类Object,无论哪一个类加载器加载,最后都会交给启动类加载器,保证安全。

三、计算机网络部分

1. TCP与UDP的区别。

2.三次握手、四次挥手。为啥是三次不是两次?

3.HTTPS和HTTP的区别。HTTP2.0,3.0?

关于网络部分这里就不细说了,有想看的小伙伴可以看看这位博主写的关于面试的博文2020-2021Android中高级面试题大全

四、Android面试题

1、什么是ANR如何避免它?

答:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:ApplicationNotResponding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要这样,这样系统就不会显示ANR给用户。
不同的组件发生ANR的时间不一样,Activity是5秒,BroadCastReceiver是10秒,Service是20秒(均为前台)。
如果开发机器上出现问题,我们可以通过查看/data/anr/traces.txt即可,最新的ANR信息在最开始部分。

  • 主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞。
  • 主线程中存在耗时的计算
  • 主线程中错误的操作,比如Thread.wait或者Thread.sleep等Android系统会监控程序的响应状况,一旦出现下面两种情况,则弹出ANR对话框
  • 应用在5秒内未响应用户的输入事件(如按键或者触摸)
  • BroadcastReceiver未在10秒内完成相关的处理
  • Service在特定的时间内无法处理完成20秒
    修正:
    1、使用AsyncTask处理耗时IO操作。
    2、使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
    3、使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
    4、Activity的onCreate和onResume回调中尽量避免耗时的代码。BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。
  • 解决方案:*
    将所有耗时操作,比如访问网络,Socket通信,查询大量SQL语句,复杂逻辑计算等都放在子线程中去,然后通过handler.sendMessage、runonUIThread、AsyncTask、RxJava等方式更新UI。无论如何都要确保用户界面的流畅度。如果耗时操作需要让用户等待,那么可以在界面上显示度条。

    资料分享

    分享一份由几位大佬一起收录整理在GITHUB上的 Android学习PDF+架构视频+面试文档+源码笔记 ,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构等资料,祝大家早日拿到自己理想的offer。