面试整理问答题

https://blog.csdn.net/m0_63270506/article/details/124295807

一、垃圾回收

复制代码
一:垃圾回收机制的意义
    垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存;
    内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
    内存溢出:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。 
    内存泄露量大到一定程度会导致内存溢出。但是内存溢出不一定是内存泄露引起的。
    内存溢出原因: 
           1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 
           2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 
           3.代码中存在死循环或循环产生过多重复的对象实体; 
           4.使用的第三方软件中的BUG; 
           5.启动参数内存值设定的过小
    内存溢出的解决方案:
           第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数 
 一定不要忘记加。)
           第二步,检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。
           第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

二:垃圾回收机制的工作原理

      创建的对象存储在堆里面,把堆比喻为院子中的土地,把对象比喻为土地的管理者,院子比喻为java虚拟机,当创建一个对象时,java虚拟机将给对象分配土地,当对象不再使用时,JVM进行释放。

引用计数:每个对象都包含一个计数器,当对象被引用时,计数器技术+1,当不被引用时-1,即为null;垃圾回收机制对其循环,当对象为null时,进行回收释放内存,虽然开销不是很大,但是在对象的整个生命周期垃圾回收机制进行循环工作,发现为null进行收回,但是有个缺陷,当对象出现循环引用时,本该回收的对象而计数器不显示0,所以垃圾回收机制也不回进行回收。

自适应技术:

停止—复制:暂停程序的运行,循环所有的对象找到“存活的”对象,将其复制到另一个堆里面,再将没有复制的进行回收,为了不占用存,引入在同一个堆里面,将没有占用的内存堆用来存放复制的“存活对象”,重新进行整理分配空间。为了防止出现没被引用的对象少,而花费开销复制了很多“存活对象”引入标记清扫,对对象进行循环标记引用的对象,复制到另一片堆里面,而不被引用的进行释放空间。最后JVM提供了自动检测监视,如果对象都很稳定,垃圾回收器效率低则使用“标记—清扫”,否则使用“停止—复制”,进行回收。
复制代码

二、java堆和栈的区别

复制代码

复制代码

 三、mysql

复制代码
1.MYSQL优化主要分为以下四大方面:
设计:存储引擎,字段类型,范式与逆范式
功能:索引,缓存,分区分表。
架构:主从复制,读写分离,负载均衡。
合理SQL:测试,经验。

MyISAM与InnoDB存储引擎的主要特点
MyISAM存储引擎的特点是:表级锁、不支持事务和全文索引,适合一些CMS内容管理系统作为后台数据库使用,但是使用大并发、重负荷生产系统上,表锁结构的特性就显得力不从心;

InnoDB存储引擎的特点是:行级锁、事务安全(ACID兼容)、支持外键、不支持FULLTEXT类型的索引(5.6.4以后版本开始支持FULLTEXT类型的索引)。InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全存储引擎。InnoDB是为处理巨大量时拥有最大性能而设计的。它的CPU效率可能是任何其他基于磁盘的关系数据库引擎所不能匹敌的;

2.mysql三范式与逆范式
 第一范式
确保数据表中每列(字段)的原子性。
如果数据表中每个字段都是不可再分的最小数据单元,则满足第一范式。

第二范式(依赖性)
在第一范式的基础上更进一步,目标是确保表中的每列都和主键相关。
如果一个关系满足第一范式,并且除了主键之外的其他列,都依赖于该主键,则满足第二范式。

第三范式(唯一性)
 满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。

逆范式化指的是通过增加冗余或重复的数据来提高数据库的读性能。
例如:在上例中的 user_role用户-角色中间表增加字段role_name。
逆范式化可以减少关联查询时,join表的次数。

3.索引优化
①全值匹配。
②最佳左前缀法则:带头大哥不能死,中间兄弟不能断;带头大哥可跑路,老二也可跟着跑,其余兄弟只能死。
③索引列上不计算。
④覆盖索引记住用。
⑤不等于、is null、is not null导致索引失效。
⑥like百分加右边,加左边导致索引失效,解决方法:使用覆盖索引。
⑦字符串不加单引号导致索引失效。
⑧少用or,用or导致索引失效。

