Android面试之Java基础

2019-03-05  本文已影响0人  不思进取的码农

前言

  18年经历了很多,19年希望通过自己的努力重拾自己。新的一年新的篇章。

1、HashMap实现原理,JDK8以后对HashMap做了怎样的优化。

(1)结构上来说:hashMap :“链表散列”的数据结构,即数组和链表的结合体;它的底层就是一个数组结构,数组中的每一项又是一个链表,每当新建一个HashMap时,就会初始化一个数组(数组长度16,如果当大于0.75的轮廓值数组扩大一倍,扩容是非常耗费内存的)。

(2)实现方式:HashMap是基于哈希表的Map接口的非同步实现(线程不安全)

(3)在JDK1.6中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间

详细了解HashMap原理:HashMap的实现原理

详细了解红黑树:什么是红黑树?

2.数组和链表的区别

(1).内存上:

  1.数组是一块连续的内存区域,链表在内存中可以存在任何地方,不要求连续

  2数组需要预留内存空间,在使用前要先申请占内存的大小,可能会浪费内存空间,链表不需要预留空间,每一个数据都保存了下一个数据的内存地址

(2).效率上

数组的优点

  1.随机访问性强

  2.查找速度快

数组的缺点

  1.插入和删除效率低

  2.可能浪费内存

  3.内存空间要求高,必须有足够的连续内存空间

  4.数组大小固定,不能动态拓展

链表的优点

  1.插入删除速度快

  2.内存利用率高,不会浪费内存

  3.大小没有固定,拓展很灵活

链表的缺点

  1.不能随机查找,必须从第一个开始遍历,查找效率低

3.Hashtable、HashMap、TreeMap有什么不同

Hashtable、HashMap、TreeMap都是常见的一些Map实现,是以键值对的形式存储和对操作数据的容器类型。

• Hashtable,同步,不支持null键和值,很少被推荐

• HashMap,支持null键和值,不同步,用键值对存取的首选

• TreeMap基于红黑树的一种访问的Map,存取的时间复杂度都是O(log(n)),且有序

4.HashMap在高并发下如果没有处理线程安全会有怎样的隐患,具体表现是什么。

可能造成死循环,具体表现链表的循环指向;

5.JAVA中四种修饰符的限制范围。

6.接口和抽象类的区别 

一个类可以实现多个接口,但只能继承一个抽象类;

抽象类可以包含具体的方法,接口所有的方法都是抽象的(JDK8开始新增功能接口中有default方法);

抽象类可以声明和使用字段,接口则不能,但可以创建静态的final常量;抽象类的方法可以是protected、public、private或者默认的package,接口的方法都是public;抽象类可以定义构造函数,接口不能;接口被声明为public,省略后,包外的类不能访问接口

7.JDK是什么,以及和JRE的区别

JDK是java的标准开发包,包含:Java编译器、Java运行时环境(JRE)

JRE(Java运行时环境)包含:JJava类库,Java类加载器和Java虚拟机(JVM)

区别:JRE是Java Runtime Enviroment是指Java的运行环境,是面向Java程序的使用者,而不是开发者

8.JVM

(1).JVM内存结构

主要分为三大块 堆内存、方法区、栈;

栈又分为JVM栈(私有栈)、本地方法栈

堆(heap space),堆内存是运行时数据区,用以保存类的实例(对象)JVM中最大的一块,是一个

方法区(Method area),存储类信息、常量、静态变量等数据,是线程共享的区域

程序计数器(Program counter Register),是一块较小的内存空间,是当前线程所执行的字节码的行号指示器

JVM栈(JVM stacks),也是线程私有的,生命周期与线程相同,每个方法被执行时都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息

本地方法栈(Native Mthod Stacks),为虚拟机使用的native方法服务

(2).JVM内存结构

关于垃圾回收和常见的GC算法,请参考:GC专家系列-理解java垃圾回收

9.Java中的数据类型

Java是一种强类型语言,强类型语言包含的含义:

(1).所有的变量必须先声明、后使用

(2)指定类型的变量只能接受指定类型与之匹配,也就是说每个变量和每个表达式在编译的时候就确定的类型。类型限制了一个变量能被赋予的值,也限制了一个表达式可以产生的值。

特点:强类型语言在编译时进行更严格的语法检查,从而减少编程错误

Java支持两种数据类型:

  (1)基本数据类型

    (2)  引用类型

  引用类型包含:类、数组、接口以及特殊的类型null类型(null类型是一种直接量)

  所谓引用类型就是对一个对象的引用,对象包括实例和数组。

