Java面试题-自成一套

自成一套

Java基础

jvm jre jdk 分别是什么?

a. JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)

b. JRE(Java Runtime Environment,Java 运行环境),包含 JVM 标准实现及 Java 核心类库。JRE 是 Java 运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)

c. JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,JVM 是一种用于计算设备的规范,它是一个虚构出来的计算机

Java 三大特性

面向对象是利于语言对现实事物进行抽象。面向对象具有以下四大特征:

(1)继承:继承是从已有类得到继承信息创建新类的过程

(2)封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。

(3)多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。

(4)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。

int 和 Integer 有什么区别,以及以下程序结果

(1)Integer 是 int 的包装类,int 则是 java 的一种基本数据类型

(2)Integer 变量必须实例化后才能使用,而 int 变量不需要

(3)Integer 实际是对象的引用,当 new 一个 Integer 时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值

(4)Integer 的默认值是 null,int 的默认值是 0

Java 八大基本数据类型

a. 浮点型:float,double

b. 字符型:char

的是你吗12w hujonwrt6 w强. 整型:[email protected],short,int,C 入1塞k,ol./分区,

d. Boolean 型:boolean

&、| 和&&、||的区别

&、|有两种用法:(1)按位运算;(2)逻辑运算。逻辑运算时不会短路

&&、||逻辑运算符,会短路。

==和 Equals 区别

(1) ==

如果比较的是基本数据类型,那么比较的是变量的值

如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)

(2) equals

如果没重写 equals 方法比较的是两个对象的地址值

如果重写了 equals 方法后我们往往比较的是对象中的属性的内容

equals 方法是从 Object 类中继承的,默认的实现就是使用==

hashcode 与 equal

1.equal()相等的两个对象他们的 hashCode()肯定相等,

2.hashCode()相等的两个对象他们的 equal()不一定相等,

所有对于需要大量并且快速的对比的话如果都用 equal()去做显然效率太低,所以解决方式是,每当需要对比 的时候,首先用 hashCode()去对比,如果 hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果 hashCode()相同,此时再对比他们的 equal(),如果 equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!

String StringBuilder StringBuffer 的区别

1.String 为字符串常量,而 StringBuilder 和 StringBuffer 均为字符串变量,即 String 对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

2.String buffer 和 String build 区别

(1)StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,

(2)只是 StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。

(3)在单线程程序下,StringBuilder 效率更快,因为它不需要加锁,不具备多线程安全而 StringBuffer 则每次都需要判断锁,效率相对更低

方法重载与重写的区别

重写:子类的方法覆盖父类的方法,要求返回值、方法名和参数都相同。 子类抛出的异常不能超过父类相应方法抛出的异常。(子类异常不能超出父类异常) 子类方法的的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)

重载:是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,

java 集合

Java 集合类存放于 java.util 包中,是一个用来存放对象的容器。Collection 是 List 接口和 Set 接 口的父接口

List 实现类:

Vector 底层数据结构是数组,查询快增删慢;线程安全,效率低,

ArrayList 底层数据结构是数组查询快增删慢;线程不安全,效率高,

ArrayList 初始容量为 10 扩容算法(原来的容量*3)/2+1 1.7 1.8 初始容量+(初始容量/2)

LinkedList 底层数据结构是链表,查询慢,增删快;线程不安全,效率高

ArrarList 和 LinkedList 区别

(1)ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。

(2)对于随机访问 get 和 set,ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。

(3)对于新增和删除操作 add 和 remove,LinkedList 比较占优势,因为 ArrayList 要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList 的速度反而优于 LinkedList。但若是批量随机的插入删除数据,LinkedList 的速度大大优于 ArrayList. 因为 ArrayList 每插入一条数据,要移动插入点及之后的所有数据

Set 实现类:

HashSet:不能保证元素的顺序不可重复;不是线程安全的;集合元素可以为空不可以重复,有序因为底层采用链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性

TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。

TreeSet 和 HashSet 区别

HashSet 是采用 hash 表来实现的。其中的元素没有按顺序排列,add()、remove()以及 contains()等方法都是复杂度为 O(1)的方法。

TreeSet 是采用树结构实现(红黑树算法)。元素是按顺序进行排列,但是 add()、remove()以及 contains()等方法都是复杂度为 O(log (n))的方法。它还提供了一些方法来处理排序的 set,如 first(), last(), headSet(), tailSet()等等。

Map 的实现类

HashMap

为什么用的是Hash表

如果不用hash表,那么他存储的元素将是无序的,如果往里面添加元素并且不重复的话需要用equals()
进行判断,如果是单个元素还好,如果是多个那么比较起来特别麻烦

HashMap1.7和1.8的区别

1.7:数组+链表

HashMap默认哈希表是16,在1.7之前如果需要对其进行添加元素的话,首先会去调用对象的hashCode()方法,然后根据哈希算法对这个hashCode()进行运算,运算之后生成一个数组的索引值,根据这个索引值找到对应的位置,如果没有对象栈那么直接添加进去如果存在对象栈,那么通过equals()比较两者的内容,如果相同,那么后进来的会把存在的Value值覆盖,反之形成链表,jdk1.7的时候后进来的放在前面,形成链表,造成碰撞,如果元素过多的话会降低效率,hashMap提供了加载因子(0.75)减少类似这个问题的发生,但是相应的效率低,如果进行查询的话极端情况下可能查找到最后一个才是自己想要的元素,因此效率可能会更低

加载因子(当元素到达现有Hash表的75%进行扩容)一旦扩容那么它将链表中的元素进行重排序

1.8:数组+链表+红黑树
jdk1.8对他进行了优化,添加了红黑树算法,那么他触发红黑树的条件是,当碰撞的元素的个数大于8,并且总元素(hash表)大于64.在这种情况下他会将链表转换为红黑树,在哈希表扩容时,如果发现链表长度小于 6,则会由树重新退化为链表,除了添加之外其他操作都比链表效率高,因为在jdk1.8的时候添加是添加到了红黑树的末尾,在此期间他要对红黑树存储的元素进行比较找位置.其他方面红黑树减少了对比的次数那么相应的效率就上来啦

HashTable

hashTable 是线程安全的一个 map 实现类,它实现线程安全的方法是在各个方法上添加了 synchronize 关键字。 但是现在已经不再推荐使用 HashTable 了,因为现在有了 ConcurrentHashMap 这个专门用于多线程场景下的 map实现类,其大大优化了多线程下的性能。

ConcurrentHashMap: 并发数/并发级别:16

这个 map 实现类是在 jdk1.5 中加入的,其在 jdk1.6/1.7 中的主要实现原理是 segment 段锁,它不再使用和 HashTable 一样的 synchronize 一样的关键字对整个方法进行加锁,转而利用 segment 段落锁来对其进行加锁,以 保证 Map 的多线程安全。

其实可以理解为,一个 ConcurrentHashMap 是由多个 HashTable 组成,所以它允许获取不用段锁的线程同时持有该资源,segment 有多少个,理论上就可以同时有多少个线程来持有它这个资源。

其默认的 segment 是一个数组,默认长度为 16。也就是说理论商可以提高 16 倍的性能

在 JAVA 的 jdk1.8 中 则 对 ConcurrentHashMap 又 再 次 进 行 了 大 的 修 改 , 取 消 了 segment 段 锁 字 段 , 采 用 了CAS+Synchronize 技术来保障线程安全。底层采用数组+链表+红黑树的存储结构

为什么要这样做?

锁分段X(段的长度不好评定,太大资源浪费,太小元素过多) jdk1.7

CAS 算法(操作系统底层的算法)

  • CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的一种特殊指令,用于管理共享数据的并发访问。
  • CAS 是一种无锁的非阻塞算法的实现。
  • CAS 包含了 3 个操作数:
    需要读写的内存值 V
    进行比较的值 A
    拟写入的新值 B
  • 有且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。

HashMap 和 HashTable 区别

(1)线程安全性不同

HashMap 是线程不安全的,HashTable 是线程安全的,其中的方法是 Synchronize 的,在多线程并发的情况下,可以直接使用 HashTable,但是使用 HashMap时必须自己增加同步处理。

(2)是否提供 contains 方法

HashMap 只有 containsValue 和 containsKey 方法;HashTable 有 contains、containsKey 和 containsValue 三个方法,其中 contains 和 containsValue 方法功能相同。