3.索引使用的B树和B+树区别
b树 每个节点都存储数据点,叶子节点无链表
b+树 只有叶子存储数据 并且有链表
一般关系型数据库使用B+树来做索引,NoSQL数据库用哈希来做索引。

数据库选用B+树的最主要原因:
B+树还有一个最大的好处,方便扫库,B树必须用中序遍历的方法按序扫库,而B+树直接从叶子结点挨个扫一遍就完了,B+树支持range-query非常方便(b+tree 数据节点存在指针,所以范围查找不需要多次查找。),而B树不支持。这是数据库选用B+树的最主要原因。
复制代码

 三、Mongodb与Redis应用指标对比

MongoDB和Redis都是NoSQL,采用结构型数据存储。二者在使用场景中,存在一定的区别,这也主要由于

二者在内存映射的处理过程,持久化的处理方法不同。MongoDB建议集群部署,更多的考虑到集群方案,Redis
更偏重于进程顺序写入,虽然支持集群,也仅限于主-从模式。

MongoDB 更类似 MySQL,支持字段索引、游标操作,其优势在于查询功能比较强大,擅长查询 JSON 数据,能存储海量数据,但是不支持事务。

Redis 内存中的数据结构存储系统,它可以用作数据库、缓存消息中间件。它支持多种类型的数据结构,如 字符串(strings), 散列(hashes),列表(lists), 集合(sets), 有序集合(sorted sets)
指标 MongoDB(v2.4.9) Redis(v2.4.17) 比较说明
实现语言  C++ C/C++ -
协议 BSON、自定义二进制 类Telnet -
性能 依赖内存,TPS较高 依赖内存,TPS非常高 Redis优于MongoDB
可操作性 丰富的数据表达、索引;最类似于关系数据库,支持丰富的查询语言 数据丰富,较少的IO MongoDB优于Redis
内存及存储 适合大数据量存储,依赖系统虚拟内存管理,采用镜像文件存储;内存占有率比较高,官方建议独立部署在64位系统(32位有最大2.5G文件限制,64位没有改限制) Redis2.0后增加虚拟内存特性,突破物理内存限制;数据可以设置时效性,类似于memcache 不同的应用角度看,各有优势
可用性 支持master-slave,replicaset(内部采用paxos选举算法,自动故障恢复),auto sharding机制,对客户端屏蔽了故障转移和切分机制 依赖客户端来实现分布式读写;主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制;不支持自动sharding,需要依赖程序设定一致hash机制 MongoDB优于Redis;单点问题上,MongoDB应用简单,相对用户透明,Redis比较复杂,需要客户端主动解决。(MongoDB 一般会使用replica sets和sharding功能结合,replica sets侧重高可用性及高可靠性,而sharding侧重于性能、易扩展)
可靠性 从1.8版本后,采用binlog方式(MySQL同样采用该方式)支持持久化,增加可靠性 依赖快照进行持久化;AOF增强可靠性;增强可靠性的同时,影响访问性能 MongoDB优于Redis
一致性 不支持事务,靠客户端自身保证 支持事务,比较弱,仅能保证事务中的操作按顺序执行 Redis优于MongoDB
数据分析 内置数据分析功能(mapreduce) 不支持 MongoDB优于Redis
应用场景 海量数据的访问效率提升 较小数据量的性能及运算 MongoDB优于Redis

 

四、关于ActiveMQ、RocketMQ、RabbitMQ、Kafka 区别

特性ActiveMQRabbitMQRocketMQkafka
开发语言 java erlang java scala
单机吞吐量 万级 万级 10万级 10万级
时效性 ms级 us级 ms级 ms级以内
可用性 高(主从架构) 高(主从架构) 非常高(分布式架构) 非常高(分布式架构)
功能特性 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 MQ功能比较完备,扩展性佳 只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。

