基于面试问题整理了答案,希望能对大家上岸有所帮助~。

一面:
(一)(敲代码)给定一个数组,求第k小的数。

     快速排序+array[k-1];记住快速排序的写法。

public class Main {
public static void main(String[] args) throws Exception {
int[] arrays = new int[]{4, 9, 7, 4, 1, 3, 6};
int t = getMinK(arrays,2);
System.out.println(t);
}
private static int getMinK(int[] arrays, int i) throws Exception {
if(i<0 || i>=arrays.length) throw new Exception("has problem");
QuickSort(arrays,0,arrays.length-1);
return arrays[i-1];
}
public static void QuickSort(int[] array, int low, int high) {
if(low<high){
int start = low,end = high,key = array[low];
while (start<end){
if(start<end && array[end]>= key){
end--;
}
if(start<end){
array[start++] = array[end];
}
if(start<end && array[start] < key){
start++;
}
if(start<end){
array[end--] = array[start];
}
array[start] = key;
QuickSort(array,low,start-1);
QuickSort(array,start+1,end);
}
}
}
}

(二)TCP和UDP的区别,UDP如何实现可靠传输。

问题一:

      1.Tcp 面向连接,提供可靠的传输; UDP面向无连接,提供不可靠传输;2.UDP传输效率更高,因为它没有计时器、连接管理等,但不能保证完整性;3.TCP 面向字节流 ; UDP 面向数据包4.适用范围不同:UDP一般用于即时通信,譬如:直播、DNS地址解析;TCP主要用于可靠服务,譬如传送文件FTP、邮件发送接收SMTP。

问题二:

       TCP如何保证可靠传输:校验和、流量控制、拥塞控制、超时重传机制、ARQ协议。

       UDP:

      1. 数据完整性 –> 加上一个16或者32位的CRC验证字段

      2. 乱序 –> 加上一个数据包序列号SEQ

      3. 丢包 –> 需要确认和重传机制,就是和Tcp类似的Ack机制

      4. 协议字段(过滤一些非法包)–> protol 字段,标识当前使用协议

      Proto协议(1Byte)+CRC校验(2Byte)+数据包序列号SEQ(4Byte)+是否回包isAck(1Byte)+UDP

(三)工作内存和主内存。(这里是给了一个例子,主线程有一个变量a,子线程里用到了这个变量,问这两处的变量是不是一个东西)

     Java内存模型分为主内存,和工作内存。主内存是所有的线程所共享的,工作内存是每个线程自己有一个,不是共享的。

    (1)主内存是在运行期间所有变量的存放区域,当工作内存是运行期间中某一线程独立私有的内存存放区域。

    (2)线程间无法访问对方的工作内存空间,都是通过主内存交换来实现

    (3)主内存的变量在工作内存中的值是复制过去的副本,读写完成后刷新主内存,这意味着主内存如果发生了改变,工作内存并无法获得最新的结果
    (4)多个线程对一个共享变量进行修改时,都是对自己工作内存的副本进行操作,相互不可见。主内存最后得到的结果是不可预知的

(四)GC讲一下。

      主要是对Java中的堆和方法区进行管理。

      如何判定一个对象可被回收?引用计数法和可达性分析算法。

      垃圾收集算法:标记-清除算法、 复制算法、标记-整理算法、分代收集算法四种。

      标记-清除算法:“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。1.效率问题;2.空间问题。

      复制算法:将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,顺序分配,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。简单但代价大。

      标记-整理算法:标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

      分代收集算法:一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。新生代采用复制算法,老年代采用标记-清除算法或者标记-整理算法。

      垃圾收集器:Serial 收集器(使用一条垃圾收集线程去完成垃圾收集工作,必须要暂停,必须暂停其他所有的工作线程,复制+标记整理)、ParNew(多线程回收,复制+标记整理)、CMS(获取最短回收停顿时间为目标的收集器、第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。基于“标记-清除算法”的。经历初始标记、并发标记、重新标记和标记清除四个阶段。特点并发收集、低停顿。劣势是对 CPU 资源敏感、无法处理浮动垃圾);G1收集器(满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.基于标记整理算法。初始标记;并发标记;最终标记;筛选回收。空间整合、并行与并发)

