Java开发的基础知识整理(自己整理的,干货)基础知识,数据库,网络,Redis

********************************************************************************************************************************************************
        一.java基础面试知识点
        
1、java中==和equals和hashCode的区别:
        1、equals方法用于比较对象的内容是否相等(覆盖以后)

        2、hashcode方法只有在集合中用到

        3、当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。
        4、将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。.
        

2、int、char、long各占多少字节数
    
        
        1字节: byte , boolean
        2字节: short , char
        4字节: int , float
        8字节: long , double

        编码与中文:
        Unicode/GBK: 中文2字节
        UTF-8: 中文通常3字节,在拓展B区之后的是4字节
        综上,中文字符在编码中占用的字节数一般是2-4个字节。
        
3、int与integer的区别
        1、Integer是int的包装类,int则是java的一种基本数据类型 
        2、Integer变量必须实例化后才能使用,而int变量不需要 
        3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 
        4、Integer的默认值是null,int的默认值是0
        
        
        
        由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。
        
        Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)
        
        非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)
        
        对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false;java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);而java API中对Integer类型的valueOf的定义如下:
                                        public static Integer valueOf(int i){
                                    assert IntegerCache.high >= 127;
                                    if (i >= IntegerCache.low && i <= IntegerCache.high){
                                        return IntegerCache.cache[i + (-IntegerCache.low)];
                                    }
                                    return new Integer(i);
                                }
                                
                                
                                
         java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了
        
        
4、探探对java多态的理解
        1、面向对象的三大基本特征:封装、继承、多态

        2、多态是指:父类引用指向子类对象,在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

        3、实现多态的三要素:继承 重写 父类引用指向子类对象(多态存在的三个必要条件)
         
            多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
            实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
            多态的作用:消除类型之间的耦合关系。
            
         Java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。
         多态的好处:
         1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
        2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。

        3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。

        4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。

        5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
         
         
5、String、StringBuffer、StringBuilder区别
            String:是java.lang包中的immutable类,String里面所有的属性几乎也是final,由于它的不可变性,类似拼接,裁剪字符串等动作都会产生大量无用的中间对象。由于字符串操作在项目中很常见,所以对String的操作对项目的性能往往有很明显的影响。
            StringBuffer : 这个类是为了解决String拼接产生多余对象的问题而提供的一个类。StringBuffer保证了线程的安全,也带来了多余的开销。
            StringBuilder : StringBuilder的功能与StringBuffer一样。但是区别是StringBuilder没有处理线程安全,减少了开销。

            当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

            和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

            StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

            由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
         (1)字符修改上的区别(主要,见上面分析)

         (2)初始化上的区别,String可以空赋值,后者不行,报错
         
         
           尽量不要使用new 方法来创建新的字符串,因为new 方法是直接在堆中创建一个对象,不会去字符串常量池中,进行操作。
           
           
           
6、什么是内部类?内部类的作用:           
            将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
            内部类的作用:
            1.成员内部类
            成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
            当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
            2.局部内部类
            局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
            3.匿名内部类
            匿名内部类就是没有名字的内部类
            4.静态内部类
            指被声明为static的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但是可以访问静态成员变量和静态方法(包括私有类型)
            一个 静态内部类去掉static 就是成员内部类,他可以自由的引用外部类的属性和方法,无论是静态还是非静态。但是不可以有静态属性和方法、
            作用
            1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,   
            2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。   
            3.方便编写事件驱动程序   
            4.方便编写线程代码
            
            匿名内部类
            匿名内部类是一种没有类名的内部类,不使用class,extends,implements,没有构造函数,他必须继承其他类或实现其他接口。匿名内部类的好处是使代码更加简洁,紧凑,但是带来的问题是易读性下降。
            内部类好处:
            1.隐藏你不想让别人知道的操作,也即封装性。
            2.一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量
            内部类的使用时机

            1、实现事件监听器的时候(比方说actionListener 。。。采用内部类很容易实现);

            2、编写事件驱动时(内部类的对象可以访问外部类的成员方法和变量,注意包括私有成员);

            3、在能实现功能的情况下,为了节省编译后产生的字节码(内部类可以减少字节码文件,即java文件编译后的.class文件);

            
            
7、抽象类和接口区别
       抽象类:抽象类是用来捕捉子类的通用特性的 。

    1、抽象类使用abstract修饰;

    2、抽象类不能实例化,即不能使用new关键字来实例化对象;

    3、含有抽象方法(使用abstract关键字修饰的方法)的类是抽象类,必须使用abstract关键字修饰;

    4、抽象类可以含有抽象方法,也可以不包含抽象方法,抽象类中可以有具体的方法;

    5、如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类;

    6、抽象类中的抽象方法只有方法体,没有具体实现;

                意义:

                1,为子类提供一个公共的类型;

                2,封装子类中重复内容(成员变量和方法);

                3,定义有抽象方法,子类虽然有不同的实现,但该方法的定义是一致的。
    

  接口:接口是抽象方法的集合。   更加抽象,只关注设计,不关注具体实现。

    1、接口使用interface修饰;

    2、接口不能被实例化;

    3、一个类只能继承一个类,但是可以实现多个接口;

    4、接口中方法均为抽象方法;

    5、接口中不能包含实例域或静态方法(静态方法必须实现,接口中方法是抽象方法,不能实现);

         
         
         抽象方法可以有public、protected和default这些修饰符        接口方法默认修饰符是public。不可以使用其它修饰符。
         抽象方法可以有main方法并且我们可以运行它    接口没有main方法,因此我们不能运行它。(java8以后接口可以有default和static方法,所以可以运行main方法)    
         接口用于抽象事物的特性,抽象类用于代码复用。类是对事物的抽象,抽象类是对类的抽象,接口是对抽象类的抽象。从这个角度来看 java 容器类,你会发现,它的设计正体现了这种关系。不是吗?从 Iterable 接口,到 AbstractList 抽象类,再到 ArrayList 类。
         
9、抽象类与接口的应用场景
            接口:好处:一、规范性二、扩展性三、接口在项目就是一个业务逻辑,面向接口编程就是先把客户的业务提取出来,作为接口。        是抽象方法的集合
            A. 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
            B. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
            C. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
            D. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。
            抽象类:
            A. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
            B. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
            C. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能


泛型中extends和super的区别
            1. <? extends T> 只能用于方法返回,告诉编译器此返参的类型的最小继承边界为T,T和T的父类都能接收,但是入参类型无法确定,只能接受null的传入
            2. <? super T>只能用于限定方法入参,告诉编译器入参只能是T或其子类型,而返参只能用Object类接收
            3. ? 既不能用于入参也不能用于返参
         
         
         
13、 父类的静态方法能否被子类重写        静态变量和静态方法都不能被重写,只能被继承,即不能实现多态。
            先给一个答案,不能,父类的静态方法能够被子类继承,但是不能够被子类重写,即使子类中的静态方法与父类中的静态方法完全一样,也是两个完全不同的方法。静态变量也不能够被重写。重写指的是根据运行时对象的类型来决定调用哪个方法,而不是根据编译时的类型。可以被继承,如果子类中有相同的静态方法和静态变量,那么父类的方法以及变量就会被覆盖。要想调用就就必须使用父类来调用。从上述代码可以看出,子类中覆盖了父类的静态方法的话,调用的是子类的方法,这个时候要是还想调用父类的静态方法,应该是用父类直接调用。如果子类没有覆盖,则调用的是父类的方法。静态变量与此相似。在编译时所分配的内存会一直存在(不会被回收),直到程序退出内存才会释放这个空间,在实例化之前这个方法就已经存在于内存,跟类的对象没什么关系。子类中如果定义了相同名称的静态方法,并不会重写,而应该是在内存中又分配了一块给子类的静态方法,没有重写这一说,只是单纯的名字重复了。
            
                                                    public class Fu {  
                                            public static void show() {  
                                                System.out.println("父类的静态方法");  
                                            }  
                                            public void method() {  
                                                System.out.println("父类的普通方法");  
                                            }  
                                        }  
                                            
                                                    
                                                    public class Zi extends Fu {  
                                            public static void main(String[] args) {  
                                                Fu fu = new Zi();  
                                                fu.show();  
                                                fu.method();  
                                            }  
                                            public static void show() {  
                                                System.out.println("子类的静态");  
                                            }  
                                            public void method() {  
                                                System.out.println("子类的普通方法");  
                                            }  
                                              
                                        }  

                                        输出结果是:
                                        父类的静态方法
                                        子类的普通方法

