Java基础巩固

Java

主要记录下之前不熟的点

说一下接口和抽象类的区别吧

从他们的继承规则,所含变量类型,所含方法类型这三方面来说吧:
先说一下jdk8之前,然后再说一下jdk8之后接口中方法的一些改变

  1. java中的类只能继承一个抽象类,但可以实现多个接口。所以说在一个类已经继承了一个父类的情况下,在这两者中,它唯一的选择就是实现接口。
  2. 接口中变量类型都是public static final,抽象类则没有这种限制
  3. 接口中的全部方法为public abstract,而抽象类中也可以包含非抽象方法
    再jdk8中,接口中可以定义静态方法,它通过接口名.静态方法调用,接口中也可以定义默认实现,jdk9中,接口允许定义私有方法。

Java和C++的区别

可以从以下几个方面分析

  1. 指针,java不提供指针,更安全
  2. 继承机制
  3. CG

java多态的三种实现方式

方法的重载,继承或实现接口,父类引用指向子类对象

为什么说java是一种解释与编译共存的语言

先来理解什么是解释型语言,什么是编译型语言。
解释型语言:指不用提前编译,在用到时才进行解释执行,每次用到时都要重新解释效率较低。
编译新语言:提前编译成可以直接运行的二进制机器码。
java语言得先经过javac编译成class格式的字节码文件,再通过JVM中的解释器逐行将class文件解释执行。后来也出现了像JIT这种运行时编译,将热点代码编译成机器码存起来,方便下次调用执行

Java的基本数据类型,以及他们的大小

java的基本数据类型所占大小像其他语言一样随着硬件的架构发生变化,也是java语言有更改好的移植性的原因
image.png

讲一下java的泛型吧

  1. 泛型是jdk5以后引入的,他为程序员提供了一种编译时的安全检测机制,编译时就能发现类型的错误,本质上是参数化类型,即将使用到的具体的数据类型参数化
  2. 类型擦除:java中的泛型都是伪泛型,在编译期间泛型会被抹除,即类型擦除

例如你不可以直接向一个指定类型的list中添加非指定类型的数据,但可以通过反射获取到add()方法添加

  1. 几个常用到泛型的地方:泛型类、泛型接口,泛型方法
  • 泛型类和接口定义的方法差不多,都是在类/接口名后面加,字母可任意。类内的方法或属性都可以声明为T类型。
  • 泛型方法 生命泛型方法时必须要在返回值前面加来表明这是一个泛型方法,持有一个泛型T

public static < E > void printArray( E[] inputArray )

为什么重写equals必须重写hashcode

先来阅读一下object类源码,看到hashcode方法有如下注释
image.png
即如果两个对象通过equals比较像等,则通过hashcode也应返回相同的值

为什么HashMap中的数组长度必须为2的幂次方

image.png

JVM

CMS收集器

是一款以尽可能缩短垃圾回收停顿时间为目的的垃圾回收器。实现了用户线程与垃圾回收线程基本上听不工作
四个步骤:

  1. 初始标记:标记与GC Roots直接相关联的对象。
  2. 并发标记:同时开启用户线程与GC线程,这个阶段会对GC Roots进行跟踪,记录可达对象
  3. 重新标记:由于并发标记过程中,用户对象仍在运行,可达对象可能会发生变化。对这部分对象进行重新标记,这段的时间比初始标记的时间稍长,但远比并发标记时间短
  4. 并发清除:开启用户线程,同时对未标记的区域进行清除
    CMS的缺点:
  5. 对CPU敏感(默认的垃圾回收线程=(CPU数量+3)/4,随着cpu的增加,GC线程的比例降低,但是当cpu数量小于4时,就要分出接近一半的运算能力执行GC线程)
  6. 采用标记清除算法,容易产生内存碎片
  7. 无法处理浮动垃圾(从而要预留空间存储浮动垃圾)

G1收集器

