Java知识汇总(日常更新)

Java基础

面向对象

1. 面向对象的优缺点

  • 优点:容易维护、复用、扩展。由于面向对象有封装、继承和多态三大特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
  • 缺点:性能比面向过程低

构造函数

是一种特殊的方法,主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。

2. 封装

把一个对象的属性私有化,同时提供一些可以被外界访问的属性和方法,便于使用,提高复用性和安全性。

3. 继承

使用已存在的类作为基础并建立新的类,新的类可以定义自己的属性和方法,也可以使用父类非private修饰的属性和方法。

4. 多态

多态分为编译时多态和运行时多态,其中编译时多态主要指方法的重载,运行时多态指的是方法的重写。

多态的实现

Java实现多态有三个必要条件:

  • 继承:多态中必须存在继承关系
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法
  • 向上转型(父类引用指向子类对象):多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

只有满足以上三个条件,我们才能在同一个继承结构中处理不同的对象,从而达到执行不同的行为。

5.抽象技术的目的是什么?

抽象是将一类对象的共同特征总结出来构造类的过程,只关注对象有哪些属性和行为。

重写和重载

构造器是否可以被重写

构造器不能被继承,所以不能被重写,但是可以被重载

重载和重写的区别

  • 重载:发生在同一个类中,方法名相同参数列表不同,可以是参数类型不同,个数不同,顺序不同,与方法返回值和访问修饰符无关
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时会调用子类的方法

static

static能创建独立于具体对象的域变量或方法,以至于即使没有创建对象也能使用属性和调用方法。还有一个关键作用是用来形成静态代码块以优化程序性能,它只会在类加载时执行一次,很多时候用于初始化操作。

静态只能访问静态,非静态既可以访问非静态,也可以访问静态

finalize

首先finalize是Object类中的方法,它有些类似于c++中的析构函数,而在Java中没有析构函数这一概念,finalize就可理解为是替代析构函数的,它与析构函数不同的地方在于,析构函数是一定会释放对象的,而finalize不会立即释放对象,它一般不由我们自己调用,而是在垃圾回收器空闲的时候调用,我们可以给对矮对象赋空值,使用System.gc方法来唤醒垃圾回收器,提醒它尽快回收对象,而垃圾回收器会调用每个对象的finalize方法去回收对象。

finally中的代码一定会被执行吗?

在正常情况下,finally中的代码是一定会被执行的,它是与try···catch结合使用的,用于一段无论程序是否出现异常都需要被执行的代码,比如释放数据库连接对象、IO流对象。但是也有例外情况,一种是在try或catch中直接return,方法直接出栈,finally就不会执行,另一种是执行到try或catch时,虚拟机异常退出或手动退出,finally也不会执行,例如System.exit(0).

equals默认比较的地址还是内容?

首先equals是Object类中的方法,它的初始代码是return(this==obj); 也就是说对于基本数据类型,它比较的是内容,而对于引用数据类型比较的是地址,如果要实现引用数据类型比较内容,就必须重写equals方法,String中的equals方法就是重写过的,所以它比较的就是内容。

==和equals的区别

== 作用是判断两个对象的地址是否相等,即,判断两个对象是不是同一个对象。对于基本数据类型比较的是内容,引用数据类型比较的是地址

所以比较是否相等时,都是用equals 并且在对常量相比较时,把常量写在前面,因为使用object的
equals object可能为null 则空指针

阿里的代码规范中只使用equals ,阿里插件默认会识别,并可以快速修改,把“==”替换成equals

为什么重写equals必须重写hashCode?

  1. 使用hashCode方法提前校验,可以避免每次对比都调用equals方法,提高效率
  2. 保证是同一对象,如果重写了equals,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,所以要重写来避免。

hashCode与equals之间的相关规定:

  • 如果两个对象相等,则hashCode值也一定相同
  • 两个对象相等,对两个对象分别调用equals都返回true
  • 两个对象有相同的HashCode值,他们也不一定相等。

总结:就是存在一种情况:就是说重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象hashcode不相等,所以要避免这种情况。hashCode与equals之间是有相关规定的,如果两个对象相等,则hashcode值也一定相同。

抽象类和接口的区别