14、进程和线程的区别
         线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
         从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。
         
       (1) 划分尺度:线程更小,所以多线程程序并发性更高;

  (2) 资源分配&处理器调度:进程是资源分配的基本单位,线程是处理器调度的基本单位。  

      (3) 地址空间:进程拥有独立的地址空间;线程没有独立的地址空间,同一进程内多个线程共享其资源;

    (4) 执行:每个线程都有一个程序运行的入口,顺序执行序列和程序的出口,但线程不能单独执行,必须组成进程,一个进程至少有一个主线程。简而言之,一个程序至少有一个进程,一个进程至少有一个线程
         
         
15、若父类中final方法的访问权限为private,将导致子类中不能直接继承该方法,因此,此时可以在子类中定义相同方法名的函数,此时不会与重写final的矛盾,而是在子类中重新地定义了新方法。
                 final成员变量表示常量,只能被赋值一次,赋值后其值不再改变。类似于C++中的const。
            final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
                 
                 例如 String a="A"; a="B" 。a是String对象的一个引用(我们这里所说的String对象其实是指字符串常量),当a=“B”执行时,并不是原本String对象("A")发生改变,而是创建一个新的对象("B"),令a引用它。
                 
                 
                 很多人都认为finally语句块一定会执行,但真的是这样么?答案是否定的。
                 只有与finally对应的try语句块得到执行的情况下,finally语句块才会执行。以上两种情况在执行try语句块之前已经返回或抛出异常,所以try对应的finally语句并没有执行。
          但是,在某些情况下,即使try语句执行了,finally语句也不一定执行。例如以下情况:try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。 
        特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。 
          使用finalize还需要注意一个事,调用super.finalize();一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
        
        
        
16、Java对象的序列化有两种方式:    
         为什么序列化?
          1)永久性保存对象,保存对象的字节序列到本地文件中;
          2)通过序列化对象在网络中传递对象;
          3)通过序列化在进程间传递对象。        
         a.是相应的对象实现了序列化接口Serializable,这个使用的比较多,对于序列化接口Serializable接口是一个空的接口,它的主要作用就是标识这个对象时可序列化的。
         b.实现序列化的第二种方式为实现接口Externalizable。public interface Externalizable extends java.io.Serializable (Externlizable接口继承了java的序列化接口,并增加了两个方法:void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;)
         
         实现Java的序列化接口需要注意一下几点:
          1.java中的序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列化。
          2.也是最应该注意的,如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,不要先接收对象B,那样会报错.尤其在使用上面的Externalizable的时候一定要注意读取的先后顺序。
          3.实现序列化接口的对象并不强制声明唯一的serialVersionUID,是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。
        static修饰的变量应该也是不会被序列化的,因为只有堆内存会被序列化.所以静态变量会天生不会被序列化。可以看到在序列化前 static 修饰的变量赋值为22,而反序列化后读取的这个变量值为33,由此可以看出 static 修饰的变量本身是不会被序列化的。我们读取的值是当前jvm中的方法区对应此变量的值,所以最后输出的值为我们对static 变量后赋的值。
         
         内部类绝对不允许序列化。
         
         
         
19、静态内部类的设计意图
            (静态内部类只可以访问外部类中的静态成员变量与成员方法而非静态的内部类即可以访问静态的也可以访问非静态的外部类成员方法与成员变量)。这两个差异是静态内部类与非静态外部类最大的差异,也是静态内部类之所以存在的原因。了解了这个差异之后,程序开发人员还需要知道,在什么情况下该使用静态内部类。如在程序测试的时候,为了避免在各个Java源文件中书写主方法的代码,可以将主方法写入到静态内部类中,以减少代码的书写量,让代码更加的简洁。
                     
20、内部类有一个特征:内部类当中可以调用外部类当中的属性和方法,而外部类却不能调用内部类当中的。
            匿名内部类:
            首先我们应该知道匿名内部类匿名是因为匿名内部类的具体名字不会被我们在程序当众编写出来,因为它已经在主方法当中被实例化了。匿名内部类可以继承两类数据结构:一:抽象类二:接口。
            本来我们是应该先继承这个抽象类再开始创建对象的,否则对象是无法创建的,但是为了简便,人们创建了了匿名内部类,允许我们在主方法当中进行抽象类的实例化,同时也可以进行对象的创建。
            
23、string 转换成 integer的方式及原理????????????????????


************************************************************************************************************************************************
         二.java深入面试题
1、哪些情况下的对象会被垃圾回收机制处理掉?
https://blog.csdn.net/q291611265/article/details/45114995
                    标记计数(给内存中的对象给打上标记,对象被引用一次,计数就加1,引用被释放了,计数就减一,当这个计数为0的时候,这个对象就可以被回收了。当然,这也就引发了一个问题:循环引用的对象是无法被识别出来并且被回收的。)
                    和根搜索算法(从一个根出发,搜索所有的可达对象,这样剩下的那些对象就是需要被回收的判断完了哪些对象是没用的,这样就可以进行回收了)
                    清理方法:
                    1、直接删除(大量碎片)
                    2、整理成连续内存
                    3、分为两块区域,一块使用,一块放着。
                    比较效率的一中做法是将以上的几种方法给结合起来。首先将内存分块,分为新生代,老年代和永久代。
                    新生代又可分为3块。分别为Edon区,Survivor0,survivor1。
                    刚产生的对象是放在Edon区中,当这个区块放满了以后就将其存活的部分复制到survivor0块中,并且将Edon区中的数据清空,等到survivor0满了就将其中的存活的数据放到survivor1中,清空survivor0,垃圾回收到了一定次数还未被回收的对象,就可以放到老年区。一般来说,刚才产生的对象大多是要在下一次垃圾回收的时候就要被回收掉的,只有一小部分对象会被保留下来,这些被保留下来的对象都是比较稳定的,所以在老年区中的对象回收方法可以采用整理的方法,而在Edon区等新生代中采用复制的方法比较好。
                    
                    垃圾回收他是在虚拟机空闲的时候或者内存紧张的时候执行的,什么时候回收不是由程序员来控制的,这也就是java比较耗内存的原因之一。    
                    还有在垃圾回收的时候当检测到对象没有用了,需要被回收的时候并不会马上被回收,而是将其放入到一个准备回收的队列,去执行finalize方法。。然等到下次内存回收的时候要是他还是没有被任何人引用的话,就将其给回收了。(如果在finalize方法中重新给对象加个引用,这样对象是有可能不会被回收的)不过finalize方法不推荐使用,他跟C++中的析构函数不同,我们既不能确定什么时候他回被回收,也不能保证这个方法一定会被执行。
                    
                    
                    
3、utf-8的编码规则:

        如果一个字节,最高位为0,表示这是一个ASCII字符(00~7F)
        如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数
        一个utf8数字占1个字节
        一个utf8英文字母占1个字节
        少数是汉字每个占用3个字节,多数占用4个字节。                    


4、静态代理和动态代理的区别,什么场景使用?
        可以看到,我们可以通过LogHandler代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。
        AOP(AspectOrientedProgramming):将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码---解耦。
        正是因为在所有的类里,核心代码之前的操作和核心代码之后的操作都做的是同样的逻辑,因此我们需要将它们提取出来,单独分析,设计和编码,这就是我们的AOP思想。一句话说,AOP只是在对OOP的基础上进行进一步抽象,使我们的类的职责更加单一。
        动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强
        
        其实所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。
 
        代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户需要上facebook,而普通网络无法直接访问,网络代理帮助用户先翻墙,然后再访问facebook。这就是代理的作用了。

        纵观静态代理与动态代理,它们都能实现相同的功能,而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!

        优点:拥有静态代理的优点,同时省去了很多代码,并且扩展性更强,通过反射可以执行任意类型的被代理类方法
        缺点:只能代理实现了接口的类,而没有实现接口的类就不能实现动态代理。通过反射在性能上可以会有一定程度上的性能损耗。
        应用:被代理类庞大时,需要在某些方法执行前后处理一些事情时,亦或接口类与实现类经常变动时(因为使用反射所以方法的增删改并不需要修改invoke方法)。


8、Java中实现多态的机制是什么?

         Java提供了编译时多态和运行时多态两种多态机制。前者是通过方法重载实现的,后者是通过方法的覆盖实现的。
         
9、如何将一个Java对象序列化到文件里?
         
        使用输入输出流,,一个是ObjectOutputStream 对象,ObjectOutputStream 负责向指定的流中写入序列化的对象。当从文件中读取序列化数据时,主要需要两个对象,一个是FileInputStream ,一个是ObjectInputStream 对象,ObjectInputStream 负责从指定流中读取序列化数据并还原成序列化前得对象。另外,序列化的读取数据与写入的顺序相同,比如我们序列化时先写入数据A ,再写入B ,最后写入C ;那么我们再读取数据的时候,读取到的第一个数据为A ,读取到的第二个数据为B ,最后读取到的数据为C ,即:先写入先读取的原则。
         
         
         
10、JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。 
            Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
         https://www.jianshu.com/p/9be58ee20dee
         
11、注解的理解
            注解,也叫元数据。一种代码级别的说明,在JDK1.5之后引入的特性,与类、接口、枚举同一层次。可以声明在包、类、字段、方法、局部变量、方法参数等前面,来对这些元素进行说明,注释等。
            作用分类:
            1)编写文档:通过代码里的标识的元数据生成文档【生成文档doc文档】
            2)代码分析:通过代码里的标识的元数据对代码进行分析【使用反射】
            3)编译检查:通过代码里的标识的元数据让编译器能过实现基本的编译检查【Override】
         
         
         Java注解按照是否含有参数分为三种:
         1、不带参数,语法结构为:@Annotation
         2、带一个参数的注解,语法结构为:@Annotation(参数)
         3、带多个参数的注解,语法结构为:@Annotiation(参数1, 参数2, 参数3...)
         
         
         java内置注解:
         1、@Override注解

         @Override被用于标注方法,用于说明所标注的方法是重写父类的方法:
         
         2、@Deprecated

         @Deprecated 用于说明所标注元素(成员变量或方法)因存在安全问题或有更好选择而不鼓励程序员使用,如果强行使用,则编译器会发出警告。
         
         
        3、 @SuppressWarnings注解

        @SuppressWarnings用于取消编译器所显示的警告,该注解常用属性值如下:
         
         重头戏之一——元注解:元注解的作用就是负责注解其他注解
                提供了四个用于修饰自定义注解的元注解:@Target、@Retention 、@Documented和@Inherited
         @Target:

            用于指定被修饰的自定义注解只能用于修饰程序中哪些元素
        @Retention:

            用于指定被修饰的自定义注解可以保留多久
        @Documented:
            
            执行javadoc命令时,被该元注解修饰的自定义注解也会生成在文档中
         
         @Inherited:

            如果父类所使用的注解有@Inherited修饰,则子类可以继承该注解,否则不能继承。
         
         
12、说说你对依赖注入的理解         
          这个例子就演示一个最简单的注入方式的例子,也就是构造子方式注入,通过将依赖对象注入到对象的构造子中来实现。另外还有一种常用的注入方式,就是属性方式注入,意思就是通过将依赖对象注入到对象的属性中来实现。
         
         开始时,NewTask类在构造时绑定开发人员,现在这种依赖可以在使用时按需要进行绑定。
        
        
        
        

        
16、Object类的equal和hashCode方法重写,为什么?          
         为什么必须要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。
         
         
         
         
         
         
         
************************************************************************         
三.数据结构
         
         容器类
        1. List的实现类主要有: LinkedList, ArrayList, Vector, Stack。
            (01) LinkedList是双向链表实现的双端队列;它不是线程安全的,只适用于单线程。
            (02) ArrayList是数组实现的队列,它是一个动态数组;它也不是线程安全的,只适用于单线程。
            (03) Vector是数组实现的矢量队列,它也一个动态数组;不过和ArrayList不同的是,Vector是线程安全的,它支持并发。
            (04) Stack是Vector实现的栈;和Vector一样,它也是线程安全的。
        
        2. Set的实现类主要有: HastSet和TreeSet。
            (01) HashSet是一个没有重复元素的集合,它通过HashMap实现的;HashSet不是线程安全的,只适用于单线程。
            (02) TreeSet也是一个没有重复元素的集合,不过和HashSet不同的是,TreeSet中的元素是有序的;它是通过TreeMap实现的;TreeSet也不是线程安全的,只适用于单线程。
         
         
       3.Map的实现类主要有: HashMap,WeakHashMap, Hashtable和TreeMap。
            (01) HashMap是存储“键-值对”的哈希表;它不是线程安全的,只适用于单线程。
            (02) WeakHashMap是也是哈希表;和HashMap不同的是,HashMap的“键”是强引用类型,而WeakHashMap的“键”是弱引用类型,也就是说当WeakHashMap 中的某个键不再正常使用时,会被从WeakHashMap中被自动移除。WeakHashMap也不是线程安全的,只适用于单线程。
            (03) Hashtable也是哈希表;和HashMap不同的是,Hashtable是线程安全的,支持并发。
            (04) TreeMap也是哈希表,不过TreeMap中的“键-值对”是有序的,它是通过R-B Tree(红黑树)实现的;TreeMap不是线程安全的,只适用于单线程。
            更多关于这些集合类的介绍,可以参考“Java 集合系列目录(Category)”。
         
         
         1. List和Set

                JUC集合包中的List和Set实现类包括: CopyOnWriteArrayList, CopyOnWriteArraySet和ConcurrentSkipListSet。ConcurrentSkipListSet稍后在说明Map时再说明。
                
                (01) CopyOnWriteArrayList相当于线程安全的ArrayList,它实现了List接口。CopyOnWriteArrayList是支持高并发的。
                (02) CopyOnWriteArraySet相当于线程安全的HashSet,它继承于AbstractSet类。CopyOnWriteArraySet内部包含一个CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。

        2. Map

                JUC集合包中Map的实现类包括: ConcurrentHashMap和ConcurrentSkipListMap
                (01) ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。ConcurrentHashMap是通过“锁分段”来实现的,它支持并发。
                (02) ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap); 它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发。
                (03)ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发
                                
                                
        3. Queue
                JUC集合包中Queue的实现类包括: ArrayBlockingQueue, LinkedBlockingQueue, LinkedBlockingDeque, ConcurrentLinkedQueue和ConcurrentLinkedDeque。它们的框架如下图所示:                        
                
                (01) ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。
                (02) LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。
                (03) LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。
                (04) ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。
                (05) ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。
                                
                
                Array是数组,ArrayList是Array的加强版。

            (1)array可以保存基本类型和对象类型,arrayList只能保存对象类型

            (2)array数组的大小是固定的不能更改,而ArrayList的大小可以改变

            (3)Array数组在存放的时候一定是同种类型的元素。ArrayList就不一定了,因为ArrayList可以存储Object。

            (4)ArrayList有更加丰富的方法如addAll()、removeAll()、iterator()

7、List和Map的实现方式以及存储方式                
                List:

                        常用实现方式有:ArrayList和LinkedList

                        ArrayList的存储方式:数组,查询快

                        LinkedList的存储方式:链表,插入,删除快

                        Set:

                        常用实现方式有:HashSet和TreeSet

                        HashSet的存储方式:哈希码算法,加入的对象需要实现hashcode()方法,快速查找元素

                        TreeSet的存储方式:按序存放,想要有序就要实现Comparable接口


                        附加:

                        集合框架提供了2个实用类:collections(排序,复制、查找)和Arrays对数组进行(排序,复制、查找)

                        Map:

                        常用实现方式有:HashMap和TreeMap

                        HashMap的存储方式:哈希码算法,快速查找键值

                        TreeMap存储方式:对键按序存放

                
                
8、HashMap的实现原理(数组和链表的结合)                
                当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置
                
                当去变量这个Entry数组的时候,去判断两个Entry对象的key的hash是否相同则仅仅表示它们的存储位置是相同的,然后继续判断两个Entry的key是否相等或者equals是否相等,如果条件都满足,则表示要添加的元素,key已经重复,则直接将新值覆盖掉旧值,并且return返回,一旦条件不满足,则直接将添加的元素添加到Entry对象中。
                
                
                
                
解决hash冲突的办法

                开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
                再哈希法
                链地址法 (Java中hashmap的解决办法就是采用的链地址法。)
                建立一个公共溢出区
                
                
                ConcurrentHashMap的锁分段技术

13、ConcurrentHashMap的实现原理                HashTable容器在竞争激烈的并发环境效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,假如容器有多把锁,每一把锁用于锁住容器中一部分数据,那么多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问率,这就是ConcurrentHashMap的锁分段技术。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问。    
                
                
                 
15、HashTable实现原理                 
                 (1)Hashtable 是一个散列表,它存储的内容是键值对(key-value)映射。

                (2)Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。

                (3)Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。

16、TreeMap具体实现
????????????????
                
                
                
                
                
17、HashMap和HashTable的区别                
                
                4、key和value是否允许null值
 

      其中key和value都是对象,并且不能包含重复key,但可以包含重复的value。

      通过上面的ContainsKey方法和ContainsValue的源码我们可以很明显的看出:

      Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
      5、两个遍历方式的内部实现上不同
      Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。

      6、hash值不同
      哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

      hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。

      Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值,Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length取模,&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而&0x7FFFFFFF后,只有符号外改变,而后面的位都不变。

      7、内部实现使用的数组初始化和扩容方式不同
      HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
      Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
      Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
                
                
                
                
                
18、HashMap与HashSet的区别                
            总结一下HashSet和HashMap的区别:

                (1)HashSet实现了Set接口, 仅存储对象; HashMap实现了 Map接口, 存储的是键值对.

                (2)HashSet底层其实是用HashMap实现存储的, HashSet封装了一系列HashMap的方法. 依靠HashMap来存储元素值,(利用hashMap的key键进行存储), 而value值默认为Object对象. 所以HashSet也不允许出现重复值, 判断标准和HashMap判断标准相同, 两个元素的hashCode相等并且通过equals()方法返回true

                
                
                    
20、集合Set实现Hash怎么防止碰撞    
                    HashMap,HashSet其实都是采用的拉链法来解决哈希冲突的,就是在每个位桶实现的时候,我们采用链表(jdk1.8之后采用链表+红黑树)的数据结构来去存取发生哈希冲突的输入域的关键字(也就是被哈希函数映射到同一个位桶上的关键字)。首先来看使用拉链法解决哈希冲突的几个操作:
                
                
                
21、ArrayList和LinkedList的区别,以及应用场景                    
                    ArrayList底层是用数组实现的,可以认为ArrayList是一个可改变大小的数组。随着越来越多的元素被添加到ArrayList中,其规模是动态增加的。
                    LinkedList底层是通过双向链表实现的, LinkedList和ArrayList相比,增删的速度较快。但是查询和修改值的速度较慢。同时LinkedList还实现了Queue接口,所以他还提供了offer(),peek(), poll()等方法。?

                    在内存中这两者也是有区别的,因为底层分别用数组和链表实现的,所以在内存中ArrayList是连续的,而linkedList在底层中是用链表实现的所以在内存中可以不是连续的内存。
        
                
                
24、堆的结构
                    数组   上浮  下沉            

25、堆和树的差别
在二叉排序树中,每个结点的值均大于其左子树上所有结点的值,小于其右子树上所有结点的值,对二叉排序树进行中序遍历得到一个有序序列。所以,二叉排序树是结点之间满足一定次序关系的二叉树;

  堆是一个完全二叉树,并且每个结点的值都大于或等于其左右孩子结点的值(这里的讨论以大根堆为例),所以,堆是结点之间满足一定次序关系的完全二叉树。

  具有n个结点的二叉排序树,其深度取决于给定集合的初始排列顺序,最好情况下其深度为log n(表示以2为底的对数),最坏情况下其深度为n;
  具有n个结点的堆,其深度即为堆所对应的完全二叉树的深度log n 。

  在二叉排序树中,某结点的右孩子结点的值一定大于该结点的左孩子结点的值;在堆中却不一定,堆只是限定了某结点的值大于(或小于)其左右孩子结点的值,但没有限定左右孩子结点之间的大小关系。

  在二叉排序树中,最小值结点是最左下结点,其左指针为空;最大值结点是最右下结点,其右指针为空。在大根堆中,最小值结点位于某个叶子结点,而最大值结点是大根堆的堆顶(即根结点)。

  二叉排序树是为了实现动态查找而设计的数据结构,它是面向查找操作的,在二叉排序树中查找一个结点的平均时间复杂度是O(log n);
  堆是为了实现排序而设计的一种数据结构,它不是面向查找操作的,因而在堆中查找一个结点需要进行遍历,其平均时间复杂度是O(n)。

                

26、堆与栈的区别很明显:

                    1.栈内存存储的是局部变量而堆内存存储的是实体;

                    2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

                    3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
                     首先,讨论的堆和栈指的是内存中的“堆区”和“栈区”,OC语言是C语言的超集,所以先了解C语言的内存模型的内存管理会有很大的帮助。C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:

                        1、栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。

                        2、堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。在iOS开发中所说的“内存泄漏”说的就是堆区的内存。

                        3、静态区:全局变量和静态变量(在iOS中就是用static修饰的局部变量或者是全局全局变量)的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。

                        4、常量区:常量存储在这里,不允许修改。

                        5、代码区:存放函数体的二进制代码。

                         

                        堆和栈的区别:

                        1、堆空间的内存是动态分配的,一般存放对象,并且需要手动释放内存。当然,iOS引入了ARC(自动引用计数管理技术)之后,程序员就不需要用代码管理对象的内存了,之前MRC(手动管理内存)的时候,程序员需要手动release对象。另外,ARC只是一种中间层的技术,虽然在ARC模式下,程序员不需要像之前那么麻烦管理内存,但是需要遵循ARC技术的规范操作,比如使用属性限定符weak、strong、assigen等。因此,如果程序员没有按ARC的规则并合理的使用这些属性限定符的话,同样是会造成内存泄漏的。

                        2、栈空间的内存是由系统自动分配,一般存放局部变量,比如对象的地址等值,不需要程序员对这块内存进行管理,比如,函数中的局部变量的作用范围(生命周期)就是在调完这个函数之后就结束了。这些在系统层面都已经限定住了,程序员只需要在这种约束下进行程序编程就好,根本就没有把这块内存的管理权给到程序员,肯定也就不存在让程序员管理一说。

                         

                        从申请的大小方面讲:

                        栈空间比较小;

                        堆空间比较大。

                         

                        从数据存储方面来说:

                        栈空间中一般存储基本数据类型,对象的地址;

                        堆空间一般存放对象本身,block的copy等
         
         
27、深拷贝和浅拷贝的区别
                        浅拷贝的明显特征为:多个引用指向了同一块堆内存空间,任意改变一个引用都会导致其他引用的值发生变化。

                        深拷贝的明显特征为:在内存中又开辟了新的一块空间,指向了不同的内存块,改变一个引用是不会影响另外一个引用的。
         
         
         
28、讲一下对树,B+树的理解

30、讲一下对图的理解         
         
         
         
        
        ********************************************************************************************************************************************************************************************************************
***********************************************************************************************************************    ************************************************************************     
四、多线程知识


1、开启线程的三种方式   https://www.cnblogs.com/xiaostudy/p/9800601.html     (源码分析和三种实现方法代码)
              1.继承Thread类,并复写run方法,创建该类对象,调用start方法开启线程。
              2.实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。
              3.创建FutureTask对象,创建Callable子类对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一个Runnable)。
              创建Thread类对象,将FutureTask对象传递给Thread对象。调用start方法开启线程。这种方式可以获得线程执行完之后的返回值。
        
         
         
         
4、run()和start()方法区别     
            线程的run()方法是由java虚拟机直接调用的,如果我们没有启动线程(没有调用线程的start()方法)而是在应用代码中直接调用run()方法,那么这个线程的run()方法其实运行在当前线程(即run()方法的调用方所在的线程)之中,而不是运行在其自身的线程中,从而违背了创建线程的初衷;
             
                     
            1,start()方法来启动线程,真正实现了多线程运行,这时无需等待。run方法体代码执行完毕而直接继续执行下面的代码: 通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。

            然后通过此Thread类调用方法run()来完成其运行操作的,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程终止,而CPU再运行其它线程。

            2,run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码: 而如果直接用run方法,这只是调用一个方法而已,程序中依然只有主线程–这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。

            3,调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。

            这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。

            4,还有就是尽管线程的调度顺序是不固定的,但是如果有很多线程被阻塞等待运行,调度程序将会让优先级高的线程先执行,而优先级低的线程执行的频率会低一些。

    


5、如何控制某个方法允许并发访问线程的个数              https://www.cnblogs.com/androidsuperman/p/6349586.html
                
         static Semaphore sSemaphore = new Semaphore(6) 表示该方法最多允许几个线程访问
         sSemaphore.acquire():调用一次,允许进入方法的线程数量减一
         sSemaphore.release():调用一次,允许进入方法的线程数量加1
         当允许进入方法的线程数量减为0的时候其他线程就等待,直到允许的线程数量大于0
         sSemaphore.availablePermits():可以获取当前允许进入方法的线程数量
         
         
6、在Java中wait和seelp方法的不同;

             对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

            sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

            在调用sleep()方法的过程中,线程不会释放对象锁。

            而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

         
7、wait和notify关键字的理解                            https://blog.csdn.net/liuguangqiang/article/details/49180319
      wait释放掉锁,因此wait期间,该对象可被其他对象调用,notify后,重新获得机锁,但并不是立即运行         
         
                            
         
         
         补充:
         
         如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

         需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。
         
8、什么导致线程阻塞


                    Java中实现线程阻塞的方法:
                    (1)线程睡眠                
                                    
                    (2)线程等待                
                                    
                    (3)线程礼让                
                                    
                    (4)线程自闭            
                                                    
                    (5)suspend() 和 resume() 方法                
                                    
                                    
                    1)线程执行了Thread.sleep(intmillsecond);方法,当前线程放弃CPU,睡眠一段时间,然后再恢复执行

                    2)线程执行一段同步代码,但是尚且无法获得相关的同步锁,只能进入阻塞状态,等到获取了同步锁,才能回复执行。

                    3)线程执行了一个对象的wait()方法,直接进入阻塞状态,等待其他线程执行notify()或者notifyAll()方法。

                    4)线程执行某些IO操作,因为等待相关的资源而进入了阻塞状态。比如说监听system.in,但是尚且没有收到键盘的输入,则进入阻塞状态。
    
                                        
                                    
9、线程如何关闭                                    
                                    1.设置退出标志,使线程正常退出,正常地退出run()方法后线程结束。

                                    2.使用interrupt()方法终止线程。

                                    3.使用stop方法强制终止线程(不推荐使用)
                                    
                                    
                                    
                                    
                                    1.使用退出标志终止线程

                                    正常来说线程执行体run()方法执行完之后,线程就会进入死亡状态,但是在开发过程中线程通常会进入一个无限循环,长时间地执行一些监听的任务,只有在满足一定条件的时候才能结束。我们可以通过一个变量来控制循环,那他便会在下一次循环前退出run()方法。
                                    
                                    
                                    2.使用interrupt()方法结束线程
                                    public class MyThread extends Thread {
                                        public volatile boolean exit = false; 
                                            public void run() { 
                                            while (!exit){
                                                //do something
                                            }
                                        } 
                                    }
    
                                在这里定义了一个由volatile 修饰的布尔值变量exit,当把exit修改成true,循环就会退出,线程也就正常关闭。volatile关键字是的exit变量在多个线程中具有可见性。    
                                    
                                    
                            public class ThreadTest {
 
                                static class MyThread extends Thread {
                                    @Override
                                    public void run() {
                                        while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
                                            try{
                                                Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
                                            }catch(InterruptedException e){
                                                e.printStackTrace();
                                                System.out.println("ThreadSafe:run()"+e.getMessage());
                                                break;//捕获到异常之后,执行break跳出循环。
                                            }
                                        }
                                    }
                                }
                             
                                public static void main(String[] args) throws Exception {
                                    Thread thread = new ThreadSafe();
                                    thread.start();
                                    System.out.println("在50秒之内按任意键中断线程!");
                                    System.in.read();
                                    thread.interrupt();
                                    Thread.sleep(5000);
                                    System.out.println("线程已经退出!thread.is" + thread.isAlive());
                                }
                             
                            }

            
                        例子中,启动线程,并在线程调用sleep()方法进入阻塞状态的时候,主线程调用了MyThread的interrupt()方法,此时线程就会跑出InterruptdException结束run()方法,从而结束线程。从最后一句主线程打印中看出,MyThread结束了, 已经死亡。
         
         
         
         
            
10、JAVA中线程同步的方法(7种)汇总 
         
         
        一、同步方法 
         
        二、同步代码块 
         
        三、wait与notify
         
        四、使用特殊域变量(volatile)实现线程同步 
         
        五、使用重入锁实现线程同步     
         
        六、使用局部变量实现线程同步    

        七、使用阻塞队列实现线程同步    

        
        
        
        
        
        
        
        
11、数据一致性如何保证


12、如何保证线程安全

notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM会在等待的线程中调度一个线程去获得对象锁,执行代码。
13、如何实现线程同步
 同步代码块,同步方法,rentrantlock

java中同步的几种方式(同上)
同步方法
同步代码块
特殊域变量,volatile
使用局部变量ThreadLocal实现
使用重入锁ReentrantLock


14、两个进程同时要求写或者读,能不能实现?如何防止进程的同步

允许多个读者同时执行读操作;
不允许读者、写者同时操作;
不允许多个写者同时操作。    

共享内存

15、线程间list操作
                        线程间操作List
                        1使用Collections.synchronizedList()构建List;2操作list的方法使用同步锁。
                        为何会两者都用,因为Collections.synchronizedList()构建的list只针对list的add(obj)、poll(obj)等方法做同步,在多线程中直接使用方法会同步,但是在操作list时,add(obj)、poll(obj)方法之前不能保证obj是否被其他线程操作过    


                        1.创建阶段(Created)
                        2.应用阶段(In Use)
                        3.不可见阶段(Invisible)
                        4.不可达阶段(Unreachable)
                        5.收集阶段(Collected)
                        6.终结阶段
                        7.对象空间又一次分配阶段

