目录
10.collections.sort和arrays.sort
16.给你一个联合索引ABC,查询条件为A=1,B>2,C=3,问你这个索引有没有被用到?
1.hash冲突的解决方法
(1)链地址法(hashMap,hashset采用的方式)
相同的hash放入一个链表中。
(2)开放地址法(ThreadLocalMap采用的就是这种)
这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:
1、线性探测
dii=1,2,3,…,m-1
这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
2、二次探测
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。
3、di=伪随机数序列。
缺点:
① 处理溢出需另编程序。一般可另外设立一个溢出表,专门用来存放上述哈希表中放不下的记录。此溢出表最简单的结构是顺序表,查找方法可用顺序查找。
② 按上述算法建立起来的哈希表,删除工作非常困难。假如要从哈希表 HT 中删除一个记录,按理应将这个记录所在位置置为空,但我们不能这样做,而只能标上已被删除的标记,否则,将会影响以后的查找。
③ 线性探测法很容易产生堆聚现象。所谓堆聚现象,就是存入哈希表的记录在表中连成一片。按照线性探测法处理冲突,如果生成哈希地址的连续序列愈长 ( 即不同关键字值的哈希地址相邻在一起愈长 ) ,则当新的记录加入该表时,与这个序列发生冲突的可能性愈大。因此,哈希地址的较长连续序列比较短连续序列生长得快,这就意味着,一旦出现堆聚 ( 伴随着冲突 ) ,就将引起进一步的堆聚。
(3)再哈希法
种方法是同时构造多个不同的哈希函数,当发生冲突时,再计算hash,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。
(4)建立公共溢出区
将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
2.Http有哪些方法
https://blog.csdn.net/wuyoudeyuer/article/details/80509901
(1)GET,POST,PUT,DELETE的区别
HTTP中的GET,POST,PUT,DELETE就对应着对这个资源的查,改,增,删4个操作
https://blog.csdn.net/weixin_37509652/article/details/78542362
- 根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的。(等的意味着对同一URL的多个请求应该返回同样的结果)
- 根据HTTP规范,POST表示可能修改变服务器上的资源的请求。
3.java容器框架整体架构
Java的容器就是Set、Map和List
https://blog.csdn.net/u013277209/article/details/77773229
(1)List
- ArrayList
内部是使用数组实现的,换句话说,ArrayList封装了对内部数组的操作,比如向数组中添加、删除、插入新的元素或者数据的扩展和重定向。它能够自动扩展大小以适应存储元素的不断增加。它的底层是基于数组实现的,因此它具有数组的一些特点,例如查找修改快而插入删除慢。
- LinkedList
由双向链表实现的。
F表示头结点引用,L表示尾结点引用,链表的每个结点都有三个元素,分别是前继结点引用(P),结点元素的值(E),后继结点的引用(N)。
- Vector
实现了一个可增长的对象数组,内部是以动态数组的形式来存储数据的。随着元素的增加,内部机制可以自行扩充空间。
Vector的public方法都是用synchronized 来修饰的,因此是线程安全的。
增长机制:
vector 默认的扩容机制是按照容器现有容量的一倍进行增长。由于 Vector 容器分配的是一块连续的内存空间, 每次容器的增长并不是在原有连续的内容空间后进行简单的叠加, 而是重新申请一块更大的新内存, 并把现有容器中的元素逐个复制过去, 然后销毁原有内存。
(2)Set
- HashSet
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素.对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成.
- TreeSet
TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
(3)Map
hashMap的扩容是先增加再扩容,因为put不一定是增加一个节点,可能是修改。
4.IOC和AOP的实战
(1)IOC
通常在开发的时候,使用一个对象,是通过new关键字初始化的。这样就导致当前模块已经不是不觉种和new对象耦合了,二我们通常都是更高层次的抽象模块调用低层次的实现模块,这样就产生了模块依赖于具体的实现类。
例如:
业务层要调用Dao层,当然Dao层采用接口开发,这样一定程度上满足了松耦合,使业务层不依赖于具体的数据库Dao层。
但是,采用new的时候,就需要new特定的Dao实现类,这样就是导致了耦合。即使使用抽象工厂方法,如果出现数据库迁移时,还是需要修改Dao层的。
因此,提出了IOC,实现,业务层不再newDao实现类,而是由容器自动为我们业务层设置Dao的实现类,是通过反射来是实现的。
(2)AOP
Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持。 JDK动态代理只能为接口创建动态代理实例,而不能对类创建动态代理。需要获得被目标类的接口信息(应用Java的反射技术),生成一个实现了代理接口的动态代理类(字节码),再通过反射机制获得动态代理类的构造函数,利用构造函数生成动态代理类的实例对象,在调用具体方法前调用invokeHandler方法来处理。 CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类。 但是Spring AOP基于注解配置的情况下,需要依赖于AspectJ包的标准注解,但是不需要额外的编译以及AspectJ的织入器,而基于XML配置不需要。
5.session和cookie有什么联系和区别
https://blog.csdn.net/liyifan687/article/details/80077928
HTTP是一种无状态的协议,为了分辨链接是谁发起的,需自己去解决这个问题。不然有些情况下即使是同一个网站每打开一个页面也都要登录一下。而Session和Cookie就是为解决这个问题而提出来的两个机制。
(1)应用场景
- 登录网站,今输入用户名密码登录了,第二天再打开很多情况下就直接打开了。这个时候用到的一个机制就是cookie。
- session一个场景是购物车,添加了商品之后客户端处可以知道添加了哪些商品,而服务器端如何判别呢,所以也需要存储一些信息就用到了session。
(2)介绍
- Cookie
通俗讲,是访问某些网站后在本地存储的一些网站相关信息,下次访问时减少一些步骤。更准确的说法是:Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一服务器,是在客户端保持状态的方案。Cookie的主要内容包括:名字,值,过期时间,路径和域。使用Fiddler抓包就可以看见,比方说我们打开百度的某个网站可以看到Headers包括Cookie。key-value存储
- Session
存在服务器的一种用来存放用户数据的类HashTable结构。
浏览器第一次发送请求时,服务器自动生成了一HashTable和一Session ID来唯一标识这个HashTable,并将其通过响应发送到浏览器。浏览器第二次发送请求会将前一次服务器响应中的Session ID放在请求中一并发送到服务器上,服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的HashTable。
一般这个值会有个时间限制,超时后毁掉这个值,默认30分钟。
当用户在应用程序的 Web页间跳转时,存储在 Session 对象中的变量不会丢失而是在整个用户会话中一直存在下去。
Session的实现方式和Cookie有一定关系。建立一个连接就生成一个session id,打开几个页面就好几个了,这里就用到了Cookie,把session id存在Cookie中,每次访问的时候将Session id带过去就可以识别了。
(3)区别
- 存储数据量方面:session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象
- 一个在客户端一个在服务端。因Cookie在客户端所以可以编辑伪造,不是十分安全。
- Session过多时会消耗服务器资源,大型网站会有专门Session服务器,Cookie存在客户端没问题。
- 域的支持范围不一样,比方说a.com的Cookie在a.com下都能用,而www.a.com的Session在api.a.com下都不能用,解决这个问题的办法是JSONP或者跨域资源共享。
6.Redis缓存和数据库的数据一致性
https://blog.csdn.net/qq_27384769/article/details/79499373
(1)需求起因
修改数据时,是怎么保证数据库于redis中数据一致
- 如果先改数据库,再淘汰缓存中的数据可能存在缓存失败,缓存中就是旧数据,数据不一致
- 如果先淘汰缓存,再修改数据库,但数据库修改失败了,就会导致数据库是旧数据,缓存无数据,都会数据不一致。
尽管两种修改数据的方式都会引起数据库不一致,但是对于第二种,先清除缓存,即使数据库修改失败,当缓存中没有数据时,再次更新缓存的话,还是能保证数据一致。
因此写数据是:采用先删除缓存数据,再修改数据库。
读数据是:先读缓存,如果缓存中有,直接读,如果没有,就读数据库,然后再存在缓存中。
(2)数据库不一致原因
在分布式的情况下,多个线程共同读写一个数据的话,在数据库层面并发的读写并不能保证完成顺序,也就是说后发出的读请求很可能先完成(读出脏数据)
a)发生了写请求A,A的第一步淘汰了cache(如上图中的1
b)A的第二步写数据库,发出修改请求(如上图中的2)
c)发生了读请求B,B的第一步读取cache,发现cache中是空的(如上图中的步骤3)
d)B的第二步读取数据库,发出读取请求,此时A的第二步写数据还没完成,读出了一个脏数据放入cache(如上图中的步骤4)
(3)解决思路
1)service的上游是多个业务应用,上游发起请求对同一个数据并发的进行读写操作,上例中并发进行了一个uid=1的余额修改(写)操作与uid=1的余额查询(读)操作
2)service的下游是数据库DB,假设只读写一个DB
3)中间是服务层service,它又分为了这么几个部分
3.1)最上层是任务队列
3.2)中间是工作线程,每个工作线程完成实际的工作任务,典型的工作任务是通过数据库连接池读写数据库
3.3)最下层是数据库连接池,所有的SQL语句都是通过数据库连接池发往数据库去执行的
- 第一种方案:采用延时双删策略
步骤:
1)先删除缓存
2)再写数据库
3)休眠500毫秒
4)再次删除缓存
缺点:
结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。
- 第二种方案:异步更新缓存(基于订阅binlog的同步机制)
思路:
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis
1)读Redis:热数据基本都在Redis
2)写MySQL:增删改都是操作MySQL
3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis
读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
当有数据更新请求时,先把它丢到队列里去,当更新完后在从队列里去除,如果在更新的过程中,遇到以上场景,先去缓存里看下有没有数据,如果没有,可以先去队列里看是否有相同商品ID在做更新,如果有也把查询的请求发送到队列里去,然后同步等待缓存更新完成。
7.MySQL外键删除策略
【1】show create table 数据表名;
【2】找到CONSTRAINT `外键名` FOREIGN KEY (`xxxx`) REFERENCES `xxxxxx` (`id`)
【3】alter table drop foreign key 外键名;
8.数据库分区分表方案
https://www.cnblogs.com/3gods/p/7406207.html
https://www.cnblogs.com/phpper/p/6937896.html
https://blog.csdn.net/KingCat666/article/details/78324678
(1)为什么要分表和分区?
日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率。
分表从表面意思说就是把一张表分成多个小表,分区则是把一张表的数据分成N多个区块,但还属于一个大表,这些区块可以在同一个磁盘上,也可以在不同的磁盘上。
分表和分区的区别
1,实现方式上
mysql的分表是真正的分表,一张表分成很多表后,每一个小表都是完正的一张表,都对应三个文件(MyISAM引擎:一个.MYD数据文件,.MYI索引文件,.frm表结构文件)。
2,数据处理上
分表后数据都是存放在分表里,总表只是一个外壳,存取数据发生在一个一个的分表里面。分区则不存在分表的概念,分区只不过把存放数据的文件分成了许多小块,分区后的表还是一张表,数据处理还是由自己来完成。
3,提高性能上
分表后,单表的并发能力提高了,磁盘I/O性能也提高了。分区突破了磁盘I/O瓶颈,想提高磁盘的读写能力,来增加mysql性能。
在这一点上,分区和分表的测重点不同,分表重点是存取数据时,如何提高mysql并发能力上;而分区呢,如何突破磁盘的读写能力,从而达到提高mysql性能的目的。
4,实现的难易度上
分表的方法有很多,用merge来分表,是最简单的一种方式。这种方式和分区难易度差不多,并且对程序代码来说可以做到透明的。如果是用其他分表方式就比分区麻烦了。 分区实现是比较简单的,建立分区表,跟建平常的表没什么区别,并且对代码端来说是透明的
(2)什么是分表?
分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,我们可以称为子表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些子表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的子表名,然后去操作它。
(3)什么是分区?
分区和分表相似,都是按照规则分解表。不同在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,可以是同一块磁盘也可以在不同的机器。分区后,表面上还是一张表,但数据散列到多个位置了。app读写的时候操作的还是大表名字,db自动去组织分区的数据。
(4)mysql分表和分区有什么联系呢?
1.都能提高mysql的性高,在高并发状态下都有一个良好的表现。
2.分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,我们可以采取分表和分区结合的方式(如果merge这种分表方式,不能和分区配合的话,可以用其他的分表试),访问量不大,但是表数据很多的表,我们可以采取分区的方式等。
3.分表技术是比较麻烦的,需要手动去创建子表,app服务端读写时候需要计算子表名。采用merge好一些,但也要创建子表和配置子表间的union关系。
4.表分区相对于分表,操作方便,不需要创建子表。
(5)介绍
1.分表
在分表之前,首先要选中合适的分表策略(以哪个字典为分表字段,需要将数据分为多少张表),使数据能够均衡的分布在多张表中,并且不影响正常的查询。在企业级应用中,往往使用org_id(组织主键)做为分表字段,在互联网应用中往往是userid。在确定分表策略后,当数据进行存储及查询时,需要确定到哪张表里去查找数据,
数据存放的数据表 = 分表字段的内容 % 分表数量
2.分库
分表能够解决单表数据量过大带来的查询效率下降的问题,但是不能给数据库的并发访问带来质的提升,面对高并发的写访问,当Master无法承担高并发的写入请求时,不管如何扩展Slave服务器,都没有意义了。我们通过对数据库进行拆分,来提高数据库的写入能力,即所谓的分库。分库采用对关键字取模的方式,对数据库进行路由。
数据存放的数据库=分库字段的内容%数据库的数量
3.即分表又分库
数据库分表可以解决单表海量数据的查询性能问题,分库可以解决单台数据库的并发访问压力问题
当数据库同时面临海量数据存储和高并发访问的时候,需要同时采取分表和分库策略。一般分表分库策略如下:
中间变量 = 关键字%(数据库数量*单库数据表数量)
库 = 取整(中间变量/单库数据表数量)
表 = (中间变量%单库数据表数量)
(5)分表方式
1、mysql集群
事实它并不是分表,但起到了和分表相同的作用。集群可分担数据库的操作次数,将任务分担到多台数据库上。集群可以读写分离,减少读写压力。从而提升数据库性能。
2、自定义规则分表
大表可以按照业务的规则来分解为多个子表。通常为以下几种类型,也可自己定义规则。
1 Range(范围)–这种模式允许将数据划分不同范围。例如可以将一个表通过年份划分成若干个分区。
2 Hash(哈希)–这种模式允许通过对表的一个或多个列的Hash Key进行计算,最后通过这个Hash码不同数值对应的数据区域进行分区。例如可以建立一个对表主键进行分区的表。
3 Key(键值)-上面Hash模式的一种延伸,这里的Hash Key是MySQL系统产生的。
4 List(预定义列表)–这种模式允许系统通过预定义的列表的值来对数据进行分割。
5 composite(复合模式) –以上模式的组合使用
3、利用merge存储引擎来实现分表
我觉得这种方法比较适合,那些没有事先考虑,而已经出现了得,数据查询慢的情况。这个时候如果要把已有的大数据量表分开比较痛苦,最痛苦的事就是改代码,因为程序里面的sql语句已经写好了,现在一张表要分成几十张表,甚至上百张表,这样sql语句是不是要重写呢。
ALTER TABLE user_total ENGINE=MRG_MYISAM UNION=(user1,user2) INSERT_METHOD=LAST;
9.set去重对象
使用hashSet,添加User类,User类包含用户名和年龄两个字段,定义用户名和年龄相同的对象为相同对象,hashSet中只添加一个。
HashSet原理
因此如果比较两个对象是否相同,就需要重写对象的hashCode和equals方法。
User类:重写hashCode和equals方法
添加对象
输出结果
10.collections.sort和arrays.sort
https://blog.csdn.net/qq_41653753/article/details/91472203(之前整理过,细节见11)
Arrays.sort和Collections.sort的实现原理:collections.sort方法底层就是调用的Array.sort方法
这里重点说一下TimeSort排序方法
Timsort的核心思路
TimSort 算法为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果。
综上述过程,Timsort算法的过程包括
(0)如何数组长度小于某个值,直接用二分插入排序算法
(1)找到各个run,并入栈
(2)按规则合并run
举个栗子:1,2,3,6,4,5,8,6,4
(1)分区:分区的思想是扫描一次数组,把连续正序列(如果是升序排序,那么正序列就是升序序列)(也就是后面所指的run),如果是反序列,把分区里的元素反转一下。划分分区结果为
[1,2,3,6],[4,5,8],[6,4]
然后反转反序列
[1,2,3,6],[4,5,8],[4,6]
(2)合并:考虑一个极端的例子,比如分区的长度分别为 10000,10,1000,10,10,我们当然希望是先让10个10合并成20, 20和1000合并成1020如此下去, 如果从从左往右顺序合并的话,每次都用到10000这个数组和去小的数组合并,代价太大了。所以我们可以用一个策略来优化合并的顺序。
https://blog.csdn.net/chunshucai/article/details/42030037
11.说一下mybatis俩种传参机制
(1)${ }
将传入的数据直接显示生成在sql中。仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。
eg:select id,name,age from student where id =${id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1.
(2)#{ }
传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。
eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id ='1'.
在大多数情况下还是经常使用#,但在不同情况下必须使用$.
使用#可以很大程度上防止sql注入。(语句的拼接),但是如果使用在order by 中就需要使用 $
12.用一个数组模拟栈
public class test {
public static void main(String[] args) {
Stack s=new Stack(3);
s.put(33);
s.put(34);
s.put(35);
s.put(36);
s.peek();
s.push();
s.push();
s.push();
s.push();
}
}
class Stack {
private int index=0;
private int length;
private int []stack;
Stack(int length){
this.length=length;
this.stack=new int[length];
}
//入栈
public void put(int i) {
if(index<length) {
stack[index]=i;
index++;
System.out.println(i+"入栈");
}else {
System.out.println("栈满,"+i+"未能入栈");
}
}
//出栈
public int push() {
if(index>0) {
int i=stack[index-1];
index--;
System.out.println(i+"出栈");
return i;
}else {
System.out.println("栈空,不能取");
return -1;
}
}
//取栈顶
public int peek() {
if(index>0) {
int i=stack[index-1];
System.out.println(i+"为栈顶");
return i;
}else {
System.out.println("栈空,不能取");
return -1;
}
}
}
13.Java异常
(1)异常架构图
1. Throwable
Throwable是 Java 语言中所有错误或异常的超类。
Throwable包含两个子类: Error 和 Exception。它们通常用于指示发生了异常情况。
Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。
2. Exception
Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
3. RuntimeException
RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
编译器不会检查RuntimeException异常。例如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既"没有通过throws声明抛出ArithmeticException异常",也"没有通过try...catch...处理该异常",也能通过编译。这就是我们所说的"编译器不会检查RuntimeException异常"!
如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
4. Error
和Exception一样,Error也是Throwable的子类。它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。
和RuntimeException一样,编译器也不会检查Error。
(3)种类
(01) 运行时异常
定义: RuntimeException及其子类都被称为运行时异常。
特点: Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。
虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
(02) 被检查的异常
定义: Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
特点: Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。
被检查异常通常都是可以恢复的。
(03) 错误
定义: Error类及其子类。
特点: 和运行时异常一样,编译器也不会对错误进行检查。
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。
按照Java惯例,我们是不应该是实现任何新的Error子类的!
14.线程join方法
(1)作用
Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
public class JoinTest {
public static void main(String [] args) throws InterruptedException {
ThreadJoinTest t1 = new ThreadJoinTest("小明");
ThreadJoinTest t2 = new ThreadJoinTest("小东");
t1.start();
/**join方法可以传递参数,join(10)表示main线程会等待t1线程10毫秒,10毫秒过去后,
* main线程和t1线程之间执行顺序由串行执行变为普通的并行执行
*/
t1.join(10);
t2.start();
}
}
class ThreadJoinTest extends Thread{
public ThreadJoinTest(String name){
super(name);
}
@Override
public void run(){
for(int i=0;i<1000;i++){
System.out.println(this.getName() + ":" + i);
}
}
}
(2)join与start调用顺序问题
join要写在start后面,如果下面这样写,就不起作用,t1与t2会交替执行。
t1.join();
t1.start();
t2.start();
(3)join方法实现原理
https://blog.csdn.net/chenkaibsw/article/details/80912878
其实,join方法是通过调用线程的wait方法来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可以看出,join方法实际上是通过调用wait方法, 来实现同步的效果的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,因为它相当于放弃了CPU的使用权。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。
分析:
isAlive()方法下面会做详细的介绍,先看wait(0),wait(0)是什么意思呢,查看下面wait()方法源码,其实wait()方法就是调用了wait(0)方法实现的,wait(0)就是让其一直等待。到这里会发现,其实join方法本质就是利用上面的线程实例作为对象锁的原理,当线程终止时,会调用线程自身的notifyAll()方法,通知所有等待在该线程对象上的线程的特征。
为什么要isAlive()方法:举例
public class HighConcurrency {
// 1.现在有T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t1线程,等待t1线程执行完
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t2线程,等待t2线程执行完
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t3.start();//这里三个线程的启动顺序可以任意,大家可以试下!
t2.start();
}
}
首先执行了t3.start(),在t3线程里的run方法里执行了t2.join(),此时有两种情况,可能还没有执行t2.start(),t2处于初始状态,也有可能执行了t2.start(),t2处于运行时状态,所以看到这里就明白了,join源码中while(isAlive()),其实相当于while(this.isAlive())就相当于判断这里的t2是不是已经是不是运行状态(有没有调用start方法)。这么设计是为了防止t2线程没有运行,此时t3线程如果直接执行wait(0)方法,那么将会一直等待下去,造成代码卡死。
15.最短路径问题
(1)Dijkstra(迪杰斯特拉)算法
(2)Floyd(弗洛伊德)算法
16.给你一个联合索引ABC,查询条件为A=1,B>2,C=3,问你这个索引有没有被用到?
没用到,是因为最左匹配原则当遇到>,<号时,符号后面的就不能匹配了,因此c匹配不上,
如果是
A>2 可以
A=2,B>2可以
A=2,B=3,C>4 可以
17.获取shell脚本参数
18.广义表
广义表(a,(a,b),d,e,((i,j),k))的长度是( ),深度是( )
其长度为5、深度为3、为什么呢?
长度的求法为最大括号中的逗号数加1,
深度的求法为上面每个元素的括号匹配数加1的最大值,
19.滑动窗口协议
滑动窗口协议有
1、停止等待协议,发送窗口=1,接受窗口=1;
2、退后N帧协议,发送>1,接收=1;
3、选择重传协议,发送>1,接收>1;