Java面试题及答案整理

特别说明

感谢原文整理,本文主要是解答了面试问题,大部分面试问题来自关注我学java 文章整理,偶有改动。戳此查看面试题整理原文


更新中。。。目前只是部分答案,包括Java基础、排序算法、数据库、Spring部分


1. Java 篇

(1). Java基础知识

 java中==和equals和hashCode的区别

"=="是运算符:
  用来比较两个值、两个对象的内存地址是否相等;

“equals()”:
  equals是Object类的方法,默认情况下比较两个对象是否是同一个对象,内部实现是通过“==”来实现的。
  如果想比较两个对象的其他内容,则可以通过重写equals方法,

例如:String类就重写了equals方法,改成了对象的内容是否相等。
下面是String类中的equals()方法:

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

“hashCode()”:
  hashCode也是Object类里面的方法,返回值是一个对象的哈希码,同一个对象哈希码一定相等,但不同对象哈希码也有可能相等。
  如果两个对象通过equals方法比较相等,那么他的hashCode一定相等;
  如果两个对象通过equals方法比较不相等,那么他的hashCode有可能相等;


 int与integer的区别
  1.Integer是int的包装类,int则是java的一种基本的数据类型;
Int是Java八种基本数据类型之一,一般大小为4字节32位,取值范围为2-31~231-1 。两个Int类型变量用“==”比较的是内容的大小。

        int m = Integer.MAX_VALUE;//2147483647
        int n = Integer.MIN_VALUE;//-2147483648
        System.out.println(m+"\n"+n);//自行验证

例如:int a = 100;int b = 100;a == b为True。
Integer是对Int类型的封装,它是一个对象,可以通过new来常见Integer对象。但是对象通过“==”比较的是对象内存地址。
例如:Integer a = new Integer(100);Integer b = new Integer(100);a == b为False
  2.Integer变量必须实例化之后才能使用,而int变量不需要实例化;
  3.Integer实际是对象的引用,当new一个Integer时,实际上生成一个指针指向对象,而int则直接存储数值;
由此可以引出一个问题:使用new和不使用的差别,是否相等呢?

Integer a = 128;int b = 128;a == b? //true
Integer a = 100; Integer b = 100;a == b? //true
Integer a = 128; Integer b = 128;a == b? //false
Integer a = 100;Integer b = new Integer(100); a==b? //false
1、int和Integer之间的比较,只要值相等就是true,因为比较时会自动拆箱,将Integer转为int进行比较。
2、两个非new的Integer比较跟大小有关:Integer类内部 通过静态内部类提供了一个缓存池,范围在-128~127之间,如果超过这个范围 Integer 值都是new出来的对象,所以两个并不会指向同一个对象。
3、一个非new的Integer和一个new的Integer比较时,一定返回false。

  4.Integer的默认值是null,而int的默认值是0。

下面是Integer的valueOf方法的一小段源码,更多的大家自行查看:
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

 抽象类的意义

  抽象类是它的所有子类的公共属性的集合,是包含一个或多个抽象方法的类。抽象类可以看作是对类的进一步抽象。在面向对象领域,抽象类主要用来进行类型隐藏。使我们能在一个更高、更抽象的级别上考虑问题,从而简化了问题的复杂性。抽象类进一步描述了多个具体类的共同特征和行为。使用抽象类可以更好地利用这些共同属性和操作,避免代码的重复编写,从而减少出错的几率,提高程序的开发效率。


 接口和抽象类的区别

 1.内部方法:接口内部全是抽象方法,默认都是public abstract修饰,且没有方法体,抽象度更高,抽象类的方法可以是具体的非抽象方法,也可是抽象的,且抽象方法有方法体。
 2.内部变量:接口内部的变量都是默认被public static final修饰(一般不在接口内定义变量),也就是说接口里的变量都是不可改变的常量。而抽象类中的变量可以是正常的定义方式。
 3.继承和实现:接口可以被类实现,且一个类可以实现多个接口,一个类只能继承一个抽象类。重点类在实现接口时必须实现其中所有的接口中的方法。继承抽象类时必须实现其内部的所有抽象方法(抽象方法没有方法体)或者重写抽象方法,那么该继承类也将是抽象类(含有抽象方法)。
 4.接口可以实现多继承,但是抽象类不能。


 能否创建一个包含可变对象的不可变对象?

可以:不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝,最常见的例子就是对象中包含一个日期对象的引用

 谈谈对java多态的理解

Java的多态体现在一下几个方面:
 继承:通过继承实现多态(典型的抽象类)
 实现接口:通过实现接口实现多态
 重写:子类继承父类时,可以对父类的方法进行重写,将原有的方法改变成符合自己子类需要的样子。需要注意的是重写方法的时候需要注意子类的方法访问修饰符范围不能小于父类方法。例如父类使用protected,那么子类可以使用protected或者public,而不能使用private。
 重载:重载发生在同类中,指的是两个名字相同的方法,他们必须包含不同的参数数量或者类型,即两个同名方法必须拥有不同的参数注入。因为只有这样,程序执行的时候虚拟机才能自动匹配使用相应的方法。重点:返回值不能作为重载的标志,也就是说两个方法如果仅仅只是返回类型不同,是不行的。
 虚函数:Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。

 String、StringBuffer、StringBuilder区别

在这里插入图片描述

String:首先观察源代码,String是被final修饰的类,那么就以为着:String的定义是必须要赋值的且是不可改变的。我们常常进行的String变动,都是进行创建了新的String。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {...}

StringBuffer:通过源码可以发现,他也是被final修饰的,但是它是继承自AbstractStringBuilder类,然而AbstractStringBuilder 类由实现了Appendable接口,通过append方法可以实现字符串操作也不会产生无用的对象。不会像String一样每次操作都产生无用对象。重点:在源码中,StringBuffer的所有方法都被synchronize修饰,也就是说是线程安全的。

 public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence{...}
 abstract class AbstractStringBuilder implements Appendable, CharSequence {...}

StringBuilder:对比StringBuffer分析,可以发现,两者都十分相近,不过StringBuilder的方法没有被synchronize修饰,也就是说StringBuilder并非线程安全的,但是同样的优势就体现出来了,StringBuilder在效率上将会比StringBuffer高很多。

public final class StringBuilder extends AbstractStringBuilder  implements java.io.Serializable, CharSequence{...}

 泛型中extends和super的区别

在泛型中,常用的extends 和super的用法其实是和Java中的其他用法是差不多的
<? extends T> ?包含T和T的子类
<? super T>?可以包含T和T的父类

 进程和线程的区别

进程是计算机分配资源的最小单位,线程是计算机CPU执行的最小单位。进程之间很少进行交流,但是线程之间的通信是比较常见的。对于线程而言,既有自己独有的内存区域,也有共享进程的资源。


 final,finally,finalize的区别

final:可以修饰变量,对象,方法,类,但是变量不可再更改,对象不能在改变,方法不能在重写(可以被继承和重载),类不能再被继承。修饰接口无意义,所以不能修饰接口。
finally:通常是和try以及catch语句结合一起使用的,用作异常处理,finally的语句在try结构中一般是必须执行的,通常用来释放锁等必要操作,以防止死锁的情况发生。
finalize:通常是指析构方法,是用来析构对象使用的,通常使用在垃圾回收部分,不过Java的垃圾回收是自动的,所以这个方法也是有虚拟机完成调用的,不需要程序员进行人为调用。

 序列化的方式

