本文已经收录自 JavaGuide (59k+ Star):【Java学习+面试指南】 一份涵盖大部分Java程序员所须要掌握的核心知识。
参见 issue : 面向过程 :面向过程性能比面向对象高??html
这个并非根本缘由,面向过程也须要分配内存,计算内存偏移量,Java性能差的主要缘由并非由于它是面向对象语言,而是Java是半编译语言,最终的执行代码并非能够直接被CPU执行的二进制机械码。而面向过程语言大多都是直接编译成机械码在电脑上执行,而且其它一些面向过程的脚本语言性能也并不必定比Java好。java
Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不一样系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。git
什么是字节码?采用字节码的好处是什么?程序员
在 Java 中,JVM能够理解的代码就叫作字节码
(即扩展名为.class
的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言经过字节码的方式,在必定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特色。因此 Java 程序运行时比较高效,并且,因为字节码并不针对一种特定的机器,所以,Java程序无须从新编译即可在多种不一样操做系统的计算机上运行。
Java 程序从源代码到运行通常有下面3步:github
咱们须要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,而后经过解释器逐行解释执行,这种方式的执行速度会相对比较慢。并且,有些方法和代码块是常常须要被调用的(也就是所谓的热点代码),因此后面引进了 JIT 编译器,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次能够直接使用。而咱们知道,机器码的运行效率确定是高于 Java 解释器的。这也解释了咱们为何常常会说 Java 是编译与解释共存的语言。面试
HotSpot采用了惰性评估(Lazy Evaluation)的作法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所须要编译的部分。JVM会根据代码每次被执行的状况收集信息并相应地作出一些优化,所以执行的次数越多,它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协做使用。可是 ,AOT 编译器的编译质量是确定比不上 JIT 编译器的。
总结:spring
Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不一样系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不一样系统的 JVM 实现是 Java 语言“一次编译,随处能够运行”的关键所在。编程
JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它可以建立和编译程序。小程序
JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的全部内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其余的一些基础构件。可是,它不能用于建立新程序。segmentfault
若是你只是为了运行一下 Java 程序的话,那么你只须要安装 JRE 就能够了。若是你须要进行一些 Java 编程方面的工做,那么你就须要安装JDK了。可是,这不是绝对的。有时,即便您不打算在计算机上进行任何Java开发,仍然须要安装JDK。例如,若是要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为何须要JDK呢?由于应用程序服务器会将 JSP 转换为 Java servlet,而且须要使用 JDK 来编译 servlet。
可能在看这个问题以前不少人和我同样并无接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差别?下面我经过收集到的一些资料,为你解答这个被不少人忽视的问题。
对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案:
问:OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别?答:很是接近 - 咱们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望将来,咱们的目的是开源Oracle JDK的全部部分,除了咱们考虑商业功能的部分。
总结:
我知道不少人没学过 C++,可是面试官就是没事喜欢拿我们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来!
一个程序中能够有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不必定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。
简单说应用程序是从主线程启动(也就是 main()
方法)。applet 小程序没有 main()
方法,主要是嵌在浏览器页面上运行(调用init()
或者run()
来启动),嵌入浏览器这点跟 flash 的小游戏相似。
java编程思想第四版:2.2.2节
![]()
在讲继承的时候咱们就知道父类的私有属性和构造方法并不能被继承,因此 Constructor 也就不能被 override(重写),可是能够 overload(重载),因此你能够看到一个类中有多个构造函数的状况。
封装把一个对象的属性私有化,同时提供一些能够被外界访问的属性的方法,若是属性不想被外界访问,咱们大可没必要提供方法给外界访问。可是若是一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
继承是使用已存在的类的定义做为基础创建新类的技术,新类的定义能够增长新的数据或新的功能,也能够用父类的功能,但不能选择性地继承父类。经过使用继承咱们可以很是方便地复用之前的代码。
关于继承以下 3 点请记住:
所谓多态就是指程序中定义的引用变量所指向的具体类型和经过该引用变量发出的方法调用在编程时并不肯定,而是在程序运行期间才肯定,即一个引用变量到底会指向哪一个类的实例对象,该引用变量发出的方法调用究竟是哪一个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式能够实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
可变性
简单的来讲:String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[]
,因此 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value
可是没有用 final 关键字修饰,因此这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,你们能够自行查阅源码。
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count; AbstractStringBuilder() { } AbstractStringBuilder(int capacity) { value = new char[capacity]; }
线程安全性
String 中的对象是不可变的,也就能够理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操做,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,因此是线程安全的。StringBuilder 并无对方法进行加同步锁,因此是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,而后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象自己进行操做,而不是生成新的对象并改变对象引用。相同状况下使用 StringBuilder 相比使用 StringBuffer 仅能得到 10%~15% 左右的性能提高,但却要冒多线程不安全的风险。
对于三者使用的总结:
因为静态方法能够不经过对象进行调用,所以在静态方法里,不能调用其余非静态变量,也不能够访问非静态变量成员。
Java 程序在执行子类的构造方法以前,若是没有用 super()
来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。所以,若是父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super()
来调用父类中特定的构造方法,则编译时将发生错误,由于 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不作事且没有参数的构造方法。
刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。可是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。所以,最终决定 javax 包将成为标准API的一部分。
因此,实际上java和javax没有区别。这都是一个名字。
备注:在JDK8中,接口也能够定义静态方法,能够直接用接口名调用。实现类和实现是不能够调用的。若是同时实现两个接口,接口中定义了同样的默认方法,则必须重写,否则会报错。(详见issue:https://github.com/Snailclimb/JavaGuide/issues/146)
static
修饰的,那么这个成员变量是属于类的,若是没有使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。new运算符,new建立对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用能够指向0个或1个对象(一根绳子能够不系气球,也能够系一个气球);一个对象能够有n个引用指向它(能够用n条绳子系住一个气球)。
方法的返回值是指咱们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的做用:接收出结果,使得它能够用于其余的操做!
主要做用是完成对类对象的初始化工做。能够执行。由于一个类即便没有声明构造方法也会有默认的不带参数的构造方法。
对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。
帮助子类作初始化工做。
== : 它的做用是判断两个对象的地址是否是相等。即,判断两个对象是否是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。
equals() : 它的做用也是判断两个对象是否相等。但它通常有两种使用状况:
举个例子:
public class test1 { public static void main(String[] args) { String a = new String("ab"); // a 为一个引用 String b = new String("ab"); // b为另外一个引用,对象的内容同样 String aa = "ab"; // 放在常量池中 String bb = "ab"; // 从常量池中查找 if (aa == bb) // true System.out.println("aa==bb"); if (a == b) // false,非同一对象 System.out.println("a==b"); if (a.equals(b)) // true System.out.println("aEQb"); if (42 == 42.0) { // true System.out.println("true"); } } }
说明:
面试官可能会问你:“你重写过 hashcode 和 equals 么,为何重写equals时必须重写hashCode方法?”
hashCode() 的做用是获取哈希码,也称为散列码;它其实是返回一个int整数。这个哈希码的做用是肯定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
散列表存储的是键值对(key-value),它的特色是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(能够快速找到所须要的对象)
咱们先以“HashSet 如何检查重复”为例子来讲明为何要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其余已经加入的对象的 hashcode 值做比较,若是没有相符的hashcode,HashSet会假设对象没有重复出现。可是若是发现有相同 hashcode 值的对象,这时会调用 equals()
方法来检查 hashcode 相等的对象是否真的相同。若是二者相同,HashSet 就不会让其加入操做成功。若是不一样的话,就会从新散列到其余位置。(摘自个人Java启蒙书《Head first java》第二版)。这样咱们就大大减小了 equals 的次数,相应就大大提升了执行速度。
经过咱们能够看出:hashCode()
的做用就是获取哈希码,也称为散列码;它其实是返回一个int整数。这个哈希码的做用是肯定该对象在哈希表中的索引位置。hashCode()
在散列表中才有用,在其它状况下没用。在散列表中hashCode() 的做用是获取对象的散列码,进而肯定该对象在散列表中的位置。
推荐阅读:Java hashCode() 和 equals()的若干问题解答
线程与进程类似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程当中能够产生多个线程。与进程不一样的是同类的多个线程共享同一块内存空间和一组系统资源,因此系统在产生一个线程,或是在各个线程之间做切换工做时,负担要比进程小得多,也正由于如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其余的数据存储设备中,也就是说程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,所以进程是动态的。系统运行一个程序便是一个进程从建立,运行到消亡的过程。简单来讲,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每一个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操做系统载入内存中。
线程是进程划分红的更小的运行单位。线程和进程最大的不一样在于基本上各进程是独立的,而各线程则不必定,由于同一进程中的线程极有可能会相互影响。从另外一角度来讲,进程属于操做系统的范畴,主要是同一段时间内,能够同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不一样状态的其中一个状态(图源《Java 并发编程艺术》4.1.4节)。
线程在生命周期中并非固定处于某一个状态而是随着代码的执行在不一样状态之间切换。Java 线程状态变迁以下图所示(图源《Java 并发编程艺术》4.1.4节):
由上图能够看出:
线程建立以后它将处于 NEW(新建) 状态,调用 start()
方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程得到了 cpu 时间片(timeslice)后就处于 RUNNING(运行) 状态。
操做系统隐藏 Java虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源: HowToDoInJava: Java Thread Life Cycle and Thread States),因此 Java 系统通常将这两个状态统称为 RUNNABLE(运行中) 状态 。
当线程执行 wait()
方法以后,线程进入 WAITING(等待)状态。进入等待状态的线程须要依靠其余线程的通知才可以返回到运行状态,而 TIME_WAITING(超时等待) 状态至关于在等待状态的基础上增长了超时限制,好比经过 sleep(long millis)
方法或 wait(long millis)
方法能够将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的状况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的 run()
方法以后将会进入到 TERMINATED(终止) 状态。
final关键字主要用在三个地方:变量、方法、类。
在 Java 中,全部的异常都有一个共同的祖先java.lang包中的 Throwable类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,两者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error(错误):是程序没法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操做无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 再也不有继续执行操做所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)通常会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,由于它们在应用程序的控制和处理能力之 外,并且绝大多数是程序运行时不容许出现的情况。对于设计合理的应用程序来讲,即便确实发生了错误,本质上也不该该试图去处理它所引发的异常情况。在 Java中,错误经过Error的子类描述。
Exception(异常):是程序自己能够处理的异常。</font>Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由Java虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以0时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。
注意:异常和错误的区别:异常能被程序自己处理,错误是没法处理。
语句时,finally语句块将在方法返回以前被执行。
在如下4种特殊状况下,finally块不会被执行:
下面这部份内容来自issue:https://github.com/Snailclimb...。
注意: 当try语句和finally语句中都有return语句时,在方法返回以前,finally语句的内容将被执行,而且finally语句的返回值将会覆盖原始的返回值。以下:
public static int f(int value) { try { return value * value; } finally { if (value == 2) { return 0; } } }
若是调用 f(2)
,返回值将是0,由于finally语句的返回值覆盖了try语句块的返回值。
对于不想进行序列化的变量,使用transient关键字修饰。
transient关键字的做用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。
方法1:经过 Scanner
Scanner input = new Scanner(System.in); String s = input.nextLine(); input.close();
方法2:经过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String s = input.readLine();
Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上颇有规则,并且彼此之间存在很是紧密的联系, Java I0流的40多个类都是从以下4个抽象类基类中派生出来的。
按操做方式分类结构图:
按操做对象分类结构图:
问题本质想问:无论是文件读写仍是网络发送接收,信息的最小存储单元都是字节,那为何 I/O 流操做要分为字节流操做和字符流操做呢?
回答:字符流是由 Java 虚拟机将字节转换获得的,问题就出在这个过程还算是很是耗时,而且,若是咱们不知道编码类型就很容易出现乱码问题。因此, I/O 流就干脆提供了一个直接操做字符的接口,方便咱们平时对字符进行流操做。若是音频文件、图片等媒体文件用字节流比较好,若是涉及到字符的话使用字符流比较好。
Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不一样的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持同样,比较简单,可是性能和可靠性都很差;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可使用同步阻塞I/O来提高开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发详见笔主的这篇文章: https://gitee.com/SnailClimb/...
详见笔主的这篇文章: https://gitee.com/SnailClimb/...
做者的其余开源项目推荐: