1、JDK&JRE&JVM

  • JDK,即Java Development Kit

Java开发工具包,提供了Java的开发环境和运行环境

包含了编译Java源文件的编译器Javac,还有调试和分析的工具。

  • JRE,即Java Runtime Environment,Java运行环境,包含Java虚拟机及一些基础类库
  • JVM,即Java Virtual Machine,Java虚拟机,提供执行字节码文件的能力

如果只是运行Java程序,那么只需要装JRE即可

JVM是实现Java跨平台的核心,但JVM本身不是跨平台的,也就是说,不同平台需要安装不同的JVM

image-20210226003719315

2、==与equals的区别

2.1、==

  • 如果比较的是基本数据类型,那么比较的是数值
  • 如果比较的是引用类型,那么比较引用指向的值,也就是地址。

2.2、equals

  • 默认比较的也是地址,equals方法是定义在Object类上的,默认的实现就是比较地址 ,Object上equals方法的源码如下,可以看到,Object的equals方法底层调用了 “==” 进行比较
1
2
3
public boolean equals(Object obj) {
return (this == obj);
}
  • 对于自定义的类,如果需要比较的是内容(类似String、Integer),那么就需要重写equals方法,我们可以看一下String和Integer中的equals方法

  • String的equals方法

    • 先判断传入的对象和当前字符串对象地址是否相同
    • 如果地址不同,判断传入的对象是否为String类型,如果不是,直接返回false
    • 如果传入的对象是String类型对象,那么判断长度是否相等,如果不等,直接返回false
    • 如果两者长度相等,那么就将两者char数组中的字符一一取出,逐一判断是否相等,若有不等的字符,直接返回false
    • 如果以上条件均满足,返回true
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public boolean equals(Object anObject) {
    if (this == anObject) {
    return true;
    }
    if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = value.length;
    if (n == anotherString.value.length) {
    char v1[] = value;
    char v2[] = anotherString.value;
    int i = 0;
    while (n-- != 0) {
    if (v1[i] != v2[i])
    return false;
    i++;
    }
    return true;
    }
    }
    return false;
    }
  • Integer的equals方法

    • 先判断传入对象的类型,如果不为Integer类型,那么直接return false
    • 否则比较两个Integer对象的值

    注意:判断两个Integer对象值是否相等不能用”==”,需要使用equals方法。

    1
    2
    3
    4
    5
    6
    public boolean equals(Object obj) {
    if (obj instanceof Integer) {
    return value == ((Integer)obj).intValue();
    }
    return false;
    }

2.3、几个对象?

1
2
3
String s1 = new String("zs");
String s2 = new String("zs");
System.out.println(s1 == s2);

false

使用new关键字创建String对象时开辟了新空间,故地址不同。

1
2
3
4
String s3 = "zs";
String s4 = "zs";
System.out.println(s3 == s4);
System.out.println(s3 == s1);

true

使用了字符串常量池,s3和s4均指向常量池中的”zs”字符串对象

false

s1指向堆的地址,而s3指向常量池的地址

1
2
3
String s5 = "zszs";
String s6 = s3+s4;
System.out.println(s5 == s6);

false

字符串是一个不可变值,在使用 “+” 号连接字符串时,实际上是通过new关键字创建了一个新对象,这个新对象的值为s3 + s4,也即zszs,虽然内容相同,但地址不同。

1
2
3
4
final String s7 = "zs";
final String s8 = "zs";
String s9 = s7+s8;
System.out.println(s5 == s9);

true

由于使用final修饰,此时s7和s8不是一个变量,而是一个常量。

1
2
final String s10 = s3+s4;
System.out.println(s5 == s10);

false

由于s3、s4没有加final修饰,依然还是变量,所以仍然需要使用new StringBuilder去拼接字符串,此时创建了一个新对象,结果为false。

3、final

  • 使用final修饰类,表示这个类不可被继承,例如String类
  • 使用final修饰方法,表示这个方法不可被重写
  • 使用final修饰变量,这个变量就是常量

注意:在使用final修饰变量时

  • 如果修饰的是基本数据类型,表示这个值本身不可被修改
  • 如果修饰的是引用类型,那么代表引用的指向不可被改变,而被修饰引用的属性值是可以被修改的。
  • 比如以下的代码是可以的
1
2
final Student student = new Student(1,"Andy");
student.setAge(18);//注意,这个是可以的!

4、接口与抽象类的区别

4.1、JDK8之前

  • 语法:

    • 抽象类:方法可以有抽象的,也可以有非抽象, 有构造器
    • 接口:方法都是抽象,属性都是常量,默认有public static final修饰
    • 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
    • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
  • 设计:

    • 抽象类:同一类事物的抽取,比如针对Dao层操作的封装,如,BaseDao,BaseServiceImpl
    • 接口:通常更像是一种标准的制定,定制系统之间对接的标准

4.2、JDK8之后

  • 接口里面可以有实现的方法,注意要在方法的声明上加上default或者static