要实现序列化,首先需要类是实现了serializable接口,就可以被序列化。
序列化是为了将类状态保留下来,持久化到磁盘或者是远程调用。因为一个类的声明周期通常不会大于JVM的生命周期,所以为了使得类状态可以在JVM停止运行之后,在下一次被使用时依然可用,所以需要使用到序列化。
值得注意的是,序列化只会保存对象的状态,即其成员变量,其余的静态变量等不会被保存。如果需要指定某一变量不被序列化(如密码和账户名),可以使用关键字transient修饰该变量即可。

 string 转换成 integer的方式及原理

观察如下源码可以发现:先将String转换char数组,首先判断第一个字符是不是负号,如果是就表明为负数,否则转换为数字,然后依次判断后面的每个字符。
//s是输入的字符串,radix是输入字符串的进制,如果radix=10,表示十进制,默认是10进制,等于2就是二进制
    public static int parseInt(String s, int radix)throws NumberFormatException
    {
        int result = 0;//返回结果
        boolean negative = false;//判断是否为负数
        int i = 0, len = s.length();
        int limit = -Integer.MAX_VALUE;
        int multmin;
        int digit;

        if (len > 0) {
            char firstChar = s.charAt(0);
            if (firstChar < '0') { // Possible leading "+" or "-"
                if (firstChar == '-') {
                    negative = true;
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+')
                    throw NumberFormatException.forInputString(s);

                if (len == 1) // Cannot have lone "+" or "-"
                    throw NumberFormatException.forInputString(s);
                i++;
            }
            multmin = limit / radix;
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                digit = Character.digit(s.charAt(i++),radix);
                if (digit < 0) {
                    throw NumberFormatException.forInputString(s);
                }
                if (result < multmin) {
                    throw NumberFormatException.forInputString(s);
                }
                result *= radix;
                if (result < limit + digit) {
                    throw NumberFormatException.forInputString(s);
                }
                result -= digit;
            }
        } else {
            throw NumberFormatException.forInputString(s);
        }
        return negative ? result : -result;
    }

 静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?

静态属性可以被继承。
静态方法也可以被继承,不可以被重写但是可以隐蔽(也就是子类在重写方法后,父类方法将不可见,也就是不能被使用。无法形成多态),但是可以被重载。

在这里插入图片描述


 成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用

成员内部类:成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象

当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问。外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限

Inner inner = new Inner();//在testx类里面使用
testx.Inner inner = new testx().new Inner();;//在外部使用
外部类.this.成员变量
外部类.this.成员方法
静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

Inner inner = new Inner();//在testx类里面使用
testx.Inner inner = new testx().Inner();;//在外部使用
局部内部类:是写在方法体内的类,这种类的作用域仅限于方法内,相当于是局部变量

局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的

匿名内部类是以超类或者接口的名字进行使用的,最常见的例如新建线程,或者事件监听

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。

        new Thread(new Runnable() {
            @Override
            public void run() {
                //代码执行体
            }
        })

 讲一下常见编码方式?
常见的字节码编码方式有GBK,UTF-8,ASCII,GB2312,Unicode等

ASCII编码:采用的编码方式是用一个字节的低七位进行编码,是最早的一种编码方式之一,里面包含一些可以显示的符号(数字,字母,某些标点)和不可显示的符号(一些特殊符号),不包括中文,能表示的字符十分有限,后来继而产生了扩展的ASCII码。
GB2312编码:采用双字节编码,第一个字节称为“高位字节”(也称“区字节)”,第二个字节称为“低位字节”(也称“位字节”)。兼容ASCII。

GBK编码:兼容GBK编码,英文字母采用单字节编码,中文部分采用双字节编码。同时完全兼容ASCII。

Unicode编码:Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以。

UTF-8编码:8位元编码,针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部份修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。理论编码长度可以达到6个字节,但是实际上

  1. 带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母则需要2字节编码(Unicode范围由U+0080~U+07FF)。
  2. 其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字,使用3字节编码。
  3. 其他极少使用的语言字符使用4字节编码。

UTF-8编码:大部分字符都使用两个字节表示,但是不兼容ASCII。

UTF-32编码:每个字符都是用4个字节的定长码,就使用空间来讲,效率较低。


 如何格式化日期?
 Java的异常体系
 什么是异常链
 throw和throws的区别
 反射的原理,反射创建类实例的三种方式是什么。
 java当中的四种引用
强软弱虚
 深拷贝和浅拷贝的区别是什么?

浅拷贝将对象复制一份,但是不会拷贝对象中的引用
而深拷贝就不同,是直接将对象和所有的引用都拷贝一份

 什么是编译器常量?使用它有什么风险?

公共静态不可变(public static final )变量也就是我们所说的编译器常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。
这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。

 你对String对象的intern()熟悉么?

intern()方法的设计之初就是为了解决String占用空间的问题,所以的这个方法实际上是在new一个String的时候,在常量池上新建一个字符串常量。但是值得注意的是,new String(“a”)+new String(“b”),常量池会形成一个“ab”的常量。当你再使用“ab”字符串时,就不会重新new,而是直接在常量池提取。从而减少内存的开销。这里有篇很详细的博文,大家自行验证:戳这里去看别人家的文章

        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = new String("11");
        String s5 = "11";
        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true

 a=a+b与a+=b有什么区别吗?

在两个变量的数据类型一样时:a+=b 和a=a+b 是没有区别的。
但是当两个变量的数据类型不同时,就需要考虑一下数据类型自动转换的问题了。
也就是涉及到精度了

byte a = 1;
a = a+1;//报错,因为Java中整数默认是int类型。从int转为byte需要强转
a = (byte)a+1;//精度损失
a += 1;//不会报错

 静态代理和动态代理的区别,什么场景使用?

静态:

动态:

 Java中实现多态的机制是什么?
 如何将一个Java对象序列化到文件里?
 说说你对Java反射的理解
 说说你对Java注解的理解
 说说你对依赖注入的理解
 说一下泛型原理,并举例说明

 String为什么要设计成不可变的?
 Object类的equal和hashCode方法重写,为什么?

(2).多线程

 开启线程的三种方式?

  • 继承Thread类(重写run方法)
  • 实现Runnable接口(重写run方法)
  • 实现Callable接口(重写call方法)(有返回值)(可以使用future类进行获取返回值)

 说说进程,线程,协程之间的区别
在这里插入图片描述

  • 进程,是计算机系统资源分配和调度的一个最小单位。一般进程之间通信的方式主要有:管道、流管道、有名管道。后两者是全双工通信,前面的管道则是半双工通信。除此之外,还有信号量、信号、消息列队、共享内容、套接字等一些列通信方式。

  • 线程可以分为两类,用户级线程和内核级线程。用户级线程都是有应用程序完成,内核意识不到存在,应用启动后,系统会分配一个进程号给进程,以及对应的内存空间等资源。用户级线程的好处是比较高效,不需要进入内核空间,但是并发性能不高。内核级线程则是所有的工作都是内核完成,应用程序没有进行线程管理,只能调用内核线程的接口。内核级可以更好的分配到不同的CPU,并发性更好。在现代操作系统中,往往使用组合方式实现多线程,即线程创建完全在用户空间中完成,并且一个应用程序中的多个用户级线程被映射到一些内核级线程上,相当于是一种折中方案。

  • 协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

  • 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

 线程之间是如何通信的?

主要的通信方式是内存共享
另外就是一些方法,如wait、notify、notifyall等

 什么是Daemon线程?它有什么意义?

  • daemon线程又叫守护线程,他不同于其他线程,JVM的生命周期不会以他为参考,通常JVM的生命会和非守护线程的生命周期一起结束,也就是说JVM在执行完最后一个非守护线程后就停止,并不会因为还有守护线程而继续。例如垃圾回收线程,JVM一旦启动,垃圾回收线程就运行,直到JVM停止。所以守护线程提供的一般是公共服务。可以通过setDaemon(true)将一个线程设置为守护线程。

 为什么要有线程,而不是仅仅用进程?

  • 进程通常只能同时做一件事,那么对于多个任务并发执行将成为不可能。然而进程里面容纳多个线程以后,每个线程可以执行一个任务,实现了多个任务的同时执行。另外就是线程的设计可以增加CPU的利用率。

 什么是可重入锁(ReentrantLock)?

  • 可重入锁也叫递归锁,指外层函数在获取锁以后,内层函数依然可以获取锁进行执行。在Java中synchronize和ReentrantLock都是可重入锁。

 什么是线程组,为什么在Java中不推荐使用?