(3)key 和 value 是否允许 null 值

Hashtable 中,key 和 value 都不允许出现 null 值。HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null。

(4)数组初始化和扩容机制

HashTable 在不指定容量的情况下的默认容量为 11,而 HashMap 为 16,Hashtable 不要求底层数组的容量一定要为 2 的整数次幂,而 HashMap 则要求一定为 2 的整数次幂。

Hashtable 扩容时,将容量变为原来的 2 倍加 1,而 HashMap 扩容时,将容量变为原来的 2 倍。

线程和进程的区别

1.一个进程至少有一个线程

2.进程是资源的分配和调度的一个独立单元,而线程是 CPU 调度的基本单元.

3.一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。

进程间的几种通信方式说一下?

  1. 管道(pipe):管道分为pipe(无名管道)和fififo(命名管道)两种,无名管道是半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
  2. 信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  3. 消息队列(message queue):消息队列是由消息组成的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
  4. 信号(signal):信号用于通知接收进程某一事件已经发生。
  5. 共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制配合使用,如:信号量来实现进程间的同步通信。
  6. 套接字(socket):socket,套接字是一种通信机制,凭借这种机制,系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
    线程间的几种通信方式知道不?

1、锁机制

互斥锁:提供了排它方式阻止数据结构被并发修改的方法。

读写锁:允许多个线程同时读共享数据,而对写操作互斥。

条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

2、信号量机制:包括无名线程信号量与有名线程信号量

3、信号机制:类似于进程间的信号处理。

线程间通信的主要目的是用于线程同步,所以线程没有像进程通信中用于数据交换的通信机制

线程的生命周期

新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的 start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明 此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了 start()此线程立即就会执行;

运行状态(Running):当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。 注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状 态又可以分为三种:

1.等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 – 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 – 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。

sleep yield wait join 方法的区别

sleep()方法

运行中的线程,执行 sleep()方法,放弃 CPU,转到阻塞状态。但不会放弃占有的资源,例如锁。sleep()时间结束,进入可运行状态。

yield()方法

执行 yield()方法,如果此时有相同或更高优先级的其他线程处于就绪状态,那么 yield()方法把当前线程放到可运行池中。如果只有相同优先级的线程,那么,该线程可能接着马上执行。如果没有相同或更高优先级线程,则什么也不做。yield()方法并不会释放锁。

wait()方法

wait 方法用于线程通信,与 notify,nofityall 配合使用。它必须在 synchronized 语句块内使用。wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入 synchronized 数据块,当前线程被放入对象等待池,该方法是 java.lang.Object 的方法。

join()方法

当前运行的线程执行另一个线程的 join()方法,当前线程进入阻塞状态,直到另一个线程运行结束,它才会恢复到可就绪状态。join()方法,调用了 wait(),因此它会释放锁。

创建线程的三种方式

一、继承 Thread 类创建线程类

(1)定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任务。因此把 run() 方法称为执行体。

(2)创建 Thread 子类的实例,即创建了线程对象。

(3)调用线程对象的 start()方法来启动该线程。

二、通过 Runnable 接口创建线程类

(1)定义 runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样是该线程的线程执行体。

(2)创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建Thread 对象,该 Thread 对象才是真正的线程对象。

(3)调用线程对象的 start()方法来启动该线程。

三、通过 Callable 和 Future 创建线程

(1)创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体,并且有返回值。

(2)创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable对象的 call()方法的返回值。

(3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

(4)调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值

Io 流

1、IO 流的分类

根据处理数据类型的不同分为:字符流和字节流

根据数据流向不同分为:输入流和输出流

A)输入流和输出流区别

输入流只能进行读操作,输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

B) 字符流和字节流区别

读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

处理对象不同:字节流能处理所有类型的数据(如图片、视频等),而字符流只能处理字符类型的数据

字节流:一次读入或读出是 8 位二进制。

字符流:一次读入或读出是 16 位二进制。

设备上的数据无论是图片或者视频,文字,它们都以二进制存储的。二进制的最终都是以一个 8 位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理 字符数据。

结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。

C)缓冲流:

BufferedInputStrean 、BufferedOutputStream、 BufferedReader、 BufferedWriter 增加缓冲功能,避免频繁读写硬盘

Java NIO 简介

  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO支持更加高效的方式进行文件的读写操作

Java NIO 与 IO 的主要区别

IO NIO
面向流(Stream Oriented) 面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
(无) 选择器(Selectors)

通道和缓冲区

Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

简而言之,Channel 负责传输, Buffer 负责存储

缓冲区的基本属性

  • Buffer 中的重要概念:
  • 容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
  • 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
  • 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制
  • 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个position.

标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

谈谈你对反射的理解

(1)什么是反射机制?有什么作用?

所谓的反射机制就是 java 语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。

Java 的反射机制的实现要借助于 4 个类:class,Constructor,Field,Method;

其中 class 代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。

在 Java 运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于 Java 语言的反射(Reflection)机制。

Java 8新特性简介

  • 速度更快
  • 代码更少(增加了新的语法 Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常 Optional

Lambda 表达式

  • Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

创建对象的 5 种方式

使用new关键字 调用了构造函数
使用Class类的newInstance方法 调用了构造函数
使用Constructor类的newInstance方法 调用了构造函数
使用clone方法 没有调用构造函数
使用反序列化 没有调用构造函数

Synchronized lock volatile 的区别

一、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 和 volatile 区别

  1. volatile 关键字解决的是变量在多个线程之间的可见性;而 sychronized 关键字解决的是多个线程之间访问共享资源的同步性。

  2. volatile 只能用于修饰变量,而 synchronized 可以修饰方法,以及代码块。(volatile 是线程同步的轻量级实现,所以 volatile 性能比 synchronized 要好,

  3. 多线程访问 volatile 不会发生阻塞,而 sychronized 会出现阻塞。

  4. volatile 能保证变量在多个线程之间的可见性,但不能保证原子性;而 sychronized 可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。
    线程安全包含原子性和可见性两个方面。
    对于用 volatile 修饰的变量,JVM 虚拟机只是保证从主内存加载到线程工作内存的值是最新的。

三、公平锁和非公平锁的区别

公平锁,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁, 否则就会加入到等待队列中,以后会按照FIFO 的规则从队列中取

非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

volatile 关键字

Java 提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:

  • 对于多线程,不是一种互斥关系
  • 不能保证变量状态的“原子性操作”

说说自己是怎么使用 synchronized 关键字,在项目中用到了吗

synchronized关键字最主要的三种使用方式:

修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁

修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态synchronized 方法占用的锁是当前实例对象锁。

修饰代码块,对给定对象加锁,进入同步代码块前要获得给定对象的锁。 synchronized(this)代码块锁定的是当前对象。synchronized 关键字加到 static 静态方法和synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!

Synchronized用过吗,其原理是什么?

Synchronized是由JVM实现的一种互斥同步方式,如果你查看被Synchronized修饰程序块编译后的字节码,会发现,被Synchronized修饰过的程序块,在编译前后被编译器生成了monitorenter和monitorexit两个字节码指令。这两个指令是什么意思呢?在虚拟机执行到monitorenter指令时,首先会尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器+ 1;当执行monitorexit指令时将锁计数器-1; 当计数器为0时,锁就被释放了。如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。Java中Synchronize通过在对象头设置标记,达到了获取锁和释放锁的目的。

你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?

“锁”的本质其实是monitorenter和monitorexit字节码指令的一个Reference类型的参数,即要锁定和解锁的对象。使用Synchronized可以修饰不同的对象,对应的对象锁可以这么确定。

1.如果Synchronized明确指定了锁对象,比如Synchronized(变量名)、Synchronized(this)等,说明加解锁对象为该对象。

2.如果没有明确指定:若Synchronized修饰的方法为非静态方法,表示此方法对应的对象为锁对象;若Synchronized修饰的方法为静态方法,则表示此方法对应的类对象为锁对象。

注意,当一个对象被锁住时,对象里面所有用Synchronized修饰的方法都将产生堵塞,而对象里非Synchronized修饰的方法可正常被调用,不受锁影响。

什么是可重入性,为什么说Synchronized是可重入锁?

可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况。比如下面的伪代码,一个类中的同步方法调用另一个同步方法,假如Synchronized不支持重入,进入method2方法时当前线程获得锁,method2方法里面执行method1时当前线程又要去尝试获取锁,这时如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己。对Synchronized来说,可重入性是显而易见的,刚才提到,在执行monitorenter指令时,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器+1,其实本质上就通过这种方式实现了可重入性。

为什么说Synchronized是非公平锁?

非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。

单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!

//双重校验锁实现对象单例(线程安全)
public class Singleton { 
  private volatile static Singleton uniqueInstance;
  private Singleton() {
  }
  public static Singleton getUniqueInstance() { 
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
  //类对象加锁 
  synchronized (Singleton.class) {
    if (uniqueInstance == null) {
      uniqueInstance = new Singleton();
    } 
  }
}
  return uniqueInstance; 
} 
}

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用getUniqueInstance() 后发现uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗

JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

谈谈 synchronized和ReenTrantLock 的区别

① 两者都是可重入锁

“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API

synchronized 是依赖于 JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/fifinally 语句块来完成)