1. RabbitMQ:优点是用天生具有高并发、高可用的erlang语言进行编写的。基于AMQP协议,Erlang是天然集群化的语言 ;

缺点:

  1. erlang开发,很难去看懂源码,基本职能依赖于开源社区的快速维护和修复bug,不利于做二次开发和维护。
  2. RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。
  3. 需要学习比较复杂的接口和协议,学习和维护成本较高。

2.ActiveMQ:优点:是用java语言开发的,与java程序有天然的耦合性,基于STOMP协议 ;缺点:官方社区现在对ActiveMQ 5.x维护越来越少,较少在大规模吞吐的场景中使用。

3.KafKa: 优点: 

  • 性能卓越,单机写入TPS约在百万条/秒,最大的优点,就是吞吐量高。
  • 时效性:ms级
  • 可用性:非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
  • 消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;
  • 有优秀的第三方Kafka Web管理界面Kafka-Manager;
  • 在日志领域比较成熟,被多家公司和多个开源项目使用;
  • 功能支持:功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用

缺点:

  1. Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
  2. 使用短轮询方式,实时性取决于轮询间隔时间;
  3. 消费失败不支持重试;
  4. 支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
  5. 社区更新较慢;

4.RocketMQ 优点:

    1. 单机吞吐量:十万级
    2. 可用性:非常高,分布式架构
    3. 消息可靠性:经过参数优化配置,消息可以做到0丢失
    4. 功能支持:MQ功能较为完善,还是分布式的,扩展性好
    5. 支持10亿级别的消息堆积,不会因为堆积导致性能下降
    6. 源码是java,我们可以自己阅读源码,定制自己公司的MQ,可以掌控

 缺点:

  1. 支持的客户端语言不多,目前是java及c++,其中c++不成熟;
  2. 社区活跃度一般
  3. 没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码

 

五、事物

1.原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做

2.一致性:数据不会因为事务的执行而遭到破坏
3.隔离性:一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰
4.持久性:一个事物一旦提交,它对数据库的改变就是永久的
事务的实现方式:实现方式共有两种:编码方式;声明式事务管理方式
基于AOP技术实现的声明式事务管理,实质就是:在方法执行前后进行拦截,然后再目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务.
声明式事务管理又有两种实现方式:基于xml配置文件的方式;另一个实在业务方法上进行@Transaction注解,将事务规则应用到业务逻辑中
一种常见的事务管理配置:事务拦截器TransactionInterceptor和事务自动代理BeanNameAutoProxyCreator相结合的方式

2、事务的隔离级别

数据库事务的隔离级别有4个,由低到高依次为Read uncommitted(未授权读取、读未提交)、Read committed(授权读取、读提交)、Repeatable read(可重复读取)、Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。

脏读(Dirty Reads) 
脏读(Dirty Read):A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。 

解决办法:如果在第一个事务提交前,任何其他事务不可读取其修改过的值,则可以避免该问题。

不可重复读(Non-repeatable Reads) 
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。 

解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。

幻象读 
指两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中。一般情况下,幻象读应该正是我们所需要的。但有时候却不是,如果打开的游标,在对游标进行操作时,并不希望新增的记录加到游标命中的数据集中来。隔离级别为 游标稳定性 的,可以阻止幻象读。例如:目前工资为1000的员工有10人。那么事务1中读取所有工资为1000的员工,得到了10条记录;这时事务2向员工表插入了一条员工记录,工资也为1000;那么事务1再次读取所有工资为1000的员工共读取到了11条记录。 

解决办法:如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题。

3、悲观锁和乐观锁

悲观锁,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统的数据访问层中实现了加锁机制,也无法保证外部系统不会修改数据。

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以只会在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回用户错误的信息,让用户决定如何去做。实现乐观锁一般来说有以下2种方式:

    • 使用版本号 
      使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
    • 使用时间戳 
      乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

 

