比较对象内容是否一致。
和equals成对出现。计算对象的hash码值。
资源释放。GC调用。
一般在重量级的工具中定义。如:DataSource、SqlSessionFactory、线程池
克隆方法(原型模式)。深拷贝?浅拷贝?
不推荐使用。单例对象,无所谓克隆。非单例对象,不需要克隆。
在业务实现流程中,如果需要留存备份,做备份缓存的时候,可能使用。
native方法:底层方法, 是基于C语言实现 java中的序列化是增量序列化 add向末尾加 remove 末尾删除 调用这些方法时, 一般是arraylist快 数组扩容时,相反
Set是只有key没有value的map。
键值对。key不重复。value不限制。所有的map在使用的时候,建议,多数据导入的时候,批量实现。undefined
是Java5开始提供的,线程安全的特殊实现包。其中包括线程安全的集合,线程池,线程锁,线程安全的包装对象(Atomic) current包 保存了list set map接口,作用是保证线程安全
本身类型的对象代表线程。
常用方法:
其中定义了方法run
线程资源绑定对象。底层是map。key是Thread.currentThread()。value是要绑定的资源。
定义:一次性创建若干线程对象,在使用的时候,直接启动。当启动的线程终止后,返回到线程池,等待后续使用。
注意:代码中使用了线程池。那么最终一定要shutdown。否则进程不会结束。因为线程池是由一个精灵线程管理的,即使所有的线程都结束了,精灵线程也会用就运行下去。
Executor:顶级接口
Executors:线程池工具类,定义了若干静态方法,快速创建线程池对象。
ExecutorService:常用接口,代表线程池
网络开发编码相对简单。复杂在于网络的模式。是否阻塞,是否异步。 常见的Socket开发,都是同步阻塞的。 开发最简单。
监听某端口,提供服务。
ServerSocket ss = new ServerSocket(port);
ss.accept(socket);
Socket s = new Socket(ip, port);
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询前去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。
以银行取款为例:
同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
以银行取款为例:
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器通知可读写时再继续进行读写,不断循环直到读写完成)
Blocking IO: 同步阻塞的编程方式。
BIO编程方式通常是在JDK1.4版本之前常用的编程方式。编程实现过程为:首先在服务端启动一个ServerSocket来监听网络请求,客户端启动Socket发起网络请求,默认情况下ServerSocket回建立一个线程来处理此请求,如果服务端没有线程可用,客户端则会阻塞等待或遭到拒绝。
且建立好的连接,在通讯过程中,是同步的。在并发处理效率上比较低。大致结构如下:
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
使用线程池机制改善后的BIO模型图如下:
Unblocking IO(New IO): 同步非阻塞的编程方式。
NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的最大并发问题,NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会通知相应的应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。
同步非阻塞,服务器实现模式为一个请求一个通道,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程复杂,JDK1.4开始支持。
Asynchronous IO: 异步非阻塞的编程方式
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
通过JDBC通用开发回顾来复习
JavaEE企业级规范|标准。开发基于网络请求-应答模型的应用,代码应该遵循Servlet标准规范。 Servlet源码讲解
标准中定义:
遵循这套规范,提供服务端容器的,称为中间件提供商(Tomcat);遵循这套规范开发提供服务的,称为服务提供者(编写Servlet相关代码的)。
interface Servlet{}
class GenericServlet implements Servlet{}
class MyServlet extends GenericServlet{}
class Main(){
public static void main(String[] args){
Servlet s = (Servlet) new MyServlet();
}
}
过滤器(Filter) VS 拦截器(Interceptor)
监听器。监听各种事件,处理不同的事件。
常用Listener:
注意:
MVC:分层,模型层(module)、视图层(view)、控制层(controller)。
Java虚拟机将堆内存划分为新生代、老年代和永久代,永久代是HotSpot虚拟机特有的概念( JDK1.8之后为metaspace替代永久代),它采用永久代的方式来实现方法区,其他的虚拟机实现没有这一概念,而且HotSpot也有取消永久代的趋势,在JDK 1.7中HotSpot已经开始了“去永久化”,把原本放在永久代的字符串常量池移出。
永久代主要存放常量、类信息、静态变量等数据,与垃圾回收关系不大,新生代和老年代是垃圾回收的主要区域。
内存简图如下:
在新生代中经历了多次(具体看虚拟机配置的阀值,一般为15)GC后仍然存活下来的对象会进入老年代中。
老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据
对这一区域而言,Java虚拟机规范指出可以不进行垃圾收集,一般而言不会进行垃圾回收。
Scavenge GC,指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Scavenge GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Scavenge GC。
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
当年轻代堆空间紧张时会被触发
相对于全收集而言,收集间隔较短
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。
垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。
垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。
此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。简图如下:
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。
此算法需要暂停整个应用,同时,会产生内存碎片。简图如下:
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。简图如下:
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它的特点是:只用一个CPU(计算核心)/一条收集线程去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World” -后面简称STW)。可以使用-XX:+UseSerialGC打开。
虽然是单线程收集, 但它却简单而高效, 在VM管理内存不大的情况下(收集几十M~一两百M的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内。
ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: +UseConcMarkSweepGC的默认新生代收集器)。
由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial. 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(ParNew收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads=参数控制GC线程数)。
与ParNew类似, Parallel Scavenge也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量:
系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运算而不需要太多交互的任务-可以最高效率地利用CPU时间,尽快地完成程序的运算任务. Parallel Scavenge提供了如下参数设置系统吞吐量:
Serial Old是Serial收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法
Parallel Old是Parallel Scavenge收集器的老年代版本, 使用多线程和“标记-整理”算法, 吞吐量优先, 主要与Parallel Scavenge配合在注重吞吐量及CPU资源敏感系统内使用;
CMS(Concurrent Mark Sweep)收集器是一款具有划时代意义的收集器, 一款真正意义上的并发收集器, 虽然现在已经有了理论意义上表现更好的G1收集器, 但现在主流互联网企业线上选用的仍是CMS(如Taobao、微店).
CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器), 基于”标记-清除”算法实现, 整个GC过程分为以下4个步骤:
CMS特点:
G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗CPU的服务器治理大内存.
每块区域既有可能属于O区、也有可能是Y区, 因此不需要一次就对整个老年代/新生代回收. 而是当线程并发寻找可回收的对象时, 有些区块包含可回收的对象要比其他区块多很多. 虽然在清理这些区块时G1仍然需要暂停应用线程, 但可以用相对较少的时间优先回收垃圾较多的Region. 这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率.
G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions. 如果存活时间达到阀值, 这部分对象就会被提升到老年代.如图:
其特点是:
一整块堆内存被分为多个Regions.
存活对象被拷贝到新的Survivor区或老年代.
年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域尺寸.
Young GC会有STW事件, 进行时所有应用程序线程都会被暂停.
多线程并发GC.
G1老年代GC特点如下:
见资料—jvm优化4.2.3
类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间。
除了类的信息外,方法区中可能还会存放运行时常量池信息,包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。
java堆在虚拟机启动的时候建立,它是java程序最主要的内存工作区域。几乎所有的java对象实例都存放在java堆中。
堆空间是所有线程共享的,这是一块与java应用密切相关的内存空间。
java的NIO库允许java程序使用直接内存。直接内存是在java堆外的、直接向系统申请的内存空间。
通常访问直接内存的速度会优于java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。
由于直接内存在java堆外,因此它的大小不会直接受限于Xmx指定的最大堆大小,但是系统内存是有限的,java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
垃圾回收系统是java虚拟机的重要组成部分,垃圾回收器可以对方法区、java堆和直接内存进行回收。
其中,java堆是垃圾收集器的工作重点。和C/C++不同,java中所有的对象空间释放都是隐式的,也就是说,java中没有类似free()或者delete()这样的函数释放指定的内存区域。
对于不再使用的垃圾对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括java堆、方法区和直接内存中的全自动化管理。
每一个java虚拟机线程都有一个私有的java栈,一个线程的java栈在线程创建的时候被创建
java栈中保存着帧信息、局部变量、方法参数,同时和java方法的调用、返回密切相关。
本地方法栈和java栈非常类似,最大的不同在于java栈用于方法的调用,而本地方法栈则用于本地方法的调用,
作为对java虚拟机的重要扩展,java虚拟机允许java直接调用本地方法(通常使用C编写)
PC(Program Counter)寄存器也是每一个线程私有的空间,java虚拟机会为每一个java线程创建PC寄存器。
在任意时刻,一个java线程总是在执行一个方法,这个正在被执行的方法称为当前方法。
如果当前方法不是本地方法,PC寄存器就会指向当前正在被执行的指令。
如果当前方法是本地方法,那么PC寄存器的值就是undefined
执行引擎是java虚拟机的最核心组件之一,它负责执行虚拟机的字节码,现代虚拟机为了提高执行效率,会使用即时编译(just in time)技术将方法编译成机器码后再执行。
Java HotSpot Client VM(-client),为在客户端环境中减少启动时间而优化的执行引擎;本地应用开发使用。(如:eclipse)
Java HotSpot Server VM(-server),为在服务器环境中最大化程序执行速度而设计的执行引擎。应用在服务端程序。(如:tomcat)
Java HotSpot Client模式和Server模式的区别
注意:在部分JDK1.6版本和后续的JDK版本(64位系统)中,-client参数已经不起作用了,Server模式成为唯一
jps - l
显示线程id和执行线程的主类名
jps -v
显示线程id和执行线程的主类名和JVM配置信息
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:垃圾回收消耗总时间
NGCMN:新生代最小容量 NGCMX:新生代最大容量 NGC:当前新生代容量 S0C:第一个幸存区大小 S1C:第二个幸存区的大小 EC:伊甸园区的大小 OGCMN:老年代最小容量 OGCMX:老年代最大容量 OGC:当前老年代大小 OC:当前老年代大小 MCMN:最小元数据容量 MCMX:最大元数据容量 MC:当前元数据空间大小 CCSMN:最小压缩类空间大小 CCSMX:最大压缩类空间大小 CCSC:当前压缩类空间大小 YGC:年轻代gc次数 FGC:老年代GC次数
配置方式:java options MainClass arguments
options - JVM启动参数。 配置多个参数的时候,参数之间使用空格分隔。
参数命名: 常见为 -参数名
参数赋值: 常见为 -参数名=参数值 | -参数名:参数值
-Xmx3550m -Xms3550m -Xmn2g -Xss128k
适合开发过程的测试应用。要求物理内存大于4G。
-Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=160m -XX:MaxTenuringThreshold=0
适合高并发本地测试使用。且大数据对象相对较多(如IO流)讲原理,方法论。 掌握要求:理解、能够描述、可以在开发过程中,找到使用的框架中可能的设计模式。undefined
类不能被外部代码new对象。类型只有唯一的一个对象。
目的:避免线程级资源重复创建、避免特征类型对象重复创建。
线程级资源:在JVM生命周期中,只需要唯一一份的对象。如:DataSource、SqlSessionFactory、Spring(ApplicationContext)
特征类型:class ManStat{} class WomanStat{}。 Season
特点: 私有化构造, 唯一化对象
懒汉:使用的时候创建唯一对象。多线程并发可能创建多对象。线程不安全
饿汉:类加载的时候,创建唯一对象。可以保证多线程环境中的单例。线程安全
静态工厂:不需要工厂类型的对象,就可以得到产品的开发方式。都用于开发工具类。
实例工厂:必须先有工厂类型的对象,才能创建产品的开发方式。实例工厂的实现,有多种。
单产品工厂:一个工厂中只有一个方法,只创建唯一一种产品。
多产品工厂:
多方法实现:之前学习的简单工厂。有多个方法,每个方法生产不同的产品对象。
配置实现:通过文本或配置类型来决定,工厂可以生产什么产品。需要反射技术配合。
接口隔离思想实现工厂:
工厂应该有多态特性。定义一个接口标准(class或interface),通过子类型或实现类提供生产产品的具体实现。在定义接口标准的时候,如果标准特征太多,建议定义成多个小的接口。(可以定义一个子接口,继承所有的标准。类似mvc开发的service层 / DAO层接口类以及实现类)
是工厂模式的升级。将工厂拆分成链状。大的工厂创建小的工厂,小的工厂创建产品。 如:JDBC, DriverManager -> Connection -> Statement -> ResultSet
使用一个Builder构建器类型的对象,通过一系列的配置、操作,最终得到想要的产品对象。类似工厂。如:StringBuilder、XxxRequestBuilder。
StringBuilder可以成为一个构建器。用于构建一个字符串对象。
可以通过配置或操作,定义最终的产品的特征。
StringBuilder builder = new StringBuilder("");
builder.append(“abc”); // 操作或配置
String str = builder.toString(); // 构建
大多数构建器模式编写的构建器对象,特征是: new XxxBuilder() -> builder.xxx() -> createXxx()|build()|xxxx -> 产品对象。
对已有的对象,进行包装,提供更强的功能。如:IO。
new BufferedReader(new InputStreamReader(new FileInputStream(“path”), “UTF-8”));
代理就是给某对象增加额外功能,且不能修改这个对象对应的源码。undefined
给指定类型的对象,增加固定的额外逻辑。
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();
给多个类型,增加额外的附属逻辑,且不修改源码。
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获取的是类型直接实现的接口,而不是所有的接口。需要通过递归(循环)的方式,找到所有父类实现的接口。否则生成的代理对象,有可能不可使用。
几乎和代理一样,就是不加额外功能。相当于Java对象关系中的依赖。A类型中有B类型的引用,A类型的方法中调用了B类型中的某方法。
一般都和接口回调类似。定义一个标准,代表某一个结果。(如:方法insert()代表新增数据到数据库。 insert(DoInsert接口), 接口的方法为doInsert(Connection conn); 那么,使用这个标准的人,就需要提供接口的实现,实现过程每个人提供的不同。最终的结果都是数据入库。)
定义一个规则,当什么情况发生的时候,调用什么方法。如:HttpServlet中的service方法。
创建某类型的实例,根据这个实例,创建其他的对象。首先创建的实例就是原型。对clone的实现。
在类型中,clone方法,是对象浅拷贝。 实现原型设计模式,必须重写clone方法。除非原型类型中没有任何引用类型的属性。
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();} }
当类型的实例,在创建过程中相对复杂,且有规律的时候,可以考虑使用原型。
将代码分为三个角色,分别是观察者,事件源,事件。当观察者创建后,事件源发生事件的时候,由观察者自动处理。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>
将一个复杂的操作,拆分成若干个简单的操作,顺序执行。当任何节点执行错误,直接返回,所有节点执行正常,总体逻辑执行结束。Filter是典型的责任链设计模式。
类型A的所有特性都开发给类型B使用。
class A{ … }
class B extends A{ … }
//类加载
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());
}
}
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();
}
}
尽量不要使用having 效率低 在写SQL语句的同时要考虑性能
select 3
from 1
where 2
group by 4
having 5
order by 6
所有的联合查询结果都是笛卡尔积的子集。
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.如果排序有多个条件,在不影响业务结果的前提下,先使用识别度高的字段做排序(如:按照注册时间和姓名排序)。
MySQL中提供8个存储引擎, 下面介绍几种主要的存储引擎undefined
在MySQL中,对索引的查看和删除操作是所有索引类型通用的。
这是最基本的索引,它没有任何限制MyIASM中默认的BTREE类型的索引,也是我们大多数情况下用到的索引。
索引应该在数据相对稳定后再创建比较好。除非表中的数据必须分散存入。如:客户表的数据是客户注册生成的。客户表这种分散存入数据的存在,会周期性的删除重建索引。
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))
) 极度不推荐使用.
注意
SHOW INDEX FROM [table_name]
SHOW KEYS FROM [table_name] # 只在MySQL中可以使用keys关键字。
忌讳:除特殊情况外,尽可能的不删除主键索引。undefined
DROP INDEX index_name ON talbe_name -- 全索引删除
ALTER TABLE table_name DROP INDEX index_name --非主键索引删除
ALTER TABLE table_name DROP PRIMARY KEY --删除主键索引
与普通索引类似,不同的就是:**索引列的值必须唯一,但允许有空值**(注意和主键不同)。**如果是组合索引,则列值的组合必须唯一**,创建方法和普通索引类似
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))
)
MySQL从3.23.23版开始支持全文索引和全文检索,**FULLTEXT索引仅可用于 MyISAM 表**;他们可以从**CHAR、VARCHAR或TEXT列**中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或CREATE INDEX被添加。 对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个非常消耗时间非常消耗硬盘空间的做法。
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)
)
CREATE TABLE article(id int not null, title varchar(255), time date);
平时用的SQL查询语句一般都有比较多的限制条件,所以为了进一步榨取MySQL的效率,就要考虑建立组合索引。例如上表中针对title和time建立一个组合索引:
ALTER TABLE article ADD INDEX index_title_time (title(50),time(10))。
建立这样的组合索引,其实是相当于分别建立了下面两组组合索引:
–title
–title,time
为什么没有time这样的组合索引呢?
这是因为MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这两列的查询都会用到该组合索引,如下面的几个SQL所示:
参考:通过索引优化SQL分析
只要列中包含有NULL值都将不会被包含在索引中,组合索引中只要有一列含有NULL值,那么这一列对于此组合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
create table table_name(c1 varchar(32) default ‘0’)
对串列进行索引,如果可能应该指定一个前缀长度。
例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
CREATE INDEX index_name ON table_name (column(length))
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。 like “%aaa%” 不会使用索引,而like “aaa%”可以使用索引 (最左前缀)。
例如:select * from users where YEAR(adddate)<2007
,
将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′
处理复杂查询。如:行列转换
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
);
--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:
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
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 商业化开发中,强制要求,不能全表扫描。尽量将查询type提升到ref级别之上,必须是index级别之上。 const > eq_ref > ref > range > index > ALL
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
--union all 不会去除重复
--union 去除重复
select id from t where num=10
union all
select id from t where num=20
注意,运算的两个结果集,数据量最好不要超过千单位。
union - 并集排除重复
union all - 并集不排除重复
in 和 not in 也要慎用,否则会导致全表扫描,如:
查询t1表中num为t2中id大于10的人的id
select id from t1 where num in(select id from t2 where id > 10)
此时外层查询会全表扫描,不使用索引。可以修改为:
将子查询拆分成多表查询
select id from t1,(select id from t2 where id > 10)t2 where t1.num = t2.id
此时索引被使用,可以明显提升查询效率。
下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
模糊查询如果是必要条件时,可以使用select id from t where name like 'abc%'
来实现模糊查询,此时索引将被使用。 如果头匹配是必要逻辑,建议使用全文搜索引擎(Elastic search、Solr、Lucene(二者底层实现)等)。
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100 `
应改为:
select id from t where num=100*2
应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
应改为:
select id from t where name like 'abc%'
不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
很多时候用 exists 代替 in 是一个好的选择:不是所有情况中都可使用的。
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。