Java面试常见问题集锦

一、JAVA

  1. JDK和JRE、JVM
    JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。
    Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
    JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。
  2. JVM的类加载机制
    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。
    站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:
    启动类加载器:Bootstrap ClassLoader,跟上面相同。它负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
    扩展类加载器:Extension ClassLoader,该加载器由sun.misc.LauncherKaTeX parse error: Expected 'EOF', got '\jre' at position 26: …ader实现,它负责加载JDK\̲j̲r̲e̲\lib\ext目录中,或者由…AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
    双亲委派模型是指:
    如果一个类收到了类加载的请求,不会自己先尝试加载,先找父类加载器去完成。当顶层启动类加载器表示无法加载这个类的时候,子类才会尝试自己去加载。当回到自身还无法加载时,并不会向下找,而是抛出 ClassNotFound 异常。
  3. JVM内存结构
    Java内存模型,是指多线程并发时,将内存分为主内存和工作内存,每条线程运行时拥有自己的内存区域.
    Java内存模型主要从三个特性进行解释。
    一是原子性,线程工作时,一共八个原子操作,保证了原子性。工作时会从主内存中载入(复制)数据,执行的原子操作为read,load,将数据调入CPU运算,执行的原子操作为use,运算完毕从CPU返回工作内存,执行的原子操作为assign,然后从工作内存写回主内存,执行原子操作为store,write。其中read,load和store,write操作具有不可分割性,即每两个之间具有连续性。
    剩下两个为Lock,和unLock原子操作,用于实现线程安全的访问,这两个操作是synchronized关键词的“底层实现”。
    二是可见性,具体实现为volatile关键字.(并不保证原子性)
    可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
    三是有序性,即程序执行的顺序按照代码的先后顺序执行,具体实现为先行发生原则

JVM在运行时将数据划分为了5个区域来存储。PC Register(PC寄存器)、JVM栈、堆(Heap)、方法区域(MethodArea)、本地方法栈(NativeMethod Stacks)。
方法区:存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。持久代。
堆:几乎所有的对象实例和数组都在这里分配内存。由年轻代和老年代组成。
Java 栈:栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。
程序计数器:每条线程都有一个独立的的程序计数器,各线程间的计数
器互不影响。

  1. JVM的垃圾回收机制
    触发GC(Garbage Collector)的条件
    1)GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用。但下面的条件例外。
    2)Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制调用GC线程。若GC一次之后仍不能满足内存分配,JVM会再进行两次GC,若仍无法满足要求,则JVM将报“out of memory”的错误,Java应用将停止。
    1、对象没有引用
    2、作用域发生未捕获异常
    3、程序在作用域正常执行完毕
    4、程序执行了System.exit()
    5、程序发生意外终止(被杀进程等)
    垃圾收集算法
    1 tracing算法
    基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.
    这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:

从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
2 Copying算法
为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:

这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
3 compacting算法
为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:

4 Generation算法 
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
5. 多态、接口、抽象类
多态:作用:提高程序的扩展性。避免代码的冗余。
一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
public class A {
public String show(D obj) {
return (“A and D”);
}
public String show(A obj) {
return (“A and A”);
}
}
public class B extends A{
public String show(B obj){
return (“B and B”);
}
public String show(A obj){
return (“B and A”);
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println(“1–” + a1.show(b));
System.out.println(“2–” + a1.show©);
System.out.println(“3–” + a1.show(d));
System.out.println(“4–” + a2.show(b));
System.out.println(“5–” + a2.show©);
System.out.println(“6–” + a2.show(d));
System.out.println(“7–” + b.show(b));
System.out.println(“8–” + b.show©);
System.out.println(“9–” + b.show(d));
}
}

结果:
1–A and A//对象A没有b,调用A
2–A and A
3–A and D
4–B and A//对象A没有b,调用A的方法,但A的方法被重写
5–B and A
6–A and D
7–B and B
8–B and B
9–A and D

