考研败犬备战春招小笔记

记录本人在备战春招遇见的一些不会的,忘记的i知识点。如果你碰巧也忘记了这些知识点,希望能对你有所帮助!该帖子会坚持更新。大家一起加油!

4-12记录

@RestController vs @Controller
Controller 返回⼀个⻚⾯单独使⽤ @Controller 不加 @ResponseBody 的话⼀般使⽤在要返回⼀个视图的情况,这种情况属于⽐᫾传统的Spring MVC 的应⽤,对应于前后端不分离的情况。
@RestController 返回JSON 或 XML 形式数据但 @RestController 只返回对象,对象数据直接以 JSON 或 XML 形式写⼊ HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是⽬前⽇常开发所接触的最常⽤的情况(前后端分离)。
@ResponseBody 注解的作⽤是将 Controller 的⽅法返回的对象通过适当的转换器转换为指定的格式之后,写⼊到HTTP 响应(Response)对象的 body 中,通常⽤来返回 JSON 或者XML 数据,返回 JSON 数据的情况⽐较多

IoC(Inverse of Control:控制反转)是⼀种设计思想,就是 将原本在程序中⼿动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语⾔中也有应⽤,并⾮ Spring 特有。 IoC 容器是Spring ⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。

AOP(Aspect-Oriented Programming:⾯向切⾯编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDK Proxy,去创建代理对象,⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了,这时候Spring AOP会使⽤Cglib ,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理

AOP与AspectJ 的区别:
Spring AOP 属于运⾏时增强,⽽ AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying), ⽽ AspectJ 基于字节码操作(BytecodeManipulation)。Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java ⽣态系统中最完整的 AOP 框架了。AspectJ 相⽐于 Spring AOP 功能更加强⼤,但是 Spring AOP 相对来说更简单,如果我们的切⾯比较少,那么两者性能差异不⼤。但是,当切⾯太多的话,最好选择 AspectJ ,它⽐Spring AOP 快很多。

@Component 和 @Bean 的区别
1.作⽤对象不同: @Component 注解作⽤于类,⽽ @Bean 注解作⽤于⽅法。
2. @Component 通常是通过类路径扫描来⾃动侦测以及⾃动装配到Spring容器中(我们可以使⽤ @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类⾃动装配到Spring 的 bean 容器中)。 @Bean 注解通常是我们在标有该注解的⽅法中定义产⽣这个bean, @Bean 告诉了Spring这是某个类的示例,当我需要⽤它的时候给我。
3. @Bean 注解⽐ Component 注解的⾃定义性更强,⽽且很多地⽅我们只能通过 @Bean 注解来注册bean。⽐如当我们引⽤第三⽅库中的类需要装配到 Spring 容器时,则只能通过@Bean 来实现。

#{}和${}
是 P r o p e r t i e s ⽂ 件 中 的 变 量 占 位 符 , 它 可 以 ⽤ 于 标 签 属 性 值 和 s q l 内 部 , 属 于 静 态 ⽂ 本 替 换 , ⽐ 如 {} 是 Properties ⽂件中的变量占位符,它可以⽤于标签属性值和 sql 内部,属于静态⽂本替换,⽐如 Propertiessql{driver}会被静态替换为 com.mysql.jdbc.Driver 。
#{} 是 sql 的参数占位符,Mybatis 会将 sql 中的 #{} 替换为?号,在 sql 执⾏前会使⽤PreparedStatement 的参数设置⽅法,按序给 sql 的?号占位符设置参数值,⽐如ps.setInt(0, parameterValue), #{item.name} 的取值⽅式为使⽤反射从参数对象中获取item 对象的 name 属性值,相当于 param.getItem().getName() 。

Dao 接⼝⾥的⽅法,是不能重载的,因为是全限名+⽅法名的保存和寻找策略。
Dao 接⼝的⼯作原理是 JDK 动态代理,Mybatis 运⾏时会使⽤ JDK 动态代理为 Dao 接⼝⽣成代理 proxy 对象,代理对象 proxy 会拦截接⼝⽅法,转⽽执⾏ MappedStatement 所代表的 sql,然后将 sql 执⾏结果返回。

Mybatis动态sql:
Mybatis 动态 sql 可以让我们在 Xml 映射⽂件内,以标签的形式编写动态 sql,完成逻辑判断
和动态拼接 sql 的功能。提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。

Mybatis 仅⽀持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是⼀对⼀,collection 指的就是⼀对多查询。在 Mybatis 配置⽂件中,可以配置是否启⽤延迟加载 lazyLoadingEnabled=true|false,原理是使⽤ CGLIB 创建⽬标对象的代理对象,当调⽤⽬标⽅法时,进⼊拦截器⽅法。

4-9记录

布隆过滤器是⼀个⾮常神奇的数据结构,通过它我们可以⾮常⽅便地判断⼀个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法。但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: 布隆过滤器说某个元素存在,⼩概率会误判。布隆过滤器说某个元素不在,那么这个元素⼀定不在。
1.使⽤布隆过滤器中的哈希函数对元素值进⾏计算,得到哈希值(有⼏个哈希函数得到⼏个哈希值)。
2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
当我们需要判断⼀个元素是否存在于布隆过滤器的时候,会进⾏:

  1. 对给定元素再次进⾏相同的哈希计算;
  2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在⼀个值不为 1,说明该元素不在布隆过滤器中。然后,⼀定会出现这样⼀种情况:不同的字符串可能哈希出来的位置相同。

