Java面试题以及知识点整理(1)

版权声明:本文为博主原创文章,未经博主允许不得转载。转载请附上博主博文网址,并标注作者。违者必究 https://blog.csdn.net/HuHui_/article/details/78114063

1.HashMap和HashTable的区别,及其实现原理。

HashTable底层是用HashMap实现的,与HashMap的区别是,HashTable是按存入顺序排序的,而HashMap不是。HashMap的原理是有一个大的table数组组成,每个数组元素是一个Entry。为了处理冲突,通常会将Entry用链表实现。

2.ArrayList,LinkedList 和Vector的区别和实现原理。

ArrayList是基于数组的可变长数组,因为这个特性,所以它更适合实现get和set;LinkedList是基于双向链表的,所以比较适合实现插入和删除等操作;但以上两个都是非线程安全的,Vector的实现和ArrayList差不多,改进的地方是使用synchronized实现了线程安全。

3.TreeMap和TreeSet区别和实现原理。

其中 TreeMap 是 Map 接口的常用实现类,而 TreeSet 是 Set 接口的常用实现类。TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现的一样),因此二者的实现方式完全一样。而 TreeMap 的实现就是红黑树算法。

相同点:

TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是拍好序的。


TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步

运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。

不同点:

最主要的区别就是TreeSet和TreeMap非别实现Set和Map接口


TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)

TreeSet中不能有重复对象,而TreeMap中可以存在

TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序。

ConcurrentHashMap实现原理(锁分离技术)

4.String和StringBuffer,StringBuilder区别和联系,String为啥不可变,在内存中的具体形态。

String:字符串常量,字符串长度不可变。(引用堆内存的常量池)

StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。

StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。

StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。

5..java中多线程机制,实现多线程的两种方式(继承Thread类和实现Runnable接口)的区别和联系。

一个类只能继承一个父类,存在局限;一个类可以实现多个接口。在实现Runnable接口的时候调用Thread的Thread(Runnable run)或者Thread(Runnablerun,String name)构造方法创建进程时,使用同一个Runnable实例,建立的多线程的实例变量也是共享的;但是通过继承Thread类是不能用一个实例建立多个线程,故而实现Runnable接口适合于资源共享;当然,继承Thread类也能够共享变量,能共享Thread类的static变量;

6..java线程阻塞调用wait函数和sleep区别和联系,还有函数yield,notify等的作用。

wait是Object的方法,sleep是Thread类的方法;

wait让出CPU资源的同时会放弃锁,sleep让出CPU资源的同时不会释放锁;

wait需要notify或者notifyall来唤醒,sleep在沉睡指定时间后,会自动进入就绪状态;

wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。
yield()没有参数。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU占有权,但让出的时间是不可设定的。
yield()也不会释放锁标志。
7. java中的同步机制,synchronized关键字,锁(重入锁)机制,其他解决同步的方volatile关键字ThreadLocal类的实现原理要懂。

  1. TCP/UDP区别,TCP三次握手,SYN攻击

TCP是面向连接的可靠传输,需要三次握手,保证可靠通信;有重传机制;

UDP是无连接的不可靠传输,但是速度快,适用于视频和电话会议等实时应用场景;

TCP三次握手是:SYN=x(SYN_SEND)、ACK=x+1,SYN=y(SYN_RECV)、ACK=y+1(ESTABLISHED);

SYN攻击是:SYN攻击属于DOS攻击的一种,它利用TCP协议缺陷,通过发送大量的半连接请求,耗费CPU和内存资源。

检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。

一类是通过防火墙、路由器等过滤网关防护,另一类是通过加固TCP/IP协议栈防范。过滤网关防护主要包括超时设置,SYN网关和SYN代理三种。调整tcp/ip协议栈,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等。

但一般服务器所能承受的连接数量比半连接数量大得多

9.Spring的事务有哪些?

什么是数据库事务:访问并可能改变数据库中个数据项的一个程序执行单元。

实现方式共有两种:编码方式即采用注解的方式(类头的@Transactional为默认事务配置);声明式事务管理方式(bean)。

基于AOP技术实现的声明式事务管理,实质就是:在方法执行前后进行拦截,然后在目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。

声明式事务管理又有两种方式:基于XML配置文件的方式;另一个是在业务方法上进行@Transactional注解,将事务规则应用到业务逻辑中。

10.Java的锁有哪几种?

1、自旋锁:自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。

2、自旋锁的其他种类

3、阻塞锁:常用的有五个状态的锁都是阻塞所。

4、可重入锁:ReentrantLock

5、读写锁:写锁是排他锁,读锁是共享锁。

6、互斥锁

7、悲观锁:在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制。

8、乐观锁:乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得

非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

  1. 集合

Collection 接口的接口 对象的集合

├ List 子接口 按进入先后有序保存 可重复

│├ LinkedList 接口实现类 链表 插入删除 没有同步 线程不安全

│├ ArrayList 接口实现类 数组 随机访问 没有同步 线程不安全

│└ Vector 接口实现类 数组 同步 线程安全

│   └ Stack

├ Queue 子接口 队列集合

└ Set 子接口 仅接收一次,并做内部排序

├ HashSet

│   └ LinkedHashSet 插入的次序

└ TreeSet

Map 接口 键值对的集合

├ Hashtable 接口实现类 同步 线程安全 键值非空

├ HashMap 接口实现类 没有同步 线程不安全

│├ LinkedHashMap 插入次序

│└ WeakHashMap

├ TreeMap 基于红黑树,可以返回子树 排序的

└ IdentifyHashMap

Collections 是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。相当于对 Array 进行类似操作的类—— Arrays 。
16.Arraylist如何实现排序

