java基础必修

1. JRE和jdk的区别

定义:
   JRE是java运行的环境。面向Java程序使用者,而不是开发者。如果你仅仅下载并安装了JRE,那么你的系统只能运行java程序。JRE是运行java程序所必须的环境的集合,包含JVM标准实现Java核心类库。它包括Java虚拟机,java平台核心类和支持文件。它包括开发工具(编译器和调试器)
JDK又称为J2SDK,是java开发工具包,他提供了java开发环境(提供了编译器,javac等工具,用于将java文件编译为class文件)和运行环境(提供了JVM和RunTime辅助包,用于解析class文件使其运行)。如果你下载并安装了jdk,那么你不仅可以开发程序,也同时拥有了运行java程序的平台。Jdk是整个java核心,包括了java运行环境(JRE),一堆java工具tools. jar和java标准类库。
区别:
JRE主要包含:java类库的class文件(都在lib目录打包成了jar)和虚拟机(jvm. dll);

JDK主要包含:java类库的class文件并自带一个JRE。南无为什么jdk要自带一个jre呢?而且jdk/jre/bin下的client和server两个文件夹都包含jvm. dll(说明jdk自带jre有两个虚拟机)

2. JAVA中equals()和==的区别

  • equals()是判断两个变量或者实例指向同一个内存空间的值是否相同
    ==是判断两个变量或者实例是不是指向同一个内存空间
  • java中的数据类型,可分为两类
  1. 基本数据类型,也称为原始数据类型。Byte,short,char,double,int,long,float,bollean。他们之间的比较应该用= =比较他们的值。
  2. 复合数据类型(类)
    当他们用= =进行比较的时候,比较的是他们在内存存放的地址,所以除非是同一个new出来的对象,他们比较后的结果为true,否者比较的结果为false。JAVA当中所有类都是继承object这个基类得的,在object类中定义了一个equals()的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库中把这个方法覆盖掉了,如String,Integer,Date这些类中equals()有其自身的实现,而不再是比较类在堆内存的存放地址了。对于复合数据类型的equals()比较,在没有覆盖equals()方法的情况下,他们之间比较还是基于他们在内存中存放的地址值的,因为object的equals()方法也是用双等线==进行比较的,所以比较后的结果跟双等号的结果相同。

3. 两个对象的hashCode()相同,则equals()也一定为true吗

  • 首先,答案肯定不一定。同时反过来equals()为true,hashCode()也不一定相同
  • 类的hashCode()方法和equals()方法都可以重写,返回的值完全于自己的定义。
  • hashCode()返回的对象的哈希码值,equals()返回两个对象是否相等。
  • 关于hashCode()和equals()方法是有一些常规协定:
  1. 两个对象用equals()比较返回true,那么两个对象的hashCode()方法必须返回相同的结果。
  2. 两个对象用equals()比较返回false,不要求hashCcode()方法也一定返回不同的值,但是最好返回不同值,以提高hash表的性能,。
  3. 重写equals()方法必须冲重写hashCode方法,以保证equals()方法相等时两个对象的hashCode返回相同的值。

4. String str=“i”与String str=new String(“i”)一样吗

  • 不一样,因为内存分配方式不一样
  1. String str=“i”的方式,java虚拟机会将其分配到常量池中,而String str=new String(“i”)则会被分配到堆内存中。
  2. String str=“i”,因为String是final类型的所以“i”应该是在常量池中,而new String(“i”)则是新建对象放到堆内存中。

5. java如何将字符串反转

  1. 利用StringBuffer或StringBuilder的reverse成员方法
Public static String reverse(String str){
           return new StringBuilder(str). reverse(). toString();
}
  1. 利用String的toCharArray方法将字符串转化为char类型数组,然后将各个字符串进行重新拼接:
Public static String reverse3(String str){
          String reverse=””;
         Int length=str. length();
         for(int i=0;i<length;i++){
reverse=str. charAt(i)+reverse;
}
  return reverse;
}