它尽量的避免全局的垃圾回收,将堆内存分为多个固定大小的region,同时跟踪每一个区域的垃圾回收状况,维持了一个优先级列表,再规定的时间内选取优先级最高的区域进行GC。这种区域的划分以及优先级区域回收机制保证了G1在有限的时间内取得最高的垃圾回收效率。
特点:

  1. 并行与并发(充分利用多核cpu的性能,减少stop-the-world的时间)、
  2. 分代收集(虽然可以不配合其他收集器就能管理整个GC堆,让保留了分代的概念,对新建对象,存货很久的对象采用不同方式)、
  3. 空间整合(整体看采用标记整理算法,局部在两个region之间采用复制算法)、
  4. 可预测停顿(虽然CMS和G1的目的都是为了尽可能缩短GC的停顿时间,G1还建立了可预测的事件模型,让使用者明确指出在一个长度为M毫秒的时间片内)。
    步骤:初始标记、并发标记、最终标记、筛选回收

JAVA创建对象的过程

  1. 类的加载检验:在执行new指令之前,会先是否能在常量池中定位到这个类的符号引用,并且检查这个类的符号引用是否已经被加载,解析和初始化过。如果没有,那就必修先执行响应类加载过程。

  2. 分配内存:在类加载检查通过后,就可以确定应该为对象在堆内存中分配多大的空间。分配空间时有两种方法:指针碰撞法与空闲列表法。他们却决于堆内存是否规整,是否规整又取决于采用的是哪一种垃圾收集器。指针碰撞法对应Serial,ParNew垃圾收集器,空闲列表法,指维护了一个空闲的列表,指出哪些对内存是可用的,然后找出一块足够的空间为对象分配,并更新列表。
    这里还存在一个分配内存时的并发问题
    解决方法:CAS+冲突重试、TLAB(Thread Local Allocation Buffer):即在Eden区中事先为每个线程划分一块内存,线程中为对象分配内存时首先在TLAB中分配,空间不足,在使用CAS+冲突重试

  3. 初始化零值:将分配到的内存空间都初始化为0值,不包括对象头,这样可以使java代码在不赋初始值的情况下可以直接调用这些字段对应的零值

  4. 设置对象头:对对象头进行相应设置,对象头分为两部分,第一部分成为MarkWord(用于存储对象自身运行时的数据,包含哈希码,GC分代年龄,锁状态标记),另一部分是类型指针,JVM通过这个指针判断对象属于哪个对象实例

  5. 执行方法,根据程序员的意愿进行初始化值。

对象的内存布局

对象头:两部分,上一问中有解释。
实例数据
对齐填充

高并发/多线程

JMM的HAPPEN-BEFORE规则,哪些情况不能进行指令重排

(关与锁的有两条,关于线程的有3条)

  1. 在同一个线程中要保证串行语义
  2. Object的构造方法的执行与结束必须先于finalize()
  3. 传递性
  4. volatile修饰的变量写操作先于读
  5. unlock先于下一次lock
  6. 线程中断操作先于被中断线程的代码
  7. 线程所有操作先于线程结束
  8. 线程的start方法先于其他动作。

计网

简单介绍计网各层

计网体系结构有三种分法,分别为OSI七层体系结构:应用层、表示层、会话层...;TCP/IP四层结构:最底层为网络接口层;五层协议结构:应用层,传输层,网络层,数据链路层、物理层。这里以五层协议介绍

  1. 应用层的任务是通过应用进程间的交互完成特定的应用,应用层中的协议规定的是应用进程之间的通信与交互的规则。其中常见的协议DNS,HTTP,SMTP,FTP等
  2. 传输层的任务是向两台主机间进程间的通信提供通用的数据传输服务,通用即不同的应用进程均可以使用。复用和分用,其中主要的协议TCP,UDP
  3. 网络层计算机网络通信的两个进程间会经过多端数据链路和子网。网络层的任务就是通过选择合适的路由器及交换节点,将数据在双方进程中传输。常见协议IP。其中分组单位成为IP数据报。
  4. 数据链路层,由于数据的传输总是要在一段段数据链路中传输,就需要专门的数据链路协议。它将上层的ip数据报封装成帧,其中包含控制信息(同步信息,地址信息,差错控制等),它使链路两端接收到的数据做到无比特差错。控制信息使接收端接收到一个帧提取数据时知道从哪里开始哪里结束。控制信息还包括差错检验,常见的例如CRC循环入冗余检测,它会将错误的帧丢弃,也就是说它只能保证接收端收到的数据的帧是没有差错的,这时丢失的帧就要靠运输层中的TCP保证。当然数据链路层也有纠错的协议,在网上查帖子时看到如下解释:

 1. 对于通信质量良好的有线传输链路,数据链路层协议不采用确认和重传机制,不要求数据链路层向上提供可靠的传输,因为局域网信道质量很好,因此产生比特差错的概率是很小的,因此以太网提供的是尽最大努力的交付,是不可靠的交付。如果在数据链路层出现了差错就靠上层协议来完成改正差错的任务。例如,如果高层使用TCP协议,那么TCP发现丢失了一些数据的时,经过一段时间后,TCP就把这些数据重新传递给以太网进行重传。但以太网不知道这是重传帧,而是当做新的数据帧来发送。
 2. 对于通信质量较差的无线传输链路,数据链路层协议使用确认和重传机制,数据链路层向上提供可靠的传输服务。
