Java面试-基础问题整理

1. String类为什么是final的。

答:申明为final的类是不能被继承的,这防止了String类被子类修改。由于String类是被设计为Immutable的,也就是不可变的,用final修饰,能防止String类被子类修改成可变的。

*题外话:*

关于String不可变的原因,收集了以下几个点:

1.由于String常量池的设计,当需要创建String时,会先从常量池中找是否已经存在该值,如果有则返回其引用,而不是创建一个相同值的String对象。如果String可变,那改变一个值会将所有引用这个值的变量都改了。

2.String大量作为参数传递,如果String值可变,那在方法内部就可以改变String的值,这会非常麻烦,以及可能造成安全上的问题。

3.Because immutable objects can not be changed, they can be shared among multiple threads freely. This eliminates the requirements of doing synchronization.

关于如何创建不可变的类的规则:

1.immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。

2.Immutable类的所有的属性都应该是final的。

3.对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)。

4.类应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。

5.如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)

(final关键字可以参考Java编程思想140,java核心技术卷一160)

2. Java集合类:list、set、queue、map实现类


HashSet, linkedHashSet,HashMap,linkedHashMap:都是基于散列表实现的,即链表数组。

LinkedHashMap继承了HashMap,并在node上多加了两个指针,来维护每个node之间的顺序,原有的hashmap的结构是没变的。

HashSet只是封装下HashMap,实际就是一个hashMap,只是这个map只能对键进行操作,set里的元素被当做是Map的key来存的,map的value存的是一个static final new object(),循环这个set其实就是取得HashMap的keySet来循环。

LinkedHashSet继承了HashSet,调用的是HashSet里构造linkedHashMap的构造方法,实际就是个linkedHashMap,然后只能对map的key进行操作。

TreeSet,TreeMap: 都是基于红黑树实现的,特点是所得到的结果是经过排序的,次序由元素实现的Comparable或者Comparator决定。将元素加到TreeSet比HashSet慢,不过TreeSet是排序的。

PriorityQueue:是接口Queue的实现。 使用堆数据结构,堆是一个可以自我调整的二叉树,对树执行添加和删除操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。优先级队列可以按任意的顺序插入元素,却总是按照排序的顺序进行检索,也就是无论何时调用remove方法,总会获得当前优先级队列中最小的元素(即优先级最高的元素,习惯上将1设为最高优先级)。默认是按元素的自然顺序排序,如存储Integer,char,String类型的元素,也可以通过自定义Comparator来排序。但是与TreeSet不同,如果仅迭代这些元素,是不会对元素进行排序的,PriorityQueue可以确保调用peek,poll,remove方法时,获取的元素是队列中优先级最高的元素。

DelayQueue:无界阻塞队列,用于放置实现了Delayed接口的对象,其中对象只能在到期时才能从队列中取出。DelayQueue其实就是在每次往优先级队列中添加元素,然后以元素的delay/过期值作为排序的因素,以此来达到先过期的元素会拍在队首,每次从队列里取出来都是最先要过期的元素。

(集合相关参考java编程思想第17章,java核心技术卷一第十三章)

3. ArrayList和LinkedList各自实现和区别

ArrayList是采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,所以最大的缺点就是插入删除时很慢,因为每次操作都需要移动其他元素的位置。优点是随机访问很快,可以通过查看该集合是否实现了RandomAccess接口来检测一个特定的集合是否支持高效的随机访问。

LinkedList是采用链表来存储数据,链表将对象存储在独立的节点中,每个节点还存放着序列中下一个节点的引用。链表增删快,但是查询慢,每次查询需要迭代整个列表。

4. HashMap的源码,实现原理,底层结构。

Hashmap底层是以链表数组实现的,数组并不保存键本身,也不直接保存值,而是保存key和value节点的链表。原理是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码(哈希值)(哈希值还需对map的size取余,才是最终下标,保证计算出来的下标都在size内)。也就是key和Value存储在key的散列码对应的那个数组下标的链表里面。由于不同的key可以产生相同的散列码,这个冲突由链表解决,如果key的equals()也相同,那直接替换oldVaue;如果不相同,那将value add到链表的最后。取值也是一样,先查询key的散列码对应的链表元素是否存在,存在就循环链表里每一个元素,然后取出与key相同(equals())的那个元素。可以尝试用LinkedList实现一个简单的hashmap。源码里是用的Node[] table。

(hashmap相关参考java编程思想483~495,java核心技术卷一576~598)

5. 如何自己实现一个Map,描述

可以直接用LinkedList数组来存储,list里存包含键值对的Entry对象。put方法存放数据,get方法取数据。还需要一个entrySet方法可以遍历整个map。

Put方法的基本思路是:先根据key的hash值与size取余来确定该key-value在数组中存放的位置。如果数组中这个位置没有元素,那直接new一个entry对象,存到LinkedList里,再把linkedList设置到数组这个位置上。如果数组中已有元素,将这个linkedList取出来,循环list并将新的key与list里所有key比较,如果找到相同的key,直接覆盖value。如果没有相同的key,那也new一个entry对象加到这个linkedlist末尾。

