话术汇总

  1. 存储过程

存储过程(procedure)是为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译。存储过程里边可以定义变量、写if判断、写循环。他不能return返回,但是可以通过参数返回,有三种参数(in输入、our输出 还有 inout 即可输入又可输出。因为存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般SQL语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。当对数据库进行复杂操作时(如对多个表进行Update,Insert,Query,Delete时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。.存储过程可以重复使用,可减少数据库开发人员的工作量。安全性高,可设定只有某此用户才具有对指定存储过程的使用权。

  1. 当然他也有缺点,最大的缺点就是可移植性查,另外学习起来也有一定的难度。还有就是采用存储过程后也就意味着你的系统有一些业务逻辑不是在应用程序里处理,这种架构会增加一些系统维护和调试成本。所以,个人认为,普通业务逻辑尽量不要使用存储过程,定时性的ETL任务或报表统计函数可以根据团队资源情况采用存储过程处理。存储过程可以快速解决问题,但是移植性、维护性、扩展性不好,它有时会约束软件的架构,约速程序员的思维,在你的系统没有性能问题时不建议用存储过程。如果你要完成的功能只是一次或有限次的工作,如数据订正、数据迁移等等,存储过程也可以拿上用场。如果你的系统很小,并且有50%的开发人员熟练掌握PL/SQL,人员结构稳定,那存储过程可以减少很多代码量,并且性能不错。当系统变复杂了,开发人员多了,存储过程的弊端就会呈现。mysql存储过程用call调用,Oracle用excecute调用;mysql写存储过程,一般先要用delimiter $$,设置 $$为命令终止符号,Java基础篇
    1. Spring的IOC和AOP

spring的核心是一个beanFactory,基于工厂模式实现。当项目启动时,首先加载web.xml;在web.xml中加载spring的配置文件,就会对工厂进行初始化。创建对象;这样我们在使用时,就可以把创建好的对象之间注入到需要用到的类中。bean创建对象默认的是单例模式;这个就称为控制反转依赖注入;注入的方式有3中:set注入、构造器注入、注解式注入;set注入又分为byType和ByName;我们现在一般都使用注解的方式实现;实现控制反转,把对象的创建交给spring管理的注解主要有:@controller、@service、@reponstory、@conmspant;依赖注入的注解主要有:@autowaired(根据类型)@resource(默认根据名称,名称找不到再根据类型);基于spring的强大,在框架中引入其他第三方插件时,就可以直接交给spring来管理。比如redis。引入时,只需要在spring文件里边配置一个bean。用的时候直接引用就可以了。好处是:可以实现应用直接的解耦。不需要重复去创建对象;

 

 

 spring的AOP称为面向切面编程、基于代理模式实现。主要有五大通知:前置、后置、最终、环绕、异常;用它一般可以做:权限拦截、操作日志记录、性能统计;我之前用AOP做过一个操作日志的记录;使用的是注解的方式。首先在spring配置文件中,开启注解。然后自定义一个切面类,普通类+@ascpect注解,就变成了一个切面类;在前面类里边定义了一个空方法,加上@pointcat的注解,作为切入点。切入点我们用的自定义注解的方式,把注解的全路径放在这个pointcat的参数里就可以了。自定义注解就是一个@interface;然后在切面类中定义前置、后置和异常三大通知。再前置通知里边获取请求的参数,在后置和异常里边获取结果,并保存日志; 然后再需要加日志的方法上,加上我们自定义的注解,就OK了。这样,当执行到家里注解的方式时,就会自动触发我们的切面类,进行日志的记录。

 

  1. SpringMVC的工作原理

tomcat启动时,也是先加载web.xml,找到spring mvc的前端总控制器DispatcherServlet,并且通过DispatcherServlet来加载相关的配置文件信息。

当浏览器发出一个请求之后,首先找到DispatcherServlet,通过这个控制器和代码中的requestMapping注解找到对应的controller中的方法,参数直接通过方法参数接收就可以,然后调用service、dao操作数据库,返回数据。如果页面跳转,controller的方法可以返回ModelAndView和string类型,再通过spring-mvc配置文件中的视图解析器找到对应的页面。

数据可以用request或ModelAndView返回到页面。如果是ajax请求,可以直接返回List或对象,加上responseBody注解,进行返回

 

 

  1. 堆栈
  • 栈的空间比较小,存放函数的参数值,局部变量的值等
  • 堆的空间比较大,用于存放new的对象。
  • 与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
  • 堆(数据结构):堆可以被看成是一棵树,如:二叉树。

栈(数据结构):一种先进后出的数据结构。

 

项目周期

1、需求阶段:产出物-需求规格说明书(项目的背景、项目的展望、项目的功能)

2、设计阶段:

产出物1-概要设计规格说明书(项目的架构、框架、技术、功能模块)

产出物2-详细设计规格说明书

产出物3-demo

3、开发阶段:开发计划进度表;日报、周报、月报;---技术文档

4、测试阶段:测试日报、周报、月报。禅道

5、验收阶段:用户操作手册、项目部署手册;

6、试运行阶段:运维手册

 

  1. Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。

方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。与此相对,覆盖者可能不会限制它所覆盖的方法的访问。

 

 

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

  • 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
  • 类可以实现很多个接口,但是只能继承一个抽象类
  • 类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
  • 抽象类可以在不提供接口方法实现的情况下实现接口。
  • Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
  • Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
  • 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。

 

 

什么是值传递和引用传递?

对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。

对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。

Java反射机制

 

通过类(Class对象),可以得出当前类的fields、method、construtor、interface、superClass、modified等,同是可以通过类实例化一个实例、设置属性、唤醒方法。Spring中一切都是返射、struts、hibernate都是通过类的返射进行开发的。我们代码中一般也就用他写一些工具类。比如jdbc工具类等

Java反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;生成动态代理。

 

算法复杂度

算法复杂度分为时间复杂度和空间复杂度。其作用: 时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。

时间复杂度常用大O符号表述

&和&&的区别

&和&&都可以用作逻辑与的运算符,&&为短路与,&不是短路与。

另外&可以做为整数的位运算符

 

例1:对于if(str != null&& !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。

例2:If(x==33 &++y>0) y会增长,if(x==33 && ++y>0)不会增长

Overload和Override的区别

Overload是重载的意思,Override是重写覆盖的意思,也就是重写。

Override子类覆盖父类的方法,将子类传与父类的引用,调用的还是子类的方法。

Overload一个类多个方法,方法名称相同,参数个数或类型或顺序不同。

字节流与字符流的区别

字节流是按字节读取或写入设备,但字符流是以字符为单位读取或写入设备。

如果是二进制文件,需要用字节流读取。一般来说,字符流只处理文本文件(txt、word、excel等)。在设备中,大多数情况是以字节形式存储数据的,因此字符流通过需要传入字节流当参数。

XML和Json的特点

  1. xml有且只有一个根节点,所有的标签都需要自定义,是纯文本格式;

 

2、json:json对象(就是在{}中存储键值对,键和值之间用冒号分隔,键 值 对之间用逗号分隔);

json数组(就是[]中存储多个json对象,json对象之间用逗号分隔)

(两者间可以进行相互嵌套)数据传输的载体之一

 

  1. 内部类

一个文件中,可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。

内部类就是在一个类的内部定义的类。内部可以定义在除参数位置上的任意位置。

 

  1. Java技能
    1. 锁机制:
      1. sql锁:

悲观锁:就是考虑问题很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁;

实现:sql语句后边加上for update

例子:Select  id,nam from biao for update

乐观锁:就是考虑问题很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁

实现:在表里边加一个vesion

例子:

Select  max(nub) ,version from biao

Update biao set nub=nub+1,version=vsersion+1 where id=id and version =version

方法锁:

方法锁主要包括:synchronized锁和lock锁

区别:

1)Lock是一个接口,而synchronized是Java中的关键字;

2)synchronized当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,不能够响应中断,(释放:1执行完线程自动释放2发生异常jvm让线程释放)((比如调用sleep方法)),这样的好处是不会导致死锁现象发生

Lock锁,可以不让等待的线程一直无期限地等待下去,比如只等待一定的时间或者响应中断。

但Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时必须在try{}catch{}块中进行,需要在finally块中释放锁;

3)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock(可重入锁,唯一实现了Lock接口的类)是个不错的方案。

 

 

  1. 项目文档

‘需求规格说明书’(项目的背景、展望、主要使用对象,主要功能模块)”、

“概要设计文档(在需求说明基础上设计:项目架构、框架、功能模块细化、主要关键技术、接口计划、服务器计划等)”、

“详细设计文档(对页面元素、功能模块、数据库表进行详细设计,一般同时美工会出demo)”,

“用户使用说明书(项目完成后,给用户的操作手册,一般是截图+文字)”,

“开发任务计划表”,“开发进度表”,(用Excel或者project完成)

“项目部署手册(图文并茂方式,讲述项目部署的方案和需要注意的地方)”

项目中一般有2中必须的设计图,主要是为了帮助开发者理清业务关系:

1、porerdesigner做ER模型图,可以直接导出sql

2、visio画流程图(业务流程的流转(必须))

3、visio画时序图(数据的流转(非必须))

 

  1. 电商分布式架构划分
  1. 运营管理平台
  2. 移动app接口
  3. 微信公众号管理
  4. 商城门户
  1. 门户子系统(主要)
  2. 订单子系统(主要)
  3. 商品子系统(主要)
  4. 支付子系统(主要)
  5. 购物车子系统(主要)
  6. 搜索子系统(主要)
  7. 登录系统(主要)
  8. 客服子系统
  9. 物流子系统
  10. 评价子系统
  11. 积分子系统
  12. 短信子系统
  13. 邮件子系统

 

  1. Jvm调优

因为我们的项目,web服务器中间件都用的是Tomcat,所以jvm的调优一般都是针对Tomcat的调优。调优工具可以可以借用jdk自带的VisualVM工具JDK安装目录/bin目录下,双击jvisualvm.exe文件,直接启动,就可以查看到当前堆空间大小分配情况线程监控情况垃圾回收监控根据这些信息,进行相应的调整。

 

我觉的Jvm调优的重点就是垃圾回收(gc,garbage collection)和内存管理。垃圾回收的时候会导致,整个虚拟机暂停服务。因此,应该尽可能地缩短垃圾回收的处理时间。

Java中垃圾回收机制是jvm自动完成的,我们不用操作,但可以通过修改一些配置对他进行影响;

  1. 首先可以开启server模式,这样虽
  2. 然启动慢点,但是长期运行效率高;(修改%JAVA_HOME%/jre/lib/i386/jvm.cfg文件开启)

2、针对JVM堆的设置,JVM初始堆内存分配有-Xms指定,默认是物理内存的1/64;

最大分配堆内存有-Xmx指定,默认是物理内存的1/4;当堆内存小于40%时,JVM就会自动增加,直到最大值。当空余堆内存大于70%时,JVM就会自动减少,直到最小值。因此,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值;

3、配置年轻代(Xmn)的值,持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8

4、设置个线程的堆栈大小Xss每个线程默认堆栈大小为1M,可根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数也是有限制的,不能无限生成,经验值在3000~5000左右。

5、回收器的选择,JVM给了三种选择:串行收集器、并行收集器、并发收集器,JVM会根据当前系统配置进行判断,自动选择。我们可以对一些参数进行设置。串行收集器只适用于小数据量的情况,一般不用处理可以配置年轻代使用并发收集器,年代使用并行收集器如果响应时间有限,就选择并发收集器,尽可能设大年轻代

如果吞吐量优先选择并行收集器尽可能设大年轻代

6禁用TomcatDNS查询。(当 Web 应用程序记录客户端的信息时,它也会记录客户端的 IP 地址或者通过域名服务器查找机器名转换为 IP 地址。 DNS 查询需要占用网络,并且可能从很多很远的服务器或者不起作用的服务器上去获取对应的 IP, 这样会消耗一定的时间。为了消除 DNS 查询对性能的影响,可以关闭 DNS 查询。方法是修改 server.xml 文件中的 enableLookups 参数值。)

7、线程数配置:Tomcat连接数过大可能引起的死机。所以,可以根据并发在Tomcat的server.xml中修改他的最大线程数、初始化线程数等参数。一般也就估计这配置,小了就配大点。没有具体评估过

    我们在项目中,一般也就是项目出现问题以后,再去优化Tomcat年老代溢出java.lang.OutOfMemoryError: Java heap space、持久溢出java.lang.OutOfMemoryError: PermGen space、堆栈溢出java.lang.StackOverflowError、线程溢出Fatal: Stack size too smal、内存溢出java.lang.OutOfMemoryError: unable to create new native thread是会抛出不同溢出。根据溢出去进行修改。

 

  1. Java集合