对 Cache Aside Pattern(旁路缓存模式)
Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删除 cache 。如果更新数据库成功,⽽删除缓存这⼀步失败的情况的话,简单说两个解决⽅案:

  1. 缓存失效时间变短(不推荐,治标不治本) :我们让缓存数据的过期时间变短,这样的话缓
    存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适
    ⽤。
  2. 增加cache更新重试机制(常⽤): 如果 cache 服务当前不可⽤导致缓存删除失败的话,
    我们就隔⼀段时间进⾏重试,重试次数可以⾃⼰定。如果多次重试还是失败的话,我们可以
    把当前更新失败的 key 存⼊队列中,等缓存服务可⽤之后,再将 缓存中对应的 key 删除即

4-8记录

@PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值。

@PathVariable(“xxx”)
通过 @PathVariable 可以将URL中占位符参数{xxx}绑定到处理器类的方法形参中@PathVariable(“xxx“)

@RequestMapping("show5/{id}/{name}")
public ModelAndView test5(@PathVariable("id") Long ids ,@PathVariable("name") String names){
    ModelAndView mv = new ModelAndView();
    mv.addObject("msg","占位符映射:id:"+ids+";name:"+names);
    mv.setViewName("hello2");
    return mv;
}

参数名字需要和RequestMapping路径名字一样。

4-6记录

Redis 单线程模型详解
Redis 基于 Reactor 模式来设计开发了⾃⼰的⼀套⾼效的事件处理模型 (Netty 的线程模型也基
于 Reactor 模式,Reactor 模式不愧是⾼性能 IO 的基⽯),这套事件处理模型对应的是 Redis
中的⽂件事件处理器(file event handler)。由于⽂件事件处理器(file event handler)是单线程
⽅式运⾏的,所以我们⼀般都说 Redis 是单线程模型。

Redis 通过IO 多路复⽤程序 来监听来⾃客户端的⼤量连接(或者说是监听多个 socket),它会
将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发⽣。
这样的好处⾮常明显: I/O 多路复⽤技术的使⽤让 Redis 不需要额外创建多余的线程来监听客户
端的⼤量连接,降低了资源的消耗(和 NIO 中的 Selector 组件很像)。
另外, Redis 服务器是⼀个事件驱动程序,服务器需要处理两类事件: 1. ⽂件事件; 2. 时间事
件。时间事件不需要多花时间了解,我们接触最多的还是 ⽂件事件(客户端进⾏读取写⼊等操作,涉
及⼀系列⽹络通信)。
⽂件事件处理器(file event handler)主要是包含 4 个部分:
多个 socket(客户端连接)
IO 多路复⽤程序(⽀持多个客户端连接的关键)
⽂件事件分派器(将 socket 关联到相应的事件处理器)
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

虽然说 Redis 是单线程模型,但是, 实际上,Redis 在 4.0 之后的版本中就已经加⼊了对多线程的⽀持。
Redis6.0 引⼊多线程主要是为了提⾼⽹络 IO 读写性能,因为这个算是 Redis 中的⼀个性能瓶颈(Redis 的瓶颈主要受限于内存和⽹络)。
虽然,Redis6.0 引⼊了多线程,但是 Redis 的多线程只是在⽹络数据的读写这类耗时操作上使⽤了, 执⾏命令仍然是单线程顺序执⾏。因此,你也不需要担⼼线程安全问题。Redis6.0 的多线程默认是禁⽤的,只使⽤主线程。如需开启需要修改 redis 配置⽂件 redis.conf

Redis 通过⼀个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键),过期字典的值是⼀个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。

如果假设你设置了⼀批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进⾏删除的呢?
常⽤的过期数据的删除策略就两个(重要!⾃⼰造缓存轮⼦的时候需要格外考虑的东⻄):

  1. 惰性删除 :只会在取出key的时候才对数据进⾏过期检查。这样对CPU最友好,但是可能会
    造成太多过期 key 没有被删除。
  2. 定期删除 : 每隔⼀段时间抽取⼀批 key 执⾏删除过期key操作。并且,Redis 底层会通过限
    制删除操作执⾏的时⻓和频率来减少删除操作对CPU时间的影响。

定期删除对内存更加友好,惰性删除对CPU更加友好。两者各有千秋,所以Redis 采⽤的是 定期
删除+惰性/懒汉式删除 。
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉
了很多过期 key 的情况。这样就导致⼤量过期 key 堆积在内存⾥,然后就Out of memory了。
怎么解决这个问题呢?答案就是: Redis 内存淘汰机制。