Get方法跟put方法用相同的算法查找key,即根据key的hash值与size取余来确定该key-value在数组中存放的位置,没有就返回null。有就循环比较是否有相同的key,有就取出对应的value。

EntrySet方法主要是要返回一个有迭代器的集合,可以遍历,所以可以直接new一个hashSet,然后遍历整个链表数组,将每个entry都加到HashSet里。

6. Hash冲突怎么办?哪些解决散列冲突的方法,rehash?

以下内容网上查的,都能查到就不贴过来了。个人觉得得看点书理解下,推荐看下算法导论-11章 散列表。现在hashMap里是用的链表处理冲突。

一般解决hash冲突有四种方式:开放定址法,再哈希,链接法,建立公共溢出区。

7. HashMap冲突很厉害,最差性能,你会怎么解决?

HashMap使用链接法解决冲突,最差性能就是所有元素全部存放在同一个地址,形成一个单链表,这样查找、删除元素性能就是O(n)。可以考虑在节点过多时将单链表转成红黑树,双向链表删除元素是O(1),红黑树查找性能是O(logN),jdk1.8的hashmap已经实现了。 

8. Hashtable,HashMap,ConcurrentHashMap 底层实现原理与线程安全问题

HashTable是用同步关键字保证线程安全,在线程竞争激烈的情况下,效率非常低。如线程1使用put方法添加元素,线程2不但不能使用put添加元素,也不能使用get获取元素。

HashMap是非线程安全的。在多线程环境下使用put操作会引起死循环,导致CPU利用率接近100%,因为多线程会导致HashMap的Entry链表形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。

JDK1.7中ConcurrentHashMap实现:使用分段锁技术控制并发访问,首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。因此当多个线程访问不同数据段的数据时,线程间不存在锁竞争,从而有效提高并发访问效率。

JDK1.7里ConcurrentHashMap数据结构:之前了解过HashMap是以链表数组存储的,数组下标表示通过Key的hash值计算得到位置,key和value就存在该数组位置的Entry链表里,一个Entry节点包含key,value,和next节点。

ConcurrentHashMap是由Segment数组和Entry数组构成(Entry数组可以理解成一个HashMap)。Segment是一种可重入锁(ReetrantLock),在ConcurrentHashMap里扮演锁的角色。Entry用于存键值对数据。一个ConcurrentHashMap包含一个Segment数组,segment的结构和HashMap类似,是链表数组结构。一个segment包含一个Entry数组,每个Entry是一个链表结构的元素。每个Segment守护着一个Entry数组里的元素,当对Entry数组的数据进行修改时,必须首先获得与它对应的Segment锁。

下图来自网络:


9. 反射中,Class.forName和classloader的区别

Class.forName默认加载并初始化class,即会执行静态代码块和初始化静态变量。

ClassLoader默认只加载类进JVM,并不初始化。

如JDBC链接,就需要使用Class.forName,因为需要执行静态代码块注册驱动到DriverManager上,之后才能通过DriverManager获取连接。

 Class.forName("com.mysql.jdbc.Driver");//通过这种方式将驱动注册到驱动管理器上。

题外话:

Proxy.newProxyInstance()是在类的字节码文件不存在,也就是没有xx.class文件的时候,先根据该类的接口创建一个代理类的实例即xx.class文件,然后通过classLoader加载进JVM,最后根据JVM里的字节码文件创建实例并返回。

Class.forName()是在类的实例存在即XX.class文件存在,调用该方法让JVM将该文件加载进JVM,成为JVM里的一份字节码文件,也就是Class对象。不同于ClassLoader的是,Class.forName默认会初始化类,也就是load进JVM后,还会执行类里的静态方法和静态成员变量的赋值。

ClassLoader,也是在类的实例存在即XX.class文件存在,调用该方法让JVM将该文件加载进JVM,成为JVM里的一份字节码文件,也就是class对象。

(类加载机制,参考深入理解Java虚拟机第7章)

10. Java7、Java8的新特性

Java7:(只关注过这两个。。)

1.switch中可以使用字串了

2.运用List tempList = new ArrayList<>(); 即泛型实例化类型自动推断

Java8:

1.Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。

2.方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

3.默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。

4.Stream API −新添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中。

5.Date Time API − 加强对日期与时间的处理。

6.Optional 类 − Optional 类已经成为Java 8 类库的一部分,用来解决空指针异常。

Lambda,方法引用,Stream具体语法可参考:

https://blog.csdn.net/jinzhencs/article/details/50748202

11. JDK1.8 ConcurrentHashMap原理及实现

Jdk1.8里已经不用分段锁了,没有了segment数组。整体实现其实和HashMap是差不多的,只是加了线程安全相关的代码,结合了Synchronized、volitale关键字,以及CAS的方式,去实现线程安全。

此外HashMap在1.8中数据结构有改动,如下图2-4描述:

12. string、stringbuilder、stringbuffer区别

String对象是不可变的,String类里每一个看起来能修改String值的方法,实际上都创建了一个新的String对象,用来包含修改后的值,原始的String对象并没有改变。