线程组ThreadGroup对象中的stop,resume,suspend会导致安全问题,主要是死锁问题,已经被官方废弃,多以价值已经大不如以前。
线程组ThreadGroup不是线程安全的,在使用过程中不能及时获取安全的信息。
线程不安全

 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

乐观锁CAS
悲观锁

 Java中用到的线程调度算法是什么?

Java使用的是抢占式调度:因为抢占式调度不会因为某一个线程的执行受阻而终止整个程序,而是会执行其他线程。
在这里插入图片描述

 同步方法和同步块,哪个是更好的选择?

同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对
象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通
常会导致他们停止执行并需要等待获得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样
从侧面来说也可以避免死锁。

 run()和start()方法区别

  • run和start方法的最本质区别是:run是执行方法体,不过不是启动新的线程去执行的,是在当前线程执行的而且是当前线程获得CPU执行时间片段后立即执行的,start就不一样了,是启动了一条新的线程运行了run方法,不过start的方法是提交了一个新线程,并且使得新线程处于就绪状态,并非立即执行,获得时间片段后才会执行run方法体。

 如何控制某个方法允许并发访问线程的个数?

AQS 重入锁 读写锁 countdownlatch 栅栏(cyclicBarrier) 信号量(semaphore)

  • semaphore:信号量,通过acquire获取一个许可,release释放一个许可
  • countdownlatch:线程计数器:利用countdown实现标记,使用await方法等待countdown的实执行。
  • 线程池
  • CyclicBarrier:最重要的就是await方法,意思是让一组线程在等待某个状态后才开始同时执行。

 在Java中wait和sleep方法的不同;
wait方法是基于对象监视器的方法,是Object的方法,会使得线程进入阻塞状态,重要的是会放弃已获得的资源锁。等待其他线程调用了对象的notify或者notifyall方法后才会再次进入就绪状态等待执行。
sleep方法是基于线程的方法,是Thread方法。使用sleep方法后,线程不会放弃已获得的资源,而且在一定睡眠时间结束后,会自动回复到执行状态。

 Thread类中的yield方法有什么作用?
 什么是不可变对象,它对写并发应用有什么帮助?

不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即
对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可
变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。
不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的。既然
它们的状态无法修改,这些常量永远不会变。
不可变对象永远是线程安全的。
只有满足如下状态,一个对象才是不可变的;
它的状态不能在创建后再被修改;
所有域都是 final 类型;并且,
它被正确创建(创建期间没有发生 this 引用的逸出)。

 谈谈wait/notify关键字的理解

 为什么wait, notify 和 notifyAll这些方法不在thread类里面?

这三个方法是基于对象监视器的,所有的对象都是Object的子类

 什么导致线程阻塞?

wait方法sleep方法
获取对象锁但是被其他线程占有了,进入阻塞状态
IO请求

 讲一下java中的同步的方法
 谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解
 static synchronized 方法的多线程访问和作用
 同一个类里面两个synchronized方法,两个线程同时访问的问题

简言之:不行。synchronize修饰方法时,加锁是对于对象的,所以会直接锁定一个对象,那么其他线程只能进行等待。

多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放. 因此就算你有多余的cpu可以执行, 但是你没有锁, 所以你还是不能进入synchronized方法执行, CPU因此而空闲. 如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU无法得到利用, 系统吞吐量低下. 因此要尽量避免某个线程对锁的长期占有 !

 你如何确保main()方法所在的线程是Java程序最后结束的线程?

栈先进后出:main栈帧先进

 谈谈volatile关键字的作用

 谈谈ThreadLocal关键字的作用
 谈谈NIO的理解
 什么是Callable和Future?
 ThreadLocal、synchronized 和volatile 关键字的区别

 synchronized与Lock的区别
 ReentrantLock 、synchronized和volatile比较
 在Java中CycliBarriar和CountdownLatch有什么区别?
 CopyOnWriteArrayList可以用于什么应用场景?
 ReentrantLock的内部实现
 lock原理
 Java中Semaphore是什么?
 Java中invokeAndWait 和 invokeLater有什么区别?
 多线程中的忙循环是什么?
 怎么检测一个线程是否拥有锁?
 死锁的四个必要条件?
 对象锁和类锁是否会互相影响?
 什么是线程池,如何使用?
 Java线程池中submit() 和 execute()方法有什么区别?
 Java中interrupted 和 isInterruptedd方法的区别?
 用Java实现阻塞队列
 BlockingQueue介绍:
 多线程有什么要注意的问题?
 如何保证多线程读写文件的安全?
 多线程断点续传原理
 断点续传的实现
 实现生产者消费者模式
 Java中的ReadWriteLock是什么?
 用Java写一个会导致死锁的程序,你将怎么解决?
 SimpleDateFormat是线程安全的吗?
 Java中的同步集合与并发集合有什么区别?
 Java中ConcurrentHashMap的并发度是什么?
 什么是Java Timer类?如何创建一个有特定时间间隔的任务?

(3).集合

Collection 和Collections 的区别?
修改对象A的equals方法的签名,那么使用HashMap存放这个对象实例的时候,会调用哪个equals方法?
List,Set,Map的区别
List和Map的实现方式以及存储方式
HashMap的实现原理
HashMap如何put数据(从HashMap源码角度讲解)?
HashMap的扩容操作是怎么实现的?
HashMap在JDK1.7和JDK1.8中有哪些不同?
ConcurrentHashMap的实现原理
HashTable实现原理
ArrayMap和HashMap的对比
HashMap和HashTable的区别
HashMap与HashSet的区别
集合Set实现Hash怎么防止碰撞
数组和链表的区别
Array和ArrayList有何区别?什么时候更适合用Array
.EnumSet是什么?
Comparable和Comparator接口有何区别?
Java集合的快速失败机制 “fail-fast”?
fail-fast 与 fail-safe 之间的区别?
BlockingQueue是什么?
Iterator类有什么作用
poll()方法和remove()方法区别?
JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。

(4).JVM

什么情况下会触发类的初始化?
谈谈你对解析与分派的认识。
你知道哪些或者你们线上使⽤什么GC策略?它有什么优势,适⽤于什么场景?
Java类加载器包括⼏种?它们之间的⽗⼦关系是怎么样的?双亲委派机制是什么意思?有什么好处?
如何⾃定义⼀个类加载器?你使⽤过哪些或者你在什么场景下需要⼀个⾃定义的类加载器吗?
堆内存设置的参数是什么?
Perm Space中保存什么数据?会引起OutOfMemory吗?
栈空间引发OOM的原因是什么?存储的内容是什么?

栈用来存储线程的局部变量表、操作数栈、动态链接、方法出口等信息。如果请求栈的深度不足时抛出的错误会包含类似下面的信息:
java.lang.StackOverflowError

另外,由于每个线程占的内存大概为1M,因此线程的创建也需要内存空间。操作系统可用内存-Xmx-MaxPermSize即是栈可用的内存,如果申请创建的线程比较多超过剩余内存的时候,也会抛出如下类似错误:

java.lang.OutofMemoryError: unable to create new native thread

一个线程占用多少空间?占据哪里的空间?可以无限new线程吗?