Java里边主要有两种集合,collection接口和map接口,其中collection下又包含list和set两个子接口;

List子接口:有序,可以有重复元素。和数组类似,List可以动态增长,查找元素效率高,相对插入删除元素效率低,因为会引起其他元素位置改变。

Set子接口:无序,不允许重复。检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。set集合中的元素不按特定方式排序,只是简单的把对象加入集合中,就像往口袋里放东西。

List接口下有三个实现类ArrayList LinkedListVector

Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用

ArrayList  实现一个动态数组,它的规模可变并且能像链表一样被访问。它提供的功能类似Vector类但不同步,它是以Array方式实现的List,允许快速随机存取。特点是读快改慢;

 

LinkedList实现一个链表,提供最佳顺序存取,适合插入和移除元素。由这个类定义的链表也可以像栈或队列一样被使用。提供最佳顺序存取适合插入和移除元素。特点是改快读慢

Set接口HashSet TreeSet 两个实现类

HashSet 能够快速定位一个元素,

要注意的是:存入HashSet中的对象必须实现HashCode()方法; 

TreeSet 将放入其中的元素按序存放它实现的是SortedSet接口,也就是加入了对象比较的方法。通过对集中的对象迭代,我们可以得到一个升序的对象集合。

Map接口的实现类主要有HashMap HashTableTreeMap

当元素的顺序很重要时选用TreeMap,当元素不必以特定的顺序进行存储时,使用HashMap。HashMap不是同步的,Hashtable同步的,Hashtable不推荐使用,因为HashMap提供了所有类似的功能,并且速度更快。当需要在多线程环境下使用时,HashMap也可以转换为同步的。HashMap没法保证映射的顺序一直不变,但是作为HashMap的子类LinkedHashMap可以。

HashMap可以通过Map m = Collections.synchronizedMap(hashMap)来达到同步的效果。

 

  1. Jdk1.8新特新

1Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可

2、Lambda 表达式(collection排序时使用);

3、Person::new调用Person类构造函数,创建对象;

  1. Solr

 Solr是一个基于Lucene开发的全文搜索引擎,项目中一般用它做首页的全文搜索或者做文件管理系统中的文件检索,因为他提供了分词的支持,同时采用文件存储数据,所以无论是查询速度还是匹配精确度方面,都比数据库的like查询方便了很多。比如:电商网站上,用户想买手机,输入“苹果手机”,但是系统中录入的是“苹果牌手机”,那么用like查询就很难匹配到,但lucene就可以解决这个问题。

使用时,Lucene官方紧紧提供了核心实现的jar包,我们只能通过api文档,自己实现创建索引和查询的逻辑,并进行优化处理,开发比较繁琐,但比较基础,更灵活,做文档的检索时可能相对更好点。而solr是Apache开发的一个开源的web工程,他帮我们实现了创建索引、查询等操作,经内部进行了一定的优化,然后以restful风格对外提供了接口。并且提供了solrj的sdk工具包,方便Java开发者直接使用。一般做电商等网站的全文搜索,我们使用时,只需要官方下载到solr工程的war包,根据我们的实际情况,修改solrhome 、solrconfig.xml和schema.xm文件,简单配置一下使用的数据库和创建索引的sql和字段,然后把war包进行部署就可以了。可以通过他提供的页面进行创建和查询索引的操作。也可以在项目中集成solrj的jar包,直接调用他的方法,进行创建索引和查询的操作。

Solr支持高亮显示搜索关键字,支持匹配权重配比(无特殊排序要求时,默认根据查询相关度来进行排序,可以在配置文件中配置,通过权重配置相关度) 、支持分页,支持自动分词等。我们一般都采用IK分词器,代替他默认的标准分词器。因为IK对中文的支持比较好。如果想要支持拼音,还可以使用它的拼音分词器。Solr也支持集群部署,官方提供了SolrCloud的部署方案,一般通过solrcloud+zookeeper实现,zookeeper在solr集群中主要有两大作用:1负载均衡2集中式配置管理。需要注意的是:zookeeper一般采用选举机制,所以一般都是2n+1(奇数)台。

另外由于solr查询时,不是直接查询数据库,而是开始时,先把数据库中的数据同步到索引文件中,查询时,直接查询索引文件。索引数据库数据修改时,需要同时同步到solr的索引文件中,也就是创建索引。创建索引分为全量创建和增量创建。可以通过配置文件实现自动增量更新,自动增量更新可定时将数据库中的数据导入到solr索引库中。除了solr以外,最近又出了一个搜索引擎叫ElasticSearch,我简单了解了一下,他和solr一样,也是一个基于Lucene的,rest风格的,分布式搜索引擎。Solr特点是:查询快,但更新索引时慢(即插入删除慢),对用于电商等提前录入好数据,之后查询特别多的应用;而ElasticSearch简称ES建立索引快、查询相对慢点,综合上表现为实时性查询快,用于新浪了,facebook了等搜索。

Solr在5版本以后,引入了内置jetty,可以直接启动运行;solr6.0以上版本,要求jdk必须是1.8以上,4.8版本以上要求jdk1.7 启动,目前最新的是7.3.1

1、zookerpper的作用;

2、solr、Lucene、es的区别;

3、solr集群搭建主要步骤,和集群方案;

4、solr高亮和分页

5、solr权重配置;

6、索引创建;全局索引和增量索引;

7、solr部署主要步骤;

8、solrhome

9、solrconfig.xml,主要定义solr的处理程序(handler)和一些扩展程序;

solrconfig.xml 文件不仅指定了 Solr 如何处理索引、突出显示、分类、搜索以及其他请求,还指定了用于指定缓存的处理方法的属性,以及用于指定 Lucene 管理索引的方法的属性。

10、schema.xml,主要定义索引的字段和字段类型。

    fieldtype、Fields、copyField、dynamicField

  1. 数据库比较
  1. 首先主要是关系型数据库、nosql数据库和缓存数据库的区别;我觉的:

关系型数据库(mysql、Oracle)就想当于一个储物柜,里边分了各种小格子,把各种数据有序的保存在里边,查询时非常方便;有比较完善的sql语句;

  • mysql免费开源,自动提交事务,是基于表级别的一个事务,而Oracle不自动提交,是基于行级别的事务;mysql安装包小,使用简单,Oracle安装包较大,还会收费,更适合于大数据的处理、并发量高;
  • mysql用limit进行分页,第一个参数表示启始数,第二个参数表示要查询的条数。
  • oracle用伪列rownum来进行分页,因为伪列只能用小于,不能用大于,所以需要一个两层的嵌套查询。
  • mysql主键可以自增。Oracle必须用序列(sequence);

字段类型有些区别;

  • mysql一般都是先有用户,然后创建库、创建表。而Oracle一般是,先创建数据库,然后创建临时表空间、物理表空间,再创建用户,创建表。

nosql数据库(mongdb)就相当于一个仓库,比如把衣服放在一堆,书本放在一堆,这样他的存储效率肯定很高,但是查找时,不方便精确查找。所以nosql更适合于数据量比较大,但是数据之间关系不是特别强,或者不是特别重要的数据;比如:电商里边的评论数据;

他的collection相当于数据库,document相当于表,document中数据主要以json格式存储;

缓存数据库(redis、memcached):缓存数据库由于是保存在内存中的,所以他的存储效率非常高;但是他不适合持久化存储数据,尽管redis支持持久化,但是他也仅仅是在一定程度上保证了数据的安全性,还是有很多情况会造成数据丢失的,比如redis穿透、雪崩等。

所以他仅仅适合做为数据库和应用程序直接的中间层使用,不能作为真正的存储数据库使用。即使使用,也只能是一些不是特别重要的数据,比如购物车,在B2C的项目中,用户购物车一般都是临时存储,如果放入购物车长时间不提交,那么用户再购买的可能性很小了,所以即使丢了,也不会造成太多的损失。以这个,来换取项目的运行效率非常划算;

  1. dubbo

dubbo是一个流式分布式架构的服务框架,一般结合maven的模块试开发使用。传统的单架构项目,不方便维护和升级;通过maven的模块式开发,就可以把一个单架构的工程,拆封成一个一个的小模块,包括(jar和war);jar包可以被war包直接应用。war包可以分布式部署,但是这样不同的war之间的交互就成为了问题。我们之前的做法是,两个war之间进行交互时,一般采用webservice或httpclient。但是在这种分布式架构中,多个war之间的交互会特别频繁,如果用webservice或httpclient架构师很多时候自己都搞不清楚他们之间的依赖关系了。而dubbo解决了这个问题,有了dubbo架构以后,多个war之间不在直接进行交互,而是统一和dubbo的注册中心zookeeper进行交互。无论是发布服务还是调用服务,都通过dubbo实现,并且提供了web页面,来监控和管理各个接口直接的调用。

使用首先搭建dubbo的注册中心zookeeper,然后下载dubbo的服务治理工程dubbo_admin.war,解压修改他的配置文件dubbo.properties设置用户名密码和注册中心zookeeper的ip端口。部署Tomcat的webapp中,启动Tomcat,浏览器用ip+端口访问,就可以看到dubbo_adminweb页面了。接着在我们要发布或者调用服务的项目中,导入dubbo提供的相关jar包,加载配置文件,把要发布的服务注册到zookeeper里边。或者从zookeeper里边调用服务就可以了。dubbo_admin的web页面中,可以监控和管理所有的服务提供者和调用

    dubbo架构中,service接口实体对象时各个工程共享的,service实现类发布服务工程独有的。只需要发布服务的工程他的接口注册到zookeeper中,其他工程就可以从zookeeper中进行调用了。

dubbo的好处是:项目拆分成各个独立的小工程,通过接口调用方式,互相交互数据,可以单独进行部署和升级,这样减少了他们直接的耦合性和代码的复用性。还可以针对不同模块采用不同的部署策略,比如订单模块并发比较高,所以可以把订单模块这个war包,单独部署多套,都注册到同一个zookeeper中。当客户端调用时,zookeeper会帮忙进行负载处理。

 

问题:当dubbo注册中心zookeeper宕机以后,dubbo架构还正常运行吗?

服务治理和dubbo类似的还有ESB(企业服务总线)技术:他们的最大区别在于:dubbo只做中介,不对服务做处理。ESB会对服务做处理;

  1. Zookerpper

zookerper主要功能是为分布式系统提供一致性协调(Coordination)服务,主要有两大功能:统一配置管理和集群负载均衡,

统一配置管理分布式系统都有好多机器,这些机器上边的配置是一致的,正常情况下需要修改好一个以后,然后scp到其他服务器,每次修改,就要把所有的配置都修改一遍,Zookeeper提供了这样的一种服务:一种集中管理配置的方法,我们在这个集中的地方修改了配置,所有对这个配置感兴趣的都可以获得变更。这样就省去手动拷贝配置了,还保证了可靠和一致性。 

集群负载均衡在分布式的集群中,经常会由于各种原因,比如硬件故障,软件故障,网络问题,有些节点会进进出出。有新的节点加入进来,也有老的节点退出集群。这个时候,集群中有些机器(比如Master节点)需要感知到这种变化,然后根据这种变化做出对应的决策。Zookeeper会对通过选举机制,选举一个主节点作为管理者。选举一般都是奇数台,否则会选举失败。并且集群中,有半数以上宕机,则会认为整个集群挂掉。

我们项目中用到zookerpper的地方主要有:solr集群redis集群和dubbo注册中心。

  1. Freemarker

freemarker是一个模板化引擎语言,传统项目中,后台页面基本上都是list列表,页面都是类似的,只是展示的数据不一样。这样我们就可以把他的样式做成一个freemarker模板,然后传入数据就可以展示不同的页面。他的模板一般是以.ftl结尾的。如果使用这个呢,可以使我们开发人员不需要太关注前台。但是对于很多门户或商城类项目,每个页面都是不一样,所以用他也不是很方便。另外,由于可以把freemarker模板直接转换成html、jsp、java、xml、word等各种文档。所以我们经常使用他做代码生成、word生成或者首页静态化等。我这里就用到了首页静态化的功能。由于任何用户访问时,首先会访问到我们的首页,所以很多东西都希望能在首页展示,但是放的东西多了,就会加载很慢。给用户的体验度很不好。所以我们在项目启动时,直接把首页需要的数据查询出来,通过freemarker模板生成静态的html页面,之后用户访问时,都去访问这个静态页面,这样就不需要频繁访问数据库,减轻了数据库压力,提高了用户体验度。但是缺点是,数据库数据数据变换了以后,数据无法实时更新。我们一般通过定时器的方式,每天凌晨重新生成。