24、volatile的原理
                        大家可能会误认为对变量i加上关键字volatile后,这段程序就是线程安全的。大家可以尝试运行上面的程序。下面是我本地运行的结果:981 
                        可能每个人运行的结果不相同。不过应该能看出,volatile是无法保证原子性的(否则结果应该是1000)。原因也很简单,i++其实是一个复合操作,包括三步骤: 
                          (1)读取i的值。 
                          (2)对i加1。 
                          (3)将i的值写回内存。 


       (1)修改volatile变量时会强制将修改后的值刷新的主内存中。 
  (2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。 
volatile关键字的作用

其实volatile关键字的作用就是保证了可见性和有序性(不保证原子性),如果一个共享变量被volatile关键字修饰,那么如果一个线程修改了这个共享变量后,其他线程是立马可知的。为什么是这样的呢?比如,线程A修改了自己的共享变量副本,这时如果该共享变量没有被volatile修饰,那么本次修改不一定会马上将修改结果刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是没有被A修改之前的值。如果该共享变量被volatile修饰了,那么本次修改结果会强制立刻刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是被A修改之后的值了。 
volatile能禁止指令重新排序,在指令重排序优化时,在volatile变量之前的指令不能在volatile之后执行,在volatile之后的指令也不能在volatile之前执行,所以它保证了有序性。

它实现的基础靠的就是指令屏障
(1)内存可见性的保证是基于屏障指令的。

(2)禁止指令重排在编译时 JVM 编译器遵循内存屏障的约束,运行时靠屏障指令组织重排。

(3)synchronized 关键字可以保证变量原子性和可见性;volatile 不能保证原子性


25、关于NIO的原理    非阻塞 IO    缓冲和通道        

26、volatile关键字和synchronized关键字的区别

(1)、volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。 
(2)、volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。 
(3)、volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。


27、synchronized与Lock的区别
两者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

       采用synchronized关键字来实现同步的话,就会导致一个问题:

  如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

  因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。


volatile和synchronized的区别:
                    1、volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
                    2、volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
                    3、volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
                    4、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
                    5、volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

Synchronized 和 ReentrantLock 的区别:
                    1.ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。
                    2.ReentrantLock必须在finally中释放锁,否则后果很严重,编码角度来说使用synchronized更加简单,不容易遗漏或者出错。
                    3.ReentrantLock 的性能比synchronized会好点。
                    4.ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。

30、Lock实现原理

                        ????????????????????????


31、死锁产生的必要条件

                            二、死锁产生的原因

                            1. 系统资源的竞争

                            系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。

                            2. 进程运行推进顺序不合适

                            进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。

                            回到顶部
                            
                            
                            三、死锁的四个必要条件

                            互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

                            请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

                            不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

                            循环等待条件: 若干进程间形成首尾相接循环等待资源的关系

                            这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
                            
                            
                            
32、避免死锁
            加锁顺序 
            加锁时限 
            死锁检测                            
                            
                            
                            
                            
33、对象锁和类锁是否会互相影响                        
                            不会,类锁用于所有对象,对象锁只用于本对象
                            
                            
                            
                            
34、什么是线程池,如何使用                ???????????????????            
                            
                    使用线程池的好处有很多,比如节省系统资源的开销,节省创建和销毁线程的时间等,当我们需要处理的任务较多时,就可以使用线程池        
                            
                            
                            
                            
40、如何保证多线程读写文件的安全                            
                            
                            
                            
                            
41、多线程断点续传原理                            
    所谓断点续传,也就是要从文件已经下载的地方开始继续下载                        
                            
                            
                            
42、断点续传的实现                            
                            
                            
                            
                            
*********************************************************************************************************


                            
五、算法                            
        
                                    (1)直接插入排序


                                            template<class T>
                                                void InsertSort(T* array, int n) {               //array待排序数组,n:数组元素数量
                                                    int i, j;                                    //循环变量
                                                    T temp;                                      //存储待排序元素
                                                    for (i = 1; i < n; i++) {
                                                        j = i;
                                                        temp = array[i];                         //待排序元素赋值给临时变量
                                                        while (j > 0 && temp < array[j - 1]) {   //当未达到数组的第一个元素或者待插入元素小于当前元素
                                                            array[j] = array[j - 1];             //就将该元素后移
                                                            j--;                                 //下标减一,继续比较
                                                        }
                                                        array[j] = temp;                         //插入位置已经找到,立即插入
                                                    } 
                                                }


                                     (2)希尔排序
                                            void shellSort(int list[], int count) {
                                                    for (int gap = count / 2; gap >= 1; gap /= 2) {
                                                        for (int i = 0; i < gap; ++i) {
                                                            insertSort(list, count, i, gap);
                                                        }
                                                    }
                                                }

                                                void insertSort(int list[], int count, int head, int gap) {
                                                    int temp, i, j;
                                                    for (i = head + gap; i < count; i += gap) {
                                                        temp = list[i];
                                                        for (j = i - gap; j >= head; j -= gap) {
                                                            if (list[j] <= temp) {
                                                                break;
                                                            }
                                                            list[j+gap] = list[j];
                                                        }
                                                        list[j+gap] = temp;
                                                    }
                                                }


                                (3)简单选择排序
                                
                                        public static void simpleSelectSort(int[] array) {
                                            if (null == array || array.length <= 1) {
                                                return;
                                            }

                                            for (int i = 0; i < array.length - 1; i++) {
                                                int temp;
                                                int index = i;

                                                for (int j = i + 1; j < array.length; j++) {
                                                    if (array[index] > array[j]) {
                                                        index = j;
                                                    }
                                                }

                                                temp = array[index];
                                                array[index] = array[i];
                                                array[i] = temp;
                                            }
                                        }
                                
                            (4)堆排序    
                                    比较当前父节点是否大于子节点,如果大于就交换,直到一趟建堆完成~        体验堆排序时,我们是左子树和右子数都是已经有父>子这么一个条件的了
                                /**
                                 * 建堆
                                 *
                                 * @param arrays          看作是完全二叉树
                                 * @param currentRootNode 当前父节点位置
                                 * @param size            节点总数
                                 */
                                public static void heapify(int[] arrays, int currentRootNode, int size) {

                                    if (currentRootNode < size) {
                                        //左子树和右字数的位置
                                        int left = 2 * currentRootNode + 1;
                                        int right = 2 * currentRootNode + 2;

                                        //把当前父节点位置看成是最大的
                                        int max = currentRootNode;

                                        if (left < size) {
                                            //如果比当前根元素要大,记录它的位置
                                            if (arrays[max] < arrays[left]) {
                                                max = left;
                                            }
                                        }
                                        if (right < size) {
                                            //如果比当前根元素要大,记录它的位置
                                            if (arrays[max] < arrays[right]) {
                                                max = right;
                                            }
                                        }
                                        //如果最大的不是根元素位置,那么就交换
                                        if (max != currentRootNode) {
                                            int temp = arrays[max];
                                            arrays[max] = arrays[currentRootNode];
                                            arrays[currentRootNode] = temp;

                                            //继续比较,直到完成一次建堆
                                            heapify(arrays, max, size);
                                        }
                                    }
                                }


                             /**
                                 * 完成一次建堆,最大值在堆的顶部(根节点)                 显然,一个普通的数组并不能有这种条件(父>子),因此,我们往往是从数组最后一个元素来进行建堆     
                                 */
                                public static void maxHeapify(int[] arrays, int size) {

                                    // 从数组的尾部开始,直到第一个元素(角标为0)
                                    for (int i = size - 1; i >= 0; i--) {
                                        heapify(arrays, i, size);
                                    }

                                }
                                
                               //接下来不断建堆,然后让数组最后一位与当前堆顶(数组第一位)进行交换即可排序:
                               
                                for (int i = 0; i < arrays.length; i++) {

                                    //每次建堆就可以排除一个元素了
                                    maxHeapify(arrays, arrays.length - i);

                                    //交换
                                    int temp = arrays[0];
                                    arrays[0] = arrays[(arrays.length - 1) - i];
                                    arrays[(arrays.length - 1) - i] = temp;

                                }
                                
                                
                                五、冒泡排序
                                
                                public void bubbleSort(int[] list) {
                                    int temp = 0; // 用来交换的临时数
                                 
                                    // 要遍历的次数
                                    for (int i = 0; i < list.length - 1; i++) {
                                        // 从后向前依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
                                        for (int j = list.length - 1; j > i; j--) {
                                            // 比较相邻的元素,如果前面的数大于后面的数,则交换
                                            if (list[j - 1] > list[j]) {
                                                temp = list[j - 1];
                                                list[j - 1] = list[j];
                                                list[j] = temp;
                                            }
                                        }
                                 
                                        System.out.format("第 %d 趟:\t", i);
                                        printAll(list);
                                    }
                                }
                                
                                
                                
                                6.快速排序
                                
                                    void quicksort(int left, int right) {
                                        int i, j, t, temp;
                                        if(left > right)
                                            return;
                                        temp = a[left]; //temp中存的就是基准数
                                        i = left;
                                        j = right;
                                        while(i != j) { //顺序很重要,要先从右边开始找
                                            while(a[j] >= temp && i < j)
                                                j--;
                                            while(a[i] <= temp && i < j)//再找右边的
                                                i++;       
                                            if(i < j)//交换两个数在数组中的位置
                                            {
                                                t = a[i];
                                                a[i] = a[j];
                                                a[j] = t;
                                            }
                                        }
                                        //最终将基准数归位
                                        a[left] = a[i];
                                        a[i] = temp;
                                        quicksort(left, i-1);//继续处理左边的,这里是一个递归的过程
                                        quicksort(i+1, right);//继续处理右边的 ,这里是一个递归的过程
                                    }
                                
                                
                                quicksort(1, n); //快速排序调用
                                
                                
                                
                            7.归并排序
                                                    速度仅次于快速排序,内存少的时候使用,可以进行并行计算的时候使用    
                                
                                
                                public static void mergeSort(int[] arr) {
                                        sort(arr, 0, arr.length - 1);
                                    }

                                    public static void sort(int[] arr, int L, int R) {
                                        if(L == R) {
                                            return;
                                        }
                                        int mid = L + ((R - L) >> 1);
                                        sort(arr, L, mid);
                                        sort(arr, mid + 1, R);
                                        merge(arr, L, mid, R);
                                    }

                                    public static void merge(int[] arr, int L, int mid, int R) {
                                        int[] temp = new int[R - L + 1];
                                        int i = 0;
                                        int p1 = L;
                                        int p2 = mid + 1;
                                        // 比较左右两部分的元素,哪个小,把那个元素填入temp中
                                        while(p1 <= mid && p2 <= R) {
                                            temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
                                        }
                                        // 上面的循环退出后,把剩余的元素依次填入到temp中
                                        // 以下两个while只有一个会执行
                                        while(p1 <= mid) {
                                            temp[i++] = arr[p1++];
                                        }
                                        while(p2 <= R) {
                                            temp[i++] = arr[p2++];
                                        }
                                        // 把最终的排序的结果复制给原数组
                                        for(i = 0; i < temp.length; i++) {
                                            arr[L + i] = temp[i];
                                        }
                                    }
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                              
package exer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;

public class Calculator {

    public static void main(String[] args) {
        Scanner in1=new Scanner(System.in);
        Scanner in2=new Scanner(System.in);
        
        String str1=in1.nextLine();
        String str2=in2.nextLine();
        Integer total=Integer.parseInt(str1);
        List<Integer>list=new ArrayList<>();
        //对输入的价格进行分解,并插入进列表
        String [] prices=str2.split("\\s+");
        for(String str:prices){
            Integer price=Integer.parseInt(str);
            if(price!=null&&price<=10000&&price>0){
                list.add(price);
            }
        }
        
        //对列表进行排序
        Collections.sort(list);
        //对排好序的价格进行相加,知道大于预算价格
        Integer sum=0;
        Iterator<Integer> iterator=list.iterator();
        while(iterator.hasNext()){
            Integer temp=iterator.next();
            if(total>=temp){
                total-=temp;
                sum+=temp;
            }else{
                break;
            }
        }
        
        System.out.println(sum);
        
        
    }
}        
                                
                                
****************************************************************************************************************************************************                                
                六.数据库相关
                    
1、MySQL InnoDB、Mysaim的特点?                                
                    两种类型的主要区别就是InnoDB支持事务处理与外键和行级锁,而MyISAM不支持。            
                                
                                1、存储结构

                                    MyISAM :每个MyISAM在磁盘上存储成三个文件。分别: .frm文件存储表定义 ? .MYD是数据文件的扩展名 ? .MYI是索引文件的扩展名。

                                    InnoDB :所有的表都保存在同一个数据文件中。Innodb表的大小只受限于操作系统文件的大小,一般为2GB。

                                    2、存储空间

                                    MyISAM:可被压缩,存储空间小。支持三种不同的存储格式:静态表、动态表、压缩表。

                                    Innodb:需要更多的内存和存储,它会在主内存中建立专用的缓冲池用于高速缓冲数据和索引。

                                    3、事务支持

                                    MyISAM :强调的是性能 但不提供事务支持。

                                    Innodb:提供事务支持、外部键等高级数据库功能,有事务、回滚、崩溃修复能力。

                                    4、GURD操作

                                    MyISAM:不支持行级锁,在增删时会锁定整个表格,效率不如Innodb.

                                    Innodb:支持行级锁,在增删时效率更高。

        
        
        注意:
                                    另外,InnoDB表的行级锁也不是绝对的,例如在执行一个SQL语句时Mysql不能确定要扫描的范围,InnoDB的表会被全锁全表,例如update table set num = 1 where name like "?d%"
        
2、乐观锁和悲观锁的区别?         
                                    悲观锁:
                                    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
                                    
                                    乐观锁:
                                    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

                                    
                                    2. CAS算法

                                    即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

                                    需要读写的内存值 V
                                    进行比较的值 A
                                    拟写入的新值 B
                                    当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

                                    
                                    乐观锁的缺点:
                                    1 ABA 问题
                                    2 循环时间长开销大(自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销)
                                    3 只能保证一个共享变量的原子操作
                                    
                                    CAS与synchronized的使用情景
                                    简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
                                    
                                    
                                     CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
                                    
3、数据库隔离级别是什么?有什么作用?   作用是保持事务的特性,避免脏读、幻读、不可重复读、更新失败等。
  (事务的四个特性:原子性,一致性,隔离性,持久性)                                    
                                    数据库的四种隔离级别:
                                    串行化(SERIALIZABLE)、可重复读(REPEATABLE READ)、读已提交(READ COMMITED)、读未提交(READ UNCOMMITED)四个等级
                                    
                                    串行化(SERIALIZABLE):所有事务都一个接一个地串行执行,这样可以避免幻读(phantom reads)。对于基于锁来实现并发控制的数据库来说,串行化要求在执行范围查询(如选取年龄在10到30之间的用户)的时候,需要获取范围锁(range lock)。如果不是基于锁实现并发控制的数据库,则检查到有违反串行操作的事务时,需要滚回该事务。
                                    
                                    可重复读(REPEATABLE READ):所有被Select获取的数据都不能被修改,这样就可以避免一个事务前后读取数据不一致的情况。但是却没有办法控制幻读,因为这个时候其他事务不能更改所选的数据,但是可以增加数据,因为前一个事务没有范围锁。
                                    
                                    读已提交(READ COMMITED):被读取的数据可以被其他事务修改。这样就可能导致不可重复读。也就是说,事务的读取数据的时候获取读锁,但是读完之后立即释放(不需要等到事务结束),而写锁则是事务提交之后才释放。释放读锁之后,就可能被其他事物修改数据。该等级也是SQL Server默认的隔离等级。
                                    
                                    读未提交(READ UNCOMMITED):这是最低的隔离等级,允许其他事务看到没有提交的数据。这种等级会导致脏读(Dirty Read)
                                    
                                    
                                    
                                    
                                    
                                    读未提交(Read Uncommitted)
                                                读未提交,顾名思义,就是可以读到未提交的内容。
                                                因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。
                                                如无特殊情况,基本是不会使用这种隔离级别的。
                                    读提交(Read Committed)
                                                读提交,顾名思义,就是只能读到已经提交了的内容。
                                                这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。这种隔离级别能够有效的避免脏读,但除非在查询中显示的加锁,如:
                                                select * from T where ID=2 lock in share mode;
                                                select * from T where ID=2 for update;
                                                不然,普通的查询是不会加锁的。
                                                那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?
                                    可重复读(Repeated Read)
                                                可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。
                                                可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。
                                    串行化(Serializable)
                                                这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。
                                                这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。
                                                
                                        


4、主从原理
                                    1、什么是binlog
                                     binlog是一个二进制格式的文件,用于记录用户对数据库更新的SQL语句信息,例如更改数据库表和更改内容的SQL语句都会记录到binlog里,但是对库表等内容的查询不会记录。
                                     2、@Deprecatedbinlog的作用
                                    当有数据写入到数据库时,还会同时把更新的SQL语句写入到对应的binlog文件里,这个文件就是上文说的binlog文件。使用mysqldump备份时,只是对一段时间的数据进行全备,但是如果备份后突然发现数据库服务器故障,这个时候就要用到binlog的日志了。

                                    1.从库的IO线程向主库的主进程发送请求,主库验证从库,交给主库IO线程负责数据传输;

                                    2.主库IO线程对比从库发送过来的master.info里的信息,将binlog文件信息,偏移量和binlog文件名等发送给从库

                                    3.从库接收到信息后,将binlog信息保存到relay-bin中,同时更新master.info的偏移量和binlog文件名

                                    4.从库的SQL线程不断的读取relay-bin的信息,同时将读到的偏移量和文件名写道relay-log.info文件,binlog信息写进自己的数据库,一次同步操作完成。

                                    5.完成上次同步后,从库IO线程不断的向主库IO线程要binlog信息

                                    6.从库如果也要做主库,也要打开log_bin 和log-slave-update参数


                                    mysql主从复制存在的问题:
                                    主库宕机后,数据可能丢失
                                    从库只有一个sql Thread,主库写压力大,复制很可能延时
                                     
                                    解决方法:
                                    半同步复制---解决数据丢失的问题
                                    并行复制----解决从库复制延迟的问题

                                    4.MySQL主备同步的基本原理。
                                    mysql支持单向、异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器。mysql复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新、删除等等)。因此,要进行复制,必须在主服务器上启用二进制日志。每个从服务器从主服务器接收主服务器已经记录到其二进制日志的保存的更新。
                                    当一个从服务器连接主服务器时,它通知主服务器从服务器在日志中读取的最后一次成功更新的位置。从服务器接收从那时起发生的任何更新,并在本机上执行相同的更新。然后封锁并等待主服务器通知新的更新。从服务器执行备份不会干扰主服务器,在备份过程中主服务器可以继续处理更新。