(五)线程安全

       在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制(synchorized和Lock)保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

       比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

       在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

       而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 

那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

(六)volatile和synchronized的区别

        可见性、同步性;修饰对象;阻塞与否;编译器优化与否。

     (1)volatile关键字解决的是变量在多个线程之间的可见性(一个线程修改的状态对另一个线程是可见的。);而sychronized关键字解决的是多个线程之间访问共享资源的同步性。 

     (2)volatile只能用于修饰变量,而synchronized可以修饰变量,方法,以及代码块。

     (3)volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

     (4)volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

补充:synchronized和Lock的区别

     (1)synchronized不会引起死锁,而Lock发生异常时,如果没有unLock解锁,可能会引起死锁现象。

     (2)Lock是接口,Sychronsized是关键字;

     (3)当竞争资源激烈时,Lock性能高于Sychronized。

     (4)Lock可以让等待的锁响应中断,但sychronsized不可以。一直等待下去。

(七)LruCache

        用于内存缓存,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。

        LruCache是一个泛型类,它内部采用了LinkedHashMap以强引用的方式存储外界的缓存对象,提供get和put的操作完成缓存的获取和添加,当缓存满时,移除较早使用的对象,并添加新的对象。LruCache是线程安全的。

(八)一般我们调试的时候,会用到断点,断点在底层是怎么实现的,为什么还可以看到一些变量的值。

       这些工具都支持本地调试和远程调试。都是靠JPDA(Java平台调试机制)的支撑实现的。包括JVMTI(Java虚拟机工具接口,主要用于开发和监控上)、JDWP(Java Debugger Wire Protocol debugger和它要debug的JVM之间进行通讯的协议)、JDI(Java Debug Interface,JDI是三个模块中最高层的一个接口,通过JDI,debugger可以更方便的编写符合JDWP格式的数据,用来进行调试数据传输。JDI的引入,提高了开发debugger的效率。)

(九)地址对齐(struct A{ short a; int b;short c;};  struct B{ short a; int b; short c;}; 问这两个结构体在内存里分配的空间一样大吗。)

(十)Linux的c/c++程序,为什么可以在Windows运行加载?

        如果仅仅只用了c/c++的标准库,或者第三方的库,可以在windows里再编译加载……如果用了linux的API的话,肯定没戏…

(十一)重写和重载的区别。

        重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。   

        重写:发生在父子类中,实质是对父类函数的重新定义。方法名、参数列表必须相同,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。如果需父类中原有的方法可使用super关键字。

(十二)HTTP如何实现断点续传的。

        一个最简单的断点续传实现大概如下:1.客户端下载一个1024字节的文件,已经下载了其中512字节. 2.网络中断,客户端请求续传,因此需要在HTTP头中申明本次需要续传的片段:Range:bytes=512-这个头通知服务端从文件的512位置开始传输文件3. 服务端收到断点续传请求,从文件的512位置开始传输,并且在HTTP头中增加:Content-Range:bytes 512-1024并且此时服务端返回的HTTP状态码应该是206(PartialContent的响应报文),而不是200(表示成功)。

(十三)问了一下项目。

     Dex2C+DexVMP。

二面:

(一)ArrayList和LinkedList的区别。

      1.是否保证线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

      2.底层数据结构:Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构。

      3. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。

      4.是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。

      5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)

(二)List如何删除。(为什么用iterator的不用List的删除方法,讲了一下ConcurrentModificationException)

       Iterator.remove()遍历器。当使用iterator迭代获取元素并需要移除元素时,需使用iterator的remove()方法移除元素。如果使用list.remove(),将在iterator下一次调用next()时抛出ConcurrentModificationException。如果中途调用list.add(),同样会修改modCount,导致抛出异常,这是因为lastRet的下标永远比cursor小1,下次走next()方法的时候lastRet的值就变成了跟上次删除的一样,相当于下标减1了,

