面向对象的三大特征
- 封装
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或对象操作,对不可信的信息进行隐藏。 - 继承
对象的继承关系代表了一种‘is a’的关系,比如A和B,可以描述为‘B是A’,表明B继承A。 - 多态
接口的多种不同的实现方式即为多态。
面向对象和面向过程的区别
面向过程:
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展
面向对象:
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
- 缺点:性能比面向过程低
Java代码的运行
源代码->编译器将源代码编译为字节码->JVM(Java虚拟机) 解释器将字节码解释为可执行的二进制机器码->程序运行
JDK与JRE
JDK是Sun Microsystems针对Java开发人员发布的免费软件开发工具包(SDK,Software development kit)。除JRE(Java Runtime Environment),也就是Java运行环境外还包含提供给开发者使用的javac(编译器)、jar(打包器)、javadoc(文档生成器)等工具包。
重载(overloading)与重写(overriding)
- 重载
1.是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
2.每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
3.最常用的地方就是构造器的重载。 - 重写
1.重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。
2.重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或对象操作,对不可信的信息进行隐藏。
equals与==的区别
1.对于字符串变量来说,使用“==”和“equals()”方法比较字符串时,其比较方法不同。“==”比较两个变量本身的值,即两个对象在内存中的首地址。“equals()”比较字符串中所包含的内容是否相同。
2.对于非字符串变量来说,"=="和"equals"方法的作用是相同的都是用来比较其对象在堆内存的首地址,即用来比较两个引用变量是否指向同一个对象。
String、StringBuff、StringBuild的区别
- String类保存字符串的方式为:private final char value[],所以string对象不可变。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中保存字符串的方式为char[] value,所以StringBuilder对象与StringBuilder对象可变。
- String是常量,线程安全。StringBuff,对调用方法加了同步锁(synchronized)因此线程安全,StringBuild,未加锁因此线程不安全。
- String改变会生成新的String对象,相对效率较低,StringBuff与StringBuild改变不会产生新的对象,并且StringBuild未加锁,因此效率最高,但是可能存在线程安全的问题。操作少量数据时,使用String对象,操作大量数据,单线程时使用StringBuild,多线程时使用StringBuff。
类成员访问修饰符
访问修饰符 | 同一个类 | 同包 | 不同包,子类 | 不同包,非子类 |
---|---|---|---|---|
private | √ | |||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
默认 | √ | √ |
”static”关键字是什么意思?Java中是否可以覆盖(override)一个static方法?
“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
Java语言支持的8中基本数据类型对应的长度、对应的包装类
基本数据类型 | 长度(字节) | 包装类 | 默认值 |
---|---|---|---|
boolean | 1 | Boolean | false |
byte | 1 | Byte | (byte)0 |
char | 2 | Character | '/uoooo'(null) |
short | 2 | Short | (short)0 |
int | 4 | Integer | 0 |
long | 8 | Long | 0L |
float | 4 | Float | 0.0f |
double | 8 | Double | 0.0d |
接口和抽象类的区别
- 类可以实现多个接口,但只能继承一个抽象类
- 接口中所有的方法都是抽象的。而抽象类则可以包含非抽象的方法。
- 都不可被实例化,但抽象类如果包含main方法是可以被调用的。
final、finally、finalize
- final: 常量声明。final类无法继承,final方法无法重写,final值无法改变。
- finally: 处理异常。 不管有无异常都会执行的块,关闭连接通常在其中完成。
- finalize: 帮助进行垃圾回收。finalize()方法在一个对象被销毁和回收前会被调用。
native方法是什么?
native方法是非Java代码实现的方法。
如何原地交换两个变量的值?
- 加减法
int a = 5,b = 10;
a = a + b;
b = a - b;
a = a - b;
同理可用乘除法。
注意类型范围,防止溢出。
-
异或法
int a = 5,b = 10;
a = a ^ b;//1111 = 101 ^ 1010;
b = b ^ a;
a = a ^ b;
用最有效率的方法计算 2 乘以 8
2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。
集合框架中的泛型有什么优点?
Java1.5 引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型。因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现 ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和 instanceOf 操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。
Java 集合框架的基础接口有哪些?
- Collection 为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java 平台不提供这个接口任何直接的实现。
- Set 是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。
- List 是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List 更像长度动态变换的数组。
- Map 是一个将 key 映射到 value 的对象。一个 Map 不能包含重复的 key,每个 key 最多只能映射一个 value。尽管 Map 接口和它的实现也是集合框架的一部分,但 Map 不是集合,集合也不是 Map。因此,Map 继承 Collection 毫无意义,反之亦然。如果 Map 继承 Collection 接口,那么元素去哪儿?Map 包含key-value 对,它提供抽取 key 或 value 列表集合的方法,但是它不适合“一组对象”规范。
-
一些其它的接口有 Queue、Dequeue、SortedSet、SortedMap 和 ListIterator。
Iterator 和 ListIterator 的区别是什么?
- Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List。
- Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。
- ListIterator 实现了 Iterator 接口,并包含其他的功能。比如:增加元素,替换元素,获取前一个和后一个元素的索引等等。
Java 中的 HashMap 的工作原理是什么?
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
HashMap 和 HashTable 有什么区别?
- HashMap 是非线程安全的,HashTable 是线程安全的。
- HashMap 的键和值都允许有 null 值存在,而 HashTable 则不行。
- 因为线程安全的问题,HashMap 效率比 HashTable 的要高。
- HashTable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线程环境,而 HashTable 适合于多线程环境。
- 一般现在不建议用 HashTable,一是 HashTable 是遗留类,内部实现很多没优化和冗余。二是即使在多线程环境下,现在也有同步的 ConcurrentHashMap 替代,没有必要因为是多线程而用 HashTable。
ConcurrentHashMap的并发度是什么?
ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,ConcurrentHashMap的并发度就是segment的大小,默认值为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这样在多线程情况下就能避免争用。
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
- 快速失败:当你在迭代一个集合的时候,如果有另一个线程正在修改你正在访问的那个集合时,就会抛出一个 ConcurrentModification 异常。 在 java.util 包下的都是快速失败。
- 安全失败:你在迭代的时候会去底层集合做一个拷贝,所以你在修改上层集合的时候是不会受影响的,不会抛出 ConcurrentModification 异常。在java.util.concurrent 包下的全是安全失败的。
值传递与引用传递
- 值传递
对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。我们可以来看下面的一个例子:
public class Break {
public static void change(int a) {
a = 1;
}
public static void main(String[] args) {
int a = 2;
System.out.println(a);
change(a);
System.out.println(a);
}
}
输出结果是: 2 2
这个只是传递一份拷贝,和a的值没有什么关系,也可以看成是方法change的值没有一个变量来接收。
2.引用传递
对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。
public class Break {
public static void change(int[] a) {
a[0] = 3;
}
public static void main(String[] args) {
int[] a = {1, 2};
System.out.println(a[0]);
change(a);
System.out.println(a[0]);
}
}
输出结果是: 1 3
这个传递的,就是实际传递的是引用的地址值。所以a[0]的值会改变。
什么是线程?线程和进程区别在哪?
- 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
- 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。每个线程都拥有单独的栈内存用来存储本地数据。
Java中用到的线程调度算法是什么?
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
如何实现多线程?
java.lang.Thread 类的实例就是一个线程,但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口,所以你可以继承java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。
用Thread还是用Runnable?
大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。
Thread 类中的start() 和 run() 方法有什么区别?
这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
Runnable和Callable有什么不同?
使用ExecutorService、Callable、Future可以实现有返回结果的多线程。
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。
常用线程池
- newCachedThreadPool创建一个可缓存线程池程
- newFixedThreadPool 创建一个定长线程池
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
-
newSingleThreadExecutor 创建一个单线程化的线程池
sleep和wait的区别
- sleep是Thread线程类的方法,而wait是Object顶级类的方法。
- sleep可以在任何地方使用,而wait只能在同步方法或者同步块中使用。
- sleep,wait调用后都会暂停当前线程并让出cpu的执行时间,但不同的是sleep不会释放当前持有的对象的锁资源,到时间后会继续执行,而wait会放弃所有锁并需要notify/notifyAll后重新获取到对象锁资源后才能继续执行。
- sleep需要捕获或者抛出异常,而wait/notify/notifyAll不需要。
如何强制启动一个线程?
这个问题就像是如何强制进行Java垃圾回收,目前还没有可靠方法,虽然你可以使用System.gc()来进行垃圾回收,但是不保证能成功。在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。
volatile关键字的作用是什么?
- 多线程使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据。
- Java代码执行中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率。
什么是乐观锁和悲观锁?
- 乐观锁:对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-设置这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
- 悲观锁:对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,直接对操作资源上了锁。
共享锁和排它锁
- 共享锁【S锁,MyISAM 叫做读锁】
又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
select * from table lock in share mode
- 排他锁【X锁,MyISAM 叫做写锁】
数据库的增删改操作默认都会加排他锁,而查询不会加任何锁。
又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
select * from table for update
- 计划锁
- 意向锁
- 意向共享锁
- 意向排它锁
可重入锁、可中断锁、公平锁、读写锁
- 可重入锁实际是指锁的分配机制:基于线程的分配,而不是基于方法调用的分配。synchronized和Lock都具备可重入性。
- 可中断锁,顾名思义,就是可以相应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
- 公平锁即尽量以请求锁的顺序来获取锁。在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
- 读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。
表锁、页锁、行锁
在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。
synchronized与lock的区别
public interface Lock {
void lock(); //用来获取锁。如果锁已被其他线程获取,则进行等待。
void lockInterruptibly() throws InterruptedException;//当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
boolean tryLock();//方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
void unlock();//解锁
Condition newCondition();
}
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
在静态方法和非静态方法上加 Synchronized的区别
- Synchronzied 修饰非静态方法==》对象锁。
- Synchronzied 修饰静态方法==》其实是类锁,因为是静态方法,它把整个类锁起来了;
jdk1.8新特性
- Lambda表达式
- 函数式接口
- 方法引用和构造器调用
- Stream API
- 接口中的默认方法和静态方法
- 新时间日期API
常见算法
- 二分查找
public static int binSearch(int srcArray[], int key) {
int mid = srcArray.length / 2;
if (key == srcArray[mid]) {
return mid;
}
int start = 0;
int end = srcArray.length - 1;
while (start <= end) {
mid = (end - start) / 2 + start;
if (key < srcArray[mid]) {
end = mid - 1;
} else if (key > srcArray[mid]) {
start = mid + 1;
} else {
return mid;
}
}
return -1;
}
- 冒泡排序
public static void bubbleSort(int srcArray[]) {
for (int i = 0; i < srcArray.length - 1; i++) {//外层循环控制排序趟数
for (int j = 0; j < srcArray.length - 1 - i; j++) {//内层循环控制每一趟排序多少次
if (srcArray[j] > srcArray[j + 1]) {
int temp = srcArray[j];
srcArray[j] = srcArray[j + 1];
srcArray[j + 1] = temp;
}
}
}
}
- 快速排序
public static void quickSort(int[] arr, int low, int high) {
int i, j, temp, t;
if (low > high) {
return;
}
i = low;
j = high;
//temp就是基准位
temp = arr[low];
while (i < j) {
//先看右边,依次往左递减
while (temp <= arr[j] && i < j) {
j--;
}
//再看左边,依次往右递增
while (temp >= arr[i] && i < j) {
i++;
}
//如果满足条件则交换
if (i < j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j - 1);
//递归调用右半数组
quickSort(arr, j + 1, high);
}
常见数据结构
数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式)。
- 线性表
- 数组
采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)。 - 链表
对于链表的新增,删除等操作(在找到指定操作位置后),仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历链表逐一进行比对,复杂度为O(n)。
- 数组
- 栈与队列
- 树与二叉树
- 树
- 二叉树基本概念
- 二叉查找树
- 平衡二叉树
- 红黑树
对一棵相对平衡的有序二叉树,对其进行插入,查找,删除等操作,平均复杂度均为O(logn)。
- Hash表
在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1)。
哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法。
常用设计模式
- 单例模式
单例设计模式简单说就是无论程序如何运行,采用单例设计模式的类(Singleton类)永远只会有一个实例化对象产生。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
- 工厂模式
程序在接口和子类之间加入了一个过渡端,通过此过渡端可以动态取得实现了共同接口的子类实例化对象。 - 代理模式
指由一个代理主题来操作真实主题,真实主题执行具体的业务操作,而代理主题负责其他相关业务的处理。比如生活中的通过代理访问网络,客户通过网络代理连接网络(具体业务),由代理服务器完成用户权限和访问限制等与上网相关的其他操作(相关业务)。
对称加密与非对称加密
- 对称加密:
对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。
常用对称加密算法:AES、IDEA - 非对称加密:
非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。
常用非对称加密算法:RSA、ElGamal
何谓RESTful?
RESTful(Representational State Transfer)架构风格,是一个Web自身的架构风格,底层主要基于HTTP协议(ps:提出者就是HTTP协议的作者),是分布式应用架构的伟大实践理论。RESTful架构是无状态的,表现为请求-响应的形式,有别于基于Bower的SessionId不同。
何谓MVC?
- MVC是Model—View—Controler的简称,即模型—视图—控制器。
- 模型:处理。
- 视图:展示。
- 控制器:接受。
- 流程:控制器接受用户发来的请求,调用相应模型处理逻辑,然后返回数据给控制器,控制器调用相应视图展示结果给用户。
SpringMVC工作流程
- 客户端请求提交到DispatcherServlet
- 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller
- DispatcherServlet将请求提交到Controller
- Controller调用业务逻辑处理后,返回ModelAndView
- DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
- 视图负责将结果显示到客户端
strus2与Spring MVC的区别
- Struts2是类级别上的拦截,一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文。而且Struts过滤后是去Struts配置文件中找Action,而SpringMVC过滤后是去controller中找对应于@RequestMapping注解的url绑定的方法。
- 因为拦截器原因,导致Struts2的action比较乱,因为它要定义属性来获取请求中参数的数据,而属性在一个类的方法间是共享的(方法间不能独享request、response数据),所以会有点乱。而SpringMVC中请求参数与controller中方法的形参自动配对(在名字相同,或请求参数与形参的属性名相同,或通过@RequestParam注解指定条件下会自动将请求参数的值赋给形参)方法间可以独享request、response数据。
- SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。
SpringMVC常用参数绑定注解
- @RequestParam
- @RequestBody
- @RequestHeader
- @CookieValue
- @PathVariable
SpringMVC怎样设定重定向和转发的?
- 在返回值前面加"forward:"就可以让结果转发,譬如"forward:user.do?name=jianshu"
- 在返回值前面加"redirect:"就可以让返回值重定向,譬如"redirect:http://www.baidu.com"
SpringIOC容器
Spring IOC负责创建对象、管理对象(通过依赖注入)、整合对象、配置对象以及管理这些对象的生命周期,在Spring中BeanFactory是IOC容器的实际代表者。
BeanFactory 接口和 ApplicationContext 接口有什么区别 ?
- ApplicationContext 接口继承BeanFactory接口,Spring核心工厂是BeanFactory ,BeanFactory采取延迟加载,第一次getBean时才会初始化Bean, ApplicationContext是会在加载配置文件时初始化Bean。
- ApplicationContext是对BeanFactory扩展,它可以进行国际化处理、事件传递和bean自动装配以及各种不同应用层的Context实现。
开发中基本都在使用ApplicationContext, web项目使用WebApplicationContext ,很少用到BeanFactory。
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
IHelloService helloService = (IHelloService) beanFactory.getBean("helloService");
helloService.sayHello();
IOC、DI
- IOC(Inversion of Control):由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
- DI(Dependency Injection):被注入对象依赖IOC容器配置依赖对象。
依赖注入的几种方式
- set注入
控制层代码:
private OrderServiceImp orderService;
public void setOrderService(OrderServiceImp orderService) {
this.orderService = orderService;
}
Spring配置XML文件:其中配置声明OrderAction类存在属性orderService。程序运行时候,会将已经实例化的orderService对象调用setOrderService方式注入。
- 构造器注入
控制层代码:
private OrderServiceImp orderService;
public OrderAction(OrderServiceImp orderService) {
this.orderService = orderService;
}
Spring配置XML文件:
- 注解注入
Spring中bean实例化有几种方式?
- 使用类构造器实例化(默认无参数)
- 静态工厂
- 实例工厂
简单说下Bean的生命周期
- bean定义
- bean初始化
有两种方式初始化:- 在配置文件中通过指定init-method属性来完成
- 实现org.springframwork.beans.factory.InitializingBean接口
- bean调用
三种方式获得bean实例(见上题) - bean销毁
有两种方式销毁:- 使用配置文件指定的destroy-method属性
- 实现org.springframwork.bean.factory.DisposeableBean接口
**注意:
在配置元素,通过 init-method 指定Bean的初始化方法,通过 destroy-method 指定Bean销毁方法
- destroy-method 只对 scope="singleton" 有效
- 销毁方法,必须关闭ApplicationContext对象(手动调用),才会被调用
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
applicationContext.close();**
Bean的作用域
- singleton
当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。 - prototype
Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean() 方法)时都会创建一个新的bean实例。根据经验,对所有有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用 singleton作用域。 - request
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。 - session
在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。 - global session
在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。
AOP的理解
- 面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足,除了类(classes)以外,AOP提供了切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。
-
Spring的一个关键的组件就是AOP框架,可以自由选择是否使用AOP 提供声明式企业服务,特别是为了替代EJB声明式服务。最重要的服务是声明性事务管理,这个服务建立在Spring的抽象事物管理之上。允许用户实现自定义切面,用AOP来完善OOP的使用,可以把Spring AOP看作是对Spring的一种增强。
Spring里面的applicationContext.xml文件能不能改成其他名字?
ContextLoaderListener是一个ServletContextListener, 它在你的web应用启动的时候初始化。缺省情况下, 它会在WEB-INF/applicationContext.xml文件找Spring的配置。 你可以通过定义一个
Spring如何处理线程并发问题?
Spring使用ThreadLocal解决线程安全问题
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
如何解决GET、POST请求中文乱码问题?
GET请求中文乱码问题解决
- 重新编码参数
String name= new String(request.getParamter("name").getBytes("ISO8859-1"),"utf-8")
- 修改tomcat中server.xml的配置
POST请求中文乱码问题解决
- web.xml中配置字符编码过滤器
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
utf-8
CharacterEncodingFilter
/*
过滤器、监听器、拦截器
过滤器
所谓过滤器顾名思义是用来过滤的,Java的过滤器能够为我们提供系统级别的过滤,也就是说,能过滤所有的web请求,这一点,是拦截器无法做到的。在Java Web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或
者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者struts的action前统一设置字符集,或者去除掉一些非法字符(聊天室经常用到的,一些骂人的话)。filter 流程是线性的,url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收。
监听器
Java的监听器,也是系统级别的监听。监听器随web应用的启动而启动。Java的监听器在c/s模式里面经常用到,它会对特定的事件产生产生一个处理。监听在很多模式下用到,比如说观察者模式,就是一个使用监听器来实现的,在比如统计网站的在线人数。又比如struts2可以用监听来启动。Servlet监听器用于监听一些重要事件的发生,监听器对象可以在事情发生前、发生后可以做一些必要的处理。
拦截器
java里的拦截器提供的是非系统级别的拦截,也就是说,就覆盖面来说,拦截器不如过滤器强大,但是更有针对性。Java中的拦截器是基于Java反射机制实现的,更准确的划分,应该是基于JDK实现的动态代理。它依赖于具体的接口,在运行期间动态生成字节码。拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截然后再之前或者之后加入某些操作。java的拦截器主要是用在插件上,扩展件上比如Hibernate Spring Struts2等,有点类似面向切片的技术,在用之前先要在配置文件即xml,文件里声明一段的那个东西。
拦截器和过滤器的区别
- 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
- 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
- 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
- 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
- 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
- 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
- 拦截器需要在Spring配置文件中配置,过滤器只需要在web.xml中配置
为什么要有事务传播行为?
spring管理事务有几种方式?
- 编程式事务,在代码中硬编码。(不推荐使用)
- 声明式事务,在配置文件中配置(推荐使用)
声明式事务又分为两种:- 基于XML的声明式事务
- 基于注解的声明式事务
事务
'#{}'和'${}'的区别是什么?
'#{}'是预编译处理,{}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
一对一、一对多关联查询
MyBatis缓存
- MyBatis的缓存分为一级缓存和二级缓存。
- 一级缓存,SqlSession级别,默认开启。
- 二级缓存,namespace级别,需手动配置和开启。
- 总配置文件开启二级缓存。
- 映射文件添加
标签。 - 实体类实现序列化接口。
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
核心注解是启动类上的@SpringBootApplication
它由以下四个注解组成:
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能。
- @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
- @ComponentScan:Spring组件扫描。
SpringCloud五大组件
- 服务发现
Netflix Eureka - 客户端负载均衡
Netflix Ribbon - 断路器
Netflix Hystri - 服务网关
Netflix Zuul - 分布式配置
Spring Cloud Config
dubbo支持的通信协议
- dubbo://
- rmi://
- hessian://
- http://
- webservice://
- thrift://
- memcached://
- redis://
底层采用socket进行通信
CAP理论
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
分布式环境下的session处理策略
- 粘性session
upstream mycluster{
#这里添加的是上面启动好的两台Tomcat服务器
ip_hash;#粘性Session
server 192.168.22.229:8080 weight=1;
server 192.168.22.230:8080 weight=1;
}
- 服务器session复制
- session共享机制
-
粘性session处理方式
-
非粘性session处理方式
-
- session持久化到数据库
-
terracotta实现session复制
分布式事务
ByteTCC、LCN
阿里分布式事务框架GTS开源了一个免费社区版Fescar
分布式锁
基于数据库做分布式锁
- 基于表主键唯一做分布式锁
- 基于表字段版本号做分布式锁
- 基于数据库排他锁做分布式锁
基于 Redis 做分布式锁
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
- 基于 REDIS 的 SETNX()、EXPIRE() 方法做分布式锁
- 基于 REDIS 的 SETNX()、GET()、GETSET()方法做分布式锁
- 基于 REDLOCK 做分布式锁
- 基于 REDISSON 做分布式锁
基于 ZooKeeper 做分布式锁
基于 Consul 做分布式锁
RabbitMQ
MyISAM与InnoDB的区别
- 存储结构
- 存储空间
- 可移值性、备份、恢复
- 事务支持
- AUTO_INCREMENT
- 表锁差异
- 全文索引
- 表主键
- 表的具体行数
- CURD操作
- 外键
MySQL默认采用的是MyISAM。
索引底层实现原理
-
索引的本质
-
二叉树
-
B树
4.B+树
-
带有顺序访问指针的B+Tree
SQL优化
避免全表扫描,改为索引扫描。
- 适当的索引。
- 尽量不要有空判断的语句,因为空判断将导致全表扫描,而不是索引扫描。尽量不要有空判断的语句,因为空判断将导致全表扫描,而不是索引扫描。 对于空判断这种情况,可以考虑对这个列创建数据库默认值。
- 尽量不要使用不等于条件,因为,这会导致全表扫描,对于不等于这种情况,考虑改为范围查询解决。
- 尽量不要使用or条件,因为,这会导致全表扫描,对于or这种情况,可以改为 分别查询,然后 union all。
- 尽量不要使用左右模糊查询,因为,这会导致全表扫描, 对于左右模糊查询的情况,试着改为右侧模糊查询,这样是可以索引查找的。
- 尽量不要在执行算数运算后的比较,因为,函数、算术运算或其他表达式运算通常将导致全表扫描,对于这种情况,可以考虑冗余部分数据到表中。
- 尽量使用exists代替in。
- 尽量避免一次性返回大数据量,可以考虑分页返回。
union与union all
- union :得到两个查询结果的并集,并且自动去掉重复行。不会排序。
- union all:得到两个查询结果的并集,不会去掉重复行。也不会排序。
Oracle与MySQL分页查询的写法
- Oracle
SELECT * FROM
(
SELECT A. *, ROWNUM RN
FROM(SELECT * FROM TABLE_NAME)A
WHERE ROWNUM <= 40
)
WHERE RN >=21
2.MySQL
SELECT * FROM TABLE LIMIT 5, 10;
SQL小贴士
- 执行顺序
from->where->group by->having->select->order by - HAVING 子句
在 SQL 中增加 HAVING 子句原因是,WHERE 关键字无法与合计函数一起使用。 - union去重,union all 不去重。
- case when then else end
- 当前时间
SELECT NOW(),CURDATE(),CURTIME() FROM DUAL
结果:
NOW() | CURDATE() | CURTIME() |
---|---|---|
2008-12-29 16:25:46 | 2008-12-29 | 16:25:46 |
- 时间间隔
mysql> SELECT DATEDIFF('2017-08-08','2017-08-17');
+-------------------------------------+
| DATEDIFF('2017-08-08','2017-08-17') |
+-------------------------------------+
| -9 |
+-------------------------------------+
1 row in set
查看数据库引擎命令
show variables like '%storage_engine%';
Linux下查询所有tomcat进程命令
ps -ef|grep tomcat