java里每新起一个线程,jvm会向操作系统请求新起一个本地线程,此时操作系统会用空闲的内存空间来分配这个线程。所以java里线程并不会占用
jvm的内存空间,而是会占用操作系统空闲的内存空间
jdk1.4默认的单个线程是占用256k的内存
jdk1.5以后默认的单个线程是占用1M的内存
可以通过-Xss参数设定,一般默认就好
所以既然会消耗内存,当然不可以无限new啦

做GC时,⼀个对象在内存各个Space中被移动的顺序是什么?
在这里插入图片描述
在这里插入图片描述

你有没有遇到过OutOfMemory问题?你是怎么来处理这个问题的?处理 过程中有哪些收获?

可以先尝试扩大内存,利用Xms等调整分配的内存,如果还是不行就考虑程序有错,可以dump文件出来分析。
生成dump文件的命令:
jmap -dump:format=b,file=20170307.dump 16048
file后面的是自定义的文件名,最后的数字是进程的pid
分析可以使用Java自带的 jvisualvm工具进行分析,也可是第三方的例如jprofiler。

StackOverflow异常有没有遇到过?⼀般你猜测会在什么情况下被触发?如何指定⼀个线程的堆栈⼤⼩?⼀般你们写多少?

触发Stackoverflow的条件是递归条件过深,没有出口。

-Xms	指定jvm堆的初始大小,默认为物理内存的1/64,最小为1M;可以指定单位,比如k、m,若不指定,则默认为字节。
-Xmx	指定jvm堆的最大值,默认为物理内存的1/4或者1G,最小为2M;单位与-Xms一致。
-Xmn	指定jvm堆中年轻代的大小
-Xss  	设置单个线程栈的大小,一般默认为1M。

内存模型以及分区,需要详细到每个区放什么。

分派:静态分派与动态分派。
虚拟机在运行时有哪些优化策略
请解释StackOverflowError和OutOfMemeryError的区别?

StackOverflowError:递归过深,递归没有出口。
OutOfMemoryError:JVM空间溢出,创建对象速度高于GC回收速度。

.在JVM中,如何判断一个对象是否死亡?

计数器方法
根的可达性判断

2.计算机网络

从网络加载一个10M的图片,说下注意事项
OSI网络体系结构与TCP/IP协议模型
TCP的3次握手和四次挥手
为什么TCP链接需要三次握手,两次不可以么,为什么?
TCP协议如何来保证传输的可靠性
TCP与UDP的区别
TCP与UDP的有哪些应用
HTTP1.0与2.0的区别
HTTP报文结构
HTTP的长连接和短连接?
HTTP与HTTPS的区别以及如何实现安全性
如何验证证书的合法性

Get与POST的区别

GET:请求指定的页面信息,并返回实体主体。
POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。

TCP的拥塞处理
TCP是如何进行流量控制
TCP和UDP分别对应的常见应用层协议
IP地址的分类
ABC类
有了唯一的Mac地址为啥还需要IP地址?


 1. MAC地址就好比一个人的身份证,有了身份证,你就能知道这个人在哪吗?我们需要利用IP地址来确定这个人的所在区域,在局域网中,可以使用本地的MAC地址进行通信。
 2. 到达目标机器的局域网之后,ip就没有用了,此时需要利用MAC地址来确定目标机器。因为局域网的IP对应的机器是不固定的,所以,在局域网中使用IP寻址是不严谨的。
 3. 假定MAC地址就是广播地址,那么所有接受过MAC包的路由器都会把这个消息进行转发,因此目标机器会收到很多重复的包(因为各个接受过该包的路由器都将其转发)。(为了防止上述现象的发生,目前路由器可以做到不转发那些将MAC地址作为广播地址的IP数据包)
 4. 如果不使用IP地址,那么就需要维护一个极其庞大的MAC地址表,在查找目的机器的时候,就需要向全世界发送数据包,可想而知会造成多大的网络流量。

交换机、集线器与路由器有什么区别?
宝典戳戳戳

网桥的作用
宝典戳戳戳

ARP是地址解析协议,简单语言解释一下工作原理。

主机A的IP地址为192.168.1.1,MAC地址为0A-11-22-33-44-01;
主机B的IP地址为192.168.1.2,MAC地址为0A-11-22-33-44-02;
当主机A要与主机B通信时,地址解析协议可以将主机B的IP地址(192.168.1.2)解析成主机B的MAC地址,以下为工作流程:
第1步:根据主机A上的路由表内容,IP确定用于访问主机B的转发IP地址是192.168.1.2。然后A主机在自己的本地ARP缓存中检查主机B的匹配MAC地址。
第2步:如果主机A在ARP缓存中没有找到映射,它将询问192.168.1.2的硬件地址,从而将ARP请求帧广播到本地网络上的所有主机。源主机A的IP地址和MAC地址都包括在ARP请求中。本地网络上的每台主机都接收到ARP请求并且检查是否与自己的IP地址匹配。如果主机发现请求的IP地址与自己的IP地址不匹配,它将丢弃ARP请求。
第3步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中。
第4步:主机B将包含其MAC地址的ARP回复消息直接发送回主机A。
第5步:当主机A收到从主机B发来的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。本机缓存是有生存期的,生存期结束后,将再次重复上面的过程。主机B的MAC地址一旦确定,主机A就能向主机B发送IP通信了。

网络接口卡(网卡)的功能?

1、数据的封装与解封
发送时将上一层传递来的数据加上首部和尾部,成为以太网的帧。接收时将以太网的帧剥去首部和尾部,然后送交上一层
2、链路管理
主要是通过CSMA/CD(Carrier Sense Multiple Access with Collision Detection ,带冲突检测的载波监听多路访问)协议来实现
3、数据编码与译码
即曼彻斯特编码与译码。其中曼彻斯特码,又称数字双向码、分相码或相位编码(PE),是一种常用的的二元码线路编码方式之一,被物理层使用来编码一个同步位流的时钟和数据。在通信技术中,用来表示所要发送比特 流中的数据与定时信号所结合起来的代码。 常用在以太网通信,列车总线控制,工业总线等领域。

IO中同步与异步,阻塞与非阻塞区别
URI和URL的区别
GET请求中URL编码的意义
常见状态码及原因短语
说说Session、Cookie 与 Application

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie(通行证)。客户端会把Cookie保存起来。
当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。
客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。用户与服务器建立连接的同时,服务器会自动为其分配一个SessionId。
目前比较常用的两种存储session的方式:
1、存储在服务器端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side session。
2、将session数据加密,然后存储在cookie中。这种专业术语叫做client side session。flask采用的就是这种方式,但是也可以替换成其他形式。

如何避免浏览器缓存
什么是分块传送。
谈谈SQL 注入
DDos 攻击
DDos攻击有那些预防方法?
什么是XSS 攻击
从输入网址到获得页面的过程


3.数据结构与算法

(1)数据结构

链表(增删查操作)
单向链表
双向链表
队列(增删查操作)
普通队列
优先队友

二叉树(前序、中序、后序)
平衡树(尽量会实现代码)

红黑树(了解性质、应用场景)

B树(了解性质、应用场景)

六脉神剑

深度优先搜索
广度优先搜索
最短路径
最小生成树

将图的所有点连接起来但是又不多余线的图,也就是假如图有N个点,那么需要的线条就是N-1条。

Prim算法
Kruskal算法
拓扑
字符串
Knuth-Morris-Pratt算法
Boyer-Moore算法

(2)几种算法思想

递归

通过重复将问题分解为同类的子问题而解决问题的方法

递推

贪心
枚举
动态规划
回溯法
银行家算法

典型的避免死锁算法。
操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。
当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。
若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。