使用Collections.sort()传入ArrayList,会采用默认的方式进行排序(字典序)

使用Collections.sort()传入ArrayList和自己实现Commparator接口的类的对象,实现自定义排序

使用List.sort()传入自己实现Commparator接口的类的对象,实现自定义排序
17.Spring的annotation(注解)如何实现

Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。

12 Java虚拟机的一些参数配置

Trace跟踪参数:

-XX:+PrintGCDetails 打印GC详细信息;

-XX:+PrintGCTimeStamps 打印GC时间戳;

-Xloggc:log/gc.log 指定GC log的位置;

-XX:+PrintHeapAtGC 每一次GC前和GC后,都打印堆信息;

-XX:+TraceClassLoading 监控类的加载;

-XX:+PrintClassHistogram 按下Ctrl+Break后,打印类的信息;

堆的分配参数:

-Xmx20m -Xms5m 指定最大堆和最小堆;

问题1: -Xmx(最大堆空间)和 –Xms(最小堆空间)应该保持一个什么关系,可以让系统的性能尽可能的好呢?

问题2:如果你要做一个Java的桌面产品,需要绑定JRE,但是JRE又很大,你如何做一下JRE的瘦身呢?

-Xmn 官方推荐新生代占堆的3/8,幸存代占新生代的1/10

    设置新生代大小

-XX:NewRatio=4

    新生代(eden+2*s)和老年代(不包含永久区)的比值

    例如:4,表示新生代:老年代=1:4,即新生代占整个堆的1/5


-XX:SurvivorRatio=2(幸存代)

    设置两个Survivor区和eden的比值

    例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump

如果发生了OOM异常,那就把dump信息导出到d:/a.dump文件中。

-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p //p代表的是当前进程的pid

上方参数的意思是说,执行printstack.bat脚本,而这个脚本做的事情是:D:/tools/jdk1.7_40/bin/jstack -F %1 > D:/a.txt,即当程序OOM时,在D:/a.txt中将会生成线程的dump。

-XX:PermSize=16m -XX:MaxPermSize:设置永久区的初始空间和最大空间;

栈的分配参数:

Xss 设置栈空间的大小。通常只有几百K;-Xss128K

-XX:+UseParallelGC -XX:ParallelGCThreads=20此配置仅对年轻代有效。

-XX:MaxGCPauseMillis=100 : 设置每次年轻代垃圾回收的最长时间

-XX:+UseCMSCompactAtFullCollection :打开对年老代的压缩。

常见配置汇总

堆设置


    -Xms :初始堆大小

    -Xmx :最大堆大小

    -XX:NewSize=n :设置年轻代大小

    -XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

    -XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

    -XX:MaxPermSize=n :设置持久代大小


收集器设置


    -XX:+UseSerialGC :设置串行收集器

    -XX:+UseParallelGC :设置并行收集器

    -XX:+UseParalledlOldGC :设置并行年老代收集器

    -XX:+UseConcMarkSweepGC :设置并发收集器


垃圾回收统计信息


    -XX:+PrintGC

    -XX:+PrintGCDetails

    -XX:+PrintGCTimeStamps

    -Xloggc:filename


并行收集器设置


    -XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。

    -XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间

    -XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)


并发收集器设置


    -XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。

    -XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

28.Raft协议的leader选举,正常情况下,网络抖动造成follower发起leader选举,且该follower的
Term比现有leader高。集群中所有结点的日志信息当前一致,这种情况下会选举成功吗?

只要term加1之后,之前的leader就作废,这轮选举一定能选出来。我觉得还得看下这个term什么时候会加1,意思就是题目中follower在什么情况下term比之前leader高。因为follower在一个时间段内因为网络波动收不到leader的心跳,他就自动转化为candidate状态且term+1。

13.静态类与单例模式的区别

单例模式比静态类有很多优势:

(1)单例可以继承类,实现接口,而静态类不能(可以集成类,但不能集成实例成员);

(2)单例可以被延迟初始化,静态类一般在第一次加载是初始化;

(3)单例类可以被集成,他的方法可以被覆写;

(4)或许最重要的是,单例类可以被用于多态而无需强迫用户只假定唯一的实例。举个例子,你可能在开始时只写一个配置,但是以后你可能需要支持超过一个配置集,或者可能需要允许用户从外部文件中加载一个配置对象,或者编写自己的。你的代码不需要关注全局的状态,因此你的代码会更加灵活。

观点二:(静态方法)静态方法中产生的对象,会随着静态方法执行完毕而释放掉,而且执行类中的静态方法时,不会实例化静态方法所在的类。如果是用singleton, 产生的那一个唯一的实例,会一直在内存中,不会被GC清除的(原因是静态的属性变量不会被GC清除),除非整个JVM退出了。这个问题我之前也想几天,并 且自己写代码来做了个实验。

观点三:(Good!)

由于DAO的初始化,会比较占系统资源的,如果用静态方法来取,会不断地初始化和释放,所以我个人认为如果不存在比较复杂的事务管理,用singleton会比较好。

14.设计模式原则
设计模式六大原则(1):单一职责原则

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

设计模式六大原则(2):里氏替换原则

定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。

定义2:所有引用基类的地方必须能透明地使用其子类的对象。

问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

设计模式六大原则(3):依赖倒置原则

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

设计模式六大原则(4):接口隔离原则

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

设计模式六大原则(5):迪米特法则

定义:一个对象应该对其他对象保持最少的了解。

问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

解决方案:尽量降低类与类之间的耦合。

设计模式六大原则(6):开闭原则

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

猜你喜欢

转载自blog.csdn.net/HuHui_/article/details/78114063