(三)重写和重载的区别。

       重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。   

       重写:发生在父子类中,实质是对父类函数的重新定义。方法名、参数列表必须相同,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。如果需父类中原有的方法可使用super关键字。

(四)如何实现多态,多态的底层实现原理是什么?

        继承(多个子类继承并重写父类当中的同一方法);使用接口实现(实现接口并覆盖父类的同一方法)。

        实现是动态绑定,Java 对于方法调用动态绑定的实现主要依赖于方法表。即在运行时才把方法调用与方法实现关联起来。当某个方法被调用时,JVM 首先要查找相应的常量池,得到方法的符号引用,并查找调用类的方法表以确定该方法的直接引用,最后才真正调用该方法。

(五)Handler原理。

       Handler、Message、MessageQueue、Looper。

       sendMessage发送消息,dispatchMessage将Message交给特定的Handler去处理。最后调用HandlerMessage进行处理。MEssageQueue的enqueueMessage和next进行入队和出队。

       异步消息处理机制主要是用来解决子线程更新UI的问题

主要有四个部分:

      ①. Message (消息)中

      在线程之间传递,可在内部携带少量信息,用于不同线程之间交换数据,可以使用what、arg1、arg2字段携带整型数据,obj字段携带Object对象

      ②. Handler (处理者)

      要用于发送和处理消息,sendMessage()用来发送消息,最终会回到handleMessage()进行处理。

       ③. MessageQueue (消息队列)

      主要存放所有通过Handler发送的消息,它们会一直存在于队列中等待被处理,每个线程只有一个MessageQueue

      ④. Looper (循环器)

      调用loop()方法后,会不断从MessageQueue 取出待处理的消息,然后传递到handleMessage进行处理

(六)HandlerThread原理。

      使用步骤如下:

   (1)创建HandlerThread对象,可以重写onLooperPrepared()方法做一些初始化操作;

   (2)调用handlerThread.start()方法;

   (3)创建Handler,在构造中传入handlerThread.getLooper()方法创建的Looper对象,重写handleMessage(Message msg)方法,在子线程处理消息;

   (4)使用Handler对象在相关场景发送处理消息;

   (5)适时退出异步操作quit方法

    源码:

    (1)HandlerThread 继承了Thread,是一种可以使用Handler的Thread,在run方法中通过Looper.prepare()来创建消息队列,并通过loop()来开启消息循环,可以通过quit方法或者quitSafely方法来终止线程进行,允许在HandlerThread中创建Handler了。

   (2)普通Thread主要用于在run方法中执行一个耗时操作,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。

      整个HandlerThread做的工作本质是在Thread中封装了Looper对象,以此在创建的Handler中能够进行异步的消息处理。

HandlerThread handlerThread = new HandlerThread("handlerThread"){
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
Log.d("执行","准备工作");
}
};
handlerThread.start();
//必须在调用handlerThread.start()之后才能创建Handler
final Handler handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("消息","在 "+Thread.currentThread().getName()+" 线程,收到 "+msg.obj);
}
};
handler.sendMessage(Message.obtain(handler,1,"test1"));

(七)Looper。

        Android是依靠事件驱动的,Looper.preapare()创建消息队列,通过Looper.loop()不断进行消息循环。

(八)事件分发机制

(1)在我们点击屏幕时,会有下列事件发生:

     Activity调用dispathTouchEvent()方法,把事件传递给Window;

     Window再将事件交给DecorView(DecorView是View的根布局);

     DecorView再传递给ViewGroup;

     Activity ——> Window ——> DecorView ——> ViewGroup——> View

(2)事件分发的主要有三个关键方法

     dispatchTouchEvent() 分发

     onInterceptTouchEvent() 拦截 ,只有ViewGroup独有此方法

     onTouchEvent() 处理触摸事件

     Activity首先调用dispathTouchEvent()进行分发,接着调用super向下传递

     ViewGroup首先调用dispathTouchEvent()进行分发,接着会调用onInterceptTouchEvent()(拦截事件)。若拦截事件返回为true,表示拦截,事件不会向下层的ViewGroup或者View传递;false,表示不拦截,继续分发事件。默认是false,需要提醒一下,View是没有onInterceptTouchEvent()方法的