六、Nginx的正反向代理

一、正向代理

在如今的网络环境下,我们如果由于技术需要要去访问国外的某些网站,此时你会发现位于国外的某网站我们通过浏览器是没有办法访问的,此时大家可能都会用一个操作FQ进行访问,FQ的方式主要是找到一个可以访问国外网站的代理服务器,我们将请求发送给代理服务器,代理服务器去访问国外的网站,然后将访问到的数据传递给我们。

上述这样的代理模式称为正向代理,正向代理最大的特点:

  1. 客户端非常明确要访问的服务器地址;
  2. 服务器只清楚请求来自哪个代理服务器,而不清楚来自哪个具体的客户端;
  3. 正向代理模式屏蔽或者隐藏了真实客户端信息。

二、反向代理

客户端给服务器发送的请求,nginx服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。此时请求的来源也就是客户端是明确的,但是请求具体由哪台服务器处理的并不明确了,nginx扮演的就是一个反向代理角色。

反向代理,主要用于服务器集群分布式部署的情况下,反向代理隐藏了服务器的信息。

三、负载均衡

我们已经明确了所谓代理服务器的概念,那么接下来,nginx扮演了反向代理服务器的角色,它是以依据什么样的规则进行请求分发的呢?不用的项目应用场景,分发的规则是否可以控制呢?

这里提到的客户端发送的、nginx反向代理服务器接收到的请求数量,就是我们说的负载量

请求数量按照一定的规则进行分发到不同的服务器处理的规则,就是一种均衡规则

所以,将服务器接收到的请求按照规则分发的过程,称为负载均衡。

nginx支持的负载均衡调度算法方式如下:

  1. weight轮询(默认):接收到的请求按照顺序逐一分配到不同的后端服务器,即使在使用过程中,某一台后端服务器宕机,nginx会自动将该服务器剔除出队列,请求受理情况不会受到任何影响。 这种方式下,可以给不同的后端服务器设置一个权重值(weight),用于调整不同的服务器上请求的分配率;权重数据越大,被分配到请求的几率越大;该权重值,主要是针对实际工作环境中不同的后端服务器硬件配置进行调整的。
  2. ip_hash(大部分使用此算法):每个请求按照发起客户端的ip的hash结果进行匹配,这样的算法下一个固定ip地址的客户端总会访问到同一个后端服务器,这也在一定程度上解决了集群部署环境下session共享的问题。
  3. fair:智能调整调度算法,动态的根据后端服务器的请求处理到响应的时间进行均衡分配,响应时间短处理效率高的服务器分配到请求的概率高,响应时间长处理效率低的服务器分配到的请求少;结合了前两者的优点的一种调度算法。但是需要注意的是nginx默认不支持fair算法,如果要使用这种调度算法,请安装upstream_fair模块
  4. url_hash(极少数使用此算法):按照访问的url的hash结果分配请求,每个请求的url会指向后端固定的某个服务器,可以在nginx作为静态服务器的情况下提高缓存效率。同样要注意nginx默认不支持这种调度算法,要使用的话需要安装nginx的hash软件包

七、Eureka和Zookeeper的区别

1、Zookeeper当master挂了,会在30-120s进行leader选举,这点类似于redis的哨兵机制,在选举期间Zookeeper是不可用的,这么长时间不能进行服务注册,是无法忍受的,别说30s,5s都不能忍受。这时Zookeeper集群会瘫痪,这也是Zookeeper的CP,保持节点的一致性,牺牲了A/高可用。而Eureka不会,即使Eureka有部分挂掉,还有其他节点可以使用的,他们保持平级的关系,只不过信息有可能不一致,这就是AP,牺牲了C/一致性。