还有就是热销商品的商品详情页面也做了静态化处理,首页我们是通过定时器每天凌晨一点,去重新生成;商品详情我们是在商品信息修改以后,给定时器表(id,业务id、type,remark)中推送一条信息,到第二天凌晨一点时,定时任务扫描表,发现有需要重新生成的页面,就调用freemarker模板,重新生成html页面,以商品id作为html页面名称,然后删除任务表中的数据。为了预防大量静态页面给服务器造成压力,我们把html页面直接生成到Nginx的静态页面服务器上。访问时,不用经过Tomcat,直接通过Nginx访问。

 

和他类似的技术,我还知道一种volicity,但是这个技术现在基本上用的很少了,主要是他对jsp的兼容不是很好。

 

freemarker主要语法用${}输出变量,$#等要用原样输出符${r"#"},空值会抛异常,<#list 做循环,循环下标用变量_index

${book.name?if_exists } //用于判断如果存在,就输出这个值 
${book.name?default(‘xxx’)}//默认值xxx 
${book.name!"xxx"}//默认值xxx 
${book.date?string('yyyy-MM-dd')} //日期格式 
${book?string.number} 20 //三种不同的数字格式 
${book?string.currency}--<#-- $20.00 --> 
${book?string.percent}—<#-- 20% -->

用lt、lte、gt和gte来替代<、<=、>和>=

如果存在特殊字符,可以使用${r"..."}进行过滤

  1. App接口开发
  1. 由于移动端app一般时不能直接访问数据库的,所以需要我们Java后台开发接口,供移动端去调用;
  2. App接口的开发一般都是restful分格的接口。就是不需要跳转页面,都是直接返回需要的数据。所有的方法上都需要@ResponseBody注解,把返回结果转换成json。所以我们一般用@RestController代替@controller;
  3. 因为app接口如果获取不到想要数据,就很可能发生闪退,所以我们需要在controller中的所有方法中,用try catch捕获异常,把异常也返回。
  4. 为了让用户识别异常,我们一般需要定义一组错误编码。
  5. 由于接口比较多,为了方便客户端处理接口返回数据,我们定义了一个统一的返回对象类。里边主要包含三个参数:Boolean success,String code,Object data;
  6. 安全方面考虑,接口最后都要把http协议转换成https(http+CA证书)协议。
  7. 在接口的拦截器里边,采用对称加密的方式,进行签名认证。 对称加密就是服务端给调用端一个appid和一个appsecret;
  8. 接口文档:接口说明、接口的url,传入参数、响应参数。
  9. 接口自己测试:一般在浏览器上安装一个restclient的插件,用这个插件就可以测试。
  10. 接口的调试:方法一:手机连接公司的内外;方法二:通过Nginx把自己的ip代理到外网。

 

  1. 第三方支付

B2C电商的支付,一般由于支付金额比较小,支付比较频繁,所以一般采用第三方支付,常用的第三方支付有:支付宝、微信、易宝支付等。他们的原理都差不多。都是在点击支付时,直接调用第三方支付接口,传入appid、appsecret、订单编号、订单金额、回调url,直接跳转到第三方支付页面,接下来的支付过程,我们都不需要管,支付成功以后,第三方支付平台会直接回调我们的url。给我们返回:状态码、订单编号、支付流水号三个参数。我们首先根据订单编号,找到我们的订单,把支付流水号和状态码更新到我们的订单里边。回调url,一般有两种,一种用同步get方法回调,一种用异步的类似ajax方法回调,同步方法回调,一般是成功以后才会回调,并且只回调一次,回调成功以后我们可以直接跳转到我们的支付成功页面、异步方法回调,一般要求我们返回一个success字符串,第三方平台如果没有接受到success,就会认为没有调用成功,他会重复多次调用。比如支付宝会在25小时之内,调用8次;一般情况下第三方支付都采用第二种方式,因为比较安全,但支付宝是同时采用了两种。

我之前接触过一个B2B的电商,他们由于交易金额比较大,第三方支付无法实现,所以是直接和银行对接。大体上是,首先平台和银行签订合同,银行为平台开设一个总账号,当企业在平台注册以后,平台会为企业调用银行接口,创建一个子账号,这个子张号是挂在总账号下边的,也是一个在银行实际存在的账号,但是,只能通过外部银行卡给里边转账,而不能给外部银行卡转出。可以在子行号直接互相转账。

  1. 微信扫描支付

1、商户系统根据用户选择的商品生成订单(此步骤不分析)

2、用户确认支付后根据微信【统一下单API】,向微信支付系统发出请求(我们通过httpclient方式请求的)

分析:商户确认支付即点击“结算”按钮跳转到收银台,然后在点击微信支付时,会调用商户系统后台,后台做处理准备微信需要的参数,然后通过httpclient调用微信的【统一下单API:https://api.mch.weixin.qq.com/pay/unifiedorder】,其中需要准备的主要参数:

appid(公众号ID),String(32),微信支付分配的公众号ID

商户号(mch_id),String(32),微信支付分配的商户号

随机字符串(nonce_str),String(32),随机字符串,主要是为了保证签名不可预测

签名(sign),String(32),通过签名算法得到的签名值,签名算法大致为:需要参与的字段包含公众号、商户号、随机字符串、一些其他字段,最重要是key(在微信支付系统中配置的密钥),然后这些字段格式为:key1=value1&key2=value2...,然后把这个字符串通过MD5加密并把加密结果转成大写。

商户订单号(out_trade_no),String(32),商户系统内部订单号,我们系统用的是交易流水号(订单号-商户号-时间戳)

标价金额(total_fee),int,订单总金额,单位为分,不能带小数点

通知地址(notify_url),String(256),异步接收微信支付结果通知的回调地址,必须为外网能访问的URL,不能带参数

交易类型(trade_type),String(16),JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,我们用的是NATIVE

3、微信支付系统收到请求后,先生成预支付订单,然后给商户系统返回二维码连接

4、商户系统拿到返回值字符串,转换成对象,然后取出二维码连接生成二维码

5、用户通过微信“扫一扫”功能扫描二维码,微信客户端将扫码内容发送到微信支付系统

6、微信支付系统接收到支付请求,验证链接有效性后发起支付,请求客户确认,然后我们的微 信端就会弹出需要确认支付的页面

7、用户输入密码,确认支付并提交授权

8、微信支付系统根据用户授权完成交易

9、微信支付系统支付成功后向微信客户端返回交易结果,并将交易结果通过短信、微信提示用户

10、微信支付系统通过发送异步消息通知商户系统后台支付结果,商户系统需回复接收情况,通 知微信支付系统不再发送该单的通知

接收到微信的支付完成回调请求,微信支付系统会传过来一个字符串,格式是xlm的我们将其转换成map格式,然后验证一些主要参数,return_code和result_code均 为success;公众号,商户id不为空;对签名进行验证,防止数据泄漏,验证方法是将返回集解析出来,然后重新按照签名规则生成签名,将两个新旧签名比较,如果相同则验证通过;以上验证全部通过则认为威信支付系统支付成功,接下来处理商户系统。

商户系统也需要验证一些支付异常情况,订单已取消的支付成功了;订单已经支付了,重复支付;订单金额不一致,支付金额与订单金额不一致;以上均为异常支付,需要退款。如果无异常支付,则更新本地数据。另外商户系统在进行上述验证及更新操作时,需将此段代码加锁,因为微信支付系统在与商户系统交互时,如果微信收到的用户应答不是成功或超时,则认为微信通知失败,则微信会重新发起通知,通知频率为:通知频率为:15/15/30/180/1800/1800/1800/1800/3600,单位:秒

11、未收到支付通知的情况,商户系统可调用【查询订单APP】

12、商户确认订单已经支付后给用户发货

 

 

  1. 支付宝当面扫码支付

 

一、流程:

1、用户请求支付,调用我方接口,我方根据订单信息和商品信息构造符合支付宝要求的请求参数(请求参数中具有一个我方的回调地址,当支付成功的时候,支付宝会回调这个接口)去请求一个支付二维码(可设置支付二维码的过期时间)。我方将支付二维码持久化到图片服务器,然后图片地址给前端,让前端展示给用户。

2、剩下这一步就是用户和支付宝的交互了。用户支付成功后,支付宝回调我们的接口,我们的接口开始去更新订单状态,写支付信息到我们的数据库中,如此一个完整的支付场景就完成了。支付宝会根据我们返回的值,判断这次交易是否成功,不成功则不扣钱。
二、难点

1、如何确保是支付宝回调的我们的接口?

如果是被恶意的第三方调用我们的接口,并且通过了将订单状态更新了,那么就相当于我们形成了损失。

支付宝自身提供了一套校验机制(这套校验机制是怎么做的,值得学习),我们可以通过调用支付宝的校验接口去校验回调是否来自支付宝。

2、如何保证幂等性?

如果是因为网络原因、用户多次点击。那么要保证这些操作造成的结果是一致的。

我的处理方案:先去数据库中查询状态,如果状态是订单已支付,那么返回支付已完成的消息,否则去更新订单信息。

缺点:如果正在更新状态,一个请求又过来了,那么还是不能保证幂等性。

改进:使用一个全局分布式锁,每次要进行这个操作(其中还是有查询状态这个操作),去持有这个分布式锁,执行成功之后去释放这个分布式锁(这是为了避免高并发带来的问题)。

 

 

  1. 三方登录

自己的登录:用户名、密码和验证码、密码用md5加密;

 

第三方登录,我的理解就是基于用户在第三方平台上已有的账号和密码来快速完成己方应用的登录或者注册的功能。遵循一个Oauth2.0国际通用协议,允许用户在不提供用户名和密码的情况下,让第三方应用访问一些资源。使用第三方登录时,我们不需要用户再次输入用户名和密码,而是直接通过一个唯一openid来进行授权登录。对于普通用户来说,如果能用QQ、微信、百度、新浪这些平台的账号一键注册登录各个平台,无疑会方便很多。对于我们的应用来说,通过授权,借助QQ、微信这些用户量比较大的第三方平台增强自己的知名度也非常划算。

我们的平台集成了QQ、微信、百度、新浪四种第三方登录方式,实现的方式都是类似的。首先去各大开放平台进行注册成为开发者,并创建应用,填写回调地址,获取appid(应用唯一的识别标志)、appkey(给应用分配的密钥),(名称可能不一样);下载api文档和sdk开发工具包;就可以开始开发了。

首先在我们网站的登录页面根据api集成第三方登录的logo图标,并给与点击事件,当用户点击此图标时,发送请求,直接跳转到第三方平台的登录页面,第三方平台也会自动检测电脑是否有已登录的账号。登录成功以后,第三方平台会自动调用我们传递的回调地址,并传递回一个code参数;我们拿到code以后,再次调用第三方api提供的接口,传入code、app_id、appkey等参数,调用获取access_token的接口(接口调用,有第三方提供的sdk包,直接导入jar包,根据api文档,传递参数调用方法就可以,我们没必要太过关心第三方平台是用webservic接口或httpclient接口。)。获取到access_token同时,会获取到openid,拿到openid以后,就相当于拿到了登录授权。用openid去自己的用户表中查找是否与对应的用户,如果有,就直接查出用户信息,创建自己的session就可以了。如果没有,则新创建一个用户,把openid放进去。如果还需要其他信息,可以通过openid再次调用第三方平台的接口获取用户信息,如果用户信息还是不够,可以创建完用户以后再次跳转一个页面,让用户不全信息。信息补全以后,创建session,完成登录。这样一个第三方登录就完成了。

  1. 单点登录

我们的单点登录系统,主要包含了登录验证、token校验、注销、注册几大功能,单点登录系统提供了统一的登录和注册页面,提供了统一的登录token校验接口。