5、重写和重载

5.1、重载

重载发生在一个类里面,方法名相同,参数列表不同

重载和返回值类型无关,即以下写法不构成重载

1
2
public int add(int a,int b);
public double add(int a,int b);

5.2、重写

发生在父类和子类中,方法名相同,参数列表相同

6、ArrayList和LinkedList

6.1、底层数据结构的区别

ArrayList底层使用了数组,连续一块内存空间。

LinkedList,双向链表,不是连续的内存空间

6.2、常规结论

  • 注意,以下结论不这么严谨

ArrayList,查找快,因为是连续的内存空间,方便寻址,但删除,插入慢,因为需要发生数据迁移
LinkedList,查找慢,因为需要通过指针一个个寻找,但删除,插入块,因为只要改变前后节点的指针指向即可。

6.3、ArrayList细节分析

增加

  • 添加到末尾,正常不需要做特别的处理,除非现有的数组空间不够了,需要扩容

    • 数组初始化容量多大?10,当你知道需要存储多少数据时,建议在创建的时候,直接设置初始化大小

    • 怎么扩容?

      • 当发现容量不够之后,就进行扩容
      • 按原先数组容量的1.5倍进行扩容,位运算,下面是关键的源码
    • int oldCapacity = elementData.length;
      int newCapacity = oldCapacity + (oldCapacity >> 1);
      
      1
      2
      3
      4
      5

      - - 再将原先数组的元素复制到新数组,使用数组工具类Arrays的copyOf方法

      - ```java
      elementData = Arrays.copyOf(elementData, newCapacity)
    • 如果添加到其他位置,那么这个时候就要做数组整体的搬迁

删除

  • 删除末尾,并不需要迁移
  • 删除其他位置,这个时候需要搬迁

修改

  • 修改之前先定位

6.4、LinkedList细节分析

  • 提供了两个引用:first和last

  • 增加

添加到末尾,创建一个新的节点,将之前的last节点设置为新节点的pre,新节点设置为last

我们看下源码:

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
void linkLast(E e) {
//获取到最后一个节点​
final Node<E> l = last;
//构建一个新节点,将当前的last作为这个新节点的pre​
final Node<E> newNode = new Node<>(l, e, null);
//把last指向新节点​
last = newNode;
//如果原先没有最后一个节点​
if (l == null)
//将first指向新节点​
first = newNode;
else
//否则,将原先的last的next指向新节点​
l.next = newNode;
size++;
modCount++;
}
Node节点的定义:内部类
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

7、ArrayList和Vector

7.1、基本介绍

ArrayList:线程不安全,效率高,常用
Vector:线程安全的,效率低

7.2、CopyOnWriteArrayList

由于ArrayList是线程不安全的,在高并发读写情况下,ArrayList可能会报ConcurrentModificationException 异常,所以我们必须寻找在高并发条件下能够保证线程安全的替代品。

Vector是线程安全的,但由于它使用synchronized来保证安全性,所以导致它效率低,所以在高并发条件下也不考虑Vector。

对于并发量小的情况,我们可以使用Collections工具类的synchronizedList()来包装一个ArrayList,使原本线程不安全的ArrayList对象线程安全。

在高并发条件下,我们可以使用CopyOnWriteArrayList来保证线程安全,这种数据结构使用读写分离来提高效率。

8、HashMap、HashTable和ConcurrentHashMap的区别

8.1、基本区别

Hashtable是线程安全的,但效率低
HashMap是线程不安全的,但效率高
Collections.synchronizedMap(),工具类提供了同步包装器的方法,来返回具有线程安全的集合对象,但性能依然有问题

1
2
3
4
5
6
7
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
//在这个类的内部方法实现上,也只是单纯加上了锁
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}

为解决这样的矛盾问题,所以JDK提供了并发包,来平衡这样的问题(java.util.concurrent)

8.2、ConcurrentHashMap(重点)

  • 兼顾了线程安全和效率的问题

分析:HashTable锁了整段数据(用户操作是不同的数据段,依然需要等待)
解决方案:把数据分段,执行分段锁(分离锁),核心把锁的范围变小,这样出现并发冲突的概率就变小
在保存的时候,计算所存储的数据是属于哪一段,只锁当前这一段

  • 注意:分段锁(分离锁)是JDK1.8之前的一种的方案,JDK1.8之后做了优化。

JDK1.7跟JDK1.8在ConcurrentHashMap的实现上存在以下区别:

1,数据结构

JDK1.7采用链表的方式,而JDK1.8则采用链表+红黑树的方式

2,发生hash碰撞之后

JDK1.7发生碰撞之后,会采用链表的方式来解决

JDK1.8发生碰撞之后,默认采用链表,但当链表的长度超过8,且数组容量超过64时,会转换为红黑树存储

3,保证并发安全

JDK1.7采用分段锁的方式,而JDK1.8采用CAS和synchronized的组合模式

4,查询复杂度

JDK1.7采用链表的方式,时间复杂度为O(n),而JDK1.8在采用红黑树的方式时,时间复杂度为O(log(n))

9、IO流的分类和选择

9.1、分类

  • 按方向分:输入流,输出流

(注意,是站在程序的角度来看方向),输入流用于读文件,输出流用于写文件

  • 按读取的单位分:字节流,字符流

  • 按处理的方式分:节点流,处理流

比如,FileInputStream和BufferedInputStream(后者带有缓存区功能-byte[])

  • IO流的4大基类:InputStream,OutputStream,Reader,Writer

9.2、选择

字节流可以读取任何文件
读取文本文件的时候:选择字符流(假如有解析文件的内容的需求,比如逐行处理,则采用字符流,比如txt文件)
读取二进制文件的时候,选择字节流(视频,音频,doc,ppt)

10、Exception和Error

10.1、Exception和Error的区别

Exception和Error两者均是Throwable的子类,一个异常只有是Throwable的子类,才能被程序捕获和抛出

image-20210226202153816

  • Error指正常情况下不太可能出现的情况,绝大部分的Error指程序崩溃,处于非正常不可恢复的状态,如OutOfMemoryError和StackOverflowError
  • Exception是程序正常运行中可以预料的意外情况,可以捕获和处理

10.2、运行时异常和一般异常的区别

非运行时异常

  • 在编译时被强制检查的异常,在方法声明中声明的异常,如ClassNotFoundException、IOException

运行时异常

  • 不受检查但通常在编码中可以避免的逻辑错误,根据需要来判断如何处理,不需要再编译器强制要求;如ConcurrentModificationException、NullPointerException等

11、Servlet生命周期

  • Web容器加载Servlet类并实例化(默认延迟加载,一次)
  • 调用init方法进行初始化(一次)
  • 用户请求Servlet,请求到达服务器时,运行其service方法(每次)
  • service方法中根据用户请求方式来调用对应的doXXX方法(每次)
  • 销毁实例时调用destroy方法(一次)

12、转发(forward)和重定向(redirect)的区别

  • 请求转发是容器控制的跳转,服务器直接访问目标地址,把目标地址响应的内容读取出来,直接发给浏览器。浏览器不知道请求从哪里来,浏览器地址不改变
  • 重定向是服务器收到请求后,返回一个状态码给浏览器,浏览器请求新地址,地址栏改变
  • 请求转发效率更高,尽量用请求转发,但请求转发不能跳转到其他服务器上,重定向可以跳转到其他服务器上。

13、jsp内置对象

  • request

向客户端请求数据

  • response

封装了jsp产生的响应,然后被发送到客户端以响应客户请求。

  • pageContext

为jsp页面包装页面的上下文

  • session

用于保存每个用户的信息,以便跟踪每个用户的操作状态

  • application

应用程序对象

  • out

向客户端输出数据

  • config

表示Servlet的配置,当一个Servlet初始化时,容器把某些信息通过此对象传递给这个Servlet

  • page

Jsp实现类的实例,它是Jsp本身,通过这个可以对它进行访问

  • exception

反映运行时的异常

14、Get和Post的区别

  • Get将表单中数据按照param-value的形式添加到action所指向的URL后面,且两者用”?”连接,各变量间使用”&”连接。
  • Post将表单中数据存放再form数据体中,按照变量和值对应的方式,传递到action所指向的URL
  • Get是不安全的,因为传输过程中数据被放在请求URL中;而Post的所有操作对用户来说都是不可见的
  • Get传输的数据量小,这主要是因为URL有长度限制;而Post可以传输大量的数据,所以上传文件只能用Post
  • Get限制form表单数据必须为ASCII字符,Post支付整个ISO字符集

15、Session和Cookie的区别

  • session保存在服务器,客户端不知道其中信息;cookie保存在客户端。
  • session中保存的是对象,cookie保存的是字符串
  • session不能 区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到。而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的
  • session需要借助cookie才能正常访问,如果客户端完全禁止cookie,那么session将失效
  • session存储的数据大小受服务器内存控制,而cookie一般大小为4k

16、sleep和wait的区别

  • 所属的类不同

sleep方法定义在Thread上

wait方法定义在Object上

  • 对于所资源的处理方式不同

sleep不会释放锁

wait会释放锁

  • 适用范围

sleep可以使用在任何代码块

wait必须在同步方法或同步代码块中执行

  • 和wait配套使用的方法
  • void notify()

唤醒在此对象监视器上等待的单个线程

  • void notifyAll()

唤醒在此对象监视器上等待的所有线程

17、JSP和Servlet的区别

  • 技术角度

JSP本质就是一个Servlet
JSP的工作原理:JSP->翻译->Servlet(java)->编译->Class(最终跑的文件)

  • 应用角度

JSP特点在于实现视图,Servlet特点在于实现控制逻辑