5、select * from table t where size > 10 group by size order by size的sql语句执行顺序
                                    因此执行顺序将是 from table t > where size > 10 > group by size > order by size

                                    Mysql的执行顺序是如下:

                                    FORM: 对FROM的左边的表和右边的表计算笛卡尔积。产生虚表VT1
                                    ON: 对虚表VT1进行ON筛选,只有那些符合<join-condition>的行才会被记录在虚表VT2中。
                                    JOIN: 如果指定了OUTER JOIN(比如left join、 right join),那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2中,产生虚拟表VT3, rug from子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为止。
                                    WHERE: 对虚拟表VT3进行WHERE条件过滤。只有符合<where-condition>的记录才会被插入到虚拟表VT4中。
                                    GROUP BY: 根据group by子句中的列,对VT4中的记录进行分组操作,产生VT5.
                                    CUBE | ROLLUP: 对表VT5进行cube或者rollup操作,产生表VT6.
                                    HAVING: 对虚拟表VT6应用having过滤,只有符合<having-condition>的记录才会被 插入到虚拟表VT7中。
                                    SELECT: 执行select操作,选择指定的列,插入到虚拟表VT8中。
                                    DISTINCT: 对VT8中的记录进行去重。产生虚拟表VT9.
                                    ORDER BY: 将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10.
                                    LIMIT:取出指定行的记录,产生虚拟表VT11, 并将结果返回。