从代码层面看,抽象类可以有抽象方法和非抽象方法,成员变量可以是任意修饰符,而接口只能有抽象方法,并且被public abstract修饰,成员变量默认是被public static final修饰,也就是所谓的常量。抽象类可以有构造器,接口不允许有。

从设计层面来看,抽象类是对事物的抽象,即类的抽象;接口是对行为的抽象,抽象类是一种模板式设计,而接口是一种规范。

自动装箱和自动拆箱

  • 自动装箱

不需要调用构造方法,把基本类型转换为类类型就叫装箱

1
2
3
4
5
int i=5;
//基本类型转换成封装类型
Integer it=new Integer(i);
//自动转换就叫装箱
Integer it2=i;
  • 自动拆箱
    不需要调用Integer的intValue方法就自动转换成int类型就叫拆箱
    1
    2
    3
    4
    5
    6
    int i=5;
    Integer it=new Integer(i);
    //封装类型转换成基本类型
    int i2=it.intValue();
    //自动转换就叫拆箱
    int i3=it;

String、StringBuffer、StringBuilder

三者区别

  • 可变性
    • String类的对象是不可变的,StringBuffer和StringBuilder都继承自AbstractStringBuilder类,它是使用字符数组保存字符串的,所以他们都是可变的。
  • 线程安全
    • String不可变嘛,所以是是线程安全的,StringBuffer对调用的方法加了同步锁,所以是线程安全的。StringBuilder是非线程安全的,性能略高。

字符串反转的实现

示例代码

1
2
3
4
5
String str="abcdefg";
StringBuffer sb = new StringBuffer();
sb.append(str);
sb.reverse();
System.out.println(sb.toString());

String类的常用方法

  • indexOf(): 返回指定字符的索引
  • charAt(): 返回指定索引处的字符
  • replace() : 字符串替换
  • trim(): 去除字符串两端的空白
  • split(): 分割字符串,返回一个分割后的字符串数组
  • getBytes(): 返回字符串的byte类型数组
  • length(): 返回字符串长度
  • toLowerCase(): 将字符串转成小写字符
  • toUpperCase(): 将字符串转成大写字符
  • subString(): 截取字符串
  • equals(): 字符串比较

String实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package String;
import java.util.StringTokenizer;

public class test2 {
public static void main(String[] args) {
Compare();
System.out.println("--------");
SearchLast();
remove();
StringReplace();
StringReverse();
SearchString();
StringSplit();
System.out.println();
StringSplit2();
StringToUpper();
StringCompare();
}

//字符串比较
public static void Compare(){
String str="Hello World";
String str2="hello world";
Object str3=str;

//对象位置出现字符不同则返回两个字符的编码之差,后面的字符不再比较,故为-32
System.out.println(str.compareTo(str2));
//0
System.out.println(str.compareToIgnoreCase(str2));
//0
System.out.println(str.compareTo(str3.toString()));
}

//查找字符串最后一次出现的位置
public static void SearchLast(){
String str="Hello World,Hello Yolin";
int lastIndex = str.lastIndexOf("Yolin");
if (lastIndex==-1){
System.out.println("没有找到字符串Yolin");
}else{
//最后出现的位置:18
System.out.println("最后出现的位置:"+lastIndex);
}
}

//删除字符串中的一个字符
public static void remove(){
String str="This is Java";
//Thi is Java
System.out.println(removeCharAt(str, 3));
}
public static String removeCharAt(String s,int pos){
return s.substring(0,pos)+s.substring(pos+1);
}

//字符串替换
public static void StringReplace(){
String s="Hello World!";
//Hexxo Worxd!
System.out.println(s.replace("l","x"));
//Hellx Wxrld!
System.out.println(s.replace("o","x"));
//HeDlo World!
System.out.println(s.replaceFirst("l","D"));
//HeDDo WorDd!
System.out.println(s.replaceAll("l", "D"));
}

//字符串反转
public static void StringReverse(){
String s="hanchen";
String s1 = new StringBuffer(s).reverse().toString();
//nehcnah
System.out.println(s1);
}

//字符串搜索
public static void SearchString(){
String str="Google and Baidu";
int s = str.indexOf("and");
if (s==-1){
System.out.println("没有找到字符串and");
}else {
//and的位置:7
System.out.println("and的位置:"+s);
}
}

//字符串分割
public static void StringSplit(){
String str="www.yolin.xyz";
String[] s;
//注意 . 需要转义
String[] splits = str.split("\\.");
//输出: www/yolin/xyz/
for (String split:splits){
System.out.print(split+"/");
}
}

//字符串分割(StringTokenizer)
public static void StringSplit2(){
String str="This is String , Split by StringTokenizer, created by runoob";
StringTokenizer st = new StringTokenizer(str);
//通过空格分割:ThisisString,SplitbyStringTokenizer,createdbyrunoob
while(st.hasMoreElements()){
System.out.print(st.nextElement());
}
System.out.println();
//通过逗号分割:This is String Split by StringTokenizer created by runoob
StringTokenizer st2 = new StringTokenizer(str, ",");
while(st2.hasMoreElements()){
System.out.print(st2.nextElement());
}

}

//小写转大写
public static void StringToUpper(){
String str="yolin";
String s = str.toUpperCase();
System.out.println();
System.out.println("转换大写后:"+s);
}

//字符串性能测试
public static void StringCompare(){
long startTime=System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
String s1="hello";
String s2="hello";
}
long endTime=System.currentTimeMillis();
System.out.println("String关键字创建字符串:"+(endTime-startTime)+"毫秒");

long startTime1=System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
String s1=new String("hello");
String s2=new String("hello");
}
long endTime1=System.currentTimeMillis();
System.out.println("String对象创建字符串:"+(endTime1-startTime1)+"毫秒");
}
}