单点登录的主要原理就是在登录成功以后,生成一个令牌,这个令牌要求每次登录唯一不可重复,我们就简单的用了一个随机的UUID,因为我们的系统在部署时,各个模块都是通过Nginx映射到同一个一级域名下的,cookie只要把他的作用域设置成一级域名,就可以在所有同一个一级域名下的模块中共享。所以我们把随机生成的token,以字符串 “token”为key,放在cookie里边,然后用生成的token做key,用户对象信息转成json字符串后,作为value,放到redis里边,都设置有效期30分钟;

然后做了一个统一校验token的接口;各个模块在自己的拦截器里边,调用此token校验接口,验证是否登录,如果返回null,则说明没有登录,拦截到统一的登录页面,并把进入拦截到的url放入cookie里边,方便登录成功以后,获取这个url,进行原路径跳转,而不是每次登录都进入首页,提供用户的体验度。如果返回用户信息,则说明已经登录,模块创建自己的session,并放行url。统一校验token的接口的主要流程是,首先从cookie里边获取到token,然后通过token到redis里边获取用户信息。这两个过程,任何一个失败,都直接返回null,如果成功,就把cookie和token的值从新设置一遍(这个是为了刷新有效期); 这样就实现了多个模块只需要登录一次就可以的流程。

还有就是注销,注销也是调用统一的注销接口,注销时需要首先从cookie中获取token,根据token删除redis中的用户信息,然后在删除cookie中的token。

 

 

  1. 订单管理

  首先是订单表的设计,主要包括订单表和订单详情表,订单表主要包含订单的主要信息,比如订单的编号、总额、数量、状态、收货人信息等。其中收货人信息必须要冗余到订单表中,不能简单用Id进行管理。订单详情表和订单表是多对一关系,订单详情表主要计量订单中的商品的详细信息,这些信息也要冗余进来,不能通过id进行简单的关联,因为订单一旦生成,这些信息一般不会再允许改变。

  订单在用户结算购物车时生成,如果同时购买多个商家的商品,在结算购物车时需要进行分单,同时生成多张订单。在用户中心,每个用户都可以看到并跟踪自己的订单,进行支付、申请退货、确认收货、评价等操作。商家后台可以看到商家自己的所有订单,进行确认发货操作。而在运营管理平台,可以监控所有的订单,但是不能进行操作。

  订单的状态主要包括:待支付、待发货、已发货、已完成、已取消;

  生成订单时,应该对库存进行一次校验,防止超卖;

 

  1. 购物车

购物车在未登录的情况下,因为没有用户信息,所以这时候如果想要存储购物车信息,只能保存在浏览器客户端。有两种选择cookie和localstorage,其中cookie,是每个浏览器都有的,既可以通过js操作,也可以随着请求传递到服务器端,在Java里边通过request和reponse对其进行操作,可以设置cookie的有效期、作用域、作用路径等,。但是缺点是有可能被禁用,不能跨域,存储量小。Localstorage是html5的本地存储,存储量可以达到5M,本身也不支持跨域,但可以借助其他方式解决。但是他只能通过js操作,不能随着请求传递到后台用Java操作;无论是cookie还是localstorage都是以key-value的方式存储,因为都没有用户信息,所以我们一般用“固定前缀+商品id”作为key,以商品数量作为value;加“固定前缀”主要是为了获取购物车列表时方便,value中只保存数量,其他数据展示时再通过id从数据库里边获取,这样是为了保证信息的准确性。如果要追求效率,也可以把要展示的商品信息都保存在value中。

登录以后,购物车一般存储在数据库或者缓存中,之前接触过一个B2B的电商,因为他的金额数量较大,交易周期比较长,购物车中的信息可能会存放很久,这种情况下,还是保存在数据库中比较安全;(主键、用户id、商品id,商品数量);这个项目,我们是个B2C的,购物车里边的数据都是临时存储,如果用户加入购物车以后,长时间比如1周还不购物,购物的欲望估计很小了,这时候即使丢失也无所谓。就像唯品会的购物车,只能存放20分钟,督促用户去尽快购买。这种情况下,保存在redis里边是个不错的选择。Redis是一个第三方缓存数据库,他支持多种(5种)数据格式,也支持持久化。在这里我们使用了他的hash类型的数据格式操作相对比较方便。代码里边通过spring整合redis官方提供的jedis的jar包,来操作redis。Hash类型存储时,有3个参数。第一个参数key,我们存放“固定前缀_用户id”。第二个参数field,我们存储“产品id”,第三个参数存储“产品数量”;当给购物车存放一个商品或者取出一个商品时,通过用户id和产品id,可以直接获取购物车中商品的数量,然后进行加减操作,在进行覆盖操作就可以。如果想要获取购物车列表信息,可以直接用“固定前缀_用户id”获取,返回一个map,然后迭代map,获取到购物车中所有的商品信息。

另外,由于用户未登录时,购物车信息只能保存在客户端,不能和用户建立联系,所以如果有其他人借用电脑,购物车信息就可能混乱,这个是不可避免的,如果要考虑这点,最好的方式,就是用户不登录时,不让他操作购物车。还有就是,如果未登录时,保存了购物车信息,在登录成功后,一定要把购物车信息同步给登录用户。

 

  1. 代码中事务是如何控制的?

 

java中的事务主要有两种,JDBC事务(本地事物)和JTA(Java Transaction API)事务(分布式事物);事务有四大特性ACID原子性、一致性、隔离性和持久性。框架中,我们一般把事物交给spring来管理。spring配置事务的方式一般有两种,一个是声明式事务,一个是注解式事务。注解事务,比较简单灵活,在spring配置文件中配置一个<tx:annotation-driven>的注解,然后在需要的方法上加@Transactional注解就可以了。声明式事务,切点一般是扫描service层实现类,通过方法名匹配配置传播特性,决定哪些方法上加事务,哪些不需要事物。事务主要有五大隔离级别和7种传播特性;五大隔离级别由低到高:主要控制是否出现脏读,不可重复读和幻觉读;7种传播特性主要决定是新建事务,还是取当前事务;

 

脏读:

指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

不可重复读:

 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

幻觉读:

指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户

事物主要是为了保证一次操作里边涉及到多次增删改时,保证他们可以同步提交或回滚。他有四大特性ACID(原子性、一致性、隔离性、持久性)

  1. 原子性(Atomicity):事务是数据库逻辑工作单元,事务中包含的操作要么都执行成功,要么都执行失败。
  2. 一致性(Consistency):事务执行的结果必须是使数据库数据从一个一致性状态变到另外一种一致性状态。当事务执行成功后就说数据库处于一致性状态。如果在执行过程中发生错误,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这是数据库就处于不一致状态。
  3. 隔离性(Isolation):一个事务的执行过程中不能影响到其他事务的执行,即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行各个事务之间无不干扰。
  4. 持续性(Durability):即一个事务执一旦提交,它对数据库数据的改变是永久性的。之后的其它操作不应该对其执行结果有任何影响。

我们框架中一般都通过Spring的AOP去管理事物;常用两种有方式:声明式事物和注解式事物;

 

声明式事物就是通过在配置里边配置他的事物传播特性和切入点的方式实现;切入点我们一般扫描所有的service层的方法做为切入点;事物传播特性一般通过方法名称判断去使用只读事物还是可增删改事物;

 

注解式事物:在Spring配置文件里边开启以后,在需要事物增删改事物的方法上加上@Transactional注解就可以了;

 

事务的隔离级别也分为四种,四个级别可以逐个解决脏读、不可重复读、幻读这几类问题

 

7种事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播

  1. mybatis缓存?

mybatis一级缓存是SqlSession级别的缓存,默认支持一级缓存,不需要在配置文件去配置。

mybaits的二级缓存是mapper范围级别,,除了在SqlMapConfig.xml设置二级缓存的总开关<settingname='cacheEnabled'value='true'/>

,还要在具体的mapper.xml中开启二级缓存:

<mappernamespace='cn.hpu.mybatis.mapper.UserMapper'>

<!-- 开启本mappernamespace下的二级缓存 -->

<cache></cache>

useCache配置禁用二级缓存(默认情况是true,即该sql使用二级缓存。)

<selectid='findOrderListResultMap' resultMap='ordersUserMap' useCache='false'>

 mybatis刷新缓存(就是清空缓存flushCache='true' 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新)

<insertid='insertUser' parameterType='cn.itcast.mybatis.po.User' flushCache='true'>

  1. Restful

Restful(Representational State Transfer表现层状态转化),是一种软件架构设计的设计风格而不是标准,只是提供了一组设计原则和约束条件。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。他的理念是把每一个URI代表一种资源,浏览器可以通过http的四个关键词对这种资源进行操作。形成四种不同的请求(get、post、put、delete);但java里边一般只支持get和post,如果想要支持put和delete,需要在web.xml中加一个过滤器,还需要前台传一个_method的参数,告诉后台是put还是delete请求。个人感觉也不是很方便。我们用restful分格,一种是写rest分格的api,一种是写rest风格的url。rest风格的url就是用路径传参,代替传统的?号传参。用注解@PathVariable来接收参数。rest风格的api,就是把同一个资源的请求,用一个rui表示,用四个关键字来区分不同的请求。使得接口调用人员不会对请求的资源地址产生混淆和大量的检查方法名的麻烦,形成一个统一的接口。另外,Rest风格的接口方法,一定是直接响应客户数据的,而不是跳转页面的,在springmvc中,一般我们把所有需要用到ReponseBody注解响应数据的方法,成为rest方法。他还提供了一个@RestController的注解,用来代替@Controller+@ReponseBody;

 

 

  1. 索引

索引可以理解为数据1   的查询目录,建索引的目的就是 提高对表的查询速度;没有索引时,查询时全表检索,有了索引就可以根据索引快速查找需要的数据;但是索引也不能乱建,因为索引需要维护,会导致增删改的效率降低。会使数据的维护变的复杂,影响开发的效率,索引也会占用数据库的物理空间;所以我们一般在项目的开发阶段、测试阶段、试运行阶段都很少去创建索引,因为有了索引,系统bug造成垃圾数据特别不好删除。只有在项目正式上线后才去增加索引,以提高项目运行速度。索引我们一般都创建在经常作为查询条件的字段、排序的字段和作为关联关系的字段上边。尽量避免在大文本字段、数据量比较小的字段(比如性别),增删改性能大于检索性能的字段上边;另外,有些情况,即使增加了索引,索引也不会生效,比如:索引字段使用了不等于(!=或者<>)符合,用了函数、进行了运算,使用了is null或is not null

和不匹配的数据类型进行比较、like查询时两边都用了%等;还有一个要注意的地方是,如果在多个字段上建立联合索引,那么组合索引的第一个列被where子句引用时,索引才会起作用。因为想要使用索引增加查询效率,必然要牺牲增删改效率,为了解决这个问题,我们经常对数据库做主从复制,读写分离。同时创建两个数据库,一主一从,两个数据库数据完全一致,主的数据库用来进行写的操作,操作后数据库会自动把数据同步到从的数据库,从的数据库用来执行读的操作。这样我们建立索引时,就可以只在读的数据库创建就可以了。这样就索引即能增加查询效率,有不影响增删改效率。这样做了之后,我们还可以对他进一步优化,比如数据库引擎的优化,主数据库因为执行增删改操作,所以用事务型引擎Innodb

读的数据库,不需要事务,就可以用效率更高的MyIASM引擎。同时根据实际情况,也可以配置一主多从或者多主多从。索引的创建常用的有2中方式:CREATE 【UNIQUE】INDEX index_name ON table_name (column_list);或者ALTER TABLE table_name ADD INDEX index_name (id,name);修改用:ALTER TABLE table_name REBUILD INDEX index_name (column_list);删除用:DROP INDEX index_name ON talbe_name

或者:ALTER TABLE table_name DROP INDEX index_name

查看用:select * from all_indexes where table_name='student';

Conlum_list中多个字段用”,”号分割。

代替分号 ,因为分号在存储过程里边要用;创建好以后,               在用delimiter ;恢复;

我之前写过一些存储过程,但最近接触的比较多的还是电商类的互联网项目,这类项目中,存储过程并不建议使用。最近一次存储过程记得是,为测试人员写的一个创建测试数据的存储过程。当时测试人员为了进行报表统计功能的测试,需要经常重复性的生成大量的数据,还设计到了很多张表。如果手动一条条加数据特别麻烦,于是找我想个办法,我就帮他写了一个存储过程;好久没写了,手比较生,当时也是一边找资料,一边写的。很简单的东西,写了近2个小时;

 