2、在之前的文章已经提到过Eureka有自我保护机制(15分钟内超过85%的服务节点没有心跳/down),这点我觉得确实要比Zookeeper好,即使服务不可用,也会保留当前失效的微服务,默认90秒,在这90秒Eureka不会注销微服务,在这90秒内仍然可以接受新的服务注册,只是不会同步到其他节点上。当坏掉的服务恢复的时候,会自动加入到节点上,也是高可用的一种。然后退出自我保护机制,这也是应对网络异常的一种机制

总结:Zookeeper出现网络等故障的时候导致整个服务注册瘫痪太要命了。Eureka能很好的应对网络故障导致失去节点的情况。

八、线程

1.进程与线程的关系

一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。,亦即执行处理机调度的基本单位。 进程和线程的关系:

  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

  • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。

  • 处理机分给线程,即真正在处理机上运行的是线程。

  • 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

2.创建线程的方式

 继承Thread类和实现Runable接口

 

九、接口的幂等性

1. 接口调用存在的问题

        现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用RPC通信或者restful,既然是通信,那么就有可能在服务器处理完毕后返回结果的时候挂掉,这个时候用户端发现很久没有反应,那么就会多次点击按钮,这样请求有多次,那么处理数据的结果是否要统一呢?那是肯定的!尤其在支付场景。

2. 什么是接口幂等性

       接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性。

3. 什么情况下需要保证接口的幂等性

   在增删改查4个操作中,尤为注意就是增加或者修改,

4. 那么如何设计接口才能做到幂等呢?

   常见的两种实现方案:  1. 通过代码逻辑判断实现    2. 使用token机制实现      下面以支付系统为例,分别对接口的幂等性进行说明与实现

    A: 通过代码逻辑判断实现接口幂等性,只能针对一些满足判断的逻辑实现,具有一定局限性

 用户购买商品的订单系统与支付系统;订单系统负责记录用户的购买记录已经订单的流转状态(orderStatus),支付系统用于付款,提供如下接口,订单系统与支付系统通过分布式网络交互。

boolean pay(int accountid,BigDecimal amount) //用于付款,扣除用户的   

这种情况下,支付系统已经扣款,但是订单系统因为网络原因,没有获取到确切的结果,因此订单系统需要重试。由上图可见,支付系统并没有做到接口的幂等性,订单系统第一次调用和第二次调用,用户分别被扣了两次钱,不符合幂等性原则(同一个订单,无论是调用了多少次,用户都只会扣款一次)。如果需要支持幂等性,付款接口需要修改为以下接口:

  boolean pay(int orderId,int accountId,BigDecimal amount)

通过orderId来标定订单的唯一性,付款系统只要检测到订单已经支付过,则第二次调用不会扣款而会直接返回结果:

在不同的业务中不同接口需要有不同的幂等性,特别是在分布式系统中,因为网络原因而未能得到确定的结果,往往需要支持接口幂等性。

B: 使用token机制实现接口幂等性,通用性强的实现方法

     token机制实现步骤:

     1. 生成全局唯一的token,token放到redis或jvm内存,token会在页面跳转时获取.存放到pageScope中,支付请求提交先获取token

     2. 提交后后台校验token,执行提交逻辑,提交成功同时删除token,生成新的token更新redis ,这样当第一次提交后token更新了,页面再次提交携带的token是已删除的token后台验证会失败不让提交


     token特点:   要申请,一次有效性,可以限流

     注意: redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用

十、类的加载方式

1.虚拟机加载类的途径:通过new 关键字创建类的实例;如Dog dog = new Dog();
2.调用 Class.forName() 方法。通过反射加载类型,并创建对象实例
如:Class clazz = Class.forName(“Dog”);
Object dog =clazz.newInstance();
3.调用某个 ClassLoader 实例的 loadClass() 方法
通过该 ClassLoader 实例的 loadClass() 方法载入。应用程序可以通过继承 ClassLoader 实现自己的类装载器。
如:Class clazz = classLoader.loadClass(“Dog”);
Object dog =clazz.newInstance();
三者的区别:
1和2使用的类加载器是相同的,都是当前类加载器。(即:this.getClass.getClassLoader)。3由用户指定类加载器。如果需要在当前类路径以外寻找类,则只能采用第3种方式。第3种方式加载的类与当前类分属不同的命名空间。另外,1是静态加载,2、3是动态加载
Class的装载包括3个步骤:加载(loading),连接(link),初始化(initialize)。
十一、JAVA的容器
List,Map,Set ,Collection ,List ,LinkedList ,ArrayList ,Vector ,Stack ,Map ,Hashtable ,HashMap ,WeakHashMap