接口:抽象类的延伸,因为类不能同时继承多个父类,
抽象类:抽象方法必须为public或者protected,抽象类不能用来创造对象,继承抽象类,子类必须实现父类的抽象方法。
6. 抽象类和接口的对比
参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
什么时候使用抽象类和接口

  1. 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
  2. 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
  3. 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
  4. 重载与重写的区别
    重载 overloading
  1. 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载是一个类中多态性的一种表现。
  2. Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。
  3. 重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。
    重写overriding
  4. 父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
  5. 若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
  6. 子类函数的访问修饰权限不能少于父类的;
  1. Java程序运行步骤
    0.先父类后子类
  2. 加载static,包括静态变量,静态构造块,静态实例化构造方法
  3. 加载实例化构造方法,非静态变量,非静态构造,构造方法
  4. 进入main主程序
    在继承中,程序启动先运行父类子类静态构造块,当用父类对象new子类构造函数时,先运行父类构造块,在运行父类构造函数,然后子类构造块,子类构造函数,若是父类方法被重写,则运行子类中重写的方法
  5. 进程和线程关系及区别
  1. 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
  2. 线程的划分尺度小于进程,使得多线程程序的并发性高。
  3. 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  4. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  5. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
  1. Java多线程实现方式
    主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。
    第一种:
    public class CallableAndFuture {
    public static void main(String[] args) {
    Callable callable = new Callable() {
    public Integer call() throws Exception {
    return new Random().nextInt(100);
    }
    };
    FutureTask future = new FutureTask(callable);
    new Thread(future).start();
    try {
    Thread.sleep(5000);// 可能做一些事情
    System.out.println(future.get());
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }
    }
    }
    第二种:
    public class CallableAndFuture {
    public static void main(String[] args) {
    ExecutorService threadPool = Executors.newSingleThreadExecutor();
    Future future = threadPool.submit(new Callable() {
    public Integer call() throws Exception {
    return new Random().nextInt(100);
    }
    });
    try {
    Thread.sleep(5000);// 可能做一些事情
    System.out.println(future.get());
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }
    }
    }
    第三种:
    public class CallableAndFuture {
    public static void main(String[] args) {
    ExecutorService threadPool = Executors.newCachedThreadPool();
    CompletionService cs = new ExecutorCompletionService(threadPool);
    for(int i = 1; i < 5; i++) {
    final int taskID = i;
    cs.submit(new Callable() {
    public Integer call() throws Exception {
    return taskID;
    }
    });
    }
    // 可能做一些事情
    for(int i = 1; i < 5; i++) {
    try {
    System.out.println(cs.take().get());
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }
    }
    }
    }
    区别:前面两个启动方式为start(),方法为run(),Thread类实际也是Runnable接口的子类,就是实现了Runnable。
    Runnable优点:避免单继承的局限(单继承避免结构上的混乱,即菱形继承问题),一个类可以继承多个接口(接口可以多继承,多实现,主要是里面的方法并没有具体实现)。适合于资源的共享。
    后面的Callable方法为call(),可以有返回结果,call()可以抛出受检查的异常,比如ClassNotFoundException,而run()不能抛出受检查的异常。

  2. 线程的状态

  3. 公平锁和非公平锁
    公平锁是指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来一次获得锁。
    公平锁的好处是等待锁的线程不会饿死,但是整体效率相对低一些;非公平锁的好处是整体效率相对高一些,但是有些线程可能会饿死或者说很早就在等待锁,但要等很久才会获得锁。其中的原因是公平锁是严格按照请求所的顺序来排队获得锁的,而非公平锁时可以抢占的,即如果在某个时刻有线程需要获取锁,而这个时候刚好锁可用,那么这个线程会直接抢占,而这时阻塞在等待队列的线程则不会被唤醒。
    公平锁可以使用new ReentrantLock(true)实现。非公平锁new ReentrantLock()。

  4. 类锁和对象锁
    类锁:在方法上加上static synchronized的锁,或者synchronized(xxx.class)的锁。如下代码中的method1和method2:
    对象锁:参考method4, method5,method6.
    public class LockStrategy
    {
    public Object object1 = new Object();
    public static synchronized void method1(){}
    public void method2(){
    synchronized(LockStrategy.class){}
    }
    public synchronized void method4(){}
    public void method5()
    {
    synchronized(this){}
    }
    public void method6()
    {
    synchronized(object1){}
    }
    }

  5. 悲观锁和乐观锁
    悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
    乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性
    两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

  6. 可重入锁
    可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
    在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁。可重入锁最大的作用是避免死锁。

  7. String、StringBuffer与StringBuilder之间区别

  8. 三者在执行速度方面的比较:StringBuilder > StringBuffer > String
    原因:String:字符串常量,不可改变的对象;
    StringBuffer:字符串变量,可改变的对象;
    StringBuilder;字符串变量,可改变的对象。

  9. StringBuilder:线程非安全的;StringBuffer:线程安全的。StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(…)。只是StringBuffer会在方法上加synchronized关键字,进行同步。

  10. 如果要操作少量的数据用String;单线程操作字符串缓冲区 下操作大量数据StringBuilder;多线程操作字符串缓冲区 下操作大量数据StringBuffer。

  11. ArrayList和LinkedList的大致区别:
    1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
    2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
    3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

  12. HashMap和HashTable有什么区别?
    重写equals要满足几个条件:自反性、对称性、传递性、一致性。
    (1)HashMap是非线程安全的,HashTable是线程安全的。
    (2)HashMap的键和值都允许有null存在,而HashTable则都不行。
    (3)因为线程安全、哈希效率的问题,HashMap效率比HashTable的要高。

  13. HashMap原理
    HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
    当两个不同的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

  14. 如果HashMap的大小超过了负载因子(load factor)定义的容量
    当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

  15. ConcurrentHashMap原理
    锁分段技术
    HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