6. Java中abstract和interface的区别

  • 不同点
  1. Interface需要实现,要用implements,而abstract class需要继承,要用extends。
  2. 一个类可以实现多个interface,单一个类只能继承一个abstract class。
  3. Interface强调特定功能的实现,而abstractClass强调所属关系。
  4. 尽管interface实现类及其abstract class的子类都必须要实现相应的抽象方法,但实现形式不同。Interface中的每一个方法都是抽象方法,都只是声明,没有方法体,实现类必须要实现。而abstract class的子类可以有选择的实现。
  • 相同点
  1. 两者都是抽象类,都不用实例化。
  2. Interface实现类及abstract class子类都必须实现已经声明的抽象的该方法。

7. 抽象类可以使用final修饰吗

  • 当然不可以,通过理解抽象类作用我们发现了抽象类必须要被继承,如果使用final修饰抽象类,这个抽象类就无法被继承,自然就无法使用了。

8. java中的IO流

指的就是将不同的输入输出源通过流的方式进行输入或输出操作,流是一种抽象的描述,在程序中指的是数据的一种转移方式。

  • IO流的分类:
  1. 按照数据流向:输入流,输出流。
  2. 按照流数据格式:字符流,字节流。
  3. 按照流数据的包装过程:节点流(低级流),处理流(高级流)
  • 最基本的几种进行简单的介绍:
  1. inputStream/Reader:说有的输入流的基类,前者是字节输入流,后者是字符输入流。
  2. OutputStream/Writter:所有输出流的基类,前者是字节输出流,后者是字符输出流。
  • java文本文件的读取的大致过程如下:
  1. 构建文件对象,
  2. 使用文件对象构造Reader对象可以是FileReader,InputStreamReader,RandomAccessFile等。
  3. 使用Reader对象构造BufferedReader对象(主要使用**readLine()方法,用于按行读取文件)
  4. 安行读取文件,将每行获取的字符串进行处理。

9. File常用的方法

  • Files. exists()检测文件路径是否存在
  • Files. createFile()创建文件
  • files. createDirectory()穿件文件夹
  • Filse. delete()删除文件或者目录
  • Files. copy()复制文件
  • Files. move()移动文件
  • Files. size()查看文件个数
  • Fies. read()读取文件
  • Files. write()写入文件

10. 类加载过程

  1. 加载
    加载指的将类class文件读取到内存中,并为之创建一个java. lang. Class对象,也就是说,当程序中使用任何类时,系统将为之建立一个java. lang. Class对象
    类加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供这些类加载器通常被称为系统的类加载器。除此以外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
    通过使用不同的类加载器,可以不同来源加载类的二进制数据,通常有如下的几种来源。

    • 从本地文件系统加载class文件,这是当前绝大部分示例程序的类加载方式。
    • 从jar包加载class文件,这种方式也是很常见的,前面介绍JDBC编程用到的数据库驱动类就放在jar文件中,JVM可以从jar文件中直接加载class文件。
    • 通过网络加载class文件。
    • 把一个java源文件动态编译,并执行加载。
      类加载通常无需等待到首次使用该类时才加载该类,java虚拟机允许系统预先加载某些类
  2. 链接
    当类记载之后,系统为之生成一个对应Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中类中。类连接又分为如下三个阶段。

    1. 验证:
      验证阶段用于检验加载的类是否有正确的内部结构,并和其他类协调一致。Java是相对c++语言是安全的语言,例如它有c++不具有的数组越界的检查。在这本身就是对自身安全的一种保护。验证阶段是java非常重要的一个阶段,他会直接保证应用是否会被恶意入侵的一道重要防线,越是严谨的验证机制越安全。验证的主要目的在于确保class文件的字节流中包含信息符合当前虚拟机的要求,不会危害虚拟机的是自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号验证。
      四种验证进一步说明:
    • 文件格式校验:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否当前虚拟机处理的范围内。常量池中是否有不被支持的常量类型。指向常量当中的索引值是否存在常量或者不符合类型的常量。
    • 元数据验证:对字节码描述信息进行语义的分析,分析是否符合java语言语法的规范。
    • 字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要针对元数据验证后对方法体进行验证。保证类方法在运行时不会有危害行为。
    • 符号引用验证:主要针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要确定访问类型数据涉及的引用情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
    1. 准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
    2. 解析:将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是一组符号来描述所引用的目标,符号可以是任何的字面量,只要不会出现冲突能够定位就行。布局和内存无关。直接引用:是指指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
    3. 初始化
      初始化是为类的静态变量赋予正确的初始值,准备阶段和初始阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a=10,它的执行过程是这样的,首先字节码文件被加载到内存中,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始数值0,即a=0,然后解析,到初始化这一步骤,才把a的真正值10赋给a,此时a=10.