数据容器主要分为了两类:
Collection: 存放独立元素的序列。又分为两种类型的接口:List和Set。两者最明显的差别为List支持放入重复的对象,而Set不支持。List接口常用的实现类有:ArrayList,LinkedList,Vector和Stack;Set接口常用的实现有HashSet,TreeSet。
Map:存放key-value型的元素对。(这对于需要利用key查找value的程序十分的重要!)。而Map的常用实现有TreeMap和HashMap。

一、ArrayList

1. ArrayList基于数组方式实现,无容量的限制。

2. ArrayList在执行插入元素时可能要扩容,在删除数组时并不会减少数组的容量(如希望相应的减少数组的容量,可以调用ArrayList的trimToSize()),在查找元素时需要遍历数组,对于非null的元素采取equals的方式寻找。

3.ArrayList是非线程安全的。

二、LinkedList

1. LinkedList基于双向链表机制实现。

2. LinkedList在插入元素时,必须创建一个新的Entry对象,并切换相应元素的前后元素的引用;在查找元素时,须遍历列表;在删除元素时,要遍历列表,找到要删除的元素,然后从列表上将此元素删除即可。

3. LinkedList是非线程安全的。

从LinkedList和ArrayList的区别来看,我们可容易的得出什么时候使用ArrayList,什么时候使用LinkedList:

     1)对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
     2)  对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

三、Vector

      和ArrayList的不同点有

      1) Vector是基于Synchronized实现的线程安全的ArrayList。即Vector是线程安全的。

      2)在插入元素时容量扩充的机制和ArrayList稍微有所不同,Vector是扩充2倍,并可通过传入capacityIncrement来控制容量的扩充。而ArrayList是扩充1.5倍并加1。

四、Stack

      Stack继承与Vector,在Vector的基础上实现了Stack所要求的后进先出(LIFO)的弹出即压入操作,其提供了push,pop,peek等三个主要方法。

五、HashSet

      1. HashSet基于HashMap实现,无容量限制。

      2. HashSet是非线程安全的。 

      3. HashSet不保证有序。

 

六、TreeSet

      1、TreeSet基于TreeMap实现,支持排序。

      2、TreeSet是非线程安全的。

从对HashSet和TreeSet的描述来看,TreeSet和HashSet一样,也是完全基于Map来实现的,并且都不支持get(int)来获取指定位置的元素(需要遍历获取),另外TreeSet还提供了一些排序方面的支持。例如传入Comparator实现、descendingSet以及descendingIterator等。

七、LinkedHashSet:

      1. LinkedHashSet 以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代

      2. LinkedHashSet同样是非线程安全的。

LinkedHashSet, HashSet,TreeSet三者的区别:

HashSet:哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定顺序来存放;

LinkedHashSet:以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代;  

TreeSet:提供一个使用红黑树结构存储Set接口的实现,对象以升序顺序存储,访问和遍历的时间很快。

八、HashMap

       1、HashMap采用数组方式存储key,value构成的Entry对象,无容量限制。

       2、HashMap基于Key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式来解决。

       3、HashMap在插入元素时可能会要扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中。

       4、HashMap是非线程安全的。

       5、HashMap遍历使用的是Iterator

 

九、HashTable

      1、HashTable是线程安全的。

      2、HashTable中无论是Key,还是Value都不允许为null。

      3.   HashTable遍历使用的是Enumeration。