源码解读
ConcurrentHashMap(1.7及之前)中主要实体类就是三个:ConcurrentHashMap(整个Hash表),Segment(桶),HashEntry(节点)
19. ThreadLocal
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1、每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2、将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
20. 设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
FactoryMethod(工厂方法模式)是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类。
迭代器模式属于行为型模式,其意图是提供一种方法顺序访问一个聚合对象中得各个元素,而又不需要暴露该对象的内部表示。至少可以历遍first,next,previous,last,isOver,或是历遍选择符合某种条件的子元素.
21. 关于java中位运算的左移、右移、无符号右移
<< :左移运算符,num<<n,相当于num*2n;

:右移运算符,num>>n,相当于num/2n;

:无符号右移,忽略符号位,空位以0补齐。
无符号右移规则和右移运算是一样的,只是填充时不管左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,因为对于正数来说这种运算没有意义。

二、框架

  1. Spring注解
    @Autowired 注释:
  2. 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
  3. Spring通过一个BeanPostProcessor对@Autowired 进行解析,所以要让@Autowired 起作用必须事先在 Spring 容器中声明AutowiredAnnotationBeanPostProcessor Bean。
  4. 在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。
    注意事项:
    在使用@Autowired时,首先在容器中查询对应类型的bean;
    如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
    如果查询的结果不止一个,那么@Autowired会根据名称来查找;
    如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false
    @Resource
    @Resource 的作用相当于 @Autowired,只不过 @Autowired 按 byType 自动注入,面@Resource 默认按 byName 自动注入罢了。@Resource 有两个属性是比较重要的,分别是 name 和 type,Spring 将@Resource 注释的 name 属性解析为 Bean 的名字,而 type 属性则解析为 Bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。
    虽然我们可以通过 @Autowired 或 @Resource 在 Bean 类中使用自动注入功能,但是 Bean 还是在 XML 文件中通过 进行定义 —— 也就是说,在 XML 配置文件中定义 Bean,通过@Autowired 或 @Resource 为 Bean 的成员变量、方法入参或构造函数入参提供自动注入的功能。能否也通过注释定义 Bean,从 XML 配置文件中完全移除 Bean 定义的配置呢?答案是肯定的,我们通过 Spring 2.5 提供的@Component 注释就可以达到这个目标了。
    为什么 @Repository 只能标注在 DAO 类上呢?这是因为该注解的作用不只是将类识别为 Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。
    @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次。
    @Service 通常作用在业务层,但是目前该功能与 @Component 相同。
    @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同。
    @Repository 通常作用在控制层,但是目前该功能与 @Component 相同。
    通过在类上使用 @Repository、@Component、@Service 和 @Constroller 注解,Spring 会自动创建相应的 BeanDefinition 对象,并注册到 ApplicationContext 中。这些类就成了 Spring 受管组件。这三个注解除了作用于不同软件层次的类,其使用方式与 @Repository 是完全相同的。
  5. 动态代理
    一个典型的动态代理创建对象过程可分为以下四个步骤:
    1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
    2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
    Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
    3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
    Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
    4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
    Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
    为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
    生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
    public class Main1 {
    public static void main(String[] args) {
    UserService userService = new UserServiceImpl();
    InvocationHandler invocationHandler = new MyInvocationHandler(userService);
    UserService userServiceProxy =
    (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),
    userService.getClass().getInterfaces(), invocationHandler);
    System.out.println(userServiceProxy.getName(1));
    System.out.println(userServiceProxy.getAge(1));
    }
    }
  6. spring事务管理
    Spring是通过AOP的手段达到事务控制的,具体实现是靠spring-asm.jar和cglib.jar,因为这两个jar都提供了与动态代理有关的功能,实现运行时植入新特性的功能。
    spring的事务声明有两种方式,编程式和声明式。spring主要是通过“声明式事务”的方式对事务进行管理,即在配置文件中进行声明,通过AOP将事务切面切入程序,最大的好处是大大减少了代码量。
  7. mybatis中的#和$的区别
    #{} 和 ${} 在预编译中的处理是不一样的。#{} 在预处理时,会把参数部分用一个占位符 ? 代替;而 ${}在动态解析阶段,则只是简单的字符串替换。(#{} 的参数替换是发生在 DBMS(数据库管理系统) 中,而 ${} 则发生在动态解析过程中。)
    优先使用 #{}。因为 ${} 会导致 sql 注入的问题。
    但是表名用参数传递进来的时候,只能使用 。 M y B a t i s 排 序 时 使 用 o r d e r b y 动 态 参 数 时 需 要 注 意 , 用 {} 。MyBatis排序时使用order by 动态参数时需要注意,用 MyBatis使orderby而不是#。
  8. Redis
    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库
  9. 使用redis有哪些好处?
    (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
    (2) 支持丰富数据类型,支持string,list,set,sorted set,hash
    (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
    (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
    redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
  10. 缺点
    Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
  11. 分布式
    redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。
    这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量
    通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。
    读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。
  12. redis常见性能问题和解决方案:
    (1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
    (2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
    (3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
    (4) 尽量避免在压力很大的主库上增加从库
    (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3… 这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
  13. 机器学习
    计算机有能力去学习,而不是通过预先准确实现的代码;机器学习是一种通过利用数据,训练出模型,然后使用模型预测的一种方法。
    监督学习算法:线性回归,逻辑回归,神经网络,SVM
    无监督学习算法:聚类算法,降维算法
    强化学习算法:推荐算法
  14. SVM
    实现分类或者回归。在低维空间进行计算,通过核函数将输入空间映射到高维特征空间,构造最优分离超平面。

三、数据库

  1. 增删改查
    create table student(id number primary key, name varchar, sex varchar,age number);
    insert into student values(1,李四,男,null);
    delete from student whew id =1;
    update student set name = ‘李四’;
    select * from student;
  2. 数据库事务属性
    1、数据库事务的属性-ACID(四个英文单词的首写字母):
    1)原子性(Atomicity)
    所谓原子性就是将一组操作作为一个操作单元,是原子操作,即要么全部执行,要么全部不执行。
    2)一致性(Consistency)
    事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
    3)隔离性(Isolation)
    隔离性指并发的事务是相互隔离的。即一个事务内部的操作及正在操作的数据必须封锁起来,不被其它企图进行修改的事务看到。
    4)持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。即一旦一个事务提交,DBMS(Database Management System)保证它对数据库中数据的改变应该是永久性的,持久性通过数据库备份和恢复来保证。
  3. 事务隔离级别
    对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
    • 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
    • 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
    • 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
    数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
    一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱
    数据库提供了4中隔离级别:
    隔离级别 描述
    READ UNCOMMITTED(读未提交数据) 允许事务读取未被其他事务提交的变更,脏读、不可重复读和幻读的问题都会出现
    READ COMMITED(读已提交数据) 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读问题仍然会出现
    REPEATABLE READ(可重复读) 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题依然存在
    SERIALIZABLE(串行化) 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低
    Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED
    Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ
  4. 数据库的优化
    个人理解,数据库性能最关键的因素在于IO,因为操作内存是快速的,但是读写磁盘是速度很慢的,优化数据库最关键的问题在于减少磁盘的IO,就个 人理解应该分为物理的和逻辑的优化, 物理的是指oracle产品本身的一些优化,逻辑优化是指应用程序级别的优化物理优化的一些原则:
    1)Oracle的运行环境(网络,硬件等)
    2)使用合适的优化器
    3)合理配置oracle实例参数
    4)建立合适的索引(减少IO)
    5)将索引数据和表数据分开在不同的表空间上(降低IO冲突)
    6)建立表分区,将数据分别存储在不同的分区上(以空间换取时间,减少IO)
    逻辑上优化:
    1)可以对表进行逻辑分割,如中国移动用户表,可以根据手机尾数分成10个表,这样对性能会有一定的作用
    2)Sql语句使用占位符语句,并且开发时候必须按照规定编写sql语句(如全部大写,全部小写等)oracle解析语句后会放置到共享池中, 如:
    select * from Emp where name=?这个语句只会在共享池中有一条,而如果是字符串的话,那就根据不同名字存在不同的语句,所以占位符效率较好
    3)数据库不仅仅是一个存储数据的地方,同样是一个编程的地方,一些耗时的操作,可以通过存储过程等在用户较少的情况下执行,从而错开系统使用的高峰时间,提高数据库性能
    4)尽量不使用*号,如select * from Emp,因为要转化为具体的列名是要查数据字典, 比较耗时
    5)选择有效的表名,对于多表连接查询,可能oracle的优化器并不会优化到这个程度, oracle 中多表查询是根据FROM字句从右到左的数据进行的,那么最好右边的表(也就是基础表)选 择数据较少的表,这样排序更快速,如果有link表(多对多中间表),那么将link表放最右边作为基础表,在默认情况下oracle会自动优化,但是如 果配置了优化器的情况下,可能不会自动优化,所以平时最好能按照这个方式编写sql
    6)Where字句规则:
    Oracle 中Where字句时从右往左处理的,表之间的连接写在其他条件之前,能过滤掉非常多的数据的条件,放在where的末尾, 另外!=符号比较的列将不使用索引,列经过了计算(如变大写等)不会使用索引(需要建立起函数), is null、is not null等优化器不会使用索引
    7)使用Exits Not Exits 替代 In Not in
    8)合理使用事务,合理设置事务隔离性,数据库的数据操作比较消耗数据库资源的,尽量使用批量处理,以降低事务操作次数
  5. 数据库函数
    SQL数据库常用函数、
    1常用数据库聚合函数
    max()
    min()
    sum()
    avg()
    count()
    2字符串处理函数
    len() 与 datalength() 区别:len是返回字符长度 datalength是返回字节长度
    LTrim() RTrim() Trim ()
    isnull(@FilterStr,N’’)如果时空将其替换
    charindex(N’;’, @TmpList)返回字符串中表达式的起始位置而不是index
    paitndex(’%ssd%’,@temp) 与charindex作用基本类似
    substring(@TmpList, 1, @Index - 1)
    Replace(‘字符串’,要替换的字符’,‘替换后的字符’)
    uppre和lower函数
    left(,) 和 right(,) 取前多少个字符 和 后多少个字符
    stuff ( character_expression , start , length , replaceWith_expression )替换指定位置指定长度的字符串
    3数字函数
    Ceiling()取整 取大 floor() 取整 取小
    abs()绝对值
    round(,) 四舍五入
    square() 平方
    sqrt()开根号
    rand() 随机值
    4日期函数
    getdate() 获取系统时间
    dateadd(时间单位,加减的变量,日期) 日期的加减
    datediff(时间单位,日期,日期) 俩日期的时间差
    datepart(时间单位,日期) 取出日期中的指定部分
    datename(时间单位,日期) 与datepart功能相同
    isdate()判断是否为如期格式 返回0或1
    5常用数据库语法
    over() 此函数为分析函数亦可叫开窗函数可以在后台做一些操作 例:
    ROW_NUMBER() over(Partition by 分组字段order by 排序字段) 分组排序之后生成行号
    sum()over(Partition by 分组字段),比如对某列分组后进行加总
    SUM,AVG,COUNT,MIN,MAX等使用OVER(PARTITION BY)语句
    将返回结果过滤替换语法
    case when RowNum > 1 then TotalMeasureTime else 0 end
    case RowNum when >1 then TotalMeasureTime elee 0 end
    union 合并两个查询结果 两个查询结果必须有相同的列 union all 是所有结果包括重复项
  6. 数据库索引
    1 索引特点:
    第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
    第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
    第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
    第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
    第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
    2 索引不足:
    第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
    第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
    第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
    3 应该建索引列的特点:
    1)在经常需要搜索的列上,可以加快搜索的速度;
    2)在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
    3)在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;
    4)在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
    5)在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
    6)在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
    4 不应该建索引列的特点:
    第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
    第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
    第三,对于那些定义为blob数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
    第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。
    5 索引类型:
    B树索引(默认类型)
    位图索引
    HASH索引
    索引组织表索引
    反转键(reverse key)索引
    基于函数的索引
    分区索引(本地和全局索引)
    位图连接索引

