(*)支付实现
1)那么多人进行支付,都是用微信或支付宝的第三方支付平台。那么到底怎么区分到底是那个用户的支付接口呢?
用户在申请微信支付接口的时候,将会生成以下签名:
1.appid:微信公众帐号或开放平台app的唯一标识(贵公司恒天财富app申请支付接口的话,会分配一个id唯一标识)
2.mch_id:商户号/支付号/匹配号(配置文件的partner)
3.partnerkey:商户秘钥/支付秘钥
4.sign:数字签名,根据微信官方提供的秘钥和一套算法生成的加密信息,就是为了保证交易的安全性
2)我们呢是用的二维码支付:因为二维码能存更多信息,也能表示更多的数据类型,且容错能力强
3)我们用的二维码生成插件是qrious.js
(1)dubbox
**1)dubbo服务治理中间件:
随着互联网的发展,网站的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,所以有了dubbox 这样的分布式服务框架的需求。
节点角色说明:
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor(监控器): 统计服务的调用次数和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明:
0. 服务容器负责启动,加载,运行服务提供者。
1. 服务提供者在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时发送一次统计数据到监控中心。
2)dubbox项目架构
①表现层拆分成一个单独的war包
②服务层拆分成一个单独的war包
③web层和service服务层通过dubbox服务治理中间件远程调用
1.rpc 远程调用 hessain2 二进制序列化
2.nio 异步通信 netty
3)Dubbox和Dubbo本质上没有区别,名字的含义扩展了Dubbo而已,以下扩展出来的功能,也是选择Dubbox很重要的考察点。
支持REST风格远程调用(HTTP + JSON/XML);
支持基于Kryo和FST的Java高效序列化实现;
支持基于Jackson的JSON序列化;
支持基于嵌入式Tomcat的HTTP remoting体系;
升级Spring至3.x;
升级ZooKeeper客户端;
支持完全基于Java代码的Dubbo配置;
Dubbox:相对于Dubbo支持了REST,估计是很多公司选择Dubbox的一个重要原因之一,但如果使用Dubbo的RPC调用方式,服务间仍然会存在API强依赖,各有利弊,懂的取舍吧。
4)Dubbo服务治理
特性 描述
透明远程调用 就像调用本地方法一样调用远程方法;只需简单配置,没有任何API侵入;
负载均衡机制 Client端LB,可在内网替代F5等硬件负载均衡器;
容错重试机制 服务Mock数据,重试次数、超时机制等;
自动注册发现 注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者;
性能日志监控 Monitor统计服务的调用次调和调用时间的监控中心;
服务治理中心 路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等手动配置。
自动治理中心 无,比如:熔断限流机制、自动权重调整等;
**5) <!-- 指定dubbo发布服务端口 -->
<dubbo:protocol name="dubbo" port="20881"></dubbo:protocol>
<!-- 指定服务名称 -->
<dubbo:application name="pyg-manager-service"/>
<!-- 指定服务注册中心地址 -->
<dubbo:registry address="zookeeper://192.168.25.128:2181"/>
<!-- 注解方式发布服务 -->
<dubbo:annotation package="com.pyg.manager.service.impl" />
6)默认使用Dubbo协议
连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO异步传输
序列化:Hessian二进制序列化
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要使用dubbo协议传输大文件或超大字符串
使用场景:常规远程服务方法调用
从上面的适用范围总结,dubbo适合小数据量大并发的服务调用,以及消费者机器远大于生产者机器数的情况,不适合传输大数据量的服务比如文件、视频等,除非请求量很低。
(2)谈一谈对集合的理解?
实现Collection接口的集合是单列集合,常用的有:set(集)、list(列表)。
*list:list中有ArrayList,LinkedList,Vector。ArrayList,Vector底层都是基于array数组的,不同的是ArrayList是不同步的,线程不安全。Vector是同步的,加synchronized锁,是线程安全的。所以在性能上ArrayList优于Vector。LinkedList不同于前面两种List,不是基于array的,它是基于Node节点的,当LinkedList增删数据时,不需要像基于Array的List一样,必须进行大量的数据移动,只需要更改nextNode就行了,这也是LinkedList的优势。
*set:
HashSet:虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是 在HashMap的基础上来实现的,这个就是Set和List的根本区别。HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。看看 HashSet的add(Object obj)方法的实现就可以一目了然了。
public boolean add(Object obj){
return map.put(obj, PRESENT) == null;
}
这个也是为什么在Set中不能像在List中一样有重复的项的根本原因,因为HashMap的key是不能有重复的。
LinkedHashSet:HashSet的一个子类,一个链表。 TreeSet:SortedSet的子类,它不同于HashSet的根本就是TreeSet是有序的。它是通过SortedMap来实现的。(HashSet和TreeSet均为有序的顺序由小到大)
实现Map接口的Map是双列集合,常用的有:HashMap、LinkedHashMap,TreeMap不常用。HashMap初始容量16,默认加载因子0.75
加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。
反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)
冲突的机会越大,则查找的成本越高.
因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.
如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。不过一般我们都不用去设置它,让它取默认值0.75就好了。
1.集合的安全性问题
1)ArrayList、HashSet、HashMap都不是线程安全。在集合中Vector(Vector是基于Array的List)和HashTable倒是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了synchronized 关键字。
(3)Object类常用的方法有哪些?
此处的Object在Java中被定义为一个顶级父类,它是任何类父类,我们可以显示的继承它,也可以隐式继承
1.与线程相关的方法
2.wait和notify
1.equals(Object obj)
2.finalize()
3.getClass
4.hashCode()
5.notify
6.notifyAll();
7.wait()
8.Clone()
9.toString()
(4)JVM的理解
JVM将内存主要划分为:方法区、虚拟机栈、本地方法栈、堆、程序计数器。
1)程序计数器:
程序计数器是线程私有的区域,很好理解嘛~,每个线程当然得有个计数器记录当前执行到那个指令。占用的内存空间小,可以把它看成是当前线程所执行的字节码的行号指示器。如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器的值为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2)java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的。其生命周期与线程相同。如何理解虚拟机栈呢?本质上来讲,就是个栈。里面存放的元素叫栈帧,栈帧好像很复杂的样子,其实它很简单!它里面存放的是一个函数的上下文,具体存放的是执行的函数的一些数据。执行的函数需要的数据无非就是局部变量表(保存函数内部的变量)、操作数栈(执行引擎计算时需要),方法出口等等。
3)本地方法栈
本地方法栈与虚拟机栈所发挥的作用很相似,他们的区别在于虚拟机栈为执行Java代码方法服务,而本地方法栈是为Native方法服务。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
4)堆
Java堆可以说是虚拟机中最大一块内存了。它是所有线程所共享的内存区域,几乎所有的实例对象都是在这块区域中存放。当然,睡着JIT编译器的发展,所有对象在堆上分配渐渐变得不那么“绝对”了。 Java堆是垃圾收集器管理的主要区域。由于现在的收集器基本上采用的都是分代收集算法,所有Java堆可以细分为:新生代和老年代。在细致分就是把新生代分为:Eden空间、From Survivor空间、To Survivor空间。当堆无法再扩展时,会抛出OutOfMemoryError异常。
5)方法区
方法区存放的是类信息、常量、静态变量等。方法区是各个线程共享区域,很容易理解,我们在写Java代码时,每个线程度可以访问同一个类的静态变量对象。由于使用反射机制的原因,虚拟机很难推测那个类信息不再使用,因此这块区域的回收很难。另外,对这块区域主要是针对常量池回收,值得注意的是JDK1.7已经把常量池转移到堆里面了。同样,当方法区无法满足内存分配需求时,会抛出OutOfMemoryError。
6)类加载机制
1)双亲委派模型图
双亲委派模型要求除了启动类加载器之外,其余的类加载器都应当有自己的父类加载器,父子关系采用组合而非继承的方式实现,以复用父加载器的代码。
*双亲委派模型的工作工程:
1.该类加载器收到了类加载的请求,
2.将这个请求委派给父类加载器完成
3.若父类加载器反馈无法完成这个加载请求(它 的搜索范围中额米有找到所需的类),子类加载器进行加载。*
2)JNDI(Java Naming and Directory Interface,Java命名和目录接口),
JNDI服务供应接口(SPI)的实现JNDI的代码是由启动类加载器去加载的,但JNDI的目的是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署的ClassPath下的JNDI接口提供者(SPI),但启动类加载器并不认识这些代码。因此不得不对双亲委派模型进行“破坏”,Java设计团队引入了线程上下文类加载器,这个类加载器可以通过java.lang.Thread类的setContextClassLoader()进行设置。若创建线程时还未设置,他将会从父线程中继承一个,若全局都没有,则默认采用应用程序类加载器。通过上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完程类加载的动作,这实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器。
3)OSGi(Open Service Gateway Initiative)技术是Java动态化模块化系统的一系列规范。
OSGI能够实现热插拔以及动态加载依靠的正是对双亲委派模型的扩展,具体逻辑如下。
收到类加载请求时,OSGi会按照下面的顺序进行搜索:
1.将以java.*开头的类委派给父类加载器加载;
2.否则,将委派类表名单内的类委派给父类加载器加载;
其余的类查找都是在平级的类加载器中进行的。
类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。 其中加载、验证、准备、初始化、和卸载这5个阶段的顺序是确定的。而解析阶段不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定。
关于初始化:JVM规范明确规定,有且只有5中情况必须执行对类的初始化(加载、验证、准备自然再此之前要发生):
1.遇到new、getstatic、putstatic、invokestatic,如果类没有初始化,则必须初始化,这几条指令分别是指:new新对象、读取静态变量、设置静态变量,调用静态函数。
2.使用java.lang.reflect包的方法对类进行反射调用时,如果类没初始化,则需要初始化
3.当初始化一个类时,如果发现父类没有初始化,则需要先触发父类初始化。
4.当虚拟机启动时,用户需要制定一个执行的主类(包含main函数的类),虚拟机会先初始化这个类。 5.但是用JDK1.7启的动态语言支持时,如果一个MethodHandle实例最后解析的结果是REF_getStatic、REF_putStatic、Ref_invokeStatic的方法句柄时,并且这个方法句柄所对应的类没有进行初始化,则要先触发其初始化。
(5)垃圾回收器gc()
GC(GarbageCollection)是垃圾回收机制,在Java中开发人员无法使用指针来自由的管理内存,GC是JVM对内存(实际上就是对象)进行管理的方式。GC使得Java开发人员摆脱了繁琐的内存管理工作,让程序的开发更有效率。
Java的内存管理实际上就是对象的管理,其中包括对象的分配和释放。
对于程序员来说,分配对象使用new关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为”不可达的”。GC将负责回收所有”不可达”对象的内存空间。
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。但是,为了保证GC能够在不同平台实现的问题,Java规范对GC的很多行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、什么时候进行回收等重要问题都没有明确的规定。因此,不同的JVM的实现者往往有不同的实现算法。
JVM的垃圾回收机制中,判断一个对象是否死亡,并不是根据是否还有对象对其有引用,而是通过可达性分析。对象之间的引用可以抽象成树形结构,通过树根(GC Roots)作为起点,从这些树根往下搜索,搜索走过的链称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明这个对象是不可用的,该对象会被判定为可回收的对象。
那么那些对象可作为GC Roots呢?主要有以下几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象。
2.方法区中类静态属性引用的对象。
3.方法区中常量引用的对象
4.本地方法栈中JNI(即一般说的Native方法)引用的对象。
另外,Java还提供了软引用和弱引用,这两个引用是可以随时被虚拟机回收的对象,我们将一些比较占内存但是又可能后面用的对象,比如Bitmap对象,可以声明为软引用或弱引用。但是注意一点,每次使用这个对象时候,需要显示判断一下是否为null,以免出错。
(6)spring框架的两个核心IOC和AOP怎么理解的?
IOC:控制反转也叫依赖注入,IOC利用java反射机制,AOP利用代理模式。所谓控制反转是指,本来被调用者的实例是有调用者来创建的,这样的缺点是耦合性太强,IOC则是统一交给spring来管理创建,将对象交给容器管理,你只需要在spring配置文件中配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。
AOP:面向切面编程。(Aspect-Oriented Programming)
AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。
aop框架具有的两个特征:
1.各个步骤之间的良好隔离性
2.源代码无关性
spring 框架的优点是一个轻量级比较简单易学的框架,实际使用中的有点优点有哪些呢!
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
(7)mvc
MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。
整个MVC的流程就是:用户发送一个请求,首先由控制器接收,控制器根据请求去调用哪个业务逻辑层处理并返回数据,接着控制层调用相应的视图层返回给用户。MVC设计模式的优点就是让代码之间耦合性降低,有很高的重用性,部署快速等。缺点:不适合小型,中等规模的应用程序、增加系统结构和实现的复杂性、没有明确的定义、
视图对模型数据的低效率访问。
(8)sleep和wait方法的区别:
① 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
③ 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
synchronized(x){
x.notify()
//或者wait()
}
(9)熟悉的设计模式
1.创建型模式(五种):工厂方法模式,抽象工厂模式,单例模式,建造者模式
2.结构型模式(七种):适配器模式,代理模式
3.行为型模式(十一种):策略模式,观察者模式
①单例模式:
分为懒汉式和饿汉式和登记式,适用于一个类只有一个实例的情况
1.懒汉式:会在第一次调用方法的时候会做一个判断,如果实例不存在,则创建一个,如果实例存在,则直接返回。
2.饿汉式:在程序启动或单件模式类被加载的时候,单件模式实例就已经被创建。 如何选择:如果说懒汉式是时间换空间,那么饿汉式就是空间换时间。因此如果单例模式实例在系统中经常会被用到,饿汉式是一个不错的选择。反之如果单例模式在系统中会很少用到或者几乎不会用到,那么懒汉式是一个不错的选择。
②工厂模式:
1.概述:属于创建型设计模式,需要生成的对象叫做产品 ,生成对象的地方叫做工厂 。
2.使用场景:在任何需要生成复杂对象的地方,都可以使用工厂方法模式。直接用new可以完成的不需要用工厂模式
一句话总结工厂模式:方便创建 同种产品类型的 复杂参数 对象
工厂模式重点就是适用于 构建同产品类型(同一个接口 基类)的不同对象时,这些对象new很复杂,需要很多的参数,而这些参数中大部分都是固定的,so,懒惰的程序员便用工厂模式封装之。
(如果构建某个对象很复杂,需要很多参数,但这些参数大部分都是“不固定”的,应该使用Builder模式)
为了适应程序的扩展性,拥抱变化,便衍生出了 普通工厂、抽象工厂等模式。
3.分类:
3.1简单(静态)工厂
举个例子:
–我喜欢吃面条,抽象一个面条基类,(接口也可以),这是产品的抽象类。
–先来一份兰州拉面(具体的产品类)
–程序员加班必备也要吃泡面(具体的产品类):
–准备工作做完了,我们来到一家“简单面馆”(简单工厂类),简单面馆就提供两种面条(产品),你说你要啥,他就给你啥。如果点1,面馆给你做兰州拉面,如果点2,面馆做泡面。
特点:
1 它是一个具体的类,非接口抽象类。有一个重要的create()方法,利用if或者 switch创建产品并返回。
2 create()方法通常是静态的,所以也称之为静态工厂。
缺点:
1 扩展性差(我想增加一种面条,除了新增一个面条产品类,还需要修改工厂类方法)
2 不同的产品需要不同额外参数的时候 不支持。
3.2另一种简单工厂(反射):就是将上面的静态工厂,利用反射获取对象,个人觉得不是很好,因为这样和简单的new一个对象一样,工厂方法应该用于复杂对象的初始化,这样像为了工厂而工厂。
3.3多方法工厂:
使用方法1,2实现的工厂,都有一个缺点:不同的产品需要不同额外参数的时候不支持。而且如果使用时传递的type、Class出错,将不能得到正确的对象,容错率不高。而多方法的工厂模式为不同产品,提供不同的生产方法,使用时 需要哪种产品就调用该种产品的方法,使用方便、容错率高。
工厂如下:
public class MulWayNoodlesFactory {
/**
* 模仿Executors 类
* 生产泡面
*
* @return
*/
public static INoodles createPm() {
return new PaoNoodles();
}
/**
* 模仿Executors 类
* 生产兰州拉面
*
* @return
*/
public static INoodles createLz() {
return new LzNoodles();
}
/**
* 模仿Executors 类
* 生产干扣面
*
* @return
*/
public static INoodles createGk() {
return new GankouNoodles();
}
}
使用时:
/**
* 多方法静态工厂(模仿Executor类)
*/
System.out.println("==============================模仿Executor类==============================" +
"\n 这种我比较青睐,增加一个新面条,只要去增加一个static方法即可,也不修改原方法逻辑");
INoodles lz2 = MulWayNoodlesFactory.createLz();
lz2.desc();
INoodles gk2 = MulWayNoodlesFactory.createGk();
gk2.desc();
输出:
==============================模仿Executor类==============================
这种我比较青睐,增加一个新面条,只要去增加一个static方法即可,也不修改原方法逻辑
兰州拉面 上海的好贵 家里才5 6块钱一碗
还是家里的干扣面好吃 6块一碗
源码撑腰环节
查看java源码:java.util.concurrent.Executors类便是一个生成Executor 的工厂 ,其采用的便是 多方法静态工厂模式:
例如ThreadPoolExecutor类构造方法有5个参数,其中三个参数写法固定,前两个参数可配置,如下写。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
③适配器模式:
1.类适配器模式:Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定局限性; 且src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
但同样由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
2.对象适配器模式:
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,组合大于继承,所以它解决了类适配器必须继承src的局限性问题,也不再强求dst必须是接口。 同样的它使用成本更低,更灵活。
(和装饰者模式初学时可能会弄混,这里要搞清,装饰者是对src的装饰,使用者毫无察觉到src已经被装饰了(使用者用法不变)。 这里对象适配以后,使用者的用法还是变的。
即,装饰者用法: setSrc->setSrc,对象适配器用法:setSrc->setAdapter.)
3.接口适配器模式:
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承src类,而是持有src类的实例,以解决兼容性的问题。 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。
(10)JSP有哪些内置对象?作用分别是什么?
JSP有9个内置对象:
- request:封装客户端的请求,其中包含来自GET或POST请求的参数;
- response:封装服务器对客户端的响应;
- pageContext:通过该对象可以获取其他对象;
- session:封装用户会话的对象;
- application:封装服务器运行环境的对象;
- out:输出服务器响应的输出流对象;
- config:Web应用的配置对象;
- page:JSP页面本身(相当于Java程序中的this);
- exception:封装页面抛出异常的对象。
(11)线程池的使用
引入线程池的原因:
由于线程的生命周期中包括,创建-就绪-运行-阻塞-挂机-结束 阶段,当我们处理的任务数目比较小的时候,我们可以自己创建几个线程来处理相应的任务,但是有大量的任务时,由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题。
线程池的使用:
Executor类给我们提供了的静态方法,就可以创建相应的线程池:
[java] view plain copy
<span style="font-size:18px;">public static executorService newSigleExecutor();
public static executorService newFixedThredPool();
public static executorService newCachedThreadPool();</span>
newSignalExecutor()返回一个包含单线程的Executor,将多个任务交给Executor时,这个线程处理完一个任务后会接着处理下一个任务,若该线程出现异常,将会有一个新的线程来替代它
newFixedThreadPool()返回一个包含指定数目线程数的线程池,如果任务数量多于线程数目的话,那么没有执行的任务必须等待,直到任务完成为止
newCachedThreadPool()根据用户的任务数目创建相应的线程来处理,该线程池不会对线程的数目加以限制,完全依赖于虚拟机能创建的线程输入,但是可能会引起内存不足。
我们只需要将执行的任务放入run方法中即可,将runable接口的实现类交给线程池的execute方法,作为它的一个参数
(12)tomacat,jvm优化
(13)线程并发问题:valitile:加了volatile修饰符的变量则是直接读写主存。
一旦一个共享变量(类的成员变量、类的静态成员变量)被valitile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作是的可见性,即一个线程修改了某个变量的的,新的值对其它线程来说是立即可见的。
2)禁止进行指令重排序。 指令重排是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。程序执行到volatile修饰变量的读操作或者写操作时,在其前面的操作肯定已经完成,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
volitile本质实在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
1)volatile仅能使用在变量级别;
synchronized则可以使用在变量、方法、和类级别
2)volatile仅能实现变量的修改可见性,并不能保证原子性;
synchronized则可以保证变量的修改可见性和原子性
3)volatile不会造成线程的阻塞;
synchronized则可能会造成线程的阻塞
4)volatile标记的变量不会被编译器优化;
synchronized标记的变量可以被编译器优化
(14)threadLoacal
1)ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
2)ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
1.void set(Object value)
设置当前线程的线程局部变量的值。
2.public Object get()
该方法返回当前线程所对应的线程局部变量。
3.public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
4.protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。这样就不要进行类型的强转了。
3)ThreadLocal是如何做到为每一个线程维护变量的副本的呢?
在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
4)Thread同步机制的比较
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
Spring使用ThreadLocal解决线程安全问题
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
(15)事务
1.事务特性
ACID
原子性:强调事务的不可分割,要么全部成功,要么全部失败
一致性:事务的执行的前后,数据的完整性保持一致
持久性:一旦事务提交了或者回滚了.都要将这些操作持久化到数据库中.
隔离性:一个事务的执行不要受其他事务影响.
2.不考虑隔离性会出现的读问题:
脏读:一个事务读取到了另一个事务没有提交的数据
不可重复读:在一个事务中两次查询的结果不一致(针对于update操作)
虚读(幻读):在一个事务中两次查询的结果不一致(针对于insert操作)
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
1 Read uncommitted 读未提交:最低级别,任何情况都无法保证。
2 Read committed 读已提交(Oracle):可避免脏读的发生。
3 Repeatable read 可重复读(MySQL):可避免脏读、不可重复读的发生。
4 Serializable 串行化 没有并发问题:可避免脏读、不可重复读、幻读的发生。
(16)mysql索引:hash,Btree
(18)读写分离:程序里面如何实现读写分离?
(19)Redis
spring-data-redis把jedis融入到spring中,无缝整合到spring中,对jedis中的方法(jedisCluster.命令)进行了增强,通过spring-data-redis访问redis服务器,底层还是jedis。
1)redisTemplate:所有的redis对象都封装在redisTemplate模版中了,模版提供了各种操作,异常处理以及序列化,支持发布订阅。
2)spring-data-redis针对jedis提供了如下功能:
1.连接池自动管理,提供了一个高度封装的RedisTemplate类
2.针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据进行操作
ListOperations:针对list类型的数据进行操作
boundValueOps、boundHashOps(“index_cache”).delete(“1”);
1)数据类型
2)常用命令:set,get,del,incr(递增),decr(递减)
3)单线程?多线程?
4)setNX命令:set if Not exists
setNX可以用来实现分布式锁
setNX key value,将key的值设为value,当且仅当key不存在
5)redis持久化方式
redis支持四种持久化方式,一是 Snapshotting(快照)也是默认方式;二是Append-only file(缩写aof)的方式;三是虚拟内存方式;四是diskstore方式。
6)redis事务
Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC命令表示事务命令结束。MULTI和EXEC中的命令是一起执行的,并不能将其中一条命令的执行结果作为另一条命令的执行参数,所以这个时候就需要引进Redis事务家族中的另一成员:WATCH命令。
7)redis并发问题: Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:
1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
2.服务器角度,利用setnx实现锁。 对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。
(20)负载策略:
1.加权轮询: 第一,如果可以把加权轮询算法分为先深搜索和先广搜索,那么nginx采用的是先深搜索算法,即将首先将请求都分给高权重的机器,直到该机器的权值降到了比其他机器低,才开始将请求分给下一个高权重的机器。
第二,当所有后端机器都down掉时,nginx会立即将所有机器的标志位清成初始状态,以避免造成所有的机器都处在timeout的状态,从而导致整个前端被夯住。
2.ip hash策略: hash值既与ip有关又与后端机器的数量有关。经测试,上述算法可以连续产生1045个互异的value,这是此算法硬限制。nginx使用了保护机制,当经过20次hash仍然找不到可用的机器时,算法退化成轮询。
3.fair
fair策略是扩展策略,默认不被编译进nginx内核。它根据后端服务器的响应时间判断负载情况,从中选出负载最轻的机器进行分流。
这种策略具有很强的自适应性,但是实际的网络环境往往不是那么简单,因此须慎用。
4.通用hash、一致性hash
通用hash和一致性hash也是种扩展策略。通用hash可以以nginx内置的变量为key进行hash,一致性hash采用了nginx内置的一致性hash环,可支持memcache。
(21)tomacat:session共享
可以在tomcat里面做一些配置,使用filter方法存储,使用tomcat sessionmanager方法存储,会暂用IO资源,可以存在redis里面。
(22)zookeeper上面的节点类型
1)持久节点:创建之后就一直存在,直到有删除操作来主动清除这个节点
2)持久顺序节点:在持久节点的基础上,每个父节点为他的第一级子节点维护一份时序,记录每个子节点创建的先后顺序。
3)临时节点:临时节点的生命周期和客户端会话绑定,如果客户端会话失败,这个节点也会自动清除。
4)临时顺序节点:可以用来实现分布式锁
(23)solr深分页
1.长期以来,我们一直有一个深分页问题。如果直接跳到很靠后的页数,查询速度会比较慢。这是因为Solr的需要为查询从开始遍历所有数据。
2.solr4.7是怎么解决这个问题的?
1)Solr4.7的发布改变了这一状况,引入了游标的概念。游标是一个动态结构,不需要存储在服务器上。游标包含了查询的结果的偏移量,因此,Solr的不再需要每次从头开始遍历结果直到我们想要的记录,游标的功能可以大幅提升深翻页的性能。
2)用法:
游标的使用非常简单。在第一个查询中,我们需要传递一个额外的参数-cursorMark=*,告诉Solr返回游标。在返回中除了搜索结果,我们还可以得到nextCursorMark信息。除了平时返回的结果外,还多了一个游标数据nextCursorMark,使用这个值作为我们翻下一页的参数。在这个基础上要得到下一页数据怎么办:让cursorMark的值等于上次返回的nextCursorMark。
(24)lucene:倒排索引
什么是倒排索引呢?索引我们都知道,就是为了能更快的找到文档的数据结构,比如给文档编个号,那么通过这个号就可以很快的找到某一篇文档,而倒排索引不是根据文档编号,而是通过文档中的某些个词而找到文档的索引结构。
(25)MQ:保证顺序消费
ActiveMQ面试专题
ActiveMQ服务器宕机怎么办?
这得从ActiveMQ的储存机制说起。在通常的情况下,非持久化消息是存储在内存中的,持久化消息是存储在文件中的,它们的最大限制在配置文件的节点中配置。但是,在非持久化消息堆积到一定程度,内存告急的时候,ActiveMQ会将内存中的非持久化消息写入临时文件中,以腾出内存。虽然都保存到了文件里,但它和持久化消息的区别是,重启后持久化消息会从文件中恢复,非持久化的临时文件会直接删除。那如果文件增大到达了配置中的最大限制的时候会发生什么?我做了以下实验: 设置2G左右的持久化文件限制,大量生产持久化消息直到文件达到最大限制,此时生产者阻塞,但消费者可正常连接并消费消息,等消息消费掉一部分,文件删除又腾出空间之后,生产者又可继续发送消息,服务自动恢复正常。 设置2G左右的临时文件限制,大量生产非持久化消息并写入临时文件,在达到最大限制时,生产者阻塞,消费者可正常连接但不能消费消息,或者原本慢速消费的消费者,消费突然停止。整个系统可连接,但是无法提供服务,就这样挂了。 具体原因不详,解决方案:尽量不要用非持久化消息,非要用的话,将临时文件限制尽可能的调大。
丢消息怎么办?
这得从java的java.net.SocketException异常说起。简单点说就是当网络发送方发送一堆数据,然后调用close关闭连接之后。这些发送的数据都在接收者的缓存里,接收者如果调用read方法仍旧能从缓存中读取这些数据,尽管对方已经关闭了连接。但是当接收者尝试发送数据时,由于此时连接已关闭,所以会发生异常,这个很好理解。不过需要注意的是,当发生SocketException后,原本缓存区中数据也作废了,此时接收者再次调用read方法去读取缓存中的数据,就会报Software caused connection abort: recv failed错误。
通过抓包得知,ActiveMQ会每隔10秒发送一个心跳包,这个心跳包是服务器发送给客户端的,用来判断客户端死没死。如果你看过上面第一条,就会知道非持久化消息堆积到一定程度会写到文件里,这个写的过程会阻塞所有动作,而且会持续20到30秒,并且随着内存的增大而增大。当客户端发完消息调用connection.close()时,会期待服务器对于关闭连接的回答,如果超过15秒没回答就直接调用socket层的close关闭tcp连接了。这时客户端发出的消息其实还在服务器的缓存里等待处理,不过由于服务器心跳包的设置,导致发生了java.net.SocketException异常,把缓存里的数据作废了,没处理的消息全部丢失。
解决方案:用持久化消息,或者非持久化消息及时处理不要堆积,或者启动事务,启动事务后,commit()方法会负责任的等待服务器的返回,也就不会关闭连接导致消息丢失了。持久化消息非常慢。
默认的情况下,非持久化的消息是异步发送的,持久化的消息是同步发送的,遇到慢一点的硬盘,发送消息的速度是无法忍受的。但是在开启事务的情况下,消息都是异步发送的,效率会有2个数量级的提升。所以在发送持久化消息时,请务必开启事务模式。其实发送非持久化消息时也建议开启事务,因为根本不会影响性能。消息的不均匀消费。
有时在发送一些消息之后,开启2个消费者去处理消息。会发现一个消费者处理了所有的消息,另一个消费者根本没收到消息。原因在于ActiveMQ的prefetch机制。当消费者去获取消息时,不会一条一条去获取,而是一次性获取一批,默认是1000条。这些预获取的消息,在还没确认消费之前,在管理控制台还是可以看见这些消息的,但是不会再分配给其他消费者,此时这些消息的状态应该算作“已分配未消费”,如果消息最后被消费,则会在服务器端被删除,如果消费者崩溃,则这些消息会被重新分配给新的消费者。但是如果消费者既不消费确认,又不崩溃,那这些消息就永远躺在消费者的缓存区里无法处理。更通常的情况是,消费这些消息非常耗时,你开了10个消费者去处理,结果发现只有一台机器吭哧吭哧处理,另外9台啥事不干。解决方案:将prefetch设为1,每次处理1条消息,处理完再去取,这样也慢不了多少。
死信队列。
如果你想在消息处理失败后,不被服务器删除,还能被其他消费者处理或重试,可以关闭AUTO_ACKNOWLEDGE,将ack交由程序自己处理。那如果使用了AUTO_ACKNOWLEDGE,消息是什么时候被确认的,还有没有阻止消息确认的方法?有!
消费消息有2种方法,一种是调用consumer.receive()方法,该方法将阻塞直到获得并返回一条消息。这种情况下,消息返回给方法调用者之后就自动被确认了。另一种方法是采用listener回调函数,在有消息到达时,会调用listener接口的onMessage方法。在这种情况下,在onMessage方法执行完毕后,消息才会被确认,此时只要在方法中抛出异常,该消息就不会被确认。那么问题来了,如果一条消息不能被处理,会被退回服务器重新分配,如果只有一个消费者,该消息又会重新被获取,重新抛异常。就算有多个消费者,往往在一个服务器上不能处理的消息,在另外的服务器上依然不能被处理。难道就这么退回–获取–报错死循环了吗?在重试6次后,ActiveMQ认为这条消息是“有毒”的,将会把消息丢到死信队列里。如果你的消息不见了,去ActiveMQ.DLQ里找找,说不定就躺在那里。 6. ActiveMQ中的消息重发时间间隔和重发次数吗? ActiveMQ:是Apache出品,最流行的,能力强劲的开源消息总线。是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现。JMS(Java消息服务):是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。 首先,我们得大概了解下,在哪些情况下,ActiveMQ服务器会将消息重发给消费者,这里为简单起见,假定采用的消息发送模式为队列(即消息发送者和消息接收者)。 ① 如果消息接收者在处理完一条消息的处理过程后没有对MOM进行应答,则该消息将由MOM重发. ② 如果我们队某个队列设置了预读参数(consumer.prefetchSize),如果消息接收者在处理第一条消息时(没向MOM发送消息接收确认)就宕机了,则预读数量的所有消息都将被重发! ③ 如果Session是事务的,则只要消息接收者有一条消息没有确认,或发送消息期间MOM或客户端某一方突然宕机了,则该事务范围中的所有消息MOM都将重 ④ 说到这里,大家可能会有疑问,ActiveMQ消息服务器怎么知道消费者客户端到底是消息正在处理中还没来得急对消息进行应答还是已经处理完成了没有应答或是宕机了根本没机会应答呢?其实在所有的客户端机器上,内存中都运行着一套客户端的ActiveMQ环境,该环境负责缓存发来的消息,负责维持着和ActiveMQ服务器的消息通讯,负责失效转移(fail-over)等,所有的判断和处理都是由这套客户端环境来完成的。 我们可以来对ActiveMQ的重发策略(Redelivery Policy)来进行自定义配置,其中的配置参数主要有以下几个可用的属性: 属性 默认值 说明 --collisionAvoidanceFactor 默认值0.15 , 设置防止冲突范围的正负百分比,只有启用useCollisionAvoidance参数时才生效。 --maximumRedeliveries 默认值6 , 最大重传次数,达到最大重连次数后抛出异常。为-1时不限制次数,为0时表示不进行重传。 --maximumRedeliveryDelay 默认值-1, 最大传送延迟,只在useExponentialBackOff为true时有效(V5.5),假设首次重连间隔为10ms,倍数为2,那么第二次重连时间间隔为 20ms,第三次重连时间间隔为40ms,当重连时间间隔大的最大重连时间间隔时,以后每次重连时间间隔都为最大重连时间间隔。 --initialRedeliveryDelay 默认值1000L, 初始重发延迟时间 --redeliveryDelay 默认值1000L, 重发延迟时间,当initialRedeliveryDelay=0时生效(v5.4) --useCollisionAvoidance 默认值false, 启用防止冲突功能,因为消息接收时是可以使用多线程并发处理的,应该是为了重发的安全性,避开所有并发线程都在同一个时间点进行消息接收处理。所有线程在同一个时间点处理时会发生什么问题呢?应该没有问题,只是为了平衡broker处理性能,不会有时很忙,有时很空闲。 --useExponentialBackOff 默认值false, 启用指数倍数递增的方式增加延迟时间。 --backOffMultiplier 默认值5, 重连时间间隔递增倍数,只有值大于1和启用useExponentialBackOff参数时才生效。
(26)分布式锁
1.数据库实现分布锁:两种方式都是依赖数据库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。
1)基于数据库排他锁实现分布式的锁:基于MySql的InnoDB引擎,在查询语句后面增加forupdate,数据库会在查询过程中给数据库表增加排他锁,InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。
2)基于数据库表:要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。
2.基于缓存实现分布式锁
使用缓存来代替数据库来实现分布式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。并且很多缓存服务都提供了可以用来实现分布式锁的方法,比如Tair的put方法,redis的setnx方法等。并且,这些缓存服务也都提供了对数据的过期自动删除的支持,可以直接设置超时时间来控制锁的释放。
3.基于Zookeeper实现分布式锁:基于zookeeper临时有序节点可以实现的分布式锁。
每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
(27)什么是死锁?如何避免死锁?
死锁是指两个以上的线程永远阻塞的情况,这种情况产生至少需要两个以上的线程和两个以上的资源。
1.加锁顺序
2.加锁时限
3.死锁检测,它是一种更好的死锁预防机制。
(28)常见的数据结构和算法:
1.数据结构:
1)线性表:向量(vector)、列表(list)等顺序容器
2)栈、队列和串:
栈:后进先出
队列:它只允许在一端插入,另一端删除 串:串也称字符串,是由字符构成的有限序列。串中任意个连续字符构成的串称为子串,原串称为主串。前缀子串指第一个字符到某个字符构成的子串,后缀子串是某个字符到最后一个字符构成的子串。
3)树(二叉树、红黑树等):
①二叉树遍历
先序遍历:遍历顺序规则为【根左右】
中序遍历:遍历顺序规则为【左根右】
后序遍历:遍历顺序规则为【左右根】
4)数组:
5)图:
2.算法:
1)二分查找
2)冒泡排序
3)选择排序
(29)常见的运行时异常跟编译时异常
1.ClassNotFoundException、NullPointerException、IllegalArgumentException、UnkownTypeException
2.编译异常:IOException、SQLException等以及用户自定义的Exception异常
(30)ajax原理
Ajax的工作原理相当于在用户和服务器之间加了—个中间层,使用户操作与服务器响应异步化。
1)Js中的ajax使用:
i. 开发步骤:
1. 获取XMLHttpRequest对象(XMLHttpRequest)
2. 确定请求方式和路径
3. 发送请求
4. 编写回调函数
ii. api:
1. 获取XMLHttpRequest对象:
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}else{// code for IE6, IE5
xmlhttp=new ActiveXObject(“Microsoft.XMLHTTP”);
}
2. 常用方法:
a. open(请求的方式,请求的路径,[是否异步]) 确定请求的方式和路径
b. send([post请求的参数]) 发送请求
3. 常用属性:
a. onreadystatechange:监测XMLHttpRequest对象状态的变化
b. readyState:XMLHttpRequest对象状态
i. 0.对象已创建
ii. 1.对象调用了open
iii. 2.对象调用了send
iv. 3.部分已完成
v. 4.响应已完成
c. status:响应状态码
i. 200: “OK”
ii. 404: 未找到页面
d. responseText: 响应回来的内容
i. xmlhttp.onreadystatechange = function(){}
4. 注意:若post请求需要发送参数,必须设置请求参数的mime类型
i. xmlhttp.setRequestHeader(“content-type”, form表单的enctype属性的默认值);
ii. 如:xmlhttp.setRequestHeader(“content-type”, “application/x-www-form-urlencoded”);
2)$.ajax(选项)
a. url:请求路径
b. type:请求方式 默认get
c. data:请求参数
d. success:成功之后的回调 值为 function(obj){}
e. error:错误之后的回调 值为function(){}
f. dataType:返回数据的格式,常用的值 : 默认值 或者 "json"
g. async:是否异步 默认异步 true
(31)session与cookie是如何交互的?
**用户首次访问服务器,服务器会为每个用户单独创建一个session对象(HttpSession),并为每个session分配唯一一个id(sessionId),sessionId通过cookie保存到用户端。当用户再次访问服务器时,需将对应的sessionId携带给服务器,服务器通过这个唯一sessionId就可以找到用户对应的session对象,从而达到管理用户状态。
1)cookie是如何加到Http的Header中的呢?
1.HttpServletResponse调用addCookie
2.ResponseFacade调用addCookie
3.Response调用addCookieInternal方法
4.generateCookieString
5.addHeader
6.MimeHeaders调用headers.addValue(name).setString(value)设置值
2)session常用API
存:session.setAttribute(key,value);
取:session.getAttribute(key);
删除:session.removeAttribute(key);
(32)quartz实现定时任务调度
1.job:表示一个任务,要执行的具体内容
2.jobDetail:表示一个具体的可执行的调度程序
3.Trigger:定时器,代表一个调度参数的配置,什么时候去调。
cron表达式:
* * * * * ? *
秒 分 时 日 月 周 年
4.Scheduler:总调度容器,可以注册多个JobDetail和Trigger
(33)servlet生命周期
用户第一次访问服务器时,servlet执行一次init和service方法,完成创建和服务操作,
然后在不关闭服务器和浏览器的情况下每一次刷新页面,也就是向服务器发送一次请求,servlet就执行一次service方法,
所以说servlet是单实例,多线程。
当servlet被移除或服务器正常关闭的时候servlet执行销毁方法。
(34)hash冲突怎么解决?
虽然我们不希望发生冲突,但实际上发生冲突的可能性仍是存在的。当关键字值域远大于哈希表的长度,而且事先并不知道关键字的具体取值时。冲突就难免会发 生。另外,当关键字的实际取值大于哈希表的长度时,而且表中已装满了记录,如果插入一个新记录,不仅发生冲突,而且还会发生溢出。因此,处理冲突和溢出是 哈希技术中的两个重要问题。
1.HashMap位置决定与存储
通过前面的源码分析可知,HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。当程序执行put(String,Obect)方法 时,系统将调用String的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。
我们知道Entry含有的属性是Value,Key,还有一只指向下一个指针Next。当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。
2.Hash碰撞产生及解决 Hashmap里面的bucket出现了单链表的形式,散列表要解决的一个问题就是散列值的冲突问题,通常是两种方法:链表法和开放地址法。链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。java.util.HashMap采用的链表法的方式,链表是单向链表。
源码中包含了一个设计:系统总是将新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处——如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链),如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序代码的 e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链。 HashMap里面没有出现hash冲突时,没有形成单链表时,hashmap查找元素很快,get()方法能够直接定位到元素,但是出现单链表后,单个bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。 通过上面可知如果多个hashCode()的值落到同一个桶内的时候,这些值是存储到一个链表中的。最坏的情况下,所有的key都映射到同一个桶中,这样hashmap就退化成了一个链表——查找时间从O(1)到O(n)。也就是说我们是通过链表的方式来解决这个Hash碰撞问题的。
3.Java8碰撞优化提升 为什么会有这么大的性能提升,尽管这里用的是大O符号(大O描述的是渐近上界)?其实这个优化在JEP-180中已经提到了。如果某个桶中的记录过大的话(当前是TREEIFY_THRESHOLD=8),HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n)。它是如何工作的?前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的,不过如果实现了当然最好。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就并别指望能获得性能提升了。这个性能提升有什么用处?比方说恶意的程序,如果它知道我们用的是哈希算法,它可能会发送大量的请求,导致产生严重的哈希碰撞。然后不停的访问这些key就能显著的影响服务器的性能,这样就形成了一次拒绝服务攻击(DoS)。JDK8中从O(n)到O(logn)的飞跃,可以有效地防止类似的攻击,同时也让HashMap性能的可预测性稍微增强了一些。
(35)为什么用angularJS?
1)更少的代码,实现更加强劲的功能
2)AngularJS非常全面
似 Backbone 或者 JavaScriptMVC,anguar是一个快速的前端开发解决方案。没有其它的插件或者架构足以开发数据驱动的web应用。下面列出了AnguarJS的一些特性:
方便的REST: RESTful逐渐成为了标准的服务器和客户端沟通的方式。使用一行javascript代码,你就可以快速的从服务器端得到数据。AugularJS将这些变成了JS对象,作为Model,遵循MVVM(model view view-model)设计模式。
MVVM救星:Model将和ViewModel互动(通过$scope对象),将监听Model的变化。这些可以通过View来发送和渲染,由HTML来展示你的代码。View可以通过$routeProvider对象来支配,所以你可以深度的链接和组织你的View和Cont roller,将他们变成导航URL。AngualrJS同时提供了无状态的Controller,可以用来初始化和控制$scope对象。
数据绑定和依赖注入:在MVVM设计模式中的任何东西无论发生任何事情都自动的和UI通信。这帮助我们去除了wrapper,getter/setter方法或者class定义。AngularJS将帮助我们处理所有的这些内容,所以你可以处理数据像处理基本javascript数据类型,例如,数组一样简单。当然你也可以通过自定义处理复杂数据。正因为所有事情的发生都是自动的,所以你不必调用一个main()来执行你的代码,而是通过依赖关系来驱动。
可扩展的HTML:大多数的网站都是使用非语义的<div>标签来搭建的。你需要自己在CSS的class中定义相关的DOM层次结构。而使用AngularJS,你可以操作XML一样操作HTML,给你无穷的方式来完成标签和属性定义。AngularJS通过自己的编译器和directives来完成相关的设置。
使用HTML模板:如果你曾经使用过Mustache , Hogan.js,或者handlerbars的话,你就可以快速的理解AngularJS的模板引擎语法,应为它是纯HTML的。AngularJS通过DOM浏览来完成此类功能,使用上面提到的directives。模板被作为DOM元素传递到Angular的编译器中,可以被扩展,执行或者重用。这很关键,这样一来你就拥有了DOM组件,而非字符串,允许你直接的操作扩展DOM树。
企业级别的测试:AnguarJS并不依赖于第三方的插件或者是框架,包括测试。如果你熟悉QUnit, Mocha 或者 Jasmine的话,那么对于理解Angular的单元测试和Scenario Runner来说就非常简单。
以上的这些基本的原则能够帮助知道你使用Angular来创建高效性能可维护的代码。只要你有代码保存数据,AnguarJS会帮助你处理所有的重量级内容,提供一个富客户端的超棒体验!
(36)选择使用Spring框架的原因(Spring框架为企业级开发带来的好处有哪些)?
- 非侵入式:支持基于POJO的编程模式,不强制性的要求实现Spring框架中的接口或继承Spring框架中的类。
- IoC容器:IoC容器帮助应用程序管理对象以及对象之间的依赖关系,对象之间的依赖关系如果发生了改变只需要修改配置文件而不是修改代码,因为代码的修改可能意味着项目的重新构建和完整的回归测试。有了IoC容器,程序员再也不需要自己编写工厂、单例,这一点特别符合Spring的精神"不要重复的发明轮子"。
- AOP(面向切面编程):将所有的横切关注功能封装到切面(aspect)中,通过配置的方式将横切关注功能动态添加到目标代码上,进一步实现了业务逻辑和系统服务之间的分离。另一方面,有了AOP程序员可以省去很多自己写代理类的工作。
- MVC:Spring的MVC框架是非常优秀的,从各个方面都可以甩Struts 2几条街,为Web表示层提供了更好的解决方案。
- 事务管理:Spring以宽广的胸怀接纳多种持久层技术,并且为其提供了声明式的事务管理,在不需要任何一行代码的情况下就能够完成事务管理。
- 其他:选择Spring框架的原因还远不止于此,Spring为Java企业级开发提供了一站式选择,你可以在需要的时候使用它的部分和全部,更重要的是,你甚至可以在感觉不到Spring存在的情况下,在你的项目中使用Spring提供的各种优秀的功能。
(37)Springmvc就是spring框架的一个模块,所以它可以和spring框架可以进行无缝整合,它是一个基于mvc设计思想的前端web框架,主要作用就是对前端请求进行处理。他的 前端控制器是一个servlet.它的请求拦截是基于方法级别的.他的执行流程是:
Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成
1. 客户端请求提交到DispatcherServlet
2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller
3. DispatcherServlet将请求提交到Controller
4. Controller调用业务逻辑处理后,返回ModelAndView
5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
6. 视图负责将结果显示到客户端
(38)oracal存储函数
存储过程和存储函数都是为了完成特定功能的sql语句集。
存储过程和存储函数的区别:
***1.存储函数可以在select查询语句调用,select ename,fun_emp_dname(deptno) from emp,因为存储函数有一个return返回值;而存储过程不可以。
2.定义的语法不一样procedure,function
3.存储过程可以通过out类型来返回参数
1.存储过程:
声明pro_add_sal存储过程,作用是给指定员工涨1000工资,并打印出涨前和涨后工资
create or replace procedure pro_add_sal(pno in number)
as
totalSal number;
begin
select sal into totalSal from emp where empno = pno; –查询工资并赋值给totalSal
dbms_output.put_line(totalSal);
update emp set sal = sal + 1000 where empno = pno;
dbms_output.put_line(totalSal + 1000);
commit;
end;
begin
pro_add_sal(7499);
end;
2.存储函数:
定义:保存一段可执行的sql语句,方便开发调用过程名
语法:
create [or replace] function 方法名(参数 in|out 类型) return 参数类型
as
定义变量名 类型要和return返回类型一致
begin
return 变量名;
end;
1)声明fun_emp_totalsal存储函数,查询指定员工的年薪
create or replace function fun_emp_totalsal(pno number) return number--in 可以忽略 out 不能忽略
as
totalsal number; --和return类型一致
begin
select (sal*12)+nvl(comm,0) into totalsal from emp where empno = pno;
return totalsal;
end;
declare
totalsal number;
begin
totalsal := fun_emp_totalsal(7499);
dbms_output.put_line(totalsal);
end;
2)在select 调用存储函数
select ename,fun_emp_dname(deptno) from emp;
3.java程序调用存储函数:
CallableStatement callSt = connection.prepareCall("{?(?:返回值) = 存储函数(参数)}");
callSt.registerOutParameter(2,OracleTypes.NUMBER);//存储过程的:注册返回值类型
(39)hashmap底层原理:
哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。首先HashMap里面实现一个静态内部类Entry,其重要的属性有key,value,next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。
1)如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?
数组下标相同,并不表示hashCode相同。如果得到的index相同,那么使用equels方法比较,如果还相同,就会有覆盖的危险。
当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因子,随着map的size越来越大,Entry[]会以一定的规则加长长度。
2)null key总是存放在Entry[]数组的第一个元素。
3)table初始大小并不是构造函数中的initialCapacity!!而是 >= initialCapacity的2的n次幂!!!!
4)解决hash冲突的办法
1.开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
2.再哈希法
3.链地址法:拉链法处理冲突的办法是:把具有相同散列地址的关键字(同义词)值放在同一个单链表中,称为同义词链表。
4.建立一个公共溢出区
(40)应用程序如何使用shiro框架
***shiro核心控制器:DelegatingFilterProxy
产生shiro控制器的方式,使用cglib生成代理。
1.应用程序application Code从shiro中那东西就找subject
2.shiro中有三个组件:subject、shiroSecurityManager服务我们,但都不能办实事,最后交给Realm
subject:相当于银行的柜员
shiroSecurityManager:银行经理
Realm:认证、授权
3.shiro整体分析
①用户在登录的时,将username、password传递给表现层
②表现层action获取到参数时,login()方法实现登录,得到subject,UsernamePasswordToken对象封装用户的请求参数 ③调用subject中login(usernamePasswordToken)到Shiro的AuthRealm域中进行授权、认证,返回SimpleAuthenticationInfo(user,user.getPassword(),this.getName())对象
④调用密码比较器CustomCredentialsMatcher,将加密后的密码与数据库中的用户密码进行比较,如果一直就成功,失败抛异常。
⑤成功,User user = (User)subject.getPrinciple();获取到用户,立即加载数据,将用户信息放入到session域中
⑥跳转页面,到系统首页。
(41)项目中多线程用到什么?
1.后台线程:比如定期执行一些特殊任务,如定期更新配置文件,任务调度(如quartz),一些监控用于定期信息采集等。 2.tomcat内部采用的就是多线程,上百个客户端访问同一个web应用,tomcat接入后都是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用到我们的servlet程序,比如doGet或者doPost方法。
3.特别耗时的操作,如备份数据库,可以开个线程执行备份,然后执行返回,前台不断向后台询问线程执行状态
*当你发现一个业务逻辑执行效率特别低,耗时特别长,就可以考虑使用多线程。
valitile
(42)get和post请求的区别?
1.效率
GET的意思是『得』,从服务器获取数据(也可以上传数据,参数就是),效率较高
POST的意思是『给』,但可以向服务器发送数据和下载数据,效率不如GET
2.缓存
GET 请求能够被缓存,默认的请求方式也是有缓存的
POST请求默认不会缓存 缓存是针对URL来进行缓存的,GET请求由于其参数是直接加在URL上-的,一种参数组合就有一种URL的缓存,可以根据参数来进行一一对应,重复请求是幂等的(不论请求多少次,结果都一样);
而POST请求的URL没有参数,每次请求的URL都相同,数据体(HTTPBody)可能不同,无法一一对应,所以缓存没有意义
3.安全性
GET的所有参数全部包装在URL中,明文显示,且服务器的访问日志会记录,非常不安全
POST的URL中只有资源路径,不包含参数,参数封装在二进制的数据体中,服务器也不会记录参数,相对安全。所有涉及用户隐私的数据都要用POST传输 POST的安全是相对的,对于普通用户来说他们看不到明文,数据封装对他们来说就是屏障。但是对于专业人士,它们会抓包会分析,没有加密的数据包对他们来说也是小case。所以POST仅仅是相对安全,唯有对数据进行加密才会更安全。当然加密也有被破解的可能性,理论上所有的加密方式都可以破解,只是时间长短的问题。而加密算法要做的就是使得破解需要的时间尽量长,越长越安全。由于我们也需要解密,加密算法太过复杂也并非好事,这就要结合使用情况进行折中或者足够实际使用即可。绕的有点远,具体的话,我将在后续的文章之中介提及,并介绍一些常用的加密算法。
4.数据量
HTTP协议中均没有对GET和POST请求的数据大小进行限制,但是实际应用中它们通常受限于软硬件平台的设计和性能。
GET:不同的浏览器和服务器不同,一般限制在2~8K之间,更加常见的是1k以内
POST方法提交的数据比较大,大小靠服务器的设定值限制,PHP默认是2M(具体的话大家以后看后端给的开发文档就行了)
(43)进程和线程关系及区别
1.定义
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
2.关系 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
(48)
1)webService 规范:
1.JAX-WS基于xml的规范,jaxws开发的webService传输soap协议。soap=http+xml
2.JAXM&SAAJ
3.JAX-RS卡伊发布restful风格webService,直接采用http协议,可以返回xml或json,比较轻量。
2)webService要素:
1.soap特点:跨平台,跨语言,w3c指定的标准。
2.WSDL是webService的使用说明书。
2.UDDI
(49)CXF框架是用来实现webService技术的。
CXF-server发布服务spring配置:jaxws:server/CXF-client访问,spring配置:jaxws:client
(50)restful风格
1)优点:软件编写更简洁,基于http协议,支持多种消息格式,xml,json,更易于实现缓存机制。
2)使用一种路径,通过发送不同的请求,来决定使用哪个方法,post、get、put、delete。
(51)static关键字
说到static,首先要记住的最重要的一点就是,类属性中被static所引用的变量,会被作为GC的root根节点。作为根节点就意味着,这一类变量是基本上不会被回收的。因此,static很容易引入内存泄漏的风险。
你曾经遇到过一个内存泄漏的问题,就是因为static修饰的一个Map类型的变量导致的,最后排查了堆栈信息找到了问题的所在,并且解决了这个问题。那么,面试官这个时候内心中对你的印象,就会不自然的提升几分。
而且,对于static,更深入的理解是,static会将所引用的属性、方法以及内部类,与类直接产生引用关系,而非与类的实例。这就是为什么,你可以使用类名.属性、类名.方法以及类名.内部类名,来直接引用一个被static所修饰的属性、方法或者内部类。
(52)tomcat的classloader机制
jvm默认定义了三种classloader,分别是bootstrap classloader、extension classloader、system classloader
1.bootstrap是jvm的一部分,用C写的,每一个java程序都会启动它,去加载%JAVA_HOME%/jre/lib/rt.jar
2.extension也差不多,它会去加载%JAVA_HOME%/jre/lib/ext/下的类
3.system则是会去加载系统变量CLASSPATH下的所有类
这3个部分,在上面的tomcat
classloader模型图中都有体现。不过可以看到extension没有画出来,可以理解为是跟bootstrap合并了,都是去%JAVA_HOME%/jre/lib下面加载类
另外,java的classloader一般是采用委托机制,即classloader都有一个parent classloader,当它收到一个加载类的请求时,会首先请求parent classloader加载,如果parent classloader加载不到,才会自己去尝试加载(如果自己也加载不到,则抛出ClassNotFoundException)。
(53)ConcurrentHashMap和普通的同步HashMap有什么区别。
一个是HashMap的数据结构,一个是锁分段的技术。 ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中。ConcurrentHashMap中默认是把segments初始化为长度为16的数组。根据ConcurrentHashMap.segmentFor的算法,3、4对应的Segment都是segments[1],7对应的Segment是segments[12]。以上就是ConcurrentHashMap的工作机制,通过把整个Map分为N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
(54)ReentrantLock和synchronized关键字有什么区别。
就是synchronized由于是底层JVM实现的互斥,因此效率会高一些。而ReentrantLock的功能则比synchronized更多,比如定时获取某个锁,多个等待条件等。
(55)反射
1、什么是反射机制?
动态加载对象,并对对象进行剖析。简单说,反射机制值得是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
2.反射的优点和缺点
优点:灵活性
缺点:执行效率较低;很多问题不能在编译时发现,只有等待运行时才抛出,增加开发难度。
3.用过哪些使用反射的第三方库,以及他们是如何实现的
Spring,Spring MVC,Mybatis
具体举例:Spring-beans.jar的BeanUtil.CopyProperties
4.3. JAVA反射API
反射API用来生成在当前JAVA虚拟机中的类、接口或者对象的信息。
Class类:反射的核心类,可以获取类的属性,方法等内容信息。
Field类:Java.lang.reflect.表示类的属性,可以获取和设置类的中属性值。
Method类:Java.lang.reflect。表示类的方法,它可以用来获取类中方法的信息或者执行方法
Construcor类:Java.lang.reflect。表示类的构造方法。
5.反射的基本步骤:
1、获得Class对象,就是获取到指定的名称的字节码文件对象。
2、实例化对象,获得类的属性、方法或构造函数。
3、访问属性、调用方法、调用构造函数创建对象。
6.需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:
Class.forName(classname) 用于做类加载
obj.getClass() 用于获得对象的类型
类名.class 用于获得指定的类型,传参用
(56)文件IO、NIO、网络IO以及网络协议
1)传统的IO操作,比如read(),当没有数据可读的时候,线程一直阻塞被占用,直到数据到来。NIO中没有数据可读时,read()会立即返回0,线程不会阻塞。
2)tomcat的运行模式有3种:
1、 bio
默认的模式,性能非常低下,没有经过任何优化处理和支持.
2、 nio
nio(new I/O),是Java SE 1.4及后续版本提供的一种新的I/O操作方式(即java.nio包及其子包)。Java nio是一个基于缓冲区、并能提供非阻塞I/O操作的Java API,因此nio也被看成是non-blocking I/O的缩写。它拥有比传统I/O操作(bio)更好的并发运行性能。
3、 apr
是从操作系统级别来解决异步的IO问题,大幅度的提高性能.
3)启动NIO模式
修改server.xml里的Connector节点,修改protocol为org.apache.coyote.http11.Http11NioProtocol
4)网络协议http
http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式。
http请求由三部分组成,分别是:请求行、消息报头、请求正文。
高层协议有:文件传输协议FTP、电子邮件传输协议SMTP、域名系统服务DNS、网络新闻传输协议NNTP和HTTP协议等
(57)负载均衡器nginx
常见的负载均衡器就两种,一种是软负载均衡,比如nginx、Apache、lvs这一类的。另外一种则是硬件负载均衡,常见的主要就是F5。这两种方式各有优劣,其中硬件负载均衡如要用于简单应用、大访问量的场景,而软件复杂均衡则主要用于复杂应用,较小访问量的场景。当然了,两者还有一个不得不考虑的区别是,硬件复杂均衡一般都是非常贵的,而软负载均衡则基本上没有任何成本。
在负载均衡器方面,也有一些问题是比较常见的。比如如何保持会话,如何做流量控制,负载均衡策略都有哪几种,如何检查后端服务器的健康状态等等。
1.反向代理
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。简单来说就是真实的服务器不能直接被外部网络访问,所以需要一台代理服务器,而代理服务器能被外部网络访问的同时又跟真实服务器在同一个网络环境,当然也可能是同一台服务器,端口不同而已。
2.负载均衡
负载均衡策略:
1、RR(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
2、权重
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
3.ip_hash 上面的2种方式都有一个问题,那就是下一个请求来的时候请求可能分发到另外一个服务器,当我们的程序不是无状态的时候(采用了session保存数据),这时候就有一个很大的很问题了,比如把登录信息保存到了session中,那么跳转到另外一台服务器的时候就需要重新登录了,所以很多时候我们需要一个客户只访问一个服务器,那么就需要用iphash了,iphash的每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题
4、fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
5.url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
3.HTTP服务器(包含动静分离)
Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,同时现在也很流行动静分离,就可以通过Nginx来实现
4.正向代理
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
(58)GC
1)垃圾回收方法:
引用计数法:给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1。
可达性分析算法:以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。这里的根集一般包括java栈中引用的对象、方法区常良池中引用的对象
本地方法中引用的对象等。
2)回收算法也有如下几种:
1.标记-清除(Mark-sweep):不足:效率低;标记清除之后会产生大量碎片。
2.复制(Copying)
3.标记-整理(Mark-Compact)
4.分代收集算法
1)年轻代:是所有新对象产生的地方。
2)年老代:在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中
3)持久代:用于存放静态文件,比如java类、方法等。持久代对垃圾回收没有显著的影响。
年轻代:涉及了复制算法;年老代:涉及了“标记-整理(Mark-Sweep)”的算法。
(59)zookeeper用来做什么?zookeeper高可用松耦合
zookeeper的实际运用场景:
1.有一组服务器向客户端提供某种服务
2.分布式锁服务。Zookeeper提供了一个锁服务解决了这样的问题,能让我们在做分布式数据运算时候,保证数据操作的一致性。 3.配置管理。可以把zookeeper当成一个高可用的配置存储器,把这样的事情交给zookeeper进行管理,我们将集群的配置文件拷贝到zookeeper的文件系统的某个节点上,然后用zookeeper监控所有分布式系统里配置文件的状态,一旦发现有配置文件发生了变化,每台服务器都会收到zookeeper的通知,让每台服务器同步zookeeper里的配置文件,zookeeper服务也会保证同步操作原子性,确保每个服务器的配置文件都能被正确的更新。 4.为分布式系统提供故障修复的功能。集群管理最麻烦的事情就是节点故障管理,zookeeper可以让集群选出一个健康的节点作为master,master节点会知道当前集群的每台服务器的运行状况,一旦某个节点发生故障,master会把这个情况通知给集群其他服务器,从而重新分配不同节点的计算任务。master故障了,那怎么办了?zookeeper也考虑到了这点,zookeeper内部有一个“选举领导者的算法”,master可以动态选择,当master故障时候,zookeeper能马上选出新的master对集群进行管理。
(60)jdk1.8有什么新特性?
1.接口的默认方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,
2.Lambda 表达式
3.函数式接口:“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
4.Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用
5.Lambda 作用域:在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,
或者实例的字段以及静态变量。
6.访问局部变量:可以直接在lambda表达式中访问外层的局部变量
7.访问对象字段与静态变量
和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的
8.访问接口的默认方法
9.Date API:Java 8 在包java.time下包含了一组全新的时间日期API。
10.Annotation 注解:Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。
(61)timer
Java中Util包提供的定时器类。简单来说,它能让程序在指定的时间开始执行某些特定功能,也能让特定功能按照指定的周期循环执行。
Timer timer = new Timer();
timer.schedule(TimerTask task,Date firstTime,Long period):安排指定的任务在指定的时间开始进行重复的固定延迟执行。
(62)solr
1.创建索引库
2.安装IK分词器,创建索引域类型,创建域字段
3.必须配置索引域、复制域、动态域字段
4.配置solr服务器地址
5.solr模板:spring-data-solr,solrTempalte.saveBean
(63)用到哪几种锁?
1.共享锁/排它锁:共享锁和排他锁是从同一时刻是否允许多个线程持有该锁的角度来划分。
共享锁允许同一时刻多个线程进入持有锁,访问临界区资源。而排他锁就是通常意义上的锁,同一时刻只允许一个线程访问临界资源。对于共享锁,主要是指对数据库读操作中的读锁,在读写资源的时候如果没有线程持有写锁和请求写锁,则此时允许多个线程持有读锁。
2.悲观锁/乐观锁:主要用于数据库数据的操作中,而对于线程锁中较为少见。
对于乐观锁,在进行数据读取的时候不会加锁,而在进行写入操作的时候会判断一下数据是否被其它线程修改过,如果修改则更新数据,如果没有则继续进行数据写入操作。乐观锁不是系统中自带的锁,而是一种数据读取写入思想。应用场景例如:在向数据库中插入数据的时候,先从数据库中读取记录修改版本标识字段,如果该字段没有发生变化(没有其他线程对数据进行写操作)则执行写入操作,如果发生变化则重新计算数据。
对于悲观锁,无论是进行读操作还是进行写操作都会进行加锁操作。对于悲观锁,如果并发量较大则比较耗费资源,当然保证了数据的安全性。
3.可重入锁/不可重入
这两个概念是从同一个线程在已经持有锁的前提下能否再次持有锁的角度来区分的。
对于可重入锁,如果该线程已经获取到锁且未释放的情况下允许再次获取该锁访问临界区资源。此种情况主要是用在递归调用的情况下和不同的临界区使用相同的锁的情况下。
对于不可重入锁,则不允许同一线程在持有锁的情况下再次获取该锁并访问临界区资源。对于不可重入锁,使用的时候需要小心以免造成死锁。
4.公平锁/非公平锁
这两个概念主要使用线程获取锁的顺序角度来区分的。
对于公平锁,所有等待的线程按照按照请求锁的先后循序分别依次获取锁。 对于非公平锁,等待线程的线程获取锁的顺序和请求的先后不是对应关系。有可能是随机的获取锁,也有可能按照其他策略获取锁,总之不是按照FIFO的顺序获取锁。
在使用ReentrantLock的时候可以通过构造方法主动选择是实现公平锁还是非公平锁。
5.自旋锁/非自旋锁
自旋锁在进行锁请求等待的时候不进行wait挂起,不释放CPU资源,执行while空循环。直至获取锁访问临界区资源。适用于等待锁时间较短的情景,如果等待时间较长,则会耗费大量的CPU资源。而如果等待时间较短则可以节约大量的线程切换资源。
非自旋锁在进行锁等待的时候会释放CPU资源,可以通多sleep wait 或者CPU中断切换上下文,切换该线程。在线程等待时间较长的情况下可以选择此种实现机制。
除此之外还有一种介于两者之间的锁机制——自适应自旋锁。当线程进行等待的时候先进性自旋等待,在自旋一定时间(次数)之后如果依旧没有持有锁则挂起等待。在jvm中synchronized锁已经使用该机制进行处理锁等待的情况。
(64)spring常用注解有哪些?
1.声明bean的注解
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service层)
@Repository 在数据访问层使用(dao层)
@Controller 在展现层使用,控制器的声明(C)
2.注入bean的注解
@Autowired:由Spring提供
@Inject:由JSR-330提供
@Resource:由JSR-250提供
都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写代码)。
3.java配置类相关注解
@Configuration 声明当前类为配置类,相当于xml形式的Spring配置(类上)
@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上)
@Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上)
@ComponentScan 用于对Component进行扫描,相当于xml中的(类上)
@WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解
4.切面(AOP)相关注解
Spring支持AspectJ的注解式切面编程。
@Aspect 声明一个切面(类上)
使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After 在方法执行之后执行(方法上)
@Before 在方法执行之前执行(方法上)
@Around 在方法执行之前与之后执行(方法上)
@PointCut 声明切点
在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)
5.@Bean的属性支持
@Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean)
其设置类型包括:
Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),
Protetype (每次调用新建一个bean),
Request (web项目中,给每个http request新建一个bean),
Session (web项目中,给每个http session新建一个bean),
GlobalSession(给每一个 global http session新建一个Bean实例)
@StepScope 在Spring Batch中还有涉及
@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod
@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
6.@Value注解
@Value 为属性注入值(属性上)
7.环境切换
@Profile 通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上)
@Conditional Spring4中可以使用此注解定义条件话的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。(方法上)
8.异步相关
@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上)
@Async 在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)
9.定时任务相关
@EnableScheduling 在配置类上使用,开启计划任务的支持(类上)
@Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)
10.@Enable*注解说明
这些注解主要用来开启对xxx的支持。
@EnableAspectJAutoProxy 开启对AspectJ自动代理的支持
@EnableAsync 开启异步方法的支持
@EnableScheduling 开启计划任务的支持
@EnableWebMvc 开启Web MVC的配置支持
@EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持
@EnableJpaRepositories 开启对SpringData JPA Repository的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableCaching 开启注解式的缓存支持
SpringMVC部分
@EnableWebMvc 在配置类中开启Web MVC的配置支持,如一些ViewResolver或者MessageConverter等,若无此句,重写WebMvcConfigurerAdapter方法(用于对SpringMVC的配置)。
@Controller 声明该类为SpringMVC中的Controller
@RequestMapping 用于映射Web请求,包括访问路径和参数(类或方法上)
@ResponseBody 支持将返回值放在response内,而不是一个页面,通常用户返回json数据(返回值旁或方法上)
@RequestBody 允许request的参数在request体中,而不是在直接连接在地址后面。(放在参数前)
@PathVariable 用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
@RestController 该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
@ControllerAdvice 通过该注解,我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,
这对所有注解了 @RequestMapping的控制器内的方法有效。
@ExceptionHandler 用于全局处理控制器里的异常
@InitBinder 用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。
@ModelAttribute 本来的作用是绑定键值对到Model里,在@ControllerAdvice中是让全局的@RequestMapping都能获得在此处设置的键值对。
(65)数据库中有相同的数据怎么处理?
distinct关键字
(66)sql索引
(67)js选择器
(1)基本选择器
id:#
class: .
标签:
*:
a,b,c
(2)层次选择器:
a b 选择器a选择的区域里面所有为选择器b的后代元素
a>b 选择器a选择的区域里面所有为选择器b的子代元素
a+b 选择器a选择器的区域后面第一个为选择器b的兄弟元素
a~b 选择器a选择器的区域后面所有的为选择器b的兄弟元素
(3)基本过滤选择器:
:first 第一个
:last 最后一个
:even 偶数
:odd 奇数
:eq() =index
:gt() >index
:lt() <index
(4)内容过滤选择器:
:has(选择器)
(5)可见性过滤选择器:
:visibel 可见
:hidden 不可见
(6)属性选择器:
[属性]
[属性=属性值]
(7)表单选择器:
:input 只要是form的子标签 就会被选中
input 直选中标签为input的元素
(68)栈帧
栈帧(stackframe)也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。 机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。为单个过程(函数调用)分配的那部分栈称为栈帧。栈帧其实是两个指针寄存器,寄存器%ebp为帧指针,而寄存器%esp为栈指针,当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的)。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。总之简单一句话,栈帧的主要作用是用来控制和保存一个过程的所有信息的。
(69)activeMQ
ActiveMQ消息中间件
ActiveMQ点对点消息发送流程:
1.消息生产者把消息发送activeMQ消息服务进行存储
1)消息生产者发送消息,首先需要activeMQ消息服务器开辟一块空间,存储消息。
2)并且必须给这块空间起一个标识。用来唯一标识这块消息空间
2.消息消费者必须监听这块空间(此空间监听方法是根据空间名称监听的),监听空间名称必须和消息生产空间名称一致。
③发布/订阅模式特点:
1.一条消息可以被多个消费者接收
2.消息被消费后,默认持久化,消息一直存在。
3.消息必须先被监听,才能收到消息
1.消息发送:JMSTemplate.convertandsend(ids)
2.监听器:实现MessageListener接口
Message message
ActiveMQObjectMessage =(ActiveMQObjectMessage)message;
()
()
()
()
()
()
()
(*)五大框架
1.Springmvc与struts2区别
①. SpringMVC的入口是Servlet,而Struts2是Filter
②. SpringMVC会稍微比Struts2快些,SpringMVC是基于方法设计,而Struts2是基于类,每次发一次请求都会实例一个Action
③. SpringMVC使用更加简洁,开发效率SpringMVC比Struts2高,支持JSR303,处理ajax请求更加方便
④. Struts2的OGNL表达式使页面的开发效率相比SpringMVC更高些
2.Hibernate与mybatis区别
开发效率方面:
①. hibernate开发中,sql语句已经被封装,直接可以使用,加快系统开发(但是对于庞大复杂系统项目来说,负责语句较多,hibernate就不是一个很好的方案)
②. Mybatis属于半自动化,sql需要手工完成,稍微繁琐
SQL优化方面
①. Hibernate自动生成SQL,有些语句较为繁琐,会多消耗一些性能
②. MyBatis手动编写SQL,可以避免不需要的查询,提高系统性能
对象管理方面
①. hibernate是完整的ORM框架,开发过程中,无需过多关注底层实现,只需要管理对象即可
②. MyBatis需要自行管理映射关系
(*)springboot