③ ReenTrantLock 比 synchronized 增加了一些高级功能

①等待可中断

ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

②可实现公平锁

ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair) 构造方法来制定是否是公平的。

③可实现选择性通知(锁可以绑定多个条件)

synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在Condition实例中的所有等待线程。

如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。

讲一下Java内存模型

在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。

说白了, volatile 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。

为什么要用线程池?

线程池提供了一种限制和管理资源(包括执行一个任务),每个线程池还维护一些基本统计信息.

使用线程池的好处:

降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。

提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

实现Runnable接口和Callable接口的区别

如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。

Java JUC 是什么

在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等

Condition 是什么?

  • Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
  • 在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll。
  • Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用其 newCondition() 方法。

介绍一下Atomic 原子类

Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。

所以,所谓原子类说简单点就是具有原子/原子操作特征的类

JUC 包中的原子类是哪4类?

基本类型

使用原子的方式更新基本类型

AtomicInteger:整形原子类

AtomicLong:长整型原子类

AtomicBoolean :布尔型原子类

数组类型

使用原子的方式更新数组里的某个元素

AtomicIntegerArray:整形数组原子类

AtomicLongArray:长整形数组原子类

AtomicReferenceArray :引用类型数组原子类

引用类型

AtomicReference:引用类型原子类

AtomicStampedRerence:原子更新引用类型里的字段原子类

AtomicMarkableReference :原子更新带有标记位的引用类型

对象的属性修改类型

AtomicIntegerFieldUpdater:原子更新整形字段的更新器

AtomicLongFieldUpdater:原子更新长整形字段的更新器

AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。

volatile 关键字 内存可见性

  • 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。

  • 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能实时地看到其他线程写入的值,有时甚至是根本不可能的事情。

  • 为了解决这个问题我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的 volatile 变量。

TCP、UDP 协议的区别

UDP 在传送数据之前不需要先建立连接,远程主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等

TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的运输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),一系列操作难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。

在浏览器中输入url地址 ->> 显示主页的过程

总体来说分为以下几个过程:

  1. DNS解析
  2. TCP连接
  3. 发送HTTP请求
  4. 服务器处理请求并返回HTTP报文
  5. 浏览器解析渲染页面
  6. 连接结束

TCP 三次握手和四次挥手(面试常客)

客户端 –发送带有 SYN 标志的数据包– 一次握手–服务端

服务端 –发送带有 SYN/ACK 标志的数据包– 二次握手–客户端

客户端 –发送带有带有 ACK 标志的数据包– 三次握手–服务端

为什么要三次握手?

三次握手的目的是建立可靠的通信信道,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

第一次握手:Client 什么都不能确认;

		Server 确认了对方发送正常

第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;

		Server 确认了:自己接收正常,对方发送正常

第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;

		Server 确认了:自己发送、接收正常,对方发送接收正常所以三次握手就能确认双发收发功能都正

					    常,缺一不可。			     

为什么要传回 SYN

接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。

SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。

传了 SYN,为啥还要传 ACK

双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。

什么时候需要使用到4次握手?

断开一个 TCP 连接需要“四次挥手”:

客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送

服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号

服务器-关闭与客户端的连接,发送一个FIN给客户端

客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1

为什么要四次挥手

任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。

写出 java 5 种运行时异常

NullPointerException - 空指针引用异常

ClassCastException - 类型强制转换异常。

IllegalArgumentException - 传递非法参数异常。

ArithmeticException - 算术运算异常

IndexOutOfBoundsException - 下标越界异常

NumberFormatException - 数字格式异常

final、finally、finalize 区别

final 用于修饰类、成员变量和成员方法。final 修饰的类,不能被继承(String、StrngBuilder、 StringBuffer、Math,不可变类),其中所有的方法都不能被重写,所有不能同时用 abstract 和 final 修饰(abstract 修饰的是抽象类,抽象类是用于被子类继承的,和 final 起相反的作用);final 修饰的方法不能被重写,但是子类可以用父类中 final 修饰的方法;final 修饰的成员变量是不可变的,如果成员变量是基本数据类型,初始化之后成员变量的值不能被改变,如果成员变量是引用类型,那么它只能指向初始化时指向的那个

finally 是在异常处理时提供 finally 块来执行任何清除操作。不管有没有异常被抛出、捕获都会被执 行。try 块中的内容是在无异常时执行到结束。catch 块中的内容,是在 try 块内容发生 catch 所声明的异常时,跳转到 catch 块中执行。finally 块则是无论异常是否发生都会执行 finally 块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,可以放在 finally 块中。

finalize:Object 类中定义的方法,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize() 方法可以整理系统资源或者执行其他清理工作。

Finally 一定会被执行吗?

至少有两种情况下 finally 语句是不会被执行的:

(1)try 语句没有被执行到,如在 try 语句之前就返回了,这样 finally 语句就不会执行,这也说明了 finally 语句被执行的必要而非充分条件是:相应的 try 语句一定被执行到。

(2)在 try 块中有 System.exit(0);这样的语句,System.exit(0);是终止 Java 虚拟机 JVM 的,连 JVM 都停止了,所有都结束了,当然 finally 语句也不会被执行

什么是 java 序列化,如何实现 java 序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现 :将需要被序列化的类实现Serializable接口 ,该接口没有需要实现的方法只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流.

Object 中有哪些方法

(1)protected Object clone()—>创建并返回此对象的一个副本。

(2)boolean equals(Object obj)—>指示某个其他对象是否与此对象“相等”。

(3)protected void finalize()—>当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

(4)Class getClass()—>返回一个对象的运行时类。

(5)int hashCode()—>返回该对象的哈希码值。

(6)void notify()—>唤醒在此对象监视器上等待的单个线程。

(7)void notifyAll()—>唤醒在此对象监视器上等待的所有线程。

(8)String toString()—>返回该对象的字符串表示。

(9)void wait()—>导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

void wait(long timeout)—>导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。

void wait(long timeout, int nanos)—>导致当前的线程等待,直到其他线程调用此对象的 notify()

产生死锁的基本条件

产生死锁的原因:

(1) 系统资源不足。

(2) 进程运行推进的顺序不合适。

(3) 资源分配不当等。

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

死锁的解除与预防:

理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面要考虑如何不让这四个必要条件成立,确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。

什么是线程池,如何使用?

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。

在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。

ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);

ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);

ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

然后调用他们的 execute 方法即可。

优点:

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

Java 自带有哪几种线程池?