四、算法

  1. 常用的排序算法的时间复杂度和空间复杂度
    类别 排序方法 时间复杂度(平均) 时间复杂度(最好) 时间复杂度(最坏) 空间复杂度 稳定性 复杂性
    插入排序 直接插入排序 O(N2) O(N) O(N2) O(1) 稳定 简单
    希尔排序 O(N1.3)(O(nlog2n)) O(N) O(N2) O(1) 不稳定 较复杂
    选择排序 直接选择排序 O(N2) O(N) O(N2) O(1) 不稳定 简单
    堆排序 O(Nlog2N) O(Nlog2N) O(Nlog2N) O(1) 不稳定 较复杂
    插入排序 冒泡排序 O(N2) O(N) O(N2) O(1) 稳定 简单
    快速排序 O(N
    log2N) O(Nlog2N) O(N2) O(log2n)~O(n) 不稳定 较复杂
    归并排序 O(N
    log2N) O(Nlog2N) O(Nlog2N) O(n) 稳定 较复杂
    基数排序 O(d(r+n)) O(d(r+n)) O(d(r+n)) O(rd+n) 稳定 较复杂
  2. 二叉树遍历
    遍历即将树的所有结点访问且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。
    前序遍历:根节点->左子树->右子树
    中序遍历:左子树->根节点->右子树
    后序遍历:左子树->右子树->根节点
    例如:求下面树的三种遍历

前序遍历:abdefgc
中序遍历:debgfac
后序遍历:edgfbca
3. 尾递归

五、其他

  1. TCP的三次握手(建立连接)和四次挥手(关闭连接)
  2. TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
    (1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。
    (2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。
    (3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。
    完成三次握手,客户端与服务器开始传送数据。
    确认号:其数值等于发送方的发送序号 +1(即接收方期望接收的下一个序列号)。
  3. 四次挥手,客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
    (1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
    (2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
    (3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
    (4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值