由于String对象不可变,用+拼接字符串,会产生一大堆需要垃圾回收的中间对象,可以简单理解为每调用一次+就生成一个新的String对象。

StringBuilder的append方法拼接字符串,只会生成一个StringBuilder对象,效率更高。

StringBuffer是线程安全的,方法上都加了同步关键字,开销更大。

13 .异常的结构,运行时异常和非运行时异常,各举个例子

Throwable类是java中所有异常的超类,有Exception和Error两个子类。

Error是程序无法处理的错误,如OutOfMemoryError,stackOverFlowError,NoClassDefFoundError。

Exception是程序本身可以处理的异常。RuntimeException是Exception的一个子类,其子类如ArrayIndexOutOfBoundsException,NullPointerException,ClassCastException等。非运行时异常如IOException,FileNotFoundException。

14. String 类的常用方法

Length(),replace(),split(),indexOf(),CharAt(),trim(),toUpperCase(),subString(),equals().....

15. Java 的引用类型有哪几种

强引用,软引用,弱引用,虚引用。

强引用:直接new一个对像并赋值给一个变量,这个变量就是对这个对象的一个强引用。只要这个强引用没被释放,那么这个对象就不会被回收。

软引用:用SoftReference包裹的对象,如果一个对象只存在一个软引用,在内存溢出之前,将会被回收。软引用主要用于实现内存敏感的高速缓存。当内存不够用时,sf对象会被回收,sf.get()会返回null。

弱引用:用WeakReference包裹的对象,如果一个对象只存在一个弱引用,在垃圾回收时这个对象会被回收。WeakHashMap就是用的WeakReference来保存对象。WeakReference的Entry继承了WeakReference类。

虚引用: Phantomreference,垃圾回收时回收,永远无法通过引用取到对象值。

pf.get();//永远返回null

pf.isEnQueued();//返回对象是否已被回收

虚引用主要用于检测对象是否已经从内存中删除,可以通过这个通知机制来做额外的清场工作。 因此有些情况可以用PhantomReference 代替finalize(),做资源释放更明智。

(引用相关参考java编程思想518,深入理解JVM虚拟机65页,博客资料

 16. 抽象类和接口的区别

一般说来抽象类都包含一个或多个抽象方法,此外还可以包含具体数据和具体方法。不过也可以将没有包含抽象方法的类申明为抽象类,抽象类不能被实例化。

Jdk1.8之前接口只能有空方法,jdk1.8里接口可以有多个default的具体方法。

一个类可以实现多个接口,但是只能继承一个超类。

17. java的基础类型和字节大小。

Int: 4字节(32位,最大值2^31-1)

byte:1 字节(8位)

short:2 字节(16位)

Long:8 字节(64位)

double:8 字节(64位)

float:4字节(32位)

boolean:1位

char:2 字节(16位)

18. hashCode() 与 equals() 生成算法、方法怎么重写

Jdk1.7中,hashCode的定义可以调用Objects.hash(),可传入多个需要散列的属性作为参数,hashcode方法可以简单的写成:Objects.hash(name, id);

Equals就是必须和hashCode的定义一致,如果x.equals(y),那么x和y的hashcode必须相同。也就是定义equals和hashcode的域要相同。

(hashcode和equals参考Java核心技术卷一169-175)


19. Java类加载器包括几种?他们之间的父子关系是怎么样的?双亲委派机制是什么意思?有什么好处?

    启动类加载器:加载java_home/lib下面jvm识别的jar

    扩展类加载器:加载java_home/lib/ext下面的jar

    应用程序类加载器:加载用户类路径也就是classpath上的jar

    自定义加载器:

    双亲委派模型:先让父类加载,如果没有父类,就到顶层的启动类加载器,如果都没有加载出来,那再自己去加载。

(参考深入理解JVM虚拟机第7章)

20.讲讲NIO

BIO就是同步阻塞式IO,当一个客户端向server端发起请求,必须等到Server端把这个请求处理完成,才能接收到server端的返回消息。同时,如果有其他请求发送到server端,server端是不会对新请求做出响应的,必须等到之前的请求处理完。这就是同步阻塞式。

NIO是同步非阻塞式IO,NIO面向Channel和Buffer,而不是传统的IO流。客户端发送的连接请求被作为一个channel注册到Selector上,Selector不断轮询各个channel的状态,只有在channel状态是可连接或者可读或者可写的时候,才会进行IO操作。因此NIO不用开启一个线程等待读或者写数据,而BIO则会一直等。

21. String编码UTF-8 和GBK的区别?

GBK包含全部中文字符;UTF-8则包含全世界所有国家需要用到的字符。GBK编码专门用来解决中文编码的,是双字节的。不论中英文都是双字节的。UTF-8 编码是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码。

所以如果是UTF-8,英文是1字节,中文是三字节。

GBK,英文中文都是2字节。

22. 什么时候使用字节流、什么时候使用字符流?

使用字符流的应用场景:如果是读写数据的时候需要转换成字符,比如处理文字则使用字符流。

使用字节流的应用场景:如果读写的数据都不需要转换成字符的时候,比如处理图片则使用字节流。

23. 递归读取文件夹下的文件,代码怎么实现



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容