序列化和反序列化

概念

序列化就是将对象转成字节序列的过程,反序列化就是将字节序列转成对象的过程,常见的将对象转换成json格式,就可以理解为是一个序列化的过程

什么情况下需要序列化

当Java对象需要持久化存储或在网络上传输时

序列化如何实现

对象所属的类必须实现Serializable接口,该接口没有需要实现的方法(标记接口),实现的目的是告诉JVM这个类的对象可以被序列化

可序列化对象为何要定义SerialVersionUID值

SerialVersionUID是为了序列化对象版本控制,告诉JVM各版本反序列化时是否兼容,如果在新版本中这个值修改了,新版本就不兼容旧版本,反序列化会抛异常。

值传递和引用传递有何区别?

  • 值传递:指的是在方法调用时,传递的参数是值的拷贝
  • 引用传递:指在方法调用时,传递的参数是按引用地址进行传递,传递的是值的引用,即传递前和传递后都指向同一个引用。

BIO,NIO,AIO的区别

  • BIO:Block IO 同步阻塞式IO,就是我们平常使用的传统IO,它的特点是模式简单使用方便,并发处理能力低。
  • NIO:Non IO 同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用
  • AIO:Asynchronous IO是NIO 的升级,也叫NIO2,实现了异步非堵塞IO,异步IO的操作基于事件和回调机制。

集合

数组和链表的区别

  1. 数组是静态分配内存的,链表是动态分配内存的
  2. 数组元素在栈区,链表元素在堆区
  3. 数组在内存中是连续的,链表不连续

ArrayList和LinkedList区别

ArrayList底层是数组,元素有索引值,所以查询快,而增加与删除操作需要重新计算数组大小和更新索引,所以效率低

LinkedList底层是链表,插入或删除操作只需要移动节点的指向,所以增删快,而查询需要遍历链表所以查询慢。

两者再向尾部添加元素时,因LinkedList是通过new一个Node对象来存的,所以当数据量大于千万级别时,new对象的时间大于扩容时间,则会出现ArrayList效率高于LinkedList.

ArrayList扩容机制

ArrayList扩容的目的就是实现数组大小的动态增长,普通数组是不可变长的,因为在定义时就已经确认了大小,而ArrayList本质就是用一个新容量的数组存放旧数组,对外看起来是集合容量的改变。

底层代码是发生在添加元素时,首先判断size是否大于默认容量10。如果小于默认容量,直接在原来的基础上添加,且size+1.如果大于等于10就要进行扩容,核心方法是grow(),它会创建一个新数组,通过位运算符将新数组容量更新为旧容量的1.5倍,再将旧数组拷贝复制到新数组中

