Java面试题目
作者:张佳辉
- Java面试题目
- 一、Java的九大内置对象 和 jsp四大作用域
- 二、Java面向对象的四个特征
- 三、访问修饰符 public,private,protected,以及不写时的区别?
- 四、Java的基本数据类型
- 五、Java反射
- 六、接口和抽象类异同
- 七、String 和 StringBuilder、StringBuffer 的区别?
- 八、== 和 equals 的区别是什么?
- 九、String s = "xyz" 和 String s = new String("xyz") 区别?
- 十、List 和 Set,Map 的区别
- 十一、Array 和 ArrayList 有何区别?
- 十二、ArrayList 和 LinkedList 的区别是什么?
- 十三、HashMap介绍
- 十四、并发和并行有什么区别?
- 十五、重载(Overload)和重写(Override)的区别?
- 十六、单例模式
- 十七、Java 中的 Math 函数中包含的基本方法:ceil,round,floor
- 十八、Java 中 IO 流分为几种?
- 十九、常见的异常类有哪些?
- 二十、Java多线程
- 二十一、Java常见的排序算法
- 二十二、Spring常用注解
- 二十三、final 在 java 中有什么作用?
- 二十四、Java 中 Map 有哪些实现类和使用场景
- 二十五、Java 中 Set 有哪些实现类?
- 二十六、栈和队列的区别
- 二十七、GET 和 POST 的区别
- 二十八、什么是控制反转(IOC)?什么是依赖注入?
- 二十九、Spring中都应用了哪些设计模式?
- 三十、数组和集合的区别
- 三十一、简述 OSI 的体系结构,并简述每一层的作用
- 三十二、简述 TCP/IP 四层体系结构
- 三十三、TCP 和 UDP 有哪些不同?
一、Java的九大内置对象 和 jsp四大作用域
对象 | 描述 | 所属作用域 | 作用域描述 |
---|---|---|---|
request | 这是与请求相关联的 HttpServletRequest 对象 | request | 在当前请求中有效 |
response | 这是与客户端的响应关联的 HttpServletResponse 对象 | page | 在当前页面有效 |
out | 这是用于将输出发送到客户端的 PrintWriter 对象 | page | 在当前页面有效 |
session | 这是与请求相关联的 HttpSession 对象 | session | 在当前会话有效 |
application | 这是与应用程序上下文相关联的 ServletContext 对象 | application | 在当前程序应用有效 |
config | 这是与该页面相关联的 ServletConfig 对象 | page | 在当前页面有效 |
pageContext | 这封装了使用服务器特定的功能,如更高性能的 JspWriter | page | 在当前页面有效 |
page | 这只是—个同义词,用于调用由翻译的 servlet 类定义的方法 | page | 在当前页面有效 |
Exception | Exception 对象允许指定的 JSP 访问异常数语 | page | 在当前页面有效 |
二、Java面向对象的四个特征
封装,继承,多态,抽象;
三、访问修饰符 public,private,protected,以及不写时的区别?
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default(默认不写) | √ | √ | × | × |
private | √ | × | × | × |
四、Java的基本数据类型
除了8大基本类型(primitive type),剩下的都是引用类型(reference type)。
- 基本数据类型:数据直接存储在栈上
- 引用数据类型区别:数据存储在堆上,栈上只存储引用地址
数据类型 | 占用字节 | 默认值 | 类型 | 封装器类 |
---|---|---|---|---|
byte | 1 | 0 | 整型 | Byte |
short | 2 | 0 | 整型 | Short |
int | 4 | 0 | 整型 | Integer |
long | 8 | 0L | 整型 | Long |
float | 4 | 0.0f | 浮点型 | Float |
double | 8 | 0 | 浮点型 | Double |
char | 2 | \u0000 (空格) | 字符型 | Character |
Boolean | / | false | 布尔型 | / |
基本类型之间的转换分为自动转换和强制转换:
- 自动转换(隐式):无需任何操作。
- 强制转换(显式):需使用转换操作符(type)。
将6种数据类型按下面顺序排列一下:
double > float > long > int > short > byte (从小到大自动转换)
如果从小转换到大,那么可以直接转换,而从大到小,或char 和其他6种数据类型转换,则必须使用强制转换。
五、Java反射
Java反射概述:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射就是把 Java 类中的各种成分映射成一个个的 Java 对象
Java 反射的创建方式有哪些?
第一种:通过 Object类 的getClass方法
Class cla = foo.getClass();
第二种:通过对象实例方法获取对象
Class cla = foo.class;
第三种:通过 Class 方式
Class cla = Class.forName("xx.xx.Foo");
1、使用Java反射获取类的信息
void printClassName(Object obj) {
System.out.println("The class of " + obj + " is " + obj.getClass().getName());
}
2、使用Java反射创建实例
Constructor<类名> con = classType.getConstructor(String.class, int.class);
Object obj = con.newInstance("xxx", 123);
3、使用Java反射修改实例中的属性值
Person p = new Person();
Class classType = Person.class;
//getMethod()获取public方法
Method method = classType.getMethod("setName", String.class);
method.invoke(p, "ckl");
//getDeclareMethod()获取所有声明的方法
Method method2 = classType.getDeclaredMethod("test");
method2.setAccessible(true);
method2.invoke(p);
4、使用Java反射调用实例中的方法
Person p = new Person();
Class classType = Person.class;
Field field = classType.getDeclaredField("name");
Field field2 = classType.getDeclaredField("age");
field.setAccessible(true);
field2.setAccessible(true);
field.set(p, "xxx");
field2.set(p, 123);
六、接口和抽象类异同
相同点:
- 接口和抽象类都不能被实例化,他们都位于继承树顶端,用于被其他类继承和实现
- 接口和抽象类都可以包含抽象方法,实现接口/继承抽象类的普通子类都必须实现这些抽象方法
不同点:
- 接口里只能包含抽象方法,静态方法和默认方法,不能为普通方法提示方法实现,抽象类则完全可以包含普通方法
- 接口中只能定义静态常量,抽象类中既可以定义普通成员变量,也可以定义静态常量
- 接口里不包含构造器,抽象类里可以包含构造器,抽象类中构造器是让其子类调用这些构造器完成属于抽象类的初始化操作
- 接口里不能包含初始化块,但是抽象类则可以包含初始化块
- 一个类最多只能有一个直接父类,包含抽象类,但是一个类可以直接实现多个接口。
不同点 | 接口 | 抽象类 |
---|---|---|
方法 | 抽象方法、静态方法、默认方法 | 全部的普通方法 |
常量/变量 | 静态常量 | 普通成员变量,静态常量 |
构造器 | 不包含构造器 | 可以包含构造器(初始化) |
初始化块 | 不包含 | 包含 |
类 | 多个接口 | 只能一个 |
七、String 和 StringBuilder、StringBuffer 的区别?
- String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。
- StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。
- StringBuilder:StringBuffer 的非线程安全版本,不安全,没有使用 synchronized,具有更高的性能,推荐优先使用。
八、== 和 equals 的区别是什么?
-
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。
(基本数据类型 == 比较的是值,引用数 据类型 == 比较的是内存地址)
-
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
九、String s = “xyz” 和 String s = new String(“xyz”) 区别?
两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象。
另外,String s = new String(“xyz”) 还会通过 new String() 在堆里创建一个内容与 “xyz” 相同的对象实例。
所以前者其实理解为被后者的所包含。
十、List 和 Set,Map 的区别
- List 以索引来存取元素,有序的,元素是允许重复的,可以插入多个null。
- List 有基于数组、链表实现两种方式
- Set 不能存放重复元素,无序的,只允许一个null
- Set、Map 容器有基于哈希存储和红黑树两种方式实现
- Set 基于 Map 实现,Set 里的元素值就是 Map的键值
- Map 保存键值对映射,映射关系可以一对一、多对一
总结:
List 是有序的,元素允许重复,可以为空;
Set 是无序的,元素不能重复,只允许一个为空;
Map 是保存键值对的;
十一、Array 和 ArrayList 有何区别?
- 定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。
- ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。
十二、ArrayList 和 LinkedList 的区别是什么?
- ArrayList:是动态数组的数据结构实现,查找和遍历的效率较高;
- LinkedList:是双向链表的数据结构,增加和删除的效率较高;
十三、HashMap介绍
HashMap 数据结构为 数组+链表,其中:链表的节点存储的是一个 Entry 对象,每个Entry 对象存储四个属性(hash,key,value,next)
如果 HashMap() 不传值,默认大小是16,负载因子是0.75.
衡量hashMap需要扩容的条件是:hashMap.size(当前数据长度) >= capacity(HashMap总长度) * loadfactor(阈值)
hashMap是线程不安全的。
在java1.7中hashMap会产生死循环、数据丢失、数据覆盖的问题;而在java1.8中会有数据覆盖的问题。
HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素。HashSet 不允许重复的值。
十四、并发和并行有什么区别?
并发:两个或多个事件在同一时间间隔发生。
并行:两个或者多个事件在同一时刻发生。
并行是真正意义上,同一时刻做多件事情,而并发在同一时刻只会做一件事件,只是可以将时间切碎,交替做多件事情。
十五、重载(Overload)和重写(Override)的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:一个类中有多个同名的方法,但是具有有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)。
重写:发生在子类与父类之间,子类对父类的方法进行重写,参数都不能改变,返回值类型可以不相同,但是必须是父类返回值的派生类。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。
十六、单例模式
定义:保证程序只有一个对象的实例,叫做单例模式;
简单实现:
//普通员工
public class Staff {
public void work(){
//干活
}
}
//副总
public class VP extends Staff {
public void work() {
//管理下面的经理
}
}
//CEO,饿汉单例模式
public class CEO extends Staff {
private static final CEO mCeo=new CEO();
//构造函数私有
private CEO(){
}
//公有的静态函数,对外暴露获取单例对象的接口
public static CEO getCeo(){
return mCeo;
}
public void work() {
//管理VP
}
}
//公司类
public class Company {
private List<Staff> allStaff=new ArrayList();
public void addStaff(Staff per){
allStaff.add(per);
}
public void showAllStaffs(){
for (Staff per:allStaff) {
System.out.println("Obj:"+per.toString());
}
}
}
十七、Java 中的 Math 函数中包含的基本方法:ceil,round,floor
Math提供了三个与取整有关的方法:ceil、floor、round
(1)ceil:向上取整;
Math.ceil(11.3) = 12;
Math.ceil(-11.3) = 11;
(2)floor:向下取整;
Math.floor(11.3) = 11;
Math.floor(-11.3) = -12;
(3)round:四舍五入;
加 0.5 然后向下取整。
Math.round(11.3) = 11;
Math.round(11.8) = 12;
Math.round(-11.3) = -11;
Math.round(-11.8) = -12;
十八、Java 中 IO 流分为几种?
- 按流划分,可以分为输入流和输出流;
- 按单位划分,可以分为字节流和字符流;
字节流:inputStream、outputStream;
字符流:reader、writer;
十九、常见的异常类有哪些?
异常名称 | 介绍 |
---|---|
NullPointerException | 空指针异常 |
SQLException | 数据库相关的异常 |
IndexOutOfBoundsException | 数组下角标越界异常 |
FileNotFoundException | 打开文件失败时抛出 |
IOException | 当发生某种IO异常时抛出 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出此异常 |
NoSuchMethodException | 无法找到某一方法时,抛出 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常 |
NumberFormatException | 当试图将字符串转换成数字时,失败了,抛出 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数 |
ArithmeticExecption | 当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例 |
常见的异常:
- ArithmeticException(算术异常)
- ClassCastException (类转换异常)
- IllegalArgumentException (非法参数异常)
- IndexOutOfBoundsException (下标越界异常)
- NullPointerException (空指针异常)
- SecurityException (安全异常)
二十、Java多线程
1.1 并发编程的三要素
并发编程的三要素:(也是带来线程安全所在)
- 原子性:原子是不可再分割的最小单元,原子性是指一个或多个操作要么全部执行成功,要么全部执行失败。
- 可见性:一个线程对共享变量的修改,另一个线程能看到(synchronized,volatile)
- 有序性:程序的执行顺序按照代码的先后顺序
线程安全的问题原因有:
- 线程切换带来的原子性问题
- 缓存导致的可见性问题
- 编译优化带来的有序性问题
解决方案:
- JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
- synchronized、volatile、LOCK,可以解决可见性问题
- Happens-Before 规则可以解决有序性问题
1.2 线程与进程
进程:内存中运行的运用程序,每个进程都有自己独立的内存空间,一个进程可以由多个线程,例如在Windows系统中,xxx.exe就是一个进程。
线程:进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。
1.3 形成死锁的四个必要条件
- 互斥条件:线程(进程)对所分配的资源具有排它性,即一个资源只能被一个进程占用,直到该进程被释放。
- 请求与保持条件:一个进程(线程)因请求被占有资源而发生堵塞时,对已获取的资源保持不放。
- 不剥夺条件:线程(进程)已获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源。
- 循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。
1.4 创建线程的四种方式
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口
- Executors 工具类创建线程池
1.5 Java用到的线程调度算法是什么?
计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获取到CPU的使用权才能执行指令,所谓多线程的并发运行,其实从宏观上看,各线程轮流获取CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待,CPU的调度,JVM有一项任务就是负责CPU的调度,线程调度就是按照特定的机制为多个线程分配CPU的使用权。
有两种调度模型:分时调度 和 抢占式调度
分时调度:让所有的线程轮流获得CPU的使用权,并且平均分配到各个线程占有CPU的时间片。
抢占式调度:Java虚拟机采用抢占式调度模型,是指优先让线程池中优先级高的线程首先占用CPU,如果线程池中优先级相同,那么随机选择一个线程,使其占有CPU,处于这个状态的CPU会一直运行,优先级高的分的CPU的时间片相对会多一点。
二十一、Java常见的排序算法
1、插入排序
将 待插元素,依次与已排序好的 子数列元素 从后到前进行比较,如果当前元素值比待插元素值大,则将移位到与其相邻的后一个位置,否则直接将待插元素插入当前元素相邻的后一位置,因为说明已经找到插入点的最终位置。
public class InsertSort {
public static void sort(int[] arr) {
if (arr.length >= 2) {
for (int i = 1; i < arr.length; i++) {
//挖出一个要用来插入的值,同时位置上留下一个可以存新的值的坑
int x = arr[i];
int j = i - 1;
//在前面有一个或连续多个值比x大的时候,一直循环往前面找,将x插入到这串值前面
while (j >= 0 && arr[j] > x) {
//当arr[j]比x大的时候,将j向后移一位,正好填到坑中
arr[j + 1] = arr[j];
j--;
}
//将x插入到最前面
arr[j + 1] = x;
}
}
}
}
2、快速排序
简单的说, 就是设置一个 标准值, 将大于这个值的放到右边(不管排序), 将小于这个值的放到左边(不管排序), 那么这样只是区分了左小右大, 没有排序, 没关系, 左右两边再重复这个步骤.直到不能分了为止。
public class QuickSort {
public static void sort(int[] arr,int begin,int end) {
//先定义两个参数接收排序起始值和结束值
int a = begin;
int b = end;
//先判断a是否大于b
if (a >= b) {
//没必要排序
return;
}
//基准数,默认设置为第一个值
int x = arr[a];
//循环
while (a < b) {
//从后往前找,找到一个比基准数x小的值,赋给arr[a]
//如果a和b的逻辑正确--a<b ,并且最后一个值arr[b]>x,就一直往下找,直到找到后面的值大于x
while (a < b && arr[b] >= x) {
b--;
}
//跳出循环,两种情况,一是a和b的逻辑不对了,a>=b,这时候排序结束.二是在后面找到了比x小的值
if (a < b) {
//将这时候找到的arr[b]放到最前面arr[a]
arr[a] = arr[b];
//排序的起始位置后移一位
a++;
}
//从前往后找,找到一个比基准数x大的值,放在最后面arr[b]
while (a < b && arr[a] <= x) {
a++;
}
if (a < b) {
arr[b] = arr[a];
//排序的终止位置前移一位
b--;
}
}
//跳出循环 a < b的逻辑不成立了,a==b重合了,此时将x赋值回去arr[a]
arr[a] = x;
//调用递归函数,再细分再排序
sort(arr,begin,a-1);
sort(arr,a+1,end);
}
}
3、冒泡排序
每次冒泡过程都是从数列的 第一个元素 开始,然后依次和剩余的元素进行比较, 跟列队一样, 从左到右两两相邻的元素比大小, 高的就和低的换一下位置. 最后最高(值最大)的肯定就排到后面了。
public class MaoPao {
public static void sort(int[] arr){
for (int i = 1; i < arr.length; i++) { //第一层for循环,用来控制冒泡的次数
for (int j = 0; j < arr.length-1; j++) { //第二层for循环,用来控制冒泡一层层到最后
//如果前一个数比后一个数大,两者调换 ,意味着泡泡向上走了一层
if (arr[j] > arr[j+1] ){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
4、选择排序
首先在未排序数列中找到 最小元素,然后将其与数列的 首部元素 进行交换,然后,在剩余未排序元素中继续找出最小元素,将其与已排序数列的末尾位置元素交换。以此类推,直至所有元素圴排序完毕.
public static void sort(int[] arr){
for(int i = 0; i < arr.length - 1 ; i++){
int min = i; // 遍历的区间最小的值
for (int j = i + 1; j < arr.length ;j++){
if(arr[j] < arr[min]){
// 找到当前遍历区间最小的值的索引
min = j;
}
}
if(min != i){
// 发生了调换
int temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
}
5、归并排序
把一串数从中平等分为两份,再把两份再细分,直到不能细分为止. 这就是 分而治之 的分的步骤。再从最小的单元,两两合并,合并的规则是将其按从小到大的顺序放到一个临时数组中,再把这个临时数组替换原数组相应位置。
public static void mergeSort(int[] a,int s,int e){
int m = (s + e) / 2;
if (s < e){
mergeSort(a,s,m);
mergeSort(a,m+1,e);
//归并
merge(a,s,m,e);
}
}
private static void merge(int[] a, int s, int m, int e) {
//初始化一个从起始s到终止e的一个数组
int[] temp = new int[(e - s) + 1];
//左起始指针
int l = s;
//右起始指针
int r = m+1;
int i = 0;
//将s-e这段数据在逻辑上一分为二,l-m为一个左边的数组,r-e为一个右边的数组,两边都是有序的
//从两边的第一个指针开始遍历,将其中小的那个值放在temp数组中
while (l <= m && r <= e){
if (a[l] < a[r]){
temp[i++] = a[l++];
}else{
temp[i++] = a[r++];
}
}
//将两个数组剩余的数放到temp中
while (l <= m){
temp[i++] = a[l++];
}
while (r <= e){
temp[i++] = a[r++];
}
//将temp数组覆盖原数组
for (int n = 0; n < temp.length; n++) {
a[s+n] = temp[n];
}
}
6、堆排序
堆排序, 顾名思义, 就是将数据以堆的结构, 或者说类似于二叉树的结构, 每次都整理二叉树将最大值找到, 然后放到数组末尾. 个人感觉有点像 选择排序。都是每次遍历选择一个最大值或最小值
/**
* 最大顶堆排序
* @param a
*/
public static void topMaxHeapSort(int[] a){
//先创建最大顶堆
createTopMaxHeap(a);
int end = a.length - 1 ;
while(end > 0 ){
//把0位的最大值放到最后
swaparr(a, 0, end);
//将计算的长度减一.不考虑最后的那个值
end--;
//重新调整堆结构
handleMaxHeapFromIndex(a, end);
}
//打印下看看
// System.out.println(Arrays.toString(a));
}
/** [3,7,1,4,9,5,6,7,2,6,8,3]
* `````````3
* ``````/ \
* `````7 1
* ````/ \ / \
* ``4 9 5 6
* `/ \ / \ /
* `7 2 6 8 3
* 变成 [9, 7, 6, 7, 6, 5, 1, 4, 2, 3, 8, 3]
* `````````9
* ```````/ \
* `````7 6
* ````/ \ / \
* ``7 6 5 1
* `/ \ / \ /
* `4 2 3 8 3
* 构建最大顶堆, 变成父节点都比子节点大的树
* @param a
*/
public static void createTopMaxHeap(int[] a){
handleMaxHeapFromIndex(a,a.length-1);
//打印下看看
// System.out.println(Arrays.toString(a));
}
private static void handleMaxHeapFromIndex(int[] a,int end){
//从倒数第二排最后一个开始, 从下往上, 层层处理把最大的换上去构建最大顶堆
//如上面的注释, 就是从5开始. 再往后就没意义了
for (int i = end / 2; i>=0; i--) {
//从i开始往后面调整它的堆
//左子节点, 右子节点
// 设置一个用于玩下遍历和判断的子节点, 默认就是左边的儿子
int child = 2 * i + 1;
while (child <= end){
//如果右子节点比左边大
int leftson = child;
int rightson = child + 1;
if(rightson <= end && a[rightson] > a[leftson]){
//就设置为右边的儿子
child++;
}
//再比较父子,如果儿子比父亲大,就互换
if(a[i] < a[child]){
swaparr(a,i,child);
}
//继续循环
i = child;
//继续选择它的左儿子
child = 2 * i + 1;
}
}
}
private static void swaparr(int[] arr,int a,int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
二十二、Spring常用注解
注解名称 | 解释 |
---|---|
@Component | 泛指各种组件 |
@Controller、@Service、@Repository | 都可以称为@Component |
@Controller | 控制层 |
@Service | 业务层 |
@Repository | 数据访问层 |
二十三、final 在 java 中有什么作用?
- 修饰变量:表示变量只能一次赋值以后值不能被修改(常量);
- 修饰方法:表示方法不能被重写;
- 修饰类:表示该类不能被继承;
比如常用的 String 类就是最终类。
二十四、Java 中 Map 有哪些实现类和使用场景
1、HashMap
HashMap默认初始容量是16,阈值是原容量的0.75,达到阈值后HashMap开始扩容,扩容为原来的2倍,以此类推。
HashMap 什么时候进行扩容呢?
当HashMap的元素个数超过HashMap当前大小乘阈值的值的时候就会进行扩容,扩容为原来的2倍;
HashMap中的元素个数 > HashMap当前长度(初始大小16) * 阈值(默认0.75)
你知道HashMap的工作原理吗?
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
当两个对象的hashcode相同会发生什么?
它们会储存在同一个bucket位置的LinkedList中。键对象的equals()方法用来找到键值对。
如果两个键的hashcode相同,你如何获取值对象?
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到LinkedList中正确的节点,最终找到要找的值对象。
2、HashTable
这个专门用于多线程场景下的map实现类,其大大优化了多线程下的性能。
HashMap 和 Hashtable 有什么区别?
- HashMap是线程不安全的,HashTable是线程安全的;
- HashMap中允许键和值为null,HashTable不允许;
- HashMap的默认容器是16,为2倍扩容,HashTable默认是11,为2n+1扩容;
3、ConcurrentHashMap
它不再使用和HashTable一样的synchronize一样的关键字对整个方法进行枷锁,而是转而利用segment段落锁来对其进行加锁,以保证Map的多线程安全。
其实可以理解为,一个ConcurrentHashMap是由多个HashTable组成
4、TreeMap
因为红黑树查找效率高,只有O(lgn)。它是一种自平衡的二叉查找树。在每次插入和删除节点时,都可以自动调节树结构,以保证树的高度是lg(n)。
5、LinkedHashMap
LinkedHashMap它的特点主要在于linked,带有这个字眼的就表示底层用的是链表来进行的存储。
6、weakHashMap
如果当其中一个Key-Value不再使用被回收时,就将其加入ReferenceQueue队列中。当下次再次调用该WeakHashMap时,就会去更新该map,比如ReferenceQueue中的key-value,将其中包含的key-value全部删除掉。这就是所谓的“自动删除”。
二十五、Java 中 Set 有哪些实现类?
1、HashSet
- HashSet是set接口的实现类,set下面最主要的实现类就是HashSet(也就是用的最多的),此外还有LinkedHashSet和TreeSet。
- HashSet是无序的、不可重复的。通过对象的hashCode和equals方法保证对象的唯一性。
- HashSet内部的存储结构是哈希表,是线程不安全的。
2、TreeSet
TreeSet对元素进行排序的方式:
- 元素自身具备比较功能,需要实现Comparable接口,并覆盖compareTo方法。
- 元素自身不具备比较功能,需要实现Comparator接口,并覆盖compare方法。
3、LinkedHashSet
LinkedHashSet是一种有序的Set集合,即其元素的存入和输出的顺序是相同的。
二十六、栈和队列的区别
- 队列 是先进先出,有出口和入口,先进去可以先出来。
- 栈 就像一个箱子,后放上去的,可以先出来
- 二者的遍历数据速度不同。
二十七、GET 和 POST 的区别
- get 请求参数是连接在url后面的,而 post 请求参数是存放在requestbody内的;
- get 请求因为浏览器对url长度有限制,所以参数个数有限制,而 post 请求参数个数没有限制;
- 因为 get 请求参数暴露在url上,所以安全方面 post 比get更加安全;
- get 请求只能进行url编码,而 post 请求可以支持多种编码方式;
- get 请求参数会保存在浏览器历史记录内,post 请求并不会;
- get 请求浏览器会主动 cache,post 并不会,除非主动设置;
- get 请求产生1个tcp数据包,post请求产生2个tcp数据包;
- 在浏览器进行回退操作时,get 请求是无害的,而 post 请求则会重新请求一次;
- 浏览器在发送 get 请求时会将header和data一起发送给服务器,服务器返回200状态码,而在发送 post 请求时,会先将header发送给服务器,服务器返回100,之后再将data发送给服务器,服务器返回200 OK;
二十八、什么是控制反转(IOC)?什么是依赖注入?
借助Spring实现具有依赖关系的对象之间的解耦。
对象A运行需要对象B,由主动创建变为IOC容器注入,这便是控制反转。
获得依赖对象的过程被反转了,获取依赖对象的过程由自身创建变为由IOC容器注入,这便是依赖注入。
二十九、Spring中都应用了哪些设计模式?
1、简单工厂模式
简单工厂模式的本质就是一个工厂类根据传入的参数,动态的决定实例化哪个类。
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象。
2、工厂方法模式
应用程序将对象的创建及初始化职责交给工厂对象,工厂Bean。
定义工厂方法,然后通过config.xml配置文件,将其纳入Spring容器来管理,需要通过factory-method指定静态方法名称。
3、单例模式
Spring用的是双重判断加锁的单例模式,通过getSingleton方法从singletonObjects中获取bean。
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
4、代理模式
Spring的AOP中,使用的Advice(通知)来增强被代理类的功能。Spring实现AOP功能的原理就是代理模式(① JDK动态代理,② CGLIB字节码生成技术代理。)对类进行方法级别的切面增强。
5、装饰器模式
装饰器模式:动态的给一个对象添加一些额外的功能。
Spring的ApplicationContext中配置所有的DataSource。这些DataSource可能是不同的数据库,然后SessionFactory根据用户的每次请求,将DataSource设置成不同的数据源,以达到切换数据源的目的。
在Spring中有两种表现:
一种是类名中含有Wrapper,另一种是类名中含有Decorator。
6、观察者模式
定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
Spring中观察者模式一般用在listener的实现。
7、策略模式
策略模式是行为性模式,调用不同的方法,适应行为的变化 ,强调父类的调用子类的特性 。
getHandler是HandlerMapping接口中的唯一方法,用于根据请求找到匹配的处理器。
8、模板方法模式
Spring JdbcTemplate的query方法总体结构是一个模板方法+回调函数,query方法中调用的execute()是一个模板方法,而预期的回调doInStatement(Statement state)方法也是一个模板方法。
三十、数组和集合的区别
-
数组是大小固定的,一旦创建无法扩容;集合大小不固定;
-
数组的存放的类型只能是一种,集合存放的类型可以不是一种(不加泛型时添加的类型是Object);
-
数组的大小固定,而集合是可以动态扩展容量,可以根据需要动态改变大小;
-
数组不论是效率还是类型检查都是最好的;
三十一、简述 OSI 的体系结构,并简述每一层的作用
- 应用层(数据):确定进程之间通信的性质以满足用户需要以及提供网络与用户应用;
- 表示层(数据):主要解决用户信息的语法表示问题,如加密解密;
- 会话层(数据):提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制,如服务器验证用户登录便是由会话层完成的
- 传输层(段):实现网络不同主机上用户进程之间的数据通信,可靠与不可靠的传输,传输层的错误检测,流量控制等;
- 网络层(包):提供逻辑地址(IP)、选路,数据从源端到目的端的传输;
- 数据链路层(帧):将上层数据封装成帧,用MAC地址访问媒介,错误检测与修正;
- 物理层(比特流):设备之间比特流的传输,物理接口,电气特性等;
三十二、简述 TCP/IP 四层体系结构
-
应用层
HTTP、TELNET、FTP、SMTP
-
传输层
TCP、UDP
-
网络层
IP、ICMP
-
数据接口
PPP
三十三、TCP 和 UDP 有哪些不同?
TCP(Transmission Control Protocol,传输控制协议)提供的是面向连接,可靠的字节流服务。即客户和服务器交换数据前,必须现在双方之间建立一个TCP连接,之后才能传输数据。并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP(User Data Protocol,用户数据报协议)是一个简单的面向数据报的运输层协议。它不提供可靠性,不可靠,只是把应用程序传给IP层的数据报发送出去,但是不能保证它们能到达目的地。由于UDP在传输数据报前不用再客户和服务器之间建立一个连接,且没有超时重发等机制,所以传输速度很快。
不同点:
- 报头不同
- 特点不同
- 协议不同
TCP三次握手过程:
- 主机A通过向主机B 发送一个含有同步序列号标志位的数据段(SYN)给主机B ,向主机B 请求建立连接,通过这个数据段,主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。
- 主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用哪个序列号作为起始数据段来回应我。
- 主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:“我已收到回复,我现在要开始传输实际数据了”。
这样3次握手就完成了,主机A和主机B 就可以传输数据了。
TCP四次挥手过程:
- 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求。
- 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1。
- 由B 端再提出反方向的关闭请求,将FIN置1。
- 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束。