分治

将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

(3)必学十大排序算法

Java中的十种排序方式(代码都是升序)
排序
基于比较的排序:冒泡、选择、插入、并归、快速、希尔、堆排序
排序所发稳定性:如果两个值相等,排序后顺序发生了变化,则不稳定,反之稳定。
稳定性
常见递推式与复杂度:
在这里插入图片描述

1.冒泡排序

执行顺序:从头开始,如果第一个数比第二个数小,则不动,如果比第二个数比较小,就交换这两个数据。一次往后移动,这样循环一次就可以将最大的数移动到最后位置。下一次循环就忽略上一次排序的最大数,在剩下的元素里再进行循环。连续多次就可以实现。(因为这样的排序,比较大或者比较小的数会连续的想后移动,一次排序一个,形似冒泡。)
冒泡排序的时间复杂度是十分不确定的,对于已有顺序的复杂度很小,但是对于大量随机数据,是可能会更高的,因为一旦if语句判定成功,就要进行数据交换。

class Sort{
    //冒泡排序:
    public int[] BubbleSort(int [] in ){
        int end = in.length;//缩小排序范围,将已经排序的数据排除在循环外
        //boolean isSort = false;//判断是不是已经有序
        int endnum = 1;//记录最后一次的交换位置,这个值后的表示已经有序,当数组完全有序,第一轮扫描就结束
        while (end>=0 /*&& (!isSort)*/) {
            endnum--;//先减。如果已经排序,那么if语句不执行,end将直接等于0.
            for (int i = 0; i < end-1; i++) {
                //isSort = true;
                if(in[i]>in[i+1]){
                 //   isSort = false;
                    int m = in[i];//m是实现数据交换的中间变量
                    in[i] = in[i+1];
                    in[i+1] = m;
                    endnum = i+1;//记录最后一次交换的位置
                }
            }
            end=endnum;
        }
        return in;
    }
}
2.选择排序

多次循环,每轮跳出最大值,将最大值(升序情况,降序相反)放到最后一个未排序的位置,与其交换值。
相比于冒泡排序,时间复杂度可能更高o(n^2),但是,交换的次数更少,如果是处理随机数据,选择排序可能比冒泡排序更好。因为他并不会像冒泡一样,if判定为真就执行交换,而是遍历一次才交换一次数据。
注意:数组选择排序不能保证绝对的稳定性。如果多组相等值,可能会导致混乱,例如:
1,2,3,5,10,10,2,3,2.
虽然可以把第二个10放在最后,但是最后一个2被放在第二个2前面了。

以上仅是数组的选择排序,但是链表的选择排序是稳定的

    public int[] SelectSort(int[] in){
        int end = in.length-1;
        int index = 0;
        int max = in[0];
        while (end>=0){
            for (int i = 0; i <= end; i++) {//找出每轮最大值
                if(max<=in[i]){//保证稳定性,这里要相等,使最大的为最后一个最大的数
                //比如:123525214,需要让最大的值为第二个5.但不能保证绝对稳定。
                    max = in[i];
                    index = i;
                }
            }
            in[index] = in[end];//交换为最后一值的大小;
            in[end] = max;//把最大值放在最后
            max = 0;
            end--;
        }
        return in;
    }
3.堆排序(如果你不知道堆,就不用看了,看了也不懂)

堆排序可以认为是对选择排序的优化。从数组中挑一个最大的。不过优化了选择最大的时间复杂度进行了优化。
在这里插入图片描述

步骤:原地建堆
原地建堆
大顶堆:最大值就会在堆顶,将堆第一个数据与最后一个数据进行交换,再把最后一个元素放到堆顶。对堆顶进行siftDown操作。一直重复,直到堆只有一个元素。siftDown的时间复杂度是o(n*logn)。空间复杂度o(1)。
堆排序不是稳定的排序。

    public int[] Heapsort(int[] in){
        int heapsize = in.length ;
        //原地建堆
        for (int i =(heapsize>>1)-1; i >=0; i--) {
            in = siftDown(i,in,heapsize);
        }
        while(heapsize>1){
            //交换堆顶元素
            --heapsize;
            int tem = in[0];
            in[0] = in[heapsize];
            in[heapsize] = tem;
            //对0元素进行siftDown,恢复堆的性质
            in = siftDown(0,in,heapsize);
        }
        return in;
    }
    private int[] siftDown(int index,int[] arr,int heapsize){
        Integer element = arr[index];
        int half = heapsize >>1;
        while (index<half){
            int childindex = (index<<1)+1;
            Integer child = arr[childindex];
            int rightindex = childindex+1;
            if(rightindex<heapsize && arr[rightindex]-child>0){
                child = arr[childindex = rightindex];
            }
            if(element-child>=0) break;
            arr[index] = child;
            index = childindex;
        }
        arr[index] = element;
        return arr;
    }

4.插入排序(扑克牌玩过没?)

这样
在执行过程中,插入排序将序列分为两部分,头部是已经排好序的,尾部待排序。是稳定的排序。
插入排序的交换次数比较多,数量比较小的情况下比较适合。
**时间复杂度:**插入排序的时间复杂度跟逆序对正相关的。
在这里插入图片描述

    public int[] InsertSort(int[] in){
        //类似于扑克牌 ,前面部分相当于在手里的牌,
        //i指向的牌相当于拿起来的牌,然后遍历手里的牌,
        // 找个合适的位置放进去
        for (int i = 1; i < in.length; i++) {
            int j = i;
            //重点是与前一个值比较
            while(in[j]<in[j-1]){
                int tem = in[j];
                in[j] = in[j-1];
                in[j-1]=tem;
                j--;
            }
        }
        return in;
    }

【优化交换次数】:将待插入的数据放在旁边,将前面有序的比待插入的数大的都往后挪一位,然后将待插入元素放到正确位置就可以。

    public int[] InsertSort(int[] in){
        //类似于扑克牌 ,前面部分相当于在手里的牌,
        //i指向的牌相当于拿起来的牌,然后遍历手里的牌,
        // 找个合适的位置放进去
        for (int i = 1; i < in.length; i++) {
            int j = i;
            int insert = in[j];
            //重点是移动前面大的
            while(j>0&&insert<in[j-1]){
                in[j] = in[j-1];
                j--;
            }
            in[j] = insert;
        }
        return in;
    }

【优化查找】还可以使用二分查找的方法进行查找插入。能将该方法进一步进行优化。二分查找的方法代码如下:

    public int[] InsertSort2(int[] in){
        for (int i = 1; i < in.length; i++) {
            int j = i;
            int insert = in[j];
            int index = search(in,insert,j);//查找插入位置
            while(j>index){//重点是移动前面大的
                in[j] = in[j-1];
                j--;
            }
            in[j] = insert;
        }
        return in;
    }
    //二分查找进行优化,返回第一个大于x的元素索引,插入位置
    public int search(int [] in,int x){
        if(in == null||in.length==0) return -1;
        int left = 0;
        int right = in.length;
        while(left!=rigth){
            int mid = (left+right)>>1;
            if(x<in[mid]){
                right = mid;
            } else{
                left = mid+1;
            }
        }
        return left;
    }
5.归并排序(Merge Sort)