函数(function)与存储过程的结构类似,但是存储过程中不能用return返回值,函数必须有一个return子句,用于返回函数值;当然函数也可以用类似存储过程的参数返回;函数除了直接调用以外,可以直接放在select语句中使用,存储过程不行,存储过程定义关键字用procedure,函数定义用function。一般情况下如果要求有且有一个返回值,用函数,否则,用存储过程

     触发器(trigger)一般是执行sql语句的增删改查时使用,比如修改A表的一条数据时,让他自动触发一个操作,在B表里边新增一条记录;触发器因为需要绑定在表上,所以有数量限制,他里边的语法和存储过程、函数类似。

 

    视图(View)实际上是多张表之间进行关联查询,把查询结果虚拟成一张表,就叫视图。创建视图也非常简单,create view viewname as + 查询语句,就可以了。视图的使用和表的使用完全一样,但不要在视图上进行增删改的操作,因为他本身就是一个查询结果的集合。利用视图,可以使我们把复杂的查询简单化,可以增强数据的安全性,屏蔽用户对基表的操作;他一般无法提高查询效率。

 

  1. 数据库优化应该从哪些方法考虑?
  1. 数据库选型:首先应该从数据库的选型来看,首先是关系型数据库,目前最流行的是mysql和Oracle,mysql更适合于中小型项目,安装使用比较简单,免费开源。Oracle适合于大型项目,安装包比较大,使用比较耗内存,收费,但是效率高、可靠性好,使用行级锁,比mysql的表级锁粒度更细,对并发性的支持要好的多。NOSQL数据库(比如:mongdb),事务性比较差,但效率高,主要适用于数据量比较大,但关系型要求不是很强的数据,比如评论数据。缓存数据库(比如:redis),由于保存在内存中,所以速度特别快,但是不适应持久化保存。所以缓存数据库一般作为应用和数据库的中间层来使用。
  2. 设计规范:其次应该从数据库设计规范来考虑。好的规范,可以是数据库运行效率更高,优化更方便。以mysql为例;
  • 由于mysql对大小写在windows和linux上敏感型不一致,为了避免麻烦,我们一般建议数据库名、表名、字段名都用小写+下划线命名;
  • 数据库名最后于项目名称一致。
  • 表名最好采用:“数据库名_表名”的方式;
  • 字段名最后见名知意,不要太长;
  • 字段选用合适的字段类型,比如姓名可以用varchar(10),而不要用默认的长度255,日期最好用date或datetime或时间戳。金额最好用decimal,而不是double、float等。
  • 关联表以外的表最好都有主键id、创建时间、创建人、最后修改时间、最后修改人字段。
  • 数据库设计时,以三大范式为参考,但是必要时,可以使用冗余;
  • 对于预期数据量特别大的表,可能没有都会有很大增长的表,要考虑水平分表,比如系统日志表,可以考虑表名中加入月份,每月生成新表。
  • 对于字段比较多的表,比如企业信息表,可以考虑纵向分表,把表分成两张1对1的表。把经常查询的字段放在一张表里,不经常用到的和大文本字段放在另外一张表里。
  • 开发阶段,介意每个表只有主键,不应该包含其他外键。因为开发过程中,系统不稳定,可能出现很多垃圾数据,有了外键,数据的清理会给我们开发照成很多不便。要求开发人员用代码维护表之间的关系,这样更能增加代码的健壮性。
  1. 索引:开发测试完成以后,一定要记得给数据库添加索引,增加sql的执行效率。添加索引也要注意,索引应该添加到经常作为查询条件或者排序字段、或者作为外键的列上。而那些不经常查询的字段、大文本字段、可选值比较少的字段(比如性别)等不需要创建索引。另外,sql中的不等于、is null、函数、like两边同时使用%等,会使索引失效,应该尽量避免使用;
  2. 配置:数据库的配置优化(my.ini),比如:最大连接数、最大缓存数、最大线程数、物理内存等;
  3. 读写分离、主从复制优化。因为一般查询多、修改少,索引可以增加查询效率,但会影响修改效率。所以做了读写分离以后,可以把索引只建立在读的数据库上。并且查询不需要事务,所以可以用非事务行引擎;
  4. 数据库集群,数据库压力过大时,用集群来分担压力是个很不错的方案;

 

  1. 有没有做过项目的重构,代码的优化?
  1. 现在的项目,一般都采用迭代式开发的模式,就是在需求不是特别确定,只有大概需求时,就快速进入开发,把开发分成多个阶段,需求一部分一部分的完成。这样的开发模式,可以适应现在千变万化的互联网模式,但是当后期项目做大以后,就会遗留很多问题。促使我们不得不去对项目进行重构,对代码进行优化。
  2. 项目的重构主要是从项目的整体架构上考虑,对项目的各个模块进行解耦,降低各个模块的关联性,方便以后进行集群或升级。
  3. 代码的优化,主要是针对各个具体流程节点的代码逻辑的优化,
  4. 一方面从java的三大核心思想:封装、继承、多态方面考虑。
  5. 一方面从代码思维逻辑的严谨性考虑。
  6. 还有就是通用接口、工具类等的性能的优化。
  7. 另外代码的简洁、可读性也非常重要,所以代码的注释和规范性也是优化必须注意的一个地方。
  1. 根据项目选择合适的web服务?

java中的web服务器,主要是与JSP/Servlet兼容的Web服务器,比较常用的有Tomcat、jetty、JBoss、WebSphere 和 WebLogic 等;目前最流行的还是tomcat,适合于中小型项目,大型项目也可以借助其他其他工具用集群方式部署;jetty是相对于tomcat的一个更轻量级的框架,在小型项目或者云台托管的项目更高效简单。而JBoss、WebSphere、WebLogic是主要运用于大型的企业级web服务,这三个里边只有Jboss是开源免费的,WebSphere和WebLogic都是商业web服务,一套都有上万元以上。他们功能强大,性能稳定,支持分布式部署,有很好的售后支持。WebLogic应该是商业版本里边目前市场占有率最高的服务器,配置简单功能强大。WebSphere是IBM的产品,功能强大,而且有IBM的开发工具相配套,开发Web程序十分方便

  1. 工作中遇到的问题

1、内存溢出,可以调节tomcat内存解决。还要找出代码中内存溢出的漏洞,一般是流没有关闭或者死循环;

2、数据库连接超出最大连接量:修改mysql配置文件的最大连接数;

3、代码编译失败、缓存:清理Eclipse编译缓存、清理tomcat缓存、清理浏览器缓存。

4、tomcat启动端口号被占用:打开任务管理器,杀进程;

5、tomcat启动超时:修改启动时间;

  1. 多线程高并发问题:

处理高并发一般有分流和队列两种分式。分流可以用集群实现。队列可以用activeMQ消息队列或者线程锁。集群我搭建过nginx+tomcat+redis的集群;消息队列就是把所有的线程消息集中管理,排成对一个一个的进行。具体的实现方式,我没有去转入的研究过。线程锁的方式在生成订单编号时用到过,就是在生成编号的方法上加上syn开头的同步锁。对了我们做项目中有个同事提到了用redis可以实现,说redis是单线程的。如果把库存放到redis里边,就可以防止负库存的实现。还有就是我们项目中有时候会用到线程池的技术,比如做数据的批量导入时,如果使用单线程方式,速度会很慢,如果引入线程池,同时开启n个线程,同时进行就可以增加很大的效率。
问题:java开启线程的几种方式:我了解的有继承Thread类,实现runable接口。但是我们现在基本上这样用的比较少,大部分时间都是开启一个spring的线程池。ThreadPoolTaskExecutor

  1. FastDfs

什么是FastDFS

FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。

Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。

Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。

上传流程:

 

 

文件下载流程

 

 

  1. 接口

常用的接口开发方式主要有Httpclient、Webservice、hession、ESB、dubbo几种。(restful风格接口的一种规范,(把所有的请求归纳为4大类、get(查)、post(改)、detete(删)、put(增))

  1. Httpclient本身就是restful分格 (模仿浏览器客户端获取数据的一种方式。用get(获取)、post(提交)),使用比较简单,现在应用的比较多。

   a) 非REST风格的url:localhost:8080/springmvc?name=小白&password=123;

b) REST风格的url:localhost:8080/springmvc/小白/123;(参数用@PathVariable接收)

2、Webservice 是传统开发中最常用的接口调用方式,(有四种实现工具:cxf、xfire、axis、axis2)。我用过cxf方式,因为cxf方式和spring集成比较简单。开始webservice是基于soap协议的,数据格式是xml,现在也支持restful。

3、hession我也就简单了解了一下,他是采用二进制协议的。相比WebService,Hessian更简单、快捷。使用方法当时网上看过,也做过个例子,但现在忘了。

4、ESB是企业服务总线,是企业内部多个项目之间接口调用比较频繁时的一种解决方案;

所有项目都和ESB交互。不需要知道谁提供的服务(数据转换,调用监控)

ESB帮忙管理服务,进行数据转换

ESB处理高并发时,容易出现问题。ActiveMq(消息队列MQ)

开源:mule收费:IBM ESB

5、Dubbo一般叫服务注册,是分布式立体架构中做的一种治理服务的方案。不负责数据转换,只管中间搭线。(支持负载均衡和分布式)

dubbo更适合于一些访问量大,高并发的项目,比如电商;

 

  1. webservice

webservice是一种跨平台,跨语言,跨框架的接口开发和调用技术。在java中通常有四种技术框架分别是xfire,cxf, axis,axis2。我们用的是cxf,因为cxf使用简单,并且可以和spring无缝集成.webservice使用wsdl(web service definition language - web service定义语言)语言,soap(简单对象)协议、xml数据格式;

webservice服务端配置流程(没必要主动说)

 

首先在web.xml中引入cxfServlet核心类,指定对以/cxf开头的url路径提webservice服务,之后我们在要发布成服务的接口的实现类上添加@webservice注解,之后在spring-webservice.xml中发布webservice服务,通过jaxws:endpoint这个标签,并且 在标签配置implementor和address来表明实现服务的类,以及发布的地址,最后在浏 览器中输入相关的webservice地址?wsdl来验证服务是否发布成功。

webservice客户端的配置(没必要主动说)

 

首先通过wsdl2java根据发布的webservice服务端地址的wsdl生成客户端调用的中 间桥梁java类,将生成的java类拷贝到客户端项目中,配置 spring-client.xml文件, 通过jaxws:client定义一个bean,并通过address属性指明要访问的webservice的服务地 址,通过serviceClass指明充当中间桥梁 的服务类,之后获取该bean,就可以通过它来 访问发布的webservice接口中的方法。

restful风格的webService

最近随着restful的流程,webService也支持了restful风格,用起来比较简单,主要是基于cxf框架,使用注解的方式;直接面向对象,不需要关注xml和对象的转换;

webService主要分为服务端和客户端,因为都是内部调用,所以服务端和客户端都是我们自己开发,发布服务只需要引入jar包和他的xml文件,在Sevice层接口加上WebService注解,接口实现类上家Path、get(post、dete、put)注解;实体类上需要加注解@XmlBootElement;

调用服务时,映入jar包和配置文件后,在controller里边可以直接用@Resource注入要调用的服务;就想调用本地服务一样;应用的配置都在xml文件中,通过jaxs标签应用

 

  1. httpclient

HttpClient字面理解就是http请求的客户端,他是Apache开发的一套HTTP协议的客户端编程工具包。是纯Java语音编写的,支持https协议,实现了http的getpost、delete、put等全部方法,我们一般只用get和post方法,httpclient可以直接跨域请求http协议的url,并拦截返回响应结果。