(3)事件在ViewGroup和ViewGroup、ViewGroup和View之间进行传递,最终到达View;

   View调用dispathTouchEvent()方法,然后在OnTouchEvent()进行处理事件;OnTouchEvent() 返回true,表示消耗此事件,不再向下传递;返回false,表示不消耗事件,交回上层处理。

(九)项目的难点是什么,收获最大的是什么,现在回看项目,有哪些需要改进的地方。

     Dex2C的思路,从google的Java2C到Dex2Jar的Dex2Jar,学习了挺多。改进地方:多重循环下的作用域划分。

(十)还问了我愿不愿意去北京实习,我拒绝了...
(十一)算法:给定一个有序数组和target,判断target是否在数组里重复出现,如果有,输出第一次出现的下标。


private static int JudgeDeu1(int[] arrays) {
HashSet<Integer> hashSet = new HashSet<>();
for(int i =0;i<arrays.length;i++){
if(!hashSet.contains(arrays[i])){
hashSet.add(arrays[i]);
}else{
return i-1;
}
}
return -1;
}
private static int JudgeDeu(int[] arrays) {
for(int i =1;i<arrays.length;i++){
if(arrays[i] == arrays[i-1]){
return (i-1);
}
}
return -1;
}

三面:
(一)Object有哪些公有方法。

  (1)clone:保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法;

  (2)equals:在Object中与==是一样的,子类一般需要重写该方法。

  (3)hashCode:该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。

  (4)getClass:final方法,获得运行时类型。

  (5)wait:wait()方法一直等待,直到获得锁或者被中断。1. 其他线程调用了该对象的notify方法 2. 其他线程调用了该对象的notifyAll方法 3. 其他线程调用了interrupt中断该线程 4. 时间间隔到了 

  (6)notify:唤醒在该对象上等待的某个线程

  (7)notifyAll:唤醒在该对象上等待的所有线程

  (8)toString:转换成字符串,一般子类都有重写

  (9)finalize:当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

(二)讲一下equals()。

       equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

       情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”(两个对象的地址是不是相等)比较这两个对象。

       情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

       == : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。

       补充:四种整数类型(byte 1字节、short 2字节、int 4字节、long 8字节)、两种浮点数类型(float 四字节、double 8字节)、一种字符类型(char 2字节)、一种布尔类型(boolean)。

(3)hashCode 与 equals

      hashCode()介绍

      hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。

       散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

       为什么要有 hashCode?

       我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

        通过我们可以看出:hashCode() 的作用就是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。**hashCode() 在散列表中才有用,在其它情况下没用。**在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

         补充:hashCode()与equals()的相关规定

       (1)如果两个对象相等,则hashcode一定也是相同的;(2)两个对象相等,对两个对象分别调用equals方法都返回true;(3)两个对象有相同的hashcode值,它们也不一定是相等的;(4)equals 方法被覆盖过,则 hashCode 方法也必须被覆盖;(5)hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

      哈希散列处理冲突的方法:

      1.开放定址法:线性探测再散列;二次探测再散列;伪随机探测再散列;2.再哈希法;3.链地址法。

(四)wait()的用法,notifyAll()的用法,notify()唤醒的是哪一个线程。

​https://www.jianshu.com/p/c518f9c07a80​​​

     1. wait():wait()方法一直等待,直到获得锁或者被中断。执行该方法后,会释放锁,使线程等待,进入阻塞队列。

     2. notify():notify()默认的唤醒策略是:先进入wait()的线程先被唤醒。

     3. notifyAll():notifyAll()唤醒所有的等待线程,默认的唤醒策略是:LIFO(后进先出)。

     有 synchronized 的地方不一定有 wait() 和 notify(),但有 wait() 和 notify() 的地方必有 synchronized,这是因为 wait() 和 notify() 不属于线程类,而是每一个对象都具有的方法。而且,这两个方法都和对象锁有关,有锁的地方,必须有 synchronized。

