基础篇
目录
String和StringBuilder和StringBuffer区别
String a = "A" 和 String a = new String("A") 创建字符串的区别
final 和 finally 和 finalize 的区别
Java中基本数据类型有哪些?
byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间
short:16位,最[大数据]存储量是65536,数据范围是-32768~32767之间
int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1
long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1
float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F
double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加
boolean:只有true和false两个取值
char:16位,存储Unicode码,用单引号赋值
Integer 和 int的区别
int是基本数据类型,变量中直接存放数值,变量初始化时值是0
Integer是引用数据类型,变量中存放的是该对象的引用,变量初始化时值时null
Integer是int类型的包装类,将int封装成Integer,符合java面向对象的特性,可以使用各种方法比如和其他数据类型间的转换
Integer和int的深入对比:
-
两个通过new生成的Integer对象,由于在堆中地址不同,所以永远不相等
-
int和Integer比较时,只要数值相等,结果就相等,因为包装类和基本数据类型比较时,会自动拆箱,将Integer转化为int
-
通过new生成的Integer对象和非通过new生成的Integer对象相比较时,由于前者存放在堆中,后者存放在Java常量池中,所以永远不相等
-
两个非通过new生成的Integer对象比较时,如果两个变量的数值相等且在-128到127之间,结果就相等。这是因为给Integer对象赋一个int值,java在编译时,会自动调用静态方法valueOf(),根据java api中对Integer类型的valueOf的定义,对于-128到127之间的整数,会进行缓存,如果下次再赋相同的值会直接从缓存中取,即享元模式
String和StringBuilder和StringBuffer区别
三者底层都是char[]
存储数据,JDK1.9之后使用的是byte[] ,因为往往我们存储都是短字符串,使用byte[]这样更节约空间。
由于String底层的char[]有final
修饰,因此每次对String的操作都会在内存中开辟空间,生成新的对象,所以String不可变
StringBuilder和StringBuffer是可变字符串,没有final修饰,适合字符串拼接,另外StringBuffer是线程安全的,方法有synchronized
修饰,但是性能较低,StringBuilder是线程不安全的,方法没有synchronized修饰,性能较高
String a = "A" 和 String a = new String("A") 创建字符串的区别
String c = "A" 首先去常量池找 “A”,如果有,会把a指向这个对象的地址 ,如果没有则在栈中创建三个char型的值'A',堆中创建一个String对象object,值为"A",接着object会被存放进字符串常量池中,最后将a指向这个对象的的地址
new String("A") : 如果常量池中么有“A”就会走上面相同的流程先创建“A”,然后在堆中创建一个String对象,它的值共享栈中已有的char值“A”。
以下代码创建了几个对象?
-
String s = "a" +"b" + "c" + "d";这条语句创建了几个对象?
创建了一个对象,因为相对于字符串常量相加的表达式,编译器会在编译期间进行优化,直接将其编译成常量相加的结果。
-
String s; 创建几个对象? 没有创建对象。
-
String a = "abc"; String b = "abc"; 创建了几个对象
创建了一个对象,只是在第一条语句中创建了一个对象,a和b都指向相同的对象"abc",引用不是对象
== 和 equals 的区别是什么
==
比较对象比较的是地址,对于Object对象中的equals
方法使用的也是 == ,比较的是对象的地址,默认情况下使用对象的equals比较Object中的equals方法,也就是比较地址,如果要实现自己的比较方式需要复写equals 方法。
对于包装类比如:Integer都是复写过equals方法,比较的是int 值。
final 和 finally 和 finalize 的区别
当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰
finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下
finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
JDK 和 JRE 有什么区别?
JRE(Java Runtime Enviroment) :是Java的运行环境,JRE是运行Java程序所必须环境的集合,包含JVM标准实现及 Java核心类库
JDK(Java Development Kit) :是Java开发工具包,它提供了Java的开发环境(提供了编译器javac等工具,用于将java文件编译为class文件)和运行环境(提 供了JVM和Runtime辅助包,用于解析class文件使其得到运行)。JDK是整个Java的核心,包括了Java运行环境(JRE),一堆Java工具tools.jar和Java标准类库 (rt.jar)。
面向对象四大特性
抽象 : 是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么 - 举例:定义一个persion类,了就是对人
这种事物的抽象
封装:对数据的访问只能通过已定义的接口,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口,比如在Java中,把不需要暴露的内容和实现细节隐藏起来,或者private修饰,然后提供专门的访问方法,如JavaBean。 - 生活举例:电脑主机就是把主板等封装到机壳,提供USB接口,网卡接口,电源接口等。 JavaBean就是一种封装。
继承:新类(子类,派生类)继承了原始类的特性,子类可以从它的父类哪里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
多态:多态是指允许不同类的对象对同一消息做出响应。对象的多种形态,当编译时类型和运行时类型不一样,就是多态,意义在于屏蔽子类差异
方法覆盖和重载
方法的覆盖是子类和父类之间的关系,方法的重载是同一个类中方法之间的关系。 覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。 覆盖要求参数列表相同;重载要求参数列表不同。
普通类和抽象类
抽象类不能被实例化, 需要通过子类实例化 抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。 抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体 含有抽象方法的类必须申明为抽象类 抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类
接口和抽象类
定义接口使用interface,定义抽象类使用abstract class
接口由全局常量,抽象方法,(java8后:静态方法,默认方法)
抽象类由构造方法,抽象方法,普通方法
接口和类是实现关系,抽象类和类是继承关系
ArrayList和LinkedList区别?
ArrayList是基于数组实现的,根据索引随机访问元素性能高,但是插入和删除元素性能差,因为这会涉及到移位操作
LinkedList是基于双链表实现的,不支持索引,随机访问元素需要从头查找,因此性能差,但是添加删除性能高因为不涉及移位操作,但是LinkedList容易造成内存的碎片化,增加内存管理难度。
根据实际需要,如果项目中使用查找较多,使用ArrayList,如果使用增删较多,请使用LinkedList
HashMap在什么情况下扩容?如何扩容?
HashMap的数组初始容量是16,负载因子是0.75,也就是说当数组中的元素个数大于12个,会成倍扩容
HashMap是如何Put一个元素的?
首先,将key进行hash运算,将这个hash值与上当前数组长度减1的值,计算出索引。此时判断该索引位置是否已经有元素了,如果没有,就直接放到这个位置
如果这个位置已经有元素了,也就是产生了哈希碰撞,那么判断旧元素的key和新元素的key的hash值是否相同,并且将他们进行equals比较,如果相同证明是同一个key,就覆盖旧数据,并将旧数据返回,如果不相同的话
再判断当前桶是链表还是红黑树,如果是红黑树,就按红黑树的方式,写入该数据,
如果是链表,就依次遍历并比较当前节点的key和新元素的key是否相同,如果相同就覆盖,如果不同就接着往下找,直到找到空节点并把数据封装成新节点挂到链表尾部。然后需要判断,当前链表的长度是否大于转化红黑树的阈值,如果大于就转化红黑树,最后判断数组长度是否需要扩容。
HashMap是如何Get一个元素的?
首先将key进行哈希运算,计算出数组中的索引位置,判断该索引位置是否有元素,如果没有,就返回null
如果有值,判断该数据的key是否为查询的key,如果是就返回当前值的value
如果第一个元素的key不匹配,判断是红黑树还是链表
如果是红黑树,就就按照红黑树的查询方式查找元素并返回
如果是链表,就遍历并匹配key,让后返回value值
什么是Hash冲突?
哈希冲突,也叫哈希碰撞,指的是两个不同的值,计算出了相同的hash,也就是两个不同的数据计算出同一个下标,通常解决方案有:
拉链法,把哈希碰撞的元素指向一个链表
解决方式:
开放寻址法,把产生冲突的哈希值作为值,再进行哈希运算,直到不冲突
再散列法,就是换一种哈希算法重来一次
建立公共溢出区,把哈希表分为基本表和溢出表,将产生哈希冲突的元素移到溢出表
HashMap是如何解决Hash冲突的?
采用拉链法,将哈希碰撞的元素,转化为链表
从JDK1.8开始,增加了红黑树,当链表的长度大于8,就将链表转化为红黑树,来优化查询性能
创建线程是几种方式
方式一:继承Thread类,覆写run方法,创建实例对象,调用该对象的start方法启动线程 方式二:创建Runnable接口的实现类,类中覆写run方法,再将实例作为此参数传递给Thread类有参构造创建线程对象,调用start方法启动
方式三:创建Callable接口的实现类,类中覆写call方法,创建实例对象,将其作为参数传递给FutureTask类有参构造创建FutureTask对象,再将FutureTask对象传递给Thread类的有参构造创建线程对象,调用start方法启动
说一下Java中的集合体系,以及他们的特点
按照两种接口来分类:
Collection接口
List:
ArrayList:底层数据结构是数组,查询性能高,增删性能低
Vector:底层数据结构是数组,查询性能高,增删性能低
LinkedList:底层数据结构是双向链表,查询性能低,增删性能高
Set:
HashSet:无序不重复的,底层数据结构是数组+链表+红黑树,判断重复依据是hashCode()和equals()
TreeSet:有序不重复的,底层数据结构是数组+链表+红黑树,排序方式分为自然排序,比较器排序
Map接口
HashMap:key的值没有顺序,线程不安全
TreeMap:key的值可以自然排序,线程不安全
HashTable:它的key和value都不允许为null,线程安全
Properties:它的key和value都是String类型的,线程安全
Synchronized 和 lock的区别
他们都是用来解决并发编程中的线程安全问题的,不同的是
synchronized是一个关键字,依靠Jvm内置语言实现,底层是依靠指令码来实现;Lock是一个接口,它基于CAS乐观锁来实现的
synchronized在线程发生异常时,会自动释放锁,不会发生异常死锁,Lock在异常时不会自动释放锁,我们需要在finally中释放锁
synchronized是可重入,不可判断,非公平锁,Lock是可重入,可判断的,可手动指定公平锁或者非公平锁
线程的几种状态
新建状态:线程刚创建,还没有调用start方法之前
就绪状态:也叫临时阻塞状态,当调用了start方法后,具备cpu的执行资格,等待cpu调度器轮询的状态
运行状态:就绪状态的线程,获得了cpu的时间片,真正运行的状态
冻结状态:也叫阻塞状态,指的是该线程因某种原因放弃了cpu的执行资格,暂时停止运行的状态,比如调用了wait,sleep方法
死亡状态:线程执行结束了,比如调用了stop方法
sleep 和 wait的区别
第一,sleep方法是Thread类的静态方法,wait方法是Object类的方法
第二:sleep方法不会释放对象锁,wait方法会释放对象锁
第三:sleep方法必须捕获异常,wait方法不需要捕获异常
Synchronized和Lock的区别
他们都是用来解决并发编程中的线程安全问题的,不同的是
synchronized是一个关键字,依靠Jvm内置语言实现,底层是依靠指令码来实现;Lock是一个接口,它基于CAS乐观锁来实现的
synchronized在线程发生异常时,会自动释放锁,不会发生异常死锁,Lock在异常时不会自动释放锁,我们需要在finally中释放锁
synchronized是可重入,不可判断,非公平锁,Lock是可重入,可判断的,可手动指定公平锁或者非公平锁