11. Java中覆盖和重载的区别

  • 子类继承父类,但重写了父类的方法,因此虽然是从父类拿到的方法但重写之后与父类方法有了区别,因此称为覆盖(即子类方法覆盖父类方法)
  • 重载的含义,一个类中可以有多个同名不同参数的方法。

12. Java的垃圾回收机制

垃圾收集GC是java语言的核心技术之一,在java中程序员不需要关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。

  1. 什么样的对象才是垃圾
    这个问题其实很简单,duiyujava对象来讲,如果说这个对象没有被其他对象所引用,该对象就是无用的,此对象就被称为垃圾,其占用的内存也就被销毁。那么自然而然就引出了我们第二个问题,判断对象为垃圾的算法有哪些?
  2. 标记垃圾算法
    在java中标记垃圾的算法主要有两种,引用计数法和可达性分析法。
  • 引用计数法
    引用计数法就是给对象中添加一个引用计数器,每当有一个地方引用他,计数器就加一,当引用失效,计数器就减一,任何时候计数器为0的对象就是不可能再被使用的,可以当做垃圾回收。这种方法实现起来很简单而且优缺点都很明显
    优点:执行效率很高,程序执行受影响较小。
    缺点:无法检测出循环引用的情况,导致内存泄漏。
    • 可达性分析算法
      这个算法基本思想就是通过一系列称为“GC Roots”对象作为起点,从这些节点开始向下搜索,节点走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的话,则证明此对象是不可引用的。
      那么什么对象可以作为GCRoots?
    • 虚拟机栈中引用的对象
  • 方法区中的常量引用对象
  • 方法区的类静态属性引用对象
  • 本地方方法栈中的运用对象
  • 活跃线程中的引用对象
  1. 如何将垃圾回收
    在java中存在着四种垃圾回收算法,标记清除法,复制算法,标记整理法以及分代回收算法。我们接下来分别介绍他们。
    • 标记清除法
      该算法分为“标记”和“清除”两个阶段:标记阶段的任务是标记所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。他是最基础的收集算法,效率也很高,但是会带来两个明显的问题:
  2. 效率问题
  3. 空间问题(标记清除后产生的大量不连续的碎片)
  • 复制算法
    为了解决效率问题,我们开发出复制算法。他可以将内存分为大小相同的两块。每次使用其中的一块。当第一块的内存使用完后,就将还存活的对象复制到另一块,然后再把使用的空间一次清理掉。这样就使每次内存回收都是对内存空间的一半进行回收。
    简单的来讲就是该对象分为对象面以及空闲面,对象在对象面上创建,对面上存活的对象会被复制到空闲面上,接下来就是可以清除对象面上的内存。
    这种算法的优缺点也很明显:
  1. 优点:解决碎片化问题,顺序分配内存简单高效。
  2. 缺点:只适用于存活率较低的场景上,如果极端情况下对象面上的对象全部存活,就要浪费一半的存储空间。
  • 标记整理法
    为了解决复制算法的缺陷,充分利用内存空间,提出标记整理算法。该算法标记阶段和标记清除一样,但在完成标记之后,它不是直接清理可回收对象,而是将存活的对象都向一端移动,然后清理掉端边界以外的内存。
  • 分代收集算法
    当前虚拟机的垃圾收集都采用分=分代收集算法,这种算法就是根据具体的情况选择具体的垃圾回收算法。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集的算法。
    比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法只需要付出少量对象的复制成本就可以完成每次的垃圾收集。而老年代的对象存活率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记”清除或“标记”整理算法进行垃圾收集。
    ###13. Java的文本文件读取过程
  1. 构建文件对象,
  2. 使用文件构造器Reader对象可以是FileReader,InputStreamReader,randomAcc-
    essFile。
  3. 使用Reader对象构造Buffereder对象(主要按**readLine()方法,勇于按行读取文件)
  4. 按行读取文件,将每行获取的字符串进行处理。