(五)讲一下clone(),深拷贝和浅拷贝的区别。

       就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

       clone是Object中的方法,目的是实现对象的浅拷贝。两种方法:拷贝构造方法,复写clone方法。 1.一定要实现Cloneable接口;2.复写clone()方法,注意:默认是浅拷贝;3.特殊:String类虽然是引用类型,但是是final类,同时也有字符串常量池的存在,不必进行处理

       深拷贝:对所有的成员变量进行了复制。深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。1.通过重写clone方法来实现深拷贝;2、通过对象序列化实现深拷贝。

(六)讲一下jmm。

       Java内存模型。目的:能够屏蔽掉各种硬件和操作系统的内存访问差异,实现平台一致性,使得Java程序能够“一次编写,到处运行”。

       主内存和工作内存。主内存是所有的线程所共享的,工作内存是每个线程自己有一个,不是共享的。线程间通信的步骤:首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去;然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

      可见性、有序性:当一个对象在多个内存中都存在副本时,如果一个内存修改了共享变量,其它线程也应该能够看到被修改后的值,此为可见性。保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。

(七)如何在栈上开辟空间

     (1)基本数据类型、局部变量和对象的引用变量都是存放在栈内存中的;(2)数据一执行完毕,变量会立即释放,节约内存空间。(3)没有默认初始化值

(八)如何在堆里开辟空间

      (1)new创建的实例化对象及数组,是存放在堆内存中的;(2)用完之后靠垃圾回收机制不定期自动消除;(4)有默认初始化值。

(九)怎么会造成栈溢出,堆溢出。

      栈溢出

      两种情况:

      1.线程请求的栈深度大于虚拟机允许的最大深度 StackOverflowError

      2.虚拟机在扩展栈深度时,无法申请到足够的内存空间 OutOfMemoryError

      堆溢出

      创建对象时如果没有可以分配的堆内存,就会出现堆溢出。Java虚拟机堆大小进行设置:java –Xmx512m 

(十)类加载机制,什么时候需要对类进行初始化。(以后看)

       虚拟机是如何加载这些 Class 文件:加载->连接->初始化。连接又被分为验证->准备->解析。

       类加载过程的第一步,主要完成以下三个事情:1.通过全类名获取定义此类的二进制字节流;2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构;3在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。

       连接分为验证、准备、解析。验证包括文件格式验证、元数据验证、字节码验证以及符号引用验证;准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配;解析:虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。

       初始化:初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序字节码;执行类构造器<cliinit>方法的过程。必须要对类进行初始化:1.当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。2.使用 java.lang.reflect 包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。3.初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。4.当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。

(十一)静态变量,实例变量,构造函数的初始化顺序。


    父类静态变量->子类静态变量->父类实例变量->父类构造函数->子类实例变量->子类构造函数。

     (1)在new B一个实例时首先要进行类的装载。(类只有在使用New调用创建的时候才会被java类装载器装入)

     (2)在装载类时,先装载父类A,再装载子类B

     (3)装载父类A后,完成静态动作(包括静态代码和变量,它们的级别是相同的,按照代码中出现的顺序初始化)

     (4)装载子类B后,完成静态动作

     (5)类装载完成,开始进行实例化

     (6)在实例化子类B时,先要实例化父类A2,实例化父类A时,先成员实例化(非静态代码)

     (7)父类A的构造方法

     (8)子类B的成员实例化(非静态代码)

     (9)子类B的构造方法

(十二)如何减少GC,有哪些高频方法会经常创建对象

        1.对象不用时显示置null。

        2.少用System.gc()。

        3.尽量少用静态变量。

        4.尽量使用StringBuffer,而不用String累加字符串。

        5.分散对象创建或删除的时间。

        6.少用finalize函数。当垃圾回收器将要回收对象所占内存之前被调用。

        7.如果需要使用经常用到的图片,可以使用软引用类型,它可以尽可能将图片保存在内存中,供程序调用,而不引起OOM。

       8.能用基本类型就不用基本类型封装类。

       9.增大-Xmx的值。

