目录
一、Cookie和session有什么区别?
二、Get和Post有什么区别?
三、Http和Https有什么区别?
四、Http和RestTemplate有什么区别?
五、什么是双亲委派模型?
六、说一下 JVM 运行时数据区?
七、String、StringBuffer、StringBuilder有什么区别?
八、实现一个线程有哪几种方式?有什么区别?
九、三次握手和四次挥手
十、ArrayList和LinkList有哪些区别?
十一、Set和List的区别?
十二、简单说下你对深拷贝和浅拷贝的理解
十三、HashMap底层原理是什么?
十四、Map接口实现类有哪些?哪些是有序的?哪些是无序的?
十五、JVM垃圾回收机制和常见算法
十六、转发和重定向有什么区别?
十七、并发和并行有哪些区别?
十八、类加载时机是什么样的?
十九、常见的线程安全的类有哪些?
二十、HashTable和HashMap有哪些区别?
二十一、简单说下二分查找和冒泡排序的实现思路
1.cookie存储在客户端,session存储在服务端
2.cookie不安全,session安全
3.cookie的值只能是字符串,session值可以是object类型
4.cookie最多存储4k,session存储数据没有限制
5.cookie可能会被暴力注册不安全、session可用于验证码缓存、用户登录信息缓存
1.安全性:
Get请求提交的数据会在地址栏显示,post请求参数在请求体中,不会在地址栏显示,因此post请求更安全。
2.在HTTP协议中,GET请求将参数放在URL的查询字符串中,而POST请求将参数放在请求体中。因此,GET请求的参数有长度限制(通常为1024个字符),而POST请求的参数没有长度限制。
3.幂等性:GET请求具有幂等性,即多次相同的GET请求返回的结果是一致的,不会对服务器产生影响;而post请求不具有幂等性,多次相同的post请求可能会对服务器产生不同的影响,例如插入多条数据。
1. 安全性:最明显的区别是安全性。HTTP是明文协议,所有的通信数据都是以明文形式传输,容易被窃听者拦截并查看敏感信息。而HTTPS通过使用TLS(传输层安全)协议进行加密,能够保护数据在传输过程中的安全性,使得窃听者无法直接获取到明文数据。
2. 加密方式:HTTP不提供数据加密的功能,而HTTPS使用公钥加密算法和对称加密算法相结合的方式来加密数据。在建立连接时,客户端与服务器进行握手,通过交换密钥来确保通信的机密性和完整性,然后使用对称加密算法对实际的数据进行加密传输。
3. 端口:HTTP默认使用80端口进行通信,而HTTPS默认使用443端口。这样做是为了避免与其他常用的非加密协议冲突,方便网络设备和防火墙进行端口的过滤和管理。
4. 证书:HTTPS需要使用数字证书来验证服务器的身份和建立安全连接。数字证书由受信任的证书颁发机构(CA)签发,包含了服务器的公钥以及其他相关信息,用于验证服务器的身份。这样可以防止中间人攻击(Man-in-the-Middle Attack)。
5. 性能:由于加密和解密过程的开销,HTTPS通信相对于HTTP通信会稍微降低一些性能。然而,随着硬件和软件的发展,加密算法的改进和优化,HTTPS的性能已经得到大幅提升,不会对正常的Web访问造成明显影响
RestTemplate 是一个 Spring 框架提供的用于发送 HTTP请求的客户端工具,它封装了 Java 原生的 HTTP 客户端库,并提供了一组简洁易用的 API 来发送 HTTP 请求和处理响应。而 HTTP(Hypertext Transfer Protocol)是一种应用层协议,用于在Web应用程序之间传输数据。
因此,RestTemplate 和 HTTP 是不同的概念,它们的区别如下:
1.功能不同**:HTTP 是一种通信协议,定义了客户端和服务器之间如何通信,包括请求方式、报文格式、状态码等内容;而 RestTemplate 则是一个HTTP客户端工具,用于发送 HTTP 请求和处理响应。
2.技术实现**:HTTP 协议的实现通常是由操作系统或网络设备提供的底层网络库来完成的,例如 Java 中的 HttpURLConnection或Apache HttpClient;而RestTemplate利用了Spring 框架的封装和管理机制,将 HTTP 请求和响应的构造和解析过程交给了框架自身来处理,可以更加方便地使用和管理。
3.使用场景**:HTTP 协议是Web应用程序之间通信的基础,用于实现浏览器与服务器之间的数据传输;而 RestTemplate通常用于Web 应用程序内部的服务调用,**也可以用于与外部 Web API 进行交互。
需要注意的是,RestTemplate 是基于 HTTP 协议的客户端工具,它遵循了 HTTP 的相关规范和约定,例如 URI 的格式、HTTP 请求方法、请求头、响应码等。因此,在使用 RestTemplate 时需要理解 HTTP 协议的相关知识,并根据实际需求选择合适的请求方式和参数设置。
总之:
HTTP是通信协议,RestTemplate是发送HTTP 请求的工具;
HTTP协议是通过操作系统或者底层网络库实现的,而RestTemplate是利用spring封装了http工具;
HTTP通常用于浏览器和服务端之间的数据传输,RestTemplate通常用于程序内部服务的调用。
如果一个类加载器收到了类加载的请求,它不会自己先去加载而是把这个请求委托给父类的加载器去执行,如果分类的加载器还存在其父类加载器,就进一步向上委托,请求最终达到顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回。如果父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
组成部分:堆、方法区、栈、本地方法栈、程序计数器
1、堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。
2、方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译
器编译后的代码。
3、栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动
态链接、方法出口等信息。
4、本地方法栈与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。
5、程序计数器(PC寄存器)程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时
就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。
String是字符串常量,一旦创建不能修改该对象的内容,线程安全。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
使用场景:
StringBuffer**类:需要频繁修改字符串并且要求线程安全**,应该使用;
StringBuilder**类:如果不需要线程安全,并且需要高性能**,则应该使用
String类:适用于存储不变的字符串常量,例如数据库连接串、URL等。
- String
- String 是不可变的,即一旦被创建就不能被修改。
- 每次对 String 类型进行改变时,都会生成一个新的对象,所以频繁修改字符串会导致资源浪费。
- String 常用于存储少量且频繁读取的数据。
- StringBuffer
- StringBuffer 是可变的,可以通过 append() 方法来修改其值。
- 在对 StringBuffer 进行修改时,不会生成新的对象,而是直接在原有的 StringBuffer 上进行修改。
- StringBuffer 的线程安全,适合在多线程环境下使用。
- StringBuilder
- StringBuilder 与 StringBuffer 一样也是可变的,可以通过 append() 方法来修改其值。
- 同样地,StringBuilder 在进行修改时,也不会生成新的对象,而是直接在原有的 StringBuilder 上进行修改。
- 不同的是,StringBuilder 不是线程安全的,适合在单线程环境下使用。
1、继承Thread类并重写run方法,创建子类对象后调用start()方法发启动线程
优点:简单易用
缺点:Java只支持单继承,如果该类已经继承了其他类,则无法使用该方式
2、实现Runnable接口,实现run()方法,将实现了Runnable接口的对象传递给Thread类的构造函数创建线程对象,然后调用start()方法启动线程;
优点:灵活性高,可以实现多个接口。
缺点:需要额外的类来实现Runnable接口,稍微复杂一些。
3.实现Callable和Future接口,与Runnable接口类似,但是Callable会返回结果,并且可以抛出异常;
优点:可以返回结果,可以抛出异常。
缺点:相比Runnable略微复杂一些。
4.使用线程池,通过ThreadPoolExecutor或Executors工厂类创建线程池,将任务提交到线程池中执行;
优点:可以控制线程数量,避免创建过多线程导致系统崩溃。
缺点:需要手动管理线程池,稍微复杂一些。
最常用的方式是第二种——实现Runnable接口,因为它的灵活性高,可以同时实现其他接口。同时,使用线程池也是非常常见的方式,尤其是在处理大量任务时,可以控制线程数量,避免资源浪费和系统崩溃。
三次握手:
第一次:客户端向服务器发出连接请求,等待服务器端确认
第二次:服务器端向客户端返回一个响应,告诉客户端收到了请求
第三次:客户端向服务器再次发送确认消息,建立连接
四次挥手:
第一次:客户端向服务器发出取消连接请求
第二次:服务器向客户端返回一个响应表示收到客户端取消申请
第三次:服务器向客户端发起确认取消信息
第四次:客户端再次发送确认信息,连接取消
结构不同:LinkList底层基于链表实现,ArrayList底层基于数组实现。
查询速度:LinkList查询慢,增删快,ArrayList有索引,查询快,增删慢。
内存占用不同:LinkList没有索引,内存占用少**,没有连续内存空间,ArrayList有索引,内存占用大,**有连续内存空间。
Set无序不重复,List有序可重复。
深拷贝:深拷贝则是将整个对象都复制一份,包括其所有的属性,子属性以及引用对象。这样,在深拷贝完成后,原始对象和复制对象互相独立,它们拥有自己的内存地址,修改任一对象的属性不会影响另一个对象的属性。
浅拷贝:浅拷贝只复制了对象的引用,而没有复制对象本身。也就是说,在浅拷贝完成后,原始对象和复制对象共享同一个内存地址,它们指向的是同一个对象。因此,如果修改任一对象的属性,另一个对象的属性也会被修改。
区别:由于深拷贝需要复制整个对象,因此通常比浅拷贝更耗费时间和内存资源。
深拷贝需要开启一份新的内存,有独立的内存,浅拷贝不复制引用类型,拷贝前后指向的是同一份内存地址。
hashMap底层是基于哈希表数组,依赖hashCode方法和equals方法保证"键"的唯一
jdk1.8之前:数组+链表+头插法
jdk1.8之后:数组+链表+红黑数+尾插法
当new HashMap()时;底层没有创建数组,首次调用put()方法时,底层创建长度为16的数组。默认的负载因子大小为0.75,数组长度为16,默认情况下,当HashMap中元素个数超过16*0.75=12的时候,就把数组大小扩展为32。
树化和反树化:链表长度大于8,树化为红黑树,链表长度小于6,反树化为链表,节约资源浪费。
HashMap中的key是通过哈希值计算出来的,每个key都不可以重复,每个数组元素是一个链表,链表中存储着哈希值相同的键值对。当进行插入、删除、查找等操作时,首先需要根据键的哈希值计算出在数组中的位置,然后再在链表中进行操作。
HashMap的put方法用于向HashMap中添加键值对,首先会根据hash算法计算出hashcode值,找到数组中对应下标的位置,如果该位置没有元素,就直接插入键值对,如果该位置有元素,就需要遍历数组,查找使用equals方法与链表中的值进行进行比较,是否存在相同的键如果相同就替换旧值,不同就放入链表尾部。
HashMap:HashMap是最常用的Map实现类之一,它根据键的hashCode值存储数据,具有很快的查找速度。HashMap是无序的,即不保证元素的顺序。
LinkedHashMap(有序):LinkedHashMap在HashMap的基础上通过使用链表维护插入顺序或访问顺序,保持了元素的迭代顺序与插入顺序(插入顺序模式)或访问顺序(访问顺序模式)一致。
TreeMap(有序):TreeMap通过对键进行排序,实现了按照键的自然顺序或者指定的比较器顺序进行排序,默认是按键的升序排序。TreeMap是有序的。
ConcurrentHashMap:ConcurrentHashMap是线程安全的HashMap实现,它支持高并发的读写操作。和HashMap一样,ConcurrentHashMap是无序的。
Hashtable:Hashtable是早期的HashMap实现,在多线程环境下是线程安全的。Hashtable是无序的。
GC回收垃圾流程第一步是定位内存空间中没有引用到的对象,然后才能进行回收。那么GC是如何定位这些内存中没有用到的对象呢?
一、引用计数器算法(废弃)
引用计数器算法是给每一个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1;当引用失效的时候,计数器减1;当计数器为0的时候,JVM就默认对象不再使用,是“垃圾”了,这种定位算法存在一个缺点:就是无法解决循环依赖的问题。当A引用B,B也引用A的时候,此时AB对象的引用都不为0,此时也就无法垃圾回收,所以一般主流虚拟机都不采用这个方法。
二、根搜索算法(可达性分析算法)
根搜索算法是通过“GC ROOT”根对象作为起始节点,从该节点开始往下搜索,搜索通过的路径称之为“引用链”,当一个对象没有被GC Root 的引用链连接的时候,说明这个对象是不可用的且无法达到的,然后才可以被GC进行回收。
GC Root对象包括:
虚拟机栈中引用的对象
方法区域的类静态属性引用的对象
方法区域中常量引用的对象
本地方法栈中JNI(Native方法)引用的对象
经过以上算法分析后,确定好了要回收的对象,可以进行下一步回收了,以下是常见的GC回收算法:
三、回收算法
(一)标记清除法
标记-清除算法包括两个阶段:“标记”和“清除”,
在标记阶段,遍历堆然后确定所有要回收的对象,并做标记;
在清除阶段,将标记阶段确定不可用的对象清除;
标记-清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后会产生大量的不连续空间(内存碎片),这样当程序需要分配大内存对象的时候,可能无法找到足够的连续空间
(二)标记复制算法
复制算法就是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另外一块内存上,然后把原来的那块内存清理掉。复制算法实现简单,运行效率高,并且解决了标记清除法内存碎片的问题。缺点是由于每次只能使用其中的一半,造成内存的利用率不高。
(三)标记-整理算法
标记-整理算法和标记-清除算法一样,标记--整理算法是把存活对象往内存一端移动,然后直接回收边界以外的内存。标记--整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。
(四)分代收集算法
分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法、老年代采用标记--整理算法。
四、JVM内存模型
Java虚拟机中有三个主要的内存区域:方法区、Java栈和Java堆,它们分别用于存储不同类型的数据。
(一)方法区(Method Area):
用于存储类的结构信息,如类名、属性、方法、接口等。在JDK 8及之前的版本中,方法区是一个逻辑上独立于堆的内存区域,它通常也被称为永久代(PermGen)。而在JDK 8之后,方法区被移除,取而代之的是元空间
(二)Java栈(Java Stack):
每个线程都会有自己的Java栈,用于存储局部变量、操作数栈、方法出口等信息。Java栈是一个先进后出(FILO)的数据结构。当一个方法被调用时,就会在Java栈中创建一个新的栈帧;当方法执行结束时,对应的栈帧就会被弹出。
(三)Java堆(Java Heap):
用于存储对象实例和数组。所有线程共享一个Java堆,而且Java堆也是JVM管理的最大的一块内存区域。在Java程序运行过程中,所有通过new关键字创建的对象都会在Java堆中分配空间。
五、JVM引用类型(重要)
Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用、虚引用。
(一)强引用:
级别最高的引用,普通变量赋值即为强引用,如 A a = new A();如果一个对象被强引用、那么垃圾回收器绝对不会回收它;当内存空间不足,Java虚拟机宁可跑内存溢出异常,也绝对不会随意回收具有强引用的对象来解决内存不足的问题。
(二)软引用:
如果一个对象具有软引用,那么如果内存空间足够,垃圾回收器不会回收它,一旦内存空间不够了,垃圾回收器就会回收这些软引用对象的内存。软引用可用来实现内存敏感的高速缓存。
(三)弱引用:
弱引用的生命周期更短,弱引用的作用是使对象能够被垃圾回收器回收,即如果一个对象只被弱引用所引用,无论内存空间是否足够,这个对象在下一次垃圾回收时都会被回收。
弱引用可以解决内存泄露的问题。
缓存:如果我们需要为某些数据进行缓存而不想影响垃圾回收器对这些数据的回收,可以使用弱引用来实现缓存功能。
监视器:有时候我们需要对某些对象进行监视,但是又不想让监视器本身影响到这些对象的生命周期,可以使用弱引用来实现监视器功能。
(四)虚引用:
虚引用是最弱的一种引用类型,它的作用是在对象被垃圾回收器回收时收到一个系统通知或者后续处理操作。虚引用必须和引用队列联合使用。
总之,强引用垃圾回收器不会回收,软引用内存空间不足时会回收,弱引用不管内存空间是否足够,垃圾回收器都会回收它;虚引用是最弱一种引用,必须联合引用队列一起使用。
六、GC会回收JVM中哪些内存?
内存运行时JVM会有一个运行时数据区来管理内存。它包括5大部分:程序计数器、虚拟机栈、本地方法栈、方法区、堆。因为程序计数器、虚拟机栈、本地方法栈是线程私有的,随线程而生、随线程而死、生命周期比较短,因此不用考虑内存回收的问题。需要考虑内存回收的是GC主要进行回收的内存是JVM中的方法区和堆。
总结:GC 的目的在于实现无用对象内存自动释放,减少内存碎片、加快分配速度。
转发是服务器内部行为,重定向是客户端行为。
转发浏览器地址不会发生变化,重定向浏览器地址会发生变化。
转发是一次请求,重定向是两次请求。
并发:在同一时刻,有多个线程在单个CPU上交替执行--一个厨师同一时刻炒三个菜
并行:在同一时刻,有多个线程在多个CPU上同时执行--三个厨师同时炒三个菜
类加载步骤包括:
加载:将编译后的.class文件加载到JVM,并创建一个Class对象
验证:验证.class文件格式是否规范、也有安全层面的验证、验证类的元信息,字节码,符号引用
准备:为类的静态变量分配内存,赋默认值
解析:将符号引用转为直接引用
初始化:为静态变量赋初始值
作用:将字节码.class运输到jvm虚拟机里面
Vector:只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。Vector类是线程安全的,适用于多线程环境。如果不需要线程安全,可以考虑使用ArrayList类,它在大部分情况下具有更好的性能。
Hashtable:Hashtable类是Java中的一个哈希表实现,它继承自Dictionary类,并实现了Map接口。Hashtable使用键值对存储数据,其中键和值都是对象。由于它的每个公共方法都被synchronized修饰,保证同一时间只有一个线程可以访问Hashtable的内部数据结构,因此可以保证多线程下线程安全。
ConcurrentHashMap:
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它是对Hashtable进行改进和优化的结果。ConcurrentHashMap实现了ConcurrentMap接口,并且是高效并发的。
特点:
- 线程安全性:ConcurrentHashMap是线程安全的,多个线程可以同时读取和修改其中的键值对,而不需要额外的同步机制。
- 分段锁:ConcurrentHashMap采用了分段锁的策略,将整个数据结构划分成多个段(Segment),每个段都拥有一把锁。不同的线程可以同时访问不同的段,从而提高了并发性能。
- 高效并发更新:ConcurrentHashMap在更新操作时,只需要锁定特定的段,而不需要锁定整个数据结构,从而减小了锁的粒度,提高了并发更新的效率。
HashTable和HashMap都是常用的Java集合类,它们的不同之处如下:
1、线程安全性:
HashTable是线程安全的,而HashMap是非线程安全的。
2、遍历方式和迭代器:
Hashtable的迭代器(Iterator)是通过Enumeration接口实现的,只能单向遍历集合元素,而HashMap的迭代器是通过Iterator实现的,Iterator接口在功能上更加强大灵活,可以双向遍历集合元素(如List),并且可以进行删除操作。
3、初始容量和扩容机制:
Hashtable默认的初始容量是11,每次扩容时容量变为原来的两倍加一。
而HashMap默认的初始容量是16,每次扩容时容量变为原来的两倍。
综上所述,如果需要在多线程环境中使用集合类,应该选择Hashtable;如果不需要考虑线程安全问题,可以选择HashMap。同时,由于HashMap支持null值,因此在使用HashMap时要注意空指针异常的问题。
二分查找的实现思路就是将有序数组不断地二分为两部分,通过与目标值的比较,确定目标值在哪一部分中,然后再在该部分中进行二分查找,直到找到目标值或者确定目标值不存在。
冒泡排序的实现思路就是相邻的两个元素进行比较,如果顺序不对则交换它们的位置,重复进行多轮这样的比较和交换操作,直到所有元素都排好序为止。