(1)newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:工作线程的创建数量几乎没有限制(其实也有限制的,数目为 Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为 1 分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

在使用 CachedThreadPool 时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

(2)newFixedThreadPool

创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool 是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

(3)newSingleThreadExecutor

创建一个单线程化的 Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

(4)newScheduleThreadPool

创建一个定长的线程池,而且支持定时的以及周期性的任务执行。例如延迟 3 秒执行。

JVM

JVM 内存分哪几个区,每个区的作用是什么?

(1)方法区:

a. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生 GC,在这里进行的 GC 主要是对方法区里的常量池和对类型的卸载

b. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。

c. 该区域是被线程共享的。

d. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

jdk1.7以前除了hotspot,其他JVM厂商早就没有永久区了
jdk1.8之后hotspot也没有永久区了,取而代之的是元空间(MetaSpace)

元空间(MetaSpace)相较于永久区最大的不同,使用的是物理内存,垃圾回收机制在元空间快满的时候才会使用垃圾回收器,类的加载全都是在元空间

随着动态类加载的情况越来越多,这块内存变得不太可控,如果设置小了,系统运行过程中就容易出现内存溢出,设置大了又浪费内存

(2)虚拟机栈:

a. 虚拟机栈也就是我们平常所称的栈内存,它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。

b. 虚拟机栈是线程私有的,它的生命周期与线程相同。

c. 局部变量表里存储的是基本数据类型、returnAddress 类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译期间确定

d. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式

e. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接,动态链接就是将常量池中的符号引用在运行期转化为直接引用。

(3)本地方法栈:

本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务。

(4)堆:

java 堆是所有线程共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。

(5)程序计数器:

内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个 java 虚拟机规范没有规定任何 OOM 情况的区域。

heap(堆) 和 stack(栈) 有什么区别

(1)申请方式

stack:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间

heap:需要程序员自己申请,并指明大小,在 c 中 malloc 函数,对于 Java 需要手动 new Object()的形式开辟

(2)申请后系统的响应

stack:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

heap:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

(3)申请大小的限制

stack:栈是向低地址扩展的数据结构,是一块连续的内存的区域。也就是说栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下,栈的大小是 2M(也有的说是 1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。

heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的, 自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见, 堆获得的空间比较灵活,也比较大。

(4)申请效率的比较

stack:由系统自动分配,速度较快。但程序员是无法控制的。

heap:由 new 分配的内存,一般速度比较慢,容易产生内存碎片,不过用起来最方便。

(5)heap 和 stack 中的存储内容

stack:在函数调用时,第一个进栈的是主函数后的下一条指令(函数调用语句的下一条可执行语句)的地址, 然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

heap:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

java 类加载过程?

(1)加载

a. 通过一个类的全限定名获取该类的二进制流。

b. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。

c. 在内存中生成该类的 Class 对象,作为类的数据访问入口。

(2)验证

验证的目的是为了确保 Class 文件的字节流中的信息不会危害到虚拟机.在该阶段主要完成以下四种

验证:

a. 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.

b. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。

c. 字节码验证:通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。

d. 符号引用验证:确保解析动作能正确执行。最后为类的静态变量分配内存并将其初始化为默认值

(3)解析

主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

(4)初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。

什么是类加载器,类加载器有哪些?

通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器:

(1)启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,无法被 java 程序直接引用。

(2)扩展类加载器(extensions Classloader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

(3)系统类加载器(system Classloader)也叫应用类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

(4)用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现。

java 中垃圾收集的方法有哪些?

1)引用计数法应用于:微软的 COM/ActionScrip3/Python 等

a) 如果对象没有被引用,就会被回收,缺点:需要维护一个引用计算器

2)复制算法应用于:新生代

a) 效率高,缺点:需要内存容量大,比较耗内存

3)标记清除

a) 效率比较低,会产生碎片。

4)标记压缩

a) 效率低速度慢,需要移动对象,但不会产生碎片。

5)标记清除压缩的集合,多次 GC 后才 Compact

a) 适用于占空间大刷新次数少的老年代0,是 标记清除和标记压缩  的集合体

如何判断一个对象是否存活?(或者 GC 对象的判定方法)

判断一个对象是否存活有两种方法:

(1)引用计数法

所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象 A 引用对象 B,对象 B 又引用者对象 A,那么此时 A,B 对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。

(2)可达性算法(引用链法)

该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。

在 java 中可以作为 GC Roots 的对象有以下几种:虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈 JNI引用的对象。

简述 java 内存分配与回收策略以及 Minor GC 和 Major GC(full GC)

内存分配:

(1)栈区:栈分为 java 虚拟机栈和本地方法栈

(2)堆区:堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。堆区是 gc 的主要区域,通常情况下分为两个区块新生代和老年代。

更细一点年轻代又分为 Eden 区,主要放新创建对象,From survivor 和 To survivor 保存 gc 后幸存下的对象,默认情况下各自占比 8:1:1。

(3)方法区:被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被 Java 虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)

(4)程序计数器:当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。线程私有的。

回收策略以及 Minor GC 和 Major GC:

(1)对象优先在堆的 Eden 区分配。

(2)大对象直接进入老年代。

(3)长期存活的对象将直接进入老年代。

当 Eden 区没有足够的空间进行分配时,虚拟机会执行一次 Minor GC.Minor GC 通常发生在新生代的 Eden 区,在这个区的对象生存期短,往往发生 GC的频率较高,回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代 GC 的时候不会触发 Minor GC,但是通过配置在 Full GC之前进行一次 Minor GC 这样可以加快老年代的回收速度。

什么是分布式垃圾回收(DGC)?它是如何工作的?

DGC叫做分布式垃圾回收。

RMI 使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。

类加载器双亲委派模型机制?

当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。

设计模式

你所知道的设计模式有哪些

Java 中一般认为有 23 种设计模式

总体来说设计模式分为三大类:

创建型模式,共 5 种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共 7 种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共 11 种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

工厂设计模式(Factory)

什么是工厂设计模式?

工厂设计模式,就是用来生产对象的,在 java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦

1、对于简单工厂和工厂方法来说,两者的使用方式实际上是一样的,如果对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;

2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产。

代理模式(Proxy)

什么是代理模式?

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了

为什么要用代理模式?

中介隔离作用:

在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

开闭原则,增加功能:

代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。

有哪几种代理模式?

如果按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理。

⚫ 静态代理是由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行之前,代理类.class 文件就已经被创建了。

⚫ 动态代理是在程序运行时通过反射机制动态创建的。

静态代理总结:

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点:我们得为每一个服务创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

JDK 动态代理总结:

优点:相对于静态代理,动态代理大大减少了开发任务,同时减少了对业务接口的依赖,降低了耦合度。

缺点:Proxy 是所有动态生成的代理的共同的父类,因此服务类必须是接口的形式,不能是普通类的形式,因为 Java 无法实现多继承。

CGLib 动态代理(CGLib Proxy)

JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要 CGLib 了。CGLib 采用了底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对 final 修饰的类进行代理。JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础。

CGLib 动态代理和JDK动态代理如何选择?

CGLib 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。

简述动态代理的原理?常用的动态代理的实现方式?

动态代理的原理: 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理过滤。代理对象决定是否以及何时将方法调用转到原始对象上动态代理的方式(换句话说就是过滤之后交由原始对象确定)

基于接口实现动态代理: JDK 动态代理

基于继承实现动态代理: Cglib、Javassist 动态代理

数据库

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:

  1. 限定数据的范围: 禁止不带任何限制数据范围条件的查询语句。

  2. 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;

  3. 垂直分区: 根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
    垂直拆分的优点: 查询时减少数据量和减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
    垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;

  4. 水平分区: 保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。
    水平拆分可以支持非常大的数据量。分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以水平拆分最好分库 ,应用端改造2少,但分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。
    尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
    数据库分片的两种常见方案:
    客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding
    JDBC 、阿里的TDDL是两种比较常用的实现。
    中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat
    、360的Atlas、网易的DDB等等都是这种架构的实现。

关系数据库中连接池的机制是什么?

前提:为数据库连接建立一个缓冲池。

(1)从连接池获取或创建可用连接

(2)使用完毕之后,把连接返回给连接池

(3)在系统关闭前,断开所有连接并释放连接占用的系统资源

(4)能够处理无效连接,限制连接池中的连接总数不低于或者不超过某个限定值。

概念:

最小连接数是连接池一直保持的数据连接。如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费掉。

最大连接数是连接池能申请的最大连接数。如果数据连接请求超过此数,后面的数据连接请求将被加入到等待队列中,这会影响之后的数据库操作。

数据库池连接数量一直保持一个不少于最小连接数的数量,当数量不够时,数据库会创建一些连接,直到一个最大连接数,之后连接数据库就会等待。

SQL 的 select 语句完整的执行顺序

SQL Select 语句完整的执行顺序:

(1)from 子句组装来自不同数据源的数据;

(2)where 子句基于指定的条件对记录行进行筛选;

(3)group by 子句将数据划分为多个分组;

(4)使用聚集函数进行计算;

(5)使用 having 子句筛选分组;

(6)计算所有的表达式;

(7)select 的字段;

(8)使用 order by 对结果集进行排序。

数据库三大范式