————————————————
版权声明:本文为CSDN博主「Cool_Uncle」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37365385/article/details/80964404

  1. 物理层,比特流透明传输,尽可能屏蔽掉传输介质和物理设备间的差异。

有关RST标识

image.png

在进行TCP三次握手之前客户端和服务器要做哪些准备?

客户端为主动连接,服务器为被动连接,在进行TCP连接前,需要在内存中创建TCB(传输控制块)。它包含连接双方的socket以及数据缓冲区。由于服务器一开始无法预测谁会给它发起连接,所以将客户端的socket默认设为0。

讲一下TCP的三次握手

image.png

  1. 在连接创建之前,两端都处于closed。服务器先创建TCB,进入LISTEN状态。
  2. 客户端创建TCB,并发出连接请求报文。SYN=1,seq=x。客户端进入SYN-SEND状态(TCP规定请求连接的报文不允许携带数据,但是仍要花费一个序列号)
  3. 服务器接收到客户端发送的请求报文后,如果同意连接,会发送ACK=1,SYN=1,seq=y,ack=x+1的报文给客户端,服务器进入SYN-RSVD状态。
  4. 客户端接受来自服务器的报文后,会发送ACK=1,seq=x+1,ack=y+1的报文给服务器确认连接。并进入Established状态。(TCP规定ACK=1报文可以携带数据,不携带则不消耗序列号)
  5. 服务器接受来自客户端的确认报文进入Established状态。

为什么要三次握手而不是两次或四次

  • 为什么不是两次:为了防止之前已应失效的请求连接重新申请连接。即如果客户端之前发送过一次请求连接报文,但是由于网络问题,它并没有送达到服务器。之后客户端第二次发起连接请求给服务器并接受服务器发挥的ACK成功建立连接并传输数据后释放连接。之后先前那个由于网络问题被阻塞的请求报文,达到服务器。如果此时是两次握手的原则,C/S之间仍然能成功建立连接,但是并不互相传输数据,白白浪费网络资源。而如果采用三次连接,服务器也需要向客户端发送一个SYN=1的连接请求报文,客户端不予回应,连接则不会建立。
  • 为什么不是四次,经过三次握手已经能成功保证双方收发都是正常的,无需浪费资源在进行第四次握手确认。

TCP连接的释放,四次分手

image.png
C/S双发都可以主动发起释放连接请求

  1. 一开始双方都处于Established状态,客户端发出释放连接的报文,FIN=1,seq=u,进入FIN-WAIT-1状态(FIN不能携带数据,并且需要消耗一个序列号)
  2. 服务器接收到连接释放报文,发出确认报文,ACK=1,ack=u+1,seq=v,进入CLOSE-WAIT状态,即半关闭状态。此时客户端已经不再向服务器发送数据,服务器可继续向客户端发送未发完的数据
  3. 在接受服务器的确认报文后,客户端进入FIN-WAIT-2状态,接受服务器的数据
  4. 服务器的数据也发完后,向客户端发出释放连接请求,FIN=1,ACK=1,seq=w,ack=u+1并进入LAST-ACK阶段。
  5. 客户端发送确认报文ACK=1,ack=w+1,seq=u+1后并不是直接进入CLOSED,而是进入TIME-WAIT状态,等待2*MSL(报文最长存活时间)后,撤销TCB,才进入CLOSED
  6. 服务器接受确认报文后,撤销TCB,进入CLOSED