6、如何优化数据库性能(索引、分库分表、批量操作、分页算法、升级硬盘SSD、业务优化、主从部署) 
                                    建立索引、优化SQL语句、优化表结构    、拆分表

                                    表的拆分一般分为横向拆分和纵向拆分,横向拆分不改变表结构,重复多建立几张表。这种优化方式类似于上文提到的SQL语句优化的例子;另一种是纵向拆分,两个表通过字段相关联。

7、何时不会使用索引
                                    单键值的b树索引列上存在null值,导致COUNT(*)不能走索引
                                    索引列上有函数运算或者是表达式的一部分,导致不走索引
                                    数据类型隐式转换导致不走索引
                                    表的数据库小或者需要选择大部分数据,不走索引 (Mysql估计使用索引比全表扫描慢)
                                    负向查询(not  , not in , not like , <>, != , !>, !< ) 不会使用索引
                                    组合索引最左前缀
                                    like '%liu' 百分号在前
                                    查询条件中使用or的每个列都必须有索引才能走索引,否则考虑使用union替代or

8、一般在什么字段上建索引(过滤数据最多的字段)
                                    1. 表的主键、外键必须有索引

                                    2. 数据量超过300的表应该有索引

                                    3. 经常与其他表进行连接的表,在连接字段上应该建立索引

                                    4. 经常出现在where字句中的字段,特别是大表的字段,应该建立索引

                                    5. 索引应该建在选择型高的字段上

                                    6. 索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引

                                    7. 复合索引的建立需要进行仔细分析,尽量考虑使用单字段索引代替:


9、如何从一张表中查出name字段不包含“XYZ”的所有行
select * from t where t.name NOT LIKE ‘%XYZ%'


13、mysql存储引擎中索引的实现机制
                                    三、聚簇索引与非聚簇索引的区别
                                        1. 聚簇索引的顺序就是数据的物理存储顺序,所以一个表最多只能有一个聚簇索引。如InnoDB主索引。

                                        2. 非聚集索引中的逻辑顺序并不等同于表中行的物理顺序,索引是指向表中行的位置的指针。如MyISAM 主索引与辅索引。

                                                1. InnoDB 使用的是聚簇索引,将主键组织到一棵 B+树中,而行数据就储存在叶子节 点上,若使用"whereid=14"这样的条件查找主键,则按照 B+树的检索算法即可查找到 对应的叶节点,之后获得行数据。若对 Name 列进行条件搜索,则需要两个步骤:第一步 在辅助索引 B+树中检索 Name,到达其叶子节点获取对应的主键。第二步使用主键在主 索引 B+树种再执行一次 B+树检索操作,最终到达叶子节点即可获取整行数据。
                                                2. MyISM 使用的是非聚簇索引,非聚簇索引的两棵 B+树看上去没什么不同,节点 的结构完全一致只是存储的内容不同而已,主键索引 B+树的节点存储了主键,辅助键索引                                 B+树存储了辅助键。表数据存储在独立的地方,这两颗 B+树的叶子节点都使用一个地址 指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通
                                                过辅助键检索无需访问主键的索引树。

14、数据库事务的几种粒度
                                        所谓的锁策略,就是在锁的开销和数据的安全性之间寻求平衡,这种平衡当然也会影响性能。
                                        表锁(table lock)

                                        表锁是MySQL中最基本的锁策略,且是开销最小的策略。一个用户对表有写操作时,它会锁定整张表,这会阻塞其他用户对该表的所有读写操作。
                                        尽管存储引擎可以管理自己的锁,MySQL本身还是会使用各种有效的表所来实现不同的目的。如服务器会为ALTER TABLE之类的语句使用表所而忽略存储引擎的锁机制。

                                        行锁(row lock)

                                        行级锁可以最大程度地支持并发处理。在InnoDB和XtraDB及一些其他存储引擎中实现了行级锁,行级锁只能在存储引擎层实现,MySQL服务层并没有实现
    


                                    

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值