HashMap的key存对象的时候,需要注意什么

  • 如果自定义对象作为Map的键,那么必须重写hashCode和equals方法

  • 绕口令式hashCode与equals

    1. 如果两对象equals()是true,那么它们的hashCode()值一定相等
    2. 如果两对象的hashCode()值相等,它们的equals不一定相等(hash冲突啦)
  • 总结:

    ​ 1.hashCode()在散列表中才有用,在其他情况下没用

    ​ 2.哈希值冲突了场景,hashCode相等,但equals不等

    ​ 3.hashCode: 计算键的hashcode作为存储键信息的数组下标用于查找键对象的存储位置

    ​ 4.equals:HashMap使用equals()判断当前的键是否与表中存在的键相同。

java异常种类,具体怎么用项目里

  • 先定义一个异常码枚举处理

    public enum ErrorCodeEnum {
       SYS_ERROR("SYS_ERROR", "系统错误,请重试"),
       UNKNOWN_ERROR("UNKNOWN_SYS_ERROR", "未知的系统异常"),
       SERVICE_INVOKE_FAIL("SERVICE_INVOKE_FAIL", "服务调用失败"),
       ILLEGAL_ARGS("ILLEGAL_ARGS", "参数校验错误")
  • 枚举构造函数

    /**
        * 结果码值.
        */
       private String code;
       /**
        * 描述.
        */
       private String desc;
    
       ErrorCodeEnum(String code, String desc) {
           this.code = code;
           this.desc = desc;
       }
  • 一个通过code找枚举的方法

    public static ErrorCodeEnum getByValue(String code) {
           for (ErrorCodeEnum result : values()) {
               System.out.println(result.ordinal());
               if (StringUtils.equals(result.getCode(), code)) {
                   return result;
               }
           }
           return null;
       }

    自定义异常

    • 注意有个异常枚举

    • 继承了RuntimeException

      public  class TestException extends RuntimeException {
         private static final long serialVersionUID = -8581672033133636908L;
         /*** 错误码枚举*/
         private ErrorCodeEnum errorCode;

总结

  • 定义异常码类就可以,如果业务非常复杂可以把异常码类多搞几个出来
  • 项目中一个异常类够用了,通过不同异常码定义去区分业务异常
  • 强烈建议开发时多定义异常码,越细越好,上了生产方便快速定位BUG
  • 关键枚举异常码+自定义异常

接口联调问题案例

互联网项目接口联调非常重要,今天和这个系统对接明天又换了一家公司对接,联调的时候会遇到各种问题

  1. 编码问题gbk、utf-8 不说了最简单的也最容易乱码的问题
  2. 环境不通 ping 命令 curl 命令可以帮到你,联调前一代要检查下环境
  3. 数据格式转换异常,接口文档没看清楚给了错误的格式,粗心问题

Java设计模式思想(单例模式,工厂模式,策略模式)

单例模式:单例模式核心只需要new一个实例对象的模式,比如数据库连接,在线人数等,一些网站上看到的在线人数统计就是通过单例模式实现的,把一个计时器存放在数据库或者内存中,当有人登陆的时候取出来加一再放回去,有人退出登陆的时候取出来减一再放回去,但是当有两个人同时登陆的时候,会同时取出计数器,同时加一,同时放回去,这样的话数据就会错误,所以需要一个全局变量的对象给全部人使用,只需要new出一个实例对象,这就是单例模式的应用,并且单例模式节省资源,因为它控制了实例对象的个数,并有利于gc回收。

策略模式:就是将几个类中公共的方法提取到一个新的类中,从而使扩展更容易,保证代码的可移植性,可维护性强。比如有个需求是写鸭子对象,鸭子有叫,飞,外形这三种方法,如果每个鸭子类都写这三个方法会出现代码的冗余,这时候我们可以把鸭子中的叫,飞,外形这三个方法提取出来,放到鸭父类中,让每个鸭子都继承这个鸭父类,重写这三个方法,这样封装的代码可移植性强,当用户提出新的需求比如鸭子会游泳,那么对于我们oo程序员来讲就非常简单了我们只需要在鸭父类中加一个游泳的方法,让会游泳的鸭子重写游泳方法就可以了。

工厂模式:简单的工厂模式主要是统一提供实例对象的引用,通过工厂模式接口获取实例对象的引用。比如一个登陆功能,后端有三个类,controller类,interface类,实现接口的实现类。当客户端发出一个请求,当请求传到controller类中时,controller获取接口的引用对象,而实现接口的实现类中封装好了登陆的业务逻辑代码。当你需要加一个注册需求的时候只需要在接口类中加一个注册方法,实现类中实现方法,controller获取接口的引用对象即可,不需要改动原来的代码,这种做法是的可拓展性强。

SpringMVC主流程

1、用户发送请求至前端控制器DispatcherServlet。

2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。

3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、 DispatcherServlet调用HandlerAdapter处理器适配器。

5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

6、Controller执行完成返回ModelAndView。

7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

9、ViewReslover解析后返回具体View.

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、DispatcherServlet响应用户。

SpringAOP/IOC

1.理解“控制反转“

    控制反转,也称为依赖注入,是面向对象编程中的一种设计理念,用来降低程序代码之间的耦合度。

   程序的耦合:

        调用者与被调用者的依赖关系

   比如:程序员A负责编写A类,程序员B负责B类开发,A类的某些功能需要实例化B类的对象调用其方法才能完成,那么A类就依赖B类,如果程序员B不写B类 难道程序员A要一直等下去吗?

        企业开发的原则:

        "编译时不依赖,运行时才依赖"

理解“面向切面编程”

那么什么是面向切面编程呢?

比如在两个类中,可能都需要在每个方法中记录日志。按照面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。

也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

4种分布式session解决方案

直接将信息存储在cookie中
cookie是存储在客户端上的一小段数据,客户端通过http协议和服务器进行cookie交互,通常用来存储一些不敏感信息

缺点:

​ 数据存储在客户端,存在安全隐患
​ cookie存储大小、类型存在限制
​ 数据存储在cookie中,如果一次请求cookie过大,会给网络增加更大的开销

session复制

session复制是小型企业应用使用较多的一种服务器集群session管理机制,在真正的开发使用的并不是很多,通过对web服务器(例如Tomcat)进行搭建集群。

存在的问题:

session同步的原理是在同一个局域网里面通过发送广播来异步同步session的,一旦服务器多了,并发上来了,session需要同步的数据量就大了,需要将其他服务器上的session全部同步到本服务器上,会带来一定的网路开销,在用户量特别大的时候,会出现内存不足的情况
优点:

服务器之间的session信息都是同步的,任何一台服务器宕机的时候不会影响另外服务器中session的状态,配置相对简单
Tomcat内部已经支持分布式架构开发管理机制,可以对tomcat修改配置来支持session复制,在集群中的几台服务器之间同步session对象,使每台服务器上都保存了所有用户的session信息,这样任何一台本机宕机都不会导致session数据的丢失,而服务器使用session时,也只需要在本机获取即可

session绑定:

利用nginx的反向代理和负载均衡,之前是客户端会被分配到其中一台服务器进行处理,具体分配到哪台服务器进行处理还得看服务器的负载均衡算法(轮询、随机、ip-hash、权重等),但是我们可以基于nginx的ip-hash策略,可以对客户端和服务器进行绑定,同一个客户端就只能访问该服务器,无论客户端发送多少次请求都被同一个服务器处理缺点

  • 容易造成单点故障,如果有一台服务器宕机,那么该台服务器上的session信息将会丢失
  • 前端不能有负载均衡,如果有,session绑定将会出问题

优点

  • 配置简单

基于redis存储session方案

优点:

这是企业中使用的最多的一种方式
spring为我们封装好了spring-session,直接引入依赖即可
数据保存在redis中,无缝接入,不存在任何安全隐患
redis自身可做集群,搭建主从,同时方便管理
缺点:

多了一次网络调用,web容器需要向redis访问
总结:
一般会将web容器所在的服务器和redis所在的服务器放在同一个机房,减少网络开销,走内网进行连接

浅析 Comparable和 Comparator的区别

Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些 类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:

1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
2、比较者等于被比较者,那么返回0
3、比较者小于被比较者,那么返回负整数

Comparator可以认为是是一个外比较器,个人认为有两种情况可以使用实现Comparator接口的方式:
1、一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较。
2、一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式。
Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:

1、o1大于o2,返回正整数
2、o1等于o2,返回0
3、o1小于o3,返回负整数

这两种比较器Comparable和Comparator,后者相比前者有如下优点:

个性化比较:如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那么可以实现Comparator接口,自定义一个比较器,写比较算法。
解耦:实现Comparable接口的方式比实现Comparator接口的耦合性要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。从这个角度说,其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。

Collections 工具类常用方法:

排序操作

void reverse(List list)//反转

void shuffle(List list)//随机排序

void sort(List list)//按自然排序的升序排序

void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑

void swap(List list, int i , int j)//交换两个索引位置的元素

void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。

查找,替换操作

int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的

int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)

int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)

