一、 Object
1 方法
1.1 boolean equals(Object obj)
比较对象内容是否一致。
- 前提: 参数是否是null
- 比较参数和当前对象类型是否一致,通过getClass()比较。
- 比较内容
- 定义在数据载体类型之中。如:实体(domain开源开发| entity国内开发| pojo只依赖JDK就可以使用、运行的类型的对象| javabean Java开发中的最小单元,就是类)。
- 因为数据载体类型的对象,存储在唯一性容器中的可能性最大。(EJB 企业级javaBean)
- 数据载体类型:PO、BO、VO PO - 实体。persistence object BO - 业务数据对象。bo|bdo。不需要存储的数据对象。 VO - 视图数据对象。Model
1.2 hashCode
和equals成对出现。计算对象的hash码值。
- 是配合equals实现多数据对象比较的。为了提高比对效率的。
- 相同对象返回相同Hash码。不同对象尽可能返回不同Hash码。
1.3 fianlize
资源释放。GC调用。
一般在重量级的工具中定义。如:DataSource、SqlSessionFactory、线程池
1.4 clone
克隆方法(原型模式)。深拷贝?浅拷贝?
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
不推荐使用。单例对象,无所谓克隆。非单例对象,不需要克隆。
在业务实现流程中,如果需要留存备份,做备份缓存的时候,可能使用。
2 实体定义企业级规则
- 实现接口Serilizable
- 私有化属性
- 提供getter/setter
- override hashCode & equals
- 提供至少一个无参构造
二、 集合
1 Collection
1.1 List
- ArrayList: 底层是数组。相对查询性能高。线程不安全。轻量级
- LinkedList: 底层是双向循环链表。相对增删效率高。线程不安全。轻量级
- Vector: 除了线程安全外,和ArrayList完全相同。重量级
native方法:底层方法, 是基于C语言实现 java中的序列化是增量序列化 add向末尾加 remove 末尾删除 调用这些方法时, 一般是arraylist快 数组扩容时,相反
1.2 Set
Set是只有key没有value的map。
2 Map
键值对。key不重复。value不限制。所有的map在使用的时候,建议,多数据导入的时候,批量实现。undefined
- HashMap:key算法为散列算法。
- LinkedHashMap: key有序。类似ArrayList的有序。
- TreeMap: key排序Map。要求key必须可排序。 实现一:key类型实现Comparable接口; 实现二:提供Comparator比较器。
- Hashtable: 和HashMap区别是,线程安全。所有方法都是同步的。key和value不能为null。
- Properties: 是Hashtable的子类型,key和value都是String。
3 java.util.concurrent.*
是Java5开始提供的,线程安全的特殊实现包。其中包括线程安全的集合,线程池,线程锁,线程安全的包装对象(Atomic) current包 保存了list set map接口,作用是保证线程安全
- 线程安全的集合。不是使用同步方法机制实现的。
- 不需要特殊学习。所有API和普通集合完全一致。
- atomic子包:定义了常见包装类型的线程安全类型。
三、 IO
1.分类
- 方向:输入|输出。针对当前流对象所在内存而言。
- 单位:字节|字符。字符包含编码。
- 功能:节点|处理【装饰】,装饰器模式。 流的相对复杂操作。在读写的过程中,对文件数据进行操作。
2 Stream
- FileInputStream | FileOutputStream 字节文件输入输出
- DataInputStream | DataOutputStream 数据输入输出,在网络应用中还是比较有用的。文件上传的时候。客户端上传文件,控制器处理为MultipartFile,服务对象保存到FastDFS。upload_file(byte[],“文件后缀名”, NameValuePair[])
- BufferedInputStream | BufferedOutputStream 有缓冲的字节输入输出。效率高。内存压力大点。
- RandomAccessFile 文件随机访问流,可读,可写。可以随机访问文件的任意位置。
3 Reader|Writer
- FileReader | FileWriter
- BufferedReader(readLine()) | BufferedWriter
- PrintWriter
- InputStreamReader | OutputStreamWriter : 桥转换流,将字节流转换为字符流,并提供字符集设定。
4 Object流
- ObjectInputStream | ObjectOutputStream 对象序列化流
- ObjectOutputStream对象输出流,对同对象的变化再输出,是一个增量输出。且此流,对文件的写出操作,一般都是覆盖性的。
四、 线程
1 Thread
本身类型的对象代表线程。
常用方法:
- start(): 启动线程。开启一个子线程,并启动。
- run(): 线程具体执行的内容。默认无返回值。线程执行结束后,JVM处理。
- yield():放弃CPU执行时间片。run方法停止,等待CPU时间片分配。
- join():连接,等待连接的线程执行结束后,再运行。
- interrupt():中断,中断线程阻塞状态。被中断阻塞的线程抛出InterruptedException。被中断异常。
2 Runnable
其中定义了方法run
- void run(); 方法含义和Thread中的run方法一致。
- 使用: class MyRunnable implements Runnable{ public void run(){…}} new Thread(new MyRunnable()).start(); 就是一个接口回调。在Thread类中,先使用Runnable接口定义,后有Runnable的接口实现。
3 Callable
- concurrent.Callable Object call()
- FutureTask - 配合线程池或异步处理存在的。 可以解决的问题,就是线程处理返回值,和线程异常问题。 在异步多线程开发中,使用Callable做返回值处理。 商业开发中,使用较少。。需要做异步多线程开发的时候,使用netty较多。
4 ThreadLocal
线程资源绑定对象。底层是map。key是Thread.currentThread()。value是要绑定的资源。
- set(Object obj){ map.put(Thread.currentThread(), obj); }
- get(){ map.get(Thread.currentThread()); } 注意:当线程资源使用结束后,必须remove(){map.remove(Thread.currentThread());}。因为JVM中的线程对象是可复用的。线程对象的唯一确定,只考虑线程ID。
5 Executor线程池
定义:一次性创建若干线程对象,在使用的时候,直接启动。当启动的线程终止后,返回到线程池,等待后续使用。
注意:代码中使用了线程池。那么最终一定要shutdown。否则进程不会结束。因为线程池是由一个精灵线程管理的,即使所有的线程都结束了,精灵线程也会用就运行下去。
Executor:顶级接口
Executors:线程池工具类,定义了若干静态方法,快速创建线程池对象。
ExecutorService:常用接口,代表线程池
五、 网络
网络开发编码相对简单。复杂在于网络的模式。是否阻塞,是否异步。 常见的Socket开发,都是同步阻塞的。 开发最简单。
1 Socket/ServerSocket
1.1 服务端
监听某端口,提供服务。
ServerSocket ss = new ServerSocket(port);
ss.accept(socket);
1.2 客户端
Socket s = new Socket(ip, port);
2 BIO、NIO、AIO
2.1 同步异步
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询前去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。
以银行取款为例:
同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
2.2 阻塞非阻塞
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
以银行取款为例:
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器通知可读写时再继续进行读写,不断循环直到读写完成)
2.3 BIO
Blocking IO: 同步阻塞的编程方式。
BIO编程方式通常是在JDK1.4版本之前常用的编程方式。编程实现过程为:首先在服务端启动一个ServerSocket来监听网络请求,客户端启动Socket发起网络请求,默认情况下ServerSocket回建立一个线程来处理此请求,如果服务端没有线程可用,客户端则会阻塞等待或遭到拒绝。
且建立好的连接,在通讯过程中,是同步的。在并发处理效率上比较低。大致结构如下:
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
使用线程池机制改善后的BIO模型图如下:
2.4 NIO
Unblocking IO(New IO): 同步非阻塞的编程方式。
NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的最大并发问题,NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会通知相应的应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。
同步非阻塞,服务器实现模式为一个请求一个通道,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程复杂,JDK1.4开始支持。
2.5 AIO
Asynchronous IO: 异步非阻塞的编程方式
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
代码语言:javascript复制AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
六、 反射
通过JDBC通用开发回顾来复习
七、Servlet
JavaEE企业级规范|标准。开发基于网络请求-应答模型的应用,代码应该遵循Servlet标准规范。 Servlet源码讲解
标准中定义:
- 请求的类型必须是ServletRequest,服务器响应类型必须是ServletResponse,
- 服务器中的服务组件类型必须是Servlet,服务器提供的唯一服务方法一定是void service(ServletRequest, ServletResponse)方法。
遵循这套规范,提供服务端容器的,称为中间件提供商(Tomcat);遵循这套规范开发提供服务的,称为服务提供者(编写Servlet相关代码的)。
1 Servlet结构
1.1 Servlet
- 是Servlet规范的顶级接口。核心关注方法是服务方法。
- public void service(ServletRequest request, ServletResponse response) throws IOException, ServletException; 这个方法是Servlet规范中唯一的服务方法。
1.2 GenericServlet
- 是Servlet规范提供的Servlet接口适配器类型。如果开发中,只针对service方法做强化处理开发,建议继承此类型。
- 适配器模式:是连接两个不同类型的中间桥梁。可以通过这个桥梁,将两个不相关的类型连接起来。
interface Servlet{}
class GenericServlet implements Servlet{}
class MyServlet extends GenericServlet{}
class Main(){
public static void main(String[] args){
Servlet s = (Servlet) new MyServlet();
}
}
1.3 HttpServlet
- 是GenericServlet的子类型,是依赖于Http协议的Servlet实现。因为在这个类型中的唯一方法service中,直接进行方法参数的强制类型转换。
- 这个类提供了一个受保护的service方法。方法签名是: protected void service(HttpServlertRequest, HttpServletResponse)throws IOException, ServletException; 此方法和http协议耦合,只处理基于http协议的请求-应答模型。 方法中提供了模板实现,根据不同请求方式,调用不同处理逻辑,如:get请求,调用doGet方法;post请求调用doPost方法。这种设计好处在于,适用于Rest开发模式下的请求处理。可以根据不同的请求方式,提供不同的处理业务。 web.xml <servlet> <servlet-name>userServlet</servlet-name> <servlet-class>UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>userServlet</servlet-name> <url-pattern>/user/*</url-pattern> </servlet-mapping> public class UserServlet extends HttpServlet{ public void doPost(){ // 调用service中的新增方法 } public void doPut(){ // 调用service中的修改方法 } public void doDelete(){ // 调用service中的删除方法 } public void doGet(){ String pathInfo = request.getPathInfo(); // 就是web.xml中配置的url-pattern内的* if(pathInfo.equals("...")){ ... } } }
2 Filter
- 过滤器。Servlet标准规范中的一个技术体系。
- 核心功能是:为Servlet提供一个安全的服务环境。并设置统一的配置信息。 Filter执行的时候,Servlet是未调用状态。服务未提供。在服务提供前,完成所有的安全校验和统一配置,可以节省服务器资源消耗。
- 主要处理内容:安全校验、设置字符集、静态资源处理
- 实现接口Filter,核心方法是void doFilter(ServletRequest, ServletResponse,FilterChain)
过滤器(Filter) VS 拦截器(Interceptor)
- 拦截器是做什么的? 是给服务增加额外功能的代码。如:日志、事务、授权、登录成功后的资源初始化、退出成功后的资源回收。
- 理论上,在过滤器中可以实现的逻辑,在拦截器中都可以实现。反之不可。
- 如果将过滤需要实现的逻辑,附加到拦截器上,会造成服务器资源的额外消耗。 因为每个拦截器执行,意味着,服务已经开始执行了。
3 Listener
监听器。监听各种事件,处理不同的事件。
常用Listener:
- ServletContextListener监听ServletContext初始化和销毁的监听器,
- HttpSessionListener 监听HttpSession初始化和销毁的监听器,
- HttpSessionAttributeListener 监听HttpSession中的Attribute变更的监听器,
- HttpSessionBindingListener 监听HttpSession中的Attribute绑定的监听器,
- ServletRequestListener 监听ServletRequest请求对象创建销毁的监听器,
- ServletRequestAttributeListener 监听ServletRequest中Attribute变更的监听器。
注意:
- 开发应用的时候,自开发监听器的场景比较少。使用较多的如:ContextLoaderListener。
- Listener一般都会配合实现参数的定制。
4 MVC模式
MVC:分层,模型层(module)、视图层(view)、控制层(controller)。
- 一般情况下,MVC框架指哪一个层次的开发框架?控制层框架。如:SpringMVC、Struts
- 三个层次中,最不好划分的就是模型层。在WEB应用中,除视图逻辑和控制器外都是模型层代码。
- MVC框架特点:一定有一个核心控制器(核心Servlet),如:SpringMVC中的DispatcherServlet。在传统的MVC框架中,核心控制器是使用Servlet开发实现的。核心控制器是框架与客户端的唯一接触点(单点接触)。单点接触的好处是:服务代码和客户端请求解耦。MVC框架中都会提供一个服务顶级接口,和若干适配器类型。让开发者可以快速开发。如:SpringMVC中的接口Controller。
- 框架侵入性(使用某技术的时候,对应的开发的代码是否可以只依赖JVM运行):可以只依赖JVM运行的,框架技术成为无侵入性。有侵入性的框架,有人成为侵入性 | 重量级。
- 框架量级(重量级|轻量级):指封装级别。必须依赖企业级中间件容器的框架成为重量级框架,如:EJB。不依赖企业级中间件容器的是轻量级框架,如:SpringMVC、Spring、MyBatis。
八、 企业开发中异常处理
异常处理详细讲解以及企业中的异常处理规范
- 异常怎么处理( 异常处理规范 )? 异常的处理:代码中,绝对不能直接throws。 异常的传递:代码中,除自开发的根以外,不能只try…catch。
- 代码举例:
- Mapper|DAO: 如果是手写的,代码必须try…catch,处理异常,并将catch捕获的异常封装后(也可以不封装)再抛出。 通知调用者,当前方法出现了异常。
- service:代码必须try…catch,且将捕获的异常封装后再抛出。
- controller:只try…catch,除非自定义了ExceptionHandler。
九、 GC ( 垃圾回收机制 )
1.1 什么是分代,分代的必要性是什么
- Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代、老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存分代策略。
- 堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。给堆内存分代是为了提高对象内存分配和垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率。
- 有了内存分代,情况就不同了, 新创建的对象会在新生代中分配内存,经过多次回收仍然存活下来的对象存放在老年代中,静态属性、类信息等存放在永久代中 ,新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC,老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收,永久代中回收效果太差,一般不进行垃圾回收,还可以 根据不同代的特点采用合适的垃圾收集算法。分代收集大大提升了收集效率 ,这些都是内存分代带来的好处。
1.2 分代的划分
Java虚拟机将堆内存划分为新生代、老年代和永久代,永久代是HotSpot虚拟机特有的概念( JDK1.8之后为metaspace替代永久代),它采用永久代的方式来实现方法区,其他的虚拟机实现没有这一概念,而且HotSpot也有取消永久代的趋势,在JDK 1.7中HotSpot已经开始了“去永久化”,把原本放在永久代的字符串常量池移出。
永久代主要存放常量、类信息、静态变量等数据,与垃圾回收关系不大,新生代和老年代是垃圾回收的主要区域。
内存简图如下:
1.2.1 新生代(Young Generation)
- 新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~ 95% 的空间,回收效率很高。
- HotSpot将新生代划分为三块,一块较大的Eden(伊甸)空间和两块较小的Survivor(幸存者)空间,默认比例为8:1:1。 划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
- GC开始时,对象只会存在于Eden区和From Survivor区,To Survivor区是空的(作为保留区域)。 GC进行时,Eden区中所有存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。 接着, From Survivor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。
1.2.2 老年代(Old Generationn)
在新生代中经历了多次(具体看虚拟机配置的阀值,一般为15)GC后仍然存活下来的对象会进入老年代中。
老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
1.2.3 永久代(Permanent Generationn)
永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据
对这一区域而言,Java虚拟机规范指出可以不进行垃圾收集,一般而言不会进行垃圾回收。
2 垃圾回收算法及分代垃圾收集器
2.1 垃圾收集器的分类
2.1.1 次收集器
Scavenge GC,指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Scavenge GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Scavenge GC。
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
当年轻代堆空间紧张时会被触发
相对于全收集而言,收集间隔较短
3 垃圾回收算法及分代垃圾收集器
3.1 垃圾收集器的分类
3.1.1 次收集器
- Scavenge GC,指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Scavenge GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Scavenge GC。
- 一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
- 当年轻代堆空间紧张时会被触发
- 相对于全收集而言,收集间隔较短
3.1.2 全收集器
- Full GC,指发生在老年代的GC,出现了Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Scavenge GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的速度一般会比Scavenge GC慢10倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC。
- 当老年代或者持久代堆空间满了,会触发全收集操作
- 可以使用System.gc()方法来显式的启动全收集
- 全收集一般根据堆大小的不同,需要的时间不尽相同,但一般会比较长。
3.1.3 垃圾回收器的常规匹配
3.2 常见垃圾回收算法
3.2.1 引用计数(Reference Counting)
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。
垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
3.2.2 复制(Copying)
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。
垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。
此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。简图如下:
3.2.3 标记-清除(Mark-Sweep)
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。
此算法需要暂停整个应用,同时,会产生内存碎片。简图如下:
3.2.4 标记-整理(Mark-Compact)
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。简图如下:
3.3 分代垃圾收集器
3.3.1 串行收集器(Serial)
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它的特点是:只用一个CPU(计算核心)/一条收集线程去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World” -后面简称STW)。可以使用-XX: UseSerialGC打开。
虽然是单线程收集, 但它却简单而高效, 在VM管理内存不大的情况下(收集几十M~一两百M的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内。
3.3.2 并行收集器(ParNew)
ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: UseConcMarkSweepGC的默认新生代收集器)。
由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial. 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(ParNew收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads=参数控制GC线程数)。
3.3.3 Parallel Scavenge收集器
与ParNew类似, Parallel Scavenge也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量:
系统吞吐量=运行用户代码时间/(运行用户代码时间 垃圾收集时间)
停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运算而不需要太多交互的任务-可以最高效率地利用CPU时间,尽快地完成程序的运算任务. Parallel Scavenge提供了如下参数设置系统吞吐量:
3.3.4 Serial Old收集器
Serial Old是Serial收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法
3.3.5 Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本, 使用多线程和“标记-整理”算法, 吞吐量优先, 主要与Parallel Scavenge配合在注重吞吐量及CPU资源敏感系统内使用;
3.3.6 CMS收集器(Concurrent Mark Sweep)
CMS(Concurrent Mark Sweep)收集器是一款具有划时代意义的收集器, 一款真正意义上的并发收集器, 虽然现在已经有了理论意义上表现更好的G1收集器, 但现在主流互联网企业线上选用的仍是CMS(如Taobao、微店).
CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器), 基于”标记-清除”算法实现, 整个GC过程分为以下4个步骤:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark: GC Roots Tracing过程)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep: 已死对象将会就地释放, 注意:此处没有压缩) 其中1,3两个步骤(初始标记、重新标记)仍需STW. 但初始标记仅只标记一下GC Roots能直接关联到的对象, 速度很快; 而重新标记则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录, 虽然一般比初始标记阶段稍长, 但要远小于并发标记时间.
CMS特点:
- CMS默认启动的回收线程数=(CPU数目 3)/4 当CPU数>4时, GC线程一般占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.
- 无法处理浮动垃圾, 可能出现Promotion Failure、Concurrent Mode Failure而导致另一次Full GC的产生: 浮动垃圾是指在CMS并发清理阶段用户线程运行而产生的新垃圾. 由于在GC阶段用户线程还需运行, 因此还需要预留足够的内存空间给用户线程使用, 导致CMS不能像其他收集器那样等到老年代几乎填满了再进行收集. 因此CMS提供了-XX:CMSInitiatingOccupancyFraction参数来设置GC的触发百分比(以及-XX: UseCMSInitiatingOccupancyOnly来启用该触发百分比), 当老年代的使用空间超过该比例后CMS就会被触发(JDK 1.6之后默认92%). 但当CMS运行期间预留的内存无法满足程序需要, 就会出现上述Promotion Failure等失败, 这时VM将启动后备预案: 临时启用Serial Old收集器来重新执行Full GC(CMS通常配合大内存使用, 一旦大内存转入串行的Serial GC, 那停顿的时间就是大家都不愿看到的了).
- 最后, 由于CMS采用”标记-清除”算法实现, 可能会产生大量内存碎片. 内存碎片过多可能会导致无法分配大对象而提前触发Full GC. 因此CMS提供了-XX: UseCMSCompactAtFullCollection开关参数, 用于在Full GC后再执行一个碎片整理过程. 但内存整理是无法并发的, 内存碎片问题虽然没有了, 但停顿时间也因此变长了, 因此CMS还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction用于设置在执行N次不进行内存整理的Full GC后, 跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理).
3.3.7 分区收集- G1收集器
G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗CPU的服务器治理大内存.
- G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS).
- -XX: UseG1GC启用G1收集器. 与其他基于分代的收集器不同, G1将整个Java堆划分为多个大小相等的独立区域(Region), 虽然还保留有新生代和老年代的概念, 但新生代和老年代不再是物理隔离的了, 它们都是一部分Region(不需要连续)的集合.如:
每块区域既有可能属于O区、也有可能是Y区, 因此不需要一次就对整个老年代/新生代回收. 而是当线程并发寻找可回收的对象时, 有些区块包含可回收的对象要比其他区块多很多. 虽然在清理这些区块时G1仍然需要暂停应用线程, 但可以用相对较少的时间优先回收垃圾较多的Region. 这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率.
G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions. 如果存活时间达到阀值, 这部分对象就会被提升到老年代.如图:
其特点是:
一整块堆内存被分为多个Regions.
存活对象被拷贝到新的Survivor区或老年代.
年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域尺寸.
Young GC会有STW事件, 进行时所有应用程序线程都会被暂停.
多线程并发GC.
G1老年代GC特点如下:
- 并发标记阶段 1 在与应用程序并发执行的过程中会计算活跃度信息. 2 这些活跃度信息标识出那些regions最适合在STW期间回收(which regions will be best to reclaim during an evacuation pause). 3 不像CMS有清理阶段.
- 再次标记阶段 1 使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多. 2 空region直接被回收.
- 拷贝/清理阶段(Copying/Cleanup Phase) 1 年轻代与老年代同时回收. 2 老年代内存回收会基于他的活跃度信息.
收集器设置
见资料—jvm优化4.2.3
十、 JVM
1.1 类加载子系统与方法区
类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间。
除了类的信息外,方法区中可能还会存放运行时常量池信息,包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。
1.2 Java堆
java堆在虚拟机启动的时候建立,它是java程序最主要的内存工作区域。几乎所有的java对象实例都存放在java堆中。
堆空间是所有线程共享的,这是一块与java应用密切相关的内存空间。
1.3 直接内存
java的NIO库允许java程序使用直接内存。直接内存是在java堆外的、直接向系统申请的内存空间。
通常访问直接内存的速度会优于java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。
由于直接内存在java堆外,因此它的大小不会直接受限于Xmx指定的最大堆大小,但是系统内存是有限的,java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
1.4 垃圾回收系统
垃圾回收系统是java虚拟机的重要组成部分,垃圾回收器可以对方法区、java堆和直接内存进行回收。
其中,java堆是垃圾收集器的工作重点。和C/C 不同,java中所有的对象空间释放都是隐式的,也就是说,java中没有类似free()或者delete()这样的函数释放指定的内存区域。
对于不再使用的垃圾对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括java堆、方法区和直接内存中的全自动化管理。
1.5 Java栈
每一个java虚拟机线程都有一个私有的java栈,一个线程的java栈在线程创建的时候被创建
java栈中保存着帧信息、局部变量、方法参数,同时和java方法的调用、返回密切相关。
1.6 本地方法栈
本地方法栈和java栈非常类似,最大的不同在于java栈用于方法的调用,而本地方法栈则用于本地方法的调用,
作为对java虚拟机的重要扩展,java虚拟机允许java直接调用本地方法(通常使用C编写)
1.7 PC寄存器
PC(Program Counter)寄存器也是每一个线程私有的空间,java虚拟机会为每一个java线程创建PC寄存器。
在任意时刻,一个java线程总是在执行一个方法,这个正在被执行的方法称为当前方法。
如果当前方法不是本地方法,PC寄存器就会指向当前正在被执行的指令。
如果当前方法是本地方法,那么PC寄存器的值就是undefined
1.8 执行引擎
执行引擎是java虚拟机的最核心组件之一,它负责执行虚拟机的字节码,现代虚拟机为了提高执行效率,会使用即时编译(just in time)技术将方法编译成机器码后再执行。
Java HotSpot Client VM(-client),为在客户端环境中减少启动时间而优化的执行引擎;本地应用开发使用。(如:eclipse)
Java HotSpot Server VM(-server),为在服务器环境中最大化程序执行速度而设计的执行引擎。应用在服务端程序。(如:tomcat)
Java HotSpot Client模式和Server模式的区别
- 当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,服务起来之后,性能更高
- JDK安装目录/jre/lib/(x86、i386、amd32、amd64)/jvm.cfg
- 文件中的内容,-server和-client哪一个配置在上,执行引擎就是哪一个。如果是JDK1.5版本且是64位系统应用时,-client无效。 –64位系统内容 -server KNOWN -client IGNORE –32位系统内容 -server KNOWN -client KNOWN
注意:在部分JDK1.6版本和后续的JDK版本(64位系统)中,-client参数已经不起作用了,Server模式成为唯一
十一 JVM优化
4.1 JDK常用JVM优化相关命令
4.1.1 jps
jps - l
显示线程id和执行线程的主类名
jps -v
显示线程id和执行线程的主类名和JVM配置信息
4.1.2 jstat
- jstat用来查看GC和堆相关信息, 命令格式:
jstat <option> vmid [interval [count]]
其中[]表示可选,interval表示采样间隔时间(s|ms),count表示输出结果数,比如: jstat -参数 线程id 执行时间(单位毫秒) 执行次数 jstat -gc 19098 200 5 # 每隔200ms查看一次GC和堆的相关信息, 共查看5次
S0C:第一个幸存区的大小 S1C:第二个幸存区的大小 S0U:第一个幸存区的使用大小 S1U:第二个幸存区的使用大小 EC:伊甸园区的大小 EU:伊甸园区的使用大小 OC:老年代大小 OU:老年代使用大小 MC:方法区大小 MU:方法区使用大小 CCSC:压缩类空间大小 CCSU:压缩类空间使用大小 YGC:年轻代垃圾回收次数 YGCT:年轻代垃圾回收消耗时间 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间 GCT:垃圾回收消耗总时间
- jstat -gccapacity 统计堆区信息,输出字段如下:
NGCMN:新生代最小容量 NGCMX:新生代最大容量 NGC:当前新生代容量 S0C:第一个幸存区大小 S1C:第二个幸存区的大小 EC:伊甸园区的大小 OGCMN:老年代最小容量 OGCMX:老年代最大容量 OGC:当前老年代大小 OC:当前老年代大小 MCMN:最小元数据容量 MCMX:最大元数据容量 MC:当前元数据空间大小 CCSMN:最小压缩类空间大小 CCSMX:最大压缩类空间大小 CCSC:当前压缩类空间大小 YGC:年轻代gc次数 FGC:老年代GC次数
4.2 JVM常见参数
配置方式:java options MainClass arguments
options - JVM启动参数。 配置多个参数的时候,参数之间使用空格分隔。
参数命名: 常见为 -参数名
参数赋值: 常见为 -参数名=参数值 | -参数名:参数值
4.2.1 内存设置
- -Xms:初始堆大小,JVM启动的时候,给定堆空间大小。
- -Xmx:最大堆大小,JVM运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
- -Xmn:设置年轻代大小。整个堆大小=年轻代大小 年老代大小 元空间大小。元空间一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
- -Xss: 设置每个线程的Java栈大小。JDK5.0以后每个线程Java栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
- -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:MaxTenuringThreshold:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
4.2.2 内存设置经验分享
- JVM中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制。
- Tomcat配置方式: 编写catalina.bat|catalina.sh,增加JAVA_OPTS参数设置。windows和linux配置方式不同。windows - set “JAVA_OPTS=%JAVA_OPTS% 自定义参数”;linux - JAVA_OPTS="$JAVA_OPTS 自定义参数"
常见设置:
-Xmx3550m -Xms3550m -Xmn2g -Xss128k
适合开发过程的测试应用。要求物理内存大于4G。-Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=160m -XX:MaxTenuringThreshold=0
适合高并发本地测试使用。且大数据对象相对较多(如IO流) - 环境: 16G物理内存,高并发服务,重量级对象中等(线程池,连接池等),常用对象比例为40%(运行过程中产生的对象40%是生命周期较长的) -Xmx10G -Xms10G -Xss1M -XX:NewRatio=3 -XX:SurvivorRatio=4 -XX:MaxPermSize=2048m -XX:MaxTenuringThreshold=5 java -Xmx10G -Xms10G -Xss1M -XX:NewRatio=3 -XX:SurvivorRatio=4 -XX:MaxPermSize=2048m -XX:MaxTenuringThreshold=5 主类名 类型启动参数
十二、 设计模式
讲原理,方法论。 掌握要求:理解、能够描述、可以在开发过程中,找到使用的框架中可能的设计模式。undefined
1 单例
类不能被外部代码new对象。类型只有唯一的一个对象。
目的:避免线程级资源重复创建、避免特征类型对象重复创建。
线程级资源:在JVM生命周期中,只需要唯一一份的对象。如:DataSource、SqlSessionFactory、Spring(ApplicationContext)
特征类型:class ManStat{} class WomanStat{}。 Season
特点: 私有化构造, 唯一化对象
开发方式
懒汉:使用的时候创建唯一对象。多线程并发可能创建多对象。线程不安全
饿汉:类加载的时候,创建唯一对象。可以保证多线程环境中的单例。线程安全
2 工厂
静态工厂:不需要工厂类型的对象,就可以得到产品的开发方式。都用于开发工具类。
实例工厂:必须先有工厂类型的对象,才能创建产品的开发方式。实例工厂的实现,有多种。
单产品工厂:一个工厂中只有一个方法,只创建唯一一种产品。
多产品工厂:
多方法实现:之前学习的简单工厂。有多个方法,每个方法生产不同的产品对象。
配置实现:通过文本或配置类型来决定,工厂可以生产什么产品。需要反射技术配合。
接口隔离思想实现工厂:
工厂应该有多态特性。定义一个接口标准(class或interface),通过子类型或实现类提供生产产品的具体实现。在定义接口标准的时候,如果标准特征太多,建议定义成多个小的接口。(可以定义一个子接口,继承所有的标准。类似mvc开发的service层 / DAO层接口类以及实现类)
3 工厂方法
是工厂模式的升级。将工厂拆分成链状。大的工厂创建小的工厂,小的工厂创建产品。 如:JDBC, DriverManager -> Connection -> Statement -> ResultSet
4 构建器
使用一个Builder构建器类型的对象,通过一系列的配置、操作,最终得到想要的产品对象。类似工厂。如:StringBuilder、XxxRequestBuilder。
StringBuilder可以成为一个构建器。用于构建一个字符串对象。
可以通过配置或操作,定义最终的产品的特征。
StringBuilder builder = new StringBuilder("");
builder.append(“abc”); // 操作或配置
String str = builder.toString(); // 构建
大多数构建器模式编写的构建器对象,特征是: new XxxBuilder() -> builder.xxx() -> createXxx()|build()|xxxx -> 产品对象。
5 装饰(包装)
对已有的对象,进行包装,提供更强的功能。如:IO。
new BufferedReader(new InputStreamReader(new FileInputStream(“path”), “UTF-8”));
6 代理
代理就是给某对象增加额外功能,且不能修改这个对象对应的源码。undefined
静态代理
代码语言:javascript复制给指定类型的对象,增加固定的额外逻辑。
interface IA{ String xxx(); }
class A implements IA{ String xxx(){ String s = "abc"; return s; } }
class AProxy implements IA{ A a; String xxx(){ //记录开始时间 String s = a.xxx(); // 记录结束时间,输出结束时间 - 开始时间 return s; } }
IA a = new AProxy(); a.xxx();
动态代理
代码语言:javascript复制给多个类型,增加额外的附属逻辑,且不修改源码。
interface IA{ String xxx(); }
class A implements IA{ String xxx(){ return "abc"; } }
class MyInvocationHandler implements InvocationHandler{
IA ia;
Object invoke(Object proxy, Method method, Object[] args){ // before method.invoke(args) // after }
}
class MyProxyFactory{
IA getProxy(){
return Proxy.newProxyInstance(MyProxyFactory.class.getClassLoader(), A.class.getInterfaces(), new MyInvocationHandler());
}
}
开发注意:被代理的类型(A),都实现了什么接口?是否A.class.getInterfaces()就能获取A实现的所有接口?
Class.getInterfaces获取的是类型直接实现的接口,而不是所有的接口。需要通过递归(循环)的方式,找到所有父类实现的接口。否则生成的代理对象,有可能不可使用。
7 委派
几乎和代理一样,就是不加额外功能。相当于Java对象关系中的依赖。A类型中有B类型的引用,A类型的方法中调用了B类型中的某方法。
8 策略
一般都和接口回调类似。定义一个标准,代表某一个结果。(如:方法insert()代表新增数据到数据库。 insert(DoInsert接口), 接口的方法为doInsert(Connection conn); 那么,使用这个标准的人,就需要提供接口的实现,实现过程每个人提供的不同。最终的结果都是数据入库。)
9 模板
定义一个规则,当什么情况发生的时候,调用什么方法。如:HttpServlet中的service方法。
10 原型
创建某类型的实例,根据这个实例,创建其他的对象。首先创建的实例就是原型。对clone的实现。
在类型中,clone方法,是对象浅拷贝。 实现原型设计模式,必须重写clone方法。除非原型类型中没有任何引用类型的属性。
代码语言:javascript复制class A{ int i = 1; B b ; static A a = new A(); public A clone(){ A tmp = super.clone(); tmp.b = b.clone(); return tmp; } }
class B{}
class Main{ void xxx(){ A a1 = A.a.clone();} }
当类型的实例,在创建过程中相对复杂,且有规律的时候,可以考虑使用原型。
11 观察者
代码语言:javascript复制将代码分为三个角色,分别是观察者,事件源,事件。当观察者创建后,事件源发生事件的时候,由观察者自动处理。Servlet中的Listener。
class ServletContext{
public ServletContext(){ init(); }
public void init(){
// 初始化上下文。默认初始化。
// 判断web.xml配置文件中是否定义了<listener>标签。
// 如果定义了<listener>标签,则创建这个标签配置的对象。
Object listener;
if(listener instanceOf ServletContextListener) {
ServletContextEvent event = new ServletContextEvent();
event.setSource(this);
( (ServletContextListener) listener ).contextInitalized(event);
}
}
}
class MyServletContextListener implements ServletContextListener{ ... }
<listener><listener-class>package.MyServletContextListener</listener-class></listener>
12 职责链|责任链
将一个复杂的操作,拆分成若干个简单的操作,顺序执行。当任何节点执行错误,直接返回,所有节点执行正常,总体逻辑执行结束。Filter是典型的责任链设计模式。
13 享元
类型A的所有特性都开发给类型B使用。
class A{ … }
class B extends A{ … }
二刺螈中的设计模式
十四 JDBC
代码语言:javascript复制//类加载
Class.forName("com.jdbc.cj.mysql.Driver");
//注册驱动
DriverManager.registerDriver(new com.jdbc.cj.mysql.Driver());
实现:是驱动包中的驱动类,提供静态初始化代码块,调用registerDriver。
class Driver implements java.sql.Driver{
static{
DriverManager.registerDirver(new Driver());
}
}
代码语言:javascript复制import javax.sql.DataSource;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
/**
* 约定,类名的小写就是表名。属性名的小写就是字段名。
*/
public class BaseDao implements IBaseDao {
private DataSource dataSource;
@Override
public void insert(Object obj) throws Exception {
StringBuilder builder = new StringBuilder("insert into ");
// 拼接SQL
//builder.append("表名(字段名) values(?,?,?)");
Class clazz = obj.getClass();
String tableName =
clazz.getName().substring(clazz.getName().lastIndexOf(".") 1).toLowerCase();
builder.append(tableName).append(" ( ");
Field[] fields = clazz.getDeclaredFields();
for(Field f : fields){
String columnName = f.getName().toLowerCase();
builder.append(columnName).append(",");
}
builder.deleteCharAt(builder.length()-1);
builder.append(" ) ").append("values( ");
for(int i = 0; i < fields.length; i ){
builder.append("? ,");
}
builder.deleteCharAt(builder.length()-1);
builder.append(" ) ");
String sql = builder.toString();
Connection connection = this.getConnection();
PreparedStatement pstm = connection.prepareStatement(sql);
for(int i = 0; i < fields.length; i ){
pstm.setObject(i 1, fields[i].get(obj));
}
pstm.executeUpdate();
pstm.close();
connection.close();
}
@Override
public List<Object> select(Class clazz) throws Exception {
StringBuilder builder = new StringBuilder("select ");
Method[] methods = clazz.getDeclaredMethods();
int columnCount = 0;
List<String> fieldNames = new ArrayList<>();
for(Method m : methods){
if(m.getName().startsWith("set")){
String columnName = m.getName().substring(3).toLowerCase();
fieldNames.add(m.getName().substring(3));
builder.append(columnName).append(",");
columnCount ;
}
}
builder.deleteCharAt(builder.length()-1);
builder.append(" from ").append(clazz.getName().substring(clazz.getName().lastIndexOf(".") 1).toLowerCase());
Connection connection = this.getConnection();
PreparedStatement pstm = connection.prepareStatement(builder.toString());
ResultSet rs = pstm.executeQuery();
List<Object> rtnList = new ArrayList<>();
while(rs.next()){
Object rtnObj = clazz.newInstance();
for(int i = 1; i <= columnCount; i ) {
Object columnValue = rs.getObject(i);
Method m = clazz.getMethod("set" fieldNames.get(i-1));
m.invoke(rtnObj, columnValue);
}
rtnList.add(rtnObj);
}
return rtnList;
}
@Override
public Object selectById(Serializable id, Class clazz) throws Exception {
// select xx,yy,zz from table where id = ?
StringBuilder builder = new StringBuilder("select ");
Method[] methods = clazz.getDeclaredMethods();
int columnCount = 0;
List<String> fieldNames = new ArrayList<>();
for(Method m : methods){
if(m.getName().startsWith("set")){
String columnName = m.getName().substring(3).toLowerCase();
fieldNames.add(m.getName().substring(3));
builder.append(columnName).append(",");
columnCount ;
}
}
builder.deleteCharAt(builder.length()-1);
builder.append(" from ").append(clazz.getName().substring(clazz.getName().lastIndexOf(".") 1).toLowerCase());
builder.append(" where id = ? ");
Connection connection = this.getConnection();
PreparedStatement pstm = connection.prepareStatement(builder.toString());
/* Method getIdMethod = clazz.getMethod("getId");
if(getIdMethod == null){
return null;
}
getIdMethod.invoke(obj, args0, args1);*/
pstm.setObject(1, id);
ResultSet rs = pstm.executeQuery();
if(rs.next()){
Object rtnObj = clazz.newInstance();
for(int i = 1; i <= columnCount; i ) {
Object columnValue = rs.getObject(i);
Method m = clazz.getMethod("set" fieldNames.get(i-1));
m.invoke(rtnObj, columnValue);
}
return rtnObj;
}
return null;
}
private Connection getConnection() throws Exception{
return dataSource.getConnection();
}
}
十三、 MySQL
1 常用窗口函数
- 字符串拼接:concat
- 字符串拆分:substr(“要拆分的字符串”, start, length)
- 获取版本:version()
- 字符串替换:replace(“原字符串”, “被替换的部分字符串”, “新字符串”)
- 日期格式化:date_format(date日期对象, “%y-%m-%d”);
- 内置变量: @@identity 获取当前会话的最近一次自增的数据。
2 查询分析
尽量不要使用having 效率低 在写SQL语句的同时要考虑性能
单表查询时
代码语言:javascript复制select 3
from 1
where 2
group by 4
having 5
order by 6
多表关联查询时
所有的联合查询结果都是笛卡尔积的子集。
代码语言:javascript复制select 3
from t1 join t2 on xxx 1
where 2
group by 4
having 5
order by 6
注意:
1.尽可能的先使用where排除过滤不需要的数据,再分组。
2.where条件如果是多个,先排除过滤掉更多的数据(如:条件是姓名以“张”开头,且是男性 where name like ‘张%’ and 3.gender = ‘男’)。
4.如果可以通过where或子查询解决,尽可能不用having。
5.如果排序有多个条件,在不影响业务结果的前提下,先使用识别度高的字段做排序(如:按照注册时间和姓名排序)。
3 数据库存储引擎
MySQL中提供8个存储引擎, 下面介绍几种主要的存储引擎undefined
- InnoDB - 默认的引擎。支持事务、索引。默认情况下,一句一事务、自动提交。可以使用start transaction;开启事务,通过commit或rollback提交或回滚事务。存储的数据一表一文件。表头,数据,约束在同一个文件中存储。写操作的效率较好,读操作的效率一般。在互联网应用读写分离架构中,写库使用。
- MyISAM - 不支持事务,支持索引。存储的数据一表三文件。分为表头文件、约束文件、数据文件。在使用这个引擎做查询的时候,忌讳select *操作。写select column只要检索数据文件即可。写select * 必须检索表头文件和数据文件。写效率较低,因为每次写入的时候,需要检索表头做字段匹配,检索约束做数据约束查询,访问数据文件写数据。一般在互联网应用读写分离架构中,读库使用。做读写分离的时候,需要建立主备模式。如果读库和写库的表引擎不同,需要先创建表,再建立主备关联。主备关联建立后,只做读写操作,不做建表操作。
- MGR_MyISAM - 和MyISAM的区别是,为表头数据做集合索引,在表头查询管理上效率相对更高。
- Memory - 内存型数据库,一般用于缓存。如:电商中的商品类目信息,可以在数据库启动的时候,通过脚本,读取其他database的数据,整理后,保存在memory引擎库中,项目运行过程中,读取商品类目列表的时候,都访问memory引擎库,查询效率高,且重启中会自动再次初始化。Memory引擎不能完全替代redis之类的内存缓存应用。
十四 索引
在MySQL中,对索引的查看和删除操作是所有索引类型通用的。
1 普通索引
这是最基本的索引,它没有任何限制MyIASM中默认的BTREE类型的索引,也是我们大多数情况下用到的索引。
1.1 创建索引
代码语言:javascript复制索引应该在数据相对稳定后再创建比较好。除非表中的数据必须分散存入。如:客户表的数据是客户注册生成的。客户表这种分散存入数据的存在,会周期性的删除重建索引。
CREATE INDEX index_name ON table_name (column(length)) 推荐
ALTER TABLE table_name ADD INDEX index_name (column(length)) 推荐
CREATE TABLE table_name (
id int not null auto_increment,
title varchar(30) ,
PRIMARY KEY(id) ,
INDEX index_name (title(5))
) 极度不推荐使用.
注意
- 索引不是创建就好了, 而是需要维护的.
- 索引维护方式: 删除重建
1.2 查看索引
代码语言:javascript复制SHOW INDEX FROM [table_name]
SHOW KEYS FROM [table_name] # 只在MySQL中可以使用keys关键字。
1.3 删除索引
代码语言:javascript复制忌讳:除特殊情况外,尽可能的不删除主键索引。undefined
DROP INDEX index_name ON talbe_name -- 全索引删除
ALTER TABLE table_name DROP INDEX index_name --非主键索引删除
ALTER TABLE table_name DROP PRIMARY KEY --删除主键索引
2 唯一索引
与普通索引类似,不同的就是:**索引列的值必须唯一,但允许有空值**(注意和主键不同)。**如果是组合索引,则列值的组合必须唯一**,创建方法和普通索引类似
2.1 创建索引
代码语言:javascript复制CREATE UNIQUE INDEX index_name ON table_name (column(length))
ALTER TABLE table_name ADD UNIQUE index_name (column(length))
CREATE TABLE table_name (
id int not null auto_increment,
title varchar(30) ,
PRIMARY KEY(id) ,
UNIQUE index_name (title(length))
)
3 全文索引(FULLTEXT)
MySQL从3.23.23版开始支持全文索引和全文检索,**FULLTEXT索引仅可用于 MyISAM 表**;他们可以从**CHAR、VARCHAR或TEXT列**中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或CREATE INDEX被添加。 对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个非常消耗时间非常消耗硬盘空间的做法。
3.1 创建索引
代码语言:javascript复制CREATE FULLTEXT INDEX index_name ON table_name(column)
ALTER TABLE table_name ADD FULLTEXT index_name( column)
CREATE TABLE table_name (
id int not null auto_increment,
title varchar(30) ,
PRIMARY KEY(id) ,
FULLTEXT index_name (title)
)
4 组合索引(最左前缀)
代码语言:javascript复制CREATE TABLE article(id int not null, title varchar(255), time date);
平时用的SQL查询语句一般都有比较多的限制条件,所以为了进一步榨取MySQL的效率,就要考虑建立组合索引。例如上表中针对title和time建立一个组合索引:
代码语言:javascript复制ALTER TABLE article ADD INDEX index_title_time (title(50),time(10))。
建立这样的组合索引,其实是相当于分别建立了下面两组组合索引:
代码语言:javascript复制–title
–title,time
为什么没有time这样的组合索引呢?
这是因为MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这两列的查询都会用到该组合索引,如下面的几个SQL所示:
- 使用到上面的索引 SELECT * FROM article WHERE title='测试' AND time=1234567890; SELECT * FROM article WHERE title='测试';
- 不使用上面的索引 SELECT * FROM article WHERE time=1234567890;
5 索引优化
- 上面都在说使用索引的好处,但过多的使用索引将会造成滥用。因此索引也会有它的缺点。虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE次数大于查询次数时,放弃索引。
- 因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件。建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。
参考:通过索引优化SQL分析
5.1 索引不会包含有NULL值的列
代码语言:javascript复制只要列中包含有NULL值都将不会被包含在索引中,组合索引中只要有一列含有NULL值,那么这一列对于此组合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
create table table_name(c1 varchar(32) default ‘0’)
5.2 使用短索引
对串列进行索引,如果可能应该指定一个前缀长度。
例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
代码语言:javascript复制CREATE INDEX index_name ON table_name (column(length))
5.3 索引列排序
- MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;
- 尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
- 优先考虑字段数据识别度,识别度高的排列在前面;其次考虑查询业务场景,查询业务常用的字段,排列在前面。
5.4 like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。 like “�a%” 不会使用索引,而like “aaa%”可以使用索引 (最左前缀)。
5.5 不要在列上进行运算
例如:select * from users where YEAR(adddate)<2007
,
将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′
6 索引总结
- MySQL只对以下操作符才使用索引:<,<=,=,>,>=,between,in, 以及某些时候的like(不以通配符%或_开头的情形)。
- 理论上每张表里面最多可创建16个索引,不过除非是数据量真的很多,否则过多的使用索引也是极不推荐的。
- 建议:一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
7 复杂查询(行列转换)
代码语言:javascript复制处理复杂查询。如:行列转换
case when 条件 then 结果 else 其他结果 end
--DDL:
drop table if EXISTS t_score;
create table t_score(
id int auto_increment primary key,
u_name varchar(32),
c_name varchar(32),
score double
);
代码语言:javascript复制--DML
insert into t_score(u_name, c_name, score) values
('张三', 'JavaSE', 80),
('张三', 'JDBC', 90),
('张三', 'Servlet', 85),
('李四', 'JavaSE', 70),
('李四', 'JDBC', 80),
('李四', 'Servlet', 80),
('王五', 'JavaSE', 90),
('王五', 'JDBC', 90),
('王五', 'Servlet', 60);
问题:
一条SQL实现查看每个学生的每科成绩。 结果展示如下:
实现SQL:
代码语言:javascript复制select u_name as '学生姓名',
max(case when c_name = 'JavaSE' then score else 0 end) as 'JavaSE',
max(case when c_name = 'JDBC' then score else 0 end) as 'JDBC',
max(case when c_name = 'Servlet' then score else 0 end) as 'Servlet'
from t_score
group by u_name
十五 SQL优化方式
1 避免全表扫描
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 商业化开发中,强制要求,不能全表扫描。尽量将查询type提升到ref级别之上,必须是index级别之上。 const > eq_ref > ref > range > index > ALL
MySQL EXPLAIN type类型说明
2 避免判断null值
代码语言:javascript复制应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
代码语言:javascript复制select id from t where num=0
3 避免不等值判断
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
4 避免使用or逻辑
应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
代码语言:javascript复制select id from t where num=10 or num=20
可以这样查询:
代码语言:javascript复制--union all 不会去除重复
--union 去除重复
select id from t where num=10
union all
select id from t where num=20
注意,运算的两个结果集,数据量最好不要超过千单位。
代码语言:javascript复制union - 并集排除重复
union all - 并集不排除重复
5 慎用in和not in逻辑
in 和 not in 也要慎用,否则会导致全表扫描,如:
查询t1表中num为t2中id大于10的人的id
代码语言:javascript复制select id from t1 where num in(select id from t2 where id > 10)
此时外层查询会全表扫描,不使用索引。可以修改为:
将子查询拆分成多表查询
代码语言:javascript复制select id from t1,(select id from t2 where id > 10)t2 where t1.num = t2.id
此时索引被使用,可以明显提升查询效率。
6 注意模糊查询
下面的查询也将导致全表扫描:
代码语言:javascript复制select id from t where name like '�c%'
模糊查询如果是必要条件时,可以使用select id from t where name like 'abc%'
来实现模糊查询,此时索引将被使用。 如果头匹配是必要逻辑,建议使用全文搜索引擎(Elastic search、Solr、Lucene(二者底层实现)等)。
7 避免查询条件中字段计算
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
代码语言:javascript复制select id from t where num/2=100 `
应改为:
代码语言:javascript复制select id from t where num=100*2
8 避免查询条件中对字段进行函数操作
应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
代码语言:javascript复制select id from t where substring(name,1,3)='abc'--name以abc开头的id
应改为:
代码语言:javascript复制select id from t where name like 'abc%'
9 WHERE子句“=”左边注意点
不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
10 组合索引使用
在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
11 不要定义无意义的查询
不要写一些没有意义的查询,如需要生成一个空表结构:
代码语言:javascript复制select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
代码语言:javascript复制create table #t(...)
12 exists
很多时候用 exists 代替 in 是一个好的选择:不是所有情况中都可使用的。
代码语言:javascript复制select num from a where num in(select num from b)
用下面的语句替换:
代码语言:javascript复制select num from a where exists(select 1 from b where num=a.num)
13 索引也可能失效
并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
14 表格字段类型选择
- 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,因为这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
- 尽量使用 varchar 代替 char ,因为首先可变长度字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
15 查询语法中的字段
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
16 索引无关优化
- 不使用*、尽量不使用union,union all等关键字、尽量不使用or关键字、尽量使用等值判断。
- 表连接建议不超过5个。如果超过5个,则考虑表格的设计。(互联网应用中)
- 表连接方式使用外联优于内联。
- 外连接有基础数据存在。如:A left join B,基础数据是A。 A inner join B,没有基础数据的,先使用笛卡尔积完成全连接,在根据连接条件得到内连接结果集。
- 大数据量级的表格做分页查询时,如果页码数量过大,则使用子查询配合完成分页逻辑。 Select * from table limit 1000000, 10 Select * from table where id in (select pk from table limit 100000, 10)