再高大上的框架,也须要扎实的基础才能玩转,高频面试问题更是基础中的高频实战要点。html
Java 学习者和爱好者,有必定工做经验的技术人,准面试官等。java
本教程是系列教程,包含 Java 基础,JVM,容器,多线程,反射,异常,网络,对象拷贝,JavaWeb,设计模式,Spring-Spring MVC,Spring Boot / Spring Cloud,Mybatis / Hibernate,Kafka,RocketMQ,Zookeeper,MySQL,Redis,Elasticsearch,Lucene面试
微信搜:JavaPub,阅读全套系列面试题教程
算法
[toc]编程
JDK 和 JREwindows
JDK(Java Development Kit)是Java开发运行环境,是java开发工具包,JDK包含了JRE的全部东西,同时还包含了编译java源码的编译器javac,还包含了不少java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。设计模式
JRE(Java Runtime Environment)它是Java运行环境,若是你不须要开发只须要运行Java程序,那么你能够安装JRE。(可是在运行JSP程序时,咱们仍是须要JDK,由于应用服务器会将 JSP 转换为 Java servlet,而且须要使用 JDK 编译 servlet。)数组
若是你须要运行java程序,只需安装JRE就能够了。若是你须要编写java程序,须要安装JDK。安全
JVM服务器
JVM(Java Virtual Machine) 就是咱们常说的 java 虚拟机是 JRE 的一部分,它是整个 java 实现跨平台的最核心的部分,全部的 java 程序会首先被编译为 .class 的类文件,这种类文件能够在虚拟机上执行。
JVM 主要工做是解释本身的指令集(即字节码)并映射到本地的 CPU 指令集和 OS 的系统调用。Java 语言是跨平台运行的,不一样的操做系统会有不一样的 JVM 映射规则,使之与操做系统无关,完成跨平台性。
JVM是Java Virtual Machine(Java虚拟机)的缩写,是经过在实际的计算机上仿真模拟各类计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操做系统平台相关的信息,使得Java程序只须要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是Java可以“一次编译,处处运行的”缘由。
附一张关系图:
总结:使用JDK(调用JAVA API)开发JAVA程序后,经过JDK中的编译程序(javac)将Java程序编译为Java字节码,在JRE上运行这些字节码,JVM会解析并映射到真实操做系统的CPU指令集和OS的系统调用。
equals 和 == 都是来判断两个对象是否相等
public boolean equals(Object obj) { return (this == obj); }
从源码能够看出,里面使用的就是 == 比较,因此这种状况下比较的就是它们在内存中的存放地址。
@Override public boolean equals(Object other) { if (other == this) { return true; } if (other instanceof String) { String s = (String)other; int count = this.count; if (s.count != count) { return false; } if (hashCode() != s.hashCode()) { return false; } char[] value1 = value; int offset1 = offset; char[] value2 = s.value; int offset2 = s.offset; for (int end = offset1 + count; offset1 < end; ) { if (value1[offset1] != value2[offset2]) { return false; } offset1++; offset2++; } return true; } else { return false; } }
从源码能够看出, String 类复写了 equals 方法,当使用 == 比较内存的存放地址不相等时,接下来会比较字符串的内容是否 相等,因此 String 类中的 equals 方法会比较二者的字符串内容是否同样。
答案是不必定的
java.lang.Object类中有两个很是重要的方法:
public boolean equals(Object obj) public int hashCode()
Object
类是类继承结构的基础,因此是每个类的父类。全部的对象,包括数组,都实现了在 Object
类中定义的方法。
如下是Object对象API关于equal方法和hashCode方法的说明:
简而言之,在集合查找时,hashcode能大大下降对象比较次数,提升查找效率!
Java对象的eqauls方法和hashCode方法是这样规定的:
对以上俩点的说明
想象一下,假如两个Java对象A和B,A和B相等(eqauls结果为true),但A和B的哈希码不一样,则A和B存入HashMap时的哈希码计算获得的HashMap内部数组位置索引可能不一样,那么A和B颇有可能容许同时存入HashMap,显然相等/相同的元素是不容许同时存入HashMap,HashMap不容许存放重复元素。
也就是说,不一样对象的hashCode可能相同;假如两个Java对象A和B,A和B不相等(eqauls结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同这时HashMap会在该位置创建一个连接表,将A和B串起来放在该位置,显然,该状况不违反HashMap的使用原则,是容许的。固然,哈希冲突越少越好,尽可能采用好的哈希算法以免哈希冲突。
总而言之(all in all):
换句话说,equals()方法不相等的两个对象,hashcode()有可能相等(个人理解是因为哈希码在生成的时候产生冲突形成的)。反过来,hashcode()不等,必定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
这是一个很基础,很能体现你基础是否扎实、是否有钻研精神的知识点。
final关键字的字面意思是最终的, 不可修改的. 这彷佛是一个看见名字就大概能知道怎么用的语法。
这三点必需要答出来:
要注意final类中的全部成员方法都会被隐式地指定为final方法。
第三点尤其重要
当final修饰的是一个基本数据类型数据时, 这个数据的值在初始化后将不能被改变; 当final修饰的是一个引用类型数据时, 也就是修饰一个对象时, 引用在初始化后将永远指向一个内存地址, 不可修改. 可是该内存地址中保存的对象信息, 是能够进行修改的.
JavaPub参考巨人(有一些简单例子,便于更好的理解final):https://www.cnblogs.com/dolph...
返回值:-1
四舍五入的原理是在参数上加0.5而后作向下取整。
一些案例:
public class test { public static void main(String[] args){ System.out.println(Math.round(1.3)); //1 System.out.println(Math.round(1.4)); //1 System.out.println(Math.round(1.5)); //2 System.out.println(Math.round(1.6)); //2 System.out.println(Math.round(1.7)); //2 System.out.println(Math.round(-1.3)); //-1 System.out.println(Math.round(-1.4)); //-1 System.out.println(Math.round(-1.5)); //-1 System.out.println(Math.round(-1.6)); //-2 System.out.println(Math.round(-1.7)); //-2 } }
固然,每个Java学习者都知道它不是基础类型,可是你要知道更多细节。
Java中的数据类型分为两大类,基本数据类型和引用数据类型。
基本数据类型只有8种,可按照以下分类
引用数据类型很是多,大体包括:
类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型。
简单来讲,全部的非基本数据类型都是引用数据类型。
在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
只要是引用数据类型变量,其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址(引用/句柄)
ps:经过变量地址能够找到变量的具体内容,就如同经过房间号能够找到房间通常
在方法中定义的非全局基本数据类型变量,调用方法时做为参数是按数值传递的
//基本数据类型做为方法参数被调用 public class Main{ public static void main(String[] args){ int msg = 100; System.out.println("调用方法前msg的值:\n"+ msg); //100 fun(msg); System.out.println("调用方法后msg的值:\n"+ msg); //100 } public static void fun(int temp){ temp = 0; } }
引用数据类型变量,调用方法时做为参数是按引用传递的
//引用数据类型做为方法参数被调用 class Book{ String name; double price; public Book(String name,double price){ this.name = name; this.price = price; } public void getInfo(){ System.out.println("图书名称:"+ name + ",价格:" + price); } public void setPrice(double price){ this.price = price; } } public class Main{ public static void main(String[] args){ Book book = new Book("Java开发指南",66.6); book.getInfo(); //图书名称:Java开发指南,价格:66.6 fun(book); book.getInfo(); //图书名称:Java开发指南,价格:99.9 } public static void fun(Book temp){ temp.setPrice(99.9); } }
调用时为temp在栈中开辟新空间,并指向book的具体内容,方法执行完毕后temp在栈中的内存被释放掉
JavaPub参考巨人:https://blog.csdn.net/py1215/...
String、StringBuffer、StringBuilder
答:不同。
由于内存的分配方式不同。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”)方式,则会被分到堆内存中。
String str1 = "i"; String str2 = "i"; String str3 = new String("i"); System.out.println(str1 == str2);//ture System.out.println(str2 == str3);//false
解释:
在String str1="i"中,把i值存在常量池,地址赋给str1。假设再写一个String str2="i",则会把i的地址赋给str2,可是i对象不会从新建立,他们引用的是同一个地址值,共享同一个i内存。(须要注意的是:String str="i"; 由于String 是final类型的,因此“i”应该是在常量池。)
假设再写一个String str3=new String(“i”),则会建立一个新的i对象,而后将新对象的地址值赋给str3。虽然str3和str1的值相同可是地址值不一样。(而new String("i");则是新建对象放到堆内存中。)
拓展知识:
俩种办法:
public static void main(String[] args) { String str = "ABCDE"; System.out.println(reverseStringByStringBuilderApi(str)); System.out.println(reverseString(str)); } /** * 和StringBuffer()同样,都用了Java自实现的方法,使用位移来实现 * @param * @return */ public static String reverseStringByStringBuilderApi(String str) { if (str != null && str.length() > 0) { return new StringBuilder(str).reverse().toString(); } return str; } public static String reverseString(String str) { if (str != null && str.length() > 0) { int len = str.length(); char[] chars = new char[len]; for (int i = len - 1; i >= 0; i--) { chars[len - 1 - i] = str.charAt(i); } return new String(chars); } return str; }
更多交换方式参考:https://blog.csdn.net/py1215/...
下面列举了20个经常使用方法。格式:返回类型 方法名 做用。
答案是:没必要须
这道题考察的是抽象类的知识:
抽象类的基本使用示例:
//定义一个抽象类 abstract class A{ //普通方法 public void fun(){ System.out.println("存在方法体的方法"); } //抽象方法,没有方法体,有abstract关键字作修饰 public abstract void print(); } //单继承 //B类是抽象类的子类,是一个普通类 class B extends A{ //强制要求覆写 @Override public void print() { System.out.println("Hello World !"); } } public class TestDemo { public static void main(String[] args) { //向上转型 A a = new B(); //被子类所覆写的过的方法 a.print(); } }
JavaPub参考巨人:https://www.jianshu.com/p/053...
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类同样,一样能够拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:
不能,抽象类是被用于继承的,final修饰表明不可修改、不可继承的。
这个在前面几题有过介绍。
语法层面上的区别,也是咱们平常官方的一些说法:
接口的设计目的,是对类的行为进行约束。而抽象类的设计目的,是代码复用。总结来讲,继承是一个 "是否是"的关系,而 接口 实现则是 "有没有"的关系。若是一个类继承了某个抽象类,则子类一定是抽象类的种类,而接口实现则是有没有、具有不具有的关系。
抽象和继承是 Java 中很是重要的东西,深刻理解可让咱们对 Java 技术理解更深入,参考的这篇知乎博文很是好。
JavaPub参考巨人:https://www.zhihu.com/questio...
Java中的流分为两种,一种是字节流,另外一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种因此一共四个):InputStream,OutputStream,Reader,Writer。Java中其余多种多样变化的流均是由它们派生出来的.
字符流和字节流是根据处理数据的不一样来区分的。字节流按照8位传输,字节流是最基本的,全部文件的储存是都是字节(byte)的储存,在磁盘上保留的并非文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。
读文本的时候用字符流,例如txt文件。读非文本文件的时候用字节流,例如mp3。理论上任何文件都可以用字节流读取,但当读取的是文本数据时,为了能还原成文本你必须再通过一个转换的工序,相对来讲字符流就省了这个麻烦,能够有方法直接读取。
字符流处理的单元为2个字节的Unicode字符,分别操做字符、字符数组或字符串,而字节流处理单元为1个字节, 操做字节和字节数组。因此字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,因此它对多国语言支持性比较好!
代码Demo参考:https://www.yisu.com/zixun/12...
BIO
BIO全称是Blocking IO,是JDK1.4以前的传统IO模型,自己是同步阻塞模式。
线程发起IO请求后,一直阻塞IO,直到缓冲区数据就绪后,再进入下一步操做。针对网络通讯都是一请求一应答的方式,虽然简化了上层的应用开发,但在性能和可靠性方面存在着巨大瓶颈,试想一下若是每一个请求都须要新建一个线程来专门处理,那么在高并发的场景下,机器资源很快就会被耗尽。
NIO
NIO也叫Non-Blocking IO 是同步非阻塞的IO模型。线程发起io请求后,当即返回(非阻塞io)。同步指的是必须等待IO缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待IO缓冲区,能够先作一些其余操做,可是要定时轮询检查IO缓冲区数据是否就绪。Java中的NIO 是new IO的意思。实际上是NIO加上IO多路复用技术。普通的NIO是线程轮询查看一个IO缓冲区是否就绪,而Java中的new IO指的是线程轮询地去查看一堆IO缓冲区中哪些就绪,这是一种IO多路复用的思想。IO多路复用模型中,将检查IO数据是否就绪的任务,交给系统级别的select或epoll模型,由系统进行监控,减轻用户线程负担。
NIO主要有buffer、channel、selector三种技术的整合,经过零拷贝的buffer取得数据,每个客户端经过channel在selector(多路复用器)上进行注册。服务端不断轮询channel来获取客户端的信息。channel上有connect,accept(阻塞)、read(可读)、write(可写)四种状态标识。根据标识来进行后续操做。因此一个服务端可接收无限多的channel。不须要新开一个线程。大大提高了性能。
AIO
AIO是真正意义上的异步非阻塞IO模型。
上述NIO实现中,须要用户线程定时轮询,去检查IO缓冲区数据是否就绪,占用应用程序线程资源,其实轮询至关于仍是阻塞的,并不是真正解放当前线程,由于它仍是须要去查询哪些IO就绪。而真正的理想的异步非阻塞IO应该让内核系统完成,用户线程只须要告诉内核,当缓冲区就绪后,通知我或者执行我交给你的回调函数。
AIO能够作到真正的异步的操做,但实现起来比较复杂,支持纯异步IO的操做系统很是少,目前也就windows是IOCP技术实现了,而在Linux上,底层仍是是使用的epoll实现的。
资料:
BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动链接数不是特别高(小于单机1000)的状况下,这种模型是比较不错的,可让每个链接专一于本身的 I/O 而且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池自己就是一个自然的漏斗,能够缓冲一些系统处理不了的链接或请求。可是,当面对十万甚至百万级链接的时候,传统的 BIO 模型是无能为力的。所以,咱们须要一种更高效的 I/O 处理模型来应对更高的并发量。NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N能够理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操做方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不一样的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持同样,比较简单,可是性能和可靠性都很差;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可使用同步阻塞I/O来提高开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操做以后会直接返回,不会堵塞在那里,当后台处理完成,操做系统会通知相应的线程进行后续的操做。AIO 是异步IO的缩写,虽然 NIO 在网络操做中,提供了非阻塞的方法,可是 NIO 的 IO 行为仍是同步的。对于 NIO 来讲,咱们的业务线程是在 IO 操做准备好时,获得通知,接着就由这个线程自行进行 IO 操做,IO操做自己是同步的。查阅网上相关资料,我发现就目前来讲 AIO 的应用还不是很普遍,Netty 以前也尝试使用过 AIO,不过又放弃了。
Files.exists():检测文件路径是否存在。
Files.createFile():建立文件。
Files.createDirectory():建立文件夹。
Files.delete():删除一个文件或目录。
Files.copy():复制文件。
Files.move():移动文件。
Files.size():查看文件个数。
Files.read():读取文件。
Files.write():写入文件。
微信关注:JavaPub ,带走全套宝典