14. 多线程

  • 有三种多线程的方法:
  1. 实现Runnable接口
  2. 实现Callable接口
  3. 继承Thread类
    实现Runnable接口和Callable接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此需要通过Thread来调用。可以理解为任务是通过线程驱动从而执行的。
  • start()和run()的区别
  1. start的方法
    通过该方法启动线程的同时也创建了一个线程,真正实现了多线程。无需等待run()方法中的代码执行完毕,就能接着执行下面的代码。此时start()这个线程处于就绪状态,当得到cpu的时间片后就会执行其中的run()方法,这个run()方法包含了要执行的这个线程的内容,run()方法运行结束,此线程也就终止了。
  2. run方法
    通过run’方法启动线程其实就是调用一个类中的方法,当做普通方法的方式调用。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。
    而run方法是业务逻辑实现的地方,本质上和任意的类的任意成员函数并没有任何区别,可以重复执行,被一个线程反复调用,也可以被单独调用。
  • 总结下
  1. start()可以启动一个新线程,run()不能
  2. Start()不能被反复调用,run可以
  3. Start()中run代码可以不执行就继续执行下面的代码,即进行了主线程的切换。直接调用run方法必须等待其代码全部执行完毕执行完才能继续执行下面的代码。
  4. Start()实现了多线程,run()没有实现多线程

15. java反射机制

  • 什么是反射
  1. java法射机制的核心是在程序运行时动态地加载类并获取类的详细信息,从而操作类或对象的属性或方法。本质是jvm得到class对象后,再通过clas对象进行反编译,从而获取对象的各种属性。
  2. Java属于先编译在运行的语言程序中对象的类型在编译期间就确定下来,而当程序在运行期间可能需要动态加载某些类,这些类因为之前用不到,所以没有加载到JVM。通过反射,可以在运行期间动态地创建对象调用其属性,不需要提前编译期知道运行对象是谁。
  • 反射的原理
    Class对象的由来将. class文件读入内存,并为之创建一个class对象
  • 反射的优缺点
  1. 优点
    在运行期间获得各个类的各种内容,进行反编译,对于java这种先编译在运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
  2. 缺点
    反射会消耗一定的系统资源,因此,如果不需要动态的创建一个对象,那么就不需要用反射。
    反射调用方法时可以忽略权限的检查,因此可能会破坏封装性而导致安全问题。
  • 反射的用途
    1、反编译:. class->. java
    2、通过 反射机制访问java对象的属性方法,构造方法等。
    3、当我们使用IDEA,比如ecplise时,当我们输入一个对象或者类,并想调用它的属性和方法是,一按点号,编译器就会自动列出它的属性或者方法,这里就用到了反射。
    4、反射最重要的用途就是开发各种通用的框架。比如很多框架(spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就用到了反射,运行时动态加载需要加载的对象。

15. hashmap的实现

  • 数组+链表:数组寻址容易,插入删除难,链表寻址难,插入删除容易。
  • 链表+ 长度超过8,转变为红黑树
  • 存put(k,v):首先把(k,v)封装到node对象,然后调用hashcode()方法计算k的hash值,最后通过哈希算法将hash值转化为数组下标,下标如果没有元素就把Node添加到该位置,如果下标对应的位置有链表,比较链表中每个key的equals()返回,若全为true则覆盖,若全为false这添加到链表尾部。
  • 红黑树:
  1. 节点要么黑,要么红。
  2. 根结为黑
  3. 空的叶子节点为黑
  4. 红节点的子节点必须为黑
  5. 从一个节点到该节点的子孙节点的所有路径包含相同数目的黑节点
  6. 保证红黑树的方法:变色,旋转(左旋转and右旋转)
  7. 线程池
    以上我们介绍可以看出,在一个应用程序中,我们需要多次使用线程池,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而java中,内存资源是无比宝贵的,所以,我们就提出了线程池的概念。
  • 线程池:Java中开辟出一种管理线程的概念,这个概念就叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存消耗。
    那么,我们应该如何创建一个线程池呢?Java当中已经提供了创建线程池的一个类:Executor
    而为我们创建时,一般使用他的子类:
ThreadPoolExecutor
  public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)

