我正在参加「掘金·启航计划」
1. IllegalArgumentException:非法参数异常
这个异常表示传入的参数不合法。
开发中经常遇见要对用户传过来的值进行判断,然后确定是否可以执行操作,就可以在参数不合法的时候抛出这个异常,然后在controller层捕获,进行对应的返回。当然也可以使用自定义异常。
2. sql:对于NULL值,不能使用=‘null’,而要使用IS null
写错了一时还反应不过来,找了好大一会错误...
3. java中写文件分隔符,一定要使用File.separator,这是跨平台的
windos向右 \ ,linux向左/
4. Variable ‘xxx‘ is accessed from within inner class, needs to be declared final
变量在内部类中访问,那么他必须是final的。至于为什么这么设计,我现在懒得想---
5. SpringBoot,加载jar包外配置文件
更新项目,有个上传文件大小的设置,改动了配置文件。改动之后上传大文件,一直不成功,java程序报错文件太大。
后来经过研究发现,jar包的同级目录下有一个配置文件,优先加载了jar包外的配置文件,而jar包外配置文件还是默认10M。
在此总结一下,springboot加载配置文件优先级为:
- 项目jar包同级下的config文件夹是优先级最高的。(在jar包的同一目录下建config文件夹,把配置文件放到config文件夹下)
- 项目jar包同级下直接放配置文件是第二优先级。
- 项目内部的classpath同级config文件夹是第三优先级。
- 项目内部的classpath同级放配置文件是第四优先级。
也就是说在项目内src/main/resources 文件夹下创建的application.properties 配置文件的优先级是最低的,这也就是为什么我一直加载不到正确配置文件的原因!
那么yml和properties优先级呢?
当三种文件路径相同时,三个文件中的配置信息都会生效,但是当三个文件中有配置信息冲突时,加载顺序是yml>yaml>properties。也就是说一旦出现相同配置,最后生效的是properties中的配置。改端口实测,确实是这样。
6. mysql in走不走索引
IN通常是走索引的,当IN后面的数据在数据表中超过30%的匹配时是全表扫描,不走索引,因此IN走不走索引和后面的数据量有关系。
in(子查询)不走索引可以通过join优化。
7. list转set集合
Set<String> set = list.stream() .collect(Collectors.groupingBy(user::getId)) .keySet();
8. mysql now()效率
localtime调用会去访问本地文件,引起底层库级别的锁冲突(类似bug#72701),对于高性能服务器,应该尽量避免调用localtime函数。
还有就是用日期函数获取时间,不走索引,解决这个可以先在sql里定义一个变量,得到时间。然后>time或者其他什么的,这就走索引。
总之,为了避免不必要的麻烦,干脆不用mysql函数,降低出错的可能性
9. 写sql的时候,一定要注意条件类型对不对,比如数据库是varchar,我们就应该传‘123’,而不是123。传后者不走索引,in()同理。
10. in和exists
in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询,一直以来认为exists比in效率高的说法是不准确的。如果查询的两个表大小相当,那么用in和exists差别不大;
如果两个表中一个较小一个较大,则子查询表大的用exists,子查询表小的用in;(这个没试过)
11. 不要select * 不要select*
在百万数据量以上,速度可能会有数量级的差异。
12. 小表驱动大表原则
在网上看到mysql:
SELECT * FROM 小表 INNER JOIN 大表 ON 小表.id=大表.id
效率高于
SELECT * FROM 大表 INNER JOIN 小表 ON 小表.id=大表.id
自己实际测测就知道了,只要有一个表建立了索引就会使用那个表的索引,如果两个表都有的话mysql会自动采用小表驱动大表
left join 和 right join的话,确实是小表驱动大表。left,左边是驱动表;right,右边是驱动表。这个时候必须小表驱动大表,然后大表建立索引。
因为join肯定是有一个表需要进行全表扫描,所以要选择小的全表扫描。我们还需要建立索引,否则大的表依旧是全表扫描,没有意义。所以一定要在大表对应的字段上建立索引。
至于将前表数据放到joinbuffer会产生io,选择小表比较好,我觉得倒是非常次要的原因。和两个表都全表扫描相比,这点io算不了什么。
13. mybatis的ExecutorType
执行sql有三种执行模式,三种模式分别对应着三种执行器。分别为
- SIMPLE SimpleExecutor 每次执行都会创建一个statement,用完后关闭
- REUSE ReuseExecutor ReuseExecutor不会关闭statement,而是把statement放到缓存中。缓存的key为sql语句,value即为对应的statement。也就是说不会每一次调用都去创建一个 Statement 对象,而是会重复利用以前创建好的(如果SQL相同的话),这也就是在很多数据连接池库中常见的 PSCache 概念 。
- BATCH BatchExecutor 批处理, 事务是没法自动提交的。因为 BatchExecutor 只有在调用了 SqlSession 的 commit 方法的时候,它才会去执行 executeBatch 方法。
springboot项目配置这些模式:
mybatis: executor-type: batch
14. submit与execute区别
submit不管是Runnable还是Callable类型的任务都可以接受,有返回值。但是Runnable返回值均为void,所以使用Future的get()获得的还是null。
execute只能接受Runnable类型的任务
异常:
1. execute中的是Runnable接口的实现,所以只能使用try、catch来捕获CheckedException,通过实现UncaughtExceptionHande接口处理UncheckedException
2.submit中抛出异常,不管提交的是Runnable还是Callable类型的任务,如果不对返回值Future调用get()方法,都会吃掉异常
15. 多线程传集合进去,出现CurrentModifiedException
原因:ArrayList.Sublist只是一个视图,详情看阿里巴巴开发规范
直接把sublist得到的结果放到多线程执行了,直接拉垮!!
16. sqlSession线程不安全
每一个线程都应该有自己的sqlSession对象,该对象不能共享,因为sqlSession线程不安全。绝不能多个线程共享一个,例如放到静态字段中
两个方面:1. 由于JDBC的Connection对象本身是线程不安全的,而session对象中包含了connection对象,所以线程不安全。
2. 由于一级缓存是session级别的,所以如果多个线程同时使用session,当线程A进行插入操作的过程,线程B进行查询并缓存了数据,就会出现一级缓存与数据库数据不一致的问题。
17. 手动搞出来的mybatis的sqlsession,不会自动提交,需要我们手动commit!!!
18. CountDownLatch 和shutdown + isTerminated和future.get
CountDownLatch更加具有实用价值,因为不需要关闭线程池。
shutdown + isTerminated就很蠢,需要关闭线程池。
执行Callable任务可拿到一个Future对象, Future表示异步计算的结果。future.get也可以获得线程的执行结果。future.isdone可以判断是否完成。
CountDownLatch中定义了:private volatile int state; volatile:保证可见性。
19. submit提交线程,真正的执行顺序是怎么样的?
假设核心线程5个,最大线程10个,工作队列长度5.
前五个都会创建线程执行,此时核心线程达到5,达到5之后不再创建新的线程,而是放入工作队列。直到工作队列达到上限5,新进来的任务就会创建新的线程(所以说,并不是先提交就先执行)。直达线程数达到十个,这时候如果队列也满了,还有线程进来,就会执行拒绝策略。
太蠢了,上次设置核心线程1,最大处理器数量*2,队列10000.怪不得那么慢。。。。。
有个疑问,为什么一定要等到队列满了,才创建新的线程??设置一个百分比不更好吗,比如按照队列容量百分比加载线程。
线程池参数:
- int corePoolSize:线程池核心线程数量
- int maximumPoolSize: 线程池最大线程数量
- long keepAliveTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
- TimeUnit unit:存活时间的单位(时分秒等等...)
- BlockingQueue workQueue:存放任务的队列
- RejectedExecutionHandler handler:超出线程范围和队列容量的任务的处理策略
线程池执行顺序:workCount = 正在工作的线程数
首先检测线程池运行状态,如果不是running,则直接拒绝。 如果workCount < corePoolSize,则创建并启动一个线程线程来执行提交任务。 如果workCount >= corePoolSize,且线程池阻塞队列未满,则将任务添加到阻塞队列中。 如果workCount >= corePoolSize && workCount < maximumPoolSize,并且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。 如果workCount >= maximumPoolSize ,并且阻塞队列已满,则根据拒绝策略来处理改任务,默认是直接抛出异常
20. 以太坊为什么需要gas这个概念?
世界计算机 图灵机 停机问题 gas
以太坊是世界计算机,并且是图灵完备的,那么图灵机的停机问题以太坊一样会有。所以让程序运行消耗gas,没有gas就运行不了,避开停机问题。
21. 前端界面,程序执行进度展示
可以借助Redis实现,执行完一个阶段,就往Redis放一条记录,例如(id,百分比),然后前端根据id拿到百分比渲染就ok。
虽然看起来和实际做起来都很捞,但,又不是不能用
22. Response Headers,Connection:keep-alive和close的区别
偶然发现项目中这个属性是close,使用短链接。去线上看发现是长连接,但开发环境是短链接。前端会这样我也不知道怎么回事。
短连接:所谓短连接,就是每次请求一个资源就建立连接,请求完成后连接立马关闭。每次请求都经过“创建tcp连接->请求资源->响应资源->释放连接”这样的过程
长连接:所谓长连接(persistent connection),就是只建立一次连接,多次资源请求都复用该连接,完成后关闭。要请求一个页面上的十张图,只需要建立一次tcp连接,然后依次请求十张图,等待资源响应,释放连接。
23. HashMap新建和赋值
new HashMap<Integer, String>() {{ put("0","成功");}};
调用put方法。如果已经存在一个相同key,则返回key对应的旧value。如果不存在这个key,返回null
因为put()有返回值,所以不能new HashMap().put()。所以可以采用匿名内部类实现,赋值少的话比较简洁
24. 关于final
如果将一个成员变量设置为final,那么一定要直接初始化他或在静态代码块初始化。
当然,使用构造方法初始化也可以,但是必须所有构造方法都初始化这个final修饰的东西,因为final必须赋值且仅能赋值一次。
25. 为什么String作为参数,传入方法。在方法中改变String的值不会影响到原来的String?
因为String是final修饰的,不可变,在方法中看似重新赋值了,其实是创建了一个新的String,并没有改变原来的。
在原来的地方,还是指向最开始的String。