不断地将当前序列分割成两个子序列,直到两个子序列不能再分。再将子序列不断的合并成一个有序序列,直到最后只剩下一个有序序列。
000
如果不考虑空间问题,则可以直接将数组拆分为两个数组进行组合就可以了。如下图。
在这里插入图片描述
合并的时候需要先拷贝整个数组的左半边数组,为了节省空间,就可以在输入数组上直接进行比较填入。例如下图,第一个元素,左边较小,就先填入左边的数据。依次后推。
在这里插入图片描述
具体的实现代码如下:(使用递归的,不太好理解!!!)

    //归并排序
    public int[] MergeSort(int[] in){
        in = divide(in,0,in.length);
        return in;
    }
    private int[] divide(int[] in,int begin,int end){
        //利用递归,不断地拆分序列,直到长度为1,然后调用merge方法,
        //进行合并序列,直到所有的序列都被合并在最后的序列中
        if(end-begin<2) return in;
        int mid = (begin+end)>>1;
        in = divide(in,begin,mid);//前半部分
        in = divide(in,mid,end);//后半部分
        in = merge(in,begin,mid,end);//合并
        return in;
    }
    private int[] merge(int[] in,int begin,int mid,int end){
        int [] leftarr = new int[in.length>>1];//得到左半部分数据,进行保留
        int li = 0,le = mid-begin;//li为左侧指针,le为左侧截止位置
        int ri = mid,re = end;//ri为右侧指针,re为右侧截止位置
        int ai = begin;//ai表示最终返回数组的,当前填入数据的指针
        for (int i = li; i <le ; i++) {
            leftarr[i] = in[begin+i];//获得左边数组
        }
        while (li<le){//在左边数组没填完之前都需要进入
            if(ri<re && in[ri]<leftarr[li]){//如果右侧数组小于左侧数组指针的数
                in[ai++] = in[ri++];//在返回数组填入小的右侧数据
            }else{
                in[ai++] = leftarr[li++];//反之填入左侧数据
            }
        }
        return in;
    }
休眠排序(请不要使用此方法,纯属娱乐)

用线程的休眠机制进行排序。。。(适用于数字型的排序),给出一个数组,然后遍历数组,循环建立线程,对相应建立的线程进行sleep睡眠相应的时间进行输出。实现排序。

    //休眠排序(娱乐)
    public void SleepSort(int[] in){
        for (int i : in) {
            new SortThread(i).start();
        }
    }
class SortThread extends Thread{
    private int value;
    public SortThread(int value){
        this.value =value;
    }
    public void run(){
        try{
            Thread.sleep(value);
            System.out.println(value);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
6.快速排序(Quick Sort)(不稳定)

执行流程:
1.首先选择一个轴点元素;
2.按照轴点元素将所有 的元素分割为两个子序列;
 将大于轴点元素放在右边;
 小于轴点的放左边;
 等于的元素随便放置都可以。
3.对子序列进行继续切分操作,直到不能再切分。
如下图,带颜色的是轴点元素。
kuai

在这里插入图片描述
具体代码实现如下:

    //快速排序
    public int[] QuickSort(int[] in){
        quickSort(in,0,in.length);
        return in;
    }
    public int [] quickSort(int[] in,int begin,int end){
        if(end-begin<2) return in;
        int mid = indexOfpivot(in,begin, end);
        quickSort(in,begin,mid);
        quickSort(in,mid+1,end);
        return in;
    }
    public int indexOfpivot(int[] in,int begin,int end){
        int value = in[begin];
        boolean change = true;//改变运行方向,左边换右边,右边换左边。两边依次执行
        end--;
        while (begin<end){
            if(change) {
                if (in[end] <= value) {//如果右边的值小于轴点。就将右边这个值交换到begin位置
                    // (在左边,其实是个无效位,因为轴点已经取出了)
                    in[begin++] = in[end];//并且将begin指向下一个有效位,
                    // 注意此时end指向的位置是无效位,已经把值赋值到begin了                    
                    change =false;//改变为true。下次执行右端数据。从begin开始判断
                } else {
                    end--;
                }
            }
            else{
                if(in[begin]>=value){//如果左边的值大于轴点值,那么以为它需要放在右边,
                    // 因为此时end位为无效位,所以将这个数值放去end位最合适
                    in[end--] = in[begin];//end--,是指向下一个有效位
                    change = true;//改变为true。下次执行右端数据。从end开始判断
                }else {
                    begin++;
                }
            }
        }
        in[begin] = value;
        return begin;
    }
7.希尔排序

把序列看做一个矩阵。分为m列,逐列进行排序,m从某个整数逐渐减为1,当m为1的时候,整个序列将完全有序。
因此希尔排序也被称为递减增量排序。矩阵的列数取决于步长序列。
希尔本人给出的步长序列是n/2k,比如n为16,步长序列为1,2,4,8。希尔不认的步长序列最差复杂度是o(n2)。目前科学家研究的最佳步长序列算法见下文:
希尔
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
疑惑吗?如果上来直接分为1列不就直接排好了?
其实不是,前面的几次排列,每次逆序对的数量在不断减少的。到这里请返回去看插入排序,在每一次的步长排序中,逆序对都减少,那么底层就很适合用插入排序来实现。所以也有人认为希尔排序实际是插入排序的一种优化。

    //希尔排序
    public int[] ShellSort(int[] in){
        ArrayList<Integer> arr = new ArrayList<>();//希尔步长序列
        int inistep = in.length>>1;
        while (inistep>0){
            arr.add(inistep);
            inistep>>=1;
        }//产生希尔步长
        for (Integer step:arr) {//步长
            for (int col = 0; col < step; col++) {//每一列col+step*i
                for (int begin = col+step; begin < in.length; begin+=step) {//排序
                    int cur =begin;
                    while(cur>col && in[cur]<in[cur-step]){//此处跟插入排序一致,还可以优化。
                        int tem = in[cur-step];
                        in[cur-step] = in[cur];
                        in[cur] = tem;
                        cur -=step;
                    }
                }
            }
        }
        return in;
    }

在这里插入图片描述
________________________________________________________________________

以上都是基于比较的排序,时间复杂度最低都是o(n*logn)。下面的方法都是基于比较的排序方式,属于典型的利用空间换时间,某些时候,平均时间复杂度可以更小。

8.计数排序(Count Sort针对一定范围的整数排序)

核心思想是统计整数在序列中出现的次数,进而推导出有序的索引。时间复杂度是o(N);
在这里插入图片描述
数组的长度取决于序列中最大的数的大小。统计完成后直接从小到大的依次输出就可以得到排序的结果。
这个实现很简单,就不加注释了,如下!

//计数排序
    public int[] CountSort(int[] in){
        int max = 0,min = Integer.MAX_VALUE;
        for (int i = 0; i <in.length ; i++) {
            max = Math.max(max,in[i]);//最大值
            min = Math.min(min,in[i]);//最小值
        }
        int[] out = new int[max+1-min];//指引大小
        for (int i = 0; i < in.length; i++) {
            out[in[i]-min]++;
        }
        int count = 0;
        for (int i = 0; i < out.length; i++) {
            while(out[i]>0){
                in[count] = i+min;//存入真实值
                out[i]--;//次数减1
                count++;//in的index加1
            }
        }
        return in;
    }
9.基数排序(Radix Sort)

适用于整数排序(尤其是非负整数)。
执行流程:依次对个位数、十位数、百位数、千位数、、、等进行排序。(从低到高)
在这里插入图片描述
针对每个位上的数进行一次计数排序。可以理解为把计数排序的索引更换成数字的每个位。这样都是在0-9之间的数。

    //基数排序
    public int[] Radixsort(int[] in ){
        int[] count = new int[10];//计数内存
        int[] out = new int[in.length];//中间缓存空间
        int max = 0;//最大值
        for (int i = 0; i <in.length ; i++) {
            if(in[i]>max){
                max = in[i];//获取最大值,用来判断最大数有几位,作为循环条件
            }
        }
        for (int divider= 1; divider <= max; divider=divider*10) {//获取除数,用来获取每个位上的数字
            for (int i = 0; i < count.length; i++) {
                count[i] = 0;//清空coun,因为是复用的,必须清空
            }
            for (int j = 0; j <in.length ; j++) {
                count[in[j]/divider%10]++;//计算每个位,除以一个数,再模10就可以得到各位数
            }
            for (int i = 1; i <count.length ; i++) {
                count[i]+= count[i-1];//统计每个位的出现次数,累计起来
            }
            for (int i = in.length-1; i >=0; i--) {
                out[--count[in[i]/divider%10]] = in[i];//恢复每个数位置,存入每个数
            }
            for (int i = 0; i <in.length ; i++) {
                in[i]=out[i];//将数据存回输入数组
            }
        }
        return in;
    }
10.桶排序(Bucket Sort)

执行流程:创建一定数量的桶(数组,链表),按照一定的规则将序列中的元素均匀分配到桶里面,分别对桶每个桶进行排序。将所有非空的桶合并成有序数列。桶排序没有具体的规范式,需要自己进行规划。代码大家自己操作吧!!!(实际上是我写了,没跑对!)

4.数据库

请简洁描述Mysql中InnoDB支持的四种事务隔离级别名称,以及逐级之间的区别?
在这里插入图片描述
Read Uncommitted(读取未提交内容) >> 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容) >> 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读) >> 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control 间隙锁)机制解决了该问题。注:其实多版本只是解决不可重复读问题,而加上间隙锁(也就是它这里所谓的并发控制)才解决了幻读问题。
Serializable(可串行化) >> 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

