往期文章回顾整理列表:
1.JRE、JDK、JVM及JIT分别是什么?各有什么不同?
理解这几个术语的概念我们最好在官网文档上面去查,下面这图来自Java8平台标准版文档。
如图所示说明了Oracle Java SE产品的组件,我们可以清晰看到JDK的概念包含JRE,JRE的组成里面又包括JVM,而JIT是JVM中一种特殊的编译器。即JDK是JRE 的超集,包含JRE中的所有内容,以及开发applet和应用程序所需的编译器和调试器等工具。JRE提供了库,Java虚拟机(JVM)和其他组件来运行用Java编程语言编写的applet和应用程序。请注意,JRE包含Java SE规范不需要的组件,包括标准和非标准Java组件。
JDK: Java Development Kit,Java语言的软件开发工具包
我们所编写的Java应用需要用到的jar包或者一些可执行文件(java、javac、javadoc等)都是JDK提供的,我们平时所谓的配置”Java环境”其实就是下载JDK的过程,配置完成后我们就可以在eclipse或者是idea中编写Java代码了。
JRE: Java Runtime Environment,java运行时环境
java运行时环境是JVM的一个超集,代表了Java程序完整的运行时环境。比如说我们不是开发者,但是要运行某个Java写的exe程序,那就要求我们机器上最起码要有个JRE环境才能运行起来。事实上,JRE是以JVM为支撑,集用户界面、集成库、lang和util工具包等共同完成了堆Java程序运行的支撑。
JVM: Java Virtual Machine,Java虚拟机
作为Java程序开发者,JVM是对我们最重要的一个概念。我们常说的Java是跨平台语言其实指的就是Java的设计在各大操作系统中都实现了一个Java虚拟机,抽象化硬件设备,然后让JVM与操作系统进行交流,保证”一次编译,处处运行”。JVM中有很多重要的知识点,以后老四也会一一浅析。
JIT: Just In Time Compiler,即时编译器
这是一种特殊的JVM编译器,作为优化虚拟机的一种手段。我们知道Java代码是分为”解释执行”和”编译执行”两种方式的,JIT运用到的就是编译执行这里,在源代码变异成字节码的过程中,它负责把检测到的相似的子节点变异成单一运行的机器码,通过这种方式节省CPU的利用率。但是请注意: 即时编译器并不是虚拟机必需的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。
2.一个”.java”源文件中除了内部类是否可以包含多个类?如果包含的话这些有什么特点?
当然可以。如《Java编程思想》一书所言: 当编写一个java源代码文件时,此文件通常被称为编译单元(有时也被称为转译单元)。每个编译单元都必须有一个后缀名.java,而在编译单元内则可以有一个public类,该类的名称必须与文件的名称相同(包括大小写,但不包括文件的后缀名.java)。每个编译单元只能有一个public类,否则编译器就不会接受。如果在该编译单元之中还有额外的类的话,那么在包之外的世界是无法看见这些类的,这是因为它们不是public类,而且它们主要用来为主public类提供支持。
上面的话通俗一点来讲就是: 一个Java类文件中public类不是必须的,但是如果源文件中有一个(如果有就只能有一个)public类的话,文件名必须与这个public类同名,原因是为了方便虚拟机在相应的路径中找到相应的类所对应的字节码文件。所以在没有public类的Java文件中,文件名和类名都没什么联系。老四在这篇《[笔记]琐碎的Java基础知识不整理版随手记 持续更新》文章中整理了很多Java相关琐碎但是机器重要的知识点,闲着没事的时候可以前去参考一下。
3.什么是竞态条件?举个例子说明。
竞态条件的概念也是来自Java并发编程中的。
我们都知道在一个程序中运行多个线程是额米有问题的,但是如果多个线程同时访问了程序的相同资源(变量、对象、数据库等)就会出现线程不安全的问题。而且所谓的多个线程访问统一资源指的是对资源进行写操作,作为共享资源,资源在同一时刻资源不发生变化,多个线程读取相同的资源是线程安全的。
一段经典的示例代码如下:
1 2 3 4 5 6 |
public class Counter { protected long count = 0; public void add(long value){ this.count = this.count + value; } } |
假设存在线程A和线程B交错切换执行上述代码中add()方法
- this.count = 0
- A: 读取this.count到一个寄存器(0)
- B: 读取this.count到一个寄存器(0)
- B: 将寄存器的值加2
- B: 回写寄存器值(2)到内存.。this.count现在等于2
- A: 将寄存器的值加3
- A: 回写寄存器值(3)到内存.。this.count 现在等于3
我们的需求应该是当线程B将寄存器的值加2后,线程A应该得到this.count的值为2,然后加3最后得到我们想要的值为5。但是因为系统中的线程是交叉执行的,你不知道系统会在什么时候切换线程,上述这种情况就导致线程A与B分别读取到寄存器的值为0,分别进行加2加3操作,最后只返回了数值3。同理,this.count的值还有可能返回2,返回5等。
正如上述情况所述,当两个线程竞争同一个系统资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称为临界区。上述代码中的add()方法就是一个临界区,它会产生竞态条件。避免竞态条件的方式当然就是使用同步代码块(关键字synchronized)或者Lock显式锁,保证同一时刻只有一个线程对该资源进行访问,处理完毕后释放以让其他线程介入再次进行资源处理。
4.MVC的各个部分都有那些技术来实现?如何实现?
搞JavaWeb的离不开MVC的概念,这道题是老四拿出来给大家复习一下基本的概念,然后带着大家一起复习一下Spring MVC中的一些比较重要的知识点。
MVC: Model View Controller,三个单词分别代表应用的业务逻辑(JavaBean)、应用界面(前端html,JSP等)、处理控制过程(控制器,Servlet等)。通过这种把应用逻辑, 处理过程和显示逻辑分成不同的组件实现的方式缩写成了MVC。各个语言一般都有自己MVC框架,像PHP中的叫做ThinkPHP,而Java中比较重要的就是SpringMVC了,Struts2现在应该很少用到了,老四现在基本上也不怎么会用了~~~接着就带大家简单复习一些Spring中一些比较基础和重要的知识点。
- DispatcherServlet –前端控制器,分发请求
-
HandlerMapping –处理器映射
下面有三个重要的实现类:
— BeanNameUrlHandlerMapping (根据控制器的名字将控制器映射到URL)
— SimpleUrlHandlerMapping (用上下文配置文件中定义的属性集合将控制器映射到URL)
— CommonsPathMapHandlerMapping (使用控制器代码中的元数据将控制器映射到URL)
-
Controller –控制器
-
ModelAndView -视图模型
- 在Controller控制器中使用ThreadLocal变量
- 声明scope=”prototype”
关于ThreadLocal关键字老四曾在浅析阿里巴巴Java开发手册系列中的《阿里巴巴Java开发规约第一章-并发处理篇 吐血浅析》第十五条中有对应的说明,可以参考一下。
5.什么是Web Service?
WebService严格来讲并不算做一种新技术,也不是框架,其实是一种概念并且定义了跨语言通信的一种规范,用于不同的编程语言、不同的应用、不同的操作系统平台之间的交互通信。比如在Windows server上有个C#的应用,在Linux CentOS上已经部署了一个Java应用,通过WebService规范下实现的技术手段可以实现A与B进行通信,互相访问查看对方的数据。很多大型互联网公司或者大型数据机构一般都会暴露自己的WebService服务给第三方调用,比如说天气预报、银联支付等,将WebService运用到极致,发挥其各种开发数据的功能,就演变成了我们常说的SOA(Service-Oriented Architecture,面向服务的架构)应用。
我们当程序员的自然就是在具体平台开发WebService接口并且调用WebService接口。每种开发语言都有自己的WebService实现框架。Java中有以下这些服务框架供我们使用(包括但不限于):
- Apache Axis I & II
- Codehaus XFire
- Apache CXF
- Apache Wink
- Jboss
- RESTEasyd
其中Apache CXF是我们Java领域相对比较主流的WebService服务框架,有机会老四也会单独来写。您可以前往Apache CXF官方主页进行学习查阅。
6.Interface与abstract类的区别,即Java中接口和抽象类的区别。
搞Java的离不开这两个重要的类,无论是学习还是工作,抑或是笔面试,基本都会涉及到它俩之间的比较和使用。老四之前在浅析设计模式系列文章也较详细的阐述了这个问题,可以前往《浅析设计模式第十章-模板方法模式》参考一下。
7.简单说一下IO和NIO的差,为什么在有IO的情况下创造出NIO?
Java中的I/O是面向流的,而新设计的NIO则是面向缓冲区的。什么意思呢?说白了NIO的设计就是为了解决I/O流阻塞模式的问题。我们都知道Java的I/O中各种各样的流都是阻塞的,当一个线程无论在读(read())还是写(write())的过程中,线程都是被阻塞的不能做任何事情,直至数据完全被写入或者读取完毕。
NIO的设计就改变了上面的做法,是非阻塞模式。也就是说一个线程请求读取或写入数据的时候,如果当前通道没有数据,线程不会阻塞,它可以去处理其他事情(一般是去别的通道上执行I/O操作),不需要等待必须完全写入或者等待。另外重点是NIO的三大组件一定要掌握:
- Selector: 多路复用器。轮询注册在其上的Channel,当发现某个或者多个Channel处于”就绪状态”后(accept接收连接事件、connect连接完成事件、read读事件、write写事件),从阻塞状态返回就绪的Channel的SelectionKey集合,之后进行IO操作。
- Channel: 封装了socket。
- ServerSocketChannel: 封装了ServerSocket,用于accept客户端连接请求。
- SocketChannel: 一对SocketChannel组成一条虚电路,进行读写通信。
- Buffer: 用于存取数据,最主要的是ByteBuffer。
- position: 下一个将被操作的字节位置。
- limit: 在写模式下表示可以进行写的字节数,在读模式下表示可以进行读的字节数。
- capacity: Buffer的大小。
这里注意一下,老四虽然说NIO是非阻塞模式,但其实NIO严格的来讲应该是多路复用IO。IO模型有四种,分别是: 阻塞IO、非阻塞IO、多路复用IO、异步IO这四种IO模型,NIO会阻塞在Selector的select方法上,关于这些比较深度的知识老四会在以后专门专题来讲,看到这些你可以查阅相关书籍或者Google来帮助你更深入的理解。
老四写了几个关于NIO的示例类,展示一下NIO的基本使用方法,分别是NIO缓冲区、NIO通道、阻塞式IO、Files操作类、非阻塞式IO、管道等,由于篇幅原因,这里只贴一个,其余代码可以文末自助获取下载查阅,没有账号的使用邮箱注册一下小站即可免费下载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
package com.glorze.nio; import java.nio.ByteBuffer; import org.junit.Test; /** * NIO缓冲区 * @ClassName TestBuffer * @author: 高老四 * @since: 2018年12月17日 下午6:27:53 * * 一、缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据 * * 根据数据类型不同(boolean 除外),提供了相应类型的缓冲区: * ByteBuffer * CharBuffer * ShortBuffer * IntBuffer * LongBuffer * FloatBuffer * DoubleBuffer * * 上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区 * * 二、缓冲区存取数据的两个核心方法: * put() : 存入数据到缓冲区中 * get() : 获取缓冲区中的数据 * * 三、缓冲区中的四个核心属性: * capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。 * limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写) * position : 位置,表示缓冲区中正在操作数据的位置。 * mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置 * * 0 <= mark <= position <= limit <= capacity * * 四、直接缓冲区与非直接缓冲区: * 非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中 * 直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率 */ public class BufferTest { @Test public void test3() { // 分配直接缓冲区 ByteBuffer buf = ByteBuffer.allocateDirect(1024); System.out.println(buf.isDirect()); } @Test public void test2() { String str = "abcde"; ByteBuffer buf = ByteBuffer.allocate(1024); buf.put(str.getBytes()); buf.flip(); byte[] dst = new byte[buf.limit()]; buf.get(dst, 0, 2); System.out.println(new String(dst, 0, 2)); System.out.println(buf.position()); // mark() : 标记 buf.mark(); buf.get(dst, 2, 2); System.out.println(new String(dst, 2, 2)); System.out.println(buf.position()); // reset() : 恢复到 mark 的位置 buf.reset(); System.out.println(buf.position()); // 判断缓冲区中是否还有剩余数据 if (buf.hasRemaining()) { // 获取缓冲区中可以操作的数量 System.out.println(buf.remaining()); } } @Test public void test1() { String str = "abcde"; // 1. 分配一个指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("-----------------allocate()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); // 2. 利用 put() 存入数据到缓冲区中 buf.put(str.getBytes()); System.out.println("-----------------put()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); // 3. 切换读取数据模式 buf.flip(); System.out.println("-----------------flip()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); // 4. 利用 get() 读取缓冲区中的数据 byte[] dst = new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst, 0, dst.length)); System.out.println("-----------------get()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); // 5. rewind() : 可重复读 buf.rewind(); System.out.println("-----------------rewind()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); // 6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于"被遗忘"状态 buf.clear(); System.out.println("-----------------clear()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); System.out.println((char) buf.get()); } } |
8.什么是尾递归?为什么需要尾递归?
关于递归和尾递归的相关知识老四在浅析数据结构的知识中也说过,具体可以参考一下《浅析数据结构排序篇之快速排序Quick Sort》这篇文章。
9.简单的说一说Java中的反射机制。
Java中的反射是重中之重,过阶段老四会单独发文来讲,在这里提及一些我们需要注意的重点供你参考和查阅,可以查阅相关书籍或者谷歌来学习、复习。
首先反射主要使用java.lang.reflect包下的接口和类,包括Class、 Method、 Field、 Constructor和Aray等,这些类分别代表类、方法、成员变量、构造器和数组,Java程序可以使用这些类动态地获取某个对象、某个类的运行时信息,并可以动态地创建Java对象,动态地调用Java方法,访问并修改指定对象的成员变量值。这就是反射的概念。
通过反射查看类信息
我们知道JVM加载一个类之后都会为该类生成一个Class对象,这个Class对象可以获取到这个类相关的各种详细信息。获取Class对象的三种方式:
- 使用Class.forName(String clazzName)静态方法
- 调用某个类的class属性获取对应的Class对象,如Glorze.class,一般多用这种方式,代码更安全,性能更好。
- 调用某个对象的getClass()方法
Class类包含的一些基本方法,老四仅列举几个,剩下的可以查阅JDK文档,有很多。jdk8 api文档在线阅读地址,可以结合谷歌翻译来阅读。
- Constructor<?> getConstructors(): 返回Class对象对应类所有的public构造器
- Method[] getMethods(): 返回所有的public方法
- Field[] getFields(): 返回所有的public成员变量
- Class<?>[] getInterfaces(): 返回Class对应的类所实现的所有接口
- String getName(): 以字符串的形式返回类名称
Java8新增的方法参数反射:
Java8在java.lang.reflect包下新增了一个Executable抽象类,该对象代表可执行的类成员,提供了大量方法来获取修饰该方法或构造器的注解信息;还提供了isVarArgs0方法用于判断该方法或构造器是否包含数量可变的形参,以及通过getModifiersO方法来获取该方法或构造器的修饰符。除此之外,Executable提供了如下两个方法来获取该方法或参数的形参个数及形参名。
使用反射生成并操作对象
通过反射生成对象的两种方式:
- 使用Class对象的newInstance()方法,这种方式比较常用
- 使用Class对象获取指定的Constructor对象,然后在调用Constructor对象的newInstance()方法
调用反射对象的方法
当获得某个类对应的Class象后,我们就可以通过该Class象的getMethods()方法或者getMethode()方法来获取全部方法或指定方法。获得Method对象后,在Method里包含一个invoke()方法,我们通过这个invoke()方法实现调用反射对象的方法。
同理还有访问成员变量值(getFields()、getField())、操作数组(java.lang.reflect.Array)等。
JDK动态代理,AOP
AOP(Aspect Oriented Programming,面向切面编程)是我们使用Spring过程中机器重要的概念,它的底层实现有一部分就是通过JDK动态代理来实现对方法的解耦切入的。所以AOP是重点,如何通过Java反射,JDK动态代理实现的AOP也是重中之重。
在java.lang.reflect包下提供了一个Proxy代理类和一个InvocationHandler接口,我们就是通过使用这两个类来实现动态代理的。
Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
Proxy提供了如下两个方法来创建动态代理类和动态代理实例:
- static Class<?> getProxyClass(ClassLoader loader,Cass<?>… interfaces):创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。第一个ClassLoader参数指定生成动态代理类的类加载器。
- static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, Invocation Handler h): 直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke()方法。
关于JDK动态代理老四也会下一篇文章中重点浅析一下,敬请期待。
反射和泛型
在反射中我们需要注意,通过使用Class<T>泛型可以让我们避免强制类型转换,同时反射也能够获取泛型信息(getGenericType()方法)。
总结一下反射的优缺点:
- 反射提高了程序的灵活性和扩展性。
- 降低耦合性,提高自适应能力。
- 允许程序创建和控制任何类的对象,无需提前硬编码目标类。
- 使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
- 使用反射会模糊程序内部逻辑,反射绕过了源代码的技术,因而带来维护的问题,反射代码比相应的直接代码更复杂。
10.什么是控制反转(Inversion of Control)与依赖注入(Dependency Injection)?IoC的优点是什么?
IoC是spring的核心,贯穿始终。所谓IoC对于spring框架来说就是由spring来负责控制对象的生命周期和对象间的关系。IoC(Inversion of Control,控制反转)的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。
举例来说比如我们Spring项目中配置的数据库或者数据库连接池,不需要我们操心数据库的连接,如果不看源码你都不知道你的Connection是怎么构造的,何时构造。如果我们自己编写JDBC代码,我们需要通过编码获得Connection对象,但是同Spring等带有依赖注入特性的框架,我们只需要告诉Spring我要使用数据库,我要连接数据库,Spring就会在需要连接数据库的时候动态注入一个Connection对象,想打针似的给你扎进来,通过Sprig来完成各种各样的对象管理和控制。至于如何注入?一般都是同过Java的反射机制来注入,Java反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性。
这里多提一嘴,就是面试中也经常问到的一个问题,就是Spring管理bean的生命周期,文字描述老四就不多说了,给出一张Spring管理bean的生命周期示意图供你们参考一下。
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击”给你买杜蕾斯”),也可扫描小站放的支付宝领红包二维码,线下支付享受优惠的同时老四也可以获得对应赏金,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。
资源下载
隐藏内容:******,购买后可见!
下载价格:0 G币
您需要先登录后,才能购买资源
欢迎访问高老四博客(glorze.com),本站技术文章代码均为老四亲自编写或者借鉴整合,其余资源多为网络收集,如涉及版权问题请与站长联系。如非特殊说明,本站所有资源解压密码均为:glorze.com。