10.自动装箱和自动拆箱

  基本数据类型的自动装箱、是自J2SE 5.0开始提供的功能。
  ①Integer  i= 100;          自动装箱:简单数据类型→引用数据类型

  ②int g = new Integer;  自动拆箱:引用类型→简单数据类型

11.成员变量和局部变量

12.Switch能否用string做参数?

Java7之前,switch 只能支持 byte、short、char、int或者其对应的封装类以及 Enum 类型。在 Java7中,String支持被加上了。

13.equals与==的区别。

equals是逻辑相等,==完全是对象是否是同一个(地址相等)。

13.Object有哪些公用方法

(1)clone

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常

(2)equals

在Object中与==是一样的,子类一般需要重写该方法

(3)hashCode

该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到

(4)getClass

final方法,获得运行时类型

(5)wait

使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生:1.其他线程调用了该对象的notify方法 2.其他线程调用了该对象的notifyAll方法 3.其他线程调用了interrupt中断该线程 4.时间间隔到了 此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常

(6)noyify

唤醒在该对象上等待的某个线程

(7)noyifyAll

唤醒在该对象上等待的所有线程

(7)toString

转换成字符串,一般子类都有重写,否则打印句柄

14 Java的四种引用,强弱软虚,用到的场景。

(1)强引用(StrongReference)强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。

如下:

1Object o=new Object();  //强引用

当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:

1o=null;    //帮助垃圾收集器回收此对象

显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。

举例:

public void test(){

    Object o=new Object();

//省略其他操作

}

在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。

但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。

强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:

private transient Object[] elementData;

public void clear() {

        modCount++;

        // Let gc do its work

        for (int i = 0; i < size; i++)

            elementData[i] = null;

        size = 0;

}

在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。

(2)软引用(SoftReference)

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存

String str=new String("abc");                                    //强引用

SoftReference softRef=new SoftReference(str);    //软引用

当内存不足时,等价于:

If(JVM.内存不足()) {

str = null;  //转换为软引用

System.gc(); //垃圾回收器进行回收

}

软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建