在Mysql中ENUM的用法是什么?
枚举类型:用来描述数据库中限定的类型,例如:

create table XXX(
id char(9) primary key
city enum("beijin","chengdu","guangzhou") not null
job_type enum("HR","CRUD","CEO");
)

枚举类型的使用需要注意的事项:
尽量不要存放数值型,因为枚举的使用也是用数值型的下标来获取的。
枚举中是否可以存取null由声明的状态决定,如果声明时注明NOT null ,则不允许存取null值。
向枚举中存入未列举的词时,会提示数据截断警告,但是依然会操作成功。

CHAR和VARCHAR和NVARCHAR 的区别?

char是固定长度的,也就是说在声明char类型是所规范的长度就是固定,当输入的长度大于定义的长度时,就会导致数据被截断,小于时,数据库会自动在后面添加空值补齐。
但是varchar不一样,是不支持Unicode的字符串,他的长度并不固定,所以输入的过长不会被截断(8000以内),输入的过短也不会补齐。更加灵活。
nvarchar包含 n 个字符的可变长度 Unicode 字符数据,因为对Unicode的支持,字节的存储大小是所输入字符个数的两倍。所输入的数据字符长度可以为零,可以输入中文。

事务是如何通过日志来实现的,说得越深入越好

(1)有多少种日志
redo和undo

(2)日志的存放形式
redo:在页修改的时候,先写到 redo log buffer 里面, 然后写到 redo log 的文件系统缓存里面(fwrite),然后再同步到磁盘文件( fsync)。

Undo:在 MySQL5.5 之前, undo 只能存放在 ibdata文件里面, 5.6 之后,可以通过设置 innodb_undo_tablespaces 参数把 undo log 存放在 ibdata之外。

(3)事务是如何通过日志来实现基本流程如下:
因为事务在修改页时,要先记 undo,在记 undo 之前要记 undo 的 redo, 然后修改数据页,再记数据页修改的 redo。 Redo(里面包括 undo 的修改) 一定要比数据页先持久化到磁盘。 当事务需要回滚时,因为有 undo,可以把数据页回滚到前镜像的 状态,崩溃恢复时,如果 redo log 中事务没有对应的 commit 记录,那么需要用 undo把该事务的修改回滚到事务开始之前。 如果有 commit 记录,就用 redo 前滚到该事务完成时并提交掉。

drop,delete与truncate的区别
作用范围和结果不同
(1)DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。
TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。

(2)表和索引所占空间。

当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,
DELETE操作不会减少表或索引所占用的空间。且常常是配合where一起使用的。
drop语句将表所占用的空间全释放掉。

(3)一般而言,drop > truncate > delete

局部性原理与磁盘预读

磁盘预读:因为磁盘的速度与主存的速度差距很大,为了提高读取的效率,磁盘往往都不是按需读取,而是进行了预读,以减少磁盘的IO次数,提高效率,往往会预读一定的数据放入主存。
局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间) ,因此对于具有局部性的程序来说,磁盘预读可以提高I/0效率。

预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k) ,主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

数据库范式

  1. 第一范式 1NF
    定义: 属于第一范式关系的所有属性都不可再分,即数据项不可分。
    理解: 第一范式强调数据表的原子性,是其他范式的基础。
    简言之:列不可再分。同一列表示一个属性。

  2. 第二范式 2NF
    定义: 若某关系R属于第一范式,且每一个非主属性完全函数依赖于任何一个候选码,则关系R属于第二范式。
    此处我们需要理解非主属性、候选码和完全函数依赖的概念。
    也就是说不依赖于别的属性,主需要依赖于一个候选码就可以确定一个元组,例如学号。学号确定那么其他的属性都是唯一对应的关系。那么一个表可以有多个候选码,比如学号、姓名等。主码可以随机选一个或者多个候选码。
    理解: 第二范式是指每个表必须有一个(有且仅有一个)数据项作为关键字或主键(primary key),其他数据项与关键字或者主键一一对应,即其他数据项完全依赖于关键字或主键。由此可知单主属性的关系均属于第二范式。
    简言之:一个表只可以描述一个事情。

  3. 第三范式 3NF
    定义: 非主属性既不传递依赖于码,也不部分依赖于码。
    理解: 第三范式要求在满足第二范式的基础上,任何非主属性不依赖于其他非主属性,即在第二范式的基础上,消除了传递依赖。
    简言之:一个表包含订单编号,顾客编号,顾客姓名,那么就形成了顾客姓名依赖顾客编号,顾客编号依赖订单编号的传递依赖。
    订单编号--->顾客编号---->顾客姓名

  4. BC范式 BCFN
    定义: 关系模式R<U,F>中,若每一个决定因素都包含码,则R<U,F>属于BCFN。
    理解: 根据定义我们可以得到结论,一个满足BC范式的关系模式有:
    所有非主属性对每一个码都是完全函数依赖;
    所有主属性对每一个不包含它的码也是完全函数依赖;
    没有任何属性完全函数依赖于非码的任何一组属性。

  5. 第四范式 4NF
    定义: 限制关系模式的属性之间不允许有非平凡且非函数依赖的多值依赖。
    理解: 显然一个关系模式是4NF,则必为BCNF。也就是说,当一个表中的非主属性互相独立时(3NF),这些非主属性不应该有多值,若有多值就违反了4NF。

  6. 第五范式 5NF
    第五范式有以下要求:
    (1)必须满足第四范式;
    (2)表必须可以分解为较小的表,除非那些表在逻辑上拥有与原始表相同的主键。

存储过程与触发器的区别

存储过程:是拥有特定功能的与SQL语句集,存储在数据库中,一次编译过后,再次使用就不需要再次编译,用户通过指定存储过程的名字给出参数(如果该存储过程带有 参数)来执行它。存储过程是数据库的一个重要对象。
触发器:一段时间自动执行的程序,是一种特殊的存储过程,触发器和普通的存储过程的区别是:触发器是对某一个表进行操作时触发。例如update、insert、delete等等。进行这些操作的时候系统会自动执行该表对应的触发器。

锁的优化策略