我们项目里边有写好的工具类,里边封装好了他的get和post方法,传入url和参数,返回String类型数据(一般json字符串或xml字符串然后我们进行解析就可以了。

的调用步骤是:

1. 创建HttpClient对象。

2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。

3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。

4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。

5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。

6. 释放连接。无论执行方法是否成功,都必须释放连接

 

 

  1. 权限

我负责系统管理模块,系统管理主要是对用户角色和权限的管理,不同角色的人登录应该看到不同的权限和内容,权限通常有3,5,7张表甚至更多来完成,我们当时用了五张,包括员工信息表、角色信息表、权限信息表和两张中间关系表:一张员工角色关系表,一张角色权限关系表。可以对用户赋角色,然后角色赋权限,权限表里存着不同的权限的url,当用户登录时,从session中获取用户id,通过用户id获取用户的所有角色id,再通过角色id获取所有权限url。把当前用户的权限信息用ztree以树形结构展示,就实现了不同的人登录展示不同的权限树。(当然,在这里可能有一些安全问题,就是用户直接在浏览器输入非法权限的url执行非法操作的问题,我们采用filter拦截器在用户执行操作时先判断该用户有无该权限的url,在有权限的情况下才放行,否则提示非法操作,并且终止该操作)。

 

  1. Shiro

Apache Shiro是Java的一个安全框架。对比Spring Security,它相当简单,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。

    Shiro主要包含登录认证、授权、会话管理三大功能另外还提供了加密、缓存web基础等功能。他不会维护用户权限,这个还需要我们自己完成。他工作流程是:首先我们通过SecurityUtils获取Subject用它来进行认证和授权然后通过安全管理器securityMananager管理所有的subject。给Shiro的SecurityManager注入Realm,通过realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。框架中使用一般是,先导入相关jar包,web.xml中加shiro-filter过滤器,配置shiro.xml里边配置过滤器和安全管理器还可以自定义form 认证过滤器、自定义realm(领域)加密等。提供了多种授权方式;编程授权、注解授权、jsp标签授权

 

  1. B2B直接掉银行接口支付

我们的支付是直接和农业银行对接的。首先银行为我们创建一个总账号(众陶联),然后在这个总账号下可以开设多个子账号,子账户和普通账号类似,子账号只能进行充值,不能提现,转账也只能是子账号直接互相转;企业在我们平台注册时,开始只是注册基本信息,想要交易时,就需要进行资质认证;企业把需要认证的信息上传到平台,运营后台进行审核,审核通过后,直接调用农业银行提供的开设子账号接口,开设子账号,然后把子账号线上反馈给用户(采购商或供应商); 采购商支付主要有几种情况:1、缴纳保证金;2、交易支付;3、支付委托金; 平台除了有一个总账号以外,也有一个平台自己的子账号;

银行接口:

  1. 子账号之间转账接口;
  2. 查询账户明细接口;
  3. 提现接口(总账号转钱到其他账号)
  4. 开启子账号接口;
  5. 账号余额查询;

接口调用方式:

  1. 异步,首先发送请求给接口,接口返回(调用成功(交易id)或失败);银行去进行操作,操作完成以后,会回调我们的url,传会交易id和结果;

注意:

  1. 调用银行接口:传入订单id,交易金额、回调url、备注、secret
  2. 记录交易记录;
  1. Mysql数据库引擎

1、Mysql数据库的引擎有很多种,最重要的就是他的事物型引擎和非事务型引擎,我们一般都采用他的事务型引擎,因为增删改操作必须要用到事务;非事务引擎,可以在做主从复制、读写分离后的从数据库上使用,因为从数据库一般仅仅是读的操作,不需要事务,这样会更快。

  1. keepalive+nginx实现双机热备

Keepalived是一个基于VRRP协议来实现的服务高可用方案,可以利用其来避免IP单点故障,类似的工具还有heartbeat、corosync、pacemaker。但是它一般不会单独出现,而是与

 

其它负载均衡技术(如lvs、haproxy、nginx)一起工作来达到集群的高可用。

Nginx+Keepalived一般是一主一备,正常情况下都有主机的nginx绑定公网虚拟ip,提供负载均衡服务。主备机器上都有一个keepalive通过脚本监控着nginx。当主机nginx挂掉以后,主机keepalive检测到异常后,就进行自杀,这样备机的keepalive就会接收到信息,备机的keepalive就会通知备机的nginx监管主机的工作。当主机恢复以后,备机keepalive同样会收到消息,就会把主动权又交还给主机。

 

  1. Redis

Redis是一个继memcached后的又一个第三方缓存数据库,他比memcached强大很多,支持更多的数据类型(String、list、set、sort set、hash),支持持久化,支持集群;Redis虽然支持持久化,但是他并不适合持久化的保存数据。因为他不是很稳定。但是由于他是保存在内存中,读取速度非常快,所以在项目中一般都用它作为数据库和应用程序直接的中间层来使用,已减轻数据库压力,提高运                                行效率。

我们项目中很多地方用到了redis;比如商品的三级分类、省市县、关于我们、联系我们、友情链接,常见问题等经常查询但是不经常改变的数据.redis还可以在tomcat集群里边实现session的共享。由于他的单线程的,所以在电商平台里边也经常用他做“防止超卖”, 生成规则的商品编号等。还有就是购物车也用到了redis

 代码中,我们一般都通过spring整合redis官方提供的jedis工具包来操作redis。可以是单机版,也可以是集群。Redis本身就支持集群操作redis_cluster,另外redis还支持主从复杂,还有他独特的哨兵模式,在主服务器宕机时,从服务器可以自动转换为主服务器。另外,他也有他的分片机制,就像mysql的水平分表设计,数据量大时,可以把数据存储到不同的库中,来减轻redis负担。

Redis的持久化方式主要有2种,RDB和AOF,RDB是一种快照方式,默认每隔5分钟创建一个快照副本,这种方式占用空间大,而且会丢失间隔时间5分钟之内的数据,但是他适合做备份,恢复时,可以根据需要恢复任意间隔时间点的数据。AOF是一种日志的持久化记录方式,每秒钟,都把redis中新增的数据记录到日志文件中,这种方式只有一个文件,占用空间少,最多丢失1秒内的数据。相对比较好,但是如果想要恢复5分钟或10分钟前某个时间点的数据,就不行了。所以实际项目中,我们一般会两种方式同时使用。如果搭建集群的话,还可以通过集群互相备份数据,只要集群不同时挂掉,单个redis就可以从集群中的其他服务器获取到最新数据。

 还有就是,由于redis不是很稳定,有时候会发生“穿透”和“雪崩”; redis,都是按照key去缓存查询,如果不存在对应的value,就应该去数据库查找。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,这就叫“缓存雪崩”。解决办法是,对查询结果为空的情况也进行缓存,并且给缓存设置不同的有效期。当然redis容灾的最有效的方法还是搭建集群。

  1. Java操作文件

Java中,一般用poi来操作Excel,用itext来操作word或PDF;

导出Excel需要注意的地方主要有:

他的2003版本和2007版本用的类是不一样的。

2003 HSSFWorkbook

2007 XSSFWorkbook

并且poi3.8版本后,提供了一个超大数据量导出的类;SXSSFWorkbook

导出的步骤主要就是:

  1. 创建工作簿
  2. 创建sheet
  3. 创建行
  4. 创建列
  5. 把工作簿写入到输出流
  6. 如果是导出到固定地方,那么直接new一个输出流,如果让用户自己选择保存路径,就通过response获取输出流;

 

  1. spring_bootspring_cloud

spring boot 我理解就是把 spring,spring-mvc,spring-data-jpa 等等的一些常用的常用的基础框架组合起来,提供默认的可插拔的设计配置。spring boot对spring的配置进行简化,几乎零配置。同时对spring 需要的jar 也进行了整合,解决jar冲突的问题。可以理解为springMvc的升级版。提供了spring-boot-starter-parent的整合工具包。集成了他以后,项目中大部分jar包就不需要再引用了。内置了Tomcat插件,可以直接用main方法运行;

spring_cloud是基于spring_boot的微服务分布式架构,他主要包括:

  1. 服务注册中心:Eureka Server
  2. 服务注册者客户端:Eureka Client
  3. 服务消费端:负载均衡客户端:ribbon和feign
  4. 断路器Hystrix
  5. 路由网关zuulZuul的主要功能是路由转发和过滤器zuul默认和Ribbon结合实现了负载均衡的功能。
  1. Nginx

我们的项目,为了安全,一般都部署在内外环境中,只有Nginx服务器会绑定域名,放到公网环境上。当用户从公网发送一个请求过来时,首先请求到Nginx,然后通过Nginx再反向代理到其他内外服务器上。

我们的服务器一般都采用centos7,因为Nginx是c语言开发的,所以Nginx环境的安装,需要一些gcc环境的支持,如果用rpm安装,他的默认路径是/etc/nginx;会自动绑定到服务上,直接用systemctl start Nginx就可以。

Nginx的核心配置文件是conf下的Nginx.xml,他主要有两大功能,一是反向代理,通过配置文件中的server和location来实现,可以配置多个server,制定不同的端口或同一个端口,不同的域名来实现代理,也可以通过一个server里边配置多个location来实现代理。二是负载均衡,负载均衡一般通过配置upstream来实现,常用的负载机制有轮循、权重和ip_hash三种;轮循就是各个服务器按顺序轮着进行;权重就是通过配置weight权重,来分配url的访问次数。Ip_hash是根据客户请求的sessionid,把同一个用户的请求,锁定到同一个url上。

Nginx除了他的反向代理和负载均衡以外,他本身也是一个静态服务器,可以用它发布html、css、img等一系列静态资源。我们项目中做静态化生成的静态页面就放在了Nginx服务器上。

 

  1. 微信公众号

首先微信提供了一个官方的微信公众号平台,我们申请成功以后,可以在这个平台上直接进行配置。比如消息回复,有消息自动回复,被关注时被动回复、关键字回复等。回复的消息包括文字消息、图片消息、语音消息、视频消息等。还有就是自定义菜单,最多可以配置3*5个菜单,点击菜单可以回复消息,跳转链接,跳转小程序。如果想要进一步使用更复杂的功能,就需要成为开发者,调用他的api接口文档了。微信公众号的接口都是restful风格的接口,我们可以用httpclient直接调用,接口有些是json数据格式,有些事xml数据格式。调用接口文档首先需要接入测试和网页授权。接入时,必须使用域名和80端口,在公众号平台配置好url,发送请求,平台url里边接收参数,按照要求生成签名,与传过来的签名进行比较,验证签名,如果成功返回一个字符串。这样就接入成功了。Json和xml的解析我们当时封装了工具类。解析方法网上都有,很简单。

  1. MQ消息队列
  • MQ简称消息队列,他是一个不同项目之间进行通讯时,对消息进行管理的组件。有了MQ,可以使项目之间交互由同步转换成异步,解耦了消息的处理过程。把消息统一管理起来,按照顺序,根据客户端的处理能力一个一个的进行操作,MQ具有送达保证、排序保证、峰值处理、异步通信几大特性。在高并发时,对于减轻数据库压力非常有效。MQ一般有点对点和发布订阅两种方式,点对点就是一个消息只允许接收一次,发布订阅模式,一个消息可以被多次接收。目前主流的产品有RabbitMQ、ActiveMQ和Kafka;ActiveMq是Java语言开发的,RabbitMQ是Erlang语言开发的,理论上,RabbitMQ的性能比ActiveMq更强,是非Java系统的首选,ActiveMq是Java的,整套系统如果本来就是Java的,配合的默契更佳。Kafka相当于一个分布式的MQ,传统的MQ,消息被消化掉后会被mq删除,而kafka中消息被消化后不会被删除,而是到配置的expire时间后,才删除,他把写入压力均摊到各个节点。可以通过增加节点降低压力;
  1. Angular.js

AngularJS 是一个 JavaScript 框架。它是一个以 JavaScript 编写的库。和他类似的还有vue.js, AngularJS它更适用于开发CRUD应用,即数据操作比较多的应用。为了实现这些,AngularJs引入了一些非常棒的特性,包括模板机制、数据绑定、指令、依赖注入、路由等。数据绑定可能是AngularJS最酷最实用的特性。通过数据与模板的绑定,能够让我们摆脱繁琐的DOM操作,而将注意力集中在业务逻辑上。AngularJS内置了很多指令用来控制模板,如ng-repeat,ng-class等,也有很多指令来帮我们完成业务逻辑。还有AngularJS服务啊路由之类的功能也都是非常实用的。AngularJS不依赖(也不妨碍)任何其他的框架,我们甚至可以基于其它的框架来开发AngularJS应用。在我们之前的一个项目中也用到了AngularJS,通过AngularJS,我们实现了前后端分离开发,前端使用路由,页面的性能会有很大的提升,同时也会减少后端的压力,页面跳转可以不需要经过后端,后端只负责提供数据做为展示。

 

  1. 工作流

工作流就是我们日常工作中的一些需要一组人完成的一个流程。比如请假流程、报销流程、入职流程、离职流程等,首先需要有人发起流程-然后经过一系列审批,最后结束流程。这些流程如果比较固定、比较单一,那么我们通过状态判断一般就可以实现。但是在OA系统中,很多工作需要大家共同协同办公,这种流程特别多。并且很多流程中间的审核环节不是固定的,经常变动,如果仅仅使用状态通过代码判断实现就比较麻烦了。这时候用这种比较成熟的工作流插件就会特别方便。他的核心思想就是,把流程的审核过程和流程定义进行分离,把流程归纳为三部分:“发起”“审批”“结束”;这样就可以把创建流程和流程结束的代码固定下来,中间流程审批的代码使用递归的方式实现。这样多个流程就可以共用一套代码,也不怕流程审核过程变更了。目前常用的工作流就是jbpm5和activity5。jbpm是基于hibernate的,activity是基于mybatis的。我们项目用的是mybatis,所以为了方便集成,就选择了activity。

activity开发步骤:

  1. 集成activity(jbpm)插件;
  2. 导入activity(jbpm)的jar包(官网下),
  3. 写配置文件,配置流程引擎配置对象activiti-context.xml(.jpdl.xml)
  4. 绘制的流程图.bpmn(  processdefinition.xml)文件
  5. 写java工具类,调用流程引擎配置对象的方法,执行自动建表(23个表,jbpm5  18张表)
  6. 写java工具类,部署流程(利用activiti的api将上面定义好的工作流bpmn文件和png文件上传到activiti的数据库)
  7. 写java工具类,启动流程
  8. 写java工具类,查询当前、人的任务(我的待办)
  9. 写java工具类、处理流程(自动到下一个节点,重复8,直到结束);

学习activity工作流的要点:

  • 1插件

在Eclipse中安装Activity插件,可以在Eclipse中绘制Activity工作流图

  • 1个引擎:(建议交给Spring创建)

ProcessEngine对象,Activity工作流引擎。这是Activiti工作的核心。负责生成流程运行时的各种实例及数据、监控和管理流程的运行。

所有的操作都是从获取引擎开始的,所以一般会把引擎作为全局变量

ProcessEngine processEngine =ProcessEngines.getDefaultProcessEngine(); 

 

  • 1个配置文件

activiti.cfg.xml。Activiti核心配置文件,配置流程引擎创建工具的基本参数和数据库连接池参数 

 

  • 523张数据库表

Activiti的后台是有数据库的支持,所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。用途也和服务的API对应。

1、ACT_RE_*: 'RE'表示repository。 这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。

2、ACT_RU_*: 'RU'表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。

3、ACT_ID_*: 'ID'表示identity。 这些表包含身份信息,比如用户,组等等。

4、ACT_HI_*: 'HI'表示history。 这些表包含历史数据,比如历史流程实例,变量,任务等等。

5、ACT_GE_*: 通用数据,用于不同场景下,如存放资源文件。

 

 

  • 5个service(建议交给Spring创建):

repositoryService 资源

runtimeService 运行流程

taskService 任务

historyService 历史流程

formService 表达

 

 

  • 7个基本操作:
  1. 设计流程图(各种组件,如连线、用户任务、网关)
  2. 流程定义增删改查
  3. 流程变量增删改查
  4. 启动流程定义
  5. 任务增删改查
  6. 完成任务
  7. 历史信息查询

 

  1. 常用Linux命令

查看tomcat进程:ps –ef|grep tomcat

杀进程:kill -9 进程id

查看Tomcat日志: tail -300f Tomcat目录/logs/catalina.out

重启mysql:systemctl restart mysql

环境变量配置文件位置: /etc/profile

防火墙配置:/etc/sysconfig/iptables

Nginx配置:/etc/nginx/nginx.cnf

解压缩命令:tar –zxvf 文件名

创建一级目录:mkdir 目录

创建多级目录:mkdir -p 目录

复制文件及文件夹:cp -rf 原文件(夹) 目标文件(夹)

远程复制:scp 用户名@ip:远程文件(夹) 当前文件(夹)

删除文件及文件夹:rm -rf 文件(夹)(可用*做通配符)

移动文件及文件夹:mv 原文件(夹) 目标文件(夹)

卸载:rpm -e --nodeps 包名

安装:rpm -ivh 包名

检查jdk是否安装:rpm -qa|grep -i jdk

赋予权限:chmod  777 文件

查看端口号占用情况:

netstat -apn | grep 8080(根据端口号查询进程id)

ps -aux|grep 进程id(根据进程id查看进程)

shell脚本就是一个.sh的文件,里边放了多条shell命令(我们常用的Linux命令都是shell命令),还要if判断、for循环等;shell脚本一般都以#!/bin/bash开头,就是一个声明,没有太大意义。可以参考tomcat下的stratup.sh

项目部署

    1. eclipse直接import导出war包;
    2. 把war包上传到tomcat的webapps下;
    3. 启动tomcat:进入tomcat的bin目录,执行./startup.sh
  1. Java底层优化篇
  2. JVM

JRE:Java运行时环境(JRE),是将要执行Java程序的Java虚拟机。

JDK:Java开发工具包(JDK),是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。

 

JVM内存结构

 

 

程序计数器

是最小的一块内存区域,可以看作是当前线程所执行的字节码的行号指示器。是唯一一个在java虚拟机规范中没有规定任何“内存溢出错误”的区域

 

Java虚拟机栈

主要用于存放局部变量、对象引用、操作数栈等,空间比较小,规定 两种异常:StackOverflowError和OutOfMemoryErroy异常;

 

 

本地方法栈

和虚拟机栈的作用是非常相似的,区别是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是虚拟机执行native方法服务;

 

Java(heap)

是java虚拟机内存中最大的一块区域,在jvm启动时创建,存放了对象实例和数组(所有new 的对象),其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,为了避免运行过程中频繁调整heap的大小,通常这两个值设置成一样;

 

Java堆是垃圾收集器管理的主要区域,一般采用分代收集算法回收。堆被分为新生代和老年代,新生代主要存储新创建的对象和尚未进入老年代的对象,老年代存储经过多次新生代GC(回收),仍然存活的对象;新生带可通过-Xmn参数来设置; 新生的对象有可能直接进入老年代,比如:启动时大小直接超过了新生代的分配;

方法

存储已被加载的类信息、常量、静态变量、编译后的代码等数据;可以通过-XX:PermiSize和-XX:MaxPermiSize来设置方法区的大小;在jdk1.7以后,逐步改为采用native memory来实现方法区;

 

运行常量池

是方法区的一部分;

 

垃圾回收

目前大部分JVM都采用分代收集算法,把堆分为新生代和老年代,新生代的特点是每次回收都有大量对象需要被回收,老年代每次只有少量对象被回收。所以根据他们的特点,新生代一般采用copying算法进行回收,就是创建2块相同的内存,把存活的对象复杂到另外一块上,然后把旧的一起清楚;老年代一般采用“标记-整理算法”,先把需要回收的对象进行标记,然后把存活的对象都向一段移动,然后把边界以外的清楚;

 

常用的垃圾回收器有:并行收集器、窜行收集器、并发收集器

JVM加载机制和双亲委派模型

(1) Bootstrap ClassLoader : 将存放于<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用

     (2) Extension ClassLoader : 将<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。

     (3) Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。

     

双亲委派模型

 

工作过程:如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

     好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。

JVM调优 

 

因为我们的项目,web服务器中间件都用的是Tomcat,所以jvm的调优一般都是针对Tomcat的调优。调优工具可以可以借用jdk自带的VisualVM工具JDK安装目录/bin目录下,双击jvisualvm.exe文件,直接启动,就可以查看到当前堆空间大小分配情况线程监控情况垃圾回收监控根据这些信息,进行相应的调整。

 

我觉的Jvm调优的重点就是垃圾回收(gc,garbage collection)和内存管理。垃圾回收的时候会导致,整个虚拟机暂停服务。因此,应该尽可能地缩短垃圾回收的处理时间。

Java中垃圾回收机制是jvm自动完成的,我们不用操作,但可以通过修改一些配置对他进行影响;

  1. 首先可以开启server模式,这样虽
  2. 然启动慢点,但是长期运行效率高;(修改%JAVA_HOME%/jre/lib/i386/jvm.cfg文件开启)

2、针对JVM堆的设置,JVM初始堆内存分配有-Xms指定,默认是物理内存的1/64;

最大分配堆内存有-Xmx指定,默认是物理内存的1/4;当堆内存小于40%时,JVM就会自动增加,直到最大值。当空余堆内存大于70%时,JVM就会自动减少,直到最小值。因此,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值;

3、配置年轻代(Xmn)的值,持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8

4、设置个线程的堆栈大小Xss每个线程默认堆栈大小为1M,可根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数也是有限制的,不能无限生成,经验值在3000~5000左右。

5、回收器的选择,JVM给了三种选择:串行收集器、并行收集器、并发收集器,JVM会根据当前系统配置进行判断,自动选择。我们可以对一些参数进行设置。串行收集器只适用于小数据量的情况,一般不用处理可以配置年轻代使用并发收集器,年代使用并行收集器如果响应时间有限,就选择并发收集器,尽可能设大年轻代

如果吞吐量优先选择并行收集器尽可能设大年轻代

6禁用TomcatDNS查询。(当 Web 应用程序记录客户端的信息时,它也会记录客户端的 IP 地址或者通过域名服务器查找机器名转换为 IP 地址。 DNS 查询需要占用网络,并且可能从很多很远的服务器或者不起作用的服务器上去获取对应的 IP, 这样会消耗一定的时间。为了消除 DNS 查询对性能的影响,可以关闭 DNS 查询。方法是修改 server.xml 文件中的 enableLookups 参数值。)

7、线程数配置:Tomcat连接数过大可能引起的死机。所以,可以根据并发在Tomcat的server.xml中修改他的最大线程数、初始化线程数等参数。一般也就估计这配置,小了就配大点。没有具体评估过

    我们在项目中,一般也就是项目出现问题以后,再去优化Tomcat年老代溢出java.lang.OutOfMemoryError: Java heap space、持久溢出java.lang.OutOfMemoryError: PermGen space、堆栈溢出java.lang.StackOverflowError、线程溢出Fatal: Stack size too smal、内存溢出java.lang.OutOfMemoryError: unable to create new native thread是会抛出不同溢出。根据溢出去进行修改。

 

  1. 多线程
    1. 线程和进程的区别

线程是进程的一个子集;每个进程可以有多个线程

 

  1. 多线程的应用场景

多线程主要是为了提高cpu或者内存的利用率,但是如果用不好了,可能起到相反的效果;多线程可以使我们并行的去处理一些事情。

  1. 批量从excel导入数据到数据库中;(多线程操作增加速度)
  2. 注册成功以后,给用户发送邮件;(因为发送邮件的过程需要1分钟左右,为了提升用户的体验度,我们使用异步发送)

 

  1. Java中多线程的实现方式
  1. 实现runable接口;
  2. 继承thread类;(thread类也是实现的runable接口)
  3. 实现callable和future接口(可以有返回值,可以捕获异常)
  4. 使用Executors线程池:

使用线程池的好处在于:在项目启动时,可以给我们初始化多个线程,使用时只需要从线程时拿取一个线程就Ok了;用完以后线程池会帮我们自动回收; 

  1. 常用的线程池:
  1. 固定数量的线程次;(排队等待)
  2. 支持定时任务的线程池;()
  3. 支持缓存的线程池;(常用的)
  4. 单线程的线程池;(效率最低)
  1. 线程的几种状态;
  1. 新建  执行start进入就绪
  2. 就绪---执行run进入运行
  3. 运行-----wait/sleep进入阻塞
  4. 阻塞
  5. 死亡
  1. Startrun的区别;

Start是方法被用来启动新创建的线程;start里边调用了run方法;

所以一般启动一个线程用start方法;run方法列表用来写要执行的方法体;

 

  1. wait和sleep的区别;

Wait是等待,必须通过notify进行唤醒,才能继续运行;

Sleep是休眠,休眠时间到了会自动醒来;

 

  1. 如何给线程进行排序

用join方法,可以使线程按顺序执行;

本地线程threadlocal

  1. Java集合
    1. 话术

Java里边主要有两种集合,collection接口和map接口,其中collection下又包含list和set两个子接口;

List子接口:有序,可以有重复元素。和数组类似,List可以动态增长,查找元素效率高,相对插入删除元素效率低,因为会引起其他元素位置改变。

Set子接口:无序,不允许重复。检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。set集合中的元素不按特定方式排序,只是简单的把对象加入集合中,就像往口袋里放东西。

List接口下有三个实现类ArrayList LinkedListVector

Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用

ArrayList  实现一个动态数组,它的规模可变并且能像链表一样被访问。它提供的功能类似Vector类但不同步,它是以Array方式实现的List,允许快速随机存取。特点是读快改慢;

 

LinkedList实现一个链表,提供最佳顺序存取,适合插入和移除元素。由这个类定义的链表也可以像栈或队列一样被使用。提供最佳顺序存取适合插入和移除元素。特点是改快读慢

Set接口HashSet TreeSet 两个实现类

HashSet 能够快速定位一个元素,

要注意的是:存入HashSet中的对象必须实现HashCode()方法; 

TreeSet 将放入其中的元素按序存放它实现的是SortedSet接口,也就是加入了对象比较的方法。通过对集中的对象迭代,我们可以得到一个升序的对象集合。

Map接口的实现类主要有HashMap HashTableTreeMap

当元素的顺序很重要时选用TreeMap,当元素不必以特定的顺序进行存储时,使用HashMap。HashMap不是同步的,Hashtable同步的,Hashtable不推荐使用,因为HashMap提供了所有类似的功能,并且速度更快。当需要在多线程环境下使用时,HashMap也可以转换为同步的。HashMap没法保证映射的顺序一直不变,但是作为HashMap的子类LinkedHashMap可以。

HashMap可以通过Map m = Collections.synchronizedMap(hashMap)来达到同步的效果。

 

 

  • Java最顶层的接口是Iterable(迭代);
  • Collection接口,继承了Iterable接口;
  • List和set接口,继承了collection;
  • Map是个独立的接口;
  • List主要有ArrayList和LinkedList两个实现类;
  • Set主要有HashSet和TreeSet两个实现类
  • Map主要有HashMap和TreeMap两个实现类
  •  
  •  

 

  1. List

我们用的比较多List包括ArrayList和LinkedList,这两者的区别也很明显,从其名称上就可以看出。ArrayList的底层的通过数组实现,所以其随机访问的速度比较快,但是对于需要频繁的增删的情况,效率就比较低了。而对于LinkedList,底层通过链表来实现,所以增删操作比较容易完成,但是对于随机访问的效率比较低。

 

  1. Queue

一般可以直接使用LinkedList完成,从上述类图也可以看出,LinkedList继承自Deque,所以LinkedList具有双端队列的功能。PriorityQueue的特点是为每个元素提供一个优先级,优先级高的元素会优先出队列。

  1. Set

Set与List的主要区别是Set是不允许元素重复的,而List则可以允许元素重复的。判断元素的重复需要根据对象的hash方法和equals方法来决定。这也是我们通常要为集合中的元素类重写hashCode方法和equals方法的原因。

 

元素加入List的时候,不执行额外的操作,并且可以重复。而加入Set之前需要先执行hashCode方法,如果返回的值在集合中已存在,则要继续执行equals方法,如果equals方法返回的结果也为真,则证明该元素已经存在,会将新的元素覆盖老的元素,如果返回hashCode值不同,则直接加入集合。这里记住一点,对于集合中元素,hashCode值不同的元素一定不相等,但是不相等的元素,hashCode值可能相同。

HashSet和LinkedHashSet的区别在于后者可以保证元素插入集合的元素顺序与输出顺序保持一致。而TresSet的区别在于其排序是按照Comparator来进行排序的,默认情况下按照字符的自然顺序进行升序排列。

 

  1. Iterable

到Collection类继承自Iterable,该接口的作用是提供元素遍历的功能,也就是说所有的集合类(除Map相关的类)都提供元素遍历的功能。Iterable里面包含了Iterator的迭代器

  1. Map

Map类型的集合最大的优点在于其查找效率比较高,理想情况下可以实现O(1)的时间复杂度。Map中最常用的是HashMap,LinkedHashMap与HashMap的区别在于前者能够保证插入集合的元素顺序与输出顺序一致。这两者与TreeMap的区别在于TreeMap是根据键值进行排序的,当然其底层的实现也有本质的区别,如HashMap底层是一个哈希表,而TreeMap的底层数据结构是一棵树

 

TreeMap和LinkedHashMap的区别,前者是按字符串排序进行输出的,而后者是根据插入顺序进行输出的。HashMap与TreeMap的区别,与之前提到的HashSet与TreeSet的区别是一致的,在后续进行源码分析的时候,我们可以看到HashSet和TreeSet本质上分别是通过HashMap和TreeMap来实现的,所以它们的区别自然也是相同的。HashTable现在已经很少使用了,与HashMap的主要区别是HashTable是线程安全的,不过由于其效率比较低,所以通常使用HashMap,在多线程环境下,通常用CurrentHashMap来代替。

 

  HashMap基于哈希表的 Map 接口的实现。允许使用 null 值和 null 键。值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。

 

 

HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。

 

HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。

 

我们一般对哈希表的散列很自然地会想到用hash值对length取模(即除法散列法),Hashtable中也是这样实现的,这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低,HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,这也是HashMap对Hashtable的一个改进。

 

 

 

  1. ArrayList底层实现?

ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,在new的时候,不会开辟空间,而是在第一次放入值时,开辟一个10个长度的空间;当放入值超过10个时,会自动进行扩容,扩大到之前的1.5倍;ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类; 

  1. LinkedList底层实现

LinkedList 和 ArrayList 一样,都实现了 List 接口,但其内部的数据结构有本质的不同。LinkedList 是基于链表实现的(通过名字也能区分开来),所以它的插入和删除操作比 ArrayList 更加高效。但也是由于其为基于链表的,所以随机访问的效率要比 ArrayList 差。LinkedList不是线程安全的,所以在多线程的环境下使用LinedList需要注意LinkedList类型变量的线程同步问题。当然,有一种方式可以创建一个线程安全的LinkedList: 
List list = Collections.synchronizedList(new LinkedList(…)); 

 

LinkedList是基于双向列表的,有first(从前向后)和last(从后向前)两条列组成;

当给Linkedlist添加1个值时,

First:首先把这个值new 成一个node对象;然后把这个node对象,放到firt里边最后一个对象的next里边;。

Last:把last值,给node的next;然后把node整个给last

  1. ArrayList和LinkedList的比较

LinkedList 和 ArrayList 一样,都实现了 List 接口,但其内部的数据结构有本质的不同。

ArrayList底层是基于数组实现的;当添加第一个值时,开辟空间,默认开辟10个长度的数组;当里边数值超过10时,会自动扩容,每次扩容到它的1.5倍;由于他的扩容机制是创建1个新的数组,然后把就数组的值给新数组,效率比较低。所以我们在做ArrayList的优化时,可以根据预估数据量给他一次性开辟好空间,避免自动扩容。另外由于数组有下标,所以查找时非常方便;但插入时,可能导致下标重新指定。所以效率可能较低;

LinkedList是基于双向列表的结构;当给里边添加值时,首先把当前值创建成一个node对象;然后把当前对象赋给first列最后1个值的next中;同时,把last列直接给到node的next中。然后把当前node再赋给last中;这样就形成了一个双向列表;

再插入值时,只需要把插入节点的next先拿出来,然后放到当前node的next中。然后把当前node再给到插入节点的next中就可以了

 

  1. HashMap的底层原理是什么?

hashMap底层是一个数组+Entry对象的单向列表组成的;在创建是默认不开辟空间;在添加第一个值的时候开辟空间,默认数组长度为16;Entry里边主要包含key、value、next和hash4个值;当给hashmap添加1个值时,首先先把当前值new 成一个entry对象;hash是根据key计算出来的hashcode值。把这个值和数组的长度求余,根据这个余数来确定entry存放在数组的位置;找到位置以后,首先把位置中原先的值,拿出来,放到当前entry的next里边;最后在把当前entry放到这个位置;hashmap的默认扩容因子是0.75;当map中的值超过3/4时,会自动扩容,扩容到原理的2倍;

  1. Java中常见数据结构:list与map -底层如何实现

1:集合

    Collection(单列集合)

        List(有序,可重复)

            ArrayList

                底层数据结构是数组,查询快,增删慢

                线程不安全,效率高

            Vector

                底层数据结构是数组,查询快,增删慢

                线程安全,效率低

            LinkedList

                底层数据结构是链表,查询慢,增删快

                线程不安全,效率高

        Set(无序,唯一)

            HashSet

                底层数据结构是哈希表。

                哈希表依赖两个方法:hashCode()和equals()

                执行顺序:

                    首先判断hashCode()值是否相同

                        是:继续执行equals(),看其返回值

                            是true:说明元素重复,不添加

                            是false:就直接添加到集合

                        否:就直接添加到集合

                最终:

                    自动生成hashCode()和equals()即可

                    

                LinkedHashSet

                    底层数据结构由链表和哈希表组成。

                    由链表保证元素有序。

                    由哈希表保证元素唯一。

            TreeSet

                底层数据结构是红黑树。(是一种自平衡的二叉树)

                如何保证元素唯一性呢?

                    根据比较的返回值是否是0来决定

                如何保证元素的排序呢?

                    两种方式

                        自然排序(元素具备比较性)

                            让元素所属的类实现Comparable接口

                        比较器排序(集合具备比较性)

                            让集合接收一个Comparator的实现类对象

    Map(双列集合)

        A:Map集合的数据结构仅仅针对键有效,与值无关。

        B:存储的是键值对形式的元素,键唯一,值可重复。

        

        HashMap

            底层数据结构是哈希表。线程不安全,效率高

                哈希表依赖两个方法:hashCode()和equals()

                执行顺序:

                    首先判断hashCode()值是否相同

                        是:继续执行equals(),看其返回值

                            是true:说明元素重复,不添加

                            是false:就直接添加到集合

                        否:就直接添加到集合

                最终:

                    自动生成hashCode()和equals()即可

            LinkedHashMap

                底层数据结构由链表和哈希表组成。

                    由链表保证元素有序。

                    由哈希表保证元素唯一。

        Hashtable

            底层数据结构是哈希表。线程安全,效率低

                哈希表依赖两个方法:hashCode()和equals()

                执行顺序:

                    首先判断hashCode()值是否相同

                        是:继续执行equals(),看其返回值

                            是true:说明元素重复,不添加

                            是false:就直接添加到集合

                        否:就直接添加到集合

                最终:

                    自动生成hashCode()和equals()即可

        TreeMap

            底层数据结构是红黑树。(是一种自平衡的二叉树)

                如何保证元素唯一性呢?

                    根据比较的返回值是否是0来决定

                如何保证元素的排序呢?

                    两种方式

                        自然排序(元素具备比较性)

                            让元素所属的类实现Comparable接口

                        比较器排序(集合具备比较性)

                            让集合接收一个Comparator的实现类对象

    

2.关于集合选取原则

    

    是否是键值对象形式:

        是:Map

            键是否需要排序:

                是:TreeMap

                否:HashMap

            不知道,就使用HashMap。

            

        否:Collection

            元素是否唯一:

                是:Set

                    元素是否需要排序:

                        是:TreeSet

                        否:HashSet

                    不知道,就使用HashSet

                    

                否:List

                    要安全吗:

                        是:Vector

                        否:ArrayList或者LinkedList

                            增删多:LinkedList

                            查询多:ArrayList

                        不知道,就使用ArrayList

            不知道,就使用ArrayList

            

3:集合的常见方法及遍历方式

    Collection:

        add()

        remove()

        contains()

        iterator()

        size()

        

        遍历:

            增强for

            迭代器

            

        |--List

            get()

            

            遍历:

                普通for

        |--Set

    

    Map:

        put()

        remove()

        containskey(),containsValue()

        keySet()

        get()

        value()

        entrySet()

        size()

        遍历:

            根据键找值

            根据键值对对象分别找键和值

  1. 如何给别人介绍自己会的知识点
    1. 技术:
  1. 概念:给别人讲解一个技术点时,应该首先给你要讲的知识点一个概念,他是做什么的;
  2. 类比:可以引用一些类似的技术做比较,这样更方便对方理解;
  3. 特点:要清晰的把握住这个技术的特点;
  4. 应用场景:一定要有实际项目中的应用场景;
  5. 使用中遇到的问题、解决问题的过程;

 

    1. 模块
  1. 做什么:这个模块实现了什么功能,是做什么的;
  2. 为什么做:为什么做这个模块,和哪个业务流程有关系;
  3. 如何做:整体思路
  4. 难点在哪:技术难点
  5. 如何解决:解决方案
  6. 开发过程中遇到了什么问题,如何解决的?
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页