这是其中最重要的一个构造方法,这个方法决定了创建出的线程池的各种属性,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程数量,而keepAliveTuime就是线程池中 除核心线程以外的其他最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心的线程可以保留最长的空闲时间,而util,就是计算这个时间单位,workQueue,就是等待队列,任务可以存储在任务队列中等待被执行,执行的FIFO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了知乎,拒绝某些任务。

  • 线程池的执行流程又是怎样的
    有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,如果有,就将任务保存在任务队列中,等待执行,如果满了,再判断在在最大容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务如果超出了,就调用handler实现拒绝策略。
  • handler的拒绝策略
  1. CachedThreadPool:可缓存线程池,该线程池中没有核心线程,非核心线程的数量为无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
  2. SecudleThredPool:周期性执行任务的线程,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。使用与执行周期性的任务。
  3. SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
  4. FixedThredPool:定长的线程池,有核心线程,核心线程即为最大的线程数量,没有非核心线程。
  5. 数组与链表的区别
  • 不同:
  1. 链表是链式结构,数组是顺序结构的存储结构。
  2. 链表通过指针连接元素与元素,数组则是把所有元素按次序以此存储。
  3. 链表通过插入删除元素相对数组较为简单,不需要移动元素,且较为简单实现长度扩充,但是寻找某个元素较为困难,属猪寻找某个元素较为简单,但插入删除比较困难,由于最大长度需要编程一开始指定,故当达到最大长度时,扩充长度不如链表方便。
  • 相同:
  1. 两种结构均可实现数据的顺序结构存储,构造出来的模型呈线性结构。
  2. 数据库中事务的四大特性
    如果一个数据库声称支持事务的特性,那么该数据库必须具备以下四个特性:
  • 原子性
    原子性是指事务包含所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须完全应用到数据库,如果数据库操作失败则不能对数据库有任何影响。
  • 一致性
    一致性是指事务必须使得数据从一个一致性状态转变到另一个一致性状态,也就是说一个事物执行之前和执行之后都必须保持一致性状态。
    那转账来说,假设A用户和B用户两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次帐,事务结束后两个用户的钱加起来还得是5000,这就是事务的一致性。、
  • 隔离性
    隔离性是指多个用户并发访问数据库时,比如操作同一张表时,数据库为用户每一个都开启事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
    即要达到一种效果:对于任意两个并发事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发执行。
  • 持久性
    持久性是指一个事物一旦提交了,那么对数据库中的数据改变就是永久性的,即便在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
    例如我们再使用JDBC操作数据库时,在提交事务方法后,提示用户操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库发生故障,也必须要将我们的事务完全执行完成,否者就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
  • 脏读
    脏读是指一个事务处理过程理读取另外一个为未提交的事务的处理数据。当一个事务正在多次修改某个数据,而在这个事务中这多次修改的数据还未提交,这时候一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100
    元,对应的sql语句:
  update account set money=money+100 where name=’B’;  (此时A通知B)

        update account set money=money - 100 where name=’A’;

