Java基础之面试常见问题

来源:互联网 发布:阿里云香港空间怎么样 编辑:程序博客网 时间:2024/06/09 08:34

1.equals与==的区别?
==是判断两个变量或实例是不是指向同一内存空间,equals是判断两个变量或实例所指的内存空间的值是不是相同。

2.Object有哪些公用的方法?
equals( ),测试两个对象是否相等。
clone( ),进行对象拷贝
getClass( )返回和当前对象相关的Class对象

3.HashCode的作用,与equals有什么区别?
同样用于鉴定2个对象是都相等的,Java几个中有list和set两类,其中set不予许元素重复实现,那么这个不允许重复实现的方法,如果用equal去比较的话,如果存在1000个元素,你new一个新元素出来,需要去调用1000次equal去逐个和他们比较是否是同一个对象,这样会大大降低效率。hashcode实际上是返回对象的存储地址,如果这个位置上没有元素,就把元素直接存储在上面,如果这个位置上已经存在元素,这个时候才去调用equal方法与新元素进行比较,相同的话就不存在了,散列到其他地址上。

4.Override和Overload的含义区别?
Overload是重新加载,它可以表现类的多态性,可以是函数里面可以有相同的函数名,但是参数名、返回值、类型不能相同;或者说可以改变参数、类型、返回值但是函数名字必须不变。
Override是重写的意思,在子类继承分类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这个函数时自动调用子类的方法,而弗雷相当于被覆盖(重写)了。

5.抽象类和接口的区别?
一个类只能继承单个类,但是可以实现多个接口,接口强调特定功能的实现,而抽象类强调所属关系,抽象类中的所有方法并不一定要是抽象的,你可以选择在抽象类中实现一些基本的方法,而接口要求所有的方法都必须是抽象的。

6.解析XML的几种方式的原理与特点:DOM、SAX、PULL
DOM:消耗内存,先把xml文档都读取到内存中,然后再用DOM API来访问树形结构,并获取数据。这个写起来很简单,但是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机。
SAX:解析效率高,占用内存少,基于事件驱动的,更简单地说就是对文档进行顺序扫描,当扫描到文档(doucment)开始与结束、元素开始与结束、文档结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
PULL:与SAX类似,也是基于事件驱动,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档、结束文档、开始标签、结束标签),当处于某个元素时,可以调用XmlPullParser的getAttribute()方法来获取属性的值,也可调用它的nextText()获取本节点的值。

7.Java垃圾回收与内存分配策略

7.1垃圾回收是什么?
就是释放那些不再持有引用的对象的内存
7.2怎么判断一个对象是否需要收集?
引用计数:给对象添加引用计数器,每当有一个地方引用它,计数器就加1,引用失败减1,当引用次数变为0时就将其释放。
可达性分析:以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。
*根集:一般包括Java栈中引用的对象,方法区常量池中引用的对象,本地方法中引用的对象。
7.3垃圾回收机制
1.标记-清除:
算法和名字一样,分为两个阶段:标记和清除,标记所有需要回收的对象,然后统一清除回收。
2.标记-压缩
此算法结合了“标记-清除”和“复制”两个算法的优点,也是分两阶段,第一阶段,从根节点开始标记所有被引用对象。第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。
3.复制
内存空间划分为两个相等的区域A,B,将A中正在使用对象复制到B中,每次只处理正在使用中的对象。
4.分代收集算法
把内存空间分为两个或者多个域,如年轻代和老年代,年轻代的特点是对象会很快被回收,因此在年轻代使用效率比较高的算法。当一个对象经过几次回收后依然存活,对象就会被放入成为老年的内存空间,老年代则采用标记-压缩算法。

8 . ArrayList、 LinkedList、 Vector 的区别
1.ArrayList 和 Vector 底层是采用数组方式存储数据, Vector 由于使用了 synchronized方法(线程安全)所以性能上比 ArrayList 要差。
2.LinkedList 使用双向链表实现存储,随机存取比较慢
3.HashMap 的底层源码实现:当我们往 HashMap 中 put 元素的时候,先根据 key 的hashCode 重新计算 hash 值,根据 hash 值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
4.Fail-Fast 机制 :在使用迭代器的过程中有其他线程修改了 map,那么将抛出ConcurrentModificationException,这就是所谓 fail-fast 机制。这一机制在源码中的实现是通过 modCount 域, modCount 顾名思义就是修改次数,对 HashMap 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 Map

9 . HashMap 和 HashTable 的区别
HashTable 比较老,是基于 Dictionary 类实现的, HashTable 则是基于 Map 接口实现的HashTable 是线程安全的, HashMap 则是线程不安全的 HashMap 可以让你将空值作为一个表的条目的 key 或 value.

为什么哈希表的容量一定要是2的整数次幂?
首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。

10 . 什么是线程池,线程池的作用是什么
答:线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。就好比原来去食堂打饭是每个人看谁抢的赢,谁先抢到谁先吃,有了线程吃之后,就是排好队形,今天我跟你关系好,你先来吃饭。比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。
单个线程的弊端:
a. 每次new Thread新建对象性能差
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或者OOM
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。

11. Java 线程池
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
(1). newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
(2). newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
(3) newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。ScheduledExecutorService比Timer更安全,功能更强大
(4)、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

12.静态块、静态变量、构造函数、构造方法的加载顺序?
静态代码块:是在当类被载入(内存)时,(最先被调用的),静态代码块被执行,且之被执行一次,静态块常用来执行类属性的初始化。执行类的载入之前就会调用。
非静态代码块:是在当类的对象被创建载入(内存)时,(最先被调用的),每创建一个对象,即每载入一个对象,非静态代码块都执行一次。执行类对象的载入之前就会调用。
静态变量:与静态代码块一样,也是在类加载时就会被自动执行,属于整个类的属性。
静态方法、非静态方法:都是进行方法调用时才执行,调用静态方法,前提是:类已经加载到内存中;调用非静态方法,前提是类的对象已经加载到内存中。

那么,类什么时候被加载/类加载时机:【即类被使用时,就会加载】
第一:生成该类对象的时候,会加载该类及该类的所有父类;
第二:访问该类的静态成员的时候;
第三:class.forName(“类名”);

代码块是自动的,方法是被动的。

作用:静态代码块可以用来初始化一些项目最常用的变量和对象;静态方法可以用作不创建对象也可以能需要执行的代码。

执行顺序可以简单记为:静态区–>非静态区–>构造方法–>使用对象…(唯一需重点注意的是静态区代码仅在该类被加载时执行一次,之后无论该类被实例化多少次,静态区代码都不会再执行!)

13.Lock和synchronized的区别?
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

Lock和synchronized有以下几点不同:
  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5)Lock可以提高多个线程进行读操作的效率。
  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

原创粉丝点击