第一范式(1NF)是指数据表中的每个字段必须是不可拆分的最小单元,也就是确保每一列的原子性。

第二范式(2NF)是指满足 1NF 后,要求表中的所有列,都必须依赖于主键,而不能有任何一列与主键没有关系,也就是说一个表只描述一件事情。

第三范式(3NF)是指必须先满足第二范式(2NF),另外要求表中的每一列只与主键直接相关而不是间接相关。

数据库五大约束

1.主键约束(Primay Key Coustraint) 唯一性,非空性;

2.唯一约束 (Unique Counstraint)唯一性,可以空,但只能有一个;

3.默认约束 (Default Counstraint) 该数据的默认值;

4.外键约束 (Foreign Key Counstraint) 需要建立两表间的关系;

5.非空约束(Not Null Counstraint):设置非空约束,该字段不能为空。

分页

Mysql: select * from emp limit 0 ,5;

Oracle: select e.* from (select emp.* ,rownum rn from emp) e where rn between 1 and 5;

Mysql与 Oracle 相比,Mysql 有什么优势?

Mysql 是开源软件,随时可用,无需付费。

Mysql 是便携式的带有命令提示符的 GUI。

使用 Mysql 查询浏览器支持管理

存储引擎是什么?Mysql 中使用什么存储引擎?

存储引擎称为表类型,数据使用各种技术存储在文件中。

技术涉及:

Storage mechanism(存储机制) , Locking levels(锁级别) , Indexing(索引) , Capabilities and functions(能力和功能)

MySQL的主从复制

主从复制(也称 AB 复制)允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)

主从复制的有好处

MySQL中主从复制的优点包括:

  • 横向扩展 - 在多个从站之间分配负载以提高性能。在此环境中,所有写入和更新都必须在主服务器上进行。但是,读取可以在一个或多个从设备上进行。该模型可以提高写入性能(因为主设备专用于更新),同时显着提高了越来越多的从设备的读取速度。
  • 数据安全性 - 因为数据被复制到从站,并且从站可以暂停复制过程,所以可以在从站上运行备份服务而不会破坏相应的主数据。
  • 分析 - 可以在主服务器上创建实时数据,而信息分析可以在从服务器上进行,而不会影响主服务器的性能。
  • 远程数据分发 - 可以使用复制为远程站点创建数据的本地副本,而无需永久访问主服务器。

一主一从复制原理

  • 主服务器上面的任何修改都会通过自己的 I/O Tread(I/O 线程)保存在Binary log(二进制日志) 里面。
  • 从服务器上面也启动一个 I/O Thread,通过配置好的用户名和密码, 连接到主服务器上面请求读取二进制日志,然后把读取到的二进制日志写到本地的一个Realy log(中继日志)里面。
  • 从服务器上面同时开启一个 SQL Thread 定时检查 Realy log(这个文件也是二进制的),如果发现有更新立即把更新的内容在本机的数据库上面执行一遍。

一主多从

如果一主多从的话,这时主库既要负责写又要负责为几个从库提供二进制日志。此时可以稍做调整,将二进制日志只给某一从,这一从再开启二进制日志并将自己的二进制日志再发给其它从。或者是干脆这个从不记录只负责将二进制日志转发给其它从,这样架构起来性能可能要好得多,而且数据之间的延时应该也稍微要好一些。

MySQL主从复制一致性

数据同步一致性解决方案:半同步复制
MySQL的Replication默认是一个异步复制的过程,从MySQL5.5开始,MySQL以插件的形式支持半同步复制,我先谈下异步复制,这样可以更好的理解半同步复制。

1)异步复制

MySQL默认的复制是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果挂掉了,此时主上已经提交的事务可能并没有传到从库上。

2)半同步复制

介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。

MySQL主从复制搭建步骤(Docker)

主服务器搭建

镜像拉取,主机MySQL容器创建,修改外部映射文件my.cnf,赋予从机权限

从服务器搭建

启动 从机,每一台从机创建用户赋权,建立从机之间的关系

如果读写同时操作如何保证读写的数据一致性?

一般情况下,读写不会同时使用,如果需要做这样操作的话,那么在数据库中间件(Mycat)他有一种策略去解决这个问题,也就是说他去读从库的时候会先判断一下我们当前环境是否存在主从复制发生,如果有那么他会把这次的请求数据直接交给主库完成,不去读从库,把他重定向交给主库处理,那么这个时候他是能保证数据的一致性的!

oracel 与 mysql 的区别

A).自动增长的数据类型处理

MYSQL 有自动增长的数据类型,插入记录时不用操作此字段,会自动获得数据值。ORACLE 没有自动增长的数据类型,需要建立一个自动增长的序列号,插入记录时要把序列号的下一个值赋于此字段。

B).单引号的处理

MYSQL 里可以用双引号包起字符串,ORACLE 里只可以用单引号包起字符串。在插入和修改字符串前必须做单引号的替换:把所有出现的一个单引号替换成两个单引号。

C).空字符的处理

MYSQL 的非空字段也有空的内容,ORACLE 里定义了非空字段就不容许有空的内容。按 MYSQL 的 NOT NULL 来

定义 ORACLE 表结构, 导数据的时候会产生错误。因此导数据时要对空字符进行判断,如果为 NULL 或空字符,需

要把它改成一个空格的字符串。

D).日期字段的处理

MYSQL 日期字段分 DATE 和 TIME 两种,ORACLE 日期字段只有 DATE,包含年月日时分秒信息用当前数据库的

系统时间为 SYSDATE, 精确到秒

E).并发性

mysql 以表级锁为主,对资源锁定的粒度很大,如果一个 session 对一个表加锁时间过长,会让其他 session 无法

更新此表中的数据。虽然 InnoDB 引擎的表可以用行级锁,但这个行级锁的机制依赖于表的索引,如果表没有索引或者 sql 语句没有使用索引,那么仍然使用表级锁。

oracle 使用行级锁,对资源锁定的粒度要小很多,只是锁定 sql 需要的资源,并且加锁是在数据库中的数据行上不依赖与索引。所以 oracle 对并发性的支持要好很多

F).提交方式

oracle 默认不自动提交,需要用户手动提交。mysql 默认是自动提交。

数据库优化

(1)Sql 语句优化

1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

2.应尽量避免在 where 子句中对字段进行 null 值判断。

3.应尽量避免在 where 子句中使用!=或<>操作符, MySQL 只有对以下操作符才使用索引:<,<=,=,>,>=

5.in 和 not in 也要慎用,否则会导致全表扫描,对于连续的数值,能用 between 就不要用 in

6.应尽量避免在 where 子句中对字段进行表达式操作,应尽量避免在 where 子句中对字段进行函数操作

7.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型

(2)索引优化

1.数据量超过百万级别的表应该有索引;

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

3.经常出现在 Where 子句 order by,group by 中的字段,特别是大表的字段,应该建立索引;

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

索引

(1)什么是索引?

索引是对数据库表中一列或多列的值进行排序的一种结构

(2)索引的优点

①建立索引的列可以保证行的唯一性,生成唯一的 rowId

②建立索引可以有效缩短数据的检索时间

③建立索引可以加快表与表之间的连接

④为用来排序或者是分组的字段添加索引可以加快分组和排序顺序

(3)索引的缺点

①创建索引和维护索引需要时间成本,这个成本随着数据量的增加而加大

②创建索引和维护索引需要空间成本,每一条索引都要占据数据库的物理存储空间,数据量越大,占用空间也越大(数据表占据的是数据库的数据空间)

③会降低表的增删改的效率,因为每次增删改索引需要进行动态维护,导致时间变长

(4)索引什么时候会失效

1.like模糊查询以%开头索引失效
2.不在索引列上做任何操作(计算、函数、类型类型转换),否则会导致索引失效而转向全表扫描
2.字符串不加单引号索引失效
4.or查询每列都要有索引,不然索引失效
5,mysql在使用!= 或者 < > 的时候无法使用索引会导致全表扫描
6,联合索引要遵循最左匹配原则

(5)什么样的列需要建立索引

1.在经常使用在 WHERE 子句中的列上面创建索引,加快条件的判断速度。

2.在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询 时间

3.在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度.

(6)什么样的列不需要建立索引

1、频繁更新的字段不适合创建索引,因为每次更新不单单是更新记录,还会更新索引,保存索引文件;