深拷贝和浅拷贝

浅拷贝仅指向被复制的内存地址,原地址发生改变,复制出来的对象也会改变。深拷贝是内容的拷贝,计算机会开辟一块新内存地址。

Iterator

迭代器Iterator是什么,有何特点

Iterator是遍历Collection集合的接口,通过集合的iterator方法来获取迭代器对象,该对象的常用方法有

  • next():返回迭代中的下一个元素
  • hasNext():如果迭代具有更多元素,返回true

特点 : Iterator只能单向遍历,但更加安全,可确保在当前遍历集合元素被更改时抛异常

1
2
3
4
5
6
7
Collection<String> c=new ArrayList<String>();
c.add("demo");
Iterator<String> it=c.iterator();
while(it.hasNext()){
String s=it.next();
System.out.println(s);
}

Iterator与ListIterator

Iterator可以遍历Set和List集合,而ListIterator只能遍历List集合

Iterator只能单向遍历,而ListIterator可以双向遍历

ListIterator实现了Iterator接口,在其基础上添加了新功能,如替换一个元素,获取前面或后面元素的索引。

反射

什么是反射机制

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

通过反射获取Class类实例

有三种方式分别是

  • ①已知具体的类,通过类class属性获取(最安全可靠,性能最高)
1
Class c1=Person.class;
  • ②已知类的实例,调用实例的getClass()获取Class对象
1
Class c2=person.getClass();
  • ③已知类的全类名,可通过Class类的静态方法forName() 获取
1
Class c3=Class.forName("demo.student");

反射的应用场景

  1. 使用JDBC连接数据库时使用Class.forName().通过反射加载数据库驱动程序
  2. Spring框架中的xml配置,通过xml配置装载Bean 的过程。
    • 将程序内所有xml或Properties配置文件加载到内存中
    • 得到对应实体类字节码和属性信息
    • 通过反射机制获得某个类的Class实例
    • 动态配置实例的属性

反射优缺点

优点:动态加载类,提高代码的灵活性

缺点:性能开销大,涉及了动态类型的解析,JVM无法进行优化。

网络

TCP三次握手

为什么要三次握手?

为了防止服务器端开启一些无用的连接增加服务器开销

  1. 客户端向服务器发送报文表示自己想要进行连接,假设报文序号seq=100
  2. 服务器接收后会告诉客户端,已经准备好接收序号101的信息,并发送一个确认报文(ACK=1),表示已正确接收,同时还会发送一个随机序号如200
  3. 客户端收到后也会给服务器发送确认报文ACK,告诉它已经准备好接收信号为201的信息

补充:

  • 序号(seq):由于tcp的可靠连接,所以tcp会为字节流中每一个字节都加上一个编号
  • 确认号(ack):表示已正确接收的编号为N,要求发送端下一个应该发送的序号为N+1
  • ACK:确认标志位,为1时表示确认号有效
  • SYN:SYN置为1,表示这是一个连接请求或连接接受报文。

http状态码

  • 200 请求成功
  • 302 重定向
  • 404 请求资源不存在
  • 500 服务器端错误

MySQL

引擎

MySQL存储引擎

  • Innodb:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计目标就是处理大数据容量的数据库系统。
  • MyISAM:(原本MySQL的默认引擎),不提供事务的支持,也不支持行级锁和外键
  • MEMORY:所有的数据都在内存中,数据的处理速度快,但安全性不高

MyISAM与InnoDB区别

· MyISAM Innodb
存储结构 每张表被存放在三个文件中:frm表格定义、MYD(MYData)数据文件、MYI(MYIndex)索引文件 所有表都保存在同一文件中(也可能是多个文件,或者是独立的表空间文件)
外键 不支持 支持
事务 不支持 支持
锁支持(锁是避免资源竞争) 表级锁定 行级锁定、表级锁定、锁定力度小并发能力高
SELECT MyISAM更优 ·
INSERT · InnoDB更优
select count(*) myisam更快,因为其内部维护了一个计数器,可直接调取 ·
索引实现方式 B+树索引,myisam是堆表 B+树索引,Innodb是索引组织表
哈希索引 支持 不支持