(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

这时候就可以使用软引用

Browser prev = new Browser();              //获取页面进行浏览

SoftReference sr = new SoftReference(prev); //浏览完毕后置为软引用   

if(sr.get()!=null){

rev = (Browser) sr.get();          //还没有被回收器回收,直接获取

}else{

prev = new Browser();              //由于内存吃紧,所以对软引用的对象回收了

sr = new SoftReference(prev);      //重新构建

}

这样就很好的解决了实际的问题。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

(3)、弱引用

如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。这个引用不会在对象的垃圾回收判断中产生任何附加的影响。比如说Thread中保存的ThreadLocal的全局映射,因为我们的Thread不想在ThreadLocal生命周期结束后还对其造成影响,所以应该使用弱引用,这个和缓存没有关系,只是为了防止内存泄漏所做的特殊操作。

(4)、幽灵引用(虚引用)

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存后,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存回收后采取必要的行动。由于Object.finalize()方法的不安全性、低效性,常常使用虚引用完成对象回收后的资源释放工作。当你创建一个虚引用时要传入一个引用队列,如果引用队列中出现了你的虚引用,说明它已经被回收,那么你可以在其中做一些相关操作,主要是实现细粒度的内存控制。比如监视缓存,当缓存被回收后才申请新的缓存区。

15.hashcode的作用。

hashCode用于返回对象的散列值,用于在散列函数中确定放置的桶的位置。

1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;

2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;

3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”

16. String、StringBuffer与StringBuilder的区别。

String字符串常量

StringBuffer字符串变量(线程安全)

StringBuilder字符串变量(非线程安全)

简要的说,String类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

而如果是使用StringBuffer类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

17. Collection包结构,与Collections的区别。


(1)Collection是单列集合

List  元素是有序的、可重复

有序的collection,可以对列表中每个元素的插入位置进行精确地控制。

可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。

可存放重复元素,元素存取是有序的。

List接口中常用类

l Vector:线程安全,但速度慢,已被ArrayList替代。

底层数据结构是数组结构

l ArrayList:线程不安全,查询速度快。

            底层数据结构是数组结构

l LinkedList:线程不安全。增删速度快。

            底层数据结构是列表结构

Set(集) 元素无序的、不可重复。

取出元素的方法只有迭代器。不可以存放重复元素,元素存取是无序的。

Set接口中常用的类

l HashSet:线程不安全,存取速度快。

        它是如何保证元素唯一性的呢?依赖的是元素的hashCode方法和euqals方法。

l TreeSet:线程不安全,可以对Set集合中的元素进行排序。

它的排序是如何进行的呢?通过compareTo或者compare方法中的来保证元素的唯一性。元素是以二叉树的形式存放的。

(2)Map是一个双列集合

|--Hashtable:线程安全,速度快。底层是哈希表数据结构。是同步的。

不允许null作为键,null作为值。

      |--Properties:用于配置文件的定义和操作,使用频率非常高,同时键和值都是字符串。

是集合中可以和IO技术相结合的对象。(到了IO在学习它的特有和io相关的功能。)

|--HashMap:线程不安全,速度慢。底层也是哈希表数据结构。是不同步的。

允许null作为键,null作为值。替代了Hashtable.

  |--LinkedHashMap: 可以保证HashMap集合有序。存入的顺序和取出的顺序一致。

|--TreeMap:可以用来对Map集合中的进行排序.

(3)Collection 和 Collections的区别

Collection是集合类的上级接口,子接口主要有Set 和List、Map。

Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

18. try catch finally,try里有return,finally还执行么?

执行,并且finally的执行早于try里面的return

结论:

1、不管有木有出现异常,finally块中代码都会执行;

2、当try和catch中有return时,finally仍然会执行;

3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;

4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

19.锁的等级:方法锁、对象锁、类锁。

(1)基础

Java中的每一个对象都可以作为锁。

· 对于同步方法,锁是当前实例对象

· 对于静态同步方法,锁是当前对象的Class对象

· 对于同步方法块,锁是Synchonized括号里配置的对象

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。那么锁存在哪里呢?锁里面会存储什么信息呢?

(2)同步的原理

JVM规范规定JVM基于进入和退出 Monitor 对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明,但是方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。

20.ThreadLocal的设计理念与作用

(1)ThreadLocal的介绍

ThreadLocal是一个线程的内部存储类,可以在每个线程的内部存储数据,当某个数据的作用域应该对应线程的时候就应该使用它;而是当某个很复杂的逻辑下的对象传递,需要在线程这个作用域内贯穿其中,用ThreadLocal可以避免这个创建多个静态类。当你创建一个ThreadLocal对象后,注意,是一个对象,你在不同线程中去访问它的值是可以不一样的,并且是和线程相关联的。它的实现原理其实比较简单,每个线程中都会维护一个ThreadLocalMap,当在某个线程中访问时,会取出这个线程自己的Map并且用当前ThreadLocal对象做索引来取出相对应的Value值,从而达到不同线程不同值的效果。

(2)实现原理

 1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。

/* ThreadLocal values pertaining to this thread. This map is maintained

* by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

 2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

public T get() {

  Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t);

    if (map != null) {

        ThreadLocalMap.Entry e = map.getEntry(this);

        if (e != null)

            return (T)e.value;

    }

    return setInitialValue();

}

 3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。

4、ThreadLocalMap里面存储的Entry对象本质上是一个WeakReference<ThreadLocal>。也就是说,ThreadLocalMap里面存储的对象本质是一个对ThreadLocal对象的弱引用,该ThreadLocal随时可能会被回收!即导致ThreadLocalMap里面对应的Value的Key是null。我们需要把这样的Entry给清除掉,不要让它们占坑,避免内存泄漏。

那为什么需要弱引用呢?因为线程的声明周期是长于ThreadLocal对象的,当此对象不再需要的时候如果线程中还持有它的引用势必也会产生内存泄漏的问题,所以自然应该是用弱引用来进行key的保存。

21.wait()和sleep()的区别。

① 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。

sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。

② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。

Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。

③ 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

  synchronized(x){      x.notify() //或者wait()    }

22.foreach与正常for循环效率对比。

需要循环数组结构的数据时,建议使用普通for循环,因为for循环采用下标访问,对于数组结构的数据来说,采用下标访问比较好。

需要循环链表结构的数据时,一定不要使用普通for循环,这种做法很糟糕,数据量大的时候有可能会导致系统崩溃.

原因:foreach使用的是迭代器

23.反射的作用与原理。

(1)概念

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

(2)功能

反射机制主要提供了以下功能:

· 在运行时判断任意一个对象所属的类;

· 在运行时构造任意一个类的对象;

· 在运行时判断任意一个类所具有的成员变量和方法;

· 在运行时调用任意一个对象的方法;

· 生成动态代理。

24.多态的意义和实现原理是怎么样的

详见:

(1)原理是什么样

  (2) Java 多态的例子详解及原理解析

  (3)什么是多态?实现多态的机制是什么?

上一篇下一篇

猜你喜欢

热点阅读