待办
目录
1、面向对象和面向过程的区别
- 面向过程:性能高,降低系统开销,但是耦合性较大
- 面向对象:低耦合、易复用、易维护、易扩展
Java性能低的原因:
Java代码并不是最终被CPU直接执行的代码,而是会被Java虚拟机编译成字节码文件。
2、JVM、JDK、JRE
2.1 JVM
Java虚拟机是运行字节码文件的虚拟机。不针对某一特定的机器语言,因此具有跨平台性,无论在Windows、Linux还是macOS系统,都可以运行,使用相同的代码都会产生相同的结果。
字节码即为Java文件被编译后形成的 .class 文件,可以被JVM虚拟机识别。
Java程序从源代码到运行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KuE6qiOL-1581489404391)(3B0F769412E841B8BC98F85A57CF82DD)]
注意:
- 在从 .class 到机器码这一步,JVM首先加载字节码文件,然后通过解释器逐行解释执行,因此执行速度相对较慢。
- 热点代码:被经常调用的方法和代码块。
- JIT编译器:属于运行时编译。当JIT编译器完成第一次编译之后,会将机器码保存下来,下次可以直接使用。
2.2 JDK
- JDK:Java Development Kit,包含JRE,以及编译器和工具(javadoc和jdb),可以创建和编译Java程序。
- JRE: Java Runtime Environment , Java程序运行环境,只能运行编译好的 .class 文件,不能创建和编译Java文件。
3、构造器
3.1 构造器能否被重载?
父类的私有方法和构造方法不能被重写,但是可以重载
4、重载和重写的区别
- 重写:发生在子类中,子类对父类的方法体进行改变,方法名、参数类型、参数顺序、参数个数相同,返回值类型小于父类,抛出的异常范围小于父类,访问修饰符大于父类,如果父类方法修饰符是private则子类不能重写。
- 重载:在同一个类中,方法签名相同,参数类型,顺序和参数个数不同,方法返回值和修饰符可以不同。
5、Java 面向对象编程三大特性: 封装 继承 多态
-
封装:封装是把一个对象的属性进行私有化,同时提供一些可以被外界访问的方法,在方法中设置访问条件,从而可以保证数据的安全。
-
继承:可以基于已存在类定义新类,新类拥有已存在类的所有属性和方法,但是新类只能访问已存在类的非私有方法和属性;可以添加新的属性和方法,方便代码的复用。
-
多态:一个引用变量具有多个形态,具体指向的哪个形态的对象在运行期才能确定。
实现多态两个方法:
(1)继承,多个子类对同一个方法的重写
(2)接口,实现接口,并覆盖接口中的同一个方法
6、字符串
6.1 String StringBuilder StringBuffer区别
- 可变性:
String是不可变,因为String类使用final关键字修饰字符数组来保存字符串;StringBuilder和StringBuffer都继承自AbstractStringBuilder,该类也是使用字符数组保存字符串,但是没有final修饰
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
-
线程安全:String对象不可变,类似于常亮因此线程安全;StringBuilder和StringBuffer继承自AbstractStringBuilder,而StringBuffer对方法进行了加锁处理,所以线程安全,而StringBuilder未加锁所以线程不安全。
-
性能:每次改变String对象都会重新生成一个String对象;而StringBuffer和StringBuilder都是直接操作的对象本身,因此性能较高,而StringBuffer有加锁过程所以性能稍低于StringBuilder。
-
使用总结:
- 操作少的数据:String
- 多线程大数据:StringBuffer
- 单线程大数据:StringBuilder
6.2 字符串拼接方法及性能比较
方法 | 底层原理 | 性能(循环50000次耗时) |
---|---|---|
“+” | StringBuilder.append(),每次都会new一个StringBuilder | 5119 |
String.concat() | 使用数组(新建),新建String | 3623 |
StringBuilder | 使用数组(添加到原数组),线程不安全 | 3 |
StringBuffer | 使用数组(添加到原数组),线程安全 | 4 |
StringUtils.join() | StringBuilder.append() | 25726 |
7、包装类型
基本类型 | 包装类型 |
---|---|
byte | Byte |
boolean | Boolean |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
7.1 自动装拆箱
- 装箱:将基本类型的值包装成对应的引用类型
- 拆箱:将包装类型转换成基本类型数据
8、值得比较
8.1 == 与 equals比较
= =: 对于对象判断地址是否相等,对于基本类型判断的是值是否相等;
equals: 当没被重写时作用同 = =,如果被重写则判断对象的内容是否一样。
注意:
- String类中的equals被重写了,因为Object对象的equals方法判断的是地址;
- 在创建String对象时会先去内存常量池中查找是否存在值相同的对象,存在则直接使用,不存在才创建。
8.2 hashCode和equals
8.2.1 hashCode()介绍
hashCode() 是获取哈希码(散列值),返回一个 int 整数来确定对象在哈希表中的索引位置,以便于快速查找到该对象。
8.2.2 为什么重写equals时必须重写hashCode方法
例如,利用 HashSet 保存对象,首先加入一个对象时,会先调用 hashCode() 方法获取哈希值,然后与集合中其他对象的 哈希值进行比较,如果不存在相同值,会添加对象,如果相同则会再次调用 equals 方法比较对象的内容是否相同,如果不相同则加入当前对象,重新扩列到其他位置,如果相同则不会加入该对象。
- 如果两个对象相同,则其哈希值一定相同,且调用 equals 方法返回 true
- 如果两个对象哈希值相等,对象也不一定相等
- hashCode() 默认是对堆上的对象产生独特值,如果没有重写 hashCode() 则该 class 的两个对象无论如何都不会相等。
8.3 String.hashCode()
尽量不要在switch中使用String.hashCode()方法,因为存在冲突,例如Aa和BB的hashCode都是2112。
使用31(质数)作为乘数,为了尽可能使哈希链较短,提高查询速度,同时避免溢出。
9、多线程
9.1 线程,进程,程序
- 程序:含有指令和数据的文件,静态的代码。
- 线程:比进程更小的执行单元。
- 进程:程序的一次执行过程,系统运行程序的基本单元。
在一个操作系统内可以同时执行的多个程序为进程,进程之间相互独立;一个程序内能同时执行的多个程序段为线程。
9.2 线程的状态
创建状态 (new线程对象)—> 就绪状态 (调用 start方法,线程队列排队)运行状态 (获得CPU执行权,调用ru方法)—>阻塞状态 (调用 sleep, suspend,wait方法,发生阻塞,不能进入排队队列,只有引起阻塞原因被排除后,再次进入就绪)—> 死亡状态 (调用stop或run方法执行结束)
超时等待:在等待(就绪)状态基础上增加超时时间,例如:sleep( long mills ) 或 wait( long mills ) 方法,当超时时间到时,会返回到就绪状态。
9.3 多线程实现方式
- 继承 Thread 类,覆写run方法,但是不适合资源共享。
- 实现 Runnable 接口,重写run方法,start启动线程,适合资源共享,并且 避免了 Thread 方式单继承的缺点。
- 实现 Callable 接口,重写 call 方法,有返回值。
9.4 sleep和wait
- sleep():Thread类的方法,释放资源不释放锁
- wait():Object类的方法,释放资源释放锁,只能用于同步代码块中
9.5 死锁问题
锁的相互嵌套调用导致的,同步方法中嵌套同步代码块,同步代码块中调用同步函数。
9.6 synchronized 的底层怎么实现
待办1
10、关键字
10.1 final
- 修饰 变量 时:当修饰基本类型变量,该变量值初始化之后不能再改变;当修饰引用类型变量时,初始化之后不能再指向其他对象。
- 修饰 类 时:该类不能被继承,final类中所有成员方法都会被隐式地指定为final。
- 修饰 方法 时:锁定方法,该方法定义不能被修改(重写)。类中所有private方法被隐式地指定为final。
11、异常
11.1 Java异常类层次结构图
- Error(错误):是程序无法处理的错误,代码运行时 JVM(Java 虚拟机)出现的问题。
- Exception(异常):是程序本身可以处理的异常。
异常和错误的区别:异常能被程序本身处理,错误是无法处理。
11.2 在以下4种特殊情况下,finally块不会被执行
- 在finally语句块第一行发生了异常。
- 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行
- 程序所在的线程死亡。
- 关闭CPU。
注意:
- System.exit(0)是正常退出程序,会将整个虚拟机里的内容都停掉清空
- System.exit(1)或者说非0表示非正常退出程序
- 当try语句和finally语句中都有return语句时,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。
12、序列化与反序列化
序列化是将Java对象转为字节序列,从而进行持久化或数据通信。反序列化是将字节序列转为Java对象。
12.1 序列化的底层原理
- 将对象实例相关的元数据输出
- 递归输出类的超类描述,直到不再有超类
- 从最顶层的超类开始输出类的实例对象的数据
- 从上之下递归输出对象的数据
12.2 序列化和反序列化步骤
被序列化的对象需要继承 Serializable 类。
- 序列化步骤:
FileOutputStream fos = new FileOutputStream("d:/object.object");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(new User());
- 反序列化步骤:
FileInputStream fis = new FileInutStream(“d:/object.object”);
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User)fis.readObject();
12.3 transient
static和transient修饰的成员变量不能被序列化,transient只能修饰变量,不能修饰类和方法。
13、I/O流
13.1 获取键盘输入方法
- Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
- BufferedReader (需要处理异常)
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String s = bufferedReader.readLine();
13.2 四个抽象类基类
Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
13.3 既然有了字节流,为什么还要有字符流?
- 避免乱码
- 提高性能,字符串是由Java虚拟机将字节转换得来,比较耗时
一般视屏,图片等媒体资源采用字节流,涉及的字符的文本等采用字符流。
13.4 BIO、NIO、AIO 有什么区别?
- BIO:同步阻塞 I/O 模式(Blocking I/O),数据的读写阻塞在一个线程内完成。不用过多考虑系统的过载和限流。
- NIO:同步非阻塞 I/O模式(Non-blocking),对应 java.nio 包,提供了 Channel,Selector,Buffer 等抽象,面向缓冲基于通道的 I/O 方法。NIO提供了 SocketChannel 和 ServerSocketChannel ,都支持阻塞和非阻塞。非阻塞模式对于低负载、低并发提升开发效率和更好的维护性。
- AIO:异步非阻塞 I/O 模式(Asynchronous),异步NIO基于事件和回调机制,应用操作后直接返回,当后台处理完成后,操作系统会通知相关线程进行后续操作。
14、集合
14.1 数组转成集合
14.1.1 Arrays.asList()使用指南
将一个数组转换为一个 List 集合。Arrays.asList() 将数组转换为集合后,底层其实还是数组。Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays。 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。
返回的集合不能使用集合相关方法,该方法只是转换接口,后台数据仍是数组。
使用注意事项:
传递的数组必须是对象数组,不能是基本类型(当传入一个原生数据类型数组时,Arrays.asList() 的真正得到的参数就不是数组中的元素,而是数组对象本身!)
14.1.2 数组转成集合正确操作
- 直接方法
public static <T> List<T> arrayToList(final T[] array){
int len = array.length-1;
final List<T> list = new ArrayList<>();
while(len >= 0){
list.add(array[len--]);
}
return list;
}
- 最简便方法(推荐)
List list = new ArrayList<>(Array.asList("a","b","c"));
- 使用 Java8 的Stream
Integer [] myArray = {1, 2, 3};
List list = Arrays.stream(myArray).collect(Collectors.toList());
//基本类型也可以实现转换
int [] myArray2 = {1, 2, 3};
List list2 = Arrays.stream(myArrays).collect(Collectors.toList())
- 使用 Guava
- 对于不可变集合,使用 Immutable 类及其 of(参数) 方法与 copeOf(数组) 工厂方法
List <String> il1 = ImmutableList.of("string","elements");
String[] arr = {"string","elements"};
List <String> il2 = ImmutableList.copyOf(arr);
- 对于可变集合,使用 Lists 类及其 newArrayList 工厂方法
List <String> l1 = Lists.newArrayList(collection); // from collection
List <String> l2 = Lists.newArrayList(array); // from array
List <String> l3 = Lists.newArrayList("string","elements"); // from varargs
- 使用Apache Commons Collections
List<String> list = new ArrayList<>();
CollectionUtils.addAll(list,str);
15、循环/迭代器
15.1 不要在 foreach 循环里进行元素的 remove/add 操作
如果要对元素进行 remove 操作,可以使用迭代器的 remove 方法(如果并发需要对Iterator对象加锁),而不是集合类的 remove 方法。因为如果列表在任何时候从结构上修改创建迭代器之后,以任何方式除非迭代器自身的 remove/add 方法,迭代器都会抛出一个 ConcurrentModificationException,这就是单线程状态下产生的 fail-fast机制。
fail-fast机制:多线程下对 fail-fast 集合进行修改时,可能会抛出 ConcurrentModificationException,单线程下也会产生。
java.util 包下所有集合类都是 fail-fast ,而java.util.concurrent 包下所有类都是 fail-safe 的。