void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。

int frequency(Collection c, Object o)//统计元素出现次数

int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).

boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素

String,StringBuffer,StringBuilder的区别

下楼梯回答:分别从

线程安全

​ 执行效率

​ 存储空间

​ 使用场景

线程安全性:

​ 线程安全 String 、StringBuffer

​ 非线程安全 StringBuilder

执行效率:

StringBuilder > StringBuffer > String

存储空间:

String 的值是不可变的,每次对String的操作都会生成新的String对象,

效率低耗费大量内存空间,从而引起GC。

StringBuffer和StringBuilder都是可变。

使用场景:

1如果要操作少量的数据用 String

2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

HashMap的考察

下楼梯大法

存储结构

​ 默认容量

​ 装载因子

​ hashcode/equals

​ 1.7和1.8版本变化

1.内部存储结构:数组+链表+红黑树(JDK8)

2.默认容量16,默认装载因子0.75。

3.key和value对数据类型的要求都是泛型。

4.key可以为null,放在table[0]中。

5.hashcode:计算键的hashcode作为存储键信息的数组下标用于查找键对象的存储位置。equals:HashMap使用equals()判断当前的键是否与表中存在的键相同。

为什么要设计出迭代器

迭代器本质是一种设计模式,为了解决为不同的集合类提供统一的遍历操作接口。

