1:TCP和UDP的区别,TCP为什么是三次握手,不是两次?
因为TCP是全双工协议,区别在于TCP可靠,UDP不可靠,效率更高。
详解:
TCP(传输控制协议)和 UDP(用户数据报协议)是两种不同的传输层协议,它们有以下区别:
- 连接方式
- TCP 是面向连接的协议,在数据传输之前,需要先建立连接,数据传输完成后,需要关闭连接。
- UDP 是无连接的协议,不需要建立连接,直接将数据报发送出去。
- 可靠性
- TCP 提供可靠的传输服务,通过序列号、确认应答、重传机制等保证数据的准确传输。
- UDP 不保证数据的可靠传输,可能会出现数据丢失、重复或乱序的情况。
- 传输效率
- TCP 由于需要建立连接、进行确认应答等,传输效率相对较低。
- UDP 没有这些额外的开销,传输效率较高。
- 应用场景
- TCP 适用于对数据准确性要求高的场景,如文件传输、电子邮件、远程登录等。
- UDP 适用于对实时性要求高、对数据准确性要求相对较低的场景,如视频会议、在线游戏、实时监控等。
TCP 采用三次握手而不是两次握手,主要有以下原因:
- 防止已失效的连接请求报文段被误接收:如果采用两次握手,当客户端发送的第一个连接请求报文段在网络中滞留,没有及时到达服务器,而客户端又重新发送了一个连接请求报文段,服务器收到后发送确认报文段并建立连接。此时,如果第一个连接请求报文段又到达了服务器,服务器会认为这是客户端的又一次连接请求,从而再次建立连接,这样就会导致服务器资源的浪费。而采用三次握手,客户端在收到服务器的确认报文段后,会发送一个确认报文段给服务器,服务器只有在收到客户端的确认报文段后,才会认为连接建立成功,这样就可以避免已失效的连接请求报文段被误接收。
- 确保双方都能正常收发数据:三次握手可以让客户端和服务器都确认对方的接收和发送能力。第一次握手,客户端向服务器发送连接请求,服务器可以知道客户端的发送能力正常;第二次握手,服务器向客户端发送确认报文段,客户端可以知道服务器的接收和发送能力正常;第三次握手,客户端向服务器发送确认报文段,服务器可以知道客户端的接收能力正常。这样,通过三次握手,双方都可以确认对方的接收和发送能力正常,从而保证数据的可靠传输。
TCP 三次握手的过程如下:
第一次握手:客户端发送 SYN 报文
- 客户端向服务器发送一个带有 SYN(同步序列号)标志的 TCP 报文段,该报文段中还包含客户端随机生成的初始序列号(Sequence Number,Seq),假设为 x。这个 SYN 报文表示客户端希望与服务器建立连接,并告知服务器自己的初始序列号。
- 此时,客户端进入 SYN_SENT 状态,等待服务器的响应。
第二次握手:服务器发送 SYN + ACK 报文
- 服务器接收到客户端的 SYN 报文后,会检查其中的信息。如果服务器同意建立连接,它会向客户端发送一个 SYN + ACK 报文段。这个报文段中,SYN 标志位为 1,表示这也是一个同步请求,同时 ACK 标志位也为 1,表示对客户端 SYN 报文的确认。
- 服务器会为自己生成一个初始序列号,假设为 y,同时在报文中的确认号(Acknowledgment Number,Ack)字段设置为客户端的序列号加 1,即 x + 1,表示服务器已经成功接收了客户端的 SYN 报文,期望客户端发送序列号为 x + 1 的数据。
- 发送完 SYN + ACK 报文后,服务器进入 SYN_RCVD 状态。
第三次握手:客户端发送 ACK 报文
- 客户端收到服务器的 SYN + ACK 报文后,会检查其中的确认号是否正确以及 SYN 标志位。如果确认无误,客户端会向服务器发送一个 ACK 报文段,ACK 标志位为 1,确认号为服务器的序列号加 1,即 y + 1,而序列号为客户端在第一次握手中使用的序列号加 1,即 x + 1。
- 发送完 ACK 报文后,客户端进入 ESTABLISHED 状态,表示连接已经建立成功,可以开始发送数据。
- 服务器收到客户端的 ACK 报文后,也会进入 ESTABLISHED 状态,至此,TCP 连接建立完成,双方可以开始进行数据传输。
TCP 连接的挥手过程用于终止已经建立的连接,通常需要四次挥手,具体过程如下:
第一次挥手:客户端发送 FIN 报文
- 当客户端数据传输完毕,想要关闭连接时,会向服务器发送一个带有 FIN(结束标志)标志的 TCP 报文段,该报文段中还包含客户端的序列号(假设为 m)。这个 FIN 报文表示客户端不再有数据要发送给服务器,但仍然可以接收服务器的数据。
- 发送 FIN 报文后,客户端进入 FIN_WAIT_1 状态。
第二次挥手:服务器发送 ACK 报文
- 服务器接收到客户端的 FIN 报文后,会发送一个 ACK 报文作为应答。该 ACK 报文的确认号为客户端的序列号加 1,即 m + 1,而序列号为服务器自己当前的序列号(假设为 n)。这个 ACK 报文表示服务器已经收到客户端的 FIN 报文,同意关闭客户端到服务器方向的连接。
- 服务器发送完 ACK 报文后,客户端到服务器方向的连接就关闭了,但服务器到客户端方向的连接仍然可以继续传输数据。此时客户端进入 FIN_WAIT_2 状态,而服务器进入 CLOSE_WAIT 状态。
第三次挥手:服务器发送 FIN 报文
- 当服务器也完成了数据传输,不再需要向客户端发送数据时,它会向客户端发送一个 FIN 报文,其序列号为服务器之前的序列号加 1,即 n + 1(如果在发送 FIN 之前服务器又发送了一些数据,那么序列号会相应增加),表示服务器也希望关闭连接。
- 发送 FIN 报文后,服务器进入 LAST_ACK 状态。
第四次挥手:客户端发送 ACK 报文
- 客户端收到服务器的 FIN 报文后,会发送一个 ACK 报文进行确认。该 ACK 报文的确认号为服务器的序列号加 1,即 n + 2,序列号为客户端在 FIN_WAIT_2 状态下的序列号加 1(即 m + 1,如果客户端在等待过程中没有收到其他数据)。
- 客户端发送完 ACK 报文后,进入 TIME_WAIT 状态。经过一段时间(通常是 2 倍的 MSL,即最长报文段寿命)后,客户端才会真正关闭连接。服务器收到 ACK 报文后,立即关闭连接,进入 CLOSED 状态。设置 TIME_WAIT 状态及等待时间是为了确保客户端发送的 ACK 报文能够被服务器正确接收,如果服务器没有收到 ACK 报文,会重新发送 FIN 报文,客户端在 TIME_WAIT 状态下可以处理这种情况,保证连接的可靠关闭。
2:Dubbox 和Dubbo的区别?
Dubbox 和Dubbo本质上没有区别,只是扩展了Dubbo,以下扩展出来的功能
- 支持REST风格远程调用(HTTP + JSON/XML);
- 支持基于Kryo和FST的Java高效序列化实现;
- 支持基于Jackson的JSON序列化;
- 支持基于嵌入式Tomcat的HTTP remoting体系;
- 升级Spring至3.x;
- 升级ZooKeeper客户端;
- 支持完全基于Java代码的Dubbo配置;
3:Dubbo一般使用什么注册中心?还有别的选择吗?
Dubbo 一般用zookeeper做注册中心
还可以用:Redis,Nacos,Consul
4:说说Java的异常类?
Throwable -> Error,Exception
Error:严重问题,例如内存溢出 OutOfMemoryError
Exception ->运行时异常: RuntimeException,编译时异常:IOException
5:ArrayList和LinkedList的区别?分别用在什么场景?
都是对List接口的实现,但数据结构上一个是Array(动态数组),一个是Link(双向链表)
ArrayList:查找快,插入删除慢,适用于频繁查找和修改的场景
LinkedList:插入删除快,查找修改慢,适用于频繁增删的场景
1. 底层数据结构
ArrayList
:基于动态数组实现。它使用一个数组来存储元素,当数组空间不足时,会自动进行扩容操作。LinkedList
:基于双向链表实现。每个元素(节点)包含数据、指向前一个节点的引用和指向后一个节点的引用。
2. 随机访问性能
ArrayList
:支持快速的随机访问,因为它可以通过索引直接访问数组中的元素,时间复杂度为 O(1)。LinkedList
:随机访问性能较差,因为需要从头节点或尾节点开始遍历链表,直到找到目标元素,时间复杂度为 O(n)。
3. 插入和删除性能
ArrayList
:在数组末尾插入或删除元素的效率较高,时间复杂度为 O(1);但在数组中间或开头插入或删除元素时,需要移动大量元素,时间复杂度为 O(n)。LinkedList
:在链表的任意位置插入或删除元素的效率较高,因为只需要修改相邻节点的引用,时间复杂度为 O(1);但在查找要插入或删除的位置时,仍然需要遍历链表,时间复杂度为 O(n)。
4. 内存占用
ArrayList
:由于使用数组存储元素,会预先分配一定的内存空间,可能会造成一定的内存浪费;但由于每个元素只存储数据本身,所以单个元素的内存占用较小。LinkedList
:由于每个节点需要额外存储指向前一个节点和后一个节点的引用,所以单个节点的内存占用较大;但它不需要预先分配连续的内存空间,内存利用率较高。
ArrayList
的适用场景
- 随机访问频繁:如果需要频繁地根据索引访问元素,而插入和删除操作较少,那么
ArrayList
是更好的选择。例如,在一个学生成绩列表中,需要经常根据学生的序号(索引)查询成绩。 - 数据量相对稳定:如果数据量相对稳定,不会频繁地进行插入和删除操作,那么
ArrayList
的性能会更好。
LinkedList
的适用场景
- 插入和删除频繁:如果需要频繁地在列表中间或开头插入或删除元素,而随机访问操作较少,那么
LinkedList
是更好的选择。例如,在一个任务队列中,需要经常在队列头部添加新任务或在队列尾部移除已完成的任务。 - 内存使用灵活:如果对内存的使用比较敏感,希望在数据量变化较大时能够灵活地分配和释放内存,那么
LinkedList
是更好的选择。
LinkedList
和 ArrayList
不同,它并没有像 ArrayList
那样的扩容机制。解释:
底层结构
LinkedList
是基于双向链表实现的,双向链表由一系列节点组成,每个节点包含三个部分:存储的数据、指向前一个节点的引用以及指向后一个节点的引用。
无需扩容的原因
由于链表的特性,它不需要预先分配连续的内存空间来存储元素。当你向 LinkedList
中添加新元素时,它会创建一个新的节点,并将这个新节点连接到链表的合适位置(头部、尾部或者中间)。而在删除元素时,会把对应的节点从链表中移除,并且释放该节点占用的内存。
// 在尾部添加元素
linkedList.add("apple");// 在头部添加元素
linkedList.addFirst("cherry");// 在指定位置添加元素
linkedList.add(1, "date");
题外话:LinkedList 可作为栈(后进先出),可作为队列(先进先出)
6:说说List排序?
- 使用 Collections.sort()默认正序,可以传第二个参数自定义排序
- 自定义bean实现 Comparable 接口。
- 实现Comparator接口自定义比较器
numbers、books都是List集合,简单的示例这里就不过多描述
// 使用 Collections.sort() 方法对列表进行排序 import java.util.Collections;
Collections.sort(numbers);// 使用 Comparator 按价格排序 import java.util.Comparator;
books.sort(Comparator.comparingDouble(Book::getPrice));
7:你都知道哪些常用的Map集合?
HashMap、HashTable、TreeMap、LinkedHashMap
HashMap
- 键和值都允许为 null。
- 不保证元素的顺序,即插入顺序和遍历顺序可能不同。
- 基于哈希表实现,因此查找、插入和删除操作的时间复杂度通常为 O (1)。
- 使用场景:适用于不需要保证元素顺序,且需要快速查找的场景。
@Test
void testHashMap() {HashMap<Object, Object> map = new HashMap<>();map.put("k1", "v1");map.put("k2", "v2");map.put("k3", "v3");map.put(null, null);System.out.println(map);// {null=null, k1=v1, k2=v2, k3=v3}
}
Hashtable
- 键和值都不允许为 null。
- 是线程安全的,因为它的方法都被 synchronized 修饰,所以在多线程环境下可以直接使用,但这也导致了它的性能相对较低。
- 同样基于哈希表实现,查找、插入和删除操作的时间复杂度通常为 O (1)。
- 使用场景:在需要线程安全的场景下可以使用,但如果对性能要求较高,建议使用 ConcurrentHashMap 替代。
@Test
void testHashtable() {Hashtable<Object, Object> hashtable = new Hashtable<>();hashtable.put("k3", "v3");hashtable.put("k1", "v1");hashtable.put("k2", "v2");// hashtable.put(null, null); java.lang.NullPointerExceptionSystem.out.println(hashtable);// {k3=v3, k2=v2, k1=v1}
}
TreeMap
- 不允许键为 null,但值可以为 null。
- 基于红黑树实现,会根据键的自然顺序或者自定义的比较器进行排序。
- 查找、插入和删除操作的时间复杂度为 O (log n)。
- 使用场景:适用于需要对键进行排序的场景,例如按时间顺序排序等。
@Test
void testTreeMap() {TreeMap<Integer, Object> treeMap = new TreeMap<>();treeMap.put(3, "v3");treeMap.put(1, "v1");treeMap.put(2, "v2");//treeMap.put(null, null); java.lang.NullPointerExceptiontreeMap.put(4, null);System.out.println(treeMap);// {1=v1, 2=v2, 3=v3, 4=null}
}
LinkedHashMap
- 键和值都允许为 null。
- 继承自 HashMap,同时维护了一个双向链表,因此可以保证元素的插入顺序或者访问顺序。
- 查找、插入和删除操作的时间复杂度为 O (1)。
- 使用场景:适用于需要保证元素插入顺序或者访问顺序的场景,例如实现 LRU(最近最少使用)缓存。
@Test
void testLinkedHashMap() {LinkedHashMap<Integer, Object> linkedHashMap = new LinkedHashMap<>();linkedHashMap.put(3, "v3");linkedHashMap.put(1, "v1");linkedHashMap.put(2, "v2");linkedHashMap.put(4, null);linkedHashMap.put(null, null);System.out.println(linkedHashMap);// {3=v3, 1=v1, 2=v2, 4=null, null=null}
}
8:Collection集合接口和Map接口有什么关系?
没有直接关系,但是一些子类会有依赖,
Collection是最基本的集合接口,声明了适用于JAVA集合(只包括Set和List)的通用方法。
Map接口并不是Collection接口的子接口,但是它仍然被看作是Collection框架的一部分。
9:equal 和 hashcode 作用和区别?
首先,我们要明白hashCode()和equals()方法的作用是什么,然后才能说他的区别,说了区别之后在说明使用的时候需要注意的地方,这样的回答思路基本是OK的。
hashCode()和equals()的作用是什么?
hashCode()和equals()的作用其实是一样的,目的都是为了比较两个对象是否相等一样
hahsCode()和equals()的区别是什么?
从两个角度介绍他们的区别:一个是性能,一个是可靠性。
1、equals()既然已经实现比较两个对象的功能了,为什么还需要hashCode()呢?
因为重写的equals()里一般比较的较为全面和复杂,它会对这个对象内所以成员变量一一进行比较,这样效率很低,而通过hashCode()对比,则只要生成一个hash值就能比较了,效率很高
2、那hashCode的效率这么高,为啥还要用equals()呢?
因为hashCode()并不是完全可靠,非常有可能的情况是,两个完全不同的对象的hash值却是一样的。
结论:
- equals()相等的两个对象它们的hashCode()肯定相等,即equals()绝对可靠。
- hahsCode()相同的两个对象,它们的equals()不一定相同。即用hashCode()比较相同的时候不可靠
- hashCode()不同的两个对象,它们的那么equals()肯定不同。即用hashCode()比较不同的时候绝对可靠
hashCode()和equals使用的注意事项
1、对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低。解决方案是:
每当需要比较的时候,先用hahsCode()去比,
如果hashCode()不一样,则两个对象肯定不一样,就没有必要再用equals()比较了;
如果hashCode()一样,则这两个对象有可能相同,这时候再去比较这两个对象的equals(),如equals()也相同,则表示这两个真的相同的,这样既大大提高了效率,又保证了准确性。
2. 事实上,我们平时用的集合框架中的hashMap,hashSet,hashTable 中对key的比较就是使用上述这种方法。
3. Obejct默认的equals和HashCode方法返回的是对象的地址相关信息。
所以当我们通过new关键字创建了两个内容相同的对象,虽然它们的内容相同,但是它们在内存中分配的地址不同,导致它们的hashCode()不同,这肯定不是我们想要的。所以当我们要将某个类应用到集合中去的时候,就必须重写equals()方法和hashCode()方法。
扩展
1、阿里巴巴开发规约明确规定:
只要重写了equals()方法,就必须重写hashCode()方法。
因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
如果对象定义为Map的健,那么就必须重写equals()方法和hashCode()方法。
String重写了equals()方法和hashCode()方法,所以我们可以非常愉快的时候String对象作为key。
2、是不是每个对象都要重写这两个方法,到底什么时候重写?
事实上一般情况下,我们并不需要重写这两个方法,只有该类被应用到集合框架中去的时候,才应该重写。
3、我能不能值重写equals()方法,不重写hashCode()方法?
如果重写了equals()方法,比如说基于对象的内容实现的,而保留了hashCode()的实现不改变,那么最终出现的情况很可能是,两个对象明明是“相等的”,但是hashCode()却不一样。
4、为什么需要hashCode
通过hashCode可以提高对比的性能,这一点,在上面的比对过程中有体现