当只执行第一条sql语句时,A通知B用户查看账户B发现确实钱已经到账(此时即发生了脏读),而之后无论第二条sql语句是否执行,只要该事务不提交,则所有的操作都将会滚,那么当B以后再次查看账户时就会发现钱其实并没有转。

  • 不可重复读
    不可重复度是指在对于数据库中的某个数据,一个事务范围内查询却返回不同的数据值,这是由于存在查询间隔,被另外一个事务修改并提交了。
    例如事务T1在读取某一数据,而事务T2立马修改了这个数据并提交事务给数据库,事务T1再次查询该数据就得到了不同的结果,发生了不可重复读。
    不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务交的数据。
    在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就可能发生问题,例如对同一个数据A和B依次查询就可能不同了,A和B就可能打起来了。。。。
  • 虚读(幻读)
    幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有行的某一个数据项做了从“1”修改为“2”的操作,这时候T2又对这个表中插入一行数据,而这个数据项的值还是为1并且提交到了数据库。而操作事务T1的用户如果再次查看刚刚修改的数据,会发现还有一项没有修改,其实这行是从事务T2添加的,就好像幻觉一样,这就发生了幻读。
    幻读和不可重复读都是读取了另一条已经提交的事务(这点与脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的都是一个数据项,而幻读针对的是一批数据整体(比如数据的个数)

16. mysql为我们提供的四种隔离级别

  1. Serializable(串行化):可避免脏读,不可重复读,幻读的发生。
  2. Repeatable read(可重复读):为避免脏读的发生。
  3. Read committed(读已提交):可避免脏读的发生。
  4. Read uncommitted(读未提交):最低级别,任何情况都无法保证。
    以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高执行的效率越低。像Seriallizable这种级别,就是以锁的方式(类似于java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该按照实际的情况。在mysql数据库中默认的级别为Repeartable read(可重复读)

17. 索引的优缺点

  1. 为什么要创建索引呢(优点)
    这是因为,创建索引可以大大的提高系统的性能,第一,通过唯一索引的创建,可以保证数据库表中每一行数据的唯一性。
    第二,可以大大地加快数据的检索速度,这也是创建索引的最重要的原因。
    第三,可以加快表与表的连接,特别是在实现数据的参考完整性特性方面的特别意义。
    第四,在使用分组与排序的子句进行数据的检索时,同样可以显著减少查询分组和排序的时间。
    第五,通过创建索引和使用索引,可以在查询过程中,使用优化隐藏器,提高系统性能。

  2. 建立索引的不利因素
    也许会有人问:增加索引有如此多的优点,为什么不对表中每一列创建一个索引呢?这种想法固然有其合理性,然而也有其片面性。虽然索引有许多优点,但是为表中每一列添加索引,是非常不明智的选择,这是因为,增加索引也有不利的方面。
    第一,创建索引和维护索引需要耗费很多时间,这种时间随着数据的增多而增加。
    第二,索引需要占据屋里空间,除了数据表占数据空间以外,每一个索引还占有一定的物理空间,如果要建立聚族索引,那么需要的空间就会更大。
    第三,当对表中的数据进行增加,删除,修改的时候,索引也要动态地去维护,这样就降低了数据的维护速度。

  3. 查询索引怎么建立?为什么最左前缀?

    1. 建立索引的原则
      用于索引的最好备选数据列那些出现在where字句,join字句,order by字句或Group by字句中的列。
      仅仅出现在select关键字后面的输出数据列表中的数据列不是很好的备选项

SELECT
col_a <- 不是备选列
FROM
tbl1 LEFT JOIN tbl2
ON tbl1. col_b = tbl2. col_c <- 备选列
WHERE
col_d = expr; <- 备选列
当然,显示数据列与where字句中使用的数据列当然也可能相同。我的观点是输出列表的数据列本质上不是用于索引的很好备选项。

    1. 复合索引的建立以及最左前缀原则
      索引字符串的前缀。如果你需要索引一个字符串数据列,那么最好在任何适当的情况下都应该指定前缀的长度。
      例如,如果有char(200)数据列,如果前面10个或20个字符都不同,就不索引整个数据列,索引前面10个或20个字符会节省大量的空间,你可以索引char,varchar,binary,varbinary,blob和text数据列的前缀。
      假设你在表的state,city和zip数据列建立复合索引。索引中的数据行按照state/city/zip次序排列,因此它们也会自动按照state/city和state次序排列。这就意味着,即使你在查询中只指定了state值,或者指定了state和city值,mysql也可以使用这个索引。因此,这个索引可以被使用与搜索如下所示的数据列组合:state,city,zip. State,city. State. Mysql不能利用这个索引来搜索没有包含在最左前缀的内容。例如,如果你按照city或zip来搜索,就不会使用这个索引。如果你搜索给定的state值和具体的Zip代码(索引1和3列)
      该索引也是不能用于这种组合值得,尽管Mysql可以利用索引来查找匹配的state从而缩小搜索的范围。、

如果你考虑给已经索引过的表添加索引,那么就要考虑你将增加的索引是否已有的多列索引的最左前缀。如果是这样的,不用添加索引,因为已经有了,例如你在state,city和zip 上建立了索引,那么就没必要在增加state索引

18. mysql慢查询

  • 概念
    Mysql的慢查询,全名是慢查询日志,是mysql提供的一种日志记录,用来记录mysql中响应时间超过阈值的语句。
  • 具体环境中,运行时间超过long_query_time值得sql语句,这会被记录到慢查询日志中。
  • long_query_time的默认值为10,意思是记录运行10秒以上的语句。
  • 默认情况下,mysql的数据并不启动慢查询日志,需要手动来设置这个参数。
  • 当然,如果不是调优需要的话,一般不建议启动该参数,因为一旦开启了该参数,应为开启慢查询日志会或多或少带来一定的性能影响。
  • 慢查询日志记录写入文件和数据库。

19. 详解mysql如何处理死锁的

  • 什么是死锁
    官方定义如下:两个事务都持有对方需要的锁,并且等待对方释放,并且双方都不会释放自己的锁。
    这就好比你有一个人质,对哦方有一个人质,你两去谈判说换人。你让对面换人,对面让你换人。
  • 为什么会形成死锁
    看到这里,也许你会有这样的疑问,事务和谈判不一样,为什么事务不能使用完锁就立马释放呢?居然还要操作完之后一直持有锁?这就涉及到mysql的并发控制了。
    Mysql的并发控制有两种方式,一个是MVCC,一个是两阶段的锁协议。那么为什么要并发控制呢?是因为多个用户同时操作mysql的时候,为了提高性能并且要求如同多个用户的请求过来之后如同串行执行一样(可串行化调度)。具体的并发控制这里不再展开。咱们继续深入讨论两阶段锁协议。
    。两阶段锁协议
    官方定义:
    两阶段锁协议是指所有事务必须分为两个阶段对数据加锁和解锁,对任何数据进行读、写操作之前,事务首先要对该数据加锁并封锁,在释放一个锁之后,事务不再申请和获得任何其他封锁。
    对应到Mysql上分为两个为阶段:
    1、扩展阶段(事务之后,commit之前):获取锁
    2、收缩阶段(commit之后):释放锁
    就是说呢,只要遵循两两段协议,才能实现 可串行化调度。

但是两阶段协议不要求事务必须一次性将所有使用的数据加锁,并且加锁阶段没有顺序要求,所以这种并发控制方式形成死锁。

  • mysql如何处理死锁
    Mysql有两种处理死锁的方式:
    1、等待,直到超时。
    2、发起死锁检测,主动回滚一条事务,让其他事务继续执行,由于性能的原因,一般是使用死锁检测来进行处理死锁。
    。死锁检测
    死锁检测的原理是构建一个以事务为顶点,锁为边的有向图,判断有向图是否存在环,存在环即有死锁。
    。回滚
    检测到死锁之后,选择更新或删除的行数最少的事务回滚,基于INFORMATION_SCHEMA. INNODB_TRX表中的trx_weight字段判断。
  • 如何避免死锁
    。收集死锁信息
    1、利用命令SHOW ENGINE INNODBV STATUS查看死锁的原因。
    2、调试阶段开启innodb_print_all_deadlocks,收集所有死锁信息日志。
    。减少死锁
    1、使用事务,不使用lock tables。
    2、保证没有长事务。
    3、操作完之后立即提交事务,特别是在交互式命令行中。
    4、如果在(SELECT . . . FOR UPDATE OR SELECT . . . LOCK SHARE MODE),尝试降低隔离级别。
    5、修改多个表或者多个行的时候,将修改的顺序保持一致。
    6、创建索引,可以使创建的锁更少。
    7、最好不要用(SELECT UPDFATE OR SELECT . . . LOCK IN SHARE MODE)。
    8、如果上述都无法解决问题,那么尝试使用lock table st1,t2,t3锁住多张表。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值