(十三)AsyncTask的原理

       AsyncTask更简单的从子线程到主线程。封装了线程和Handler。背后的处理机制也是基于异步消息处理机制的。AsyncTask是一个抽象类,并制定三个泛型参数,这三个参数的用途如下:1.Params:执行AsyncTask需要传入的参数,可用于在后台任务中使用;2.Progress:后台任务执行时,如果需要界面上显示进度,此为进度单位;3.Result:任务执行结束后,需要对结果返回。

几个方法:

        1.onPreExecute在后台任务开始执行之前进行调用,用于界面的初始化操作,譬如显示一个进度条对话框等。

        2.doInBackground(处理具体耗时任务):会在子线程中运行,在这里处理所有耗时任务,在这里是不可以更新UI元素,如果需要反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。

        3.onProgressUpdate(进行UI操作):后台调用了publishProgress(Progress...)方法后,onProgressUpdate将会被很快调用,其方法中携带的参数是从后台任务中传递过来的,在这里对UI进行更新,利用参数中的数值可以对界面元素进行相应的更新。

        4.onPostExecute(任务收尾工作):当后台任务执行完毕后并使用return语句返回后,该方法很快被调用。返回数据会作为参数传递到此方法中。可利用返回数据来进行UI操作。譬如:提醒任务执行结果,以及关闭进度条对话框等。

源码分析:

       1.首先来看一下execute方法,执行executeOnExecutor方法,首先判断AsyncTask的状态,PENDING(就绪)、Running(运行)、FINISHED(完成);如果是后两者,抛出异常,确保每次都是第一次运行异步任务。

       2.将当前的运行状态切换至RUNNING状态,执行onPreExecute方法。初始化UI操作。

       3.给mWorker传入参数,mWorker是Callable子类,包含Call方法和一个变量mParams,设置子线程优先执行,然后就执行到了doInbackGround方法。

       4.最后再调用mWorker的postResult方法,通过Message机制和Handler的handleMessage方法,首先判断装Message 的waht子段携带的信息,若为MESSAGE_POST_RESULT,则调用finish方法,再调用onPostExecute方法,若为MESSAGE_POST_PROGRASS,则调用OnPrograssUpdate方法。

(十四)Listview的复用机制,Listview和Recyclerview的区别。

        convertView用于将之前加载好的布局进行缓存,以便之后可以进行复用;ViewHolder可以对控件的实例进行缓存。若convertview为null时,将控件实例存放在ViewHolder中。

      (1)RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);

      (2)ListView只能处理子项的点击事件,但却不能处理子项里面的某个按钮,Recyclerview既可以处理所有的View点击事件。

      (3) RecyclerView提供了API来实现item的动画效果。RecyclerView可以进行局部刷新。View复用较为简单。

(十五)一般怎么学习新技术?

   阅读源码啊。

(十六)用过哪些数据结构。

       栈、队列、二叉树、哈希表。MesageQueue:插入和读取(本身伴随删除)。插入和读取对应的方法分别为enqueueMessage和next。前者作用是往消息队列中插入一条消息;后者的作用是从消息队列中取出一条消息并将其从消息队列中移除。内部实现不是队列,而是使用单链表的数据结构维护消息列表。

