Java基础
Java语言有哪些特点
-
简单易学(Java 语言的语法与 C 语言和 C++语言很接近)
-
面向对象(封装,继承,多态)
-
跨平台性(Java 虚拟机实现跨平台性)
-
支持网络编程并且很方便(Java 语言诞生本身就是为简化网络编程设计的)
-
支持多线程(多线程机制使应用程序在同一时间并行执行多项任)
-
健壮性(Java 语言的强类型机制、异常处理、垃圾的自动收集等)
-
安全性
说说你对面向对象的理解
-
面向对象三大基本特征:封装、继承、多态。
-
封装:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,让外部程序通过该类提供的方法来实现对内部信息的操作和访问,提高了代码的可维护性;
-
继承:实现代码复用的重要手段,通过extends实现类的继承,实现继承的类被称为子类,被继承的类称为父类;
-
多态的实现离不开继承,在设计程序时,我们可以将参数的类型定义为父类型。在调用程序时根据实际情况,传入该父类型的某个子类型的实例,这样就实现了多态。
什么是字节码?采用字节码的好处是什么?
java中的编译器和解释器:Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执
行的二进制机器码---->程序运行。
采用字节码的好处:Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
JVM、JRE 和 JDK 的关系
-
JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
-
JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
-
JVM:Java Virtual Machine 的简称,Java 虚拟机,Java 程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此 Java 语言可以实现跨平台。
-
具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具;而JRE包含了JVM,简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
访问修饰符
public(公开) > protected(子类) > 不写(默认default)(同包)> private(当前类)
Java有哪些数据类型
- 基本数据类型
- 整数类型(byte,short,int,long)
- 浮点类型(float,double)
- 字符型(char)
- 布尔型(boolean)
- 引用数据类型
- 类(class)
- 接口(interface)
- 数组([])
抽象类和接口的区别
-
抽象类多用于在同类事物中有无法具体描述的方法的场景,而接口多用于不同类之间,定义不同类之间的通信规则。
-
接口只有定义,而抽象类可以有定义和实现。
-
接口需要实现implement,抽象类只能被继承extends,一个类可以实现多个接口,但一个类只能继承一个抽象类。
-
抽象类倾向于充当公共类的角色,当功能需要累积时,用抽象类;接口被运用于实现比较常用的功能,功能不需要累积时,用接口。
- JDK8中接口除了支持抽象方法和默认方法之外还支持静态方法
重载(Overload)和重写(Override)的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
- 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
- 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为 private 则子类中就不是重写。
== 和 equals 的区别是什么?
== 比较基本数据类型时,比较的是两个数值是否相等; 比较引用类型是,比较的是对象的内存地址是否相等。 equals() 没有重写时,Object默认以==来实现,即比较两个对象的内存地址是否相等; 重写以后,按照对象的内容进行比较
hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()
hashCode()方法的主要用途是获取哈希码,equals()主要用来比较两个对象是否相等。二者之间有两个约定,如果两个对象相等,它们必须有相同的哈希码;但如果两个对象的哈希码相同,他们却不一定相等。也就是说,equals()比较两个对象相等时hashCode()一定相等,hashCode()相等的两个对象equqls()不一定相等。 加分回答 Object类提供的equals()方法默认是用==来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。而实际的业务中,我们通常的需求是,若两个不同的对象它们的内容是相同的,就认为它们相等。鉴于这种情况,Object类中equals()方法的默认实现是没有实用价值的,所以通常都要重写。 由于hashCode()与equals()具有联动关系,所以equals()方法重写时,通常也要将hashCode()进行重写,使得这两个方法始终满足相关的约定。
static修饰符的用法
-
static可修饰类,方法,代码块,变量。不可以修饰构造器。
-
被static修饰的类为静态类,可以在不创建实例的情况下访问它的静态方法或静态成员变量,而其实例方法或实例成员变量只能通过其实例对象来访问。
-
在静态方法中不能使用this,因为this是随着对象创建而存在。
-
静态成员变量随着静态类的加载而创建。
请你说一下final关键字
-
final被用来修饰类和类的成分。
-
final属性:变量引用不可变,但对象内部内容可变;被final修饰的变量必须被初始化。
-
final方法:该方法不能被重写,但子类可以使用该方法。
-
final参数:参数在方法内部不允许被修改。
-
final类:该类不能被继承,所有方法不能被重写,但未被声明为final的成员变量可以改变。
java中IO流分为几种?
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
请你说说BIO、NIO、AIO
- BIO:同步并阻塞,服务实现模式为一个连接对应一个线程,即客户端发送一个连接,服务端要有一个线程来处理,如果连接多了,线程数量不够,就只能等待,即会发生阻塞。
- NIO:同步非阻塞,服务实现模式为一个线程可以处理多个连接,即客户端发送的连接都会注册到多路复用器上,然后进行轮询连接,有IO请求就处理。
- AIO:异步非阻塞,引入了异步通信,采用的是proactor模式,特点是:有效的请求才启动线程,先由操作系统完成再通知服务端。
说说你对反射的了解
反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。 获取Class对象的三种方式:getClass();xx.class;Class.forName("xxx"); 反射的优缺点: 优点:运行期间能够动态的获取类,提高代码的灵活性。 缺点:性能比直接的Java代码要慢很多。 应用场景:spring的xml配置模式,以及动态代理模式都用到了反射。
说说String类和new String()
//String类的底层
private final char value[];
String类被final修饰,所以不能被继承。创建String对象时可以使用字符串直接量,如String str="1abc", 另一种String str=new String("1abc"),前者使用常量池来管理,后者先判断常量池中是否已存在此字符串,不存在就也在常量池创建,再在堆内存创建一个新的对象,因此后者开销更大。
String、StringBuffer、Stringbuilder有什么区别
- String是final修饰不可被继承不可变,即时修改也是新建一个变量,String通过不变的特性实现了线程之间的可见性,一定条件下可以保证线程安全。
- StringBuffer可变 实现了Synchronize封装使得它线程安全 适用于多线程。
- StringBulider可变 线程不安全 适用于单线程;bulider比Buffer的执行效率高。
Java的异常体系
Java中的所有异常都来自顶级父类Throwable,Throwable 包含两个子类:Error 和 Exception。Error表示严重的错误,通常程序员无法自行解决,可能是虚拟机、磁盘、操作系统层面出了故障。Exception 又分为两类:运行时异常(RuntimeException 例:空指针异常、下标越界)和编译时异常(非RuntimeException 例:IO异常、SQL异常)。
Java的异常处理机制
异常处理机制分为两步,捕获异常与处理异常,我们可以用try包括可能发生异常的代码块,再使用catch方法捕获,在catch方法中我们可以处理异常,最后我们还能使用finally去包括一段不管有无异常都会执行的代码常用于回收资源。
请你讲一下Java 8的新特性
-
Lambda表达式:可将功能视为方法参数,或者将代码视为数据。使用 Lambda 表达式,可以更简洁地表示单方法接口(称为功能接口)的实例。
-
方法引用:提供了非常有用的语法,可直接引用已有Java类或对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
-
对接口进行了改进:允许在接口中定义默认方法,默认方法必须使用default修饰。
-
Stream API 将集合转换为流,对这个流可以进行操作批量改变集合中的数据。
-
Date Time API :加强对日期与时间的处理。
JVM
说说你了解的JVM内存模型
JVM内存模型主要由三部分组成:类加载子系统、执行引擎、运行时数据区。
-
类加载子系统:根据全限定名称来载入类或接口。
-
执行引擎:负责执行那些被包含在被载入类的方法中的指令。
-
运行时数据区,用来存储字节码,对象,参数,返回值,局部变量及运行结果等。运行时数据区包括:方法区,堆,虚拟机栈,本地方法栈,程序技计数器。方法去和堆被所有线程所共享,存在线程安全问题。虚拟机栈,本地方法栈和程序计数器线程私有,不存在线程安全问题。
内存泄漏和内存溢出
- 内存溢出:指的是程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内幕才能,于是就发生了内存溢出。
-
引起内存溢出的原因有:
1.内存加载的数据量过于庞大,如一次从数据库取出过多的数据。
2.代码中存在死循环或者死循环中产生大量的对象实体。
3.启动内存值设定过小。 -
解决内存溢出的方案:
1.修改JVM启动参数,直接增加内存。
2.检查错误日志,查看“OutOfMemory”错误之前是否存在异常。
3.对代码进行debug分析。
4.使用内存工具动态查看内存使用情况。 -
常见的内存溢出出现在:1.堆,对象创建过多。2.栈溢出。3.方法区和运行时常量池,创建大量动态类。
- 内存泄漏:内存泄漏指程序运行过程中分配内存给临时变量,用完之后却没有被GC回收,始终占用着内存,既不能被使用也不能分配给其他程序,于是就发生了内存泄漏。
JVM的类加载机制
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 java 类型。
类加载器有哪些
-
启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,无法被 java 程序直接引用。
-
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
-
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载Java 类 。 一 般 来 说 , Java 应 用 的 类 都 是 由 它 来 完 成 加 载 的 。 可 以 通 过ClassLoader.getSystemClassLoader()来获取它。
-
用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现。
JVM的垃圾回收机制
垃圾回收通常有三件事情要做,第一将决定哪些对象要回收,jvm决定了将未被引用的对象占用空间回收,通常用引用计数法或者可达性分析法,后者是jvm主流使用方法. 第二什么时候回收JVM,通常在内存不够用时,或定期回收, 第三用什么方式回收。有分代回收,分区域回收法,收集器由年轻代收集器Serial,ParNew,Parallel Scavenge, 老年代有CMS,Serial Old, Parallel Old . G1 是分区回收器的新工具,可以并发标记,并发回收。
CMS垃圾回收器
CMS垃圾收集器采用标记清除算法,使用多线程实现,所以它的应用场景一般为服务端系统的老年代。它是一个以达到在垃圾回收期间用户线程低停顿为目标的垃圾收集器。
JVM的双亲委派模型
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行; 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器; 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
JVM调优
- JVM 调优的工具:
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和jvisualvm 这两款视图监控工具。
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
- 常用的 JVM 调优的参数:
-
-Xms2g:初始化推大小为 2g;
-
-Xmx2g:堆最大内存为 2g;
-
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
-
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-
-XX:+PrintGC:开启打印 gc 信息;
-
-XX:+PrintGCDetails:打印 gc 详细信息。
JAVA集合容器
说说你对ArrayList的理解
ArrayList是基于数组实现的,它的内部封装了一个Object[]数组。通过默认构造器创建容器时,该数组先被初始化为空数组,之后在首次添加数据时再将其初始化成长度为10的数组。扩容就是数组拷贝,将旧数组中的数据拷贝到新数组里,而新数组的长度为原来长度的1.5倍。
ArrayList和LinkedList的区别
-
ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。
-
随机查询通过ArrayList查询,删除或增加多用LinkedList实现不需要重新计算索引和大小
-
LinkedList比Array更占内存,链表包括前后指向的引用和数据体
List与Set的区别
- List和Set都是Collection接口的子接口
- List代表有序的元素可以重复的集合,集合中每个元素都有对应的顺序索引。
- Set代表无序的元素不可重复的集合,它通常不能记住元素的添加顺序。
- 加分回答 虽然Set代表无序的集合,但是它有支持排序的实现类,即TreeSet。TreeSet可以确保集合元素处于排序状态,并支持自然排序和定制排序两种排序方式,它的底层是由TreeMap实现的。TreeSet也是非线程安全的,但是它内部元素的值不能为null。
HashMap的实现原理和扩容机制
- 在JDK1.8之前,HashMap的底层是数组加链表,在JDK1.8之后是数组+链表+红黑树; 它的put流程是:基于哈希算法来确定元素位置,当我们向集合存入数据时,他会计算传入的key的哈希值,并利用哈希值取绝对值再根据集合长度取余来确定元素的位置,如果这个位置已经存在其他元素了,就会发生哈希碰撞,则hashmap就会通过链表将这些元素组织起来,如果链表的长度达到8时,就会转化为红黑树,从而提高查询速度。
- 扩容机制:HashMap中数组的默认初始容量为16,当达到默认负载因子0.75时,会以2的指数倍进行扩容。
- Hashmap时非线程安全的,在多线程环境下回产生循环死链,因此在多线程环境下建议使用ConcurrentHashMap。
说说ConcurrentHashMap
ConcurrentHashMap的底层数据结构和hashmap类似都是使用数组+链表+红黑树实现的,所以基本功能和hashmap类似,并在其基础上加上了同步机制。
- 在jdk1.7中使用分段锁segment来对数组进行分段,segment继承自ReentrantLock,分段后每个segment中的数组内容可以分开被线程操作。在size操作时先不加锁,最多计算三次,前后两次结果使相同的话就直接返回,若不相同则加锁计算。
- jdk1.8后采用Synchronize+CAS+Node的方式来保证并发安全性。使用此方式每次同步操作的对象不是整个数组而是每个Entry的头节点,它减小了封锁粒度,提升了效率。查询时不会加锁,只在修改时加锁。线程通过cas来抢占任务,成功后开始加锁操作。并且ConcurrentHashmap支持扩容时执行获取操作。
说说HashMap和Hashtable的区别
HashMap和Hashtable都是典型的Map实现,它们的区别在于是否线程安全,是否可以存入null值。
-
Hashtable在实现Map接口时保证了线程安全性,而HashMap则是非线程安全的。所以,Hashtable的性能不如HashMap,因为为了保证线程安全它牺牲了一些性能。
-
Hashtable不允许存入null,无论是以null作为key或value,都会引发异常。而HashMap是允许存入null的,无论是以null作为key或value,都是可以的。 加分回答 虽然Hashtable是线程安全的,但仍然不建议在多线程环境下使用Hashtable。因为它是一个古老的API,从Java 1.0开始就出现了,它的同步方案还不成熟,性能不好。如果要在多线程环下使用HashMap,建议使用ConcurrentHashMap。它不但保证了线程安全,也通过降低锁的粒度提高了并发访问时的性能。
你知道哪些线程安全的集合
java.uti包中的集合类大部分都是非线程安全的,例如:ArrayList/LinkedList/HashMap等等,但也有少部分是线程安全的,像是Vector和Hashtable,它们属于很古老的API了,是基于Synchronized实现的,性能很差,在实际的开发中不常用。一般可以使用collections工具类中的syncheronizedXxx()方法将非线程安全的集合包装成线程安全的类。在java5之后可以使用concurrent包提供的大量的支持并发访问的集合类,例如ConcurrentHashMap/CopyOnWriteArrayList等
泛型、泛型擦除
- 泛型:Java在jdk1.5引入了泛型,在没有泛型之前,每次从集合中读取的对象都必须进行类型转换,如果在插入对象时,类型出错,那么在运行时转换处理的阶段就出错;在提出泛型之后就可以明确的指定集合接受哪些对象类型,编译器就能知晓并且自动为插入的代码进行泛化,在编译阶段告知是否插入类型错误的对象,程序会变得更加安全清晰。
- 泛型擦除:Java泛型是伪泛型,因为Java代码在编译阶段,所有的泛型信息会被擦除,Java的泛型基本上都是在编辑器这个层次上实现的,在生成的字节码文件中是不包含泛型信息的,使用泛型的时候加上的类型,在编译阶段会被擦除掉,这个过程称为泛型擦除。
并发编程
线程的创建方式
-
继承Thread类,重写run()方法;
-
实现Runnable接口,并实现该接口的run()方法;
-
实现Callable接口,重写call()方法。前两种方式线程执行完后都没有返回值,最后一种带返回值;一般推荐实现Runnable接口的方式。
线程的状态
-
新建状态(New):新创建了一个线程对象。
-
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
-
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
-
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
-
死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期。
线程阻塞
-
等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
-
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
-
其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法
怎么保证线程安全
两个及两个以上线程操作共享资源时,就会造成线程安全问题。解决线程安全问题有三种同步方式:1.原子类.遵循CAS原则,即“比较和替换”适用于解决单共享变量线程安全问题 2.volatile关键字,轻量化的synchronize,适用解决单共享变量的线程安全问题。3.加锁;加锁有两种方式:synchronize关键字和lock接口,适用解决多共享变量的线程安全问题
说说你了解的线程同步方式
在Java中主要通过加锁的方式来实现线程同步,而锁有两类,分别是sychronized和Lock接口;两者之间的区别是:synchronized是隐式锁,出了作用域之后会自动释放。而Lock是显示锁,需要手动开启和释放。synchronizd可以作用在方法和代码块上,而Lock锁只能作用在代码块上。
线程通信方式
线程的通信方式有两种:monitor和condition两种。具体使用那种通信方式与线程同步的方式有关。对于Synchronized来说,使用的是monitor的同步方式。尝试用的方法有waite(),notify(),notifyAll().对于lock锁接口来说,使用的是condition,依赖于lock锁的创建而创建。常使用的方法有await(),signal(),signalAll()。
进程和线程的区别
- 进程是资源分配的最小单位,线程是cpu调度的最小单位。
- 进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而提高效率。
- 线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序小的能独立运行的基本单位。同一个进程中的多个线程之间可以并发执行。
线程和协程的区别
线程是操作系统内核进行命令执行调度的单位,协程是程序内的一种抽象,通过记录程序运行上下文状态信息,模拟多线程执行。
请你说说多线程
-
线程是程序执行的最小单元,一个进程可以拥有多个线程。
-
各个线程之间共享程序的内存空间(代码段、数据段和堆空间)和系统分配的资源(CPU,I/O,打开的文件),但是各个线程拥有自己的栈空间。
-
多线程优点:减少程序响应时间;提高CPU利用率;创建和切换开销小;数据共享效率高;简化程序结构。
说说你对线程池的理解
线程池:主要起到管理线程作用,可以减少因频繁创建线程和销毁线程带来的内存消耗,提高程序的运行效率。
线程池的核心参数
corePoolSize(核心线程数)、workQueue(等待队列)、maxinumPoolSize(最大线程数)、handler(拒绝策略)、keepAliveTime(空闲线程存活时间)。
MySQL
索引的基本原理
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理:就是把无序的数据变成有序的查询
-
把创建了索引的列的内容进行排序
-
对排序结果生成倒排表
-
在倒排表内容上拼上数据地址链
-
在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
索引设计的原则?
查询更快、占用空间更小
-
适合索引的列是出现在where子句中的列,或者连接子句中指定的列
-
基数较小的表,索引效果较差,没有必要在此列建立索引
-
使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
-
不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
-
定义有外键的数据列一定要建立索引。
-
更新频繁字段不适合创建索引
-
若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
-
尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
-
对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
-
对于定义为text、image和bit的数据类型的列不要建立索引。
简述mysql中索引类型及对数据库的性能的影响
-
普通索引:允许被索引的数据列包含重复的值。
-
唯一索引:可以保证数据记录的唯一性。
-
主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建。
-
联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。
-
全文索引:通过建立 倒排索引 ,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
-
索引可以极大的提高数据的查询速度。
-
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
-
但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件
-
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都会跟着变。
事务的基本特性(ACID)
-
原子性:指的是一个事务中的操作要么全部成功,要么全部失败。
-
一致性:指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
-
隔离性:指的是一个事务的修改在最终提交前,对其他事务是不可见的。
-
持久性:指的是一旦事务提交,所做的修改就会永久保存到数据库中。
事务的隔离级别
-
read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。
-
read commit 读已提交,两次读取结果不一致,叫做不可重复读。不可重复读解决了脏读的问题,他只会读取已经提交的事务。用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。
-
repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
-
serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
请你说说InnoDB的MVCC
MVCC为多版本并发控制,指的就是使用读已提交和可重复读这两种隔离级别的事务在执行普通select操作的时候访记录的版本链的过程,这样子可以使不同的事务读写并发操作,从而提高系统性能。 这两个隔离级别的一个很大不同就是生成readview的时机不同,读已提交在每一次进行普通select操作前就会生成一个readview,而可重复读只在第一次进行普通select的时候生成一个readview,数据的可重复读其实就是read view的重复使用。
简单来说就是:读写不冲突,非加锁方式
锁的类型有哪些
-
基于锁的属性分类:共享锁、排他锁。
-
基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎 )、记录锁、间隙锁、临键锁。
-
基于锁的状态分类:意向共享锁、意向排它锁。
-
共享锁又称读锁,简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。
-
排他锁又称写锁,简称X锁;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取。避免了出现脏数据和脏读的问题。
-
表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;
特点: 粒度大,加锁简单,容易冲突; -
行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问;
特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高; -
记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录。精准条件命中,并且命中的条件字段是唯一索引加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。
-
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般 -
间隙锁属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复读)的事务级别中。
触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事务里,A事务的两次查询出的结果会不一样。
比如表里面的数据ID 为 1,4,5,7,10 ,那么会形成以下几个间隙区间,-n-1区间,1-4区间,7-10区间,10-n区间 (-n代表负无穷大,n代表正无穷大) -
临建锁也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住
触发条件:范围查询并命中,查询命中了索引。
结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之后,在范围区间内数据不允许被修改和插入。 -
意向共享锁:当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁。
-
意向排他锁:当一个事务试图对整个表进行加排它锁之前,首先需要获得这个表的意向排它锁。
关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
- 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
- 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
- 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
MySQL性能优化
-
硬件和操作系统层面的优化
从硬件层面来说,影响MySQL性能因素主要是cpu、可用内存大小、磁盘读写速度、网络带宽;从操作系统层面来说,应用文件句柄数、操作系统的网络配置都会影响MySQL的性能。这部分的优化一般是由DBA或者运维工程师去完成。在硬件基础资源的优化中,我们重点应该关注的是服务本身所承载的体量,然后提出合理的指标要求,避免出现资源浪费的一个现象。 -
架构设计层面的优化
MySQL是一个磁盘IO访问非常频繁的关系型数据库,在高并发和高性能的场景中MySQL数据库必然会承受巨大的并发压力。在此时我们优化的方式主要可以分为几个部分:1)搭建MySQL主从集群。单个MySQL服务容易出现单点故障,一旦服务宕机将会导致依赖MySQL数据库的应用全部无法响应,主从集群或者主主集群都可以保证服务的高可用性。 2)读写分离设计。在读多写少的场景中通过读写分离的方案可以去避免读写冲突导致的性能问题。 3)引用分库分表的机制。通过分库可以降低单个服务器节省的一个IO压力;通过分表的方式可以去降低单表数据量,从而去提升sql查询效率。 4)针对热点数据可以引入更为高效的分布数据库,比如Redis、MongoDB等。它们可以很好地缓解MySQL的访问压力,同时还能提升数据的检索性能。 -
MySQL程序配置的优化
MySQL是一个经过互联网大厂检验过的生产级别的成熟数据库,对于木有SQL数据库本身的优化一般可以通过MySQL配置文件my.cnf来完成。比如:1)MySQL5.7版本默认的最大连接数是151个,这个值是可以在my.cnf中去修改; 2)binlog日志,默认是不开启的。我们也可以在这个文件中去修改开启; 3)缓存池Bufferpool,默认大小配置等。而这些配置一般是和用户的安装环境以及使用场景有关系,因此这些配置官方只会提供一个默认的配置,具体的情况还是得由使用者去根据实际情况去修改。修改这些配置需要注意以下两点:1)配置的作用域,它可以分为会话级别和全局范围。2)是否支持热加载。根据这点我们要注意全局参数的设定,对于已经存在的会话是无法失效的,会话参数的设定会随着会话的销毁而失效。3)全局类的统一配置,建议配置在默认配置文件中,否则重启服务会导致配置失效。 -
SQL执行的优化
1)慢SQL的定位和排查。我们可以通过慢查询日志和慢查询日志工具分析得到有问题的SQL列表。
2)执行计划分析。针对慢SQL可以使用关键字explain来查看当前sql的执行计划,可以重点关注type、key、rows、filterd等字段从而去定位该SQL执行慢的根本原因再去有的放矢的进行优化。
3)使用show profile工具。show profile工具由MySQL提供,可以用来分析当前会话中SQL语句资源消耗情况的工具,可以用于SQL调优的测量。在当前会话中默认情况下show profile是关闭状态,打开之后会保存最近15次的运行结果。针对运行慢的SQL,通过profile工具进行详细分析可以得到SQL执行过程中所有资源的开销情况,如CPU开销、内存开销等等。
常见MySQL优化规则
- SQL的查询一定要基于索引来进行数据扫描
- 避免索引列上使用函数或者运算符,这样会导致索引失效
- where字句中like %号尽量放置在右边
- 使用索引扫描,联合索引中的列从左往右,命中越多越好
- 尽可能使用SQL语句用到的索引完成排序,避免使用文件排序的方式
- 查询有效的列信息即可,少用*代替列消息
- 永远用小结果集驱动大结果集
中间件
Redis(缓存)
项目中的缓存是怎么使用的?
项目中由于登录模块中的token基本上每个接口都会用到,使用频率也很高。在token中获取用户登录信息,这些数据如果每次都从数据库中获取,并发量一高就会出现阻塞甚至宕机现象,这也是不得不使用缓存的原因。
Redis的数据类型
Redis的数据结构分为5种,字符串(String)、哈希(hash)、列表(list)、集合(set)、有序集合(zset),string可存储大小为2M的数据;list保证数据线性有序且元素可重复,还可以当做简单的消息队列使用;每种类型支持多个编码,每一种编码采取一个特殊的结构来实现。
-
list(有序可重)的底层数据结构是双向链表/压缩列表
-
set(不可重)= hash + 整数数组
-
zset(有序不重)= ziplist + 跳表
-
hash(存键值对)= ziplist + hash
Redis的持久化策略
-
RDB: redis database 在指定的时间间隔内,将内存中的数据集的快照写入磁盘,文件名dump.rdb 适合大规模的数据恢复,对数据库的完整性和一致性要求不是很高 一定时间间隔备份一次,如果数据库意外down掉,就会失去最后一次快照的所有修改。
-
AOF: append only file 以日志的形式记录每个写操作,只允许追加文件,不允许改写文件,redis启动时会读取这个文件,并从头到尾执行一遍,以此来恢复数据,文件名appendonly.aof 在最恶劣的环境下,也丢失不会超过2秒的数据,完整性较高,但是会对磁盘持续的进行IO,代价太大。企业级最少需要5G才能支持 如果.aof文件大小超过原来的一倍,会进行重写压缩,保留最小的指令集合。
-
优先级 aof>rdb
Redis如何与数据库保持双写一致性
先更新数据库,再删除缓存。如果失败,采用重试机制。
Redis的缓存淘汰策略
- 惰性删除:客户端访问一个key的时候,redis先检查它的过期时间,如果已经过期了就立刻删除这个key。
- 定期删除:redis会将设置了过期时间的key保存到一个字典里面,然后每过十秒就扫描一次。这个定期删除也不扫描字典中所有的key,而是采用了一种简单的弹性策略。
- 定期删除对内存更加友好,而惰性删除对CPU更加友好,所以redis采用的是定期删除+惰性/懒汉式删除。
单线程的Redis为什么这么快
Redis有多快?官方给出的答案是读写速度10万/秒
-
Redis是完全基于内存的,所以读写效率非常的高,当然Redis存在持久化操作,在持久化操作是都是fork子进程和利用Linux系统的页缓存技术来完成,并不会影响Redis的性能。
-
单线程操作:单线程并不是坏事,单线程可以避免了频繁的上下文切换,频繁的上下文切换也会影响性能的。
-
合理高效的数据结构
-
采用了非阻塞lo多路复用机制:多路I/O复用模型是利用select、poll、epoll可以同时监察多个流的lo事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有l0事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
redis的线程模型是怎么样的
redis内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis才叫做单线程的模型。它采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含4个部分:1)多个 socket 2)IO多路复用程序
3)文件事件分派器 4)事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
redis和memcached 的区别
-
存储方式不同:memcache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小;Redis有部份存在硬盘上,这样能保证数据的持久性。
-
数据支持类型:memcache对数据类型支持相对简单;Redis有复杂的数据类型。
-
使用底层模型不同:它们之间底层实现方式,以及与客户端之间通信的应用协议不一样,Redis自己构建了vm机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。value值大小不同:Redis最大可以达到1gb;memcache只有1mb。
如何实现redis事务
Redis通过MULTI、EXEC、WATCH等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
在传统的关系式数据库中,常常用ACID性质来检验事务功能的可靠性和安全性。在Redis中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当Redis运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。
缓存穿透、击穿、雪崩的区别
- 缓存穿透:是指客户端查询了根本不存在的数据,舍得这个请求直达存储层,导致其负载过大甚至造成宕机。
解决方案:1)对于不存在的数据缓存到redis中,设置key、value值为null,并设置一个短期过期时间段,避免过期时间太长影响客户正常使用; 2)对参数进行校验,对不合法参数进行拦截; 3)拉黑该ip地址; 4)使用布隆过滤器
- 缓存击穿:当一份访问量非常大的热点数据缓存失效的瞬间,大量的请求直达存储层,导致服务崩溃。
解决方案:1)设置热点数据“永不过期”; 2)加互斥锁,上面的现象是多个线程同时去数据库查询这条数据,那么我们就可以在第一个查询数据的请求上使用一个互斥锁来锁住它,其他线程走到这一步拿不到锁就只能等着,等第一个线程查询到了数据,然后将数据缓存到redis,后面的线程进来发现有缓存就直接走缓存了
- 缓存雪崩:是指大量的数据设置了同一个过期时间,当值数据同时过期后所有请求都达到数据库。
解决方案:1)随机设置key失效时间,避免大量key同时失效; 2)若是集群部署,可将热点数据均匀分布在不同的Redis库中; 3)热点数据不设置过期时间 4)跑定时任务,在缓存失效前更新新的缓存
简单介绍一下哨兵模式
sentinel,哨兵是 redis 集群中非常重要的一个组件,主要有以下功能:
-
集群监控:负责监控 redis master 和 slave 进程是否正常工作。
-
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
-
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
-
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
-
故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
-
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
-
哨兵通常需要 3 个实例,来保证自己的健壮性。
-
哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
-
对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
Redis的主从同步机制
redis主从同步指任意个从节点都可以从主节点复制数据,除了多个从节点连接到同一个主节点外,一个从节点还可以接收另一个从节点的连接,形成一个树的结构,使得redis可以完成一个单层树复制。连接过程:1,启动一个从节点,从节点发送一个PSYNC命令,主节点收到后开起一个后台线程生成一份RDB文件发送给从节点,从节点收到后线程存入磁盘,再加载进内存(全量同步)。 2,RDB发送完成后,主机点还会发送在生成RDB期间,缓存中新出现的写命令发送给从节点 3,当从节点断开并重新连接时主节点会复制期间丢失的数据给从节点(增量同步)
写数据永远发生在主服务器上,然后同步给从服务器
框架
Spring
简单介绍Spring
Spring是一个开源免费的轻量级的控制反转(IOC)和面向切面(AOP)的容器框架
说说你对IOC的理解
IOC是控制反转的意思,是面向对象的一种编程思想,在不使用IOC的情况下,我们要维护对象与对象之间的关系,可能会造成耦合性问题,而IOC可以避免这种情况的产生,它可以帮我们维护对象之间的关系,降低对象的耦合性问题。 控制反转,控制就是获取资源的方式,之前由程序员new对象主动创建,反转之后,就变成了对象由IOC容器创建,实现ioc的方式是依赖注入(DI)。
如何实现一个IOC容器
-
配置文件配置包扫描路径
-
递归包扫描获取.class文件
-
反射、确定需要交给IOC管理的类
-
对需要注入的类进行依赖注入
说说你对AOP的理解
AOP是面向切面编程,它是一种编程思想,它是一种通过预编译方式和运行期间动态代理的方式实现不修改源代码的情况下给程序动态添加功能的一种技术,可以降低代码的耦合度,便于管理,提高代码的可重用性。 AOP的实现方式有两种: JDK动态代理,可以在运行时创建接口的代理实例。 CGLIB动态代理:可以在运行期间创建子类的动态实例。 AOP的应用场景有:事务,日志管理等。
spring的设计模式
-
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
-
单例模式:Bean默认为单例模式。
-
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
-
模板方法:用来解决代码重复的问题。比如.RestTemplate,JmsTemplate,JpaTemplate。
-
观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现-ApplicationListener。
说说Spring事务管理
Spring为事务提供了一致的模板,支持两种事务编程模型:
- 编程式事务:Spring提供了TransactionTeplate模板,利用模板我们可以通过编程的方式实现事务管理,而无需关心资源获取,复用,释放事务以及异常处理,编程式事务用法麻烦,但是可以事务管理的范围控制得更加精准。
- 声明式事务:声明式事务用法十分方便,我们只需要在类或方法上加上@Transactional注解便可以声明事务性;它是Spring事务管理的亮点,在IOC配置中,指定事务的边界和事务属性,Spring会自动在特定的事务边界上应用事务特性。在springboot中,创建项目的添加的数据库访问的起步依赖时,springboot会自动进行配置,即自动实例化正确的事务管理;另外在@Transcational注解上,我们还可以使用isolation属性声明事务的隔离级别。
spring事务的传播行为
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
-
PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
-
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
-
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
-
PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
-
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
-
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
-
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
spring事务什么时候会失效?
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种
-
发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!解决方法很简单,让那个this变成UserService的代理类即可!
-
方法不是public的
-
数据库不支持事务
-
没有被spring管理
-
异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)
spring的隔离级别
spring有五大隔离级别,默认值为ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
-
ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
-
ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
-
ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server的默认级别;
-
ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL的默认级别;
-
ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
Bean
一般应用中spring容器默认单例模式,bean对象会在应用启动时,也就是spring容器初始化时创建,应用停止时,spring容器被销毁,bean对象也被销毁。
- 生命周期:Bean定义、Bean初始化、Bean生存期、Bean销毁。
- 创建方式:构造器,静态工厂,实例工厂,setter注入的方式。
bean的作用域
-
singleton: 单例,默认作用域。
-
prototype: 原型,每次创建一个新对象。
-
request:请求,每次http请求都会创建一个新对象,该作用域仅在基于web的Spring ApplicationContext情形下有效。
-
session:会话,同一会话共享一个实例,不同的会话使用不同的实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
-
global-session:全局会话,所有会话共享一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- 注意:缺省的Spring bean的作用域是Singleton。使用prototype作用域需要慎重的思考,因为频繁创建和销毁bean会带来很大的性能开销。
单例bean是线程安全的吗
不是,Spring框架中的单例bean不是线程安全的。
spring中的bean默认是单例模式,spring框架并没有对单例bean进行多线程的封装处理。实际上大部分时候spring bean无状态的(比如dao类),所有某种程度上来说bean也是安全的,但如果bean有状态的话(比如view model对象),那就要开发者自己去保证线程安全了,最简单的就是改变bean的作用域,把“singleton"变更为“prototype”,这样请求bean相当于new Bean()了,所以就可以保证线程安全了。
有状态就是有数据存储功能。无状态就是不会保存数据。
BeanFactory和FactoryBean的区别
BeanFactory是最基础的IOC容器,给Spring 的容器定义一套规范,给IOC容器提供了一套完整的规范; FactoryBean只是SpringIOC容器创建Bean的一种形式。
@Autowired和@Resource注解的区别
- @Autowied是Spring提供的注解,@Resource是JDK提供的注解。
- @Autowied是只能按类型注入,@Resource默认按名称注入,也支持按类型注入。@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用
@Component、@Controller、@Repository和@Service的区别
- @Component:这将java类标记为bean。它是任何Spring管理组件的通用构造型。spring的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
- @Controller:这将一个类标记为Spring Web MVC控制器。标有它的Bean会自动导入到loC容器中。
- @Service:此注解是组件注解的特化。它不会对@Component注解提供任何其他行为。您可以在服务层类中使用@Service而不是@Component,因为它以更好的方式指定了意图。
- @Repository:这个注解是具有类似用途和功能的@Component注解的特化。它为DAO提供了额外的好处。它将DAO导入loC容器,并使未经检查的异常有资格转换为Spring DataAccessException。
Spring MVC
说说你对MVC的理解
MVC是一种设计模式,在这种模式下软件被分为三层,即Model(模型)、View(视图)、Controller(控制器)。Model代表的是数据,View代表的是用户界面,Controller代表的是数据的处理逻辑,它是Model和View这两层的桥梁。将软件分层的好处是,可以将对象之间的耦合度降低,便于代码的维护。
Spring Boot、Spring MVC 和 Spring 有什么区别
- spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等
- springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端
- springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、mongodb、es,可以开箱即用
介绍一下Spring MVC的执行流程
-
用户发送请求至前端控制器 DispatcherServlet。
-
DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
-
处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器及处理器拦截器
(如果有则生成)一并返回给 DispatcherServlet。 -
DispatcherServlet 调用 HandlerAdapter 处理器适配器。
-
HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)
-
Controller 执行完成返回 ModelAndView。
-
HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
-
DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
-
ViewReslover 解析后返回具体 View。
-
DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
-
DispatcherServlet 响应用户
Spring Boot
Spring Boot的启动流程
调用run方法,run方法的执行流程是:创建SpringBoot项目时,会默认生成一个application入口类,该类中含有的main方法可以实现项目的启动,main方法中spring application的静态方法,即run方法完成对spring application的实例化操作,针对实例化对象调用另一个run方法来实现整个项目的初始化和启动。run方法的操作有:获取监听器的参数配置,打印banner信息,创建并初始化容器,监听器发送通知。
说说Soring Boot的起步依赖
starter配置,约定大于配置。Spring Boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的 starter(启动器),starter 中整合了该场景下各种可能用到的依赖,用户只需要在 Maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。starter 提供了大量的自动配置,让用户摆脱了处理各种依赖和配置的困扰。所有这些 starter 都遵循着约定成俗的默认配置,并允许用户调整这些配置,即遵循“约定大于配置”的原则。
Spring Boot的自动装配
Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置
说说你对Spring Boot的理解,以及它和Spring的区别
SpringBoot是Spring提供的一个快速开发工具包。SpringBoot本身不提供Spring的核心功能而是作为Spring的脚手框架,达到快速构建项目的目的。 springBoot的核心功能包括:1,自动配置:针对spring常用的功能SpringBoot提供自动配置功能。2,起步依赖:springBoot通过起步依赖为项目的依赖管理提供帮助。起步依赖把常用的库聚合在一起,组成了几个为特定的功能而定制的依赖。3,端点监控:SpringBoot可以对正在运行的项目提供监控。
Spring Boot有哪些优点
可以快速构建项目 - 可以对主流开发框架的无配置集成 - 项目可独立运行,无需外部依赖Servlet容器 - 提供运行时的应用监控 - 可以极大地提高开发、部署效率 - 可以与云计算天然集成
-
独立运行的 Spring 项目
Spring Boot 可以以 jar 包的形式独立运行,Spring Boot 项目只需通过命令“ java–jar xx.jar” 即可运行。 -
内嵌 Servlet 容器
Spring Boot 使用嵌入式的 Servlet 容器(例如 Tomcat、Jetty 或者 Undertow 等),应用无需打成 WAR 包 。 -
提供 starter 简化 Maven 配置
Spring Boot 提供了一系列的“starter”项目对象模型(POMS)来简化 Maven 配置。 -
提供了大量的自动配置
Spring Boot 提供了大量的默认自动配置,来简化项目的开发,开发人员也通过配置文件修改默认配置。 -
自带应用监控
Spring Boot 可以对正在运行的项目提供监控。 -
无代码生成和 xml 配置
Spring Boot 不需要任何 xml 配置即可实现 Spring 的所有配置。
Spring Boot常用的注解
-
@SpringBootApplication:它是SpringBoot的核心注解,用于开启自动配置,准确的说是通过该注解内的
-
springbootApplication注解又包含三个注解:
1.@EnableutoConfiguration注解:组合了 @Configuration 注解,实现配置文件的功能。
2.@ComponentScan,用于扫描指定的包和组件。
3.@SpringBootConfiguration:声明当前类springboot应用的配置类,项目中只能有一个一般无须我们添加。
- @Import:@EnableAutoConfiguration实现自动配置的核心功能是通过@Import注解中的ImportSelection实现的。
- @Conditional:通过在该注解中设置条件用于判断是否加载并配置某个Bean对象。
MyBatis
Mybatis Plus 的优点以及与 Mybatis的区别
Mybatis-Plus是一个Mybatis的增强工具,只是在Mybatis的基础上做了增强却不做改变,MyBatis-Plus支持所有Mybatis原生的特性,所以引入Mybatis-Plus不会对现有的Mybatis构架产生任何影响。
优点:
-
依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring 。
-
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 。
-
预防Sql注入:内置 Sql 注入剥离器,有效预防Sql注入攻击 。
-
通用CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 。
-
多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题 。
-
支持热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
-
支持ActiveRecord:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作
-
支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码(生成自定义文件,避免开发重复代码),支持模板引擎、有超多自定义配置等。
-
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )。
-
支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词 。
-
内置分页插件:基于 Mybatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询。
-
内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询 。
-
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,预防误操作。
-
默认将实体类的类名查找数据库中的表,使用@TableName(value="table1")注解指定表名,@TableId指定表主键,若字段与表中字段名保持一致可不加注解。
mybatis的优缺点有哪些?
- 优点:
(1)简单易学,容易上手(相比于Hibernate) 基于SQL编程;
(2)JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
(3)很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持,而JDBC提供了可扩展性,所以只要这个数据库有针对Java的jar包就可以就可以与MyBatis兼容),开发人员不需要考虑数据库的差异性。
(4)提供了很多第三方插件(分页插件 / 逆向工程);
(5)能够与Spring很好的集成;
(6)MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,解除sql与程序代码的耦合,便于统一管理和优化,并可重用。
(7)提供XML标签,支持编写动态SQL语句。
(8)提供映射标签,支持对象与数据库的ORM字段关系映射。
(9)提供对象关系映射标签,支持对象关系组建维护。
- 缺点:
(1)SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求。
(2)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
介绍一下MyBatis的缓存机制
- 一级缓存:也称为本地缓存,它默认启动且不能关闭,存在于SQLSession的生命周期中,即它是SQLSession级别的缓存。
在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;第二次以后则直接去一级缓存中取。当执行的SQL查询中间发生了增删改的操作,mybatis会把SqlSession的缓存清空。。
- 二级缓存:默认不开启,存在于SqlSessionFactory 的生命周期中,即它是SqlSessionFactory级别的缓存。
二级缓存的作用域是nameSpace。Mybatis需要手动设置启动二级缓存。一个会话,查询一条数据,这个数据会被放在当前会话的一级缓存中;如果会话被关闭了,一级缓存汇总的数据会被保存到二级缓存。新的会话查询信息就会参照二级缓存。
在MyBatis中${}和#{}有什么区别
- ${}:拼接符 使用$设置参数时,MyBatis会创建普通的SQL语句,执行SQL时,直接将参数拼接在SQL中,可能会产生SQL注入攻击,但在某些场景中,比如需要动态指定SQL语句中的列名时,就只能使用$拼接符。
- #{}:占位符 使用#设置参数时,MyBatis会创建预编译的SQL语句,预编译的SQL语句执行效率高,并且可以防止SQL注入攻击,在实际开发中,大部分情况下使用#占位符。
Spring Cloud(微服务)
什么是微服务
- 就目前而言,对于微服务,业界并没有一个统一的标准定义。但通常而言,微服务架构是一种设计模式,或者说是一种设计风格,它提倡将单一的应用程序划分成一组小的服务。
- 微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底的去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事件。从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或者销毁,拥有自己独立的数据库。
微服务的优缺点
- 优点:
1)每个服务足够内聚,足够小,代码易理解,方便修改和维护
2)开发简单,开发效率提高,一个服务可能就只做一件事情
3)微服务能够被小团队单独开发
4)微服务只是业务逻辑的代码,不会和HTML、CSS或其他界面混合
5)每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库
6)易于第三方集成,允许容易且灵活的方式集成自动部署
- 缺点:
1)单一职责原则;例:苹果手机
2)开发人员要处理分布式系统的复杂性增强
3)多服务运维的难度随着服务的增加,运维的压力也在增大
4)服务间的通信成本增加
5)系统集成测试难道加大
微服务技术栈有哪些
-
服务开发:Spring、SpringMVC、SpringBoot
-
服务配置与管理:Netflix的Archaius、阿里的Diamond
-
服务注册与发现:Eureka、Consul、Zookeeper
-
服务调用:Rest、RPC、gRPC
-
服务熔断器:Hystrix、Envoy
-
负载均衡:Ribbon、Nginx
-
服务接口调用:Feign
-
服务路由(Api网关):Zuul
-
服务部署:Docker、OpenStack、Kubernetes
springcloud核心组件有哪些?
-
服务注册与发现——Netflix Eureka、Nacos、Zookeeper
-
客户端负载均衡——Netflix Ribbon、SpringCloud LoadBalancer
-
服务熔断器——Netflix Hystrix、Alibaba Sentinel、Resilience4J
-
服务网关——Netflix Zuul、SpringCloud Gateway
-
服务接口调用——Netflix Feign、 Resttemplate、Openfeign
-
链路追踪——Netflix Sleuth、Skywalking、Pinpoint
-
聚合Hystrix监控数据——Netflix Turbine
-
监控中心---- SpringBoot Admin
-
配置中心——Spring Cloud Config 、Apollo、nacos
微服务架构原理是什么?
主要是面向SOA理念,更细小粒度服务的拆分,将功能分解到各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。
注册中心的原理是什么?
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用
配置中心的原理是什么?
在服务运行之前,将所需的配置信息从配置仓库拉取到本地服务,达到统一化配置管理的目的
配置中心是如何实现自动刷新的?
-
配置中心Server端承担起配置刷新的职责
-
提交配置触发post请求给server端的bus/refresh接口
-
server端接收到请求并发送给Spring Cloud Bus总线
-
Spring Cloud bus接到消息并通知给其它连接到总线的客户端
-
其它客户端接收到通知,请求Server端获取最新配置
-
全部客户端均获取到最新的配置
配置中心是如何保证数据安全的?
1.保证容器文件访问的安全性,即保证所有的网络资源请求都需要登录
2.将配置中心里所有配置文件中的密码进行加密,保证其密文性
3.开发环境禁止拉取生产环境的配置文件
用zookeeper和eureka做注册中心有什么区别?
Zookeeper保证的是CP(一致性,容错性), 而Eureka则是AP(可用性,容错性)。
Ribbon负载均衡原理是什么?
-
Ribbon通过ILoadBalancer接口对外提供统一的选择服务器(Server)的功能,此接口会根据不同的负载均衡策略(IRule)选择合适的Server返回给使用者。
-
IRule是负载均衡策略的抽象,ILoadBalancer通过调用IRule的choose()方法返回Server
-
IPing用来检测Server是否可用,ILoadBalancer的实现类维护一个Timer每隔10s检测一次Server的可用状态
-
IClientConfig主要定义了用于初始化各种客户端和负载均衡器的配置信息,器实现类为DefaultClientConfigImpl
微服务熔断降级机制是什么?
微服务框架是许多服务互相调用的,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。Hystrix 是隔离、熔断以及降级的一个框架。如果调用某服务报错(或者挂了),就对该服务熔断,在 5 分钟内请求此服务直接就返回一个默认值,不需要每次都卡几秒,这个过程,就是所谓的熔断。但是熔断了之后就会少调用一个服务,此时需要做下标记,标记本来需要做什么业务,但是因为服务挂了,暂时没有做,等该服务恢复了,就可以手工处理这些业务。这个过程,就是所谓的降级。
什么是Hystrix?实现原理是什么?
- Hystrix是一个延迟和容错库,旨在隔离对远程系统、服务和第三方库的访问点,停止级联故障,并在 不可避免发生故障的复杂分布式系统中实现快速恢复。主要靠Spring的AOP实现
- 实现原理:
正常情况下,断路器关闭,服务消费者正常请求微服务,一段事件内,失败率达到一定阈值,断路器将断开,此时不再请求服务提供者,而是只是快速失败的方法(断路方法);断路器打开一段时间,自动进入“半开”状态,此时,断路器可允许一个请求方法服务提供者,如果请求调用成功,则关闭断路器,否则继续保持断路器打开状态。断路器hystrix是保证了局部发生的错误,不会扩展到整个系统,从而保证系统的即使出现局部问题也不会造成系统雪崩
注册中心挂了,或者服务挂了,应该如何处理?
- 注册中心挂了,可以读取本地持久化里的配置
- 服务挂了 应该配有服务监控中心 感知到服务下线后可以通过配置的邮件通知相关人员排查问题。
说说你对RPC、RMI如何理解?
- RPC 远程过程调用协议,通过网络从远程计算机上请求调用某种服务。
- RMI:远程方法调用 能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java 虚拟机中的对象上的方法。
什么是服务雪崩?什么是服务限流?
- 当服务A调用服务B,服务B调用C,此时大量请求突然请求服务A,假如服务A本身能抗住这些请求,但是如果服务C抗不住,导致服务C请求堆积,从而服务B请求堆积,从而服务A不可用,这就是服务雪崩,解决方式就是服务降级和服务熔断。
- 服务限流是指在高并发请求下,为了保护系统,可以对访问服务的请求进行数量上的限制,从而防止系统不被大量请求压垮,在秒杀中,限流是非常重要的。
什么是服务熔断?什么是服务降级?区别是什么?
- 服务熔断是指,当服务A调用的某个服务B不可用时,上游服务A为了保证自己不受影响,从而不再调用服务B,直接返回一个结果,减轻服务A和服务B的压力,直到服务B恢复。
- 服务降级是指,当发现系统压力过载时,可以通过关闭某个服务,或限流某个服务来减轻系统压力,这就是服务降级。
- 区别:
1)相同点:1.都是为了防止系统崩溃 2.都让用户体验到某些功能暂时不可用
2)不同点:熔断是下游服务故障触发的,降级是为了降低系统负载
SOA、分布式、微服务之间有什么关系和区别?
-
分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和微服务基本上都是分布式架构的
-
SOA是一种面向服务的架构,系统的所有服务都注册在总线上,当调用服务时,从总线上查找服务信息,然后调用
-
微服务是一种更彻底的面向服务的架构,将系统中各个功能个体抽成一个个小的应用程序,基本保持一个应用对应的一个服务的架构
SpringBoot 和 SpringCloud 的区别
- SpringBoot 专注于快速方便的开发单个个体微服务。
- SpringCloud 是关注全局的微服务协调整理治理框架,它将 SpringBoot 开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
- SpringBoot 可以离开 SpringCloud 独立使用开发项目, 但是 SpringCloud 离不开 SpringBoot ,属于依赖的关系
- SpringBoot 专注于快速、方便的开发单个微服务个体,SpringCloud 关注全局的服务治理框架。
Spring Cloud 和 dubbo 的区别
-
服务调用方式 dubbo 是 RPC; springcloud 是 Rest Api
-
注册中心,dubbo 是 zookeeper; springcloud 是 eureka,也可以是 zookeeper
-
服务网关,dubbo 本身没有实现,只能通过其他第三方技术整合;springcloud 有 Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud 支持断路器,与 git 完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。
-
dubbo停更了5年,于2017.7重启
手写代码题
手写一个线程安全的单例
//双重检验锁保证线程安全的懒汉式单例
public class Single {
//私有构造函数
private Single(){}
//声明对象
private static Single single = null;
//暴露获取对象的方法
public static Single getInstance(){
if(single == null){
synchronized (Single.class){
if(single == null)
single = new Single();
}
}
return single;
}
}
//静态内部类
public class Single {
private Single(){}
private static class innerSingle(){
private static final Single instance = new Single();
}
public static Single getInstance(){
return innerSingle.instance;
}
}
//枚举
public enum Single(){
//定义一个枚举代表一个单例
INSTANCE;
}
排序算法
//简单选择排序
public void selectSort (int[] arr){
//遍历所有的数
for (int i=0; i < arr.length ; i ++){
int minIndex = i ;
//把当前遍历的数和后面所有的数进行比较,并记录下最小的数的下标
for (int j=i+1;j < arr.length;j++){
if (arr[j] < arr[minIndex]){
//记录最小的数的下标
minIndex = j ;
}
}
//如果最小的数和当前遍历的下标不一致,则交换
if (i != minIndex){
int temp = arr [i];
arr [i]= arr [minIndex];
arr [minIndex]= temp ;
}
}
}
//冒泡排序
public void bubbleSortOpt (int[] arr ){
if (arr ==nul1){
throw new NullPoniterException ();
}
if (arr.length < 2){
return;
}
int temp =0;
for (int i=0; i < arr.length-1; i++){
for(int j=0; j < arr.length-i-1; j++){
if (arr[j] > arr[j+1]){
temp = arr[j];
агг[j]= arr[j+1];
arr[j+1]= temp;
}
}
}
}
//快速排序
public void quicksort (int[] arr, int start , int end ){
if ( start < end ){
//把数组中的首位数字作为基准数
int stard = arr[start];
//记录需要排序的下标
int low = start ;
int high = end ;
//循环找到比基准数大的数和比基准数小的数
while (1ow < high ){
//右边的数字比基准数大
while (1ow < high && arr[high] >= stard ){
high --;
}
//使用右边的数替换左边的数
arr[low] = arr[high];
//左边的数字比基准数小
while (1ow < high && arr[low]<= stard ){
low++;
}
//使用左边的数替换右边的数
arr[high] = arr[low];
}
//把标准值赋给下标重合的位置
arr[low] = stard ;
//处理所有小的数字
quickSort (аrr, start , low );
//处理所有大的数字
quickSort (arr, low+1, end );
}
}