什么情况下设置了索引但无法使用

  1. 对于创建的多列索引(复合索引),不是使用的第一部分就不会使用索引(左原则)
  2. 对于使用 like 查询, 查询如果是 ‘%aaa’ 不会使用索引,而 ‘aaa%’ 会使用到索引。
  3. 如果条件中有 or, 有条件没有使用索引,即使其中有条件带索引也不会使用,换言之, 就是要求使用的所有字段,都必须单独使用时能使用索引。
  4. 如果列类型是字符串,那么一定要在条件中使用引号引用起来,否则不使用索引。
  5. 如果mysql认为全表扫面要比使用索引快,则不使用索引。

哪些情况下适合建索引

  1. 频繁作为where条件语句查询的字段
  2. 关联字段需要建立索引,例如外键字段,student表中的classid, classes表中的schoolid 等
  3. 排序字段可以建立索引
  4. 分组字段可以建立索引,因为分组的前提是排序
  5. 统计字段可以建立索引,例如count(),max()

哪些情况下不适合建索引

  1. 频繁更新的字段不适合建立索引
  2. where条件中用不到的字段不适合建立索引
  3. 表数据可以确定比较少的不需要建索引
  4. 数据重复且发布比较均匀的的字段不适合建索引(唯一性太差的字段不适合建立索引),例如性别,真假值
  5. 参与列计算的列不适合建索引

解释MySQL外连接、内连接与自连接的区别

内连接:两个表都满足的项才会被连接成新虚拟表
外连接分为左连接、右连接,分别以左边和右边表的条件满足为准,另一个表没有对应项的会填充null值
自连接就是一个表和自己连接,可以理解为一种特殊的内连接,这样会形成两个并列的表

完整性约束包括哪些?
数据完整性(Data Integrity)是指数据的精确(Accuracy)和可靠性(Reliability)。
分为以下四类:
1、实体完整性:规定表的每一行在表中是惟一的实体。
2、域完整性:是指表中的列必须满足某种特定的数据类型约束,其中约束又包括 取值范围、精度等规定。
3、参照完整性:是指两个表的主关键字和外关键字的数据应一致,保证了表之间的数据的一致性,防止了数据丢失或无意义的数据在数据库中扩散。
4、用户定义的完整性:不同的关系数据库系统根据其应用环境的不同,往往还需 要一些特殊的约束条件。用户定义的完整性即是针对某个特定关系数据库的约束 条件,它反映某一具体应用必须满足的语义要求。

Mysql 的存储引擎,myisam和innodb的区别。

MySQL默认的引擎是myisam。
myisam1、不支持数据库事务 2、不支持行级锁,写操作锁定整个表 ,效率低 3、不支持外键1、执行读取速度很快2、占用内存空间少3、存储空间占用少
innodb上面的都支持和上面相对

如何进行SQL优化
1、建立索引
2、避免在where进行null判断
3、将in换成exist(in不能走索引)
4、使用like语句尽量以具体参数开头,不要以%(%不走索引)
5、减少使用SELECT*,把*换成具体内容更快
。。。
更多戳这里,偷窥别人家的秘籍

乐观锁和悲观锁是什么,INNODB的标准行级锁有哪2种,解释其含义。

InnoDB实现了以下两种类型的行锁:
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

锁兼容XIXSIS
X冲突冲突冲突冲突
IX冲突兼容冲突兼容
S冲突冲突兼容兼容
IS冲突兼容兼容兼容

MVCC的含义,如何实现的

MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
详情请看别人家的文章:戳这里,修炼其他门派秘籍

MYSQL的主从延迟怎么解决

这点太复杂。。。大家找别人家秘籍。

spring

(1)spring概述

使用Spring框架的好处是什么?
1.方便解耦,简化开发
  通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP编程的支持
  通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3.声明事物的支持
  在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4.方便程序的测试
  可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5.方便集成各种优秀框架
  Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6.降低Java EE API的使用难度
  Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。

Spring由哪些模块组成?

1.Spring Core:Spring框架的核心容器,他提供了Spring框架的基本功能。这个模块中最主要的一个组件为BeanFactory,它使用工厂模式来创建所需的对象。同时BeanFactory使用IOC思想,通过读取XML文件的方式来实例化对象,可以说BeanFactory提供了组件生命周期的管理,组件的创建,装配以及销毁等功能;
2.Spring AOP:采用了面向切面编程的思想,使Spring框架管理的对象支持AOP,同时这个模块也提供了事务管理,可以不依赖具体的EJB组件,就可以将事务管理集成到应用程序中;
3.Spring ORM:提供了对现有的ORM框架的支持,例如Hibernate等;
4.Spring DAO:提供了对DAO(Data Access Object,数据访问对象)模式和JDBC的支持。DAO可以实现将业务逻辑与数据库访问的代码分离,从而降低代码的耦合度。通过对JDBC的抽象,简化了开发工作,同时简化了对异常的处理(可以很好的处理不同数据库厂商抛出的异常);
5.Spring Web:提供了Servlet监听器的Context和Web应用的上下文。同时还集成了一些现有的Web框架,例如Struts;
6.Spring Context:扩展核心容器,提供了Spring上下文环境,给开发人员提供了很多非常有用的服务,例如国际化,Email和JNDI访问等;
7.Spring Web MVC:提供了一个构建Web应用程序的MVC的实现

解释AOP模块
解释WEB 模块
核心容器(应用上下文) 模块。
什么是Spring IOC 容器?

IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装。

IOC的优点是什么?

第一,资源集中管理,实现资源的可配置和易管理。
第二,降低了使用资源双方的依赖程度,即耦合度。

ApplicationContext通常的实现是什么?

1>FileSystemXmlApplicationContext:此容器从一个XML文件中加载bean的定义,XML Bean配置文件的全路径名必须提供给它的构造函数
2>ClassPathXmlApplicationContext:此容器也从一个XML文件中加载bean的定义,这里需要正确设置classpath因为这个容器将在classpath里找bean配置
3>WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean

(2)spring依赖注入

什么是Spring的依赖注入?
有哪些不同类型的IOC(依赖注入)方式?
什么是Spring beans?
一个 Spring Bean 定义 包含什么?
解释Spring支持的几种bean的作用域。
Spring框架中的单例bean是线程安全的吗?
解释Spring框架中bean的生命周期
哪些是重要的bean生命周期方法? 你能重载它们吗?
什么是bean装配?
什么是bean的自动装配?
解释不同方式的自动装配 。
自动装配有哪些局限性 ?

(3) spring 注解

怎样开启注解装配?
首先需要导入aop包,和约束context,然后需要在xml文件中加入注解支持。
<context:annotation -config/>

谈谈@Required、 @Autowired、 @Qualifier注解。
@Resource

(4) spring 数据访问

在Spring框架中如何更有效地使用JDBC?
使用Spring通过什么方式访问Hibernate?
Spring框架的事务管理有哪些优点?

(5) Spring面向切面编程(AOP)

解释AOP
Aspect 切面
在Spring AOP 中,关注点和横切关注的区别是什么?
通知
有几种不同类型的自动代理?
什么是织入。什么是织入应用的不同点?

(6) springMVC

什么是Spring的MVC框架?
DispatcherServlet
WebApplicationContext
什么是Spring MVC框架的控制器?
@Controller 注解
@RequestMapping 注解

JavaWeb

(1)servlet与Tomcat

Servlet生命周期
forward和redirect的区别
tomcat容器是如何创建servlet类实例?用到了什么原理?
什么是cookie?Session和cookie有什么区别?
Servlet安全性问题
Tomcat 有哪几种Connector 运行模式(优化)?

(2)JSP

jsp静态包含和动态包含的区别
jsp有哪些内置对象?作用分别是什么?
jsp和servlet的区别、共同点、各自应用的范围?
写出5种JSTL常用标签
JSP是如何被执行的?执行效率比SERVLET低吗?
说出Servlet和CGI的区别?
简述JSP的设计模式。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值