(十七)数组和链表的区别,适用场景,为什么数组查找快,链表插入快。hashmap查找的时间复杂度,最好和最坏情况下是多少。

       数组和链表是两种基本的数据结构,他们在内存存储上的表现不一样,数组特点:1.在内存中,数组是一块连续的区域; 2.数组需要预留空间;3.在数组起始位置处,插入数据和删除数据效率低;4.随机访问效率很高,时间复杂度可以达到O(1);5.数组开辟的空间,在不够使用的时候需要扩容,扩容的话,就会涉及到需要把旧数组中的所有元素向新数组中搬移 6.数组的空间是从栈分配的。链表特点:1.在内存中,元素的空间可以在任意地方,空间是分散的,不需要连续 ;2.链表中的元素都会两个属性,一个是元素的值,另一个是指针,此指针标记了下一个元素的地址;3.查找数据时效率低,时间复杂度为O(N);4.空间不需要提前指定大小,是动态申请的;5.任意位置插入元素和删除元素效率较高,时间复杂度为O(1) ;6.链表的空间是从堆中分配的。

      1 在访问方式上:数组可以随机访问其中的元素;链表则必须是顺序访问,不能随机访问 

      2 空间的使用上:链表可以随意扩大;数组则不能。

      CPU缓存会把一片连续的内存空间读入,因为数组结构是连续的内存地址,所以数组全部或者部分元素被连续存在CPU缓存里面,平均读取 每个元素的时间只要3个CPU时钟时间。而链表的节点分散在堆空间里面,这时候CPU缓存帮不上忙,只能是去读取内存,平均读取时间需要100个CPU时钟周期。 这样算下来,数组访问的速度比链表快33倍! 

       其中,这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。举个例子,比如我们要在哈希表中执行插入操作:那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。

       HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

(十八)会什么算法。

排序、贪心等。

(十九)快速排序思想讲一下, 复杂度是多少,最优和最坏情况复杂度是多少,怎么算出来的。

步骤如下:

        1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;

        2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];

        3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换;

        4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换;

        5)重复第3、4步,直到i=j;3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。时间复杂度为:O(n) =O(nlogn)

       最好最坏:

       最好情况,每次拿到的元素都是中枢元素,每次都能均匀的划分序列,O(nlog2n)。最坏情况是枢纽元为最大或者最小数字,那么所有数都划分到一个序列去了,时间复杂度为O(n^2)。  

                                        字节跳动暑期实习Android开发岗面经(附问题答案)_链表                           

(二十)找出数组里重复次数最多的数(我这里了讲一种要排序的,一种不用排序然后用hashmap,他问我时间复杂度各是多少)

          1.HashMap,2.快排之后记录出现次数。最好情况,每次拿到的元素都是中枢元素,每次都能均匀的划分序列,O(nlog2n)。最坏情况是枢纽元为最大或者最小数字,那么所有数都划分到一个序列去了 时间复杂度为O(n^2)。字节跳动暑期实习Android开发岗面经(附问题答案)_头条面经_02

(二十一)HTTP的报文格式是什么。

       对报文进行描述的起始行、包含属性的头部块,包含数据的主体部分。

       请求报文:它会向Web服务器请求一个动作;响应报文:它会将请求的结果返回给客户端。

       请求报文起始行: <method> <request-URL> <version>

       相应报文起始行:  <version> <status> <reason-phrase>

       头部块:添加了一些附加信息,共同决定了客户端和服务器能做什么事情。

       实体的主体部分:该部分其实就是HTTP要传输的内容,是可选的。HTTP报文可以承载很多类型的数字数据,比如,图片、视频、HTML文档电子邮件、软件应用程序等等。

(二十二)get和post的区别。

    (1)get是从服务器上获取数据,post是向服务器传送数据。

    (2)对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。

   (3)get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。

   (4)get安全性非常低,post安全性较高。但是执行效率却比Post方法好。

   (5)get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。

(二十三)你在访问一个网站的时候,发生了什么,涉及到什么协议,讲传输层里的。

   (1)浏览器查找域名的IP地址(DNS地址解析:通过域名获取对应IP);

   (2)建立TCP链接(TCP连接、IP(建立TCP协议时,发送数据在网络层使用IP协议)、OSPF(IP数据报在路由器之间选择,路由选择使用这个)、ARP(路由器与服务器通信时,需要将IP地址转换为MAC地址));

   (3)浏览器会向web服务器发送一个Http请求(TCP建立完成后,使用的是HTTP协议访问网页);

  (4)服务器处理请求并返回Http报文;(TCP/IP)

  (5)浏览器解析渲染画面显示HTML;

  (6)链接结束。