1. == 和 equals() 的区别是什么?
== 的作用:
- 基本数据类型:判断值是否相等
- 引用数据类型:判断引用地址是否相等,String类型要单独讨论
equals() 的作用:
- 在Object中和 == 一样是判断引用地址是否相等
- 在String中是判断其字符串值是否相等
内存模型
- str1通过new会在堆创建一个字符串对象
- str2直接创建也会在堆创建一个字符串对象
- str3直接创建,但并不会在堆创建一个字符串对象,而是直接指向str2在堆的字符串对象。因为没有通过new创建的String对象,会先在字符串池中去找是否已有值相等的String对象,若有则将栈指针指向堆中已有的String对象,若没有才会在堆中创建新的String对象
2. 两个对象的hashCode()相同,则equals()一定为true,对吗?为什么?
hashCode():是根据内存地址按hash算法得出的一个正数
若equals()为true,hashCode()也为true
若hashCode()为true,equals()不一定为true,因为可能存在hash冲突(概率较低)
那么hashCode在实际应用中有什么作用呢?
以HashSet为例确保添加的元素是不重复的:
- 重写hashCode()与equals()方法
- 先判断hash值是否相等,若不等,则元素不重复
- 若hash值相等,再判断equlas()值是否相等,如值不等则元素不重复
那么为什么要使用hashCode呢?
如果在存储的时候逐个equals()比较,效率较低,哈希算法提高了去重复的效率,降低了使用equals()方法的次数
3. String类的常用方法?
判断功能
boolean equals(Object obj)
boolean equalsIgnoreCase(String str)
boolean isEmpty()
boolean contains(String str)
boolean startsWith(String str)
boolean endsWith(String str)
获取功能
int length()
char charAt(int index)
int indexOf(int ch)
int indexOf(String str)
int indexOf(int ch,int fromIndex)
int indexOf(String str,int fromIndex)
String substring(int start)
String substring(int start,int end)
转换功能
byte[] getBytes()
char[] toCharArray()
static String valueOf(char[] chs)
static String valueOf(int i)
String toLowerCase()
String toUpperCase()
String concat(String str)
其它功能
- 替换功能
String replace(char old,char new)
String replace(String old,String new)
- 去除字符串两空格
String trim()
- 按字典顺序比较两个字符串
int compareTo(String str)
int compareToIgnoreCase(String str)
4. final的作用?
修饰类
表示该类不能被继承,请谨慎使用,若非该类已十分明确不会被继承或出于安全方面考虑,并不建议设计为final类
修饰方法
表示该方法不能被子类重写(覆盖),但能够被重载,即在子类中可以创建多个与final方法方法名相同,但参数不同的方法
注意若父类中final方法的访问修饰符为private,那么子类是不会直接继承父类的final方法的,那么这时在子类中创建相同的方法名与参数是不会有final冲突的
修饰变量
final修饰变量是较为常见的,也是这里需要重点学习的部分
final修饰变量表示该变量仅能被赋值一次,赋值后值不再改变
- 当为基本数据类型时,表示其初始化后就不能再被更改
- 当为引用数据类型时,表示其初始化后就不能再指向其它对象,但被指向的对象是可以更改的,其本质是一样的即保证栈中的地址是无法更改的
- 当为成员变量时,必须要显示初始化,即声明变量时就初始化,或声明变量时未初始化,但在该成员变量的类中所有构造方法(无参、有参)中赋初值
- 当为参数时,表示该参数只读,只能读取使用,但不能被更改
5. IO流分几种?
按数据类型分:
- 字节流:InputStream、OutputStream,任何数据类型都能支持
- 字符流:Reader、Writer,非纯文本格式数据可能会导致文件格式破坏
按数据流向分:
- 输入流:读取文件数据
- 输出流:将数据写入文件中
按功能分:
- 字节流:FileInputStream、FileOutputStream,直接和源数据进行输入输出
-
处理流:在字节流的基础上进行了功能的扩展或加强,又分为以下两种
- 转换流:InputStreamReader、OutputStreamWriter,字节流转字符流,字符流转字节流
- 缓冲流:BufferedInputStream,BufferedOutputStream BufferedReader,BufferedReader,可对节点流经行包装,使读写更快
扩展:关于缓冲流,这里涉及到了设计模式中的装饰者模式,其作用即基于已有功能基础上,提供增强的功能
其实现思路如下:
- 子类继承父类
- 子类中声明一个父类类型的成员变量
- 通过子类中的带参构造方法接收外部传递的父类类型参数,并赋值给子类的父类成员类型成员变量
- 在实现功能时,调用外部传递的父类类型参数实现原有功能,自己实现增强功能
6. BIO、NIO、AIO有什么区别?
先结合生活场景简单介绍下同步、异步、阻塞、非阻塞,以银行取款为例:
- 同步:自己到银行取款
- 异步:委托别人(将各种需要的资料给别人)帮自己到银行取款,期间自己可以做别的,然后等别人取好给自己
- 阻塞:在ATM排队取款,只能站着等
- 非阻塞:在银行取个号,坐椅子上玩自己的,等广播叫号到自己了就去,没到号不能插队,也可以不断问大堂经理到自己没,如果说没到就不能去
BIO(Blocking I/O)
同步且阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解
NIO(Non-Blocking I/O)
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持
AIO(Asynchronous I/O)
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持
扩展:Netty为什么使用NIO而不是AIO?
Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优化
7. Files的常用方法?
Files.read() 读取文件
Files.write() 写入文件
Files.exists() 检测文件路径是否存在
Files.createFile() 创建文件
Files.createDirectory() 创建文件夹
Files.delete() 删除文件或者目录
Files.copy() 复制文件
Files.move() 移动文件
Files.size() 查看文件个数
8. abstract的作用?
抽象类
- public abstract class ClassName{}
- 抽象类不能被实例化,只有非抽象子类可以被实例化
- 抽象类可以有自己的构造方法
- 抽象类不同于接口,接口的接口方法是不允许实现的,但抽象类中普通方法可以被实现
- 抽象类不能用final关键字来形容,因为final修饰类表示该类不能被继承,但抽象类需要子类来实现抽象方法
- 抽象类中可以没有抽象方法,但只要有一个抽象方法,就一定是抽象类
- 继承了抽象类的子类必须全部重写抽象类的方法,只有子类也为抽象类时才可以不用全部重写
- 一个类只能单继承抽象类,但可以实现多个接口类
抽象方法
- 抽象方法类似与接口中的方法,是不允许在抽象类中实现的,只能由子类来实现
- 抽象方法的访问修饰符不能用private,因为private表示只有本类可以调用,而抽象方法需要子类来实现
- 抽象方法不能使用static关键字,因为static修饰方法,可以直接通过类名进行调用,但抽象类是不能实例化的