2、表记录太少,不需要创建索引;

3、经常增删改的表;

4、数据重复且分布平均的字段,因此为经常查询的和经常排序的字段建立索引。注意某些数据包含大量重复数据,因此他建立索引就没有太大的效果,例如性别字段,只有男女,不适合建立索引。

(7)常见索引

Mysql 常见索引有:主键索引、唯一索引、普通索引、全文索引、组合索引

普通索引:最基本的索引,没有任何限制

唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。

主键索引:它 是一种特殊的唯一索引,不允许有空值。

全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时好空间。

组合索引:用多个列组成的索引。

b-tree 和 b+tree 的区别

(1)非叶子节点只存储键值信息

(2)所有叶子节点之间都有一个链指针

(3)数据记录都存放在叶子节点中

存储过程 存储函数

存储过程/函数是什么?

存储过程和存储函数是事先经过编译并存储在数据库中的一段 sql 语句的集合

存储过程与存储函数的区别?

1.存储函数必须有返回值,而存储过程没有返回值。

2.存储函数的参数类型只能是 in ,存储过程的参数可以是 in ,out 类型

(1)优点

• 运行速度:对于复杂的业务逻辑,因为在存储过程创建的时候,数据库已经对其进行了一次解析和优化。存储过程一旦执行,在内存中就会保留一份这个存储过程,这样下次再执行同样的存储过程时,可以从内存中直接调用,所以执行速度会比普通 sql 快。

•减少网络传输:存储过程直接就在数据库服务器上跑,所有的数据访问都在数据库服务器内部进行,不需要传输数据到其它服务器,所以会减少一定的网络传输。但是在存储过程中没有多次数据交互,那么实际上网络传输量和直接 sql 是一样的。而且我们的应用服务器通常与数据库是在同一内网,大数据的访问的瓶颈会是硬盘的速度,而不是网速。

(2)缺点

开发调试复杂,由于 IDE 的问题,存储过程的开发调试要比一般程序困难

  • 没办法应用缓存。虽然有全局临时表之类的方法可以做缓存,但同样加重了数据库的负担。如果缓存并发严

     重,经常要加锁,那效率实在堪忧 
    
  • 不支持群集,数据库服务器无法水平扩展,或者数据库的切割(水平或垂直切割)。数据库切割之后,存储
    过程并不清楚数据存储在哪个数据库中

  • 移植性差

事务的四大特性

原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都提交,要么都回滚。 比如:转账转过去的加和转时候的减必须一次发生

一致性(Consistency)

事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 比如:转账时双方的总数在转账的时候保持一致

隔离性(Isolation)

事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。 比如:多个用户操纵,防止数据干扰,就要为每个客户开启一个自己的事务;

持久性(Durability)

持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

事务的并发问题:

⚫ 脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据

⚫ 不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致

⚫ 幻读:系统管理员 A 将数据库中所有员工等级从具体等级改为 ABCDE 等级,但是系统管理员 B 就在这个时候插入了一条具体等级的记录,当系统管理员 A 改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:

不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件行,解决幻读需要锁表

MySQL 事务隔离级别:

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted) 是 是 是
不可重复读(read-committed) 否 是 是
可重复读(repeatable-read) 否 否 是
串行化(serializable) 否 否 否

事务的传播机制

REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。

SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。

MANDATORY:中文翻译为强制,支持使用当前事务,如果当前事务不存在,则抛出 Exception。

REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。

NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。

NEVER:无事务执行,如果当前有事务则抛出 Exception。

NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟 REQUIRED一样

MyISAM 和 InnoDB 两者之间区别

  1. 存储结构

MyISAM:每个 MyISAM 在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)。

InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB 表的大小只受限于操作系统文件的大小,一般为 2GB。

  1. 事务支持

MyISAM:强调的是性能,每次查询具有原子性,其执行数度比 InnoDB 类型更快,但是不提供事务支持。

InnoDB:提供事务支持事务,外键。 具有事务(commit)、回滚(rollback)功能

  1. 表锁差异

MyISAM:只支持表级锁,用户在操作 myisam 表时,select,update,delete,insert 语句都会给表自动加锁,如果加锁以后的表满足 insert 并发的情况下,可以在表的尾部插入新的数据。

InnoDB:支持事务和行级锁,是 innodb 的最大特色。行锁大幅度提高了多用户并发操作的新能。但是 InnoDB 的行锁,只是在 WHERE 的主键是有效的,非主键的 WHERE 都会锁全表的。

MyISAM 锁的粒度是表级,而 InnoDB 支持行级锁定。简单来说就是, InnoDB 支持数据行锁定,而 MyISAM 不支持行锁定,只支持锁定整个表。即 MyISAM 同一个表上的读锁和写锁是互斥的,MyISAM 并发读写时如果等待队列中既有读请求又有写请求,默认写请求的优先级高,即使读请求先到,所以 MyISAM 不适合于有大量查询和修改并存的情况,那样查询进程会长时间阻塞。因为 MyISAM 是锁表,所以某项读操作比较耗时会使其他写进程饿死。

  1. 全文索引

MyISAM:支持(FULLTEXT 类型的)全文索引

InnoDB:不支持(FULLTEXT 类型的)全文索引

5)表主键

MyISAM:允许没有任何索引和主键的表存在,索引都是保存行的地址。

InnoDB:如果没有设定主键或者非空唯一索引,就会自动生成一个 6 字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。InnoDB 的主键范围更大,最大是 MyISAM 的 2 倍

6)外键

MyISAM:不支持

InnoDB:支持

  1. MyISAM 管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的 SELECT 查询,那么 MyISAM 是更好的选择。

  2. InnoDB 用于事务处理应用程序,具有众多特性,包括 ACID 事务支持。如果应用中需要执行大量的INSERT 或 UPDATE 操作,则应该使用 InnoDB,这样可以提高多用户并发操作的性能。

你们公司有哪些数据库设计规范

(一)基础规范

1、表存储引擎必须使用 InnoD,表字符集默认使用 utf8,必要时候使utf8mb4

解读:

(1)通用,无乱码风险,汉字 3 字节,英文 1 字节

(2)utf8mb4 是 utf8 的超集,有存储 4 字节例如表情符号时,使用它

2、禁止使用存储过程,视图,触发器,Event

解读:

(1)对数据库性能影响较大,互联网业务,能让站点层和服务层干的事情,不要交到数据库层

(2)调试,排错,迁移都比较困难,扩展性较差

3、禁止在数据库中存储大文件,例如照片,可以将大文件存储在对象存储系统,数据库中存储路径

4、禁止在线上环境做数据库压力测试

5、测试,开发,线上数据库环境必须隔离

(二)命名规范

1、库名,表名,列名必须用小写,采用下划线分隔

解读:abc,Abc,ABC 都是给自己埋坑

2、库名,表名,列名必须见名知义,长度不要超过 32 字符

解读:tmp,wushan 谁知道这些库是干嘛的

3、库备份必须以 bak 为前缀,以日期为后缀

4、从库必须以-s 为后缀

5、备库必须以-ss 为后缀

(三)表设计规范

1、单实例表个数必须控制在 2000 个以内

2、单表分表个数必须控制在 1024 个以内

3、表必须有主键,推荐使用 UNSIGNED 整数为主键

潜在坑:删除无主键的表,如果是 row 模式的主从架构,从库会挂住

4、禁止使用外键,如果要保证完整性,应由应用程式实现

解读:外键使得表之间相互耦合,影响 update/delete 等 SQL 性能,有可能造成死锁,高并发情况下容易成为数据库瓶颈

5、建议将大字段,访问频度低的字段拆分到单独的表中存储,分离冷热数据

(四)列设计规范

1、根据业务区分使用 tinyint/int/bigint,分别会占用 1/4/8 字节

2、根据业务区分使用 char/varchar

解读:

(1)字段长度固定,或者长度近似的业务场景,适合使用 char,能够减少碎片,查询性能高

(2)字段长度相差较大,或者更新较少的业务场景,适合使用 varchar,能够减少空间

3、根据业务区分使用 datetime/timestamp

解读:前者占用 5 个字节,后者占用 4 个字节,存储年使用 YEAR,存储日期使用 DATE,存储时间使用 datetime

4、必须把字段定义为 NOT NULL 并设默认值

解读:

(1)NULL 的列使用索引,索引统计,值都更加复杂,MySQL 更难优化

(2)NULL 需要更多的存储空间

(3)NULL 只能采用 IS NULL 或者 IS NOT NULL,而在=/!=/in/not in 时有大坑

5、使用 INT UNSIGNED 存储 IPv4,不要用 char(15)

6、使用 varchar(20)存储手机号,不要使用整数

解读:

(1)牵扯到国家代号,可能出现+/-/()等字符,例如+86

(2)手机号不会用来做数学运算

(3)varchar 可以模糊查询,例如 like ‘138%’

7、使用 TINYINT 来代替 ENUM

解读:ENUM 增加新值要进行 DDL 操作

(五)索引规范

1、唯一索引使用 uniq_[字段名]来命名

2、非唯一索引使用 idx_[字段名]来命名

3、单张表索引数量建议控制在 5 个以内

解读:

(1)互联网高并发业务,太多索引会影响写性能

(2)生成执行计划时,如果索引太多,会降低性能,并可能导致 MySQL 选择不到最优索引

(3)异常复杂的查询需求,可以选择 ES 等更为适合的方式存储

4、组合索引字段数不建议超过 5 个

解读:如果 5 个字段还不能极大缩小 row 范围,八成是设计有问题

5、不建议在频繁更新的字段上建立索引

6、非必要不要进行 JOIN 查询,如果要进行 JOIN 查询,被 JOIN 的字段必须类型相同,并建立索引

解读:踩过因为 JOIN 字段类型不一致,而导致全表扫描的坑么?

7、理解组合索引最左前缀原则,避免重复建设索引,如果建立了(a,b,c),相当于建立了(a), (a,b), (a,b,c)

(六)SQL 规范

1、禁止使用 select *,只获取必要字段

解读:

(1)select *会增加 cpu/io/内存/带宽的消耗

(2)指定字段能有效利用索引覆盖

(3)指定字段查询,在表结构变更时,能保证对应用程序无影响

2、insert 必须指定字段,禁止使用 insert into T values()

解读:指定字段插入,在表结构变更时,能保证对应用程序无影响

3、隐式类型转换会使索引失效,导致全表扫描

4、禁止在 where 条件列使用函数或者表达式

解读:导致不能命中索引,全表扫描

5、禁止负向查询以及%开头的模糊查询

解读:导致不能命中索引,全表扫描

6、禁止大表 JOIN 和子查询

7、同一个字段上的 OR 必须改写问 IN,IN 的值必须少于 50 个

8、应用程序必须捕获 SQL 异常

解读:方便定位线上问题

说明:本规范适用于并发量大,数据量大的典型互联网业务,可直接参考。

JAVA WEB

HTTP协议

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

Http 的长连接和短连接