为什么要等2*MSL后才关闭

  • 如果客户端发送确认报文后,并没有送达到服务器,那么会触发超时重传,如果客户端立即关闭,那么服务器会一直给客户端发送FIN报文。
  • 使本次连接时间内的所有报文段都失效。从而使新的连接中不会出现旧连接的报文

如果建立连接后客户端/服务器出现故障怎么处理

TCP有KEEP-ALIVE机制

  • 客户端出故障,两小时未给服务器发送数据,服务器则会每75s向客户端发送探测报文,10次都未响应则断开连接。
  • 服务器故障(若服务器崩溃,会发送一个RST报文,告知客户端释放连接);若是断电断网等,则需客户端开启keep-alive,方法同上。

TCP怎么保证数据可靠

  1. 发送端将应用数据分为合适大小的数据块并进行编号,接收端接收数据块按编号排序好无误后上交给应用层
  2. ARQ协议(自动重传请求),若收到重复的报文段,则会丢弃。另外发送端发送一个报文后,会启动一个定时器,超时将会进行重传。
  3. 通过一个可变的滑动窗口协议来进行流量控制。发送双方都有一个固定大小的缓冲空间。接收端会通过确认报文中的窗口字段来控制发送窗口的大小,从而控制发送方的发送速率,使接收方收到的数据不会超过自身缓冲空间。
  4. 在报文中有一个数据和首部检验和,接收端收到报文若检验和没有出错则接受,出错丢弃数据
  5. 拥塞控制,相对于流量控制是一个宏观的概念。涉及到所有网络,路由器以及和传输相关的因素。进行拥塞控制的算法,慢开始、拥塞避免、快重传和快恢复(fast retransmit and recovery,FRR)
  6. 数据传输前会进行三次握手建立可靠连接。

ARQ协议

可分为停止等待ARQ和连续ARQ
停止等待ARQ:大体概括来讲就是发送数据后需要等待收到确认后,再放松后续数据,若收到重复数据会丢弃,超时未收到确认会重传
连续ARQ(就是计网课上讲的流水线协议):提高信道的利用率,维持一个发送窗口,可采用累计确认(GBN),即按需到达的最后一个数据的确认号,会丢弃掉无序的数据。还有另一种处理流水线差错的方法SR,选择性重传,通过在接收端也设置一个接受窗口,设置缓冲区,可接受无序到达的数据

DNS什么时候用TCP

使用TCP时的情况:查询数据超过512字节
或区域传送时使用TCP,主要有一下两点考虑:
1.辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多。
2.TCP是一种可靠的连接,保证了数据的准确性。

地址栏中输入一个网址到返回页面会发什么

另一篇文章中对这个问题进行了详细介绍

HTTP1.0和HTTP1.1版本的区别

  • 除了老生常谈的长连接与短连接外
  • 还有增添了24个响应状态码:409(Config)、410(GONE)
  • 增加了请求报文头缓存标识,提供了更多的缓存策略
  • 1.0只能请求整个对象,且不能断点续传。1.1支持只请求对象的一部分。

Java编程测试TCP与UDP

TCP

package IO;

