文章目录
- hashCode方法有什么特点?
- Object类的clone方法有什么特点?
- 八种数据结构?
- equals方法有什么特点?
- 集合体系?
- HashSet去重原理?
- HashSet的扩容原理?
- 负载因子对HashSet扩容的影响?
- Vector和ArrayList的区别?
- ArrayList扩容原理是怎么样?
- Stack和Vector有什么区别?
- LinkedList和ArrayQueue有什么特点?
- Hashtable与HashMap的区别?
- 泛型的继承?
- 泛型的通配符有好处呢?
- 泛型的上下边界是什么?
- JVM内存划分为几大块?
- 成员变量和局部变量有什么区别?
- 类变量和实例变量的区别?
- 被static修饰的方法有什么特点?
- 构造方法有哪些特点?
- 强制转换有哪些特点?
- Java自动类型转换有哪些特点?
- 什么是方法的重载?
- 短路与和与运算有什么区别?
- Java的跨平台原理是怎么样的?
- 原码和反码和补码的关系是怎么样的?
- Java的基本数据类型有哪几个?以及所占用的空间是多少?
- 谈谈你对JavaSE和JavaEE以及JavaME的理解?
- JDK和JRE和JVM有和区别?
- 自动类型转换有哪些特点?
- a++和++a有什么区别?
- 什么是位运算?
- 权限修饰符有哪些?
- 抽象类有哪些特点?
- 权限修饰符和方法的重写有关系吗?
- final关键字有何作用?
- 被static修饰的方法能否继承或重写呢?
- 接口有什么特点?
- 什么是多态的向上转型和向下转型?
- String类中常有的方法有哪些?
- 常量池有什么特点?
- Scanner的next与nextLine有什么区别?
- Object类中有哪些方法?
- Object中的equals和toString有什么特点?
- String和StringBuilder有何区别?
- BigInteger和BigDecimal的区别?
- 什么是自动拆装箱?
- hashCode方法有什么特点?
- Object类的clone方法有什么特点?
- 八种数据结构?
- 单列集合有哪些派系?
- equals方法有什么特点?
- 集合体系?
- HashSet去重原理?
- HashSet的扩容原理?
- 负载因子对HashSet扩容的影响?
- Vector和ArrayList的区别?
- ArrayList扩容原理是怎么样?
- Stack和Vector有什么区别?
- LinkedList和ArrayQueue有什么特点?
- Hashtable与HashMap的区别?
- 泛型的继承?
- 泛型的通配符有好处呢?
- 泛型的上下边界是什么?
- Java中异常的体系?
- JVM在遇到异常时默认的处理方式时怎么样的?
- Java处理异常有哪些?
- 在继承时异常的特点?
- 什么是泛型的擦除?有何特点?
- 谈谈你对线程和进程的理解?
- 多线程就一定快吗?
- 同步方法和静态同步方法的锁对象是什么?
- 并行和并发有什么特点?
- 什么是线程的死锁?
- 线程的生命周期?
- wait方法和sleep方法有什么区别?
- wait和park的区别?
- 字符流可以拷贝非文本文件吗?
- IO流的体系是怎么样的?
- AutoCloseable接口有何作用?
- 字符流和字节流有什么区别?
- 字符流能否拷贝非文本文件?
- 关闭和刷新的区别?
- 序列化流有特点?
- 缓冲流有何特点?
- TCP和UPD的特点?
- TCP报文中包含有哪些字段?
- 类加载器有哪几种?
- 注解的生命周期?
- 谈谈你对反射的理解?
- Java的元注解哪些?
- 枚举有什么特点?
- 类加载机制的双亲委派?
- 如何打破双亲委派?
- 类在何时会被加载?
- 动态代理有什么好处?
hashCode方法有什么特点?
- 概念:hashCode方法属于Object类中的native修饰的方法,该方法用于获取对象的hash值,目的也是为了对比两个对象
- hash冲突:默认情况下是根据对象的内存地址值来计算,通常情况下,如果两个对象的内存地址值不一样,那么两个对象的hash值就不一样,但hash算法有缺陷,有的时候两个对象的内存地址值不一样,但是计算出来的hash值一样,我们把这种情况成为hash冲突(hash碰撞);
- 如果两个对象不一样,那么两个对象的hash值有可能一样(hash冲突)
- 但是两个对象的hash值如果不一样,那么两个对象绝对不一样(因为hash算法是固定的,同一个对象不管计算多少次,hash值肯定一样)
- 重写hashCode:我们实际开发中,判断两个对象是否一致的依据通常不是内存地址值,因此我们的hashCode的计算依据也不应该是内存地址值,所以一般我们会重写对象的hashCode,让hashCode的计算依据变为对象的属性值,我们通常希望如果两个对象的属性值不一样,那么hashCode就应该不一样
- 使用hash值判断两个对象是否一致:
- 先对比hashCode
- 一样:不能说明两个对象一样,有可能是hash冲突,这个时候需要调用equals方法
- 调用equals返回true:说明两个对象真的一样
- false:就是出现了hash冲突
- 不一样:能说明两个对象不一样
- 一样:不能说明两个对象一样,有可能是hash冲突,这个时候需要调用equals方法
- 先对比hashCode
- 其他应用:hashCode的方法对HashSet、HashMap等集合的存储、去重、元素排列都用着重要的影响
Object类的clone方法有什么特点?
- 概念:用于克隆对象用的,克隆出来的对象和源对象的属性值是一样的,但是内存地址值不一样
- 应用:clone方法是被protected、native修饰,只能在子类中调用,因此我们需要重写这个方法,但是功能还是用的是父类的,通过super去调用父类的clone方法
- 注意事项:被克隆的对象必须要实现Cloneable接口,否则出现异常
八种数据结构?
-
线性:
- 数组:查询快,增删慢
- 链表:查询慢,增删快,内存占用相对较大
- 队列:先进先出
- 栈:先进后出
-
非线性:
-
堆:数组实现的二叉树,可以堆属性堆数组进行排序
- 大根堆:父节点比子节点大
- 小根堆:父节点比子节点小
-
散列表:数组+链表
-
树:右子树比左子树,读取顺序是从左到右
-
图:
- 有向图:有指向的图
- 无向图:无指向的图
-
equals方法有什么特点?
- 概念:equals方法属于Object类中方法,该方法用于比较两个对象
- 实际使用:默认情况下,equals方法对比的是两个对象的内存地址值,在实际应用中,我们不希望两个对象的比较依据是内存地址值,我们更加希望能够以两个对象的属性值作为比较依据,因此实际开发中,我们都会重写equals方法,让比较的依据变为属性值的比较
- 其他应用:比较有参考一样的就是Java中的String类,因为使用字面量和new关键字来创建字符串时,两个字符串的内容是一致的,但是内存地址值却不是一致的,我们比较字符串的依据大部分都是以字符串内容为准,因此Java对String中的equlas方法进行了重写,比较的是字符串里面的字符
- 集合框架方法的应用:equals对HashSet、HashMap等元素的去重提供了重要保障,底层有依赖于equlas方法来对比两个对象是否是一致的
集合体系?
-
单列集合
-
Collection
- List
- ArrayList
- 1)底层是数组,查询快,增删慢
- 2)默认初始化大小是10,扩容是1.5扩容,底层扩容采用的System.copy,扩容效率高
- LinkedList
- 1)底层采用链表(双向链表)
- 2)增删快,查询慢
- 3)LinkedList是属于List和Queue两大派系的,其中属于Queue下面的Deque(双向队列),因此LinkedList中具备了索引相关API,还具备队列相关的API,由于底层是链表,还具备链表相关的操作(头和尾)
- Vector
- 1)底层采用数组,查询快,增删慢
- 2)默认初始化大小,10,2倍扩容,与ArrayList相比是线程安全的,效率较低
- Stack
- 1)继承与Vector,扩容机制和底层原理和Vector保持一致
- 2)提供了许多栈相关的操作,比如压栈,弹栈等,可以看作Stack是数组来是实现的栈,LinkedList则是由链表实现的栈;
- ArrayList
- Set:
- HashSet
- 1)无序,唯一
- 2)底层采用散列表(数组+链表),但是在JDK1.8做了改进,加了红黑树来优化链表
- 3)底层原理
- 1)默认初始化数组大小16,底层采用负载因子来控制数组的扩容,负载因子是0.75
- 负载因子=集合中实际存储的元素/数组的长度
- 3)关于hash冲突
- 1)存储元素时,先通过元素的hashCode方法获取到hash值,然后取余散列表中数组的长度,得到一个槽位,来存储;
- 2)关于String对于hashCode的算法:
31*上一次的hash值+字符的ASCII
- 3)关于Hash算法的特点:
- 1)有时候不同的两个对象计算出来的hash值是相同的;
- 2)如果是不同的hashCode,则两个对象肯定不同
- 4)关于hash冲突本身
- 1)不同的元素计算出同一个hash值,这种情况调用元素本身的equlas来对比,看是否存储这个元素
- 2)不同的元素计算出不同的hash值,hash值取余数组的长度,得到同一个槽位,hashSet采用拉链法拼接到下一个元素形成链表
- 4)关于底层扩容
- 1)通过负载因子来扩容数组长度,负载因子默认0.75,数组长度默认16;HashSet运行我们自己自定义负载因子和数组长度
- 2)当数组长度大于等于64并且链表长度大于等于8时,将链表转换为红黑树,这个时候查询速度能够提升,当红黑树的元素个数小于等于6时,红黑树将转换为链表,这个时候增删性能能够提升;
- 3)当存储的元素到达一定程度时(符合负载因子),那么将会进行数组的扩容,为2倍
- TreeSet
- 1)存取无序,但支持排序,底层采用红黑树算法
- 2)存储的元素必须实现Compreable接口
- 3)元素的排列通过compareTo方法的返回值来决定
- 1)正数:存储在这棵树的右边
- 2)负数:存储在这棵树的左边
- 3)0:不存储这个元素
- 4)读取的顺序:从左到右
- LinkedHashSet
- 1)底层采用散列表+链表
- 2)存取有序,元素唯一,其他原理和HashSet一致
- 3)由于LinkedHashSet底层具备了一个链表(有序),因此在迭代访问时速度会比较快,但增删速度相对于HashSet较慢;
- HashSet
- Queue:
- 1)Queue本身就是一个队列(单项队列),Queue接口中存在很多关于队列的一些操作,offer相关方法
- 2)关于Queue旗下有两大派系
- 1)Deque:双向队列,在Java中关于链表,队列,栈这些数据结构其实现方式大多都是采用数组或者是链表来实现;
- 1)LinkedList:采用链表实现的双向队列,线程不安全
- 2)ArrayDeque:采用数组实现的双向队列
- 1)底层采用数组
- 2)默认初始化大小16,每次扩容2倍
- 3)线程不安全,效率高
- 4)不能存储null值,支持双向迭代
- 2)BlokingQueue:延迟队列
- 1)Deque:双向队列,在Java中关于链表,队列,栈这些数据结构其实现方式大多都是采用数组或者是链表来实现;
- 2)其下面有一个Deque子接口(双向队列)
- List
-
Map:
- HashMap:HashSet的底层就是HashMap
- LinkedHashMap:LinkedHashSet的底层就是LinkedHashMap
- TreeMap:TreeHashSet的底层就是TreeHashMap
- Hashtable:和HashMap,底层也是散列表
-
HashSet去重原理?
- 比较HashCode
- 一样:调用equals
- true:不存
- false:存
- 不一样:存
- 一样:调用equals
HashSet的扩容原理?
- 1)通过负载因子来扩容数组长度,负载因子默认0.75,数组长度默认16;HashSet运行我们自己自定义负载因子和数组长度
- 2)当存储的元素到达一定程度时(符合负载因子),那么将会进行数组的扩容,为2倍
- 3)当数组长度大于等于64并且链表长度大于等于8时,将链表转换为红黑树,这个时候查询速度能够提升,当红黑树的元素个数小于等于4时,红黑树将转换为链表,这个时候增删性能能够提升;
负载因子对HashSet扩容的影响?
- 默认情况下,HashSet的负载因子为0.75,负载因子 = 当前实际存储的元素个数 / 数组长度
- 如果负载因子越大:代表HashSet存储元素时造成Hash冲突概率越大,但HashSet内存空间得以很好的利用,采取时间换空间
- 如果负载因子越小:代表HashSet存储元素时造成Hash冲突概率越小,但会造成过多的内存空间浪费,采取空间换时间
Vector和ArrayList的区别?
- 相同点:
- 1)底层都是数组
- 2)默认大小都是10
- 不同点:
- 1)Vector扩容是2倍,ArrayList扩容是1.5倍
- 2)Vector是线程安全,ArrayList是线程不安全
- 3)Vector支持Enumration迭代,ArrayList不支持,Enumration迭代运行迭代时对集合进行增删
ArrayList扩容原理是怎么样?
- 底层是数组,查询快、增删慢
- 默认分配的大小10,也支持用户自定义数组大小
- 当ArrayList发生扩容时,默认按照1.5倍扩容
- ArrayList是线程不安全的,效率较高,但由于里面的方法都是线程不安全,包括扩容方法,因此在多线程扩容时会出现线程安全问题
Stack和Vector有什么区别?
- 相同点:Stack是继承与Vector,所以在底层原理、扩容、线程安全问题等都是一模一样的
- 不同点:Stack提供了一些关于栈的操纵,我们可以把Stack当做一个栈这种数据结构来使用,Stack是数组实现的栈
LinkedList和ArrayQueue有什么特点?
- 相同点:
- 1)都是Queue下的Deque的实现类,两个都是双向队列
- 2)都是二代集合,都是线程不安全
- 不同点:
- 1)LinkedList底层是链表,ArrayQueue底层是数组
- 2)LinkedList属于量大派系的实现类,即属于List也属于Queue,ArrayQueue只属于Queue派系
Hashtable与HashMap的区别?
- 1)Hashtable属于一代集合,继承Dictinoary类,同时也实现了Map接口,HashMap实现Map接口的
- 2)Hashtable线程安全的,HashMap线程不安全
- 3)JDK1.8对HashMap进行了优化,但是对Hashtable并没有优化
- 4)HashMap默认数组长度16,但是Hashtable是11,两者的负载因子0.75
- 5)HashMap数组扩容是2倍,Hashtable数组扩容是2倍+1
- 6)HashMap存储元素时,采用的算法是:
元素本身的hash算法 ^ (元素本身的hash算法 >>> 16)
,但是Hashtable采用的是元素本身的hash算法 - 7)HashMap运行存储null key和null value,但Hashtable不允许
- 8)HashMap不支持Enumeration迭代,Hashtable支持,并且Hashtable在使用Enumeration迭代元素时,运行对集合进行增删;
泛型的继承?
泛型:把类型明确的工作留到创建对象或者是调用方法时来明确;
- 1)在继承时确定泛型的类型
- 2)在继承时,连泛型一起继承下来,等到创建对象时再来确定泛型类型
class Fu<E>{}
// 在继承的时候选择把泛型的类型一起继承下来,这样创建子类对象的时候还可以明确泛型的类型
class Zi<E> extends Fu<E>{}
// 在继承的时候确定泛型的类型,泛型的类型已经被确定死了,不可以再更改了
class Zi extends Fu<Person>{}
泛型的通配符有好处呢?
在方法传参时,不仅要匹配形参本身,还要匹配形参的泛型类型,在匹配形参泛型类型时不存在多态;
泛型通配符运行在方法传参时,仅仅匹配形参本身就行,泛型类型不用匹配(任意泛型类型都可以)
例如:
method(A<Integer> a){}
method(A<Number> a){}
这两个方法是一模一样的,在编译期间会报错的;
泛型的上下边界是什么?
-
关于泛型擦除:泛型只存在于编译期间,运行期间都会被擦除掉,这也会引起一些问题,如关于泛型桥接方法等问题…
-
关于传递参数:我们在方法调用时,不仅要匹配方法参数本身,还要匹配参数的泛型类型,并且泛型类型不存在子父类等问题
-
关于泛型通配符:因此我们可以使用泛型通配符来接收任意泛型类型,在方法传参时只需要匹配参数本身即可,参数的泛型类型任意,单这会引发一个问题,那就是泛型的类型统统提升为Object类型,并且泛型通配符太广泛了,任意的的泛型类都可以往里面传递
-
泛型的上下边界:泛型的上下边界就是来约定在方法调用时,参数的泛型的类型传参问题
- 上边界:
? extends E
,代表只能传递泛型类型本身或者E类型的子类 - 下边界:
? super E
,代表只能传递泛型类型本身或者E类型的父类 - 有了上下边界约定之后,不仅在传递参数上面做了优化,在使用上边界的情况下,使用泛型类型时都会提升为E类型,不至于提升为Object类型
- 上边界:
JVM内存划分为几大块?
- 1)方法区:类的基本信息
- 1)关于类的:类名、类的权限修饰符、类的构造方法、构造方法的权限修饰符、构造方法的参数列表
- 2)关于方法:类有哪些方法、方法的权限修饰符、方法名、方法参数、方法返回值类型
- 3)关于成员变量的:成员变量的名称、权限修饰符
- 4)关于静态内容:被static修饰的成员的内容都存储在方法
- 2)堆内存:new出来的东西
- 1)对象本身销毁的内容、包括数组等
- 2)对象内部的成员变量的值
- 3)常量池
- 3)栈内存:方法调用消耗的内存
- 1)局部变量
- 4)本地方法栈:执行本地方法消耗的内存
- 5)程序计数器:存储下一条指令的执行地址
成员变量和局部变量有什么区别?
- 成员变量
- 1)定义在类中方法外的变量
- 2)可以使用权限修饰符修饰
- 3)成员变量有默认值
- 4)成员变量是存储在堆内存
- 5)成员变量是随着对象的创建而存在,对象的销毁而销毁
- 局部变量
- 1)方法内部的变量
- 2)不可以使用权限修饰符修饰
- 3)没有默认值,使用前必须要赋值
- 4)存储在栈内存
- 5)随着方法的调用而存在,方法的结束而销毁
类变量和实例变量的区别?
- 类变量:
- 1)被static修饰
- 2)随着类的加载而加载
- 3)所有对象共享类变量,类变量只会存在一份,存储在方法区
- 实例变量
- 1)没有被static修饰符的变量
- 2)随着对象的创建而加载
- 3)每次创建一个新的对象,实例变量都会存在一份新的,存储在堆内存
被static修饰的方法有什么特点?
- 1)随着类的加载而加载
- 2)不能使用this
- 3)不能使用普通成员变量和成员方法
构造方法有哪些特点?
- 1)在创建对象的时候,必定会调用这个对象的构造方法来初始化;
- 2)构造方法不能被继承
- 3)每个类都有一个默认的无参构造方法,JDK提供的,当你提供了其他构造方法时,那么默认的构造方法就会没有;
- 4)每个构造方法的第一句代码有一行super(),代表调用父类的无参构造函数,如果父类没有无参构造函数,就会报错
- 5)在初始化子类的时候,首先应该先初始化父类;加载父类—>加载子类—>初始化父类—>初始化子类
强制转换有哪些特点?
概念:把取值范围大的转换为取值范围小到,需要强制转换
- 整数之间的强制转换
- 转换之后的数据类型,能不能存储的下这个数据
- 能:转换没有问题
- 不能:砍去前面几个字节,数据丢失
- 转换之后的数据类型,能不能存储的下这个数据
- 浮点浮点之间的转换
- 转换之后的数据类型,能不能存储的下这个数据
- 能:转换没问题
- 不能:超出容量大小限制(Infinity)
- 转换之后的数据类型,能不能存储的下这个数据
- 浮点与整数之间的转换:一定会精度丢失
- 转换之后的数据类型,能不能存储的下这个数据
- 能:丢失精度而已
- 不能:
- 转成long/int:直接转换为long/int的最大值
- 转成short/bye:首先会转换为int类型,要看这个数是否超出了int的取值范围
- 超出了int的取值范围:-1
- 没有超出int的取值范围:从int转为short/bye,砍去前面几个字节,造成数据丢失
- 转换之后的数据类型,能不能存储的下这个数据
1)将数据类型大的转换为小的时候,需要强制转换(可能会丢失精度)
2)i++,i+=,i=i+1;前面两个带有强制类型转换功能,后面没有;
Java自动类型转换有哪些特点?
1)将数据类型范围大的 转换为 数据类型范围小的,就需要强制类型转换
1)浮点转换为整数,会丢失精度;
2)如果数据大小超出转换的范围,那么会数据丢失(异常)
2)+=、++等运算带有强制转换功能
什么是方法的重载?
方法名相同,参数列表不同;
public static void open(){}
public static void open(int a){}
static void open(int a,int b){}
public static void open(double a,int b){}
public static void open(int a,double b){}
public void open(int i,double d){}
public static void OPEN(){}
public static void open(int i,int j){}
短路与和与运算有什么区别?
短路与在第一个条件为false时,后面的运算就不会执行(效率高)
与运算不管前面的条件是否为false,会去执行后面所有的运算
Java的跨平台原理是怎么样的?
- 跨平台:一次编译,到处运行,同一份Java代码,在任意平台上编译一次后,可以到任意操纵系统平台执行
- 实现:由JVM来实现,Sun公司在目前主流的操作系统平台上都开发了一套对应的JVM虚拟机,我们编写的代码最终是执行在JVM上,最终由JVM和操作系统来通信;我们遵守Java的规范,由JVM来遵守操作系统的规范,不同平台的JVM会将Java字节码翻译成不同平台对应的指令,跨平台只需要在不同的操纵系统平台安装对应的JVM虚拟机即可;
原码和反码和补码的关系是怎么样的?
- 原码:十进制在二进制中的表现形式,包含符号位(最高位),最高位为1,代表是负数,0代表是正数;一个字节的取值范围-128~127
- 反码:
- 正数:原码本身
- 负数:最高位不变,其余取反
- 补码:
- 正数:原码本身
- 负数:反码+1
Java的基本数据类型有哪几个?以及所占用的空间是多少?
- int(4)
- char(2)
- double(8)
- float(4)
- short(2)
- long(8)
- boolean(1)
- byte(1)
谈谈你对JavaSE和JavaEE以及JavaME的理解?
- JavaSE:桌面应用
- JavaEE:开发企业级应用(分布式,云服务,大数据,数据挖掘,数据分析,WEB网站)
- JavaME:移动开发(手机端)
JDK和JRE和JVM有和区别?
- JDK:Java开发工具包,包含JRE和一些其他的代码调试工具,编译工具,诊断工具(调试工具)
- JRE:Java运行环境,包含JVM和一些Java提供的类库
- JVM:Java虚拟机,Java代码最终运行的平台,对运行时的Java程序进行管控,内存分配,管理,线程调度管理,垃圾回收管理;
自动类型转换有哪些特点?
- 1)数据类型小的与数据类型大的发生运算时,其结果是数据类型大的
- 2)int以下的数据类型发生运算时,其结果为int类型
- 3)整数和小数发生运算时,其结果是小数类型
- 4)小数类型默认是double,整数类型默认为int类型
1)数据类型小的与数据类型大的发生运算时,其结果自动转换为数据类型大的
2)整数与小数相加,其结果是小数类型
3)int类型以下的数据类型相加其结果都是int(只有在变量发生运算时)
4)+=、++等运算带有强制转换功能
a++和++a有什么区别?
- 1)a++是先赋值再自增
- 2)++a是先增长再赋值
- 3)如果两个都是单独运算,没有区别
- 4)不管是++a还是a++都带有强制类型转换功能
什么是位运算?
即与二进制的位移运算,分为有符号位移和无符号位移,其中有符号位移含有左右位移,无符号位移只有右位移;
- 有符号位移:
- 左位移:二进制整体往左移动,右边补0,口诀:每往左移动一位,就乘以2的N次方
- 右位移:二进制整体往右移动,口诀:每往右移动一位,就除以2的N次方
- 正数:高位(左边)补0,
- 负数:高位(左边)补1,
- 有符号位移:只包含右位移,无论正数还是负数都是高位(左边)补0,口诀:每往右移动一位,就除以2的N次方
权限修饰符有哪些?
- public:任意包的任意类都可以访问
- protected:任意包的子类
- default:同包下的任意类
- private:本类
抽象类有哪些特点?
- 1)抽象类不能实例化,不能创建对象
- 2)抽象类可以没有抽象方法,但是抽象方法必须在抽象类中
- 3)抽象类可以有构造方法,是留给子类用的
- 4)在继承抽象类的时候,必须重写里面的所有的抽象方法,不然这个类要变成抽象类
权限修饰符和方法的重写有关系吗?
有关系,子类方法的权限修饰符要大于等于重写的那个方法的权限修饰符;
final关键字有何作用?
- 1)final修饰的成员是常量,不可以修改
- 2)final修饰的方法不能被重写
- 3)final修饰的类不能被继承
被static修饰的方法能否继承或重写呢?
- 1)可以被继承
- 2)可以被重写,重写时,子类方法必须也要是static修饰
接口有什么特点?
- 1)接口中的成员变量为public static final修饰
- 2)接口中的方法为public abstract
- 3)接口中没有构造方法,不能被实例化
- 4)接口中的静态方法(JDK8)不能被继承,静态变量可以被继承
- 5)接口与接口允许多继承
- 6)接口中没有静态代码块
- 7)实现接口时,需要重写其所有的抽象方法
- 8)JDK8推出默认方法和静态方法,JDK9推出了私有方法,由于默认方法的存在(被继承),会导致多实现接口时的钻石问题,JDK默认需要子类强制重写来解决这个问题;
什么是多态的向上转型和向下转型?
- 1)向上转型:使用父类引用接收子类对象
- 1)如果子类和父类出现同名方法,调用该方法时:调用子类的
- 2)如果子类存在该方法时,父类不存在,调用该方法时:报错
- 好处:提升代码的扩展性
- 2)向下转型:将父类强制转换为子类对象(父类原本就是该对象)
- 好处:可以使用子类独有的功能
String类中常有的方法有哪些?
- 1)equals
- 2)equalsIgoreCase
- 3)length
- 4)charAt
- 5)indexOf
- 6)subString
- 7)spilt
常量池有什么特点?
- 1)常量池也是堆内存中的一块区域,常量池的数据只会存在一次;
- 2)字符串串存储
- 1)new出来的字符串都存储在堆内存
- 2)采用字面量方式创建的字符串都存储在常量池
- 3)常量与常量相拼接的结果存储在常量池
- 4)常量与变量相拼接的结果存储在堆内存
Scanner的next与nextLine有什么区别?
- 1)nextLine会接收回车符与空格符
- 2)next()方法不会接收回车符与空格符
Object类中有哪些方法?
equals
toString
hashCode
wait
notifly
notiflyAll
clone
finalize
getClass
Object中的equals和toString有什么特点?
Object类是所有类的父类,因此Object中的方法所有类都会具备
- 1)equals:
- 默认情况下,Object类中的equals方法比较的是内存地址值,子类可以选择重写或者不重写
- 2)toString
- 当输出一个对象时,默认调用这个对象的toString
- 默认情况下,Object类中的toString输出的是这个对象的内存地址值,我们一般希望输出这个对象的内容,因此我们一般会重写toString方法;
String和StringBuilder有何区别?
- 1)StringBuilder内置一个字符缓冲区(char[]),当追加字符串时并不会产生新的字符串,而是将字符串存储到字符缓冲区中,并且SpringBuilder可以自动扩容;
- 2)String在拼接任意字符串时都会产生新的字符串,在大量字符串拼接时,拼接速度慢,拼接占用的内存大
BigInteger和BigDecimal的区别?
-
1)BigInteger和BigDecimal针对于大数据量的计算,都可以根据字符串来构建
-
2)BigInteger:
- 1)只能描述整数类型,不可以描述小数
- 2)计算结果不能为小数
-
2)BigDecimal:
- 1)包含BigInteger的功能
- 2)可以描述小数
- 3)计算结果可以为小数,但是如果是无限小数的,要通过一些策略来调整
什么是自动拆装箱?
自动拆装箱是JDK1.5推出的新特性,包含拆箱与装箱;在JDK1.5之前基本数据类型转换为引用数据类型需要借助引用数据类型的构造方法,如new Integer(1)
,才能完成装箱,如果是猜想需要借助对应的value方法,如num.intValue()
。这样基本数据类型在于引用数据类型发生运算的时候极为不便,另外,Java集合中只能存储引用数据类型,我们在存储基本数据类型时都需要完成手动装箱。推出了自动拆装箱后,集合中也能存储基本数据类型,基本数据类型与引用数据类型也可以直接参与运算;
-
1)是JDK1.5的新特性
-
2)装箱:基本数据类型自动转换为包装类
-
自动装箱: Integer num = 10; 手动装箱: Integer num = new Integer(10); Integer num = Integer.valueOf(10);
-
-
3)拆箱:包装类自动转换为对应基本数据类型
-
自动拆箱 int num2 = new Integer(10); 手动拆箱: int a = num.intValue();
-
hashCode方法有什么特点?
- 概念:hashCode方法属于Object类中的native修饰的方法,该方法用于获取对象的hash值,目的也是为了对比两个对象
- hash冲突:默认情况下是根据对象的内存地址值来计算,通常情况下,如果两个对象的内存地址值不一样,那么两个对象的hash值就不一样,但hash算法有缺陷,有的时候两个对象的内存地址值不一样,但是计算出来的hash值一样,我们把这种情况成为hash冲突(hash碰撞);
- 如果两个对象不一样,那么两个对象的hash值有可能一样(hash冲突)
- 但是两个对象的hash值如果不一样,那么两个对象绝对不一样(因为hash算法是固定的,同一个对象不管计算多少次,hash值肯定一样)
- 重写hashCode:我们实际开发中,判断两个对象是否一致的依据通常不是内存地址值,因此我们的hashCode的计算依据也不应该是内存地址值,所以一般我们会重写对象的hashCode,让hashCode的计算依据变为对象的属性值,我们通常希望如果两个对象的属性值不一样,那么hashCode就应该不一样
- 使用hash值判断两个对象是否一致:
- 先对比hashCode
- 一样:不能说明两个对象一样,有可能是hash冲突,这个时候需要调用equals方法
- 调用equals返回true:说明两个对象真的一样
- false:就是出现了hash冲突
- 不一样:能说明两个对象不一样
- 一样:不能说明两个对象一样,有可能是hash冲突,这个时候需要调用equals方法
- 先对比hashCode
- 其他应用:hashCode的方法对HashSet、HashMap等集合的存储、去重、元素排列都用着重要的影响
Object类的clone方法有什么特点?
- 概念:用于克隆对象用的,克隆出来的对象和源对象的属性值是一样的,但是内存地址值不一样
- 应用:clone方法是被protected、native修饰,只能在子类中调用,因此我们需要重写这个方法,但是功能还是用的是父类的,通过super去调用父类的clone方法
- 注意事项:被克隆的对象必须要实现Cloneable接口,否则出现异常
八种数据结构?
-
线性:
- 数组:查询快,增删慢
- 链表:查询慢,增删快,内存占用相对较大
- 队列:先进先出
- 栈:先进后出
-
非线性:
-
堆:数组实现的二叉树,可以堆属性堆数组进行排序
- 大根堆:父节点比子节点大
- 小根堆:父节点比子节点小
-
散列表:数组+链表
-
树:右子树比左子树,读取顺序是从左到右
-
图:
- 有向图:有指向的图
- 无向图:无指向的图
-
单列集合有哪些派系?
Collection是单列集合的顶层接口
-
List
-
Set
-
Queue
equals方法有什么特点?
- 概念:equals方法属于Object类中方法,该方法用于比较两个对象
- 实际使用:默认情况下,equals方法对比的是两个对象的内存地址值,在实际应用中,我们不希望两个对象的比较依据是内存地址值,我们更加希望能够以两个对象的属性值作为比较依据,因此实际开发中,我们都会重写equals方法,让比较的依据变为属性值的比较
- 其他应用:比较有参考一样的就是Java中的String类,因为使用字面量和new关键字来创建字符串时,两个字符串的内容是一致的,但是内存地址值却不是一致的,我们比较字符串的依据大部分都是以字符串内容为准,因此Java对String中的equlas方法进行了重写,比较的是字符串里面的字符
- 集合框架方法的应用:equals对HashSet、HashMap等元素的去重提供了重要保障,底层有依赖于equlas方法来对比两个对象是否是一致的
集合体系?
-
单列集合
-
Collection
- List
- ArrayList
- 1)底层是数组,查询快,增删慢
- 2)默认初始化大小是10,扩容是1.5扩容,底层扩容采用的System.copy,扩容效率高
- LinkedList
- 1)底层采用链表(双向链表)
- 2)增删快,查询慢
- 3)LinkedList是属于List和Queue两大派系的,其中属于Queue下面的Deque(双向队列),因此LinkedList中具备了索引相关API,还具备队列相关的API,由于底层是链表,还具备链表相关的操作(头和尾)
- Vector
- 1)底层采用数组,查询快,增删慢
- 2)默认初始化大小,10,2倍扩容,与ArrayList相比是线程安全的,效率较低
- Stack
- 1)继承与Vector,扩容机制和底层原理和Vector保持一致
- 2)提供了许多栈相关的操作,比如压栈,弹栈等,可以看作Stack是数组来是实现的栈,LinkedList则是由链表实现的栈;
- ArrayList
- Set:
- HashSet
- 1)无序,唯一
- 2)底层采用散列表(数组+链表),但是在JDK1.8做了改进,加了红黑树来优化链表
- 3)底层原理
- 1)默认初始化数组大小16,底层采用负载因子来控制数组的扩容,负载因子是0.75
- 负载因子=集合中实际存储的元素/数组的长度
- 3)关于hash冲突
- 1)存储元素时,先通过元素的hashCode方法获取到hash值,然后取余散列表中数组的长度,得到一个槽位,来存储;
- 2)关于String对于hashCode的算法:
31*上一次的hash值+字符的ASCII
- 3)关于Hash算法的特点:
- 1)有时候不同的两个对象计算出来的hash值是相同的;
- 2)如果是不同的hashCode,则两个对象肯定不同
- 4)关于hash冲突本身
- 1)不同的元素计算出同一个hash值,这种情况调用元素本身的equlas来对比,看是否存储这个元素
- 2)不同的元素计算出不同的hash值,hash值取余数组的长度,得到同一个槽位,hashSet采用拉链法拼接到下一个元素形成链表
- 4)关于底层扩容
- 1)通过负载因子来扩容数组长度,负载因子默认0.75,数组长度默认16;HashSet运行我们自己自定义负载因子和数组长度
- 2)当数组长度大于等于64并且链表长度大于等于8时,将链表转换为红黑树,这个时候查询速度能够提升,当红黑树的元素个数小于等于6时,红黑树将转换为链表,这个时候增删性能能够提升;
- 3)当存储的元素到达一定程度时(符合负载因子),那么将会进行数组的扩容,为2倍
- TreeSet
- 1)存取无序,但支持排序,底层采用红黑树算法
- 2)存储的元素必须实现Compreable接口
- 3)元素的排列通过compareTo方法的返回值来决定
- 1)正数:存储在这棵树的右边
- 2)负数:存储在这棵树的左边
- 3)0:不存储这个元素
- 4)读取的顺序:从左到右
- LinkedHashSet
- 1)底层采用散列表+链表
- 2)存取有序,元素唯一,其他原理和HashSet一致
- 3)由于LinkedHashSet底层具备了一个链表(有序),因此在迭代访问时速度会比较快,但增删速度相对于HashSet较慢;
- HashSet
- Queue:
- 1)Queue本身就是一个队列(单项队列),Queue接口中存在很多关于队列的一些操作,offer相关方法
- 2)关于Queue旗下有两大派系
- 1)Deque:双向队列,在Java中关于链表,队列,栈这些数据结构其实现方式大多都是采用数组或者是链表来实现;
- 1)LinkedList:采用链表实现的双向队列,线程不安全
- 2)ArrayDeque:采用数组实现的双向队列
- 1)底层采用数组
- 2)默认初始化大小16,每次扩容2倍
- 3)线程不安全,效率高
- 4)不能存储null值,支持双向迭代
- 2)BlokingQueue:延迟队列
- 1)Deque:双向队列,在Java中关于链表,队列,栈这些数据结构其实现方式大多都是采用数组或者是链表来实现;
- 2)其下面有一个Deque子接口(双向队列)
- List
-
Map:
- HashMap:HashSet的底层就是HashMap
- LinkedHashMap:LinkedHashSet的底层就是LinkedHashMap
- TreeMap:TreeHashSet的底层就是TreeHashMap
- Hashtable:和HashMap,底层也是散列表
-
HashSet去重原理?
- 比较HashCode
- 一样:调用equals
- true:不存
- false:存
- 不一样:存
- 一样:调用equals
HashSet的扩容原理?
- 1)通过负载因子来扩容数组长度,负载因子默认0.75,数组长度默认16;HashSet运行我们自己自定义负载因子和数组长度
- 2)当存储的元素到达一定程度时(符合负载因子),那么将会进行数组的扩容,为2倍
- 3)当数组长度大于等于64并且链表长度大于等于8时,将链表转换为红黑树,这个时候查询速度能够提升,当红黑树的元素个数小于等于4时,红黑树将转换为链表,这个时候增删性能能够提升;
负载因子对HashSet扩容的影响?
- 默认情况下,HashSet的负载因子为0.75,负载因子 = 当前实际存储的元素个数 / 数组长度
- 如果负载因子越大:代表HashSet存储元素时造成Hash冲突概率越大,但HashSet内存空间得以很好的利用,采取时间换空间
- 如果负载因子越小:代表HashSet存储元素时造成Hash冲突概率越小,但会造成过多的内存空间浪费,采取空间换时间
Vector和ArrayList的区别?
- 相同点:
- 1)底层都是数组
- 2)默认大小都是10
- 不同点:
- 1)Vector扩容是2倍,ArrayList扩容是1.5倍
- 2)Vector是线程安全,ArrayList是线程不安全
- 3)Vector支持Enumration迭代,ArrayList不支持,Enumration迭代运行迭代时对集合进行增删
ArrayList扩容原理是怎么样?
- 底层是数组,查询快、增删慢
- 默认分配的大小10,也支持用户自定义数组大小
- 当ArrayList发生扩容时,默认按照1.5倍扩容
- ArrayList是线程不安全的,效率较高,但由于里面的方法都是线程不安全,包括扩容方法,因此在多线程扩容时会出现线程安全问题
Stack和Vector有什么区别?
- 相同点:Stack是继承与Vector,所以在底层原理、扩容、线程安全问题等都是一模一样的
- 不同点:Stack提供了一些关于栈的操纵,我们可以把Stack当做一个栈这种数据结构来使用,Stack是数组实现的栈
LinkedList和ArrayQueue有什么特点?
- 相同点:
- 1)都是Queue下的Deque的实现类,两个都是双向队列
- 2)都是二代集合,都是线程不安全
- 不同点:
- 1)LinkedList底层是链表,ArrayQueue底层是数组
- 2)LinkedList属于量大派系的实现类,即属于List也属于Queue,ArrayQueue只属于Queue派系
Hashtable与HashMap的区别?
- 1)Hashtable属于一代集合,继承Dictinoary类,同时也实现了Map接口,HashMap实现Map接口的
- 2)Hashtable线程安全的,HashMap线程不安全
- 3)JDK1.8对HashMap进行了优化,但是对Hashtable并没有优化
- 4)HashMap默认数组长度16,但是Hashtable是11,两者的负载因子0.75
- 5)HashMap数组扩容是2倍,Hashtable数组扩容是2倍+1
- 6)HashMap存储元素时,采用的算法是:
元素本身的hash算法 ^ (元素本身的hash算法 >>> 16)
,但是Hashtable采用的是元素本身的hash算法 - 7)HashMap运行存储null key和null value,但Hashtable不允许
- 8)HashMap不支持Enumeration迭代,Hashtable支持,并且Hashtable在使用Enumeration迭代元素时,运行对集合进行增删;
泛型的继承?
泛型:把类型明确的工作留到创建对象或者是调用方法时来明确;
- 1)在继承时确定泛型的类型
- 2)在继承时,连泛型一起继承下来,等到创建对象时再来确定泛型类型
class Fu<E>{}
// 在继承的时候选择把泛型的类型一起继承下来,这样创建子类对象的时候还可以明确泛型的类型
class Zi<E> extends Fu<E>{}
// 在继承的时候确定泛型的类型,泛型的类型已经被确定死了,不可以再更改了
class Zi extends Fu<Person>{}
泛型的通配符有好处呢?
在方法传参时,不仅要匹配形参本身,还要匹配形参的泛型类型,在匹配形参泛型类型时不存在多态;
泛型通配符运行在方法传参时,仅仅匹配形参本身就行,泛型类型不用匹配(任意泛型类型都可以)
例如:
method(A<Integer> a){}
method(A<Number> a){}
这两个方法是一模一样的,在编译期间会报错的;
泛型的上下边界是什么?
-
关于泛型擦除:泛型只存在于编译期间,运行期间都会被擦除掉,这也会引起一些问题,如关于泛型桥接方法等问题…
-
关于传递参数:我们在方法调用时,不仅要匹配方法参数本身,还要匹配参数的泛型类型,并且泛型类型不存在子父类等问题
-
关于泛型通配符:因此我们可以使用泛型通配符来接收任意泛型类型,在方法传参时只需要匹配参数本身即可,参数的泛型类型任意,单这会引发一个问题,那就是泛型的类型统统提升为Object类型,并且泛型通配符太广泛了,任意的的泛型类都可以往里面传递
-
泛型的上下边界:泛型的上下边界就是来约定在方法调用时,参数的泛型的类型传参问题
- 上边界:
? extends E
,代表只能传递泛型类型本身或者E类型的子类 - 下边界:
? super E
,代表只能传递泛型类型本身或者E类型的父类 - 有了上下边界约定之后,不仅在传递参数上面做了优化,在使用上边界的情况下,使用泛型类型时都会提升为E类型,不至于提升为Object类型
- 上边界:
Java中异常的体系?
- Throwable:Java所有异常的顶层
- Error:JVM和操作系统之间的一些错误异常,不可以处理
- Exception:程序运行时的一些异常,由于程序员疏忽导致的,可以处理
- RuntimeException:当一个异常继承与RuntimeException,那么改异常就是一个运行时异常,直接继承Exception就是一个编译时异常,在编译时一定要处理
- 索引下标越界
- 空指针异常
- 算数异常
- 类型转换异常
- RuntimeException:当一个异常继承与RuntimeException,那么改异常就是一个运行时异常,直接继承Exception就是一个编译时异常,在编译时一定要处理
JVM在遇到异常时默认的处理方式时怎么样的?
向上级(方法的调用者),最终肯定会抛给main方法,main方法还没有处理就抛给JVM,如果一个异常抛给了JVM,那么程序终止了;
Java处理异常有哪些?
- throw向上级抛出异常(异常还没有处理)
- catch:捕获异常
- 1)可以写多个catch块,但是父类异常必须写在后面
- 2)finally的代码块一定会执行,如果存在return,则返回的一定时finally的返回值,不管有没有出现异常
在继承时异常的特点?
- 1)如果父类没有抛出异常,那么子类也不能抛出异常
- 2)子类不可以抛出比父类大的异常
什么是泛型的擦除?有何特点?
- 1)泛型只存在于编译期间,在运行期间会被擦除,分为有限制擦除和无限制擦除
- 1)有限制擦除:当泛型定义了上边界时,那么泛型在运行期间被擦除为了上边界
- 2)无限制擦除:当泛型没有定义上边界或只定义了下边界时,那么泛型在运行期间被擦除为了Object
- 2)在实现带有泛型的接口时,由于泛型在运行期间会被擦除,导致我们编写的实现类没有实现接口中的那个带有泛型的抽象方法,那么JVM会帮我们自动添加桥接方法
谈谈你对线程和进程的理解?
- 线程:线程是进程中的一个执行线路,一个进程可以有多个线程,线程依附于进程,一个进程必须至少有一条线程;
- 进程:是独立在操作系统中的一个程序单元,不依附于线程,一般来说一个进程就是一个应用程序,也有可能一个应用程序拥有多个进程,进程强调独立,各个进程单元互不影响(影响很小)
多线程就一定快吗?
- 如果任务量多,且每个任务执行时间长,那么可以使用多线程把CPU空闲的时间利用起来让多个程序"同时"运行
- 如果任务量多,且每个任务执行时间短,那么这个时候单线程性能更高
同步方法和静态同步方法的锁对象是什么?
- 普通同步方法:this
- 静态同步方法是:本类的字节码对象
并行和并发有什么特点?
- 并行:多个应用程序真正意义上的同时执行,操作系统拥有多个执行单元,但这对硬件要求比较高,每个CPU能够并行的程序有限
- 并发:多个应用程序交替执行,由于CPU运行速度非常快,分配给每个应用程序执行的时间片也很短,让我们感觉到是"同时"运行,虽然是交替执行,但是也能解决多个应用程序"同时处理"任务;再也不是执行多个任务时,必须要等到第一个任务执行完毕才可以执行后面的任务;
什么是线程的死锁?
- 在多条线程中,使用锁嵌套时,当各自线程获取各自不同的锁时,会触发死锁问题;所以在使用锁时尽量不要使用锁的嵌套;
线程的生命周期?
- 新建:刚创建好的线程,还没有调用start方法开启线程,只是创建了一个线程对象,在堆内存中开辟了内存空间而已;
- 可运行:线程调用了start方法,现在已经开启了,但是不能确定该线程是否真正处于运行状态,但是线程已经分配的栈空间
- 锁阻塞:在多条线程并发时,由于未获取到锁资源而造成的阻塞
- 等待:使用wait方法进行等待后的线程,该线程在被唤醒期间将会一直处于等待状态,并且会释放锁
- 计时等待:分为sleep计时等待和wait计时等待,其中sleep计时等待不会释放锁资源,wait及时等待会释放锁资源,并且wait计时等待需要在获取到锁资源后并且使用锁对象来调用
- 终止:线程任务执行完毕,线程栈空间释放,但是线程对象的堆内存空间不一定释放
wait方法和sleep方法有什么区别?
- wait:
- 1)调用对象不同,wait方法是锁对象来调用
- 2)wait方法会释放锁
- 3)wait方法必须在同步代码块中使用(必须获取到锁之后才可以使用)
- 4)wait方法等待的线程,必须要有其他线程来唤醒,否则永远等待下去
- sleep:
- 1)线程中的静态方法
- 2)sleep不会释放锁
- 3)任意地方
- 4)会自动苏醒
wait和park的区别?
1)wait必须是锁对象来调用,park是LockSupport的静态方法
2)wait会释放锁,park不会释放锁
3)wait等待的线程通过notifly随机唤醒,park可以指定唤醒
4)wait不支持预处理,park可以预处理一次
5)wait线程会中断失败,park线程可以中断成功
字符流可以拷贝非文本文件吗?
- 不行,因为字符流在拷贝数据时,会将读取到的数据转换为对应的字符,而分文本数据的字节本质上不是一个字符,造成乱码(数据丢失)
IO流的体系是怎么样的?
- 分类
- 如果按照输入或输出的指向不同,分为输入流和输出流
- 如果按照操作的单位不同,分为字节流和字符流
- 体系:
- InputStream(字节输入流)
- FileInputStream
- BufferedInputStream
- ObjectInputStream
- DataInputStream
- SequenceInputStream
- ByteArrayInputStream
- PushbackInputStream
- PipedInputStream
- OutputStream(字节输出流)
- FileOutputStream
- BufferedOutputStream
- ObjectOutputStream
- DataOutputStream
- ByteArrayOutputStream
- PipedOutputStream
- PrintStream
- Reader(字符输入流)
- FileReader
- BufferedReader
- InputStreamReader
- Writer(字符输出流)
- FileWriter
- BufferedWriter
- OutputStreamWriter
- InputStream(字节输入流)
AutoCloseable接口有何作用?
-
定义:在JDK1.7中退出了try()语法,来处理异常的资源释放,要求该类必须实现AutoCloseable接口,才可以将其放入到try()中
-
好处:
- 1)当try{}中的代码块执行完毕时,jdk会自动调用该类的close方法资源
- 2)当try{}中的代码块执行出现异常时,jdk会判断资源是否创建成功(try()),如果创建成功则会调用close方法,如果创建失败则不会;
字符流和字节流有什么区别?
- 1)操作的单位不同,字符流读取一次操作的是一个字符,字节流读取一次操作的是一个字节,由于不同的编码表所编码的字符占用的字节是不同的,这样下来字符流在操作文本文件时会变得较为简便;
- 2)字符流在读取数据时会将其根据默认的编码表转换为字符,如果操作的文件不是文本文件(或者编码表与文件的编码不一样),则会出现数据错乱(乱码),因此字符串不可以拷贝非文本文件,即使拷贝文本文件也要编码表能够对应上
- 3)字符流有缓冲区的概念,在使用字符流输出流时,调用write是将其写入到缓冲区,并没有写入到磁盘,需要调用flush方法将其写入磁盘,并且在调用close方法前,也会将缓冲区的内容写入到磁盘;
字符流能否拷贝非文本文件?
不能,原因参考《字符流和字节流有什么区别?》;
关闭和刷新的区别?
原因参考《字符流和字节流有什么区别?》;
- 补充:FileOutputStream没有缓冲区的概念,每次调用write方法写出数据时,都是将其写入到磁盘,而且FileOutputStream的flush方法是空的;
序列化流有特点?
- 1)可以将Java对象序列化到磁盘(存储)这个过程叫序列化,也可以将磁盘中存储的对象读取到内存中来(反序列化)
- 2)对类的每一次修改都会导致类的版本更新,如果没有锁定版本号,在更改了类时,反序列化的时候就会出现异常,我们可以锁定版本来解决这个问题
- 3)默认情况下,对象的所有变量都会被序列化(static修饰的除外),如果希望某些成员变量不被序列化,可以使用
transient
关键字来修饰变量 - 4)实现序列化的类必须要实现
Serializable
接口 - 5)被static修饰的成员无法序列化到磁盘中
缓冲流有何特点?
- 1)分为字节/字符的输入/输出流;内置有8192个字节/字符缓冲区,缓冲流也支持自定义缓存大小(大小一般不超过8192)
- 字节缓存输入流
- 字节缓存输出流
- 字符缓存输入流
- 字符缓存输出流
- 2)缓冲流效率比普通流高,但是不如我们自定义的缓冲区效率高
- 原因:
- 字节缓冲输入流:每一次读取磁盘的数据都会读取8192个字节(效率高),但是读取到缓冲区(堆),需要将堆内存已经读取到的数据,一次一次的返回到方法中的变量(栈内存),这一步消耗资源;
- 字节缓冲输出流:需要每一次的将方法中的变量数据(栈内存)读取到缓冲区(堆内存),这一步消耗资源,读满8192个字节(缓冲区读满),然后将8192个字节一次性全部写入到磁盘(效率高)
- 原因:
TCP和UPD的特点?
- TCP:
- 特点:
- 1)可靠连接
- 2)安全性高
- 3)传输效率低
- 三次握手:
- 四次挥手:
- TCP报文:
- 序列号
- 确认号
- 标志位:
- SYN
- ACK
- FIN
- 特点:
- UPD:
- 特点:
- 1)不可靠连接
- 2)安全性低
- 3)传输效率高
- UDP报文:
- 1)源端口
- 2)目的端口
- 特点:
TCP报文中包含有哪些字段?
一个完整的TCP报文包含首部和数据载荷:
- 首部:包含TCP协议的大部分功能
- 源端口
- 目的端口
- 序列号
- 确认号
- 标志位
- SYN
- ACK
- FIN
- 数据载荷:就是实际存储传输数据
类加载器有哪几种?
- 1)BootstrapClassLoader(引导类加载器):负责加载Java核心类,JRE_HOME/lib下的类
- 2)ExtClassLoader(扩展类加载器):JVM的扩展功能,JRE_HOME/lib/ext下的类
- 3)AppClassLoader(应用类加载器):第三方的Java类都是由该类加载器加载
注解的生命周期?
- SOURCE:只存在Java源代码,编译成字节码就没有
- CLASS:存在Java源代码,编译字节码也存在,但是运行到内存就不存在;
- RUNTIME:Java源代码,编译字节码也存在,运行到内存也存在
谈谈你对反射的理解?
反射是Java中的一种机制,该机制运行我们在运行期间对任意的Java类进行解刨(除了枚举),主要解刨的对象是:类的信息、成员变量、构造方法、成员方法、一系列注解相关的等基本信息;对应的类有:Class、Field、Constructor、Method、Annotation,获取到这些反射相关对象之后,可以对该数据进行修改、调用等操作,包括任意的权限修饰符修饰的信息,这样一来反射虽然强大,但也破坏了类的封装性(私有性)
Java的元注解哪些?
- 1)@Target:默认情况下注解可以标注在任意位置,约束注解能够标注的位置
- 2)@Retention:规定注解的生命周期,默认情况下注解可以存在Java源代码,并且也能存在于字节码文件中,但运行期间注解会被擦除,那么如果想要在运行期间该注解可以被解析到,可以使用@Retention注解标注其生命周期为RUNTIME
- 3)@Inherited:标注当前注解可以被继承
枚举有什么特点?
- 1)应用场景:是JDK1.5的新特性,用来代替静态常量的写法
- 2)枚举本身也是一个Java类型(特殊Java类),不可以通过new来创建枚举实例,枚举能够创建的实例个数根据枚举项的数量来决定,获取第一个枚举项的实例时,其他枚举项的所有实例都将被创建
- 3)Java中所有的枚举类都继承与Enum类,其常用方法values(),可以获取所有的枚举项
- 4)枚举可以有构造方法,但默认都是private修饰的,只能在枚举类中调用,并且JDK对枚举的反射做了限制,不允许反射枚举
- 5)枚举可以拥有抽象方法,但是在定义枚举项时必须要重写该方法
类加载机制的双亲委派?
- 概念:当加载一个类时交给AppClassLoader加载,会默认向上委派,交给他的父加载器加载(逻辑上的),又会交给他的父加载器,一致到最顶层的父加载器(BootstarpClassLoader),这个时候首先由BootstarpClassLoader加载器加载,如果发现不能加载,则向下委派,交给子加载器加载,如果也不能加载,最终由AppClassLoader(最底层的类加载器)来加载;
如何打破双亲委派?
- 当一个类被加载时,首先会调用类加载器的loadClass方法来进行双亲委派,最终加载类是通过findClass方法来加载的,我们可以自定义一个类加载器,如果希望双亲委派,就不要重写loadClass方法,如果要打破双亲委派,重写loadClass方法;相当于每次调用loadClass方法都会进行一次新的加载
- 加载类的时候,使用ClassLoader的loadClass方法,直接调用findClass方法来加载类,就相当于跳过了双亲委派
类在何时会被加载?
- 1)在创建对象时,首先会将这个类加载器到内存,如果这个类有父类,那么首先将父类加载到内存
- 2)访问类的静态成员时,
- 如果是fianl修饰的:
- 基本数据类型:不会加载
- 应用数据类型:除了常量池中的String之外都会导致类加载;
- 如果不是fianl修饰的:该类会被加载
- 如果是fianl修饰的:
- 3)class.forName()时,该类会被加载(参数2没有传递false)
- 4)main方法所在的类总是被最先加载
动态代理有什么好处?
- 1)代理分为静态代理和动态代理,其本质都是对一个类进行增强
- 2)动态代理与静态代理的区别时,当目标对象新增功能方法时,动态代理的代理类不需要我们自己手动新增代理方法,静态代理就需要随着目标对象的变更而变更
- 3)动态代理是借助于JDK的API,Proxy,需要传递与目标对象一样的类加载器、目标对象所实现的接口的字节码对象,核心功能调度类,当代理对象执行任何方法时,都会走调度了invoke方法,实现所有功能的统一代理;