HTTP 协议有 HTTP/1.0 版本和 HTTP/1.1 版本。HTTP1.1 默认保持长连接(HTTP persistent connection,也翻译为持久连接),数据传输完成了保持 TCP 连接不断开(不发 RST 包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。

在 HTTP/1.0 中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。从 HTTP/1.1 起,默认使用的是长连接,用以保持连接特性。

http 常见的状态码有哪些?

200 OK 客户端请求成功

301 Moved Permanently (永久移除),请求的 URL 已移走。Response 中应该包含一个 Location URL, 说明资源现在所处的位置

302found 重定向

400 Bad Request 客户端请求有语法错误,不能被服务器所理解

401 Unauthorized 请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用

403 Forbidden 服务器收到请求,但是拒绝提供服务

404 Not Found 请求资源不存在,eg:输入了错误的 URL

500 Internal Server Error 服务器发生不可预期的错误

503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常

GET 和 POST 的区别?

(1)GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据,参数之间以&相连,如:

login.action?name=zhagnsan&password=123456。

POST 把提交的数据则放置在是 HTTP 方法体中.

(2)GET 方式提交的数据最多只能是 1024 字节,理论上 POST 没有限制,可传较大量的数据。其实这样说是错误的,不准确的:“GET 方式提交的数据最多只能是 1024 字节",因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟 URL 的长度有直接关系了。而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对 URL 长度的限制是 2083 字节(2K+35)。对于其他浏览器,如 Netscape、FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。

(3)POST 的安全性要比 GET 的安全性高。这里安全的含义是真正的 Security 的含义,比如:通过 GET 提交数据,用户名和密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。

Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,在 FORM(表单)中,Method默认为"GET",实质上,GET 和 POST 只是发送机制不同,并不是一个取一个发!

在单点登录中,如果 cookie 被禁用了怎么办?

单点登录的原理是后端生成一个 session ID,然后设置到 cookie,后面的所有请求浏览器都会带上 cookie, 然后服务端从 cookie 里获取 session ID,再查询到用户信息。所以,保持登录的关键不是 cookie,而是通过 cookie 保存和传输的 session ID,其本质是能获取用户信息的数据。除了 cookie,还通常使用 HTTP 请求头来传输。但是这个请求头浏览器不会像 cookie 一样自动携带,需要手工处理。

servlet 是线程安全的吗?为什么?

Servlet 对象并不是一个线程安全的对象。

Servlet 第一次被调用的时候,init()方法会被调用,然后调用 service() 方法,从第二次被请求开始,就直接调用 service()方法。

因为 servlet 是单实例的,所以后面再次请求同一个 Servlet 的时候都不会创建 Servlet 实例,而且 web 容器会针对每个请求创建一个独立的线程,这样多个并发请求会导致多个线程同时调用 service() 方法,这样就会存在线程不安全的问题。

如何解决 Servlet 线程不安全的问题?

(1)不要在 servlet 中使用成员变量。

(2)可以给 servlet 中的方法添加同步锁,Synchronized,但是不提倡,数据并发访问会造成阻塞等待。

(3)可以实现 SingleThreadModel 接口,如下。这样可以避免使用成员变量的问题,但是也不提倡,原因同上。

Public class Servlet1 extends HttpServlet implements SingleThreadModel{

………

}

谈谈过滤器的作用

java过滤器能够对目标资源的请求和响应进行截取.

谈谈拦截器的作用

拦截器是JavaWeb开发中必须用的技术,可以对整个系统字符集编码、URL访问权限过滤、过滤敏感词信息、session用户是否存在日志记录等等,拦截器只对controller请求起作用

拦截器和过滤器有什么区别

拦截器是基于 java 的反射机制的,而过滤器是基于函数回调。

拦截器不依赖 servlet 容器,过滤器依赖与 servlet 容器。

拦截器只能对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用。

拦截器可以访问 action 上下文、值栈里的对象,而过滤器不能访问。

在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次

拦截器和过滤器的执行顺序

过滤前 – 拦截前 – Action 处理 – 拦截后 – 过滤后。

过滤是一个横向的过程,首先把客户端提交的内容进行过滤(例如未登录用户不能访问内部页面的处理);过滤通过后,拦截器将对用户提交数据进行检查,做一些前期的数据处理,接着把处理后的数据发给对应的 Action;Action 处理完成返回后,在此次期间拦截器还可以做其他操作,再向上返回到过滤器的后续操作

Servlet 是什么

Servlet(Server Applet)是 Java Servlet 的简称,他是用 Java 编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态 Web 内容

Serlvet 工作原理

首先客户发送个请求,Servlet 容器会创建特定于这个请求的 ServletRequest 对象和 ServletResponse 对象然后调用 Servlet 的 service()方法。Service()方法从ServletRequest 对象获得客户请求信息,处理该请求,然后通过 ServletResponse 对象向客户返回响应信息。

Servlet 生命周期

1,初始化阶段

调用 init()方法

Servlet 容器创建一个 Servlet 实例并且调用 Servlet 的 init()方法进行初始化。在 Servlet 的整个生命周期内init() 方法只被调用一次。

2,响应客户请求阶段 调用 service()方法

对于用户到达 Servlet 的请求,Servlet 容器会创建特定于这个请求的 ServletRequest 对象和 ServletResponse 对象,然后调用 Servlet 的 service 方法。service 方法从 ServletRequest 对象获得客户请求信息,处理该请求,并通过 ServletResponse 对象向客户返回响应信息。

3,终止阶段 调用 destroy()方法

当 WEB 应用被终止,或 Servlet 容器终止运行,或 Servlet 容器重新装载 Servlet 新实例时,Servlet 容器会先调用 Servlet 的 destroy()方法,在 destroy()方法中可以释放掉 Servlet 所占用的资源

Servlet 什么时候创建

1,默认情况下,当 WEB 客户第一次请求访问某个 Servlet 的时候,WEB 容器将创建这个 Servlet 的实例。

2,当 web.xml 文件中如果元素中指定了子元素时,Servlet 容器在启动 web 服务器时,将按照顺序创建并初始化 Servlet 对象。

servlet 与 jsp 的区别和联系

1.jsp 经编译后就变成了 Servlet.

2.JSP 的本质就是 Servlet,JVM 只能识别 java 的类,不能识别 JSP 的代码,

3.Web 容器将 Jsp 的代码编译成 JVM 能够识别 java 类

4.jsp 更擅长表现于页面显示,servlet 更擅长于逻辑控制.

5.JSP 侧重于视图,Servlet 主要用于控制逻辑

Session 与 cookie 的区别

一、对于 cookie:

①cookie 是创建于服务器端,存在浏览器端

②cookie 的生命周期可以通过 cookie.setMaxAge(2000)来设置,如果没有设置 setMaxAge, 则 cookie 的生命周期当浏览器关闭的时候,就消亡了

  • 存在的位置:

cookie 存在于客户端,临时文件夹中

session:存在于服务器的内存中,一个 session 域对象为一个用户浏览器服务

  • 安全性

cookie 是以明文的方式存放在客户端的,安全性低,可以通过一个加密算法进行加密后存放session 存放于服务器的内存中,所以安全性好

  • 网络传输量

cookie 会传递消息给服务器,session 本身存放于服务器,不会有传送流量

  • 生命周期(以 30 分钟为例)

(1)cookie 的生命周期是累计的,从创建时,就开始计时,30 分钟后,cookie 生命周期结束,

(2)session 的生命周期是间隔的,从创建时,开始计时如在 30 分钟,没有访问 session,那么 session 生命周期被销毁,但是,如果在 30 分钟内(如在第 29 分钟时)访问过 session,那么将重新计算 session 的生命周期

(3) 关机会造成 session 生命周期的结束,但是对 cookie 没有影响

转发于重定向的区别

1.转发只访问服务器一次。重定向访问服务器两次

2.转发地址栏没有变化;重定向地址栏有变化

3.转发在服务器端完成的;重定向是在客户端完成的

4.转发不会执行转发后的代码;重定向会执行重定向之后的代码

5 转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成

jQuery 选择器

1.基本选择器

基本选择器是 JQuery 最常用的选择器,也是最简单的选择器,它通过元素 id、class 和标签名来查找 DOM 元素

2.层级选择器

如果想通过 DOM 元素之间的层次关系来获取特定元素, 例如后代元素, 子元素, 相邻元素, 兄弟元素等, 则需要使用层次选择器。

3.过滤选择器

按照不同的过滤规则, 过滤选择器可以分为基本过滤, 内容过滤, 可见性过滤, 属性过滤, 子元素过滤和表单对象属性过滤选择器。

Ajax

AJAX 全称为“Asynchronous JavaScript and XML”(异步 JavaScript 和 XML),是一种创建交互式网页应用的网页开发技术

JDBC

1.Jdbc 是什么

(1)JDBC(Java DataBase Connectivity,java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,使数据库开发人员能够编写数据库应用程序

2.jdbc 连接数据库步骤

1,使用 jdbc 编程需要连接数据库,注册驱动和数据库信息

2,操作 Connection,打开 Statement 对象

3,通过 Statement 对象执行 SQL,返回结果到 ResultSet 对象

4,使用 ResultSet 读取数据,然后通过代码转化为具体的实体对象

5,关闭数据库相关的资源

3.Jdbc 缺点

工作量比较大,需要连接,然后处理 jdbc 底层事务,处理数据类型,还需要操作 Connection,Statement 对象和 ResultSet 对象去拿数据并关闭他们

4.Jdbc 优点

执行效率要比 hibernate mybatis 要快,hibernate ,mybatis 框架都是对 jdbc 的封装

框架

简述 Spring 中 IOC 容器常用的接口和具体的实现类

BeanFactory: SpringIOC 容器的基本设置,是最底层的实现,面向框架本身的.

ApplicationContext: BeanFactory 的子接口, 提供了更多高级的功能, 面向开发者的.

ConfigurableApplicationContext: ApplicationContext 的子接口,扩展出了 close 和 refresh 等关闭刷新容器的方法

ClassPathXmlApplicationContext:从 classpath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中获取。

FileSystemXmlApplicationContext :由文件系统中的 XML 配置文件读取上下文。

XmlWebApplicationContext:由 Web 应用的 XML 文件读取上下文。

Spring MVC的优点:

1.基于组件技术, 全部的应用对象,无论控制器和视图,还是业务对象之类的都是java组件,并且和Spring其他基础结构紧密集成.

2.不依赖于Servlet API(目标虽是如此,但是在实现的时候确实是依赖于Servlet的)

3.可以任意使用各种视图技术,而不仅仅局限于JSP

4.支持各种请求资源的映射策略

5.易于扩展

简述 Spring 中如何基于注解配置 Bean 和装配 Bean

(1)首先要在 Spring 中配置开启注解扫描

(2)在具体的类上加上具体的注解

(3)Spring 中通常使用@Autowired 或者是@Resource 等注解进行 bean 的装配

Spring中 @Autowired注解与@Resource注解的区别

相同点:

@Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。

不同点:

(1)提供方:

  • @Autowired是由org.springframework.beans.factory.annotation.Autowired提供,换句话说就是由Spring提供;
  • @Resource是由javax.annotation.Resource提供,即J2EE提供,需要JDK1.6及以上。

(2)注入方式:

  • @Autowired只按照byType 注入;
  • @Resource默认按byName自动注入,也提供按照byType 注入;

(3)属性:

  • @Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。
  • @Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。

请解释 Spring Bean(IOC) 的生命周期?

(1)默认情况下,IOC容器中bean的生命周期分为五个阶段:

首先调用构造器或者是通过工厂的方式创建 Bean 对象,然后给 bean 对象的属性注入值,再调用初始化方法(init-method)进行初始化,那么此时就可以使用Bean,IOC 容器关闭时销毁 Bean 对象.

(2)当加入了 Bean 的后置处理器后,IOC 容器中 bean 的生命周期分为七个阶段:

首先他会通过构造器或工厂方法创建Bean实例,然后为Bean的属性设置值和对其他Bean的引用,再将Bean实例传递给Bean后置处理器的postProcessBeforelnitialization方法,去调用Bean的初始化方法(init-method),将Bean实例传递给Bean后置处理器的postProcessAfterlnitialization方法,那么这个时候就可以使用Bean了,当容器关闭时,调用Bean的销毁方法(destroy-method)

简述 SpringMvc 里面拦截器是如何定义,如何配置,拦截器中三个重要的方法

定义:有两种方式

实现 HandlerInterceptor 接口 继承 HandlerInterceptorAdapter

拦截器中三个重要的方法:

preHandle postHandle afterCompletion

Spring 框架中都用到了哪些设计模式?

Spring框架中使用到了大量的设计模式,下 面列举了比较有代表性的:

  • 代理模式- 在AOP和remoting中被用的比较多。
  • 单例模式- 在spring配置文件中定义的bean默认为单例模式。
  • 模板方法- 用来解决代码重复的问题。比如:RestTemplate,JmsTemplate,JpaTemplate。
  • 前端控制器- Spring提供了DispatcherSerlet来对请求进行分发。
  • 视图帮助(ViewHelper)- Spring提供了一系列的JSP标签,高效得来辅助将分散的代码整合在视图里。
  • 依赖注入- 贯穿于BeanFactory/ApplicationContext接口的核心理念。
  • 工厂模式- BeanFactory用来创建对象的实例

开发中主要使用Spring的什么技术?

  1. I0C容器管理各层组件
  2. 使用AOP配置声明式事务
  3. 整合其他框架.

Mybatis 工作原理

1.加载 mybatis 全局配置文件(数据源、mapper 映射文件等),解析配置文件,MyBatis 基于 XML 配置文件生成Configuration,和一个个 MappedStatement(包括了参数映射配置、动态 SQL 语句、结果映射配置),其对应着