import org.junit.Test;
import sun.management.BaseOperatingSystemImpl;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPTest2 {
    @Test
    public void Client() throws IOException {
        Socket socket = null;
        OutputStream os = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            InetAddress inet = InetAddress.getLocalHost();
            socket = new Socket(inet, 8888);
            os = socket.getOutputStream();
            os.write("我是客户端".getBytes());
            socket.shutdownOutput();//必须要加这句话,否则阻塞,C/S互相等待
            is = socket.getInputStream();
            baos = new ByteArrayOutputStream();
            byte[] buf = new byte[5];
            int len;
            while ((len = is.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }
            System.out.println(baos.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
                os.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    @Test
    public void Server() {
        ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream bos = null;
        FileOutputStream fos = null;
        try {
            ss = new ServerSocket(8888);
            socket = ss.accept();
            is = socket.getInputStream();
            fos = new FileOutputStream(new File("TCPTest"));
            byte[] buf = new byte[10];
            int len;
            //使用byte[]如果有中文会出现乱码,可以改用如下方法
            //注意:此处ByteArray后面是OutputStream不是Input
            bos = new ByteArrayOutputStream();
            while ((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
                bos.write(buf, 0, len);
            }
            System.out.println("接受语句存储本地成功");
            System.out.println(bos.toString());
            OutputStream os = socket.getOutputStream();
            os.write("已经成功收到客户端的信息".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
                bos.close();
                is.close();
                socket.close();
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

image.png

image.png
UDP

public class UDPTest {
    @Test
    public void sender() throws IOException {
        DatagramSocket socket=new DatagramSocket();
        byte[] buf ="我是UDP方式发送的消息".getBytes();
        DatagramPacket packet=new DatagramPacket(buf,0,buf.length,InetAddress.getLocalHost(),1234);
        socket.send(packet);
        socket.close();

    }
    @Test
    public void receiver() throws IOException {
        DatagramSocket socket=new DatagramSocket(1234);
        byte[] buf=new byte[1024];
        DatagramPacket packet=new DatagramPacket(buf,0,buf.length);
        socket.receive(packet);
        System.out.println(new String(packet.getData(),0,packet.getLength()));
    }
}

image.png

拥塞控制算法

看到一张图,挺清晰的
image.png

粘包拆包发生的原因以及解决办法

粘包、拆包发生原因
发生TCP粘包或拆包有很多原因
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
等等。
粘包、拆包解决办法
通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
原文链接https://blog.csdn.net/zhangjunli/article/details/103212526

HTTP2.0和HTTP1.1的区别

多路复用
头部压缩
服务器推送
一篇将这个文章:https://blog.csdn.net/ailunlee/article/details/97831912

数据库

SQL优化

  • 尽量避免全表查询 select *
  • 避免使用xxx=null作为查询条件,会导致放弃索引全文扫描
  • 避免使用!=,<>符号
  • 用in替换or,mysql对in进行了优化,时间复杂度为logn
    关于IN和OR的区别,在High performance mysql 3rd中,有一段话描述的非常清楚:

IN() list comparisons
In many database servers, IN() is just a synonym for multiple OR clauses, because the two are logically equivalent. Not so in MySQL, which sorts the values in the
IN() list and uses a fast binary search to see whether a value is in the list. This is O(log n) in the size of the list, whereas an equivalent series of OR clauses is O(n) in
the size of the list (i.e., much slower for large lists).
对于许多数据库服务器而言,IN()列表不过是多个OR语句的同义词而已,因为IN和OR在逻辑上是等同的。不仅是在MySQL数据库服务器,对于许多其他的数据库服务器使用到IN查询时,都是按照如下方式处理的:
[1] 对IN列表中的数值进行排序。
[2] 对于查询的匹配,每次使用二分查找去匹配IN列表的数值。
所以对于第[2]步,每次比较的算法复杂度大概为O(log n)。相反,对于同样逻辑的OR列表,每次都要遍历,所以OR相应的算法复杂度为O(n)(因此对于遍历非常大的OR列表,会很缓慢!)。

  • 避免在条件的=左边使用函数及表达式
  • 索引问题:最左前缀问题,索引不是越多越好,指分部稀少不适合做索引

怎样预防SQL注入

  • 简单有效的方式时用预编译,PreparedStatement,它提前将sql编译好,输入的值只是作为参数通过SetXXX作为参数传入。这一点在MyBatis中用#{}一样可以预编译。而使用${}则是直接替换值

  • 通过正则表达式过滤

  • 通过字符串过滤

  • 前端通过JS函数对不合规的字符进行屏蔽。

产生死锁的四个条件

  1. 互斥条件:一个资源只能被一个进程占用。
  2. 请求与保持条件:一个进程请求另一个资源,并不会释放已有资源
  3. 不剥夺条件:一个进程持有某一个资源,在未使用完之前,无法被其他进程强行剥夺。
  4. 循环等待:若干个进程形成一个相互等待,头尾相连的循环等待关系。

Q.E.D.