学习是一条漫长之路
本篇总结持续中,记得收藏
Java 基础
-
面向对象都有哪些特性以及你对这些特性的理解
面向对象有3大特性,即封装、继承、多态
封装:封装将复杂的功能封装起来,对外开放一个接口,简单调用即可。
继承:继承是从已有类得到继承信息创建新类的过程,提供继承信息的类被称为父类——又称为超类、基类,得到继承信息的类被称为子类——又称为派生类
多态:多态性是指允许不同子类型的对象对同一种消息做出不同的响应,简单来说就是同一个对象调用同样的方法但是做了不同的事情。
多态性分为编译时的多态和运行的多态
编译时的多态称作重载
运行时的多态称作重写 -
访问权限修饰符都有哪些?以及他们的区别
public > protected > default > private
public:当前类、同包、子类、其他包都可访问
protected:当前类、同包、子类可以访问
default:当前类、同包可以访问
private:只有当前类可以访问 -
Collection 和 Collections 的区别
java.util.Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,Collection 接口在 Java 类库中有很多具体的实现,Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有 List 和 Set
java.util.Collections 是一个包装类,它包含有各种有关集合操作的静态多态方法,此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等提供方法 -
String s = new String(“abc”) 创建了几个对象?
两个对象,第1个对象是字符串常量 abc,第二个对象是 new String() 产生的,在堆中分配内存给这个对象,该对象内容指向字符串常量 abc 还有一个引用 s 指向第2个对象 -
重写(Overriding)和重载(OverLoading)的区别
方法的重写和重载是多态性的不同表现,二者的差别在于:
1.重载是编译时的多态,重写是运行时的多态
2.重载的特点是:在同一类中,方法名相同,参数类型、参数顺序、以及参数个数不同具有这些特点则称为重载方法
3.重写的特点是:参数列表、返回类型都要和被重写的方法相同,访问修饰符权限不能大于被重写方法的修饰符 -
& 和 && 的区别
1.&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
2.&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式
3.&还可以用作 位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作 -
String 是否可以被继承
String 类是 final 类,不可以被继承 -
char 型变量能不能存储一个中文汉字,为什么?
char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的 -
说出几个常见的 runtime Exception
NullpointerException 空指针异常
ClassCastException 数据类型转换异常
IllegalArgumentException 传递非法参数异常
ArithmeticException 算数运算异常
IndexOutOfBooundsException 下标越界异常
NumberFormatException 数字格式异常
SecurityException 安全异常
SQLException sql语句异常
IOException IO流异常 -
RunTimeException和其他Exception区分
其他Exception,受检查异常,必须要开发者解决以后才能编译通过,比如通过 try-catch 处理
RuntimeException,运行时异常 ,又称为不受检查异常,编译期间可以通过,但是在运行期间可能出错,比如经典的空指针、1/0 算数异常等等 -
Erro 和 Exception 区分
Error 表示恢复起来很困难的一种严重问题,例如内存溢出,不可能指望程序处理这样的情况
Exception 表示一种设计实现问题,他表示如果程序运行异常,从不会发生的情况 -
抽象类 abstract 和接口 interface 有什么区别?
抽象类:
1.抽象类可以定义构造函数
2.可以有抽象方法和普通方法
3.接口中的成员全都是 public 的
4.抽象类中可以包含静态方法
5.有抽象方法的类必须是抽象类,而抽象类中可有可不有抽象方法
7.一个类只能继承一个抽象类
接口:
1.接口中不能定义构造函数
2.方法全部都是抽象方法
3.接口中定义的变量实际上都是常量,都由 public final 修改
4.接口中不能有静态方法
5.一个类可以实现多个接口
二者相同点:
1.都不能被实例化
2.可以将抽象类和接口类型作为引用类型
3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要
被声明为抽象类 -
抽象的(abstract)方法是否可同时是静态的(static), 是否可同时是本地方法(native),是否可同时被 synchronized?
都不能,抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized 和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。 -
静态变量和实例变量的区别?
静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对
象共享内存。 -
== 和 equals 的区别?
equals 和 == 最大的区别一个是方法一个是运算符
== :如果比较的对象是基本类型,则比较的是数值是否相等,如果比较的是引用数据类型,则比较的是对象的地址是否相等。
equals():用来比较方法两个对象的内容是否相等
注意:equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变
量所指向的对象的地址 -
break 和 continue 的区别?
break 和 continue 都是用来控制循环的语句。
break 用于完全结束一个循环,跳出循环执行循环后的语句。
continue 用于跳过本次循环,执行下次循环。 -
throw 和 throws 的区别?
throw:
1.throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理
2.throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常
throws:
1.throws 语句主要用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常处理
2.throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
3.throws 表示出现异常的一种可能性,并不一定会发生这种异常。 -
final、finally、finalize 的区别?
final:用于声明属性、方法和类,分别表示属性不可变,方法不可覆盖,类不可被继承
finally:异常处理的一部分,表示总是执行
finalize: Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法
提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。 -
switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String上?
Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型。从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。 -
数组有没有 length() 方法?String 有没有 length() 方法?
数组没有 length() 方法,而是有 length 的属性,String 有 length() 方法。 -
String 、StringBuilder 、StringBuffer 的区别?
- String :是只读字符串,也就意味着 String 引用的字符串是不能改变的
- StringBuffer:StringBuffer 速度没有 StringBuilder 快,但是它安全性高
- StringBuilder :它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方法都没有被 synchronized 修饰,因此它的效率理论上也比 StringBuffer 要高。
- Java 的基本数据类型都有哪些?各占几个字节?
整形:
byte 1字节
short 2字节
int 4字节
long 8字节
浮点型:
float 4字节
double 8字节
字符型:
char 2字节 - String 是基本数据类型吗?
String 是引用类型,底层用 char 数组实现的 - short s1=1; s1 = s1 + 1; 有错吗?short s1 = 1; s1+=1有错吗?
前者不正确,后者正确。对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 =(short)(s1 + 1);其中有隐含的强制类型转换。 - int 和 和 Integer 有什么区别?
(1)Integer是int的包装类;int是基本数据类型;
(2)Integer变量必须实例化后才能使用;int变量不需要;
(3)Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
(4)Integer的默认值是null;int的默认值是0。 - 如何实现字符串反转?
通过 StringBuffer 或者 StringBuilder 中的reverse()方法即可完成
String str = "123456789qwertyuiop";
StringBuffer sBuffer = new StringBuffer(str);
StringBuilder sBuilder =new StringBuilder(str);
System.out.println(sBuffer.reverse());
System.out.println(sBuilder.reverse());
- 什么是 java 序列化,如何实现 java 序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
实体类可以通过实现 Serializable 接口来实现 Java 实例化 - List 的三个子类的特点
- ArrayList 底层结构是数组,底层查询快,增删慢
- LinkedList 底层结构是链表型的,增删快,查询慢
- Voctor 底层结构时数组,线程安全的,增删慢,查询慢
- List 和 Map、Set 的区别
- 结构特点:
List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的 - 实现类:
- List 接口
List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现,线程安全的,效率低)。 - Map接口
Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null键;HashTable:线程安全,低效,不支持 null 值和 null 键;LinkedHashMap:是 HashMap 的一个子类,保存了记录的插入顺序;SortMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。 - Set接口
Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法;LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMp)。 - 区别:
List 集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素;Map 中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复;Set 集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet 类,可以按照默认顺序,也可以通过实现 Java.util.Comparator< Type>接口来自定义排序方式。
- HashMap 和 HashTable 有什么区别?
- HashTable 线程安全,不允许键值对(key/value)有 null 值
- HashMap 线程不安全,允许键值对(key/value)有 null 值
- JDK 和 JRE 有什么区别?
- JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
- JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。
- String 类的常用方法都有那些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
- Java 中 IO 流分为几种 ?
- 按照功能来分:输入流(input)、输出流(output)。
- 按类型来分:字节流和字符流
- 字节流和字符流的区别在于:字节流按照8位传输以字节为单位输入输出数据,字符流按16位传输以字符为单位输入输出数据。
- BIO、NIO、AIO有什么区别?
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel (通道)通讯,实现了多路复用
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非阻塞 IO,异步 IO 的操作基于事件和回调机制
- Files 的常用方法都有哪些?
- Files.exists():检测文件路径是否存在。
- Files.createFile():创建文件。
- Files.createDirectory():创建文件夹。
- Files.delete():删除一个文件或目录。
- Files.copy():复制文件。
- Files.move():移动文件。
- Files.size():查看文件个数。
- Files.read():读取文件。
- Files.write():写入文件。
- 如何实现数组和 List 之间的转换?
- List 转换为数组:调用 ArrayList 的 toArray 方法。
- 数组转换为List:调用 Arrays 的 asList 方法。
- 在 Queue 中 poll() 和 remove() 有什么区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。 - 迭代器 Iterator 是什么?
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为 “轻量级” 对象,因为创建它的代价小。 - Iterator 怎么使用,有什么特点?
Java 中的 Iterator 功能比较简单,并且只能单向移动:
- 使用方法 iterator() 要求容器返回一个 iterator,第一次调用 iterator 的 next() 方法时,它返回序列的第一个元素。注意:iterator() 方法是 java.lang.iterable 接口,被Collection 继承。
- 使用 next() 获得序列中的下一个元素。
- 使用 hashNext() 检查序列中是否还有元素。
- 使用 remove() 将迭代器新返回的元素删除。
iterator 是 Java 迭代器最简单的实现,为 List 设计的 ListIterator 具有更多的功能,它可以从两个方面遍历List,也可以从List中插入和删除元素。
Java 多线程
- 并行和并发有什么区别?
- 并行是指两个或多个事件在同一时刻发生;并发是指两个或者多个时间在同一时间间隔发生
- 并行是在不同实体上的多个事件,并发实在同一实体上的多个事件
- 在一台处理器上“同时”处理多个任务,在多态处理器上同时处理多个任务,如 hadoop 分布式集群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
- 线程和进程的区别?
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程,进程在执行过程中拥有独立的内存单元。而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分配的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行 - 守护线程是什么?
守护线程 daemon thread 是个服务线程,给其他的线程提供服务的。 - 创建线程有几种方式?
- 继承Thread类创建线程类
- 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动该线程。
- 通过Runnable接口创建线程类
- 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动该线程。
- 通过Callable和Future创建线程
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
- 说一下 runnable 和 callable 有什么区别?
- Runnable 接口中的 run() 方法的返回值是 void,它做的事情只是纯粹地去执行 run() 方法中的代码而已。
- Callable 接口中的 call() 方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可以用来获取异步执行的结果。
- 线程都有哪些状态?
- 创建状态:在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
- 就绪状态:当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
- 运行状态:线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
- 阻塞状态:线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
- 死亡状态:如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪
- sleep() 和 wait() 有什么区别?
- sleep() 方法是线程类 Thread 的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争 cpu 的执行时间。因为 sleep() 是 static 静态的方法,它不能改变对象的机制锁,当一个synchronized块中调用了 sleep() 方法,线程虽然进入休眠,但是对象的机制锁没有被释放,其他线程仍然无法访问这个对象。
- wait() 方法是Object类得方法,当一个线程执行到wait 方法时,它就进入一个和该对象相关的等待池中,同时释放对象的机制锁,使得其他线程能够访问,可以通过 notify,notifyAll 方法来唤醒等待的线程。
- notiry() 和 notiryAll() 方法有什么区别?
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
- 线程的 run() 和 start() 方法有什么区别?
每个线程都是通过某个特定的Thread对象所对应的方法 run() 来完成其操作的,方法 run() 称为线程体,通过调用 Thread 类的 start() 方法来启动一个线程 。
- start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
- run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
- 什么是死锁?
死锁是指两个或多个线程在执行过程中,由于竞争资源或者由于彼此通信造成的一种堵塞线程,如果没有外力干扰,他们都无法执行下去,此时称系统处于死锁状态,这些永远在互相等待的进程称为 - 怎么防止死锁?
死锁形成的四个必要条件:- 互斥条件:进程中对分配的资源不允许其他进程进行访问,如果其他进程访问该资源,只能等待,直到占有该资源的进程完成后释放该资源 。
- 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求堵塞,但是又对自己获取的资源保持占有。
- 不可剥夺条件:指的是进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放。
- 环路等待条件:是指进程发生死锁后,若干线程之间形成一种头尾相接的循环等待资源关系。
这四个条件就是形成死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
- 说一下 synchronized 底层实现原理?
synchronized 可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
Java 中每一个对象都可以作为锁,这是 synchronized 实现同步的基础:- 普通同步方法,锁是当前实例
- 静态同步方法,琐是当前类得 class 对象
- 同步方法块,锁是括号里面的对象
- synchronized 和 volatile 的区别是什么?
- volatile 本质是在告诉jvm 当前变量在工作内存中的值是不确定的,需要从主存中读取;synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被堵塞住。
- volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别上。
- volatile 仅能实现变量的修改可见性,不能保证原子性;synchronized 则可以保证变量的修改可见性和原子性。
- volatile 不会造成线程阻塞;synchronized 可能会造成线程阻塞。
- volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
- synchronized 和 Lock 有什么区别?
- 首先 synchronized 是Java内置关键字,在jvm层面,Lock是个java类。
- synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁。
- synchronized 会自动释放锁,Lock 需要在 finally 中手工释放锁(unlock() 方法释放锁),否则容易造成线程死锁。
- 用 synchronized 关键字的两个线程1和线程2吗,如果当前线程1获得锁,线程2等待。如果线程1堵塞了,线程2会一直等待下去;而Lock 锁就不一定会等待下去,如果尝试获取不到锁,线程可以不同一直等待就结束了。
- synchronized 的锁可重入、不可中断、非公平,而 Lock 锁可重入、可判断、可公平。
- Lock 锁适合大量同步代码的同步问题,而 synchronized 锁适合代码少量的同步问题。
- synchronized 和 ReentrantLock 的区别是什么?
synchronized 是Java中的关键字,ReentrantLock是类,这是二者本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock 比synchronized 的扩展性体现在几点上:- ReentrantLock 可以对获取锁的等待时间进行设置,这样就避免了死锁发生
- ReentrantLock 可以获取各种锁的信息
- ReentrantLock 可以灵活的实现多路通知
另外,二者的锁机制其实也是不一样的,ReentrantLock 底层调用的是 Unsafe的park 方法加锁,synchronized 操作的应该是对象头中的 mark word。
- 手写饿汉模式和懒汉模式
懒汉模式:当你需要此实例在给你创建
public class Signlock {
//懒,所以就不实例化了,加载类较快,但是第一次访问类可能会有点慢
private static Signlock lock = null;
// 创建私有构造器
private Signlock() {
}
public static Signlock get() {
if(lock ==null){
synchronized (Signlock.class){
if(lock ==null) //这里的二次判断很有必要啊,在多线程中同时到达第一个if的可能有多个,若此处没有二次二次判断,会导致new Signlock()同步执行多次。
lock =new Signlock ();
}
}
return lock;
}
}
饿汉模式:不管你需不需要此实例,都给你创建
public class Signlock {
private static final Signlock lock = new Signlock();
// 创建私有构造器
private Signlock() {
}
public static Signlock get() {
return lock;
}
}
Java Web
- jsp 和 servlet 有什么区别?
- jsp经编译后就变成了 Servlet JSP的本质就是Servlet,JVM只能识别 Java 类,不能识别 JSP 的代码,Web 容器将JSP的代码编译成JVM能够识别的Java类
- jsp 更擅长表现于页面显示,servlet 更擅长与逻辑控制。
- Servlet 中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest 对象或者 HttpServletResponse对象以及HttpServlet对象得到。
- Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何嵌入到一个类中,由JSP容器完成。而Servlet 则是个完整的 Java类,这个类的Service 方法用于生成对客户端的相应。
- Jsp中有哪些内置对象?作用分别是什么?
- request:封装客户端的请求,其中包含来自GET和POST请求的参数。
- response:封装服务器对客户端的相应。
- pageContext:通过此对象可以获取其他对象。
- session:封装用户会话的对象。
- application:封装服务器运行环境的对象,全局对象。
- out:输出服务器响应的输出流对象。
- config:Web应用的配置对象。
- page:JSP页面本身。
- exception:封装页面抛出异常的对象。
- 说一下 JSP 的 4 种作用域?
- page 代表与一个页面相关的对象和属性。
- request 代表与客户端发出的一个请求相关的对象和属性,一个请求可能跨越多个页面,涉及多个Web组件,需要在页面显示的临时数据可以至于此作用域。
- session 代表与某个用户与服务器建立的一次会话数据,根某个用户相关的数据应该放在用户自己的 session 中。
- application 代表与整个Web应用程序相关的属性,它实质上是跨越了整个Web应用程序,包括多个页面,请求和会话的一个全局作用域。
- Session 和 Cookie有什么区别呢?
- 由于HTTP协议是无状态协议,所以服务端需要记录用户的状态时,就需要使用某种手段来识别具体的用户,这个手段就是Session ,典型的场景列如:购物车,当你点击下单时,由于HTTP协议无状态,这个时候不知道是哪个用户操作的,所以服务端要为特定的用户创建特定的Session,用于标识这个用户,并且跟踪用户,这样才知道用户购物车里有几本书。Session是保存在服务端的,有一个唯一标识,在服务端保存Session 的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
- 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
- Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
- 说一下Session的工作原理?
其实Session是一个存放在服务器上的类似于一个散列表格的文件,里面存有我们需要的信息,在我们需要用的时候从里面取出来,类似于一个比较大的 Map,里面的键存储的是用户的sessionid,用户向服务器端发送请求的时候会带上这个 sessionid,这时就可以根据这个sessionid取出里面对应的value值了。 - 如何客户端禁止 Cookie 那么 Session 还能用吗?
Cookie 与 Session,一般认为是两个独立的东西,Session 采用的实在服务端保持状态的方案,而Cookie 采用的实在客服端保持状态的方案。但为什么禁用 Cookie就不能得到 Session呢?因为 Session 使用 Session ID 来确定当前对话所对应的服务器Session,而 Session ID 是通过 Cookie 来传递的,禁用 Cookie相当于失去了Session ID,理应的就得不到Session了。
假如用户关闭了Cookie的情况下仍然要使用Session,其中实现的方法有下列几种:- 设置 php.ini 配置文件中的 “session.use_trans_sid=1”,或者编辑器打开 "–enable-trans-sid"选项,让 PHP自动跨页传递 Session ID。
- 手动通过 URL传值,隐藏表单传递 Session ID。
- 用文件、数据库等形式保存Session ID,在跨页面中手动调用。
- 什么是 XSS 攻击,如何避免呢?
- XSS攻击又称为跨脚本攻击,原理就是攻击者向有XSS漏洞的网站中输入恶意的 HTML代码,当用户浏览该网站时,这段HTML代码就会自动执行,从而达到攻击的目的。XSS攻击类似于SQL注入攻击,SQL注入攻击中以SQL语句作为用户输入,从而达到查询、修改、删除数据的目的,而在Xss攻击中,通过插入恶意脚本,实现对用户浏览器的控制,获取用户的一些信息。XSS是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式。
Xss防范的总体思路:对输入(URL参数)进行过滤,对输出进行编码。
- 什么是CSRF攻击,如何避免?
- CSRF也被称为 one-click attack 或者 session riding,中文全称是 跨站请求伪造 ,一般来说,攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去。使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假信息等。 攻击者利用网站对请求的验证漏洞而实现这样的攻击行为。网站能够确认请求来源于用户的浏览器,却不能验证是否源于用户的真实意愿下的操作行为。
那么如何避免呢? - 验证 HTTP Referer 字段
HTTP头中的Referer字段记录了该HTTP请求的来源地址,在通常情况下,访问一个安全受限页面的请求来自同一个网站,而如果黑客要对其实施 CSRF攻击,他一般只能在自己的网站构造请求,因此,可以通过验证 Referer值来防御CSRF攻击
- 使用验证码
关键页面加上验证码,后台收到请求通过判断验证码可以防御 CSRF,但这种方法对用户不太友好。
- 在请求地址中添加 token 并验证
CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有用户验证信息都存在于 Cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie 来通过安全验证。要抵御 CSRF,关键在于请求中放入黑客所不能伪造的信息,并且该信息不能存在 cookie 中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于session之中,然后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。
对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。
而对于 POST 请求来说,要在 form 的最后加上 ,这样就把token以参数的形式加入请求了。
- 在 HTTP 头中自定义属性并验证
这种方法也是使用 token 进行验证,和上一种方法不同的是这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。
网络
- Http响应码 301 和 302 代表什么,有什么区别?
- 301 代表永久性转移
- 302 代表暂时性转移
- forwar 和 redirect 的区别?
- 请求方式不同
redirect:客户端发起的请求
forward:服务端发起的请求
- 浏览器地址表现不同
redirect:浏览器地址显示被请求的
forward:浏览器地址不显示被请求的url
- 参数传递不同
redirect:重新开始一个请求,原页面的请求生命周期结束
forward:重定向另一个连接的时候,request变量是在其生命周期内的。另一个页面也可以使用,其实质是把目标地址 include。
- 底层运作不同
redirect:发送的请求信息又回送给客户机,让客户机再转发到另一个资源上,需要在服务器和客户机之间增加一次通信。
forward:服务器端直接找到目标,并include过来。
- 定义不同
直接转发方式(Forward):客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的。
间接转发方式(Redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。
- tcp为什么要三次握手,两次不行吗?为什么?
- 为了实现可靠数据传输,TCP协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的,三次握手的过程即是通信双方相互告知序列号起始值,并确定对方已经收到了序列号起始值的毕竟步骤。
- 如果只是两次握手,顶多只有连接请求方的起始序列号能被确认,响应方的序列号则得到不验证。
- OSI的七层模型都有哪些?
- 应用层:网络服务与最终用户的一个接口
- 表示层:数据的表示、安全、压缩。
- 会话层:建立、管理、终止会话。
- 传输层:定义传输数据的协议端口号,以及流控和差错校验。
- 数据链路层:建立逻辑链接、进行硬件地址寻址、差错校验等功能。
- 物理层:建立、维护、断开物理连接。
- get 和 post 请求有什么区别?
- get 在浏览器回退时是无害的,而 post 会再次提交请求
- get 产生的url地址可被 bookmark,而 post 不可以
- get 请求会被浏览器主动缓存,而 post 不会,除非手动设置
- get 请求只能手动 url 编码。而 post 支持多种编码格式
- get 请求在 url 中传送的参数是有长度限制的,而 post 没有限制
- get 对比 post 请求不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息
- get 参数通过 url 传递,post 放在请求体中。
Java 框架
- 为什么要使用 Spring?
Spring 是一个轻量级的控制反转 IOC 和面向切面编程的 AOP 的容器框架- 控制反转
Spring 通过一种称控制反转IOC 的技术促进了松耦合, - 面向切面
Spring 提供了面向切面编程的支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发,应用对象只实现他们应该做的——完成业务逻辑仅此而已。它们并不负责其它的系统级关注点,列如日志或者事务支持。
- 控制反转
- 解释一下什么是 AOP?
AOP 面向切面编程,在不改变原代码的基础上为代码增加新的功能,列如:日志、事务处理 - 解释下什么是 IOC?
IOC即控制反转,又称为依赖注入,其原理就是让 bean 与 bean 之间以配置文件的形式组织在一起,而不是以硬编码的方法耦合在一起,相当于将需要的接口实现都注入到需要它的类中。
IOC的好处:低耦合,易管理。 - Spring 事务实现方式有哪些?
- 编程式事务管理,我们需要在代码中通过调用 beginTransaction()、commit()、rollback() 等事务管理相关的办法,这就是编程式事务管理。
- 基于 TransactionProxyFactoryBean 的声明式事务管理
- 基于 @Transaction 的声明式事务管理
- 基于 Aspectj AOP 配置事务
- 说一下 Spring 的事务隔离机制?
事务隔离机制指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:
* 脏读:一个事务读到另一个事务未提交的更新数据
* 幻读:列如第一个事务对一个表中的数据进行了修改(比如这种修改是修改全部数据),同时,第二个事务也修改了这个表中的数据(这种修改时向表中插入一行新数据), 那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好像发生了幻觉。
* 不可重复读:比方说在同一个事务中先后执行两条一模一样的select语句,期间在此次事务中没有执行过任何DDL语句,但先后得到的结果不一致,这就是不可重复读。 - 什么是Spring Boot?
SpringBoot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,节省了繁杂的配置,提供了各种启动器,开发者能够快速上手。 - 为什么要使用Spring Boot?
Spring Boot 的优点,例如:
* 独立运行
* 简化配置
* 自动配置
* 五代码生成和XML配置
* 应用监控
* 上手容易
* … - Spring Boot 的核心配置文件有哪几个?他们的区别是什么?
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
application 配置文件主要用于 SpringBoot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景:- 使用 Spring Cloud 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性;
- 一些加密、解密的场景;
- Spring Boot 的配置文件有哪几种格式?他们有什么区别?
.properties 和 .yml 的区别在于写法不同。
- .properties
server.port=8082
- .yml
server:
port:8082
.yml格式不支持 @PropertySource 注解导入配置
- Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
@SpringBootApplication是Spring Boot的核心注解,此注解的作用是启动项目,主要由3个注解组成:
- @SpringBootConfiguration:组合了@Configuration注解,实现了配置文件的功能。
- @EnableAutoConfiguration:打开了自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exculde={ DataSourceAutoConfiguraion.class})。
- @ComponentScan:Spring组件扫描。
- 开启 SpringBoot 特性由哪几种方式?
- 继承spring-boot-starter-parent项目
- 导入spring-boot-dependencied项目依赖
- SpringBoot需要独立的容器运行吗?
可以不需要,内置了Tomcat、Jetty等容器。 - 运行 Spring Boot 有哪几种方式?
- 打包用命令或者放到容器中运行
- 用 Maven、Gradle 插件运行
- 直接启动类main方法运行
- Spring Boot 自动配置的原理是什么?
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。 - Spring Boot 的目录结构是怎样的?
cn
+- javastack
+- MyApplication.java
|
+- customer
| +- Customer.java
| +- CustomerController.java
| +- CustomerService.java
| +- CustomerRepository.java
|
+- order
+- Order.java
+- OrderController.java
+- OrderService.java
+- OrderRepository.java
- 这个目录结构是目前主流推荐的做法,而在主入口类上加 @SpringBootApplication 注解来开启 Spring Boot 的各项能力,如自动配置,组件扫描等。
- 你如何理解 Spring Boot 中的 Starters?
- Starters 可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring 及其他技术,而不需要导出找示例代码和依赖包,如你所想使用 Spring JPA 访问数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。
- 如何在 Spring Boot 启动的时候运行一些特定的代码?
- 可以实现接口 ApplicationRunner 或者 CommandLineRunner ,这两个接口实现方式一样,他们都只提供了一个 run 方法。
- Spring Boot 有哪几种读取配置的方式?
- Spring Boot 可以通过 @PropertySource,@Value,@Environment,@ConfigurationProperties 来绑定变量。
- Spring Boot 实现热部署有哪几种方式?
主要有两种方式- Spring loaded
- Spring-boot-devtools ——添加pom.xml依赖
- 如何理解Spring Boot 配置加载顺序?
在Spring Boot 里面,可以使用以下几种方式来加载配置:- properties 文件;
- YAML 文件;
- 系统环境命令;
- 命令行参数;
- Spring Boot 如何定义多套不同环境配置?
application.properties //普通环境
application-dev.properties //开发环境
application-test.properties //测试环境
application-prod.properties //生产环境
- Spring Boot 可以兼容老 Spring 项目吗?如何做到?
可以兼容,通过 @ImportResource 注解导入老 Spring 项目配置文件。 - 保护 Spring Boot 应用有哪些方法?
- 在生产中使用HTTPS
- 使用 Snyk 检查你的依赖关系
- 升级到最新版本
- 启动 CSRF 保护
- 使用内容安全策略防止 XSS 攻击
- …