Redis 提供 6 种数据淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)
    中挑选最近最少使⽤的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru(least recently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移除
    最近最少使⽤的 key(这个是最常⽤的)
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-eviction:禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报
    错。这个应该没⼈使⽤吧!
    4.0 版本后增加以下两种:
  7. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中
    挑选最不经常使⽤的数据淘汰
  8. allkeys-lfu(least frequently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移
    除最不经常使⽤的 key

4-4记录

MySQL是一种关系型数据库,开源免费,方便扩展。
MyISAM引擎和InnoDB引擎的区别
MyISAM是MySQL的默认数据库引擎(5.5版之前),然性能极佳,⽽且提供了⼤量的特性,包括全⽂索引、压缩、空间函数等,但MyISAM不⽀持事务和⾏级锁,⽽且最⼤的缺陷就是崩溃后⽆法安全恢复。5.5版本之后,MySQL引⼊了InnoDB(事务性数据库引擎),MySQL5.5版本后默认的存储引擎为InnoDB,大部分时候我们使用的都是InnoDB存储引擎。
两者对比:
1.是否⽀持⾏级锁 : MyISAM 只有表级锁(table-level locking),⽽InnoDB ⽀持⾏级锁(rowlevel locking)和表级锁,默认为⾏级锁。
2.是否⽀持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原⼦性,其执⾏速度⽐InnoDB类型更快,但是不提供事务⽀持。但是InnoDB 提供事务⽀持事务,外部键等⾼级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能⼒(crash recoverycapabilities)的事务安全(transaction-safe (ACID compliant))型表。
3. 是否⽀持外键: MyISAM不⽀持,⽽InnoDB⽀持。
4. 是否⽀持MVCC :仅 InnoDB ⽀持。应对⾼并发事务, MVCC⽐单纯的加锁更⾼效;MVCC只 在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下⼯作;MVCC可以使⽤ 乐 观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统⼀。

字符集指的是⼀种从⼆进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。MySQL中每⼀种字符集都会对应⼀系列的校对规则
MySQL采⽤的是类似继承的⽅式指定字符集的默认值,每个数据库以及每张数据表都有⾃⼰的默认值,他们逐层继承。

MySQL索引主要有BTree索引 和 哈希索引
对于哈希索引来说,底层的数据结构就是哈希表,因此在绝⼤多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余⼤部分场景,建议选择BTree索引,BTree索引使用的是B树中的B+树。

MyISAM: B+Tree叶节点的data域存放的是数据记录的地址在索引检索的时候,⾸先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“⾮聚簇索引”。
InnoDB: 其数据⽂件本身就是索引⽂件。相⽐MyISAM,索引⽂件和数据⽂件是分离的,其表数据⽂件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据⽂件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。⽽其余的索引都作为ᬀ助索引,ᬀ助索引的data域存储相应记录主键的值⽽不是地址,这也是和MyISAM不同的地⽅。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再⾛⼀遍主索引。 因此,在设计表的时候,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮单调的字段作为主键,这样会造成主索引频繁分裂。

MySQL 8.0 版本后移除了查询缓存的使用。

并发事务带来的问题
脏读(Dirty read): 当⼀个事务正在访问数据并且对数据进⾏了修改,⽽这种修改还没有提交到数据库中,这时另外⼀个事务也访问了这个数据,然后使⽤了这个数据。因为这个数据是还没有提交的数据,那么另外⼀个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。即读了尚未提交的数据。
丢失修改(Lost to modify): 指在⼀个事务读取⼀个数据时,另外⼀个事务也访问了该数据,那么在第⼀个事务中修改了这个数据后,第⼆个事务也修改了这个数据。这样第⼀个事务内的修改结果就被丢失,因此称为丢失修改。
不可重复读(Unrepeatableread): 指在⼀个事务内多次读同⼀数据。在这个事务还没有结束时,另⼀个事务也访问该数据。那么,在第⼀个事务中的两次读数据之间,由于第⼆个事务的修改导致第⼀个事务两次读取的数据可能不太⼀样。
幻读(Phantom read): 幻读与不可重复读类似。它发⽣在⼀个事务(T1)读取了⼏⾏数据,接着另⼀个并发事务(T2)插⼊了⼀些数据时。在随后的查询中,第⼀个事务(T1)就会发现多了⼀些原本不存在的记录。

表级锁和行级锁区别:
表级锁: MySQL中锁定 粒度最⼤ 的⼀种锁,对当前操作的整张表加锁,实现简单,资源消耗也⽐᫾少,加锁快,不会出现死锁。其锁定粒度最⼤,触发锁冲突的概率最⾼,并发度最低,MyISAM和 InnoDB引擎都⽀持表级锁。
⾏级锁: MySQL中锁定 粒度最⼩ 的⼀种锁,只针对当前操作的⾏进⾏加锁。 ⾏级锁能⼤⼤减少数据库操作的冲突。其加锁粒度最⼩,并发度⾼,但加锁的开销也最⼤,加锁慢,会出现死锁

InnoDB的锁算法
Record lock:单个⾏记录上的锁
Gap lock:间隙锁,锁定⼀个范围,不包括记录本身
Next-key lock:record+gap 锁定⼀个范围,包含记录本身
1.innodb对于⾏的查询使⽤next-key lock
2. Next-locking keying为了解决Phantom Problem幻读问题
3. 当查询的索引含有唯⼀属性时,将next-key lock降级为record key
4. Gap锁设计的⽬的是为了阻⽌多个事务将记录插⼊到同⼀范围内,⽽这会导致幻读问题的产⽣

MySQL单表记录数过⼤时,数据库的CRUD性能会明显下降,⼀些常⻅的优化措施如下:
限定数据的范围,禁⽌不带任何限制数据范围条件的查询语句。
读/写分离,主库负责写,从库负责读。
垂直分区:根据数据库⾥⾯数据表的相关性进⾏拆分。将数据表列的拆分,把⼀张列⽐较多的表拆分为多张表。甚至可以放到单独的库。
好处:可以使得列数据变⼩,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
缺点:主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应⽤层进⾏Join来解决。此外,垂直分区会让事务变得更加复杂;
⽔平分区:保持数据表结构不变,通过某种策略存储数据分⽚。这样每⼀⽚数据分散到不同的表或者库中,达到了分布式的⽬的。 ⽔平拆分可以⽀撑⾮常⼤的数据量。
但是分表仅仅是解决了单⼀表数据过⼤的问题,但由于表的数据还是在同⼀台机器上,其实对于提升MySQL并发能⼒没有什么意义,所以⽔平拆分最好分库。

Redis是一种非关系型数据库,而且是存在内存中的内存数据库。除了做缓存之外,Redis 也经常⽤来做分布式锁,甚⾄是消息队列Redis 还⽀持事务 ,持久化、Lua 脚本、多种集群⽅案
Redis和MemCached的区别:
1.Redis ⽀持更丰富的数据类型(⽀持更复杂的应⽤场景)。Redis 不仅仅⽀持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只⽀持最简单的 k/v 数据类型。
2. Redis ⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤,⽽ Memecache 把数据全部存在内存之中。
3. Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
4. Redis 在服务器内存使⽤完之后,可以将不⽤的数据放到磁盘上。但是,Memcached 在服务器内存使⽤完之后,就会直接报异常。
5. Memcached 没有原⽣的集群模式,需要依靠客户端来实现往集群中分⽚写⼊数据;但是Redis ⽬前是原⽣⽀持 cluster 模式的.
6. Memcached 是多线程,⾮阻塞 IO 复⽤的⽹络模型;Redis 使⽤单线程的多路 IO 复⽤模型。 (Redis 6.0 引⼊了多线程 IO )
7. Redis ⽀持发布订阅模型、Lua 脚本、事务等功能,⽽ Memcached 不⽀持。并且,Redis⽀持更多的编程语⾔。
8. Memcached过期数据的删除策略只⽤了惰性删除,⽽ Redis 同时使⽤了惰性删除与定期删除

4-3记录

进程间的通信方式
管道/匿名管道(Pipes) :⽤于具有亲缘关系的⽗⼦进程间或者兄弟进程之间的通信。
有名管道(Names Pipes) : 匿名管道由于没有名字,只能⽤于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘⽂件的⽅式存在,可以实现本机任意两个进程通信
信号(Signal) :信号是⼀种⽐᫾复杂的通信⽅式,⽤于通知接收进程某个事件已经发⽣
消息队列(Message Queuing)消息队列克服了信号承载信息量少,管道只能承载⽆格式字 节流以及缓冲区⼤⼩受限等缺。
信号量(Semaphores)信号量是⼀个计数器,⽤于多进程对共享数据的访问,信号量的意图在于进程间同步。⽤于解决与同步相关的问题并避免竞争条件
共享内存(Shared memory) :使得多个进程可以访问同⼀块内存空间,不同进程可以及时看到对⽅进程中对共享内存中数据的更新。
套接字(Sockets) : 此⽅法主要⽤于在客户端和服务器之间通过⽹络进⾏通信。套接字是⽀持 TCP/IP 的⽹络通信的基本操作单元。

CPU寻址:现代处理器使⽤的是⼀种称为 虚拟寻址(Virtual Addressing) 的寻址⽅式。使⽤虚拟寻址,CPU需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有⼀个被称为 内存管理单元(Memory Management Unit,MMU) 的硬件。
虚拟内存:虚拟内存是计算机系统内存管理的⼀种技术,虚拟内存的重要意义是它定义了⼀个连续的虚拟地址空间,并且把内存扩展到硬盘空间。
虚拟内存的实现需要建⽴在离散分配的内存管理⽅式的基础上。请求分⻚存储管理,请求分段存储管理 , 请求段⻚式存储管理。

4-2记录

Tcp三次握手,接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
双⽅通信⽆误必须是两者互相发送信息都⽆误。传了 SYN,证明发送⽅到接收⽅的通道没有问题,但是接收⽅到发送⽅的通道还需要 ACK 信号来进⾏验证。

TCP的拥塞控制采⽤了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。

输入url访问一个网站需要那几个协议。
1.DNS解析
2. TCP连接
3. 发送HTTP请求
4. 服务器处理请求并返回HTTP报⽂
5. 浏览器解析渲染⻚⾯
6. 连接结束

Http短链接和长连接。在HTTP/1.0中默认使⽤短连接。也就是说,客户端和服务器每进⾏⼀次HTTP操作,就建⽴⼀次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web⻚中包含有其他的Web资源(如JavaScript⽂件、图像⽂件、CSS⽂件等),每遇到这样⼀个Web资源,浏览器就会重新建⽴⼀个HTTP会话⽽从HTTP/1.1起,默认使⽤⻓连接,⽤以保持连接特性。使⽤⻓连接的HTTP协议,会在响应头加入Connection:keep-alive。当⼀个⽹⻚打开完成后,客户端和服务器之间⽤于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使⽤这⼀条已经建⽴的连接。KeepAlive不会永久保持连接,它有⼀个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现⻓连接需要客户端和服务端都⽀持⻓连接。
Http是一个无状态的协议, 需要Session 机制解决这个问题,Session 的主要作⽤就是通过服务端记录⽤户的状态。

Http和Https的区别HTTP的URL由“http://”起始且默认使⽤端⼝80,⽽HTTPS的URL由“https://”起始且默认使⽤端⼝443。
HTTP协议运⾏在TCP之上,所有传输的内容都是明⽂,客户端和服务器端都⽆法验证对⽅的身份。HTTPS是运⾏在SSL/TLS之上的HTTP协议,SSL/TLS 运⾏在TCP之上。所有传输的内容都经过加密,加密采⽤对称加密,但对称加密的密钥⽤服务器⽅的证书进⾏了⾮对称加密。所以说,HTTP 安全性没有 HTTPS⾼,但是 HTTPS ⽐HTTP耗费更多服务器资源。

Cookie 被禁⽤可以利用URL重写把SessionID直接附加在URL路径的后面
Cookie ⼀般⽤来保存⽤户信息。Session 的主要作⽤就是通过服务端记录⽤户的状态。Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端

\s表示匹配任何空白字符,包括空格、制表符、换页符等等。

4-1记录

线程池创建通过ThreadPoolExecutor的方式来创建。
因为Executors 返回线程池对象可能会造成内存泄漏
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列⻓度为
Integer.MAX_VALUE ,可能堆积⼤量的请求,从⽽导致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为
Integer.MAX_VALUE ,可能会创建⼤量线程,从⽽导致 OOM。
FixedThreadPool : 该⽅法返回⼀个固定线程数量的线程池
SingleThreadExecutor: ⽅法返回⼀个只有⼀个线程的线程池。
CachedThreadPool: 该⽅法返回⼀个可根据实际情况调整线程数量的线程池

ThreadPoolExecutor的重要参数
corePoolSize : 核⼼线程数。线程数定义了最⼩可以同时运⾏的线程数量。
maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数
量变为最⼤线程数
workQueue : 当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达
到的话,新任务就会被存放在队列中
饱和策略:
ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的处理。
ThreadPoolExecutor.CallerRunsPolicy :调⽤执⾏⾃⼰的线程运⾏任务。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。
ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉。
ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。

所谓原⼦类说简单点就是具有原⼦/原⼦操作特征的类,一个操作是不可中断的。即使是多个线程一起执行。操作一旦开始,就不会被其他线程干扰。

AQS 的全称为( AbstractQueuedSynchronizer )AQS 是⼀个⽤来构建锁和同步器的框架,使⽤ AQS 能简单且⾼效地构造出应⽤⼴泛的⼤量的同
步器,⽐如我们提到的 ReentrantLock , Semaphore ,其他的诸如ReentrantReadWriteLock , SynchronousQueue , FutureTask 等等皆是基于 AQS 的。当然,我们⾃⼰也能利⽤ AQS ⾮常轻松容易地构造出符合我们⾃⼰需求的同步器。

AQS的原理是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的⼯作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占⽤,那么就需要⼀套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是⽤ CLH 队列锁实现的,即将暂时获取不到锁的线程加⼊到队列中。
AQS定义两种资源共享方式Exclusive(独占):只有⼀个线程能执⾏,如 ReentrantLock 。⼜可分为公平锁和⾮公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
⾮公平锁:当线程要获取锁时,⽆视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执⾏,如CountDownLatch 、 Semaphore 、 CountDownLatch 、 CyclicBarrier 、 ReadWriteLock 。

CountDownLatch 是AQS组件之一,它的作⽤就是 允许 count 个线程阻塞在⼀个地⽅,直⾄所有线程的任务都执⾏完毕。可以用CompletableFuture 类来改进

方法区和永久代的关系,在不同的 JVM 上⽅法区的实现肯定是不同的了。 ⽅法区和永久代的关系很像Java 中接⼝和类的关系,类实现了接⼝,⽽永久代就是 HotSpot 虚拟机对虚拟机规范中⽅法区的⼀种实现⽅式。 也就是说,永久代是 HotSpot (虚拟机)的概念,⽅法区是 Java 虚拟机规范中的定义,是⼀种规范,⽽永久代是⼀种实现,⼀个是标准⼀个是实现,其他的虚拟机实现并没有永久代这⼀说法。
Java创建对象的过程:类加载检查,虚拟机遇到⼀条 new 指令时,⾸先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引⽤,并且检查这个符号引⽤代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执⾏相应的类加载过程。
分配内存接下来虚拟机将为新⽣对象分配内存。对象所需的内存⼤⼩在类加载完成后便可确定,为对象分配空间的任务等同于把⼀块确定⼤⼩的内存从 Java 堆中划分出来。分配⽅式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配⽅式由 Java 堆是否规整决定,⽽ Java堆是否规整⼜由所采⽤的垃圾收集器是否带有压缩整理功能决定。若GC 收集器的算法是"标记-清除",是不规整的。若是"标记-整理"(也称作"标记-压缩")是规整的。复制算法也是规整的。若Java内存规整就选择指针碰撞,不规整选择空闲列表。内存分配并发必须要保证线程安全。使用CAS+失败重试: CAS 是乐观锁的⼀种实现⽅式。所谓乐观锁就是,每次不加锁⽽是假设没有冲突⽽去完成某项操作,如果因为冲突失败就重试,直到成功为⽌。虚拟机采⽤ CAS配上失败重试的⽅式保证更新操作的原⼦性。
TLAB: 为每⼀个线程预先在 Eden 区分配⼀块⼉内存,JVM 在给线程中的对象分配内存时,⾸先在 TLAB 分配,当对象⼤于 TLAB 中的剩余内存或 TLAB 的内存已⽤尽时,再采⽤上述的 CAS 进⾏内存分配
初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。
设置对象头:虚拟机要对对象进⾏必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运⾏状态的不同,如是否启⽤偏向锁等,对象头会有不同的设置⽅式
执⾏ init ⽅法:从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从 Java 程序的视⻆来看,对象创建才刚开始, ⽅法还没有执⾏,所有的字段都还为零。所以⼀般来说,执⾏ new 指令之后会接着执⾏ ⽅法,把对象按照程序员的意愿进⾏初始化,这样⼀个真正可⽤的对象才算完全产⽣出来。

对象的访问定位有句柄和直接指针。

判断对象是否死亡。
引用计数法:给对象中添加⼀个引⽤计数器,每当有⼀个地⽅引⽤它,计数器就加1;当引⽤失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使⽤的。
可达性分析算法是通过⼀系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所⾛过的路径称为引⽤链,当⼀个对象到 GC Roots 没有任何引⽤链相连的话,则证明此对象是不可⽤的。

3-31记录

字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是⾮常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就⼲脆提供了⼀个直接操作字符的接⼝,⽅便我们平时对字符进⾏流操作。如果⾳频⽂件、图⽚等媒体⽂件⽤字节流⽐᫾好,如果涉及到字符的话使⽤字符流比较好。

对象的相等,⽐的是内存中存放的内容是否相等。⽽引⽤相等,⽐᫾的是他们指向的内存地址是
否相等。

对于不想进⾏序列化的变量,使⽤ transient 关键字修饰。transient 关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和⽅法。

BIO (Blocking I/O): 同步阻塞 I/O 模式
NIO (Non-blocking/New I/O): NIO 是⼀种同步⾮阻塞的 I/O 模型,对于低负载、低并发的应⽤程序,可以使⽤同步阻塞 I/O 来提升开发速率和更
好的维护性;对于⾼负载、⾼并发的(⽹络)应⽤,应使⽤ NIO 的⾮阻塞模式来开发
AIO (Asynchronous I/O): AIO 也就是 NIO 2。异步⾮阻塞的 IO 模型

RandomAccess 接⼝只是标识,没有实现,标识实现这个接⼝的类具有随机访问功能

Vector 类中所有的方法都是同步的,可以由两个线程安全的访问同一个Vector对象,但是一个线程访问Vector 的话就会在同步操作上耗费大量的时间。
ArrayList 不是同步的,所有在不需要保证线程安全时建议使用ArrayList 。所以使用ArrayList取代Vector

ArrayList的默认初始容量为10,当然也可以自定义指定初始容量,随着动态的向其中添加元素,其容量可能会动态的增加,那么扩容的公式为:
新容量 = 旧容量/2 + 旧容量
比如:初始容量为4,其容量的每次扩充后的新容量为:4->6->9->13->19->…
即每次扩充至原有基础的1.5倍。
Vector的初始大小为10,如果没有指定每次增长的大小,则默认是翻倍增长。

HashMap线程不安全,效率高,Hashtable线程安全但是效率低。如果要保证线程安全可以使用ConcurrentHashMap
HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有⼀个,null 作为值可以有多个;
HashTable 不允许有 null 键和 null 值,否则会抛出NullPointerException
HashMap的初始大小为16,增长时,会将其扩充为 2 的幂次⽅⼤⼩。
Hashtable默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1

HashSet是在HashMap的基础上实现的,HashMap存储键值对,使⽤键(Key)计算hashcode,HashSet存储对象。HashSet 使⽤成员对象来计算 hashcode 值,对于两个对象来说hashcode 可能相同,所以 equals() ⽅法⽤来判断对象的相等性当你把对象加⼊ HashSet 时, HashSet 会先计算对象的 hashcode 值来判断对象加⼊的位置,同时也会与其他加⼊的对象的 hashcode 值作⽐较,如果没有相符的 hashcode , HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调⽤ equals() ⽅法来检查hashcode 相等的对象是否真的相同。如果两者相同, HashSet 就不会让加⼊操作成功。
LinkedHashSet 是 HashSet 的⼦类,能够按照添加的顺序遍历;
TreeSet 底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排
序。

JDK1.8 之后在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。
若桶中链表元素个数大于等于8时,链表转换成树结构;若桶中链表元素个数小于等于6时,树结构还原成链表。因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

红⿊树就是为了解决⼆叉查找树的缺陷,因为⼆叉查找树在某些情况下会退化成⼀个线性结构。

HashMap的长度之所以是2的幂次方,是为了均匀分布,减少碰撞。
2的n次方实际就是1后面n个0,2的n次方-1 实际就是n个1;
例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了;
例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞;

ConcurrentHashMap 底层采⽤数组+链表/红⿊⼆叉树
到了 JDK1.8 的时候已经摒弃了 Segment 的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,Node 只能⽤于链表的情况,红⿊树的情况需要使⽤ TreeNode 。当冲突链表达到⼀定⻓度时,链表会转换成红⿊树。并发控制使⽤ synchronized 和 CAS 来操作

Hashtable 和JDK1.8 之前的 HashMap 的底层数据结构类似都是采⽤ 数组+链表 的形式,数组是HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的;Hashtable使用同一把锁。使⽤ synchronized 来保证线程安全,效率⾮常低下。当⼀个线程访问同步⽅法时,其
他线程也访问同步⽅法,可能会进⼊阻塞或轮询状态

进程和线程
⼀个进程中可以有多个线程,多个线程共享进程的堆和⽅法区 (JDK1.8 之后的元空间)资源,但是每个线程有⾃⼰的程序计数器、虚拟机栈 和 本地⽅法栈。
线程 是 进程 划分成的更⼩的运⾏单位。线程和进程最⼤的不同在于基本上各进程是独⽴
的,⽽各线程则不⼀定,因为同⼀进程中的线程极有可能会相互影响。线程执⾏开销⼩,但不利
于资源的管理和保护;⽽进程正相反。

通常情况下,我们创建的变量是可以被任何⼀个线程访问并修改的。如果想实现每⼀个线程都有
⾃⼰的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问题。
ThreadLocal 类主要解决的就是让每个线程绑定⾃⼰的值,可以将 ThreadLocal 类形象的⽐喻成
存放数据的盒⼦,盒⼦中可以存储每个线程的私有数据

使用线程池的好处
降低资源消耗。通过重复利⽤已创建的线程降低线程创建和销毁造成的消耗。
提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
提⾼线程的可管理性。线程是稀缺资源,如果⽆限制的创建,不仅会消耗系统资源,还会降
低系统的稳定性,使⽤线程池可以进⾏统⼀的分配,调优和监控。

Runnable接口和Callable接口的区别。
Runnable ⾃ Java 1.0 以来⼀直存在,但 Callable 仅在 Java 1.5 中引⼊,⽬的就是为了来处理 Runnable 不⽀持的⽤例。 Runnable 接⼝不会返回结果或抛出检查异常,但是 Callable 接⼝可以。所以,如果任务不需要返回结果或抛出异常推荐使⽤ Runnable 接⼝,这样代码看起来会更加简洁。

execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否。 submit() ⽅法⽤于提交需要返回值的任务。线程池会返回⼀个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执⾏成功,并且可以通过 Future 的 get() ⽅法来获取返回值, get() ⽅法会阻塞当前线程直到任务完成,⽽使⽤ getlong timeoutTimeUnitunit ⽅法则会阻塞当前线程⼀段时间后⽴即返回,这时候有可能任务没有执⾏完。

3-30记录

GC要做的是判断哪些内存需要回收,什么时候回收,怎么回收。这三个问题。
如何判断对象死亡,有引用计数法,循环引用的问题。还有根搜索算法,通过一系列称为GC Roots的点作为起点,向下搜索,当一个对象到任何GC Roots没有引用链相连,说明其已经死亡。
垃圾收集算法,标记清除,效率低,内部碎片多,分两个阶段,标记和清除。 复制算法,按内存容量划分为等大小的两块。每次只使用其中一块。 效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。标记整理算法。结合以上两个算法,标记后不是清理对象,而是将存活对象移到内存的一端。然后清除端边界外的对象。分代收集算法,分为老生代(每次只有少量需要回收)使用标记复制算法。新生代(每次垃圾回收都有大量垃圾需要被回收)使用复制算法。
不可达对象并不等于可回收对象。至少还要经过两次标记过程。
Serial 最基本的垃圾收集器,使用复制算法,单线程。
ParNew垃圾收集器(Serial+多线程) 在垃圾收集过程同样也要暂停所有其他的工作线程,多数Java虚拟机运行在Server模式下新生代的默认垃圾收集器。
Parallel Scavenge 收集器(多线程复制算法、高效)新生代垃圾收集器。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个
重要区别。
Serial Old 收集器(单线程标记整理算法 )Serial Old 是 Serial 垃圾收集器老年代版本

强引用:把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用引用时,它处于可达状态。不可能被垃圾回收机制回收。即使不用JVM也不会回收。Java内存泄漏主要原因之一。
虚引用:主要作用是跟踪对象被垃圾回收的状态。

重写发⽣在运⾏期,是⼦类对⽗类的允许访问的⽅法的实现过程进⾏重新编写。

  1. 返回值类型、⽅法名、参数列表必须相同,抛出的异常范围⼩于等于⽗类,访问修饰符范围
    ⼤于等于⽗类。
  2. 如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法,但是被 static 修饰
    的⽅法能够被再次声明。
  3. 构造⽅法⽆法被重写
    ⽅法的重写要遵循“两同两⼩⼀⼤”
    “两同”即⽅法名相同、形参列表相同;
    “两⼩”指的是⼦类⽅法返回值类型应⽐⽗类⽅法返回值类型更⼩或相等,⼦类⽅法声明抛出
    的异常类应⽐⽗类⽅法声明抛出的异常类更⼩或相等;
    “⼀⼤”指的是⼦类⽅法的访问权限应⽐⽗类⽅法的访问权限更⼤或相等。

重载发生在编译器而重写发生在运行期。

所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编
程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该
引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。
在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接⼝(实现接⼝并
覆盖接⼝中同⼀⽅法)。

Java自动拆装箱
装箱:将基本类型⽤它们对应的引⽤类型包装起来;
拆箱:将包装类型转换为基本数据类型;
装箱过程是通过调用包装器的valueOf方法实现的,
而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。

接⼝和抽象类的区别是什么?

  1. 接⼝的⽅法默认是 public ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现),
    ⽽抽象类可以有⾮抽象的⽅法。
  2. 接⼝中除了 static 、 final 变量,不能有其他变量,⽽抽象类中则不⼀定。
  3. ⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键
    字扩展多个接⼝。
  4. 接⼝⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰
    符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
  5. 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏
    为的规范。

hashcode与equals
如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals
⽅法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。因此,
equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。

Java的值传递
按值调⽤(call by value)表示⽅法接收的是调⽤者提供的值,⽽按引⽤调⽤(call by reference)表示⽅法
接收的是调⽤者提供的变量地址。⼀个⽅法可以修改传递引⽤所对应的变量值,⽽不能修改传递
值调⽤所对应的变量值。 它⽤来描述各种程序设计语⾔(不只是 Java)中⽅法参数传递⽅式。
Java 程序设计语⾔总是采⽤按值调⽤。也就是说,⽅法得到的是所有参数值的⼀个拷⻉,也就
是说,⽅法不能修改传递给它的任何参数变量的内容。

浅拷贝与深拷贝的区别
浅拷贝可以使用列表自带的copy()函数(如list.copy()),或者使用copy模块的copy()函数。深拷贝只能使用copy模块的deepcopy()。
如果拷贝的对象里的元素只有值,没有引用,那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开。如果拷贝的对象里的元素包含引用(像一个列表里储存着另一个列表,存的就是另一个列表的引用),那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象和原对象完全分离开并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分离开。浅拷贝复制引用也就是将同一个地址指向引用,而深拷贝则是在堆内存重新开辟空间,重新赋值。

程序是含有指令和数据的文件,被存储在磁盘中,程序是静态的代码。而进程则是程序的一次执行过程,也就是说进程是动态的。

final主要用在变量,方法,类中。
对于⼀个 final 变量,如果是基本数据类型的变量,则其数值⼀旦在初始化之后便不能更改;如果是引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象。
final 修饰类中的方法 说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。
当⽤ final 修饰⼀个类时,表明这个类不能被继承。final 类中的所有成员⽅法都会被隐式地指定为 final ⽅法。使⽤ final ⽅法的原因有两个。第⼀个原因是把⽅法锁定,以防任何继承类修改它的含义;第⼆个原因是效率。
类中所有的 private ⽅法都隐式地指定为 final。

3-29记录

Java虚拟机的内存分为,线程私有空间-虚拟机栈区,本地方法栈,程序计数器PC,共享区-方法区(永久代),类实例区(Java堆)。直接内存 不受JVm和GC管理。
方法区,即我们常说的永久代,用于存储被JVM加载的类的信息,常量,静态变量,即时编译器编译后的代码等数据。
Java堆从GC角度还可以分为,新生代(Eden区,From Survivor区和To Survivor区) 和老年代。新生代用来存放新生的对象,一般占据堆的1/3区域,由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。Eden区是Java新对象的出生地。要是新创建的对象占用内存较大,则直接分配到老年代。当Eden区内存不够就会触发MinorGC进行垃圾回收。From Survivor是上一次GC的幸存者,作为这一次GC的扫描者。To Survivor是保留了一次GC过程的幸存者。MinorGC采用复制算法,过程是,复制,清空,互换。老年代占用2/3的堆空间。主要存放应用程序中生命周期长的内存对象。老年代对象比较稳定。所以MajorGC不会频繁执行。采用标记清楚算法。扫描一遍所有老年代,标记出存活的对象,然后回收没有标记的对象。会产生内存碎片,为减少内存损耗,我们一般需要进行合并或者标记出来方便下次分配。永久代,指内存的永久保存区域。主要存放Class和Meta(元数据) 的信息。Class被加载的时候放入永久区域。和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以会导致OOM异常,即内存溢出异常。
Java8中,永久代被移出,被一个元空间代替,元空间并不在虚拟机中,而是使用本地内存,类的元数据放入native memory,字符串池和类的静态变量放入Java堆中。可以加载多少类的元数据不再由MaxPermSize控制。而由系统实际可用空间控制。

3-28记录

27号考公去了所以无更
@DateTimeFormat
@DateTimeFormat转换前端string类型到后端date类型,此字段通常加到属性上面
@JsonFormat转换后端date类型到前端string类型,若是只用到此注解,加到属性上或者方法上均可以;若是跟@DateTimeFormat配合使用,此注解添加到getter方法上面,注意加【timezone=“GMT+8”】后端

@BigDecimal 用来处理需要精确的小数计算。
一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。
BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

RedisTemplate集合BoundHashOps.values() 获取map中的值要求jdk1.8及以上
BoundHashOperationsHK 代表Hash中的key HV代表Hash中的value
泛型E,K,V本质上这些个都是通配符,没区别,只是编码时的一种约定的东西。比如代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样的:
? 表示不确定的 java 类型
T (type) 表示具体的一个java类型
K V (key value) 分别代表java键值中的Key Value
E (element) 代表Element

>>:带符号右移。正数右移高位补0,负数右移高位补1。比如:
4 >> 1,结果是2;-4 >> 1,结果是-2。-2 >> 1,结果是-1。
>>>:无符号右移。无论是正数还是负数,高位通通补0。
在Java中,String的getBytes()方法是得到一个操作系统默认的编码格式的字节数组

3-26记录

redis 可以作为数据库,缓存,消息代理。
使用redis的默认配置器动redis服务后,默认会存在16个库,编号从0-15
可以使用select 库的编号 来选择一个redis的库
清空当前的库 FLUSHDB
清空全部的库 FLUSHALL

KEYS * 匹配数据库中所有key 。
KEYS h?llo 匹配hello ,hallo 和hxllo 等。
KEYS h*llo 匹配hllo 和heeeeello 等。
incr和incrby区别是 incrby可以设置增量 而incr是增1

String类似的使用场景:value除了是我们的字符串还可以是我们的数字!
计数器,统计多单位的数量,粉丝数,对象缓存存储
List是可以放重复值的

主从复制
主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据
无法解决: 1.master节点出现故障的自动故障转移

redis哨兵模式
Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。简单的说哨兵就是带有自动故障转移功能的主从架构。

**

3-25 记录

**

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

@ResponseBody
@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据,需要注意的呢,在使用此注解之后不会再走试图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。

 @RequestMapping("/login")
  @ResponseBody
  public User login(User user){
    return user;
}

等同于下面代码

  @RequestMapping("/login")
  public void login(User user, HttpServletResponse response){
    response.getWriter.write(JSONObject.fromObject(user).toString());
}

@RestController注解标识的类,Spring会将其下的所有方法return的数据都转换成JSON格式且不会被Spring视图解析器扫描到,也就是此类下面的所有方法都不可能返回一个视图页面。且这个注解只能用在类上,不能用在方法体上。

@PathVariable可以用来映射URL中的占位符到目标方法的参数中

@RequestMapping("/testPathVariable/{id}")
    public String testPathVariable(@PathVariable("id") Integer id)
    {
        System.out.println("testPathVariable:"+id);
        return SUCCESS;
    }

序列化:序列化是将对象转换为容易传输的格式的过程。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。在另一端,反序列化将从该流重新构造对象。是对象永久化的一种机制。默认序列化是jdk序列化

在企业中 pojo类都会序列化序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

redis watch 可以用来作乐观锁 用两个线程去模拟乐观锁 认为任何情况下都不会出问题,所以不会上锁,更新数据时查询一下是否改变

@NotEmpty 用在集合类上面
加了@NotEmpty的String类、Collection、Map、数组,是不能为null或者长度为0的(String Collection Map的isEmpty()方法)
@NotBlank只用于String,不能为null且trim()之后size>0
@NotNull:不能为null,但可以为empty,没有Size的约束

你可能感兴趣的:(备战春招,java,spring,redis)