Java基础综合总结
用代码实现冒泡排序
public static void main(String[] args) {
int[] a = {6,4,1,2,6,10};
sort(a);
System.out.println(Arrays.toString(a));
}
private static void sort(int[] a) {
for (int i = 0; i < a.length-1; i++) {
for (int j = 1; j < a.length-i; j++) {
if(a[j-1]>a[j]){
swap(a,j-1,j);
}
}
}
}
private static void swap(int[] a, int j, int i) {
int temp = a[j];
a[j] = a[i];
a[i] = c;
}
HashMap
- 哈希表,散列表
- 存键值对数据
- 作用:快速定位查找数据
- 用Entry[]数组存数据
- key.hashCode()获得键的哈希值,用哈希值计算下标i
- 键值对封装成Entry实例,放入i位置
构造方法为什么只能进行重载,而不能进行重写
- 构造方法名是和类名同等的,重写是基于继承而实现的
- 因为重写是基于方法名相同,两同(方法名相同,参数列表相同),两小(),一大(访问修饰符),而子类继承父类,两个类名肯定不能相同,则构造方法名肯定不相同,则根本不存在所谓的继承
JAVA编译器每次遇到自增,自减运算符的时候都会开辟一块新的内存空间来保存赋值之前的值
如j=j++
则会有temp=j;
j=j+1;
j=temp
j=++j
则会有j=j+1
temp=j;
j=temp}
线程池
- 线程是进程执行的单位;
- 线程池的优点:
- 提高线程的可管理性
- 提高响应速度,不需要等待线程创建就能立即执行
- 降低资源消耗,重复利用已经创建的线程
- 其中最核心的类:ThreadPoolExecutor(创建时,一般使用它的子类)
参数:- 核心线程数
- 最大线程数
- 队列类型
流程: - 当有需求,但是需求量并没有超过核心线程数的时候,将会新建线程
- 当需求大于核心线程,但小于最大线程时,将会把多余的任务放在队列中
- 当需求大于核心线程,且队列中满时,且当前需求小于最大线程时,将会创建线程
- 当既大于核心线程,也满队列,同时也大于最大线程数,则会拒绝继续进行线程访问
- 当池中线程有空闲,并且线程数大于corePoolSize,一旦空闲线程的空闲时间超出keepAliveTime参数指定的时间,则线程会被释放
- ExecutorService/Executors
- pool = Executors.newFixedThreadPool(5)
- pool = Executors.newCachedThreadPool()
- pool = Executors.newSingleThreadExecutor()
- pool.execute(任务)
- pool.submit(任务)
volatile能保证线程安全吗
首先,用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最后的值.
不能保证线程的安全性.因为volatile解决的是多线程间共享变量的可见性问题和禁止指令的重排优化,
同时使用volatile会增加性能的开销
简述java中&和&&的区别
&是按位与.只有当两个表达式均为true的时候,结果才为true
&&是短路与,具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式.
java中HashMap和HashSet,List的特点和区别
- 区别:
- HashMap继承了Map中的abstractMap接口,HashSet继承的是Collection的Set接口,List是Collection的下级接口
- HashMap和HashSet线程不安全,List中Vector线程安全,ArrayList和LinkedList线程不安全
- HashSet的kv不可以为null且value不可以重复,HashMap的kv可以为null且value可以重复
- HashSet和HashMap无序,List有序
- 特点:
- HashMap查找数据时通过key指定查询value,HashSet通过屏蔽value后查询指定的value来查询
- List中ArrayList查找数据快,在首端插入数据慢
- LinkedList插入数据块,查找中间数据慢
- Vector下级有Stack类(栈数据结构)(FILO)
- HashSet和HashMap扩容时翻倍扩容,List扩容时1.5倍扩容
如何理解抽象类
抽象类:(对标准的部分共性做实现,特性交给子类实现)
继承特性
优势:实现代码的复用,提高程序的扩展性
劣势:大范围扩展可能会导致类爆炸,会降低代码的可维护性.
等待和通知
- 等待:wait()
- 通知:notify(),notifyall()
- 必须在synchronized内调用(有synchronized才有监视器,有监视器才有等待区),即等待通知的对象,必须是加锁的对象
- wait()外面总是一个循环条件判断
添加序列化id的原因:
当类中的结构发生变化的时候,为了保证反序列化成功,会添加序列化id
jdk1.5新特性
- 自动装箱与拆箱:
- 自动装箱的过程:每当需要一种类型的对象时,这种基本类型就自动地封装到与它相同类型的包装中.
- 自动拆箱的过程:每当需要一个值时,被装箱对象中的值就被自动地提取出来,没必要再去调用intValue()和doubleValue()方法.
- 自动装箱,只需将该值赋给一个类型包装器引用,java会自动创建一个对象.
- 自动拆箱,只需将该对象值赋给一个基本类型即可.
- java——类的包装器
类型包装器有:Double,Float,Long,Integer,Short,Character和Boolean
- 枚举
- 把集合里的对象元素一个一个提取出来.枚举类型使代码更具可读性,理解清晰,易于维护.
- 枚举类型是强类型的,从而保证了系统安全性.
- 而以类的静态字段实现的类似替代模型,不具有枚举的简单性和类型安全性.
- 简单的用法:JavaEnum简单的用法一般用于代表一组常用常量,可用来代表一类相同类型的常量值.
- 复杂用法:Java为枚举类型提供了一些内置的方法,同时枚举常量还可以有自己的方法.可以很方便的遍历枚举对象.
- 静态导入
- 通过使用 import static,就可以不用指定 Constants 类名而直接使用静态成员,包括静态方法.
- import xxxx 和 import static xxxx的区别是前者一般导入的是类文件如import java.util.Scanner;后者一般是导入静态的方法,import static java.lang.System.out.
- 可变参数(Varargs)
- 可变参数的简单语法格式为:
methodName([argumentList], dataType… argumentName);
- 可变参数的简单语法格式为:
- 内省(Introspector)
- 内省是Java语言对Bean类属性、事件的一种缺省处理方法.例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值.通过getName/setName来访问name属性,这就是默认的规则.
- Java中提供了一套API用来访问某个属性的getter /setter方法,通过这些API可以使你不需要了解这个规则(但你最好还是要搞清楚),这些API存放于包java.beans中.
- 一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器 (PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法.
- 泛型(Generic)
- C++通过模板技术可以指定集合的元素类型,而Java在1.5之前一直没有相对应的功能.
- 一个集合可以放任何类型的对象,相应地从集合里面拿对象的时候我们也不得不对他们进行强制得类型转换.
- 猛虎引入了泛型,它允许指定集合里元素的类型,这样你可以得到强类型在编译时刻进行类型检查的好处.
- For-Each循环
- For-Each循环得加入简化了集合的遍历.假设我们要遍历一个集合对其中的元素进行一些处理.
jdk1.7新特性
- switch中可以使用字符串
- "<>"这个玩意儿的运用List tempList = new ArrayList<>(); 即泛型实例化类型自动推断.
- 两个char间的equals
- 对Java集合(Collections)的增强支持
- 在JDK1.7中,摒弃了Java集合接口的实现类,如:ArrayList、HashSet和HashMap.而是直接采用[]、{}的形式存入对象,采用[]的形式按照索引、键值来获取集合中的对象,如下:
- List list=[“item”]; //向List集合中添加元素
- String item=list[0]; //从List集合中获取元素
- Set set={“item”}; //向Set集合对象中添加元素
- Map<String,Integer> map={“key”:1}; //向Map集合中添加对象
- int value=map[“key”]; //从Map集合中获取对象
- 在JDK1.7中,摒弃了Java集合接口的实现类,如:ArrayList、HashSet和HashMap.而是直接采用[]、{}的形式存入对象,采用[]的形式按照索引、键值来获取集合中的对象,如下:
- 在try catch异常扑捉中,一个catch可以写多个异常类型,用"|"隔开
- jdk7之前,你必须用try{}finally{}在try内使用资源,在finally中关闭资源,不管try中的代码是否正常退出或者异常退出.
jdk7之后,你可以不必要写finally语句来关闭资源,只要你在try()的括号内部定义要使用的资源
jdk1.8新特性
- 接口的默认方法
- Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法
- Lambda 表达式
- 函数式接口
- 每一个lambda表达式都对应一个类型,通常是接口类型.
- 而"函数式接口"是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法.
- 因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法.
- 方法与构造函数引用
- Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法
- Lambda 作用域
- 在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似.你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量.
- 访问局部变量
- 我们可以直接在lambda表达式中访问外层的局部变量
- 访问对象字段与静态变量
- 和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写.该行为和匿名对象是一致的
- 访问接口的默认方法
- Date API
- Java 8在包java.time下包含了一组全新的时间日期API.新的日期API和开源的Joda-Time库差不多,但又不完全一样
- Annotation 注解
- 在Java 8中支持多重注解
什么是持久化(ORM对象关系映射)
将对象数据存储到数据库中进行保存,表中的一行记录映射着一个对象的数据
HashMap和HashTable的区别
- 继承的父类不同
前者继承于AbstractMap类,后者继承于Dictionary类 - 线程安全性不同
前者只适用于单线程的情况下,后者适用于多线程的情况下,但最终造成了前者的线程运行效率高,后者的线程运行效率低. - 是否提供contains方法
- key和value是否允许为null值
前者允许,后者不允许key值为null - 两个类的遍历方式的内部实现上是不同的
- hash值不同
- 前者使用key的哈希值经过扰动函数后的hash值
- 而后者直接使用了key的hash值
- 内部实现使用的数组初始化和扩容方式不同
- 前者初始化容量为16,后者初始化长度为11
- 前者扩容的时候,将容量为原来的2倍
- 后者扩容的时候,将容量为原来的2倍+1
- 前者取位桶的下标,用的是位运算,效率较高(这正是因为其容量是2的n次方,可以进行位运算);
而后者取位桶的下标的时候,使用的是取模运算,这也正是因为其使用的容量不是2的n次方,无法进行位运算.
Try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后
如果try后有finally语句,那么会先执行finally语句,再执行return语句
char类型变量中能不能存储一个中文汉字?为什么
char类型是可以存储unicode编码的字符的,其中Unicode编码字符集中包含了汉字.
但是,某个特殊的汉字没有包含在Unicode编码字符集中,则不能储存这个特殊汉字.
interface&abstract class
接口interface和抽象类abstract class一旦定义,是要求一定的稳定性的
为什么String被final进行了修饰
-
为了String中的常量池
-
为了保证安全性
因为用final修饰的String,在数据传输的时候 不会因为出现继承的原因而使得原数据出现新的子数据的情况,从而产生数据的不安全性问题. 同时在满足final修饰的情况下,常量池内的String串将保持原有的样式,这样保证了常量池的完整性.
反射的原理
在jvm运行时,java文件被编译为class文件,通过字节码找到类,以及类中的方法和属性
创建对象的10个过程
- 第一次用到a和b类
- 父类的静态变量分配
- 子类的静态变量分配
- 父类的静态变量赋值,静态代码块执行
- 子类的静态变量赋值,静态代码块执行
- 创建对象
- 父类的非静态变量分配空间
- 子类的非静态变量分配空间
- 父类的静态变量赋值
- 父类的构造方法执行
- 子类的非静态变量赋值
- 子类的构造方法执行
什么是打桩
打桩是使用代替关联代码的代码.
当关联代码并没有完成的时候进行打桩,目的是:隔离,补齐,控制
这样能够使得程序能够正常运行,以便于测试
run方法和start方法来启动线程的区别在哪里?
start方法真正做到了启动一个新的线程.
run方法只是thread的一个普通方法而已.只是在原来的线程中进行调用,没有新的线程生成
start方法启动线程自动调用run方法
什么是死锁
死锁是指两个或两个以上的线程执行过程中,因争夺资源而造成的相互等待的现象.
如何避免
最简单的方法就是阻止循环等待条件:规定所有的进程申请资源必须以一定的顺序来操作
Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别
- sleep方法是线程类的静态方法,而wait方法是Object类的方法.
- 前者被调用,会让当前线程休眠,但是锁的状态保持不变,当休眠结束后,对象将重新回到就绪状态.
- 而后者被调用,则会使得当前对象放弃对象锁,而进入对象的等待池,只有调用对象的notify方法才会使得该线程从(对象等待池)中进入(对象等锁池),当该线程获得锁的时候,会进行就绪状态.
使用static和final修饰的属性,有什么好处
- 使用static修饰,这样静态方法调用可以调用静态变量
- 使用final修饰,这样使得对象不可以更改,更加安全
注解
注解本身中是没有作用的,实际上的作用是在框架中进行反射的
饿汉单例和懒汉单例的区别
- 饿汉单例是在类初始化的时候对其进行创建实例
- 懒汉单例是在实例调用方法的时候进行创建实例
String,StringBuilder,StringBuffer
- 相同点:三者底层封装的char类型的数组
- 不同点:String被final所修饰,并存在池中,每次对其进行操作都欧式new一个String类型的对象,当频率较大的时候不建议使用
- StringBuilder线程不安全,但是效率比String高,单线程的时候建议使用,能够修改底层的char数组,不用新建新的对象
- StringBuffer线程安全,但是效率比StringBuilder低,多线程的时候建议使用能够修改底层的char数组,不用新建新的对象
java对象生命周期 7个阶段
- 创建阶段
Object obj = null; - 引用阶段
- 强引用(Strong Reference)是指JVM内存管理器从根引用集合(Root Set)出发遍寻堆中所有到达对象的路径.当到达某对象的任意路径都不含有引用对象时,对这个对象的引用就被称为强引用.
- 软引用(Soft Reference)的主要特点是具有较强的引用功能.只有当内存不够的时候,才回收这类内存,因此在内存足够的时候,它们通常不被回收.
- 弱引用 GC在进行回收时,需要通过算法检查是否回收Soft引用对象,而对于Weak引用对象, GC总是进行回收.因此Weak引用对象会更容易、更快被GC回收.
- 虚引用(Phantom Reference)的用途较少,主要用于辅助finalize函数的使用.Phantom对象指一些执行完了finalize函数,并且为不可达对象,但是还没有被GC回收的对象.
- 不可视阶段
如果一个对象已使用完,而且在其可视区域不再使用,此时应该主动将其设置为空(null).可以在上面的代码行obj.doSomething();下添加代码行obj = null;,这样一行代码强制将obj对象置为空值.这样做的意义是,可以帮助JVM及时地发现这个垃圾对象,并且可以及时地回收该对象所占用的系统资源. - 不可到达阶段
在虚拟机所管理的对象引用根集合中再也找不到直接或间接的强引用,这些对象通常是指所有线程栈中的临时变量,所有已装载的类的静态变量或者对本地代码接口(JNI)的引用.这些对象都是要被垃圾回收器回收的预备对象,但此时该对象并不能被垃圾回收器直接回收.其实所有垃圾回收算法所面临的问题是相同的——找出由分配器分配的,但是用户程序不可到达的内存块. - 可收集阶段、终结阶段与释放阶段
- 垃圾回收器发现该对象已经不可到达.
- finalize方法已经被执行.
- 对象空间已被重用.
- 当对象处于上面的三种情况时,该对象就处于可收集阶段、终结阶段与释放阶段了.虚拟机就可以直接将该对象回收了.
java类生命周期
一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段
jvm(java虚拟机)中的几个比较重要的内存区域
方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区.
常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息.
堆区:用于存放类的对象实例.
栈区:也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息.
当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢.
- 加载
"加载"是"类加载机制"的第一个过程,在加载阶段,虚拟机主要完成三件事:- 通过一个类的全限定名来获取其定义的二进制字节流
- 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口.
- 连接
连接分为验证、准备和解析三个步骤- 验证的主要作用就是确保被加载的类的正确性.也是连接阶段的第一步.说白了也就是我们加载好的.class文件不能对我们的虚拟机有危害,所以先检测验证一下.他主要是完成四个阶段的验证:
- 文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理.这里面主要对魔数、主版本号、常量池等等的校验(魔数、主版本号都是.class文件里面包含的数据信息、在这里可以不用理解).
- 元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等.
- 字节码验证:这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的.在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出威海虚拟机安全的事.
- 符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候.主要是对类自身以外的信息进行校验.目的是确保解析动作能够完成.
- 准备阶段主要为类变量分配内存并设置初始值.这些内存都在方法区分配.在这个阶段我们只需要注意两点就好了,也就是类变量和初始值两个关键词:
- 类变量(static)会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中
- 这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值.
- 解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程.
- 符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好
- 直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄.和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同.
- 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行.
- 验证的主要作用就是确保被加载的类的正确性.也是连接阶段的第一步.说白了也就是我们加载好的.class文件不能对我们的虚拟机有危害,所以先检测验证一下.他主要是完成四个阶段的验证:
- 初始化
如果一个类被直接引用,就会触发类的初始化.在java中,直接引用的情况有:- 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法.
- 通过反射方式执行以上三种行为.
- 初始化子类的时候,会触发父类的初始化.
- 作为程序入口直接运行时(也就是直接调用main方法).
- 除了以上四种情况,其他使用类的方式叫做被动引用,而被动引用不会触发类的初始化.
- 卸载
- 在类使用完之后,如果满足下面的情况,类就会被卸载:
- 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例.
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
tips:类的加载是由类加载器完成的,包括四种
- 根加载器
- 扩展加载器
- 系统加载器
- 用于自定义加载器
- 会利用双亲委派机制,来加载类,先会从顶级父类进行加载,父类无能为力的时候才会及交给子类加载器进行加载
Eunm枚举
枚举类:默认使用 public static final修饰
而枚举类的构造方法默认使用private进行修饰.
概念:JDK1.5以后推出的一种新的类型
作用:主要用于更加严格的约束变量的类型.
我们所有写出的枚举类,都是默认继承了Enum类型
这里面有一种单例模式,就是Enum类型的单例
FileOutputStream fos = new FileOutputStream(file)与FileOutputStream fos = new FileOutputStream(file,true)的区别
在于FileOutputStream(file)会覆盖之前写入的内容,
FileOutputStream(file,true)是追加,不会覆盖之前写入的内容.
JAVA中默认的对象序列化
- 性能如何,假如不好的话你的改进策略?
答:不好,原因是内部不仅会对对象的元数据,还会对数据等都要进行序列化,这样会对性能有所影响
可能借助第三方序列化框架改善性能,例如kryo,hession - 安全问题
例如:对象序列化以后的字节通过网络传输,有可能在网络中被截取.
解决方法:对象序列化的时候,对对象内容进行加密处理,
对象反序列化的时候对内容进行解密处理
答:例如运用jdk中Base64这个类对想要加密的对象,进行加密处理 - 对称加密和非对称加密
- 对称加密:A与B之间的通讯数据都用同一套的密钥来进行加密解密
- 优点:简单快捷,密钥较短,且破译困难.
- 缺点:会有密钥泄露的风险,以及存在更换密钥的需求.
- 非对称加密:用公钥和私钥来加解密的算法.
- 优点:比对称加密安全.
- 缺点:加解密比对称加密耗时.
- 对称加密:A与B之间的通讯数据都用同一套的密钥来进行加密解密
final的用途
final关键字在效率上的作用主要可以总结为以下三点:
- 缓存:final配合static关键字提高了代码性能,JVM和Java应用都会缓存final变量。
- 同步:final变量或对象是只读的,可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
- 内联:使用final关键字,JVM会显式地主动对方法、变量及类进行内联优化。
ThreadLocal
利用本地线程来在不同的层次共同使用一个属性,且保证了线程安全
提供了一种线程绑定机制,可以将对象绑定到当前线程,也能通过该线程获得绑定的对象.
例如:在使用拦截器的时候,通过拦截器时,此时可以含有id的对象绑定到当前线程,当到控制层的时候,将从当前线程中获取(在线程中进行共享)
- 保证线程安全的原理:
使得每一个线程,都有其对应的绑定对象,取消了对同一个对象的共享,没有共享,就没有线程危险的问题存在,这样保证了线程的安全. - get方法的原理
原理:- 先获取当前线程,从当前线程中获取该线程对象的map对象
- 再从该map对象中根据键值对的key(Theadlocal)来获取value(绑定对象),当value(绑定对象)值为空的时候(不存在绑定对象的时候)
- 将调用set initialvalue方法,在该方法中,将会创建一个对应的对象,并继续获取当前线程
- 并把新创建的对象放入到对应的线程map中,key为Theadlocal,value为该新创建的对象
this和super的区别
this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过.
- this调用本类中的其他方法,而super则调用父类的方法
- this.属性则是调用本类的属性,而super.属性,则是调用父类的属性
- this.方法是调用本类的方法,而super.方法,则是调用父类的方法
请说明Exception和Error之间的区别
Exception和Error体现了java处理异常的两种方式
- 前者属于可以预料的异常,分为检查性异常和非检查性异常,
- 检查形异常时需要在编译期间进行try捕获或进行抛出的
- 而非检查性异常则是在编译期间没有进行这一系列操作的
- 后者是不可以被预料到的异常.
System.gc()会发生什么
什么都不会发生,调用该方法会告诉gc,该对象要进行回收,但是回收的时间是不确定的
但是还是建议不到万不得已不要进行调用,因为jvm对于内存的管理有着其自己的策略
GC线程是否可以为守护线程
可以
- 线程分为守护线程和非守护线程,在jvm中,只要存在非守护线程,那么守护线程就要全部进行工作.
只有当全部的非守护线程结束后,守护线程才会随着jvm关闭而一同结束 - 而守护线程最典型的应用就是GC
子父类序列化
子类实现序列化,父类没有实现,则子类继承父类的属性在序列化子类时不会被序列化
JVM调优
- 主要方面:
- 合理的编写程序
- 充分并合理的使用硬件资源
- 合理的进行JVM调优
- JVM调优原则:
- 确保大多数对象"朝生夕死"(如果使用,尽量减少其生存时间)
- 提高大对象的进入门槛
- gc/full gc频率不要太高、每次gc时间不要太长、根据系统应用来定.
想要使用继承来完成业务扩展的缺陷是什么
–>可以实用组合的方式来实现功能的扩展
- 两个类之间的耦合性太强
- 如果该类是final修饰,那么无法被继承,则无法完成业务的扩展
- 工作量很大–>造成类多的时候,类爆炸(可维护性极低)
String(null)是否合法
因为null值可以强制转换为任何java类类型,(String)null也是合法的
comparable和comparator的区别
-
comparable是java.lang包的一个接口,comparator是java.util中的一个接口(都是接口)
-
comparable是内部比较器,而comparator是外部比较器:对那些没有实现Comparable接口或者对已经实现的Comparable中的排序规则不满意进行排序.无需改变类的结构,更加灵活.
都需要对其进行实现方法
finally什么时候可能不被执行
finally执行前,线程或程序终止,则不会被执行
Java中会存在内存泄漏吗,请简单描述
内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中.
存在内存泄漏.例如:设计了全局集合对象,只设计了放入方法,而没有删除机制,导致内存被占用.
如果这个集合仅仅是方法栈内部的变量,那么并不会造成内存泄漏.(例如释放连接,关闭会话)
值传递和引用传递
当基本类型作为参数传入到方法中时,传递的是原值的拷贝;
同时当对象(即引用类型)作为参数传入到方法中的时候,传递的是引用的拷贝
volatile的使用场景
作用:volatile声明的变量,保证了变量的可见性,
-
状态标志:用于指示发生了一次重要的一次性时间,例如完成了初始化的操作(及时刷新)
-
但是不具备原子性(故并不能保证线程安全)
-
Synchronized可以修饰代码块.方法,类
-
volatile可以修饰变量 (尤其是共享)
-
但两者都会降低执行效率
-
volatile可以提供线程安全,但是只能是应用非常有限的例子
尽可能去使用synchronized而不要去使用LOCK
gc
- Java的垃圾回收机制与其他语言相比有什么特点
- c/c++语言,要自己去开启与释放内存,而java语言不需要,gc会自动扫描内存,将不无法引用的内存释放掉
- Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解
- 它使得Java程序员在编写程序的时候不再需要考虑内存管理.
- 特点:
- 会影响性能
- 不确定何时会被gc掉
原生类数据类型转化
原生类的数据类型除了布尔值之外均可以任意转换
什么是内部类和匿名类
匿名类是特殊的内部类
- 内部类:将一个类定义在另一个类中,则里面的那个类就叫内部类
- 匿名内部类:内部类必须继承一个外部类或实现一个接口
IO和NIO的区别
- IO是面向流的,NIO是面向缓冲的
- IO是阻塞的,NIO是非阻塞的
- IO是单线程的,NIO是通过选择器来模拟多线程的
hashmap底层
hashmap底层封装了数组(位桶),链表(红黑树),初始长度为16(也即是2的4次方),这样便于后面的位运算;(2的n次方-1,得到的二进制数都是1,更加的均匀,效率更高);
- set(key)
- 当放入值的时候(没有超过负载容量),将计算key的哈希值,并使用扰动函数进行扰动运算,并使用对应的hash值,与位桶的长度-1进行位运算,获取最终的位桶下标位置,并放入对应位桶.(扰动函数防止的是hash碰撞);(hash碰撞也就是存储位置的碰撞)
- 当发生哈希碰撞的时候,用equals方法来比较key,当key相等则进行覆盖;当位置原本为空,则直接放入;当key不相等,则形成链表
- 当链表长度大于8的时候,将形成红黑树,这样便于二分法查找.
- 当总存储数据的个数达到负载数量时,将进行位桶的扩容,对原本的数据重新计算位桶的下标位置(但效率很低,一般要计算好使用的容量)
- get(key)
- 会根据key的被扰动后的hash值与位桶长度-1做位运算,如果对应的位桶没有形成链表或红黑树,则直接放回该位置的key
- 若对应的位桶中存在链表,则会遍历链表找到对应的key(使用equals方法),并返回对应的value
- 若对应的位桶中时以红黑树存在的,则会根据key来进行红黑树的内部匹配,并返回对应的value
synchronized和RenenTrantLock的区别
- 前者是基于jvm来进行实现,后者是基于API进行实现
- 前者属于非公平锁,谁抢到锁,谁就执行对应的方法;而后者属于公平锁,谁先抢谁就执行对应的方法
- 前者在抢锁的时候不能进行中断,而后者可以中断,然后再执行其他的方法
- 后者可以实现选择性通知
- 综上后者比前者多有三大特性:
-
后者可以实现公平锁
-
后者可以实现选择性通知
-
后者可以中断等待抢锁
synchronized是同步阻塞,使用的是悲观并发策略,lock是同步非阻塞,采用的是乐观并发策略 在jdk1.5之后,性能方面差不多,因为jvm对悲观锁进行了一个调优
-
序列化和反序列化
- 定义
- 序列化就是将对象的状态保存或记录在内存,磁盘等中,而反序列化则是还原对象状态
- 官方说法:序列化就是将java对象转换成为字节序列的过程,而反序列化就是将字节序列转换成为java对象的过程.
- 为什么要实现序列化和反序列化?
- 当两个不同领域的java服务进程进行通信的时候,则必须要通过网路流来进行通信,则必须要将对象序列化成为字节序列,并在接收端进行反序列化操作
什么是高级流,什么是低级流
-
高级流
- 处理流(转换流,缓冲流(提高效率))
-
低级流
- 节点流(file…printStream)
-
按流向可以分为:输入流和输出流
-
按类型可以分为:字符流,字节流
注:有缓冲效果的流,一般为写入操作的流,在数据写完之后一定要进行flush操作 作用是将缓冲区中未写出的数据进行一次性写入,以确保所有字符都进行了写出
hashmap底层原理线程安全么,不安全请说出原理,解决办法
-
hashMap的底层是一个链表和数组的结合体.
- HashMap底层是用的哈希表,哈希表是由数组+链表组成.
- HashMap中有两个很重要的参数,容量(Capacity)和负载因子(Load factor).
-
解决办法:而要消除这种隐患,则可以加锁或使用HashTable和ConcurrentHashMap这样的线程安全类,但是HashTable不被建议使用,推荐使用ConcurrentHashMap容器.
-
hashmap是线程不安全的.原理
- 当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点.对链表而言,新加入的节点会从头结点加入.此实现不是同步的
- 当加入新的键值对后键值对总数量超过门限值的时候会调用一个resize操作,这个操作会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组.
- 当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失.
- 而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题.
-
hashmap中的key可以为null且value可以为null,但是hashtable中的key和value均不可以为null,且两者的key值不能重复
-
HashMap解决冲突的四种方法:
- 开放地址法
- 拉链法
- 再哈希法
- 建立公共溢出区
GC回收中,什么东西能够显示对象要被进行回收
采用引用技术算法.
- 什么是引用计数算法
- 给对象添加一个引用计数器,每当一个地方引用他的时候,计数器值就会加1;
- 当引用计数值为0的时候,将面临着gc回收
- 当引用计数值为-1的时候,表示该对象已经被gc回收
concurrenthashmap为什么是线程安全
运用了分段锁技术Segment内部类extends ReentrantLock(可重入锁),它并不是锁全部的数据,而是锁一部分数据,这样多个线程访问的时候就不会出现竞争关系
但在jdk1.8之后,取消了分段锁的概念,将分段锁改为synchronized和红黑树进行保持其安全性.
垃圾回收算法
- 引用计数算法:每个对象都有一个引用计数,当计数值为0的时候,将会被gc掉,但是这种算法无法解决循环引用变量的问题—声明对象
- 标记-清除算法:该方法会引起gc后的内存碎片问题.—MajorGc
- 标记-复制算法:会将内存分为两个同样的区域,每次只使用其中一个区域,当进行gc的时候,将会复制到另一个区域,但这会需要两倍的内存空间—MinorGc
- 标记-整理算法:这种算法既避免了标记清除算法会产生的碎片,又避免了标记复制算法所带来的空间使用问题—MajorGc
元数据区
jdk8以后将永久代替换为MetaSpace(元空间)存在于本地内存.这样提高了jvm的其他内存的最大使用元数据区存放class文件,静态对象等.
产生线程安全问题的原因(怎么判断一个类是不是线程安全的)
- 存在多个线程并发执行
- 多个线程共享同一个数据集
- 多个线程在共享数据集上的操作不是原子操作
缓存
一级缓存可以跨sqlSession共享吗
不可以
一级缓存默认实现的是谁
PerpetualCache
一级缓存的对象的生命周期
SqlSession对象结束
哪个对象默认查询二级缓存
CachingExecutor
什么场景下二级缓存中要使用serialized cache
不同的SqlSession获对应不同缓存对象-性能相对较低,但缓存中的对象线程安全