十、TreeMap:

       1、SortMap的实现

       2、TreeMap是一个典型的基于红黑树的Map实现,因此它要求一定要有Key比较的方法,要么传入Comparator实现,要么key对象实现Comparable接口。

       3、TreeMap是非线程安全的。

十一、LinkedHashMap:

     1. LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.

     2. LinkedHashMap 也可以在构造时用带参数,按照应用次数排序。

 

LinkedHashMap在遍历的时候会比HashMap慢。不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

十一、序列化和反序列化的原理

1)Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程;

(2)**序列化:**对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

(3)**反序列化:**客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

(4)本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。

2、实现序列化的要求

只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!

4、JDK类库中序列化的步骤
步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
步骤二:通过对象输出流的writeObject()方法写对象:

5、JDK类库中反序列化的步骤

步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:

步骤二:通过对象输出流的readObject()方法读取对象:
十二、java内存模型和jvm的内存结构
  • Java 内存模型,描述的是多线程允许的行为
  • JVM 内存结构,描述的是线程运行所设计的内存空间
JVM 主要分为三个子系统:类加载器运行时数据区执行引擎

Java 内存模型

Java 内存模型(下文简称 JMM)就是在底层处理器内存模型的基础上,定义自己的多线程语义。它明确指定了一组排序规则,来保证线程间的可见性。


十三、spring 的控制反转(IOC)、依赖注入(DI)、面向切面(AOP)

IoC(Inversion of Control)中文名叫控制反转,就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由Spring负责的。这样控制权就由应用转移到了Spring,控制权的转移就是所谓反转。

依赖注入有3种方式
1.接口注入:通过将@Autowired注解放在构造器上来完成接口注入。。

2.setter注入
通过将@Autowired注解放在方法上来完成方法参数注入。
3 构造器注入:通过将@Autowired注解放在构造器上来完成构造器注入,默认构造器参数通过类型自动装配

AOP(Aspect-Oriented Programming面向层面的编程)将安全、事务等,于逻辑相对独立的功能抽取出来,利用spring的配置文件将这些功能插进去,实现了按照切面编程,提高了复用性
使用AOP的好处
  • 降低模块的耦合度
  • 使系统容易扩展
  • 提高代码复用性
  • AOP简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

Java的三种代理模式

1.1.静态代理(类似于装饰者模式)

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.


静态代理总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:


因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.


1.2.动态代理

动态代理有以下特点:

1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理


1.2.JDK动态代理


TCP/IP协议与Http协议的区别

TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。
Http是无状态的短连接,而TCP是有状态的长连接

1、TCP/IP连接


手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。


2、HTTP连接


HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。


HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。


为什么 TCP 需要三次握手?

原因一:防止重复连接

比如在网络状况比较复杂或者网络状况比较差的情况下,发送方可能会连续发送多次建立连接的请求。如果 TCP 握手的次数只有两次,那么接收方只能选择接受请求或者拒绝接受请求,但它并不清楚这次的请求是正常的请求,还是由于网络环境问题而导致的过期请求,如果是过期请求的话就会造成错误的连接。


所以如果 TCP 是三次握手的话,那么客户端在接收到服务器端 SEQ+1 的消息之后,就可以判断当前的连接是否为历史连接,如果判断为历史连接的话就会发送终止报文(RST)给服务器端终止连接;如果判断当前连接不是历史连接的话就会发送指令给服务器端来建立连接。


原因二:同步初始化序列化
通过上面的概念我们知道 TCP 的一个重要特征就是可靠性,而 TCP 为了保证在不稳定的网络环境中构建一个稳定的数据连接,它就需要一个“序列号”字段来保证自己的稳定性,而这个序列号的作用就是防止数据包重复发送,以及有效的解决数据包接收时顺序颠倒的问题。

CP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,如图1所示。


(1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。


(2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。


(3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。


完成三次握手,客户端与服务器开始传送数据。




posted @   红豆奶茶+红豆  阅读(200)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示