java单机并发控制

1.同步方法synchronized

2.同步块synchronized

3 可重入锁 ReentrantLock ,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。

多线程创建的方式

1、继承Thread类创建线程

2、实现Runnable接口创建线程

3、实现Callable接口通过FutureTask包装器来创建Thread线程

4、使用ExecutorService、Callable、Future实现有返回结果的线程

一般说出这3种,并把区别说下即可

Thread

​ Runnable

​ Callable

(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

怎么防止前端重复提交?

  1. 提交按钮后屏蔽提交按钮(前端js控制)

  2. 前端生产唯一id,后端通过唯一索引(简单粗暴,我喜欢)

  3. 利用Session防止表单重复提交

    推荐 方法1+方案2 组合套餐万无一失

SpringAOP的原理

spring实现AOP的思路很简单,

  • 通过预编译方式和运行期动态代理方式实现程序功能的统一维护的一种技术

  • 主要功能:日志记录、性能统计、安全控制、事务处理、异常处理等等

  • AOP实现方式

    • 预编译:AspectJ
    • 运行期动态代理(JDK动态代理、CGLib动态代理):SpringAOP、JbossAOP
  • AOP几个相关概念

面向切面的核心思想就是,让核心的业务逻辑代码,不需要去管理一些通用的逻辑,比如说事务,安全等这方面的共同逻辑,解耦业务逻辑和通用逻辑

建议把 理论+实际应用方式 告诉面试官,虽然你可能没开发过但你一定要把核心概念思想表达出来

批量往mysql导入1000万数据有什么方法?

减少IO次数

​ SQL写法优化

​ 合理设置批量大小

​ 尽量顺序插入

  1. 一条SQL语句插入多条数据

  2. 在事务中进行插入处理,切记不要1条数据提交一下,肯定要分批处理

  3. 数据有序插入,是为了减少索引的维护压力

    批量插入SQL例子,Ibatis的小demo

 <!-- 批量插入流量模块 -->
    <insert id="XXX_表-BATCH-INSERT" parameterClass="java.util.List">
        INSERT  INTO
        XXX_表(gmt_create,gmt_modified,字段A,字段B,字段C)
        VALUES
        <iterate conjunction=",">
            <![CDATA[
         (NOW(),NOW(),#vals[].字段A#,#vals[].字段B#,#vals[].字段C#)
        ]]>
        </iterate>
    </insert>

事务


START TRANSACTION;  
  INSERT  INTO
        XXX_表(gmt_create,gmt_modified,字段A,字段B,字段C)
        VALUES (NOW(),NOW(),#vals[].字段A#,#字段B[].字段C#)
...  
COMMIT;

总结

  1. ​ 合并数据+事务+有序数据的优化插入方式
  2. ​ 注意SQL批量插入的大小必须合理
  3. ​ 事务执行时间不要太长了
  4. ​ 实际开发时需要合理设置MYSQL相应配置参数,增加缓存或减少不必要日志磁盘读写

项目中日志框架,日志具体怎么配置

Loggers 记录器 (日志文件基本配置)

|

Appenders 附加器 (日志文件去哪里)

​ |

​ Layouts 布局器(日志文件输出具体格式)

三大组件成就了log4j

这里可简单理解为日志类别日志要输出的地方日志以何种形式输出

如果是log4j.xml 核心标签

log4j

root

​ logger

​ level 日志级别

            appender  日志文件输出地

​ layout 输出具体格式

appender 一般一个日志文件就对应一个,依赖layout file、append、 encoding

logger 一般代码模块类路径一一对应,如 com.程序汪公司.系统.模块名 ,依赖level appenderlevel 日志级别

root 默认日志,依赖level appender

———-log4j面试场景回顾———-

面试官:程序汪日志级别怎么配置

汪:Loggers 组件 ……

面试官:程序汪日志输出位置怎么配置,日志文件是按天生成的还是什么规则

汪:Appenders 组件 ……DailyRollingFileAppender ……

面试官:程序汪日志的输出格式怎么配置

汪:Layouts 组件 ……PatternLayout ……

具体聊下配置信息吧

1、配置根Logger:
log4j.rootLogger = [ level ] , appenderName1附加器, appenderName2附加器, …

Loggers组件在此系统中被分为五个级别:DEBUG、INFO、WARN、ERROR和FATAL。这五个级别是有顺序的,DEBUG < INFO < WARN < ERROR < FATAL

企业项目生产环境一般都是设置INFO 级别

Appenders 附加器 (日志文件去哪里)

2、配置日志信息输出目的地(appender):

log4j.appender.appenderName = className

appenderName:自定义appderName,在log4j.rootLogger设置中使用;

className:可设值如下:

(1)org.apache.log4j.ConsoleAppender(控制台)

(2)org.apache.log4j.FileAppender(文件)

(3)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)

(4)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)

#按DatePattern输出到文件 
log4j.appender.logDailyFile = org.apache.log4j.DailyRollingFileAppender 
log4j.appender.logDailyFile.layout = org.apache.log4j.PatternLayout 
log4j.appender.logDailyFile.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ssS}][%l]%n%m%n 
log4j.appender.logDailyFile.Threshold = DEBUG 
log4j.appender.logDailyFile.ImmediateFlush = TRUE 
log4j.appender.logDailyFile.Append = TRUE 
log4j.appender.logDailyFile.File = ../Struts2/WebRoot/log/DailyFile/log4j_Struts 
log4j.appender.logDailyFile.DatePattern = '.'yyyy-MM-dd-HH-mm'.log' 
log4j.appender.logDailyFile.Encoding = UTF-8

3、配置日志信息的输出格式(Layout):

log4j.appender.appenderName.layout=className

className:可设值如下:

(org.apache.log4j.PatternLayout(可以灵活地指定布局模式,企业项目中一般都是自己定义格式)

PatternLayout选项:

ConversionPattern=%m%n:设定以怎样的格式显示消息。

配置文件

log4j.properties

log4j.xml (我公司就是配置这个模式)

log4j.xml格式

我把关键词提取出来

log4j

appender 输出配置

layout 布局


一个好奇的人