索引

什么是索引

索引是一种数据结构,数据库索引是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中的数据。索引的实现通常是用B树及其变种B+树

更通俗的说索引就相当于目录,通过对内容建立索引形成目录,索引是一个文件,是占据物理空间的

索引的优缺点

优点:

  • 可大大加快数据的检索速度

缺点:

  • 创建和维护索引需要耗费时间,对表中数据增、删、改时也要动态维护,会降低执行效率
  • 索引需要占用物理空间

MyISAM索引与Innodb索引区别

  • InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引
  • InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。

索引的分类

创建索引的原则

  1. 索引不是越多越好,它会降低数据的写入性能
  2. 频繁作为查询条件的字段适合加索引
  3. 更新频繁的字段不需要加索引
  4. 区分度低的字段不适合加索引如性别
  5. 定义外键的字段需要加索引

最左前缀原则

组合索引中mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就会停止匹配,如a=1 and b=2 and c>3 and d=4,若建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到。

最左匹配原则

顾名思义就是最左优先,创建多列索引时,要根据业务要求,where子句中使用最频繁的一列放在最左边。

使用B+数的好处

B+树内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logn)时间找到最小的节点,然后通过链进行O(N)的顺序遍历即可。

数据库为什么使用B+树而不是B树

  • B树只适合随机检索,而B+树同时支持随机检索和顺序检索
  • B+树查询效率更稳定
  • 增删操作时,B+树效率更高

事务

什么是数据库事务

数据库事务可以包含数据库动作如查询、修改、删除、插入等,它们要么作为一个整体完全得到确认,要么完全失败

介绍一下事物的四大特性

① 原子性(Atomic)

事务的操作作为整体执行,要么全部执行,要么全部失败

② 一致性(Consistency)

数据在执行前和执行后处于一致状态

③ 隔离性(Isolation)

多个事务之间是隔离的,互不影响

④ 持久性 (Durability)

一旦事务提交了,会持久化写到数据库,对数据库的修改是永久性的

事务隔离级别以及MySQL默认隔离级别

SQL标椎定义了四个隔离级别:

  1. 读取未提交:最低隔离级别、事务未提交前就可被其他事务读取,(会出现幻读、脏读、不可重复读)
    • 脏读:表示一个事务能读取另一个事务还未提交的数据
    • 幻读:同一事务内多次查询返回的结果不一样,原因是另一个数组修改数据所影响
    • 不可重复读:指在一个事务内,多次读同一数据
  2. 读取已提交:一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读)
  3. 可重复读:进制读取到其他事务未提交的数据(会造成幻读)
  4. 序列化:代价最高,能防止脏读、幻读、不可重复读
隔离级别 脏读 不可重复读 幻影读
读取未提交(Read-Uncommitted)
读取已提交(Read-Committed) ×
可重复读(Repeatable-Read) × ×
序列化(Serilizable) × × ×

数据库的乐观锁和悲观锁

  • 乐观锁:对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁。适用于多读的应用类型,这样可以提高吞吐量
  • 悲观锁:对并发间操作产生的线程安全问题持悲观状态,每次去拿数据时都会持有一个独占的锁,就如synchronized。

内连接、外连接

参考资料

内连接查询 inner join

语句:

-- 组合两个表,返回关联字段相符的记录,即两个表的交集
select * from table_a a inner join table_b b on a.a_id=b.b_id

案例:

-- 在boy表和girl表中查出两表id字段一致的姓名
select boy.hid,boy.bname,girl.gname from boy inner join girl on girl.hid=boy.hid;

左连接(左外连接) left join

语句:

-- 左(外)连接,坐标的记录将会全部表示出来,而右表只会显示符合搜索条件的记录,右表记录不足的地方均为Null
select * from table_a a aleft join table_b b on a.id=b.id;

右连接(右外连接)right join

语句:

--与左连接相反,左表只显示符合条件的记录,右表权显示,左表记录不足的地方均为null
select * from table_a a right join table_b b on a.id=b.id;

触发器作用

触发器是一中特殊的存储过程,主要是通过事件来触发而被执行的。它可以强化约束,来维护数据的完整性和一致性

请我喝杯咖啡吧~

支付宝
微信